« get me outta code hell

tui-lib - Pure Node.js library for making visual command-line programs (ala vim, ncdu)
about summary refs log tree commit diff
path: root/ui/controls/Form.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/controls/Form.js')
-rw-r--r--ui/controls/Form.js143
1 files changed, 143 insertions, 0 deletions
diff --git a/ui/controls/Form.js b/ui/controls/Form.js
new file mode 100644
index 0000000..921096a
--- /dev/null
+++ b/ui/controls/Form.js
@@ -0,0 +1,143 @@
+import telc from 'tui-lib/util/telchars'
+
+import {FocusElement} from 'tui-lib/ui/primitives'
+
+export default class Form extends FocusElement {
+  constructor() {
+    super()
+
+    this.inputs = []
+    this.curIndex = 0
+    this.captureTab = true
+  }
+
+  addInput(input, asChild = true, opts = {}) {
+    // Adds the given input as a child element and pushes it to the input
+    // list. If the optional argument asChild is false, it won't add the
+    // input element as a child of the form.
+
+    this.inputs.push(input)
+
+    if (asChild) {
+      this.addChild(input, this.children.length, opts)
+    }
+  }
+
+  removeInput(input, asChild = true, opts = {}) {
+    // Removes the given input from the form's input list. If the optional
+    // argument asChild is false, it won't try to removeChild the input.
+
+    if (this.inputs.includes(input)) {
+      this.inputs.splice(this.inputs.indexOf(input), 1)
+
+      if (asChild) {
+        this.removeChild(input, opts)
+      }
+    }
+  }
+
+  selectInput(input) {
+    if (this.inputs.includes(input)) {
+      this.curIndex = this.inputs.indexOf(input)
+      this.updateSelectedElement()
+    }
+  }
+
+  keyPressed(keyBuf) {
+    // Don't do anything if captureTab is set to false. This is handy for
+    // nested forms.
+    if (!this.captureTab) {
+      return
+    }
+
+    if (telc.isTab(keyBuf) || telc.isBackTab(keyBuf)) {
+      // No inputs to tab through, so do nothing.
+      if (this.inputs.length < 2) {
+        return
+      }
+
+      if (telc.isTab(keyBuf)) {
+        this.nextInput()
+      } else {
+        this.previousInput()
+      }
+
+      return false
+    }
+  }
+
+  get selectable() {
+    return this.inputs.some(inp => inp.selectable)
+  }
+
+  updateSelectedElement() {
+    if (this.root.select && this.inputs.length) {
+      if (this.curIndex > this.inputs.length - 1) {
+        this.curIndex = this.inputs.length - 1
+      }
+
+      this.root.select(this.inputs[this.curIndex], {fromForm: true})
+    }
+  }
+
+  previousInput() {
+    // TODO: Forms currently assume there is at least one selectable input,
+    // but this isn't necessarily always the case.
+    do {
+      this.curIndex = (this.curIndex - 1)
+      if (this.curIndex < 0) {
+        this.curIndex = (this.inputs.length - 1)
+      }
+    } while (!this.inputs[this.curIndex].selectable)
+
+    this.updateSelectedElement()
+  }
+
+  nextInput() {
+    // TODO: See previousInput
+    do {
+      this.curIndex = (this.curIndex + 1) % this.inputs.length
+    } while (!this.inputs[this.curIndex].selectable)
+
+    this.updateSelectedElement()
+  }
+
+  firstInput(selectForm = true) {
+    this.curIndex = 0
+
+    // TODO: See previousInput
+    if (!this.inputs[this.curIndex].selectable) {
+      this.nextInput()
+    }
+
+    if (selectForm || (
+      this.root.isChildOrSelfSelected && this.root.isChildOrSelfSelected(this)
+    )) {
+      this.updateSelectedElement()
+    }
+  }
+
+  lastInput(selectForm = true) {
+    this.curIndex = this.inputs.length - 1
+
+    // TODO: See previousInput
+    if (!this.inputs[this.curIndex].selectable) {
+      this.previousInput()
+    }
+
+    if (selectForm || (
+      this.root.isChildOrSelfSelected && this.root.isChildOrSelfSelected(this)
+    )) {
+      this.updateSelectedElement()
+    }
+  }
+
+  selected() {
+    if (this.root.selectedElement === this) {
+      this.updateSelectedElement()
+    }
+  }
+
+  get curIndex() { return this.getDep('curIndex') }
+  set curIndex(v) { return this.setDep('curIndex', v) }
+}