TextInput.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import {FocusElement} from 'tui-lib/ui/primitives'
  2. import * as ansi from 'tui-lib/util/ansi'
  3. import telc from 'tui-lib/util/telchars'
  4. import unic from 'tui-lib/util/unichars'
  5. export default class TextInput extends FocusElement {
  6. // An element that the user can type in.
  7. constructor() {
  8. super()
  9. this.value = ''
  10. this.cursorVisible = true
  11. this.cursorIndex = 0
  12. this.scrollChars = 0
  13. }
  14. drawTo(writable) {
  15. // There should be room for the cursor so move the "right edge" left a
  16. // single character.
  17. const startRange = this.scrollChars
  18. const endRange = this.scrollChars + this.w - 3
  19. let str = this.value.slice(startRange, endRange)
  20. writable.write(ansi.moveCursor(this.absTop, this.absLeft + 1))
  21. writable.write(str)
  22. // Ellipsis on left side, if there's more characters behind the visible
  23. // area.
  24. if (startRange > 0) {
  25. writable.write(ansi.moveCursor(this.absTop, this.absLeft))
  26. writable.write(unic.ELLIPSIS)
  27. }
  28. // Ellipsis on the right side, if there's more characters ahead of the
  29. // visible area.
  30. if (endRange < this.value.length) {
  31. writable.write(ansi.moveCursor(this.absTop, this.absRight - 1))
  32. writable.write(unic.ELLIPSIS.repeat(2))
  33. }
  34. this.cursorX = this.cursorIndex - this.scrollChars + 1
  35. super.drawTo(writable)
  36. }
  37. keyPressed(keyBuf) {
  38. try {
  39. if (keyBuf[0] === 127) {
  40. this.value = (
  41. this.value.slice(0, this.cursorIndex - 1) +
  42. this.value.slice(this.cursorIndex)
  43. )
  44. this.cursorIndex--
  45. this.root.cursorMoved()
  46. return false
  47. } else if (keyBuf[0] === 13) {
  48. // These are aliases for each other.
  49. this.emit('value', this.value)
  50. this.emit('confirm', this.value)
  51. } else if (keyBuf[0] === 0x1b && keyBuf[1] === 0x5b) {
  52. // Keyboard navigation
  53. if (keyBuf[2] === 0x44) {
  54. this.cursorIndex--
  55. this.root.cursorMoved()
  56. } else if (keyBuf[2] === 0x43) {
  57. this.cursorIndex++
  58. this.root.cursorMoved()
  59. }
  60. return false
  61. } else if (telc.isEscape(keyBuf)) {
  62. // ESC is bad and we don't want that in the text input!
  63. // Also emit a "cancel" event, which doesn't necessarily do anything,
  64. // but can be listened to.
  65. this.emit('cancel')
  66. } else {
  67. const isTextInput = keyBuf.toString().split('').every(chr => {
  68. const n = chr.charCodeAt(0)
  69. return n > 31 && n < 127
  70. })
  71. if (isTextInput) {
  72. this.value = (
  73. this.value.slice(0, this.cursorIndex) + keyBuf.toString() +
  74. this.value.slice(this.cursorIndex)
  75. )
  76. this.cursorIndex += keyBuf.toString().length
  77. this.root.cursorMoved()
  78. this.emit('change', this.value)
  79. return false
  80. }
  81. }
  82. } finally {
  83. this.keepCursorInRange()
  84. }
  85. }
  86. setValue(value) {
  87. this.value = value
  88. this.moveToEnd()
  89. }
  90. moveToEnd() {
  91. this.cursorIndex = this.value.length
  92. this.keepCursorInRange()
  93. }
  94. keepCursorInRange() {
  95. // Keep the cursor inside or at the end of the input value.
  96. if (this.cursorIndex < 0) {
  97. this.cursorIndex = 0
  98. }
  99. if (this.cursorIndex > this.value.length) {
  100. this.cursorIndex = this.value.length
  101. }
  102. // Scroll right, if the cursor is past the right edge of where text is
  103. // displayed.
  104. while (this.cursorIndex - this.scrollChars > this.w - 3) {
  105. this.scrollChars++
  106. }
  107. // Scroll left, if the cursor is behind the left edge of where text is
  108. // displayed.
  109. while (this.cursorIndex - this.scrollChars < 0) {
  110. this.scrollChars--
  111. }
  112. // Scroll left, if we can see past the end of the text.
  113. while (this.scrollChars > 0 && (
  114. this.scrollChars + this.w - 3 > this.value.length)
  115. ) {
  116. this.scrollChars--
  117. }
  118. }
  119. get value() { return this.getDep('value') }
  120. set value(v) { return this.setDep('value', v) }
  121. get cursorIndex() { return this.getDep('cursorIndex') }
  122. set cursorIndex(v) { return this.setDep('cursorIndex', v) }
  123. }