@vielzeug/refine 26.10.6 → 26.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/accordion-item.cjs +2 -2
- package/dist/accordion-item.cjs.map +1 -1
- package/dist/accordion-item.js +6 -6
- package/dist/accordion-item.js.map +1 -1
- package/dist/accordion.cjs +2 -2
- package/dist/accordion.cjs.map +1 -1
- package/dist/accordion.js +2 -2
- package/dist/accordion.js.map +1 -1
- package/dist/alert.cjs +2 -2
- package/dist/alert.cjs.map +1 -1
- package/dist/alert.js +2 -2
- package/dist/alert.js.map +1 -1
- package/dist/avatar.cjs +3 -3
- package/dist/avatar.cjs.map +1 -1
- package/dist/avatar.js +2 -2
- package/dist/avatar.js.map +1 -1
- package/dist/badge.cjs +2 -2
- package/dist/badge.cjs.map +1 -1
- package/dist/badge.js +2 -2
- package/dist/badge.js.map +1 -1
- package/dist/box.cjs +2 -2
- package/dist/box.cjs.map +1 -1
- package/dist/box.js +2 -2
- package/dist/box.js.map +1 -1
- package/dist/button-group.cjs +2 -2
- package/dist/button-group.cjs.map +1 -1
- package/dist/button-group.js +2 -2
- package/dist/button-group.js.map +1 -1
- package/dist/button.cjs +9 -9
- package/dist/button.cjs.map +1 -1
- package/dist/button.js +8 -8
- package/dist/button.js.map +1 -1
- package/dist/card.cjs +2 -2
- package/dist/card.cjs.map +1 -1
- package/dist/card.js +2 -2
- package/dist/card.js.map +1 -1
- package/dist/carousel.cjs +6 -6
- package/dist/carousel.cjs.map +1 -1
- package/dist/carousel.js +6 -6
- package/dist/carousel.js.map +1 -1
- package/dist/chat-message.cjs +4 -4
- package/dist/chat-message.cjs.map +1 -1
- package/dist/chat-message.js +4 -4
- package/dist/chat-message.js.map +1 -1
- package/dist/checkbox-group.cjs +1 -1
- package/dist/checkbox-group.cjs.map +1 -1
- package/dist/checkbox-group.js +1 -1
- package/dist/checkbox-group.js.map +1 -1
- package/dist/checkbox.cjs +1 -1
- package/dist/checkbox.cjs.map +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/checkbox.js.map +1 -1
- package/dist/chip.cjs +8 -8
- package/dist/chip.cjs.map +1 -1
- package/dist/chip.js +8 -8
- package/dist/chip.js.map +1 -1
- package/dist/combobox.cjs +8 -8
- package/dist/combobox.cjs.map +1 -1
- package/dist/combobox.js +11 -11
- package/dist/combobox.js.map +1 -1
- package/dist/content/avatar/avatar.d.ts +2 -1
- package/dist/content/avatar/avatar.d.ts.map +1 -1
- package/dist/content/card/card.d.ts +6 -3
- package/dist/content/card/card.d.ts.map +1 -1
- package/dist/content/carousel/carousel.d.ts +5 -3
- package/dist/content/carousel/carousel.d.ts.map +1 -1
- package/dist/content/chat-message/chat-message.d.ts +2 -1
- package/dist/content/chat-message/chat-message.d.ts.map +1 -1
- package/dist/content/copy-command/copy-command.d.ts +1 -1
- package/dist/content/copy-command/copy-command.d.ts.map +1 -1
- package/dist/content/keyboard-key/keyboard-key.d.ts +2 -1
- package/dist/content/keyboard-key/keyboard-key.d.ts.map +1 -1
- package/dist/content/list/list.d.ts +2 -1
- package/dist/content/list/list.d.ts.map +1 -1
- package/dist/content/marquee/marquee.d.ts +3 -2
- package/dist/content/marquee/marquee.d.ts.map +1 -1
- package/dist/content/pagination/pagination.d.ts +1 -1
- package/dist/content/pagination/pagination.d.ts.map +1 -1
- package/dist/content/qr-code/qr-code.d.ts +0 -41
- package/dist/content/qr-code/qr-code.d.ts.map +1 -1
- package/dist/content/separator/separator.d.ts +1 -1
- package/dist/content/separator/separator.d.ts.map +1 -1
- package/dist/content/stepper/step.d.ts +1 -1
- package/dist/content/stepper/step.d.ts.map +1 -1
- package/dist/content/table/table.d.ts +2 -1
- package/dist/content/table/table.d.ts.map +1 -1
- package/dist/content/text/text.d.ts +13 -6
- package/dist/content/text/text.d.ts.map +1 -1
- package/dist/cookie-banner.cjs +1 -1
- package/dist/cookie-banner.cjs.map +1 -1
- package/dist/cookie-banner.js +1 -1
- package/dist/cookie-banner.js.map +1 -1
- package/dist/copy-command.cjs +6 -6
- package/dist/copy-command.cjs.map +1 -1
- package/dist/copy-command.js +7 -7
- package/dist/copy-command.js.map +1 -1
- package/dist/core/checkable.cjs +1 -1
- package/dist/core/checkable.cjs.map +1 -1
- package/dist/core/checkable.d.ts +6 -7
- package/dist/core/checkable.d.ts.map +1 -1
- package/dist/core/checkable.js +1 -1
- package/dist/core/checkable.js.map +1 -1
- package/dist/core/choice-field.cjs +1 -1
- package/dist/core/choice-field.cjs.map +1 -1
- package/dist/core/choice-field.d.ts +6 -6
- package/dist/core/choice-field.d.ts.map +1 -1
- package/dist/core/choice-field.js +1 -1
- package/dist/core/choice-field.js.map +1 -1
- package/dist/core/text-field.cjs +1 -1
- package/dist/core/text-field.cjs.map +1 -1
- package/dist/core/text-field.d.ts +8 -8
- package/dist/core/text-field.d.ts.map +1 -1
- package/dist/core/text-field.js +1 -1
- package/dist/core/text-field.js.map +1 -1
- package/dist/counter.cjs +2 -2
- package/dist/counter.cjs.map +1 -1
- package/dist/counter.js +2 -2
- package/dist/counter.js.map +1 -1
- package/dist/custom-elements.json +5184 -5047
- package/dist/datagrid.cjs +47 -47
- package/dist/datagrid.cjs.map +1 -1
- package/dist/datagrid.js +51 -51
- package/dist/datagrid.js.map +1 -1
- package/dist/date-picker.cjs +9 -9
- package/dist/date-picker.cjs.map +1 -1
- package/dist/date-picker.js +11 -11
- package/dist/date-picker.js.map +1 -1
- package/dist/dialog.cjs +3 -3
- package/dist/dialog.cjs.map +1 -1
- package/dist/dialog.js +3 -3
- package/dist/dialog.js.map +1 -1
- package/dist/disclosure/accordion/accordion.d.ts +4 -2
- package/dist/disclosure/accordion/accordion.d.ts.map +1 -1
- package/dist/disclosure/accordion-item/accordion-item.d.ts +1 -1
- package/dist/disclosure/accordion-item/accordion-item.d.ts.map +1 -1
- package/dist/disclosure/tab-item/tab-item.d.ts +1 -1
- package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
- package/dist/disclosure/tabs/tabs.d.ts +1 -1
- package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
- package/dist/drawer.cjs +4 -4
- package/dist/drawer.cjs.map +1 -1
- package/dist/drawer.js +2 -2
- package/dist/drawer.js.map +1 -1
- package/dist/feedback/alert/alert.d.ts +2 -1
- package/dist/feedback/alert/alert.d.ts.map +1 -1
- package/dist/feedback/badge/badge.d.ts +7 -4
- package/dist/feedback/badge/badge.d.ts.map +1 -1
- package/dist/feedback/chip/chip.d.ts +7 -5
- package/dist/feedback/chip/chip.d.ts.map +1 -1
- package/dist/grid-item.cjs +2 -2
- package/dist/grid-item.cjs.map +1 -1
- package/dist/grid-item.js +2 -2
- package/dist/grid-item.js.map +1 -1
- package/dist/grid.cjs +2 -2
- package/dist/grid.cjs.map +1 -1
- package/dist/grid.js +2 -2
- package/dist/grid.js.map +1 -1
- package/dist/input.cjs +7 -7
- package/dist/input.cjs.map +1 -1
- package/dist/input.js +8 -8
- package/dist/input.js.map +1 -1
- package/dist/inputs/button/button.d.ts +3 -2
- package/dist/inputs/button/button.d.ts.map +1 -1
- package/dist/inputs/button-group/button-group.d.ts +1 -37
- package/dist/inputs/button-group/button-group.d.ts.map +1 -1
- package/dist/inputs/combobox/combobox.types.d.ts +2 -2
- package/dist/inputs/combobox/combobox.types.d.ts.map +1 -1
- package/dist/inputs/counter/counter.d.ts +3 -2
- package/dist/inputs/counter/counter.d.ts.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.cjs +1 -1
- package/dist/inputs/datagrid/datagrid-model.cjs.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.d.ts +2 -1
- package/dist/inputs/datagrid/datagrid-model.d.ts.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.js +1 -1
- package/dist/inputs/datagrid/datagrid-model.js.map +1 -1
- package/dist/inputs/datagrid/datagrid.d.ts +4 -2
- package/dist/inputs/datagrid/datagrid.d.ts.map +1 -1
- package/dist/inputs/date-picker/date-picker.d.ts +3 -2
- package/dist/inputs/date-picker/date-picker.d.ts.map +1 -1
- package/dist/inputs/input/input.d.ts +5 -3
- package/dist/inputs/input/input.d.ts.map +1 -1
- package/dist/inputs/message-composer/message-composer.d.ts +3 -2
- package/dist/inputs/message-composer/message-composer.d.ts.map +1 -1
- package/dist/inputs/number-input/number-input.d.ts +1 -1
- package/dist/inputs/number-input/number-input.d.ts.map +1 -1
- package/dist/inputs/otp-input/otp-input.d.ts +3 -2
- package/dist/inputs/otp-input/otp-input.d.ts.map +1 -1
- package/dist/inputs/query-bar/query-bar.d.ts.map +1 -1
- package/dist/inputs/select/select.d.ts +2 -2
- package/dist/inputs/select/select.d.ts.map +1 -1
- package/dist/inputs/textarea/textarea.d.ts +4 -3
- package/dist/inputs/textarea/textarea.d.ts.map +1 -1
- package/dist/inputs/time-picker/time-picker.d.ts +2 -2
- package/dist/inputs/time-picker/time-picker.d.ts.map +1 -1
- package/dist/keyboard-key.cjs +3 -3
- package/dist/keyboard-key.cjs.map +1 -1
- package/dist/keyboard-key.js +3 -3
- package/dist/keyboard-key.js.map +1 -1
- package/dist/layout/box/box.d.ts +6 -3
- package/dist/layout/box/box.d.ts.map +1 -1
- package/dist/layout/grid/grid.d.ts +11 -6
- package/dist/layout/grid/grid.d.ts.map +1 -1
- package/dist/layout/grid-item/grid-item.d.ts +8 -4
- package/dist/layout/grid-item/grid-item.d.ts.map +1 -1
- package/dist/layout/navbar/navbar.d.ts +4 -2
- package/dist/layout/navbar/navbar.d.ts.map +1 -1
- package/dist/layout/sidebar/sidebar.d.ts +2 -1
- package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
- package/dist/list-item.cjs +1 -1
- package/dist/list-item.cjs.map +1 -1
- package/dist/list-item.js +1 -1
- package/dist/list-item.js.map +1 -1
- package/dist/list.cjs +2 -2
- package/dist/list.cjs.map +1 -1
- package/dist/list.js +2 -2
- package/dist/list.js.map +1 -1
- package/dist/marquee.cjs +3 -3
- package/dist/marquee.cjs.map +1 -1
- package/dist/marquee.js +3 -3
- package/dist/marquee.js.map +1 -1
- package/dist/menu.cjs +3 -3
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.js +3 -3
- package/dist/menu.js.map +1 -1
- package/dist/message-composer.cjs +10 -10
- package/dist/message-composer.cjs.map +1 -1
- package/dist/message-composer.js +7 -7
- package/dist/message-composer.js.map +1 -1
- package/dist/navbar.cjs +10 -10
- package/dist/navbar.cjs.map +1 -1
- package/dist/navbar.js +13 -13
- package/dist/navbar.js.map +1 -1
- package/dist/number-input.cjs +3 -3
- package/dist/number-input.cjs.map +1 -1
- package/dist/number-input.js +4 -4
- package/dist/number-input.js.map +1 -1
- package/dist/otp-input.cjs +4 -4
- package/dist/otp-input.cjs.map +1 -1
- package/dist/otp-input.js +4 -4
- package/dist/otp-input.js.map +1 -1
- package/dist/overlay/dialog/dialog.d.ts +7 -4
- package/dist/overlay/dialog/dialog.d.ts.map +1 -1
- package/dist/overlay/drawer/drawer.d.ts +4 -2
- package/dist/overlay/drawer/drawer.d.ts.map +1 -1
- package/dist/overlay/menu/menu.d.ts +2 -1
- package/dist/overlay/menu/menu.d.ts.map +1 -1
- package/dist/pagination.cjs +9 -9
- package/dist/pagination.cjs.map +1 -1
- package/dist/pagination.js +9 -9
- package/dist/pagination.js.map +1 -1
- package/dist/qr-code.cjs +2 -2
- package/dist/qr-code.cjs.map +1 -1
- package/dist/qr-code.js +2 -2
- package/dist/qr-code.js.map +1 -1
- package/dist/query-bar.cjs +7 -7
- package/dist/query-bar.cjs.map +1 -1
- package/dist/query-bar.js +8 -8
- package/dist/query-bar.js.map +1 -1
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/radio-group.js.map +1 -1
- package/dist/refine.cjs +123 -123
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +123 -123
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +124 -124
- package/dist/refine.js.map +1 -1
- package/dist/select.cjs +13 -13
- package/dist/select.cjs.map +1 -1
- package/dist/select.js +15 -15
- package/dist/select.js.map +1 -1
- package/dist/separator.cjs +4 -4
- package/dist/separator.cjs.map +1 -1
- package/dist/separator.js +4 -4
- package/dist/separator.js.map +1 -1
- package/dist/shared/prop-bundles.cjs +1 -1
- package/dist/shared/prop-bundles.cjs.map +1 -1
- package/dist/shared/prop-bundles.d.ts +7 -8
- package/dist/shared/prop-bundles.d.ts.map +1 -1
- package/dist/shared/prop-bundles.js +1 -1
- package/dist/shared/prop-bundles.js.map +1 -1
- package/dist/shared/types.cjs +2 -0
- package/dist/shared/types.cjs.map +1 -0
- package/dist/shared/types.d.ts +27 -9
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/sidebar.cjs +7 -7
- package/dist/sidebar.cjs.map +1 -1
- package/dist/sidebar.js +4 -4
- package/dist/sidebar.js.map +1 -1
- package/dist/step.cjs +10 -10
- package/dist/step.cjs.map +1 -1
- package/dist/step.js +10 -10
- package/dist/step.js.map +1 -1
- package/dist/tab-item.cjs +4 -4
- package/dist/tab-item.cjs.map +1 -1
- package/dist/tab-item.js +5 -5
- package/dist/tab-item.js.map +1 -1
- package/dist/table.cjs +2 -2
- package/dist/table.cjs.map +1 -1
- package/dist/table.js +2 -2
- package/dist/table.js.map +1 -1
- package/dist/tabs.cjs +2 -2
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.js +4 -4
- package/dist/tabs.js.map +1 -1
- package/dist/text.cjs +2 -2
- package/dist/text.cjs.map +1 -1
- package/dist/text.js +2 -2
- package/dist/text.js.map +1 -1
- package/dist/textarea.cjs +7 -7
- package/dist/textarea.cjs.map +1 -1
- package/dist/textarea.js +7 -7
- package/dist/textarea.js.map +1 -1
- package/dist/time-picker.cjs +7 -7
- package/dist/time-picker.cjs.map +1 -1
- package/dist/time-picker.js +7 -7
- package/dist/time-picker.js.map +1 -1
- package/dist/tooltip.cjs +1 -1
- package/dist/tooltip.cjs.map +1 -1
- package/dist/tooltip.js +1 -1
- package/dist/tooltip.js.map +1 -1
- package/package.json +13 -13
package/dist/number-input.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number-input.js","names":[],"sources":["../src/inputs/number-input/number-input.ts"],"sourcesContent":["import { clamp } from '@vielzeug/arsenal';\nimport { bind, define, getHost, html, onElement, prop, ref, watchEffect } from '@vielzeug/ore';\nimport { computed, watch as rippleWatch, signal } from '@vielzeug/ripple';\nimport { createSpinnerControl } from '../../core';\nimport { disablableBundle, roundableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledLoadingMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor, VisualVariant } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport componentStyles from './number-input.css?inline';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { INPUT_TAG } from '../input/input';\nexport type OreNumberInputEvents = {\n change: Event;\n input: InputEvent;\n};\n\n/** Number Input props */\nexport type OreNumberInputProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message */\n error?: string;\n /** Stretch to full width of container */\n fullwidth?: boolean;\n /** Helper text */\n helper?: string;\n /** Visible label */\n label?: string;\n /** Label placement: 'inset' renders the label inside the control box, 'outside' renders it above */\n 'label-placement'?: 'inset' | 'outside';\n /** Large step (for Page Up/Down, default: 10 × step) */\n 'large-step'?: number;\n /**\n * Shows an inline spinner inside the field and forces the control into `disabled` for the\n * duration: use while an async validation/submission request is in flight to prevent\n * double-submits.\n */\n loading?: boolean;\n /** Maximum allowed value */\n max?: number;\n /** Minimum allowed value */\n min?: number;\n /** Form field name */\n name?: string;\n /** Placeholder text */\n placeholder?: string;\n /** Make the input read-only */\n readonly?: boolean;\n /**\n * JS-only callback fired with the inner `<input>` element when it mounts,\n * and with `null` when it unmounts.\n * Set as a JS property: `bitNumberInput.ref = (el) => { ... }`.\n */\n ref?: ((el: HTMLInputElement | null) => void) | null;\n /** Border radius */\n rounded?: string;\n /** Component size */\n size?: ComponentSize;\n /** Step size for increment/decrement */\n step?: number;\n /**\n * Shows an inline green check icon inside the field to confirm the value has\n * passed validation. Ignored while `error` is set: an error always wins.\n */\n success?: boolean;\n /** Current numeric value */\n value?: number;\n /** Visual variant */\n variant?: VisualVariant;\n};\n\n/**\n * A numeric spin-button input with +/− controls, min/max clamping, and full keyboard support.\n *\n * @element ore-number-input\n *\n * @attr {number} value - Current value\n * @attr {number} min - Minimum value\n * @attr {number} max - Maximum value\n * @attr {number} step - Increment/decrement step (default: 1)\n * @attr {number} large-step - Step for Page Up/Down (default: 10)\n * @attr {boolean} disabled - Disables the control\n * @attr {boolean} readonly - Read-only mode\n * @attr {boolean} loading - Show an inline spinner and force the control disabled\n * @attr {boolean} success - Show an inline success check icon (suppressed while `error` is set)\n * @attr {string} label - Visible label\n * @attr {string} name - Form field name\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} placeholder - Input placeholder\n *\n * @fires input - On every keystroke.\n * @fires change - On committed value change.\n *\n * @slot prefix - Content before the input (e.g. icon)\n * @slot suffix - Content after the input (e.g. unit label)\n * @slot label - Custom label content\n * @slot helper - Custom helper text content\n * @slot error - Custom error content\n *\n * @cssprop --number-input-height - Control height\n * @cssprop --number-input-border-color - Border color\n * @cssprop --number-input-radius - Border radius\n * @cssprop --number-input-bg - Background\n * @cssprop --number-input-btn-bg - Spin button background\n *\n * @part control - Control container.\n * @part decrement-btn - Decrement stepper button.\n * @part input - Input element.\n * @part increment-btn - Increment stepper button.\n * @example\n * ```html\n * <ore-number-input label=\"Quantity\" value=\"1\" min=\"1\" max=\"99\" step=\"1\"></ore-number-input>\n * <ore-number-input label=\"Quantity\" success></ore-number-input>\n * <ore-number-input label=\"Quantity\" loading></ore-number-input>\n * ```\n */\nexport const NUMBER_INPUT_TAG = 'ore-number-input' as const;\ndefine<OreNumberInputProps>(NUMBER_INPUT_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n ...roundableBundle,\n error: prop.string(),\n fullwidth: prop.bool(false),\n helper: prop.string(),\n label: prop.string(),\n 'label-placement': prop.oneOf(['inset', 'outside'] as const, 'inset'),\n 'large-step': prop.json(undefined as number | undefined),\n loading: prop.bool(false),\n max: prop.json(undefined as number | undefined),\n min: prop.json(undefined as number | undefined),\n name: prop.string(),\n placeholder: prop.string(),\n readonly: prop.bool(false),\n ref: prop.data<((el: HTMLInputElement | null) => void) | null>(),\n step: prop.number(1),\n success: prop.bool(false),\n value: prop.json(undefined as number | undefined),\n variant: prop.string<VisualVariant>(),\n },\n setup(props) {\n const el = getHost();\n const watch = watchEffect;\n\n // `loading` behaves like a temporary `disabled`: see ore-input's identical computation.\n const isDisabled = computed(() => props.disabled.value || props.loading.value);\n const isReadonly = computed(() => Boolean(props.readonly.value));\n\n // Internal numeric value signal (string representation for the input)\n const fieldValue = signal(props.value.value != null ? String(props.value.value) : '');\n\n defineFieldValue(\n el,\n () => fieldValue.value,\n (value) => {\n fieldValue.value = value;\n },\n );\n\n // Keep fieldValue in sync when props.value changes externally\n rippleWatch(props.value, (v) => {\n const next = v != null ? String(v) : '';\n\n if (fieldValue.value !== next) fieldValue.value = next;\n });\n\n function parseValue(): number | null {\n const v = fieldValue.value.trim();\n\n if (!v) return null;\n\n const n = Number.parseFloat(v);\n\n return Number.isNaN(n) ? null : n;\n }\n\n function commit(val: number | null, _originalEvent?: Event) {\n const min = props.min.value != null ? Number(props.min.value) : undefined;\n const max = props.max.value != null ? Number(props.max.value) : undefined;\n const clamped = val != null ? clamp(val, min, max) : null;\n const nextValue = clamped != null ? String(clamped) : '';\n\n if (fieldValue.value !== nextValue) fieldValue.value = nextValue;\n\n setFieldValue(el, nextValue);\n dispatchNativeFieldEvent(el, 'change');\n }\n\n const spinner = createSpinnerControl({\n commit,\n disabled: isDisabled,\n largeStep: props['large-step'],\n max: props.max,\n min: props.min,\n parse: parseValue,\n readonly: isReadonly,\n step: props.step,\n });\n\n // Composition uses ore-input's own documented `ref` prop (fired with the raw\n // <input> on mount, `null` on unmount) instead of reaching into its shadow DOM :\n // see `packages/refine/AGENTS.md` / input.ts's `ref` JSDoc for the supported contract.\n // Set imperatively (rather than declared in the template) because ore's template\n // engine treats a function-valued attr binding as a reactive getter to invoke, not\n // a literal value to assign: a plain property assignment avoids that entirely.\n const bitInputRef = ref<HTMLElementTagNameMap['ore-input']>();\n let stopAriaWatch: (() => void) | null = null;\n let detachListeners: (() => void) | null = null;\n let refSub: { dispose(): void } | null = null;\n\n const handleFieldRef = (rawInput: HTMLInputElement | null): void => {\n if (!rawInput) {\n stopAriaWatch?.();\n detachListeners?.();\n refSub?.dispose();\n stopAriaWatch = null;\n detachListeners = null;\n refSub = null;\n props.ref.value?.(null);\n\n return;\n }\n\n rawInput.setAttribute('inputmode', 'decimal');\n // The wrapper div is a plain layout container: WAI-ARIA spinbutton semantics\n // live on the actually-focusable element (the raw <input> rendered by ore-input).\n rawInput.setAttribute('role', 'spinbutton');\n\n const handleChange = (e: Event) => {\n const val = (e.target as HTMLInputElement).value;\n const n = val !== '' ? Number.parseFloat(val) : null;\n\n commit(Number.isNaN(n ?? NaN) ? null : n, e);\n };\n\n const handleInput = (e: Event) => {\n const val = (e.target as HTMLInputElement).value;\n\n fieldValue.value = val;\n setFieldValue(el, val);\n dispatchNativeFieldEvent(el, 'input');\n };\n\n rawInput.addEventListener('change', handleChange);\n rawInput.addEventListener('input', handleInput);\n detachListeners = () => {\n rawInput.removeEventListener('change', handleChange);\n rawInput.removeEventListener('input', handleInput);\n };\n\n stopAriaWatch = watch(() => {\n const now = parseValue();\n\n if (now == null) rawInput.removeAttribute('aria-valuenow');\n else rawInput.setAttribute('aria-valuenow', String(now));\n\n if (props.min.value != null) rawInput.setAttribute('aria-valuemin', String(props.min.value));\n else rawInput.removeAttribute('aria-valuemin');\n\n if (props.max.value != null) rawInput.setAttribute('aria-valuemax', String(props.max.value));\n else rawInput.removeAttribute('aria-valuemax');\n\n if (isReadonly.value) rawInput.setAttribute('aria-readonly', 'true');\n else rawInput.removeAttribute('aria-readonly');\n });\n\n // Fire user ref callback\n props.ref.value?.(rawInput);\n\n refSub = rippleWatch(props.ref, (cb) => {\n cb?.(rawInput);\n });\n };\n\n // ore-input's own onElement/ref lifecycle calls this back with `null` when its\n // inner <input> unmounts, so no explicit teardown is needed here.\n onElement(bitInputRef, (bitInputEl) => {\n bitInputEl.ref = handleFieldRef;\n });\n\n bind({\n attr: {\n size: props.size,\n value: () => fieldValue.value || null,\n variant: props.variant,\n },\n });\n\n const isNonInteractive = computed(() => isDisabled.value || isReadonly.value);\n\n // The stepper buttons are slotted *into* ore-input's own `prefix`/`suffix` slots instead of\n // sitting outside it in a separate wrapper: they render inside ore-input's own bordered\n // box (same as its built-in clear/password-toggle buttons), matching every other field's\n // single-box look instead of floating as two detached icon buttons either side of a\n // narrow field. `@keydown` moves to ore-input itself: native keyboard events are\n // `composed: true` and bubble out past a shadow boundary, so it still sees keydowns\n // originating from ore-input's internal <input> as well as from these slotted buttons.\n return html`\n <ore-input\n class=\"field\"\n part=\"field control\"\n ref=\"${bitInputRef}\"\n value=\"${() => fieldValue.value ?? 0}\"\n label=\"${() => props.label.value ?? ''}\"\n label-placement=\"${() => props['label-placement'].value ?? 'inset'}\"\n placeholder=\"${() => props.placeholder.value ?? ''}\"\n name=\"${() => props.name.value ?? ''}\"\n helper=\"${() => props.helper.value ?? ''}\"\n error=\"${() => props.error.value ?? ''}\"\n size=\"${props.size}\"\n color=\"${() => props.color.value ?? ''}\"\n variant=\"${props.variant}\"\n rounded=\"${() => props.rounded.value}\"\n ?disabled=\"${props.disabled}\"\n ?readonly=\"${isReadonly}\"\n ?fullwidth=\"${() => props.fullwidth.value}\"\n ?loading=\"${() => props.loading.value}\"\n ?success=\"${() => props.success.value}\"\n @keydown=\"${(e: KeyboardEvent) => spinner.handleKeydown(e)}\">\n <button\n slot=\"prefix\"\n type=\"button\"\n part=\"decrement-btn\"\n aria-label=\"Decrease\"\n ?disabled=\"${() => isNonInteractive.value || spinner.atMin()}\"\n @click=\"${(e: Event) => spinner.incrementBy(-(Number(props.step.value) || 1), e)}\">\n <ore-icon name=\"minus\" size=\"14\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>\n <button\n slot=\"suffix\"\n type=\"button\"\n part=\"increment-btn\"\n aria-label=\"Increase\"\n ?disabled=\"${() => isNonInteractive.value || spinner.atMax()}\"\n @click=\"${(e: Event) => spinner.incrementBy(Number(props.step.value) || 1, e)}\">\n <ore-icon name=\"plus\" size=\"14\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>\n </ore-input>\n `;\n },\n shadow: { delegatesFocus: true },\n styles: [disabledLoadingMixin, componentStyles],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"number-input.js","names":[],"sources":["../src/inputs/number-input/number-input.ts"],"sourcesContent":["import { clamp } from '@vielzeug/arsenal';\nimport { bind, define, getHost, html, onElement, prop, ref, watchEffect } from '@vielzeug/ore';\nimport { computed, watch as rippleWatch, signal } from '@vielzeug/ripple';\nimport { createSpinnerControl } from '../../core';\nimport { disablableBundle, roundableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledLoadingMixin } from '../../styles';\nimport { type ComponentSize, type ThemeColor, VISUAL_VARIANTS, type VisualVariant } from '../../types';\nimport { defineFieldValue, dispatchNativeFieldEvent, setFieldValue } from '../shared/native-field-event';\nimport componentStyles from './number-input.css?inline';\n\nexport { ICON_TAG } from '../../content/icon/icon';\nexport { INPUT_TAG } from '../input/input';\nexport type OreNumberInputEvents = {\n change: Event;\n input: InputEvent;\n};\n\n/** Number Input props */\nexport type OreNumberInputProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Error message */\n error?: string;\n /** Stretch to full width of container */\n fullwidth?: boolean;\n /** Helper text */\n helper?: string;\n /** Visible label */\n label?: string;\n /** Label placement: 'inset' renders the label inside the control box, 'outside' renders it above */\n 'label-placement'?: 'inset' | 'outside';\n /** Large step (for Page Up/Down, default: 10 × step) */\n 'large-step'?: number;\n /**\n * Shows an inline spinner inside the field and forces the control into `disabled` for the\n * duration: use while an async validation/submission request is in flight to prevent\n * double-submits.\n */\n loading?: boolean;\n /** Maximum allowed value */\n max?: number;\n /** Minimum allowed value */\n min?: number;\n /** Form field name */\n name?: string;\n /** Placeholder text */\n placeholder?: string;\n /** Make the input read-only */\n readonly?: boolean;\n /**\n * JS-only callback fired with the inner `<input>` element when it mounts,\n * and with `null` when it unmounts.\n * Set as a JS property: `bitNumberInput.ref = (el) => { ... }`.\n */\n ref?: ((el: HTMLInputElement | null) => void) | null;\n /** Border radius */\n rounded?: string;\n /** Component size */\n size?: ComponentSize;\n /** Step size for increment/decrement */\n step?: number;\n /**\n * Shows an inline green check icon inside the field to confirm the value has\n * passed validation. Ignored while `error` is set: an error always wins.\n */\n success?: boolean;\n /** Current numeric value */\n value?: number;\n /** Visual variant */\n variant?: VisualVariant;\n};\n\n/**\n * A numeric spin-button input with +/− controls, min/max clamping, and full keyboard support.\n *\n * @element ore-number-input\n *\n * @attr {number} value - Current value\n * @attr {number} min - Minimum value\n * @attr {number} max - Maximum value\n * @attr {number} step - Increment/decrement step (default: 1)\n * @attr {number} large-step - Step for Page Up/Down (default: 10)\n * @attr {boolean} disabled - Disables the control\n * @attr {boolean} readonly - Read-only mode\n * @attr {boolean} loading - Show an inline spinner and force the control disabled\n * @attr {boolean} success - Show an inline success check icon (suppressed while `error` is set)\n * @attr {string} label - Visible label\n * @attr {string} name - Form field name\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} placeholder - Input placeholder\n *\n * @fires input - On every keystroke.\n * @fires change - On committed value change.\n *\n * @slot prefix - Content before the input (e.g. icon)\n * @slot suffix - Content after the input (e.g. unit label)\n * @slot label - Custom label content\n * @slot helper - Custom helper text content\n * @slot error - Custom error content\n *\n * @cssprop --number-input-height - Control height\n * @cssprop --number-input-border-color - Border color\n * @cssprop --number-input-radius - Border radius\n * @cssprop --number-input-bg - Background\n * @cssprop --number-input-btn-bg - Spin button background\n *\n * @part control - Control container.\n * @part decrement-btn - Decrement stepper button.\n * @part input - Input element.\n * @part increment-btn - Increment stepper button.\n * @example\n * ```html\n * <ore-number-input label=\"Quantity\" value=\"1\" min=\"1\" max=\"99\" step=\"1\"></ore-number-input>\n * <ore-number-input label=\"Quantity\" success></ore-number-input>\n * <ore-number-input label=\"Quantity\" loading></ore-number-input>\n * ```\n */\nexport const NUMBER_INPUT_TAG = 'ore-number-input' as const;\ndefine<OreNumberInputProps>(NUMBER_INPUT_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n ...roundableBundle,\n error: prop.string(),\n fullwidth: prop.bool(false),\n helper: prop.string(),\n label: prop.string(),\n 'label-placement': prop.oneOf(['inset', 'outside'] as const, 'inset'),\n 'large-step': prop.json(undefined as number | undefined),\n loading: prop.bool(false),\n max: prop.json(undefined as number | undefined),\n min: prop.json(undefined as number | undefined),\n name: prop.string(),\n placeholder: prop.string(),\n readonly: prop.bool(false),\n ref: prop.data<((el: HTMLInputElement | null) => void) | null>(),\n step: prop.number(1),\n success: prop.bool(false),\n value: prop.json(undefined as number | undefined),\n variant: prop.oneOf(VISUAL_VARIANTS),\n },\n setup(props) {\n const el = getHost();\n const watch = watchEffect;\n\n // `loading` behaves like a temporary `disabled`: see ore-input's identical computation.\n const isDisabled = computed(() => props.disabled.value || props.loading.value);\n const isReadonly = computed(() => Boolean(props.readonly.value));\n\n // Internal numeric value signal (string representation for the input)\n const fieldValue = signal(props.value.value != null ? String(props.value.value) : '');\n\n defineFieldValue(\n el,\n () => fieldValue.value,\n (value) => {\n fieldValue.value = value;\n },\n );\n\n // Keep fieldValue in sync when props.value changes externally\n rippleWatch(props.value, (v) => {\n const next = v != null ? String(v) : '';\n\n if (fieldValue.value !== next) fieldValue.value = next;\n });\n\n function parseValue(): number | null {\n const v = fieldValue.value.trim();\n\n if (!v) return null;\n\n const n = Number.parseFloat(v);\n\n return Number.isNaN(n) ? null : n;\n }\n\n function commit(val: number | null, _originalEvent?: Event) {\n const min = props.min.value != null ? Number(props.min.value) : undefined;\n const max = props.max.value != null ? Number(props.max.value) : undefined;\n const clamped = val != null ? clamp(val, min, max) : null;\n const nextValue = clamped != null ? String(clamped) : '';\n\n if (fieldValue.value !== nextValue) fieldValue.value = nextValue;\n\n setFieldValue(el, nextValue);\n dispatchNativeFieldEvent(el, 'change');\n }\n\n const spinner = createSpinnerControl({\n commit,\n disabled: isDisabled,\n largeStep: props['large-step'],\n max: props.max,\n min: props.min,\n parse: parseValue,\n readonly: isReadonly,\n step: props.step,\n });\n\n // Composition uses ore-input's own documented `ref` prop (fired with the raw\n // <input> on mount, `null` on unmount) instead of reaching into its shadow DOM :\n // see `packages/refine/AGENTS.md` / input.ts's `ref` JSDoc for the supported contract.\n // Set imperatively (rather than declared in the template) because ore's template\n // engine treats a function-valued attr binding as a reactive getter to invoke, not\n // a literal value to assign: a plain property assignment avoids that entirely.\n const bitInputRef = ref<HTMLElementTagNameMap['ore-input']>();\n let stopAriaWatch: (() => void) | null = null;\n let detachListeners: (() => void) | null = null;\n let refSub: { dispose(): void } | null = null;\n\n const handleFieldRef = (rawInput: HTMLInputElement | null): void => {\n if (!rawInput) {\n stopAriaWatch?.();\n detachListeners?.();\n refSub?.dispose();\n stopAriaWatch = null;\n detachListeners = null;\n refSub = null;\n props.ref.value?.(null);\n\n return;\n }\n\n rawInput.setAttribute('inputmode', 'decimal');\n // The wrapper div is a plain layout container: WAI-ARIA spinbutton semantics\n // live on the actually-focusable element (the raw <input> rendered by ore-input).\n rawInput.setAttribute('role', 'spinbutton');\n\n const handleChange = (e: Event) => {\n const val = (e.target as HTMLInputElement).value;\n const n = val !== '' ? Number.parseFloat(val) : null;\n\n commit(Number.isNaN(n ?? NaN) ? null : n, e);\n };\n\n const handleInput = (e: Event) => {\n const val = (e.target as HTMLInputElement).value;\n\n fieldValue.value = val;\n setFieldValue(el, val);\n dispatchNativeFieldEvent(el, 'input');\n };\n\n rawInput.addEventListener('change', handleChange);\n rawInput.addEventListener('input', handleInput);\n detachListeners = () => {\n rawInput.removeEventListener('change', handleChange);\n rawInput.removeEventListener('input', handleInput);\n };\n\n stopAriaWatch = watch(() => {\n const now = parseValue();\n\n if (now == null) rawInput.removeAttribute('aria-valuenow');\n else rawInput.setAttribute('aria-valuenow', String(now));\n\n if (props.min.value != null) rawInput.setAttribute('aria-valuemin', String(props.min.value));\n else rawInput.removeAttribute('aria-valuemin');\n\n if (props.max.value != null) rawInput.setAttribute('aria-valuemax', String(props.max.value));\n else rawInput.removeAttribute('aria-valuemax');\n\n if (isReadonly.value) rawInput.setAttribute('aria-readonly', 'true');\n else rawInput.removeAttribute('aria-readonly');\n });\n\n // Fire user ref callback\n props.ref.value?.(rawInput);\n\n refSub = rippleWatch(props.ref, (cb) => {\n cb?.(rawInput);\n });\n };\n\n // ore-input's own onElement/ref lifecycle calls this back with `null` when its\n // inner <input> unmounts, so no explicit teardown is needed here.\n onElement(bitInputRef, (bitInputEl) => {\n bitInputEl.ref = handleFieldRef;\n });\n\n bind({\n attr: {\n size: props.size,\n value: () => fieldValue.value || null,\n variant: props.variant,\n },\n });\n\n const isNonInteractive = computed(() => isDisabled.value || isReadonly.value);\n\n // The stepper buttons are slotted *into* ore-input's own `prefix`/`suffix` slots instead of\n // sitting outside it in a separate wrapper: they render inside ore-input's own bordered\n // box (same as its built-in clear/password-toggle buttons), matching every other field's\n // single-box look instead of floating as two detached icon buttons either side of a\n // narrow field. `@keydown` moves to ore-input itself: native keyboard events are\n // `composed: true` and bubble out past a shadow boundary, so it still sees keydowns\n // originating from ore-input's internal <input> as well as from these slotted buttons.\n return html`\n <ore-input\n class=\"field\"\n part=\"field control\"\n ref=\"${bitInputRef}\"\n value=\"${() => fieldValue.value ?? 0}\"\n label=\"${() => props.label.value ?? ''}\"\n label-placement=\"${() => props['label-placement'].value ?? 'inset'}\"\n placeholder=\"${() => props.placeholder.value ?? ''}\"\n name=\"${() => props.name.value ?? ''}\"\n helper=\"${() => props.helper.value ?? ''}\"\n error=\"${() => props.error.value ?? ''}\"\n size=\"${props.size}\"\n color=\"${() => props.color.value ?? ''}\"\n variant=\"${props.variant}\"\n rounded=\"${() => props.rounded.value}\"\n ?disabled=\"${props.disabled}\"\n ?readonly=\"${isReadonly}\"\n ?fullwidth=\"${() => props.fullwidth.value}\"\n ?loading=\"${() => props.loading.value}\"\n ?success=\"${() => props.success.value}\"\n @keydown=\"${(e: KeyboardEvent) => spinner.handleKeydown(e)}\">\n <button\n slot=\"prefix\"\n type=\"button\"\n part=\"decrement-btn\"\n aria-label=\"Decrease\"\n ?disabled=\"${() => isNonInteractive.value || spinner.atMin()}\"\n @click=\"${(e: Event) => spinner.incrementBy(-(Number(props.step.value) || 1), e)}\">\n <ore-icon name=\"minus\" size=\"14\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>\n <button\n slot=\"suffix\"\n type=\"button\"\n part=\"increment-btn\"\n aria-label=\"Increase\"\n ?disabled=\"${() => isNonInteractive.value || spinner.atMax()}\"\n @click=\"${(e: Event) => spinner.incrementBy(Number(props.step.value) || 1, e)}\">\n <ore-icon name=\"plus\" size=\"14\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n </button>\n </ore-input>\n `;\n },\n shadow: { delegatesFocus: true },\n styles: [disabledLoadingMixin, componentStyles],\n});\n"],"mappings":"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"}
|
package/dist/otp-input.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./inputs/shared/native-field-event.cjs"),t=require("./styles/mixins/field-variants.css.cjs"),n=require("./styles/mixins/shape.css.cjs"),r=require("./styles/mixins/theme.css.cjs"),i=require("./core/ore.cjs"),a=require("./core/nav.cjs"),o=require("./shared/prop-bundles.cjs"),
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./inputs/shared/native-field-event.cjs"),t=require("./styles/mixins/field-variants.css.cjs"),n=require("./styles/mixins/shape.css.cjs"),r=require("./styles/mixins/theme.css.cjs"),i=require("./core/ore.cjs"),a=require("./core/nav.cjs"),o=require("./shared/types.cjs"),s=require("./shared/prop-bundles.cjs"),c=require("./inputs/otp-input/otp-input.css.cjs");let l=require("@vielzeug/ripple"),u=require("@vielzeug/ore");var d=`ore-otp-input`;(0,u.define)(d,{formAssociated:!0,props:{...s.themableBundle,...s.sizableBundle,...s.disablableBundle,label:u.prop.string(`One-time password`),length:u.prop.number(6),masked:u.prop.bool(!1),name:u.prop.string(),separator:u.prop.string(),type:u.prop.oneOf([`numeric`,`alphanumeric`],`numeric`),value:u.prop.string(),variant:u.prop.oneOf(o.FIELD_VARIANTS)},setup(t){let n=(0,u.getHost)(),r=(0,u.useEmit)(),o=(0,l.computed)(()=>Number(t.length.value)||6),s=(0,l.computed)(()=>!!t.disabled.value),c=(0,l.computed)(()=>Array.from({length:o.value},(e,t)=>t)),d=(0,l.signal)(String(t.value.value||``)),normalizedPropValue=()=>String(t.value.value||``);(0,u.useField)({disabled:s,toFormValue:e=>e,value:d}),(0,u.bind)({attr:{size:t.size,value:()=>d.value||null,variant:t.variant}});function getInputs(){return[...n.shadowRoot?.querySelectorAll(`input.cell`)??[]]}let f=a.createListControl({getItems:()=>getInputs(),keys:{next:[`ArrowRight`],prev:[`ArrowLeft`]},loop:!1,onNavigate:({item:e})=>{e.focus()},signal:i.lifecycleSignal(u.onCleanup)});function getValue(){return getInputs().map(e=>e.value).join(``)}function syncInputsFromValue(e){let t=getInputs(),n=String(e||``).slice(0,t.length);for(let e of t)e.value=``;n.split(``).forEach((e,n)=>{t[n]&&(t[n].value=e)}),d.value=getValue()}e.defineFieldValue(n,()=>d.value,e=>{syncInputsFromValue(e)});function emitOtpState(t,i){let a=getInputs(),o=getValue(),s=i??(o.length===a.length&&o.split(``).every(Boolean));d.value=o,e.setFieldValue(n,o),e.dispatchNativeFieldEvent(n,`input`),e.dispatchNativeFieldEvent(n,`change`),s&&r(`complete`,{originalEvent:t,value:o})}function isAllowed(e){return t.type.value===`numeric`?/^\d$/.test(e):/^[a-z\d]$/i.test(e)}function handleInput(e,t){let n=e.target,r=n.value;if(r.length>1&&(r=r.slice(-1)),r&&!isAllowed(r)){n.value=``;return}n.value=r;let i=getInputs();emitOtpState(e),r&&t<i.length-1&&(i[t+1].focus(),i[t+1].select())}function handleKeydown(e,t){let n=e.target,r=getInputs();if(f.set(t),e.key===`Backspace`){if(n.value)n.value=``;else if(t>0){let e=r[f.navigate(`prev`)];e&&(e.select(),e.value=``)}emitOtpState(e,!1),e.preventDefault()}else f.handleKeydown(e)}function handlePaste(e){e.preventDefault();let t=(e.clipboardData?.getData(`text`)??``).split(``).filter(e=>isAllowed(e)).slice(0,o.value),n=getInputs();t.forEach((e,t)=>{n[t]&&(n[t].value=e)}),emitOtpState(e),n[Math.min(t.length,n.length-1)]?.focus()}(0,l.watch)(t.value,e=>{syncInputsFromValue(String(e||``))}),(0,l.watch)(t.length,()=>{requestAnimationFrame(()=>syncInputsFromValue(normalizedPropValue()))});let p=(0,l.computed)(()=>{let e=Number(t.length.value)||6;return t.separator.value==null?-1:Math.floor(e/2)});return(0,u.onMounted)(()=>{syncInputsFromValue(normalizedPropValue())}),u.html`
|
|
2
2
|
<div class="otp-group" part="group" role="group" aria-label="${t.label}">
|
|
3
|
-
${()=>
|
|
4
|
-
${()=>p.value>0&&e===p.value?
|
|
3
|
+
${()=>c.value.map(e=>u.html`
|
|
4
|
+
${()=>p.value>0&&e===p.value?u.html`
|
|
5
5
|
<span class="separator" aria-hidden="true">${()=>t.separator.value||`-`}</span>
|
|
6
6
|
`:``}
|
|
7
7
|
<input
|
|
@@ -19,5 +19,5 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
19
19
|
@focus="${e=>e.target.select()}" />
|
|
20
20
|
`)}
|
|
21
21
|
</div>
|
|
22
|
-
`},styles:[r.colorThemeMixin,n.sizeVariantMixin({}),r.forcedColorsFocusMixin(`.cell`),
|
|
22
|
+
`},styles:[r.colorThemeMixin,n.sizeVariantMixin({}),r.forcedColorsFocusMixin(`.cell`),c.default,t.fieldVariantMixin({accentVar:`--_cell-focus-border`,container:`.cell`,tokenPrefix:`otp-cell`})]}),exports.OTP_INPUT_TAG=d;
|
|
23
23
|
//# sourceMappingURL=otp-input.cjs.map
|
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: ({ 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
|
+
{"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 } from '../../types';\nimport { FIELD_VARIANTS, type FieldVariant } 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?: FieldVariant;\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.oneOf(FIELD_VARIANTS),\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
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{defineFieldValue as e,dispatchNativeFieldEvent as t,setFieldValue as n}from"./inputs/shared/native-field-event.js";import{fieldVariantMixin as r}from"./styles/mixins/field-variants.css.js";import{sizeVariantMixin as i}from"./styles/mixins/shape.css.js";import{colorThemeMixin as a,forcedColorsFocusMixin as o}from"./styles/mixins/theme.css.js";import{lifecycleSignal as s}from"./core/ore.js";import{createListControl as c}from"./core/nav.js";import{disablableBundle as
|
|
1
|
+
import{defineFieldValue as e,dispatchNativeFieldEvent as t,setFieldValue as n}from"./inputs/shared/native-field-event.js";import{fieldVariantMixin as r}from"./styles/mixins/field-variants.css.js";import{sizeVariantMixin as i}from"./styles/mixins/shape.css.js";import{colorThemeMixin as a,forcedColorsFocusMixin as o}from"./styles/mixins/theme.css.js";import{lifecycleSignal as s}from"./core/ore.js";import{createListControl as c}from"./core/nav.js";import{FIELD_VARIANTS as l}from"./shared/types.js";import{disablableBundle as u,sizableBundle as d,themableBundle as f}from"./shared/prop-bundles.js";import p from"./inputs/otp-input/otp-input.css.js";import{computed as m,signal as h,watch as g}from"@vielzeug/ripple";import{bind as _,define as v,getHost as y,html as b,onCleanup as x,onMounted as S,prop as C,useEmit as w,useField as T}from"@vielzeug/ore";var E=`ore-otp-input`;v(E,{formAssociated:!0,props:{...f,...d,...u,label:C.string(`One-time password`),length:C.number(6),masked:C.bool(!1),name:C.string(),separator:C.string(),type:C.oneOf([`numeric`,`alphanumeric`],`numeric`),value:C.string(),variant:C.oneOf(l)},setup(r){let i=y(),a=w(),o=m(()=>Number(r.length.value)||6),l=m(()=>!!r.disabled.value),u=m(()=>Array.from({length:o.value},(e,t)=>t)),d=h(String(r.value.value||``)),normalizedPropValue=()=>String(r.value.value||``);T({disabled:l,toFormValue:e=>e,value:d}),_({attr:{size:r.size,value:()=>d.value||null,variant:r.variant}});function getInputs(){return[...i.shadowRoot?.querySelectorAll(`input.cell`)??[]]}let f=c({getItems:()=>getInputs(),keys:{next:[`ArrowRight`],prev:[`ArrowLeft`]},loop:!1,onNavigate:({item:e})=>{e.focus()},signal:s(x)});function getValue(){return getInputs().map(e=>e.value).join(``)}function syncInputsFromValue(e){let t=getInputs(),n=String(e||``).slice(0,t.length);for(let e of t)e.value=``;n.split(``).forEach((e,n)=>{t[n]&&(t[n].value=e)}),d.value=getValue()}e(i,()=>d.value,e=>{syncInputsFromValue(e)});function emitOtpState(e,r){let o=getInputs(),s=getValue(),c=r??(s.length===o.length&&s.split(``).every(Boolean));d.value=s,n(i,s),t(i,`input`),t(i,`change`),c&&a(`complete`,{originalEvent:e,value:s})}function isAllowed(e){return r.type.value===`numeric`?/^\d$/.test(e):/^[a-z\d]$/i.test(e)}function handleInput(e,t){let n=e.target,r=n.value;if(r.length>1&&(r=r.slice(-1)),r&&!isAllowed(r)){n.value=``;return}n.value=r;let i=getInputs();emitOtpState(e),r&&t<i.length-1&&(i[t+1].focus(),i[t+1].select())}function handleKeydown(e,t){let n=e.target,r=getInputs();if(f.set(t),e.key===`Backspace`){if(n.value)n.value=``;else if(t>0){let e=r[f.navigate(`prev`)];e&&(e.select(),e.value=``)}emitOtpState(e,!1),e.preventDefault()}else f.handleKeydown(e)}function handlePaste(e){e.preventDefault();let t=(e.clipboardData?.getData(`text`)??``).split(``).filter(e=>isAllowed(e)).slice(0,o.value),n=getInputs();t.forEach((e,t)=>{n[t]&&(n[t].value=e)}),emitOtpState(e),n[Math.min(t.length,n.length-1)]?.focus()}g(r.value,e=>{syncInputsFromValue(String(e||``))}),g(r.length,()=>{requestAnimationFrame(()=>syncInputsFromValue(normalizedPropValue()))});let p=m(()=>{let e=Number(r.length.value)||6;return r.separator.value==null?-1:Math.floor(e/2)});return S(()=>{syncInputsFromValue(normalizedPropValue())}),b`
|
|
2
2
|
<div class="otp-group" part="group" role="group" aria-label="${r.label}">
|
|
3
|
-
${()=>u.value.map(e=>
|
|
4
|
-
${()=>
|
|
3
|
+
${()=>u.value.map(e=>b`
|
|
4
|
+
${()=>p.value>0&&e===p.value?b`
|
|
5
5
|
<span class="separator" aria-hidden="true">${()=>r.separator.value||`-`}</span>
|
|
6
6
|
`:``}
|
|
7
7
|
<input
|
|
@@ -19,5 +19,5 @@ import{defineFieldValue as e,dispatchNativeFieldEvent as t,setFieldValue as n}fr
|
|
|
19
19
|
@focus="${e=>e.target.select()}" />
|
|
20
20
|
`)}
|
|
21
21
|
</div>
|
|
22
|
-
`},styles:[a,i({}),o(`.cell`),
|
|
22
|
+
`},styles:[a,i({}),o(`.cell`),p,r({accentVar:`--_cell-focus-border`,container:`.cell`,tokenPrefix:`otp-cell`})]});export{E as OTP_INPUT_TAG};
|
|
23
23
|
//# sourceMappingURL=otp-input.js.map
|
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: ({ 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
|
+
{"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 } from '../../types';\nimport { FIELD_VARIANTS, type FieldVariant } 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?: FieldVariant;\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.oneOf(FIELD_VARIANTS),\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,9 +1,12 @@
|
|
|
1
1
|
import { type OverlayOpenChangeDetail } from '../../core';
|
|
2
|
-
import type
|
|
2
|
+
import { type PaddingSize } from '../../types';
|
|
3
3
|
export { ICON_TAG } from '../../content/icon/icon';
|
|
4
4
|
type DialogSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
5
|
-
|
|
6
|
-
type
|
|
5
|
+
declare const DIALOG_BACKDROPS: readonly ["opaque", "blur", "transparent"];
|
|
6
|
+
type DialogBackdrop = (typeof DIALOG_BACKDROPS)[number];
|
|
7
|
+
declare const DIALOG_ELEVATIONS: readonly ["none", "sm", "md", "lg", "xl", "2xl"];
|
|
8
|
+
type DialogElevation = (typeof DIALOG_ELEVATIONS)[number];
|
|
9
|
+
declare const DIALOG_ROUNDED: readonly ["", "none", "sm", "md", "lg", "xl", "2xl", "3xl", "full"];
|
|
7
10
|
/** Dialog component properties */
|
|
8
11
|
export type OreDialogEvents = {
|
|
9
12
|
'open-change': OverlayOpenChangeDetail;
|
|
@@ -37,7 +40,7 @@ export type OreDialogProps = {
|
|
|
37
40
|
*/
|
|
38
41
|
'return-focus'?: boolean;
|
|
39
42
|
/** Border radius */
|
|
40
|
-
rounded?:
|
|
43
|
+
rounded?: (typeof DIALOG_ROUNDED)[number];
|
|
41
44
|
/** Dialog size */
|
|
42
45
|
size?: DialogSize;
|
|
43
46
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/overlay/dialog/dialog.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,uBAAuB,EAAqB,MAAM,YAAY,CAAC;AAE7E,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/overlay/dialog/dialog.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,uBAAuB,EAAqB,MAAM,YAAY,CAAC;AAE7E,OAAO,EAAiB,KAAK,WAAW,EAAiB,MAAM,aAAa,CAAC;AAK7E,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,KAAK,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AACrD,QAAA,MAAM,gBAAgB,4CAA6C,CAAC;AACpE,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AACxD,QAAA,MAAM,iBAAiB,kDAAmD,CAAC;AAC3E,KAAK,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1D,QAAA,MAAM,cAAc,qEAAkC,CAAC;AAEvD,kCAAkC;AAElC,MAAM,MAAM,eAAe,GAAG;IAC5B,aAAa,EAAE,uBAAuB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,oHAAoH;IACpH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,mEAAmE;IACnE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4BAA4B;IAC5B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oBAAoB;IACpB,OAAO,CAAC,EAAE,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;IAC1C,kBAAkB;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,eAAO,MAAM,UAAU,EAAG,YAAqB,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type OverlayOpenChangeDetail } from '../../core';
|
|
2
2
|
export { ICON_TAG } from '../../content/icon/icon';
|
|
3
3
|
type DrawerPlacement = 'left' | 'right' | 'top' | 'bottom';
|
|
4
|
-
|
|
5
|
-
type
|
|
4
|
+
declare const DRAWER_SIZES: readonly ["sm", "lg", "full"];
|
|
5
|
+
type DrawerSize = (typeof DRAWER_SIZES)[number];
|
|
6
|
+
declare const DRAWER_BACKDROPS: readonly ["opaque", "blur", "transparent"];
|
|
7
|
+
type DrawerBackdrop = (typeof DRAWER_BACKDROPS)[number];
|
|
6
8
|
type DrawerDragHandlePlacement = 'outside' | 'inset';
|
|
7
9
|
/** Element interface exposing the imperative API for `ore-drawer`. */
|
|
8
10
|
export interface DrawerElement extends HTMLElement, Omit<OreDrawerProps, 'title'> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../../src/overlay/drawer/drawer.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,uBAAuB,EAAqB,MAAM,YAAY,CAAC;AAM7E,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,KAAK,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAC3D,KAAK,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"drawer.d.ts","sourceRoot":"","sources":["../../../src/overlay/drawer/drawer.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,uBAAuB,EAAqB,MAAM,YAAY,CAAC;AAM7E,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,KAAK,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAC3D,QAAA,MAAM,YAAY,+BAAgC,CAAC;AACnD,KAAK,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAChD,QAAA,MAAM,gBAAgB,4CAA6C,CAAC;AACpE,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AACxD,KAAK,yBAAyB,GAAG,SAAS,GAAG,OAAO,CAAC;AA8BrD,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,mEAAmE;IACnE,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"}
|
|
@@ -4,7 +4,8 @@ export interface MenuSelectDetail {
|
|
|
4
4
|
checked?: boolean;
|
|
5
5
|
value: string;
|
|
6
6
|
}
|
|
7
|
-
export
|
|
7
|
+
export declare const ORE_MENU_ITEM_TYPES: readonly ["checkbox", "radio"];
|
|
8
|
+
export type OreMenuItemType = (typeof ORE_MENU_ITEM_TYPES)[number];
|
|
8
9
|
export type OreMenuEvents = {
|
|
9
10
|
'open-change': OverlayOpenChangeDetail;
|
|
10
11
|
select: MenuSelectDetail;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/overlay/menu/menu.ts"],"names":[],"mappings":"AAgBA,OAAO,EAOL,KAAK,uBAAuB,EAG7B,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,
|
|
1
|
+
{"version":3,"file":"menu.d.ts","sourceRoot":"","sources":["../../../src/overlay/menu/menu.ts"],"names":[],"mappings":"AAgBA,OAAO,EAOL,KAAK,uBAAuB,EAG7B,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,eAAO,MAAM,mBAAmB,gCAAiC,CAAC;AAClE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,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;AAyDtD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAG,oBAA6B,CAAC;AAa3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,QAAQ,EAAG,UAAmB,CAAC"}
|
package/dist/pagination.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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("./shared/prop-bundles.cjs"),
|
|
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("./shared/types.cjs"),i=require("./shared/prop-bundles.cjs"),a=require("./icon.cjs"),o=require("./button.cjs"),s=require("./content/pagination/pagination.css.cjs");let c=require("@vielzeug/ripple"),l=require("@vielzeug/ore");function buildPageRange(e,t,n){let r=t,i=[];if(r<=2+2*n+3)return Array.from({length:r},(e,t)=>t+1);let a=Math.max(e-n,2),o=Math.min(e+n,r-1);i.push(1),a>3?i.push(`ellipsis-start`):a===3&&i.push(2);for(let e=a;e<=o;e++)i.push(e);return o<r-1-1?i.push(`ellipsis-end`):o===r-1-1&&i.push(r-1),i.push(r),i}var u=`ore-pagination`;(0,l.define)(u,{props:{...i.themableBundle,...i.sizableBundle,label:l.prop.string(`Pagination`),page:l.prop.number(1),"show-first-last":l.prop.bool(!1),"show-prev-next":l.prop.bool(!1),siblings:l.prop.number(1),"total-pages":l.prop.number(1),variant:l.prop.oneOf(r.VISUAL_VARIANTS)},setup(e){let t=(0,l.getHost)(),n=(0,l.useEmit)();function goTo(r){let i=e[`total-pages`].value||1,a=Math.min(Math.max(1,r),i);a!==e.page.value&&(t.setAttribute(`page`,String(a)),n(`change`,{page:a}))}let r=(0,c.computed)(()=>buildPageRange(e.page.value||1,e[`total-pages`].value||1,e.siblings.value??1)),i=(0,c.computed)(()=>(e.page.value||1)<=1),a=(0,c.computed)(()=>(e.page.value||1)>=(e[`total-pages`].value||1));return l.html`
|
|
2
2
|
<nav aria-label="${e.label}" part="nav">
|
|
3
3
|
<ol class="pagination" part="list">
|
|
4
|
-
${()=>e[`show-first-last`].value?
|
|
4
|
+
${()=>e[`show-first-last`].value?l.html`
|
|
5
5
|
<li>
|
|
6
6
|
<button
|
|
7
7
|
type="button"
|
|
@@ -14,7 +14,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
14
14
|
</button>
|
|
15
15
|
</li>
|
|
16
16
|
`:``}
|
|
17
|
-
${()=>e[`show-prev-next`].value?
|
|
17
|
+
${()=>e[`show-prev-next`].value?l.html`
|
|
18
18
|
<li>
|
|
19
19
|
<button
|
|
20
20
|
type="button"
|
|
@@ -28,9 +28,9 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
28
28
|
</li>
|
|
29
29
|
`:``}
|
|
30
30
|
<li style="display: contents;">
|
|
31
|
-
${()=>r.value.map(t=>{if(t===`ellipsis-start`||t===`ellipsis-end`)return
|
|
31
|
+
${()=>r.value.map(t=>{if(t===`ellipsis-start`||t===`ellipsis-end`)return l.html`
|
|
32
32
|
<span class="ellipsis" aria-hidden="true">…</span>
|
|
33
|
-
`;let n=t,r=n===(e.page.value||1),i=`Page ${n}`;return r?
|
|
33
|
+
`;let n=t,r=n===(e.page.value||1),i=`Page ${n}`;return r?l.html`
|
|
34
34
|
<button
|
|
35
35
|
type="button"
|
|
36
36
|
part="page-btn"
|
|
@@ -39,13 +39,13 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
39
39
|
@click=${()=>goTo(n)}>
|
|
40
40
|
${n}
|
|
41
41
|
</button>
|
|
42
|
-
`:
|
|
42
|
+
`:l.html`
|
|
43
43
|
<button type="button" part="page-btn" aria-label="${i}" @click=${()=>goTo(n)}>
|
|
44
44
|
${n}
|
|
45
45
|
</button>
|
|
46
46
|
`})}
|
|
47
47
|
</li>
|
|
48
|
-
${()=>e[`show-prev-next`].value?
|
|
48
|
+
${()=>e[`show-prev-next`].value?l.html`
|
|
49
49
|
<li>
|
|
50
50
|
<button
|
|
51
51
|
type="button"
|
|
@@ -58,7 +58,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
58
58
|
</button>
|
|
59
59
|
</li>
|
|
60
60
|
`:``}
|
|
61
|
-
${()=>e[`show-first-last`].value?
|
|
61
|
+
${()=>e[`show-first-last`].value?l.html`
|
|
62
62
|
<li>
|
|
63
63
|
<button
|
|
64
64
|
type="button"
|
|
@@ -73,5 +73,5 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
73
73
|
`:``}
|
|
74
74
|
</ol>
|
|
75
75
|
</nav>
|
|
76
|
-
`},styles:[e.coarsePointerMixin,n.colorThemeMixin,t.sizeVariantMixin(),
|
|
76
|
+
`},styles:[e.coarsePointerMixin,n.colorThemeMixin,t.sizeVariantMixin(),s.default]}),exports.BUTTON_TAG=o.BUTTON_TAG,exports.ICON_TAG=a.ICON_TAG,exports.PAGINATION_TAG=u;
|
|
77
77
|
//# sourceMappingURL=pagination.cjs.map
|
package/dist/pagination.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.cjs","names":[],"sources":["../src/content/pagination/pagination.ts"],"sourcesContent":["import { define, getHost, html, prop, useEmit } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\n\nimport { sizableBundle, themableBundle } from '../../shared';\nimport { coarsePointerMixin, colorThemeMixin, sizeVariantMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor, VisualVariant } from '../../types';\nimport componentStyles from './pagination.css?inline';\n\nexport { BUTTON_TAG } from '../../inputs/button/button';\nexport { ICON_TAG } from '../icon/icon';\nexport type OrePaginationEvents = {\n change: { page: number };\n};\n\nexport type OrePaginationProps = {\n color?: ThemeColor;\n label?: string;\n page?: number;\n 'show-first-last'?: boolean;\n 'show-prev-next'?: boolean;\n siblings?: number;\n size?: ComponentSize;\n 'total-pages'?: number;\n variant?: VisualVariant;\n};\n\nfunction buildPageRange(\n currentPage: number,\n totalPages: number,\n siblings: number,\n): Array<number | 'ellipsis-start' | 'ellipsis-end'> {\n const BOUNDARY = 1; // always show first and last page\n const total = totalPages;\n const pages: Array<number | 'ellipsis-start' | 'ellipsis-end'> = [];\n\n // If total is small enough, show all pages\n if (total <= 2 * BOUNDARY + 2 * siblings + 3) {\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const leftSibling = Math.max(currentPage - siblings, BOUNDARY + 1);\n const rightSibling = Math.min(currentPage + siblings, total - BOUNDARY);\n\n pages.push(1);\n\n if (leftSibling > BOUNDARY + 2) pages.push('ellipsis-start');\n else if (leftSibling === BOUNDARY + 2) pages.push(BOUNDARY + 1);\n\n for (let i = leftSibling; i <= rightSibling; i++) pages.push(i);\n\n if (rightSibling < total - BOUNDARY - 1) pages.push('ellipsis-end');\n else if (rightSibling === total - BOUNDARY - 1) pages.push(total - BOUNDARY);\n\n pages.push(total);\n\n return pages;\n}\n\n/**\n * Page-based navigation control.\n *\n * @element ore-pagination\n *\n * @attr {number} page - Current page (1-indexed, default: 1)\n * @attr {number} total-pages - Total number of pages (required)\n * @attr {number} siblings - Sibling pages around current (default: 1)\n * @attr {boolean} show-first-last - Show first/last page buttons (default: false)\n * @attr {boolean} show-prev-next - Show prev/next buttons (default: false)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant for nav buttons: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'frost' (default: 'ghost')\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} label - Accessible nav label (default: 'Pagination')\n *\n * @fires change - Emitted when the page changes, with { page: number }\n *\n * @cssprop --pagination-item-size - Width/height of each item\n * @cssprop --pagination-gap - Gap between items\n * @cssprop --pagination-radius - Border radius of items\n *\n * @part page-btn - Page button.\n * @part nav - Navigation container.\n * @part list - List container.\n * @part first-btn - First page button.\n * @part prev-btn - Previous page button.\n * @part next-btn - Next page button.\n * @part last-btn - Last page button.\n * @example\n * ```html\n * <!-- Basic -->\n * <ore-pagination page=\"3\" total-pages=\"10\" color=\"primary\"></ore-pagination>\n *\n * <!-- With prev/next and first/last buttons -->\n * <ore-pagination page=\"5\" total-pages=\"20\" show-prev-next show-first-last siblings=\"2\"></ore-pagination>\n *\n * <!-- Compact size -->\n * <ore-pagination page=\"1\" total-pages=\"5\" size=\"sm\" variant=\"ghost\"></ore-pagination>\n * ```\n */\nexport const PAGINATION_TAG = 'ore-pagination' as const;\ndefine<OrePaginationProps>(PAGINATION_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n label: prop.string('Pagination'),\n page: prop.number(1),\n 'show-first-last': prop.bool(false),\n 'show-prev-next': prop.bool(false),\n siblings: prop.number(1),\n 'total-pages': prop.number(1),\n variant: prop.string<VisualVariant>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OrePaginationEvents>();\n\n function goTo(page: number) {\n const total = props['total-pages'].value || 1;\n const next = Math.min(Math.max(1, page), total);\n\n if (next === props.page.value) return;\n\n el.setAttribute('page', String(next));\n emit('change', { page: next });\n }\n\n const pageItems = computed(() =>\n buildPageRange(props.page.value || 1, props['total-pages'].value || 1, props.siblings.value ?? 1),\n );\n\n const isFirst = computed(() => (props.page.value || 1) <= 1);\n const isLast = computed(() => (props.page.value || 1) >= (props['total-pages'].value || 1));\n\n return html`\n <nav aria-label=\"${props.label}\" part=\"nav\">\n <ol class=\"pagination\" part=\"list\">\n ${() =>\n props['show-first-last'].value\n ? html`\n <li>\n <button\n type=\"button\"\n class=\"nav-btn\"\n part=\"first-btn\"\n aria-label=\"First page\"\n ?disabled=${isFirst}\n @click=${() => goTo(1)}>\n <ore-icon name=\"chevrons-left\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : ''}\n ${() =>\n props['show-prev-next'].value\n ? html`\n <li>\n <button\n type=\"button\"\n class=\"nav-btn\"\n part=\"prev-btn\"\n aria-label=\"Previous page\"\n ?disabled=${isFirst}\n @click=${() => goTo((props.page.value || 1) - 1)}>\n <ore-icon name=\"chevron-left\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : ''}\n <li style=\"display: contents;\">\n ${() =>\n pageItems.value.map((item) => {\n if (item === 'ellipsis-start' || item === 'ellipsis-end') {\n return html`\n <span class=\"ellipsis\" aria-hidden=\"true\">…</span>\n `;\n }\n\n const pg = item as number;\n const isCurrent = pg === (props.page.value || 1);\n const pgLabel = `Page ${pg}`;\n\n return isCurrent\n ? html`\n <button\n type=\"button\"\n part=\"page-btn\"\n aria-label=\"${pgLabel}\"\n aria-current=\"page\"\n @click=${() => goTo(pg)}>\n ${pg}\n </button>\n `\n : html`\n <button type=\"button\" part=\"page-btn\" aria-label=\"${pgLabel}\" @click=${() => goTo(pg)}>\n ${pg}\n </button>\n `;\n })}\n </li>\n ${() =>\n props['show-prev-next'].value\n ? html`\n <li>\n <button\n type=\"button\"\n class=\"nav-btn\"\n part=\"next-btn\"\n aria-label=\"Next page\"\n ?disabled=${isLast}\n @click=${() => goTo((props.page.value || 1) + 1)}>\n <ore-icon name=\"chevron-right\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : ''}\n ${() =>\n props['show-first-last'].value\n ? html`\n <li>\n <button\n type=\"button\"\n class=\"nav-btn\"\n part=\"last-btn\"\n aria-label=\"Last page\"\n ?disabled=${isLast}\n @click=${() => goTo(props['total-pages'].value || 1)}>\n <ore-icon name=\"chevrons-right\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : ''}\n </ol>\n </nav>\n `;\n },\n\n styles: [coarsePointerMixin, colorThemeMixin, sizeVariantMixin(), componentStyles],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pagination.cjs","names":[],"sources":["../src/content/pagination/pagination.ts"],"sourcesContent":["import { define, getHost, html, prop, useEmit } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\n\nimport { sizableBundle, themableBundle } from '../../shared';\nimport { coarsePointerMixin, colorThemeMixin, sizeVariantMixin } from '../../styles';\nimport { type ComponentSize, type ThemeColor, VISUAL_VARIANTS, type VisualVariant } from '../../types';\nimport componentStyles from './pagination.css?inline';\n\nexport { BUTTON_TAG } from '../../inputs/button/button';\nexport { ICON_TAG } from '../icon/icon';\nexport type OrePaginationEvents = {\n change: { page: number };\n};\n\nexport type OrePaginationProps = {\n color?: ThemeColor;\n label?: string;\n page?: number;\n 'show-first-last'?: boolean;\n 'show-prev-next'?: boolean;\n siblings?: number;\n size?: ComponentSize;\n 'total-pages'?: number;\n variant?: VisualVariant;\n};\n\nfunction buildPageRange(\n currentPage: number,\n totalPages: number,\n siblings: number,\n): Array<number | 'ellipsis-start' | 'ellipsis-end'> {\n const BOUNDARY = 1; // always show first and last page\n const total = totalPages;\n const pages: Array<number | 'ellipsis-start' | 'ellipsis-end'> = [];\n\n // If total is small enough, show all pages\n if (total <= 2 * BOUNDARY + 2 * siblings + 3) {\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const leftSibling = Math.max(currentPage - siblings, BOUNDARY + 1);\n const rightSibling = Math.min(currentPage + siblings, total - BOUNDARY);\n\n pages.push(1);\n\n if (leftSibling > BOUNDARY + 2) pages.push('ellipsis-start');\n else if (leftSibling === BOUNDARY + 2) pages.push(BOUNDARY + 1);\n\n for (let i = leftSibling; i <= rightSibling; i++) pages.push(i);\n\n if (rightSibling < total - BOUNDARY - 1) pages.push('ellipsis-end');\n else if (rightSibling === total - BOUNDARY - 1) pages.push(total - BOUNDARY);\n\n pages.push(total);\n\n return pages;\n}\n\n/**\n * Page-based navigation control.\n *\n * @element ore-pagination\n *\n * @attr {number} page - Current page (1-indexed, default: 1)\n * @attr {number} total-pages - Total number of pages (required)\n * @attr {number} siblings - Sibling pages around current (default: 1)\n * @attr {boolean} show-first-last - Show first/last page buttons (default: false)\n * @attr {boolean} show-prev-next - Show prev/next buttons (default: false)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant for nav buttons: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'frost' (default: 'ghost')\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} label - Accessible nav label (default: 'Pagination')\n *\n * @fires change - Emitted when the page changes, with { page: number }\n *\n * @cssprop --pagination-item-size - Width/height of each item\n * @cssprop --pagination-gap - Gap between items\n * @cssprop --pagination-radius - Border radius of items\n *\n * @part page-btn - Page button.\n * @part nav - Navigation container.\n * @part list - List container.\n * @part first-btn - First page button.\n * @part prev-btn - Previous page button.\n * @part next-btn - Next page button.\n * @part last-btn - Last page button.\n * @example\n * ```html\n * <!-- Basic -->\n * <ore-pagination page=\"3\" total-pages=\"10\" color=\"primary\"></ore-pagination>\n *\n * <!-- With prev/next and first/last buttons -->\n * <ore-pagination page=\"5\" total-pages=\"20\" show-prev-next show-first-last siblings=\"2\"></ore-pagination>\n *\n * <!-- Compact size -->\n * <ore-pagination page=\"1\" total-pages=\"5\" size=\"sm\" variant=\"ghost\"></ore-pagination>\n * ```\n */\nexport const PAGINATION_TAG = 'ore-pagination' as const;\ndefine<OrePaginationProps>(PAGINATION_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n label: prop.string('Pagination'),\n page: prop.number(1),\n 'show-first-last': prop.bool(false),\n 'show-prev-next': prop.bool(false),\n siblings: prop.number(1),\n 'total-pages': prop.number(1),\n variant: prop.oneOf(VISUAL_VARIANTS),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OrePaginationEvents>();\n\n function goTo(page: number) {\n const total = props['total-pages'].value || 1;\n const next = Math.min(Math.max(1, page), total);\n\n if (next === props.page.value) return;\n\n el.setAttribute('page', String(next));\n emit('change', { page: next });\n }\n\n const pageItems = computed(() =>\n buildPageRange(props.page.value || 1, props['total-pages'].value || 1, props.siblings.value ?? 1),\n );\n\n const isFirst = computed(() => (props.page.value || 1) <= 1);\n const isLast = computed(() => (props.page.value || 1) >= (props['total-pages'].value || 1));\n\n return html`\n <nav aria-label=\"${props.label}\" part=\"nav\">\n <ol class=\"pagination\" part=\"list\">\n ${() =>\n props['show-first-last'].value\n ? html`\n <li>\n <button\n type=\"button\"\n class=\"nav-btn\"\n part=\"first-btn\"\n aria-label=\"First page\"\n ?disabled=${isFirst}\n @click=${() => goTo(1)}>\n <ore-icon name=\"chevrons-left\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : ''}\n ${() =>\n props['show-prev-next'].value\n ? html`\n <li>\n <button\n type=\"button\"\n class=\"nav-btn\"\n part=\"prev-btn\"\n aria-label=\"Previous page\"\n ?disabled=${isFirst}\n @click=${() => goTo((props.page.value || 1) - 1)}>\n <ore-icon name=\"chevron-left\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : ''}\n <li style=\"display: contents;\">\n ${() =>\n pageItems.value.map((item) => {\n if (item === 'ellipsis-start' || item === 'ellipsis-end') {\n return html`\n <span class=\"ellipsis\" aria-hidden=\"true\">…</span>\n `;\n }\n\n const pg = item as number;\n const isCurrent = pg === (props.page.value || 1);\n const pgLabel = `Page ${pg}`;\n\n return isCurrent\n ? html`\n <button\n type=\"button\"\n part=\"page-btn\"\n aria-label=\"${pgLabel}\"\n aria-current=\"page\"\n @click=${() => goTo(pg)}>\n ${pg}\n </button>\n `\n : html`\n <button type=\"button\" part=\"page-btn\" aria-label=\"${pgLabel}\" @click=${() => goTo(pg)}>\n ${pg}\n </button>\n `;\n })}\n </li>\n ${() =>\n props['show-prev-next'].value\n ? html`\n <li>\n <button\n type=\"button\"\n class=\"nav-btn\"\n part=\"next-btn\"\n aria-label=\"Next page\"\n ?disabled=${isLast}\n @click=${() => goTo((props.page.value || 1) + 1)}>\n <ore-icon name=\"chevron-right\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : ''}\n ${() =>\n props['show-first-last'].value\n ? html`\n <li>\n <button\n type=\"button\"\n class=\"nav-btn\"\n part=\"last-btn\"\n aria-label=\"Last page\"\n ?disabled=${isLast}\n @click=${() => goTo(props['total-pages'].value || 1)}>\n <ore-icon name=\"chevrons-right\" size=\"16\" aria-hidden=\"true\"></ore-icon>\n </button>\n </li>\n `\n : ''}\n </ol>\n </nav>\n `;\n },\n\n styles: [coarsePointerMixin, colorThemeMixin, sizeVariantMixin(), componentStyles],\n});\n"],"mappings":"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"}
|