@jimka/typescript-ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -18
- package/dist/lib/{AbstractBooleanInput-C3rj7sje.js → AbstractBooleanInput-BGesvIZy.js} +2 -2
- package/dist/lib/{AbstractBooleanInput-C3rj7sje.js.map → AbstractBooleanInput-BGesvIZy.js.map} +1 -1
- package/dist/lib/{AbstractInput-DD-NpicS.js → AbstractInput-BxOtFrER.js} +2 -2
- package/dist/lib/{AbstractInput-DD-NpicS.js.map → AbstractInput-BxOtFrER.js.map} +1 -1
- package/dist/lib/AnimatedDropdown-CZQEqQi-.js +2 -0
- package/dist/lib/AnimatedDropdown-CZQEqQi-.js.map +1 -0
- package/dist/lib/Border-BgaZkc1R.js +2 -0
- package/dist/lib/Border-BgaZkc1R.js.map +1 -0
- package/dist/lib/{BoxLayout-DgZukB5_.js → BoxLayout-DrKGfsup.js} +2 -2
- package/dist/lib/{BoxLayout-DgZukB5_.js.map → BoxLayout-DrKGfsup.js.map} +1 -1
- package/dist/lib/Button-U1rsP4v8.js +2 -0
- package/dist/lib/Button-U1rsP4v8.js.map +1 -0
- package/dist/lib/{Card-UF0fq8cQ.js → Card-BVwOS8AG.js} +2 -2
- package/dist/lib/{Card-UF0fq8cQ.js.map → Card-BVwOS8AG.js.map} +1 -1
- package/dist/lib/ComboBox-B9EDPu6T.js +2 -0
- package/dist/lib/ComboBox-B9EDPu6T.js.map +1 -0
- package/dist/lib/Component-DXf5a3g0.js +2 -0
- package/dist/lib/Component-DXf5a3g0.js.map +1 -0
- package/dist/lib/Container-D5H8VxJK.js +2 -0
- package/dist/lib/{Container-3KsvsJ6S.js.map → Container-D5H8VxJK.js.map} +1 -1
- package/dist/lib/DOM-CGn7xyTw.js +2 -0
- package/dist/lib/DOM-CGn7xyTw.js.map +1 -0
- package/dist/lib/DragManager-CI4mZQFy.js +2 -0
- package/dist/lib/{DragManager-tPMnbjeO.js.map → DragManager-CI4mZQFy.js.map} +1 -1
- package/dist/lib/FieldDecorator-CJs9gwqH.js +2 -0
- package/dist/lib/{FieldDecorator-E7WzutjO.js.map → FieldDecorator-CJs9gwqH.js.map} +1 -1
- package/dist/lib/{Fit-CFIr4P9R.js → Fit-DjRsiwlg.js} +2 -2
- package/dist/lib/{Fit-CFIr4P9R.js.map → Fit-DjRsiwlg.js.map} +1 -1
- package/dist/lib/Glyph-KEf4nIv2.js +2 -0
- package/dist/lib/Glyph-KEf4nIv2.js.map +1 -0
- package/dist/lib/{Grid-Czux--Mr.js → Grid-DdL9TVeJ.js} +2 -2
- package/dist/lib/{Grid-Czux--Mr.js.map → Grid-DdL9TVeJ.js.map} +1 -1
- package/dist/lib/GridConstraints-DkLueyrf.js +2 -0
- package/dist/lib/GridConstraints-DkLueyrf.js.map +1 -0
- package/dist/lib/{HBox-Q8y1PHlk.js → HBox-Ce0GTCJP.js} +2 -2
- package/dist/lib/{HBox-Q8y1PHlk.js.map → HBox-Ce0GTCJP.js.map} +1 -1
- package/dist/lib/{Header-zPdvC2AX.js → Header-BQIVpBte.js} +2 -2
- package/dist/lib/{Header-zPdvC2AX.js.map → Header-BQIVpBte.js.map} +1 -1
- package/dist/lib/{IconText-BvGfu7eq.js → IconText-Bo4Qcnot.js} +2 -2
- package/dist/lib/{IconText-BvGfu7eq.js.map → IconText-Bo4Qcnot.js.map} +1 -1
- package/dist/lib/LayoutConstraints-DkctJZXB.js +2 -0
- package/dist/lib/LayoutConstraints-DkctJZXB.js.map +1 -0
- package/dist/lib/LayoutSerialization-BT66TY83.js +2 -0
- package/dist/lib/LayoutSerialization-BT66TY83.js.map +1 -0
- package/dist/lib/LayoutSizes-DU3qSq0w.js +2 -0
- package/dist/lib/LayoutSizes-DU3qSq0w.js.map +1 -0
- package/dist/lib/List-DtTVTyWW.js +2 -0
- package/dist/lib/List-DtTVTyWW.js.map +1 -0
- package/dist/lib/Menu-Dz4L7EKJ.js +2 -0
- package/dist/lib/Menu-Dz4L7EKJ.js.map +1 -0
- package/dist/lib/MenuButton-VJ5uC3c1.js +2 -0
- package/dist/lib/{MenuButton-JQQaKThJ.js.map → MenuButton-VJ5uC3c1.js.map} +1 -1
- package/dist/lib/Panel-DzWdYwq1.js +2 -0
- package/dist/lib/Panel-DzWdYwq1.js.map +1 -0
- package/dist/lib/Position-A39T7-_1.js +2 -0
- package/dist/lib/Position-A39T7-_1.js.map +1 -0
- package/dist/lib/ProgressSpinner-zJKtGyNJ.js +2 -0
- package/dist/lib/ProgressSpinner-zJKtGyNJ.js.map +1 -0
- package/dist/lib/RadioButton-CYSSv4nV.js +2 -0
- package/dist/lib/RadioButton-CYSSv4nV.js.map +1 -0
- package/dist/lib/{RovingTabIndex-CFIzIjjU.js → RovingTabIndex-BSZ31Vp5.js} +2 -2
- package/dist/lib/{RovingTabIndex-CFIzIjjU.js.map → RovingTabIndex-BSZ31Vp5.js.map} +1 -1
- package/dist/lib/Scrollbar-BhtOjqkG.js +2 -0
- package/dist/lib/Scrollbar-BhtOjqkG.js.map +1 -0
- package/dist/lib/Slider-CdXm7E64.js +2 -0
- package/dist/lib/Slider-CdXm7E64.js.map +1 -0
- package/dist/lib/{Spacer-BVQncB5B.js → Spacer-D9NGW4QG.js} +2 -2
- package/dist/lib/{Spacer-BVQncB5B.js.map → Spacer-D9NGW4QG.js.map} +1 -1
- package/dist/lib/TabButton-CThevdY_.js +2 -0
- package/dist/lib/{TabButton-pZKFYg7b.js.map → TabButton-CThevdY_.js.map} +1 -1
- package/dist/lib/Text-gz6qjFfP.js +2 -0
- package/dist/lib/Text-gz6qjFfP.js.map +1 -0
- package/dist/lib/Tooltip-tkXDk5Bo.js +3 -0
- package/dist/lib/Tooltip-tkXDk5Bo.js.map +1 -0
- package/dist/lib/{VBox-D5B9ecw0.js → VBox-EIqw85Rh.js} +2 -2
- package/dist/lib/{VBox-D5B9ecw0.js.map → VBox-EIqw85Rh.js.map} +1 -1
- package/dist/lib/VirtualRowView-BxpHQ_u8.js +2 -0
- package/dist/lib/{VirtualRowView-Con9SWiU.js.map → VirtualRowView-BxpHQ_u8.js.map} +1 -1
- package/dist/lib/VirtualScroller-DwEEJE1_.js +2 -0
- package/dist/lib/{VirtualScroller-BPDmO1ir.js.map → VirtualScroller-DwEEJE1_.js.map} +1 -1
- package/dist/lib/component/button.es.js +1 -1
- package/dist/lib/component/button.es.js.map +1 -1
- package/dist/lib/component/chart.es.js +1 -1
- package/dist/lib/component/chart.es.js.map +1 -1
- package/dist/lib/component/container.es.js +1 -1
- package/dist/lib/component/container.es.js.map +1 -1
- package/dist/lib/component/diagram.es.js +1 -1
- package/dist/lib/component/diagram.es.js.map +1 -1
- package/dist/lib/component/display.es.js +1 -1
- package/dist/lib/component/display.es.js.map +1 -1
- package/dist/lib/component/editor.es.js +3 -1
- package/dist/lib/component/editor.es.js.map +1 -1
- package/dist/lib/component/input.es.js +1 -1
- package/dist/lib/component/input.es.js.map +1 -1
- package/dist/lib/component/list.es.js +1 -1
- package/dist/lib/component/list.es.js.map +1 -1
- package/dist/lib/component/menubar.es.js +1 -1
- package/dist/lib/component/menubar.es.js.map +1 -1
- package/dist/lib/component/table.es.js +2 -2
- package/dist/lib/component/table.es.js.map +1 -1
- package/dist/lib/component/tree.es.js +1 -1
- package/dist/lib/component/tree.es.js.map +1 -1
- package/dist/lib/core.es.js +1 -1
- package/dist/lib/core.es.js.map +1 -1
- package/dist/lib/layout.es.js +1 -1
- package/dist/lib/overlay.es.js +1 -1
- package/dist/lib/overlay.es.js.map +1 -1
- package/dist/lib/primitive.es.js +1 -1
- package/dist/lib/router.es.js +2 -0
- package/dist/lib/router.es.js.map +1 -0
- package/dist/lib/types/component/button/Button.d.ts +4 -3
- package/dist/lib/types/component/chart/AbstractChart.d.ts +1 -1
- package/dist/lib/types/component/chart/ChartLegend.d.ts +1 -1
- package/dist/lib/types/component/container/DialogBackdrop.d.ts +2 -1
- package/dist/lib/types/component/container/MenuItem.d.ts +1 -1
- package/dist/lib/types/component/container/ScrollStrip.d.ts +1 -0
- package/dist/lib/types/component/container/SplitGutter.d.ts +3 -2
- package/dist/lib/types/component/container/TabBar.d.ts +1 -2
- package/dist/lib/types/component/container/TabPanel.d.ts +6 -3
- package/dist/lib/types/component/container/WindowBorder.d.ts +2 -1
- package/dist/lib/types/component/container/WindowHeader.d.ts +2 -1
- package/dist/lib/types/component/display/Glyph.d.ts +2 -1
- package/dist/lib/types/component/display/Markdown.d.ts +11 -1
- package/dist/lib/types/component/display/PaginationBar.d.ts +1 -1
- package/dist/lib/types/component/display/SpinnerWrap.d.ts +2 -0
- package/dist/lib/types/component/display/Video.d.ts +1 -1
- package/dist/lib/types/component/display/VideoPlayer.d.ts +1 -1
- package/dist/lib/types/component/display/index.d.ts +1 -1
- package/dist/lib/types/component/editor/CodeEditor.d.ts +2 -1
- package/dist/lib/types/component/editor/MarkdownEditor.d.ts +7 -1
- package/dist/lib/types/component/editor/markdownTableTransformer.d.ts +2 -0
- package/dist/lib/types/component/input/Checkbox.d.ts +2 -1
- package/dist/lib/types/component/input/Link.d.ts +1 -1
- package/dist/lib/types/component/input/RadioButton.d.ts +2 -1
- package/dist/lib/types/component/input/Slider.d.ts +2 -1
- package/dist/lib/types/component/input/Text.d.ts +1 -3
- package/dist/lib/types/component/input/TextInput.d.ts +3 -2
- package/dist/lib/types/component/list/AbstractSelectableList.d.ts +2 -1
- package/dist/lib/types/component/menubar/MenuBarButton.d.ts +1 -1
- package/dist/lib/types/core/AnimatedDropdown.d.ts +6 -2
- package/dist/lib/types/core/Animation.d.ts +9 -6
- package/dist/lib/types/core/Component.d.ts +16 -11
- package/dist/lib/types/core/DOM.d.ts +14 -0
- package/dist/lib/types/core/Event.d.ts +12 -6
- package/dist/lib/types/core/index.d.ts +2 -2
- package/dist/lib/types/layout/Accordion.d.ts +2 -0
- package/dist/lib/types/layout/Border.d.ts +1 -0
- package/dist/lib/types/layout/CollapseSupport.d.ts +5 -1
- package/dist/lib/types/layout/LayoutConstraints.d.ts +1 -0
- package/dist/lib/types/layout/LayoutManager.d.ts +2 -0
- package/dist/lib/types/layout/Split.d.ts +1 -0
- package/dist/lib/types/layout/Tab.d.ts +10 -3
- package/dist/lib/types/overlay/AbstractWindow.d.ts +9 -2
- package/dist/lib/types/overlay/Dialog.d.ts +6 -0
- package/dist/lib/types/overlay/Dock.d.ts +12 -2
- package/dist/lib/types/overlay/Drawer.d.ts +5 -0
- package/dist/lib/types/overlay/Menu.d.ts +3 -1
- package/dist/lib/types/overlay/Notification.d.ts +3 -0
- package/dist/lib/types/overlay/Popover.d.ts +3 -1
- package/dist/lib/types/overlay/Rail.d.ts +7 -0
- package/dist/lib/types/overlay/Tooltip.d.ts +3 -0
- package/dist/lib/types/overlay/index.d.ts +1 -1
- package/dist/lib/types/router/RoutePattern.d.ts +18 -0
- package/dist/lib/types/router/Router.d.ts +36 -0
- package/dist/lib/types/router/index.d.ts +2 -0
- package/dist/lib/validation.es.js +1 -1
- package/llms.txt +9 -5
- package/package.json +6 -1
- package/dist/lib/AnimatedDropdown-BbGCs70E.js +0 -2
- package/dist/lib/AnimatedDropdown-BbGCs70E.js.map +0 -1
- package/dist/lib/Border-oZC_SDy8.js +0 -2
- package/dist/lib/Border-oZC_SDy8.js.map +0 -1
- package/dist/lib/Button-DEUtBqK4.js +0 -2
- package/dist/lib/Button-DEUtBqK4.js.map +0 -1
- package/dist/lib/ComboBox-GOBOAJ1N.js +0 -2
- package/dist/lib/ComboBox-GOBOAJ1N.js.map +0 -1
- package/dist/lib/Component-BR2f0Itb.js +0 -2
- package/dist/lib/Component-BR2f0Itb.js.map +0 -1
- package/dist/lib/Container-3KsvsJ6S.js +0 -2
- package/dist/lib/DragManager-tPMnbjeO.js +0 -2
- package/dist/lib/FieldDecorator-E7WzutjO.js +0 -2
- package/dist/lib/Glyph-WUqTF_Z9.js +0 -2
- package/dist/lib/Glyph-WUqTF_Z9.js.map +0 -1
- package/dist/lib/GridConstraints-Q3wvuovN.js +0 -2
- package/dist/lib/GridConstraints-Q3wvuovN.js.map +0 -1
- package/dist/lib/LayoutConstraints-CHI2lhPd.js +0 -2
- package/dist/lib/LayoutConstraints-CHI2lhPd.js.map +0 -1
- package/dist/lib/LayoutSerialization-CWU0iEuV.js +0 -2
- package/dist/lib/LayoutSerialization-CWU0iEuV.js.map +0 -1
- package/dist/lib/LayoutSizes-BW4wyGDI.js +0 -2
- package/dist/lib/LayoutSizes-BW4wyGDI.js.map +0 -1
- package/dist/lib/List-D-GhoVgB.js +0 -2
- package/dist/lib/List-D-GhoVgB.js.map +0 -1
- package/dist/lib/Menu-Wz4MK6sA.js +0 -2
- package/dist/lib/Menu-Wz4MK6sA.js.map +0 -1
- package/dist/lib/MenuButton-JQQaKThJ.js +0 -2
- package/dist/lib/Panel-DIsdaqc2.js +0 -2
- package/dist/lib/Panel-DIsdaqc2.js.map +0 -1
- package/dist/lib/Position-B-WfZYAJ.js +0 -2
- package/dist/lib/Position-B-WfZYAJ.js.map +0 -1
- package/dist/lib/ProgressSpinner-Dzaka7_c.js +0 -2
- package/dist/lib/ProgressSpinner-Dzaka7_c.js.map +0 -1
- package/dist/lib/RadioButton-eI3-vXn3.js +0 -2
- package/dist/lib/RadioButton-eI3-vXn3.js.map +0 -1
- package/dist/lib/Scrollbar-DchfiFKC.js +0 -2
- package/dist/lib/Scrollbar-DchfiFKC.js.map +0 -1
- package/dist/lib/Slider-xRxNSjFN.js +0 -2
- package/dist/lib/Slider-xRxNSjFN.js.map +0 -1
- package/dist/lib/TabButton-pZKFYg7b.js +0 -2
- package/dist/lib/Text-D1RrBY52.js +0 -2
- package/dist/lib/Text-D1RrBY52.js.map +0 -1
- package/dist/lib/Tooltip-4lU_tf6r.js +0 -3
- package/dist/lib/Tooltip-4lU_tf6r.js.map +0 -1
- package/dist/lib/VirtualRowView-Con9SWiU.js +0 -2
- package/dist/lib/VirtualScroller-BPDmO1ir.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.es.js","names":[],"sources":["../../../src/typescript/lib/component/input/DateField.ts","../../../src/typescript/lib/component/input/DateTimeField.ts","../../../src/typescript/lib/component/input/PasswordField.ts","../../../src/typescript/lib/component/input/UsernameField.ts","../../../src/typescript/lib/component/input/TextArea.ts","../../../src/typescript/lib/component/input/Toggle.ts","../../../src/typescript/lib/component/input/AutoCompleteDropdown.ts","../../../src/typescript/lib/component/input/AutoCompleteField.ts","../../../src/typescript/lib/component/input/SpinButton.ts","../../../src/typescript/lib/component/input/NumberSpinner.ts","../../../src/typescript/lib/component/input/FileField.ts","../../../src/typescript/lib/component/input/FileDropZone.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractPickerField, AbstractPickerFieldOptions } from \"~/component/input/AbstractPickerField.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { calendar } from \"~/glyphs/solid/calendar.js\";\nimport { DatePickerDropdown } from \"~/component/input/DatePickerDropdown.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(calendar);\n\n/**\n * Construction-time options for {@link DateField}.\n *\n * @category Components\n */\nexport interface DateFieldOptions extends AbstractPickerFieldOptions {\n value?: Date | null;\n /** Earliest date the picker will allow selection of. Optional. */\n minDate?: Date | null;\n /** Latest date the picker will allow selection of. Optional. */\n maxDate?: Date | null;\n}\n\n/**\n * A date-picker input component.\n *\n * Renders a text input with a calendar-glyph button on the right; clicking\n * either opens a [`DatePickerDropdown`](/api/component/input/classes/DatePickerDropdown)\n * panel that fades in via the shared\n * [`AnimatedDropdown`](/api/core/classes/AnimatedDropdown) lifecycle. The\n * native browser date-picker is no longer used.\n *\n * Inherits the [`Bindable`](/api/core/interfaces/Bindable) value contract,\n * change/binding listeners, and enabled/read-only surface from\n * [`AbstractPickerField`](/api/component/input/classes/AbstractPickerField).\n * Returns `null` from `getValue` when the field is empty.\n *\n * @category Components\n */\nclass DateField extends AbstractPickerField<Date, DatePickerDropdown, DateFieldOptions> {\n\n constructor(options?: DateFieldOptions) {\n super(options);\n\n // Button.setGlyph adds the glyph as the leading child of the\n // content row (with pointer-events: none) so the outer Fit centres\n // it within the button's inner rect.\n this._button.setGlyph(\"calendar\");\n\n // Late-built value: `applyOptions` cached it on `_options` at super-time;\n // re-apply now that `_input` exists so the inner text reflects it. The\n // enabled / readOnly re-dispatch is handled by the base constructor.\n if (this._options.value !== undefined) {\n this.setValue(this._options.value);\n }\n }\n\n /**\n * Applies a {@link DateFieldOptions} bag, caching the initial value pure\n * on `_options`. The constructor body dispatches the late-built fields\n * (value/enabled/readOnly) through their setters once `_input` exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DateFieldOptions): this {\n super.applyOptions(options);\n\n if (options.value !== undefined) {\n this._options.value = options.value;\n }\n\n if (options.minDate !== undefined) {\n this._options.minDate = options.minDate;\n }\n\n if (options.maxDate !== undefined) {\n this._options.maxDate = options.maxDate;\n }\n\n return this;\n }\n\n /**\n * Formats a Date as a YYYY-MM-DD string for display in the text input.\n *\n * @param date - The Date to format.\n * @returns A \"YYYY-MM-DD\" string.\n */\n protected formatValue(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n\n return `${y}-${m}-${d}`;\n }\n\n /**\n * Parses a \"YYYY-MM-DD\" string into a Date. Appends local midnight to\n * avoid UTC offset shifting the day.\n *\n * @param raw - The raw text typed into the input.\n * @returns The parsed Date, or null on parse failure.\n */\n protected parseRaw(raw: string): Date | null {\n const d = new Date(raw + \"T00:00:00\");\n\n return isNaN(d.getTime()) ? null : d;\n }\n\n /**\n * Builds the date dropdown with the field's selection callback and any\n * cached `minDate` / `maxDate` bounds forwarded into the dropdown's own\n * options bag.\n */\n protected createDropdown(): DatePickerDropdown {\n return new DatePickerDropdown(\n date => this.onDropdownSelected(date),\n {\n minDate: this._options.minDate ?? null,\n maxDate: this._options.maxDate ?? null,\n },\n );\n }\n\n /**\n * Called when the user picks a day from the dropdown. Commits the value,\n * closes the panel, refocuses the input, and re-fires `input` so any\n * non-AbstractInput consumer reading from the inner DOM event still\n * sees the change.\n *\n * @param date - The chosen date.\n */\n protected onDropdownSelected(date: Date): void {\n this.setValue(date);\n this.closeDropdown();\n this._input.focus();\n Event.fireEvent(this._input, \"input\");\n }\n\n /**\n * The DateField's preferred width — chosen to fit a \"YYYY-MM-DD\" string\n * plus the 24-px glyph button without overflow at the default font size.\n */\n protected getPreferredWidth(): number {\n return 160;\n }\n}\n\nconst DateFieldCallable = callable(DateField);\ntype DateFieldCallable = DateField;\nexport {\n DateField as _DateField,\n DateFieldCallable as DateField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractPickerField, AbstractPickerFieldOptions } from \"~/component/input/AbstractPickerField.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { calendar } from \"~/glyphs/solid/calendar.js\";\nimport { DateTimePickerDropdown } from \"~/component/input/DateTimePickerDropdown.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(calendar);\n\n/**\n * Construction-time options for {@link DateTimeField}.\n *\n * @category Components\n */\nexport interface DateTimeFieldOptions extends AbstractPickerFieldOptions {\n value?: Date | null;\n /** When true, the field formats and the picker exposes seconds. Default: false. */\n showSeconds?: boolean;\n /** Earliest date the picker will allow selection of. Optional. */\n minDate?: Date | null;\n /** Latest date the picker will allow selection of. Optional. */\n maxDate?: Date | null;\n}\n\n/**\n * A date-time-picker input component.\n *\n * Renders a text input with a calendar-glyph button on the right; clicking\n * either opens a\n * [`DateTimePickerDropdown`](/api/component/input/classes/DateTimePickerDropdown)\n * panel that combines a month-view calendar grid with an hour/minute\n * selector. The dropdown fades in via the shared\n * [`AnimatedDropdown`](/api/core/classes/AnimatedDropdown) lifecycle.\n *\n * Inherits the [`Bindable`](/api/core/interfaces/Bindable) value contract,\n * change/binding listeners, and enabled/read-only surface from\n * [`AbstractPickerField`](/api/component/input/classes/AbstractPickerField).\n *\n * @category Components\n */\nclass DateTimeField extends AbstractPickerField<Date, DateTimePickerDropdown, DateTimeFieldOptions> {\n\n constructor(options?: DateTimeFieldOptions) {\n super(options);\n\n // Button.setGlyph adds the glyph as the leading child of the\n // content row (with pointer-events: none) so the outer Fit centres\n // it within the button's inner rect.\n this._button.setGlyph(\"calendar\");\n\n // Late-built state: `applyOptions` cached these on `_options` at\n // super-time. `showSeconds` is read into the inherited field before\n // `setValue` so the initial formatting picks up the right precision.\n // The enabled / readOnly re-dispatch is handled by the base constructor.\n if (this._options.showSeconds !== undefined) {\n this._showSeconds = this._options.showSeconds;\n }\n\n if (this._options.value !== undefined) {\n this.setValue(this._options.value);\n }\n }\n\n /**\n * Applies a {@link DateTimeFieldOptions} bag. Late-built fields (value,\n * showSeconds) are cached pure on `_options` here and dispatched from\n * the constructor body once `_input` exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DateTimeFieldOptions): this {\n super.applyOptions(options);\n\n if (options.showSeconds !== undefined) {\n this._options.showSeconds = options.showSeconds;\n }\n\n if (options.value !== undefined) {\n this._options.value = options.value;\n }\n\n if (options.minDate !== undefined) {\n this._options.minDate = options.minDate;\n }\n\n if (options.maxDate !== undefined) {\n this._options.maxDate = options.maxDate;\n }\n\n return this;\n }\n\n /**\n * Formats a Date as a \"YYYY-MM-DD HH:MM\" (or \"YYYY-MM-DD HH:MM:SS\" when\n * `showSeconds` is true) string for display in the input.\n *\n * @param date - The Date to format.\n * @returns The formatted date-time string.\n */\n protected formatValue(date: Date): string {\n const y = date.getFullYear();\n const mo = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n const h = String(date.getHours()).padStart(2, \"0\");\n const mi = String(date.getMinutes()).padStart(2, \"0\");\n\n if (this._showSeconds) {\n const s = String(date.getSeconds()).padStart(2, \"0\");\n\n return `${y}-${mo}-${d} ${h}:${mi}:${s}`;\n }\n\n return `${y}-${mo}-${d} ${h}:${mi}`;\n }\n\n /**\n * Parses the typed text into a Date. Returns null on parse failure.\n *\n * @param raw - The raw text typed into the input.\n * @returns The parsed Date, or null.\n */\n protected parseRaw(raw: string): Date | null {\n // Require both a date and a time portion, ISO-anchored, so parsing is\n // the strict inverse of formatValue and not the locale-dependent,\n // time-optional `new Date(raw)`. Mirrors DateField/TimeField strictness.\n const [datePart, timePart] = raw.trim().split(/\\s+/);\n if (!datePart || !timePart) {\n return null;\n }\n\n const d = new Date(`${datePart}T${timePart}`);\n\n return isNaN(d.getTime()) ? null : d;\n }\n\n /**\n * Builds the date-time dropdown with the field's selection callback, the\n * cached `showSeconds` flag, and any `minDate` / `maxDate` bounds forwarded\n * into the dropdown's own options bag.\n */\n protected createDropdown(): DateTimePickerDropdown {\n return new DateTimePickerDropdown(\n date => this.onDropdownSelected(date),\n {\n showSeconds: this._showSeconds,\n minDate: this._options.minDate ?? null,\n maxDate: this._options.maxDate ?? null,\n },\n );\n }\n\n /**\n * Called when the user picks a date-time from the dropdown. Commits the\n * value and re-fires `input` on the inner field so any DOM-event\n * consumer still sees the change.\n *\n * @param date - The chosen Date.\n */\n protected onDropdownSelected(date: Date): void {\n this.setValue(date);\n Event.fireEvent(this._input, \"input\");\n }\n\n /**\n * The DateTimeField's preferred width — chosen to fit a\n * \"YYYY-MM-DD HH:MM[:SS]\" string plus the 24-px glyph button without\n * overflow at the default font size.\n */\n protected getPreferredWidth(): number {\n return 200;\n }\n}\n\nconst DateTimeFieldCallable = callable(DateTimeField);\ntype DateTimeFieldCallable = DateTimeField;\nexport {\n DateTimeField as _DateTimeField,\n DateTimeFieldCallable as DateTimeField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { TextInput, TextInputOptions } from \"~/component/input/TextInput.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link PasswordField}.\n *\n * @category Components\n */\nexport interface PasswordFieldOptions extends TextInputOptions {\n /**\n * false (default) → autocomplete=\"current-password\" (login);\n * true → autocomplete=\"new-password\" (signup / change-password).\n */\n newPassword?: boolean;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins.\n */\nconst _defaultPasswordFieldOptions: Partial<PasswordFieldOptions> = {\n padding: new Insets(3, 3, 3, 3),\n cursor: \"text\",\n backgroundColor: \"var(--ts-ui-input-bg, rgb(255, 255, 255))\",\n foregroundColor: \"var(--ts-ui-text-color, black)\",\n};\n\n/**\n * A password input component that renders an `<input type=\"password\">`\n * element, defaulting `autocomplete=\"current-password\"` and `name=\"password\"`\n * for browser credential managers.\n *\n * @category Components\n */\nclass PasswordField extends TextInput<PasswordFieldOptions> {\n\n constructor(options?: PasswordFieldOptions) {\n super(options, _defaultPasswordFieldOptions);\n\n this.updateHeight();\n this.subscribeTheme(() => this.updateHeight());\n\n this.setType(\"password\");\n\n if (this._options.name === undefined) {\n this.setName(\"password\");\n }\n\n if (this._options.autoComplete === undefined) {\n this.setAutoComplete(options?.newPassword ? \"new-password\" : \"current-password\");\n }\n }\n\n /**\n * Recalculates preferred and maximum height from the unified line box plus\n * this field's own chrome.\n *\n * @remarks Box height is `Util.lineHeightPx()` plus the field's own vertical\n * insets, padding, and border — the same sum `wrapInnerBaseline` re-adds —\n * so the rendered input and its baseline match a sibling `Text`/`ComboBox`.\n * Called at construction time and after each theme change so that font-size\n * adjustments propagate to the layout hint automatically.\n */\n private updateHeight(): void {\n const h = Util.singleLineBoxHeight(this.getInsets(), this.getPadding(), this.getBorderSize());\n\n this.setPreferredSize(200, h);\n this.setMaxSize(Number.MAX_SAFE_INTEGER, h);\n // Min-height pinned to the single-line box so the field can't be\n // vertically compressed below one line; min-width 0 keeps it\n // horizontally flexible.\n this.setMinSize(0, h);\n }\n}\n\nconst PasswordFieldCallable = callable(PasswordField);\ntype PasswordFieldCallable = PasswordField;\nexport {\n PasswordField as _PasswordField,\n PasswordFieldCallable as PasswordField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { TextInput, TextInputOptions } from \"~/component/input/TextInput.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link UsernameField}.\n *\n * @category Components\n */\nexport interface UsernameFieldOptions extends TextInputOptions {\n /** When true, seed autocomplete=\"email\" instead of \"username\" (email-based logins). */\n email?: boolean;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins.\n */\nconst _defaultUsernameFieldOptions: Partial<UsernameFieldOptions> = {\n cursor: \"text\",\n padding: new Insets(3, 3, 3, 3),\n backgroundColor: \"var(--ts-ui-input-bg, rgb(255, 255, 255))\",\n foregroundColor: \"var(--ts-ui-text-color, black)\",\n};\n\n/**\n * A username / login-identifier field — a `TextField` preset that defaults\n * `autocomplete=\"username\"` and `name=\"username\"` for browser credential\n * managers.\n *\n * Keeps internal text state in sync with the DOM on every input event.\n *\n * @category Components\n */\nclass UsernameField extends TextInput<UsernameFieldOptions> {\n\n constructor(options?: UsernameFieldOptions) {\n super(options, _defaultUsernameFieldOptions);\n\n this.updateHeight();\n this.subscribeTheme(() => this.updateHeight());\n\n this.setType(\"text\");\n\n if (this._options.name === undefined) {\n this.setName(\"username\");\n }\n\n if (this._options.autoComplete === undefined) {\n this.setAutoComplete(options?.email ? \"email\" : \"username\");\n }\n }\n\n /**\n * Recalculates preferred and maximum height from the unified line box plus\n * this field's own chrome.\n *\n * @remarks Box height is `Util.lineHeightPx()` plus the field's own vertical\n * insets, padding, and border — the same sum `wrapInnerBaseline` re-adds —\n * so the rendered input and its baseline match a sibling `Text`/`ComboBox`.\n * Called at construction time and after each theme change so that font-size\n * adjustments propagate to the layout hint automatically.\n */\n private updateHeight(): void {\n const h = Util.singleLineBoxHeight(this.getInsets(), this.getPadding(), this.getBorderSize());\n\n this.setPreferredSize(200, h);\n this.setMaxSize(Number.MAX_SAFE_INTEGER, h);\n // Min-height pinned to the single-line box so the field can't be\n // vertically compressed below one line; min-width 0 keeps it\n // horizontally flexible.\n this.setMinSize(0, h);\n }\n\n}\n\nconst UsernameFieldCallable = callable(UsernameField);\ntype UsernameFieldCallable = UsernameField;\nexport {\n UsernameField as _UsernameField,\n UsernameFieldCallable as UsernameField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { TextInput, TextInputOptions } from \"~/component/input/TextInput.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link TextArea}.\n *\n * @category Components\n */\nexport interface TextAreaOptions extends TextInputOptions {\n rows?: number;\n cols?: number;\n wrap?: string;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins.\n */\nconst _defaultTextAreaOptions: Partial<TextAreaOptions> = {\n tag: \"textarea\",\n cursor: \"text\",\n // Override Component's `overflow: \"hidden\"` default so the underlying\n // native `<textarea>` scrolls its own overflowing content (as a real\n // textarea does) instead of being clipped. `auto` shows a scrollbar per\n // axis only when that axis overflows — with the default soft wrap only the\n // vertical bar appears, once the text is taller than the box. The effective\n // default is honoured end-to-end: `applyStyle` writes it AND attaches the\n // eased wheel scroller from it (see Component.applyOverflowStyles), so no\n // constructor work is needed. Kept a class-level default, so an explicit\n // caller `overflow` still wins.\n overflow: \"auto\",\n padding: new Insets(3, 3, 3, 3),\n preferredSize: { width: 200, height: 200 },\n minSize: { width: 100, height: 100 },\n backgroundColor: \"var(--ts-ui-input-bg, rgb(255, 255, 255))\",\n foregroundColor: \"var(--ts-ui-text-color, black)\",\n};\n\n/**\n * A multi-line text area component backed by a `<textarea>` element.\n *\n * Keeps internal text state in sync with the DOM on every input event.\n *\n * @category Components\n */\nclass TextArea extends TextInput<TextAreaOptions> {\n\n constructor(text: string = \"\", options?: TextAreaOptions) {\n // Positional `text` is per-instance state, so it goes in the options bag\n // (dispatched through `setText` into `_options`), NOT `_defaultOptions`,\n // which holds class-level defaults the value getters never consult. An\n // explicit `options.text` still wins (spread last).\n super(\n text ? { text, ...options } : options,\n _defaultTextAreaOptions,\n );\n\n // The `<textarea>` corner grip is the only user-resize affordance on\n // any of these components. Pin `resize: none` as a persistent CSS rule\n // (not an inline style, which `applyStyle` would wipe on re-render) so\n // the area can never be drag-resized. There is no accompanying option\n // or setter — non-resizability is immutable by design.\n this.setElementCSSRules({ resize: \"none\" });\n }\n\n /**\n * Applies a {@link TextAreaOptions} bag, dispatching textarea-specific\n * `rows`, `cols`, and `wrap` attributes after inherited TextInput fields.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: TextAreaOptions): this {\n super.applyOptions(options);\n\n if (options.rows !== undefined) {\n this.setRows(options.rows);\n }\n\n if (options.cols !== undefined) {\n this.setCols(options.cols);\n }\n\n if (options.wrap !== undefined) {\n this.setWrap(options.wrap);\n }\n\n return this;\n }\n\n /**\n * Returns the configured `rows` attribute value, or null if not set.\n *\n * @returns The row count, or null.\n */\n getRows(): number | null {\n return this._options.rows ?? null;\n }\n\n /**\n * Sets the HTML `rows` attribute on the underlying textarea.\n *\n * @param value - The number of visible text rows.\n *\n * @returns This component, for method chaining.\n */\n setRows(value: number): this {\n this._options.rows = value;\n this.setElementAttribute(\"rows\", String(value));\n\n return this;\n }\n\n /**\n * Removes the HTML `rows` attribute from the underlying textarea.\n *\n * @returns This component, for method chaining.\n */\n clearRows(): this {\n if (this._options.rows === undefined) {\n return this;\n }\n\n this._options.rows = undefined;\n this.removeElementAttribute(\"rows\");\n\n return this;\n }\n\n /**\n * Returns the configured `cols` attribute value, or null if not set.\n *\n * @returns The column count, or null.\n */\n getCols(): number | null {\n return this._options.cols ?? null;\n }\n\n /**\n * Sets the HTML `cols` attribute on the underlying textarea.\n *\n * @param value - The visible width in average character widths.\n *\n * @returns This component, for method chaining.\n */\n setCols(value: number): this {\n this._options.cols = value;\n this.setElementAttribute(\"cols\", String(value));\n\n return this;\n }\n\n /**\n * Removes the HTML `cols` attribute from the underlying textarea.\n *\n * @returns This component, for method chaining.\n */\n clearCols(): this {\n if (this._options.cols === undefined) {\n return this;\n }\n\n this._options.cols = undefined;\n this.removeElementAttribute(\"cols\");\n\n return this;\n }\n\n /**\n * Returns the configured `wrap` attribute value, or null if not set.\n *\n * @returns The wrap mode, or null.\n */\n getWrap(): string | null {\n return this._options.wrap ?? null;\n }\n\n /**\n * Sets the HTML `wrap` attribute on the underlying textarea.\n *\n * @param value - The wrap mode (e.g. \"hard\", \"soft\", \"off\").\n *\n * @returns This component, for method chaining.\n */\n setWrap(value: string): this {\n this._options.wrap = value;\n this.setElementAttribute(\"wrap\", value);\n\n return this;\n }\n\n /**\n * Removes the HTML `wrap` attribute from the underlying textarea.\n *\n * @returns This component, for method chaining.\n */\n clearWrap(): this {\n if (this._options.wrap === undefined) {\n return this;\n }\n\n this._options.wrap = undefined;\n this.removeElementAttribute(\"wrap\");\n\n return this;\n }\n\n /**\n * Returns the baseline of the text area's **first** line so it aligns its\n * top line with sibling controls in a horizontal row; the box then extends\n * downward below the baseline.\n *\n * @returns The first-line baseline offset in pixels.\n *\n * @remarks The first-line baseline (not the box bottom) is used so a tall\n * text area does not dominate the row's ascent and drag every sibling down\n * by its full height. This stays safe for surrounding graphics only because\n * they expose their own (bottom-edge) baselines — a `null`-baseline sibling\n * would be centred against this area's inflated descent and float to the\n * row's middle.\n */\n getBaseline(): number | null {\n return this.wrapInnerBaseline(Util.measureTextBaseline());\n }\n\n protected init(element?: Handle): this {\n super.init(element);\n\n const el = element || this.getElement()!;\n\n if (this._options.rows !== undefined) {\n DOM.sink.apply(el, { setAttr: { rows: String(this._options.rows) } });\n }\n\n if (this._options.cols !== undefined) {\n DOM.sink.apply(el, { setAttr: { cols: String(this._options.cols) } });\n }\n\n if (this._options.wrap !== undefined) {\n DOM.sink.apply(el, { setAttr: { wrap: this._options.wrap } });\n }\n\n return this;\n }\n\n}\n\nconst TextAreaCallable = callable(TextArea);\ntype TextAreaCallable = TextArea;\nexport {\n TextArea as _TextArea,\n TextAreaCallable as TextArea\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Animation } from \"~/core/Animation.js\";\nimport { AbstractBooleanInput, AbstractBooleanInputOptions } from \"~/component/input/AbstractBooleanInput.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link Toggle}.\n *\n * @category Components\n */\nexport interface ToggleOptions extends AbstractBooleanInputOptions {\n value?: boolean;\n}\n\n/**\n * A custom-drawn on/off switch widget rendered as a focusable `<div>` with\n * `role=\"switch\"`.\n *\n * `Toggle` is not the same widget as\n * [`ToggleButton`](/api/component/button/classes/ToggleButton). `ToggleButton`\n * is a push-button that stays pressed; `Toggle` is the sliding-pill switch\n * pattern (System Settings, iOS-style). When toggled, the thumb slides via a\n * CSS transition; the slide is suppressed when the OS preference\n * `prefers-reduced-motion` is set.\n *\n * @category Components\n */\nclass Toggle<TOptions extends ToggleOptions = ToggleOptions>\n extends AbstractBooleanInput<TOptions>\n{\n private _track: Component;\n private _thumb: Component;\n\n /**\n * Constructs a Toggle.\n *\n * @param options - Optional construction-time options.\n */\n constructor(options?: TOptions) {\n super({ ...(options ?? {}) } as TOptions);\n\n this.setLayoutManager(new HBox());\n\n this._track = new Component();\n this._track.setBackgroundColor(\"var(--ts-ui-toggle-track-bg-off, rgb(200, 200, 200))\");\n this._track.setBorderRadius(\"999px\");\n this._track.setPreferredSize(36, 20);\n // Min = preferred = max so the outer HBox shrink-on-overallocation\n // can't collapse the pill when the Toggle is packed alongside\n // flexible siblings.\n this._track.setMinSize(36, 20);\n this._track.setMaxSize(36, 20);\n // The track owns the click + cursor surface so the pointer/click area\n // matches the visible pill exactly. The root stays inert (default\n // cursor, no click listener), so clicks on the label or in any\n // stretched empty space don't toggle and don't show the pointer cursor.\n this._track.setCursor(\"pointer\");\n\n this._thumb = new Component();\n this._thumb.setBackgroundColor(\"var(--ts-ui-toggle-thumb-bg, rgb(255, 255, 255))\");\n this._thumb.setBorderRadius(\"999px\");\n this._thumb.setPreferredSize(16, 16);\n this._thumb.setMaxSize(16, 16);\n // The track's default Absolute layout never sizes its children, so the\n // thumb collapses to 0 × 0 unless we set the rendered size explicitly.\n this._thumb.setSize({ width: 16, height: 16 });\n this._thumb.setShadow(\"0 1px 2px rgba(0, 0, 0, 0.25)\");\n this._thumb.setX(2);\n this._thumb.setY(2);\n // Pass-through so clicks on the thumb still hit the track underneath.\n this._thumb.setPointerEvents(\"none\");\n\n if (!Animation.isReducedMotion()) {\n this._thumb.setTransition(\"transform 120ms ease-out\");\n this._track.setTransition(\"background-color 120ms ease-out\");\n }\n\n this._track.addComponent(this._thumb);\n super.addComponent(this._track);\n\n this.getAria().setRole(\"switch\");\n this.getAria().setTabIndex(0);\n this.getAria().setChecked(false);\n\n this.setOutline(\"none\");\n\n // The track owns the user-toggle click so the pointer/click + cursor\n // surface is exactly the visible pill — clicks on a label or in any\n // stretched empty area pass through to the root, which has no listener\n // of its own. This pointer line stays per-subclass (a closure over the\n // widget `this`) because a listener registered on the child track would\n // otherwise bind `this` to the track; only the keyboard path, registered\n // on the root, moves into the base.\n Event.addListener(this._track, \"click\", () => this.activateFromPointer());\n this.installKeyboard();\n\n // Late-built dispatch: applyOptions buffered these onto _options because\n // children didn't exist yet. Run their setters now.\n if (this._options.value !== undefined) {\n this.applyValue(this._options.value);\n }\n\n if (this._options.label !== undefined) {\n this.applyLabel(this._options.label);\n }\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n }\n\n /**\n * Applies a {@link ToggleOptions} bag. Inherited Component fields cascade\n * through `super.applyOptions`; toggle-specific fields are stored pure on\n * `_options` so the constructor body can dispatch them after children are\n * built.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This component, for method chaining.\n */\n protected applyOptions(options: TOptions): this {\n super.applyOptions(options);\n\n if (options.value !== undefined) this._options.value = options.value;\n if (options.label !== undefined) this._options.label = options.label;\n if (options.enabled !== undefined) this._options.enabled = options.enabled;\n if (options.readOnly !== undefined) this._options.readOnly = options.readOnly;\n\n return this;\n }\n\n /**\n * Activates the toggle from a click or key by flipping the on/off state.\n * The enabled/read-only guard is applied by the base before this runs.\n */\n protected activate(): void {\n this.setValue(!this.getValue());\n }\n\n /**\n * Returns the inner track graphic — the click + cursor surface.\n *\n * @returns The track component.\n */\n protected getInteractiveSurface(): Component {\n return this._track;\n }\n\n /**\n * The toggle activates on Space and Enter (the switch idiom), widening the\n * base default of Space only.\n *\n * @returns The activation key strings.\n */\n protected activationKeys(): string[] {\n return [\" \", \"Enter\"];\n }\n\n /**\n * Returns the current on/off state.\n *\n * @returns `true` when on, `false` when off.\n */\n getValue(): boolean {\n return this._options.value ?? false;\n }\n\n /**\n * Sets the on/off state. Notifies change and binding listeners on a real\n * transition; no-op when the value is unchanged.\n *\n * @param value - The new on/off state.\n *\n * @returns This component, for method chaining.\n */\n setValue(value: boolean): this {\n const next = !!value;\n if (next === this.getValue()) {\n return this;\n }\n\n this._options.value = next;\n this.applyValue(next);\n this.notifyChange(next);\n\n return this;\n }\n\n /**\n * Resets the value to `false`.\n *\n * @returns This component, for method chaining.\n */\n clearValue(): this {\n return this.setValue(false);\n }\n\n /**\n * Returns the offset from the top of the toggle to the inline label's text\n * baseline, or `null` when there is no label (HBox falls back to bottom-edge\n * alignment).\n *\n * @returns The baseline offset in pixels, or `null`.\n *\n * @remarks The reported baseline includes the offset that centres the label\n * on the pill because `doLayout` nudges the label down to sit concentric with the taller pill;\n * the outer row must align the label at that lowered position so its text\n * baseline still meets the row's.\n */\n getBaseline(): number | null {\n const label = this._label;\n if (label === null) {\n return null;\n }\n\n const baseline = label.getBaseline();\n if (baseline === null) {\n return null;\n }\n\n return this.wrapInnerBaseline(baseline + this.labelCenterOffset());\n }\n\n /**\n * Pixels to push the inline label down so it sits centred on the pill.\n *\n * The 36×20 pill is taller than the text line, but the inner `HBox`\n * top-aligns the shorter label against the row's text baseline — leaving the\n * pill bottom-heavy, its extra height hanging entirely below the text. A\n * same-height control (the 16×16 checkbox box) needs no correction because\n * top-aligning it already centres it on the line; the taller pill does.\n * Centring the label on the pill (and folding this into {@link getBaseline})\n * makes the pill sit concentric with the text line instead.\n *\n * @returns Pixels to push the label down, or 0 when there is no label or it\n * is at least as tall as the pill.\n */\n private labelCenterOffset(): number {\n if (this._label === null) {\n return 0;\n }\n\n const trackHeight = this._track.getPreferredSize()?.height ?? 0;\n const labelHeight = this._label.getPreferredSize()?.height ?? trackHeight;\n\n return Math.max(0, Math.round((trackHeight - labelHeight) / 2));\n }\n\n /**\n * Lays out the pill and label, then nudges the label down so it sits\n * concentric with the taller pill. The\n * inner `HBox` top-aligns the label to the row's text baseline on every\n * pass, so re-reading its placed `y` here keeps the nudge idempotent.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const label = this._label;\n const offset = this.labelCenterOffset();\n if (label !== null && offset > 0) {\n label.setY(label.getY() + offset);\n }\n\n return this;\n }\n\n /**\n * Pushes a new value to the visual + ARIA state.\n */\n private applyValue(value: boolean): void {\n this.getAria().setChecked(value);\n\n // Thumb travel: track width 36, thumb size 16, 2px inset on each side =>\n // off at x=2, on at x=36-16-2=18 => translate 16px to the right.\n this._thumb.setTransform(value ? \"translateX(16px)\" : \"translateX(0px)\");\n\n this._track.setBackgroundColor(value\n ? \"var(--ts-ui-toggle-track-bg-on, rgb(30, 100, 200))\"\n : \"var(--ts-ui-toggle-track-bg-off, rgb(200, 200, 200))\");\n }\n\n}\n\nconst ToggleCallable = callable(Toggle);\ntype ToggleCallable<TOptions extends ToggleOptions = ToggleOptions> = Toggle<TOptions>;\nexport {\n Toggle as _Toggle,\n ToggleCallable as Toggle\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AnimatedDropdown, AnimatedDropdownOptions } from \"~/core/AnimatedDropdown.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { List } from \"~/component/list/List.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/** Pixel height of a single row inside the dropdown. Matches `SelectableListRow`'s cached `preferredSize(0, 22)`. */\nconst AUTOCOMPLETE_DROPDOWN_ROW_HEIGHT_PX = 22;\n\n/**\n * Construction-time options for {@link AutoCompleteDropdown}.\n *\n * @category Components\n */\ninterface AutoCompleteDropdownOptions extends AnimatedDropdownOptions {}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins.\n */\nconst _defaultAutoCompleteDropdownOptions: Partial<AutoCompleteDropdownOptions> = {\n durationMs: 100,\n backgroundColor: \"var(--ts-ui-autocomplete-bg, rgb(255, 255, 255))\",\n border: \"var(--ts-ui-input-border)\",\n borderRadius: \"var(--ts-ui-border-radius, 4px)\",\n shadow: \"var(--ts-ui-autocomplete-shadow, 2px 4px 8px rgba(0,0,0,0.15))\",\n};\n\n// The dropdown wrapper carries the visible chrome (border / radius /\n// shadow); the embedded List inherits a `:focus::after` ring from\n// `AbstractSelectableList`, which would paint a second ring inside the\n// dropdown if any code path programmatically focused the list. The\n// host-forwarded keystroke pattern keeps DOM focus on the TextField\n// throughout the dropdown's lifetime, but the suppression is\n// belt-and-braces — a future code path that does focus the list won't\n// double-stack the chrome.\n(() => {\n new StyleRule({\n scope: \"selector\",\n name: \".AutoCompleteDropdown .List:focus::after\",\n styles: {\n content: \"none\",\n },\n });\n})();\n\n/**\n * Floating dropdown panel for [`AutoCompleteField`](/api/component/input/classes/AutoCompleteField).\n *\n * Hosts a single [`List`](/api/component/list/classes/List) instance that\n * owns the row pool, the click-commit gesture, the keyboard reducer, the\n * type-ahead buffer, and the ARIA `option`-role wiring. The dropdown\n * wrapper handles only the overlay lifecycle (fade, anchored positioning,\n * viewport click-outside dismiss). Selection-follows-focus is disabled on\n * the inner list so ArrowUp/Down moves the focus highlight without\n * committing the row as the TextField's value; Enter / click still commit\n * through the list's `change` event.\n */\nclass AutoCompleteDropdown extends AnimatedDropdown<AutoCompleteDropdownOptions> {\n\n private readonly _list: List;\n private readonly _onSelect: (value: string) => void;\n private readonly _onHide: () => void;\n\n /**\n * @param onSelect - Called with the selected suggestion string when the user picks an item.\n * @param onHide - Called whenever the dropdown hides, including via viewport click-outside.\n * @param options - Optional construction-time options.\n */\n constructor(onSelect: (value: string) => void, onHide: () => void, options?: AutoCompleteDropdownOptions) {\n super(options, _defaultAutoCompleteDropdownOptions);\n\n this._onSelect = onSelect;\n this._onHide = onHide;\n\n // The inner List already exposes `role=\"listbox\"` from\n // `AbstractSelectableList`; the dropdown wrapper just provides the\n // overlay chrome and must not duplicate the listbox role\n // (nested listboxes break assistive-tech enumeration of the\n // `option` rows).\n this.setContain(\"layout\");\n this.setLayoutManager(new Fit());\n\n // `Fit` makes the inner list fill the dropdown's content box. The\n // list's own border is stripped — the dropdown root carries the\n // visible chrome (border + radius + shadow) so the two never\n // double-stack. Focus-on-row-click is disabled because the host\n // TextField keeps DOM focus throughout the dropdown's lifetime;\n // letting the list grab focus on a row click would blur the\n // TextField and tear down the autocomplete session before\n // `_onSelect` runs. Selection-follows-focus is disabled so\n // ArrowUp/Down previews a row without writing it into the\n // TextField — Enter / click still commit through `change`.\n this._list = new List();\n this._list.setBorder(\"none\");\n this._list.setBorderRadius(\"0\");\n this._list.setFocusOnRowClick(false);\n this._list.setSelectFollowsFocus(false);\n this.addComponent(this._list);\n\n // Click and keyboard commits both arrive through the list's\n // `change` event (fired by `notifyUserChange` after the click /\n // keyboard reducer mutates the selection set). Programmatic\n // writes (`setItemsArray`) bypass this path, so re-opening the\n // dropdown doesn't trigger a spurious commit.\n this._list.on(\"action\", () => {\n const value = this._list.getValue();\n\n if (value) {\n this._onSelect(value);\n }\n });\n }\n\n /**\n * Shows the dropdown anchored relative to `anchorEl`, rendering the\n * given suggestions. Key + label are identical so the list's\n * `getValue()` returns the picked suggestion text directly.\n *\n * @param anchorEl - The input element to anchor the dropdown to.\n * @param suggestions - The list of suggestion strings to display.\n *\n * @returns This dropdown, for method chaining.\n */\n show(anchorEl: Handle, suggestions: string[]): this {\n // Force the floating element into existence before any layout pass.\n // showAnimated() below mounts it, but that runs after doLayout() —\n // on first show getInnerSize() would otherwise return null and the\n // inner list's Fit layout would have nothing to fill.\n this.getElement(true);\n\n this.pauseLayout();\n this._list.setItemsArray(suggestions.map(s => ({ key: s, label: s })));\n this.resumeLayout();\n\n const perim = this.getPerimeterSize();\n const chromeH = perim.top + perim.bottom;\n const rect = DOM.source.getElementRect(anchorEl);\n\n this.setWidth(rect.width);\n this.setHeight(suggestions.length * AUTOCOMPLETE_DROPDOWN_ROW_HEIGHT_PX + chromeH);\n\n this.placeAnchored(rect);\n\n // Exclude the anchored input from the manager's outside-click test so\n // the keystroke that re-triggers the suggestions doesn't dismiss it.\n this.setAnchorElement(anchorEl);\n\n this.showAnimated();\n\n // VBox-backed list positions rows via framework setters that no-op\n // while the dropdown element is detached. Run the layout pass\n // after `showAnimated` mounts the panel so rows land at the\n // correct y offsets on first open.\n this.doLayout();\n\n return this;\n }\n\n /**\n * Hides the dropdown, detaches it from the DOM, and fires the `onHide`\n * callback (via {@link onHideComplete}). Outside-click dismissal is now\n * driven by [`LayerManager`](/api/core/namespaces/LayerManager)'s\n * `\"click-outside\"` mode rather than a per-dropdown viewport listener.\n */\n hide(): this {\n this.hideAnimated();\n\n return this;\n }\n\n /**\n * Forwards a keystroke from the host\n * [`AutoCompleteField`](/api/component/input/classes/AutoCompleteField)\n * (which keeps DOM focus on its TextField while the dropdown is\n * open) into the inner list's keyboard reducer. Mirrors\n * [`AnimatedDropdown.handleKey`](/api/core/classes/AnimatedDropdown#handlekey).\n * Returns `true` when the list consumed the key so the host can\n * `preventDefault` and stop further processing.\n *\n * @param e - The keyboard event captured by the host.\n *\n * @returns `true` when the list consumed the key.\n */\n handleKey(e: KeyboardEvent): boolean {\n return this._list.handleKey(e);\n }\n\n /**\n * Returns the framework-generated DOM element id of the inner list's\n * keyboard-focus row, suitable for writing into the host TextField's\n * `aria-activedescendant`. Returns `null` when no row holds focus.\n *\n * @returns The focused row's element id, or `null`.\n */\n getFocusedRowId(): string | null {\n return this._list.getFocusedRowId();\n }\n\n /**\n * Returns the inner [`List`](/api/component/list/classes/List). Test\n * seam — production code should not reach past the dropdown's\n * documented surface.\n *\n * @returns The hosted list instance.\n */\n getList(): List {\n return this._list;\n }\n\n /**\n * Fires the `onHide` callback once the exit fade has completed and the\n * panel is detached from the DOM.\n */\n protected onHideComplete(): void {\n this._onHide();\n }\n}\n\nconst AutoCompleteDropdownCallable = callable(AutoCompleteDropdown);\ntype AutoCompleteDropdownCallable = AutoCompleteDropdown;\nexport {\n AutoCompleteDropdown as _AutoCompleteDropdown,\n AutoCompleteDropdownCallable as AutoCompleteDropdown\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput, AbstractInputOptions } from \"~/component/input/AbstractInput.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport { AbstractStore } from \"~/data/AbstractStore.js\";\nimport { TextField } from \"~/component/input/TextField.js\";\nimport { AutoCompleteDropdown } from \"~/component/input/AutoCompleteDropdown.js\";\nimport { registerFocusWithinRing } from \"~/component/input/focusRing.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Focus ring highlighting the composite root whenever the inner TextField is\n// focused (the helper appends the focus pseudo-element).\nregisterFocusWithinRing(\".AutoCompleteField\");\n\n/**\n * Controls how typed input is matched against suggestion strings.\n *\n * - `'contains'` — substring match, case-insensitive (default).\n * - `'startsWith'` — prefix match, case-insensitive.\n * - `'containsCaseSensitive'` — substring match, case-sensitive.\n * - `'startsWithCaseSensitive'` — prefix match, case-sensitive.\n *\n * @category Components\n */\nexport type AutoCompleteMatchMode =\n | 'contains'\n | 'startsWith'\n | 'containsCaseSensitive'\n | 'startsWithCaseSensitive';\n\n/**\n * Construction-time options for {@link AutoCompleteField}.\n *\n * @category Components\n */\nexport interface AutoCompleteFieldOptions extends AbstractInputOptions {\n /** Static list of suggestion strings. */\n suggestions? : string[];\n /** Data store used when suggestions come from a remote/in-memory store. */\n store? : AbstractStore;\n /** The store field name whose value is shown as the suggestion text. Required when `store` is set. */\n displayField? : string;\n /** Minimum number of characters typed before suggestions are queried. Default: 1. */\n minChars? : number;\n /** Debounce delay in milliseconds before querying on each keystroke. Default: 200. */\n debounceMs? : number;\n /** Maximum number of suggestions to show at once. Default: 10. */\n maxSuggestions? : number;\n /** Placeholder text shown in the input when empty. */\n placeholder? : string;\n /** How the typed query is matched against suggestions. Default: `'contains'` (case-insensitive). */\n matchMode? : AutoCompleteMatchMode;\n}\n\n/**\n * @deprecated Use {@link AutoCompleteFieldOptions}.\n */\nexport type AutoCompleteFieldConfig = AutoCompleteFieldOptions;\n\n/**\n * String-literal event emitted by {@link AutoCompleteField} on top of the\n * inherited `change` / `binding`: `\"select\"` fires when the user picks a\n * suggestion from the dropdown.\n *\n * @category Components\n */\nexport type AutoCompleteFieldEvent = \"select\";\n\n/**\n * A typeahead/autocomplete text field.\n *\n * Wraps a [`TextField`](/api/component/input/classes/TextField) and an `AutoCompleteDropdown`. Suggestions may come\n * from a static string array or from an [`AbstractStore`](/api/data/classes/AbstractStore). Implements `Bindable<string>`\n * for use with the [`Binding`](/api/core/classes/Binding) system.\n *\n * @example\n * ```typescript\n * const field = new AutoCompleteField({\n * suggestions: ['Apple', 'Banana', 'Cherry'],\n * placeholder: 'Type a fruit…',\n * });\n * panel.addComponent(field);\n * field.addSelectListener(value => console.log('Selected:', value));\n * ```\n *\n * @category Components\n */\nclass AutoCompleteField extends AbstractInput<string, AutoCompleteFieldOptions> {\n\n private _textField : TextField;\n private _dropdown : AutoCompleteDropdown;\n private _debounceTimer : ReturnType<typeof setTimeout> | null = null;\n private _selectBag : ListenerBag<AutoCompleteFieldEvent> = new ListenerBag<AutoCompleteFieldEvent>();\n\n /**\n * @param options - Optional construction-time options for suggestions, store, behaviour, and base Component styling.\n */\n constructor(options?: AutoCompleteFieldOptions) {\n // Children are built below, so consumer options can't safely cascade\n // through super(). Apply them manually at the end of the constructor.\n // eslint-disable-next-line local/forward-super-options\n super();\n\n // The composite owns the visible chrome — gray edge from the shared\n // `--ts-ui-input-border` token, matching `:focus-within` outline\n // wired up at module top. The inner TextField then strips its own\n // border + browser focus outline so the two never paint over.\n this.setBorder(\"var(--ts-ui-input-border)\");\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n\n this._textField = new TextField();\n this._textField.setBorder(\"none\");\n this._textField.setBorderRadius(\"0\");\n this._textField.setOutline(\"none\");\n\n this.addComponent(this._textField);\n\n this.syncSizeFromTextField();\n this.subscribeTheme(() => this.syncSizeFromTextField());\n\n this._dropdown = new AutoCompleteDropdown(\n value => this.onSuggestionSelected(value),\n () => this.onDropdownHidden(),\n );\n\n this._textField.getAria().setRole(\"combobox\");\n this._textField.getAria().setAutoComplete(\"list\");\n this._textField.getAria().setExpanded(false);\n this._textField.getAria().setControls(this._dropdown.getId());\n this._textField.getAria().setActiveDescendant(\"\");\n\n Event.addListener(this._textField, \"input\", () => this.onInput());\n Event.addListener(this._textField, \"keydown\", (e: KeyboardEvent) => this.onKeyDown(e));\n Event.addListener(this._textField, \"blur\", () => this.onBlur());\n // Bridge user-driven text changes from the inner TextField up into\n // AbstractInput's change / binding listener fan-out so consumers\n // attached via the inherited `on(\"change\", fn)` see every keystroke\n // and every suggestion-pick (suggestions hit setValue, which writes\n // through the TextField and re-fires its own change listeners).\n this._textField.on(\"change\", value => this.notifyChange(value));\n\n if (options) {\n this.applyOptions(options);\n }\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n }\n\n /**\n * Applies an {@link AutoCompleteFieldOptions} bag, dispatching each field\n * through its setter so side effects fire identically whether the value\n * came from the constructor or a later setter call.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: AutoCompleteFieldOptions): this {\n super.applyOptions(options);\n\n if (options.suggestions !== undefined) this.setSuggestions(options.suggestions);\n if (options.minChars !== undefined) this.setMinChars(options.minChars);\n if (options.debounceMs !== undefined) this.setDebounceMs(options.debounceMs);\n if (options.maxSuggestions !== undefined) this.setMaxSuggestions(options.maxSuggestions);\n if (options.matchMode !== undefined) this.setMatchMode(options.matchMode);\n if (options.placeholder !== undefined) this.setPlaceholder(options.placeholder);\n\n // store + displayField are paired; apply via setStore when both present,\n // otherwise route through the individual options bag fields so partial\n // configuration is preserved without firing a half-configured setStore.\n if (options.store !== undefined && options.displayField !== undefined) {\n this.setStore(options.store, options.displayField);\n } else {\n if (options.store !== undefined) this._options.store = options.store;\n if (options.displayField !== undefined) this._options.displayField = options.displayField;\n }\n\n return this;\n }\n\n /**\n * Mirrors the preferred, max, and min size from the inner [`TextField`](/api/component/input/classes/TextField) onto this component\n * so that parent layout managers can calculate the correct row height.\n *\n * Called at construction time and after each theme change.\n */\n private syncSizeFromTextField(): void {\n const pref = this._textField.getPreferredSize();\n const max = this._textField.getMaxSize();\n const min = this._textField.getMinSize();\n\n if (pref) {\n this.setPreferredSize(pref.width, pref.height);\n }\n\n if (max) {\n this.setMaxSize(max.width, max.height);\n }\n\n // Mirrors the inner field's min so the composite is non-squishable\n // like a bare TextField, not just visually preferred at one line.\n if (min) {\n this.setMinSize(min.width, min.height);\n }\n }\n\n /**\n * Returns the offset from the top of the autocomplete field to the inner text field's baseline.\n *\n * @returns The baseline offset in pixels, or `null` when the text field has no baseline.\n *\n * @remarks `doLayout` places the inner [`TextField`](/api/component/input/classes/TextField) at `(0, 0)` to fill this\n * component, so this baseline excludes `insets` (which are not used to\n * position the child) and only adds the component's own border.\n */\n getBaseline(): number | null {\n return this.wrapInnerBaseline(this._textField.getBaseline());\n }\n\n /**\n * Lays out the internal text field to fill the container exactly.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n this._textField.setX(0);\n this._textField.setY(0);\n this._textField.setWidth(this.getWidth());\n this._textField.setHeight(this.getHeight());\n\n return this;\n }\n\n // ── Bindable<string> ────────────────────────────────────────────────────\n\n /**\n * Sets the field value programmatically without firing binding or select\n * listeners. Forwards directly to the inner TextField so the inherited\n * `notifyChange` does not double-fire on this path; user-driven changes\n * fire through the constructor's `_textField.on(\"change\", fn)` bridge.\n *\n * @param value - The string value to display.\n */\n setValue(value: string): this {\n this._textField.setValue(value);\n\n return this;\n }\n\n /**\n * Returns the current text value of the field, read directly from the\n * inner TextField.\n *\n * @returns The current string value.\n */\n getValue(): string {\n return this._textField.getValue();\n }\n\n /**\n * Reflects the enabled flag by forwarding to the inner TextField.\n */\n protected applyEnabled(value: boolean): void {\n this._textField.setEnabled(value);\n }\n\n /**\n * Reflects the read-only flag by forwarding to the inner TextField.\n */\n protected applyReadOnly(value: boolean): void {\n this._textField.setReadOnly(value);\n }\n\n // ── Configuration ────────────────────────────────────────────────────────\n\n /**\n * Replaces the static suggestion list.\n *\n * @param suggestions - The new list of suggestion strings.\n */\n setSuggestions(suggestions: string[]): this {\n this._options.suggestions = suggestions;\n\n return this;\n }\n\n /**\n * Configures the field to query suggestions from a store.\n *\n * @param store - The data store to filter.\n * @param displayField - The field name on each record to use as the suggestion text.\n */\n setStore(store: AbstractStore, displayField: string): this {\n this._options.store = store;\n this._options.displayField = displayField;\n\n return this;\n }\n\n /**\n * Sets the minimum number of characters required before suggestions are shown.\n *\n * @param n - Minimum character count. Default is 1.\n */\n setMinChars(n: number): this {\n this._options.minChars = n;\n\n return this;\n }\n\n /**\n * Sets the debounce delay in milliseconds applied to each input event.\n *\n * @param ms - Delay in milliseconds. Default is 200.\n */\n setDebounceMs(ms: number): this {\n this._options.debounceMs = ms;\n\n return this;\n }\n\n /**\n * Sets the maximum number of suggestions to display at once.\n *\n * @param n - Maximum item count. Default is 10.\n */\n setMaxSuggestions(n: number): this {\n this._options.maxSuggestions = n;\n\n return this;\n }\n\n /**\n * Sets how typed input is matched against suggestion strings. The default\n * `'contains'` matches anywhere, case-insensitive. The `*CaseSensitive`\n * variants opt in to case-sensitive matching.\n *\n * @param mode - One of `'contains'`, `'startsWith'`, `'containsCaseSensitive'`, `'startsWithCaseSensitive'`.\n */\n setMatchMode(mode: AutoCompleteMatchMode): this {\n this._options.matchMode = mode;\n\n return this;\n }\n\n /**\n * Sets the placeholder text shown in the inner text field when empty.\n *\n * @param placeholder - The placeholder string.\n */\n setPlaceholder(placeholder: string): this {\n this._options.placeholder = placeholder;\n this._textField.setPlaceholder(placeholder);\n\n return this;\n }\n\n // ── Events ───────────────────────────────────────────────────────────────\n\n /**\n * Registers a listener for one of this field's events. `\"select\"` fires when\n * the user picks a suggestion from the dropdown (routed through this class's\n * own listener bag); `\"change\"` and `\"binding\"` are the inherited\n * {@link AbstractInput} value events.\n *\n * @param event - The event name.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This component, for method chaining.\n */\n on(event: \"select\", listener: (value: string) => void): this;\n on(event: \"change\", listener: (value: string) => void): this;\n on(event: \"binding\", listener: () => void): this;\n on(event: \"select\" | \"change\" | \"binding\", listener: Function): this {\n if (event === \"select\") {\n this._selectBag.add(\"select\", listener);\n\n return this;\n }\n\n return super.on(event as \"change\", listener as (value: string) => void);\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match the one passed to {@link on}.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This component, for method chaining.\n */\n off(event: \"select\" | \"change\" | \"binding\", listener: Function): this {\n if (event === \"select\") {\n this._selectBag.remove(\"select\", listener);\n\n return this;\n }\n\n return super.off(event, listener);\n }\n\n /**\n * Registers a listener called when the user selects a suggestion. Retained\n * for back-compat; a thin forwarder onto `on(\"select\", fn)`.\n *\n * @param fn - Callback that receives the selected string value.\n */\n addSelectListener(fn: (value: string) => void): void {\n this.on(\"select\", fn);\n }\n\n // ── Internal event handlers ──────────────────────────────────────────────\n\n /**\n * Handles the `input` event on the internal text field.\n *\n * Schedules a debounced query when the typed value meets the `minChars`\n * threshold. AbstractInput's change / binding listeners fire through\n * the constructor's `_textField.on(\"change\", fn)` bridge, so this\n * handler doesn't fan out to them itself.\n */\n private onInput(): void {\n if (this._debounceTimer !== null) {\n clearTimeout(this._debounceTimer);\n }\n\n const minChars = this._options.minChars ?? 1;\n const current = this.getValue();\n\n if (current.length < minChars) {\n this._dropdown.hide();\n\n return;\n }\n\n this._debounceTimer = setTimeout(\n () => this.querySuggestions(current),\n this._options.debounceMs ?? 200\n );\n }\n\n /**\n * Handles `keydown` events on the internal text field for dropdown navigation.\n *\n * While the dropdown is open, navigation / commit keys are forwarded\n * into the inner list's keyboard reducer via `dropdown.handleKey`.\n * The forward is gated by an allow-list (ArrowDown / ArrowUp / Enter)\n * so printable characters fall through to the TextField's `input`\n * handler without entering the list's type-ahead buffer — otherwise\n * every keystroke would double-fire (type-ahead + `querySuggestions`).\n * Escape and Tab stay on this handler so the dropdown can close\n * without the list consuming them.\n *\n * @param e - The keyboard event from the text field.\n */\n private onKeyDown(e: KeyboardEvent): void {\n if (this._dropdown.isOpen()) {\n const forward = e.key === \"ArrowDown\"\n || e.key === \"ArrowUp\"\n || e.key === \"Enter\";\n\n if (forward && this._dropdown.handleKey(e)) {\n e.preventDefault();\n this.updateActiveDescendant();\n\n return;\n }\n }\n\n switch (e.key) {\n case \"ArrowDown\":\n // Dropdown was closed — fire the query.\n e.preventDefault();\n this.querySuggestions(this.getValue());\n break;\n\n case \"Escape\":\n this._dropdown.hide();\n this._textField.focus();\n break;\n\n case \"Tab\":\n this._dropdown.hide();\n break;\n\n default:\n break;\n }\n }\n\n /**\n * Hides the dropdown after a short delay when the field loses focus.\n *\n * The delay allows a click on a dropdown item to fire before the dropdown is hidden.\n */\n private onBlur(): void {\n setTimeout(() => {\n const active = DOM.source.getActiveElement();\n const dropEl = this._dropdown.getElement();\n\n if (dropEl && DOM.source.contains(dropEl, active)) {\n return;\n }\n\n this._dropdown.hide();\n }, 150);\n }\n\n /**\n * Returns true when `candidate` matches `query` according to the current\n * `matchMode`. Handles both axes: case-sensitivity via the `*CaseSensitive`\n * variants and position via the `startsWith*` variants.\n *\n * @param candidate - The raw suggestion string to test.\n * @param query - The raw query string.\n */\n private matches(candidate: string, query: string): boolean {\n const mode = this._options.matchMode ?? 'contains';\n\n const caseSensitive = mode === 'containsCaseSensitive'\n || mode === 'startsWithCaseSensitive';\n const startsWith = mode === 'startsWith'\n || mode === 'startsWithCaseSensitive';\n\n const haystack = caseSensitive ? candidate : candidate.toLowerCase();\n const needle = caseSensitive ? query : query.toLowerCase();\n\n return startsWith ? haystack.startsWith(needle) : haystack.includes(needle);\n }\n\n /**\n * Queries the suggestion source for the given input string and shows results.\n *\n * Stale results (where the query string no longer matches the current value)\n * are discarded.\n *\n * @param query - The string to filter suggestions by.\n */\n private querySuggestions(query: string): void {\n const maxSuggestions = this._options.maxSuggestions ?? 10;\n const matchMode = this._options.matchMode ?? 'contains';\n const suggestions = this._options.suggestions;\n const store = this._options.store;\n const displayField = this._options.displayField;\n\n if (suggestions !== undefined) {\n const filtered = suggestions\n .filter(s => this.matches(s, query))\n .slice(0, maxSuggestions);\n\n if (query === this.getValue()) {\n this.showSuggestions(filtered);\n }\n\n return;\n }\n\n if (store !== undefined && displayField !== undefined) {\n const caseSensitive = matchMode === 'containsCaseSensitive'\n || matchMode === 'startsWithCaseSensitive';\n const filterType = (matchMode === 'startsWith' || matchMode === 'startsWithCaseSensitive')\n ? 'startsWith'\n : 'contains';\n\n store.clearFilter();\n store.filterBy({\n type: filterType,\n field: displayField,\n value: query,\n caseSensitive,\n });\n\n const results = store.getRecords()\n .map(r => String(r.get(displayField)))\n .slice(0, maxSuggestions);\n\n if (query === this.getValue()) {\n this.showSuggestions(results);\n }\n }\n }\n\n /**\n * Shows the dropdown with the given list, or hides it if the list is empty.\n *\n * @param list - The suggestion strings to display.\n */\n private showSuggestions(list: string[]): void {\n if (list.length === 0) {\n this._dropdown.hide();\n\n return;\n }\n\n this._dropdown.show(this._textField.getElement(true)!, list);\n this._textField.getAria().setExpanded(true);\n }\n\n /**\n * Called by the dropdown's `onHide` callback whenever it closes, including\n * via the viewport click-outside handler.\n *\n * Resets the combobox ARIA state on the input.\n */\n private onDropdownHidden(): void {\n this._textField.getAria().setExpanded(false);\n this._textField.getAria().setActiveDescendant(\"\");\n }\n\n /**\n * Syncs `aria-activedescendant` on the input with the currently focused\n * row in the dropdown's inner list.\n */\n private updateActiveDescendant(): void {\n this._textField.getAria().setActiveDescendant(this._dropdown.getFocusedRowId() ?? \"\");\n }\n\n /**\n * Called when the user selects a suggestion from the dropdown.\n *\n * Updates the field value (which fires the inherited change listeners\n * through the constructor's TextField bridge), notifies the dedicated\n * select listeners, hides the dropdown, and returns focus to the text\n * field.\n *\n * @param value - The selected suggestion string.\n */\n private onSuggestionSelected(value: string): void {\n this.setValue(value);\n\n this._selectBag.fire(\"select\", value);\n\n this._dropdown.hide();\n this._textField.focus();\n }\n}\n\nconst AutoCompleteFieldCallable = callable(AutoCompleteField);\ntype AutoCompleteFieldCallable = AutoCompleteField;\nexport {\n AutoCompleteField as _AutoCompleteField,\n AutoCompleteFieldCallable as AutoCompleteField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Button, ButtonEvent, ButtonOptions, ClickListener } from \"~/component/button/Button.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { AutoRepeat } from \"~/core/AutoRepeat.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { chevron_up } from \"~/glyphs/solid/chevron_up.js\";\nimport { chevron_down } from \"~/glyphs/solid/chevron_down.js\";\n\n/**\n * String-literal union of the events emitted by {@link SpinButton}. Extends\n * the inherited `ButtonEvent` union with the spin-specific events.\n *\n * @category Components\n */\nexport type SpinButtonEvent = ButtonEvent | \"tick\";\n\nGlyph.register(chevron_up, chevron_down);\n\n/**\n * Construction-time options for {@link SpinButton}.\n *\n * @category Components\n */\nexport interface SpinButtonOptions extends ButtonOptions {\n /**\n * Multi-event listener bag dispatched to {@link SpinButton.on} at\n * construction time.\n */\n listeners?: {\n action?: ClickListener;\n tick?: () => void;\n };\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * Strips [`Button`](/api/component/button/classes/Button)'s chrome (border /\n * radius / insets) so the spinner sits flush in its NumberSpinner cell. The\n * `shadow`/`pressedShadow` clears can't ride here — they're `clear*` calls\n * with no equivalent option value — so they stay in the constructor body\n * guarded on the consumer options bag.\n */\nconst _defaultSpinButtonOptions: Partial<SpinButtonOptions> = {\n border: \"none\",\n borderRadius: \"0\",\n insets: new Insets(0, 0, 0, 0),\n};\n\n/**\n * A small up- or down-arrow button used inside a NumberSpinner.\n *\n * Extends Button to inherit the pressed-state appearance and click handling, then\n * adds a hold-repeat gesture: pressing and holding fires `tick` events at an\n * accelerating cadence (initial 400 ms, multiplied by 0.75 per tick, floored at 40 ms).\n *\n * @category Components\n */\nclass SpinButton extends Button<SpinButtonOptions> {\n\n private _listeners : ListenerBag<SpinButtonEvent> = new ListenerBag<SpinButtonEvent>();\n private _repeat : AutoRepeat;\n\n /**\n * @param symbol - The arrow rendered inside the button (`\"▲\"` or `\"▼\"`).\n * Mapped internally to the matching SVG glyph in the\n * framework's glyph registry.\n */\n constructor(symbol: \"▲\" | \"▼\", options?: SpinButtonOptions) {\n // Hand defaults plus the symbol-derived glyph to Button via the\n // subclass-defaults arg so they land in `_defaultOptions`. User\n // options still win because Component merges `{...defaults, ...options}`\n // at dispatch time.\n super(undefined, options, {\n ..._defaultSpinButtonOptions,\n glyph: symbol === \"▲\" ? \"chevron-up\" : \"chevron-down\",\n });\n\n this.updateSize();\n this.subscribeTheme(() => this.updateSize());\n\n // `clearShadow` / `clearPressedShadow` have no representable option\n // value (they write `box-shadow: none` and `_options.shadow = undefined`\n // — distinct from `setShadow(\"none\")` which stores the literal string),\n // so they stay in the body, guarded on the consumer bag.\n if (options?.shadow === undefined) {\n this.clearShadow();\n }\n if (options?.pressedShadow === undefined) {\n this.clearPressedShadow();\n }\n\n // Shrink the glyph so it fits the half-height (≈11 px) spin-button.\n // The 1 px upward translate compensates for sub-pixel rounding in the\n // Button's centring math: the measured input height is often odd,\n // making `(halfHeight - 8) / 2` a fractional value that the browser\n // resolves toward the bottom of the cell.\n // Pin the chevron so a theme change never re-tracks it to the title\n // line height; the 1px upward nudge corrects the centring rounding noted\n // above.\n this.pinGlyphSize(8);\n this.getGlyph()?.setTranslate(0, -1);\n\n this._repeat = new AutoRepeat({\n initialDelay: 400, // ms before the first hold-repeat tick\n decay: 0.75, // accelerate the cadence by ×0.75 each tick\n floor: 40, // fastest steady cadence, in ms\n onTick: () => this.emit(\"tick\"),\n });\n\n Event.addListener(this, \"mousedown\", () => this.onMouseDown());\n Event.addViewportListener(this, \"mouseup\", () => this.onMouseUp());\n Event.addViewportListener(this, \"mouseleave\", () => this.onMouseUp());\n\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Recalculates preferred and maximum size from the native input height divided in half.\n *\n * Called at construction time and after each theme change so font-size adjustments\n * propagate to the layout hint automatically.\n */\n private updateSize(): void {\n // Mirror the host NumberSpinner's computed outer box (its own\n // `updateHeight`): one `Util.lineHeightPx()` line box, plus the inner\n // TextField's default 3 px top + bottom padding, plus the spinner's\n // 1 px top + bottom border. SpinButton can't read the spinner it lives\n // in, so the inner padding (6) and spinner border (2) are stated here as\n // the host's known chrome.\n const INNER_INPUT_VERTICAL_PADDING = 6;\n const SPINNER_VERTICAL_BORDER = 2;\n\n const fullHeight = Util.lineHeightPx() + INNER_INPUT_VERTICAL_PADDING + SPINNER_VERTICAL_BORDER;\n // Subtract the host NumberSpinner's 1 px top + bottom border so two\n // stacked buttons fit inside the spinner's inner rect — without this,\n // an odd `fullHeight` produces `2 * floor(h/2) = h - 1` which exceeds\n // the inner height `h - 2` and clips the bottom button.\n const halfHeight = Math.floor((fullHeight - SPINNER_VERTICAL_BORDER) / 2);\n\n this.setPreferredSize(18, halfHeight);\n // Min = preferred = max so the parent's shrink-on-overallocation\n // doesn't collapse the chevron away when the spinner cell is\n // narrow.\n this.setMinSize(18, halfHeight);\n this.setMaxSize(18, halfHeight);\n }\n\n /**\n * Registers a listener for one of this spin button's events.\n *\n * @param event - `\"action\"` is the inherited DOM-routed click;\n * `\"tick\"` fires on each logical tick (initial click plus every\n * subsequent hold-repeat tick).\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This spin button, for method chaining.\n */\n on(event: \"action\", listener: ClickListener): this;\n on(event: \"tick\", listener: () => void): this;\n on(event: SpinButtonEvent, listener: Function): this {\n if (event === \"action\") {\n Event.addListener(this, \"click\", listener as ClickListener);\n } else {\n this._listeners.add(event, listener);\n }\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This spin button, for method chaining.\n */\n off(event: \"action\", listener: ClickListener): this;\n off(event: \"tick\", listener: () => void): this;\n off(event: SpinButtonEvent, listener: Function): this {\n if (event === \"action\") {\n Event.removeListener(this, \"click\", listener as ClickListener);\n } else {\n this._listeners.remove(event, listener);\n }\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event` (excluding `\"click\"`, which\n * routes through the framework's DOM event surface) with no arguments.\n *\n * @param event - The event to emit. Only non-DOM events (currently\n * `\"tick\"`) are dispatched through this method.\n */\n protected emit(event: \"tick\"): void;\n protected emit(event: \"tick\", ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n /**\n * Fires the first tick immediately and schedules subsequent accelerating ticks.\n */\n private onMouseDown(): void {\n this._repeat.start();\n }\n\n /**\n * Cancels the hold-repeat schedule when the pointer is released or leaves the viewport.\n */\n private onMouseUp(): void {\n if (!this._repeat.isRunning()) {\n return;\n }\n\n this._repeat.stop();\n }\n}\n\nconst SpinButtonCallable = callable(SpinButton);\ntype SpinButtonCallable = SpinButton;\nexport {\n SpinButton as _SpinButton,\n SpinButtonCallable as SpinButton\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput, AbstractInputOptions } from \"~/component/input/AbstractInput.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { TextField } from \"~/component/input/TextField.js\";\nimport { SpinButton } from \"~/component/input/SpinButton.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { UNBOUNDED } from \"~/primitive/Size.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { registerFocusWithinRing } from \"~/component/input/focusRing.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Focus indicator around the outer NumberSpinner so the ring sits at the\n// composite's chrome edge rather than around just the inner text column (the\n// helper appends the focus pseudo-element).\nregisterFocusWithinRing(\".NumberSpinner\");\n\n// Suppress the inner `TextField`'s own `.TextField:focus` box-shadow — without\n// this override, the inner shadow's right edge would paint a blue vertical\n// stripe between the text and the spin-button column (inside the NumberSpinner).\n(() => {\n new StyleRule({\n scope: \"selector\",\n name: \".NumberSpinner .TextField:focus\",\n styles: {\n boxShadow: \"none\",\n },\n });\n})();\n\n/**\n * Construction-time options for {@link NumberSpinner}.\n *\n * @category Components\n */\nexport interface NumberSpinnerOptions extends AbstractInputOptions {\n value?: number;\n min?: number;\n max?: number;\n step?: number;\n precision?: number | null;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins. `preferredSize` /\n * `maxSize` are *not* listed because `updateHeight` derives them from the\n * live measured input height (and re-fires on theme changes). `min`/`max`/\n * `step`/`precision`/`value`/`enabled` are late-built state — they touch\n * the inner `input`/`upBtn`/`downBtn` which don't exist yet — so they are\n * written pure by `applyOptions` and dispatched from the constructor body\n * once the children are built.\n */\nconst _defaultNumberSpinnerOptions: Partial<NumberSpinnerOptions> = {\n insets: new Insets(0, 0, 0, 0),\n border: \"1px solid var(--ts-ui-button-border, rgb(200, 200, 200))\",\n borderRadius: \"var(--ts-ui-border-radius, 4px)\",\n backgroundColor: \"var(--ts-ui-input-bg, rgb(255, 255, 255))\",\n};\n\n/**\n * A numeric input field with flanking up/down spin buttons.\n *\n * Combines a borderless [`TextField`](/api/component/input/classes/TextField) and a vertical strip of two [`SpinButton`](/api/component/input/classes/SpinButton)s\n * into a single bordered control. Supports min/max clamping, step-snapping,\n * configurable display precision, click and click-and-hold increment/decrement,\n * keyboard arrow keys, blur-on-invalid revert, and the framework `Bindable<number>`\n * interface.\n *\n * @category Components\n */\nclass NumberSpinner extends AbstractInput<number, NumberSpinnerOptions> {\n\n private _input! : TextField;\n private _upBtn! : SpinButton;\n private _downBtn!: SpinButton;\n private _btnBox! : Component;\n\n /**\n * Constructs a new NumberSpinner with default value `0`, step `1`, and unbounded min/max.\n */\n constructor(options?: NumberSpinnerOptions) {\n super(options, _defaultNumberSpinnerOptions);\n\n this._input = new TextField();\n this._input.setTextAlign(\"right\");\n this._input.setBorder(\"none\");\n this._input.setBorderRadius(\"0\");\n // Suppress the browser-default focus ring; the outer NumberSpinner's\n // `:focus-within::after` rule shows the framework focus indicator\n // instead. The matching `.NumberSpinner .TextField:focus` rule at\n // module top suppresses the inner box-shadow that would otherwise\n // paint a stripe between the text and the spin-button column.\n this._input.setOutline(\"none\");\n this._input.setText(this.formatValue(0));\n\n this._upBtn = new SpinButton(\"▲\");\n this._downBtn = new SpinButton(\"▼\");\n this._upBtn.setBorder({ borderTop: \"1px solid transparent\" });\n this._upBtn.setBorderRadius(\"0\");\n this._downBtn.setBorder({ borderTop: \"1px solid var(--ts-ui-spinner-divider, rgb(180, 180, 180))\" });\n this._downBtn.setBorderRadius(\"0\");\n\n this._btnBox = new Component();\n\n const vbox = new VBox();\n vbox.setComponentSpacing(0);\n this._btnBox.setLayoutManager(vbox);\n this._btnBox.setInsets(new Insets(0, 0, 0, 0));\n this._btnBox.addComponent(this._upBtn);\n this._btnBox.addComponent(this._downBtn);\n\n const hbox = new HBox();\n hbox.setComponentSpacing(0);\n hbox.setStretching(true);\n this.setLayoutManager(hbox);\n this.addComponent(this._input, { weight: 1 });\n this.addComponent(this._btnBox);\n\n this._upBtn.on(\"tick\", () => this.applyValue(this.getValue() + this.getStep()));\n this._downBtn.on(\"tick\", () => this.applyValue(this.getValue() - this.getStep()));\n\n Event.addListener(this._input, \"blur\", () => this.onBlur());\n Event.addListener(this._input, \"keydown\", (e: KeyboardEvent) => this.onKeyDown(e));\n\n this.updateHeight();\n this.subscribeTheme(() => this.updateHeight());\n\n this.getAria().setRole(\"spinbutton\");\n this.getAria().setValueNow(0);\n\n // Late-built state: `applyOptions` wrote these pure into `_options`\n // because the inner `input`/`upBtn`/`downBtn` didn't exist yet.\n // Dispatch now via the real setters so aria/text/disabled propagate.\n if (this._options.min !== undefined) this.setMin(this._options.min);\n if (this._options.max !== undefined) this.setMax(this._options.max);\n if (this._options.step !== undefined) this.setStep(this._options.step);\n if (this._options.precision !== undefined) this.setPrecision(this._options.precision);\n if (this._options.value !== undefined) this.setValue(this._options.value);\n if (this._options.enabled !== undefined) this.setEnabled(this._options.enabled);\n if (this._options.readOnly !== undefined) this.setReadOnly(this._options.readOnly);\n }\n\n /**\n * Applies a {@link NumberSpinnerOptions} bag. Inherited Component fields\n * cascade through `super.applyOptions`; the late-built fields\n * (`min`/`max`/`step`/`precision`/`value`/`enabled`, all of which touch\n * inner `input`/`upBtn`/`downBtn`) are written pure to `_options` here\n * and dispatched from the constructor body once children exist.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: NumberSpinnerOptions): this {\n super.applyOptions(options);\n\n if (options.min !== undefined) this._options.min = options.min;\n if (options.max !== undefined) this._options.max = options.max;\n if (options.step !== undefined) this._options.step = options.step;\n if (options.precision !== undefined) this._options.precision = options.precision;\n if (options.value !== undefined) this._options.value = options.value;\n if (options.enabled !== undefined) this._options.enabled = options.enabled;\n\n return this;\n }\n\n /**\n * Returns the offset from the top of the spinner to the inner input's text baseline.\n *\n * @returns The baseline offset in pixels, or `null` when the input has no baseline.\n */\n getBaseline(): number | null {\n return this.wrapInnerBaseline(this._input.getBaseline());\n }\n\n /**\n * Recalculates preferred and maximum height from the unified line box plus\n * the inner input's padding and the spinner's own border.\n *\n * @remarks The borderless inner `TextField` keeps its default vertical\n * padding, and its content must hold one `Util.lineHeightPx()` line box, so\n * the spinner's outer box is that inner-input box plus the spinner's own\n * vertical insets/padding/border. Reading the inner input's padding (rather\n * than the spinner's, which is zero) keeps the spinner the same height as a\n * standalone `TextField` in the same row.\n */\n private updateHeight(): void {\n // Reads the *inner* input's padding (not the spinner's own, which is\n // zero) so the spinner matches a standalone TextField's height.\n const h = Util.singleLineBoxHeight(this.getInsets(), this._input.getPadding(), this.getBorderSize());\n\n this.setPreferredSize(120, h);\n this.setMaxSize(UNBOUNDED, h);\n // Min-height pinned to the single-line box so the field can't be\n // vertically compressed below one line; min-width 0 keeps it\n // horizontally flexible.\n this.setMinSize(0, h);\n }\n\n /**\n * Returns the current numeric value.\n *\n * @returns The most recently committed value.\n */\n getValue(): number {\n return this._options.value ?? 0;\n }\n\n /**\n * Programmatically sets the value without firing change or binding listeners.\n *\n * @param n - The new value. It is clamped to [min, max] and snapped to the configured step.\n *\n * @remarks Used by the [`Bindable`](/api/core/interfaces/Bindable) interface; does not fire listeners so that\n * binding write-backs do not trigger feedback loops.\n */\n setValue(n: number): this {\n this._setValueSilent(n);\n\n return this;\n }\n\n /**\n * Returns the lower bound for the value.\n *\n * @returns The minimum allowed value (defaults to `-Infinity`).\n */\n getMin(): number {\n return this._options.min ?? -Infinity;\n }\n\n /**\n * Sets the lower bound for the value and updates the `aria-valuemin` attribute.\n *\n * @param n - The new minimum value. Pass `-Infinity` to remove the lower bound.\n */\n setMin(n: number): this {\n this._options.min = n;\n\n this.getAria().setValueMin(isFinite(n) ? n : null);\n\n return this;\n }\n\n /**\n * Returns the upper bound for the value.\n *\n * @returns The maximum allowed value (defaults to `Infinity`).\n */\n getMax(): number {\n return this._options.max ?? Infinity;\n }\n\n /**\n * Sets the upper bound for the value and updates the `aria-valuemax` attribute.\n *\n * @param n - The new maximum value. Pass `Infinity` to remove the upper bound.\n */\n setMax(n: number): this {\n this._options.max = n;\n\n this.getAria().setValueMax(isFinite(n) ? n : null);\n\n return this;\n }\n\n /**\n * Returns the increment/decrement step.\n *\n * @returns The current step (defaults to `1`).\n */\n getStep(): number {\n return this._options.step ?? 1;\n }\n\n /**\n * Sets the increment/decrement step.\n *\n * @param n - The new step value.\n */\n setStep(n: number): this {\n this._options.step = n;\n\n return this;\n }\n\n /**\n * Returns the explicit display precision in decimal places, or `null` if it is derived from the step.\n *\n * @returns The precision, or null if not explicitly set.\n */\n getPrecision(): number | null {\n return this._options.precision ?? null;\n }\n\n /**\n * Sets the display precision (number of decimal places to render). Pass `null` to derive from the step.\n *\n * @param decimals - The number of decimal places, or `null` to derive from `step`.\n */\n setPrecision(decimals: number | null): this {\n this._options.precision = decimals;\n\n this._input.setText(this.formatValue(this.getValue()));\n\n return this;\n }\n\n /**\n * Reflects the enabled flag: toggles the inner input's native `disabled`\n * attribute, suppresses pointer events on the spin buttons, and dims the\n * whole control.\n *\n * @param enabled - The new enabled state.\n */\n protected applyEnabled(enabled: boolean): void {\n if (enabled) {\n this._input.setDisabledAttribute(false);\n this._upBtn.setPointerEvents(\"auto\");\n this._downBtn.setPointerEvents(\"auto\");\n this.clearOpacity();\n } else {\n this._input.setDisabledAttribute(true);\n this._upBtn.setPointerEvents(\"none\");\n this._downBtn.setPointerEvents(\"none\");\n this.setOpacity(0.5);\n }\n }\n\n /**\n * Forwards the read-only flag to the inner text input. The spin buttons\n * intentionally stay live so a user can still adjust the value through\n * them while typing is suppressed.\n *\n * @param value - The new read-only state.\n */\n protected applyReadOnly(value: boolean): void {\n this._input.setReadOnly(value);\n }\n\n /**\n * Applies a user-driven value: clamps, snaps to step, formats, updates the DOM, and fires listeners.\n *\n * @param n - The proposed new value (raw, before clamping and snapping).\n */\n private applyValue(n: number): void {\n if (!this.isEnabled()) {\n return;\n }\n\n const next = this.normalize(n);\n if (next === this.getValue()) {\n this._input.setText(this.formatValue(next));\n\n return;\n }\n\n this._options.value = next;\n this._input.setText(this.formatValue(next));\n this.getAria().setValueNow(next);\n\n this.notifyChange(next);\n }\n\n /**\n * Programmatic value update path. Clamps, snaps, formats, and updates the DOM without firing listeners.\n *\n * @param n - The new value.\n */\n private _setValueSilent(n: number): void {\n const next = this.normalize(n);\n\n this._options.value = next;\n this._input.setText(this.formatValue(next));\n this.getAria().setValueNow(next);\n }\n\n /**\n * Clamps `n` to `[min, max]`, snaps to the nearest step multiple, then re-quantises to the display precision.\n *\n * @param n - The raw input value.\n *\n * @returns The normalised value ready to be stored.\n */\n private normalize(n: number): number {\n let v = Math.min(this.getMax(), Math.max(this.getMin(), n));\n v = Math.round(v / this.getStep()) * this.getStep();\n v = parseFloat(v.toFixed(this.derivePrecision()));\n\n return v;\n }\n\n /**\n * Reads the input field, parses the text, and either commits via `applyValue` or reverts on parse failure.\n */\n private onBlur(): void {\n const parsed = parseFloat(this._input.getText().valueOf());\n if (isNaN(parsed)) {\n this._input.setText(this.formatValue(this.getValue()));\n\n return;\n }\n\n this.applyValue(parsed);\n }\n\n /**\n * Handles ArrowUp/ArrowDown to step the value, and Enter to commit the current text.\n *\n * @param e - The keyboard event.\n */\n private onKeyDown(e: KeyboardEvent): void {\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n this.applyValue(this.getValue() + this.getStep());\n\n return;\n }\n\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n this.applyValue(this.getValue() - this.getStep());\n\n return;\n }\n\n if (e.key === \"Enter\") {\n this.onBlur();\n }\n }\n\n /**\n * Formats a numeric value to the configured display precision.\n *\n * @param n - The value to format.\n *\n * @returns The formatted string with the appropriate decimal places.\n */\n private formatValue(n: number): string {\n return n.toFixed(this.derivePrecision());\n }\n\n /**\n * Returns the explicit precision if one is set, otherwise infers it from the configured step.\n *\n * @returns The number of decimal places to render.\n */\n private derivePrecision(): number {\n const precision = this._options.precision ?? null;\n if (precision !== null) {\n return precision;\n }\n\n const stepStr = String(this.getStep());\n const dotIdx = stepStr.indexOf(\".\");\n\n return dotIdx >= 0 ? stepStr.length - dotIdx - 1 : 0;\n }\n}\n\nconst NumberSpinnerCallable = callable(NumberSpinner);\ntype NumberSpinnerCallable = NumberSpinner;\nexport {\n NumberSpinner as _NumberSpinner,\n NumberSpinnerCallable as NumberSpinner\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput, AbstractInputOptions } from \"~/component/input/AbstractInput.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Above this many selected files the label collapses to a \"N files\" summary\n// rather than joining every name. Empirically chosen: a handful of names still\n// reads as a useful list, but a long comma-joined run just overflows the field\n// and the count is the only legible signal past that point.\nconst FILENAME_JOIN_LIMIT = 3;\n\n// The empty-state label, shown whenever no file is selected.\nconst NO_FILE_LABEL = \"no file selected\";\n\n/**\n * Hidden native `<input type=\"file\">` owned by {@link FileField}. The native\n * control is the single source of truth for a *picked* `FileList`; this small\n * `Component` subclass owns the behavioural HTML attributes (`type`, `multiple`,\n * `accept`) through typed setters per [ARCHITECTURE.md](/ARCHITECTURE.md) so the\n * raw `setElementAttribute` seam never leaks to a call site.\n *\n * Not exported — purely an internal detail of `FileField`.\n */\nclass HiddenFileInput extends Component {\n\n // Cached native attributes, so reads return the canonical value and `init()`\n // can replay them once the element exists — `setElementAttribute` is a no-op\n // before render, so the cache is the single source of truth (same recipe as\n // TextInput's `_options.type` / `init()` replay). These live in private\n // backing fields rather than an options bag because this internal control is\n // not consumer-configurable.\n private _type: string | null = null;\n private _multiple: boolean = false;\n private _accept: string | null = null;\n\n constructor() {\n super({ tag: \"input\" });\n\n // `setDisplayed(false)`, not `setDisplay(\"none\")`: both render the element\n // `display: none`, but only the former marks the component undisplayed so\n // it drops out of `getLaidOutComponents`. Left displayed, this zero-content\n // input still claimed a layout slot in the field's HBox — and with no\n // preferred width it contributed `_defaultComponentWidth`, inflating the\n // row past the container and shrinking the button + filename label until\n // their text clipped. The native control still opens via `click()` while\n // `display: none`.\n this.setDisplayed(false);\n this.setType(\"file\");\n }\n\n /**\n * Returns the cached native `type`, or `null` before it is set.\n *\n * @returns The input type string, or `null`.\n */\n getType(): string | null {\n return this._type;\n }\n\n /**\n * Sets the native `type` attribute through the typed-setter seam, mirroring\n * [`TextInput.setType`](/api/component/input/classes/TextInput#settype) — the\n * value is cached and {@link init} replays it once the element exists.\n *\n * @param value - The input type (always `\"file\"` here).\n */\n setType(value: string): void {\n this._type = value;\n this.setElementAttribute(\"type\", value);\n }\n\n /**\n * Sets or clears the native `multiple` attribute. Cached for replay at\n * render time.\n *\n * @param value - `true` to allow multi-file selection.\n */\n setMultipleAttribute(value: boolean): void {\n this._multiple = value;\n\n if (value) {\n this.setElementAttribute(\"multiple\", \"\");\n } else {\n this.removeElementAttribute(\"multiple\");\n }\n }\n\n /**\n * Sets the native `accept` attribute (the OS picker's type filter). Cached\n * for replay at render time.\n *\n * @param value - A comma-separated `accept` token list, e.g. `\".csv,image/*\"`.\n */\n setAcceptAttribute(value: string): void {\n this._accept = value;\n this.setElementAttribute(\"accept\", value);\n }\n\n /**\n * Sets or clears the native `disabled` attribute.\n *\n * @param value - `true` to disable the control.\n */\n setDisabled(value: boolean): void {\n this.setDisabledAttribute(value);\n }\n\n /**\n * Opens the OS file picker by synthesising a click on the native input.\n */\n openPicker(): void {\n const el = this.getElement();\n\n if (el) {\n DOM.sink.click(el);\n }\n }\n\n /**\n * Returns the live `FileList` the native control currently holds, or `null`\n * before the element exists.\n *\n * @returns The native `FileList`, or `null`.\n */\n getFiles(): FileList | null {\n const el = this.getElement();\n\n return el ? DOM.source.getFiles(el) : null;\n }\n\n /**\n * Clears the native selection (`el.value = \"\"`). The browser only permits\n * programmatically *clearing* a file input, never populating it.\n */\n clearFiles(): void {\n const el = this.getElement();\n\n if (el) {\n DOM.sink.setValue(el, \"\");\n }\n }\n\n /**\n * Replays the cached behavioural attributes onto the freshly created element.\n * `setElementAttribute` is a no-op before the element exists (it does not\n * queue), so the constructor-time / constructor-tail writes must be\n * re-applied here — the same replay TextInput's `init()` performs.\n *\n * @param element - The element being initialised, when provided by the caller.\n *\n * @returns This component, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n const el = element ?? this.getElement();\n\n if (!el) {\n return this;\n }\n\n if (this._type !== null) {\n DOM.sink.apply(el, { setAttr: { type: this._type } });\n }\n\n if (this._multiple) {\n DOM.sink.apply(el, { setAttr: { multiple: \"\" } });\n }\n\n if (this._accept !== null) {\n DOM.sink.apply(el, { setAttr: { accept: this._accept } });\n }\n\n return this;\n }\n}\n\n/**\n * Construction-time options for {@link FileField}.\n *\n * @category Components\n */\nexport interface FileFieldOptions extends AbstractInputOptions {\n /** Allow selecting more than one file. Maps to the native `multiple` attribute. Default false. */\n multiple?: boolean;\n /** Native file-type filter, e.g. `\".sql,.csv\"` or `\"image/*\"`. Maps to the native `accept` attribute. */\n accept?: string;\n /** Trigger button label. Defaults to \"Choose file…\" / \"Choose files…\" per `multiple`. */\n buttonText?: string;\n}\n\n/**\n * A styled file-selection control: a trigger [`Button`](/api/component/button/classes/Button)\n * that opens the OS file picker, plus a filename label. Wraps a hidden native\n * `<input type=\"file\">` and implements [`Bindable<File[]>`](/api/core/interfaces/Bindable).\n *\n * The native input is the source of truth for a *picked* `FileList`; the field\n * reads it on the native `change` event and fans the new value out through\n * {@link AbstractInput}'s `change` / `binding` listeners. A separate cache holds\n * files handed in by a [`FileDropZone`](/api/component/input/classes/FileDropZone)\n * drop, which the browser cannot push back into the native control.\n *\n * @remarks **Write contract is clear-only.** The browser forbids assigning a\n * non-empty `FileList` to a file input outside a user gesture, so\n * {@link setValue} honours only the empty case (it clears the control). A\n * non-empty argument is a no-op plus one `console.warn`; it never throws, so a\n * record load through [`Binding`](/api/core/classes/Binding) stays\n * side-effect-tolerant. {@link getValue} always returns the live `File[]`, so\n * the read side of the binding contract is complete.\n *\n * @category Components\n */\nclass FileField<TOptions extends FileFieldOptions = FileFieldOptions>\n extends AbstractInput<File[], TOptions>\n{\n private _button: Button;\n private _label: Text;\n private _input: HiddenFileInput;\n\n // Files supplied by a FileDropZone drop. The browser cannot push these into\n // the native `<input>`, so they live here and supersede the (empty) native\n // FileList in `getValue`. Cleared and superseded by the next native `change`.\n private _droppedFiles: File[] = [];\n\n /**\n * Constructs a FileField.\n *\n * @param options - Optional construction-time options.\n *\n * @remarks The parameter is typed as the concrete {@link FileFieldOptions}\n * rather than the class's `TOptions` parameter so that passing an options\n * literal (e.g. `new FileField({ accept: \".png\" })`) cannot narrow `TOptions`\n * to that literal — which would type the instance as `FileField<{ accept:\n * \".png\" }>` and fail the weak-type assignability check when it is later used\n * as a base `Component` (e.g. `container.addComponent(field)`). `TOptions`\n * stays at its `FileFieldOptions` default.\n */\n constructor(options?: FileFieldOptions) {\n super({ ...(options ?? {}) } as TOptions);\n\n this.setLayoutManager(new HBox());\n\n this._button = new Button(this.defaultButtonText());\n this._button.on(\"action\", () => this.openPicker());\n\n this._label = new Text(NO_FILE_LABEL, { truncate: true });\n this._label.setForegroundColor(\"var(--ts-ui-form-text, inherit)\");\n\n this._input = new HiddenFileInput();\n Event.addListener(this._input, \"change\", () => this.onNativeChange());\n\n super.addComponent(this._button);\n // The label is the flexible cell: the button keeps its preferred width\n // while the label absorbs the remaining row width and truncates a long\n // filename, instead of both shrinking proportionally and clipping the\n // button's own text.\n super.addComponent(this._label, { weight: 1 });\n super.addComponent(this._input);\n\n if (this._options.multiple !== undefined) {\n this.setMultiple(this._options.multiple);\n }\n\n if (this._options.accept !== undefined) {\n this.setAccept(this._options.accept);\n }\n\n if (this._options.buttonText !== undefined) {\n this._button.setText(this._options.buttonText);\n }\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n }\n\n /**\n * Caches the {@link FileFieldOptions} fields on `_options` so the\n * constructor body can dispatch them once the children exist. The setters\n * are intentionally not dispatched here (the children aren't built yet\n * during `super()`), matching the [`AbstractInput`](/api/component/input/classes/AbstractInput)\n * cascade rule.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This component, for method chaining.\n */\n protected applyOptions(options: TOptions): this {\n super.applyOptions(options);\n\n if (options.multiple !== undefined) this._options.multiple = options.multiple;\n if (options.accept !== undefined) this._options.accept = options.accept;\n if (options.buttonText !== undefined) this._options.buttonText = options.buttonText;\n\n return this;\n }\n\n /**\n * The trigger label used when the caller supplies none, pluralised per the\n * `multiple` flag.\n *\n * @returns \"Choose files…\" when multi-select is on, else \"Choose file…\".\n */\n private defaultButtonText(): string {\n return this.isMultiple() ? \"Choose files…\" : \"Choose file…\";\n }\n\n /**\n * Opens the OS file picker. Bound as the trigger button's `action` handler.\n */\n private openPicker(): void {\n if (!this.isEnabled() || this.isReadOnly()) {\n return;\n }\n\n this._input.openPicker();\n }\n\n /**\n * Reads the native `FileList` on every native `change`, supersedes any stale\n * drop cache, relabels, and fans the new value out. Bound as the hidden\n * input's `change` handler.\n */\n private onNativeChange(): void {\n this._droppedFiles = [];\n\n const files = this.getValue();\n\n this.updateLabel(files);\n this.notifyChange(files);\n }\n\n /**\n * Stores a `FileList` dropped onto a [`FileDropZone`](/api/component/input/classes/FileDropZone)\n * as this field's live value, relabels, and notifies. The browser cannot\n * push these into the hidden `<input>`, so they are held in the drop cache\n * and returned by {@link getValue} until the next native `change`.\n *\n * Package-internal: called only by `FileDropZone` via the `_FileField`\n * export. Not part of the public consumer surface.\n *\n * @param files - The dropped `FileList`.\n */\n acceptDroppedFiles(files: FileList): void {\n this._droppedFiles = Array.from(files);\n\n const value = this.getValue();\n\n this.updateLabel(value);\n this.notifyChange(value);\n }\n\n /**\n * Returns the currently selected files: the drop cache when populated,\n * otherwise the live native `FileList`. Satisfies\n * [`Bindable`](/api/core/interfaces/Bindable)'s read side.\n *\n * @returns The selected files (possibly empty).\n */\n getValue(): File[] {\n if (this._droppedFiles.length > 0) {\n return this._droppedFiles;\n }\n\n const files = this._input.getFiles();\n\n return files ? Array.from(files) : [];\n }\n\n /**\n * Clears the selection when given an empty array; otherwise a no-op plus one\n * `console.warn`. See the class remarks for why the write contract is\n * clear-only (the browser security model forbids populating a file input).\n *\n * @param value - An empty array to clear; a non-empty array is rejected.\n *\n * @returns This component, for method chaining.\n */\n setValue(value: File[]): this {\n if (value && value.length > 0) {\n console.warn(\n \"FileField '\" + this.getId() + \"' setValue ignored a non-empty File[]: the browser \"\n + \"security model forbids programmatically populating a file input. Only setValue([]) \"\n + \"(clearing) is supported.\",\n );\n\n return this;\n }\n\n return this.clearValue();\n }\n\n /**\n * Clears the native selection and the drop cache, relabels, and notifies.\n * Convenience alias matching the\n * [`Toggle.clearValue()`](/api/component/input/classes/Toggle#clearvalue)\n * convention.\n *\n * @returns This component, for method chaining.\n */\n clearValue(): this {\n this._droppedFiles = [];\n this._input.clearFiles();\n\n const value = this.getValue();\n\n this.updateLabel(value);\n this.notifyChange(value);\n\n return this;\n }\n\n /**\n * Returns whether multi-file selection is enabled.\n *\n * @returns `true` when the native `multiple` attribute is set.\n */\n isMultiple(): boolean {\n return this._options.multiple ?? false;\n }\n\n /**\n * Toggles multi-file selection (the native `multiple` attribute). When the\n * caller never set an explicit button label, the default label re-pluralises\n * to match.\n *\n * @param value - `true` to allow selecting more than one file.\n *\n * @returns This component, for method chaining.\n */\n setMultiple(value: boolean): this {\n this._options.multiple = !!value;\n this._input.setMultipleAttribute(this._options.multiple);\n\n if (this._options.buttonText === undefined) {\n this._button.setText(this.defaultButtonText());\n }\n\n return this;\n }\n\n /**\n * Returns the native `accept` filter, or `null` when unset.\n *\n * @returns The `accept` token list, or `null`.\n */\n getAccept(): string | null {\n return this._options.accept ?? null;\n }\n\n /**\n * Sets the native `accept` attribute — the OS picker's type filter (it does\n * not validate drag-dropped files).\n *\n * @param value - A comma-separated `accept` token list, e.g. `\".csv,image/*\"`.\n *\n * @returns This component, for method chaining.\n */\n setAccept(value: string): this {\n this._options.accept = value;\n this._input.setAcceptAttribute(value);\n\n return this;\n }\n\n /**\n * Reflects the filename label for the current selection: the empty-state\n * text, the single file name, the joined names, or a \"N files\" summary past\n * {@link FILENAME_JOIN_LIMIT}.\n */\n private updateLabel(files: File[]): void {\n if (files.length === 0) {\n this._label.setText(NO_FILE_LABEL);\n } else if (files.length === 1) {\n this._label.setText(files[0].name);\n } else if (files.length > FILENAME_JOIN_LIMIT) {\n this._label.setText(files.length + \" files\");\n } else {\n this._label.setText(files.map(f => f.name).join(\", \"));\n }\n }\n\n /**\n * Reflects the enabled flag on the trigger button and the hidden input.\n */\n protected applyEnabled(value: boolean): void {\n this._button.setEnabled(value);\n this._input.setDisabled(!value);\n }\n\n /**\n * Reflects the read-only flag. A read-only field stays mounted but refuses\n * to open the picker (guarded in {@link openPicker}); the trigger button is\n * disabled so it reads as inert.\n */\n protected applyReadOnly(value: boolean): void {\n this._button.setEnabled(!value && this.isEnabled());\n this.getAria().setReadOnly(value);\n }\n}\n\nconst FileFieldCallable = callable(FileField);\ntype FileFieldCallable<TOptions extends FileFieldOptions = FileFieldOptions> = FileField<TOptions>;\nexport {\n FileField as _FileField,\n FileFieldCallable as FileField,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput } from \"~/component/input/AbstractInput.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { FileFieldOptions, _FileField } from \"~/component/input/FileField.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Resting and active border / background, driven by the per-control\n// `--ts-ui-filedropzone-*` tokens. Kept as module consts so `setActive` swaps\n// the whole pair atomically rather than re-deriving strings per drag event.\nconst RESTING_BORDER = \"var(--ts-ui-filedropzone-border, 2px dashed rgba(80, 140, 240, 0.40))\";\nconst RESTING_BG = \"var(--ts-ui-filedropzone-bg, rgba(80, 140, 240, 0.06))\";\nconst ACTIVE_BORDER = \"var(--ts-ui-filedropzone-active-border, 2px dashed rgba(80, 140, 240, 0.80))\";\nconst ACTIVE_BG = \"var(--ts-ui-filedropzone-active-bg, rgba(80, 140, 240, 0.18))\";\n\n// Default instructional text shown inside the zone when the caller supplies none.\nconst DEFAULT_PROMPT = \"Drop files here or click to browse\";\n\n/**\n * Construction-time options for {@link FileDropZone}.\n *\n * @category Components\n */\nexport interface FileDropZoneOptions extends FileFieldOptions {\n /** Instructional text shown inside the zone. Default \"Drop files here or click to browse\". */\n promptText?: string;\n}\n\n/**\n * A bordered drag-and-drop surface that *composes* a\n * [`FileField`](/api/component/input/classes/FileField) and additionally accepts\n * OS file drops through the HTML5 `DataTransfer.files` API. Implements\n * [`Bindable<File[]>`](/api/core/interfaces/Bindable) by delegating its value to\n * the inner field and re-emitting the field's `change` / `binding` events.\n *\n * @remarks OS file drops arrive as real HTML5 `dragover` / `drop` DOM events and\n * are wired through {@link Event.addSubtreeListener} — deliberately *not* the\n * framework's internal pointer-based component-drag system, which never sees a\n * `DataTransfer`. The native `accept` filter on the inner field constrains the\n * OS picker only; dropped files are not type-validated (out of scope). The same\n * clear-only write contract as `FileField` applies — see its remarks.\n *\n * @category Components\n */\nclass FileDropZone<TOptions extends FileDropZoneOptions = FileDropZoneOptions>\n extends AbstractInput<File[], TOptions>\n{\n private _prompt: Text;\n private _field: _FileField;\n\n // Nesting depth of active OS drags over this zone. `dragenter` / `dragleave`\n // fire per child element as the cursor crosses the composed field's button\n // and label, so a single boolean would strobe the highlight; counting\n // enter/leave pairs keeps the active state stable until the cursor truly\n // leaves the zone (depth returns to 0).\n private _dragDepth: number = 0;\n\n /**\n * Constructs a FileDropZone.\n *\n * @param options - Optional construction-time options.\n *\n * @remarks The parameter is typed as the concrete {@link FileDropZoneOptions}\n * rather than the class's `TOptions` parameter so that passing an options\n * literal (e.g. `new FileDropZone({ accept: \".png\" })`) cannot narrow\n * `TOptions` to that literal — which would type the instance as\n * `FileDropZone<{ accept: \".png\" }>` and fail the weak-type assignability\n * check when it is later used as a base `Component` (e.g.\n * `container.addComponent(zone)`). `TOptions` stays at its\n * `FileDropZoneOptions` default.\n */\n constructor(options?: FileDropZoneOptions) {\n super({ ...(options ?? {}) } as TOptions);\n\n this.setLayoutManager(new VBox());\n\n this._prompt = new Text(this._options.promptText ?? DEFAULT_PROMPT);\n this._prompt.setForegroundColor(\"var(--ts-ui-form-text, inherit)\");\n\n this._field = new _FileField(this.fieldOptions());\n this._field.on(\"change\", (value: File[]) => this.notifyChange(value));\n\n super.addComponent(this._prompt);\n super.addComponent(this._field);\n\n this.setActive(false);\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n\n Event.addSubtreeListener(this, \"dragenter\", (e: DragEvent) => this.onDragEnter(e));\n Event.addSubtreeListener(this, \"dragover\", (e: DragEvent) => this.onDragOver(e));\n Event.addSubtreeListener(this, \"dragleave\", (e: DragEvent) => this.onDragLeave(e));\n Event.addSubtreeListener(this, \"drop\", (e: DragEvent) => this.onDrop(e));\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n }\n\n /**\n * Caches the {@link FileDropZoneOptions} fields on `_options` so the\n * constructor body can read them after `super()`.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This component, for method chaining.\n */\n protected applyOptions(options: TOptions): this {\n super.applyOptions(options);\n\n if (options.promptText !== undefined) this._options.promptText = options.promptText;\n\n return this;\n }\n\n /**\n * Builds the option bag forwarded to the composed inner field — the\n * file-selection fields (`multiple` / `accept` / `buttonText`) pass through;\n * the zone-only `promptText` does not.\n *\n * @returns The inner [`FileField`](/api/component/input/classes/FileField) options.\n */\n private fieldOptions(): FileFieldOptions {\n return {\n multiple: this._options.multiple,\n accept: this._options.accept,\n buttonText: this._options.buttonText,\n };\n }\n\n /**\n * Swaps the resting / active border + background pair. Active is the wash\n * shown while a valid OS file drag hovers.\n *\n * @param active - `true` for the hovering-drag state, `false` for resting.\n */\n private setActive(active: boolean): void {\n this.setBorder(active ? ACTIVE_BORDER : RESTING_BORDER);\n this.setBackgroundColor(active ? ACTIVE_BG : RESTING_BG);\n }\n\n /**\n * Increments the drag-depth counter and lights the zone on the first enter.\n * Bound as the subtree `dragenter` handler.\n */\n private onDragEnter(e: DragEvent): void {\n e.preventDefault();\n this._dragDepth++;\n\n if (this._dragDepth === 1) {\n this.setActive(true);\n }\n }\n\n /**\n * Signals a valid drop target by calling `preventDefault`. The `FileList` is\n * only populated on `drop`, so nothing is read here. Bound as the subtree\n * `dragover` handler.\n */\n private onDragOver(e: DragEvent): void {\n e.preventDefault();\n }\n\n /**\n * Decrements the drag-depth counter and dims the zone once the cursor truly\n * leaves (depth back to 0). Bound as the subtree `dragleave` handler.\n */\n private onDragLeave(e: DragEvent): void {\n e.preventDefault();\n this._dragDepth = Math.max(0, this._dragDepth - 1);\n\n if (this._dragDepth === 0) {\n this.setActive(false);\n }\n }\n\n /**\n * Accepts the dropped `FileList`, resets the highlight, and hands the files\n * to the inner field so one code path owns formatting + notify. Bound as the\n * subtree `drop` handler.\n */\n private onDrop(e: DragEvent): void {\n e.preventDefault();\n this._dragDepth = 0;\n this.setActive(false);\n\n if (!this.isEnabled() || this.isReadOnly()) {\n return;\n }\n\n const files = e.dataTransfer?.files;\n\n if (!files || files.length === 0) {\n return;\n }\n\n this._field.acceptDroppedFiles(files);\n }\n\n /**\n * Returns the selected files, delegating to the inner field.\n *\n * @returns The selected files (possibly empty).\n */\n getValue(): File[] {\n return this._field.getValue();\n }\n\n /**\n * Delegates to the inner field's clear-only {@link _FileField.setValue}.\n *\n * @param value - An empty array to clear; a non-empty array is rejected.\n *\n * @returns This component, for method chaining.\n */\n setValue(value: File[]): this {\n this._field.setValue(value);\n\n return this;\n }\n\n /**\n * Clears the selection, delegating to the inner field's `clearValue`.\n *\n * @returns This component, for method chaining.\n */\n clearValue(): this {\n this._field.clearValue();\n\n return this;\n }\n\n /**\n * Reflects the enabled flag on the inner field.\n */\n protected applyEnabled(value: boolean): void {\n this._field.setEnabled(value);\n }\n\n /**\n * Reflects the read-only flag on the inner field.\n */\n protected applyReadOnly(value: boolean): void {\n this._field.setReadOnly(value);\n }\n}\n\nconst FileDropZoneCallable = callable(FileDropZone);\ntype FileDropZoneCallable<TOptions extends FileDropZoneOptions = FileDropZoneOptions> = FileDropZone<TOptions>;\nexport {\n FileDropZone as _FileDropZone,\n FileDropZoneCallable as FileDropZone,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"input.es.js","names":[],"sources":["../../../src/typescript/lib/component/input/DateField.ts","../../../src/typescript/lib/component/input/DateTimeField.ts","../../../src/typescript/lib/component/input/PasswordField.ts","../../../src/typescript/lib/component/input/UsernameField.ts","../../../src/typescript/lib/component/input/TextArea.ts","../../../src/typescript/lib/component/input/Toggle.ts","../../../src/typescript/lib/component/input/AutoCompleteDropdown.ts","../../../src/typescript/lib/component/input/AutoCompleteField.ts","../../../src/typescript/lib/component/input/SpinButton.ts","../../../src/typescript/lib/component/input/NumberSpinner.ts","../../../src/typescript/lib/component/input/FileField.ts","../../../src/typescript/lib/component/input/FileDropZone.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractPickerField, AbstractPickerFieldOptions } from \"~/component/input/AbstractPickerField.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { calendar } from \"~/glyphs/solid/calendar.js\";\nimport { DatePickerDropdown } from \"~/component/input/DatePickerDropdown.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(calendar);\n\n/**\n * Construction-time options for {@link DateField}.\n *\n * @category Components\n */\nexport interface DateFieldOptions extends AbstractPickerFieldOptions {\n value?: Date | null;\n /** Earliest date the picker will allow selection of. Optional. */\n minDate?: Date | null;\n /** Latest date the picker will allow selection of. Optional. */\n maxDate?: Date | null;\n}\n\n/**\n * A date-picker input component.\n *\n * Renders a text input with a calendar-glyph button on the right; clicking\n * either opens a [`DatePickerDropdown`](/api/component/input/classes/DatePickerDropdown)\n * panel that fades in via the shared\n * [`AnimatedDropdown`](/api/core/classes/AnimatedDropdown) lifecycle. The\n * native browser date-picker is no longer used.\n *\n * Inherits the [`Bindable`](/api/core/interfaces/Bindable) value contract,\n * change/binding listeners, and enabled/read-only surface from\n * [`AbstractPickerField`](/api/component/input/classes/AbstractPickerField).\n * Returns `null` from `getValue` when the field is empty.\n *\n * @category Components\n */\nclass DateField extends AbstractPickerField<Date, DatePickerDropdown, DateFieldOptions> {\n\n constructor(options?: DateFieldOptions) {\n super(options);\n\n // Button.setGlyph adds the glyph as the leading child of the\n // content row (with pointer-events: none) so the outer Fit centres\n // it within the button's inner rect.\n this._button.setGlyph(\"calendar\");\n\n // Late-built value: `applyOptions` cached it on `_options` at super-time;\n // re-apply now that `_input` exists so the inner text reflects it. The\n // enabled / readOnly re-dispatch is handled by the base constructor.\n if (this._options.value !== undefined) {\n this.setValue(this._options.value);\n }\n }\n\n /**\n * Applies a {@link DateFieldOptions} bag, caching the initial value pure\n * on `_options`. The constructor body dispatches the late-built fields\n * (value/enabled/readOnly) through their setters once `_input` exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DateFieldOptions): this {\n super.applyOptions(options);\n\n if (options.value !== undefined) {\n this._options.value = options.value;\n }\n\n if (options.minDate !== undefined) {\n this._options.minDate = options.minDate;\n }\n\n if (options.maxDate !== undefined) {\n this._options.maxDate = options.maxDate;\n }\n\n return this;\n }\n\n /**\n * Formats a Date as a YYYY-MM-DD string for display in the text input.\n *\n * @param date - The Date to format.\n * @returns A \"YYYY-MM-DD\" string.\n */\n protected formatValue(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n\n return `${y}-${m}-${d}`;\n }\n\n /**\n * Parses a \"YYYY-MM-DD\" string into a Date. Appends local midnight to\n * avoid UTC offset shifting the day.\n *\n * @param raw - The raw text typed into the input.\n * @returns The parsed Date, or null on parse failure.\n */\n protected parseRaw(raw: string): Date | null {\n const d = new Date(raw + \"T00:00:00\");\n\n return isNaN(d.getTime()) ? null : d;\n }\n\n /**\n * Builds the date dropdown with the field's selection callback and any\n * cached `minDate` / `maxDate` bounds forwarded into the dropdown's own\n * options bag.\n */\n protected createDropdown(): DatePickerDropdown {\n return new DatePickerDropdown(\n date => this.onDropdownSelected(date),\n {\n minDate: this._options.minDate ?? null,\n maxDate: this._options.maxDate ?? null,\n },\n );\n }\n\n /**\n * Called when the user picks a day from the dropdown. Commits the value,\n * closes the panel, refocuses the input, and re-fires `input` so any\n * non-AbstractInput consumer reading from the inner DOM event still\n * sees the change.\n *\n * @param date - The chosen date.\n */\n protected onDropdownSelected(date: Date): void {\n this.setValue(date);\n this.closeDropdown();\n this._input.focus();\n Event.fireEvent(this._input, \"input\");\n }\n\n /**\n * The DateField's preferred width — chosen to fit a \"YYYY-MM-DD\" string\n * plus the 24-px glyph button without overflow at the default font size.\n */\n protected getPreferredWidth(): number {\n return 160;\n }\n}\n\nconst DateFieldCallable = callable(DateField);\ntype DateFieldCallable = DateField;\nexport {\n DateField as _DateField,\n DateFieldCallable as DateField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractPickerField, AbstractPickerFieldOptions } from \"~/component/input/AbstractPickerField.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { calendar } from \"~/glyphs/solid/calendar.js\";\nimport { DateTimePickerDropdown } from \"~/component/input/DateTimePickerDropdown.js\";\nimport { callable } from \"~/core/Callable.js\";\n\nGlyph.register(calendar);\n\n/**\n * Construction-time options for {@link DateTimeField}.\n *\n * @category Components\n */\nexport interface DateTimeFieldOptions extends AbstractPickerFieldOptions {\n value?: Date | null;\n /** When true, the field formats and the picker exposes seconds. Default: false. */\n showSeconds?: boolean;\n /** Earliest date the picker will allow selection of. Optional. */\n minDate?: Date | null;\n /** Latest date the picker will allow selection of. Optional. */\n maxDate?: Date | null;\n}\n\n/**\n * A date-time-picker input component.\n *\n * Renders a text input with a calendar-glyph button on the right; clicking\n * either opens a\n * [`DateTimePickerDropdown`](/api/component/input/classes/DateTimePickerDropdown)\n * panel that combines a month-view calendar grid with an hour/minute\n * selector. The dropdown fades in via the shared\n * [`AnimatedDropdown`](/api/core/classes/AnimatedDropdown) lifecycle.\n *\n * Inherits the [`Bindable`](/api/core/interfaces/Bindable) value contract,\n * change/binding listeners, and enabled/read-only surface from\n * [`AbstractPickerField`](/api/component/input/classes/AbstractPickerField).\n *\n * @category Components\n */\nclass DateTimeField extends AbstractPickerField<Date, DateTimePickerDropdown, DateTimeFieldOptions> {\n\n constructor(options?: DateTimeFieldOptions) {\n super(options);\n\n // Button.setGlyph adds the glyph as the leading child of the\n // content row (with pointer-events: none) so the outer Fit centres\n // it within the button's inner rect.\n this._button.setGlyph(\"calendar\");\n\n // Late-built state: `applyOptions` cached these on `_options` at\n // super-time. `showSeconds` is read into the inherited field before\n // `setValue` so the initial formatting picks up the right precision.\n // The enabled / readOnly re-dispatch is handled by the base constructor.\n if (this._options.showSeconds !== undefined) {\n this._showSeconds = this._options.showSeconds;\n }\n\n if (this._options.value !== undefined) {\n this.setValue(this._options.value);\n }\n }\n\n /**\n * Applies a {@link DateTimeFieldOptions} bag. Late-built fields (value,\n * showSeconds) are cached pure on `_options` here and dispatched from\n * the constructor body once `_input` exists.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: DateTimeFieldOptions): this {\n super.applyOptions(options);\n\n if (options.showSeconds !== undefined) {\n this._options.showSeconds = options.showSeconds;\n }\n\n if (options.value !== undefined) {\n this._options.value = options.value;\n }\n\n if (options.minDate !== undefined) {\n this._options.minDate = options.minDate;\n }\n\n if (options.maxDate !== undefined) {\n this._options.maxDate = options.maxDate;\n }\n\n return this;\n }\n\n /**\n * Formats a Date as a \"YYYY-MM-DD HH:MM\" (or \"YYYY-MM-DD HH:MM:SS\" when\n * `showSeconds` is true) string for display in the input.\n *\n * @param date - The Date to format.\n * @returns The formatted date-time string.\n */\n protected formatValue(date: Date): string {\n const y = date.getFullYear();\n const mo = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n const h = String(date.getHours()).padStart(2, \"0\");\n const mi = String(date.getMinutes()).padStart(2, \"0\");\n\n if (this._showSeconds) {\n const s = String(date.getSeconds()).padStart(2, \"0\");\n\n return `${y}-${mo}-${d} ${h}:${mi}:${s}`;\n }\n\n return `${y}-${mo}-${d} ${h}:${mi}`;\n }\n\n /**\n * Parses the typed text into a Date. Returns null on parse failure.\n *\n * @param raw - The raw text typed into the input.\n * @returns The parsed Date, or null.\n */\n protected parseRaw(raw: string): Date | null {\n // Require both a date and a time portion, ISO-anchored, so parsing is\n // the strict inverse of formatValue and not the locale-dependent,\n // time-optional `new Date(raw)`. Mirrors DateField/TimeField strictness.\n const [datePart, timePart] = raw.trim().split(/\\s+/);\n if (!datePart || !timePart) {\n return null;\n }\n\n const d = new Date(`${datePart}T${timePart}`);\n\n return isNaN(d.getTime()) ? null : d;\n }\n\n /**\n * Builds the date-time dropdown with the field's selection callback, the\n * cached `showSeconds` flag, and any `minDate` / `maxDate` bounds forwarded\n * into the dropdown's own options bag.\n */\n protected createDropdown(): DateTimePickerDropdown {\n return new DateTimePickerDropdown(\n date => this.onDropdownSelected(date),\n {\n showSeconds: this._showSeconds,\n minDate: this._options.minDate ?? null,\n maxDate: this._options.maxDate ?? null,\n },\n );\n }\n\n /**\n * Called when the user picks a date-time from the dropdown. Commits the\n * value and re-fires `input` on the inner field so any DOM-event\n * consumer still sees the change.\n *\n * @param date - The chosen Date.\n */\n protected onDropdownSelected(date: Date): void {\n this.setValue(date);\n Event.fireEvent(this._input, \"input\");\n }\n\n /**\n * The DateTimeField's preferred width — chosen to fit a\n * \"YYYY-MM-DD HH:MM[:SS]\" string plus the 24-px glyph button without\n * overflow at the default font size.\n */\n protected getPreferredWidth(): number {\n return 200;\n }\n}\n\nconst DateTimeFieldCallable = callable(DateTimeField);\ntype DateTimeFieldCallable = DateTimeField;\nexport {\n DateTimeField as _DateTimeField,\n DateTimeFieldCallable as DateTimeField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { TextInput, TextInputOptions } from \"~/component/input/TextInput.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link PasswordField}.\n *\n * @category Components\n */\nexport interface PasswordFieldOptions extends TextInputOptions {\n /**\n * false (default) → autocomplete=\"current-password\" (login);\n * true → autocomplete=\"new-password\" (signup / change-password).\n */\n newPassword?: boolean;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins.\n */\nconst _defaultPasswordFieldOptions: Partial<PasswordFieldOptions> = {\n padding: new Insets(3, 3, 3, 3),\n cursor: \"text\",\n backgroundColor: \"var(--ts-ui-input-bg, rgb(255, 255, 255))\",\n foregroundColor: \"var(--ts-ui-text-color, black)\",\n};\n\n/**\n * A password input component that renders an `<input type=\"password\">`\n * element, defaulting `autocomplete=\"current-password\"` and `name=\"password\"`\n * for browser credential managers.\n *\n * @category Components\n */\nclass PasswordField extends TextInput<PasswordFieldOptions> {\n\n constructor(options?: PasswordFieldOptions) {\n super(options, _defaultPasswordFieldOptions);\n\n this.updateHeight();\n this.subscribeTheme(() => this.updateHeight());\n\n this.setType(\"password\");\n\n if (this._options.name === undefined) {\n this.setName(\"password\");\n }\n\n if (this._options.autoComplete === undefined) {\n this.setAutoComplete(options?.newPassword ? \"new-password\" : \"current-password\");\n }\n }\n\n /**\n * Recalculates preferred and maximum height from the unified line box plus\n * this field's own chrome.\n *\n * @remarks Box height is `Util.lineHeightPx()` plus the field's own vertical\n * insets, padding, and border — the same sum `wrapInnerBaseline` re-adds —\n * so the rendered input and its baseline match a sibling `Text`/`ComboBox`.\n * Called at construction time and after each theme change so that font-size\n * adjustments propagate to the layout hint automatically.\n */\n private updateHeight(): void {\n const h = Util.singleLineBoxHeight(this.getInsets(), this.getPadding(), this.getBorderSize());\n\n this.setPreferredSize({ width: 200, height: h });\n this.setMaxSize({ width: Number.MAX_SAFE_INTEGER, height: h });\n // Min-height pinned to the single-line box so the field can't be\n // vertically compressed below one line; min-width 0 keeps it\n // horizontally flexible.\n this.setMinSize({ width: 0, height: h });\n }\n}\n\nconst PasswordFieldCallable = callable(PasswordField);\ntype PasswordFieldCallable = PasswordField;\nexport {\n PasswordField as _PasswordField,\n PasswordFieldCallable as PasswordField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { TextInput, TextInputOptions } from \"~/component/input/TextInput.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link UsernameField}.\n *\n * @category Components\n */\nexport interface UsernameFieldOptions extends TextInputOptions {\n /** When true, seed autocomplete=\"email\" instead of \"username\" (email-based logins). */\n email?: boolean;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins.\n */\nconst _defaultUsernameFieldOptions: Partial<UsernameFieldOptions> = {\n cursor: \"text\",\n padding: new Insets(3, 3, 3, 3),\n backgroundColor: \"var(--ts-ui-input-bg, rgb(255, 255, 255))\",\n foregroundColor: \"var(--ts-ui-text-color, black)\",\n};\n\n/**\n * A username / login-identifier field — a `TextField` preset that defaults\n * `autocomplete=\"username\"` and `name=\"username\"` for browser credential\n * managers.\n *\n * Keeps internal text state in sync with the DOM on every input event.\n *\n * @category Components\n */\nclass UsernameField extends TextInput<UsernameFieldOptions> {\n\n constructor(options?: UsernameFieldOptions) {\n super(options, _defaultUsernameFieldOptions);\n\n this.updateHeight();\n this.subscribeTheme(() => this.updateHeight());\n\n this.setType(\"text\");\n\n if (this._options.name === undefined) {\n this.setName(\"username\");\n }\n\n if (this._options.autoComplete === undefined) {\n this.setAutoComplete(options?.email ? \"email\" : \"username\");\n }\n }\n\n /**\n * Recalculates preferred and maximum height from the unified line box plus\n * this field's own chrome.\n *\n * @remarks Box height is `Util.lineHeightPx()` plus the field's own vertical\n * insets, padding, and border — the same sum `wrapInnerBaseline` re-adds —\n * so the rendered input and its baseline match a sibling `Text`/`ComboBox`.\n * Called at construction time and after each theme change so that font-size\n * adjustments propagate to the layout hint automatically.\n */\n private updateHeight(): void {\n const h = Util.singleLineBoxHeight(this.getInsets(), this.getPadding(), this.getBorderSize());\n\n this.setPreferredSize({ width: 200, height: h });\n this.setMaxSize({ width: Number.MAX_SAFE_INTEGER, height: h });\n // Min-height pinned to the single-line box so the field can't be\n // vertically compressed below one line; min-width 0 keeps it\n // horizontally flexible.\n this.setMinSize({ width: 0, height: h });\n }\n\n}\n\nconst UsernameFieldCallable = callable(UsernameField);\ntype UsernameFieldCallable = UsernameField;\nexport {\n UsernameField as _UsernameField,\n UsernameFieldCallable as UsernameField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { TextInput, TextInputOptions } from \"~/component/input/TextInput.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link TextArea}.\n *\n * @category Components\n */\nexport interface TextAreaOptions extends TextInputOptions {\n rows?: number;\n cols?: number;\n wrap?: string;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins.\n */\nconst _defaultTextAreaOptions: Partial<TextAreaOptions> = {\n tag: \"textarea\",\n cursor: \"text\",\n // Override Component's `overflow: \"hidden\"` default so the underlying\n // native `<textarea>` scrolls its own overflowing content (as a real\n // textarea does) instead of being clipped. `auto` shows a scrollbar per\n // axis only when that axis overflows — with the default soft wrap only the\n // vertical bar appears, once the text is taller than the box. The effective\n // default is honoured end-to-end: `applyStyle` writes it AND attaches the\n // eased wheel scroller from it (see Component.applyOverflowStyles), so no\n // constructor work is needed. Kept a class-level default, so an explicit\n // caller `overflow` still wins.\n overflow: \"auto\",\n padding: new Insets(3, 3, 3, 3),\n preferredSize: { width: 200, height: 200 },\n minSize: { width: 100, height: 100 },\n backgroundColor: \"var(--ts-ui-input-bg, rgb(255, 255, 255))\",\n foregroundColor: \"var(--ts-ui-text-color, black)\",\n};\n\n/**\n * A multi-line text area component backed by a `<textarea>` element.\n *\n * Keeps internal text state in sync with the DOM on every input event.\n *\n * @category Components\n */\nclass TextArea extends TextInput<TextAreaOptions> {\n\n constructor(text: string = \"\", options?: TextAreaOptions) {\n // Positional `text` is per-instance state, so it goes in the options bag\n // (dispatched through `setText` into `_options`), NOT `_defaultOptions`,\n // which holds class-level defaults the value getters never consult. An\n // explicit `options.text` still wins (spread last).\n super(\n text ? { text, ...options } : options,\n _defaultTextAreaOptions,\n );\n\n // The `<textarea>` corner grip is the only user-resize affordance on\n // any of these components. Pin `resize: none` as a persistent CSS rule\n // (not an inline style, which `applyStyle` would wipe on re-render) so\n // the area can never be drag-resized. There is no accompanying option\n // or setter — non-resizability is immutable by design.\n this.setElementCSSRules({ resize: \"none\" });\n }\n\n /**\n * Applies a {@link TextAreaOptions} bag, dispatching textarea-specific\n * `rows`, `cols`, and `wrap` attributes after inherited TextInput fields.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: TextAreaOptions): this {\n super.applyOptions(options);\n\n if (options.rows !== undefined) {\n this.setRows(options.rows);\n }\n\n if (options.cols !== undefined) {\n this.setCols(options.cols);\n }\n\n if (options.wrap !== undefined) {\n this.setWrap(options.wrap);\n }\n\n return this;\n }\n\n /**\n * Returns the configured `rows` attribute value, or null if not set.\n *\n * @returns The row count, or null.\n */\n getRows(): number | null {\n return this._options.rows ?? null;\n }\n\n /**\n * Sets the HTML `rows` attribute on the underlying textarea.\n *\n * @param value - The number of visible text rows.\n *\n * @returns This component, for method chaining.\n */\n setRows(value: number): this {\n this._options.rows = value;\n this.setElementAttribute(\"rows\", String(value));\n\n return this;\n }\n\n /**\n * Removes the HTML `rows` attribute from the underlying textarea.\n *\n * @returns This component, for method chaining.\n */\n clearRows(): this {\n if (this._options.rows === undefined) {\n return this;\n }\n\n this._options.rows = undefined;\n this.removeElementAttribute(\"rows\");\n\n return this;\n }\n\n /**\n * Returns the configured `cols` attribute value, or null if not set.\n *\n * @returns The column count, or null.\n */\n getCols(): number | null {\n return this._options.cols ?? null;\n }\n\n /**\n * Sets the HTML `cols` attribute on the underlying textarea.\n *\n * @param value - The visible width in average character widths.\n *\n * @returns This component, for method chaining.\n */\n setCols(value: number): this {\n this._options.cols = value;\n this.setElementAttribute(\"cols\", String(value));\n\n return this;\n }\n\n /**\n * Removes the HTML `cols` attribute from the underlying textarea.\n *\n * @returns This component, for method chaining.\n */\n clearCols(): this {\n if (this._options.cols === undefined) {\n return this;\n }\n\n this._options.cols = undefined;\n this.removeElementAttribute(\"cols\");\n\n return this;\n }\n\n /**\n * Returns the configured `wrap` attribute value, or null if not set.\n *\n * @returns The wrap mode, or null.\n */\n getWrap(): string | null {\n return this._options.wrap ?? null;\n }\n\n /**\n * Sets the HTML `wrap` attribute on the underlying textarea.\n *\n * @param value - The wrap mode (e.g. \"hard\", \"soft\", \"off\").\n *\n * @returns This component, for method chaining.\n */\n setWrap(value: string): this {\n this._options.wrap = value;\n this.setElementAttribute(\"wrap\", value);\n\n return this;\n }\n\n /**\n * Removes the HTML `wrap` attribute from the underlying textarea.\n *\n * @returns This component, for method chaining.\n */\n clearWrap(): this {\n if (this._options.wrap === undefined) {\n return this;\n }\n\n this._options.wrap = undefined;\n this.removeElementAttribute(\"wrap\");\n\n return this;\n }\n\n /**\n * Returns the baseline of the text area's **first** line so it aligns its\n * top line with sibling controls in a horizontal row; the box then extends\n * downward below the baseline.\n *\n * @returns The first-line baseline offset in pixels.\n *\n * @remarks The first-line baseline (not the box bottom) is used so a tall\n * text area does not dominate the row's ascent and drag every sibling down\n * by its full height. This stays safe for surrounding graphics only because\n * they expose their own (bottom-edge) baselines — a `null`-baseline sibling\n * would be centred against this area's inflated descent and float to the\n * row's middle.\n */\n getBaseline(): number | null {\n return this.wrapInnerBaseline(Util.measureTextBaseline());\n }\n\n protected init(element?: Handle): this {\n super.init(element);\n\n const el = element || this.getElement()!;\n\n if (this._options.rows !== undefined) {\n DOM.sink.apply(el, { setAttr: { rows: String(this._options.rows) } });\n }\n\n if (this._options.cols !== undefined) {\n DOM.sink.apply(el, { setAttr: { cols: String(this._options.cols) } });\n }\n\n if (this._options.wrap !== undefined) {\n DOM.sink.apply(el, { setAttr: { wrap: this._options.wrap } });\n }\n\n return this;\n }\n\n}\n\nconst TextAreaCallable = callable(TextArea);\ntype TextAreaCallable = TextArea;\nexport {\n TextArea as _TextArea,\n TextAreaCallable as TextArea\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Animation } from \"~/core/Animation.js\";\nimport { AbstractBooleanInput, AbstractBooleanInputOptions } from \"~/component/input/AbstractBooleanInput.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link Toggle}.\n *\n * @category Components\n */\nexport interface ToggleOptions extends AbstractBooleanInputOptions {\n value?: boolean;\n}\n\n/**\n * A custom-drawn on/off switch widget rendered as a focusable `<div>` with\n * `role=\"switch\"`.\n *\n * `Toggle` is not the same widget as\n * [`ToggleButton`](/api/component/button/classes/ToggleButton). `ToggleButton`\n * is a push-button that stays pressed; `Toggle` is the sliding-pill switch\n * pattern (System Settings, iOS-style). When toggled, the thumb slides via a\n * CSS transition; the slide is suppressed when the OS preference\n * `prefers-reduced-motion` is set.\n *\n * @category Components\n */\nclass Toggle<TOptions extends ToggleOptions = ToggleOptions>\n extends AbstractBooleanInput<TOptions>\n{\n private _track: Component;\n private _thumb: Component;\n\n /**\n * Constructs a Toggle.\n *\n * @param options - Optional construction-time options.\n */\n constructor(options?: TOptions) {\n super({ ...(options ?? {}) } as TOptions);\n\n this.setLayoutManager(new HBox());\n\n this._track = new Component();\n this._track.setBackgroundColor(\"var(--ts-ui-toggle-track-bg-off, rgb(200, 200, 200))\");\n this._track.setBorderRadius(\"999px\");\n this._track.setPreferredSize({ width: 36, height: 20 });\n // Min = preferred = max so the outer HBox shrink-on-overallocation\n // can't collapse the pill when the Toggle is packed alongside\n // flexible siblings.\n this._track.setMinSize({ width: 36, height: 20 });\n this._track.setMaxSize({ width: 36, height: 20 });\n // The track owns the click + cursor surface so the pointer/click area\n // matches the visible pill exactly. The root stays inert (default\n // cursor, no click listener), so clicks on the label or in any\n // stretched empty space don't toggle and don't show the pointer cursor.\n this._track.setCursor(\"pointer\");\n\n this._thumb = new Component();\n this._thumb.setBackgroundColor(\"var(--ts-ui-toggle-thumb-bg, rgb(255, 255, 255))\");\n this._thumb.setBorderRadius(\"999px\");\n this._thumb.setPreferredSize({ width: 16, height: 16 });\n this._thumb.setMaxSize({ width: 16, height: 16 });\n // The track's default Absolute layout never sizes its children, so the\n // thumb collapses to 0 × 0 unless we set the rendered size explicitly.\n this._thumb.setSize({ width: 16, height: 16 });\n this._thumb.setShadow(\"0 1px 2px rgba(0, 0, 0, 0.25)\");\n this._thumb.setX(2);\n this._thumb.setY(2);\n // Pass-through so clicks on the thumb still hit the track underneath.\n this._thumb.setPointerEvents(\"none\");\n\n if (!Animation.isReducedMotion()) {\n this._thumb.setTransition(\"transform 120ms ease-out\");\n this._track.setTransition(\"background-color 120ms ease-out\");\n }\n\n this._track.addComponent(this._thumb);\n super.addComponent(this._track);\n\n this.getAria().setRole(\"switch\");\n this.getAria().setTabIndex(0);\n this.getAria().setChecked(false);\n\n this.setOutline(\"none\");\n\n // The track owns the user-toggle click so the pointer/click + cursor\n // surface is exactly the visible pill — clicks on a label or in any\n // stretched empty area pass through to the root, which has no listener\n // of its own. This pointer line stays per-subclass (a closure over the\n // widget `this`) because a listener registered on the child track would\n // otherwise bind `this` to the track; only the keyboard path, registered\n // on the root, moves into the base.\n Event.addListener(this._track, \"click\", () => this.activateFromPointer());\n this.installKeyboard();\n\n // Late-built dispatch: applyOptions buffered these onto _options because\n // children didn't exist yet. Run their setters now.\n if (this._options.value !== undefined) {\n this.applyValue(this._options.value);\n }\n\n if (this._options.label !== undefined) {\n this.applyLabel(this._options.label);\n }\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n }\n\n /**\n * Applies a {@link ToggleOptions} bag. Inherited Component fields cascade\n * through `super.applyOptions`; toggle-specific fields are stored pure on\n * `_options` so the constructor body can dispatch them after children are\n * built.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This component, for method chaining.\n */\n protected applyOptions(options: TOptions): this {\n super.applyOptions(options);\n\n if (options.value !== undefined) this._options.value = options.value;\n if (options.label !== undefined) this._options.label = options.label;\n if (options.enabled !== undefined) this._options.enabled = options.enabled;\n if (options.readOnly !== undefined) this._options.readOnly = options.readOnly;\n\n return this;\n }\n\n /**\n * Activates the toggle from a click or key by flipping the on/off state.\n * The enabled/read-only guard is applied by the base before this runs.\n */\n protected activate(): void {\n this.setValue(!this.getValue());\n }\n\n /**\n * Returns the inner track graphic — the click + cursor surface.\n *\n * @returns The track component.\n */\n protected getInteractiveSurface(): Component {\n return this._track;\n }\n\n /**\n * The toggle activates on Space and Enter (the switch idiom), widening the\n * base default of Space only.\n *\n * @returns The activation key strings.\n */\n protected activationKeys(): string[] {\n return [\" \", \"Enter\"];\n }\n\n /**\n * Returns the current on/off state.\n *\n * @returns `true` when on, `false` when off.\n */\n getValue(): boolean {\n return this._options.value ?? false;\n }\n\n /**\n * Sets the on/off state. Notifies change and binding listeners on a real\n * transition; no-op when the value is unchanged.\n *\n * @param value - The new on/off state.\n *\n * @returns This component, for method chaining.\n */\n setValue(value: boolean): this {\n const next = !!value;\n if (next === this.getValue()) {\n return this;\n }\n\n this._options.value = next;\n this.applyValue(next);\n this.notifyChange(next);\n\n return this;\n }\n\n /**\n * Resets the value to `false`.\n *\n * @returns This component, for method chaining.\n */\n clearValue(): this {\n return this.setValue(false);\n }\n\n /**\n * Returns the offset from the top of the toggle to the inline label's text\n * baseline, or `null` when there is no label (HBox falls back to bottom-edge\n * alignment).\n *\n * @returns The baseline offset in pixels, or `null`.\n *\n * @remarks The reported baseline includes the offset that centres the label\n * on the pill because `doLayout` nudges the label down to sit concentric with the taller pill;\n * the outer row must align the label at that lowered position so its text\n * baseline still meets the row's.\n */\n getBaseline(): number | null {\n const label = this._label;\n if (label === null) {\n return null;\n }\n\n const baseline = label.getBaseline();\n if (baseline === null) {\n return null;\n }\n\n return this.wrapInnerBaseline(baseline + this.labelCenterOffset());\n }\n\n /**\n * Pixels to push the inline label down so it sits centred on the pill.\n *\n * The 36×20 pill is taller than the text line, but the inner `HBox`\n * top-aligns the shorter label against the row's text baseline — leaving the\n * pill bottom-heavy, its extra height hanging entirely below the text. A\n * same-height control (the 16×16 checkbox box) needs no correction because\n * top-aligning it already centres it on the line; the taller pill does.\n * Centring the label on the pill (and folding this into {@link getBaseline})\n * makes the pill sit concentric with the text line instead.\n *\n * @returns Pixels to push the label down, or 0 when there is no label or it\n * is at least as tall as the pill.\n */\n private labelCenterOffset(): number {\n if (this._label === null) {\n return 0;\n }\n\n const trackHeight = this._track.getPreferredSize()?.height ?? 0;\n const labelHeight = this._label.getPreferredSize()?.height ?? trackHeight;\n\n return Math.max(0, Math.round((trackHeight - labelHeight) / 2));\n }\n\n /**\n * Lays out the pill and label, then nudges the label down so it sits\n * concentric with the taller pill. The\n * inner `HBox` top-aligns the label to the row's text baseline on every\n * pass, so re-reading its placed `y` here keeps the nudge idempotent.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const label = this._label;\n const offset = this.labelCenterOffset();\n if (label !== null && offset > 0) {\n label.setY(label.getY() + offset);\n }\n\n return this;\n }\n\n /**\n * Pushes a new value to the visual + ARIA state.\n */\n private applyValue(value: boolean): void {\n this.getAria().setChecked(value);\n\n // Thumb travel: track width 36, thumb size 16, 2px inset on each side =>\n // off at x=2, on at x=36-16-2=18 => translate 16px to the right.\n this._thumb.setTransform(value ? \"translateX(16px)\" : \"translateX(0px)\");\n\n this._track.setBackgroundColor(value\n ? \"var(--ts-ui-toggle-track-bg-on, rgb(30, 100, 200))\"\n : \"var(--ts-ui-toggle-track-bg-off, rgb(200, 200, 200))\");\n }\n\n}\n\nconst ToggleCallable = callable(Toggle);\ntype ToggleCallable<TOptions extends ToggleOptions = ToggleOptions> = Toggle<TOptions>;\nexport {\n Toggle as _Toggle,\n ToggleCallable as Toggle\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AnimatedDropdown, AnimatedDropdownOptions } from \"~/core/AnimatedDropdown.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { List } from \"~/component/list/List.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/** Pixel height of a single row inside the dropdown. Matches `SelectableListRow`'s cached `preferredSize(0, 22)`. */\nconst AUTOCOMPLETE_DROPDOWN_ROW_HEIGHT_PX = 22;\n\n/**\n * Construction-time options for {@link AutoCompleteDropdown}.\n *\n * @category Components\n */\ninterface AutoCompleteDropdownOptions extends AnimatedDropdownOptions {}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins.\n */\nconst _defaultAutoCompleteDropdownOptions: Partial<AutoCompleteDropdownOptions> = {\n durationMs: 100,\n backgroundColor: \"var(--ts-ui-autocomplete-bg, rgb(255, 255, 255))\",\n border: \"var(--ts-ui-input-border)\",\n borderRadius: \"var(--ts-ui-border-radius, 4px)\",\n shadow: \"var(--ts-ui-autocomplete-shadow, 2px 4px 8px rgba(0,0,0,0.15))\",\n};\n\n// The dropdown wrapper carries the visible chrome (border / radius /\n// shadow); the embedded List inherits a `:focus::after` ring from\n// `AbstractSelectableList`, which would paint a second ring inside the\n// dropdown if any code path programmatically focused the list. The\n// host-forwarded keystroke pattern keeps DOM focus on the TextField\n// throughout the dropdown's lifetime, but the suppression is\n// belt-and-braces — a future code path that does focus the list won't\n// double-stack the chrome.\n(() => {\n new StyleRule({\n scope: \"selector\",\n name: \".AutoCompleteDropdown .List:focus::after\",\n styles: {\n content: \"none\",\n },\n });\n})();\n\n/**\n * Floating dropdown panel for [`AutoCompleteField`](/api/component/input/classes/AutoCompleteField).\n *\n * Hosts a single [`List`](/api/component/list/classes/List) instance that\n * owns the row pool, the click-commit gesture, the keyboard reducer, the\n * type-ahead buffer, and the ARIA `option`-role wiring. The dropdown\n * wrapper handles only the overlay lifecycle (fade, anchored positioning,\n * viewport click-outside dismiss). Selection-follows-focus is disabled on\n * the inner list so ArrowUp/Down moves the focus highlight without\n * committing the row as the TextField's value; Enter / click still commit\n * through the list's `change` event.\n */\nclass AutoCompleteDropdown extends AnimatedDropdown<AutoCompleteDropdownOptions> {\n\n private readonly _list: List;\n private readonly _onSelect: (value: string) => void;\n private readonly _onHide: () => void;\n\n /**\n * @param onSelect - Called with the selected suggestion string when the user picks an item.\n * @param onHide - Called whenever the dropdown hides, including via viewport click-outside.\n * @param options - Optional construction-time options.\n */\n constructor(onSelect: (value: string) => void, onHide: () => void, options?: AutoCompleteDropdownOptions) {\n super(options, _defaultAutoCompleteDropdownOptions);\n\n this._onSelect = onSelect;\n this._onHide = onHide;\n\n // The inner List already exposes `role=\"listbox\"` from\n // `AbstractSelectableList`; the dropdown wrapper just provides the\n // overlay chrome and must not duplicate the listbox role\n // (nested listboxes break assistive-tech enumeration of the\n // `option` rows).\n this.setContain(\"layout\");\n this.setLayoutManager(new Fit());\n\n // `Fit` makes the inner list fill the dropdown's content box. The\n // list's own border is stripped — the dropdown root carries the\n // visible chrome (border + radius + shadow) so the two never\n // double-stack. Focus-on-row-click is disabled because the host\n // TextField keeps DOM focus throughout the dropdown's lifetime;\n // letting the list grab focus on a row click would blur the\n // TextField and tear down the autocomplete session before\n // `_onSelect` runs. Selection-follows-focus is disabled so\n // ArrowUp/Down previews a row without writing it into the\n // TextField — Enter / click still commit through `change`.\n this._list = new List();\n this._list.setBorder(\"none\");\n this._list.setBorderRadius(\"0\");\n this._list.setFocusOnRowClick(false);\n this._list.setSelectFollowsFocus(false);\n this.addComponent(this._list);\n\n // Click and keyboard commits both arrive through the list's\n // `change` event (fired by `notifyUserChange` after the click /\n // keyboard reducer mutates the selection set). Programmatic\n // writes (`setItemsArray`) bypass this path, so re-opening the\n // dropdown doesn't trigger a spurious commit.\n this._list.on(\"action\", () => {\n const value = this._list.getValue();\n\n if (value) {\n this._onSelect(value);\n }\n });\n }\n\n /**\n * Shows the dropdown anchored relative to `anchorEl`, rendering the\n * given suggestions. Key + label are identical so the list's\n * `getValue()` returns the picked suggestion text directly.\n *\n * @param anchorEl - The input element to anchor the dropdown to.\n * @param suggestions - The list of suggestion strings to display.\n *\n * @returns This dropdown, for method chaining.\n */\n show(anchorEl: Handle, suggestions: string[]): this {\n // Force the floating element into existence before any layout pass.\n // showAnimated() below mounts it, but that runs after doLayout() —\n // on first show getInnerSize() would otherwise return null and the\n // inner list's Fit layout would have nothing to fill.\n this.getElement(true);\n\n this.pauseLayout();\n this._list.setItemsArray(suggestions.map(s => ({ key: s, label: s })));\n this.resumeLayout();\n\n const perim = this.getPerimeterSize();\n const chromeH = perim.top + perim.bottom;\n const rect = DOM.source.getElementRect(anchorEl);\n\n this.setWidth(rect.width);\n this.setHeight(suggestions.length * AUTOCOMPLETE_DROPDOWN_ROW_HEIGHT_PX + chromeH);\n\n this.placeAnchored(rect);\n\n // Exclude the anchored input from the manager's outside-click test so\n // the keystroke that re-triggers the suggestions doesn't dismiss it.\n this.setAnchorElement(anchorEl);\n\n this.showAnimated();\n\n // VBox-backed list positions rows via framework setters that no-op\n // while the dropdown element is detached. Run the layout pass\n // after `showAnimated` mounts the panel so rows land at the\n // correct y offsets on first open.\n this.doLayout();\n\n return this;\n }\n\n /**\n * Hides the dropdown, detaches it from the DOM, and fires the `onHide`\n * callback (via {@link onHideComplete}). Outside-click dismissal is now\n * driven by [`LayerManager`](/api/core/namespaces/LayerManager)'s\n * `\"click-outside\"` mode rather than a per-dropdown viewport listener.\n */\n hide(): this {\n this.hideAnimated();\n\n return this;\n }\n\n /**\n * Forwards a keystroke from the host\n * [`AutoCompleteField`](/api/component/input/classes/AutoCompleteField)\n * (which keeps DOM focus on its TextField while the dropdown is\n * open) into the inner list's keyboard reducer. Mirrors\n * [`AnimatedDropdown.handleKey`](/api/core/classes/AnimatedDropdown#handlekey).\n * Returns `true` when the list consumed the key so the host can\n * `preventDefault` and stop further processing.\n *\n * @param e - The keyboard event captured by the host.\n *\n * @returns `true` when the list consumed the key.\n */\n handleKey(e: KeyboardEvent): boolean {\n return this._list.handleKey(e);\n }\n\n /**\n * Returns the framework-generated DOM element id of the inner list's\n * keyboard-focus row, suitable for writing into the host TextField's\n * `aria-activedescendant`. Returns `null` when no row holds focus.\n *\n * @returns The focused row's element id, or `null`.\n */\n getFocusedRowId(): string | null {\n return this._list.getFocusedRowId();\n }\n\n /**\n * Returns the inner [`List`](/api/component/list/classes/List). Test\n * seam — production code should not reach past the dropdown's\n * documented surface.\n *\n * @returns The hosted list instance.\n */\n getList(): List {\n return this._list;\n }\n\n /**\n * Fires the `onHide` callback once the exit fade has completed and the\n * panel is detached from the DOM.\n */\n protected onHideComplete(): void {\n this._onHide();\n }\n}\n\nconst AutoCompleteDropdownCallable = callable(AutoCompleteDropdown);\ntype AutoCompleteDropdownCallable = AutoCompleteDropdown;\nexport {\n AutoCompleteDropdown as _AutoCompleteDropdown,\n AutoCompleteDropdownCallable as AutoCompleteDropdown\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput, AbstractInputOptions } from \"~/component/input/AbstractInput.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport { AbstractStore } from \"~/data/AbstractStore.js\";\nimport { TextField } from \"~/component/input/TextField.js\";\nimport { AutoCompleteDropdown } from \"~/component/input/AutoCompleteDropdown.js\";\nimport { registerFocusWithinRing } from \"~/component/input/focusRing.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Focus ring highlighting the composite root whenever the inner TextField is\n// focused (the helper appends the focus pseudo-element).\nregisterFocusWithinRing(\".AutoCompleteField\");\n\n/**\n * Controls how typed input is matched against suggestion strings.\n *\n * - `'contains'` — substring match, case-insensitive (default).\n * - `'startsWith'` — prefix match, case-insensitive.\n * - `'containsCaseSensitive'` — substring match, case-sensitive.\n * - `'startsWithCaseSensitive'` — prefix match, case-sensitive.\n *\n * @category Components\n */\nexport type AutoCompleteMatchMode =\n | 'contains'\n | 'startsWith'\n | 'containsCaseSensitive'\n | 'startsWithCaseSensitive';\n\n/**\n * Construction-time options for {@link AutoCompleteField}.\n *\n * @category Components\n */\nexport interface AutoCompleteFieldOptions extends AbstractInputOptions {\n /** Static list of suggestion strings. */\n suggestions? : string[];\n /** Data store used when suggestions come from a remote/in-memory store. */\n store? : AbstractStore;\n /** The store field name whose value is shown as the suggestion text. Required when `store` is set. */\n displayField? : string;\n /** Minimum number of characters typed before suggestions are queried. Default: 1. */\n minChars? : number;\n /** Debounce delay in milliseconds before querying on each keystroke. Default: 200. */\n debounceMs? : number;\n /** Maximum number of suggestions to show at once. Default: 10. */\n maxSuggestions? : number;\n /** Placeholder text shown in the input when empty. */\n placeholder? : string;\n /** How the typed query is matched against suggestions. Default: `'contains'` (case-insensitive). */\n matchMode? : AutoCompleteMatchMode;\n}\n\n/**\n * @deprecated Use {@link AutoCompleteFieldOptions}.\n */\nexport type AutoCompleteFieldConfig = AutoCompleteFieldOptions;\n\n/**\n * String-literal event emitted by {@link AutoCompleteField} on top of the\n * inherited `change` / `binding`: `\"select\"` fires when the user picks a\n * suggestion from the dropdown.\n *\n * @category Components\n */\nexport type AutoCompleteFieldEvent = \"select\";\n\n/**\n * A typeahead/autocomplete text field.\n *\n * Wraps a [`TextField`](/api/component/input/classes/TextField) and an `AutoCompleteDropdown`. Suggestions may come\n * from a static string array or from an [`AbstractStore`](/api/data/classes/AbstractStore). Implements `Bindable<string>`\n * for use with the [`Binding`](/api/core/classes/Binding) system.\n *\n * @example\n * ```typescript\n * const field = new AutoCompleteField({\n * suggestions: ['Apple', 'Banana', 'Cherry'],\n * placeholder: 'Type a fruit…',\n * });\n * panel.addComponent(field);\n * field.addSelectListener(value => console.log('Selected:', value));\n * ```\n *\n * @category Components\n */\nclass AutoCompleteField extends AbstractInput<string, AutoCompleteFieldOptions> {\n\n private _textField : TextField;\n private _dropdown : AutoCompleteDropdown;\n private _debounceTimer : ReturnType<typeof setTimeout> | null = null;\n private _selectBag : ListenerBag<AutoCompleteFieldEvent> = new ListenerBag<AutoCompleteFieldEvent>();\n\n /**\n * @param options - Optional construction-time options for suggestions, store, behaviour, and base Component styling.\n */\n constructor(options?: AutoCompleteFieldOptions) {\n // Children are built below, so consumer options can't safely cascade\n // through super(). Apply them manually at the end of the constructor.\n // eslint-disable-next-line local/forward-super-options\n super();\n\n // The composite owns the visible chrome — gray edge from the shared\n // `--ts-ui-input-border` token, matching `:focus-within` outline\n // wired up at module top. The inner TextField then strips its own\n // border + browser focus outline so the two never paint over.\n this.setBorder(\"var(--ts-ui-input-border)\");\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n\n this._textField = new TextField();\n this._textField.setBorder(\"none\");\n this._textField.setBorderRadius(\"0\");\n this._textField.setOutline(\"none\");\n\n this.addComponent(this._textField);\n\n this.syncSizeFromTextField();\n this.subscribeTheme(() => this.syncSizeFromTextField());\n\n this._dropdown = new AutoCompleteDropdown(\n value => this.onSuggestionSelected(value),\n () => this.onDropdownHidden(),\n );\n\n this._textField.getAria().setRole(\"combobox\");\n this._textField.getAria().setAutoComplete(\"list\");\n this._textField.getAria().setExpanded(false);\n this._textField.getAria().setControls(this._dropdown.getId());\n this._textField.getAria().setActiveDescendant(\"\");\n\n Event.addListener(this._textField, \"input\", () => this.onInput());\n Event.addListener(this._textField, \"keydown\", (e: KeyboardEvent) => this.onKeyDown(e));\n Event.addListener(this._textField, \"blur\", () => this.onBlur());\n // Bridge user-driven text changes from the inner TextField up into\n // AbstractInput's change / binding listener fan-out so consumers\n // attached via the inherited `on(\"change\", fn)` see every keystroke\n // and every suggestion-pick (suggestions hit setValue, which writes\n // through the TextField and re-fires its own change listeners).\n this._textField.on(\"change\", value => this.notifyChange(value));\n\n if (options) {\n this.applyOptions(options);\n }\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n }\n\n /**\n * Applies an {@link AutoCompleteFieldOptions} bag, dispatching each field\n * through its setter so side effects fire identically whether the value\n * came from the constructor or a later setter call.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: AutoCompleteFieldOptions): this {\n super.applyOptions(options);\n\n if (options.suggestions !== undefined) this.setSuggestions(options.suggestions);\n if (options.minChars !== undefined) this.setMinChars(options.minChars);\n if (options.debounceMs !== undefined) this.setDebounceMs(options.debounceMs);\n if (options.maxSuggestions !== undefined) this.setMaxSuggestions(options.maxSuggestions);\n if (options.matchMode !== undefined) this.setMatchMode(options.matchMode);\n if (options.placeholder !== undefined) this.setPlaceholder(options.placeholder);\n\n // store + displayField are paired; apply via setStore when both present,\n // otherwise route through the individual options bag fields so partial\n // configuration is preserved without firing a half-configured setStore.\n if (options.store !== undefined && options.displayField !== undefined) {\n this.setStore(options.store, options.displayField);\n } else {\n if (options.store !== undefined) this._options.store = options.store;\n if (options.displayField !== undefined) this._options.displayField = options.displayField;\n }\n\n return this;\n }\n\n /**\n * Mirrors the preferred, max, and min size from the inner [`TextField`](/api/component/input/classes/TextField) onto this component\n * so that parent layout managers can calculate the correct row height.\n *\n * Called at construction time and after each theme change.\n */\n private syncSizeFromTextField(): void {\n const pref = this._textField.getPreferredSize();\n const max = this._textField.getMaxSize();\n const min = this._textField.getMinSize();\n\n if (pref) {\n this.setPreferredSize({ width: pref.width, height: pref.height });\n }\n\n if (max) {\n this.setMaxSize({ width: max.width, height: max.height });\n }\n\n // Mirrors the inner field's min so the composite is non-squishable\n // like a bare TextField, not just visually preferred at one line.\n if (min) {\n this.setMinSize({ width: min.width, height: min.height });\n }\n }\n\n /**\n * Returns the offset from the top of the autocomplete field to the inner text field's baseline.\n *\n * @returns The baseline offset in pixels, or `null` when the text field has no baseline.\n *\n * @remarks `doLayout` places the inner [`TextField`](/api/component/input/classes/TextField) at `(0, 0)` to fill this\n * component, so this baseline excludes `insets` (which are not used to\n * position the child) and only adds the component's own border.\n */\n getBaseline(): number | null {\n return this.wrapInnerBaseline(this._textField.getBaseline());\n }\n\n /**\n * Lays out the internal text field to fill the container exactly.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n this._textField.setX(0);\n this._textField.setY(0);\n this._textField.setWidth(this.getWidth());\n this._textField.setHeight(this.getHeight());\n\n return this;\n }\n\n // ── Bindable<string> ────────────────────────────────────────────────────\n\n /**\n * Sets the field value programmatically without firing binding or select\n * listeners. Forwards directly to the inner TextField so the inherited\n * `notifyChange` does not double-fire on this path; user-driven changes\n * fire through the constructor's `_textField.on(\"change\", fn)` bridge.\n *\n * @param value - The string value to display.\n */\n setValue(value: string): this {\n this._textField.setValue(value);\n\n return this;\n }\n\n /**\n * Returns the current text value of the field, read directly from the\n * inner TextField.\n *\n * @returns The current string value.\n */\n getValue(): string {\n return this._textField.getValue();\n }\n\n /**\n * Reflects the enabled flag by forwarding to the inner TextField.\n */\n protected applyEnabled(value: boolean): void {\n this._textField.setEnabled(value);\n }\n\n /**\n * Reflects the read-only flag by forwarding to the inner TextField.\n */\n protected applyReadOnly(value: boolean): void {\n this._textField.setReadOnly(value);\n }\n\n // ── Configuration ────────────────────────────────────────────────────────\n\n /**\n * Replaces the static suggestion list.\n *\n * @param suggestions - The new list of suggestion strings.\n */\n setSuggestions(suggestions: string[]): this {\n this._options.suggestions = suggestions;\n\n return this;\n }\n\n /**\n * Configures the field to query suggestions from a store.\n *\n * @param store - The data store to filter.\n * @param displayField - The field name on each record to use as the suggestion text.\n */\n setStore(store: AbstractStore, displayField: string): this {\n this._options.store = store;\n this._options.displayField = displayField;\n\n return this;\n }\n\n /**\n * Sets the minimum number of characters required before suggestions are shown.\n *\n * @param n - Minimum character count. Default is 1.\n */\n setMinChars(n: number): this {\n this._options.minChars = n;\n\n return this;\n }\n\n /**\n * Sets the debounce delay in milliseconds applied to each input event.\n *\n * @param ms - Delay in milliseconds. Default is 200.\n */\n setDebounceMs(ms: number): this {\n this._options.debounceMs = ms;\n\n return this;\n }\n\n /**\n * Sets the maximum number of suggestions to display at once.\n *\n * @param n - Maximum item count. Default is 10.\n */\n setMaxSuggestions(n: number): this {\n this._options.maxSuggestions = n;\n\n return this;\n }\n\n /**\n * Sets how typed input is matched against suggestion strings. The default\n * `'contains'` matches anywhere, case-insensitive. The `*CaseSensitive`\n * variants opt in to case-sensitive matching.\n *\n * @param mode - One of `'contains'`, `'startsWith'`, `'containsCaseSensitive'`, `'startsWithCaseSensitive'`.\n */\n setMatchMode(mode: AutoCompleteMatchMode): this {\n this._options.matchMode = mode;\n\n return this;\n }\n\n /**\n * Sets the placeholder text shown in the inner text field when empty.\n *\n * @param placeholder - The placeholder string.\n */\n setPlaceholder(placeholder: string): this {\n this._options.placeholder = placeholder;\n this._textField.setPlaceholder(placeholder);\n\n return this;\n }\n\n // ── Events ───────────────────────────────────────────────────────────────\n\n /**\n * Registers a listener for one of this field's events. `\"select\"` fires when\n * the user picks a suggestion from the dropdown (routed through this class's\n * own listener bag); `\"change\"` and `\"binding\"` are the inherited\n * {@link AbstractInput} value events.\n *\n * @param event - The event name.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This component, for method chaining.\n */\n on(event: \"select\", listener: (value: string) => void): this;\n on(event: \"change\", listener: (value: string) => void): this;\n on(event: \"binding\", listener: () => void): this;\n on(event: \"select\" | \"change\" | \"binding\", listener: Function): this {\n if (event === \"select\") {\n this._selectBag.add(\"select\", listener);\n\n return this;\n }\n\n return super.on(event as \"change\", listener as (value: string) => void);\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match the one passed to {@link on}.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This component, for method chaining.\n */\n off(event: \"select\" | \"change\" | \"binding\", listener: Function): this {\n if (event === \"select\") {\n this._selectBag.remove(\"select\", listener);\n\n return this;\n }\n\n return super.off(event, listener);\n }\n\n /**\n * Registers a listener called when the user selects a suggestion. Retained\n * for back-compat; a thin forwarder onto `on(\"select\", fn)`.\n *\n * @param fn - Callback that receives the selected string value.\n */\n addSelectListener(fn: (value: string) => void): void {\n this.on(\"select\", fn);\n }\n\n // ── Internal event handlers ──────────────────────────────────────────────\n\n /**\n * Handles the `input` event on the internal text field.\n *\n * Schedules a debounced query when the typed value meets the `minChars`\n * threshold. AbstractInput's change / binding listeners fire through\n * the constructor's `_textField.on(\"change\", fn)` bridge, so this\n * handler doesn't fan out to them itself.\n */\n private onInput(): void {\n if (this._debounceTimer !== null) {\n clearTimeout(this._debounceTimer);\n }\n\n const minChars = this._options.minChars ?? 1;\n const current = this.getValue();\n\n if (current.length < minChars) {\n this._dropdown.hide();\n\n return;\n }\n\n this._debounceTimer = setTimeout(\n () => this.querySuggestions(current),\n this._options.debounceMs ?? 200\n );\n }\n\n /**\n * Handles `keydown` events on the internal text field for dropdown navigation.\n *\n * While the dropdown is open, navigation / commit keys are forwarded\n * into the inner list's keyboard reducer via `dropdown.handleKey`.\n * The forward is gated by an allow-list (ArrowDown / ArrowUp / Enter)\n * so printable characters fall through to the TextField's `input`\n * handler without entering the list's type-ahead buffer — otherwise\n * every keystroke would double-fire (type-ahead + `querySuggestions`).\n * Escape and Tab stay on this handler so the dropdown can close\n * without the list consuming them.\n *\n * @param e - The keyboard event from the text field.\n */\n private onKeyDown(e: KeyboardEvent): void {\n if (this._dropdown.isOpen()) {\n const forward = e.key === \"ArrowDown\"\n || e.key === \"ArrowUp\"\n || e.key === \"Enter\";\n\n if (forward && this._dropdown.handleKey(e)) {\n e.preventDefault();\n this.updateActiveDescendant();\n\n return;\n }\n }\n\n switch (e.key) {\n case \"ArrowDown\":\n // Dropdown was closed — fire the query.\n e.preventDefault();\n this.querySuggestions(this.getValue());\n break;\n\n case \"Escape\":\n this._dropdown.hide();\n this._textField.focus();\n break;\n\n case \"Tab\":\n this._dropdown.hide();\n break;\n\n default:\n break;\n }\n }\n\n /**\n * Hides the dropdown after a short delay when the field loses focus.\n *\n * The delay allows a click on a dropdown item to fire before the dropdown is hidden.\n */\n private onBlur(): void {\n setTimeout(() => {\n const active = DOM.source.getActiveElement();\n const dropEl = this._dropdown.getElement();\n\n if (dropEl && DOM.source.contains(dropEl, active)) {\n return;\n }\n\n this._dropdown.hide();\n }, 150);\n }\n\n /**\n * Returns true when `candidate` matches `query` according to the current\n * `matchMode`. Handles both axes: case-sensitivity via the `*CaseSensitive`\n * variants and position via the `startsWith*` variants.\n *\n * @param candidate - The raw suggestion string to test.\n * @param query - The raw query string.\n */\n private matches(candidate: string, query: string): boolean {\n const mode = this._options.matchMode ?? 'contains';\n\n const caseSensitive = mode === 'containsCaseSensitive'\n || mode === 'startsWithCaseSensitive';\n const startsWith = mode === 'startsWith'\n || mode === 'startsWithCaseSensitive';\n\n const haystack = caseSensitive ? candidate : candidate.toLowerCase();\n const needle = caseSensitive ? query : query.toLowerCase();\n\n return startsWith ? haystack.startsWith(needle) : haystack.includes(needle);\n }\n\n /**\n * Queries the suggestion source for the given input string and shows results.\n *\n * Stale results (where the query string no longer matches the current value)\n * are discarded.\n *\n * @param query - The string to filter suggestions by.\n */\n private querySuggestions(query: string): void {\n const maxSuggestions = this._options.maxSuggestions ?? 10;\n const matchMode = this._options.matchMode ?? 'contains';\n const suggestions = this._options.suggestions;\n const store = this._options.store;\n const displayField = this._options.displayField;\n\n if (suggestions !== undefined) {\n const filtered = suggestions\n .filter(s => this.matches(s, query))\n .slice(0, maxSuggestions);\n\n if (query === this.getValue()) {\n this.showSuggestions(filtered);\n }\n\n return;\n }\n\n if (store !== undefined && displayField !== undefined) {\n const caseSensitive = matchMode === 'containsCaseSensitive'\n || matchMode === 'startsWithCaseSensitive';\n const filterType = (matchMode === 'startsWith' || matchMode === 'startsWithCaseSensitive')\n ? 'startsWith'\n : 'contains';\n\n store.clearFilter();\n store.filterBy({\n type: filterType,\n field: displayField,\n value: query,\n caseSensitive,\n });\n\n const results = store.getRecords()\n .map(r => String(r.get(displayField)))\n .slice(0, maxSuggestions);\n\n if (query === this.getValue()) {\n this.showSuggestions(results);\n }\n }\n }\n\n /**\n * Shows the dropdown with the given list, or hides it if the list is empty.\n *\n * @param list - The suggestion strings to display.\n */\n private showSuggestions(list: string[]): void {\n if (list.length === 0) {\n this._dropdown.hide();\n\n return;\n }\n\n this._dropdown.show(this._textField.getElement(true)!, list);\n this._textField.getAria().setExpanded(true);\n }\n\n /**\n * Called by the dropdown's `onHide` callback whenever it closes, including\n * via the viewport click-outside handler.\n *\n * Resets the combobox ARIA state on the input.\n */\n private onDropdownHidden(): void {\n this._textField.getAria().setExpanded(false);\n this._textField.getAria().setActiveDescendant(\"\");\n }\n\n /**\n * Syncs `aria-activedescendant` on the input with the currently focused\n * row in the dropdown's inner list.\n */\n private updateActiveDescendant(): void {\n this._textField.getAria().setActiveDescendant(this._dropdown.getFocusedRowId() ?? \"\");\n }\n\n /**\n * Called when the user selects a suggestion from the dropdown.\n *\n * Updates the field value (which fires the inherited change listeners\n * through the constructor's TextField bridge), notifies the dedicated\n * select listeners, hides the dropdown, and returns focus to the text\n * field.\n *\n * @param value - The selected suggestion string.\n */\n private onSuggestionSelected(value: string): void {\n this.setValue(value);\n\n this._selectBag.fire(\"select\", value);\n\n this._dropdown.hide();\n this._textField.focus();\n }\n}\n\nconst AutoCompleteFieldCallable = callable(AutoCompleteField);\ntype AutoCompleteFieldCallable = AutoCompleteField;\nexport {\n AutoCompleteField as _AutoCompleteField,\n AutoCompleteFieldCallable as AutoCompleteField\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Button, ButtonEvent, ButtonOptions, ClickListener } from \"~/component/button/Button.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { AutoRepeat } from \"~/core/AutoRepeat.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { chevron_up } from \"~/glyphs/solid/chevron_up.js\";\nimport { chevron_down } from \"~/glyphs/solid/chevron_down.js\";\n\n/**\n * String-literal union of the events emitted by {@link SpinButton}. Extends\n * the inherited `ButtonEvent` union with the spin-specific events.\n *\n * @category Components\n */\nexport type SpinButtonEvent = ButtonEvent | \"tick\";\n\nGlyph.register(chevron_up, chevron_down);\n\n/**\n * Construction-time options for {@link SpinButton}.\n *\n * @category Components\n */\nexport interface SpinButtonOptions extends ButtonOptions {\n /**\n * Multi-event listener bag dispatched to {@link SpinButton.on} at\n * construction time.\n */\n listeners?: {\n action?: ClickListener;\n tick?: () => void;\n };\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * Strips [`Button`](/api/component/button/classes/Button)'s chrome (border /\n * radius / insets) so the spinner sits flush in its NumberSpinner cell. The\n * `shadow`/`pressedShadow` clears can't ride here — they're `clear*` calls\n * with no equivalent option value — so they stay in the constructor body\n * guarded on the consumer options bag.\n */\nconst _defaultSpinButtonOptions: Partial<SpinButtonOptions> = {\n border: \"none\",\n borderRadius: \"0\",\n insets: new Insets(0, 0, 0, 0),\n};\n\n/**\n * A small up- or down-arrow button used inside a NumberSpinner.\n *\n * Extends Button to inherit the pressed-state appearance and click handling, then\n * adds a hold-repeat gesture: pressing and holding fires `tick` events at an\n * accelerating cadence (initial 400 ms, multiplied by 0.75 per tick, floored at 40 ms).\n *\n * @category Components\n */\nclass SpinButton extends Button<SpinButtonOptions> {\n\n private _listeners : ListenerBag<SpinButtonEvent> = new ListenerBag<SpinButtonEvent>();\n private _repeat : AutoRepeat;\n\n /**\n * @param symbol - The arrow rendered inside the button (`\"▲\"` or `\"▼\"`).\n * Mapped internally to the matching SVG glyph in the\n * framework's glyph registry.\n */\n constructor(symbol: \"▲\" | \"▼\", options?: SpinButtonOptions) {\n // Hand defaults plus the symbol-derived glyph to Button via the\n // subclass-defaults arg so they land in `_defaultOptions`. User\n // options still win because Component merges `{...defaults, ...options}`\n // at dispatch time.\n super(undefined, options, {\n ..._defaultSpinButtonOptions,\n glyph: symbol === \"▲\" ? \"chevron-up\" : \"chevron-down\",\n });\n\n this.updateSize();\n this.subscribeTheme(() => this.updateSize());\n\n // `clearShadow` / `clearPressedShadow` have no representable option\n // value (they write `box-shadow: none` and `_options.shadow = undefined`\n // — distinct from `setShadow(\"none\")` which stores the literal string),\n // so they stay in the body, guarded on the consumer bag.\n if (options?.shadow === undefined) {\n this.clearShadow();\n }\n if (options?.pressedShadow === undefined) {\n this.clearPressedShadow();\n }\n\n // Shrink the glyph so it fits the half-height (≈11 px) spin-button.\n // The 1 px upward translate compensates for sub-pixel rounding in the\n // Button's centring math: the measured input height is often odd,\n // making `(halfHeight - 8) / 2` a fractional value that the browser\n // resolves toward the bottom of the cell.\n // Pin the chevron so a theme change never re-tracks it to the title\n // line height; the 1px upward nudge corrects the centring rounding noted\n // above.\n this.pinGlyphSize(8);\n this.getGlyph()?.setTranslate(0, -1);\n\n this._repeat = new AutoRepeat({\n initialDelay: 400, // ms before the first hold-repeat tick\n decay: 0.75, // accelerate the cadence by ×0.75 each tick\n floor: 40, // fastest steady cadence, in ms\n onTick: () => this.emit(\"tick\"),\n });\n\n Event.addListener(this, \"mousedown\", () => this.onMouseDown());\n Event.addViewportListener(this, \"mouseup\", () => this.onMouseUp());\n Event.addViewportListener(this, \"mouseleave\", () => this.onMouseUp());\n\n this.applyListeners(options?.listeners);\n }\n\n /**\n * Recalculates preferred and maximum size from the native input height divided in half.\n *\n * Called at construction time and after each theme change so font-size adjustments\n * propagate to the layout hint automatically.\n */\n private updateSize(): void {\n // Mirror the host NumberSpinner's computed outer box (its own\n // `updateHeight`): one `Util.lineHeightPx()` line box, plus the inner\n // TextField's default 3 px top + bottom padding, plus the spinner's\n // 1 px top + bottom border. SpinButton can't read the spinner it lives\n // in, so the inner padding (6) and spinner border (2) are stated here as\n // the host's known chrome.\n const INNER_INPUT_VERTICAL_PADDING = 6;\n const SPINNER_VERTICAL_BORDER = 2;\n\n const fullHeight = Util.lineHeightPx() + INNER_INPUT_VERTICAL_PADDING + SPINNER_VERTICAL_BORDER;\n // Subtract the host NumberSpinner's 1 px top + bottom border so two\n // stacked buttons fit inside the spinner's inner rect — without this,\n // an odd `fullHeight` produces `2 * floor(h/2) = h - 1` which exceeds\n // the inner height `h - 2` and clips the bottom button.\n const halfHeight = Math.floor((fullHeight - SPINNER_VERTICAL_BORDER) / 2);\n\n this.setPreferredSize({ width: 18, height: halfHeight });\n // Min = preferred = max so the parent's shrink-on-overallocation\n // doesn't collapse the chevron away when the spinner cell is\n // narrow.\n this.setMinSize({ width: 18, height: halfHeight });\n this.setMaxSize({ width: 18, height: halfHeight });\n }\n\n /**\n * Registers a listener for one of this spin button's events.\n *\n * @param event - `\"action\"` is the inherited DOM-routed click;\n * `\"tick\"` fires on each logical tick (initial click plus every\n * subsequent hold-repeat tick).\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This spin button, for method chaining.\n */\n on(event: \"action\", listener: ClickListener): this;\n on(event: \"tick\", listener: () => void): this;\n on(event: SpinButtonEvent, listener: Function): this {\n if (event === \"action\") {\n Event.addListener(this, \"click\", listener as ClickListener);\n } else {\n this._listeners.add(event, listener);\n }\n\n return this;\n }\n\n /**\n * Removes a previously registered listener. The exact callback reference\n * must match.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This spin button, for method chaining.\n */\n off(event: \"action\", listener: ClickListener): this;\n off(event: \"tick\", listener: () => void): this;\n off(event: SpinButtonEvent, listener: Function): this {\n if (event === \"action\") {\n Event.removeListener(this, \"click\", listener as ClickListener);\n } else {\n this._listeners.remove(event, listener);\n }\n\n return this;\n }\n\n /**\n * Fires every listener registered for `event` (excluding `\"click\"`, which\n * routes through the framework's DOM event surface) with no arguments.\n *\n * @param event - The event to emit. Only non-DOM events (currently\n * `\"tick\"`) are dispatched through this method.\n */\n protected emit(event: \"tick\"): void;\n protected emit(event: \"tick\", ...payload: unknown[]): void {\n this._listeners.fire(event, ...payload);\n }\n\n /**\n * Fires the first tick immediately and schedules subsequent accelerating ticks.\n */\n private onMouseDown(): void {\n this._repeat.start();\n }\n\n /**\n * Cancels the hold-repeat schedule when the pointer is released or leaves the viewport.\n */\n private onMouseUp(): void {\n if (!this._repeat.isRunning()) {\n return;\n }\n\n this._repeat.stop();\n }\n}\n\nconst SpinButtonCallable = callable(SpinButton);\ntype SpinButtonCallable = SpinButton;\nexport {\n SpinButton as _SpinButton,\n SpinButtonCallable as SpinButton\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput, AbstractInputOptions } from \"~/component/input/AbstractInput.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { TextField } from \"~/component/input/TextField.js\";\nimport { SpinButton } from \"~/component/input/SpinButton.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { UNBOUNDED } from \"~/primitive/Size.js\";\nimport { Util } from \"~/core/Util.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { registerFocusWithinRing } from \"~/component/input/focusRing.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Focus indicator around the outer NumberSpinner so the ring sits at the\n// composite's chrome edge rather than around just the inner text column (the\n// helper appends the focus pseudo-element).\nregisterFocusWithinRing(\".NumberSpinner\");\n\n// Suppress the inner `TextField`'s own `.TextField:focus` box-shadow — without\n// this override, the inner shadow's right edge would paint a blue vertical\n// stripe between the text and the spin-button column (inside the NumberSpinner).\n(() => {\n new StyleRule({\n scope: \"selector\",\n name: \".NumberSpinner .TextField:focus\",\n styles: {\n boxShadow: \"none\",\n },\n });\n})();\n\n/**\n * Construction-time options for {@link NumberSpinner}.\n *\n * @category Components\n */\nexport interface NumberSpinnerOptions extends AbstractInputOptions {\n value?: number;\n min?: number;\n max?: number;\n step?: number;\n precision?: number | null;\n}\n\n/**\n * User-overridable visual defaults forwarded to `super` via the options bag.\n * The cascade in `Component`'s constructor dispatches each setter once with\n * the final value, so any field the caller supplied wins. `preferredSize` /\n * `maxSize` are *not* listed because `updateHeight` derives them from the\n * live measured input height (and re-fires on theme changes). `min`/`max`/\n * `step`/`precision`/`value`/`enabled` are late-built state — they touch\n * the inner `input`/`upBtn`/`downBtn` which don't exist yet — so they are\n * written pure by `applyOptions` and dispatched from the constructor body\n * once the children are built.\n */\nconst _defaultNumberSpinnerOptions: Partial<NumberSpinnerOptions> = {\n insets: new Insets(0, 0, 0, 0),\n border: \"1px solid var(--ts-ui-button-border, rgb(200, 200, 200))\",\n borderRadius: \"var(--ts-ui-border-radius, 4px)\",\n backgroundColor: \"var(--ts-ui-input-bg, rgb(255, 255, 255))\",\n};\n\n/**\n * A numeric input field with flanking up/down spin buttons.\n *\n * Combines a borderless [`TextField`](/api/component/input/classes/TextField) and a vertical strip of two [`SpinButton`](/api/component/input/classes/SpinButton)s\n * into a single bordered control. Supports min/max clamping, step-snapping,\n * configurable display precision, click and click-and-hold increment/decrement,\n * keyboard arrow keys, blur-on-invalid revert, and the framework `Bindable<number>`\n * interface.\n *\n * @category Components\n */\nclass NumberSpinner extends AbstractInput<number, NumberSpinnerOptions> {\n\n private _input! : TextField;\n private _upBtn! : SpinButton;\n private _downBtn!: SpinButton;\n private _btnBox! : Component;\n\n /**\n * Constructs a new NumberSpinner with default value `0`, step `1`, and unbounded min/max.\n */\n constructor(options?: NumberSpinnerOptions) {\n super(options, _defaultNumberSpinnerOptions);\n\n this._input = new TextField();\n this._input.setTextAlign(\"right\");\n this._input.setBorder(\"none\");\n this._input.setBorderRadius(\"0\");\n // Suppress the browser-default focus ring; the outer NumberSpinner's\n // `:focus-within::after` rule shows the framework focus indicator\n // instead. The matching `.NumberSpinner .TextField:focus` rule at\n // module top suppresses the inner box-shadow that would otherwise\n // paint a stripe between the text and the spin-button column.\n this._input.setOutline(\"none\");\n this._input.setText(this.formatValue(0));\n\n this._upBtn = new SpinButton(\"▲\");\n this._downBtn = new SpinButton(\"▼\");\n this._upBtn.setBorder({ borderTop: \"1px solid transparent\" });\n this._upBtn.setBorderRadius(\"0\");\n this._downBtn.setBorder({ borderTop: \"1px solid var(--ts-ui-spinner-divider, rgb(180, 180, 180))\" });\n this._downBtn.setBorderRadius(\"0\");\n\n this._btnBox = new Component();\n\n const vbox = new VBox();\n vbox.setComponentSpacing(0);\n this._btnBox.setLayoutManager(vbox);\n this._btnBox.setInsets(new Insets(0, 0, 0, 0));\n this._btnBox.addComponent(this._upBtn);\n this._btnBox.addComponent(this._downBtn);\n\n const hbox = new HBox();\n hbox.setComponentSpacing(0);\n hbox.setStretching(true);\n this.setLayoutManager(hbox);\n this.addComponent(this._input, { weight: 1 });\n this.addComponent(this._btnBox);\n\n this._upBtn.on(\"tick\", () => this.applyValue(this.getValue() + this.getStep()));\n this._downBtn.on(\"tick\", () => this.applyValue(this.getValue() - this.getStep()));\n\n Event.addListener(this._input, \"blur\", () => this.onBlur());\n Event.addListener(this._input, \"keydown\", (e: KeyboardEvent) => this.onKeyDown(e));\n\n this.updateHeight();\n this.subscribeTheme(() => this.updateHeight());\n\n this.getAria().setRole(\"spinbutton\");\n this.getAria().setValueNow(0);\n\n // Late-built state: `applyOptions` wrote these pure into `_options`\n // because the inner `input`/`upBtn`/`downBtn` didn't exist yet.\n // Dispatch now via the real setters so aria/text/disabled propagate.\n if (this._options.min !== undefined) this.setMin(this._options.min);\n if (this._options.max !== undefined) this.setMax(this._options.max);\n if (this._options.step !== undefined) this.setStep(this._options.step);\n if (this._options.precision !== undefined) this.setPrecision(this._options.precision);\n if (this._options.value !== undefined) this.setValue(this._options.value);\n if (this._options.enabled !== undefined) this.setEnabled(this._options.enabled);\n if (this._options.readOnly !== undefined) this.setReadOnly(this._options.readOnly);\n }\n\n /**\n * Applies a {@link NumberSpinnerOptions} bag. Inherited Component fields\n * cascade through `super.applyOptions`; the late-built fields\n * (`min`/`max`/`step`/`precision`/`value`/`enabled`, all of which touch\n * inner `input`/`upBtn`/`downBtn`) are written pure to `_options` here\n * and dispatched from the constructor body once children exist.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: NumberSpinnerOptions): this {\n super.applyOptions(options);\n\n if (options.min !== undefined) this._options.min = options.min;\n if (options.max !== undefined) this._options.max = options.max;\n if (options.step !== undefined) this._options.step = options.step;\n if (options.precision !== undefined) this._options.precision = options.precision;\n if (options.value !== undefined) this._options.value = options.value;\n if (options.enabled !== undefined) this._options.enabled = options.enabled;\n\n return this;\n }\n\n /**\n * Returns the offset from the top of the spinner to the inner input's text baseline.\n *\n * @returns The baseline offset in pixels, or `null` when the input has no baseline.\n */\n getBaseline(): number | null {\n return this.wrapInnerBaseline(this._input.getBaseline());\n }\n\n /**\n * Recalculates preferred and maximum height from the unified line box plus\n * the inner input's padding and the spinner's own border.\n *\n * @remarks The borderless inner `TextField` keeps its default vertical\n * padding, and its content must hold one `Util.lineHeightPx()` line box, so\n * the spinner's outer box is that inner-input box plus the spinner's own\n * vertical insets/padding/border. Reading the inner input's padding (rather\n * than the spinner's, which is zero) keeps the spinner the same height as a\n * standalone `TextField` in the same row.\n */\n private updateHeight(): void {\n // Reads the *inner* input's padding (not the spinner's own, which is\n // zero) so the spinner matches a standalone TextField's height.\n const h = Util.singleLineBoxHeight(this.getInsets(), this._input.getPadding(), this.getBorderSize());\n\n this.setPreferredSize({ width: 120, height: h });\n this.setMaxSize({ width: UNBOUNDED, height: h });\n // Min-height pinned to the single-line box so the field can't be\n // vertically compressed below one line; min-width 0 keeps it\n // horizontally flexible.\n this.setMinSize({ width: 0, height: h });\n }\n\n /**\n * Returns the current numeric value.\n *\n * @returns The most recently committed value.\n */\n getValue(): number {\n return this._options.value ?? 0;\n }\n\n /**\n * Programmatically sets the value without firing change or binding listeners.\n *\n * @param n - The new value. It is clamped to [min, max] and snapped to the configured step.\n *\n * @remarks Used by the [`Bindable`](/api/core/interfaces/Bindable) interface; does not fire listeners so that\n * binding write-backs do not trigger feedback loops.\n */\n setValue(n: number): this {\n this._setValueSilent(n);\n\n return this;\n }\n\n /**\n * Returns the lower bound for the value.\n *\n * @returns The minimum allowed value (defaults to `-Infinity`).\n */\n getMin(): number {\n return this._options.min ?? -Infinity;\n }\n\n /**\n * Sets the lower bound for the value and updates the `aria-valuemin` attribute.\n *\n * @param n - The new minimum value. Pass `-Infinity` to remove the lower bound.\n */\n setMin(n: number): this {\n this._options.min = n;\n\n this.getAria().setValueMin(isFinite(n) ? n : null);\n\n return this;\n }\n\n /**\n * Returns the upper bound for the value.\n *\n * @returns The maximum allowed value (defaults to `Infinity`).\n */\n getMax(): number {\n return this._options.max ?? Infinity;\n }\n\n /**\n * Sets the upper bound for the value and updates the `aria-valuemax` attribute.\n *\n * @param n - The new maximum value. Pass `Infinity` to remove the upper bound.\n */\n setMax(n: number): this {\n this._options.max = n;\n\n this.getAria().setValueMax(isFinite(n) ? n : null);\n\n return this;\n }\n\n /**\n * Returns the increment/decrement step.\n *\n * @returns The current step (defaults to `1`).\n */\n getStep(): number {\n return this._options.step ?? 1;\n }\n\n /**\n * Sets the increment/decrement step.\n *\n * @param n - The new step value.\n */\n setStep(n: number): this {\n this._options.step = n;\n\n return this;\n }\n\n /**\n * Returns the explicit display precision in decimal places, or `null` if it is derived from the step.\n *\n * @returns The precision, or null if not explicitly set.\n */\n getPrecision(): number | null {\n return this._options.precision ?? null;\n }\n\n /**\n * Sets the display precision (number of decimal places to render). Pass `null` to derive from the step.\n *\n * @param decimals - The number of decimal places, or `null` to derive from `step`.\n */\n setPrecision(decimals: number | null): this {\n this._options.precision = decimals;\n\n this._input.setText(this.formatValue(this.getValue()));\n\n return this;\n }\n\n /**\n * Reflects the enabled flag: toggles the inner input's native `disabled`\n * attribute, suppresses pointer events on the spin buttons, and dims the\n * whole control.\n *\n * @param enabled - The new enabled state.\n */\n protected applyEnabled(enabled: boolean): void {\n if (enabled) {\n this._input.setDisabledAttribute(false);\n this._upBtn.setPointerEvents(\"auto\");\n this._downBtn.setPointerEvents(\"auto\");\n this.clearOpacity();\n } else {\n this._input.setDisabledAttribute(true);\n this._upBtn.setPointerEvents(\"none\");\n this._downBtn.setPointerEvents(\"none\");\n this.setOpacity(0.5);\n }\n }\n\n /**\n * Forwards the read-only flag to the inner text input. The spin buttons\n * intentionally stay live so a user can still adjust the value through\n * them while typing is suppressed.\n *\n * @param value - The new read-only state.\n */\n protected applyReadOnly(value: boolean): void {\n this._input.setReadOnly(value);\n }\n\n /**\n * Applies a user-driven value: clamps, snaps to step, formats, updates the DOM, and fires listeners.\n *\n * @param n - The proposed new value (raw, before clamping and snapping).\n */\n private applyValue(n: number): void {\n if (!this.isEnabled()) {\n return;\n }\n\n const next = this.normalize(n);\n if (next === this.getValue()) {\n this._input.setText(this.formatValue(next));\n\n return;\n }\n\n this._options.value = next;\n this._input.setText(this.formatValue(next));\n this.getAria().setValueNow(next);\n\n this.notifyChange(next);\n }\n\n /**\n * Programmatic value update path. Clamps, snaps, formats, and updates the DOM without firing listeners.\n *\n * @param n - The new value.\n */\n private _setValueSilent(n: number): void {\n const next = this.normalize(n);\n\n this._options.value = next;\n this._input.setText(this.formatValue(next));\n this.getAria().setValueNow(next);\n }\n\n /**\n * Clamps `n` to `[min, max]`, snaps to the nearest step multiple, then re-quantises to the display precision.\n *\n * @param n - The raw input value.\n *\n * @returns The normalised value ready to be stored.\n */\n private normalize(n: number): number {\n let v = Math.min(this.getMax(), Math.max(this.getMin(), n));\n v = Math.round(v / this.getStep()) * this.getStep();\n v = parseFloat(v.toFixed(this.derivePrecision()));\n\n return v;\n }\n\n /**\n * Reads the input field, parses the text, and either commits via `applyValue` or reverts on parse failure.\n */\n private onBlur(): void {\n const parsed = parseFloat(this._input.getText().valueOf());\n if (isNaN(parsed)) {\n this._input.setText(this.formatValue(this.getValue()));\n\n return;\n }\n\n this.applyValue(parsed);\n }\n\n /**\n * Handles ArrowUp/ArrowDown to step the value, and Enter to commit the current text.\n *\n * @param e - The keyboard event.\n */\n private onKeyDown(e: KeyboardEvent): void {\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n this.applyValue(this.getValue() + this.getStep());\n\n return;\n }\n\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n this.applyValue(this.getValue() - this.getStep());\n\n return;\n }\n\n if (e.key === \"Enter\") {\n this.onBlur();\n }\n }\n\n /**\n * Formats a numeric value to the configured display precision.\n *\n * @param n - The value to format.\n *\n * @returns The formatted string with the appropriate decimal places.\n */\n private formatValue(n: number): string {\n return n.toFixed(this.derivePrecision());\n }\n\n /**\n * Returns the explicit precision if one is set, otherwise infers it from the configured step.\n *\n * @returns The number of decimal places to render.\n */\n private derivePrecision(): number {\n const precision = this._options.precision ?? null;\n if (precision !== null) {\n return precision;\n }\n\n const stepStr = String(this.getStep());\n const dotIdx = stepStr.indexOf(\".\");\n\n return dotIdx >= 0 ? stepStr.length - dotIdx - 1 : 0;\n }\n}\n\nconst NumberSpinnerCallable = callable(NumberSpinner);\ntype NumberSpinnerCallable = NumberSpinner;\nexport {\n NumberSpinner as _NumberSpinner,\n NumberSpinnerCallable as NumberSpinner\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput, AbstractInputOptions } from \"~/component/input/AbstractInput.js\";\nimport { Button } from \"~/component/button/Button.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { HBox } from \"~/layout/HBox.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Above this many selected files the label collapses to a \"N files\" summary\n// rather than joining every name. Empirically chosen: a handful of names still\n// reads as a useful list, but a long comma-joined run just overflows the field\n// and the count is the only legible signal past that point.\nconst FILENAME_JOIN_LIMIT = 3;\n\n// The empty-state label, shown whenever no file is selected.\nconst NO_FILE_LABEL = \"no file selected\";\n\n/**\n * Hidden native `<input type=\"file\">` owned by {@link FileField}. The native\n * control is the single source of truth for a *picked* `FileList`; this small\n * `Component` subclass owns the behavioural HTML attributes (`type`, `multiple`,\n * `accept`) through typed setters per [ARCHITECTURE.md](/ARCHITECTURE.md) so the\n * raw `setElementAttribute` seam never leaks to a call site.\n *\n * Not exported — purely an internal detail of `FileField`.\n */\nclass HiddenFileInput extends Component {\n\n // Cached native attributes, so reads return the canonical value and `init()`\n // can replay them once the element exists — `setElementAttribute` is a no-op\n // before render, so the cache is the single source of truth (same recipe as\n // TextInput's `_options.type` / `init()` replay). These live in private\n // backing fields rather than an options bag because this internal control is\n // not consumer-configurable.\n private _type: string | null = null;\n private _multiple: boolean = false;\n private _accept: string | null = null;\n\n constructor() {\n super({ tag: \"input\" });\n\n // `setDisplayed(false)`, not `setDisplay(\"none\")`: both render the element\n // `display: none`, but only the former marks the component undisplayed so\n // it drops out of `getLaidOutComponents`. Left displayed, this zero-content\n // input still claimed a layout slot in the field's HBox — and with no\n // preferred width it contributed `_defaultComponentWidth`, inflating the\n // row past the container and shrinking the button + filename label until\n // their text clipped. The native control still opens via `click()` while\n // `display: none`.\n this.setDisplayed(false);\n this.setType(\"file\");\n }\n\n /**\n * Returns the cached native `type`, or `null` before it is set.\n *\n * @returns The input type string, or `null`.\n */\n getType(): string | null {\n return this._type;\n }\n\n /**\n * Sets the native `type` attribute through the typed-setter seam, mirroring\n * [`TextInput.setType`](/api/component/input/classes/TextInput#settype) — the\n * value is cached and {@link init} replays it once the element exists.\n *\n * @param value - The input type (always `\"file\"` here).\n */\n setType(value: string): void {\n this._type = value;\n this.setElementAttribute(\"type\", value);\n }\n\n /**\n * Sets or clears the native `multiple` attribute. Cached for replay at\n * render time.\n *\n * @param value - `true` to allow multi-file selection.\n */\n setMultipleAttribute(value: boolean): void {\n this._multiple = value;\n\n if (value) {\n this.setElementAttribute(\"multiple\", \"\");\n } else {\n this.removeElementAttribute(\"multiple\");\n }\n }\n\n /**\n * Sets the native `accept` attribute (the OS picker's type filter). Cached\n * for replay at render time.\n *\n * @param value - A comma-separated `accept` token list, e.g. `\".csv,image/*\"`.\n */\n setAcceptAttribute(value: string): void {\n this._accept = value;\n this.setElementAttribute(\"accept\", value);\n }\n\n /**\n * Sets or clears the native `disabled` attribute.\n *\n * @param value - `true` to disable the control.\n */\n setDisabled(value: boolean): void {\n this.setDisabledAttribute(value);\n }\n\n /**\n * Opens the OS file picker by synthesising a click on the native input.\n */\n openPicker(): void {\n const el = this.getElement();\n\n if (el) {\n DOM.sink.click(el);\n }\n }\n\n /**\n * Returns the live `FileList` the native control currently holds, or `null`\n * before the element exists.\n *\n * @returns The native `FileList`, or `null`.\n */\n getFiles(): FileList | null {\n const el = this.getElement();\n\n return el ? DOM.source.getFiles(el) : null;\n }\n\n /**\n * Clears the native selection (`el.value = \"\"`). The browser only permits\n * programmatically *clearing* a file input, never populating it.\n */\n clearFiles(): void {\n const el = this.getElement();\n\n if (el) {\n DOM.sink.setValue(el, \"\");\n }\n }\n\n /**\n * Replays the cached behavioural attributes onto the freshly created element.\n * `setElementAttribute` now caches into the base class's `_elementAttributes`\n * map and replays it from `Component.init()`, so this replay is redundant —\n * kept anyway, matching the same replay TextInput's `init()` performs.\n *\n * @param element - The element being initialised, when provided by the caller.\n *\n * @returns This component, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n const el = element ?? this.getElement();\n\n if (!el) {\n return this;\n }\n\n if (this._type !== null) {\n DOM.sink.apply(el, { setAttr: { type: this._type } });\n }\n\n if (this._multiple) {\n DOM.sink.apply(el, { setAttr: { multiple: \"\" } });\n }\n\n if (this._accept !== null) {\n DOM.sink.apply(el, { setAttr: { accept: this._accept } });\n }\n\n return this;\n }\n}\n\n/**\n * Construction-time options for {@link FileField}.\n *\n * @category Components\n */\nexport interface FileFieldOptions extends AbstractInputOptions {\n /** Allow selecting more than one file. Maps to the native `multiple` attribute. Default false. */\n multiple?: boolean;\n /** Native file-type filter, e.g. `\".sql,.csv\"` or `\"image/*\"`. Maps to the native `accept` attribute. */\n accept?: string;\n /** Trigger button label. Defaults to \"Choose file…\" / \"Choose files…\" per `multiple`. */\n buttonText?: string;\n}\n\n/**\n * A styled file-selection control: a trigger [`Button`](/api/component/button/classes/Button)\n * that opens the OS file picker, plus a filename label. Wraps a hidden native\n * `<input type=\"file\">` and implements [`Bindable<File[]>`](/api/core/interfaces/Bindable).\n *\n * The native input is the source of truth for a *picked* `FileList`; the field\n * reads it on the native `change` event and fans the new value out through\n * {@link AbstractInput}'s `change` / `binding` listeners. A separate cache holds\n * files handed in by a [`FileDropZone`](/api/component/input/classes/FileDropZone)\n * drop, which the browser cannot push back into the native control.\n *\n * @remarks **Write contract is clear-only.** The browser forbids assigning a\n * non-empty `FileList` to a file input outside a user gesture, so\n * {@link setValue} honours only the empty case (it clears the control). A\n * non-empty argument is a no-op plus one `console.warn`; it never throws, so a\n * record load through [`Binding`](/api/core/classes/Binding) stays\n * side-effect-tolerant. {@link getValue} always returns the live `File[]`, so\n * the read side of the binding contract is complete.\n *\n * @category Components\n */\nclass FileField<TOptions extends FileFieldOptions = FileFieldOptions>\n extends AbstractInput<File[], TOptions>\n{\n private _button: Button;\n private _label: Text;\n private _input: HiddenFileInput;\n\n // Files supplied by a FileDropZone drop. The browser cannot push these into\n // the native `<input>`, so they live here and supersede the (empty) native\n // FileList in `getValue`. Cleared and superseded by the next native `change`.\n private _droppedFiles: File[] = [];\n\n /**\n * Constructs a FileField.\n *\n * @param options - Optional construction-time options.\n *\n * @remarks The parameter is typed as the concrete {@link FileFieldOptions}\n * rather than the class's `TOptions` parameter so that passing an options\n * literal (e.g. `new FileField({ accept: \".png\" })`) cannot narrow `TOptions`\n * to that literal — which would type the instance as `FileField<{ accept:\n * \".png\" }>` and fail the weak-type assignability check when it is later used\n * as a base `Component` (e.g. `container.addComponent(field)`). `TOptions`\n * stays at its `FileFieldOptions` default.\n */\n constructor(options?: FileFieldOptions) {\n super({ ...(options ?? {}) } as TOptions);\n\n this.setLayoutManager(new HBox());\n\n this._button = new Button(this.defaultButtonText());\n this._button.on(\"action\", () => this.openPicker());\n\n this._label = new Text(NO_FILE_LABEL, { truncate: true });\n this._label.setForegroundColor(\"var(--ts-ui-form-text, inherit)\");\n\n this._input = new HiddenFileInput();\n Event.addListener(this._input, \"change\", () => this.onNativeChange());\n\n super.addComponent(this._button);\n // The label is the flexible cell: the button keeps its preferred width\n // while the label absorbs the remaining row width and truncates a long\n // filename, instead of both shrinking proportionally and clipping the\n // button's own text.\n super.addComponent(this._label, { weight: 1 });\n super.addComponent(this._input);\n\n if (this._options.multiple !== undefined) {\n this.setMultiple(this._options.multiple);\n }\n\n if (this._options.accept !== undefined) {\n this.setAccept(this._options.accept);\n }\n\n if (this._options.buttonText !== undefined) {\n this._button.setText(this._options.buttonText);\n }\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n }\n\n /**\n * Caches the {@link FileFieldOptions} fields on `_options` so the\n * constructor body can dispatch them once the children exist. The setters\n * are intentionally not dispatched here (the children aren't built yet\n * during `super()`), matching the [`AbstractInput`](/api/component/input/classes/AbstractInput)\n * cascade rule.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This component, for method chaining.\n */\n protected applyOptions(options: TOptions): this {\n super.applyOptions(options);\n\n if (options.multiple !== undefined) this._options.multiple = options.multiple;\n if (options.accept !== undefined) this._options.accept = options.accept;\n if (options.buttonText !== undefined) this._options.buttonText = options.buttonText;\n\n return this;\n }\n\n /**\n * The trigger label used when the caller supplies none, pluralised per the\n * `multiple` flag.\n *\n * @returns \"Choose files…\" when multi-select is on, else \"Choose file…\".\n */\n private defaultButtonText(): string {\n return this.isMultiple() ? \"Choose files…\" : \"Choose file…\";\n }\n\n /**\n * Opens the OS file picker. Bound as the trigger button's `action` handler.\n */\n private openPicker(): void {\n if (!this.isEnabled() || this.isReadOnly()) {\n return;\n }\n\n this._input.openPicker();\n }\n\n /**\n * Reads the native `FileList` on every native `change`, supersedes any stale\n * drop cache, relabels, and fans the new value out. Bound as the hidden\n * input's `change` handler.\n */\n private onNativeChange(): void {\n this._droppedFiles = [];\n\n const files = this.getValue();\n\n this.updateLabel(files);\n this.notifyChange(files);\n }\n\n /**\n * Stores a `FileList` dropped onto a [`FileDropZone`](/api/component/input/classes/FileDropZone)\n * as this field's live value, relabels, and notifies. The browser cannot\n * push these into the hidden `<input>`, so they are held in the drop cache\n * and returned by {@link getValue} until the next native `change`.\n *\n * Package-internal: called only by `FileDropZone` via the `_FileField`\n * export. Not part of the public consumer surface.\n *\n * @param files - The dropped `FileList`.\n */\n acceptDroppedFiles(files: FileList): void {\n this._droppedFiles = Array.from(files);\n\n const value = this.getValue();\n\n this.updateLabel(value);\n this.notifyChange(value);\n }\n\n /**\n * Returns the currently selected files: the drop cache when populated,\n * otherwise the live native `FileList`. Satisfies\n * [`Bindable`](/api/core/interfaces/Bindable)'s read side.\n *\n * @returns The selected files (possibly empty).\n */\n getValue(): File[] {\n if (this._droppedFiles.length > 0) {\n return this._droppedFiles;\n }\n\n const files = this._input.getFiles();\n\n return files ? Array.from(files) : [];\n }\n\n /**\n * Clears the selection when given an empty array; otherwise a no-op plus one\n * `console.warn`. See the class remarks for why the write contract is\n * clear-only (the browser security model forbids populating a file input).\n *\n * @param value - An empty array to clear; a non-empty array is rejected.\n *\n * @returns This component, for method chaining.\n */\n setValue(value: File[]): this {\n if (value && value.length > 0) {\n console.warn(\n \"FileField '\" + this.getId() + \"' setValue ignored a non-empty File[]: the browser \"\n + \"security model forbids programmatically populating a file input. Only setValue([]) \"\n + \"(clearing) is supported.\",\n );\n\n return this;\n }\n\n return this.clearValue();\n }\n\n /**\n * Clears the native selection and the drop cache, relabels, and notifies.\n * Convenience alias matching the\n * [`Toggle.clearValue()`](/api/component/input/classes/Toggle#clearvalue)\n * convention.\n *\n * @returns This component, for method chaining.\n */\n clearValue(): this {\n this._droppedFiles = [];\n this._input.clearFiles();\n\n const value = this.getValue();\n\n this.updateLabel(value);\n this.notifyChange(value);\n\n return this;\n }\n\n /**\n * Returns whether multi-file selection is enabled.\n *\n * @returns `true` when the native `multiple` attribute is set.\n */\n isMultiple(): boolean {\n return this._options.multiple ?? false;\n }\n\n /**\n * Toggles multi-file selection (the native `multiple` attribute). When the\n * caller never set an explicit button label, the default label re-pluralises\n * to match.\n *\n * @param value - `true` to allow selecting more than one file.\n *\n * @returns This component, for method chaining.\n */\n setMultiple(value: boolean): this {\n this._options.multiple = !!value;\n this._input.setMultipleAttribute(this._options.multiple);\n\n if (this._options.buttonText === undefined) {\n this._button.setText(this.defaultButtonText());\n }\n\n return this;\n }\n\n /**\n * Returns the native `accept` filter, or `null` when unset.\n *\n * @returns The `accept` token list, or `null`.\n */\n getAccept(): string | null {\n return this._options.accept ?? null;\n }\n\n /**\n * Sets the native `accept` attribute — the OS picker's type filter (it does\n * not validate drag-dropped files).\n *\n * @param value - A comma-separated `accept` token list, e.g. `\".csv,image/*\"`.\n *\n * @returns This component, for method chaining.\n */\n setAccept(value: string): this {\n this._options.accept = value;\n this._input.setAcceptAttribute(value);\n\n return this;\n }\n\n /**\n * Reflects the filename label for the current selection: the empty-state\n * text, the single file name, the joined names, or a \"N files\" summary past\n * {@link FILENAME_JOIN_LIMIT}.\n */\n private updateLabel(files: File[]): void {\n if (files.length === 0) {\n this._label.setText(NO_FILE_LABEL);\n } else if (files.length === 1) {\n this._label.setText(files[0].name);\n } else if (files.length > FILENAME_JOIN_LIMIT) {\n this._label.setText(files.length + \" files\");\n } else {\n this._label.setText(files.map(f => f.name).join(\", \"));\n }\n }\n\n /**\n * Reflects the enabled flag on the trigger button and the hidden input.\n */\n protected applyEnabled(value: boolean): void {\n this._button.setEnabled(value);\n this._input.setDisabled(!value);\n }\n\n /**\n * Reflects the read-only flag. A read-only field stays mounted but refuses\n * to open the picker (guarded in {@link openPicker}); the trigger button is\n * disabled so it reads as inert.\n */\n protected applyReadOnly(value: boolean): void {\n this._button.setEnabled(!value && this.isEnabled());\n this.getAria().setReadOnly(value);\n }\n}\n\nconst FileFieldCallable = callable(FileField);\ntype FileFieldCallable<TOptions extends FileFieldOptions = FileFieldOptions> = FileField<TOptions>;\nexport {\n FileField as _FileField,\n FileFieldCallable as FileField,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput } from \"~/component/input/AbstractInput.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { FileFieldOptions, _FileField } from \"~/component/input/FileField.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n// Resting and active border / background, driven by the per-control\n// `--ts-ui-filedropzone-*` tokens. Kept as module consts so `setActive` swaps\n// the whole pair atomically rather than re-deriving strings per drag event.\nconst RESTING_BORDER = \"var(--ts-ui-filedropzone-border, 2px dashed rgba(80, 140, 240, 0.40))\";\nconst RESTING_BG = \"var(--ts-ui-filedropzone-bg, rgba(80, 140, 240, 0.06))\";\nconst ACTIVE_BORDER = \"var(--ts-ui-filedropzone-active-border, 2px dashed rgba(80, 140, 240, 0.80))\";\nconst ACTIVE_BG = \"var(--ts-ui-filedropzone-active-bg, rgba(80, 140, 240, 0.18))\";\n\n// Default instructional text shown inside the zone when the caller supplies none.\nconst DEFAULT_PROMPT = \"Drop files here or click to browse\";\n\n/**\n * Construction-time options for {@link FileDropZone}.\n *\n * @category Components\n */\nexport interface FileDropZoneOptions extends FileFieldOptions {\n /** Instructional text shown inside the zone. Default \"Drop files here or click to browse\". */\n promptText?: string;\n}\n\n/**\n * A bordered drag-and-drop surface that *composes* a\n * [`FileField`](/api/component/input/classes/FileField) and additionally accepts\n * OS file drops through the HTML5 `DataTransfer.files` API. Implements\n * [`Bindable<File[]>`](/api/core/interfaces/Bindable) by delegating its value to\n * the inner field and re-emitting the field's `change` / `binding` events.\n *\n * @remarks OS file drops arrive as real HTML5 `dragover` / `drop` DOM events and\n * are wired through {@link Event.addSubtreeListener} — deliberately *not* the\n * framework's internal pointer-based component-drag system, which never sees a\n * `DataTransfer`. The native `accept` filter on the inner field constrains the\n * OS picker only; dropped files are not type-validated (out of scope). The same\n * clear-only write contract as `FileField` applies — see its remarks.\n *\n * @category Components\n */\nclass FileDropZone<TOptions extends FileDropZoneOptions = FileDropZoneOptions>\n extends AbstractInput<File[], TOptions>\n{\n private _prompt: Text;\n private _field: _FileField;\n\n // Nesting depth of active OS drags over this zone. `dragenter` / `dragleave`\n // fire per child element as the cursor crosses the composed field's button\n // and label, so a single boolean would strobe the highlight; counting\n // enter/leave pairs keeps the active state stable until the cursor truly\n // leaves the zone (depth returns to 0).\n private _dragDepth: number = 0;\n\n /**\n * Constructs a FileDropZone.\n *\n * @param options - Optional construction-time options.\n *\n * @remarks The parameter is typed as the concrete {@link FileDropZoneOptions}\n * rather than the class's `TOptions` parameter so that passing an options\n * literal (e.g. `new FileDropZone({ accept: \".png\" })`) cannot narrow\n * `TOptions` to that literal — which would type the instance as\n * `FileDropZone<{ accept: \".png\" }>` and fail the weak-type assignability\n * check when it is later used as a base `Component` (e.g.\n * `container.addComponent(zone)`). `TOptions` stays at its\n * `FileDropZoneOptions` default.\n */\n constructor(options?: FileDropZoneOptions) {\n super({ ...(options ?? {}) } as TOptions);\n\n this.setLayoutManager(new VBox());\n\n this._prompt = new Text(this._options.promptText ?? DEFAULT_PROMPT);\n this._prompt.setForegroundColor(\"var(--ts-ui-form-text, inherit)\");\n\n this._field = new _FileField(this.fieldOptions());\n this._field.on(\"change\", (value: File[]) => this.notifyChange(value));\n\n super.addComponent(this._prompt);\n super.addComponent(this._field);\n\n this.setActive(false);\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n\n Event.addSubtreeListener(this, \"dragenter\", (e: DragEvent) => this.onDragEnter(e));\n Event.addSubtreeListener(this, \"dragover\", (e: DragEvent) => this.onDragOver(e));\n Event.addSubtreeListener(this, \"dragleave\", (e: DragEvent) => this.onDragLeave(e));\n Event.addSubtreeListener(this, \"drop\", (e: DragEvent) => this.onDrop(e));\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n }\n\n /**\n * Caches the {@link FileDropZoneOptions} fields on `_options` so the\n * constructor body can read them after `super()`.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This component, for method chaining.\n */\n protected applyOptions(options: TOptions): this {\n super.applyOptions(options);\n\n if (options.promptText !== undefined) this._options.promptText = options.promptText;\n\n return this;\n }\n\n /**\n * Builds the option bag forwarded to the composed inner field — the\n * file-selection fields (`multiple` / `accept` / `buttonText`) pass through;\n * the zone-only `promptText` does not.\n *\n * @returns The inner [`FileField`](/api/component/input/classes/FileField) options.\n */\n private fieldOptions(): FileFieldOptions {\n return {\n multiple: this._options.multiple,\n accept: this._options.accept,\n buttonText: this._options.buttonText,\n };\n }\n\n /**\n * Swaps the resting / active border + background pair. Active is the wash\n * shown while a valid OS file drag hovers.\n *\n * @param active - `true` for the hovering-drag state, `false` for resting.\n */\n private setActive(active: boolean): void {\n this.setBorder(active ? ACTIVE_BORDER : RESTING_BORDER);\n this.setBackgroundColor(active ? ACTIVE_BG : RESTING_BG);\n }\n\n /**\n * Increments the drag-depth counter and lights the zone on the first enter.\n * Bound as the subtree `dragenter` handler.\n */\n private onDragEnter(e: DragEvent): void {\n e.preventDefault();\n this._dragDepth++;\n\n if (this._dragDepth === 1) {\n this.setActive(true);\n }\n }\n\n /**\n * Signals a valid drop target by calling `preventDefault`. The `FileList` is\n * only populated on `drop`, so nothing is read here. Bound as the subtree\n * `dragover` handler.\n */\n private onDragOver(e: DragEvent): void {\n e.preventDefault();\n }\n\n /**\n * Decrements the drag-depth counter and dims the zone once the cursor truly\n * leaves (depth back to 0). Bound as the subtree `dragleave` handler.\n */\n private onDragLeave(e: DragEvent): void {\n e.preventDefault();\n this._dragDepth = Math.max(0, this._dragDepth - 1);\n\n if (this._dragDepth === 0) {\n this.setActive(false);\n }\n }\n\n /**\n * Accepts the dropped `FileList`, resets the highlight, and hands the files\n * to the inner field so one code path owns formatting + notify. Bound as the\n * subtree `drop` handler.\n */\n private onDrop(e: DragEvent): void {\n e.preventDefault();\n this._dragDepth = 0;\n this.setActive(false);\n\n if (!this.isEnabled() || this.isReadOnly()) {\n return;\n }\n\n const files = e.dataTransfer?.files;\n\n if (!files || files.length === 0) {\n return;\n }\n\n this._field.acceptDroppedFiles(files);\n }\n\n /**\n * Returns the selected files, delegating to the inner field.\n *\n * @returns The selected files (possibly empty).\n */\n getValue(): File[] {\n return this._field.getValue();\n }\n\n /**\n * Delegates to the inner field's clear-only {@link _FileField.setValue}.\n *\n * @param value - An empty array to clear; a non-empty array is rejected.\n *\n * @returns This component, for method chaining.\n */\n setValue(value: File[]): this {\n this._field.setValue(value);\n\n return this;\n }\n\n /**\n * Clears the selection, delegating to the inner field's `clearValue`.\n *\n * @returns This component, for method chaining.\n */\n clearValue(): this {\n this._field.clearValue();\n\n return this;\n }\n\n /**\n * Reflects the enabled flag on the inner field.\n */\n protected applyEnabled(value: boolean): void {\n this._field.setEnabled(value);\n }\n\n /**\n * Reflects the read-only flag on the inner field.\n */\n protected applyReadOnly(value: boolean): void {\n this._field.setReadOnly(value);\n }\n}\n\nconst FileDropZoneCallable = callable(FileDropZone);\ntype FileDropZoneCallable<TOptions extends FileDropZoneOptions = FileDropZoneOptions> = FileDropZone<TOptions>;\nexport {\n FileDropZone as _FileDropZone,\n FileDropZoneCallable as FileDropZone,\n};\n"],"mappings":"+mCASA,EAAM,SAAS,CAAQ,EA+BvB,IAAM,UAAN,cAAwB,CAAgE,CAEpF,YAAY,EAA4B,CACpC,MAAM,CAAO,EAKb,KAAK,QAAQ,SAAS,UAAU,EAK5B,KAAK,SAAS,QAAU,IAAA,IACxB,KAAK,SAAS,KAAK,SAAS,KAAK,CAEzC,CASA,aAAuB,EAAiC,CAepD,OAdA,MAAM,aAAa,CAAO,EAEtB,EAAQ,QAAU,IAAA,KAClB,KAAK,SAAS,MAAQ,EAAQ,OAG9B,EAAQ,UAAY,IAAA,KACpB,KAAK,SAAS,QAAU,EAAQ,SAGhC,EAAQ,UAAY,IAAA,KACpB,KAAK,SAAS,QAAU,EAAQ,SAG7B,IACX,CAQA,YAAsB,EAAoB,CAKtC,MAAO,GAJG,EAAK,YAIL,EAAE,GAHF,OAAO,EAAK,SAAS,EAAI,CAAC,CAAC,CAAC,SAAS,EAAG,GAGnC,EAAE,GAFP,OAAO,EAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAG,GAEzB,GACxB,CASA,SAAmB,EAA0B,CACzC,IAAM,EAAI,IAAI,KAAK,EAAM,WAAW,EAEpC,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAI,KAAO,CACvC,CAOA,gBAA+C,CAC3C,OAAO,IAAI,EACP,GAAQ,KAAK,mBAAmB,CAAI,EACpC,CACI,QAAS,KAAK,SAAS,SAAW,KAClC,QAAS,KAAK,SAAS,SAAW,IACtC,CACJ,CACJ,CAUA,mBAA6B,EAAkB,CAC3C,KAAK,SAAS,CAAI,EAClB,KAAK,cAAc,EACnB,KAAK,OAAO,MAAM,EAClB,EAAM,UAAU,KAAK,OAAQ,OAAO,CACxC,CAMA,mBAAsC,CAClC,MAAO,IACX,CACJ,EAEM,EAAoB,EAAS,SAAS,EC5I5C,EAAM,SAAS,CAAQ,EAiCvB,IAAM,cAAN,cAA4B,CAAwE,CAEhG,YAAY,EAAgC,CACxC,MAAM,CAAO,EAKb,KAAK,QAAQ,SAAS,UAAU,EAM5B,KAAK,SAAS,cAAgB,IAAA,KAC9B,KAAK,aAAe,KAAK,SAAS,aAGlC,KAAK,SAAS,QAAU,IAAA,IACxB,KAAK,SAAS,KAAK,SAAS,KAAK,CAEzC,CASA,aAAuB,EAAqC,CAmBxD,OAlBA,MAAM,aAAa,CAAO,EAEtB,EAAQ,cAAgB,IAAA,KACxB,KAAK,SAAS,YAAc,EAAQ,aAGpC,EAAQ,QAAU,IAAA,KAClB,KAAK,SAAS,MAAQ,EAAQ,OAG9B,EAAQ,UAAY,IAAA,KACpB,KAAK,SAAS,QAAU,EAAQ,SAGhC,EAAQ,UAAY,IAAA,KACpB,KAAK,SAAS,QAAU,EAAQ,SAG7B,IACX,CASA,YAAsB,EAAoB,CACtC,IAAM,EAAK,EAAK,YAAY,EACtB,EAAK,OAAO,EAAK,SAAS,EAAI,CAAC,CAAC,CAAC,SAAS,EAAG,GAAG,EAChD,EAAK,OAAO,EAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAG,GAAG,EAC3C,EAAK,OAAO,EAAK,SAAS,CAAC,CAAC,CAAC,SAAS,EAAG,GAAG,EAC5C,EAAK,OAAO,EAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAG,GAAG,EAQpD,OANI,KAAK,aAGE,GAAG,EAAE,GAAG,EAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAG,GAFxB,OAAO,EAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAG,GAEX,IAGlC,GAAG,EAAE,GAAG,EAAG,GAAG,EAAE,GAAG,EAAE,GAAG,GACnC,CAQA,SAAmB,EAA0B,CAIzC,GAAM,CAAC,EAAU,GAAY,EAAI,KAAK,CAAC,CAAC,MAAM,KAAK,EACnD,GAAI,CAAC,GAAY,CAAC,EACd,OAAO,KAGX,IAAM,EAAI,IAAI,KAAK,GAAG,EAAS,GAAG,GAAU,EAE5C,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAI,KAAO,CACvC,CAOA,gBAAmD,CAC/C,OAAO,IAAI,EACP,GAAQ,KAAK,mBAAmB,CAAI,EACpC,CACI,YAAa,KAAK,aAClB,QAAa,KAAK,SAAS,SAAW,KACtC,QAAa,KAAK,SAAS,SAAW,IAC1C,CACJ,CACJ,CASA,mBAA6B,EAAkB,CAC3C,KAAK,SAAS,CAAI,EAClB,EAAM,UAAU,KAAK,OAAQ,OAAO,CACxC,CAOA,mBAAsC,CAClC,MAAO,IACX,CACJ,EAEM,EAAwB,EAAS,aAAa,ECtJ9C,EAA8D,CAChE,QAAiB,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,EACtC,OAAiB,OACjB,gBAAiB,4CACjB,gBAAiB,gCACrB,EASM,cAAN,cAA4B,CAAgC,CAExD,YAAY,EAAgC,CACxC,MAAM,EAAS,CAA4B,EAE3C,KAAK,aAAa,EAClB,KAAK,mBAAqB,KAAK,aAAa,CAAC,EAE7C,KAAK,QAAQ,UAAU,EAEnB,KAAK,SAAS,OAAS,IAAA,IACvB,KAAK,QAAQ,UAAU,EAGvB,KAAK,SAAS,eAAiB,IAAA,IAC/B,KAAK,gBAAgB,GAAS,YAAc,eAAiB,kBAAkB,CAEvF,CAYA,cAA6B,CACzB,IAAM,EAAI,EAAK,oBAAoB,KAAK,UAAU,EAAG,KAAK,WAAW,EAAG,KAAK,cAAc,CAAC,EAE5F,KAAK,iBAAiB,CAAE,MAAO,IAAK,OAAQ,CAAE,CAAC,EAC/C,KAAK,WAAW,CAAE,cAAgC,OAAQ,CAAE,CAAC,EAI7D,KAAK,WAAW,CAAE,MAAO,EAAG,OAAQ,CAAE,CAAC,CAC3C,CACJ,EAEM,EAAwB,EAAS,aAAa,EC1D9C,GAA8D,CAChE,OAAiB,OACjB,QAAiB,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,EACtC,gBAAiB,4CACjB,gBAAiB,gCACrB,EAWM,cAAN,cAA4B,CAAgC,CAExD,YAAY,EAAgC,CACxC,MAAM,EAAS,EAA4B,EAE3C,KAAK,aAAa,EAClB,KAAK,mBAAqB,KAAK,aAAa,CAAC,EAE7C,KAAK,QAAQ,MAAM,EAEf,KAAK,SAAS,OAAS,IAAA,IACvB,KAAK,QAAQ,UAAU,EAGvB,KAAK,SAAS,eAAiB,IAAA,IAC/B,KAAK,gBAAgB,GAAS,MAAQ,QAAU,UAAU,CAElE,CAYA,cAA6B,CACzB,IAAM,EAAI,EAAK,oBAAoB,KAAK,UAAU,EAAG,KAAK,WAAW,EAAG,KAAK,cAAc,CAAC,EAE5F,KAAK,iBAAiB,CAAE,MAAO,IAAK,OAAQ,CAAE,CAAC,EAC/C,KAAK,WAAW,CAAE,cAAgC,OAAQ,CAAE,CAAC,EAI7D,KAAK,WAAW,CAAE,MAAO,EAAG,OAAQ,CAAE,CAAC,CAC3C,CAEJ,EAEM,EAAwB,EAAS,aAAa,ECvD9C,EAAoD,CACtD,IAAiB,WACjB,OAAiB,OAUjB,SAAiB,OACjB,QAAiB,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,EACtC,cAAiB,CAAE,MAAO,IAAK,OAAQ,GAAI,EAC3C,QAAiB,CAAE,MAAO,IAAK,OAAQ,GAAI,EAC3C,gBAAiB,4CACjB,gBAAiB,gCACrB,EASM,SAAN,cAAuB,CAA2B,CAE9C,YAAY,EAAe,GAAI,EAA2B,CAKtD,MACI,EAAO,CAAE,OAAM,GAAG,CAAQ,EAAI,EAC9B,CACJ,EAOA,KAAK,mBAAmB,CAAE,OAAQ,MAAO,CAAC,CAC9C,CAQA,aAAuB,EAAgC,CAenD,OAdA,MAAM,aAAa,CAAO,EAEtB,EAAQ,OAAS,IAAA,IACjB,KAAK,QAAQ,EAAQ,IAAI,EAGzB,EAAQ,OAAS,IAAA,IACjB,KAAK,QAAQ,EAAQ,IAAI,EAGzB,EAAQ,OAAS,IAAA,IACjB,KAAK,QAAQ,EAAQ,IAAI,EAGtB,IACX,CAOA,SAAyB,CACrB,OAAO,KAAK,SAAS,MAAQ,IACjC,CASA,QAAQ,EAAqB,CAIzB,MAHA,MAAK,SAAS,KAAO,EACrB,KAAK,oBAAoB,OAAQ,OAAO,CAAK,CAAC,EAEvC,IACX,CAOA,WAAkB,CAQd,OAPI,KAAK,SAAS,OAAS,IAAA,GAChB,MAGX,KAAK,SAAS,KAAO,IAAA,GACrB,KAAK,uBAAuB,MAAM,EAE3B,KACX,CAOA,SAAyB,CACrB,OAAO,KAAK,SAAS,MAAQ,IACjC,CASA,QAAQ,EAAqB,CAIzB,MAHA,MAAK,SAAS,KAAO,EACrB,KAAK,oBAAoB,OAAQ,OAAO,CAAK,CAAC,EAEvC,IACX,CAOA,WAAkB,CAQd,OAPI,KAAK,SAAS,OAAS,IAAA,GAChB,MAGX,KAAK,SAAS,KAAO,IAAA,GACrB,KAAK,uBAAuB,MAAM,EAE3B,KACX,CAOA,SAAyB,CACrB,OAAO,KAAK,SAAS,MAAQ,IACjC,CASA,QAAQ,EAAqB,CAIzB,MAHA,MAAK,SAAS,KAAO,EACrB,KAAK,oBAAoB,OAAQ,CAAK,EAE/B,IACX,CAOA,WAAkB,CAQd,OAPI,KAAK,SAAS,OAAS,IAAA,GAChB,MAGX,KAAK,SAAS,KAAO,IAAA,GACrB,KAAK,uBAAuB,MAAM,EAE3B,KACX,CAgBA,aAA6B,CACzB,OAAO,KAAK,kBAAkB,EAAK,oBAAoB,CAAC,CAC5D,CAEA,KAAe,EAAwB,CACnC,MAAM,KAAK,CAAO,EAElB,IAAM,EAAK,GAAW,KAAK,WAAW,EActC,OAZI,KAAK,SAAS,OAAS,IAAA,IACvB,EAAI,KAAK,MAAM,EAAI,CAAE,QAAS,CAAE,KAAM,OAAO,KAAK,SAAS,IAAI,CAAE,CAAE,CAAC,EAGpE,KAAK,SAAS,OAAS,IAAA,IACvB,EAAI,KAAK,MAAM,EAAI,CAAE,QAAS,CAAE,KAAM,OAAO,KAAK,SAAS,IAAI,CAAE,CAAE,CAAC,EAGpE,KAAK,SAAS,OAAS,IAAA,IACvB,EAAI,KAAK,MAAM,EAAI,CAAE,QAAS,CAAE,KAAM,KAAK,SAAS,IAAK,CAAE,CAAC,EAGzD,IACX,CAEJ,EAEM,EAAmB,EAAS,QAAQ,EC9NpC,OAAN,cACY,CACZ,CACI,OACA,OAOA,YAAY,EAAoB,CAC5B,MAAM,CAAE,GAAI,GAAW,CAAC,CAAG,CAAa,EAExC,KAAK,iBAAiB,IAAI,CAAM,EAEhC,KAAK,OAAS,IAAI,EAClB,KAAK,OAAO,mBAAmB,sDAAsD,EACrF,KAAK,OAAO,gBAAgB,OAAO,EACnC,KAAK,OAAO,iBAAiB,CAAE,MAAO,GAAI,OAAQ,EAAG,CAAC,EAItD,KAAK,OAAO,WAAW,CAAE,MAAO,GAAI,OAAQ,EAAG,CAAC,EAChD,KAAK,OAAO,WAAW,CAAE,MAAO,GAAI,OAAQ,EAAG,CAAC,EAKhD,KAAK,OAAO,UAAU,SAAS,EAE/B,KAAK,OAAS,IAAI,EAClB,KAAK,OAAO,mBAAmB,kDAAkD,EACjF,KAAK,OAAO,gBAAgB,OAAO,EACnC,KAAK,OAAO,iBAAiB,CAAE,MAAO,GAAI,OAAQ,EAAG,CAAC,EACtD,KAAK,OAAO,WAAW,CAAE,MAAO,GAAI,OAAQ,EAAG,CAAC,EAGhD,KAAK,OAAO,QAAQ,CAAE,MAAO,GAAI,OAAQ,EAAG,CAAC,EAC7C,KAAK,OAAO,UAAU,+BAA+B,EACrD,KAAK,OAAO,KAAK,CAAC,EAClB,KAAK,OAAO,KAAK,CAAC,EAElB,KAAK,OAAO,iBAAiB,MAAM,EAE9B,EAAU,gBAAgB,IAC3B,KAAK,OAAO,cAAc,0BAA0B,EACpD,KAAK,OAAO,cAAc,iCAAiC,GAG/D,KAAK,OAAO,aAAa,KAAK,MAAM,EACpC,MAAM,aAAa,KAAK,MAAM,EAE9B,KAAK,QAAQ,CAAC,CAAC,QAAQ,QAAQ,EAC/B,KAAK,QAAQ,CAAC,CAAC,YAAY,CAAC,EAC5B,KAAK,QAAQ,CAAC,CAAC,WAAW,EAAK,EAE/B,KAAK,WAAW,MAAM,EAStB,EAAM,YAAY,KAAK,OAAQ,YAAe,KAAK,oBAAoB,CAAC,EACxE,KAAK,gBAAgB,EAIjB,KAAK,SAAS,QAAU,IAAA,IACxB,KAAK,WAAW,KAAK,SAAS,KAAK,EAGnC,KAAK,SAAS,QAAU,IAAA,IACxB,KAAK,WAAW,KAAK,SAAS,KAAK,EAGnC,KAAK,SAAS,UAAY,IAAA,IAC1B,KAAK,aAAa,KAAK,SAAS,OAAO,EAGvC,KAAK,SAAS,WAAa,IAAA,IAC3B,KAAK,cAAc,KAAK,SAAS,QAAQ,CAEjD,CAYA,aAAuB,EAAyB,CAQ5C,OAPA,MAAM,aAAa,CAAO,EAEtB,EAAQ,QAAa,IAAA,KAAW,KAAK,SAAS,MAAW,EAAQ,OACjE,EAAQ,QAAa,IAAA,KAAW,KAAK,SAAS,MAAW,EAAQ,OACjE,EAAQ,UAAa,IAAA,KAAW,KAAK,SAAS,QAAW,EAAQ,SACjE,EAAQ,WAAa,IAAA,KAAW,KAAK,SAAS,SAAW,EAAQ,UAE9D,IACX,CAMA,UAA2B,CACvB,KAAK,SAAS,CAAC,KAAK,SAAS,CAAC,CAClC,CAOA,uBAA6C,CACzC,OAAO,KAAK,MAChB,CAQA,gBAAqC,CACjC,MAAO,CAAC,IAAK,OAAO,CACxB,CAOA,UAAoB,CAChB,OAAO,KAAK,SAAS,OAAS,EAClC,CAUA,SAAS,EAAsB,CAC3B,IAAM,EAAO,CAAC,CAAC,EASf,OARI,IAAS,KAAK,SAAS,EAChB,MAGX,KAAK,SAAS,MAAQ,EACtB,KAAK,WAAW,CAAI,EACpB,KAAK,aAAa,CAAI,EAEf,KACX,CAOA,YAAmB,CACf,OAAO,KAAK,SAAS,EAAK,CAC9B,CAcA,aAA6B,CACzB,IAAM,EAAQ,KAAK,OACnB,GAAI,IAAU,KACV,OAAO,KAGX,IAAM,EAAW,EAAM,YAAY,EAKnC,OAJI,IAAa,KACN,KAGJ,KAAK,kBAAkB,EAAW,KAAK,kBAAkB,CAAC,CACrE,CAgBA,mBAAoC,CAChC,GAAI,KAAK,SAAW,KAChB,MAAO,GAGX,IAAM,EAAc,KAAK,OAAO,iBAAiB,CAAC,EAAE,QAAU,EACxD,EAAc,KAAK,OAAO,iBAAiB,CAAC,EAAE,QAAU,EAE9D,OAAO,KAAK,IAAI,EAAG,KAAK,OAAO,EAAc,GAAe,CAAC,CAAC,CAClE,CAUA,UAAiB,CACb,MAAM,SAAS,EAEf,IAAM,EAAS,KAAK,OACd,EAAS,KAAK,kBAAkB,EAKtC,OAJI,IAAU,MAAQ,EAAS,GAC3B,EAAM,KAAK,EAAM,KAAK,EAAI,CAAM,EAG7B,IACX,CAKA,WAAmB,EAAsB,CACrC,KAAK,QAAQ,CAAC,CAAC,WAAW,CAAK,EAI/B,KAAK,OAAO,aAAa,EAAQ,mBAAqB,iBAAiB,EAEvE,KAAK,OAAO,mBAAmB,EACzB,qDACA,sDAAsD,CAChE,CAEJ,EAEM,EAAiB,EAAS,MAAM,EC3RhC,EAAsC,GActC,EAA4E,CAC9E,WAAiB,IACjB,gBAAiB,mDACjB,OAAiB,4BACjB,aAAiB,kCACjB,OAAiB,gEACrB,EAWI,IAAI,EAAU,CACV,MAAQ,WACR,KAAQ,2CACR,OAAQ,CACJ,QAAS,MACb,CACJ,CAAC,EAeL,IAAM,qBAAN,cAAmC,CAA8C,CAE7E,MACA,UACA,QAOA,YAAY,EAAmC,EAAoB,EAAuC,CACtG,MAAM,EAAS,CAAmC,EAElD,KAAK,UAAY,EACjB,KAAK,QAAY,EAOjB,KAAK,WAAW,QAAQ,EACxB,KAAK,iBAAiB,IAAI,CAAK,EAY/B,KAAK,MAAQ,IAAI,EACjB,KAAK,MAAM,UAAU,MAAM,EAC3B,KAAK,MAAM,gBAAgB,GAAG,EAC9B,KAAK,MAAM,mBAAmB,EAAK,EACnC,KAAK,MAAM,sBAAsB,EAAK,EACtC,KAAK,aAAa,KAAK,KAAK,EAO5B,KAAK,MAAM,GAAG,aAAgB,CAC1B,IAAM,EAAQ,KAAK,MAAM,SAAS,EAE9B,GACA,KAAK,UAAU,CAAK,CAE5B,CAAC,CACL,CAYA,KAAK,EAAkB,EAA6B,CAKhD,KAAK,WAAW,EAAI,EAEpB,KAAK,YAAY,EACjB,KAAK,MAAM,cAAc,EAAY,IAAI,IAAM,CAAE,IAAK,EAAG,MAAO,CAAE,EAAE,CAAC,EACrE,KAAK,aAAa,EAElB,IAAM,EAAU,KAAK,iBAAiB,EAChC,EAAU,EAAM,IAAM,EAAM,OAC5B,EAAU,EAAI,OAAO,eAAe,CAAQ,EAmBlD,OAjBA,KAAK,SAAS,EAAK,KAAK,EACxB,KAAK,UAAU,EAAY,OAAS,EAAsC,CAAO,EAEjF,KAAK,cAAc,CAAI,EAIvB,KAAK,iBAAiB,CAAQ,EAE9B,KAAK,aAAa,EAMlB,KAAK,SAAS,EAEP,IACX,CAQA,MAAa,CAGT,OAFA,KAAK,aAAa,EAEX,IACX,CAeA,UAAU,EAA2B,CACjC,OAAO,KAAK,MAAM,UAAU,CAAC,CACjC,CASA,iBAAiC,CAC7B,OAAO,KAAK,MAAM,gBAAgB,CACtC,CASA,SAAgB,CACZ,OAAO,KAAK,KAChB,CAMA,gBAAiC,CAC7B,KAAK,QAAQ,CACjB,CACJ,EAEM,EAA+B,EAAS,oBAAoB,EClNlE,EAAwB,oBAAoB,EA2E5C,IAAM,kBAAN,cAAgC,CAAgD,CAE5E,WACA,UACA,eAAgE,KAChE,WAA+D,IAAI,EAKnE,YAAY,EAAoC,CAI5C,MAAM,EAMN,KAAK,UAAU,2BAA2B,EAC1C,KAAK,gBAAgB,iCAAiC,EAEtD,KAAK,WAAa,IAAI,EACtB,KAAK,WAAW,UAAU,MAAM,EAChC,KAAK,WAAW,gBAAgB,GAAG,EACnC,KAAK,WAAW,WAAW,MAAM,EAEjC,KAAK,aAAa,KAAK,UAAU,EAEjC,KAAK,sBAAsB,EAC3B,KAAK,mBAAqB,KAAK,sBAAsB,CAAC,EAEtD,KAAK,UAAY,IAAI,EACjB,GAAS,KAAK,qBAAqB,CAAK,MAC/B,KAAK,iBAAiB,CACnC,EAEA,KAAK,WAAW,QAAQ,CAAC,CAAC,QAAQ,UAAU,EAC5C,KAAK,WAAW,QAAQ,CAAC,CAAC,gBAAgB,MAAM,EAChD,KAAK,WAAW,QAAQ,CAAC,CAAC,YAAY,EAAK,EAC3C,KAAK,WAAW,QAAQ,CAAC,CAAC,YAAY,KAAK,UAAU,MAAM,CAAC,EAC5D,KAAK,WAAW,QAAQ,CAAC,CAAC,oBAAoB,EAAE,EAEhD,EAAM,YAAY,KAAK,WAAY,YAAiB,KAAK,QAAQ,CAAC,EAClE,EAAM,YAAY,KAAK,WAAY,UAAY,GAAqB,KAAK,UAAU,CAAC,CAAC,EACrF,EAAM,YAAY,KAAK,WAAY,WAAiB,KAAK,OAAO,CAAC,EAMjE,KAAK,WAAW,GAAG,SAAU,GAAS,KAAK,aAAa,CAAK,CAAC,EAE1D,GACA,KAAK,aAAa,CAAO,EAGzB,KAAK,SAAS,UAAY,IAAA,IAC1B,KAAK,aAAa,KAAK,SAAS,OAAO,EAGvC,KAAK,SAAS,WAAa,IAAA,IAC3B,KAAK,cAAc,KAAK,SAAS,QAAQ,CAEjD,CASA,aAAuB,EAAyC,CAoB5D,OAnBA,MAAM,aAAa,CAAO,EAEtB,EAAQ,cAAmB,IAAA,IAAW,KAAK,eAAe,EAAQ,WAAW,EAC7E,EAAQ,WAAmB,IAAA,IAAW,KAAK,YAAY,EAAQ,QAAQ,EACvE,EAAQ,aAAmB,IAAA,IAAW,KAAK,cAAc,EAAQ,UAAU,EAC3E,EAAQ,iBAAmB,IAAA,IAAW,KAAK,kBAAkB,EAAQ,cAAc,EACnF,EAAQ,YAAmB,IAAA,IAAW,KAAK,aAAa,EAAQ,SAAS,EACzE,EAAQ,cAAmB,IAAA,IAAW,KAAK,eAAe,EAAQ,WAAW,EAK7E,EAAQ,QAAU,IAAA,IAAa,EAAQ,eAAiB,IAAA,GACxD,KAAK,SAAS,EAAQ,MAAO,EAAQ,YAAY,GAE7C,EAAQ,QAAiB,IAAA,KAAW,KAAK,SAAS,MAAe,EAAQ,OACzE,EAAQ,eAAiB,IAAA,KAAW,KAAK,SAAS,aAAe,EAAQ,eAG1E,IACX,CAQA,uBAAsC,CAClC,IAAM,EAAO,KAAK,WAAW,iBAAiB,EACxC,EAAO,KAAK,WAAW,WAAW,EAClC,EAAO,KAAK,WAAW,WAAW,EAEpC,GACA,KAAK,iBAAiB,CAAE,MAAO,EAAK,MAAO,OAAQ,EAAK,MAAO,CAAC,EAGhE,GACA,KAAK,WAAW,CAAE,MAAO,EAAI,MAAO,OAAQ,EAAI,MAAO,CAAC,EAKxD,GACA,KAAK,WAAW,CAAE,MAAO,EAAI,MAAO,OAAQ,EAAI,MAAO,CAAC,CAEhE,CAWA,aAA6B,CACzB,OAAO,KAAK,kBAAkB,KAAK,WAAW,YAAY,CAAC,CAC/D,CAOA,UAAiB,CAQb,OAPA,MAAM,SAAS,EAEf,KAAK,WAAW,KAAK,CAAC,EACtB,KAAK,WAAW,KAAK,CAAC,EACtB,KAAK,WAAW,SAAS,KAAK,SAAS,CAAC,EACxC,KAAK,WAAW,UAAU,KAAK,UAAU,CAAC,EAEnC,IACX,CAYA,SAAS,EAAqB,CAG1B,OAFA,KAAK,WAAW,SAAS,CAAK,EAEvB,IACX,CAQA,UAAmB,CACf,OAAO,KAAK,WAAW,SAAS,CACpC,CAKA,aAAuB,EAAsB,CACzC,KAAK,WAAW,WAAW,CAAK,CACpC,CAKA,cAAwB,EAAsB,CAC1C,KAAK,WAAW,YAAY,CAAK,CACrC,CASA,eAAe,EAA6B,CAGxC,MAFA,MAAK,SAAS,YAAc,EAErB,IACX,CAQA,SAAS,EAAsB,EAA4B,CAIvD,MAHA,MAAK,SAAS,MAAe,EAC7B,KAAK,SAAS,aAAe,EAEtB,IACX,CAOA,YAAY,EAAiB,CAGzB,MAFA,MAAK,SAAS,SAAW,EAElB,IACX,CAOA,cAAc,EAAkB,CAG5B,MAFA,MAAK,SAAS,WAAa,EAEpB,IACX,CAOA,kBAAkB,EAAiB,CAG/B,MAFA,MAAK,SAAS,eAAiB,EAExB,IACX,CASA,aAAa,EAAmC,CAG5C,MAFA,MAAK,SAAS,UAAY,EAEnB,IACX,CAOA,eAAe,EAA2B,CAItC,MAHA,MAAK,SAAS,YAAc,EAC5B,KAAK,WAAW,eAAe,CAAW,EAEnC,IACX,CAkBA,GAAG,EAAwC,EAA0B,CAOjE,OANI,IAAU,UACV,KAAK,WAAW,IAAI,SAAU,CAAQ,EAE/B,MAGJ,MAAM,GAAG,EAAmB,CAAmC,CAC1E,CAWA,IAAI,EAAwC,EAA0B,CAOlE,OANI,IAAU,UACV,KAAK,WAAW,OAAO,SAAU,CAAQ,EAElC,MAGJ,MAAM,IAAI,EAAO,CAAQ,CACpC,CAQA,kBAAkB,EAAmC,CACjD,KAAK,GAAG,SAAU,CAAE,CACxB,CAYA,SAAwB,CAChB,KAAK,iBAAmB,MACxB,aAAa,KAAK,cAAc,EAGpC,IAAM,EAAW,KAAK,SAAS,UAAY,EACrC,EAAW,KAAK,SAAS,EAE/B,GAAI,EAAQ,OAAS,EAAU,CAC3B,KAAK,UAAU,KAAK,EAEpB,MACJ,CAEA,KAAK,eAAiB,eACZ,KAAK,iBAAiB,CAAO,EACnC,KAAK,SAAS,YAAc,GAChC,CACJ,CAgBA,UAAkB,EAAwB,CACtC,GAAI,KAAK,UAAU,OAAO,IACN,EAAE,MAAQ,aACV,EAAE,MAAQ,WACV,EAAE,MAAQ,UAEX,KAAK,UAAU,UAAU,CAAC,EAAG,CACxC,EAAE,eAAe,EACjB,KAAK,uBAAuB,EAE5B,MACJ,CAGJ,OAAQ,EAAE,IAAV,CACI,IAAK,YAED,EAAE,eAAe,EACjB,KAAK,iBAAiB,KAAK,SAAS,CAAC,EACrC,MAEJ,IAAK,SACD,KAAK,UAAU,KAAK,EACpB,KAAK,WAAW,MAAM,EACtB,MAEJ,IAAK,MACD,KAAK,UAAU,KAAK,EACpB,MAEJ,QACI,KACR,CACJ,CAOA,QAAuB,CACnB,eAAiB,CACb,IAAM,EAAS,EAAI,OAAO,iBAAiB,EACrC,EAAS,KAAK,UAAU,WAAW,EAErC,GAAU,EAAI,OAAO,SAAS,EAAQ,CAAM,GAIhD,KAAK,UAAU,KAAK,CACxB,EAAG,GAAG,CACV,CAUA,QAAgB,EAAmB,EAAwB,CACvD,IAAM,EAAO,KAAK,SAAS,WAAa,WAElC,EAAgB,IAAS,yBACT,IAAS,0BACzB,EAAgB,IAAS,cACT,IAAS,0BAEzB,EAAW,EAAgB,EAAY,EAAU,YAAY,EAC7D,EAAW,EAAgB,EAAY,EAAM,YAAY,EAE/D,OAAO,EAAa,EAAS,WAAW,CAAM,EAAI,EAAS,SAAS,CAAM,CAC9E,CAUA,iBAAyB,EAAqB,CAC1C,IAAM,EAAiB,KAAK,SAAS,gBAAkB,GACjD,EAAiB,KAAK,SAAS,WAAa,WAC5C,EAAiB,KAAK,SAAS,YAC/B,EAAiB,KAAK,SAAS,MAC/B,EAAiB,KAAK,SAAS,aAErC,GAAI,IAAgB,IAAA,GAAW,CAC3B,IAAM,EAAW,EACZ,OAAO,GAAK,KAAK,QAAQ,EAAG,CAAK,CAAC,CAAC,CACnC,MAAM,EAAG,CAAc,EAExB,IAAU,KAAK,SAAS,GACxB,KAAK,gBAAgB,CAAQ,EAGjC,MACJ,CAEA,GAAI,IAAU,IAAA,IAAa,IAAiB,IAAA,GAAW,CACnD,IAAM,EAAgB,IAAc,yBACd,IAAc,0BAC9B,EAAiB,IAAc,cAAgB,IAAc,0BAC7C,aACA,WAEtB,EAAM,YAAY,EAClB,EAAM,SAAS,CACX,KAAM,EACN,MAAO,EACP,MAAO,EACP,eACJ,CAAC,EAED,IAAM,EAAU,EAAM,WAAW,CAAC,CAC7B,IAAI,GAAK,OAAO,EAAE,IAAI,CAAY,CAAC,CAAC,CAAC,CACrC,MAAM,EAAG,CAAc,EAExB,IAAU,KAAK,SAAS,GACxB,KAAK,gBAAgB,CAAO,CAEpC,CACJ,CAOA,gBAAwB,EAAsB,CAC1C,GAAI,EAAK,SAAW,EAAG,CACnB,KAAK,UAAU,KAAK,EAEpB,MACJ,CAEA,KAAK,UAAU,KAAK,KAAK,WAAW,WAAW,EAAI,EAAI,CAAI,EAC3D,KAAK,WAAW,QAAQ,CAAC,CAAC,YAAY,EAAI,CAC9C,CAQA,kBAAiC,CAC7B,KAAK,WAAW,QAAQ,CAAC,CAAC,YAAY,EAAK,EAC3C,KAAK,WAAW,QAAQ,CAAC,CAAC,oBAAoB,EAAE,CACpD,CAMA,wBAAuC,CACnC,KAAK,WAAW,QAAQ,CAAC,CAAC,oBAAoB,KAAK,UAAU,gBAAgB,GAAK,EAAE,CACxF,CAYA,qBAA6B,EAAqB,CAC9C,KAAK,SAAS,CAAK,EAEnB,KAAK,WAAW,KAAK,SAAU,CAAK,EAEpC,KAAK,UAAU,KAAK,EACpB,KAAK,WAAW,MAAM,CAC1B,CACJ,EAEM,GAA4B,EAAS,iBAAiB,ECjnB5D,EAAM,SAAS,EAAY,CAAY,EA0BvC,IAAM,GAAwD,CAC1D,OAAc,OACd,aAAc,IACd,OAAc,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CACvC,EAWM,WAAN,cAAyB,CAA0B,CAE/C,WAAuD,IAAI,EAC3D,QAOA,YAAY,EAAmB,EAA6B,CAKxD,MAAM,IAAA,GAAW,EAAS,CACtB,GAAG,GACH,MAAO,IAAW,IAAM,aAAe,cAC3C,CAAC,EAED,KAAK,WAAW,EAChB,KAAK,mBAAqB,KAAK,WAAW,CAAC,EAMvC,GAAS,SAAW,IAAA,IACpB,KAAK,YAAY,EAEjB,GAAS,gBAAkB,IAAA,IAC3B,KAAK,mBAAmB,EAW5B,KAAK,aAAa,CAAC,EACnB,KAAK,SAAS,CAAC,EAAE,aAAa,EAAG,EAAE,EAEnC,KAAK,QAAU,IAAI,GAAW,CAC1B,aAAc,IACd,MAAc,IACd,MAAc,GACd,WAAoB,KAAK,KAAK,MAAM,CACxC,CAAC,EAED,EAAM,YAAY,KAAM,gBAAmB,KAAK,YAAY,CAAC,EAC7D,EAAM,oBAAoB,KAAM,cAAiB,KAAK,UAAU,CAAC,EACjE,EAAM,oBAAoB,KAAM,iBAAoB,KAAK,UAAU,CAAC,EAEpE,KAAK,eAAe,GAAS,SAAS,CAC1C,CAQA,YAA2B,CAOvB,IAGM,EAAa,EAAK,aAAa,EAAI,EAA+B,EAKlE,EAAa,KAAK,OAAO,EAAa,GAA2B,CAAC,EAExE,KAAK,iBAAiB,CAAE,MAAO,GAAI,OAAQ,CAAW,CAAC,EAIvD,KAAK,WAAW,CAAE,MAAO,GAAI,OAAQ,CAAW,CAAC,EACjD,KAAK,WAAW,CAAE,MAAO,GAAI,OAAQ,CAAW,CAAC,CACrD,CAcA,GAAG,EAAyB,EAA0B,CAOlD,OANI,IAAU,SACV,EAAM,YAAY,KAAM,QAAS,CAAyB,EAE1D,KAAK,WAAW,IAAI,EAAO,CAAQ,EAGhC,IACX,CAaA,IAAI,EAAwB,EAA0B,CAOlD,OANI,IAAU,SACV,EAAM,eAAe,KAAM,QAAS,CAAyB,EAE7D,KAAK,WAAW,OAAO,EAAO,CAAQ,EAGnC,IACX,CAUA,KAAe,EAAe,GAAG,EAA0B,CACvD,KAAK,WAAW,KAAK,EAAO,GAAG,CAAO,CAC1C,CAKA,aAA4B,CACxB,KAAK,QAAQ,MAAM,CACvB,CAKA,WAA0B,CACjB,KAAK,QAAQ,UAAU,GAI5B,KAAK,QAAQ,KAAK,CACtB,CACJ,EAEM,EAAqB,EAAS,UAAU,EC/M9C,EAAwB,gBAAgB,EAMpC,IAAI,EAAU,CACV,MAAQ,WACR,KAAQ,kCACR,OAAQ,CACJ,UAAW,MACf,CACJ,CAAC,EA2BL,IAAM,GAA8D,CAChE,OAAiB,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,EACtC,OAAiB,2DACjB,aAAiB,kCACjB,gBAAiB,2CACrB,EAaM,cAAN,cAA4B,CAA4C,CAEpE,OACA,OACA,SACA,QAKA,YAAY,EAAgC,CACxC,MAAM,EAAS,EAA4B,EAE3C,KAAK,OAAS,IAAI,EAClB,KAAK,OAAO,aAAa,OAAO,EAChC,KAAK,OAAO,UAAU,MAAM,EAC5B,KAAK,OAAO,gBAAgB,GAAG,EAM/B,KAAK,OAAO,WAAW,MAAM,EAC7B,KAAK,OAAO,QAAQ,KAAK,YAAY,CAAC,CAAC,EAEvC,KAAK,OAAW,IAAI,EAAW,GAAG,EAClC,KAAK,SAAW,IAAI,EAAW,GAAG,EAClC,KAAK,OAAO,UAAU,CAAE,UAAW,uBAAwB,CAAC,EAC5D,KAAK,OAAO,gBAAgB,GAAG,EAC/B,KAAK,SAAS,UAAU,CAAE,UAAW,4DAA6D,CAAC,EACnG,KAAK,SAAS,gBAAgB,GAAG,EAEjC,KAAK,QAAU,IAAI,EAEnB,IAAM,EAAO,IAAI,EACjB,EAAK,oBAAoB,CAAC,EAC1B,KAAK,QAAQ,iBAAiB,CAAI,EAClC,KAAK,QAAQ,UAAU,IAAI,EAAO,EAAG,EAAG,EAAG,CAAC,CAAC,EAC7C,KAAK,QAAQ,aAAa,KAAK,MAAM,EACrC,KAAK,QAAQ,aAAa,KAAK,QAAQ,EAEvC,IAAM,EAAO,IAAI,EACjB,EAAK,oBAAoB,CAAC,EAC1B,EAAK,cAAc,EAAI,EACvB,KAAK,iBAAiB,CAAI,EAC1B,KAAK,aAAa,KAAK,OAAQ,CAAE,OAAQ,CAAE,CAAC,EAC5C,KAAK,aAAa,KAAK,OAAO,EAE9B,KAAK,OAAO,GAAG,WAAgB,KAAK,WAAW,KAAK,SAAS,EAAI,KAAK,QAAQ,CAAC,CAAC,EAChF,KAAK,SAAS,GAAG,WAAc,KAAK,WAAW,KAAK,SAAS,EAAI,KAAK,QAAQ,CAAC,CAAC,EAEhF,EAAM,YAAY,KAAK,OAAQ,WAAc,KAAK,OAAO,CAAC,EAC1D,EAAM,YAAY,KAAK,OAAQ,UAAY,GAAqB,KAAK,UAAU,CAAC,CAAC,EAEjF,KAAK,aAAa,EAClB,KAAK,mBAAqB,KAAK,aAAa,CAAC,EAE7C,KAAK,QAAQ,CAAC,CAAC,QAAQ,YAAY,EACnC,KAAK,QAAQ,CAAC,CAAC,YAAY,CAAC,EAKxB,KAAK,SAAS,MAAc,IAAA,IAAW,KAAK,OAAO,KAAK,SAAS,GAAG,EACpE,KAAK,SAAS,MAAc,IAAA,IAAW,KAAK,OAAO,KAAK,SAAS,GAAG,EACpE,KAAK,SAAS,OAAc,IAAA,IAAW,KAAK,QAAQ,KAAK,SAAS,IAAI,EACtE,KAAK,SAAS,YAAc,IAAA,IAAW,KAAK,aAAa,KAAK,SAAS,SAAS,EAChF,KAAK,SAAS,QAAc,IAAA,IAAW,KAAK,SAAS,KAAK,SAAS,KAAK,EACxE,KAAK,SAAS,UAAc,IAAA,IAAW,KAAK,WAAW,KAAK,SAAS,OAAO,EAC5E,KAAK,SAAS,WAAc,IAAA,IAAW,KAAK,YAAY,KAAK,SAAS,QAAQ,CACtF,CAWA,aAAuB,EAAqC,CAUxD,OATA,MAAM,aAAa,CAAO,EAEtB,EAAQ,MAAc,IAAA,KAAW,KAAK,SAAS,IAAY,EAAQ,KACnE,EAAQ,MAAc,IAAA,KAAW,KAAK,SAAS,IAAY,EAAQ,KACnE,EAAQ,OAAc,IAAA,KAAW,KAAK,SAAS,KAAY,EAAQ,MACnE,EAAQ,YAAc,IAAA,KAAW,KAAK,SAAS,UAAY,EAAQ,WACnE,EAAQ,QAAc,IAAA,KAAW,KAAK,SAAS,MAAY,EAAQ,OACnE,EAAQ,UAAc,IAAA,KAAW,KAAK,SAAS,QAAY,EAAQ,SAEhE,IACX,CAOA,aAA6B,CACzB,OAAO,KAAK,kBAAkB,KAAK,OAAO,YAAY,CAAC,CAC3D,CAaA,cAA6B,CAGzB,IAAM,EAAI,EAAK,oBAAoB,KAAK,UAAU,EAAG,KAAK,OAAO,WAAW,EAAG,KAAK,cAAc,CAAC,EAEnG,KAAK,iBAAiB,CAAE,MAAO,IAAK,OAAQ,CAAE,CAAC,EAC/C,KAAK,WAAW,CAAE,MAAO,EAAW,OAAQ,CAAE,CAAC,EAI/C,KAAK,WAAW,CAAE,MAAO,EAAG,OAAQ,CAAE,CAAC,CAC3C,CAOA,UAAmB,CACf,OAAO,KAAK,SAAS,OAAS,CAClC,CAUA,SAAS,EAAiB,CAGtB,OAFA,KAAK,gBAAgB,CAAC,EAEf,IACX,CAOA,QAAiB,CACb,OAAO,KAAK,SAAS,KAAO,IAChC,CAOA,OAAO,EAAiB,CAKpB,MAJA,MAAK,SAAS,IAAM,EAEpB,KAAK,QAAQ,CAAC,CAAC,YAAY,SAAS,CAAC,EAAI,EAAI,IAAI,EAE1C,IACX,CAOA,QAAiB,CACb,OAAO,KAAK,SAAS,KAAO,GAChC,CAOA,OAAO,EAAiB,CAKpB,MAJA,MAAK,SAAS,IAAM,EAEpB,KAAK,QAAQ,CAAC,CAAC,YAAY,SAAS,CAAC,EAAI,EAAI,IAAI,EAE1C,IACX,CAOA,SAAkB,CACd,OAAO,KAAK,SAAS,MAAQ,CACjC,CAOA,QAAQ,EAAiB,CAGrB,MAFA,MAAK,SAAS,KAAO,EAEd,IACX,CAOA,cAA8B,CAC1B,OAAO,KAAK,SAAS,WAAa,IACtC,CAOA,aAAa,EAA+B,CAKxC,MAJA,MAAK,SAAS,UAAY,EAE1B,KAAK,OAAO,QAAQ,KAAK,YAAY,KAAK,SAAS,CAAC,CAAC,EAE9C,IACX,CASA,aAAuB,EAAwB,CACvC,GACA,KAAK,OAAO,qBAAqB,EAAK,EACtC,KAAK,OAAO,iBAAiB,MAAM,EACnC,KAAK,SAAS,iBAAiB,MAAM,EACrC,KAAK,aAAa,IAElB,KAAK,OAAO,qBAAqB,EAAI,EACrC,KAAK,OAAO,iBAAiB,MAAM,EACnC,KAAK,SAAS,iBAAiB,MAAM,EACrC,KAAK,WAAW,EAAG,EAE3B,CASA,cAAwB,EAAsB,CAC1C,KAAK,OAAO,YAAY,CAAK,CACjC,CAOA,WAAmB,EAAiB,CAChC,GAAI,CAAC,KAAK,UAAU,EAChB,OAGJ,IAAM,EAAO,KAAK,UAAU,CAAC,EAC7B,GAAI,IAAS,KAAK,SAAS,EAAG,CAC1B,KAAK,OAAO,QAAQ,KAAK,YAAY,CAAI,CAAC,EAE1C,MACJ,CAEA,KAAK,SAAS,MAAQ,EACtB,KAAK,OAAO,QAAQ,KAAK,YAAY,CAAI,CAAC,EAC1C,KAAK,QAAQ,CAAC,CAAC,YAAY,CAAI,EAE/B,KAAK,aAAa,CAAI,CAC1B,CAOA,gBAAwB,EAAiB,CACrC,IAAM,EAAO,KAAK,UAAU,CAAC,EAE7B,KAAK,SAAS,MAAQ,EACtB,KAAK,OAAO,QAAQ,KAAK,YAAY,CAAI,CAAC,EAC1C,KAAK,QAAQ,CAAC,CAAC,YAAY,CAAI,CACnC,CASA,UAAkB,EAAmB,CACjC,IAAI,EAAI,KAAK,IAAI,KAAK,OAAO,EAAG,KAAK,IAAI,KAAK,OAAO,EAAG,CAAC,CAAC,EAI1D,MAHA,GAAI,KAAK,MAAM,EAAI,KAAK,QAAQ,CAAC,EAAI,KAAK,QAAQ,EAClD,EAAI,WAAW,EAAE,QAAQ,KAAK,gBAAgB,CAAC,CAAC,EAEzC,CACX,CAKA,QAAuB,CACnB,IAAM,EAAS,WAAW,KAAK,OAAO,QAAQ,CAAC,CAAC,QAAQ,CAAC,EACzD,GAAI,MAAM,CAAM,EAAG,CACf,KAAK,OAAO,QAAQ,KAAK,YAAY,KAAK,SAAS,CAAC,CAAC,EAErD,MACJ,CAEA,KAAK,WAAW,CAAM,CAC1B,CAOA,UAAkB,EAAwB,CACtC,GAAI,EAAE,MAAQ,UAAW,CACrB,EAAE,eAAe,EACjB,KAAK,WAAW,KAAK,SAAS,EAAI,KAAK,QAAQ,CAAC,EAEhD,MACJ,CAEA,GAAI,EAAE,MAAQ,YAAa,CACvB,EAAE,eAAe,EACjB,KAAK,WAAW,KAAK,SAAS,EAAI,KAAK,QAAQ,CAAC,EAEhD,MACJ,CAEI,EAAE,MAAQ,SACV,KAAK,OAAO,CAEpB,CASA,YAAoB,EAAmB,CACnC,OAAO,EAAE,QAAQ,KAAK,gBAAgB,CAAC,CAC3C,CAOA,iBAAkC,CAC9B,IAAM,EAAY,KAAK,SAAS,WAAa,KAC7C,GAAI,IAAc,KACd,OAAO,EAGX,IAAM,EAAU,OAAO,KAAK,QAAQ,CAAC,EAC/B,EAAU,EAAQ,QAAQ,GAAG,EAEnC,OAAO,GAAU,EAAI,EAAQ,OAAS,EAAS,EAAI,CACvD,CACJ,EAEM,GAAwB,EAAS,aAAa,EChc9C,EAAsB,EAGtB,EAAgB,mBAWhB,gBAAN,cAA8B,CAAU,CAQpC,MAAoC,KACpC,UAAqC,GACrC,QAAoC,KAEpC,aAAc,CACV,MAAM,CAAE,IAAK,OAAQ,CAAC,EAUtB,KAAK,aAAa,EAAK,EACvB,KAAK,QAAQ,MAAM,CACvB,CAOA,SAAyB,CACrB,OAAO,KAAK,KAChB,CASA,QAAQ,EAAqB,CACzB,KAAK,MAAQ,EACb,KAAK,oBAAoB,OAAQ,CAAK,CAC1C,CAQA,qBAAqB,EAAsB,CACvC,KAAK,UAAY,EAEb,EACA,KAAK,oBAAoB,WAAY,EAAE,EAEvC,KAAK,uBAAuB,UAAU,CAE9C,CAQA,mBAAmB,EAAqB,CACpC,KAAK,QAAU,EACf,KAAK,oBAAoB,SAAU,CAAK,CAC5C,CAOA,YAAY,EAAsB,CAC9B,KAAK,qBAAqB,CAAK,CACnC,CAKA,YAAmB,CACf,IAAM,EAAK,KAAK,WAAW,EAEvB,GACA,EAAI,KAAK,MAAM,CAAE,CAEzB,CAQA,UAA4B,CACxB,IAAM,EAAK,KAAK,WAAW,EAE3B,OAAO,EAAK,EAAI,OAAO,SAAS,CAAE,EAAI,IAC1C,CAMA,YAAmB,CACf,IAAM,EAAK,KAAK,WAAW,EAEvB,GACA,EAAI,KAAK,SAAS,EAAI,EAAE,CAEhC,CAYA,KAAe,EAAwB,CACnC,MAAM,KAAK,CAAO,EAElB,IAAM,EAAK,GAAW,KAAK,WAAW,EAkBtC,OAhBK,GAID,KAAK,QAAU,MACf,EAAI,KAAK,MAAM,EAAI,CAAE,QAAS,CAAE,KAAM,KAAK,KAAM,CAAE,CAAC,EAGpD,KAAK,WACL,EAAI,KAAK,MAAM,EAAI,CAAE,QAAS,CAAE,SAAU,EAAG,CAAE,CAAC,EAGhD,KAAK,UAAY,MACjB,EAAI,KAAK,MAAM,EAAI,CAAE,QAAS,CAAE,OAAQ,KAAK,OAAQ,CAAE,CAAC,EAGrD,MAfI,IAgBf,CACJ,EAqCM,UAAN,cACY,CACZ,CACI,QACA,OACA,OAKA,cAAgC,CAAC,EAejC,YAAY,EAA4B,CACpC,MAAM,CAAE,GAAI,GAAW,CAAC,CAAG,CAAa,EAExC,KAAK,iBAAiB,IAAI,CAAM,EAEhC,KAAK,QAAU,IAAI,EAAO,KAAK,kBAAkB,CAAC,EAClD,KAAK,QAAQ,GAAG,aAAgB,KAAK,WAAW,CAAC,EAEjD,KAAK,OAAS,IAAI,EAAK,EAAe,CAAE,SAAU,EAAK,CAAC,EACxD,KAAK,OAAO,mBAAmB,iCAAiC,EAEhE,KAAK,OAAS,IAAI,gBAClB,EAAM,YAAY,KAAK,OAAQ,aAAgB,KAAK,eAAe,CAAC,EAEpE,MAAM,aAAa,KAAK,OAAO,EAK/B,MAAM,aAAa,KAAK,OAAQ,CAAE,OAAQ,CAAE,CAAC,EAC7C,MAAM,aAAa,KAAK,MAAM,EAE1B,KAAK,SAAS,WAAa,IAAA,IAC3B,KAAK,YAAY,KAAK,SAAS,QAAQ,EAGvC,KAAK,SAAS,SAAW,IAAA,IACzB,KAAK,UAAU,KAAK,SAAS,MAAM,EAGnC,KAAK,SAAS,aAAe,IAAA,IAC7B,KAAK,QAAQ,QAAQ,KAAK,SAAS,UAAU,EAG7C,KAAK,SAAS,UAAY,IAAA,IAC1B,KAAK,aAAa,KAAK,SAAS,OAAO,EAGvC,KAAK,SAAS,WAAa,IAAA,IAC3B,KAAK,cAAc,KAAK,SAAS,QAAQ,CAEjD,CAaA,aAAuB,EAAyB,CAO5C,OANA,MAAM,aAAa,CAAO,EAEtB,EAAQ,WAAe,IAAA,KAAW,KAAK,SAAS,SAAa,EAAQ,UACrE,EAAQ,SAAe,IAAA,KAAW,KAAK,SAAS,OAAa,EAAQ,QACrE,EAAQ,aAAe,IAAA,KAAW,KAAK,SAAS,WAAa,EAAQ,YAElE,IACX,CAQA,mBAAoC,CAChC,OAAO,KAAK,WAAW,EAAI,gBAAkB,cACjD,CAKA,YAA2B,CACnB,CAAC,KAAK,UAAU,GAAK,KAAK,WAAW,GAIzC,KAAK,OAAO,WAAW,CAC3B,CAOA,gBAA+B,CAC3B,KAAK,cAAgB,CAAC,EAEtB,IAAM,EAAQ,KAAK,SAAS,EAE5B,KAAK,YAAY,CAAK,EACtB,KAAK,aAAa,CAAK,CAC3B,CAaA,mBAAmB,EAAuB,CACtC,KAAK,cAAgB,MAAM,KAAK,CAAK,EAErC,IAAM,EAAQ,KAAK,SAAS,EAE5B,KAAK,YAAY,CAAK,EACtB,KAAK,aAAa,CAAK,CAC3B,CASA,UAAmB,CACf,GAAI,KAAK,cAAc,OAAS,EAC5B,OAAO,KAAK,cAGhB,IAAM,EAAQ,KAAK,OAAO,SAAS,EAEnC,OAAO,EAAQ,MAAM,KAAK,CAAK,EAAI,CAAC,CACxC,CAWA,SAAS,EAAqB,CAW1B,OAVI,GAAS,EAAM,OAAS,GACxB,QAAQ,KACJ,cAAgB,KAAK,MAAM,EAAI,gKAGnC,EAEO,MAGJ,KAAK,WAAW,CAC3B,CAUA,YAAmB,CACf,KAAK,cAAgB,CAAC,EACtB,KAAK,OAAO,WAAW,EAEvB,IAAM,EAAQ,KAAK,SAAS,EAK5B,OAHA,KAAK,YAAY,CAAK,EACtB,KAAK,aAAa,CAAK,EAEhB,IACX,CAOA,YAAsB,CAClB,OAAO,KAAK,SAAS,UAAY,EACrC,CAWA,YAAY,EAAsB,CAQ9B,MAPA,MAAK,SAAS,SAAW,CAAC,CAAC,EAC3B,KAAK,OAAO,qBAAqB,KAAK,SAAS,QAAQ,EAEnD,KAAK,SAAS,aAAe,IAAA,IAC7B,KAAK,QAAQ,QAAQ,KAAK,kBAAkB,CAAC,EAG1C,IACX,CAOA,WAA2B,CACvB,OAAO,KAAK,SAAS,QAAU,IACnC,CAUA,UAAU,EAAqB,CAI3B,MAHA,MAAK,SAAS,OAAS,EACvB,KAAK,OAAO,mBAAmB,CAAK,EAE7B,IACX,CAOA,YAAoB,EAAqB,CACjC,EAAM,SAAW,EACjB,KAAK,OAAO,QAAQ,CAAa,EAC1B,EAAM,SAAW,EACxB,KAAK,OAAO,QAAQ,EAAM,EAAE,CAAC,IAAI,EAC1B,EAAM,OAAS,EACtB,KAAK,OAAO,QAAQ,EAAM,OAAS,QAAQ,EAE3C,KAAK,OAAO,QAAQ,EAAM,IAAI,GAAK,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAE7D,CAKA,aAAuB,EAAsB,CACzC,KAAK,QAAQ,WAAW,CAAK,EAC7B,KAAK,OAAO,YAAY,CAAC,CAAK,CAClC,CAOA,cAAwB,EAAsB,CAC1C,KAAK,QAAQ,WAAW,CAAC,GAAS,KAAK,UAAU,CAAC,EAClD,KAAK,QAAQ,CAAC,CAAC,YAAY,CAAK,CACpC,CACJ,EAEM,GAAoB,EAAS,SAAS,ECpftC,GAAiB,wEACjB,GAAiB,yDACjB,GAAiB,+EACjB,GAAiB,gEAGjB,GAAiB,qCA4BjB,aAAN,cACY,CACZ,CACI,QACA,OAOA,WAA6B,EAgB7B,YAAY,EAA+B,CACvC,MAAM,CAAE,GAAI,GAAW,CAAC,CAAG,CAAa,EAExC,KAAK,iBAAiB,IAAI,CAAM,EAEhC,KAAK,QAAU,IAAI,EAAK,KAAK,SAAS,YAAc,EAAc,EAClE,KAAK,QAAQ,mBAAmB,iCAAiC,EAEjE,KAAK,OAAS,IAAI,UAAW,KAAK,aAAa,CAAC,EAChD,KAAK,OAAO,GAAG,SAAW,GAAkB,KAAK,aAAa,CAAK,CAAC,EAEpE,MAAM,aAAa,KAAK,OAAO,EAC/B,MAAM,aAAa,KAAK,MAAM,EAE9B,KAAK,UAAU,EAAK,EACpB,KAAK,gBAAgB,iCAAiC,EAEtD,EAAM,mBAAmB,KAAM,YAAc,GAAiB,KAAK,YAAY,CAAC,CAAC,EACjF,EAAM,mBAAmB,KAAM,WAAc,GAAiB,KAAK,WAAW,CAAC,CAAC,EAChF,EAAM,mBAAmB,KAAM,YAAc,GAAiB,KAAK,YAAY,CAAC,CAAC,EACjF,EAAM,mBAAmB,KAAM,OAAc,GAAiB,KAAK,OAAO,CAAC,CAAC,EAExE,KAAK,SAAS,UAAY,IAAA,IAC1B,KAAK,aAAa,KAAK,SAAS,OAAO,EAGvC,KAAK,SAAS,WAAa,IAAA,IAC3B,KAAK,cAAc,KAAK,SAAS,QAAQ,CAEjD,CAUA,aAAuB,EAAyB,CAK5C,OAJA,MAAM,aAAa,CAAO,EAEtB,EAAQ,aAAe,IAAA,KAAW,KAAK,SAAS,WAAa,EAAQ,YAElE,IACX,CASA,cAAyC,CACrC,MAAO,CACH,SAAY,KAAK,SAAS,SAC1B,OAAY,KAAK,SAAS,OAC1B,WAAY,KAAK,SAAS,UAC9B,CACJ,CAQA,UAAkB,EAAuB,CACrC,KAAK,UAAU,EAAS,GAAgB,EAAc,EACtD,KAAK,mBAAmB,EAAS,GAAY,EAAU,CAC3D,CAMA,YAAoB,EAAoB,CACpC,EAAE,eAAe,EACjB,KAAK,aAED,KAAK,aAAe,GACpB,KAAK,UAAU,EAAI,CAE3B,CAOA,WAAmB,EAAoB,CACnC,EAAE,eAAe,CACrB,CAMA,YAAoB,EAAoB,CACpC,EAAE,eAAe,EACjB,KAAK,WAAa,KAAK,IAAI,EAAG,KAAK,WAAa,CAAC,EAE7C,KAAK,aAAe,GACpB,KAAK,UAAU,EAAK,CAE5B,CAOA,OAAe,EAAoB,CAK/B,GAJA,EAAE,eAAe,EACjB,KAAK,WAAa,EAClB,KAAK,UAAU,EAAK,EAEhB,CAAC,KAAK,UAAU,GAAK,KAAK,WAAW,EACrC,OAGJ,IAAM,EAAQ,EAAE,cAAc,MAE1B,CAAC,GAAS,EAAM,SAAW,GAI/B,KAAK,OAAO,mBAAmB,CAAK,CACxC,CAOA,UAAmB,CACf,OAAO,KAAK,OAAO,SAAS,CAChC,CASA,SAAS,EAAqB,CAG1B,OAFA,KAAK,OAAO,SAAS,CAAK,EAEnB,IACX,CAOA,YAAmB,CAGf,OAFA,KAAK,OAAO,WAAW,EAEhB,IACX,CAKA,aAAuB,EAAsB,CACzC,KAAK,OAAO,WAAW,CAAK,CAChC,CAKA,cAAwB,EAAsB,CAC1C,KAAK,OAAO,YAAY,CAAK,CACjC,CACJ,EAEM,GAAuB,EAAS,YAAY"}
|