@vielzeug/refine 2.3.0 → 3.0.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/accordion-item.cjs +1 -1
- package/dist/accordion-item.cjs.map +1 -1
- package/dist/accordion-item.js +1 -1
- package/dist/accordion-item.js.map +1 -1
- package/dist/accordion.cjs.map +1 -1
- package/dist/accordion.js.map +1 -1
- package/dist/async.cjs +1 -1
- package/dist/async.cjs.map +1 -1
- package/dist/async.js +3 -3
- package/dist/async.js.map +1 -1
- package/dist/box.cjs.map +1 -1
- package/dist/box.js.map +1 -1
- package/dist/card.cjs +1 -1
- package/dist/card.cjs.map +1 -1
- package/dist/card.js +3 -3
- package/dist/card.js.map +1 -1
- package/dist/chat-message.cjs +1 -1
- package/dist/chat-message.cjs.map +1 -1
- package/dist/chat-message.js +1 -1
- package/dist/chat-message.js.map +1 -1
- package/dist/checkbox-group.cjs +1 -1
- package/dist/checkbox-group.cjs.map +1 -1
- package/dist/checkbox-group.js +1 -1
- package/dist/checkbox-group.js.map +1 -1
- package/dist/combobox.cjs +10 -8
- package/dist/combobox.cjs.map +1 -1
- package/dist/combobox.js +62 -51
- package/dist/combobox.js.map +1 -1
- package/dist/command-palette.cjs +1 -1
- package/dist/command-palette.cjs.map +1 -1
- package/dist/command-palette.js +6 -2
- package/dist/command-palette.js.map +1 -1
- package/dist/content/card/card.css.cjs +1 -1
- package/dist/content/card/card.css.cjs.map +1 -1
- package/dist/content/card/card.css.js +1 -1
- package/dist/content/card/card.css.js.map +1 -1
- package/dist/content/card/card.d.ts +2 -2
- package/dist/content/card/card.d.ts.map +1 -1
- package/dist/content/index.d.ts +2 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/list/list-item.css.cjs +1 -1
- package/dist/content/list/list-item.css.cjs.map +1 -1
- package/dist/content/list/list-item.css.js +1 -1
- package/dist/content/list/list-item.css.js.map +1 -1
- package/dist/content/list/list-item.d.ts +9 -0
- package/dist/content/list/list-item.d.ts.map +1 -1
- package/dist/content/pagination/pagination.css.cjs +1 -1
- package/dist/content/pagination/pagination.css.cjs.map +1 -1
- package/dist/content/pagination/pagination.css.js +1 -1
- package/dist/content/pagination/pagination.css.js.map +1 -1
- package/dist/content/stats/stats.css.cjs +2 -0
- package/dist/content/stats/stats.css.cjs.map +1 -0
- package/dist/content/stats/stats.css.js +6 -0
- package/dist/content/stats/stats.css.js.map +1 -0
- package/dist/content/stats/stats.d.ts +72 -0
- package/dist/content/stats/stats.d.ts.map +1 -0
- package/dist/core/auto-resize.cjs +1 -1
- package/dist/core/auto-resize.cjs.map +1 -1
- package/dist/core/auto-resize.js +1 -1
- package/dist/core/auto-resize.js.map +1 -1
- package/dist/core/field-base.cjs +1 -1
- package/dist/core/field-base.cjs.map +1 -1
- package/dist/core/field-base.d.ts +1 -1
- package/dist/core/field-base.js +1 -1
- package/dist/core/field-base.js.map +1 -1
- package/dist/core/nav.cjs +1 -1
- package/dist/core/nav.cjs.map +1 -1
- package/dist/core/nav.d.ts +4 -1
- package/dist/core/nav.d.ts.map +1 -1
- package/dist/core/nav.js +41 -28
- package/dist/core/nav.js.map +1 -1
- package/dist/core/option-list.cjs +1 -1
- package/dist/core/option-list.cjs.map +1 -1
- package/dist/core/option-list.d.ts.map +1 -1
- package/dist/core/option-list.js +30 -28
- package/dist/core/option-list.js.map +1 -1
- package/dist/core/slider.cjs +1 -1
- package/dist/core/slider.cjs.map +1 -1
- package/dist/core/slider.js +1 -1
- package/dist/core/slider.js.map +1 -1
- package/dist/core/spinner.cjs +1 -1
- package/dist/core/spinner.cjs.map +1 -1
- package/dist/core/spinner.js +1 -1
- package/dist/core/spinner.js.map +1 -1
- package/dist/custom-elements.json +5471 -4977
- package/dist/datagrid.cjs +219 -199
- package/dist/datagrid.cjs.map +1 -1
- package/dist/datagrid.js +415 -302
- package/dist/datagrid.js.map +1 -1
- package/dist/disclosure/accordion/accordion.css.cjs +1 -1
- package/dist/disclosure/accordion/accordion.css.cjs.map +1 -1
- package/dist/disclosure/accordion/accordion.css.js +1 -1
- package/dist/disclosure/accordion/accordion.css.js.map +1 -1
- package/dist/disclosure/accordion/accordion.d.ts +5 -5
- package/dist/disclosure/accordion/accordion.d.ts.map +1 -1
- package/dist/disclosure/accordion-item/accordion-item.css.cjs +1 -1
- package/dist/disclosure/accordion-item/accordion-item.css.cjs.map +1 -1
- package/dist/disclosure/accordion-item/accordion-item.css.js +1 -1
- package/dist/disclosure/accordion-item/accordion-item.css.js.map +1 -1
- package/dist/disclosure/accordion-item/accordion-item.d.ts +3 -3
- package/dist/disclosure/accordion-item/accordion-item.d.ts.map +1 -1
- package/dist/disclosure/tab-item/tab-item.css.cjs +1 -1
- package/dist/disclosure/tab-item/tab-item.css.cjs.map +1 -1
- package/dist/disclosure/tab-item/tab-item.css.js +1 -1
- package/dist/disclosure/tab-item/tab-item.css.js.map +1 -1
- package/dist/disclosure/tab-item/tab-item.d.ts +6 -3
- package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
- package/dist/disclosure/tabs/tabs.css.cjs +1 -1
- package/dist/disclosure/tabs/tabs.css.cjs.map +1 -1
- package/dist/disclosure/tabs/tabs.css.js +1 -1
- package/dist/disclosure/tabs/tabs.css.js.map +1 -1
- package/dist/disclosure/tabs/tabs.d.ts +9 -5
- package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
- package/dist/drawer.cjs +1 -1
- package/dist/drawer.cjs.map +1 -1
- package/dist/drawer.js +1 -1
- package/dist/drawer.js.map +1 -1
- package/dist/feedback/toast/toast.d.ts.map +1 -1
- package/dist/file-input.cjs +1 -1
- package/dist/file-input.cjs.map +1 -1
- package/dist/file-input.js +2 -2
- package/dist/file-input.js.map +1 -1
- package/dist/frameworks/elements.d.ts +4 -85
- package/dist/frameworks/react.d.ts +5 -4
- package/dist/frameworks/vue.d.ts +4 -4
- package/dist/grid.cjs +2 -2
- package/dist/grid.cjs.map +1 -1
- package/dist/grid.js +21 -18
- package/dist/grid.js.map +1 -1
- package/dist/icon.cjs +1 -1
- package/dist/icon.cjs.map +1 -1
- package/dist/icon.js +1 -1
- package/dist/icon.js.map +1 -1
- package/dist/input.cjs +5 -4
- package/dist/input.cjs.map +1 -1
- package/dist/input.js +14 -8
- package/dist/input.js.map +1 -1
- package/dist/inputs/combobox/combobox-options.cjs +1 -1
- package/dist/inputs/combobox/combobox-options.cjs.map +1 -1
- package/dist/inputs/combobox/combobox-options.d.ts.map +1 -1
- package/dist/inputs/combobox/combobox-options.js +1 -0
- package/dist/inputs/combobox/combobox-options.js.map +1 -1
- package/dist/inputs/combobox/combobox.css.cjs +1 -1
- package/dist/inputs/combobox/combobox.css.cjs.map +1 -1
- package/dist/inputs/combobox/combobox.css.js +1 -1
- package/dist/inputs/combobox/combobox.css.js.map +1 -1
- package/dist/inputs/combobox/combobox.d.ts +13 -1
- package/dist/inputs/combobox/combobox.d.ts.map +1 -1
- package/dist/inputs/combobox/combobox.types.d.ts +7 -0
- package/dist/inputs/combobox/combobox.types.d.ts.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.cjs +1 -1
- package/dist/inputs/datagrid/datagrid-model.cjs.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.d.ts +17 -3
- package/dist/inputs/datagrid/datagrid-model.d.ts.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.js +93 -70
- package/dist/inputs/datagrid/datagrid-model.js.map +1 -1
- package/dist/inputs/datagrid/datagrid.css.cjs +1 -1
- package/dist/inputs/datagrid/datagrid.css.cjs.map +1 -1
- package/dist/inputs/datagrid/datagrid.css.js +1 -1
- package/dist/inputs/datagrid/datagrid.css.js.map +1 -1
- package/dist/inputs/datagrid/datagrid.d.ts +78 -36
- package/dist/inputs/datagrid/datagrid.d.ts.map +1 -1
- package/dist/inputs/file-input/file-input-upload.cjs +1 -1
- package/dist/inputs/file-input/file-input-upload.cjs.map +1 -1
- package/dist/inputs/file-input/file-input-upload.js +1 -1
- package/dist/inputs/file-input/file-input-upload.js.map +1 -1
- package/dist/inputs/index.d.ts +3 -4
- package/dist/inputs/index.d.ts.map +1 -1
- package/dist/inputs/input/input.css.cjs +1 -1
- package/dist/inputs/input/input.css.cjs.map +1 -1
- package/dist/inputs/input/input.css.js +1 -1
- package/dist/inputs/input/input.css.js.map +1 -1
- package/dist/inputs/input/input.d.ts +3 -0
- package/dist/inputs/input/input.d.ts.map +1 -1
- package/dist/inputs/select/select.css.cjs +1 -1
- package/dist/inputs/select/select.css.cjs.map +1 -1
- package/dist/inputs/select/select.css.js +1 -1
- package/dist/inputs/select/select.css.js.map +1 -1
- package/dist/inputs/select/select.d.ts +5 -0
- package/dist/inputs/select/select.d.ts.map +1 -1
- package/dist/inputs/shared/field-binding.cjs +1 -1
- package/dist/inputs/shared/field-binding.cjs.map +1 -1
- package/dist/inputs/shared/field-binding.js +6 -6
- package/dist/inputs/shared/field-binding.js.map +1 -1
- package/dist/layout/box/box.css.cjs +1 -1
- package/dist/layout/box/box.css.cjs.map +1 -1
- package/dist/layout/box/box.css.js +1 -1
- package/dist/layout/box/box.css.js.map +1 -1
- package/dist/layout/box/box.d.ts +3 -3
- package/dist/layout/box/box.d.ts.map +1 -1
- package/dist/layout/grid/grid.d.ts.map +1 -1
- package/dist/layout/navbar/navbar.css.cjs +1 -1
- package/dist/layout/navbar/navbar.css.cjs.map +1 -1
- package/dist/layout/navbar/navbar.css.js +1 -1
- package/dist/layout/navbar/navbar.css.js.map +1 -1
- package/dist/layout/navbar/navbar.d.ts +3 -1
- package/dist/layout/navbar/navbar.d.ts.map +1 -1
- package/dist/layout/sidebar/sidebar.css.cjs +1 -1
- package/dist/layout/sidebar/sidebar.css.cjs.map +1 -1
- package/dist/layout/sidebar/sidebar.css.js +1 -1
- package/dist/layout/sidebar/sidebar.css.js.map +1 -1
- package/dist/layout/sidebar/sidebar.d.ts +8 -0
- package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
- package/dist/list-item.cjs +6 -5
- package/dist/list-item.cjs.map +1 -1
- package/dist/list-item.js +23 -17
- package/dist/list-item.js.map +1 -1
- package/dist/list.cjs +1 -1
- package/dist/list.cjs.map +1 -1
- package/dist/list.js +1 -1
- package/dist/list.js.map +1 -1
- package/dist/menu.cjs +1 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.js +7 -7
- package/dist/menu.js.map +1 -1
- package/dist/navbar.cjs +2 -2
- package/dist/navbar.cjs.map +1 -1
- package/dist/navbar.js +91 -87
- package/dist/navbar.js.map +1 -1
- package/dist/navigation-menu.cjs +1 -1
- package/dist/navigation-menu.cjs.map +1 -1
- package/dist/navigation-menu.js +2 -2
- package/dist/navigation-menu.js.map +1 -1
- package/dist/number-input.cjs +1 -1
- package/dist/number-input.cjs.map +1 -1
- package/dist/number-input.js +1 -1
- package/dist/number-input.js.map +1 -1
- package/dist/overlay/popover/popover.css.cjs +1 -1
- package/dist/overlay/popover/popover.css.cjs.map +1 -1
- package/dist/overlay/popover/popover.css.js +1 -1
- package/dist/overlay/popover/popover.css.js.map +1 -1
- package/dist/overlay/popover/popover.d.ts +1 -0
- package/dist/overlay/popover/popover.d.ts.map +1 -1
- package/dist/overlay/shared/use-dialog.cjs +1 -1
- package/dist/overlay/shared/use-dialog.cjs.map +1 -1
- package/dist/overlay/shared/use-dialog.js +1 -1
- package/dist/overlay/shared/use-dialog.js.map +1 -1
- package/dist/overlay/shared/use-floating-trigger.cjs +1 -1
- package/dist/overlay/shared/use-floating-trigger.cjs.map +1 -1
- package/dist/overlay/shared/use-floating-trigger.js +1 -1
- package/dist/overlay/shared/use-floating-trigger.js.map +1 -1
- package/dist/overlay/tooltip/tooltip.d.ts.map +1 -1
- package/dist/popover.cjs +1 -1
- package/dist/popover.cjs.map +1 -1
- package/dist/popover.js +6 -6
- package/dist/popover.js.map +1 -1
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/radio-group.js.map +1 -1
- package/dist/radio.cjs +1 -1
- package/dist/radio.cjs.map +1 -1
- package/dist/radio.js +15 -13
- package/dist/radio.js.map +1 -1
- package/dist/rating.cjs +1 -1
- package/dist/rating.cjs.map +1 -1
- package/dist/rating.js +2 -2
- package/dist/rating.js.map +1 -1
- package/dist/refine.cjs +373 -324
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +382 -333
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +417 -368
- package/dist/refine.js.map +1 -1
- package/dist/select.cjs +16 -14
- package/dist/select.cjs.map +1 -1
- package/dist/select.js +118 -112
- package/dist/select.js.map +1 -1
- package/dist/shared/types.d.ts +0 -2
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/sidebar.cjs +28 -23
- package/dist/sidebar.cjs.map +1 -1
- package/dist/sidebar.js +143 -108
- package/dist/sidebar.js.map +1 -1
- package/dist/skeleton.cjs +1 -1
- package/dist/skeleton.cjs.map +1 -1
- package/dist/skeleton.js +11 -9
- package/dist/skeleton.js.map +1 -1
- package/dist/slider.cjs +1 -1
- package/dist/slider.cjs.map +1 -1
- package/dist/slider.js +21 -21
- package/dist/slider.js.map +1 -1
- package/dist/stats.cjs +18 -0
- package/dist/stats.cjs.map +1 -0
- package/dist/stats.js +88 -0
- package/dist/stats.js.map +1 -0
- package/dist/stepper.cjs +1 -1
- package/dist/stepper.cjs.map +1 -1
- package/dist/stepper.js +1 -1
- package/dist/stepper.js.map +1 -1
- package/dist/styles/mixins/animation.css.cjs +2 -0
- package/dist/styles/mixins/animation.css.cjs.map +1 -1
- package/dist/styles/mixins/animation.css.d.ts.map +1 -1
- package/dist/styles/mixins/animation.css.js +2 -0
- package/dist/styles/mixins/animation.css.js.map +1 -1
- package/dist/tab-item.cjs +1 -1
- package/dist/tab-item.cjs.map +1 -1
- package/dist/tab-item.js +3 -2
- package/dist/tab-item.js.map +1 -1
- package/dist/tabs.cjs +1 -1
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.js +3 -1
- package/dist/tabs.js.map +1 -1
- package/dist/toast.cjs +8 -8
- package/dist/toast.cjs.map +1 -1
- package/dist/toast.js +32 -33
- package/dist/toast.js.map +1 -1
- package/dist/tooltip.cjs +1 -1
- package/dist/tooltip.cjs.map +1 -1
- package/dist/tooltip.js +2 -2
- package/dist/tooltip.js.map +1 -1
- package/dist/types/elements.d.ts +207 -0
- package/package.json +70 -62
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-base.cjs","names":[],"sources":["../../src/core/field-base.ts"],"sourcesContent":["import { createStableId } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\n\n// ── Validation / context types ────────────────────────────────────────────────\n\nexport type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';\nexport type ControlValidationMode = ValidationTrigger | undefined;\n\n// ── Error/helper assistive state ──────────────────────────────────────────────\n\nexport type ErrorHelperState = {\n errorText: string;\n helperText: string;\n};\n\nexport type ErrorHelperOptions = {\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n};\n\nexport const createErrorHelperState = (options: ErrorHelperOptions): Readable<ErrorHelperState> =>\n computed(() => ({\n errorText: options.error?.value ?? '',\n helperText: options.helper?.value ?? '',\n }));\n\n// ── Counter state (opt-in) ─────────────────────────────────────────────────────\n\n/** Counter state for text fields with `maxLength`. */\nexport type CounterState = {\n counterAtLimit: boolean;\n counterNearLimit: boolean;\n counterText: string;\n};\n\nexport type CounterOptions = {\n maxLength?: Readable<number | undefined>;\n value: Readable<string | undefined>;\n};\n\n/**\n * Creates a reactive counter state signal for text fields with `maxLength`.\n * Only call this when `maxLength` may be set — the state is genuinely opt-in.\n *\n * @example\n * ```ts\n * const counter = createCounterState({ value: tf.value, maxLength: props.maxlength });\n * ```\n */\nexport const createCounterState = (options: CounterOptions): Readable<CounterState> =>\n computed<CounterState>(() => {\n const value = options.value?.value ?? '';\n const maxLength = options.maxLength?.value;\n const parsedMaxLength = Number(maxLength);\n const validMaxLength = Number.isFinite(parsedMaxLength) && parsedMaxLength > 0 ? parsedMaxLength : null;\n const hasCounter = validMaxLength !== null;\n const counterText = hasCounter ? `${value.length} / ${validMaxLength}` : '';\n const ratio = hasCounter ? value.length / validMaxLength : 0;\n\n return {\n counterAtLimit: hasCounter ? ratio >= 1 : false,\n counterNearLimit: hasCounter ? ratio >= 0.9 && ratio < 1 : false,\n counterText,\n };\n });\n\n/**\n * The single implementation of \"which modifier class does this counter state map to\" —\n * every field with a character counter (`ore-input`, `ore-textarea`, `ore-message-composer`)\n * renders the identical near-limit/at-limit styling, so this is the one place that decides it.\n *\n * @example\n * ```ts\n * html`<span class=\"${() => counterClassName(counter?.value)}\">...</span>`\n * ```\n */\nexport const counterClassName = (counter: CounterState | undefined, base = 'counter'): string => {\n if (!counter) return base;\n\n if (counter.counterAtLimit) return `${base} at-limit`;\n\n if (counter.counterNearLimit) return `${base} near-limit`;\n\n return base;\n};\n\n// ── Dirty tracking (two-state reset) ───────────────────────────────────────────\n\n/**\n * Tracks whether a control's value has ever been changed by user interaction.\n *\n * Backs the \"two-state reset()\" pattern used by `createCheckable` (`checked`/\n * `indeterminate`) and `createChoiceField` (`selectedValues`): before the first\n * interaction, the live prop is still a reliable \"current default\" to resync from\n * (e.g. an async-loaded value arriving after mount); after it, the prop is\n * contaminated by interaction-driven attribute reflection, so only a snapshot taken\n * at creation still represents \"the default\" in the native sense. `reset()` callers\n * check `isDirty` to decide which source to revert to, then call `clear()`.\n */\nexport type DirtyTracker = {\n clear: () => void;\n readonly isDirty: boolean;\n markDirty: () => void;\n};\n\nexport function createDirtyTracker(): DirtyTracker {\n let dirty = false;\n\n return {\n clear: () => {\n dirty = false;\n },\n get isDirty() {\n return dirty;\n },\n markDirty: () => {\n dirty = true;\n },\n };\n}\n\n// ── Label placement ───────────────────────────────────────────────────────────\n\nexport type LabelPlacement = 'inset' | 'outside' | undefined;\n\n// ── Assistive state (error/helper text, describedby/errormessage/invalid) ─────\n\n/**\n * A field's error/helper/disabled/validation-trigger state — everything an input\n * needs *except* a visible `<label>`. Split out from label state (below) because\n * not every field has one: `ore-message-composer` names itself via `aria-label`\n * (a chat composer's accessible name conventionally comes from context/placeholder,\n * not a floating `<label>`), so it composes this directly instead of pulling in\n * label plumbing it can't use. `createField()` composes both for fields that do\n * render a `<label>`.\n */\nexport type AssistiveStateHandle = {\n /** `aria-describedby` value. Non-null when helper or error text is present. */\n ariaDescribedBy: Readable<string | null>;\n /** `aria-errormessage` value. Non-null when error text is present. */\n ariaErrorMessage: Readable<string | null>;\n /** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */\n ariaInvalid: Readable<'true' | null>;\n /**\n * The stable `id` used for `aria-describedby` on the input. Points at the\n * assistive-text region (covers both helper text and error text per WAI-ARIA).\n */\n assistiveId: string;\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()` can call its `reportValidity()`. See `TextFieldHandle.attachFormField`\n * for why this is a post-hoc call instead of a constructor option — the same forward\n * reference applies here for choice fields.\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n disabled: Readable<boolean>;\n /** Stable `id` for the inline error message element (`aria-errormessage`). */\n errorId: string;\n /** Reactive error text. Empty string when no error is set. */\n errorText: Readable<string>;\n /** Reactive helper text. Empty string when no helper is set. */\n helperText: Readable<string>;\n triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;\n};\n\nexport type AssistiveStateOptions = {\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n validateOn?: Readable<ControlValidationMode>;\n};\n\nexport const createAssistiveState = (options: AssistiveStateOptions): AssistiveStateHandle => {\n const disabled = computed(() => Boolean(options.disabled?.value));\n const assistiveId = createStableId('helper');\n const errorId = createStableId('error');\n const resolvedAssistive = createErrorHelperState({ error: options.error, helper: options.helper });\n\n const ariaDescribedBy = computed(() =>\n resolvedAssistive.value.errorText || resolvedAssistive.value.helperText ? assistiveId : null,\n );\n const ariaErrorMessage = computed(() => (resolvedAssistive.value.errorText ? errorId : null));\n const ariaInvalid = computed<'true' | null>(() => (resolvedAssistive.value.errorText ? 'true' : null));\n\n let formField: { reportValidity(): void } | null = null;\n const attachFormField = (nextFormField: { reportValidity(): void }): void => {\n formField = nextFormField;\n };\n\n const triggerValidation = (on: Extract<ValidationTrigger, 'blur' | 'change'>): void => {\n if (options.validateOn?.value !== on) return;\n\n if (!formField) {\n // Not user-facing — this only fires when a component author wired `validateOn` but\n // forgot the matching `attachFormField(useField(...))` call, so validation silently\n // never runs instead of erroring where the mistake actually is.\n warn(\n \"triggerValidation() called before attachFormField() — validation will not run. See createTextField()/createChoiceField()/createCheckable()'s attachFormField doc comment.\",\n );\n\n return;\n }\n\n formField.reportValidity();\n };\n\n return {\n ariaDescribedBy,\n ariaErrorMessage,\n ariaInvalid,\n assistiveId,\n attachFormField,\n disabled,\n errorId,\n errorText: computed(() => resolvedAssistive.value.errorText),\n helperText: computed(() => resolvedAssistive.value.helperText),\n triggerValidation,\n };\n};\n\n// ── Label state (visible `<label>` + `aria-labelledby`) ────────────────────────\n\nexport type LabelStateHandle = {\n /** `aria-labelledby` value. Non-null when a label is visible. */\n ariaLabelledBy: Readable<string | null>;\n /** Stable `id` for the underlying form control — the `<label for=\"...\">` target. */\n fieldId: string;\n /** Stable `id` for the label element. Stamp this on your `<label id=\"...\">`. */\n labelId: string;\n /** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */\n labelVisible: Readable<boolean>;\n};\n\nexport type LabelStateOptions = {\n /**\n * Override for label visibility that takes precedence over deriving it from\n * the `label` text signal. Pass a computed signal that checks both prop and\n * slot presence to enable slot-first composition.\n *\n * @example\n * ```ts\n * const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n * const label = createLabelState({ ...options, hasLabel });\n * ```\n */\n hasLabel?: Readable<boolean>;\n /**\n * When provided, used directly as `fieldId` instead of generating one via\n * `createStableId`. Useful in tests for deterministic ID assertions.\n */\n id?: string;\n /**\n * Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are\n * computed reactively from this value (see `hasLabel` for the slot-first override).\n */\n label?: Readable<string | undefined>;\n /**\n * Label placement signal. Defaults to `'inset'`.\n *\n * Not consumed by `createLabelState()` itself — visibility is driven solely by\n * `hasLabel`/`label`. Threaded through so component authors can apply\n * placement-specific styling (`label-placement` attribute) without a second\n * option; if placement-dependent visibility is ever needed, wire it into\n * `labelVisible` here instead of adding a new option.\n */\n labelPlacement?: Readable<LabelPlacement>;\n prefix?: string;\n};\n\nexport const createLabelState = (options: LabelStateOptions): LabelStateHandle => {\n const fieldId = options.id ?? createStableId(options.prefix ?? 'field');\n const labelId = createStableId('label');\n const label$ = options.label ?? computed(() => undefined);\n\n // Slot-first composition: if the caller provides `hasLabel`, use it instead\n // of deriving visibility purely from the label text signal. This allows\n // components with a `<slot name=\"label\">` to stay visible even when the\n // `label` prop is empty but a slotted label element is present.\n const labelVisible = options.hasLabel ?? computed(() => Boolean(label$.value));\n const ariaLabelledBy = computed(() => (labelVisible.value ? labelId : null));\n\n return { ariaLabelledBy, fieldId, labelId, labelVisible };\n};\n\n// ── Field handle (assistive + label, composed) ──────────────────────────────\n\n/**\n * Common handle returned by `createField` — the base for both `TextFieldHandle`\n * and `ChoiceFieldHandle`. Composes `createAssistiveState` + `createLabelState`;\n * call those directly instead if a field has no visible `<label>` (see\n * `createAssistiveState`'s doc comment).\n *\n * ARIA signals and label state are flat on the handle — no nested `aria` or\n * `label` sub-objects. `labelId`/`labelVisible`/`ariaLabelledBy` are always\n * present but only meaningful when `label`/`hasLabel` was passed — omit both\n * and they resolve to an always-hidden, unreferenced label.\n *\n * @example\n * ```html\n * <label id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">...</label>\n * <input\n * aria-labelledby=\"${ariaLabelledBy}\"\n * aria-describedby=\"${ariaDescribedBy}\"\n * aria-invalid=\"${ariaInvalid}\" />\n * ```\n */\nexport type FieldHandle = AssistiveStateHandle & LabelStateHandle;\n\n/** Options shared by both `createTextField` and `createChoiceField`. */\nexport type FieldOptions = AssistiveStateOptions & LabelStateOptions;\n\nexport const createField = (options: FieldOptions): FieldHandle => ({\n ...createAssistiveState(options),\n ...createLabelState(options),\n});\n"],"mappings":"oFAsBA,IAAa,EAA0B,IAAA,EACrC,EAAA,SAAA,MAAgB,CACd,UAAW,EAAQ,OAAO,OAAS,GACnC,WAAY,EAAQ,QAAQ,OAAS,EACvC,EAAE,EAyBS,EAAsB,IAAA,EACjC,EAAA,SAAA,KAA6B,CAC3B,IAAM,EAAQ,EAAQ,OAAO,OAAS,GAChC,EAAY,EAAQ,WAAW,MAC/B,EAAkB,OAAO,CAAS,EAClC,EAAiB,OAAO,SAAS,CAAe,GAAK,EAAkB,EAAI,EAAkB,KAC7F,EAAa,IAAmB,KAChC,EAAc,EAAa,GAAG,EAAM,OAAO,KAAK,IAAmB,GACnE,EAAQ,EAAa,EAAM,OAAS,EAAiB,EAE3D,MAAO,CACL,eAAgB,EAAa,GAAS,EAAI,GAC1C,iBAAkB,EAAa,GAAS,IAAO,EAAQ,EAAI,GAC3D,aACF,CACF,CAAC,EAYU,GAAoB,EAAmC,EAAO,YACpE,EAED,EAAQ,eAAuB,GAAG,EAAK,WAEvC,EAAQ,iBAAyB,GAAG,EAAK,aAEtC,EANc,EA4BvB,SAAgB,GAAmC,CACjD,IAAI,EAAQ,GAEZ,MAAO,CACL,UAAa,CACX,EAAQ,EACV,EACA,IAAI,SAAU,CACZ,OAAO,CACT,EACA,cAAiB,CACf,EAAQ,EACV,CACF,CACF,CAqDA,IAAa,EAAwB,GAAyD,CAC5F,IAAM,GAAA,EAAW,EAAA,SAAA,KAAe,EAAQ,EAAQ,UAAU,KAAM,EAC1D,GAAA,EAAc,EAAA,eAAA,CAAe,QAAQ,EACrC,GAAA,EAAU,EAAA,eAAA,CAAe,OAAO,EAChC,EAAoB,EAAuB,CAAE,MAAO,EAAQ,MAAO,OAAQ,EAAQ,MAAO,CAAC,EAE3F,GAAA,EAAkB,EAAA,SAAA,KACtB,EAAkB,MAAM,WAAa,EAAkB,MAAM,WAAa,EAAc,IAC1F,EACM,GAAA,EAAmB,EAAA,SAAA,KAAgB,EAAkB,MAAM,UAAY,EAAU,IAAK,EACtF,GAAA,EAAc,EAAA,SAAA,KAA+B,EAAkB,MAAM,UAAY,OAAS,IAAK,EAEjG,EAA+C,KAsBnD,MAAO,CACL,kBACA,mBACA,cACA,cACA,gBA1BuB,GAAoD,CAC3E,EAAY,CACd,EAyBE,WACA,UACA,WAAA,EAAW,EAAA,SAAA,KAAe,EAAkB,MAAM,SAAS,EAC3D,YAAA,EAAY,EAAA,SAAA,KAAe,EAAkB,MAAM,UAAU,EAC7D,kBA3ByB,GAA4D,CACjF,EAAQ,YAAY,QAAU,GAE7B,GAWL,EAAU,eAAe,CAC3B,CAaA,CACF,EAmDa,EAAoB,GAAiD,CAChF,IAAM,EAAU,EAAQ,KAAA,EAAM,EAAA,eAAA,CAAe,EAAQ,QAAU,OAAO,EAChE,GAAA,EAAU,EAAA,eAAA,CAAe,OAAO,EAChC,EAAS,EAAQ,QAAA,EAAS,EAAA,SAAA,KAAe,IAAA,EAAS,EAMlD,EAAe,EAAQ,WAAA,EAAY,EAAA,SAAA,KAAe,EAAQ,EAAO,KAAM,EAG7E,MAAO,CAAE,gBAAA,EAFc,EAAA,SAAA,KAAgB,EAAa,MAAQ,EAAU,IAE7D,EAAgB,UAAS,UAAS,cAAa,CAC1D,EA6Ba,EAAe,IAAwC,CAClE,GAAG,EAAqB,CAAO,EAC/B,GAAG,EAAiB,CAAO,CAC7B"}
|
|
1
|
+
{"version":3,"file":"field-base.cjs","names":[],"sources":["../../src/core/field-base.ts"],"sourcesContent":["import { createId } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\n\n// ── Validation / context types ────────────────────────────────────────────────\n\nexport type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';\nexport type ControlValidationMode = ValidationTrigger | undefined;\n\n// ── Error/helper assistive state ──────────────────────────────────────────────\n\nexport type ErrorHelperState = {\n errorText: string;\n helperText: string;\n};\n\nexport type ErrorHelperOptions = {\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n};\n\nexport const createErrorHelperState = (options: ErrorHelperOptions): Readable<ErrorHelperState> =>\n computed(() => ({\n errorText: options.error?.value ?? '',\n helperText: options.helper?.value ?? '',\n }));\n\n// ── Counter state (opt-in) ─────────────────────────────────────────────────────\n\n/** Counter state for text fields with `maxLength`. */\nexport type CounterState = {\n counterAtLimit: boolean;\n counterNearLimit: boolean;\n counterText: string;\n};\n\nexport type CounterOptions = {\n maxLength?: Readable<number | undefined>;\n value: Readable<string | undefined>;\n};\n\n/**\n * Creates a reactive counter state signal for text fields with `maxLength`.\n * Only call this when `maxLength` may be set — the state is genuinely opt-in.\n *\n * @example\n * ```ts\n * const counter = createCounterState({ value: tf.value, maxLength: props.maxlength });\n * ```\n */\nexport const createCounterState = (options: CounterOptions): Readable<CounterState> =>\n computed<CounterState>(() => {\n const value = options.value?.value ?? '';\n const maxLength = options.maxLength?.value;\n const parsedMaxLength = Number(maxLength);\n const validMaxLength = Number.isFinite(parsedMaxLength) && parsedMaxLength > 0 ? parsedMaxLength : null;\n const hasCounter = validMaxLength !== null;\n const counterText = hasCounter ? `${value.length} / ${validMaxLength}` : '';\n const ratio = hasCounter ? value.length / validMaxLength : 0;\n\n return {\n counterAtLimit: hasCounter ? ratio >= 1 : false,\n counterNearLimit: hasCounter ? ratio >= 0.9 && ratio < 1 : false,\n counterText,\n };\n });\n\n/**\n * The single implementation of \"which modifier class does this counter state map to\" —\n * every field with a character counter (`ore-input`, `ore-textarea`, `ore-message-composer`)\n * renders the identical near-limit/at-limit styling, so this is the one place that decides it.\n *\n * @example\n * ```ts\n * html`<span class=\"${() => counterClassName(counter?.value)}\">...</span>`\n * ```\n */\nexport const counterClassName = (counter: CounterState | undefined, base = 'counter'): string => {\n if (!counter) return base;\n\n if (counter.counterAtLimit) return `${base} at-limit`;\n\n if (counter.counterNearLimit) return `${base} near-limit`;\n\n return base;\n};\n\n// ── Dirty tracking (two-state reset) ───────────────────────────────────────────\n\n/**\n * Tracks whether a control's value has ever been changed by user interaction.\n *\n * Backs the \"two-state reset()\" pattern used by `createCheckable` (`checked`/\n * `indeterminate`) and `createChoiceField` (`selectedValues`): before the first\n * interaction, the live prop is still a reliable \"current default\" to resync from\n * (e.g. an async-loaded value arriving after mount); after it, the prop is\n * contaminated by interaction-driven attribute reflection, so only a snapshot taken\n * at creation still represents \"the default\" in the native sense. `reset()` callers\n * check `isDirty` to decide which source to revert to, then call `clear()`.\n */\nexport type DirtyTracker = {\n clear: () => void;\n readonly isDirty: boolean;\n markDirty: () => void;\n};\n\nexport function createDirtyTracker(): DirtyTracker {\n let dirty = false;\n\n return {\n clear: () => {\n dirty = false;\n },\n get isDirty() {\n return dirty;\n },\n markDirty: () => {\n dirty = true;\n },\n };\n}\n\n// ── Label placement ───────────────────────────────────────────────────────────\n\nexport type LabelPlacement = 'inset' | 'outside' | undefined;\n\n// ── Assistive state (error/helper text, describedby/errormessage/invalid) ─────\n\n/**\n * A field's error/helper/disabled/validation-trigger state — everything an input\n * needs *except* a visible `<label>`. Split out from label state (below) because\n * not every field has one: `ore-message-composer` names itself via `aria-label`\n * (a chat composer's accessible name conventionally comes from context/placeholder,\n * not a floating `<label>`), so it composes this directly instead of pulling in\n * label plumbing it can't use. `createField()` composes both for fields that do\n * render a `<label>`.\n */\nexport type AssistiveStateHandle = {\n /** `aria-describedby` value. Non-null when helper or error text is present. */\n ariaDescribedBy: Readable<string | null>;\n /** `aria-errormessage` value. Non-null when error text is present. */\n ariaErrorMessage: Readable<string | null>;\n /** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */\n ariaInvalid: Readable<'true' | null>;\n /**\n * The stable `id` used for `aria-describedby` on the input. Points at the\n * assistive-text region (covers both helper text and error text per WAI-ARIA).\n */\n assistiveId: string;\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()` can call its `reportValidity()`. See `TextFieldHandle.attachFormField`\n * for why this is a post-hoc call instead of a constructor option — the same forward\n * reference applies here for choice fields.\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n disabled: Readable<boolean>;\n /** Stable `id` for the inline error message element (`aria-errormessage`). */\n errorId: string;\n /** Reactive error text. Empty string when no error is set. */\n errorText: Readable<string>;\n /** Reactive helper text. Empty string when no helper is set. */\n helperText: Readable<string>;\n triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;\n};\n\nexport type AssistiveStateOptions = {\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n validateOn?: Readable<ControlValidationMode>;\n};\n\nexport const createAssistiveState = (options: AssistiveStateOptions): AssistiveStateHandle => {\n const disabled = computed(() => Boolean(options.disabled?.value));\n const assistiveId = createId('helper');\n const errorId = createId('error');\n const resolvedAssistive = createErrorHelperState({ error: options.error, helper: options.helper });\n\n const ariaDescribedBy = computed(() =>\n resolvedAssistive.value.errorText || resolvedAssistive.value.helperText ? assistiveId : null,\n );\n const ariaErrorMessage = computed(() => (resolvedAssistive.value.errorText ? errorId : null));\n const ariaInvalid = computed<'true' | null>(() => (resolvedAssistive.value.errorText ? 'true' : null));\n\n let formField: { reportValidity(): void } | null = null;\n const attachFormField = (nextFormField: { reportValidity(): void }): void => {\n formField = nextFormField;\n };\n\n const triggerValidation = (on: Extract<ValidationTrigger, 'blur' | 'change'>): void => {\n if (options.validateOn?.value !== on) return;\n\n if (!formField) {\n // Not user-facing — this only fires when a component author wired `validateOn` but\n // forgot the matching `attachFormField(useField(...))` call, so validation silently\n // never runs instead of erroring where the mistake actually is.\n warn(\n \"triggerValidation() called before attachFormField() — validation will not run. See createTextField()/createChoiceField()/createCheckable()'s attachFormField doc comment.\",\n );\n\n return;\n }\n\n formField.reportValidity();\n };\n\n return {\n ariaDescribedBy,\n ariaErrorMessage,\n ariaInvalid,\n assistiveId,\n attachFormField,\n disabled,\n errorId,\n errorText: computed(() => resolvedAssistive.value.errorText),\n helperText: computed(() => resolvedAssistive.value.helperText),\n triggerValidation,\n };\n};\n\n// ── Label state (visible `<label>` + `aria-labelledby`) ────────────────────────\n\nexport type LabelStateHandle = {\n /** `aria-labelledby` value. Non-null when a label is visible. */\n ariaLabelledBy: Readable<string | null>;\n /** Stable `id` for the underlying form control — the `<label for=\"...\">` target. */\n fieldId: string;\n /** Stable `id` for the label element. Stamp this on your `<label id=\"...\">`. */\n labelId: string;\n /** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */\n labelVisible: Readable<boolean>;\n};\n\nexport type LabelStateOptions = {\n /**\n * Override for label visibility that takes precedence over deriving it from\n * the `label` text signal. Pass a computed signal that checks both prop and\n * slot presence to enable slot-first composition.\n *\n * @example\n * ```ts\n * const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n * const label = createLabelState({ ...options, hasLabel });\n * ```\n */\n hasLabel?: Readable<boolean>;\n /**\n * When provided, used directly as `fieldId` instead of generating one via\n * `createId`. Useful in tests for deterministic ID assertions.\n */\n id?: string;\n /**\n * Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are\n * computed reactively from this value (see `hasLabel` for the slot-first override).\n */\n label?: Readable<string | undefined>;\n /**\n * Label placement signal. Defaults to `'inset'`.\n *\n * Not consumed by `createLabelState()` itself — visibility is driven solely by\n * `hasLabel`/`label`. Threaded through so component authors can apply\n * placement-specific styling (`label-placement` attribute) without a second\n * option; if placement-dependent visibility is ever needed, wire it into\n * `labelVisible` here instead of adding a new option.\n */\n labelPlacement?: Readable<LabelPlacement>;\n prefix?: string;\n};\n\nexport const createLabelState = (options: LabelStateOptions): LabelStateHandle => {\n const fieldId = options.id ?? createId(options.prefix ?? 'field');\n const labelId = createId('label');\n const label$ = options.label ?? computed(() => undefined);\n\n // Slot-first composition: if the caller provides `hasLabel`, use it instead\n // of deriving visibility purely from the label text signal. This allows\n // components with a `<slot name=\"label\">` to stay visible even when the\n // `label` prop is empty but a slotted label element is present.\n const labelVisible = options.hasLabel ?? computed(() => Boolean(label$.value));\n const ariaLabelledBy = computed(() => (labelVisible.value ? labelId : null));\n\n return { ariaLabelledBy, fieldId, labelId, labelVisible };\n};\n\n// ── Field handle (assistive + label, composed) ──────────────────────────────\n\n/**\n * Common handle returned by `createField` — the base for both `TextFieldHandle`\n * and `ChoiceFieldHandle`. Composes `createAssistiveState` + `createLabelState`;\n * call those directly instead if a field has no visible `<label>` (see\n * `createAssistiveState`'s doc comment).\n *\n * ARIA signals and label state are flat on the handle — no nested `aria` or\n * `label` sub-objects. `labelId`/`labelVisible`/`ariaLabelledBy` are always\n * present but only meaningful when `label`/`hasLabel` was passed — omit both\n * and they resolve to an always-hidden, unreferenced label.\n *\n * @example\n * ```html\n * <label id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">...</label>\n * <input\n * aria-labelledby=\"${ariaLabelledBy}\"\n * aria-describedby=\"${ariaDescribedBy}\"\n * aria-invalid=\"${ariaInvalid}\" />\n * ```\n */\nexport type FieldHandle = AssistiveStateHandle & LabelStateHandle;\n\n/** Options shared by both `createTextField` and `createChoiceField`. */\nexport type FieldOptions = AssistiveStateOptions & LabelStateOptions;\n\nexport const createField = (options: FieldOptions): FieldHandle => ({\n ...createAssistiveState(options),\n ...createLabelState(options),\n});\n"],"mappings":"oFAsBA,IAAa,EAA0B,IAAA,EACrC,EAAA,SAAA,MAAgB,CACd,UAAW,EAAQ,OAAO,OAAS,GACnC,WAAY,EAAQ,QAAQ,OAAS,EACvC,EAAE,EAyBS,EAAsB,IAAA,EACjC,EAAA,SAAA,KAA6B,CAC3B,IAAM,EAAQ,EAAQ,OAAO,OAAS,GAChC,EAAY,EAAQ,WAAW,MAC/B,EAAkB,OAAO,CAAS,EAClC,EAAiB,OAAO,SAAS,CAAe,GAAK,EAAkB,EAAI,EAAkB,KAC7F,EAAa,IAAmB,KAChC,EAAc,EAAa,GAAG,EAAM,OAAO,KAAK,IAAmB,GACnE,EAAQ,EAAa,EAAM,OAAS,EAAiB,EAE3D,MAAO,CACL,eAAgB,EAAa,GAAS,EAAI,GAC1C,iBAAkB,EAAa,GAAS,IAAO,EAAQ,EAAI,GAC3D,aACF,CACF,CAAC,EAYU,GAAoB,EAAmC,EAAO,YACpE,EAED,EAAQ,eAAuB,GAAG,EAAK,WAEvC,EAAQ,iBAAyB,GAAG,EAAK,aAEtC,EANc,EA4BvB,SAAgB,GAAmC,CACjD,IAAI,EAAQ,GAEZ,MAAO,CACL,UAAa,CACX,EAAQ,EACV,EACA,IAAI,SAAU,CACZ,OAAO,CACT,EACA,cAAiB,CACf,EAAQ,EACV,CACF,CACF,CAqDA,IAAa,EAAwB,GAAyD,CAC5F,IAAM,GAAA,EAAW,EAAA,SAAA,KAAe,EAAQ,EAAQ,UAAU,KAAM,EAC1D,GAAA,EAAc,EAAA,SAAA,CAAS,QAAQ,EAC/B,GAAA,EAAU,EAAA,SAAA,CAAS,OAAO,EAC1B,EAAoB,EAAuB,CAAE,MAAO,EAAQ,MAAO,OAAQ,EAAQ,MAAO,CAAC,EAE3F,GAAA,EAAkB,EAAA,SAAA,KACtB,EAAkB,MAAM,WAAa,EAAkB,MAAM,WAAa,EAAc,IAC1F,EACM,GAAA,EAAmB,EAAA,SAAA,KAAgB,EAAkB,MAAM,UAAY,EAAU,IAAK,EACtF,GAAA,EAAc,EAAA,SAAA,KAA+B,EAAkB,MAAM,UAAY,OAAS,IAAK,EAEjG,EAA+C,KAsBnD,MAAO,CACL,kBACA,mBACA,cACA,cACA,gBA1BuB,GAAoD,CAC3E,EAAY,CACd,EAyBE,WACA,UACA,WAAA,EAAW,EAAA,SAAA,KAAe,EAAkB,MAAM,SAAS,EAC3D,YAAA,EAAY,EAAA,SAAA,KAAe,EAAkB,MAAM,UAAU,EAC7D,kBA3ByB,GAA4D,CACjF,EAAQ,YAAY,QAAU,GAE7B,GAWL,EAAU,eAAe,CAC3B,CAaA,CACF,EAmDa,EAAoB,GAAiD,CAChF,IAAM,EAAU,EAAQ,KAAA,EAAM,EAAA,SAAA,CAAS,EAAQ,QAAU,OAAO,EAC1D,GAAA,EAAU,EAAA,SAAA,CAAS,OAAO,EAC1B,EAAS,EAAQ,QAAA,EAAS,EAAA,SAAA,KAAe,IAAA,EAAS,EAMlD,EAAe,EAAQ,WAAA,EAAY,EAAA,SAAA,KAAe,EAAQ,EAAO,KAAM,EAG7E,MAAO,CAAE,gBAAA,EAFc,EAAA,SAAA,KAAgB,EAAa,MAAQ,EAAU,IAE7D,EAAgB,UAAS,UAAS,cAAa,CAC1D,EA6Ba,EAAe,IAAwC,CAClE,GAAG,EAAqB,CAAO,EAC/B,GAAG,EAAiB,CAAO,CAC7B"}
|
|
@@ -130,7 +130,7 @@ export type LabelStateOptions = {
|
|
|
130
130
|
hasLabel?: Readable<boolean>;
|
|
131
131
|
/**
|
|
132
132
|
* When provided, used directly as `fieldId` instead of generating one via
|
|
133
|
-
* `
|
|
133
|
+
* `createId`. Useful in tests for deterministic ID assertions.
|
|
134
134
|
*/
|
|
135
135
|
id?: string;
|
|
136
136
|
/**
|
package/dist/core/field-base.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-base.js","names":[],"sources":["../../src/core/field-base.ts"],"sourcesContent":["import { createStableId } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\n\n// ── Validation / context types ────────────────────────────────────────────────\n\nexport type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';\nexport type ControlValidationMode = ValidationTrigger | undefined;\n\n// ── Error/helper assistive state ──────────────────────────────────────────────\n\nexport type ErrorHelperState = {\n errorText: string;\n helperText: string;\n};\n\nexport type ErrorHelperOptions = {\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n};\n\nexport const createErrorHelperState = (options: ErrorHelperOptions): Readable<ErrorHelperState> =>\n computed(() => ({\n errorText: options.error?.value ?? '',\n helperText: options.helper?.value ?? '',\n }));\n\n// ── Counter state (opt-in) ─────────────────────────────────────────────────────\n\n/** Counter state for text fields with `maxLength`. */\nexport type CounterState = {\n counterAtLimit: boolean;\n counterNearLimit: boolean;\n counterText: string;\n};\n\nexport type CounterOptions = {\n maxLength?: Readable<number | undefined>;\n value: Readable<string | undefined>;\n};\n\n/**\n * Creates a reactive counter state signal for text fields with `maxLength`.\n * Only call this when `maxLength` may be set — the state is genuinely opt-in.\n *\n * @example\n * ```ts\n * const counter = createCounterState({ value: tf.value, maxLength: props.maxlength });\n * ```\n */\nexport const createCounterState = (options: CounterOptions): Readable<CounterState> =>\n computed<CounterState>(() => {\n const value = options.value?.value ?? '';\n const maxLength = options.maxLength?.value;\n const parsedMaxLength = Number(maxLength);\n const validMaxLength = Number.isFinite(parsedMaxLength) && parsedMaxLength > 0 ? parsedMaxLength : null;\n const hasCounter = validMaxLength !== null;\n const counterText = hasCounter ? `${value.length} / ${validMaxLength}` : '';\n const ratio = hasCounter ? value.length / validMaxLength : 0;\n\n return {\n counterAtLimit: hasCounter ? ratio >= 1 : false,\n counterNearLimit: hasCounter ? ratio >= 0.9 && ratio < 1 : false,\n counterText,\n };\n });\n\n/**\n * The single implementation of \"which modifier class does this counter state map to\" —\n * every field with a character counter (`ore-input`, `ore-textarea`, `ore-message-composer`)\n * renders the identical near-limit/at-limit styling, so this is the one place that decides it.\n *\n * @example\n * ```ts\n * html`<span class=\"${() => counterClassName(counter?.value)}\">...</span>`\n * ```\n */\nexport const counterClassName = (counter: CounterState | undefined, base = 'counter'): string => {\n if (!counter) return base;\n\n if (counter.counterAtLimit) return `${base} at-limit`;\n\n if (counter.counterNearLimit) return `${base} near-limit`;\n\n return base;\n};\n\n// ── Dirty tracking (two-state reset) ───────────────────────────────────────────\n\n/**\n * Tracks whether a control's value has ever been changed by user interaction.\n *\n * Backs the \"two-state reset()\" pattern used by `createCheckable` (`checked`/\n * `indeterminate`) and `createChoiceField` (`selectedValues`): before the first\n * interaction, the live prop is still a reliable \"current default\" to resync from\n * (e.g. an async-loaded value arriving after mount); after it, the prop is\n * contaminated by interaction-driven attribute reflection, so only a snapshot taken\n * at creation still represents \"the default\" in the native sense. `reset()` callers\n * check `isDirty` to decide which source to revert to, then call `clear()`.\n */\nexport type DirtyTracker = {\n clear: () => void;\n readonly isDirty: boolean;\n markDirty: () => void;\n};\n\nexport function createDirtyTracker(): DirtyTracker {\n let dirty = false;\n\n return {\n clear: () => {\n dirty = false;\n },\n get isDirty() {\n return dirty;\n },\n markDirty: () => {\n dirty = true;\n },\n };\n}\n\n// ── Label placement ───────────────────────────────────────────────────────────\n\nexport type LabelPlacement = 'inset' | 'outside' | undefined;\n\n// ── Assistive state (error/helper text, describedby/errormessage/invalid) ─────\n\n/**\n * A field's error/helper/disabled/validation-trigger state — everything an input\n * needs *except* a visible `<label>`. Split out from label state (below) because\n * not every field has one: `ore-message-composer` names itself via `aria-label`\n * (a chat composer's accessible name conventionally comes from context/placeholder,\n * not a floating `<label>`), so it composes this directly instead of pulling in\n * label plumbing it can't use. `createField()` composes both for fields that do\n * render a `<label>`.\n */\nexport type AssistiveStateHandle = {\n /** `aria-describedby` value. Non-null when helper or error text is present. */\n ariaDescribedBy: Readable<string | null>;\n /** `aria-errormessage` value. Non-null when error text is present. */\n ariaErrorMessage: Readable<string | null>;\n /** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */\n ariaInvalid: Readable<'true' | null>;\n /**\n * The stable `id` used for `aria-describedby` on the input. Points at the\n * assistive-text region (covers both helper text and error text per WAI-ARIA).\n */\n assistiveId: string;\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()` can call its `reportValidity()`. See `TextFieldHandle.attachFormField`\n * for why this is a post-hoc call instead of a constructor option — the same forward\n * reference applies here for choice fields.\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n disabled: Readable<boolean>;\n /** Stable `id` for the inline error message element (`aria-errormessage`). */\n errorId: string;\n /** Reactive error text. Empty string when no error is set. */\n errorText: Readable<string>;\n /** Reactive helper text. Empty string when no helper is set. */\n helperText: Readable<string>;\n triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;\n};\n\nexport type AssistiveStateOptions = {\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n validateOn?: Readable<ControlValidationMode>;\n};\n\nexport const createAssistiveState = (options: AssistiveStateOptions): AssistiveStateHandle => {\n const disabled = computed(() => Boolean(options.disabled?.value));\n const assistiveId = createStableId('helper');\n const errorId = createStableId('error');\n const resolvedAssistive = createErrorHelperState({ error: options.error, helper: options.helper });\n\n const ariaDescribedBy = computed(() =>\n resolvedAssistive.value.errorText || resolvedAssistive.value.helperText ? assistiveId : null,\n );\n const ariaErrorMessage = computed(() => (resolvedAssistive.value.errorText ? errorId : null));\n const ariaInvalid = computed<'true' | null>(() => (resolvedAssistive.value.errorText ? 'true' : null));\n\n let formField: { reportValidity(): void } | null = null;\n const attachFormField = (nextFormField: { reportValidity(): void }): void => {\n formField = nextFormField;\n };\n\n const triggerValidation = (on: Extract<ValidationTrigger, 'blur' | 'change'>): void => {\n if (options.validateOn?.value !== on) return;\n\n if (!formField) {\n // Not user-facing — this only fires when a component author wired `validateOn` but\n // forgot the matching `attachFormField(useField(...))` call, so validation silently\n // never runs instead of erroring where the mistake actually is.\n warn(\n \"triggerValidation() called before attachFormField() — validation will not run. See createTextField()/createChoiceField()/createCheckable()'s attachFormField doc comment.\",\n );\n\n return;\n }\n\n formField.reportValidity();\n };\n\n return {\n ariaDescribedBy,\n ariaErrorMessage,\n ariaInvalid,\n assistiveId,\n attachFormField,\n disabled,\n errorId,\n errorText: computed(() => resolvedAssistive.value.errorText),\n helperText: computed(() => resolvedAssistive.value.helperText),\n triggerValidation,\n };\n};\n\n// ── Label state (visible `<label>` + `aria-labelledby`) ────────────────────────\n\nexport type LabelStateHandle = {\n /** `aria-labelledby` value. Non-null when a label is visible. */\n ariaLabelledBy: Readable<string | null>;\n /** Stable `id` for the underlying form control — the `<label for=\"...\">` target. */\n fieldId: string;\n /** Stable `id` for the label element. Stamp this on your `<label id=\"...\">`. */\n labelId: string;\n /** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */\n labelVisible: Readable<boolean>;\n};\n\nexport type LabelStateOptions = {\n /**\n * Override for label visibility that takes precedence over deriving it from\n * the `label` text signal. Pass a computed signal that checks both prop and\n * slot presence to enable slot-first composition.\n *\n * @example\n * ```ts\n * const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n * const label = createLabelState({ ...options, hasLabel });\n * ```\n */\n hasLabel?: Readable<boolean>;\n /**\n * When provided, used directly as `fieldId` instead of generating one via\n * `createStableId`. Useful in tests for deterministic ID assertions.\n */\n id?: string;\n /**\n * Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are\n * computed reactively from this value (see `hasLabel` for the slot-first override).\n */\n label?: Readable<string | undefined>;\n /**\n * Label placement signal. Defaults to `'inset'`.\n *\n * Not consumed by `createLabelState()` itself — visibility is driven solely by\n * `hasLabel`/`label`. Threaded through so component authors can apply\n * placement-specific styling (`label-placement` attribute) without a second\n * option; if placement-dependent visibility is ever needed, wire it into\n * `labelVisible` here instead of adding a new option.\n */\n labelPlacement?: Readable<LabelPlacement>;\n prefix?: string;\n};\n\nexport const createLabelState = (options: LabelStateOptions): LabelStateHandle => {\n const fieldId = options.id ?? createStableId(options.prefix ?? 'field');\n const labelId = createStableId('label');\n const label$ = options.label ?? computed(() => undefined);\n\n // Slot-first composition: if the caller provides `hasLabel`, use it instead\n // of deriving visibility purely from the label text signal. This allows\n // components with a `<slot name=\"label\">` to stay visible even when the\n // `label` prop is empty but a slotted label element is present.\n const labelVisible = options.hasLabel ?? computed(() => Boolean(label$.value));\n const ariaLabelledBy = computed(() => (labelVisible.value ? labelId : null));\n\n return { ariaLabelledBy, fieldId, labelId, labelVisible };\n};\n\n// ── Field handle (assistive + label, composed) ──────────────────────────────\n\n/**\n * Common handle returned by `createField` — the base for both `TextFieldHandle`\n * and `ChoiceFieldHandle`. Composes `createAssistiveState` + `createLabelState`;\n * call those directly instead if a field has no visible `<label>` (see\n * `createAssistiveState`'s doc comment).\n *\n * ARIA signals and label state are flat on the handle — no nested `aria` or\n * `label` sub-objects. `labelId`/`labelVisible`/`ariaLabelledBy` are always\n * present but only meaningful when `label`/`hasLabel` was passed — omit both\n * and they resolve to an always-hidden, unreferenced label.\n *\n * @example\n * ```html\n * <label id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">...</label>\n * <input\n * aria-labelledby=\"${ariaLabelledBy}\"\n * aria-describedby=\"${ariaDescribedBy}\"\n * aria-invalid=\"${ariaInvalid}\" />\n * ```\n */\nexport type FieldHandle = AssistiveStateHandle & LabelStateHandle;\n\n/** Options shared by both `createTextField` and `createChoiceField`. */\nexport type FieldOptions = AssistiveStateOptions & LabelStateOptions;\n\nexport const createField = (options: FieldOptions): FieldHandle => ({\n ...createAssistiveState(options),\n ...createLabelState(options),\n});\n"],"mappings":";;;;AAsBA,IAAa,KAA0B,MACrC,SAAgB;CACd,WAAW,EAAQ,OAAO,SAAS;CACnC,YAAY,EAAQ,QAAQ,SAAS;AACvC,EAAE,GAyBS,KAAsB,MACjC,QAA6B;CAC3B,IAAM,IAAQ,EAAQ,OAAO,SAAS,IAChC,IAAY,EAAQ,WAAW,OAC/B,IAAkB,OAAO,CAAS,GAClC,IAAiB,OAAO,SAAS,CAAe,KAAK,IAAkB,IAAI,IAAkB,MAC7F,IAAa,MAAmB,MAChC,IAAc,IAAa,GAAG,EAAM,OAAO,KAAK,MAAmB,IACnE,IAAQ,IAAa,EAAM,SAAS,IAAiB;CAE3D,OAAO;EACL,gBAAgB,IAAa,KAAS,IAAI;EAC1C,kBAAkB,IAAa,KAAS,MAAO,IAAQ,IAAI;EAC3D;CACF;AACF,CAAC,GAYU,KAAoB,GAAmC,IAAO,cACpE,IAED,EAAQ,iBAAuB,GAAG,EAAK,aAEvC,EAAQ,mBAAyB,GAAG,EAAK,eAEtC,IANc;AA4BvB,SAAgB,IAAmC;CACjD,IAAI,IAAQ;CAEZ,OAAO;EACL,aAAa;GACX,IAAQ;EACV;EACA,IAAI,UAAU;GACZ,OAAO;EACT;EACA,iBAAiB;GACf,IAAQ;EACV;CACF;AACF;AAqDA,IAAa,KAAwB,MAAyD;CAC5F,IAAM,IAAW,QAAe,EAAQ,EAAQ,UAAU,KAAM,GAC1D,IAAc,EAAe,QAAQ,GACrC,IAAU,EAAe,OAAO,GAChC,IAAoB,EAAuB;EAAE,OAAO,EAAQ;EAAO,QAAQ,EAAQ;CAAO,CAAC,GAE3F,IAAkB,QACtB,EAAkB,MAAM,aAAa,EAAkB,MAAM,aAAa,IAAc,IAC1F,GACM,IAAmB,QAAgB,EAAkB,MAAM,YAAY,IAAU,IAAK,GACtF,IAAc,QAA+B,EAAkB,MAAM,YAAY,SAAS,IAAK,GAEjG,IAA+C;CAsBnD,OAAO;EACL;EACA;EACA;EACA;EACA,kBA1BuB,MAAoD;GAC3E,IAAY;EACd;EAyBE;EACA;EACA,WAAW,QAAe,EAAkB,MAAM,SAAS;EAC3D,YAAY,QAAe,EAAkB,MAAM,UAAU;EAC7D,oBA3ByB,MAA4D;GACjF,EAAQ,YAAY,UAAU,KAE7B,KAWL,EAAU,eAAe;EAC3B;CAaA;AACF,GAmDa,KAAoB,MAAiD;CAChF,IAAM,IAAU,EAAQ,MAAM,EAAe,EAAQ,UAAU,OAAO,GAChE,IAAU,EAAe,OAAO,GAChC,IAAS,EAAQ,SAAS,QAAe,KAAA,CAAS,GAMlD,IAAe,EAAQ,YAAY,QAAe,EAAQ,EAAO,KAAM;CAG7E,OAAO;EAAE,gBAFc,QAAgB,EAAa,QAAQ,IAAU,IAE7D;EAAgB;EAAS;EAAS;CAAa;AAC1D,GA6Ba,KAAe,OAAwC;CAClE,GAAG,EAAqB,CAAO;CAC/B,GAAG,EAAiB,CAAO;AAC7B"}
|
|
1
|
+
{"version":3,"file":"field-base.js","names":[],"sources":["../../src/core/field-base.ts"],"sourcesContent":["import { createId } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\n\n// ── Validation / context types ────────────────────────────────────────────────\n\nexport type ValidationTrigger = 'blur' | 'change' | 'input' | 'submit';\nexport type ControlValidationMode = ValidationTrigger | undefined;\n\n// ── Error/helper assistive state ──────────────────────────────────────────────\n\nexport type ErrorHelperState = {\n errorText: string;\n helperText: string;\n};\n\nexport type ErrorHelperOptions = {\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n};\n\nexport const createErrorHelperState = (options: ErrorHelperOptions): Readable<ErrorHelperState> =>\n computed(() => ({\n errorText: options.error?.value ?? '',\n helperText: options.helper?.value ?? '',\n }));\n\n// ── Counter state (opt-in) ─────────────────────────────────────────────────────\n\n/** Counter state for text fields with `maxLength`. */\nexport type CounterState = {\n counterAtLimit: boolean;\n counterNearLimit: boolean;\n counterText: string;\n};\n\nexport type CounterOptions = {\n maxLength?: Readable<number | undefined>;\n value: Readable<string | undefined>;\n};\n\n/**\n * Creates a reactive counter state signal for text fields with `maxLength`.\n * Only call this when `maxLength` may be set — the state is genuinely opt-in.\n *\n * @example\n * ```ts\n * const counter = createCounterState({ value: tf.value, maxLength: props.maxlength });\n * ```\n */\nexport const createCounterState = (options: CounterOptions): Readable<CounterState> =>\n computed<CounterState>(() => {\n const value = options.value?.value ?? '';\n const maxLength = options.maxLength?.value;\n const parsedMaxLength = Number(maxLength);\n const validMaxLength = Number.isFinite(parsedMaxLength) && parsedMaxLength > 0 ? parsedMaxLength : null;\n const hasCounter = validMaxLength !== null;\n const counterText = hasCounter ? `${value.length} / ${validMaxLength}` : '';\n const ratio = hasCounter ? value.length / validMaxLength : 0;\n\n return {\n counterAtLimit: hasCounter ? ratio >= 1 : false,\n counterNearLimit: hasCounter ? ratio >= 0.9 && ratio < 1 : false,\n counterText,\n };\n });\n\n/**\n * The single implementation of \"which modifier class does this counter state map to\" —\n * every field with a character counter (`ore-input`, `ore-textarea`, `ore-message-composer`)\n * renders the identical near-limit/at-limit styling, so this is the one place that decides it.\n *\n * @example\n * ```ts\n * html`<span class=\"${() => counterClassName(counter?.value)}\">...</span>`\n * ```\n */\nexport const counterClassName = (counter: CounterState | undefined, base = 'counter'): string => {\n if (!counter) return base;\n\n if (counter.counterAtLimit) return `${base} at-limit`;\n\n if (counter.counterNearLimit) return `${base} near-limit`;\n\n return base;\n};\n\n// ── Dirty tracking (two-state reset) ───────────────────────────────────────────\n\n/**\n * Tracks whether a control's value has ever been changed by user interaction.\n *\n * Backs the \"two-state reset()\" pattern used by `createCheckable` (`checked`/\n * `indeterminate`) and `createChoiceField` (`selectedValues`): before the first\n * interaction, the live prop is still a reliable \"current default\" to resync from\n * (e.g. an async-loaded value arriving after mount); after it, the prop is\n * contaminated by interaction-driven attribute reflection, so only a snapshot taken\n * at creation still represents \"the default\" in the native sense. `reset()` callers\n * check `isDirty` to decide which source to revert to, then call `clear()`.\n */\nexport type DirtyTracker = {\n clear: () => void;\n readonly isDirty: boolean;\n markDirty: () => void;\n};\n\nexport function createDirtyTracker(): DirtyTracker {\n let dirty = false;\n\n return {\n clear: () => {\n dirty = false;\n },\n get isDirty() {\n return dirty;\n },\n markDirty: () => {\n dirty = true;\n },\n };\n}\n\n// ── Label placement ───────────────────────────────────────────────────────────\n\nexport type LabelPlacement = 'inset' | 'outside' | undefined;\n\n// ── Assistive state (error/helper text, describedby/errormessage/invalid) ─────\n\n/**\n * A field's error/helper/disabled/validation-trigger state — everything an input\n * needs *except* a visible `<label>`. Split out from label state (below) because\n * not every field has one: `ore-message-composer` names itself via `aria-label`\n * (a chat composer's accessible name conventionally comes from context/placeholder,\n * not a floating `<label>`), so it composes this directly instead of pulling in\n * label plumbing it can't use. `createField()` composes both for fields that do\n * render a `<label>`.\n */\nexport type AssistiveStateHandle = {\n /** `aria-describedby` value. Non-null when helper or error text is present. */\n ariaDescribedBy: Readable<string | null>;\n /** `aria-errormessage` value. Non-null when error text is present. */\n ariaErrorMessage: Readable<string | null>;\n /** `aria-invalid` value. `'true'` when error text is present, otherwise `null`. */\n ariaInvalid: Readable<'true' | null>;\n /**\n * The stable `id` used for `aria-describedby` on the input. Points at the\n * assistive-text region (covers both helper text and error text per WAI-ARIA).\n */\n assistiveId: string;\n /**\n * Registers the real form field handle (the return value of `useField()`) so that\n * `triggerValidation()` can call its `reportValidity()`. See `TextFieldHandle.attachFormField`\n * for why this is a post-hoc call instead of a constructor option — the same forward\n * reference applies here for choice fields.\n */\n attachFormField: (formField: { reportValidity(): void }) => void;\n disabled: Readable<boolean>;\n /** Stable `id` for the inline error message element (`aria-errormessage`). */\n errorId: string;\n /** Reactive error text. Empty string when no error is set. */\n errorText: Readable<string>;\n /** Reactive helper text. Empty string when no helper is set. */\n helperText: Readable<string>;\n triggerValidation: (on: Extract<ValidationTrigger, 'blur' | 'change'>) => void;\n};\n\nexport type AssistiveStateOptions = {\n disabled?: Readable<boolean | undefined>;\n error?: Readable<string | undefined>;\n helper?: Readable<string | undefined>;\n validateOn?: Readable<ControlValidationMode>;\n};\n\nexport const createAssistiveState = (options: AssistiveStateOptions): AssistiveStateHandle => {\n const disabled = computed(() => Boolean(options.disabled?.value));\n const assistiveId = createId('helper');\n const errorId = createId('error');\n const resolvedAssistive = createErrorHelperState({ error: options.error, helper: options.helper });\n\n const ariaDescribedBy = computed(() =>\n resolvedAssistive.value.errorText || resolvedAssistive.value.helperText ? assistiveId : null,\n );\n const ariaErrorMessage = computed(() => (resolvedAssistive.value.errorText ? errorId : null));\n const ariaInvalid = computed<'true' | null>(() => (resolvedAssistive.value.errorText ? 'true' : null));\n\n let formField: { reportValidity(): void } | null = null;\n const attachFormField = (nextFormField: { reportValidity(): void }): void => {\n formField = nextFormField;\n };\n\n const triggerValidation = (on: Extract<ValidationTrigger, 'blur' | 'change'>): void => {\n if (options.validateOn?.value !== on) return;\n\n if (!formField) {\n // Not user-facing — this only fires when a component author wired `validateOn` but\n // forgot the matching `attachFormField(useField(...))` call, so validation silently\n // never runs instead of erroring where the mistake actually is.\n warn(\n \"triggerValidation() called before attachFormField() — validation will not run. See createTextField()/createChoiceField()/createCheckable()'s attachFormField doc comment.\",\n );\n\n return;\n }\n\n formField.reportValidity();\n };\n\n return {\n ariaDescribedBy,\n ariaErrorMessage,\n ariaInvalid,\n assistiveId,\n attachFormField,\n disabled,\n errorId,\n errorText: computed(() => resolvedAssistive.value.errorText),\n helperText: computed(() => resolvedAssistive.value.helperText),\n triggerValidation,\n };\n};\n\n// ── Label state (visible `<label>` + `aria-labelledby`) ────────────────────────\n\nexport type LabelStateHandle = {\n /** `aria-labelledby` value. Non-null when a label is visible. */\n ariaLabelledBy: Readable<string | null>;\n /** Stable `id` for the underlying form control — the `<label for=\"...\">` target. */\n fieldId: string;\n /** Stable `id` for the label element. Stamp this on your `<label id=\"...\">`. */\n labelId: string;\n /** Whether the label should be visible. Use to toggle `hidden` on the `<label>`. */\n labelVisible: Readable<boolean>;\n};\n\nexport type LabelStateOptions = {\n /**\n * Override for label visibility that takes precedence over deriving it from\n * the `label` text signal. Pass a computed signal that checks both prop and\n * slot presence to enable slot-first composition.\n *\n * @example\n * ```ts\n * const hasLabel = computed(() => !!props.label.value || slots.has('label').value);\n * const label = createLabelState({ ...options, hasLabel });\n * ```\n */\n hasLabel?: Readable<boolean>;\n /**\n * When provided, used directly as `fieldId` instead of generating one via\n * `createId`. Useful in tests for deterministic ID assertions.\n */\n id?: string;\n /**\n * Label text signal. When provided, `labelVisible` and `ariaLabelledBy` are\n * computed reactively from this value (see `hasLabel` for the slot-first override).\n */\n label?: Readable<string | undefined>;\n /**\n * Label placement signal. Defaults to `'inset'`.\n *\n * Not consumed by `createLabelState()` itself — visibility is driven solely by\n * `hasLabel`/`label`. Threaded through so component authors can apply\n * placement-specific styling (`label-placement` attribute) without a second\n * option; if placement-dependent visibility is ever needed, wire it into\n * `labelVisible` here instead of adding a new option.\n */\n labelPlacement?: Readable<LabelPlacement>;\n prefix?: string;\n};\n\nexport const createLabelState = (options: LabelStateOptions): LabelStateHandle => {\n const fieldId = options.id ?? createId(options.prefix ?? 'field');\n const labelId = createId('label');\n const label$ = options.label ?? computed(() => undefined);\n\n // Slot-first composition: if the caller provides `hasLabel`, use it instead\n // of deriving visibility purely from the label text signal. This allows\n // components with a `<slot name=\"label\">` to stay visible even when the\n // `label` prop is empty but a slotted label element is present.\n const labelVisible = options.hasLabel ?? computed(() => Boolean(label$.value));\n const ariaLabelledBy = computed(() => (labelVisible.value ? labelId : null));\n\n return { ariaLabelledBy, fieldId, labelId, labelVisible };\n};\n\n// ── Field handle (assistive + label, composed) ──────────────────────────────\n\n/**\n * Common handle returned by `createField` — the base for both `TextFieldHandle`\n * and `ChoiceFieldHandle`. Composes `createAssistiveState` + `createLabelState`;\n * call those directly instead if a field has no visible `<label>` (see\n * `createAssistiveState`'s doc comment).\n *\n * ARIA signals and label state are flat on the handle — no nested `aria` or\n * `label` sub-objects. `labelId`/`labelVisible`/`ariaLabelledBy` are always\n * present but only meaningful when `label`/`hasLabel` was passed — omit both\n * and they resolve to an always-hidden, unreferenced label.\n *\n * @example\n * ```html\n * <label id=\"${labelId}\" ?hidden=\"${() => !labelVisible.value}\">...</label>\n * <input\n * aria-labelledby=\"${ariaLabelledBy}\"\n * aria-describedby=\"${ariaDescribedBy}\"\n * aria-invalid=\"${ariaInvalid}\" />\n * ```\n */\nexport type FieldHandle = AssistiveStateHandle & LabelStateHandle;\n\n/** Options shared by both `createTextField` and `createChoiceField`. */\nexport type FieldOptions = AssistiveStateOptions & LabelStateOptions;\n\nexport const createField = (options: FieldOptions): FieldHandle => ({\n ...createAssistiveState(options),\n ...createLabelState(options),\n});\n"],"mappings":";;;;AAsBA,IAAa,KAA0B,MACrC,SAAgB;CACd,WAAW,EAAQ,OAAO,SAAS;CACnC,YAAY,EAAQ,QAAQ,SAAS;AACvC,EAAE,GAyBS,KAAsB,MACjC,QAA6B;CAC3B,IAAM,IAAQ,EAAQ,OAAO,SAAS,IAChC,IAAY,EAAQ,WAAW,OAC/B,IAAkB,OAAO,CAAS,GAClC,IAAiB,OAAO,SAAS,CAAe,KAAK,IAAkB,IAAI,IAAkB,MAC7F,IAAa,MAAmB,MAChC,IAAc,IAAa,GAAG,EAAM,OAAO,KAAK,MAAmB,IACnE,IAAQ,IAAa,EAAM,SAAS,IAAiB;CAE3D,OAAO;EACL,gBAAgB,IAAa,KAAS,IAAI;EAC1C,kBAAkB,IAAa,KAAS,MAAO,IAAQ,IAAI;EAC3D;CACF;AACF,CAAC,GAYU,KAAoB,GAAmC,IAAO,cACpE,IAED,EAAQ,iBAAuB,GAAG,EAAK,aAEvC,EAAQ,mBAAyB,GAAG,EAAK,eAEtC,IANc;AA4BvB,SAAgB,IAAmC;CACjD,IAAI,IAAQ;CAEZ,OAAO;EACL,aAAa;GACX,IAAQ;EACV;EACA,IAAI,UAAU;GACZ,OAAO;EACT;EACA,iBAAiB;GACf,IAAQ;EACV;CACF;AACF;AAqDA,IAAa,KAAwB,MAAyD;CAC5F,IAAM,IAAW,QAAe,EAAQ,EAAQ,UAAU,KAAM,GAC1D,IAAc,EAAS,QAAQ,GAC/B,IAAU,EAAS,OAAO,GAC1B,IAAoB,EAAuB;EAAE,OAAO,EAAQ;EAAO,QAAQ,EAAQ;CAAO,CAAC,GAE3F,IAAkB,QACtB,EAAkB,MAAM,aAAa,EAAkB,MAAM,aAAa,IAAc,IAC1F,GACM,IAAmB,QAAgB,EAAkB,MAAM,YAAY,IAAU,IAAK,GACtF,IAAc,QAA+B,EAAkB,MAAM,YAAY,SAAS,IAAK,GAEjG,IAA+C;CAsBnD,OAAO;EACL;EACA;EACA;EACA;EACA,kBA1BuB,MAAoD;GAC3E,IAAY;EACd;EAyBE;EACA;EACA,WAAW,QAAe,EAAkB,MAAM,SAAS;EAC3D,YAAY,QAAe,EAAkB,MAAM,UAAU;EAC7D,oBA3ByB,MAA4D;GACjF,EAAQ,YAAY,UAAU,KAE7B,KAWL,EAAU,eAAe;EAC3B;CAaA;AACF,GAmDa,KAAoB,MAAiD;CAChF,IAAM,IAAU,EAAQ,MAAM,EAAS,EAAQ,UAAU,OAAO,GAC1D,IAAU,EAAS,OAAO,GAC1B,IAAS,EAAQ,SAAS,QAAe,KAAA,CAAS,GAMlD,IAAe,EAAQ,YAAY,QAAe,EAAQ,EAAO,KAAM;CAG7E,OAAO;EAAE,gBAFc,QAAgB,EAAa,QAAQ,IAAU,IAE7D;EAAgB;EAAS;EAAS;CAAa;AAC1D,GA6Ba,KAAe,OAAwC;CAClE,GAAG,EAAqB,CAAO;CAC/B,GAAG,EAAiB,CAAO;AAC7B"}
|
package/dist/core/nav.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("@vielzeug/ripple"),t=require("@vielzeug/focus");var n=n=>{let r=(0,e.signal)(-1),
|
|
1
|
+
let e=require("@vielzeug/ripple"),t=require("@vielzeug/focus");var n=n=>{let r=new AbortController,i=(0,e.signal)(-1),a=!1,o,s=(0,t.createListNavigation)({direction:n.direction,disabled:()=>!!n.disabled?.value,getItems:n.getItems,isItemDisabled:n.isItemDisabled,keys:n.keys,loop:n.loop,orientation:n.orientation,typeahead:n.typeahead}),c=()=>{i.value=s.getIndex()},l=e=>{if(e){i.value=e.index;try{n.onNavigate?.(e)}finally{c()}}return s.getIndex()},u=e=>{if(a)return-1;let t=s.set(e);return c(),t},d=e=>{if(a)return-1;let t=s.navigate(e);return l(t)},f=()=>{a||(s.reset(),c())},p=e=>{if(a)return!1;let t=s.handleKeydown(e);return t?(t.change&&l(t.change),t.handled):!1},m=()=>{a||(a=!0,s.reset(),i.value=-1,r.abort(),o?.(),o=void 0)};return n.signal?.aborted?m():n.signal&&(n.signal?.addEventListener(`abort`,m,{once:!0}),o=()=>n.signal?.removeEventListener(`abort`,m)),{get disposalSignal(){return r.signal},dispose:m,get disposed(){return a},focusedIndex:i,getActiveItem:()=>a?void 0:s.getActiveItem(),handleKeydown:p,navigate:d,reset:f,set:u,[Symbol.dispose]:m}};exports.createListControl=n;
|
|
2
2
|
//# sourceMappingURL=nav.cjs.map
|
package/dist/core/nav.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav.cjs","names":[],"sources":["../../src/core/nav.ts"],"sourcesContent":["import {\n createListNavigation,\n type ListNavigationOptions as FocusListNavigationOptions,\n type ListKeyAction,\n type ListNavigation,\n type ListNavigationAction,\n type ListNavigationChange,\n type ListNavigationTypeaheadOptions,\n} from '@vielzeug/focus';\nimport { type Readable, signal } from '@vielzeug/ripple';\n\nexport type ListNavigationOptions<T> = Omit<FocusListNavigationOptions<T>, 'disabled'> & {\n disabled?: Readable<boolean | undefined>;\n};\n\nexport type ListControl<T> = {\n [Symbol.dispose](): void;\n dispose(): void;\n readonly disposed: boolean;\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n navigate(action: ListNavigationAction): number;\n reset(): void;\n set(index: number): number;\n};\n\nexport
|
|
1
|
+
{"version":3,"file":"nav.cjs","names":[],"sources":["../../src/core/nav.ts"],"sourcesContent":["import {\n createListNavigation,\n type ListNavigationOptions as FocusListNavigationOptions,\n type ListKeyAction,\n type ListNavigation,\n type ListNavigationAction,\n type ListNavigationChange,\n type ListNavigationTypeaheadOptions,\n} from '@vielzeug/focus';\nimport { type Readable, signal } from '@vielzeug/ripple';\n\nexport type ListNavigationOptions<T> = Omit<FocusListNavigationOptions<T>, 'disabled'> & {\n disabled?: Readable<boolean | undefined>;\n onNavigate?: (change: ListNavigationChange<T>) => void;\n signal?: AbortSignal;\n};\n\nexport type { ListKeyAction, ListNavigationAction, ListNavigationChange, ListNavigationTypeaheadOptions };\n\nexport type ListControl<T> = {\n [Symbol.dispose](): void;\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n navigate(action: ListNavigationAction): number;\n reset(): void;\n set(index: number): number;\n};\n\nexport const createListControl = <T>(options: ListNavigationOptions<T>): ListControl<T> => {\n const disposalController = new AbortController();\n const focusedIndex = signal(-1);\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n const navigation: ListNavigation<T> = createListNavigation<T>({\n direction: options.direction,\n disabled: () => Boolean(options.disabled?.value),\n getItems: options.getItems,\n isItemDisabled: options.isItemDisabled,\n keys: options.keys,\n loop: options.loop,\n orientation: options.orientation,\n typeahead: options.typeahead,\n });\n\n const syncIndex = (): void => {\n focusedIndex.value = navigation.getIndex();\n };\n\n const applyChange = (change: ListNavigationChange<T> | null): number => {\n if (change) {\n focusedIndex.value = change.index;\n try {\n options.onNavigate?.(change);\n } finally {\n syncIndex();\n }\n }\n\n return navigation.getIndex();\n };\n\n const set = (index: number): number => {\n if (disposed) return -1;\n const next = navigation.set(index);\n\n syncIndex();\n\n return next;\n };\n\n const navigate = (action: ListNavigationAction): number => {\n if (disposed) return -1;\n const change = navigation.navigate(action);\n\n return applyChange(change);\n };\n\n const reset = (): void => {\n if (disposed) return;\n navigation.reset();\n syncIndex();\n };\n\n const handleKeydown = (event: KeyboardEvent): boolean => {\n if (disposed) return false;\n\n const result = navigation.handleKeydown(event);\n if (!result) return false;\n if (result.change) applyChange(result.change);\n return result.handled;\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n navigation.reset();\n focusedIndex.value = -1;\n disposalController.abort();\n removeAbortListener?.();\n removeAbortListener = undefined;\n };\n\n if (options.signal?.aborted) {\n dispose();\n } else if (options.signal) {\n options.signal?.addEventListener('abort', dispose, { once: true });\n removeAbortListener = () => options.signal?.removeEventListener('abort', dispose);\n }\n\n return {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n focusedIndex,\n getActiveItem: () => (disposed ? undefined : navigation.getActiveItem()),\n handleKeydown,\n navigate,\n reset,\n set,\n [Symbol.dispose]: dispose,\n };\n};\n"],"mappings":"+DAgCA,IAAa,EAAwB,GAAsD,CACzF,IAAM,EAAqB,IAAI,gBACzB,GAAA,EAAe,EAAA,OAAA,CAAO,EAAE,EAC1B,EAAW,GACX,EACE,GAAA,EAAgC,EAAA,qBAAA,CAAwB,CAC5D,UAAW,EAAQ,UACnB,aAAgB,EAAQ,EAAQ,UAAU,MAC1C,SAAU,EAAQ,SAClB,eAAgB,EAAQ,eACxB,KAAM,EAAQ,KACd,KAAM,EAAQ,KACd,YAAa,EAAQ,YACrB,UAAW,EAAQ,SACrB,CAAC,EAEK,MAAwB,CAC5B,EAAa,MAAQ,EAAW,SAAS,CAC3C,EAEM,EAAe,GAAmD,CACtE,GAAI,EAAQ,CACV,EAAa,MAAQ,EAAO,MAC5B,GAAI,CACF,EAAQ,aAAa,CAAM,CAC7B,QAAU,CACR,EAAU,CACZ,CACF,CAEA,OAAO,EAAW,SAAS,CAC7B,EAEM,EAAO,GAA0B,CACrC,GAAI,EAAU,MAAO,GACrB,IAAM,EAAO,EAAW,IAAI,CAAK,EAIjC,OAFA,EAAU,EAEH,CACT,EAEM,EAAY,GAAyC,CACzD,GAAI,EAAU,MAAO,GACrB,IAAM,EAAS,EAAW,SAAS,CAAM,EAEzC,OAAO,EAAY,CAAM,CAC3B,EAEM,MAAoB,CACpB,IACJ,EAAW,MAAM,EACjB,EAAU,EACZ,EAEM,EAAiB,GAAkC,CACvD,GAAI,EAAU,MAAO,GAErB,IAAM,EAAS,EAAW,cAAc,CAAK,EAG7C,OAFK,GACD,EAAO,QAAQ,EAAY,EAAO,MAAM,EACrC,EAAO,SAFM,EAGtB,EAEM,MAAsB,CACtB,IAEJ,EAAW,GACX,EAAW,MAAM,EACjB,EAAa,MAAQ,GACrB,EAAmB,MAAM,EACzB,IAAsB,EACtB,EAAsB,IAAA,GACxB,EASA,OAPI,EAAQ,QAAQ,QAClB,EAAQ,EACC,EAAQ,SACjB,EAAQ,QAAQ,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EACjE,MAA4B,EAAQ,QAAQ,oBAAoB,QAAS,CAAO,GAG3E,CACL,IAAI,gBAAiB,CACnB,OAAO,EAAmB,MAC5B,EACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,eACA,kBAAsB,EAAW,IAAA,GAAY,EAAW,cAAc,EACtE,gBACA,WACA,QACA,OACC,OAAO,SAAU,CACpB,CACF"}
|
package/dist/core/nav.d.ts
CHANGED
|
@@ -2,9 +2,13 @@ import { type ListNavigationOptions as FocusListNavigationOptions, type ListKeyA
|
|
|
2
2
|
import { type Readable } from '@vielzeug/ripple';
|
|
3
3
|
export type ListNavigationOptions<T> = Omit<FocusListNavigationOptions<T>, 'disabled'> & {
|
|
4
4
|
disabled?: Readable<boolean | undefined>;
|
|
5
|
+
onNavigate?: (change: ListNavigationChange<T>) => void;
|
|
6
|
+
signal?: AbortSignal;
|
|
5
7
|
};
|
|
8
|
+
export type { ListKeyAction, ListNavigationAction, ListNavigationChange, ListNavigationTypeaheadOptions };
|
|
6
9
|
export type ListControl<T> = {
|
|
7
10
|
[Symbol.dispose](): void;
|
|
11
|
+
readonly disposalSignal: AbortSignal;
|
|
8
12
|
dispose(): void;
|
|
9
13
|
readonly disposed: boolean;
|
|
10
14
|
readonly focusedIndex: Readable<number>;
|
|
@@ -14,6 +18,5 @@ export type ListControl<T> = {
|
|
|
14
18
|
reset(): void;
|
|
15
19
|
set(index: number): number;
|
|
16
20
|
};
|
|
17
|
-
export type { ListKeyAction, ListNavigationAction, ListNavigationChange, ListNavigationTypeaheadOptions };
|
|
18
21
|
export declare const createListControl: <T>(options: ListNavigationOptions<T>) => ListControl<T>;
|
|
19
22
|
//# sourceMappingURL=nav.d.ts.map
|
package/dist/core/nav.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/core/nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,IAAI,0BAA0B,EACxD,KAAK,aAAa,EAElB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,8BAA8B,EACpC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,QAAQ,EAAU,MAAM,kBAAkB,CAAC;AAEzD,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG;IACvF,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/core/nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,IAAI,0BAA0B,EACxD,KAAK,aAAa,EAElB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,8BAA8B,EACpC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,QAAQ,EAAU,MAAM,kBAAkB,CAAC;AAEzD,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG;IACvF,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACzC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACvD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,8BAA8B,EAAE,CAAC;AAE1G,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC3B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,aAAa,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/B,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAAC;IAC/C,KAAK,IAAI,IAAI,CAAC;IACd,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,CAAC,EAAE,SAAS,qBAAqB,CAAC,CAAC,CAAC,KAAG,WAAW,CAAC,CAAC,CAkGrF,CAAC"}
|
package/dist/core/nav.js
CHANGED
|
@@ -2,46 +2,59 @@ import { signal as e } from "@vielzeug/ripple";
|
|
|
2
2
|
import { createListNavigation as t } from "@vielzeug/focus";
|
|
3
3
|
//#region src/core/nav.ts
|
|
4
4
|
var n = (n) => {
|
|
5
|
-
let r = e(-1),
|
|
5
|
+
let r = new AbortController(), i = e(-1), a = !1, o, s = t({
|
|
6
6
|
direction: n.direction,
|
|
7
7
|
disabled: () => !!n.disabled?.value,
|
|
8
8
|
getItems: n.getItems,
|
|
9
9
|
isItemDisabled: n.isItemDisabled,
|
|
10
10
|
keys: n.keys,
|
|
11
11
|
loop: n.loop,
|
|
12
|
-
onNavigate: (e) => {
|
|
13
|
-
r.value = e.index, n.onNavigate?.(e);
|
|
14
|
-
},
|
|
15
12
|
orientation: n.orientation,
|
|
16
13
|
typeahead: n.typeahead
|
|
17
|
-
}),
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
}), c = () => {
|
|
15
|
+
i.value = s.getIndex();
|
|
16
|
+
}, l = (e) => {
|
|
17
|
+
if (e) {
|
|
18
|
+
i.value = e.index;
|
|
19
|
+
try {
|
|
20
|
+
n.onNavigate?.(e);
|
|
21
|
+
} finally {
|
|
22
|
+
c();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return s.getIndex();
|
|
27
26
|
}, u = (e) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
if (a) return -1;
|
|
28
|
+
let t = s.set(e);
|
|
29
|
+
return c(), t;
|
|
30
|
+
}, d = (e) => {
|
|
31
|
+
if (a) return -1;
|
|
32
|
+
let t = s.navigate(e);
|
|
33
|
+
return l(t);
|
|
34
|
+
}, f = () => {
|
|
35
|
+
a || (s.reset(), c());
|
|
36
|
+
}, p = (e) => {
|
|
37
|
+
if (a) return !1;
|
|
38
|
+
let t = s.handleKeydown(e);
|
|
39
|
+
return t ? (t.change && l(t.change), t.handled) : !1;
|
|
40
|
+
}, m = () => {
|
|
41
|
+
a || (a = !0, s.reset(), i.value = -1, r.abort(), o?.(), o = void 0);
|
|
32
42
|
};
|
|
33
|
-
return n.signal?.aborted ?
|
|
34
|
-
|
|
43
|
+
return n.signal?.aborted ? m() : n.signal && (n.signal?.addEventListener("abort", m, { once: !0 }), o = () => n.signal?.removeEventListener("abort", m)), {
|
|
44
|
+
get disposalSignal() {
|
|
45
|
+
return r.signal;
|
|
46
|
+
},
|
|
47
|
+
dispose: m,
|
|
35
48
|
get disposed() {
|
|
36
|
-
return a
|
|
49
|
+
return a;
|
|
37
50
|
},
|
|
38
|
-
focusedIndex:
|
|
39
|
-
getActiveItem: () => a.getActiveItem(),
|
|
40
|
-
handleKeydown:
|
|
41
|
-
navigate:
|
|
42
|
-
reset:
|
|
43
|
-
set:
|
|
44
|
-
[Symbol.dispose]:
|
|
51
|
+
focusedIndex: i,
|
|
52
|
+
getActiveItem: () => a ? void 0 : s.getActiveItem(),
|
|
53
|
+
handleKeydown: p,
|
|
54
|
+
navigate: d,
|
|
55
|
+
reset: f,
|
|
56
|
+
set: u,
|
|
57
|
+
[Symbol.dispose]: m
|
|
45
58
|
};
|
|
46
59
|
};
|
|
47
60
|
//#endregion
|
package/dist/core/nav.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav.js","names":[],"sources":["../../src/core/nav.ts"],"sourcesContent":["import {\n createListNavigation,\n type ListNavigationOptions as FocusListNavigationOptions,\n type ListKeyAction,\n type ListNavigation,\n type ListNavigationAction,\n type ListNavigationChange,\n type ListNavigationTypeaheadOptions,\n} from '@vielzeug/focus';\nimport { type Readable, signal } from '@vielzeug/ripple';\n\nexport type ListNavigationOptions<T> = Omit<FocusListNavigationOptions<T>, 'disabled'> & {\n disabled?: Readable<boolean | undefined>;\n};\n\nexport type ListControl<T> = {\n [Symbol.dispose](): void;\n dispose(): void;\n readonly disposed: boolean;\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n navigate(action: ListNavigationAction): number;\n reset(): void;\n set(index: number): number;\n};\n\nexport
|
|
1
|
+
{"version":3,"file":"nav.js","names":[],"sources":["../../src/core/nav.ts"],"sourcesContent":["import {\n createListNavigation,\n type ListNavigationOptions as FocusListNavigationOptions,\n type ListKeyAction,\n type ListNavigation,\n type ListNavigationAction,\n type ListNavigationChange,\n type ListNavigationTypeaheadOptions,\n} from '@vielzeug/focus';\nimport { type Readable, signal } from '@vielzeug/ripple';\n\nexport type ListNavigationOptions<T> = Omit<FocusListNavigationOptions<T>, 'disabled'> & {\n disabled?: Readable<boolean | undefined>;\n onNavigate?: (change: ListNavigationChange<T>) => void;\n signal?: AbortSignal;\n};\n\nexport type { ListKeyAction, ListNavigationAction, ListNavigationChange, ListNavigationTypeaheadOptions };\n\nexport type ListControl<T> = {\n [Symbol.dispose](): void;\n readonly disposalSignal: AbortSignal;\n dispose(): void;\n readonly disposed: boolean;\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n navigate(action: ListNavigationAction): number;\n reset(): void;\n set(index: number): number;\n};\n\nexport const createListControl = <T>(options: ListNavigationOptions<T>): ListControl<T> => {\n const disposalController = new AbortController();\n const focusedIndex = signal(-1);\n let disposed = false;\n let removeAbortListener: (() => void) | undefined;\n const navigation: ListNavigation<T> = createListNavigation<T>({\n direction: options.direction,\n disabled: () => Boolean(options.disabled?.value),\n getItems: options.getItems,\n isItemDisabled: options.isItemDisabled,\n keys: options.keys,\n loop: options.loop,\n orientation: options.orientation,\n typeahead: options.typeahead,\n });\n\n const syncIndex = (): void => {\n focusedIndex.value = navigation.getIndex();\n };\n\n const applyChange = (change: ListNavigationChange<T> | null): number => {\n if (change) {\n focusedIndex.value = change.index;\n try {\n options.onNavigate?.(change);\n } finally {\n syncIndex();\n }\n }\n\n return navigation.getIndex();\n };\n\n const set = (index: number): number => {\n if (disposed) return -1;\n const next = navigation.set(index);\n\n syncIndex();\n\n return next;\n };\n\n const navigate = (action: ListNavigationAction): number => {\n if (disposed) return -1;\n const change = navigation.navigate(action);\n\n return applyChange(change);\n };\n\n const reset = (): void => {\n if (disposed) return;\n navigation.reset();\n syncIndex();\n };\n\n const handleKeydown = (event: KeyboardEvent): boolean => {\n if (disposed) return false;\n\n const result = navigation.handleKeydown(event);\n if (!result) return false;\n if (result.change) applyChange(result.change);\n return result.handled;\n };\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n navigation.reset();\n focusedIndex.value = -1;\n disposalController.abort();\n removeAbortListener?.();\n removeAbortListener = undefined;\n };\n\n if (options.signal?.aborted) {\n dispose();\n } else if (options.signal) {\n options.signal?.addEventListener('abort', dispose, { once: true });\n removeAbortListener = () => options.signal?.removeEventListener('abort', dispose);\n }\n\n return {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n focusedIndex,\n getActiveItem: () => (disposed ? undefined : navigation.getActiveItem()),\n handleKeydown,\n navigate,\n reset,\n set,\n [Symbol.dispose]: dispose,\n };\n};\n"],"mappings":";;;AAgCA,IAAa,KAAwB,MAAsD;CACzF,IAAM,IAAqB,IAAI,gBAAgB,GACzC,IAAe,EAAO,EAAE,GAC1B,IAAW,IACX,GACE,IAAgC,EAAwB;EAC5D,WAAW,EAAQ;EACnB,gBAAgB,EAAQ,EAAQ,UAAU;EAC1C,UAAU,EAAQ;EAClB,gBAAgB,EAAQ;EACxB,MAAM,EAAQ;EACd,MAAM,EAAQ;EACd,aAAa,EAAQ;EACrB,WAAW,EAAQ;CACrB,CAAC,GAEK,UAAwB;EAC5B,EAAa,QAAQ,EAAW,SAAS;CAC3C,GAEM,KAAe,MAAmD;EACtE,IAAI,GAAQ;GACV,EAAa,QAAQ,EAAO;GAC5B,IAAI;IACF,EAAQ,aAAa,CAAM;GAC7B,UAAU;IACR,EAAU;GACZ;EACF;EAEA,OAAO,EAAW,SAAS;CAC7B,GAEM,KAAO,MAA0B;EACrC,IAAI,GAAU,OAAO;EACrB,IAAM,IAAO,EAAW,IAAI,CAAK;EAIjC,OAFA,EAAU,GAEH;CACT,GAEM,KAAY,MAAyC;EACzD,IAAI,GAAU,OAAO;EACrB,IAAM,IAAS,EAAW,SAAS,CAAM;EAEzC,OAAO,EAAY,CAAM;CAC3B,GAEM,UAAoB;EACpB,MACJ,EAAW,MAAM,GACjB,EAAU;CACZ,GAEM,KAAiB,MAAkC;EACvD,IAAI,GAAU,OAAO;EAErB,IAAM,IAAS,EAAW,cAAc,CAAK;EAG7C,OAFK,KACD,EAAO,UAAQ,EAAY,EAAO,MAAM,GACrC,EAAO,WAFM;CAGtB,GAEM,UAAsB;EACtB,MAEJ,IAAW,IACX,EAAW,MAAM,GACjB,EAAa,QAAQ,IACrB,EAAmB,MAAM,GACzB,IAAsB,GACtB,IAAsB,KAAA;CACxB;CASA,OAPI,EAAQ,QAAQ,UAClB,EAAQ,IACC,EAAQ,WACjB,EAAQ,QAAQ,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC,GACjE,UAA4B,EAAQ,QAAQ,oBAAoB,SAAS,CAAO,IAG3E;EACL,IAAI,iBAAiB;GACnB,OAAO,EAAmB;EAC5B;EACA;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA;EACA,qBAAsB,IAAW,KAAA,IAAY,EAAW,cAAc;EACtE;EACA;EACA;EACA;GACC,OAAO,UAAU;CACpB;AACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../errors.cjs"),t=require("./overlay.cjs"),n=require("./nav.cjs"),r=require("./positioner.cjs");let i=require("@vielzeug/ripple"),a=require("@vielzeug/focus");var o=o=>{if(typeof o.getBoundary!=`function`)throw new e.RefineConfigError(`createListboxDropdown: getBoundary is required`);if(typeof o.getPanel!=`function`)throw new e.RefineConfigError(`createListboxDropdown: getPanel is required`);if(typeof o.getReference!=`function`)throw new e.RefineConfigError(`createListboxDropdown: getReference is required`);let s=(0,i.signal)(!1),c=(0,i.computed)(()=>String(s.value)),l=null,
|
|
1
|
+
const e=require("../errors.cjs"),t=require("./overlay.cjs"),n=require("./nav.cjs"),r=require("./positioner.cjs");let i=require("@vielzeug/ripple"),a=require("@vielzeug/focus");var o=o=>{if(typeof o.getBoundary!=`function`)throw new e.RefineConfigError(`createListboxDropdown: getBoundary is required`);if(typeof o.getPanel!=`function`)throw new e.RefineConfigError(`createListboxDropdown: getPanel is required`);if(typeof o.getReference!=`function`)throw new e.RefineConfigError(`createListboxDropdown: getReference is required`);let s=(0,i.signal)(!1),c=(0,i.computed)(()=>String(s.value)),l=!1,u=null,d=r.createDropdownPositioner({...o.positioning,getFloating:o.getPanel,getReference:o.getReference}),f=()=>{l||o.getFocusedOptionElement?.()?.scrollIntoView({block:`nearest`})},p=n.createListControl({disabled:(0,i.computed)(()=>!s.value),getItems:o.getItems,isItemDisabled:o.isItemDisabled,keys:o.keys,loop:o.loop??!0,onNavigate:e=>{f(),o.onNavigate?.(e)},orientation:o.orientation,signal:o.signal,typeahead:o.typeahead}),m=(0,i.computed)(()=>{let e=p.focusedIndex.value;return o.getOptionId&&s.value&&e>=0?o.getOptionId(e):null}),h=()=>typeof o.restoreFocus==`function`?o.restoreFocus():o.restoreFocus??!0,g=(e=`programmatic`,t=!0)=>{!l&&s.value&&(s.value=!1,p.reset(),u?.(),u=null,t&&h()&&o.getTrigger&&(0,a.restoreFocus)(o.getTrigger),o.onClose?.(e))},_=(e=`programmatic`)=>{l||o.isDisabled?.()||s.value||(s.value=!0,d.update(),u=d.startAutoUpdate?.()??null,o.onOpen?.(e))},v=(e=`click`,t=`trigger`)=>{l||(s.value?g(t):_(e))};t.createOutsidePointerDismissal({getTargets:()=>[o.getBoundary(),o.getPanel()],isActive:()=>s.value,onDismiss:()=>g(`outsideClick`),signal:o.signal});let y=()=>{l||(l=!0,p.dispose(),s.value&&(s.value=!1,p.reset(),u?.(),u=null))};return o.signal.addEventListener(`abort`,y,{once:!0}),{ariaActiveDescendant:m,ariaExpanded:c,close:g,dispose:y,get disposed(){return l},focusedIndex:p.focusedIndex,getActiveItem:p.getActiveItem,handleKeydown:e=>l||!s.value?!1:e.key===`Escape`?(e.preventDefault(),g(`escape`),!0):p.handleKeydown(e),isOpen:s,navigate:p.navigate,open:_,scrollFocusedIntoView:f,set:p.set,[Symbol.dispose]:y,toggle:v,updatePosition:()=>{l||d.update()}}};exports.createListboxDropdown=o;
|
|
2
2
|
//# sourceMappingURL=option-list.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"option-list.cjs","names":[],"sources":["../../src/core/option-list.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport { computed, type Readable, signal } from '@vielzeug/ripple';\n\nimport { RefineConfigError } from '../errors';\nimport {\n createListControl,\n type ListNavigationAction,\n type ListNavigationChange,\n type ListNavigationTypeaheadOptions,\n} from './nav';\nimport { createOutsidePointerDismissal, type DropdownCloseReason, type OverlayOpenReason } from './overlay';\nimport { createDropdownPositioner, type DropdownPositionerOptions } from './positioner';\n\nexport type ListboxItem = object;\n\n/** Placement-only options for the listbox dropdown positioner. */\nexport type ListboxDropdownPlacementOptions = Omit<DropdownPositionerOptions, 'getFloating' | 'getReference'>;\n\nexport type ListboxDropdownOptions<T extends ListboxItem> = {\n getBoundary: () => HTMLElement | null;\n getFocusedOptionElement?: () => HTMLElement | null;\n getItems: () => readonly T[];\n getOptionId?: (index: number) => string;\n getPanel: () => HTMLElement | null;\n getReference: () => HTMLElement | null;\n getTrigger?: () => HTMLElement | null;\n isDisabled?: () => boolean;\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n onClose?: (reason: DropdownCloseReason) => void;\n onNavigate?: (change: ListNavigationChange<T>) => void;\n onOpen?: (reason: OverlayOpenReason) => void;\n orientation?: 'both' | 'horizontal' | 'vertical';\n positioning?: ListboxDropdownPlacementOptions;\n restoreFocus?: boolean | (() => boolean);\n signal: AbortSignal;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListboxDropdown<T extends ListboxItem> = {\n [Symbol.dispose](): void;\n readonly ariaActiveDescendant: Readable<string | null>;\n readonly ariaExpanded: Readable<string>;\n close(reason?: DropdownCloseReason): void;\n /** Closes the dropdown (without restoring focus) and disposes list navigation state. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n readonly isOpen: Readable<boolean>;\n navigate(action: ListNavigationAction): number;\n open(reason?: OverlayOpenReason): void;\n scrollFocusedIntoView(): void;\n set(index: number): number;\n toggle(openReason?: OverlayOpenReason, closeReason?: DropdownCloseReason): void;\n updatePosition(): void;\n};\n\n/**\n * Internal listbox dropdown behavior for select and combobox. It owns only\n * listbox interactions; menu and other overlay families keep their own logic.\n */\nexport const createListboxDropdown = <T extends ListboxItem>(\n options: ListboxDropdownOptions<T>,\n): ListboxDropdown<T> => {\n if (typeof options.getBoundary !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getBoundary is required');\n }\n\n if (typeof options.getPanel !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getPanel is required');\n }\n\n if (typeof options.getReference !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getReference is required');\n }\n\n const isOpen = signal(false);\n const ariaExpanded = computed(() => String(isOpen.value));\n let stopPositioning: (() => void) | null = null;\n\n const positioner = createDropdownPositioner({\n ...options.positioning,\n getFloating: options.getPanel,\n getReference: options.getReference,\n });\n\n const scrollFocusedIntoView = (): void => {\n options.getFocusedOptionElement?.()?.scrollIntoView({ block: 'nearest' });\n };\n\n const list = createListControl<T>({\n disabled: computed(() => !isOpen.value),\n getItems: options.getItems,\n isItemDisabled: options.isItemDisabled,\n keys: options.keys,\n loop: options.loop ?? true,\n onNavigate: (change) => {\n scrollFocusedIntoView();\n options.onNavigate?.(change);\n },\n orientation: options.orientation,\n signal: options.signal,\n typeahead: options.typeahead,\n });\n\n const ariaActiveDescendant = computed<string | null>(() => {\n const index = list.focusedIndex.value;\n\n return options.getOptionId && isOpen.value && index >= 0 ? options.getOptionId(index) : null;\n });\n\n const shouldRestoreFocus = (): boolean => {\n return typeof options.restoreFocus === 'function' ? options.restoreFocus() : (options.restoreFocus ?? true);\n };\n\n const close = (reason: DropdownCloseReason = 'programmatic', shouldRestore = true): void => {\n if (!isOpen.value) return;\n\n isOpen.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n\n if (shouldRestore && shouldRestoreFocus() && options.getTrigger) restoreFocus(options.getTrigger);\n\n options.onClose?.(reason);\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (options.isDisabled?.() || isOpen.value) return;\n\n isOpen.value = true;\n positioner.update();\n stopPositioning = positioner.startAutoUpdate?.() ?? null;\n options.onOpen?.(reason);\n };\n\n const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DropdownCloseReason = 'trigger'): void => {\n if (isOpen.value) close(closeReason);\n else open(openReason);\n };\n\n createOutsidePointerDismissal({\n getTargets: () => [options.getBoundary(), options.getPanel()],\n isActive: () => isOpen.value,\n onDismiss: () => close('outsideClick'),\n signal: options.signal,\n });\n\n
|
|
1
|
+
{"version":3,"file":"option-list.cjs","names":[],"sources":["../../src/core/option-list.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport { computed, type Readable, signal } from '@vielzeug/ripple';\n\nimport { RefineConfigError } from '../errors';\nimport {\n createListControl,\n type ListNavigationAction,\n type ListNavigationChange,\n type ListNavigationTypeaheadOptions,\n} from './nav';\nimport { createOutsidePointerDismissal, type DropdownCloseReason, type OverlayOpenReason } from './overlay';\nimport { createDropdownPositioner, type DropdownPositionerOptions } from './positioner';\n\nexport type ListboxItem = object;\n\n/** Placement-only options for the listbox dropdown positioner. */\nexport type ListboxDropdownPlacementOptions = Omit<DropdownPositionerOptions, 'getFloating' | 'getReference'>;\n\nexport type ListboxDropdownOptions<T extends ListboxItem> = {\n getBoundary: () => HTMLElement | null;\n getFocusedOptionElement?: () => HTMLElement | null;\n getItems: () => readonly T[];\n getOptionId?: (index: number) => string;\n getPanel: () => HTMLElement | null;\n getReference: () => HTMLElement | null;\n getTrigger?: () => HTMLElement | null;\n isDisabled?: () => boolean;\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n onClose?: (reason: DropdownCloseReason) => void;\n onNavigate?: (change: ListNavigationChange<T>) => void;\n onOpen?: (reason: OverlayOpenReason) => void;\n orientation?: 'both' | 'horizontal' | 'vertical';\n positioning?: ListboxDropdownPlacementOptions;\n restoreFocus?: boolean | (() => boolean);\n signal: AbortSignal;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListboxDropdown<T extends ListboxItem> = {\n [Symbol.dispose](): void;\n readonly ariaActiveDescendant: Readable<string | null>;\n readonly ariaExpanded: Readable<string>;\n close(reason?: DropdownCloseReason): void;\n /** Closes the dropdown (without restoring focus) and disposes list navigation state. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n readonly isOpen: Readable<boolean>;\n navigate(action: ListNavigationAction): number;\n open(reason?: OverlayOpenReason): void;\n scrollFocusedIntoView(): void;\n set(index: number): number;\n toggle(openReason?: OverlayOpenReason, closeReason?: DropdownCloseReason): void;\n updatePosition(): void;\n};\n\n/**\n * Internal listbox dropdown behavior for select and combobox. It owns only\n * listbox interactions; menu and other overlay families keep their own logic.\n */\nexport const createListboxDropdown = <T extends ListboxItem>(\n options: ListboxDropdownOptions<T>,\n): ListboxDropdown<T> => {\n if (typeof options.getBoundary !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getBoundary is required');\n }\n\n if (typeof options.getPanel !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getPanel is required');\n }\n\n if (typeof options.getReference !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getReference is required');\n }\n\n const isOpen = signal(false);\n const ariaExpanded = computed(() => String(isOpen.value));\n let disposed = false;\n let stopPositioning: (() => void) | null = null;\n\n const positioner = createDropdownPositioner({\n ...options.positioning,\n getFloating: options.getPanel,\n getReference: options.getReference,\n });\n\n const scrollFocusedIntoView = (): void => {\n if (!disposed) options.getFocusedOptionElement?.()?.scrollIntoView({ block: 'nearest' });\n };\n\n const list = createListControl<T>({\n disabled: computed(() => !isOpen.value),\n getItems: options.getItems,\n isItemDisabled: options.isItemDisabled,\n keys: options.keys,\n loop: options.loop ?? true,\n onNavigate: (change) => {\n scrollFocusedIntoView();\n options.onNavigate?.(change);\n },\n orientation: options.orientation,\n signal: options.signal,\n typeahead: options.typeahead,\n });\n\n const ariaActiveDescendant = computed<string | null>(() => {\n const index = list.focusedIndex.value;\n\n return options.getOptionId && isOpen.value && index >= 0 ? options.getOptionId(index) : null;\n });\n\n const shouldRestoreFocus = (): boolean => {\n return typeof options.restoreFocus === 'function' ? options.restoreFocus() : (options.restoreFocus ?? true);\n };\n\n const close = (reason: DropdownCloseReason = 'programmatic', shouldRestore = true): void => {\n if (disposed || !isOpen.value) return;\n\n isOpen.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n\n if (shouldRestore && shouldRestoreFocus() && options.getTrigger) restoreFocus(options.getTrigger);\n\n options.onClose?.(reason);\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (disposed || options.isDisabled?.() || isOpen.value) return;\n\n isOpen.value = true;\n positioner.update();\n stopPositioning = positioner.startAutoUpdate?.() ?? null;\n options.onOpen?.(reason);\n };\n\n const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DropdownCloseReason = 'trigger'): void => {\n if (disposed) return;\n if (isOpen.value) close(closeReason);\n else open(openReason);\n };\n\n createOutsidePointerDismissal({\n getTargets: () => [options.getBoundary(), options.getPanel()],\n isActive: () => isOpen.value,\n onDismiss: () => close('outsideClick'),\n signal: options.signal,\n });\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n list.dispose();\n\n if (isOpen.value) {\n isOpen.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n }\n };\n\n options.signal.addEventListener('abort', dispose, { once: true });\n\n const handleKeydown = (event: KeyboardEvent): boolean => {\n if (disposed || !isOpen.value) return false;\n\n if (event.key === 'Escape') {\n event.preventDefault();\n close('escape');\n\n return true;\n }\n\n return list.handleKeydown(event);\n };\n\n return {\n ariaActiveDescendant,\n ariaExpanded,\n close,\n dispose,\n get disposed() {\n return disposed;\n },\n focusedIndex: list.focusedIndex,\n getActiveItem: list.getActiveItem,\n handleKeydown,\n isOpen,\n navigate: list.navigate,\n open,\n scrollFocusedIntoView,\n set: list.set,\n [Symbol.dispose]: dispose,\n toggle,\n updatePosition: () => {\n if (!disposed) positioner.update();\n },\n };\n};\n"],"mappings":"gLAiEA,IAAa,EACX,GACuB,CACvB,GAAI,OAAO,EAAQ,aAAgB,WACjC,MAAM,IAAI,EAAA,kBAAkB,gDAAgD,EAG9E,GAAI,OAAO,EAAQ,UAAa,WAC9B,MAAM,IAAI,EAAA,kBAAkB,6CAA6C,EAG3E,GAAI,OAAO,EAAQ,cAAiB,WAClC,MAAM,IAAI,EAAA,kBAAkB,iDAAiD,EAG/E,IAAM,GAAA,EAAS,EAAA,OAAA,CAAO,EAAK,EACrB,GAAA,EAAe,EAAA,SAAA,KAAe,OAAO,EAAO,KAAK,CAAC,EACpD,EAAW,GACX,EAAuC,KAErC,EAAa,EAAA,yBAAyB,CAC1C,GAAG,EAAQ,YACX,YAAa,EAAQ,SACrB,aAAc,EAAQ,YACxB,CAAC,EAEK,MAAoC,CACnC,GAAU,EAAQ,0BAA0B,CAAC,EAAE,eAAe,CAAE,MAAO,SAAU,CAAC,CACzF,EAEM,EAAO,EAAA,kBAAqB,CAChC,UAAA,EAAU,EAAA,SAAA,KAAe,CAAC,EAAO,KAAK,EACtC,SAAU,EAAQ,SAClB,eAAgB,EAAQ,eACxB,KAAM,EAAQ,KACd,KAAM,EAAQ,MAAQ,GACtB,WAAa,GAAW,CACtB,EAAsB,EACtB,EAAQ,aAAa,CAAM,CAC7B,EACA,YAAa,EAAQ,YACrB,OAAQ,EAAQ,OAChB,UAAW,EAAQ,SACrB,CAAC,EAEK,GAAA,EAAuB,EAAA,SAAA,KAA8B,CACzD,IAAM,EAAQ,EAAK,aAAa,MAEhC,OAAO,EAAQ,aAAe,EAAO,OAAS,GAAS,EAAI,EAAQ,YAAY,CAAK,EAAI,IAC1F,CAAC,EAEK,MACG,OAAO,EAAQ,cAAiB,WAAa,EAAQ,aAAa,EAAK,EAAQ,cAAgB,GAGlG,GAAS,EAA8B,eAAgB,EAAgB,KAAe,CACtF,IAAa,EAAO,QAExB,EAAO,MAAQ,GACf,EAAK,MAAM,EACX,IAAkB,EAClB,EAAkB,KAEd,GAAiB,EAAmB,GAAK,EAAQ,aAAY,EAAA,EAAA,aAAA,CAAa,EAAQ,UAAU,EAEhG,EAAQ,UAAU,CAAM,EAC1B,EAEM,GAAQ,EAA4B,iBAAyB,CAC7D,GAAY,EAAQ,aAAa,GAAK,EAAO,QAEjD,EAAO,MAAQ,GACf,EAAW,OAAO,EAClB,EAAkB,EAAW,kBAAkB,GAAK,KACpD,EAAQ,SAAS,CAAM,EACzB,EAEM,GAAU,EAAgC,QAAS,EAAmC,YAAoB,CAC1G,IACA,EAAO,MAAO,EAAM,CAAW,EAC9B,EAAK,CAAU,EACtB,EAEA,EAAA,8BAA8B,CAC5B,eAAkB,CAAC,EAAQ,YAAY,EAAG,EAAQ,SAAS,CAAC,EAC5D,aAAgB,EAAO,MACvB,cAAiB,EAAM,cAAc,EACrC,OAAQ,EAAQ,MAClB,CAAC,EAED,IAAM,MAAsB,CACtB,IAEJ,EAAW,GACX,EAAK,QAAQ,EAET,EAAO,QACT,EAAO,MAAQ,GACf,EAAK,MAAM,EACX,IAAkB,EAClB,EAAkB,MAEtB,EAiBA,OAfA,EAAQ,OAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EAezD,CACL,uBACA,eACA,QACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,aAAc,EAAK,aACnB,cAAe,EAAK,cACpB,cAvBqB,GACjB,GAAY,CAAC,EAAO,MAAc,GAElC,EAAM,MAAQ,UAChB,EAAM,eAAe,EACrB,EAAM,QAAQ,EAEP,IAGF,EAAK,cAAc,CAAK,EAc/B,SACA,SAAU,EAAK,SACf,OACA,wBACA,IAAK,EAAK,KACT,OAAO,SAAU,EAClB,SACA,mBAAsB,CACf,GAAU,EAAW,OAAO,CACnC,CACF,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"option-list.d.ts","sourceRoot":"","sources":["../../src/core/option-list.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,QAAQ,EAAU,MAAM,kBAAkB,CAAC;AAGnE,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,8BAA8B,EACpC,MAAM,OAAO,CAAC;AACf,OAAO,EAAiC,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC5G,OAAO,EAA4B,KAAK,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAExF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,GAAG,IAAI,CAAC,yBAAyB,EAAE,aAAa,GAAG,cAAc,CAAC,CAAC;AAE9G,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,WAAW,IAAI;IAC1D,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,uBAAuB,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,SAAS,CAAC,EAAE,CAAC;IAC7B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,QAAQ,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IAChE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACvD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;IACjD,WAAW,CAAC,EAAE,+BAA+B,CAAC;IAC9C,YAAY,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;IACzC,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,EAAE,8BAA8B,CAAC,CAAC,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,WAAW,IAAI;IACnD,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,QAAQ,CAAC,oBAAoB,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvD,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,KAAK,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C,wFAAwF;IACxF,OAAO,IAAI,IAAI,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,aAAa,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/B,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACvC,qBAAqB,IAAI,IAAI,CAAC;IAC9B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,UAAU,CAAC,EAAE,iBAAiB,EAAE,WAAW,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAChF,cAAc,IAAI,IAAI,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,WAAW,EACzD,SAAS,sBAAsB,CAAC,CAAC,CAAC,KACjC,eAAe,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"option-list.d.ts","sourceRoot":"","sources":["../../src/core/option-list.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,QAAQ,EAAU,MAAM,kBAAkB,CAAC;AAGnE,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,8BAA8B,EACpC,MAAM,OAAO,CAAC;AACf,OAAO,EAAiC,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC5G,OAAO,EAA4B,KAAK,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAExF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,GAAG,IAAI,CAAC,yBAAyB,EAAE,aAAa,GAAG,cAAc,CAAC,CAAC;AAE9G,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,WAAW,IAAI;IAC1D,WAAW,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,uBAAuB,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,SAAS,CAAC,EAAE,CAAC;IAC7B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,QAAQ,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IAChE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACvD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;IACjD,WAAW,CAAC,EAAE,+BAA+B,CAAC;IAC9C,YAAY,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAC;IACzC,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,EAAE,8BAA8B,CAAC,CAAC,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,WAAW,IAAI;IACnD,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACzB,QAAQ,CAAC,oBAAoB,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvD,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,KAAK,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C,wFAAwF;IACxF,OAAO,IAAI,IAAI,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,aAAa,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/B,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACvC,qBAAqB,IAAI,IAAI,CAAC;IAC9B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,UAAU,CAAC,EAAE,iBAAiB,EAAE,WAAW,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAChF,cAAc,IAAI,IAAI,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,WAAW,EACzD,SAAS,sBAAsB,CAAC,CAAC,CAAC,KACjC,eAAe,CAAC,CAAC,CA2InB,CAAC"}
|
package/dist/core/option-list.js
CHANGED
|
@@ -9,62 +9,64 @@ var s = (s) => {
|
|
|
9
9
|
if (typeof s.getBoundary != "function") throw new e("createListboxDropdown: getBoundary is required");
|
|
10
10
|
if (typeof s.getPanel != "function") throw new e("createListboxDropdown: getPanel is required");
|
|
11
11
|
if (typeof s.getReference != "function") throw new e("createListboxDropdown: getReference is required");
|
|
12
|
-
let c = a(!1), l = i(() => String(c.value)), u =
|
|
12
|
+
let c = a(!1), l = i(() => String(c.value)), u = !1, d = null, f = r({
|
|
13
13
|
...s.positioning,
|
|
14
14
|
getFloating: s.getPanel,
|
|
15
15
|
getReference: s.getReference
|
|
16
|
-
}),
|
|
17
|
-
s.getFocusedOptionElement?.()?.scrollIntoView({ block: "nearest" });
|
|
18
|
-
},
|
|
16
|
+
}), p = () => {
|
|
17
|
+
u || s.getFocusedOptionElement?.()?.scrollIntoView({ block: "nearest" });
|
|
18
|
+
}, m = n({
|
|
19
19
|
disabled: i(() => !c.value),
|
|
20
20
|
getItems: s.getItems,
|
|
21
21
|
isItemDisabled: s.isItemDisabled,
|
|
22
22
|
keys: s.keys,
|
|
23
23
|
loop: s.loop ?? !0,
|
|
24
24
|
onNavigate: (e) => {
|
|
25
|
-
|
|
25
|
+
p(), s.onNavigate?.(e);
|
|
26
26
|
},
|
|
27
27
|
orientation: s.orientation,
|
|
28
28
|
signal: s.signal,
|
|
29
29
|
typeahead: s.typeahead
|
|
30
|
-
}),
|
|
31
|
-
let e =
|
|
30
|
+
}), h = i(() => {
|
|
31
|
+
let e = m.focusedIndex.value;
|
|
32
32
|
return s.getOptionId && c.value && e >= 0 ? s.getOptionId(e) : null;
|
|
33
|
-
}),
|
|
34
|
-
c.value && (c.value = !1,
|
|
35
|
-
},
|
|
36
|
-
s.isDisabled?.() || c.value || (c.value = !0,
|
|
37
|
-
},
|
|
38
|
-
c.value ?
|
|
33
|
+
}), g = () => typeof s.restoreFocus == "function" ? s.restoreFocus() : s.restoreFocus ?? !0, _ = (e = "programmatic", t = !0) => {
|
|
34
|
+
!u && c.value && (c.value = !1, m.reset(), d?.(), d = null, t && g() && s.getTrigger && o(s.getTrigger), s.onClose?.(e));
|
|
35
|
+
}, v = (e = "programmatic") => {
|
|
36
|
+
u || s.isDisabled?.() || c.value || (c.value = !0, f.update(), d = f.startAutoUpdate?.() ?? null, s.onOpen?.(e));
|
|
37
|
+
}, y = (e = "click", t = "trigger") => {
|
|
38
|
+
u || (c.value ? _(t) : v(e));
|
|
39
39
|
};
|
|
40
40
|
t({
|
|
41
41
|
getTargets: () => [s.getBoundary(), s.getPanel()],
|
|
42
42
|
isActive: () => c.value,
|
|
43
|
-
onDismiss: () =>
|
|
43
|
+
onDismiss: () => _("outsideClick"),
|
|
44
44
|
signal: s.signal
|
|
45
45
|
});
|
|
46
|
-
let
|
|
47
|
-
|
|
46
|
+
let b = () => {
|
|
47
|
+
u || (u = !0, m.dispose(), c.value && (c.value = !1, m.reset(), d?.(), d = null));
|
|
48
48
|
};
|
|
49
49
|
return s.signal.addEventListener("abort", b, { once: !0 }), {
|
|
50
|
-
ariaActiveDescendant:
|
|
50
|
+
ariaActiveDescendant: h,
|
|
51
51
|
ariaExpanded: l,
|
|
52
|
-
close:
|
|
52
|
+
close: _,
|
|
53
53
|
dispose: b,
|
|
54
54
|
get disposed() {
|
|
55
|
-
return
|
|
55
|
+
return u;
|
|
56
56
|
},
|
|
57
|
-
focusedIndex:
|
|
58
|
-
getActiveItem:
|
|
59
|
-
handleKeydown: (e) => c.value ? e.key === "Escape" ? (e.preventDefault(),
|
|
57
|
+
focusedIndex: m.focusedIndex,
|
|
58
|
+
getActiveItem: m.getActiveItem,
|
|
59
|
+
handleKeydown: (e) => u || !c.value ? !1 : e.key === "Escape" ? (e.preventDefault(), _("escape"), !0) : m.handleKeydown(e),
|
|
60
60
|
isOpen: c,
|
|
61
|
-
navigate:
|
|
62
|
-
open:
|
|
63
|
-
scrollFocusedIntoView:
|
|
64
|
-
set:
|
|
61
|
+
navigate: m.navigate,
|
|
62
|
+
open: v,
|
|
63
|
+
scrollFocusedIntoView: p,
|
|
64
|
+
set: m.set,
|
|
65
65
|
[Symbol.dispose]: b,
|
|
66
|
-
toggle:
|
|
67
|
-
updatePosition:
|
|
66
|
+
toggle: y,
|
|
67
|
+
updatePosition: () => {
|
|
68
|
+
u || f.update();
|
|
69
|
+
}
|
|
68
70
|
};
|
|
69
71
|
};
|
|
70
72
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"option-list.js","names":[],"sources":["../../src/core/option-list.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport { computed, type Readable, signal } from '@vielzeug/ripple';\n\nimport { RefineConfigError } from '../errors';\nimport {\n createListControl,\n type ListNavigationAction,\n type ListNavigationChange,\n type ListNavigationTypeaheadOptions,\n} from './nav';\nimport { createOutsidePointerDismissal, type DropdownCloseReason, type OverlayOpenReason } from './overlay';\nimport { createDropdownPositioner, type DropdownPositionerOptions } from './positioner';\n\nexport type ListboxItem = object;\n\n/** Placement-only options for the listbox dropdown positioner. */\nexport type ListboxDropdownPlacementOptions = Omit<DropdownPositionerOptions, 'getFloating' | 'getReference'>;\n\nexport type ListboxDropdownOptions<T extends ListboxItem> = {\n getBoundary: () => HTMLElement | null;\n getFocusedOptionElement?: () => HTMLElement | null;\n getItems: () => readonly T[];\n getOptionId?: (index: number) => string;\n getPanel: () => HTMLElement | null;\n getReference: () => HTMLElement | null;\n getTrigger?: () => HTMLElement | null;\n isDisabled?: () => boolean;\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n onClose?: (reason: DropdownCloseReason) => void;\n onNavigate?: (change: ListNavigationChange<T>) => void;\n onOpen?: (reason: OverlayOpenReason) => void;\n orientation?: 'both' | 'horizontal' | 'vertical';\n positioning?: ListboxDropdownPlacementOptions;\n restoreFocus?: boolean | (() => boolean);\n signal: AbortSignal;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListboxDropdown<T extends ListboxItem> = {\n [Symbol.dispose](): void;\n readonly ariaActiveDescendant: Readable<string | null>;\n readonly ariaExpanded: Readable<string>;\n close(reason?: DropdownCloseReason): void;\n /** Closes the dropdown (without restoring focus) and disposes list navigation state. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n readonly isOpen: Readable<boolean>;\n navigate(action: ListNavigationAction): number;\n open(reason?: OverlayOpenReason): void;\n scrollFocusedIntoView(): void;\n set(index: number): number;\n toggle(openReason?: OverlayOpenReason, closeReason?: DropdownCloseReason): void;\n updatePosition(): void;\n};\n\n/**\n * Internal listbox dropdown behavior for select and combobox. It owns only\n * listbox interactions; menu and other overlay families keep their own logic.\n */\nexport const createListboxDropdown = <T extends ListboxItem>(\n options: ListboxDropdownOptions<T>,\n): ListboxDropdown<T> => {\n if (typeof options.getBoundary !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getBoundary is required');\n }\n\n if (typeof options.getPanel !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getPanel is required');\n }\n\n if (typeof options.getReference !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getReference is required');\n }\n\n const isOpen = signal(false);\n const ariaExpanded = computed(() => String(isOpen.value));\n let stopPositioning: (() => void) | null = null;\n\n const positioner = createDropdownPositioner({\n ...options.positioning,\n getFloating: options.getPanel,\n getReference: options.getReference,\n });\n\n const scrollFocusedIntoView = (): void => {\n options.getFocusedOptionElement?.()?.scrollIntoView({ block: 'nearest' });\n };\n\n const list = createListControl<T>({\n disabled: computed(() => !isOpen.value),\n getItems: options.getItems,\n isItemDisabled: options.isItemDisabled,\n keys: options.keys,\n loop: options.loop ?? true,\n onNavigate: (change) => {\n scrollFocusedIntoView();\n options.onNavigate?.(change);\n },\n orientation: options.orientation,\n signal: options.signal,\n typeahead: options.typeahead,\n });\n\n const ariaActiveDescendant = computed<string | null>(() => {\n const index = list.focusedIndex.value;\n\n return options.getOptionId && isOpen.value && index >= 0 ? options.getOptionId(index) : null;\n });\n\n const shouldRestoreFocus = (): boolean => {\n return typeof options.restoreFocus === 'function' ? options.restoreFocus() : (options.restoreFocus ?? true);\n };\n\n const close = (reason: DropdownCloseReason = 'programmatic', shouldRestore = true): void => {\n if (!isOpen.value) return;\n\n isOpen.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n\n if (shouldRestore && shouldRestoreFocus() && options.getTrigger) restoreFocus(options.getTrigger);\n\n options.onClose?.(reason);\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (options.isDisabled?.() || isOpen.value) return;\n\n isOpen.value = true;\n positioner.update();\n stopPositioning = positioner.startAutoUpdate?.() ?? null;\n options.onOpen?.(reason);\n };\n\n const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DropdownCloseReason = 'trigger'): void => {\n if (isOpen.value) close(closeReason);\n else open(openReason);\n };\n\n createOutsidePointerDismissal({\n getTargets: () => [options.getBoundary(), options.getPanel()],\n isActive: () => isOpen.value,\n onDismiss: () => close('outsideClick'),\n signal: options.signal,\n });\n\n
|
|
1
|
+
{"version":3,"file":"option-list.js","names":[],"sources":["../../src/core/option-list.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport { computed, type Readable, signal } from '@vielzeug/ripple';\n\nimport { RefineConfigError } from '../errors';\nimport {\n createListControl,\n type ListNavigationAction,\n type ListNavigationChange,\n type ListNavigationTypeaheadOptions,\n} from './nav';\nimport { createOutsidePointerDismissal, type DropdownCloseReason, type OverlayOpenReason } from './overlay';\nimport { createDropdownPositioner, type DropdownPositionerOptions } from './positioner';\n\nexport type ListboxItem = object;\n\n/** Placement-only options for the listbox dropdown positioner. */\nexport type ListboxDropdownPlacementOptions = Omit<DropdownPositionerOptions, 'getFloating' | 'getReference'>;\n\nexport type ListboxDropdownOptions<T extends ListboxItem> = {\n getBoundary: () => HTMLElement | null;\n getFocusedOptionElement?: () => HTMLElement | null;\n getItems: () => readonly T[];\n getOptionId?: (index: number) => string;\n getPanel: () => HTMLElement | null;\n getReference: () => HTMLElement | null;\n getTrigger?: () => HTMLElement | null;\n isDisabled?: () => boolean;\n isItemDisabled?: (item: T, index: number) => boolean;\n keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n loop?: boolean;\n onClose?: (reason: DropdownCloseReason) => void;\n onNavigate?: (change: ListNavigationChange<T>) => void;\n onOpen?: (reason: OverlayOpenReason) => void;\n orientation?: 'both' | 'horizontal' | 'vertical';\n positioning?: ListboxDropdownPlacementOptions;\n restoreFocus?: boolean | (() => boolean);\n signal: AbortSignal;\n typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListboxDropdown<T extends ListboxItem> = {\n [Symbol.dispose](): void;\n readonly ariaActiveDescendant: Readable<string | null>;\n readonly ariaExpanded: Readable<string>;\n close(reason?: DropdownCloseReason): void;\n /** Closes the dropdown (without restoring focus) and disposes list navigation state. */\n dispose(): void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n readonly focusedIndex: Readable<number>;\n getActiveItem(): T | undefined;\n handleKeydown(event: KeyboardEvent): boolean;\n readonly isOpen: Readable<boolean>;\n navigate(action: ListNavigationAction): number;\n open(reason?: OverlayOpenReason): void;\n scrollFocusedIntoView(): void;\n set(index: number): number;\n toggle(openReason?: OverlayOpenReason, closeReason?: DropdownCloseReason): void;\n updatePosition(): void;\n};\n\n/**\n * Internal listbox dropdown behavior for select and combobox. It owns only\n * listbox interactions; menu and other overlay families keep their own logic.\n */\nexport const createListboxDropdown = <T extends ListboxItem>(\n options: ListboxDropdownOptions<T>,\n): ListboxDropdown<T> => {\n if (typeof options.getBoundary !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getBoundary is required');\n }\n\n if (typeof options.getPanel !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getPanel is required');\n }\n\n if (typeof options.getReference !== 'function') {\n throw new RefineConfigError('createListboxDropdown: getReference is required');\n }\n\n const isOpen = signal(false);\n const ariaExpanded = computed(() => String(isOpen.value));\n let disposed = false;\n let stopPositioning: (() => void) | null = null;\n\n const positioner = createDropdownPositioner({\n ...options.positioning,\n getFloating: options.getPanel,\n getReference: options.getReference,\n });\n\n const scrollFocusedIntoView = (): void => {\n if (!disposed) options.getFocusedOptionElement?.()?.scrollIntoView({ block: 'nearest' });\n };\n\n const list = createListControl<T>({\n disabled: computed(() => !isOpen.value),\n getItems: options.getItems,\n isItemDisabled: options.isItemDisabled,\n keys: options.keys,\n loop: options.loop ?? true,\n onNavigate: (change) => {\n scrollFocusedIntoView();\n options.onNavigate?.(change);\n },\n orientation: options.orientation,\n signal: options.signal,\n typeahead: options.typeahead,\n });\n\n const ariaActiveDescendant = computed<string | null>(() => {\n const index = list.focusedIndex.value;\n\n return options.getOptionId && isOpen.value && index >= 0 ? options.getOptionId(index) : null;\n });\n\n const shouldRestoreFocus = (): boolean => {\n return typeof options.restoreFocus === 'function' ? options.restoreFocus() : (options.restoreFocus ?? true);\n };\n\n const close = (reason: DropdownCloseReason = 'programmatic', shouldRestore = true): void => {\n if (disposed || !isOpen.value) return;\n\n isOpen.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n\n if (shouldRestore && shouldRestoreFocus() && options.getTrigger) restoreFocus(options.getTrigger);\n\n options.onClose?.(reason);\n };\n\n const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n if (disposed || options.isDisabled?.() || isOpen.value) return;\n\n isOpen.value = true;\n positioner.update();\n stopPositioning = positioner.startAutoUpdate?.() ?? null;\n options.onOpen?.(reason);\n };\n\n const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DropdownCloseReason = 'trigger'): void => {\n if (disposed) return;\n if (isOpen.value) close(closeReason);\n else open(openReason);\n };\n\n createOutsidePointerDismissal({\n getTargets: () => [options.getBoundary(), options.getPanel()],\n isActive: () => isOpen.value,\n onDismiss: () => close('outsideClick'),\n signal: options.signal,\n });\n\n const dispose = (): void => {\n if (disposed) return;\n\n disposed = true;\n list.dispose();\n\n if (isOpen.value) {\n isOpen.value = false;\n list.reset();\n stopPositioning?.();\n stopPositioning = null;\n }\n };\n\n options.signal.addEventListener('abort', dispose, { once: true });\n\n const handleKeydown = (event: KeyboardEvent): boolean => {\n if (disposed || !isOpen.value) return false;\n\n if (event.key === 'Escape') {\n event.preventDefault();\n close('escape');\n\n return true;\n }\n\n return list.handleKeydown(event);\n };\n\n return {\n ariaActiveDescendant,\n ariaExpanded,\n close,\n dispose,\n get disposed() {\n return disposed;\n },\n focusedIndex: list.focusedIndex,\n getActiveItem: list.getActiveItem,\n handleKeydown,\n isOpen,\n navigate: list.navigate,\n open,\n scrollFocusedIntoView,\n set: list.set,\n [Symbol.dispose]: dispose,\n toggle,\n updatePosition: () => {\n if (!disposed) positioner.update();\n },\n };\n};\n"],"mappings":";;;;;;;AAiEA,IAAa,KACX,MACuB;CACvB,IAAI,OAAO,EAAQ,eAAgB,YACjC,MAAM,IAAI,EAAkB,gDAAgD;CAG9E,IAAI,OAAO,EAAQ,YAAa,YAC9B,MAAM,IAAI,EAAkB,6CAA6C;CAG3E,IAAI,OAAO,EAAQ,gBAAiB,YAClC,MAAM,IAAI,EAAkB,iDAAiD;CAG/E,IAAM,IAAS,EAAO,EAAK,GACrB,IAAe,QAAe,OAAO,EAAO,KAAK,CAAC,GACpD,IAAW,IACX,IAAuC,MAErC,IAAa,EAAyB;EAC1C,GAAG,EAAQ;EACX,aAAa,EAAQ;EACrB,cAAc,EAAQ;CACxB,CAAC,GAEK,UAAoC;EACxC,AAAK,KAAU,EAAQ,0BAA0B,CAAC,EAAE,eAAe,EAAE,OAAO,UAAU,CAAC;CACzF,GAEM,IAAO,EAAqB;EAChC,UAAU,QAAe,CAAC,EAAO,KAAK;EACtC,UAAU,EAAQ;EAClB,gBAAgB,EAAQ;EACxB,MAAM,EAAQ;EACd,MAAM,EAAQ,QAAQ;EACtB,aAAa,MAAW;GAEtB,AADA,EAAsB,GACtB,EAAQ,aAAa,CAAM;EAC7B;EACA,aAAa,EAAQ;EACrB,QAAQ,EAAQ;EAChB,WAAW,EAAQ;CACrB,CAAC,GAEK,IAAuB,QAA8B;EACzD,IAAM,IAAQ,EAAK,aAAa;EAEhC,OAAO,EAAQ,eAAe,EAAO,SAAS,KAAS,IAAI,EAAQ,YAAY,CAAK,IAAI;CAC1F,CAAC,GAEK,UACG,OAAO,EAAQ,gBAAiB,aAAa,EAAQ,aAAa,IAAK,EAAQ,gBAAgB,IAGlG,KAAS,IAA8B,gBAAgB,IAAgB,OAAe;EACtF,MAAa,EAAO,UAExB,EAAO,QAAQ,IACf,EAAK,MAAM,GACX,IAAkB,GAClB,IAAkB,MAEd,KAAiB,EAAmB,KAAK,EAAQ,cAAY,EAAa,EAAQ,UAAU,GAEhG,EAAQ,UAAU,CAAM;CAC1B,GAEM,KAAQ,IAA4B,mBAAyB;EAC7D,KAAY,EAAQ,aAAa,KAAK,EAAO,UAEjD,EAAO,QAAQ,IACf,EAAW,OAAO,GAClB,IAAkB,EAAW,kBAAkB,KAAK,MACpD,EAAQ,SAAS,CAAM;CACzB,GAEM,KAAU,IAAgC,SAAS,IAAmC,cAAoB;EAC1G,MACA,EAAO,QAAO,EAAM,CAAW,IAC9B,EAAK,CAAU;CACtB;CAEA,EAA8B;EAC5B,kBAAkB,CAAC,EAAQ,YAAY,GAAG,EAAQ,SAAS,CAAC;EAC5D,gBAAgB,EAAO;EACvB,iBAAiB,EAAM,cAAc;EACrC,QAAQ,EAAQ;CAClB,CAAC;CAED,IAAM,UAAsB;EACtB,MAEJ,IAAW,IACX,EAAK,QAAQ,GAET,EAAO,UACT,EAAO,QAAQ,IACf,EAAK,MAAM,GACX,IAAkB,GAClB,IAAkB;CAEtB;CAiBA,OAfA,EAAQ,OAAO,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC,GAezD;EACL;EACA;EACA;EACA;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA,cAAc,EAAK;EACnB,eAAe,EAAK;EACpB,gBAvBqB,MACjB,KAAY,CAAC,EAAO,QAAc,KAElC,EAAM,QAAQ,YAChB,EAAM,eAAe,GACrB,EAAM,QAAQ,GAEP,MAGF,EAAK,cAAc,CAAK;EAc/B;EACA,UAAU,EAAK;EACf;EACA;EACA,KAAK,EAAK;GACT,OAAO,UAAU;EAClB;EACA,sBAAsB;GACpB,AAAK,KAAU,EAAW,OAAO;EACnC;CACF;AACF"}
|
package/dist/core/slider.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./numbers.cjs");let t=require("@vielzeug/arsenal
|
|
1
|
+
const e=require("./numbers.cjs");let t=require("@vielzeug/arsenal");var n=n=>{let r=()=>e.toFiniteNumberOr(n.min?.value,0),i=()=>e.toFiniteNumberOr(n.max?.value,100),a=()=>e.toPositiveStep(n.step?.value,1),o=n=>{let a=r(),o=i();return(0,t.clamp)(e.toFiniteNumberOr(n,a),a,o)},s=t=>{let n=r();return o(Math.round(e.toFiniteNumberOr(t,n)/a())*a())},c=t=>{let n=r(),a=i(),o=e.toFiniteNumberOr(t,n);return a<=n?0:(o-n)/(a-n)*100},l=(e,t)=>{if(t.width<=0)return s(r());let n=Math.max(0,Math.min(1,(e-t.left)/t.width)),a=r(),o=i();return s(a+n*(o-a))},u=(t,n)=>{let s=e.toFiniteNumberOr(n,r());return t===`ArrowRight`||t===`ArrowUp`?o(s+a()):t===`ArrowLeft`||t===`ArrowDown`?o(s-a()):t===`Home`?r():t===`End`?i():null};return{clamp:o,fromClientX:l,handleKeydown:(e,t,r)=>{if(n.disabled?.value||n.readonly?.value)return!1;let i=u(e.key,t);return i!==null&&(e.preventDefault(),r(i),!0)},max:i,min:r,nextFromKey:u,snap:s,toPercent:c}};exports.createSliderControl=n;
|
|
2
2
|
//# sourceMappingURL=slider.cjs.map
|