@vielzeug/refine 2.2.3 → 2.2.4
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/dist/accordion.cjs +1 -1
- package/dist/accordion.cjs.map +1 -1
- package/dist/accordion.js +2 -2
- package/dist/accordion.js.map +1 -1
- package/dist/carousel.cjs +25 -24
- package/dist/carousel.cjs.map +1 -1
- package/dist/carousel.js +125 -135
- package/dist/carousel.js.map +1 -1
- package/dist/combobox.cjs +14 -14
- package/dist/combobox.cjs.map +1 -1
- package/dist/combobox.js +88 -98
- package/dist/combobox.js.map +1 -1
- package/dist/content/carousel/carousel.d.ts.map +1 -1
- package/dist/content/list/_list-item-reveal.cjs +1 -1
- package/dist/content/list/_list-item-reveal.cjs.map +1 -1
- package/dist/content/list/_list-item-reveal.d.ts +3 -3
- package/dist/content/list/_list-item-reveal.d.ts.map +1 -1
- package/dist/content/list/_list-item-reveal.js +22 -22
- package/dist/content/list/_list-item-reveal.js.map +1 -1
- package/dist/content/list/list-item.d.ts.map +1 -1
- package/dist/core/focus.cjs +1 -1
- package/dist/core/focus.cjs.map +1 -1
- package/dist/core/focus.d.ts.map +1 -1
- package/dist/core/focus.js +21 -20
- package/dist/core/focus.js.map +1 -1
- package/dist/core/index.d.ts +2 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/nav.cjs +1 -1
- package/dist/core/nav.cjs.map +1 -1
- package/dist/core/nav.d.ts +3 -62
- package/dist/core/nav.d.ts.map +1 -1
- package/dist/core/nav.js +41 -124
- 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 +5 -5
- package/dist/core/option-list.d.ts.map +1 -1
- package/dist/core/option-list.js +14 -13
- package/dist/core/option-list.js.map +1 -1
- package/dist/core/overlay.cjs +1 -1
- package/dist/core/overlay.cjs.map +1 -1
- package/dist/core/overlay.d.ts +0 -8
- package/dist/core/overlay.d.ts.map +1 -1
- package/dist/core/overlay.js +1 -3
- package/dist/core/overlay.js.map +1 -1
- package/dist/custom-elements.json +3397 -3452
- package/dist/drawer.cjs +6 -6
- package/dist/drawer.cjs.map +1 -1
- package/dist/drawer.js +65 -75
- package/dist/drawer.js.map +1 -1
- package/dist/feedback/skeleton/skeleton.d.ts.map +1 -1
- package/dist/feedback/toast/toast.d.ts.map +1 -1
- package/dist/grid.cjs +2 -2
- package/dist/grid.cjs.map +1 -1
- package/dist/grid.js +42 -41
- package/dist/grid.js.map +1 -1
- package/dist/layout/grid/grid.d.ts.map +1 -1
- package/dist/layout/navbar/navbar.d.ts.map +1 -1
- package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
- package/dist/list-item.cjs +2 -6
- package/dist/list-item.cjs.map +1 -1
- package/dist/list-item.js +40 -39
- 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 +2 -2
- package/dist/list.js.map +1 -1
- package/dist/menu.cjs +5 -5
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.js +59 -59
- package/dist/menu.js.map +1 -1
- package/dist/navbar.cjs +9 -9
- package/dist/navbar.cjs.map +1 -1
- package/dist/navbar.js +124 -116
- package/dist/navbar.js.map +1 -1
- package/dist/otp-input.cjs +1 -1
- package/dist/otp-input.cjs.map +1 -1
- package/dist/otp-input.js +2 -2
- package/dist/otp-input.js.map +1 -1
- package/dist/overlay/drawer/drawer.d.ts.map +1 -1
- package/dist/overlay/menu/menu.d.ts.map +1 -1
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +2 -3
- 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 +12 -11
- package/dist/radio.js.map +1 -1
- package/dist/rating.cjs +1 -1
- package/dist/rating.cjs.map +1 -1
- package/dist/rating.js +1 -1
- package/dist/rating.js.map +1 -1
- package/dist/refine.cjs +188 -195
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +212 -219
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +392 -399
- package/dist/refine.js.map +1 -1
- package/dist/select.cjs +1 -1
- package/dist/select.cjs.map +1 -1
- package/dist/select.js +1 -0
- package/dist/select.js.map +1 -1
- package/dist/sidebar.cjs +16 -16
- package/dist/sidebar.cjs.map +1 -1
- package/dist/sidebar.js +106 -91
- package/dist/sidebar.js.map +1 -1
- package/dist/skeleton.cjs +3 -3
- package/dist/skeleton.cjs.map +1 -1
- package/dist/skeleton.js +8 -7
- package/dist/skeleton.js.map +1 -1
- package/dist/stepper.cjs +1 -1
- package/dist/stepper.cjs.map +1 -1
- package/dist/stepper.js +3 -3
- package/dist/stepper.js.map +1 -1
- package/dist/tabs.cjs +1 -1
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.js +4 -5
- package/dist/tabs.js.map +1 -1
- package/dist/toast.cjs +13 -17
- package/dist/toast.cjs.map +1 -1
- package/dist/toast.js +86 -85
- package/dist/toast.js.map +1 -1
- package/package.json +9 -6
- package/dist/core/swipe.cjs +0 -2
- package/dist/core/swipe.cjs.map +0 -1
- package/dist/core/swipe.d.ts +0 -40
- package/dist/core/swipe.d.ts.map +0 -1
- package/dist/core/swipe.js +0 -63
- package/dist/core/swipe.js.map +0 -1
- package/dist/core/typeahead.cjs +0 -2
- package/dist/core/typeahead.cjs.map +0 -1
- package/dist/core/typeahead.d.ts +0 -53
- package/dist/core/typeahead.d.ts.map +0 -1
- package/dist/core/typeahead.js +0 -23
- package/dist/core/typeahead.js.map +0 -1
package/dist/otp-input.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-input.cjs","names":[],"sources":["../src/inputs/otp-input/otp-input.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, onMounted, prop, useEmit, useField } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, fieldVariantMixin, forcedColorsFocusMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor, VisualVariant } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport componentStyles from './otp-input.css?inline';\n\nexport type OreOtpInputEvents = {\n change: Event;\n complete: { originalEvent?: Event; value: string };\n input: Event;\n};\n\n/** OTP Input props */\nexport type OreOtpInputProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Make inputs disabled */\n disabled?: boolean;\n /** Accessible label */\n label?: string;\n /** Number of input cells */\n length?: number;\n /** Mask input (show dots instead of characters) */\n masked?: boolean;\n /** Form field name */\n name?: string;\n /** Show a separator in the middle (e.g. \"–\") */\n separator?: string;\n /** Component size */\n size?: ComponentSize;\n /** Input type: 'numeric' (digits only) or 'alphanumeric' */\n type?: 'numeric' | 'alphanumeric';\n /** Current value */\n value?: string;\n /** Visual variant */\n variant?: Exclude<VisualVariant, 'text' | 'frost'>;\n};\n\n/**\n * A segmented OTP (One-Time Password) input with N individual cells.\n * Auto-advances focus on input, auto-backs on Backspace, handles paste.\n *\n * @element ore-otp-input\n *\n * @attr {number} length - Number of cells (default: 6)\n * @attr {string} type - 'numeric' (default) | 'alphanumeric'\n * @attr {string} value - Current code value\n * @attr {boolean} disabled - Disable all cells\n * @attr {boolean} masked - Show as password\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} name - Form field name\n * @attr {string} label - Group aria-label\n * @attr {string} separator - Optional separator character shown in the middle\n *\n * @fires input - Emitted whenever a cell changes.\n * @fires change - Emitted whenever a cell changes.\n * @fires complete - Emitted when all cells are filled. detail: { value: string, originalEvent?: Event }\n *\n * @slot label - Custom label above the OTP input\n * @slot helper - Custom helper text below the OTP input\n * @slot error - Custom error content below the OTP input\n *\n * @cssprop --otp-cell-size - Width and height of each cell\n * @cssprop --otp-cell-gap - Gap between cells\n * @cssprop --otp-cell-font-size - Font size inside cells\n * @cssprop --otp-cell-radius - Border radius of cells\n * @cssprop --otp-cell-bg - Cell background color\n * @cssprop --otp-cell-border-color - Default border color\n * @cssprop --otp-cell-focus-border - Focused border/caret color\n * @cssprop --otp-cell-hover-bg - Cell background on hover (flat/ghost variants)\n * @cssprop --otp-cell-hover-border-color - Cell border on hover (flat/bordered variants)\n * @cssprop --otp-cell-focus-bg - Cell background when focused (flat variant)\n * @cssprop --otp-cell-focus-border-color - Cell border when focused (flat variant)\n *\n * @part group - Group container.\n * @part cell - Shadow part for the `cell` element.\n * @example\n * ```html\n * <ore-otp-input length=\"6\" color=\"primary\"></ore-otp-input>\n * ```\n */\nexport const OTP_INPUT_TAG = 'ore-otp-input' as const;\ndefine<OreOtpInputProps>(OTP_INPUT_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n label: prop.string('One-time password'),\n length: prop.number(6),\n masked: prop.bool(false),\n name: prop.string(),\n separator: prop.string(),\n type: prop.oneOf(['numeric', 'alphanumeric'] as const, 'numeric'),\n value: prop.string(),\n variant: prop.string<Exclude<VisualVariant, 'text' | 'frost'>>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreOtpInputEvents>();\n\n const lengthValue = computed(() => Number(props.length.value) || 6);\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const cells = computed(() => Array.from({ length: lengthValue.value }, (_, i) => i));\n const otpValue = signal(String(props.value.value || ''));\n const normalizedPropValue = () => String(props.value.value || '');\n\n // Form association: the joined cell value is the single form value for `name`,\n // since individual per-cell <input> elements inside the shadow root cannot\n // participate in an ancestor <form>'s submission on their own.\n useField<string>({ disabled: isDisabled, toFormValue: (v) => v, value: otpValue });\n\n bind({\n attr: {\n size: props.size,\n value: () => otpValue.value || null,\n variant: props.variant,\n },\n });\n\n function getInputs(): HTMLInputElement[] {\n return [...(el.shadowRoot?.querySelectorAll<HTMLInputElement>('input.cell') ?? [])];\n }\n\n const listControl = createListControl({\n getItems: () => getInputs(),\n keys: { next: ['ArrowRight'], prev: ['ArrowLeft'] },\n loop: false,\n onNavigate: (_action, index) => {\n const inputs = getInputs();\n\n inputs[index]?.focus();\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n function getValue(): string {\n return getInputs()\n .map((i) => i.value)\n .join('');\n }\n function syncInputsFromValue(raw: string) {\n const allInputs = getInputs();\n const normalized = String(raw || '').slice(0, allInputs.length);\n\n for (const input of allInputs) input.value = '';\n\n normalized.split('').forEach((c, i) => {\n if (allInputs[i]) allInputs[i].value = c;\n });\n\n otpValue.value = getValue();\n }\n defineFieldValue(\n el,\n () => otpValue.value,\n (value) => {\n syncInputsFromValue(value);\n },\n );\n function emitOtpState(originalEvent?: Event, forceComplete?: boolean): void {\n const allInputs = getInputs();\n const full = getValue();\n const complete = forceComplete ?? (full.length === allInputs.length && full.split('').every(Boolean));\n\n otpValue.value = full;\n setFieldValue(el, full);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n\n if (complete) emit('complete', { originalEvent, value: full });\n }\n function isAllowed(char: string): boolean {\n if (props.type.value === 'numeric') return /^\\d$/.test(char);\n\n return /^[a-z\\d]$/i.test(char);\n }\n function handleInput(e: Event, index: number) {\n const input = e.target as HTMLInputElement;\n let val = input.value;\n\n // Keep only last character if multiple were typed somehow\n if (val.length > 1) val = val.slice(-1);\n\n // Validate character\n if (val && !isAllowed(val)) {\n input.value = '';\n\n return;\n }\n\n input.value = val;\n\n const allInputs = getInputs();\n\n emitOtpState(e);\n\n // Auto-advance\n if (val && index < allInputs.length - 1) {\n allInputs[index + 1].focus();\n allInputs[index + 1].select();\n }\n }\n function handleKeydown(e: KeyboardEvent, index: number) {\n const input = e.target as HTMLInputElement;\n const allInputs = getInputs();\n\n listControl.set(index);\n\n if (e.key === 'Backspace') {\n if (input.value) {\n input.value = '';\n } else if (index > 0) {\n const prevIndex = listControl.navigate('prev');\n const prevInput = allInputs[prevIndex];\n\n if (prevInput) {\n prevInput.select();\n prevInput.value = '';\n }\n }\n\n emitOtpState(e, false);\n e.preventDefault();\n } else {\n listControl.handleKeydown(e);\n }\n }\n function handlePaste(e: ClipboardEvent) {\n e.preventDefault();\n\n const pasted = e.clipboardData?.getData('text') ?? '';\n const chars = pasted\n .split('')\n .filter((c) => isAllowed(c))\n .slice(0, lengthValue.value);\n const allInputs = getInputs();\n\n chars.forEach((char, i) => {\n if (allInputs[i]) allInputs[i].value = char;\n });\n\n emitOtpState(e);\n\n // Focus the cell after last pasted char\n const focusIdx = Math.min(chars.length, allInputs.length - 1);\n\n allInputs[focusIdx]?.focus();\n }\n\n watch(props.value, (value) => {\n syncInputsFromValue(String(value || ''));\n });\n\n watch(props.length, () => {\n // Cells re-render when length changes; sync after DOM updates.\n requestAnimationFrame(() => syncInputsFromValue(normalizedPropValue()));\n });\n\n const separatorIdx = computed(() => {\n const len = Number(props.length.value) || 6;\n\n return props.separator.value != null ? Math.floor(len / 2) : -1;\n });\n\n onMounted(() => {\n // Populate cells from value prop on mount\n syncInputsFromValue(normalizedPropValue());\n });\n\n return html`\n <div class=\"otp-group\" part=\"group\" role=\"group\" aria-label=\"${props.label}\">\n ${() =>\n cells.value.map(\n (i) => html`\n ${() =>\n separatorIdx.value > 0 && i === separatorIdx.value\n ? html`\n <span class=\"separator\" aria-hidden=\"true\">${() => props.separator.value || '-'}</span>\n `\n : ''}\n <input\n class=\"cell\"\n part=\"cell\"\n type=\"${() => (props.masked.value ? 'password' : 'text')}\"\n inputmode=\"${() => (props.type.value === 'numeric' ? 'numeric' : 'text')}\"\n maxlength=\"1\"\n autocomplete=\"${() => (i === 0 ? 'one-time-code' : 'off')}\"\n aria-label=\"${() => `Digit ${i + 1} of ${lengthValue.value}`}\"\n disabled=\"${() => (isDisabled.value ? true : null)}\"\n @input=\"${(e: Event) => handleInput(e, i)}\"\n @keydown=\"${(e: KeyboardEvent) => handleKeydown(e, i)}\"\n @paste=\"${(e: ClipboardEvent) => (i === 0 ? handlePaste(e) : e.preventDefault())}\"\n @focus=\"${(e: FocusEvent) => (e.target as HTMLInputElement).select()}\" />\n `,\n )}\n </div>\n `;\n },\n styles: [\n colorThemeMixin,\n sizeVariantMixin({}),\n forcedColorsFocusMixin('.cell'),\n componentStyles,\n // Must come after `componentStyles` — see `ore-input`'s identical ordering note for why.\n fieldVariantMixin({ accentVar: '--_cell-focus-border', container: '.cell', tokenPrefix: 'otp-cell' }),\n ],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"otp-input.cjs","names":[],"sources":["../src/inputs/otp-input/otp-input.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, onMounted, prop, useEmit, useField } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, fieldVariantMixin, forcedColorsFocusMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor, VisualVariant } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport componentStyles from './otp-input.css?inline';\n\nexport type OreOtpInputEvents = {\n change: Event;\n complete: { originalEvent?: Event; value: string };\n input: Event;\n};\n\n/** OTP Input props */\nexport type OreOtpInputProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Make inputs disabled */\n disabled?: boolean;\n /** Accessible label */\n label?: string;\n /** Number of input cells */\n length?: number;\n /** Mask input (show dots instead of characters) */\n masked?: boolean;\n /** Form field name */\n name?: string;\n /** Show a separator in the middle (e.g. \"–\") */\n separator?: string;\n /** Component size */\n size?: ComponentSize;\n /** Input type: 'numeric' (digits only) or 'alphanumeric' */\n type?: 'numeric' | 'alphanumeric';\n /** Current value */\n value?: string;\n /** Visual variant */\n variant?: Exclude<VisualVariant, 'text' | 'frost'>;\n};\n\n/**\n * A segmented OTP (One-Time Password) input with N individual cells.\n * Auto-advances focus on input, auto-backs on Backspace, handles paste.\n *\n * @element ore-otp-input\n *\n * @attr {number} length - Number of cells (default: 6)\n * @attr {string} type - 'numeric' (default) | 'alphanumeric'\n * @attr {string} value - Current code value\n * @attr {boolean} disabled - Disable all cells\n * @attr {boolean} masked - Show as password\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} name - Form field name\n * @attr {string} label - Group aria-label\n * @attr {string} separator - Optional separator character shown in the middle\n *\n * @fires input - Emitted whenever a cell changes.\n * @fires change - Emitted whenever a cell changes.\n * @fires complete - Emitted when all cells are filled. detail: { value: string, originalEvent?: Event }\n *\n * @slot label - Custom label above the OTP input\n * @slot helper - Custom helper text below the OTP input\n * @slot error - Custom error content below the OTP input\n *\n * @cssprop --otp-cell-size - Width and height of each cell\n * @cssprop --otp-cell-gap - Gap between cells\n * @cssprop --otp-cell-font-size - Font size inside cells\n * @cssprop --otp-cell-radius - Border radius of cells\n * @cssprop --otp-cell-bg - Cell background color\n * @cssprop --otp-cell-border-color - Default border color\n * @cssprop --otp-cell-focus-border - Focused border/caret color\n * @cssprop --otp-cell-hover-bg - Cell background on hover (flat/ghost variants)\n * @cssprop --otp-cell-hover-border-color - Cell border on hover (flat/bordered variants)\n * @cssprop --otp-cell-focus-bg - Cell background when focused (flat variant)\n * @cssprop --otp-cell-focus-border-color - Cell border when focused (flat variant)\n *\n * @part group - Group container.\n * @part cell - Shadow part for the `cell` element.\n * @example\n * ```html\n * <ore-otp-input length=\"6\" color=\"primary\"></ore-otp-input>\n * ```\n */\nexport const OTP_INPUT_TAG = 'ore-otp-input' as const;\ndefine<OreOtpInputProps>(OTP_INPUT_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n label: prop.string('One-time password'),\n length: prop.number(6),\n masked: prop.bool(false),\n name: prop.string(),\n separator: prop.string(),\n type: prop.oneOf(['numeric', 'alphanumeric'] as const, 'numeric'),\n value: prop.string(),\n variant: prop.string<Exclude<VisualVariant, 'text' | 'frost'>>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreOtpInputEvents>();\n\n const lengthValue = computed(() => Number(props.length.value) || 6);\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const cells = computed(() => Array.from({ length: lengthValue.value }, (_, i) => i));\n const otpValue = signal(String(props.value.value || ''));\n const normalizedPropValue = () => String(props.value.value || '');\n\n // Form association: the joined cell value is the single form value for `name`,\n // since individual per-cell <input> elements inside the shadow root cannot\n // participate in an ancestor <form>'s submission on their own.\n useField<string>({ disabled: isDisabled, toFormValue: (v) => v, value: otpValue });\n\n bind({\n attr: {\n size: props.size,\n value: () => otpValue.value || null,\n variant: props.variant,\n },\n });\n\n function getInputs(): HTMLInputElement[] {\n return [...(el.shadowRoot?.querySelectorAll<HTMLInputElement>('input.cell') ?? [])];\n }\n\n const listControl = createListControl({\n getItems: () => getInputs(),\n keys: { next: ['ArrowRight'], prev: ['ArrowLeft'] },\n loop: false,\n onNavigate: ({ item }) => {\n item.focus();\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n function getValue(): string {\n return getInputs()\n .map((i) => i.value)\n .join('');\n }\n function syncInputsFromValue(raw: string) {\n const allInputs = getInputs();\n const normalized = String(raw || '').slice(0, allInputs.length);\n\n for (const input of allInputs) input.value = '';\n\n normalized.split('').forEach((c, i) => {\n if (allInputs[i]) allInputs[i].value = c;\n });\n\n otpValue.value = getValue();\n }\n defineFieldValue(\n el,\n () => otpValue.value,\n (value) => {\n syncInputsFromValue(value);\n },\n );\n function emitOtpState(originalEvent?: Event, forceComplete?: boolean): void {\n const allInputs = getInputs();\n const full = getValue();\n const complete = forceComplete ?? (full.length === allInputs.length && full.split('').every(Boolean));\n\n otpValue.value = full;\n setFieldValue(el, full);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n\n if (complete) emit('complete', { originalEvent, value: full });\n }\n function isAllowed(char: string): boolean {\n if (props.type.value === 'numeric') return /^\\d$/.test(char);\n\n return /^[a-z\\d]$/i.test(char);\n }\n function handleInput(e: Event, index: number) {\n const input = e.target as HTMLInputElement;\n let val = input.value;\n\n // Keep only last character if multiple were typed somehow\n if (val.length > 1) val = val.slice(-1);\n\n // Validate character\n if (val && !isAllowed(val)) {\n input.value = '';\n\n return;\n }\n\n input.value = val;\n\n const allInputs = getInputs();\n\n emitOtpState(e);\n\n // Auto-advance\n if (val && index < allInputs.length - 1) {\n allInputs[index + 1].focus();\n allInputs[index + 1].select();\n }\n }\n function handleKeydown(e: KeyboardEvent, index: number) {\n const input = e.target as HTMLInputElement;\n const allInputs = getInputs();\n\n listControl.set(index);\n\n if (e.key === 'Backspace') {\n if (input.value) {\n input.value = '';\n } else if (index > 0) {\n const prevIndex = listControl.navigate('prev');\n const prevInput = allInputs[prevIndex];\n\n if (prevInput) {\n prevInput.select();\n prevInput.value = '';\n }\n }\n\n emitOtpState(e, false);\n e.preventDefault();\n } else {\n listControl.handleKeydown(e);\n }\n }\n function handlePaste(e: ClipboardEvent) {\n e.preventDefault();\n\n const pasted = e.clipboardData?.getData('text') ?? '';\n const chars = pasted\n .split('')\n .filter((c) => isAllowed(c))\n .slice(0, lengthValue.value);\n const allInputs = getInputs();\n\n chars.forEach((char, i) => {\n if (allInputs[i]) allInputs[i].value = char;\n });\n\n emitOtpState(e);\n\n // Focus the cell after last pasted char\n const focusIdx = Math.min(chars.length, allInputs.length - 1);\n\n allInputs[focusIdx]?.focus();\n }\n\n watch(props.value, (value) => {\n syncInputsFromValue(String(value || ''));\n });\n\n watch(props.length, () => {\n // Cells re-render when length changes; sync after DOM updates.\n requestAnimationFrame(() => syncInputsFromValue(normalizedPropValue()));\n });\n\n const separatorIdx = computed(() => {\n const len = Number(props.length.value) || 6;\n\n return props.separator.value != null ? Math.floor(len / 2) : -1;\n });\n\n onMounted(() => {\n // Populate cells from value prop on mount\n syncInputsFromValue(normalizedPropValue());\n });\n\n return html`\n <div class=\"otp-group\" part=\"group\" role=\"group\" aria-label=\"${props.label}\">\n ${() =>\n cells.value.map(\n (i) => html`\n ${() =>\n separatorIdx.value > 0 && i === separatorIdx.value\n ? html`\n <span class=\"separator\" aria-hidden=\"true\">${() => props.separator.value || '-'}</span>\n `\n : ''}\n <input\n class=\"cell\"\n part=\"cell\"\n type=\"${() => (props.masked.value ? 'password' : 'text')}\"\n inputmode=\"${() => (props.type.value === 'numeric' ? 'numeric' : 'text')}\"\n maxlength=\"1\"\n autocomplete=\"${() => (i === 0 ? 'one-time-code' : 'off')}\"\n aria-label=\"${() => `Digit ${i + 1} of ${lengthValue.value}`}\"\n disabled=\"${() => (isDisabled.value ? true : null)}\"\n @input=\"${(e: Event) => handleInput(e, i)}\"\n @keydown=\"${(e: KeyboardEvent) => handleKeydown(e, i)}\"\n @paste=\"${(e: ClipboardEvent) => (i === 0 ? handlePaste(e) : e.preventDefault())}\"\n @focus=\"${(e: FocusEvent) => (e.target as HTMLInputElement).select()}\" />\n `,\n )}\n </div>\n `;\n },\n styles: [\n colorThemeMixin,\n sizeVariantMixin({}),\n forcedColorsFocusMixin('.cell'),\n componentStyles,\n // Must come after `componentStyles` — see `ore-input`'s identical ordering note for why.\n fieldVariantMixin({ accentVar: '--_cell-focus-border', container: '.cell', tokenPrefix: 'otp-cell' }),\n ],\n});\n"],"mappings":"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"}
|
package/dist/otp-input.js
CHANGED
package/dist/otp-input.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otp-input.js","names":[],"sources":["../src/inputs/otp-input/otp-input.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, onMounted, prop, useEmit, useField } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, fieldVariantMixin, forcedColorsFocusMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor, VisualVariant } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport componentStyles from './otp-input.css?inline';\n\nexport type OreOtpInputEvents = {\n change: Event;\n complete: { originalEvent?: Event; value: string };\n input: Event;\n};\n\n/** OTP Input props */\nexport type OreOtpInputProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Make inputs disabled */\n disabled?: boolean;\n /** Accessible label */\n label?: string;\n /** Number of input cells */\n length?: number;\n /** Mask input (show dots instead of characters) */\n masked?: boolean;\n /** Form field name */\n name?: string;\n /** Show a separator in the middle (e.g. \"–\") */\n separator?: string;\n /** Component size */\n size?: ComponentSize;\n /** Input type: 'numeric' (digits only) or 'alphanumeric' */\n type?: 'numeric' | 'alphanumeric';\n /** Current value */\n value?: string;\n /** Visual variant */\n variant?: Exclude<VisualVariant, 'text' | 'frost'>;\n};\n\n/**\n * A segmented OTP (One-Time Password) input with N individual cells.\n * Auto-advances focus on input, auto-backs on Backspace, handles paste.\n *\n * @element ore-otp-input\n *\n * @attr {number} length - Number of cells (default: 6)\n * @attr {string} type - 'numeric' (default) | 'alphanumeric'\n * @attr {string} value - Current code value\n * @attr {boolean} disabled - Disable all cells\n * @attr {boolean} masked - Show as password\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} name - Form field name\n * @attr {string} label - Group aria-label\n * @attr {string} separator - Optional separator character shown in the middle\n *\n * @fires input - Emitted whenever a cell changes.\n * @fires change - Emitted whenever a cell changes.\n * @fires complete - Emitted when all cells are filled. detail: { value: string, originalEvent?: Event }\n *\n * @slot label - Custom label above the OTP input\n * @slot helper - Custom helper text below the OTP input\n * @slot error - Custom error content below the OTP input\n *\n * @cssprop --otp-cell-size - Width and height of each cell\n * @cssprop --otp-cell-gap - Gap between cells\n * @cssprop --otp-cell-font-size - Font size inside cells\n * @cssprop --otp-cell-radius - Border radius of cells\n * @cssprop --otp-cell-bg - Cell background color\n * @cssprop --otp-cell-border-color - Default border color\n * @cssprop --otp-cell-focus-border - Focused border/caret color\n * @cssprop --otp-cell-hover-bg - Cell background on hover (flat/ghost variants)\n * @cssprop --otp-cell-hover-border-color - Cell border on hover (flat/bordered variants)\n * @cssprop --otp-cell-focus-bg - Cell background when focused (flat variant)\n * @cssprop --otp-cell-focus-border-color - Cell border when focused (flat variant)\n *\n * @part group - Group container.\n * @part cell - Shadow part for the `cell` element.\n * @example\n * ```html\n * <ore-otp-input length=\"6\" color=\"primary\"></ore-otp-input>\n * ```\n */\nexport const OTP_INPUT_TAG = 'ore-otp-input' as const;\ndefine<OreOtpInputProps>(OTP_INPUT_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n label: prop.string('One-time password'),\n length: prop.number(6),\n masked: prop.bool(false),\n name: prop.string(),\n separator: prop.string(),\n type: prop.oneOf(['numeric', 'alphanumeric'] as const, 'numeric'),\n value: prop.string(),\n variant: prop.string<Exclude<VisualVariant, 'text' | 'frost'>>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreOtpInputEvents>();\n\n const lengthValue = computed(() => Number(props.length.value) || 6);\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const cells = computed(() => Array.from({ length: lengthValue.value }, (_, i) => i));\n const otpValue = signal(String(props.value.value || ''));\n const normalizedPropValue = () => String(props.value.value || '');\n\n // Form association: the joined cell value is the single form value for `name`,\n // since individual per-cell <input> elements inside the shadow root cannot\n // participate in an ancestor <form>'s submission on their own.\n useField<string>({ disabled: isDisabled, toFormValue: (v) => v, value: otpValue });\n\n bind({\n attr: {\n size: props.size,\n value: () => otpValue.value || null,\n variant: props.variant,\n },\n });\n\n function getInputs(): HTMLInputElement[] {\n return [...(el.shadowRoot?.querySelectorAll<HTMLInputElement>('input.cell') ?? [])];\n }\n\n const listControl = createListControl({\n getItems: () => getInputs(),\n keys: { next: ['ArrowRight'], prev: ['ArrowLeft'] },\n loop: false,\n onNavigate: (_action, index) => {\n const inputs = getInputs();\n\n inputs[index]?.focus();\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n function getValue(): string {\n return getInputs()\n .map((i) => i.value)\n .join('');\n }\n function syncInputsFromValue(raw: string) {\n const allInputs = getInputs();\n const normalized = String(raw || '').slice(0, allInputs.length);\n\n for (const input of allInputs) input.value = '';\n\n normalized.split('').forEach((c, i) => {\n if (allInputs[i]) allInputs[i].value = c;\n });\n\n otpValue.value = getValue();\n }\n defineFieldValue(\n el,\n () => otpValue.value,\n (value) => {\n syncInputsFromValue(value);\n },\n );\n function emitOtpState(originalEvent?: Event, forceComplete?: boolean): void {\n const allInputs = getInputs();\n const full = getValue();\n const complete = forceComplete ?? (full.length === allInputs.length && full.split('').every(Boolean));\n\n otpValue.value = full;\n setFieldValue(el, full);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n\n if (complete) emit('complete', { originalEvent, value: full });\n }\n function isAllowed(char: string): boolean {\n if (props.type.value === 'numeric') return /^\\d$/.test(char);\n\n return /^[a-z\\d]$/i.test(char);\n }\n function handleInput(e: Event, index: number) {\n const input = e.target as HTMLInputElement;\n let val = input.value;\n\n // Keep only last character if multiple were typed somehow\n if (val.length > 1) val = val.slice(-1);\n\n // Validate character\n if (val && !isAllowed(val)) {\n input.value = '';\n\n return;\n }\n\n input.value = val;\n\n const allInputs = getInputs();\n\n emitOtpState(e);\n\n // Auto-advance\n if (val && index < allInputs.length - 1) {\n allInputs[index + 1].focus();\n allInputs[index + 1].select();\n }\n }\n function handleKeydown(e: KeyboardEvent, index: number) {\n const input = e.target as HTMLInputElement;\n const allInputs = getInputs();\n\n listControl.set(index);\n\n if (e.key === 'Backspace') {\n if (input.value) {\n input.value = '';\n } else if (index > 0) {\n const prevIndex = listControl.navigate('prev');\n const prevInput = allInputs[prevIndex];\n\n if (prevInput) {\n prevInput.select();\n prevInput.value = '';\n }\n }\n\n emitOtpState(e, false);\n e.preventDefault();\n } else {\n listControl.handleKeydown(e);\n }\n }\n function handlePaste(e: ClipboardEvent) {\n e.preventDefault();\n\n const pasted = e.clipboardData?.getData('text') ?? '';\n const chars = pasted\n .split('')\n .filter((c) => isAllowed(c))\n .slice(0, lengthValue.value);\n const allInputs = getInputs();\n\n chars.forEach((char, i) => {\n if (allInputs[i]) allInputs[i].value = char;\n });\n\n emitOtpState(e);\n\n // Focus the cell after last pasted char\n const focusIdx = Math.min(chars.length, allInputs.length - 1);\n\n allInputs[focusIdx]?.focus();\n }\n\n watch(props.value, (value) => {\n syncInputsFromValue(String(value || ''));\n });\n\n watch(props.length, () => {\n // Cells re-render when length changes; sync after DOM updates.\n requestAnimationFrame(() => syncInputsFromValue(normalizedPropValue()));\n });\n\n const separatorIdx = computed(() => {\n const len = Number(props.length.value) || 6;\n\n return props.separator.value != null ? Math.floor(len / 2) : -1;\n });\n\n onMounted(() => {\n // Populate cells from value prop on mount\n syncInputsFromValue(normalizedPropValue());\n });\n\n return html`\n <div class=\"otp-group\" part=\"group\" role=\"group\" aria-label=\"${props.label}\">\n ${() =>\n cells.value.map(\n (i) => html`\n ${() =>\n separatorIdx.value > 0 && i === separatorIdx.value\n ? html`\n <span class=\"separator\" aria-hidden=\"true\">${() => props.separator.value || '-'}</span>\n `\n : ''}\n <input\n class=\"cell\"\n part=\"cell\"\n type=\"${() => (props.masked.value ? 'password' : 'text')}\"\n inputmode=\"${() => (props.type.value === 'numeric' ? 'numeric' : 'text')}\"\n maxlength=\"1\"\n autocomplete=\"${() => (i === 0 ? 'one-time-code' : 'off')}\"\n aria-label=\"${() => `Digit ${i + 1} of ${lengthValue.value}`}\"\n disabled=\"${() => (isDisabled.value ? true : null)}\"\n @input=\"${(e: Event) => handleInput(e, i)}\"\n @keydown=\"${(e: KeyboardEvent) => handleKeydown(e, i)}\"\n @paste=\"${(e: ClipboardEvent) => (i === 0 ? handlePaste(e) : e.preventDefault())}\"\n @focus=\"${(e: FocusEvent) => (e.target as HTMLInputElement).select()}\" />\n `,\n )}\n </div>\n `;\n },\n styles: [\n colorThemeMixin,\n sizeVariantMixin({}),\n forcedColorsFocusMixin('.cell'),\n componentStyles,\n // Must come after `componentStyles` — see `ore-input`'s identical ordering note for why.\n fieldVariantMixin({ accentVar: '--_cell-focus-border', container: '.cell', tokenPrefix: 'otp-cell' }),\n ],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"otp-input.js","names":[],"sources":["../src/inputs/otp-input/otp-input.ts"],"sourcesContent":["import { bind, define, getHost, html, onCleanup, onMounted, prop, useEmit, useField } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, fieldVariantMixin, forcedColorsFocusMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor, VisualVariant } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport componentStyles from './otp-input.css?inline';\n\nexport type OreOtpInputEvents = {\n change: Event;\n complete: { originalEvent?: Event; value: string };\n input: Event;\n};\n\n/** OTP Input props */\nexport type OreOtpInputProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Make inputs disabled */\n disabled?: boolean;\n /** Accessible label */\n label?: string;\n /** Number of input cells */\n length?: number;\n /** Mask input (show dots instead of characters) */\n masked?: boolean;\n /** Form field name */\n name?: string;\n /** Show a separator in the middle (e.g. \"–\") */\n separator?: string;\n /** Component size */\n size?: ComponentSize;\n /** Input type: 'numeric' (digits only) or 'alphanumeric' */\n type?: 'numeric' | 'alphanumeric';\n /** Current value */\n value?: string;\n /** Visual variant */\n variant?: Exclude<VisualVariant, 'text' | 'frost'>;\n};\n\n/**\n * A segmented OTP (One-Time Password) input with N individual cells.\n * Auto-advances focus on input, auto-backs on Backspace, handles paste.\n *\n * @element ore-otp-input\n *\n * @attr {number} length - Number of cells (default: 6)\n * @attr {string} type - 'numeric' (default) | 'alphanumeric'\n * @attr {string} value - Current code value\n * @attr {boolean} disabled - Disable all cells\n * @attr {boolean} masked - Show as password\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} name - Form field name\n * @attr {string} label - Group aria-label\n * @attr {string} separator - Optional separator character shown in the middle\n *\n * @fires input - Emitted whenever a cell changes.\n * @fires change - Emitted whenever a cell changes.\n * @fires complete - Emitted when all cells are filled. detail: { value: string, originalEvent?: Event }\n *\n * @slot label - Custom label above the OTP input\n * @slot helper - Custom helper text below the OTP input\n * @slot error - Custom error content below the OTP input\n *\n * @cssprop --otp-cell-size - Width and height of each cell\n * @cssprop --otp-cell-gap - Gap between cells\n * @cssprop --otp-cell-font-size - Font size inside cells\n * @cssprop --otp-cell-radius - Border radius of cells\n * @cssprop --otp-cell-bg - Cell background color\n * @cssprop --otp-cell-border-color - Default border color\n * @cssprop --otp-cell-focus-border - Focused border/caret color\n * @cssprop --otp-cell-hover-bg - Cell background on hover (flat/ghost variants)\n * @cssprop --otp-cell-hover-border-color - Cell border on hover (flat/bordered variants)\n * @cssprop --otp-cell-focus-bg - Cell background when focused (flat variant)\n * @cssprop --otp-cell-focus-border-color - Cell border when focused (flat variant)\n *\n * @part group - Group container.\n * @part cell - Shadow part for the `cell` element.\n * @example\n * ```html\n * <ore-otp-input length=\"6\" color=\"primary\"></ore-otp-input>\n * ```\n */\nexport const OTP_INPUT_TAG = 'ore-otp-input' as const;\ndefine<OreOtpInputProps>(OTP_INPUT_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n label: prop.string('One-time password'),\n length: prop.number(6),\n masked: prop.bool(false),\n name: prop.string(),\n separator: prop.string(),\n type: prop.oneOf(['numeric', 'alphanumeric'] as const, 'numeric'),\n value: prop.string(),\n variant: prop.string<Exclude<VisualVariant, 'text' | 'frost'>>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreOtpInputEvents>();\n\n const lengthValue = computed(() => Number(props.length.value) || 6);\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const cells = computed(() => Array.from({ length: lengthValue.value }, (_, i) => i));\n const otpValue = signal(String(props.value.value || ''));\n const normalizedPropValue = () => String(props.value.value || '');\n\n // Form association: the joined cell value is the single form value for `name`,\n // since individual per-cell <input> elements inside the shadow root cannot\n // participate in an ancestor <form>'s submission on their own.\n useField<string>({ disabled: isDisabled, toFormValue: (v) => v, value: otpValue });\n\n bind({\n attr: {\n size: props.size,\n value: () => otpValue.value || null,\n variant: props.variant,\n },\n });\n\n function getInputs(): HTMLInputElement[] {\n return [...(el.shadowRoot?.querySelectorAll<HTMLInputElement>('input.cell') ?? [])];\n }\n\n const listControl = createListControl({\n getItems: () => getInputs(),\n keys: { next: ['ArrowRight'], prev: ['ArrowLeft'] },\n loop: false,\n onNavigate: ({ item }) => {\n item.focus();\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n function getValue(): string {\n return getInputs()\n .map((i) => i.value)\n .join('');\n }\n function syncInputsFromValue(raw: string) {\n const allInputs = getInputs();\n const normalized = String(raw || '').slice(0, allInputs.length);\n\n for (const input of allInputs) input.value = '';\n\n normalized.split('').forEach((c, i) => {\n if (allInputs[i]) allInputs[i].value = c;\n });\n\n otpValue.value = getValue();\n }\n defineFieldValue(\n el,\n () => otpValue.value,\n (value) => {\n syncInputsFromValue(value);\n },\n );\n function emitOtpState(originalEvent?: Event, forceComplete?: boolean): void {\n const allInputs = getInputs();\n const full = getValue();\n const complete = forceComplete ?? (full.length === allInputs.length && full.split('').every(Boolean));\n\n otpValue.value = full;\n setFieldValue(el, full);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n\n if (complete) emit('complete', { originalEvent, value: full });\n }\n function isAllowed(char: string): boolean {\n if (props.type.value === 'numeric') return /^\\d$/.test(char);\n\n return /^[a-z\\d]$/i.test(char);\n }\n function handleInput(e: Event, index: number) {\n const input = e.target as HTMLInputElement;\n let val = input.value;\n\n // Keep only last character if multiple were typed somehow\n if (val.length > 1) val = val.slice(-1);\n\n // Validate character\n if (val && !isAllowed(val)) {\n input.value = '';\n\n return;\n }\n\n input.value = val;\n\n const allInputs = getInputs();\n\n emitOtpState(e);\n\n // Auto-advance\n if (val && index < allInputs.length - 1) {\n allInputs[index + 1].focus();\n allInputs[index + 1].select();\n }\n }\n function handleKeydown(e: KeyboardEvent, index: number) {\n const input = e.target as HTMLInputElement;\n const allInputs = getInputs();\n\n listControl.set(index);\n\n if (e.key === 'Backspace') {\n if (input.value) {\n input.value = '';\n } else if (index > 0) {\n const prevIndex = listControl.navigate('prev');\n const prevInput = allInputs[prevIndex];\n\n if (prevInput) {\n prevInput.select();\n prevInput.value = '';\n }\n }\n\n emitOtpState(e, false);\n e.preventDefault();\n } else {\n listControl.handleKeydown(e);\n }\n }\n function handlePaste(e: ClipboardEvent) {\n e.preventDefault();\n\n const pasted = e.clipboardData?.getData('text') ?? '';\n const chars = pasted\n .split('')\n .filter((c) => isAllowed(c))\n .slice(0, lengthValue.value);\n const allInputs = getInputs();\n\n chars.forEach((char, i) => {\n if (allInputs[i]) allInputs[i].value = char;\n });\n\n emitOtpState(e);\n\n // Focus the cell after last pasted char\n const focusIdx = Math.min(chars.length, allInputs.length - 1);\n\n allInputs[focusIdx]?.focus();\n }\n\n watch(props.value, (value) => {\n syncInputsFromValue(String(value || ''));\n });\n\n watch(props.length, () => {\n // Cells re-render when length changes; sync after DOM updates.\n requestAnimationFrame(() => syncInputsFromValue(normalizedPropValue()));\n });\n\n const separatorIdx = computed(() => {\n const len = Number(props.length.value) || 6;\n\n return props.separator.value != null ? Math.floor(len / 2) : -1;\n });\n\n onMounted(() => {\n // Populate cells from value prop on mount\n syncInputsFromValue(normalizedPropValue());\n });\n\n return html`\n <div class=\"otp-group\" part=\"group\" role=\"group\" aria-label=\"${props.label}\">\n ${() =>\n cells.value.map(\n (i) => html`\n ${() =>\n separatorIdx.value > 0 && i === separatorIdx.value\n ? html`\n <span class=\"separator\" aria-hidden=\"true\">${() => props.separator.value || '-'}</span>\n `\n : ''}\n <input\n class=\"cell\"\n part=\"cell\"\n type=\"${() => (props.masked.value ? 'password' : 'text')}\"\n inputmode=\"${() => (props.type.value === 'numeric' ? 'numeric' : 'text')}\"\n maxlength=\"1\"\n autocomplete=\"${() => (i === 0 ? 'one-time-code' : 'off')}\"\n aria-label=\"${() => `Digit ${i + 1} of ${lengthValue.value}`}\"\n disabled=\"${() => (isDisabled.value ? true : null)}\"\n @input=\"${(e: Event) => handleInput(e, i)}\"\n @keydown=\"${(e: KeyboardEvent) => handleKeydown(e, i)}\"\n @paste=\"${(e: ClipboardEvent) => (i === 0 ? handlePaste(e) : e.preventDefault())}\"\n @focus=\"${(e: FocusEvent) => (e.target as HTMLInputElement).select()}\" />\n `,\n )}\n </div>\n `;\n },\n styles: [\n colorThemeMixin,\n sizeVariantMixin({}),\n forcedColorsFocusMixin('.cell'),\n componentStyles,\n // Must come after `componentStyles` — see `ore-input`'s identical ordering note for why.\n fieldVariantMixin({ accentVar: '--_cell-focus-border', container: '.cell', tokenPrefix: 'otp-cell' }),\n ],\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../../src/overlay/drawer/drawer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../../src/overlay/drawer/drawer.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,yBAAyB,CAAC;AAKjC,KAAK,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAC3D,KAAK,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AACvC,KAAK,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAC;AACxD,KAAK,yBAAyB,GAAG,SAAS,GAAG,OAAO,CAAC;AAiCrD,sEAAsE;AACtE,MAAM,WAAW,aAAc,SAAQ,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;IAC/E,iEAAiE;IACjE,IAAI,IAAI,IAAI,CAAC;IACb,sEAAsE;IACtE,IAAI,IAAI,IAAI,CAAC;CACd;AAED,kCAAkC;AAElC,MAAM,MAAM,eAAe,GAAG;IAC5B,aAAa,EAAE,uBAAuB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,oEAAoE;IACpE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,mEAAmE;IACnE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4DAA4D;IAC5D,uBAAuB,CAAC,EAAE,yBAAyB,CAAC;IACpD;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iCAAiC;IACjC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,eAAO,MAAM,UAAU,EAAG,YAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/overlay/menu/menu.ts"],"names":[],"mappings":"AAgBA,OAAO,EAOL,KAAK,uBAAuB,
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/overlay/menu/menu.ts"],"names":[],"mappings":"AAgBA,OAAO,EAOL,KAAK,uBAAuB,EAE7B,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAOjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,OAAO,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG;IAC1B,aAAa,EAAE,uBAAuB,CAAC;IACvC,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,mEAAmE;IACnE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,YAAY,GAAG,KAAK,GAAG,WAAW,GAAG,SAAS,CAAC;IACvF,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,aAAa,EAAG,eAAwB,CAAC;AAwDtD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAG,oBAA6B,CAAC;AAgB3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,QAAQ,EAAG,UAAmB,CAAC"}
|
package/dist/radio-group.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/states.css.cjs"),t=require("./core/choice-field.cjs"),n=require("./core/light-dom.cjs"),r=require("./core/ore.cjs"),i=require("./core/nav.cjs"),a=require("./shared/prop-bundles.cjs"),o=require("./inputs/radio-group/radio-group.css.cjs");let s=require("@vielzeug/ore");var c=(0,s.createContext)(`OreRadioGroup`),l=`ore-radio-group`;(0,s.define)(l,{formAssociated:!0,props:{...a.themableBundle,...a.sizableBundle,...a.disablableBundle,error:s.prop.string(),helper:s.prop.string(),label:s.prop.string(),name:s.prop.string(),orientation:s.prop.oneOf([`horizontal`,`vertical`],`vertical`),required:s.prop.bool(!1),value:{...s.prop.string(),reflect:!1}},setup(e){let a=(0,s.getHost)(),o=(0,s.useEmit)(),l=(0,s.useSlots)(),u=s.watchEffect,d=t.createChoiceField({disabled:e.disabled,error:e.error,helper:e.helper,label:e.label,prefix:`radio-group`,required:e.required,signal:r.lifecycleSignal(s.onCleanup),value:e.value});d.attachFormField((0,s.useField)({disabled:d.disabled,onReset:d.reset,toFormValue:e=>e,validationMessage:d.validationMessage,validity:d.validity,value:d.formValue}));let f=d.selectedValue,p=e.disabled;(0,s.bind)({attr:{size:e.size,value:()=>f.value||null}});let m=()=>n.getLightChildrenByTag(a,`ore-radio`),h=()=>p.value?[]:m().filter(e=>!e.hasAttribute(`disabled`)),g=e=>n.getChoiceLabel(m(),e),_=(e,t)=>{d.setValues(e?[e]:[]);let n=e?[g(e)]:[];o(`change`,{labels:n,originalEvent:t,values:e?[e]:[]}),d.triggerValidation(`blur`)};(0,s.provide)(c,{color:e.color,disabled:p,name:e.name,select:_,size:e.size,value:f}),u(()=>{l.elements().value,f.value;let t=m();for(let n of t){let t=n.getAttribute(`value`)??``;n.toggleAttribute(`checked`,t===f.value),e.name.value?n.setAttribute(`name`,e.name.value):n.removeAttribute(`name`),e.color.value?n.setAttribute(`color`,e.color.value):n.removeAttribute(`color`),e.size.value?n.setAttribute(`size`,e.size.value):n.removeAttribute(`size`),n.toggleAttribute(`disabled`,p.value)}}),u(()=>{l.elements().value;let e=m(),t=!1;for(let n of e){let e=n.getAttribute(`value`)===f.value;n.setAttribute(`tabindex`,e&&!p.value?`0`:`-1`),e&&!p.value&&(t=!0)}if(!t&&e.length>0){let t=e.find(e=>!e.hasAttribute(`disabled`));t&&t.setAttribute(`tabindex`,`0`)}});let v=i.createListControl({getItems:h,keys:{next:[`ArrowDown`,`ArrowRight`],prev:[`ArrowUp`,`ArrowLeft`]},loop:!0,onNavigate:(e,t
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/states.css.cjs"),t=require("./core/choice-field.cjs"),n=require("./core/light-dom.cjs"),r=require("./core/ore.cjs"),i=require("./core/nav.cjs"),a=require("./shared/prop-bundles.cjs"),o=require("./inputs/radio-group/radio-group.css.cjs");let s=require("@vielzeug/ore");var c=(0,s.createContext)(`OreRadioGroup`),l=`ore-radio-group`;(0,s.define)(l,{formAssociated:!0,props:{...a.themableBundle,...a.sizableBundle,...a.disablableBundle,error:s.prop.string(),helper:s.prop.string(),label:s.prop.string(),name:s.prop.string(),orientation:s.prop.oneOf([`horizontal`,`vertical`],`vertical`),required:s.prop.bool(!1),value:{...s.prop.string(),reflect:!1}},setup(e){let a=(0,s.getHost)(),o=(0,s.useEmit)(),l=(0,s.useSlots)(),u=s.watchEffect,d=t.createChoiceField({disabled:e.disabled,error:e.error,helper:e.helper,label:e.label,prefix:`radio-group`,required:e.required,signal:r.lifecycleSignal(s.onCleanup),value:e.value});d.attachFormField((0,s.useField)({disabled:d.disabled,onReset:d.reset,toFormValue:e=>e,validationMessage:d.validationMessage,validity:d.validity,value:d.formValue}));let f=d.selectedValue,p=e.disabled;(0,s.bind)({attr:{size:e.size,value:()=>f.value||null}});let m=()=>n.getLightChildrenByTag(a,`ore-radio`),h=()=>p.value?[]:m().filter(e=>!e.hasAttribute(`disabled`)),g=e=>n.getChoiceLabel(m(),e),_=(e,t)=>{d.setValues(e?[e]:[]);let n=e?[g(e)]:[];o(`change`,{labels:n,originalEvent:t,values:e?[e]:[]}),d.triggerValidation(`blur`)};(0,s.provide)(c,{color:e.color,disabled:p,name:e.name,select:_,size:e.size,value:f}),u(()=>{l.elements().value,f.value;let t=m();for(let n of t){let t=n.getAttribute(`value`)??``;n.toggleAttribute(`checked`,t===f.value),e.name.value?n.setAttribute(`name`,e.name.value):n.removeAttribute(`name`),e.color.value?n.setAttribute(`color`,e.color.value):n.removeAttribute(`color`),e.size.value?n.setAttribute(`size`,e.size.value):n.removeAttribute(`size`),n.toggleAttribute(`disabled`,p.value)}}),u(()=>{l.elements().value;let e=m(),t=!1;for(let n of e){let e=n.getAttribute(`value`)===f.value;n.setAttribute(`tabindex`,e&&!p.value?`0`:`-1`),e&&!p.value&&(t=!0)}if(!t&&e.length>0){let t=e.find(e=>!e.hasAttribute(`disabled`));t&&t.setAttribute(`tabindex`,`0`)}});let v=i.createListControl({getItems:h,keys:{next:[`ArrowDown`,`ArrowRight`],prev:[`ArrowUp`,`ArrowLeft`]},loop:!0,onNavigate:({event:e,item:t})=>{t.focus(),t.tagName===`ORE-RADIO`&&_(t.getAttribute(`value`)??``,e)},signal:r.lifecycleSignal(s.onCleanup)});(0,s.bind)({on:{change:e=>{e.target!==a&&(e.stopPropagation(),_(e.target.getAttribute(`value`)??``,e))},keydown:e=>{let t=h();if(!t.length)return;let n=t.indexOf(document.activeElement);n!==-1&&(v.set(n),v.handleKeydown(e))}}});let y=(0,s.createStableId)(`radio-group-legend`);return s.html`
|
|
2
2
|
<fieldset
|
|
3
3
|
role="radiogroup"
|
|
4
4
|
aria-required="${()=>String(!!e.required.value)}"
|
package/dist/radio-group.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-group.cjs","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n bind,\n createContext,\n createStableId,\n define,\n getHost,\n html,\n onCleanup,\n prop,\n provide,\n useEmit,\n useField,\n useSlots,\n watchEffect,\n when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n type ChoiceChangeDetail,\n createChoiceField,\n createListControl,\n getChoiceLabel,\n getLightChildrenByTag,\n lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n /** Theme color tint */\n color?: ThemeColor;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /** Helper text displayed below the items */\n helper?: string;\n /** Group label text */\n label?: string;\n /** Form field name */\n name?: string;\n /** Layout orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Required field */\n required?: boolean;\n /** Items size preset */\n size?: ComponentSize;\n /** Initial selected value */\n value?: string;\n};\n\nexport type RadioGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n name: Readable<string | undefined>;\n select: (value: string, originalEvent?: Event) => void;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n /** Emitted when the selection changes */\n change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n * <ore-radio value=\"free\">Free</ore-radio>\n * <ore-radio value=\"pro\">Pro</ore-radio>\n * <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n * <ore-radio value=\"left\">Left</ore-radio>\n * <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n // `prop.string()`'s own default reflection also write the raw incoming value would leave\n // two effects racing to set the same attribute from different sources.\n value: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreRadioGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const choice = createChoiceField({\n disabled: props.disabled,\n error: props.error,\n helper: props.helper,\n label: props.label,\n prefix: 'radio-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const selectedValue = choice.selectedValue;\n const isDisabled = props.disabled;\n\n bind({\n attr: {\n size: props.size,\n value: () => selectedValue.value || null,\n },\n });\n\n const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n const getEnabledRadios = (): HTMLElement[] =>\n isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n const selectRadio = (val: string, originalEvent?: Event): void => {\n choice.setValues(val ? [val] : []);\n\n const labels = val ? [getLabelForValue(val)] : [];\n const values = val ? [val] : [];\n\n emit('change', { labels, originalEvent, values });\n choice.triggerValidation('blur');\n };\n\n provide(RADIO_GROUP_CTX, {\n color: props.color as Readable<ThemeColor | undefined>,\n disabled: isDisabled,\n name: props.name,\n select: selectRadio,\n size: props.size as Readable<ComponentSize | undefined>,\n value: selectedValue,\n });\n\n // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n watch(() => {\n void slots.elements().value;\n void selectedValue.value;\n\n const radios = getSlottedRadios();\n\n for (const radio of radios) {\n const val = radio.getAttribute('value') ?? '';\n\n radio.toggleAttribute('checked', val === selectedValue.value);\n\n if (props.name.value) radio.setAttribute('name', props.name.value);\n else radio.removeAttribute('name');\n\n if (props.color.value) radio.setAttribute('color', props.color.value);\n else radio.removeAttribute('color');\n\n if (props.size.value) radio.setAttribute('size', props.size.value);\n else radio.removeAttribute('size');\n\n radio.toggleAttribute('disabled', isDisabled.value);\n }\n });\n\n // Roving tabindex: only the selected (or first) radio is tabbable.\n watch(() => {\n void slots.elements().value;\n\n const radios = getSlottedRadios();\n let hasFocusable = false;\n\n for (const radio of radios) {\n const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n if (isSelected && !isDisabled.value) hasFocusable = true;\n }\n\n if (!hasFocusable && radios.length > 0) {\n const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n if (first) first.setAttribute('tabindex', '0');\n }\n });\n\n const listControl = createListControl<HTMLElement>({\n getItems: getEnabledRadios,\n keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n loop: true,\n onNavigate: (_action, index, event) => {\n const radio = getEnabledRadios()[index];\n\n if (!radio) return;\n\n radio.focus();\n\n if (radio.tagName === 'ORE-RADIO') {\n selectRadio(radio.getAttribute('value') ?? '', event);\n }\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n bind({\n on: {\n change: (e: Event) => {\n if (e.target === el) return;\n\n e.stopPropagation();\n selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n },\n keydown: (e: KeyboardEvent) => {\n const radios = getEnabledRadios();\n\n if (!radios.length) return;\n\n const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n if (focused === -1) return;\n\n listControl.set(focused);\n listControl.handleKeydown(e);\n },\n },\n });\n\n const legendId = createStableId('radio-group-legend');\n\n return html`\n <fieldset\n role=\"radiogroup\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${choice.ariaInvalid}\"\n aria-errormessage=\"${choice.ariaErrorMessage}\"\n aria-describedby=\"${choice.ariaDescribedBy}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`\n <span aria-hidden=\"true\">*</span>\n `,\n )}\n </legend>\n <div class=\"radio-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div\n class=\"helper-text\"\n part=\"helper\"\n id=\"${choice.assistiveId}\"\n role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n aria-live=\"polite\"\n ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n ${() => choice.errorText.value || choice.helperText.value}\n </div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"radio-group.cjs","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n bind,\n createContext,\n createStableId,\n define,\n getHost,\n html,\n onCleanup,\n prop,\n provide,\n useEmit,\n useField,\n useSlots,\n watchEffect,\n when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n type ChoiceChangeDetail,\n createChoiceField,\n createListControl,\n getChoiceLabel,\n getLightChildrenByTag,\n lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n /** Theme color tint */\n color?: ThemeColor;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /** Helper text displayed below the items */\n helper?: string;\n /** Group label text */\n label?: string;\n /** Form field name */\n name?: string;\n /** Layout orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Required field */\n required?: boolean;\n /** Items size preset */\n size?: ComponentSize;\n /** Initial selected value */\n value?: string;\n};\n\nexport type RadioGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n name: Readable<string | undefined>;\n select: (value: string, originalEvent?: Event) => void;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n /** Emitted when the selection changes */\n change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n * <ore-radio value=\"free\">Free</ore-radio>\n * <ore-radio value=\"pro\">Pro</ore-radio>\n * <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n * <ore-radio value=\"left\">Left</ore-radio>\n * <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n // `prop.string()`'s own default reflection also write the raw incoming value would leave\n // two effects racing to set the same attribute from different sources.\n value: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreRadioGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const choice = createChoiceField({\n disabled: props.disabled,\n error: props.error,\n helper: props.helper,\n label: props.label,\n prefix: 'radio-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const selectedValue = choice.selectedValue;\n const isDisabled = props.disabled;\n\n bind({\n attr: {\n size: props.size,\n value: () => selectedValue.value || null,\n },\n });\n\n const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n const getEnabledRadios = (): HTMLElement[] =>\n isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n const selectRadio = (val: string, originalEvent?: Event): void => {\n choice.setValues(val ? [val] : []);\n\n const labels = val ? [getLabelForValue(val)] : [];\n const values = val ? [val] : [];\n\n emit('change', { labels, originalEvent, values });\n choice.triggerValidation('blur');\n };\n\n provide(RADIO_GROUP_CTX, {\n color: props.color as Readable<ThemeColor | undefined>,\n disabled: isDisabled,\n name: props.name,\n select: selectRadio,\n size: props.size as Readable<ComponentSize | undefined>,\n value: selectedValue,\n });\n\n // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n watch(() => {\n void slots.elements().value;\n void selectedValue.value;\n\n const radios = getSlottedRadios();\n\n for (const radio of radios) {\n const val = radio.getAttribute('value') ?? '';\n\n radio.toggleAttribute('checked', val === selectedValue.value);\n\n if (props.name.value) radio.setAttribute('name', props.name.value);\n else radio.removeAttribute('name');\n\n if (props.color.value) radio.setAttribute('color', props.color.value);\n else radio.removeAttribute('color');\n\n if (props.size.value) radio.setAttribute('size', props.size.value);\n else radio.removeAttribute('size');\n\n radio.toggleAttribute('disabled', isDisabled.value);\n }\n });\n\n // Roving tabindex: only the selected (or first) radio is tabbable.\n watch(() => {\n void slots.elements().value;\n\n const radios = getSlottedRadios();\n let hasFocusable = false;\n\n for (const radio of radios) {\n const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n if (isSelected && !isDisabled.value) hasFocusable = true;\n }\n\n if (!hasFocusable && radios.length > 0) {\n const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n if (first) first.setAttribute('tabindex', '0');\n }\n });\n\n const listControl = createListControl<HTMLElement>({\n getItems: getEnabledRadios,\n keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n\n if (item.tagName === 'ORE-RADIO') {\n selectRadio(item.getAttribute('value') ?? '', event);\n }\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n bind({\n on: {\n change: (e: Event) => {\n if (e.target === el) return;\n\n e.stopPropagation();\n selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n },\n keydown: (e: KeyboardEvent) => {\n const radios = getEnabledRadios();\n\n if (!radios.length) return;\n\n const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n if (focused === -1) return;\n\n listControl.set(focused);\n listControl.handleKeydown(e);\n },\n },\n });\n\n const legendId = createStableId('radio-group-legend');\n\n return html`\n <fieldset\n role=\"radiogroup\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${choice.ariaInvalid}\"\n aria-errormessage=\"${choice.ariaErrorMessage}\"\n aria-describedby=\"${choice.ariaDescribedBy}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`\n <span aria-hidden=\"true\">*</span>\n `,\n )}\n </legend>\n <div class=\"radio-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div\n class=\"helper-text\"\n part=\"helper\"\n id=\"${choice.assistiveId}\"\n role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n aria-live=\"polite\"\n ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n ${() => choice.errorText.value || choice.helperText.value}\n </div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
|
package/dist/radio-group.js
CHANGED
|
@@ -91,9 +91,8 @@ p(T, {
|
|
|
91
91
|
prev: ["ArrowUp", "ArrowLeft"]
|
|
92
92
|
},
|
|
93
93
|
loop: !0,
|
|
94
|
-
onNavigate: (e, t
|
|
95
|
-
|
|
96
|
-
r && (r.focus(), r.tagName === "ORE-RADIO" && O(r.getAttribute("value") ?? "", n));
|
|
94
|
+
onNavigate: ({ event: e, item: t }) => {
|
|
95
|
+
t.focus(), t.tagName === "ORE-RADIO" && O(t.getAttribute("value") ?? "", e);
|
|
97
96
|
},
|
|
98
97
|
signal: i(g)
|
|
99
98
|
});
|
package/dist/radio-group.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-group.js","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n bind,\n createContext,\n createStableId,\n define,\n getHost,\n html,\n onCleanup,\n prop,\n provide,\n useEmit,\n useField,\n useSlots,\n watchEffect,\n when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n type ChoiceChangeDetail,\n createChoiceField,\n createListControl,\n getChoiceLabel,\n getLightChildrenByTag,\n lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n /** Theme color tint */\n color?: ThemeColor;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /** Helper text displayed below the items */\n helper?: string;\n /** Group label text */\n label?: string;\n /** Form field name */\n name?: string;\n /** Layout orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Required field */\n required?: boolean;\n /** Items size preset */\n size?: ComponentSize;\n /** Initial selected value */\n value?: string;\n};\n\nexport type RadioGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n name: Readable<string | undefined>;\n select: (value: string, originalEvent?: Event) => void;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n /** Emitted when the selection changes */\n change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n * <ore-radio value=\"free\">Free</ore-radio>\n * <ore-radio value=\"pro\">Pro</ore-radio>\n * <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n * <ore-radio value=\"left\">Left</ore-radio>\n * <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n // `prop.string()`'s own default reflection also write the raw incoming value would leave\n // two effects racing to set the same attribute from different sources.\n value: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreRadioGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const choice = createChoiceField({\n disabled: props.disabled,\n error: props.error,\n helper: props.helper,\n label: props.label,\n prefix: 'radio-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const selectedValue = choice.selectedValue;\n const isDisabled = props.disabled;\n\n bind({\n attr: {\n size: props.size,\n value: () => selectedValue.value || null,\n },\n });\n\n const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n const getEnabledRadios = (): HTMLElement[] =>\n isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n const selectRadio = (val: string, originalEvent?: Event): void => {\n choice.setValues(val ? [val] : []);\n\n const labels = val ? [getLabelForValue(val)] : [];\n const values = val ? [val] : [];\n\n emit('change', { labels, originalEvent, values });\n choice.triggerValidation('blur');\n };\n\n provide(RADIO_GROUP_CTX, {\n color: props.color as Readable<ThemeColor | undefined>,\n disabled: isDisabled,\n name: props.name,\n select: selectRadio,\n size: props.size as Readable<ComponentSize | undefined>,\n value: selectedValue,\n });\n\n // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n watch(() => {\n void slots.elements().value;\n void selectedValue.value;\n\n const radios = getSlottedRadios();\n\n for (const radio of radios) {\n const val = radio.getAttribute('value') ?? '';\n\n radio.toggleAttribute('checked', val === selectedValue.value);\n\n if (props.name.value) radio.setAttribute('name', props.name.value);\n else radio.removeAttribute('name');\n\n if (props.color.value) radio.setAttribute('color', props.color.value);\n else radio.removeAttribute('color');\n\n if (props.size.value) radio.setAttribute('size', props.size.value);\n else radio.removeAttribute('size');\n\n radio.toggleAttribute('disabled', isDisabled.value);\n }\n });\n\n // Roving tabindex: only the selected (or first) radio is tabbable.\n watch(() => {\n void slots.elements().value;\n\n const radios = getSlottedRadios();\n let hasFocusable = false;\n\n for (const radio of radios) {\n const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n if (isSelected && !isDisabled.value) hasFocusable = true;\n }\n\n if (!hasFocusable && radios.length > 0) {\n const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n if (first) first.setAttribute('tabindex', '0');\n }\n });\n\n const listControl = createListControl<HTMLElement>({\n getItems: getEnabledRadios,\n keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n loop: true,\n onNavigate: (_action, index, event) => {\n const radio = getEnabledRadios()[index];\n\n if (!radio) return;\n\n radio.focus();\n\n if (radio.tagName === 'ORE-RADIO') {\n selectRadio(radio.getAttribute('value') ?? '', event);\n }\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n bind({\n on: {\n change: (e: Event) => {\n if (e.target === el) return;\n\n e.stopPropagation();\n selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n },\n keydown: (e: KeyboardEvent) => {\n const radios = getEnabledRadios();\n\n if (!radios.length) return;\n\n const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n if (focused === -1) return;\n\n listControl.set(focused);\n listControl.handleKeydown(e);\n },\n },\n });\n\n const legendId = createStableId('radio-group-legend');\n\n return html`\n <fieldset\n role=\"radiogroup\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${choice.ariaInvalid}\"\n aria-errormessage=\"${choice.ariaErrorMessage}\"\n aria-describedby=\"${choice.ariaDescribedBy}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`\n <span aria-hidden=\"true\">*</span>\n `,\n )}\n </legend>\n <div class=\"radio-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div\n class=\"helper-text\"\n part=\"helper\"\n id=\"${choice.assistiveId}\"\n role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n aria-live=\"polite\"\n ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n ${() => choice.errorText.value || choice.helperText.value}\n </div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"radio-group.js","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n bind,\n createContext,\n createStableId,\n define,\n getHost,\n html,\n onCleanup,\n prop,\n provide,\n useEmit,\n useField,\n useSlots,\n watchEffect,\n when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n type ChoiceChangeDetail,\n createChoiceField,\n createListControl,\n getChoiceLabel,\n getLightChildrenByTag,\n lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n /** Theme color tint */\n color?: ThemeColor;\n /** Disabled state */\n disabled?: boolean;\n /** Error message text */\n error?: string;\n /** Helper text displayed below the items */\n helper?: string;\n /** Group label text */\n label?: string;\n /** Form field name */\n name?: string;\n /** Layout orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Required field */\n required?: boolean;\n /** Items size preset */\n size?: ComponentSize;\n /** Initial selected value */\n value?: string;\n};\n\nexport type RadioGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n name: Readable<string | undefined>;\n select: (value: string, originalEvent?: Event) => void;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n /** Emitted when the selection changes */\n change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n * <ore-radio value=\"free\">Free</ore-radio>\n * <ore-radio value=\"pro\">Pro</ore-radio>\n * <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n * <ore-radio value=\"left\">Left</ore-radio>\n * <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n // `prop.string()`'s own default reflection also write the raw incoming value would leave\n // two effects racing to set the same attribute from different sources.\n value: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreRadioGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const choice = createChoiceField({\n disabled: props.disabled,\n error: props.error,\n helper: props.helper,\n label: props.label,\n prefix: 'radio-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const selectedValue = choice.selectedValue;\n const isDisabled = props.disabled;\n\n bind({\n attr: {\n size: props.size,\n value: () => selectedValue.value || null,\n },\n });\n\n const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n const getEnabledRadios = (): HTMLElement[] =>\n isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n const selectRadio = (val: string, originalEvent?: Event): void => {\n choice.setValues(val ? [val] : []);\n\n const labels = val ? [getLabelForValue(val)] : [];\n const values = val ? [val] : [];\n\n emit('change', { labels, originalEvent, values });\n choice.triggerValidation('blur');\n };\n\n provide(RADIO_GROUP_CTX, {\n color: props.color as Readable<ThemeColor | undefined>,\n disabled: isDisabled,\n name: props.name,\n select: selectRadio,\n size: props.size as Readable<ComponentSize | undefined>,\n value: selectedValue,\n });\n\n // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n watch(() => {\n void slots.elements().value;\n void selectedValue.value;\n\n const radios = getSlottedRadios();\n\n for (const radio of radios) {\n const val = radio.getAttribute('value') ?? '';\n\n radio.toggleAttribute('checked', val === selectedValue.value);\n\n if (props.name.value) radio.setAttribute('name', props.name.value);\n else radio.removeAttribute('name');\n\n if (props.color.value) radio.setAttribute('color', props.color.value);\n else radio.removeAttribute('color');\n\n if (props.size.value) radio.setAttribute('size', props.size.value);\n else radio.removeAttribute('size');\n\n radio.toggleAttribute('disabled', isDisabled.value);\n }\n });\n\n // Roving tabindex: only the selected (or first) radio is tabbable.\n watch(() => {\n void slots.elements().value;\n\n const radios = getSlottedRadios();\n let hasFocusable = false;\n\n for (const radio of radios) {\n const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n if (isSelected && !isDisabled.value) hasFocusable = true;\n }\n\n if (!hasFocusable && radios.length > 0) {\n const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n if (first) first.setAttribute('tabindex', '0');\n }\n });\n\n const listControl = createListControl<HTMLElement>({\n getItems: getEnabledRadios,\n keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n\n if (item.tagName === 'ORE-RADIO') {\n selectRadio(item.getAttribute('value') ?? '', event);\n }\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n bind({\n on: {\n change: (e: Event) => {\n if (e.target === el) return;\n\n e.stopPropagation();\n selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n },\n keydown: (e: KeyboardEvent) => {\n const radios = getEnabledRadios();\n\n if (!radios.length) return;\n\n const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n if (focused === -1) return;\n\n listControl.set(focused);\n listControl.handleKeydown(e);\n },\n },\n });\n\n const legendId = createStableId('radio-group-legend');\n\n return html`\n <fieldset\n role=\"radiogroup\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${choice.ariaInvalid}\"\n aria-errormessage=\"${choice.ariaErrorMessage}\"\n aria-describedby=\"${choice.ariaDescribedBy}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`\n <span aria-hidden=\"true\">*</span>\n `,\n )}\n </legend>\n <div class=\"radio-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div\n class=\"helper-text\"\n part=\"helper\"\n id=\"${choice.assistiveId}\"\n role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n aria-live=\"polite\"\n ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n ${() => choice.errorText.value || choice.helperText.value}\n </div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
|
package/dist/radio.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/shape.css.cjs"),n=require("./styles/mixins/states.css.cjs"),r=require("./styles/mixins/theme.css.cjs"),i=require("./core/checkable.cjs"),a=require("./core/ore.cjs"),o=require("./core/direction.cjs"),s=require("./core/nav.cjs"),c=require("./shared/prop-bundles.cjs"),l=require("./shared/size-presets.cjs"),u=require("./inputs/shared/templates.cjs"),d=require("./inputs/shared/native-field-event.cjs"),f=require("./radio-group.cjs"),p=require("./inputs/radio/radio.css.cjs");let m=require("@vielzeug/ore"),h=require("@vielzeug/ripple");var g=`ore-radio`;(0,m.define)(g,{formAssociated:!0,props:{...c.themableBundle,...c.sizableBundle,...c.disablableBundle,checked:m.prop.bool(!1),error:m.prop.string(),helper:m.prop.string(),name:m.prop.string(),value:m.prop.string()},setup(e){let t=(0,m.getHost)(),n=(0,m.inject)(f.RADIO_GROUP_CTX),r=(0,h.computed)(()=>n?.name.value||e.name.value||``),c=(0,h.computed)(()=>n?.size.value??e.size.value),l=(0,h.computed)(()=>n?.color.value??e.color.value),p=(0,h.computed)(()=>n?n.value.value===e.value.value:!!e.checked.value),g=()=>{let e=r.value;return e?Array.from(document.querySelectorAll(`ore-radio[name="${e}"]`)).filter(e=>!e.hasAttribute(`disabled`)):[]},_=(e,t)=>{if(n){n.select(e.getAttribute(`value`)??``,t);return}e.click()},v=s.createListControl({direction:()=>o.elementDirection(t),getItems:()=>g(),loop:!0,onNavigate:(e,t
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/shape.css.cjs"),n=require("./styles/mixins/states.css.cjs"),r=require("./styles/mixins/theme.css.cjs"),i=require("./core/checkable.cjs"),a=require("./core/ore.cjs"),o=require("./core/direction.cjs"),s=require("./core/nav.cjs"),c=require("./shared/prop-bundles.cjs"),l=require("./shared/size-presets.cjs"),u=require("./inputs/shared/templates.cjs"),d=require("./inputs/shared/native-field-event.cjs"),f=require("./radio-group.cjs"),p=require("./inputs/radio/radio.css.cjs");let m=require("@vielzeug/ore"),h=require("@vielzeug/ripple");var g=`ore-radio`;(0,m.define)(g,{formAssociated:!0,props:{...c.themableBundle,...c.sizableBundle,...c.disablableBundle,checked:m.prop.bool(!1),error:m.prop.string(),helper:m.prop.string(),name:m.prop.string(),value:m.prop.string()},setup(e){let t=(0,m.getHost)(),n=(0,m.inject)(f.RADIO_GROUP_CTX),r=(0,h.computed)(()=>n?.name.value||e.name.value||``),c=(0,h.computed)(()=>n?.size.value??e.size.value),l=(0,h.computed)(()=>n?.color.value??e.color.value),p=(0,h.computed)(()=>n?n.value.value===e.value.value:!!e.checked.value),g=()=>{let e=r.value;return e?Array.from(document.querySelectorAll(`ore-radio[name="${e}"]`)).filter(e=>!e.hasAttribute(`disabled`)):[]},_=(e,t)=>{if(n){n.select(e.getAttribute(`value`)??``,t);return}e.click()},v=s.createListControl({direction:()=>o.elementDirection(t),getItems:()=>g(),loop:!0,onNavigate:({event:e,item:t})=>{t.focus(),_(t,e)},orientation:`both`,signal:a.lifecycleSignal(m.onCleanup)}),y=t=>{if(!b.checked.value){if(n){n.select(e.value.value??``,t);return}b.toggle(t??new Event(`change`))}},b=i.createCheckable({checked:p,disabled:(0,h.computed)(()=>e.disabled.value||!!n?.disabled.value),error:e.error,helper:e.helper,onToggle:e=>{d.setFieldChecked(t,e.checked),d.dispatchNativeFieldEvent(t,`input`),d.dispatchNativeFieldEvent(t,`change`)},prefix:`radio`,signal:a.lifecycleSignal(m.onCleanup),value:e.value});b.attachFormField((0,m.useField)({disabled:b.disabled,toFormValue:e=>e,value:b.checkableFormValue}));let{assistiveId:x,checked:S,disabled:C,errorText:w,helperText:T,labelId:E,toggle:D}=b;return d.defineFieldChecked(t,()=>S.value,e=>{S.value=e}),(0,m.bind)({attr:{ariaChecked:()=>S.value?`true`:`false`,ariaDescribedby:()=>w.value||T.value?x:null,ariaDisabled:()=>C.value?`true`:null,ariaInvalid:()=>w.value?`true`:null,ariaLabelledby:E,checked:S,color:l,disabled:()=>C.value?!0:void 0,name:()=>r.value||void 0,role:`radio`,size:c,tabindex:()=>{if(!C.value)return S.value?0:-1}},class:()=>({"is-checked":S.value,"is-disabled":C.value}),on:{click:i=>{if(!C.value){if(n)n.select(e.value.value??``,i);else{if(!r.value)return;if(!S.value){let n=e.name.value;document.querySelectorAll(`ore-radio[name="${n}"]`).forEach(e=>{e!==t&&e.removeAttribute(`checked`)}),D(i)}}}},keydown:e=>{if(e.key===` `||e.key===`Enter`){e.preventDefault(),y(e);return}let n=g();if(n.length===0)return;let r=n.indexOf(t);r!==-1&&(v.set(r),v.handleKeydown(e))}}}),m.html`
|
|
2
2
|
<div class="radio-wrapper" part="radio">
|
|
3
3
|
<div class="circle" part="circle">
|
|
4
4
|
<div class="dot" part="dot"></div>
|
package/dist/radio.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio.cjs","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledStateMixin,\n forcedColorsFormControlMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message (marks field as invalid) */\n error?: string;\n /** Helper text displayed below the radio */\n helper?: string;\n /** Component size */\n size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n checked: prop.bool(false),\n error: prop.string(),\n helper: prop.string(),\n name: prop.string(),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const groupCtx = inject(RADIO_GROUP_CTX);\n const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const checkedFromState = computed(() => {\n if (groupCtx) return groupCtx.value.value === props.value.value;\n\n return Boolean(props.checked.value);\n });\n\n const getRadioGroup = (): HTMLElement[] => {\n const radioName = effectiveName.value;\n\n if (!radioName) return [];\n\n return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n (r) => !r.hasAttribute('disabled'),\n );\n };\n\n const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n if (groupCtx) {\n groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n return;\n }\n\n radio.click();\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getRadioGroup(),\n // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n // may render horizontally or vertically via CSS) — RTL mirroring of Left/Right still\n // applies via `direction` since nav.ts's default keymap covers 'both' too.\n loop: true,\n onNavigate: (_action, index, event) => {\n const nextRadio = getRadioGroup()[index];\n\n if (!nextRadio) return;\n\n nextRadio.focus();\n selectRadio(nextRadio, event);\n },\n orientation: 'both',\n signal: lifecycleSignal(onCleanup),\n });\n\n const activateSelf = (originalEvent?: Event): void => {\n if (checkable.checked.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', originalEvent);\n\n return;\n }\n\n checkable.toggle(originalEvent ?? new Event('change'));\n };\n\n const checkable = createCheckable({\n checked: checkedFromState,\n disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n error: props.error,\n helper: props.helper,\n onToggle: (payload) => {\n setFieldChecked(el, payload.checked);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n },\n prefix: 'radio',\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n checkable.attachFormField(\n useField<string | null>({\n disabled: checkable.disabled,\n toFormValue: (v) => v,\n value: checkable.checkableFormValue,\n }),\n );\n\n const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n defineFieldChecked(\n el,\n () => checked.value,\n (value) => {\n checked.value = value;\n },\n );\n\n bind({\n attr: {\n ariaChecked: () => (checked.value ? 'true' : 'false'),\n ariaDescribedby: () => (errorText.value || helperText.value ? assistiveId : null),\n ariaDisabled: () => (disabled.value ? 'true' : null),\n ariaInvalid: () => (errorText.value ? 'true' : null),\n ariaLabelledby: labelId,\n checked,\n color: effectiveColor,\n disabled: () => (disabled.value ? true : undefined),\n name: () => effectiveName.value || undefined,\n role: 'radio',\n size: effectiveSize,\n tabindex: () => {\n if (disabled.value) return undefined;\n\n return checked.value ? 0 : -1;\n },\n },\n class: () => ({\n 'is-checked': checked.value,\n 'is-disabled': disabled.value,\n }),\n on: {\n click: (e: MouseEvent) => {\n if (disabled.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', e);\n } else {\n if (!effectiveName.value) return;\n\n if (!checked.value) {\n const radioName = props.name.value;\n const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n allRadios.forEach((radio) => {\n if (radio !== el) radio.removeAttribute('checked');\n });\n\n toggle(e);\n }\n }\n },\n keydown: (e: KeyboardEvent) => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n activateSelf(e);\n\n return;\n }\n\n const radios = getRadioGroup();\n\n if (radios.length === 0) return;\n\n const selfIndex = radios.indexOf(el);\n\n if (selfIndex === -1) return;\n\n listControl.set(selfIndex);\n listControl.handleKeydown(e);\n },\n },\n });\n\n return html`\n <div class=\"radio-wrapper\" part=\"radio\">\n <div class=\"circle\" part=\"circle\">\n <div class=\"dot\" part=\"dot\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n colorThemeMixin,\n forcedColorsFormControlMixin,\n disabledStateMixin,\n coarsePointerMixin,\n sizeVariantMixin(CONTROL_SIZE_PRESET),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"radio.cjs","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledStateMixin,\n forcedColorsFormControlMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message (marks field as invalid) */\n error?: string;\n /** Helper text displayed below the radio */\n helper?: string;\n /** Component size */\n size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n checked: prop.bool(false),\n error: prop.string(),\n helper: prop.string(),\n name: prop.string(),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const groupCtx = inject(RADIO_GROUP_CTX);\n const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const checkedFromState = computed(() => {\n if (groupCtx) return groupCtx.value.value === props.value.value;\n\n return Boolean(props.checked.value);\n });\n\n const getRadioGroup = (): HTMLElement[] => {\n const radioName = effectiveName.value;\n\n if (!radioName) return [];\n\n return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n (r) => !r.hasAttribute('disabled'),\n );\n };\n\n const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n if (groupCtx) {\n groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n return;\n }\n\n radio.click();\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getRadioGroup(),\n // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n // may render horizontally or vertically via CSS) — RTL mirroring of Left/Right still\n // applies via `direction` since nav.ts's default keymap covers 'both' too.\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n selectRadio(item, event);\n },\n orientation: 'both',\n signal: lifecycleSignal(onCleanup),\n });\n\n const activateSelf = (originalEvent?: Event): void => {\n if (checkable.checked.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', originalEvent);\n\n return;\n }\n\n checkable.toggle(originalEvent ?? new Event('change'));\n };\n\n const checkable = createCheckable({\n checked: checkedFromState,\n disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n error: props.error,\n helper: props.helper,\n onToggle: (payload) => {\n setFieldChecked(el, payload.checked);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n },\n prefix: 'radio',\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n checkable.attachFormField(\n useField<string | null>({\n disabled: checkable.disabled,\n toFormValue: (v) => v,\n value: checkable.checkableFormValue,\n }),\n );\n\n const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n defineFieldChecked(\n el,\n () => checked.value,\n (value) => {\n checked.value = value;\n },\n );\n\n bind({\n attr: {\n ariaChecked: () => (checked.value ? 'true' : 'false'),\n ariaDescribedby: () => (errorText.value || helperText.value ? assistiveId : null),\n ariaDisabled: () => (disabled.value ? 'true' : null),\n ariaInvalid: () => (errorText.value ? 'true' : null),\n ariaLabelledby: labelId,\n checked,\n color: effectiveColor,\n disabled: () => (disabled.value ? true : undefined),\n name: () => effectiveName.value || undefined,\n role: 'radio',\n size: effectiveSize,\n tabindex: () => {\n if (disabled.value) return undefined;\n\n return checked.value ? 0 : -1;\n },\n },\n class: () => ({\n 'is-checked': checked.value,\n 'is-disabled': disabled.value,\n }),\n on: {\n click: (e: MouseEvent) => {\n if (disabled.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', e);\n } else {\n if (!effectiveName.value) return;\n\n if (!checked.value) {\n const radioName = props.name.value;\n const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n allRadios.forEach((radio) => {\n if (radio !== el) radio.removeAttribute('checked');\n });\n\n toggle(e);\n }\n }\n },\n keydown: (e: KeyboardEvent) => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n activateSelf(e);\n\n return;\n }\n\n const radios = getRadioGroup();\n\n if (radios.length === 0) return;\n\n const selfIndex = radios.indexOf(el);\n\n if (selfIndex === -1) return;\n\n listControl.set(selfIndex);\n listControl.handleKeydown(e);\n },\n },\n });\n\n return html`\n <div class=\"radio-wrapper\" part=\"radio\">\n <div class=\"circle\" part=\"circle\">\n <div class=\"dot\" part=\"dot\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n colorThemeMixin,\n forcedColorsFormControlMixin,\n disabledStateMixin,\n coarsePointerMixin,\n sizeVariantMixin(CONTROL_SIZE_PRESET),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
|
package/dist/radio.js
CHANGED
|
@@ -42,9 +42,8 @@ b(O, {
|
|
|
42
42
|
direction: () => s(t),
|
|
43
43
|
getItems: () => d(),
|
|
44
44
|
loop: !0,
|
|
45
|
-
onNavigate: (e, t
|
|
46
|
-
|
|
47
|
-
r && (r.focus(), f(r, n));
|
|
45
|
+
onNavigate: ({ event: e, item: t }) => {
|
|
46
|
+
t.focus(), f(t, e);
|
|
48
47
|
},
|
|
49
48
|
orientation: "both",
|
|
50
49
|
signal: o(w)
|
|
@@ -99,14 +98,16 @@ b(O, {
|
|
|
99
98
|
}),
|
|
100
99
|
on: {
|
|
101
100
|
click: (i) => {
|
|
102
|
-
if (!A.value)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
if (!A.value) {
|
|
102
|
+
if (n) n.select(e.value.value ?? "", i);
|
|
103
|
+
else {
|
|
104
|
+
if (!r.value) return;
|
|
105
|
+
if (!k.value) {
|
|
106
|
+
let n = e.name.value;
|
|
107
|
+
document.querySelectorAll(`ore-radio[name="${n}"]`).forEach((e) => {
|
|
108
|
+
e !== t && e.removeAttribute("checked");
|
|
109
|
+
}), P(i);
|
|
110
|
+
}
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
},
|
package/dist/radio.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio.js","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledStateMixin,\n forcedColorsFormControlMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message (marks field as invalid) */\n error?: string;\n /** Helper text displayed below the radio */\n helper?: string;\n /** Component size */\n size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n checked: prop.bool(false),\n error: prop.string(),\n helper: prop.string(),\n name: prop.string(),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const groupCtx = inject(RADIO_GROUP_CTX);\n const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const checkedFromState = computed(() => {\n if (groupCtx) return groupCtx.value.value === props.value.value;\n\n return Boolean(props.checked.value);\n });\n\n const getRadioGroup = (): HTMLElement[] => {\n const radioName = effectiveName.value;\n\n if (!radioName) return [];\n\n return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n (r) => !r.hasAttribute('disabled'),\n );\n };\n\n const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n if (groupCtx) {\n groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n return;\n }\n\n radio.click();\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getRadioGroup(),\n // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n // may render horizontally or vertically via CSS) — RTL mirroring of Left/Right still\n // applies via `direction` since nav.ts's default keymap covers 'both' too.\n loop: true,\n onNavigate: (_action, index, event) => {\n const nextRadio = getRadioGroup()[index];\n\n if (!nextRadio) return;\n\n nextRadio.focus();\n selectRadio(nextRadio, event);\n },\n orientation: 'both',\n signal: lifecycleSignal(onCleanup),\n });\n\n const activateSelf = (originalEvent?: Event): void => {\n if (checkable.checked.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', originalEvent);\n\n return;\n }\n\n checkable.toggle(originalEvent ?? new Event('change'));\n };\n\n const checkable = createCheckable({\n checked: checkedFromState,\n disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n error: props.error,\n helper: props.helper,\n onToggle: (payload) => {\n setFieldChecked(el, payload.checked);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n },\n prefix: 'radio',\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n checkable.attachFormField(\n useField<string | null>({\n disabled: checkable.disabled,\n toFormValue: (v) => v,\n value: checkable.checkableFormValue,\n }),\n );\n\n const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n defineFieldChecked(\n el,\n () => checked.value,\n (value) => {\n checked.value = value;\n },\n );\n\n bind({\n attr: {\n ariaChecked: () => (checked.value ? 'true' : 'false'),\n ariaDescribedby: () => (errorText.value || helperText.value ? assistiveId : null),\n ariaDisabled: () => (disabled.value ? 'true' : null),\n ariaInvalid: () => (errorText.value ? 'true' : null),\n ariaLabelledby: labelId,\n checked,\n color: effectiveColor,\n disabled: () => (disabled.value ? true : undefined),\n name: () => effectiveName.value || undefined,\n role: 'radio',\n size: effectiveSize,\n tabindex: () => {\n if (disabled.value) return undefined;\n\n return checked.value ? 0 : -1;\n },\n },\n class: () => ({\n 'is-checked': checked.value,\n 'is-disabled': disabled.value,\n }),\n on: {\n click: (e: MouseEvent) => {\n if (disabled.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', e);\n } else {\n if (!effectiveName.value) return;\n\n if (!checked.value) {\n const radioName = props.name.value;\n const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n allRadios.forEach((radio) => {\n if (radio !== el) radio.removeAttribute('checked');\n });\n\n toggle(e);\n }\n }\n },\n keydown: (e: KeyboardEvent) => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n activateSelf(e);\n\n return;\n }\n\n const radios = getRadioGroup();\n\n if (radios.length === 0) return;\n\n const selfIndex = radios.indexOf(el);\n\n if (selfIndex === -1) return;\n\n listControl.set(selfIndex);\n listControl.handleKeydown(e);\n },\n },\n });\n\n return html`\n <div class=\"radio-wrapper\" part=\"radio\">\n <div class=\"circle\" part=\"circle\">\n <div class=\"dot\" part=\"dot\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n colorThemeMixin,\n forcedColorsFormControlMixin,\n disabledStateMixin,\n coarsePointerMixin,\n sizeVariantMixin(CONTROL_SIZE_PRESET),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"radio.js","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledStateMixin,\n forcedColorsFormControlMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n change: Event;\n input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message (marks field as invalid) */\n error?: string;\n /** Helper text displayed below the radio */\n helper?: string;\n /** Component size */\n size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n checked: prop.bool(false),\n error: prop.string(),\n helper: prop.string(),\n name: prop.string(),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n\n const groupCtx = inject(RADIO_GROUP_CTX);\n const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const checkedFromState = computed(() => {\n if (groupCtx) return groupCtx.value.value === props.value.value;\n\n return Boolean(props.checked.value);\n });\n\n const getRadioGroup = (): HTMLElement[] => {\n const radioName = effectiveName.value;\n\n if (!radioName) return [];\n\n return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n (r) => !r.hasAttribute('disabled'),\n );\n };\n\n const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n if (groupCtx) {\n groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n return;\n }\n\n radio.click();\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getRadioGroup(),\n // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n // may render horizontally or vertically via CSS) — RTL mirroring of Left/Right still\n // applies via `direction` since nav.ts's default keymap covers 'both' too.\n loop: true,\n onNavigate: ({ event, item }) => {\n item.focus();\n selectRadio(item, event);\n },\n orientation: 'both',\n signal: lifecycleSignal(onCleanup),\n });\n\n const activateSelf = (originalEvent?: Event): void => {\n if (checkable.checked.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', originalEvent);\n\n return;\n }\n\n checkable.toggle(originalEvent ?? new Event('change'));\n };\n\n const checkable = createCheckable({\n checked: checkedFromState,\n disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n error: props.error,\n helper: props.helper,\n onToggle: (payload) => {\n setFieldChecked(el, payload.checked);\n dispatchNativeFieldEvent(el, 'input');\n dispatchNativeFieldEvent(el, 'change');\n },\n prefix: 'radio',\n signal: lifecycleSignal(onCleanup),\n value: props.value,\n });\n\n checkable.attachFormField(\n useField<string | null>({\n disabled: checkable.disabled,\n toFormValue: (v) => v,\n value: checkable.checkableFormValue,\n }),\n );\n\n const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n defineFieldChecked(\n el,\n () => checked.value,\n (value) => {\n checked.value = value;\n },\n );\n\n bind({\n attr: {\n ariaChecked: () => (checked.value ? 'true' : 'false'),\n ariaDescribedby: () => (errorText.value || helperText.value ? assistiveId : null),\n ariaDisabled: () => (disabled.value ? 'true' : null),\n ariaInvalid: () => (errorText.value ? 'true' : null),\n ariaLabelledby: labelId,\n checked,\n color: effectiveColor,\n disabled: () => (disabled.value ? true : undefined),\n name: () => effectiveName.value || undefined,\n role: 'radio',\n size: effectiveSize,\n tabindex: () => {\n if (disabled.value) return undefined;\n\n return checked.value ? 0 : -1;\n },\n },\n class: () => ({\n 'is-checked': checked.value,\n 'is-disabled': disabled.value,\n }),\n on: {\n click: (e: MouseEvent) => {\n if (disabled.value) return;\n\n if (groupCtx) {\n groupCtx.select(props.value.value ?? '', e);\n } else {\n if (!effectiveName.value) return;\n\n if (!checked.value) {\n const radioName = props.name.value;\n const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n allRadios.forEach((radio) => {\n if (radio !== el) radio.removeAttribute('checked');\n });\n\n toggle(e);\n }\n }\n },\n keydown: (e: KeyboardEvent) => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n activateSelf(e);\n\n return;\n }\n\n const radios = getRadioGroup();\n\n if (radios.length === 0) return;\n\n const selfIndex = radios.indexOf(el);\n\n if (selfIndex === -1) return;\n\n listControl.set(selfIndex);\n listControl.handleKeydown(e);\n },\n },\n });\n\n return html`\n <div class=\"radio-wrapper\" part=\"radio\">\n <div class=\"circle\" part=\"circle\">\n <div class=\"dot\" part=\"dot\"></div>\n </div>\n </div>\n <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n ${renderHelperRegion(assistiveId, errorText, helperText)}\n `;\n },\n styles: [\n colorThemeMixin,\n forcedColorsFormControlMixin,\n disabledStateMixin,\n coarsePointerMixin,\n sizeVariantMixin(CONTROL_SIZE_PRESET),\n componentStyles,\n ],\n});\n"],"mappings":"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"}
|
package/dist/rating.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/shape.css.cjs"),n=require("./styles/mixins/theme.css.cjs"),r=require("./core/field-base.cjs"),i=require("./core/slider.cjs"),a=require("./shared/prop-bundles.cjs"),o=require("./inputs/shared/templates.cjs"),s=require("./inputs/shared/native-field-event.cjs"),c=require("./inputs/rating/rating.css.cjs");let l=require("@vielzeug/ore"),u=require("@vielzeug/ripple");var d=`ore-rating`;(0,l.define)(d,{formAssociated:!0,props:{...a.themableBundle,...a.sizableBundle,...a.disablableBundle,error:l.prop.string(),helper:l.prop.string(),label:l.prop.string(`Rating`),max:l.prop.number(5),name:l.prop.string(),readonly:l.prop.bool(!1),solid:l.prop.bool(!1),value:l.prop.number(0)},setup(e){let t=(0,l.getHost)(),n=(0,u.computed)(()=>{let t=Math.max(1,Number(e.max?.value)||5),n=Number(e.value?.value);return Math.min(t,Math.max(0,Number.isFinite(n)?n:0))}),a=(0,u.computed)(()=>!!e.disabled.value);s.defineFieldValue(t,()=>String(n.value),e=>{t.setAttribute(`value`,e)}),(0,l.useField)({disabled:a,value:(0,u.computed)(()=>String(n.value||0))});let c=(0,l.createStableId)(`helper`),d=r.createErrorHelperState({error:e.error,helper:e.helper}),f=(0,u.computed)(()=>d.value.errorText),p=(0,u.computed)(()=>d.value.helperText),m=(0,u.computed)(()=>f.value||p.value?c:null),h=(0,u.computed)(()=>!e.readonly?.value&&!a.value),g=(0,u.signal)(null),_=(0,u.computed)(()=>g.value??n.value),v=()=>[...t.shadowRoot?.querySelectorAll(`[data-star]`)??[]],y=i.createSliderControl({max:(0,u.computed)(()=>Number(e.max?.value)||5),min:(0,u.signal)(1),step:(0,u.signal)(1)});function b(e){let n=t.shadowRoot?.querySelector(`.sparkle-layer`),r=t.shadowRoot?.querySelector(`[data-star="${e}"]`);if(!n||!r)return;let i=r.offsetLeft+r.offsetWidth/2,a=r.offsetTop+r.offsetHeight/2;for(let e=0;e<10;e++){let t=document.createElement(`span`),r=
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/shape.css.cjs"),n=require("./styles/mixins/theme.css.cjs"),r=require("./core/field-base.cjs"),i=require("./core/slider.cjs"),a=require("./shared/prop-bundles.cjs"),o=require("./inputs/shared/templates.cjs"),s=require("./inputs/shared/native-field-event.cjs"),c=require("./inputs/rating/rating.css.cjs");let l=require("@vielzeug/ore"),u=require("@vielzeug/ripple");var d=`ore-rating`;(0,l.define)(d,{formAssociated:!0,props:{...a.themableBundle,...a.sizableBundle,...a.disablableBundle,error:l.prop.string(),helper:l.prop.string(),label:l.prop.string(`Rating`),max:l.prop.number(5),name:l.prop.string(),readonly:l.prop.bool(!1),solid:l.prop.bool(!1),value:l.prop.number(0)},setup(e){let t=(0,l.getHost)(),n=(0,u.computed)(()=>{let t=Math.max(1,Number(e.max?.value)||5),n=Number(e.value?.value);return Math.min(t,Math.max(0,Number.isFinite(n)?n:0))}),a=(0,u.computed)(()=>!!e.disabled.value);s.defineFieldValue(t,()=>String(n.value),e=>{t.setAttribute(`value`,e)}),(0,l.useField)({disabled:a,value:(0,u.computed)(()=>String(n.value||0))});let c=(0,l.createStableId)(`helper`),d=r.createErrorHelperState({error:e.error,helper:e.helper}),f=(0,u.computed)(()=>d.value.errorText),p=(0,u.computed)(()=>d.value.helperText),m=(0,u.computed)(()=>f.value||p.value?c:null),h=(0,u.computed)(()=>!e.readonly?.value&&!a.value),g=(0,u.signal)(null),_=(0,u.computed)(()=>g.value??n.value),v=()=>[...t.shadowRoot?.querySelectorAll(`[data-star]`)??[]],y=i.createSliderControl({max:(0,u.computed)(()=>Number(e.max?.value)||5),min:(0,u.signal)(1),step:(0,u.signal)(1)});function b(e){let n=t.shadowRoot?.querySelector(`.sparkle-layer`),r=t.shadowRoot?.querySelector(`[data-star="${e}"]`);if(!n||!r)return;let i=r.offsetLeft+r.offsetWidth/2,a=r.offsetTop+r.offsetHeight/2;for(let e=0;e<10;e++){let t=document.createElement(`span`),r=36*e+(Math.random()*30-15),o=18+Math.random()*20,s=3+Math.random()*4,c=380+Math.random()*220;t.className=`sparkle`,t.style.cssText=[`left:${i}px`,`top:${a}px`,`--_angle:${r}deg`,`--_dist:${o}px`,`width:${s}px`,`height:${s}px`,`--_dur:${c}ms`,`animation-delay:${Math.random()*60}ms`].join(`;`),n.appendChild(t),t.addEventListener(`animationend`,()=>t.remove(),{once:!0})}}function x(r,i){if(!h.value)return;let a=Math.max(1,Number(e.max?.value)||5),o=Math.min(a,Math.max(0,r));o!==n.value&&(s.setFieldValue(t,String(o)),s.dispatchNativeFieldEvent(t,`input`),s.dispatchNativeFieldEvent(t,`change`),b(o))}function S(e,t){let n=y.nextFromKey(e.key,t);n!=null&&(e.preventDefault(),x(n,e),v()[n-1]?.focus())}let C=(0,u.computed)(()=>{let t=Number(e.max?.value)||5;return Array.from({length:t},(e,t)=>t+1)});return(0,l.bind)({attr:{size:e.size}}),l.html`
|
|
2
2
|
<div
|
|
3
3
|
class="stars"
|
|
4
4
|
part="stars"
|