@vielzeug/refine 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/dist/accordion-item.cjs +1 -1
  2. package/dist/accordion-item.cjs.map +1 -1
  3. package/dist/accordion-item.js +1 -1
  4. package/dist/accordion-item.js.map +1 -1
  5. package/dist/card.cjs +1 -1
  6. package/dist/card.cjs.map +1 -1
  7. package/dist/card.js +1 -1
  8. package/dist/card.js.map +1 -1
  9. package/dist/chat-message.cjs +1 -1
  10. package/dist/chat-message.cjs.map +1 -1
  11. package/dist/chat-message.js +1 -1
  12. package/dist/chat-message.js.map +1 -1
  13. package/dist/combobox.cjs +9 -7
  14. package/dist/combobox.cjs.map +1 -1
  15. package/dist/combobox.js +55 -48
  16. package/dist/combobox.js.map +1 -1
  17. package/dist/content/index.d.ts +2 -0
  18. package/dist/content/index.d.ts.map +1 -1
  19. package/dist/content/list/list-item.css.cjs +1 -1
  20. package/dist/content/list/list-item.css.cjs.map +1 -1
  21. package/dist/content/list/list-item.css.js +1 -1
  22. package/dist/content/list/list-item.css.js.map +1 -1
  23. package/dist/content/list/list-item.d.ts +9 -0
  24. package/dist/content/list/list-item.d.ts.map +1 -1
  25. package/dist/content/stats/stats.css.cjs +2 -0
  26. package/dist/content/stats/stats.css.cjs.map +1 -0
  27. package/dist/content/stats/stats.css.js +6 -0
  28. package/dist/content/stats/stats.css.js.map +1 -0
  29. package/dist/content/stats/stats.d.ts +72 -0
  30. package/dist/content/stats/stats.d.ts.map +1 -0
  31. package/dist/core/auto-resize.cjs +1 -1
  32. package/dist/core/auto-resize.cjs.map +1 -1
  33. package/dist/core/auto-resize.js +1 -1
  34. package/dist/core/auto-resize.js.map +1 -1
  35. package/dist/custom-elements.json +5087 -4791
  36. package/dist/datagrid.cjs +199 -179
  37. package/dist/datagrid.cjs.map +1 -1
  38. package/dist/datagrid.js +435 -321
  39. package/dist/datagrid.js.map +1 -1
  40. package/dist/disclosure/tab-item/tab-item.css.cjs +1 -1
  41. package/dist/disclosure/tab-item/tab-item.css.cjs.map +1 -1
  42. package/dist/disclosure/tab-item/tab-item.css.js +1 -1
  43. package/dist/disclosure/tab-item/tab-item.css.js.map +1 -1
  44. package/dist/disclosure/tab-item/tab-item.d.ts +3 -0
  45. package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
  46. package/dist/disclosure/tabs/tabs.css.cjs +1 -1
  47. package/dist/disclosure/tabs/tabs.css.cjs.map +1 -1
  48. package/dist/disclosure/tabs/tabs.css.js +1 -1
  49. package/dist/disclosure/tabs/tabs.css.js.map +1 -1
  50. package/dist/disclosure/tabs/tabs.d.ts +4 -0
  51. package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
  52. package/dist/frameworks/elements.d.ts +2 -1
  53. package/dist/icon.cjs +1 -1
  54. package/dist/icon.cjs.map +1 -1
  55. package/dist/icon.js +1 -1
  56. package/dist/icon.js.map +1 -1
  57. package/dist/input.cjs +5 -4
  58. package/dist/input.cjs.map +1 -1
  59. package/dist/input.js +14 -8
  60. package/dist/input.js.map +1 -1
  61. package/dist/inputs/combobox/combobox-options.cjs +1 -1
  62. package/dist/inputs/combobox/combobox-options.cjs.map +1 -1
  63. package/dist/inputs/combobox/combobox-options.d.ts.map +1 -1
  64. package/dist/inputs/combobox/combobox-options.js +1 -0
  65. package/dist/inputs/combobox/combobox-options.js.map +1 -1
  66. package/dist/inputs/combobox/combobox.css.cjs +1 -1
  67. package/dist/inputs/combobox/combobox.css.cjs.map +1 -1
  68. package/dist/inputs/combobox/combobox.css.js +1 -1
  69. package/dist/inputs/combobox/combobox.css.js.map +1 -1
  70. package/dist/inputs/combobox/combobox.d.ts +1 -0
  71. package/dist/inputs/combobox/combobox.d.ts.map +1 -1
  72. package/dist/inputs/combobox/combobox.types.d.ts +7 -0
  73. package/dist/inputs/combobox/combobox.types.d.ts.map +1 -1
  74. package/dist/inputs/datagrid/datagrid-model.cjs +1 -1
  75. package/dist/inputs/datagrid/datagrid-model.cjs.map +1 -1
  76. package/dist/inputs/datagrid/datagrid-model.d.ts +17 -3
  77. package/dist/inputs/datagrid/datagrid-model.d.ts.map +1 -1
  78. package/dist/inputs/datagrid/datagrid-model.js +93 -70
  79. package/dist/inputs/datagrid/datagrid-model.js.map +1 -1
  80. package/dist/inputs/datagrid/datagrid.css.cjs +1 -1
  81. package/dist/inputs/datagrid/datagrid.css.cjs.map +1 -1
  82. package/dist/inputs/datagrid/datagrid.css.js +1 -1
  83. package/dist/inputs/datagrid/datagrid.css.js.map +1 -1
  84. package/dist/inputs/datagrid/datagrid.d.ts +54 -4
  85. package/dist/inputs/datagrid/datagrid.d.ts.map +1 -1
  86. package/dist/inputs/index.d.ts +1 -1
  87. package/dist/inputs/index.d.ts.map +1 -1
  88. package/dist/inputs/input/input.css.cjs +1 -1
  89. package/dist/inputs/input/input.css.cjs.map +1 -1
  90. package/dist/inputs/input/input.css.js +1 -1
  91. package/dist/inputs/input/input.css.js.map +1 -1
  92. package/dist/inputs/input/input.d.ts +3 -0
  93. package/dist/inputs/input/input.d.ts.map +1 -1
  94. package/dist/inputs/select/select.css.cjs +1 -1
  95. package/dist/inputs/select/select.css.cjs.map +1 -1
  96. package/dist/inputs/select/select.css.js +1 -1
  97. package/dist/inputs/select/select.css.js.map +1 -1
  98. package/dist/inputs/select/select.d.ts +5 -0
  99. package/dist/inputs/select/select.d.ts.map +1 -1
  100. package/dist/layout/navbar/navbar.d.ts +2 -0
  101. package/dist/layout/navbar/navbar.d.ts.map +1 -1
  102. package/dist/layout/sidebar/sidebar.css.cjs +1 -1
  103. package/dist/layout/sidebar/sidebar.css.cjs.map +1 -1
  104. package/dist/layout/sidebar/sidebar.css.js +1 -1
  105. package/dist/layout/sidebar/sidebar.css.js.map +1 -1
  106. package/dist/layout/sidebar/sidebar.d.ts +8 -0
  107. package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
  108. package/dist/list-item.cjs +6 -5
  109. package/dist/list-item.cjs.map +1 -1
  110. package/dist/list-item.js +23 -17
  111. package/dist/list-item.js.map +1 -1
  112. package/dist/list.cjs +1 -1
  113. package/dist/list.cjs.map +1 -1
  114. package/dist/list.js +1 -1
  115. package/dist/list.js.map +1 -1
  116. package/dist/menu.cjs +1 -1
  117. package/dist/menu.cjs.map +1 -1
  118. package/dist/menu.js +1 -1
  119. package/dist/menu.js.map +1 -1
  120. package/dist/navbar.cjs +2 -2
  121. package/dist/navbar.cjs.map +1 -1
  122. package/dist/navbar.js +7 -5
  123. package/dist/navbar.js.map +1 -1
  124. package/dist/navigation-menu.cjs +1 -1
  125. package/dist/navigation-menu.cjs.map +1 -1
  126. package/dist/navigation-menu.js +1 -1
  127. package/dist/navigation-menu.js.map +1 -1
  128. package/dist/overlay/popover/popover.css.cjs +1 -1
  129. package/dist/overlay/popover/popover.css.cjs.map +1 -1
  130. package/dist/overlay/popover/popover.css.js +1 -1
  131. package/dist/overlay/popover/popover.css.js.map +1 -1
  132. package/dist/overlay/popover/popover.d.ts +1 -0
  133. package/dist/overlay/popover/popover.d.ts.map +1 -1
  134. package/dist/overlay/shared/use-dialog.cjs +1 -1
  135. package/dist/overlay/shared/use-dialog.cjs.map +1 -1
  136. package/dist/overlay/shared/use-dialog.js +1 -1
  137. package/dist/overlay/shared/use-dialog.js.map +1 -1
  138. package/dist/overlay/shared/use-floating-trigger.cjs +1 -1
  139. package/dist/overlay/shared/use-floating-trigger.cjs.map +1 -1
  140. package/dist/overlay/shared/use-floating-trigger.js +1 -1
  141. package/dist/overlay/shared/use-floating-trigger.js.map +1 -1
  142. package/dist/popover.cjs.map +1 -1
  143. package/dist/popover.js.map +1 -1
  144. package/dist/radio.cjs +1 -1
  145. package/dist/radio.cjs.map +1 -1
  146. package/dist/radio.js +10 -8
  147. package/dist/radio.js.map +1 -1
  148. package/dist/rating.cjs +1 -1
  149. package/dist/rating.cjs.map +1 -1
  150. package/dist/rating.js +1 -1
  151. package/dist/rating.js.map +1 -1
  152. package/dist/refine.cjs +372 -323
  153. package/dist/refine.cjs.map +1 -1
  154. package/dist/refine.iife.js +372 -323
  155. package/dist/refine.iife.js.map +1 -1
  156. package/dist/refine.js +402 -353
  157. package/dist/refine.js.map +1 -1
  158. package/dist/select.cjs +16 -14
  159. package/dist/select.cjs.map +1 -1
  160. package/dist/select.js +118 -112
  161. package/dist/select.js.map +1 -1
  162. package/dist/sidebar.cjs +28 -23
  163. package/dist/sidebar.cjs.map +1 -1
  164. package/dist/sidebar.js +112 -77
  165. package/dist/sidebar.js.map +1 -1
  166. package/dist/slider.cjs +1 -1
  167. package/dist/slider.cjs.map +1 -1
  168. package/dist/slider.js +1 -1
  169. package/dist/slider.js.map +1 -1
  170. package/dist/stats.cjs +18 -0
  171. package/dist/stats.cjs.map +1 -0
  172. package/dist/stats.js +88 -0
  173. package/dist/stats.js.map +1 -0
  174. package/dist/stepper.cjs +1 -1
  175. package/dist/stepper.cjs.map +1 -1
  176. package/dist/stepper.js +1 -1
  177. package/dist/stepper.js.map +1 -1
  178. package/dist/styles/mixins/animation.css.cjs +2 -0
  179. package/dist/styles/mixins/animation.css.cjs.map +1 -1
  180. package/dist/styles/mixins/animation.css.d.ts.map +1 -1
  181. package/dist/styles/mixins/animation.css.js +2 -0
  182. package/dist/styles/mixins/animation.css.js.map +1 -1
  183. package/dist/tab-item.cjs +1 -1
  184. package/dist/tab-item.cjs.map +1 -1
  185. package/dist/tab-item.js +3 -2
  186. package/dist/tab-item.js.map +1 -1
  187. package/dist/tabs.cjs +1 -1
  188. package/dist/tabs.cjs.map +1 -1
  189. package/dist/tabs.js +3 -1
  190. package/dist/tabs.js.map +1 -1
  191. package/dist/toast.cjs.map +1 -1
  192. package/dist/toast.js.map +1 -1
  193. package/package.json +64 -56
@@ -1 +1 @@
1
- {"version":3,"file":"stepper.js","names":[],"sources":["../src/content/stepper/stepper.ts"],"sourcesContent":["import {\n bind,\n createContext,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n provide,\n useEmit,\n useSlots,\n} from '@vielzeug/ore';\nimport { computed, type Readable, signal, watch } from '@vielzeug/ripple';\nimport { createInteraction, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport { isStepNavigable } from './_is-step-navigable';\nimport componentStyles from './stepper.css?inline';\n\n// ── Context ─────────────────────────────────────────────────────────────────\n//\n// ore-step derives all of its parent-relative state (current/completed/navigable/\n// index/total/color/size/orientation) from this context instead of ore-stepper\n// pushing 8 attributes onto every child on every change. Matches the\n// provide()/inject() coordination already used by ore-tabs/ore-tab-item,\n// ore-radio-group/ore-radio, and ore-list/ore-list-item — ore-stepper was\n// previously the one outlier doing manual `querySelectorAll` + `setAttribute`\n// fan-out, which meant every reactive change re-walked and re-wrote every step\n// regardless of whether that step's own state actually changed.\n\n/** Context provided by ore-stepper to its ore-step children. */\nexport type StepperContext = {\n clickable: Readable<boolean>;\n color: Readable<ThemeColor | undefined>;\n currentValue: Readable<string | undefined>;\n linear: Readable<boolean>;\n orientation: Readable<'horizontal' | 'vertical'>;\n size: Readable<ComponentSize | undefined>;\n /** Ordered `value`s of every sibling `ore-step`, used to derive index/total/completed. */\n stepValues: Readable<string[]>;\n};\n/** Injection key for the stepper context. */\nexport const STEPPER_CTX = createContext<StepperContext>('StepperContext');\n\nexport type OreStepperEvents = {\n change: { value: string };\n};\n\nexport type OreStepperProps = {\n /** When true, steps are clickable/keyboard-focusable for navigation. Default: display-only progress. */\n clickable?: boolean;\n /** Theme color for the current/completed step indicators */\n color?: ThemeColor;\n /** Disables the whole stepper — no step is navigable regardless of `clickable` */\n disabled?: boolean;\n /** Accessible label for the nav landmark */\n label?: string;\n /**\n * Restricts navigation to completed steps and the current step — steps ahead of the\n * current one cannot be clicked or focused, even when `clickable` is set.\n */\n linear?: boolean;\n /** Layout orientation — 'horizontal' (default, desktop) or 'vertical' (compact/mobile-friendly) */\n orientation?: 'horizontal' | 'vertical';\n /** Component size */\n size?: ComponentSize;\n /** The `value` of the currently active `ore-step` */\n value?: string;\n};\n\n/**\n * Displays progress through a sequence of numbered steps. Manages step selection and provides\n * shared state (current value, clickability, theming) to its `ore-step` children via context.\n * Can be purely informational (progress display) or interactive navigation.\n *\n * @element ore-stepper\n * @element ore-step - Child element for each step (auto-discovered)\n *\n * @attr {string} value - The value of the currently active step\n * @attr {boolean} clickable - Allow clicking/focusing steps to navigate\n * @attr {boolean} linear - Restrict navigation to completed + current steps only\n * @attr {boolean} disabled - Disables the whole stepper\n * @attr {string} orientation - 'horizontal' (default) | 'vertical'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n * @attr {string} label - Accessible nav landmark label (default: 'Progress')\n *\n * @fires change - Emitted when the active step changes via click/keyboard. detail: { value: string }\n *\n * @slot - `ore-step` elements\n *\n * @cssprop --stepper-connector-color - Color of the connector line between steps\n * @cssprop --stepper-connector-size - Thickness of the connector line\n * @cssprop --stepper-gap - Gap between steps\n *\n * @part nav - Navigation landmark element\n * @part list - Ordered list container holding the slotted steps\n *\n * @example\n * ```html\n * <!-- Display-only progress -->\n * <ore-stepper value=\"shipping\" color=\"primary\">\n * <ore-step value=\"cart\">Cart</ore-step>\n * <ore-step value=\"shipping\">Shipping</ore-step>\n * <ore-step value=\"payment\">Payment</ore-step>\n * </ore-stepper>\n *\n * <!-- Clickable navigation, mobile-friendly vertical layout -->\n * <ore-stepper value=\"shipping\" clickable linear orientation=\"vertical\">\n * <ore-step value=\"cart\">Cart</ore-step>\n * <ore-step value=\"shipping\">Shipping</ore-step>\n * <ore-step value=\"payment\" disabled>Payment</ore-step>\n * </ore-stepper>\n * ```\n */\nexport const STEPPER_TAG = 'ore-stepper' as const;\ndefine<OreStepperProps>(STEPPER_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n clickable: prop.bool(false),\n label: prop.string('Progress'),\n linear: prop.bool(false),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'horizontal'),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreStepperEvents>();\n const slots = useSlots();\n\n const getSteps = (): HTMLElement[] => Array.from(el.querySelectorAll(':scope > ore-step')) as HTMLElement[];\n\n const focusStep = (step: HTMLElement | undefined) => {\n step?.shadowRoot?.querySelector<HTMLElement>('button.control')?.focus();\n };\n\n // ────────────────────────────────────────────────────────────────\n // Selection State — mirrors ore-tabs' `selectedValue` signal + `ensureSelection()` pattern\n // ────────────────────────────────────────────────────────────────\n\n const currentValue = signal<string | undefined>(props.value.value);\n\n bind({ attr: { value: () => currentValue.value ?? null } });\n\n const setSelection = (value: string | undefined, shouldEmit = false) => {\n if (!value || value === currentValue.value) return;\n\n currentValue.value = value;\n\n if (shouldEmit) emit('change', { value });\n };\n\n const ensureSelection = () => {\n const steps = getSteps();\n\n if (steps.length === 0) return;\n\n const current = currentValue.value;\n const hasCurrent = current ? steps.some((s) => s.getAttribute('value') === current) : false;\n\n if (hasCurrent) return;\n\n const firstEnabled = steps.find((s) => !s.hasAttribute('disabled'))?.getAttribute('value') ?? undefined;\n\n if (firstEnabled) setSelection(firstEnabled);\n };\n\n watch(props.value, (value) => {\n currentValue.value = value;\n ensureSelection();\n });\n\n // ────────────────────────────────────────────────────────────────\n // Context provided to ore-step children\n // ────────────────────────────────────────────────────────────────\n\n const stepValues = computed(() => {\n void slots.elements().value;\n\n return getSteps().map((s) => s.getAttribute('value') ?? '');\n });\n\n provide(STEPPER_CTX, {\n clickable: computed(() => Boolean(props.clickable.value) && !props.disabled.value),\n color: props.color,\n currentValue,\n linear: computed(() => Boolean(props.linear.value)),\n orientation: computed(() => props.orientation.value ?? 'horizontal'),\n size: props.size,\n stepValues,\n });\n\n // Deferred to `onMounted()` (mirrors ore-tabs' `ensureSelection()` timing) rather than an\n // immediate `watch(stepValues, ...)` — `getSteps()` walks the *live* light DOM, and while the\n // browser is still parsing this element's `ore-step` children (synchronously upgrading each\n // one as its own tag is reached, which happens whenever `customElements.define()` already\n // ran before this markup was parsed — exactly what a sandboxed live-preview iframe does by\n // design), `stepValues` observes that child list mid-populate. Reading it eagerly here used\n // to call `ensureSelection()` — and therefore write `currentValue` — once per step as each\n // one was discovered, interleaved with that *same* step's own first render effect further\n // down the reactive graph. That reentrant write during a child's not-yet-finished initial\n // render corrupted its rendered output (the step's clickable/static control silently failed\n // to mount at all). Onmounted's callback runs on a microtask, strictly after the whole\n // synchronous parse (and thus every child) has completed, so `getSteps()` sees the final list.\n onMounted(() => {\n ensureSelection();\n return undefined;\n });\n\n // ────────────────────────────────────────────────────────────────\n // Keyboard Navigation (roving tabindex over navigable steps)\n // ────────────────────────────────────────────────────────────────\n\n const getNavigableSteps = (): HTMLElement[] => {\n const steps = getSteps();\n const values = steps.map((s) => s.getAttribute('value') ?? '');\n const currentIndex = values.indexOf(currentValue.value ?? '');\n const clickable = Boolean(props.clickable.value) && !props.disabled.value;\n const linear = Boolean(props.linear.value);\n\n return steps.filter((step, index) =>\n isStepNavigable({\n disabled: step.hasAttribute('disabled'),\n index,\n linear,\n stepperClickable: clickable,\n stepperCurrentIndex: currentIndex,\n }),\n );\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: getNavigableSteps,\n loop: false,\n onNavigate: ({ item }) => {\n // Select BEFORE focusing: changing `currentValue` re-renders each step's\n // control (the step's navigable/button node-slot depends on the current\n // index), which destroys the previously focused button. Ripple flushes\n // effects synchronously, so by the time focusStep() runs the replacement\n // button exists.\n const value = item.getAttribute('value');\n\n if (value) setSelection(value, true);\n\n focusStep(item);\n },\n orientation: () => (props.orientation.value === 'vertical' ? 'vertical' : 'horizontal'),\n signal: lifecycleSignal(onCleanup),\n });\n\n const handleStepClick = (e: Event) => {\n const step = e\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-step');\n\n if (!step || step.closest(STEPPER_TAG) !== el || !getNavigableSteps().includes(step)) return;\n\n const value = step.getAttribute('value');\n\n setSelection(value ?? undefined, true);\n // The click focused the step's old control; the selection re-render replaced\n // it — restore focus onto the new one (see onNavigate's ordering note).\n focusStep(step);\n };\n\n const activateFocusedStep = (): void => {\n const steps = getNavigableSteps();\n const focusedStep = steps.find(\n (step) => step === document.activeElement || step.shadowRoot?.activeElement === document.activeElement,\n );\n const value = focusedStep?.getAttribute('value');\n\n if (value) {\n setSelection(value, true);\n focusStep(focusedStep);\n }\n };\n\n const activationPress = createInteraction({ onPress: activateFocusedStep });\n\n const handleKeydown = (e: KeyboardEvent) => {\n const steps = getNavigableSteps();\n\n if (steps.length === 0) return;\n\n const path = e.composedPath();\n const stepFromEvent = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-step',\n );\n const focused = stepFromEvent ? steps.indexOf(stepFromEvent) : -1;\n\n if (focused >= 0) listControl.set(focused);\n\n if (listControl.handleKeydown(e)) return;\n\n activationPress.handleKeydown(e);\n };\n\n bind({\n on: {\n click: handleStepClick,\n keydown: handleKeydown,\n },\n });\n\n return html`\n <nav part=\"nav\" aria-label=\"${props.label}\">\n <ol class=\"steps\" role=\"list\" part=\"list\">\n <slot></slot>\n </ol>\n </nav>\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"stepper.js","names":[],"sources":["../src/content/stepper/stepper.ts"],"sourcesContent":["import {\n bind,\n createContext,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n provide,\n useEmit,\n useSlots,\n} from '@vielzeug/ore';\nimport { computed, type Readable, signal, watch } from '@vielzeug/ripple';\nimport { createInteraction, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport { isStepNavigable } from './_is-step-navigable';\nimport componentStyles from './stepper.css?inline';\n\n// ── Context ─────────────────────────────────────────────────────────────────\n//\n// ore-step derives all of its parent-relative state (current/completed/navigable/\n// index/total/color/size/orientation) from this context instead of ore-stepper\n// pushing 8 attributes onto every child on every change. Matches the\n// provide()/inject() coordination already used by ore-tabs/ore-tab-item,\n// ore-radio-group/ore-radio, and ore-list/ore-list-item — ore-stepper was\n// previously the one outlier doing manual `querySelectorAll` + `setAttribute`\n// fan-out, which meant every reactive change re-walked and re-wrote every step\n// regardless of whether that step's own state actually changed.\n\n/** Context provided by ore-stepper to its ore-step children. */\nexport type StepperContext = {\n clickable: Readable<boolean>;\n color: Readable<ThemeColor | undefined>;\n currentValue: Readable<string | undefined>;\n linear: Readable<boolean>;\n orientation: Readable<'horizontal' | 'vertical'>;\n size: Readable<ComponentSize | undefined>;\n /** Ordered `value`s of every sibling `ore-step`, used to derive index/total/completed. */\n stepValues: Readable<string[]>;\n};\n/** Injection key for the stepper context. */\nexport const STEPPER_CTX = createContext<StepperContext>('StepperContext');\n\nexport type OreStepperEvents = {\n change: { value: string };\n};\n\nexport type OreStepperProps = {\n /** When true, steps are clickable/keyboard-focusable for navigation. Default: display-only progress. */\n clickable?: boolean;\n /** Theme color for the current/completed step indicators */\n color?: ThemeColor;\n /** Disables the whole stepper — no step is navigable regardless of `clickable` */\n disabled?: boolean;\n /** Accessible label for the nav landmark */\n label?: string;\n /**\n * Restricts navigation to completed steps and the current step — steps ahead of the\n * current one cannot be clicked or focused, even when `clickable` is set.\n */\n linear?: boolean;\n /** Layout orientation — 'horizontal' (default, desktop) or 'vertical' (compact/mobile-friendly) */\n orientation?: 'horizontal' | 'vertical';\n /** Component size */\n size?: ComponentSize;\n /** The `value` of the currently active `ore-step` */\n value?: string;\n};\n\n/**\n * Displays progress through a sequence of numbered steps. Manages step selection and provides\n * shared state (current value, clickability, theming) to its `ore-step` children via context.\n * Can be purely informational (progress display) or interactive navigation.\n *\n * @element ore-stepper\n * @element ore-step - Child element for each step (auto-discovered)\n *\n * @attr {string} value - The value of the currently active step\n * @attr {boolean} clickable - Allow clicking/focusing steps to navigate\n * @attr {boolean} linear - Restrict navigation to completed + current steps only\n * @attr {boolean} disabled - Disables the whole stepper\n * @attr {string} orientation - 'horizontal' (default) | 'vertical'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n * @attr {string} label - Accessible nav landmark label (default: 'Progress')\n *\n * @fires change - Emitted when the active step changes via click/keyboard. detail: { value: string }\n *\n * @slot - `ore-step` elements\n *\n * @cssprop --stepper-connector-color - Color of the connector line between steps\n * @cssprop --stepper-connector-size - Thickness of the connector line\n * @cssprop --stepper-gap - Gap between steps\n *\n * @part nav - Navigation landmark element\n * @part list - Ordered list container holding the slotted steps\n *\n * @example\n * ```html\n * <!-- Display-only progress -->\n * <ore-stepper value=\"shipping\" color=\"primary\">\n * <ore-step value=\"cart\">Cart</ore-step>\n * <ore-step value=\"shipping\">Shipping</ore-step>\n * <ore-step value=\"payment\">Payment</ore-step>\n * </ore-stepper>\n *\n * <!-- Clickable navigation, mobile-friendly vertical layout -->\n * <ore-stepper value=\"shipping\" clickable linear orientation=\"vertical\">\n * <ore-step value=\"cart\">Cart</ore-step>\n * <ore-step value=\"shipping\">Shipping</ore-step>\n * <ore-step value=\"payment\" disabled>Payment</ore-step>\n * </ore-stepper>\n * ```\n */\nexport const STEPPER_TAG = 'ore-stepper' as const;\ndefine<OreStepperProps>(STEPPER_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n clickable: prop.bool(false),\n label: prop.string('Progress'),\n linear: prop.bool(false),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'horizontal'),\n value: prop.string(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreStepperEvents>();\n const slots = useSlots();\n\n const getSteps = (): HTMLElement[] => Array.from(el.querySelectorAll(':scope > ore-step')) as HTMLElement[];\n\n const focusStep = (step: HTMLElement | undefined) => {\n step?.shadowRoot?.querySelector<HTMLElement>('button.control')?.focus();\n };\n\n // ────────────────────────────────────────────────────────────────\n // Selection State — mirrors ore-tabs' `selectedValue` signal + `ensureSelection()` pattern\n // ────────────────────────────────────────────────────────────────\n\n const currentValue = signal<string | undefined>(props.value.value);\n\n bind({ attr: { value: () => currentValue.value ?? null } });\n\n const setSelection = (value: string | undefined, shouldEmit = false) => {\n if (!value || value === currentValue.value) return;\n\n currentValue.value = value;\n\n if (shouldEmit) emit('change', { value });\n };\n\n const ensureSelection = () => {\n const steps = getSteps();\n\n if (steps.length === 0) return;\n\n const current = currentValue.value;\n const hasCurrent = current ? steps.some((s) => s.getAttribute('value') === current) : false;\n\n if (hasCurrent) return;\n\n const firstEnabled = steps.find((s) => !s.hasAttribute('disabled'))?.getAttribute('value') ?? undefined;\n\n if (firstEnabled) setSelection(firstEnabled);\n };\n\n watch(props.value, (value) => {\n currentValue.value = value;\n ensureSelection();\n });\n\n // ────────────────────────────────────────────────────────────────\n // Context provided to ore-step children\n // ────────────────────────────────────────────────────────────────\n\n const stepValues = computed(() => {\n void slots.elements().value;\n\n return getSteps().map((s) => s.getAttribute('value') ?? '');\n });\n\n provide(STEPPER_CTX, {\n clickable: computed(() => Boolean(props.clickable.value) && !props.disabled.value),\n color: props.color,\n currentValue,\n linear: computed(() => Boolean(props.linear.value)),\n orientation: computed(() => props.orientation.value ?? 'horizontal'),\n size: props.size,\n stepValues,\n });\n\n // Deferred to `onMounted()` (mirrors ore-tabs' `ensureSelection()` timing) rather than an\n // immediate `watch(stepValues, ...)` — `getSteps()` walks the *live* light DOM, and while the\n // browser is still parsing this element's `ore-step` children (synchronously upgrading each\n // one as its own tag is reached, which happens whenever `customElements.define()` already\n // ran before this markup was parsed — exactly what a sandboxed live-preview iframe does by\n // design), `stepValues` observes that child list mid-populate. Reading it eagerly here used\n // to call `ensureSelection()` — and therefore write `currentValue` — once per step as each\n // one was discovered, interleaved with that *same* step's own first render effect further\n // down the reactive graph. That reentrant write during a child's not-yet-finished initial\n // render corrupted its rendered output (the step's clickable/static control silently failed\n // to mount at all). Onmounted's callback runs on a microtask, strictly after the whole\n // synchronous parse (and thus every child) has completed, so `getSteps()` sees the final list.\n onMounted(() => {\n ensureSelection();\n return undefined;\n });\n\n // ────────────────────────────────────────────────────────────────\n // Keyboard Navigation (roving tabindex over navigable steps)\n // ────────────────────────────────────────────────────────────────\n\n const getNavigableSteps = (): HTMLElement[] => {\n const steps = getSteps();\n const values = steps.map((s) => s.getAttribute('value') ?? '');\n const currentIndex = values.indexOf(currentValue.value ?? '');\n const clickable = Boolean(props.clickable.value) && !props.disabled.value;\n const linear = Boolean(props.linear.value);\n\n return steps.filter((step, index) =>\n isStepNavigable({\n disabled: step.hasAttribute('disabled'),\n index,\n linear,\n stepperClickable: clickable,\n stepperCurrentIndex: currentIndex,\n }),\n );\n };\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: getNavigableSteps,\n loop: false,\n onNavigate: ({ item }) => {\n // Select BEFORE focusing: changing `currentValue` re-renders each step's\n // control (the step's navigable/button node-slot depends on the current\n // index), which destroys the previously focused button. Ripple flushes\n // effects synchronously, so by the time focusStep() runs the replacement\n // button exists.\n const value = item.getAttribute('value');\n\n if (value) setSelection(value, true);\n\n focusStep(item);\n },\n orientation: () => (props.orientation.value === 'vertical' ? 'vertical' : 'horizontal'),\n signal: lifecycleSignal(onCleanup),\n });\n\n const handleStepClick = (e: Event) => {\n const step = e\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-step');\n\n if (!step || step.closest(STEPPER_TAG) !== el || !getNavigableSteps().includes(step)) return;\n\n const value = step.getAttribute('value');\n\n setSelection(value ?? undefined, true);\n // The click focused the step's old control; the selection re-render replaced\n // it — restore focus onto the new one (see onNavigate's ordering note).\n focusStep(step);\n };\n\n const activateFocusedStep = (): void => {\n const steps = getNavigableSteps();\n const focusedStep = steps.find(\n (step) => step === document.activeElement || step.shadowRoot?.activeElement === document.activeElement,\n );\n const value = focusedStep?.getAttribute('value');\n\n if (value) {\n setSelection(value, true);\n focusStep(focusedStep);\n }\n };\n\n const activationPress = createInteraction({ onPress: activateFocusedStep });\n\n const handleKeydown = (e: KeyboardEvent) => {\n const steps = getNavigableSteps();\n\n if (steps.length === 0) return;\n\n const path = e.composedPath();\n const stepFromEvent = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-step',\n );\n const focused = stepFromEvent ? steps.indexOf(stepFromEvent) : -1;\n\n if (focused >= 0) listControl.set(focused);\n\n if (listControl.handleKeydown(e)) return;\n\n activationPress.handleKeydown(e);\n };\n\n bind({\n on: {\n click: handleStepClick,\n keydown: handleKeydown,\n },\n });\n\n return html`\n <nav part=\"nav\" aria-label=\"${props.label}\">\n <ol class=\"steps\" role=\"list\" part=\"list\">\n <slot></slot>\n </ol>\n </nav>\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"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"}
@@ -43,12 +43,14 @@ let e=require("@vielzeug/ore");var t=!1;function n(){if(!t){if(typeof CSS<`u`&&C
43
43
  conic-gradient(red 0 0) no-clip subtract,
44
44
  conic-gradient(red 0 0) padding-box;
45
45
 
46
+ opacity: var(--rainbow-border-opacity, 0.7);
46
47
  pointer-events: none;
47
48
  animation: rainbow-rotate 4s linear infinite;
48
49
  }
49
50
 
50
51
  /* Turn one pseudo layer into glow halo */
51
52
  :host([effect='rainbow']) ${t}::after {
53
+ opacity: var(--rainbow-glow-opacity, 0.18);
52
54
  filter: blur(var(--blur-xl));
53
55
  }
54
56
 
@@ -1 +1 @@
1
- {"version":3,"file":"animation.css.cjs","names":[],"sources":["../../../src/styles/mixins/animation.css.ts"],"sourcesContent":["import { css } from '@vielzeug/ore';\n\n/**\n * Register the --rainbow-angle CSS custom property so browsers can\n * interpolate it inside conic-gradient().\n * Called automatically by rainbowEffectMixin on first use.\n */\nlet rainbowRegistered = false;\n\nexport function registerRainbowProperty() {\n if (rainbowRegistered) return;\n\n if (typeof CSS !== 'undefined' && CSS.registerProperty) {\n try {\n CSS.registerProperty({\n inherits: false,\n initialValue: '0deg',\n name: '--rainbow-angle',\n syntax: '<angle>',\n });\n } catch {\n // Already registered — safe to ignore\n }\n }\n\n rainbowRegistered = true;\n}\n\n/**\n * Register the --shine-angle CSS custom property so browsers can\n * interpolate it inside conic-gradient().\n * Called automatically by shineEffectMixin on first use.\n */\nlet shineRegistered = false;\n\nexport function registerShineProperty() {\n if (shineRegistered) return;\n\n if (typeof CSS !== 'undefined' && CSS.registerProperty) {\n try {\n CSS.registerProperty({\n inherits: false,\n initialValue: '0deg',\n name: '--shine-angle',\n syntax: '<angle>',\n });\n } catch {\n // Already registered — safe to ignore\n }\n }\n\n shineRegistered = true;\n}\n\n/**\n * Rainbow Border Effect Mixin\n *\n * Animated rainbow border with glow effect.\n * Perfect for highlighting call-to-action elements or special features.\n *\n * @param selector - CSS selector for the element carrying the border, matched\n * against the component's actual shadow DOM markup — a bare word like\n * 'button' is a TYPE selector (matches a literal `<button>` tag), not a\n * part/class selector. Use the selector that actually matches your root\n * element, e.g. `'.box'` for a `<div class=\"box\">`, or `'[part=\"button\"]'`\n * for a `<span part=\"button\">`/`<a part=\"button\">` (ore-button's actual\n * markup — it never renders a `<button>` tag).\n * @returns CSSResult with rainbow border animation\n *\n * @example\n * ```ts\n * import { rainbowEffectMixin } from '../../styles/effects/rainbow.css';\n *\n * return {\n * styles: [rainbowEffectMixin('[part=\"button\"]'), componentStyles],\n * template: html`...`\n * };\n * ```\n */\nexport const rainbowEffectMixin = (selector: string) => {\n registerRainbowProperty();\n\n return css`\n /* ========================================\n Rainbow Border Effect\n ======================================== */\n\n :host([effect='rainbow']) ${selector} {\n position: relative;\n border: var(--border-2) solid transparent !important;\n overflow: visible;\n }\n\n /* Rainbow border and glow layers */\n :host([effect='rainbow']) ${selector}::before, :host([effect='rainbow']) ${selector}::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--border-2));\n border: inherit;\n border-radius: inherit;\n\n /* Reserve no-clip space for glow */\n box-shadow: 0 0 calc(3 * var(--blur-xl)) rgba(0 0 0 / 0.001);\n\n /* Rainbow gradient — Okabe-Ito colorblind-safe palette */\n background: conic-gradient(\n from var(--rainbow-angle),\n #56b4e9,\n /* Sky Blue */ #0072b2,\n /* Deep Blue */ #009e73,\n /* Bluish Green */ #f0e442,\n /* Yellow */ #e69f00,\n /* Orange */ #d55e00,\n /* Vermillion */ #cc79a7,\n /* Reddish Purple */ #56b4e9 /* Back to Sky Blue */\n )\n border-box;\n\n /* Make everything inside padding-box transparent\n by subtracting padding-box from no-clip box */\n -webkit-mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n\n pointer-events: none;\n animation: rainbow-rotate 4s linear infinite;\n }\n\n /* Turn one pseudo layer into glow halo */\n :host([effect='rainbow']) ${selector}::after {\n filter: blur(var(--blur-xl));\n }\n\n /* ========================================\n Animation\n ======================================== */\n\n @keyframes rainbow-rotate {\n to {\n --rainbow-angle: 1turn;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([effect='rainbow']) ${selector}::before, :host([effect='rainbow']) ${selector}::after {\n animation: none;\n }\n }\n `;\n};\n\n/**\n * Shine Border Effect Mixin\n *\n * Animated color-aware border shine. A tight arc of the component's own\n * theme color rotates around the border, producing the glossy \"shimmer\"\n * look seen in modern card and button designs.\n *\n * Respects the `color` attribute via `--_theme-*` tokens — no extra\n * configuration needed. Falls back to the neutral theme when no color\n * is set.\n *\n * @param selector - CSS selector for the inner element, matched against the\n * component's actual shadow DOM markup — see rainbowEffectMixin's\n * `selector` doc above for why a bare word like 'button' is usually wrong.\n * @returns CSSResult with shine border animation\n *\n * @example\n * ```ts\n * styles: [shineEffectMixin('[part=\"button\"]'), componentStyles]\n * ```\n */\nexport const shineEffectMixin = (selector: string) => {\n registerShineProperty();\n\n return css`\n /* ========================================\n Shine Border Effect\n ======================================== */\n\n :host([effect='shine']) ${selector} {\n position: relative;\n overflow: visible;\n --_shine-color: light-dark(\n oklch(from var(--_theme-focus) 0.6 calc(c * 1.2) h),\n oklch(from var(--_theme-focus) 0.85 calc(c * 1.2) h)\n );\n }\n\n /* Shine border — single pseudo, glow via box-shadow (follows border-radius, no artifacts) */\n :host([effect='shine']) ${selector}::before {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--border-2));\n border: var(--border-2) solid transparent;\n border-radius: calc(var(--_radius, 0px) + var(--border-2));\n\n /* Two comet tails 180deg apart, same shape rotating together */\n background:\n conic-gradient(from var(--shine-angle), transparent 0%, transparent 75%, var(--_shine-color) 100%) border-box,\n conic-gradient(\n from calc(var(--shine-angle) + 180deg),\n transparent 0%,\n transparent 75%,\n var(--_shine-color) 100%\n )\n border-box;\n\n -webkit-mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n\n pointer-events: none;\n animation: shine-rotate 2.5s linear infinite;\n }\n\n /* ========================================\n Animation\n ======================================== */\n\n @keyframes shine-rotate {\n to {\n --shine-angle: 1turn;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([effect='shine']) ${selector}::before {\n animation: none;\n }\n }\n `;\n};\n"],"mappings":"+BAOA,IAAI,EAAoB,GAExB,SAAgB,GAA0B,CACpC,MAEJ,IAAI,OAAO,IAAQ,KAAe,IAAI,iBACpC,GAAI,CACF,IAAI,iBAAiB,CACnB,SAAU,GACV,aAAc,OACd,KAAM,kBACN,OAAQ,SACV,CAAC,CACH,MAAQ,CAER,CAGF,EAAoB,EAHlB,CAIJ,CAOA,IAAI,EAAkB,GAEtB,SAAgB,GAAwB,CAClC,MAEJ,IAAI,OAAO,IAAQ,KAAe,IAAI,iBACpC,GAAI,CACF,IAAI,iBAAiB,CACnB,SAAU,GACV,aAAc,OACd,KAAM,gBACN,OAAQ,SACV,CAAC,CACH,MAAQ,CAER,CAGF,EAAkB,EAHhB,CAIJ,CA2BA,IAAa,EAAsB,IACjC,EAAwB,EAEjB,EAAA,GAAG;;;;;gCAKoB,EAAS;;;;;;;gCAOT,EAAS,sCAAsC,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAsCxD,EAAS;;;;;;;;;;;;;;;kCAeP,EAAS,sCAAsC,EAAS;;;;KA4B7E,EAAoB,IAC/B,EAAsB,EAEf,EAAA,GAAG;;;;;8BAKkB,EAAS;;;;;;;;;;8BAUT,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAwCP,EAAS"}
1
+ {"version":3,"file":"animation.css.cjs","names":[],"sources":["../../../src/styles/mixins/animation.css.ts"],"sourcesContent":["import { css } from '@vielzeug/ore';\n\n/**\n * Register the --rainbow-angle CSS custom property so browsers can\n * interpolate it inside conic-gradient().\n * Called automatically by rainbowEffectMixin on first use.\n */\nlet rainbowRegistered = false;\n\nexport function registerRainbowProperty() {\n if (rainbowRegistered) return;\n\n if (typeof CSS !== 'undefined' && CSS.registerProperty) {\n try {\n CSS.registerProperty({\n inherits: false,\n initialValue: '0deg',\n name: '--rainbow-angle',\n syntax: '<angle>',\n });\n } catch {\n // Already registered — safe to ignore\n }\n }\n\n rainbowRegistered = true;\n}\n\n/**\n * Register the --shine-angle CSS custom property so browsers can\n * interpolate it inside conic-gradient().\n * Called automatically by shineEffectMixin on first use.\n */\nlet shineRegistered = false;\n\nexport function registerShineProperty() {\n if (shineRegistered) return;\n\n if (typeof CSS !== 'undefined' && CSS.registerProperty) {\n try {\n CSS.registerProperty({\n inherits: false,\n initialValue: '0deg',\n name: '--shine-angle',\n syntax: '<angle>',\n });\n } catch {\n // Already registered — safe to ignore\n }\n }\n\n shineRegistered = true;\n}\n\n/**\n * Rainbow Border Effect Mixin\n *\n * Animated rainbow border with glow effect.\n * Perfect for highlighting call-to-action elements or special features.\n *\n * @param selector - CSS selector for the element carrying the border, matched\n * against the component's actual shadow DOM markup — a bare word like\n * 'button' is a TYPE selector (matches a literal `<button>` tag), not a\n * part/class selector. Use the selector that actually matches your root\n * element, e.g. `'.box'` for a `<div class=\"box\">`, or `'[part=\"button\"]'`\n * for a `<span part=\"button\">`/`<a part=\"button\">` (ore-button's actual\n * markup — it never renders a `<button>` tag).\n * @returns CSSResult with rainbow border animation\n *\n * @example\n * ```ts\n * import { rainbowEffectMixin } from '../../styles/effects/rainbow.css';\n *\n * return {\n * styles: [rainbowEffectMixin('[part=\"button\"]'), componentStyles],\n * template: html`...`\n * };\n * ```\n */\nexport const rainbowEffectMixin = (selector: string) => {\n registerRainbowProperty();\n\n return css`\n /* ========================================\n Rainbow Border Effect\n ======================================== */\n\n :host([effect='rainbow']) ${selector} {\n position: relative;\n border: var(--border-2) solid transparent !important;\n overflow: visible;\n }\n\n /* Rainbow border and glow layers */\n :host([effect='rainbow']) ${selector}::before, :host([effect='rainbow']) ${selector}::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--border-2));\n border: inherit;\n border-radius: inherit;\n\n /* Reserve no-clip space for glow */\n box-shadow: 0 0 calc(3 * var(--blur-xl)) rgba(0 0 0 / 0.001);\n\n /* Rainbow gradient — Okabe-Ito colorblind-safe palette */\n background: conic-gradient(\n from var(--rainbow-angle),\n #56b4e9,\n /* Sky Blue */ #0072b2,\n /* Deep Blue */ #009e73,\n /* Bluish Green */ #f0e442,\n /* Yellow */ #e69f00,\n /* Orange */ #d55e00,\n /* Vermillion */ #cc79a7,\n /* Reddish Purple */ #56b4e9 /* Back to Sky Blue */\n )\n border-box;\n\n /* Make everything inside padding-box transparent\n by subtracting padding-box from no-clip box */\n -webkit-mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n\n opacity: var(--rainbow-border-opacity, 0.7);\n pointer-events: none;\n animation: rainbow-rotate 4s linear infinite;\n }\n\n /* Turn one pseudo layer into glow halo */\n :host([effect='rainbow']) ${selector}::after {\n opacity: var(--rainbow-glow-opacity, 0.18);\n filter: blur(var(--blur-xl));\n }\n\n /* ========================================\n Animation\n ======================================== */\n\n @keyframes rainbow-rotate {\n to {\n --rainbow-angle: 1turn;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([effect='rainbow']) ${selector}::before, :host([effect='rainbow']) ${selector}::after {\n animation: none;\n }\n }\n `;\n};\n\n/**\n * Shine Border Effect Mixin\n *\n * Animated color-aware border shine. A tight arc of the component's own\n * theme color rotates around the border, producing the glossy \"shimmer\"\n * look seen in modern card and button designs.\n *\n * Respects the `color` attribute via `--_theme-*` tokens — no extra\n * configuration needed. Falls back to the neutral theme when no color\n * is set.\n *\n * @param selector - CSS selector for the inner element, matched against the\n * component's actual shadow DOM markup — see rainbowEffectMixin's\n * `selector` doc above for why a bare word like 'button' is usually wrong.\n * @returns CSSResult with shine border animation\n *\n * @example\n * ```ts\n * styles: [shineEffectMixin('[part=\"button\"]'), componentStyles]\n * ```\n */\nexport const shineEffectMixin = (selector: string) => {\n registerShineProperty();\n\n return css`\n /* ========================================\n Shine Border Effect\n ======================================== */\n\n :host([effect='shine']) ${selector} {\n position: relative;\n overflow: visible;\n --_shine-color: light-dark(\n oklch(from var(--_theme-focus) 0.6 calc(c * 1.2) h),\n oklch(from var(--_theme-focus) 0.85 calc(c * 1.2) h)\n );\n }\n\n /* Shine border — single pseudo, glow via box-shadow (follows border-radius, no artifacts) */\n :host([effect='shine']) ${selector}::before {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--border-2));\n border: var(--border-2) solid transparent;\n border-radius: calc(var(--_radius, 0px) + var(--border-2));\n\n /* Two comet tails 180deg apart, same shape rotating together */\n background:\n conic-gradient(from var(--shine-angle), transparent 0%, transparent 75%, var(--_shine-color) 100%) border-box,\n conic-gradient(\n from calc(var(--shine-angle) + 180deg),\n transparent 0%,\n transparent 75%,\n var(--_shine-color) 100%\n )\n border-box;\n\n -webkit-mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n\n pointer-events: none;\n animation: shine-rotate 2.5s linear infinite;\n }\n\n /* ========================================\n Animation\n ======================================== */\n\n @keyframes shine-rotate {\n to {\n --shine-angle: 1turn;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([effect='shine']) ${selector}::before {\n animation: none;\n }\n }\n `;\n};\n"],"mappings":"+BAOA,IAAI,EAAoB,GAExB,SAAgB,GAA0B,CACpC,MAEJ,IAAI,OAAO,IAAQ,KAAe,IAAI,iBACpC,GAAI,CACF,IAAI,iBAAiB,CACnB,SAAU,GACV,aAAc,OACd,KAAM,kBACN,OAAQ,SACV,CAAC,CACH,MAAQ,CAER,CAGF,EAAoB,EAHlB,CAIJ,CAOA,IAAI,EAAkB,GAEtB,SAAgB,GAAwB,CAClC,MAEJ,IAAI,OAAO,IAAQ,KAAe,IAAI,iBACpC,GAAI,CACF,IAAI,iBAAiB,CACnB,SAAU,GACV,aAAc,OACd,KAAM,gBACN,OAAQ,SACV,CAAC,CACH,MAAQ,CAER,CAGF,EAAkB,EAHhB,CAIJ,CA2BA,IAAa,EAAsB,IACjC,EAAwB,EAEjB,EAAA,GAAG;;;;;gCAKoB,EAAS;;;;;;;gCAOT,EAAS,sCAAsC,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAuCxD,EAAS;;;;;;;;;;;;;;;;kCAgBP,EAAS,sCAAsC,EAAS;;;;KA4B7E,EAAoB,IAC/B,EAAsB,EAEf,EAAA,GAAG;;;;;8BAKkB,EAAS;;;;;;;;;;8BAUT,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAwCP,EAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"animation.css.d.ts","sourceRoot":"","sources":["../../../src/styles/mixins/animation.css.ts"],"names":[],"mappings":"AASA,wBAAgB,uBAAuB,SAiBtC;AASD,wBAAgB,qBAAqB,SAiBpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,sCAyElD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,MAAM,sCA+DhD,CAAC"}
1
+ {"version":3,"file":"animation.css.d.ts","sourceRoot":"","sources":["../../../src/styles/mixins/animation.css.ts"],"names":[],"mappings":"AASA,wBAAgB,uBAAuB,SAiBtC;AASD,wBAAgB,qBAAqB,SAiBpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,sCA2ElD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,MAAM,sCA+DhD,CAAC"}
@@ -73,12 +73,14 @@ var a = (t) => (n(), e`
73
73
  conic-gradient(red 0 0) no-clip subtract,
74
74
  conic-gradient(red 0 0) padding-box;
75
75
 
76
+ opacity: var(--rainbow-border-opacity, 0.7);
76
77
  pointer-events: none;
77
78
  animation: rainbow-rotate 4s linear infinite;
78
79
  }
79
80
 
80
81
  /* Turn one pseudo layer into glow halo */
81
82
  :host([effect='rainbow']) ${t}::after {
83
+ opacity: var(--rainbow-glow-opacity, 0.18);
82
84
  filter: blur(var(--blur-xl));
83
85
  }
84
86
 
@@ -1 +1 @@
1
- {"version":3,"file":"animation.css.js","names":[],"sources":["../../../src/styles/mixins/animation.css.ts"],"sourcesContent":["import { css } from '@vielzeug/ore';\n\n/**\n * Register the --rainbow-angle CSS custom property so browsers can\n * interpolate it inside conic-gradient().\n * Called automatically by rainbowEffectMixin on first use.\n */\nlet rainbowRegistered = false;\n\nexport function registerRainbowProperty() {\n if (rainbowRegistered) return;\n\n if (typeof CSS !== 'undefined' && CSS.registerProperty) {\n try {\n CSS.registerProperty({\n inherits: false,\n initialValue: '0deg',\n name: '--rainbow-angle',\n syntax: '<angle>',\n });\n } catch {\n // Already registered — safe to ignore\n }\n }\n\n rainbowRegistered = true;\n}\n\n/**\n * Register the --shine-angle CSS custom property so browsers can\n * interpolate it inside conic-gradient().\n * Called automatically by shineEffectMixin on first use.\n */\nlet shineRegistered = false;\n\nexport function registerShineProperty() {\n if (shineRegistered) return;\n\n if (typeof CSS !== 'undefined' && CSS.registerProperty) {\n try {\n CSS.registerProperty({\n inherits: false,\n initialValue: '0deg',\n name: '--shine-angle',\n syntax: '<angle>',\n });\n } catch {\n // Already registered — safe to ignore\n }\n }\n\n shineRegistered = true;\n}\n\n/**\n * Rainbow Border Effect Mixin\n *\n * Animated rainbow border with glow effect.\n * Perfect for highlighting call-to-action elements or special features.\n *\n * @param selector - CSS selector for the element carrying the border, matched\n * against the component's actual shadow DOM markup — a bare word like\n * 'button' is a TYPE selector (matches a literal `<button>` tag), not a\n * part/class selector. Use the selector that actually matches your root\n * element, e.g. `'.box'` for a `<div class=\"box\">`, or `'[part=\"button\"]'`\n * for a `<span part=\"button\">`/`<a part=\"button\">` (ore-button's actual\n * markup — it never renders a `<button>` tag).\n * @returns CSSResult with rainbow border animation\n *\n * @example\n * ```ts\n * import { rainbowEffectMixin } from '../../styles/effects/rainbow.css';\n *\n * return {\n * styles: [rainbowEffectMixin('[part=\"button\"]'), componentStyles],\n * template: html`...`\n * };\n * ```\n */\nexport const rainbowEffectMixin = (selector: string) => {\n registerRainbowProperty();\n\n return css`\n /* ========================================\n Rainbow Border Effect\n ======================================== */\n\n :host([effect='rainbow']) ${selector} {\n position: relative;\n border: var(--border-2) solid transparent !important;\n overflow: visible;\n }\n\n /* Rainbow border and glow layers */\n :host([effect='rainbow']) ${selector}::before, :host([effect='rainbow']) ${selector}::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--border-2));\n border: inherit;\n border-radius: inherit;\n\n /* Reserve no-clip space for glow */\n box-shadow: 0 0 calc(3 * var(--blur-xl)) rgba(0 0 0 / 0.001);\n\n /* Rainbow gradient — Okabe-Ito colorblind-safe palette */\n background: conic-gradient(\n from var(--rainbow-angle),\n #56b4e9,\n /* Sky Blue */ #0072b2,\n /* Deep Blue */ #009e73,\n /* Bluish Green */ #f0e442,\n /* Yellow */ #e69f00,\n /* Orange */ #d55e00,\n /* Vermillion */ #cc79a7,\n /* Reddish Purple */ #56b4e9 /* Back to Sky Blue */\n )\n border-box;\n\n /* Make everything inside padding-box transparent\n by subtracting padding-box from no-clip box */\n -webkit-mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n\n pointer-events: none;\n animation: rainbow-rotate 4s linear infinite;\n }\n\n /* Turn one pseudo layer into glow halo */\n :host([effect='rainbow']) ${selector}::after {\n filter: blur(var(--blur-xl));\n }\n\n /* ========================================\n Animation\n ======================================== */\n\n @keyframes rainbow-rotate {\n to {\n --rainbow-angle: 1turn;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([effect='rainbow']) ${selector}::before, :host([effect='rainbow']) ${selector}::after {\n animation: none;\n }\n }\n `;\n};\n\n/**\n * Shine Border Effect Mixin\n *\n * Animated color-aware border shine. A tight arc of the component's own\n * theme color rotates around the border, producing the glossy \"shimmer\"\n * look seen in modern card and button designs.\n *\n * Respects the `color` attribute via `--_theme-*` tokens — no extra\n * configuration needed. Falls back to the neutral theme when no color\n * is set.\n *\n * @param selector - CSS selector for the inner element, matched against the\n * component's actual shadow DOM markup — see rainbowEffectMixin's\n * `selector` doc above for why a bare word like 'button' is usually wrong.\n * @returns CSSResult with shine border animation\n *\n * @example\n * ```ts\n * styles: [shineEffectMixin('[part=\"button\"]'), componentStyles]\n * ```\n */\nexport const shineEffectMixin = (selector: string) => {\n registerShineProperty();\n\n return css`\n /* ========================================\n Shine Border Effect\n ======================================== */\n\n :host([effect='shine']) ${selector} {\n position: relative;\n overflow: visible;\n --_shine-color: light-dark(\n oklch(from var(--_theme-focus) 0.6 calc(c * 1.2) h),\n oklch(from var(--_theme-focus) 0.85 calc(c * 1.2) h)\n );\n }\n\n /* Shine border — single pseudo, glow via box-shadow (follows border-radius, no artifacts) */\n :host([effect='shine']) ${selector}::before {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--border-2));\n border: var(--border-2) solid transparent;\n border-radius: calc(var(--_radius, 0px) + var(--border-2));\n\n /* Two comet tails 180deg apart, same shape rotating together */\n background:\n conic-gradient(from var(--shine-angle), transparent 0%, transparent 75%, var(--_shine-color) 100%) border-box,\n conic-gradient(\n from calc(var(--shine-angle) + 180deg),\n transparent 0%,\n transparent 75%,\n var(--_shine-color) 100%\n )\n border-box;\n\n -webkit-mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n\n pointer-events: none;\n animation: shine-rotate 2.5s linear infinite;\n }\n\n /* ========================================\n Animation\n ======================================== */\n\n @keyframes shine-rotate {\n to {\n --shine-angle: 1turn;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([effect='shine']) ${selector}::before {\n animation: none;\n }\n }\n `;\n};\n"],"mappings":";;AAOA,IAAI,IAAoB;AAExB,SAAgB,IAA0B;CACpC,QAEJ;MAAI,OAAO,MAAQ,OAAe,IAAI,kBACpC,IAAI;GACF,IAAI,iBAAiB;IACnB,UAAU;IACV,cAAc;IACd,MAAM;IACN,QAAQ;GACV,CAAC;EACH,QAAQ,CAER;EAGF,IAAoB;CAHlB;AAIJ;AAOA,IAAI,IAAkB;AAEtB,SAAgB,IAAwB;CAClC,QAEJ;MAAI,OAAO,MAAQ,OAAe,IAAI,kBACpC,IAAI;GACF,IAAI,iBAAiB;IACnB,UAAU;IACV,cAAc;IACd,MAAM;IACN,QAAQ;GACV,CAAC;EACH,QAAQ,CAER;EAGF,IAAkB;CAHhB;AAIJ;AA2BA,IAAa,KAAsB,OACjC,EAAwB,GAEjB,CAAG;;;;;gCAKoB,EAAS;;;;;;;gCAOT,EAAS,sCAAsC,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAsCxD,EAAS;;;;;;;;;;;;;;;kCAeP,EAAS,sCAAsC,EAAS;;;;MA4B7E,KAAoB,OAC/B,EAAsB,GAEf,CAAG;;;;;8BAKkB,EAAS;;;;;;;;;;8BAUT,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAwCP,EAAS"}
1
+ {"version":3,"file":"animation.css.js","names":[],"sources":["../../../src/styles/mixins/animation.css.ts"],"sourcesContent":["import { css } from '@vielzeug/ore';\n\n/**\n * Register the --rainbow-angle CSS custom property so browsers can\n * interpolate it inside conic-gradient().\n * Called automatically by rainbowEffectMixin on first use.\n */\nlet rainbowRegistered = false;\n\nexport function registerRainbowProperty() {\n if (rainbowRegistered) return;\n\n if (typeof CSS !== 'undefined' && CSS.registerProperty) {\n try {\n CSS.registerProperty({\n inherits: false,\n initialValue: '0deg',\n name: '--rainbow-angle',\n syntax: '<angle>',\n });\n } catch {\n // Already registered — safe to ignore\n }\n }\n\n rainbowRegistered = true;\n}\n\n/**\n * Register the --shine-angle CSS custom property so browsers can\n * interpolate it inside conic-gradient().\n * Called automatically by shineEffectMixin on first use.\n */\nlet shineRegistered = false;\n\nexport function registerShineProperty() {\n if (shineRegistered) return;\n\n if (typeof CSS !== 'undefined' && CSS.registerProperty) {\n try {\n CSS.registerProperty({\n inherits: false,\n initialValue: '0deg',\n name: '--shine-angle',\n syntax: '<angle>',\n });\n } catch {\n // Already registered — safe to ignore\n }\n }\n\n shineRegistered = true;\n}\n\n/**\n * Rainbow Border Effect Mixin\n *\n * Animated rainbow border with glow effect.\n * Perfect for highlighting call-to-action elements or special features.\n *\n * @param selector - CSS selector for the element carrying the border, matched\n * against the component's actual shadow DOM markup — a bare word like\n * 'button' is a TYPE selector (matches a literal `<button>` tag), not a\n * part/class selector. Use the selector that actually matches your root\n * element, e.g. `'.box'` for a `<div class=\"box\">`, or `'[part=\"button\"]'`\n * for a `<span part=\"button\">`/`<a part=\"button\">` (ore-button's actual\n * markup — it never renders a `<button>` tag).\n * @returns CSSResult with rainbow border animation\n *\n * @example\n * ```ts\n * import { rainbowEffectMixin } from '../../styles/effects/rainbow.css';\n *\n * return {\n * styles: [rainbowEffectMixin('[part=\"button\"]'), componentStyles],\n * template: html`...`\n * };\n * ```\n */\nexport const rainbowEffectMixin = (selector: string) => {\n registerRainbowProperty();\n\n return css`\n /* ========================================\n Rainbow Border Effect\n ======================================== */\n\n :host([effect='rainbow']) ${selector} {\n position: relative;\n border: var(--border-2) solid transparent !important;\n overflow: visible;\n }\n\n /* Rainbow border and glow layers */\n :host([effect='rainbow']) ${selector}::before, :host([effect='rainbow']) ${selector}::after {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--border-2));\n border: inherit;\n border-radius: inherit;\n\n /* Reserve no-clip space for glow */\n box-shadow: 0 0 calc(3 * var(--blur-xl)) rgba(0 0 0 / 0.001);\n\n /* Rainbow gradient — Okabe-Ito colorblind-safe palette */\n background: conic-gradient(\n from var(--rainbow-angle),\n #56b4e9,\n /* Sky Blue */ #0072b2,\n /* Deep Blue */ #009e73,\n /* Bluish Green */ #f0e442,\n /* Yellow */ #e69f00,\n /* Orange */ #d55e00,\n /* Vermillion */ #cc79a7,\n /* Reddish Purple */ #56b4e9 /* Back to Sky Blue */\n )\n border-box;\n\n /* Make everything inside padding-box transparent\n by subtracting padding-box from no-clip box */\n -webkit-mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n\n opacity: var(--rainbow-border-opacity, 0.7);\n pointer-events: none;\n animation: rainbow-rotate 4s linear infinite;\n }\n\n /* Turn one pseudo layer into glow halo */\n :host([effect='rainbow']) ${selector}::after {\n opacity: var(--rainbow-glow-opacity, 0.18);\n filter: blur(var(--blur-xl));\n }\n\n /* ========================================\n Animation\n ======================================== */\n\n @keyframes rainbow-rotate {\n to {\n --rainbow-angle: 1turn;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([effect='rainbow']) ${selector}::before, :host([effect='rainbow']) ${selector}::after {\n animation: none;\n }\n }\n `;\n};\n\n/**\n * Shine Border Effect Mixin\n *\n * Animated color-aware border shine. A tight arc of the component's own\n * theme color rotates around the border, producing the glossy \"shimmer\"\n * look seen in modern card and button designs.\n *\n * Respects the `color` attribute via `--_theme-*` tokens — no extra\n * configuration needed. Falls back to the neutral theme when no color\n * is set.\n *\n * @param selector - CSS selector for the inner element, matched against the\n * component's actual shadow DOM markup — see rainbowEffectMixin's\n * `selector` doc above for why a bare word like 'button' is usually wrong.\n * @returns CSSResult with shine border animation\n *\n * @example\n * ```ts\n * styles: [shineEffectMixin('[part=\"button\"]'), componentStyles]\n * ```\n */\nexport const shineEffectMixin = (selector: string) => {\n registerShineProperty();\n\n return css`\n /* ========================================\n Shine Border Effect\n ======================================== */\n\n :host([effect='shine']) ${selector} {\n position: relative;\n overflow: visible;\n --_shine-color: light-dark(\n oklch(from var(--_theme-focus) 0.6 calc(c * 1.2) h),\n oklch(from var(--_theme-focus) 0.85 calc(c * 1.2) h)\n );\n }\n\n /* Shine border — single pseudo, glow via box-shadow (follows border-radius, no artifacts) */\n :host([effect='shine']) ${selector}::before {\n content: '';\n position: absolute;\n inset: calc(-1 * var(--border-2));\n border: var(--border-2) solid transparent;\n border-radius: calc(var(--_radius, 0px) + var(--border-2));\n\n /* Two comet tails 180deg apart, same shape rotating together */\n background:\n conic-gradient(from var(--shine-angle), transparent 0%, transparent 75%, var(--_shine-color) 100%) border-box,\n conic-gradient(\n from calc(var(--shine-angle) + 180deg),\n transparent 0%,\n transparent 75%,\n var(--_shine-color) 100%\n )\n border-box;\n\n -webkit-mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n mask:\n conic-gradient(red 0 0) no-clip subtract,\n conic-gradient(red 0 0) padding-box;\n\n pointer-events: none;\n animation: shine-rotate 2.5s linear infinite;\n }\n\n /* ========================================\n Animation\n ======================================== */\n\n @keyframes shine-rotate {\n to {\n --shine-angle: 1turn;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([effect='shine']) ${selector}::before {\n animation: none;\n }\n }\n `;\n};\n"],"mappings":";;AAOA,IAAI,IAAoB;AAExB,SAAgB,IAA0B;CACpC,QAEJ;MAAI,OAAO,MAAQ,OAAe,IAAI,kBACpC,IAAI;GACF,IAAI,iBAAiB;IACnB,UAAU;IACV,cAAc;IACd,MAAM;IACN,QAAQ;GACV,CAAC;EACH,QAAQ,CAER;EAGF,IAAoB;CAHlB;AAIJ;AAOA,IAAI,IAAkB;AAEtB,SAAgB,IAAwB;CAClC,QAEJ;MAAI,OAAO,MAAQ,OAAe,IAAI,kBACpC,IAAI;GACF,IAAI,iBAAiB;IACnB,UAAU;IACV,cAAc;IACd,MAAM;IACN,QAAQ;GACV,CAAC;EACH,QAAQ,CAER;EAGF,IAAkB;CAHhB;AAIJ;AA2BA,IAAa,KAAsB,OACjC,EAAwB,GAEjB,CAAG;;;;;gCAKoB,EAAS;;;;;;;gCAOT,EAAS,sCAAsC,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAuCxD,EAAS;;;;;;;;;;;;;;;;kCAgBP,EAAS,sCAAsC,EAAS;;;;MA4B7E,KAAoB,OAC/B,EAAsB,GAEf,CAAG;;;;;8BAKkB,EAAS;;;;;;;;;;8BAUT,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAwCP,EAAS"}
package/dist/tab-item.cjs CHANGED
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/theme.css.cjs"),n=require("./core/aria-reflection.cjs"),r=require("./shared/prop-bundles.cjs"),i=require("./tabs.cjs"),a=require("./disclosure/tab-item/tab-item.css.cjs");let o=require("@vielzeug/ore"),s=require("@vielzeug/ripple");var c=`ore-tab-item`;(0,o.define)(c,{props:{...r.disablableBundle,active:o.prop.bool(!1),color:o.prop.string(),size:o.prop.string(),value:o.prop.string(``),variant:o.prop.string()},setup(e){let t=(0,o.getHost)(),r=o.watchEffect,a=(0,o.inject)(i.TABS_CTX);a&&r(()=>{let e=a.color.value,n=a.size.value,r=a.variant.value;e!==void 0&&t.setAttribute(`color`,e),n!==void 0&&t.setAttribute(`size`,n),r!==void 0&&t.setAttribute(`variant`,r)});let c=(0,s.computed)(()=>a?!!a.value.value&&a.value.value===e.value.value:e.active.value),l=()=>!!e.disabled.value;(0,o.bind)({attr:{active:()=>c.value?!0:void 0}});let u=n=>{if(n.stopPropagation(),l()){n.preventDefault();return}t.dispatchEvent(new CustomEvent(`click`,{bubbles:!0,detail:{value:e.value.value}}))},d=()=>`tab-${e.value.value}`,f=(0,o.ref)();return(0,o.onMounted)(()=>{(0,o.watchEffect)(()=>{let r=f.value,i=e.value.value;if(!r)return;let a=((i?t.closest(`ore-tabs`):null)?.querySelector(`:scope > ore-tab-panel[value="${i}"]`))?.shadowRoot?.querySelector(`.panel`)??null;n.setAriaReflection(r,`ariaControlsElements`,a?[a]:[])})}),o.html`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/theme.css.cjs"),n=require("./core/aria-reflection.cjs"),r=require("./shared/prop-bundles.cjs"),i=require("./tabs.cjs"),a=require("./disclosure/tab-item/tab-item.css.cjs");let o=require("@vielzeug/ore"),s=require("@vielzeug/ripple");var c=`ore-tab-item`;(0,o.define)(c,{props:{...r.disablableBundle,active:o.prop.bool(!1),color:o.prop.string(),density:o.prop.oneOf([`compact`,`default`],`default`),size:o.prop.string(),value:o.prop.string(``),variant:o.prop.string()},setup(e){let t=(0,o.getHost)(),r=o.watchEffect,a=(0,o.inject)(i.TABS_CTX);a&&r(()=>{let e=a.color.value,n=a.density.value,r=a.size.value,i=a.variant.value;e!==void 0&&t.setAttribute(`color`,e),t.setAttribute(`density`,n),r!==void 0&&t.setAttribute(`size`,r),i!==void 0&&t.setAttribute(`variant`,i)});let c=(0,s.computed)(()=>a?!!a.value.value&&a.value.value===e.value.value:e.active.value),l=()=>!!e.disabled.value;(0,o.bind)({attr:{active:()=>c.value?!0:void 0}});let u=n=>{if(n.stopPropagation(),l()){n.preventDefault();return}t.dispatchEvent(new CustomEvent(`click`,{bubbles:!0,detail:{value:e.value.value}}))},d=()=>`tab-${e.value.value}`,f=(0,o.ref)();return(0,o.onMounted)(()=>{(0,o.watchEffect)(()=>{let r=f.value,i=e.value.value;if(!r)return;let a=((i?t.closest(`ore-tabs`):null)?.querySelector(`:scope > ore-tab-panel[value="${i}"]`))?.shadowRoot?.querySelector(`.panel`)??null;n.setAriaReflection(r,`ariaControlsElements`,a?[a]:[])})}),o.html`
2
2
  <button
3
3
  role="tab"
4
4
  type="button"
@@ -1 +1 @@
1
- {"version":3,"file":"tab-item.cjs","names":[],"sources":["../src/disclosure/tab-item/tab-item.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onMounted, prop, ref, watchEffect } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { setAriaReflection } from '../../core';\nimport { disablableBundle } from '../../shared';\nimport { coarsePointerMixin, colorThemeMixin, forcedColorsFocusMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport { TABS_CTX } from '../tabs/tabs';\nimport styles from './tab-item.css?inline';\n\nexport type OreTabItemProps = {\n /** Whether this tab is currently selected (set by ore-tabs) */\n active?: boolean;\n /** Theme color (inherited from ore-tabs) */\n color?: ThemeColor;\n /** Disable this tab */\n disabled?: boolean;\n /** Size (inherited from ore-tabs) */\n size?: ComponentSize;\n /** Unique value identifier — must match a ore-tab-panel value */\n value: string;\n /** Visual variant (inherited from ore-tabs) */\n variant?: SurfaceVariant;\n};\n\n/**\n * Individual tab trigger. Must be placed in the `tabs` slot of `ore-tabs`.\n *\n * @element ore-tab-item\n *\n * @attr {string} value - Unique identifier, matches the corresponding ore-tab-panel value\n * @attr {boolean} active - Set by the parent ore-tabs when this tab is selected\n * @attr {boolean} disabled - Prevents selection\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} variant - Inherited from ore-tabs: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} color - Inherited from ore-tabs: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @slot prefix - Icon or content before the label\n * @slot - Tab label\n * @slot suffix - Badge or count after the label\n *\n * @cssprop --tab-item-radius - Button border radius\n * @cssprop --tab-item-transition - Transition duration/easing\n * @cssprop --tab-item-padding - Button padding\n * @cssprop --tab-item-font-size - Button font size\n * @cssprop --tab-item-color - Default text color\n * @cssprop --tab-item-hover-bg - Background on hover\n * @cssprop --tab-item-active-bg - Background when active/selected\n * @cssprop --tab-item-active-color - Text color when active/selected\n * @cssprop --tab-item-active-shadow - Box shadow when active/selected\n * @part tab - Tab trigger element.\n * @example\n * ```html\n * <ore-tab-item slot=\"tabs\" value=\"overview\">Overview</ore-tab-item>\n * <ore-tab-item slot=\"tabs\" value=\"settings\" disabled>Settings</ore-tab-item>\n * ```\n */\nexport const TAB_ITEM_TAG = 'ore-tab-item' as const;\ndefine<OreTabItemProps>(TAB_ITEM_TAG, {\n props: {\n ...disablableBundle,\n active: prop.bool(false),\n color: prop.string<ThemeColor>(),\n size: prop.string<ComponentSize>(),\n value: prop.string(''),\n variant: prop.string<SurfaceVariant>(),\n },\n setup(props) {\n const el = getHost();\n const watch = watchEffect;\n\n const tabsCtx = inject(TABS_CTX);\n\n if (tabsCtx) {\n watch(() => {\n const color = tabsCtx.color.value;\n const size = tabsCtx.size.value;\n const variant = tabsCtx.variant.value;\n\n if (color !== undefined) el.setAttribute('color', color);\n\n if (size !== undefined) el.setAttribute('size', size);\n\n if (variant !== undefined) el.setAttribute('variant', variant);\n });\n }\n\n const isActive = computed(() =>\n tabsCtx ? !!tabsCtx.value.value && tabsCtx.value.value === props.value.value : props.active.value,\n );\n const isDisabled = () => Boolean(props.disabled.value);\n\n bind({\n attr: {\n active: () => (isActive.value ? true : undefined),\n },\n });\n\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled()) {\n event.preventDefault();\n\n return;\n }\n\n el.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: { value: props.value.value } }));\n };\n\n const tabId = () => `tab-${props.value.value}`;\n const buttonRef = ref<HTMLButtonElement>();\n\n // `aria-controls` as a plain IDREF attribute cannot resolve across shadow-tree boundaries\n // (the matching `id` lives inside <ore-tab-panel>'s own shadow root) — see\n // `setAriaReflection()`'s doc comment. Deferred to `onMounted()`: sibling custom elements\n // (including the peer `<ore-tab-panel>`) connect in tree order within the same synchronous\n // insertion, so a sibling's shadow content may not exist yet during this element's own\n // `setup()` — `onMounted()`'s microtask runs after the whole subtree has connected.\n onMounted(() => {\n watchEffect(() => {\n const button = buttonRef.value;\n const value = props.value.value;\n\n if (!button) return;\n\n const tabsEl = value ? el.closest('ore-tabs') : null;\n const panelEl = tabsEl?.querySelector<HTMLElement>(`:scope > ore-tab-panel[value=\"${value}\"]`);\n const panel = panelEl?.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n\n setAriaReflection(button, 'ariaControlsElements', panel ? [panel] : []);\n });\n });\n\n return html`\n <button\n role=\"tab\"\n type=\"button\"\n part=\"tab\"\n ref=\"${buttonRef}\"\n id=\"${tabId}\"\n aria-selected=\"${isActive}\"\n tabindex=\"${() => (isActive.value ? '0' : '-1')}\"\n aria-disabled=\"${isDisabled}\"\n @click=\"${handleClick}\">\n <slot name=\"prefix\"></slot>\n <slot></slot>\n <slot name=\"suffix\"></slot>\n </button>\n `;\n },\n styles: [colorThemeMixin, forcedColorsFocusMixin('button'), coarsePointerMixin, styles],\n});\n"],"mappings":"+XAwDA,IAAa,EAAe,gBAC5B,EAAA,EAAA,OAAA,CAAwB,EAAc,CACpC,MAAO,CACL,GAAG,EAAA,iBACH,OAAQ,EAAA,KAAK,KAAK,EAAK,EACvB,MAAO,EAAA,KAAK,OAAmB,EAC/B,KAAM,EAAA,KAAK,OAAsB,EACjC,MAAO,EAAA,KAAK,OAAO,EAAE,EACrB,QAAS,EAAA,KAAK,OAAuB,CACvC,EACA,MAAM,EAAO,CACX,IAAM,GAAA,EAAK,EAAA,QAAA,CAAQ,EACb,EAAQ,EAAA,YAER,GAAA,EAAU,EAAA,OAAA,CAAO,EAAA,QAAQ,EAE3B,GACF,MAAY,CACV,IAAM,EAAQ,EAAQ,MAAM,MACtB,EAAO,EAAQ,KAAK,MACpB,EAAU,EAAQ,QAAQ,MAE5B,IAAU,IAAA,IAAW,EAAG,aAAa,QAAS,CAAK,EAEnD,IAAS,IAAA,IAAW,EAAG,aAAa,OAAQ,CAAI,EAEhD,IAAY,IAAA,IAAW,EAAG,aAAa,UAAW,CAAO,CAC/D,CAAC,EAGH,IAAM,GAAA,EAAW,EAAA,SAAA,KACf,EAAU,CAAC,CAAC,EAAQ,MAAM,OAAS,EAAQ,MAAM,QAAU,EAAM,MAAM,MAAQ,EAAM,OAAO,KAC9F,EACM,MAAmB,EAAQ,EAAM,SAAS,OAEhD,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,WAAe,EAAS,MAAQ,GAAO,IAAA,EACzC,CACF,CAAC,EAED,IAAM,EAAe,GAAsB,CAGzC,GAFA,EAAM,gBAAgB,EAElB,EAAW,EAAG,CAChB,EAAM,eAAe,EAErB,MACF,CAEA,EAAG,cAAc,IAAI,YAAY,QAAS,CAAE,QAAS,GAAM,OAAQ,CAAE,MAAO,EAAM,MAAM,KAAM,CAAE,CAAC,CAAC,CACpG,EAEM,MAAc,OAAO,EAAM,MAAM,QACjC,GAAA,EAAY,EAAA,IAAA,CAAuB,EAuBzC,OAfA,EAAA,EAAA,UAAA,KAAgB,EACd,EAAA,EAAA,YAAA,KAAkB,CAChB,IAAM,EAAS,EAAU,MACnB,EAAQ,EAAM,MAAM,MAE1B,GAAI,CAAC,EAAQ,OAIb,IAAM,IAFS,EAAQ,EAAG,QAAQ,UAAU,EAAI,KAAA,EACxB,cAA2B,iCAAiC,EAAM,GAAG,EAAA,EACtE,YAAY,cAA2B,QAAQ,GAAK,KAE3E,EAAA,kBAAkB,EAAQ,uBAAwB,EAAQ,CAAC,CAAK,EAAI,CAAC,CAAC,CACxE,CAAC,CACH,CAAC,EAEM,EAAA,IAAI;;;;;eAKA,EAAU;cACX,EAAM;yBACK,EAAS;wBACP,EAAS,MAAQ,IAAM,KAAM;yBAC/B,EAAW;kBAClB,EAAY;;;;;KAM5B,EACA,OAAQ,CAAC,EAAA,gBAAiB,EAAA,uBAAuB,QAAQ,EAAG,EAAA,mBAAoB,EAAA,OAAM,CACxF,CAAC"}
1
+ {"version":3,"file":"tab-item.cjs","names":[],"sources":["../src/disclosure/tab-item/tab-item.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onMounted, prop, ref, watchEffect } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { setAriaReflection } from '../../core';\nimport { disablableBundle } from '../../shared';\nimport { coarsePointerMixin, colorThemeMixin, forcedColorsFocusMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport { TABS_CTX } from '../tabs/tabs';\nimport styles from './tab-item.css?inline';\n\nexport type OreTabItemProps = {\n /** Whether this tab is currently selected (set by ore-tabs) */\n active?: boolean;\n /** Theme color (inherited from ore-tabs) */\n color?: ThemeColor;\n /** Disable this tab */\n disabled?: boolean;\n /** Spatial density (inherited from ore-tabs) */\n density?: 'compact' | 'default';\n /** Size (inherited from ore-tabs) */\n size?: ComponentSize;\n /** Unique value identifier — must match a ore-tab-panel value */\n value: string;\n /** Visual variant (inherited from ore-tabs) */\n variant?: SurfaceVariant;\n};\n\n/**\n * Individual tab trigger. Must be placed in the `tabs` slot of `ore-tabs`.\n *\n * @element ore-tab-item\n *\n * @attr {string} value - Unique identifier, matches the corresponding ore-tab-panel value\n * @attr {boolean} active - Set by the parent ore-tabs when this tab is selected\n * @attr {boolean} disabled - Prevents selection\n * @attr {string} density - Inherited spatial density: 'default' | 'compact'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} variant - Inherited from ore-tabs: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} color - Inherited from ore-tabs: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @slot prefix - Icon or content before the label\n * @slot - Tab label\n * @slot suffix - Badge or count after the label\n *\n * @cssprop --tab-item-radius - Button border radius\n * @cssprop --tab-item-transition - Transition duration/easing\n * @cssprop --tab-item-padding - Button padding\n * @cssprop --tab-item-font-size - Button font size\n * @cssprop --tab-item-color - Default text color\n * @cssprop --tab-item-hover-bg - Background on hover\n * @cssprop --tab-item-active-bg - Background when active/selected\n * @cssprop --tab-item-active-color - Text color when active/selected\n * @cssprop --tab-item-active-shadow - Box shadow when active/selected\n * @part tab - Tab trigger element.\n * @example\n * ```html\n * <ore-tab-item slot=\"tabs\" value=\"overview\">Overview</ore-tab-item>\n * <ore-tab-item slot=\"tabs\" value=\"settings\" disabled>Settings</ore-tab-item>\n * ```\n */\nexport const TAB_ITEM_TAG = 'ore-tab-item' as const;\ndefine<OreTabItemProps>(TAB_ITEM_TAG, {\n props: {\n ...disablableBundle,\n active: prop.bool(false),\n color: prop.string<ThemeColor>(),\n density: prop.oneOf(['compact', 'default'] as const, 'default'),\n size: prop.string<ComponentSize>(),\n value: prop.string(''),\n variant: prop.string<SurfaceVariant>(),\n },\n setup(props) {\n const el = getHost();\n const watch = watchEffect;\n\n const tabsCtx = inject(TABS_CTX);\n\n if (tabsCtx) {\n watch(() => {\n const color = tabsCtx.color.value;\n const density = tabsCtx.density.value;\n const size = tabsCtx.size.value;\n const variant = tabsCtx.variant.value;\n\n if (color !== undefined) el.setAttribute('color', color);\n\n el.setAttribute('density', density);\n\n if (size !== undefined) el.setAttribute('size', size);\n\n if (variant !== undefined) el.setAttribute('variant', variant);\n });\n }\n\n const isActive = computed(() =>\n tabsCtx ? !!tabsCtx.value.value && tabsCtx.value.value === props.value.value : props.active.value,\n );\n const isDisabled = () => Boolean(props.disabled.value);\n\n bind({\n attr: {\n active: () => (isActive.value ? true : undefined),\n },\n });\n\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled()) {\n event.preventDefault();\n\n return;\n }\n\n el.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: { value: props.value.value } }));\n };\n\n const tabId = () => `tab-${props.value.value}`;\n const buttonRef = ref<HTMLButtonElement>();\n\n // `aria-controls` as a plain IDREF attribute cannot resolve across shadow-tree boundaries\n // (the matching `id` lives inside <ore-tab-panel>'s own shadow root) — see\n // `setAriaReflection()`'s doc comment. Deferred to `onMounted()`: sibling custom elements\n // (including the peer `<ore-tab-panel>`) connect in tree order within the same synchronous\n // insertion, so a sibling's shadow content may not exist yet during this element's own\n // `setup()` — `onMounted()`'s microtask runs after the whole subtree has connected.\n onMounted(() => {\n watchEffect(() => {\n const button = buttonRef.value;\n const value = props.value.value;\n\n if (!button) return;\n\n const tabsEl = value ? el.closest('ore-tabs') : null;\n const panelEl = tabsEl?.querySelector<HTMLElement>(`:scope > ore-tab-panel[value=\"${value}\"]`);\n const panel = panelEl?.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n\n setAriaReflection(button, 'ariaControlsElements', panel ? [panel] : []);\n });\n });\n\n return html`\n <button\n role=\"tab\"\n type=\"button\"\n part=\"tab\"\n ref=\"${buttonRef}\"\n id=\"${tabId}\"\n aria-selected=\"${isActive}\"\n tabindex=\"${() => (isActive.value ? '0' : '-1')}\"\n aria-disabled=\"${isDisabled}\"\n @click=\"${handleClick}\">\n <slot name=\"prefix\"></slot>\n <slot></slot>\n <slot name=\"suffix\"></slot>\n </button>\n `;\n },\n styles: [colorThemeMixin, forcedColorsFocusMixin('button'), coarsePointerMixin, styles],\n});\n"],"mappings":"+XA2DA,IAAa,EAAe,gBAC5B,EAAA,EAAA,OAAA,CAAwB,EAAc,CACpC,MAAO,CACL,GAAG,EAAA,iBACH,OAAQ,EAAA,KAAK,KAAK,EAAK,EACvB,MAAO,EAAA,KAAK,OAAmB,EAC/B,QAAS,EAAA,KAAK,MAAM,CAAC,UAAW,SAAS,EAAY,SAAS,EAC9D,KAAM,EAAA,KAAK,OAAsB,EACjC,MAAO,EAAA,KAAK,OAAO,EAAE,EACrB,QAAS,EAAA,KAAK,OAAuB,CACvC,EACA,MAAM,EAAO,CACX,IAAM,GAAA,EAAK,EAAA,QAAA,CAAQ,EACb,EAAQ,EAAA,YAER,GAAA,EAAU,EAAA,OAAA,CAAO,EAAA,QAAQ,EAE3B,GACF,MAAY,CACV,IAAM,EAAQ,EAAQ,MAAM,MACtB,EAAU,EAAQ,QAAQ,MAC1B,EAAO,EAAQ,KAAK,MACpB,EAAU,EAAQ,QAAQ,MAE5B,IAAU,IAAA,IAAW,EAAG,aAAa,QAAS,CAAK,EAEvD,EAAG,aAAa,UAAW,CAAO,EAE9B,IAAS,IAAA,IAAW,EAAG,aAAa,OAAQ,CAAI,EAEhD,IAAY,IAAA,IAAW,EAAG,aAAa,UAAW,CAAO,CAC/D,CAAC,EAGH,IAAM,GAAA,EAAW,EAAA,SAAA,KACf,EAAU,CAAC,CAAC,EAAQ,MAAM,OAAS,EAAQ,MAAM,QAAU,EAAM,MAAM,MAAQ,EAAM,OAAO,KAC9F,EACM,MAAmB,EAAQ,EAAM,SAAS,OAEhD,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,WAAe,EAAS,MAAQ,GAAO,IAAA,EACzC,CACF,CAAC,EAED,IAAM,EAAe,GAAsB,CAGzC,GAFA,EAAM,gBAAgB,EAElB,EAAW,EAAG,CAChB,EAAM,eAAe,EAErB,MACF,CAEA,EAAG,cAAc,IAAI,YAAY,QAAS,CAAE,QAAS,GAAM,OAAQ,CAAE,MAAO,EAAM,MAAM,KAAM,CAAE,CAAC,CAAC,CACpG,EAEM,MAAc,OAAO,EAAM,MAAM,QACjC,GAAA,EAAY,EAAA,IAAA,CAAuB,EAuBzC,OAfA,EAAA,EAAA,UAAA,KAAgB,EACd,EAAA,EAAA,YAAA,KAAkB,CAChB,IAAM,EAAS,EAAU,MACnB,EAAQ,EAAM,MAAM,MAE1B,GAAI,CAAC,EAAQ,OAIb,IAAM,IAFS,EAAQ,EAAG,QAAQ,UAAU,EAAI,KAAA,EACxB,cAA2B,iCAAiC,EAAM,GAAG,EAAA,EACtE,YAAY,cAA2B,QAAQ,GAAK,KAE3E,EAAA,kBAAkB,EAAQ,uBAAwB,EAAQ,CAAC,CAAK,EAAI,CAAC,CAAC,CACxE,CAAC,CACH,CAAC,EAEM,EAAA,IAAI;;;;;eAKA,EAAU;cACX,EAAM;yBACK,EAAS;wBACP,EAAS,MAAQ,IAAM,KAAM;yBAC/B,EAAW;kBAClB,EAAY;;;;;KAM5B,EACA,OAAQ,CAAC,EAAA,gBAAiB,EAAA,uBAAuB,QAAQ,EAAG,EAAA,mBAAoB,EAAA,OAAM,CACxF,CAAC"}
package/dist/tab-item.js CHANGED
@@ -13,6 +13,7 @@ c(_, {
13
13
  ...i,
14
14
  active: p.bool(!1),
15
15
  color: p.string(),
16
+ density: p.oneOf(["compact", "default"], "default"),
16
17
  size: p.string(),
17
18
  value: p.string(""),
18
19
  variant: p.string()
@@ -20,8 +21,8 @@ c(_, {
20
21
  setup(e) {
21
22
  let t = l(), n = h, i = d(a);
22
23
  i && n(() => {
23
- let e = i.color.value, n = i.size.value, r = i.variant.value;
24
- e !== void 0 && t.setAttribute("color", e), n !== void 0 && t.setAttribute("size", n), r !== void 0 && t.setAttribute("variant", r);
24
+ let e = i.color.value, n = i.density.value, r = i.size.value, a = i.variant.value;
25
+ e !== void 0 && t.setAttribute("color", e), t.setAttribute("density", n), r !== void 0 && t.setAttribute("size", r), a !== void 0 && t.setAttribute("variant", a);
25
26
  });
26
27
  let o = g(() => i ? !!i.value.value && i.value.value === e.value.value : e.active.value), c = () => !!e.disabled.value;
27
28
  s({ attr: { active: () => o.value ? !0 : void 0 } });
@@ -1 +1 @@
1
- {"version":3,"file":"tab-item.js","names":[],"sources":["../src/disclosure/tab-item/tab-item.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onMounted, prop, ref, watchEffect } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { setAriaReflection } from '../../core';\nimport { disablableBundle } from '../../shared';\nimport { coarsePointerMixin, colorThemeMixin, forcedColorsFocusMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport { TABS_CTX } from '../tabs/tabs';\nimport styles from './tab-item.css?inline';\n\nexport type OreTabItemProps = {\n /** Whether this tab is currently selected (set by ore-tabs) */\n active?: boolean;\n /** Theme color (inherited from ore-tabs) */\n color?: ThemeColor;\n /** Disable this tab */\n disabled?: boolean;\n /** Size (inherited from ore-tabs) */\n size?: ComponentSize;\n /** Unique value identifier — must match a ore-tab-panel value */\n value: string;\n /** Visual variant (inherited from ore-tabs) */\n variant?: SurfaceVariant;\n};\n\n/**\n * Individual tab trigger. Must be placed in the `tabs` slot of `ore-tabs`.\n *\n * @element ore-tab-item\n *\n * @attr {string} value - Unique identifier, matches the corresponding ore-tab-panel value\n * @attr {boolean} active - Set by the parent ore-tabs when this tab is selected\n * @attr {boolean} disabled - Prevents selection\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} variant - Inherited from ore-tabs: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} color - Inherited from ore-tabs: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @slot prefix - Icon or content before the label\n * @slot - Tab label\n * @slot suffix - Badge or count after the label\n *\n * @cssprop --tab-item-radius - Button border radius\n * @cssprop --tab-item-transition - Transition duration/easing\n * @cssprop --tab-item-padding - Button padding\n * @cssprop --tab-item-font-size - Button font size\n * @cssprop --tab-item-color - Default text color\n * @cssprop --tab-item-hover-bg - Background on hover\n * @cssprop --tab-item-active-bg - Background when active/selected\n * @cssprop --tab-item-active-color - Text color when active/selected\n * @cssprop --tab-item-active-shadow - Box shadow when active/selected\n * @part tab - Tab trigger element.\n * @example\n * ```html\n * <ore-tab-item slot=\"tabs\" value=\"overview\">Overview</ore-tab-item>\n * <ore-tab-item slot=\"tabs\" value=\"settings\" disabled>Settings</ore-tab-item>\n * ```\n */\nexport const TAB_ITEM_TAG = 'ore-tab-item' as const;\ndefine<OreTabItemProps>(TAB_ITEM_TAG, {\n props: {\n ...disablableBundle,\n active: prop.bool(false),\n color: prop.string<ThemeColor>(),\n size: prop.string<ComponentSize>(),\n value: prop.string(''),\n variant: prop.string<SurfaceVariant>(),\n },\n setup(props) {\n const el = getHost();\n const watch = watchEffect;\n\n const tabsCtx = inject(TABS_CTX);\n\n if (tabsCtx) {\n watch(() => {\n const color = tabsCtx.color.value;\n const size = tabsCtx.size.value;\n const variant = tabsCtx.variant.value;\n\n if (color !== undefined) el.setAttribute('color', color);\n\n if (size !== undefined) el.setAttribute('size', size);\n\n if (variant !== undefined) el.setAttribute('variant', variant);\n });\n }\n\n const isActive = computed(() =>\n tabsCtx ? !!tabsCtx.value.value && tabsCtx.value.value === props.value.value : props.active.value,\n );\n const isDisabled = () => Boolean(props.disabled.value);\n\n bind({\n attr: {\n active: () => (isActive.value ? true : undefined),\n },\n });\n\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled()) {\n event.preventDefault();\n\n return;\n }\n\n el.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: { value: props.value.value } }));\n };\n\n const tabId = () => `tab-${props.value.value}`;\n const buttonRef = ref<HTMLButtonElement>();\n\n // `aria-controls` as a plain IDREF attribute cannot resolve across shadow-tree boundaries\n // (the matching `id` lives inside <ore-tab-panel>'s own shadow root) — see\n // `setAriaReflection()`'s doc comment. Deferred to `onMounted()`: sibling custom elements\n // (including the peer `<ore-tab-panel>`) connect in tree order within the same synchronous\n // insertion, so a sibling's shadow content may not exist yet during this element's own\n // `setup()` — `onMounted()`'s microtask runs after the whole subtree has connected.\n onMounted(() => {\n watchEffect(() => {\n const button = buttonRef.value;\n const value = props.value.value;\n\n if (!button) return;\n\n const tabsEl = value ? el.closest('ore-tabs') : null;\n const panelEl = tabsEl?.querySelector<HTMLElement>(`:scope > ore-tab-panel[value=\"${value}\"]`);\n const panel = panelEl?.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n\n setAriaReflection(button, 'ariaControlsElements', panel ? [panel] : []);\n });\n });\n\n return html`\n <button\n role=\"tab\"\n type=\"button\"\n part=\"tab\"\n ref=\"${buttonRef}\"\n id=\"${tabId}\"\n aria-selected=\"${isActive}\"\n tabindex=\"${() => (isActive.value ? '0' : '-1')}\"\n aria-disabled=\"${isDisabled}\"\n @click=\"${handleClick}\">\n <slot name=\"prefix\"></slot>\n <slot></slot>\n <slot name=\"suffix\"></slot>\n </button>\n `;\n },\n styles: [colorThemeMixin, forcedColorsFocusMixin('button'), coarsePointerMixin, styles],\n});\n"],"mappings":";;;;;;;;;AAwDA,IAAa,IAAe;AAC5B,EAAwB,GAAc;CACpC,OAAO;EACL,GAAG;EACH,QAAQ,EAAK,KAAK,EAAK;EACvB,OAAO,EAAK,OAAmB;EAC/B,MAAM,EAAK,OAAsB;EACjC,OAAO,EAAK,OAAO,EAAE;EACrB,SAAS,EAAK,OAAuB;CACvC;CACA,MAAM,GAAO;EACX,IAAM,IAAK,EAAQ,GACb,IAAQ,GAER,IAAU,EAAO,CAAQ;EAE/B,AAAI,KACF,QAAY;GACV,IAAM,IAAQ,EAAQ,MAAM,OACtB,IAAO,EAAQ,KAAK,OACpB,IAAU,EAAQ,QAAQ;GAMhC,AAJI,MAAU,KAAA,KAAW,EAAG,aAAa,SAAS,CAAK,GAEnD,MAAS,KAAA,KAAW,EAAG,aAAa,QAAQ,CAAI,GAEhD,MAAY,KAAA,KAAW,EAAG,aAAa,WAAW,CAAO;EAC/D,CAAC;EAGH,IAAM,IAAW,QACf,IAAU,CAAC,CAAC,EAAQ,MAAM,SAAS,EAAQ,MAAM,UAAU,EAAM,MAAM,QAAQ,EAAM,OAAO,KAC9F,GACM,UAAmB,EAAQ,EAAM,SAAS;EAEhD,EAAK,EACH,MAAM,EACJ,cAAe,EAAS,QAAQ,KAAO,KAAA,EACzC,EACF,CAAC;EAED,IAAM,KAAe,MAAsB;GAGzC,IAFA,EAAM,gBAAgB,GAElB,EAAW,GAAG;IAChB,EAAM,eAAe;IAErB;GACF;GAEA,EAAG,cAAc,IAAI,YAAY,SAAS;IAAE,SAAS;IAAM,QAAQ,EAAE,OAAO,EAAM,MAAM,MAAM;GAAE,CAAC,CAAC;EACpG,GAEM,UAAc,OAAO,EAAM,MAAM,SACjC,IAAY,EAAuB;EAuBzC,OAfA,QAAgB;GACd,QAAkB;IAChB,IAAM,IAAS,EAAU,OACnB,IAAQ,EAAM,MAAM;IAE1B,IAAI,CAAC,GAAQ;IAIb,IAAM,MAFS,IAAQ,EAAG,QAAQ,UAAU,IAAI,KAAA,EACxB,cAA2B,iCAAiC,EAAM,GAAG,EAAA,EACtE,YAAY,cAA2B,QAAQ,KAAK;IAE3E,EAAkB,GAAQ,wBAAwB,IAAQ,CAAC,CAAK,IAAI,CAAC,CAAC;GACxE,CAAC;EACH,CAAC,GAEM,CAAI;;;;;eAKA,EAAU;cACX,EAAM;yBACK,EAAS;0BACP,EAAS,QAAQ,MAAM,KAAM;yBAC/B,EAAW;kBAClB,EAAY;;;;;;CAM5B;CACA,QAAQ;EAAC;EAAiB,EAAuB,QAAQ;EAAG;EAAoB;CAAM;AACxF,CAAC"}
1
+ {"version":3,"file":"tab-item.js","names":[],"sources":["../src/disclosure/tab-item/tab-item.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onMounted, prop, ref, watchEffect } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { setAriaReflection } from '../../core';\nimport { disablableBundle } from '../../shared';\nimport { coarsePointerMixin, colorThemeMixin, forcedColorsFocusMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport { TABS_CTX } from '../tabs/tabs';\nimport styles from './tab-item.css?inline';\n\nexport type OreTabItemProps = {\n /** Whether this tab is currently selected (set by ore-tabs) */\n active?: boolean;\n /** Theme color (inherited from ore-tabs) */\n color?: ThemeColor;\n /** Disable this tab */\n disabled?: boolean;\n /** Spatial density (inherited from ore-tabs) */\n density?: 'compact' | 'default';\n /** Size (inherited from ore-tabs) */\n size?: ComponentSize;\n /** Unique value identifier — must match a ore-tab-panel value */\n value: string;\n /** Visual variant (inherited from ore-tabs) */\n variant?: SurfaceVariant;\n};\n\n/**\n * Individual tab trigger. Must be placed in the `tabs` slot of `ore-tabs`.\n *\n * @element ore-tab-item\n *\n * @attr {string} value - Unique identifier, matches the corresponding ore-tab-panel value\n * @attr {boolean} active - Set by the parent ore-tabs when this tab is selected\n * @attr {boolean} disabled - Prevents selection\n * @attr {string} density - Inherited spatial density: 'default' | 'compact'\n * @attr {string} size - 'sm' | 'md' | 'lg'\n * @attr {string} variant - Inherited from ore-tabs: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} color - Inherited from ore-tabs: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @slot prefix - Icon or content before the label\n * @slot - Tab label\n * @slot suffix - Badge or count after the label\n *\n * @cssprop --tab-item-radius - Button border radius\n * @cssprop --tab-item-transition - Transition duration/easing\n * @cssprop --tab-item-padding - Button padding\n * @cssprop --tab-item-font-size - Button font size\n * @cssprop --tab-item-color - Default text color\n * @cssprop --tab-item-hover-bg - Background on hover\n * @cssprop --tab-item-active-bg - Background when active/selected\n * @cssprop --tab-item-active-color - Text color when active/selected\n * @cssprop --tab-item-active-shadow - Box shadow when active/selected\n * @part tab - Tab trigger element.\n * @example\n * ```html\n * <ore-tab-item slot=\"tabs\" value=\"overview\">Overview</ore-tab-item>\n * <ore-tab-item slot=\"tabs\" value=\"settings\" disabled>Settings</ore-tab-item>\n * ```\n */\nexport const TAB_ITEM_TAG = 'ore-tab-item' as const;\ndefine<OreTabItemProps>(TAB_ITEM_TAG, {\n props: {\n ...disablableBundle,\n active: prop.bool(false),\n color: prop.string<ThemeColor>(),\n density: prop.oneOf(['compact', 'default'] as const, 'default'),\n size: prop.string<ComponentSize>(),\n value: prop.string(''),\n variant: prop.string<SurfaceVariant>(),\n },\n setup(props) {\n const el = getHost();\n const watch = watchEffect;\n\n const tabsCtx = inject(TABS_CTX);\n\n if (tabsCtx) {\n watch(() => {\n const color = tabsCtx.color.value;\n const density = tabsCtx.density.value;\n const size = tabsCtx.size.value;\n const variant = tabsCtx.variant.value;\n\n if (color !== undefined) el.setAttribute('color', color);\n\n el.setAttribute('density', density);\n\n if (size !== undefined) el.setAttribute('size', size);\n\n if (variant !== undefined) el.setAttribute('variant', variant);\n });\n }\n\n const isActive = computed(() =>\n tabsCtx ? !!tabsCtx.value.value && tabsCtx.value.value === props.value.value : props.active.value,\n );\n const isDisabled = () => Boolean(props.disabled.value);\n\n bind({\n attr: {\n active: () => (isActive.value ? true : undefined),\n },\n });\n\n const handleClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled()) {\n event.preventDefault();\n\n return;\n }\n\n el.dispatchEvent(new CustomEvent('click', { bubbles: true, detail: { value: props.value.value } }));\n };\n\n const tabId = () => `tab-${props.value.value}`;\n const buttonRef = ref<HTMLButtonElement>();\n\n // `aria-controls` as a plain IDREF attribute cannot resolve across shadow-tree boundaries\n // (the matching `id` lives inside <ore-tab-panel>'s own shadow root) — see\n // `setAriaReflection()`'s doc comment. Deferred to `onMounted()`: sibling custom elements\n // (including the peer `<ore-tab-panel>`) connect in tree order within the same synchronous\n // insertion, so a sibling's shadow content may not exist yet during this element's own\n // `setup()` — `onMounted()`'s microtask runs after the whole subtree has connected.\n onMounted(() => {\n watchEffect(() => {\n const button = buttonRef.value;\n const value = props.value.value;\n\n if (!button) return;\n\n const tabsEl = value ? el.closest('ore-tabs') : null;\n const panelEl = tabsEl?.querySelector<HTMLElement>(`:scope > ore-tab-panel[value=\"${value}\"]`);\n const panel = panelEl?.shadowRoot?.querySelector<HTMLElement>('.panel') ?? null;\n\n setAriaReflection(button, 'ariaControlsElements', panel ? [panel] : []);\n });\n });\n\n return html`\n <button\n role=\"tab\"\n type=\"button\"\n part=\"tab\"\n ref=\"${buttonRef}\"\n id=\"${tabId}\"\n aria-selected=\"${isActive}\"\n tabindex=\"${() => (isActive.value ? '0' : '-1')}\"\n aria-disabled=\"${isDisabled}\"\n @click=\"${handleClick}\">\n <slot name=\"prefix\"></slot>\n <slot></slot>\n <slot name=\"suffix\"></slot>\n </button>\n `;\n },\n styles: [colorThemeMixin, forcedColorsFocusMixin('button'), coarsePointerMixin, styles],\n});\n"],"mappings":";;;;;;;;;AA2DA,IAAa,IAAe;AAC5B,EAAwB,GAAc;CACpC,OAAO;EACL,GAAG;EACH,QAAQ,EAAK,KAAK,EAAK;EACvB,OAAO,EAAK,OAAmB;EAC/B,SAAS,EAAK,MAAM,CAAC,WAAW,SAAS,GAAY,SAAS;EAC9D,MAAM,EAAK,OAAsB;EACjC,OAAO,EAAK,OAAO,EAAE;EACrB,SAAS,EAAK,OAAuB;CACvC;CACA,MAAM,GAAO;EACX,IAAM,IAAK,EAAQ,GACb,IAAQ,GAER,IAAU,EAAO,CAAQ;EAE/B,AAAI,KACF,QAAY;GACV,IAAM,IAAQ,EAAQ,MAAM,OACtB,IAAU,EAAQ,QAAQ,OAC1B,IAAO,EAAQ,KAAK,OACpB,IAAU,EAAQ,QAAQ;GAQhC,AANI,MAAU,KAAA,KAAW,EAAG,aAAa,SAAS,CAAK,GAEvD,EAAG,aAAa,WAAW,CAAO,GAE9B,MAAS,KAAA,KAAW,EAAG,aAAa,QAAQ,CAAI,GAEhD,MAAY,KAAA,KAAW,EAAG,aAAa,WAAW,CAAO;EAC/D,CAAC;EAGH,IAAM,IAAW,QACf,IAAU,CAAC,CAAC,EAAQ,MAAM,SAAS,EAAQ,MAAM,UAAU,EAAM,MAAM,QAAQ,EAAM,OAAO,KAC9F,GACM,UAAmB,EAAQ,EAAM,SAAS;EAEhD,EAAK,EACH,MAAM,EACJ,cAAe,EAAS,QAAQ,KAAO,KAAA,EACzC,EACF,CAAC;EAED,IAAM,KAAe,MAAsB;GAGzC,IAFA,EAAM,gBAAgB,GAElB,EAAW,GAAG;IAChB,EAAM,eAAe;IAErB;GACF;GAEA,EAAG,cAAc,IAAI,YAAY,SAAS;IAAE,SAAS;IAAM,QAAQ,EAAE,OAAO,EAAM,MAAM,MAAM;GAAE,CAAC,CAAC;EACpG,GAEM,UAAc,OAAO,EAAM,MAAM,SACjC,IAAY,EAAuB;EAuBzC,OAfA,QAAgB;GACd,QAAkB;IAChB,IAAM,IAAS,EAAU,OACnB,IAAQ,EAAM,MAAM;IAE1B,IAAI,CAAC,GAAQ;IAIb,IAAM,MAFS,IAAQ,EAAG,QAAQ,UAAU,IAAI,KAAA,EACxB,cAA2B,iCAAiC,EAAM,GAAG,EAAA,EACtE,YAAY,cAA2B,QAAQ,KAAK;IAE3E,EAAkB,GAAQ,wBAAwB,IAAQ,CAAC,CAAK,IAAI,CAAC,CAAC;GACxE,CAAC;EACH,CAAC,GAEM,CAAI;;;;;eAKA,EAAU;cACX,EAAM;yBACK,EAAS;0BACP,EAAS,QAAQ,MAAM,KAAM;yBAC/B,EAAW;kBAClB,EAAY;;;;;;CAM5B;CACA,QAAQ;EAAC;EAAiB,EAAuB,QAAQ;EAAG;EAAoB;CAAM;AACxF,CAAC"}
package/dist/tabs.cjs CHANGED
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/theme.css.cjs"),t=require("./core/keyboard.cjs"),n=require("./core/ore.cjs"),r=require("./core/direction.cjs"),i=require("./core/nav.cjs"),a=require("./shared/prop-bundles.cjs"),o=require("./disclosure/tabs/tabs.css.cjs");let s=require("@vielzeug/ore"),c=require("@vielzeug/ripple");var l=(0,s.createContext)(`TabsContext`),u=`ore-tabs`;(0,s.define)(u,{props:{...a.themableBundle,...a.sizableBundle,activation:s.prop.oneOf([`auto`,`manual`],`auto`),label:s.prop.string(),orientation:s.prop.oneOf([`horizontal`,`vertical`],`horizontal`),value:s.prop.string(),variant:s.prop.string()},setup(e){let a=(0,s.getHost)(),o=(0,s.useEmit)(),u=a.shadowRoot,d=(0,s.ref)(),f=(0,s.ref)(),p=(0,c.signal)(e.value.value),m=()=>e.activation.value===`manual`,h=()=>e.orientation.value===`vertical`;(0,s.bind)({attr:{value:()=>p.value??null}});let g=()=>[...a.querySelectorAll(`:scope > ore-tab-item[slot="tabs"]`)],_=()=>g().filter(e=>!e.hasAttribute(`disabled`)),v=e=>{e&&(e.shadowRoot?.querySelector(`[role="tab"]`)??e).focus()},y=(e,t=!1)=>{p.value=e,t&&e&&o(`change`,{value:e})},b=()=>{let e=g();if(e.length===0)return;let t=p.value;if(t&&e.some(e=>e.getAttribute(`value`)===t&&!e.hasAttribute(`disabled`)))return;let n=e.find(e=>!e.hasAttribute(`disabled`))?.getAttribute(`value`)??void 0;y(n,!1)};(0,c.watch)(e.value,e=>{p.value=e,b()});let x=i.createListControl({direction:()=>r.elementDirection(a),getItems:()=>_(),loop:!0,onNavigate:({item:e})=>{if(v(e),!m()){let t=e.getAttribute(`value`);t&&y(t,!0)}},orientation:()=>h()?`vertical`:`both`,signal:n.lifecycleSignal(s.onCleanup)});(0,s.provide)(l,{color:e.color,orientation:(0,c.computed)(()=>e.orientation.value??`horizontal`),size:e.size,value:p,variant:e.variant});let S=e=>{let t=f.value,n=d.value;if(!t||!n||!e)return;let r=e.getBoundingClientRect(),i=n.getBoundingClientRect();if(h()){let e=r.top-i.top+n.scrollTop;t.style.transition=`none`,t.style.height=`${r.height}px`,t.style.width=``,t.style.left=`0`,t.style.top=`0`,t.offsetWidth,t.style.transition=``,t.style.transform=`translateY(${e}px)`}else{let e=r.left-i.left+n.scrollLeft;t.style.transition=`none`,t.style.width=`${r.width}px`,t.style.height=``,t.style.top=``,t.style.left=`0`,t.offsetWidth,t.style.transition=``,t.style.transform=`translateX(${e}px)`}},C=()=>{let e=p.value;if(!e)return;let t=g().find(t=>t.getAttribute(`value`)===e);S(t)};(0,c.watch)(p,()=>{requestAnimationFrame(C)});let w=e=>{let t=e.composedPath().find(e=>e instanceof HTMLElement&&e.localName===`ore-tab-item`);if(!t||t.hasAttribute(`disabled`)||t.closest(`ore-tabs`)!==a)return;let n=t.getAttribute(`value`);!n||n===p.value||y(n,!0)},T=t.createInteraction({disabled:(0,c.computed)(()=>!m()),onPress:()=>{let e=_().find(e=>e===document.activeElement||e.shadowRoot?.activeElement===document.activeElement)?.getAttribute(`value`);e&&e!==p.value&&y(e,!0)}});return(0,s.bind)({on:{click:w,keydown:e=>{let t=_();if(t.length===0)return;let n=e.composedPath().find(e=>e instanceof HTMLElement&&e.localName===`ore-tab-item`)??t.find(e=>e.getAttribute(`value`)===p.value),r=n?t.indexOf(n):-1;r>=0&&x.set(r),!x.handleKeydown(e)&&T.handleKeydown(e)}}}),(0,s.onMounted)(()=>{let e=()=>{b(),C()},t=u?.querySelector(`slot[name="tabs"]`);return t&&t.addEventListener(`slotchange`,e),e(),requestAnimationFrame(e),()=>{t&&t.removeEventListener(`slotchange`,e)}}),s.html`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/theme.css.cjs"),t=require("./core/keyboard.cjs"),n=require("./core/ore.cjs"),r=require("./core/direction.cjs"),i=require("./core/nav.cjs"),a=require("./shared/prop-bundles.cjs"),o=require("./disclosure/tabs/tabs.css.cjs");let s=require("@vielzeug/ore"),c=require("@vielzeug/ripple");var l=(0,s.createContext)(`TabsContext`),u=`ore-tabs`;(0,s.define)(u,{props:{...a.themableBundle,...a.sizableBundle,activation:s.prop.oneOf([`auto`,`manual`],`auto`),density:s.prop.oneOf([`compact`,`default`],`default`),label:s.prop.string(),orientation:s.prop.oneOf([`horizontal`,`vertical`],`horizontal`),value:s.prop.string(),variant:s.prop.string()},setup(e){let a=(0,s.getHost)(),o=(0,s.useEmit)(),u=a.shadowRoot,d=(0,s.ref)(),f=(0,s.ref)(),p=(0,c.signal)(e.value.value),m=()=>e.activation.value===`manual`,h=()=>e.orientation.value===`vertical`;(0,s.bind)({attr:{value:()=>p.value??null}});let g=()=>[...a.querySelectorAll(`:scope > ore-tab-item[slot="tabs"]`)],_=()=>g().filter(e=>!e.hasAttribute(`disabled`)),v=e=>{e&&(e.shadowRoot?.querySelector(`[role="tab"]`)??e).focus()},y=(e,t=!1)=>{p.value=e,t&&e&&o(`change`,{value:e})},b=()=>{let e=g();if(e.length===0)return;let t=p.value;if(t&&e.some(e=>e.getAttribute(`value`)===t&&!e.hasAttribute(`disabled`)))return;let n=e.find(e=>!e.hasAttribute(`disabled`))?.getAttribute(`value`)??void 0;y(n,!1)};(0,c.watch)(e.value,e=>{p.value=e,b()});let x=i.createListControl({direction:()=>r.elementDirection(a),getItems:()=>_(),loop:!0,onNavigate:({item:e})=>{if(v(e),!m()){let t=e.getAttribute(`value`);t&&y(t,!0)}},orientation:()=>h()?`vertical`:`both`,signal:n.lifecycleSignal(s.onCleanup)});(0,s.provide)(l,{color:e.color,density:(0,c.computed)(()=>e.density.value??`default`),orientation:(0,c.computed)(()=>e.orientation.value??`horizontal`),size:e.size,value:p,variant:e.variant});let S=e=>{let t=f.value,n=d.value;if(!t||!n||!e)return;let r=e.getBoundingClientRect(),i=n.getBoundingClientRect();if(h()){let e=r.top-i.top+n.scrollTop;t.style.transition=`none`,t.style.height=`${r.height}px`,t.style.width=``,t.style.left=`0`,t.style.top=`0`,t.offsetWidth,t.style.transition=``,t.style.transform=`translateY(${e}px)`}else{let e=r.left-i.left+n.scrollLeft;t.style.transition=`none`,t.style.width=`${r.width}px`,t.style.height=``,t.style.top=``,t.style.left=`0`,t.offsetWidth,t.style.transition=``,t.style.transform=`translateX(${e}px)`}},C=()=>{let e=p.value;if(!e)return;let t=g().find(t=>t.getAttribute(`value`)===e);S(t)};(0,c.watch)(p,()=>{requestAnimationFrame(C)});let w=e=>{let t=e.composedPath().find(e=>e instanceof HTMLElement&&e.localName===`ore-tab-item`);if(!t||t.hasAttribute(`disabled`)||t.closest(`ore-tabs`)!==a)return;let n=t.getAttribute(`value`);n&&n!==p.value&&y(n,!0)},T=t.createInteraction({disabled:(0,c.computed)(()=>!m()),onPress:()=>{let e=_().find(e=>e===document.activeElement||e.shadowRoot?.activeElement===document.activeElement)?.getAttribute(`value`);e&&e!==p.value&&y(e,!0)}});return(0,s.bind)({on:{click:w,keydown:e=>{let t=_();if(t.length===0)return;let n=e.composedPath().find(e=>e instanceof HTMLElement&&e.localName===`ore-tab-item`)??t.find(e=>e.getAttribute(`value`)===p.value),r=n?t.indexOf(n):-1;r>=0&&x.set(r),!x.handleKeydown(e)&&T.handleKeydown(e)}}}),(0,s.onMounted)(()=>{let e=()=>{b(),C()},t=u?.querySelector(`slot[name="tabs"]`);return t&&t.addEventListener(`slotchange`,e),e(),requestAnimationFrame(e),()=>{t&&t.removeEventListener(`slotchange`,e)}}),s.html`
2
2
  <div class="tablist-wrapper">
3
3
  <div
4
4
  role="tablist"
package/dist/tabs.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.cjs","names":[],"sources":["../src/disclosure/tabs/tabs.ts"],"sourcesContent":["import {\n bind,\n createContext,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n provide,\n ref,\n useEmit,\n} from '@vielzeug/ore';\nimport { computed, type Readable, signal, watch } from '@vielzeug/ripple';\nimport { createInteraction, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport styles from './tabs.css?inline';\n\n/** Context provided by ore-tabs to its ore-tab-item and ore-tab-panel children. */\nexport type TabsContext = {\n color: Readable<ThemeColor | undefined>;\n orientation: Readable<'horizontal' | 'vertical'>;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n variant: Readable<SurfaceVariant | undefined>;\n};\n/** Injection key for the tabs context. */\nexport const TABS_CTX = createContext<TabsContext>('TabsContext');\n\nexport type OreTabsEvents = {\n change: { value: string };\n};\n\nexport type OreTabsProps = {\n /**\n * Keyboard activation mode.\n * - `'auto'` (default): Selecting a tab on arrow-key focus immediately activates it (ARIA recommendation for most cases).\n * - `'manual'`: Arrow keys only move focus; the user must press Enter or Space to activate the focused tab.\n */\n activation?: 'auto' | 'manual';\n /** Theme color */\n color?: ThemeColor;\n /** Accessible label for the tablist (passed as aria-label). Use when there is no visible heading labelling the tabs. */\n label?: string;\n /** Tab list orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Component size */\n size?: ComponentSize;\n /** Currently selected tab value */\n value?: string;\n /** Visual style variant */\n variant?: SurfaceVariant;\n};\n\n/**\n * Tabs container. Manages tab selection and syncs state to child tab items and panels.\n *\n * @element ore-tabs\n * @element ore-tab-item - Child element for tab buttons (auto-discovered)\n * @element ore-tab-panel - Child element for tab content (auto-discovered)\n *\n * @attr {string} value - The value of the currently selected tab\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @fires change - Emitted when the active tab changes with detail: { value: string }\n *\n * @slot tabs - Place `ore-tab-item` elements here\n * @slot - Place `ore-tab-panel` elements here\n *\n * @cssprop --tabs-radius - Border radius of the tablist container and panels\n * @cssprop --tabs-transition - Transition duration/easing for the active indicator\n * @cssprop --tabs-indicator-color - Color of the sliding active indicator line\n * @cssprop --tabs-bg - Background of the host element (flat variant)\n * @cssprop --tabs-tablist-bg - Tablist container background (solid/glass/frost variants)\n * @cssprop --tabs-tablist-border-color - Tablist container border color\n * @part tablist - Container that holds the slotted tab items\n * @part indicator - Active tab indicator element\n * @part panels - Container that holds tab panel content\n * @example\n * ```html\n * <ore-tabs value=\"tab1\" variant=\"bordered\">\n * <ore-tab-item slot=\"tabs\" value=\"tab1\">Overview</ore-tab-item>\n * <ore-tab-item slot=\"tabs\" value=\"tab2\">Settings</ore-tab-item>\n * <ore-tab-panel value=\"tab1\"><p>Overview content</p></ore-tab-panel>\n * <ore-tab-panel value=\"tab2\"><p>Settings content</p></ore-tab-panel>\n * </ore-tabs>\n * ```\n */\nexport const TABS_TAG = 'ore-tabs' as const;\ndefine<OreTabsProps>(TABS_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n activation: prop.oneOf(['auto', 'manual'] as const, 'auto'),\n label: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'horizontal'),\n value: prop.string(),\n variant: prop.string<SurfaceVariant>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreTabsEvents>();\n\n const shadowRoot = el.shadowRoot;\n const tablistRef = ref<HTMLElement>();\n const indicatorRef = ref<HTMLElement>();\n const selectedValue = signal<string | undefined>(props.value.value);\n const isManualActivation = () => props.activation.value === 'manual';\n const isVertical = () => props.orientation.value === 'vertical';\n\n bind({\n attr: {\n value: () => selectedValue.value ?? null,\n },\n });\n\n const getTabs = () => [...el.querySelectorAll<HTMLElement>(':scope > ore-tab-item[slot=\"tabs\"]')];\n const getEnabledTabs = () => getTabs().filter((t) => !t.hasAttribute('disabled'));\n const focusTab = (tab: HTMLElement | undefined) => {\n if (!tab) return;\n\n const focusable = tab.shadowRoot?.querySelector<HTMLElement>('[role=\"tab\"]') ?? tab;\n\n focusable.focus();\n };\n\n // ────────────────────────────────────────────────────────────────\n // Selection State Management\n // ────────────────────────────────────────────────────────────────\n\n const setSelection = (value: string | undefined, shouldEmit = false) => {\n selectedValue.value = value;\n\n if (shouldEmit && value) emit('change', { value });\n };\n\n const ensureSelection = () => {\n const tabs = getTabs();\n\n if (tabs.length === 0) return; // No tabs yet, keep current selection\n\n const current = selectedValue.value;\n const hasCurrent = current\n ? tabs.some((tab) => tab.getAttribute('value') === current && !tab.hasAttribute('disabled'))\n : false;\n\n if (hasCurrent) return;\n\n const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled'))?.getAttribute('value') ?? undefined;\n\n setSelection(firstEnabled, false);\n };\n\n watch(props.value, (value) => {\n selectedValue.value = value;\n ensureSelection();\n });\n\n // ────────────────────────────────────────────────────────────────\n // List Control for Keyboard Navigation\n // ────────────────────────────────────────────────────────────────\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getEnabledTabs(),\n loop: true,\n onNavigate: ({ item }) => {\n focusTab(item);\n\n if (!isManualActivation()) {\n const value = item.getAttribute('value');\n\n if (value) setSelection(value, true);\n }\n },\n orientation: () => (isVertical() ? 'vertical' : 'both'),\n signal: lifecycleSignal(onCleanup),\n });\n\n // ────────────────────────────────────────────────────────────────\n // Context & Indicator Management\n // ────────────────────────────────────────────────────────────────\n\n provide(TABS_CTX, {\n color: props.color,\n orientation: computed(() => props.orientation.value ?? 'horizontal'),\n size: props.size,\n value: selectedValue,\n variant: props.variant,\n });\n\n const moveIndicator = (activeTab: HTMLElement | undefined) => {\n const indicator = indicatorRef.value;\n const tablist = tablistRef.value;\n\n if (!indicator || !tablist || !activeTab) return;\n\n const tabRect = activeTab.getBoundingClientRect();\n const listRect = tablist.getBoundingClientRect();\n\n if (isVertical()) {\n const y = tabRect.top - listRect.top + tablist.scrollTop;\n\n indicator.style.transition = 'none';\n indicator.style.height = `${tabRect.height}px`;\n indicator.style.width = '';\n indicator.style.left = '0';\n indicator.style.top = '0';\n void indicator.offsetWidth;\n indicator.style.transition = '';\n indicator.style.transform = `translateY(${y}px)`;\n } else {\n const x = tabRect.left - listRect.left + tablist.scrollLeft;\n\n indicator.style.transition = 'none';\n indicator.style.width = `${tabRect.width}px`;\n indicator.style.height = '';\n indicator.style.top = '';\n indicator.style.left = '0';\n void indicator.offsetWidth;\n indicator.style.transition = '';\n indicator.style.transform = `translateX(${x}px)`;\n }\n };\n\n const updateIndicator = () => {\n const value = selectedValue.value;\n\n if (!value) return;\n\n const activeTab = getTabs().find((t) => t.getAttribute('value') === value);\n\n moveIndicator(activeTab);\n };\n\n watch(selectedValue, () => {\n requestAnimationFrame(updateIndicator);\n });\n\n // ────────────────────────────────────────────────────────────────\n // Event Handlers\n // ────────────────────────────────────────────────────────────────\n\n const handleTabClick = (e: Event) => {\n const tab = e\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item');\n\n if (!tab || tab.hasAttribute('disabled')) return;\n\n // Guard: only respond to tab-items that belong to THIS tabs instance\n if (tab.closest('ore-tabs') !== el) return;\n\n const value = tab.getAttribute('value');\n\n if (!value || value === selectedValue.value) return;\n\n setSelection(value, true);\n };\n\n const activateFocusedTab = (): void => {\n const tabs = getEnabledTabs();\n const focusedTab = tabs.find(\n (tab) => tab === document.activeElement || tab.shadowRoot?.activeElement === document.activeElement,\n );\n const focusedValue = focusedTab?.getAttribute('value');\n\n if (focusedValue && focusedValue !== selectedValue.value) setSelection(focusedValue, true);\n };\n\n const manualActivationPress = createInteraction({\n disabled: computed(() => !isManualActivation()),\n onPress: activateFocusedTab,\n });\n\n const handleKeydown = (e: KeyboardEvent) => {\n const tabs = getEnabledTabs();\n\n if (tabs.length === 0) return;\n\n const path = e.composedPath();\n const activeTabFromEvent = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item',\n );\n\n const tabFromEvent = activeTabFromEvent ?? tabs.find((t) => t.getAttribute('value') === selectedValue.value);\n const focused = tabFromEvent ? tabs.indexOf(tabFromEvent) : -1;\n\n if (focused >= 0) listControl.set(focused);\n\n if (listControl.handleKeydown(e)) return;\n\n manualActivationPress.handleKeydown(e);\n };\n\n bind({\n on: {\n click: handleTabClick,\n keydown: handleKeydown,\n },\n });\n\n // ────────────────────────────────────────────────────────────────\n // Lifecycle\n // ────────────────────────────────────────────────────────────────\n\n onMounted(() => {\n const syncSelection = () => {\n ensureSelection();\n updateIndicator();\n };\n\n const tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n\n if (tabsSlot) {\n tabsSlot.addEventListener('slotchange', syncSelection);\n }\n\n syncSelection();\n requestAnimationFrame(syncSelection);\n\n return () => {\n if (tabsSlot) {\n tabsSlot.removeEventListener('slotchange', syncSelection);\n }\n };\n });\n\n return html`\n <div class=\"tablist-wrapper\">\n <div\n role=\"tablist\"\n ref=\"${tablistRef}\"\n part=\"tablist\"\n aria-orientation=\"${props.orientation}\"\n aria-label=\"${props.label}\">\n <slot name=\"tabs\"></slot>\n </div>\n <div class=\"indicator\" ref=\"${indicatorRef}\" part=\"indicator\"></div>\n </div>\n <div class=\"panels\" part=\"panels\">\n <slot></slot>\n </div>\n `;\n },\n styles: [colorThemeMixin, styles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"tabs.cjs","names":[],"sources":["../src/disclosure/tabs/tabs.ts"],"sourcesContent":["import {\n bind,\n createContext,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n provide,\n ref,\n useEmit,\n} from '@vielzeug/ore';\nimport { computed, type Readable, signal, watch } from '@vielzeug/ripple';\nimport { createInteraction, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport styles from './tabs.css?inline';\n\n/** Context provided by ore-tabs to its ore-tab-item and ore-tab-panel children. */\nexport type TabsContext = {\n color: Readable<ThemeColor | undefined>;\n density: Readable<'compact' | 'default'>;\n orientation: Readable<'horizontal' | 'vertical'>;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n variant: Readable<SurfaceVariant | undefined>;\n};\n/** Injection key for the tabs context. */\nexport const TABS_CTX = createContext<TabsContext>('TabsContext');\n\nexport type OreTabsEvents = {\n change: { value: string };\n};\n\nexport type OreTabsProps = {\n /**\n * Keyboard activation mode.\n * - `'auto'` (default): Selecting a tab on arrow-key focus immediately activates it (ARIA recommendation for most cases).\n * - `'manual'`: Arrow keys only move focus; the user must press Enter or Space to activate the focused tab.\n */\n activation?: 'auto' | 'manual';\n /** Theme color */\n color?: ThemeColor;\n /** Spatial density; compact renders a 24px tab control for dense interfaces. */\n density?: 'compact' | 'default';\n /** Accessible label for the tablist (passed as aria-label). Use when there is no visible heading labelling the tabs. */\n label?: string;\n /** Tab list orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Component size */\n size?: ComponentSize;\n /** Currently selected tab value */\n value?: string;\n /** Visual style variant */\n variant?: SurfaceVariant;\n};\n\n/**\n * Tabs container. Manages tab selection and syncs state to child tab items and panels.\n *\n * @element ore-tabs\n * @element ore-tab-item - Child element for tab buttons (auto-discovered)\n * @element ore-tab-panel - Child element for tab content (auto-discovered)\n *\n * @attr {string} value - The value of the currently selected tab\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n * @attr {string} density - Spatial density: 'default' | 'compact'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @fires change - Emitted when the active tab changes with detail: { value: string }\n *\n * @slot tabs - Place `ore-tab-item` elements here\n * @slot - Place `ore-tab-panel` elements here\n *\n * @cssprop --tabs-radius - Border radius of the tablist container and panels\n * @cssprop --tabs-transition - Transition duration/easing for the active indicator\n * @cssprop --tabs-indicator-color - Color of the sliding active indicator line\n * @cssprop --tabs-bg - Background of the host element (flat variant)\n * @cssprop --tabs-tablist-bg - Tablist container background (solid/glass/frost variants)\n * @cssprop --tabs-tablist-border-color - Tablist container border color\n * @part tablist - Container that holds the slotted tab items\n * @part indicator - Active tab indicator element\n * @part panels - Container that holds tab panel content\n * @example\n * ```html\n * <ore-tabs value=\"tab1\" variant=\"bordered\">\n * <ore-tab-item slot=\"tabs\" value=\"tab1\">Overview</ore-tab-item>\n * <ore-tab-item slot=\"tabs\" value=\"tab2\">Settings</ore-tab-item>\n * <ore-tab-panel value=\"tab1\"><p>Overview content</p></ore-tab-panel>\n * <ore-tab-panel value=\"tab2\"><p>Settings content</p></ore-tab-panel>\n * </ore-tabs>\n * ```\n */\nexport const TABS_TAG = 'ore-tabs' as const;\ndefine<OreTabsProps>(TABS_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n activation: prop.oneOf(['auto', 'manual'] as const, 'auto'),\n density: prop.oneOf(['compact', 'default'] as const, 'default'),\n label: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'horizontal'),\n value: prop.string(),\n variant: prop.string<SurfaceVariant>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreTabsEvents>();\n\n const shadowRoot = el.shadowRoot;\n const tablistRef = ref<HTMLElement>();\n const indicatorRef = ref<HTMLElement>();\n const selectedValue = signal<string | undefined>(props.value.value);\n const isManualActivation = () => props.activation.value === 'manual';\n const isVertical = () => props.orientation.value === 'vertical';\n\n bind({\n attr: {\n value: () => selectedValue.value ?? null,\n },\n });\n\n const getTabs = () => [...el.querySelectorAll<HTMLElement>(':scope > ore-tab-item[slot=\"tabs\"]')];\n const getEnabledTabs = () => getTabs().filter((t) => !t.hasAttribute('disabled'));\n const focusTab = (tab: HTMLElement | undefined) => {\n if (!tab) return;\n\n const focusable = tab.shadowRoot?.querySelector<HTMLElement>('[role=\"tab\"]') ?? tab;\n\n focusable.focus();\n };\n\n // ────────────────────────────────────────────────────────────────\n // Selection State Management\n // ────────────────────────────────────────────────────────────────\n\n const setSelection = (value: string | undefined, shouldEmit = false) => {\n selectedValue.value = value;\n\n if (shouldEmit && value) emit('change', { value });\n };\n\n const ensureSelection = () => {\n const tabs = getTabs();\n\n if (tabs.length === 0) return; // No tabs yet, keep current selection\n\n const current = selectedValue.value;\n const hasCurrent = current\n ? tabs.some((tab) => tab.getAttribute('value') === current && !tab.hasAttribute('disabled'))\n : false;\n\n if (hasCurrent) return;\n\n const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled'))?.getAttribute('value') ?? undefined;\n\n setSelection(firstEnabled, false);\n };\n\n watch(props.value, (value) => {\n selectedValue.value = value;\n ensureSelection();\n });\n\n // ────────────────────────────────────────────────────────────────\n // List Control for Keyboard Navigation\n // ────────────────────────────────────────────────────────────────\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getEnabledTabs(),\n loop: true,\n onNavigate: ({ item }) => {\n focusTab(item);\n\n if (!isManualActivation()) {\n const value = item.getAttribute('value');\n\n if (value) setSelection(value, true);\n }\n },\n orientation: () => (isVertical() ? 'vertical' : 'both'),\n signal: lifecycleSignal(onCleanup),\n });\n\n // ────────────────────────────────────────────────────────────────\n // Context & Indicator Management\n // ────────────────────────────────────────────────────────────────\n\n provide(TABS_CTX, {\n color: props.color,\n density: computed(() => props.density.value ?? 'default'),\n orientation: computed(() => props.orientation.value ?? 'horizontal'),\n size: props.size,\n value: selectedValue,\n variant: props.variant,\n });\n\n const moveIndicator = (activeTab: HTMLElement | undefined) => {\n const indicator = indicatorRef.value;\n const tablist = tablistRef.value;\n\n if (!indicator || !tablist || !activeTab) return;\n\n const tabRect = activeTab.getBoundingClientRect();\n const listRect = tablist.getBoundingClientRect();\n\n if (isVertical()) {\n const y = tabRect.top - listRect.top + tablist.scrollTop;\n\n indicator.style.transition = 'none';\n indicator.style.height = `${tabRect.height}px`;\n indicator.style.width = '';\n indicator.style.left = '0';\n indicator.style.top = '0';\n void indicator.offsetWidth;\n indicator.style.transition = '';\n indicator.style.transform = `translateY(${y}px)`;\n } else {\n const x = tabRect.left - listRect.left + tablist.scrollLeft;\n\n indicator.style.transition = 'none';\n indicator.style.width = `${tabRect.width}px`;\n indicator.style.height = '';\n indicator.style.top = '';\n indicator.style.left = '0';\n void indicator.offsetWidth;\n indicator.style.transition = '';\n indicator.style.transform = `translateX(${x}px)`;\n }\n };\n\n const updateIndicator = () => {\n const value = selectedValue.value;\n\n if (!value) return;\n\n const activeTab = getTabs().find((t) => t.getAttribute('value') === value);\n\n moveIndicator(activeTab);\n };\n\n watch(selectedValue, () => {\n requestAnimationFrame(updateIndicator);\n });\n\n // ────────────────────────────────────────────────────────────────\n // Event Handlers\n // ────────────────────────────────────────────────────────────────\n\n const handleTabClick = (e: Event) => {\n const tab = e\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item');\n\n if (!tab || tab.hasAttribute('disabled')) return;\n\n // Guard: only respond to tab-items that belong to THIS tabs instance\n if (tab.closest('ore-tabs') !== el) return;\n\n const value = tab.getAttribute('value');\n\n if (!value || value === selectedValue.value) return;\n\n setSelection(value, true);\n };\n\n const activateFocusedTab = (): void => {\n const tabs = getEnabledTabs();\n const focusedTab = tabs.find(\n (tab) => tab === document.activeElement || tab.shadowRoot?.activeElement === document.activeElement,\n );\n const focusedValue = focusedTab?.getAttribute('value');\n\n if (focusedValue && focusedValue !== selectedValue.value) setSelection(focusedValue, true);\n };\n\n const manualActivationPress = createInteraction({\n disabled: computed(() => !isManualActivation()),\n onPress: activateFocusedTab,\n });\n\n const handleKeydown = (e: KeyboardEvent) => {\n const tabs = getEnabledTabs();\n\n if (tabs.length === 0) return;\n\n const path = e.composedPath();\n const activeTabFromEvent = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item',\n );\n\n const tabFromEvent = activeTabFromEvent ?? tabs.find((t) => t.getAttribute('value') === selectedValue.value);\n const focused = tabFromEvent ? tabs.indexOf(tabFromEvent) : -1;\n\n if (focused >= 0) listControl.set(focused);\n\n if (listControl.handleKeydown(e)) return;\n\n manualActivationPress.handleKeydown(e);\n };\n\n bind({\n on: {\n click: handleTabClick,\n keydown: handleKeydown,\n },\n });\n\n // ────────────────────────────────────────────────────────────────\n // Lifecycle\n // ────────────────────────────────────────────────────────────────\n\n onMounted(() => {\n const syncSelection = () => {\n ensureSelection();\n updateIndicator();\n };\n\n const tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n\n if (tabsSlot) {\n tabsSlot.addEventListener('slotchange', syncSelection);\n }\n\n syncSelection();\n requestAnimationFrame(syncSelection);\n\n return () => {\n if (tabsSlot) {\n tabsSlot.removeEventListener('slotchange', syncSelection);\n }\n };\n });\n\n return html`\n <div class=\"tablist-wrapper\">\n <div\n role=\"tablist\"\n ref=\"${tablistRef}\"\n part=\"tablist\"\n aria-orientation=\"${props.orientation}\"\n aria-label=\"${props.label}\">\n <slot name=\"tabs\"></slot>\n </div>\n <div class=\"indicator\" ref=\"${indicatorRef}\" part=\"indicator\"></div>\n </div>\n <div class=\"panels\" part=\"panels\">\n <slot></slot>\n </div>\n `;\n },\n styles: [colorThemeMixin, styles],\n});\n"],"mappings":"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"}
package/dist/tabs.js CHANGED
@@ -14,6 +14,7 @@ u(C, {
14
14
  ...o,
15
15
  ...a,
16
16
  activation: h.oneOf(["auto", "manual"], "auto"),
17
+ density: h.oneOf(["compact", "default"], "default"),
17
18
  label: h.string(),
18
19
  orientation: h.oneOf(["horizontal", "vertical"], "horizontal"),
19
20
  value: h.string(),
@@ -52,6 +53,7 @@ u(C, {
52
53
  });
53
54
  g(S, {
54
55
  color: e.color,
56
+ density: y(() => e.density.value ?? "default"),
55
57
  orientation: y(() => e.orientation.value ?? "horizontal"),
56
58
  size: e.size,
57
59
  value: h,
@@ -81,7 +83,7 @@ u(C, {
81
83
  let t = e.composedPath().find((e) => e instanceof HTMLElement && e.localName === "ore-tab-item");
82
84
  if (!t || t.hasAttribute("disabled") || t.closest("ore-tabs") !== a) return;
83
85
  let n = t.getAttribute("value");
84
- !n || n === h.value || O(n, !0);
86
+ n && n !== h.value && O(n, !0);
85
87
  }, P = t({
86
88
  disabled: y(() => !C()),
87
89
  onPress: () => {
package/dist/tabs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.js","names":[],"sources":["../src/disclosure/tabs/tabs.ts"],"sourcesContent":["import {\n bind,\n createContext,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n provide,\n ref,\n useEmit,\n} from '@vielzeug/ore';\nimport { computed, type Readable, signal, watch } from '@vielzeug/ripple';\nimport { createInteraction, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport styles from './tabs.css?inline';\n\n/** Context provided by ore-tabs to its ore-tab-item and ore-tab-panel children. */\nexport type TabsContext = {\n color: Readable<ThemeColor | undefined>;\n orientation: Readable<'horizontal' | 'vertical'>;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n variant: Readable<SurfaceVariant | undefined>;\n};\n/** Injection key for the tabs context. */\nexport const TABS_CTX = createContext<TabsContext>('TabsContext');\n\nexport type OreTabsEvents = {\n change: { value: string };\n};\n\nexport type OreTabsProps = {\n /**\n * Keyboard activation mode.\n * - `'auto'` (default): Selecting a tab on arrow-key focus immediately activates it (ARIA recommendation for most cases).\n * - `'manual'`: Arrow keys only move focus; the user must press Enter or Space to activate the focused tab.\n */\n activation?: 'auto' | 'manual';\n /** Theme color */\n color?: ThemeColor;\n /** Accessible label for the tablist (passed as aria-label). Use when there is no visible heading labelling the tabs. */\n label?: string;\n /** Tab list orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Component size */\n size?: ComponentSize;\n /** Currently selected tab value */\n value?: string;\n /** Visual style variant */\n variant?: SurfaceVariant;\n};\n\n/**\n * Tabs container. Manages tab selection and syncs state to child tab items and panels.\n *\n * @element ore-tabs\n * @element ore-tab-item - Child element for tab buttons (auto-discovered)\n * @element ore-tab-panel - Child element for tab content (auto-discovered)\n *\n * @attr {string} value - The value of the currently selected tab\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @fires change - Emitted when the active tab changes with detail: { value: string }\n *\n * @slot tabs - Place `ore-tab-item` elements here\n * @slot - Place `ore-tab-panel` elements here\n *\n * @cssprop --tabs-radius - Border radius of the tablist container and panels\n * @cssprop --tabs-transition - Transition duration/easing for the active indicator\n * @cssprop --tabs-indicator-color - Color of the sliding active indicator line\n * @cssprop --tabs-bg - Background of the host element (flat variant)\n * @cssprop --tabs-tablist-bg - Tablist container background (solid/glass/frost variants)\n * @cssprop --tabs-tablist-border-color - Tablist container border color\n * @part tablist - Container that holds the slotted tab items\n * @part indicator - Active tab indicator element\n * @part panels - Container that holds tab panel content\n * @example\n * ```html\n * <ore-tabs value=\"tab1\" variant=\"bordered\">\n * <ore-tab-item slot=\"tabs\" value=\"tab1\">Overview</ore-tab-item>\n * <ore-tab-item slot=\"tabs\" value=\"tab2\">Settings</ore-tab-item>\n * <ore-tab-panel value=\"tab1\"><p>Overview content</p></ore-tab-panel>\n * <ore-tab-panel value=\"tab2\"><p>Settings content</p></ore-tab-panel>\n * </ore-tabs>\n * ```\n */\nexport const TABS_TAG = 'ore-tabs' as const;\ndefine<OreTabsProps>(TABS_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n activation: prop.oneOf(['auto', 'manual'] as const, 'auto'),\n label: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'horizontal'),\n value: prop.string(),\n variant: prop.string<SurfaceVariant>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreTabsEvents>();\n\n const shadowRoot = el.shadowRoot;\n const tablistRef = ref<HTMLElement>();\n const indicatorRef = ref<HTMLElement>();\n const selectedValue = signal<string | undefined>(props.value.value);\n const isManualActivation = () => props.activation.value === 'manual';\n const isVertical = () => props.orientation.value === 'vertical';\n\n bind({\n attr: {\n value: () => selectedValue.value ?? null,\n },\n });\n\n const getTabs = () => [...el.querySelectorAll<HTMLElement>(':scope > ore-tab-item[slot=\"tabs\"]')];\n const getEnabledTabs = () => getTabs().filter((t) => !t.hasAttribute('disabled'));\n const focusTab = (tab: HTMLElement | undefined) => {\n if (!tab) return;\n\n const focusable = tab.shadowRoot?.querySelector<HTMLElement>('[role=\"tab\"]') ?? tab;\n\n focusable.focus();\n };\n\n // ────────────────────────────────────────────────────────────────\n // Selection State Management\n // ────────────────────────────────────────────────────────────────\n\n const setSelection = (value: string | undefined, shouldEmit = false) => {\n selectedValue.value = value;\n\n if (shouldEmit && value) emit('change', { value });\n };\n\n const ensureSelection = () => {\n const tabs = getTabs();\n\n if (tabs.length === 0) return; // No tabs yet, keep current selection\n\n const current = selectedValue.value;\n const hasCurrent = current\n ? tabs.some((tab) => tab.getAttribute('value') === current && !tab.hasAttribute('disabled'))\n : false;\n\n if (hasCurrent) return;\n\n const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled'))?.getAttribute('value') ?? undefined;\n\n setSelection(firstEnabled, false);\n };\n\n watch(props.value, (value) => {\n selectedValue.value = value;\n ensureSelection();\n });\n\n // ────────────────────────────────────────────────────────────────\n // List Control for Keyboard Navigation\n // ────────────────────────────────────────────────────────────────\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getEnabledTabs(),\n loop: true,\n onNavigate: ({ item }) => {\n focusTab(item);\n\n if (!isManualActivation()) {\n const value = item.getAttribute('value');\n\n if (value) setSelection(value, true);\n }\n },\n orientation: () => (isVertical() ? 'vertical' : 'both'),\n signal: lifecycleSignal(onCleanup),\n });\n\n // ────────────────────────────────────────────────────────────────\n // Context & Indicator Management\n // ────────────────────────────────────────────────────────────────\n\n provide(TABS_CTX, {\n color: props.color,\n orientation: computed(() => props.orientation.value ?? 'horizontal'),\n size: props.size,\n value: selectedValue,\n variant: props.variant,\n });\n\n const moveIndicator = (activeTab: HTMLElement | undefined) => {\n const indicator = indicatorRef.value;\n const tablist = tablistRef.value;\n\n if (!indicator || !tablist || !activeTab) return;\n\n const tabRect = activeTab.getBoundingClientRect();\n const listRect = tablist.getBoundingClientRect();\n\n if (isVertical()) {\n const y = tabRect.top - listRect.top + tablist.scrollTop;\n\n indicator.style.transition = 'none';\n indicator.style.height = `${tabRect.height}px`;\n indicator.style.width = '';\n indicator.style.left = '0';\n indicator.style.top = '0';\n void indicator.offsetWidth;\n indicator.style.transition = '';\n indicator.style.transform = `translateY(${y}px)`;\n } else {\n const x = tabRect.left - listRect.left + tablist.scrollLeft;\n\n indicator.style.transition = 'none';\n indicator.style.width = `${tabRect.width}px`;\n indicator.style.height = '';\n indicator.style.top = '';\n indicator.style.left = '0';\n void indicator.offsetWidth;\n indicator.style.transition = '';\n indicator.style.transform = `translateX(${x}px)`;\n }\n };\n\n const updateIndicator = () => {\n const value = selectedValue.value;\n\n if (!value) return;\n\n const activeTab = getTabs().find((t) => t.getAttribute('value') === value);\n\n moveIndicator(activeTab);\n };\n\n watch(selectedValue, () => {\n requestAnimationFrame(updateIndicator);\n });\n\n // ────────────────────────────────────────────────────────────────\n // Event Handlers\n // ────────────────────────────────────────────────────────────────\n\n const handleTabClick = (e: Event) => {\n const tab = e\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item');\n\n if (!tab || tab.hasAttribute('disabled')) return;\n\n // Guard: only respond to tab-items that belong to THIS tabs instance\n if (tab.closest('ore-tabs') !== el) return;\n\n const value = tab.getAttribute('value');\n\n if (!value || value === selectedValue.value) return;\n\n setSelection(value, true);\n };\n\n const activateFocusedTab = (): void => {\n const tabs = getEnabledTabs();\n const focusedTab = tabs.find(\n (tab) => tab === document.activeElement || tab.shadowRoot?.activeElement === document.activeElement,\n );\n const focusedValue = focusedTab?.getAttribute('value');\n\n if (focusedValue && focusedValue !== selectedValue.value) setSelection(focusedValue, true);\n };\n\n const manualActivationPress = createInteraction({\n disabled: computed(() => !isManualActivation()),\n onPress: activateFocusedTab,\n });\n\n const handleKeydown = (e: KeyboardEvent) => {\n const tabs = getEnabledTabs();\n\n if (tabs.length === 0) return;\n\n const path = e.composedPath();\n const activeTabFromEvent = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item',\n );\n\n const tabFromEvent = activeTabFromEvent ?? tabs.find((t) => t.getAttribute('value') === selectedValue.value);\n const focused = tabFromEvent ? tabs.indexOf(tabFromEvent) : -1;\n\n if (focused >= 0) listControl.set(focused);\n\n if (listControl.handleKeydown(e)) return;\n\n manualActivationPress.handleKeydown(e);\n };\n\n bind({\n on: {\n click: handleTabClick,\n keydown: handleKeydown,\n },\n });\n\n // ────────────────────────────────────────────────────────────────\n // Lifecycle\n // ────────────────────────────────────────────────────────────────\n\n onMounted(() => {\n const syncSelection = () => {\n ensureSelection();\n updateIndicator();\n };\n\n const tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n\n if (tabsSlot) {\n tabsSlot.addEventListener('slotchange', syncSelection);\n }\n\n syncSelection();\n requestAnimationFrame(syncSelection);\n\n return () => {\n if (tabsSlot) {\n tabsSlot.removeEventListener('slotchange', syncSelection);\n }\n };\n });\n\n return html`\n <div class=\"tablist-wrapper\">\n <div\n role=\"tablist\"\n ref=\"${tablistRef}\"\n part=\"tablist\"\n aria-orientation=\"${props.orientation}\"\n aria-label=\"${props.label}\">\n <slot name=\"tabs\"></slot>\n </div>\n <div class=\"indicator\" ref=\"${indicatorRef}\" part=\"indicator\"></div>\n </div>\n <div class=\"panels\" part=\"panels\">\n <slot></slot>\n </div>\n `;\n },\n styles: [colorThemeMixin, styles],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"tabs.js","names":[],"sources":["../src/disclosure/tabs/tabs.ts"],"sourcesContent":["import {\n bind,\n createContext,\n define,\n getHost,\n html,\n onCleanup,\n onMounted,\n prop,\n provide,\n ref,\n useEmit,\n} from '@vielzeug/ore';\nimport { computed, type Readable, signal, watch } from '@vielzeug/ripple';\nimport { createInteraction, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport styles from './tabs.css?inline';\n\n/** Context provided by ore-tabs to its ore-tab-item and ore-tab-panel children. */\nexport type TabsContext = {\n color: Readable<ThemeColor | undefined>;\n density: Readable<'compact' | 'default'>;\n orientation: Readable<'horizontal' | 'vertical'>;\n size: Readable<ComponentSize | undefined>;\n value: Readable<string | undefined>;\n variant: Readable<SurfaceVariant | undefined>;\n};\n/** Injection key for the tabs context. */\nexport const TABS_CTX = createContext<TabsContext>('TabsContext');\n\nexport type OreTabsEvents = {\n change: { value: string };\n};\n\nexport type OreTabsProps = {\n /**\n * Keyboard activation mode.\n * - `'auto'` (default): Selecting a tab on arrow-key focus immediately activates it (ARIA recommendation for most cases).\n * - `'manual'`: Arrow keys only move focus; the user must press Enter or Space to activate the focused tab.\n */\n activation?: 'auto' | 'manual';\n /** Theme color */\n color?: ThemeColor;\n /** Spatial density; compact renders a 24px tab control for dense interfaces. */\n density?: 'compact' | 'default';\n /** Accessible label for the tablist (passed as aria-label). Use when there is no visible heading labelling the tabs. */\n label?: string;\n /** Tab list orientation */\n orientation?: 'horizontal' | 'vertical';\n /** Component size */\n size?: ComponentSize;\n /** Currently selected tab value */\n value?: string;\n /** Visual style variant */\n variant?: SurfaceVariant;\n};\n\n/**\n * Tabs container. Manages tab selection and syncs state to child tab items and panels.\n *\n * @element ore-tabs\n * @element ore-tab-item - Child element for tab buttons (auto-discovered)\n * @element ore-tab-panel - Child element for tab content (auto-discovered)\n *\n * @attr {string} value - The value of the currently selected tab\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n * @attr {string} density - Spatial density: 'default' | 'compact'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @fires change - Emitted when the active tab changes with detail: { value: string }\n *\n * @slot tabs - Place `ore-tab-item` elements here\n * @slot - Place `ore-tab-panel` elements here\n *\n * @cssprop --tabs-radius - Border radius of the tablist container and panels\n * @cssprop --tabs-transition - Transition duration/easing for the active indicator\n * @cssprop --tabs-indicator-color - Color of the sliding active indicator line\n * @cssprop --tabs-bg - Background of the host element (flat variant)\n * @cssprop --tabs-tablist-bg - Tablist container background (solid/glass/frost variants)\n * @cssprop --tabs-tablist-border-color - Tablist container border color\n * @part tablist - Container that holds the slotted tab items\n * @part indicator - Active tab indicator element\n * @part panels - Container that holds tab panel content\n * @example\n * ```html\n * <ore-tabs value=\"tab1\" variant=\"bordered\">\n * <ore-tab-item slot=\"tabs\" value=\"tab1\">Overview</ore-tab-item>\n * <ore-tab-item slot=\"tabs\" value=\"tab2\">Settings</ore-tab-item>\n * <ore-tab-panel value=\"tab1\"><p>Overview content</p></ore-tab-panel>\n * <ore-tab-panel value=\"tab2\"><p>Settings content</p></ore-tab-panel>\n * </ore-tabs>\n * ```\n */\nexport const TABS_TAG = 'ore-tabs' as const;\ndefine<OreTabsProps>(TABS_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n activation: prop.oneOf(['auto', 'manual'] as const, 'auto'),\n density: prop.oneOf(['compact', 'default'] as const, 'default'),\n label: prop.string(),\n orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'horizontal'),\n value: prop.string(),\n variant: prop.string<SurfaceVariant>(),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreTabsEvents>();\n\n const shadowRoot = el.shadowRoot;\n const tablistRef = ref<HTMLElement>();\n const indicatorRef = ref<HTMLElement>();\n const selectedValue = signal<string | undefined>(props.value.value);\n const isManualActivation = () => props.activation.value === 'manual';\n const isVertical = () => props.orientation.value === 'vertical';\n\n bind({\n attr: {\n value: () => selectedValue.value ?? null,\n },\n });\n\n const getTabs = () => [...el.querySelectorAll<HTMLElement>(':scope > ore-tab-item[slot=\"tabs\"]')];\n const getEnabledTabs = () => getTabs().filter((t) => !t.hasAttribute('disabled'));\n const focusTab = (tab: HTMLElement | undefined) => {\n if (!tab) return;\n\n const focusable = tab.shadowRoot?.querySelector<HTMLElement>('[role=\"tab\"]') ?? tab;\n\n focusable.focus();\n };\n\n // ────────────────────────────────────────────────────────────────\n // Selection State Management\n // ────────────────────────────────────────────────────────────────\n\n const setSelection = (value: string | undefined, shouldEmit = false) => {\n selectedValue.value = value;\n\n if (shouldEmit && value) emit('change', { value });\n };\n\n const ensureSelection = () => {\n const tabs = getTabs();\n\n if (tabs.length === 0) return; // No tabs yet, keep current selection\n\n const current = selectedValue.value;\n const hasCurrent = current\n ? tabs.some((tab) => tab.getAttribute('value') === current && !tab.hasAttribute('disabled'))\n : false;\n\n if (hasCurrent) return;\n\n const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled'))?.getAttribute('value') ?? undefined;\n\n setSelection(firstEnabled, false);\n };\n\n watch(props.value, (value) => {\n selectedValue.value = value;\n ensureSelection();\n });\n\n // ────────────────────────────────────────────────────────────────\n // List Control for Keyboard Navigation\n // ────────────────────────────────────────────────────────────────\n\n const listControl = createListControl({\n direction: () => elementDirection(el),\n getItems: () => getEnabledTabs(),\n loop: true,\n onNavigate: ({ item }) => {\n focusTab(item);\n\n if (!isManualActivation()) {\n const value = item.getAttribute('value');\n\n if (value) setSelection(value, true);\n }\n },\n orientation: () => (isVertical() ? 'vertical' : 'both'),\n signal: lifecycleSignal(onCleanup),\n });\n\n // ────────────────────────────────────────────────────────────────\n // Context & Indicator Management\n // ────────────────────────────────────────────────────────────────\n\n provide(TABS_CTX, {\n color: props.color,\n density: computed(() => props.density.value ?? 'default'),\n orientation: computed(() => props.orientation.value ?? 'horizontal'),\n size: props.size,\n value: selectedValue,\n variant: props.variant,\n });\n\n const moveIndicator = (activeTab: HTMLElement | undefined) => {\n const indicator = indicatorRef.value;\n const tablist = tablistRef.value;\n\n if (!indicator || !tablist || !activeTab) return;\n\n const tabRect = activeTab.getBoundingClientRect();\n const listRect = tablist.getBoundingClientRect();\n\n if (isVertical()) {\n const y = tabRect.top - listRect.top + tablist.scrollTop;\n\n indicator.style.transition = 'none';\n indicator.style.height = `${tabRect.height}px`;\n indicator.style.width = '';\n indicator.style.left = '0';\n indicator.style.top = '0';\n void indicator.offsetWidth;\n indicator.style.transition = '';\n indicator.style.transform = `translateY(${y}px)`;\n } else {\n const x = tabRect.left - listRect.left + tablist.scrollLeft;\n\n indicator.style.transition = 'none';\n indicator.style.width = `${tabRect.width}px`;\n indicator.style.height = '';\n indicator.style.top = '';\n indicator.style.left = '0';\n void indicator.offsetWidth;\n indicator.style.transition = '';\n indicator.style.transform = `translateX(${x}px)`;\n }\n };\n\n const updateIndicator = () => {\n const value = selectedValue.value;\n\n if (!value) return;\n\n const activeTab = getTabs().find((t) => t.getAttribute('value') === value);\n\n moveIndicator(activeTab);\n };\n\n watch(selectedValue, () => {\n requestAnimationFrame(updateIndicator);\n });\n\n // ────────────────────────────────────────────────────────────────\n // Event Handlers\n // ────────────────────────────────────────────────────────────────\n\n const handleTabClick = (e: Event) => {\n const tab = e\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item');\n\n if (!tab || tab.hasAttribute('disabled')) return;\n\n // Guard: only respond to tab-items that belong to THIS tabs instance\n if (tab.closest('ore-tabs') !== el) return;\n\n const value = tab.getAttribute('value');\n\n if (!value || value === selectedValue.value) return;\n\n setSelection(value, true);\n };\n\n const activateFocusedTab = (): void => {\n const tabs = getEnabledTabs();\n const focusedTab = tabs.find(\n (tab) => tab === document.activeElement || tab.shadowRoot?.activeElement === document.activeElement,\n );\n const focusedValue = focusedTab?.getAttribute('value');\n\n if (focusedValue && focusedValue !== selectedValue.value) setSelection(focusedValue, true);\n };\n\n const manualActivationPress = createInteraction({\n disabled: computed(() => !isManualActivation()),\n onPress: activateFocusedTab,\n });\n\n const handleKeydown = (e: KeyboardEvent) => {\n const tabs = getEnabledTabs();\n\n if (tabs.length === 0) return;\n\n const path = e.composedPath();\n const activeTabFromEvent = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item',\n );\n\n const tabFromEvent = activeTabFromEvent ?? tabs.find((t) => t.getAttribute('value') === selectedValue.value);\n const focused = tabFromEvent ? tabs.indexOf(tabFromEvent) : -1;\n\n if (focused >= 0) listControl.set(focused);\n\n if (listControl.handleKeydown(e)) return;\n\n manualActivationPress.handleKeydown(e);\n };\n\n bind({\n on: {\n click: handleTabClick,\n keydown: handleKeydown,\n },\n });\n\n // ────────────────────────────────────────────────────────────────\n // Lifecycle\n // ────────────────────────────────────────────────────────────────\n\n onMounted(() => {\n const syncSelection = () => {\n ensureSelection();\n updateIndicator();\n };\n\n const tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n\n if (tabsSlot) {\n tabsSlot.addEventListener('slotchange', syncSelection);\n }\n\n syncSelection();\n requestAnimationFrame(syncSelection);\n\n return () => {\n if (tabsSlot) {\n tabsSlot.removeEventListener('slotchange', syncSelection);\n }\n };\n });\n\n return html`\n <div class=\"tablist-wrapper\">\n <div\n role=\"tablist\"\n ref=\"${tablistRef}\"\n part=\"tablist\"\n aria-orientation=\"${props.orientation}\"\n aria-label=\"${props.label}\">\n <slot name=\"tabs\"></slot>\n </div>\n <div class=\"indicator\" ref=\"${indicatorRef}\" part=\"indicator\"></div>\n </div>\n <div class=\"panels\" part=\"panels\">\n <slot></slot>\n </div>\n `;\n },\n styles: [colorThemeMixin, styles],\n});\n"],"mappings":"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"}