UNPKG

choices.js

Version:

A vanilla JS customisable text input/select box plugin

116 lines (101 loc) 4.05 kB
import { parseCustomProperties } from '../lib/utils'; import { ClassNames } from '../interfaces/class-names'; import WrappedElement from './wrapped-element'; import { GroupFull } from '../interfaces/group-full'; import { ChoiceFull } from '../interfaces/choice-full'; import { stringToHtmlClass } from '../lib/choice-input'; import { isHtmlOptgroup, isHtmlOption } from '../lib/html-guard-statements'; export default class WrappedSelect extends WrappedElement<HTMLSelectElement> { classNames: ClassNames; template: (data: object) => HTMLOptionElement; extractPlaceholder: boolean; constructor({ element, classNames, template, extractPlaceholder, }: { element: HTMLSelectElement; classNames: ClassNames; template: (data: object) => HTMLOptionElement; extractPlaceholder: boolean; }) { super({ element, classNames }); this.template = template; this.extractPlaceholder = extractPlaceholder; } get placeholderOption(): HTMLOptionElement | null { return ( this.element.querySelector('option[value=""]') || // Backward compatibility layer for the non-standard placeholder attribute supported in older versions. this.element.querySelector('option[placeholder]') ); } addOptions(choices: ChoiceFull[]): void { const fragment = document.createDocumentFragment(); choices.forEach((obj) => { const choice = obj; if (choice.element) { return; } const option = this.template(choice); fragment.appendChild(option); choice.element = option; }); this.element.appendChild(fragment); } optionsAsChoices(): (ChoiceFull | GroupFull)[] { const choices: (ChoiceFull | GroupFull)[] = []; this.element.querySelectorAll<HTMLElement>(':scope > option, :scope > optgroup').forEach((e) => { if (isHtmlOption(e)) { choices.push(this._optionToChoice(e)); } else if (isHtmlOptgroup(e)) { choices.push(this._optgroupToChoice(e)); } // todo: hr as empty optgroup, requires displaying empty opt-groups to be useful }); return choices; } // eslint-disable-next-line class-methods-use-this _optionToChoice(option: HTMLOptionElement): ChoiceFull { // option.value returns the label if there is no value attribute, which can break legacy placeholder attribute support if (!option.hasAttribute('value') && option.hasAttribute('placeholder')) { option.setAttribute('value', ''); option.value = ''; } return { id: 0, group: null, score: 0, rank: 0, value: option.value, // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option // This attribute is text for the label indicating the meaning of the option. If the `label` attribute isn't defined, its value is that of the element text content (ie `innerText`). label: option.label, element: option, active: true, // this returns true if nothing is selected on initial load, which will break placeholder support selected: this.extractPlaceholder ? option.selected : option.hasAttribute('selected'), disabled: option.disabled, highlighted: false, placeholder: this.extractPlaceholder && (!option.value || option.hasAttribute('placeholder')), labelClass: typeof option.dataset.labelClass !== 'undefined' ? stringToHtmlClass(option.dataset.labelClass) : undefined, labelDescription: typeof option.dataset.labelDescription !== 'undefined' ? option.dataset.labelDescription : undefined, customProperties: parseCustomProperties(option.dataset.customProperties), }; } _optgroupToChoice(optgroup: HTMLOptGroupElement): GroupFull { const options = optgroup.querySelectorAll('option'); const choices = Array.from(options).map((option) => this._optionToChoice(option)); return { id: 0, label: optgroup.label || '', element: optgroup, active: !!choices.length, disabled: optgroup.disabled, choices, }; } }