diff options
Diffstat (limited to 'input/number.ts')
| -rw-r--r-- | input/number.ts | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/input/number.ts b/input/number.ts new file mode 100644 index 0000000..11e561b --- /dev/null +++ b/input/number.ts @@ -0,0 +1,177 @@ +export interface WuiInputNumberOpts { + label: string + value: number + id?: string + hint?: string + max?: number + min?: number + is_disabled?: boolean + onChangeHandler: (new_value: number) => void +} + +const WUI_INPUT_NUMBER_CLASS = "wui_input_number" +const WUI_INPUT_NUMBER_CLASS_HINT = "wui_input_number_hint" +const WUI_INPUT_NUMBER_CLASS_HINT_TOGGLER = "wui_input_number_hint_toggler" +const WUI_INPUT_NUMBER_CLASS_INPUT = "wui_input_number_input" +const WUI_INPUT_NUMBER_CLASS_LABEL = "wui_input_number_label" + +// +// WuiInputNumber create an HTML input that allow number only, with optional +// max and min options. +// The required options are "label" and "value". +// +// Format of generated HTML output, +// +// <div [id=${id}] class="${WUI_INPUT_NUMBER_CLASS}"> +// <div> +// <label class="${WUI_INPUT_NUMBER_CLASS_LABEL}">${label}</label> +// <input +// class="${WUI_INPUT_NUMBER_CLASS_INPUT}" +// [max=${max}] +// [min=${min}] +// [disabled=${is_disabled}] +// value=${value} +// > +// [<span class="${WUI_INPUT_NUMBER_CLASS_HINT_TOGGLER}"> i</span>] +// </div> +// [<div class="${WUI_INPUT_NUMBER_CLASS_HINT}">${hint}</div>] +// </div> +// +// User can set onChangeHandler to receive new value when the input value +// changes and valid; otherwise, if the value is invalid, the input +// background will changes accordingly. +// +export class WuiInputNumber { + el: HTMLElement + private el_label!: HTMLElement + private el_input!: HTMLInputElement + private el_hint!: HTMLElement + private el_hint_toggler!: HTMLElement + private value: number = 0 + + constructor(public opts: WuiInputNumberOpts) { + this.value = opts.value + + this.el = document.createElement("div") + if (opts.id) { + this.el.id = opts.id + } + this.el.classList.add(WUI_INPUT_NUMBER_CLASS) + this.el.style.padding = "2px" + + let wrapper = document.createElement("div") + this.generateLabel(wrapper) + this.generateInput(wrapper) + if (opts.hint) { + this.generateHintToggler(wrapper) + } + this.el.appendChild(wrapper) + + if (opts.hint) { + this.generateHint() + } + } + + private generateLabel(wrapper: HTMLElement) { + this.el_label = document.createElement("label") + this.el_label.classList.add(WUI_INPUT_NUMBER_CLASS_LABEL) + this.el_label.innerHTML = `${this.opts.label} ` + wrapper.appendChild(this.el_label) + } + + private generateInput(wrapper: HTMLElement) { + this.el_input = document.createElement("input") as HTMLInputElement + this.el_input.classList.add(WUI_INPUT_NUMBER_CLASS_INPUT) + this.el_input.type = "number" + this.el_input.value = "" + this.opts.value + + let hint = "" + if (this.opts.max) { + this.el_input.max = "" + this.opts.max + hint = "The maximum value is " + this.opts.max + } + if (this.opts.min) { + this.el_input.min = "" + this.opts.min + if (hint == "") { + hint = "The " + } else { + hint += " and " + } + hint += "minimum value is " + this.opts.min + } + + if (hint !== "") { + this.el_input.title = hint + } + + if (this.opts.is_disabled) { + this.el_input.disabled = true + } + + this.el_input.onkeyup = (ev: KeyboardEvent) => { + return this.onKeyUp(ev) + } + + wrapper.appendChild(this.el_input) + } + + private generateHintToggler(wrapper: HTMLElement) { + this.el_hint_toggler = document.createElement("span") + this.el_hint_toggler.classList.add(WUI_INPUT_NUMBER_CLASS_HINT_TOGGLER) + this.el_hint_toggler.innerHTML = " ℹ" + + this.el_hint_toggler.onmouseover = () => { + this.el_hint_toggler.style.cursor = "pointer" + } + this.el_hint_toggler.onclick = () => { + this.onClickHintToggler() + } + wrapper.appendChild(this.el_hint_toggler) + } + + private generateHint() { + let hint = this.opts.hint || "" + + this.el_hint = document.createElement("div") + this.el_hint.classList.add(WUI_INPUT_NUMBER_CLASS_HINT) + this.el_hint.innerHTML = hint + this.el_hint.style.display = "none" + this.el_hint.style.backgroundColor = "gainsboro" + this.el_hint.style.borderRadius = "2px" + this.el_hint.style.padding = "4px" + this.el.appendChild(this.el_hint) + } + + private onClickHintToggler() { + if (this.el_hint.style.display === "none") { + this.el_hint.style.display = "block" + } else { + this.el_hint.style.display = "none" + } + } + + private onKeyUp(ev: KeyboardEvent) { + let target = ev.target as HTMLInputElement + + ev.preventDefault() + let newValue = parseInt(target.value) + if (newValue === null) { + this.el_input.style.backgroundColor = "lightsalmon" + return false + } + if (this.opts.max && newValue > this.opts.max) { + this.el_input.style.backgroundColor = "lightsalmon" + return false + } + if (this.opts.min && newValue < this.opts.min) { + this.el_input.style.backgroundColor = "lightsalmon" + return false + } + this.el_input.style.backgroundColor = "white" + this.value = newValue + if (this.opts.onChangeHandler) { + this.opts.onChangeHandler(this.value) + } + return true + } +} |
