/** * TySelect Web Component — THE select control (replaces ty-dropdown/ty-multiselect). * Cardinality follows native semantics. This is the ONE place * selection is read FROM the DOM rather than treated as a rendering of * state (see getSelectedValues()): it seeds initial state, once. After * this, `value` rules — flipping an option's `selected` property later * does nothing, same as native. * * Called from initializeState() at connect AND from the child observer: * a real HTML parser upgrades a custom element the moment its start tag is * seen, BEFORE its children exist — `` connects with zero * `` children when it's part of the page's own markup (as * opposed to an already-built fragment attached via innerHTML/appendChild, * where children exist at connect). Calling this only at connect would * silently miss every option parsed after that instant, which is the * common case for markup that is literally in the page's initial HTML. * * Safe to call repeatedly: no-ops once `value` has ever been explicitly * set (attribute or property) or state is already non-empty — including * after a real `.clear()`, which removes `selected` from every option via * syncSelectedTags, so there is nothing left here to (re-)seed from. */ private resolveDeclarativeSelection; /** * Get all option elements from the component (ty-option preferred, ty-tag accepted) */ private getTagElements; private getTagData; /** * Select an option - attribute only. The option stays in the list with * selected styling; chips are rendered out-of-band (ty-selected-options). */ private selectTag; /** * Deselect an option - attribute only. */ private deselectTag; /** * Get the array of currently selected values. * * Reads STATE (`_state.selectedValues`), not the DOM: `value` is the * selection, and an option's `selected` attribute is a rendering of it * (written by syncSelectedTags below), never the other way round. This * matters whenever options don't fully reflect the selection — external * search legitimately has a selected value with no matching option * present; a DOM scan would report empty and desync .value, `required` * validation and the click-toggle logic from what's actually selected. */ private getSelectedValues; private syncSelectedTags; /** * role="option" for every current option/tag — separate from * syncSelectedTags (which only runs when the `value` property changes) * so it also covers the "nothing selected yet, value never touched" * startup case. Idempotent; cheap to call from the child MutationObserver * on every light-DOM change. */ private ensureOptionRoles; /** Central update function — synchronizes everything via TyComponent's property system. */ private updateComponentValue; /** * Build rich item info for values, read off the matching option elements */ private getSelectedItems; private calculatePosition; private _setupOptionsScrollbar; private _destroyOptionsScrollbar; /** * Open dropdown dialog (desktop mode) * * `.showModal()` puts the dialog in the top layer with a backdrop, but * does NOT prevent the page behind it from scrolling. We use the shared scroll * lock utility (overflow:hidden on ) to keep wheel/touch scrolling from * leaking through to the body — same behavior and * implement. */ private openDropdown; private closeDropdown; /** Open mobile modal — handles z-index natively. */ private openMobileModal; private closeMobileModal; private handleStubClick; /** * .select-clear click — must stop propagation before it bubbles to the * stub's own click handler (which would otherwise also open the dropdown). */ private handleClearClick; /** * Keyboard-open the (closed) stub. The stub is a plain div with * tabindex="0", not a native