@vielzeug/refine 26.10.6 → 26.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/accordion-item.cjs +2 -2
- package/dist/accordion-item.cjs.map +1 -1
- package/dist/accordion-item.js +6 -6
- package/dist/accordion-item.js.map +1 -1
- package/dist/accordion.cjs +2 -2
- package/dist/accordion.cjs.map +1 -1
- package/dist/accordion.js +2 -2
- package/dist/accordion.js.map +1 -1
- package/dist/alert.cjs +2 -2
- package/dist/alert.cjs.map +1 -1
- package/dist/alert.js +2 -2
- package/dist/alert.js.map +1 -1
- package/dist/avatar.cjs +3 -3
- package/dist/avatar.cjs.map +1 -1
- package/dist/avatar.js +2 -2
- package/dist/avatar.js.map +1 -1
- package/dist/badge.cjs +2 -2
- package/dist/badge.cjs.map +1 -1
- package/dist/badge.js +2 -2
- package/dist/badge.js.map +1 -1
- package/dist/box.cjs +2 -2
- package/dist/box.cjs.map +1 -1
- package/dist/box.js +2 -2
- package/dist/box.js.map +1 -1
- package/dist/button-group.cjs +2 -2
- package/dist/button-group.cjs.map +1 -1
- package/dist/button-group.js +2 -2
- package/dist/button-group.js.map +1 -1
- package/dist/button.cjs +9 -9
- package/dist/button.cjs.map +1 -1
- package/dist/button.js +8 -8
- package/dist/button.js.map +1 -1
- package/dist/card.cjs +2 -2
- package/dist/card.cjs.map +1 -1
- package/dist/card.js +2 -2
- package/dist/card.js.map +1 -1
- package/dist/carousel.cjs +6 -6
- package/dist/carousel.cjs.map +1 -1
- package/dist/carousel.js +6 -6
- package/dist/carousel.js.map +1 -1
- package/dist/chat-message.cjs +4 -4
- package/dist/chat-message.cjs.map +1 -1
- package/dist/chat-message.js +4 -4
- package/dist/chat-message.js.map +1 -1
- package/dist/checkbox-group.cjs +1 -1
- package/dist/checkbox-group.cjs.map +1 -1
- package/dist/checkbox-group.js +1 -1
- package/dist/checkbox-group.js.map +1 -1
- package/dist/checkbox.cjs +1 -1
- package/dist/checkbox.cjs.map +1 -1
- package/dist/checkbox.js +1 -1
- package/dist/checkbox.js.map +1 -1
- package/dist/chip.cjs +8 -8
- package/dist/chip.cjs.map +1 -1
- package/dist/chip.js +8 -8
- package/dist/chip.js.map +1 -1
- package/dist/combobox.cjs +8 -8
- package/dist/combobox.cjs.map +1 -1
- package/dist/combobox.js +11 -11
- package/dist/combobox.js.map +1 -1
- package/dist/content/avatar/avatar.d.ts +2 -1
- package/dist/content/avatar/avatar.d.ts.map +1 -1
- package/dist/content/card/card.d.ts +6 -3
- package/dist/content/card/card.d.ts.map +1 -1
- package/dist/content/carousel/carousel.d.ts +5 -3
- package/dist/content/carousel/carousel.d.ts.map +1 -1
- package/dist/content/chat-message/chat-message.d.ts +2 -1
- package/dist/content/chat-message/chat-message.d.ts.map +1 -1
- package/dist/content/copy-command/copy-command.d.ts +1 -1
- package/dist/content/copy-command/copy-command.d.ts.map +1 -1
- package/dist/content/keyboard-key/keyboard-key.d.ts +2 -1
- package/dist/content/keyboard-key/keyboard-key.d.ts.map +1 -1
- package/dist/content/list/list.d.ts +2 -1
- package/dist/content/list/list.d.ts.map +1 -1
- package/dist/content/marquee/marquee.d.ts +3 -2
- package/dist/content/marquee/marquee.d.ts.map +1 -1
- package/dist/content/pagination/pagination.d.ts +1 -1
- package/dist/content/pagination/pagination.d.ts.map +1 -1
- package/dist/content/qr-code/qr-code.d.ts +0 -41
- package/dist/content/qr-code/qr-code.d.ts.map +1 -1
- package/dist/content/separator/separator.d.ts +1 -1
- package/dist/content/separator/separator.d.ts.map +1 -1
- package/dist/content/stepper/step.d.ts +1 -1
- package/dist/content/stepper/step.d.ts.map +1 -1
- package/dist/content/table/table.d.ts +2 -1
- package/dist/content/table/table.d.ts.map +1 -1
- package/dist/content/text/text.d.ts +13 -6
- package/dist/content/text/text.d.ts.map +1 -1
- package/dist/cookie-banner.cjs +1 -1
- package/dist/cookie-banner.cjs.map +1 -1
- package/dist/cookie-banner.js +1 -1
- package/dist/cookie-banner.js.map +1 -1
- package/dist/copy-command.cjs +6 -6
- package/dist/copy-command.cjs.map +1 -1
- package/dist/copy-command.js +7 -7
- package/dist/copy-command.js.map +1 -1
- package/dist/core/checkable.cjs +1 -1
- package/dist/core/checkable.cjs.map +1 -1
- package/dist/core/checkable.d.ts +6 -7
- package/dist/core/checkable.d.ts.map +1 -1
- package/dist/core/checkable.js +1 -1
- package/dist/core/checkable.js.map +1 -1
- package/dist/core/choice-field.cjs +1 -1
- package/dist/core/choice-field.cjs.map +1 -1
- package/dist/core/choice-field.d.ts +6 -6
- package/dist/core/choice-field.d.ts.map +1 -1
- package/dist/core/choice-field.js +1 -1
- package/dist/core/choice-field.js.map +1 -1
- package/dist/core/text-field.cjs +1 -1
- package/dist/core/text-field.cjs.map +1 -1
- package/dist/core/text-field.d.ts +8 -8
- package/dist/core/text-field.d.ts.map +1 -1
- package/dist/core/text-field.js +1 -1
- package/dist/core/text-field.js.map +1 -1
- package/dist/counter.cjs +2 -2
- package/dist/counter.cjs.map +1 -1
- package/dist/counter.js +2 -2
- package/dist/counter.js.map +1 -1
- package/dist/custom-elements.json +5184 -5047
- package/dist/datagrid.cjs +47 -47
- package/dist/datagrid.cjs.map +1 -1
- package/dist/datagrid.js +51 -51
- package/dist/datagrid.js.map +1 -1
- package/dist/date-picker.cjs +9 -9
- package/dist/date-picker.cjs.map +1 -1
- package/dist/date-picker.js +11 -11
- package/dist/date-picker.js.map +1 -1
- package/dist/dialog.cjs +3 -3
- package/dist/dialog.cjs.map +1 -1
- package/dist/dialog.js +3 -3
- package/dist/dialog.js.map +1 -1
- package/dist/disclosure/accordion/accordion.d.ts +4 -2
- package/dist/disclosure/accordion/accordion.d.ts.map +1 -1
- package/dist/disclosure/accordion-item/accordion-item.d.ts +1 -1
- package/dist/disclosure/accordion-item/accordion-item.d.ts.map +1 -1
- package/dist/disclosure/tab-item/tab-item.d.ts +1 -1
- package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
- package/dist/disclosure/tabs/tabs.d.ts +1 -1
- package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
- package/dist/drawer.cjs +4 -4
- package/dist/drawer.cjs.map +1 -1
- package/dist/drawer.js +2 -2
- package/dist/drawer.js.map +1 -1
- package/dist/feedback/alert/alert.d.ts +2 -1
- package/dist/feedback/alert/alert.d.ts.map +1 -1
- package/dist/feedback/badge/badge.d.ts +7 -4
- package/dist/feedback/badge/badge.d.ts.map +1 -1
- package/dist/feedback/chip/chip.d.ts +7 -5
- package/dist/feedback/chip/chip.d.ts.map +1 -1
- package/dist/grid-item.cjs +2 -2
- package/dist/grid-item.cjs.map +1 -1
- package/dist/grid-item.js +2 -2
- package/dist/grid-item.js.map +1 -1
- package/dist/grid.cjs +2 -2
- package/dist/grid.cjs.map +1 -1
- package/dist/grid.js +2 -2
- package/dist/grid.js.map +1 -1
- package/dist/input.cjs +7 -7
- package/dist/input.cjs.map +1 -1
- package/dist/input.js +8 -8
- package/dist/input.js.map +1 -1
- package/dist/inputs/button/button.d.ts +3 -2
- package/dist/inputs/button/button.d.ts.map +1 -1
- package/dist/inputs/button-group/button-group.d.ts +1 -37
- package/dist/inputs/button-group/button-group.d.ts.map +1 -1
- package/dist/inputs/combobox/combobox.types.d.ts +2 -2
- package/dist/inputs/combobox/combobox.types.d.ts.map +1 -1
- package/dist/inputs/counter/counter.d.ts +3 -2
- package/dist/inputs/counter/counter.d.ts.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.cjs +1 -1
- package/dist/inputs/datagrid/datagrid-model.cjs.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.d.ts +2 -1
- package/dist/inputs/datagrid/datagrid-model.d.ts.map +1 -1
- package/dist/inputs/datagrid/datagrid-model.js +1 -1
- package/dist/inputs/datagrid/datagrid-model.js.map +1 -1
- package/dist/inputs/datagrid/datagrid.d.ts +4 -2
- package/dist/inputs/datagrid/datagrid.d.ts.map +1 -1
- package/dist/inputs/date-picker/date-picker.d.ts +3 -2
- package/dist/inputs/date-picker/date-picker.d.ts.map +1 -1
- package/dist/inputs/input/input.d.ts +5 -3
- package/dist/inputs/input/input.d.ts.map +1 -1
- package/dist/inputs/message-composer/message-composer.d.ts +3 -2
- package/dist/inputs/message-composer/message-composer.d.ts.map +1 -1
- package/dist/inputs/number-input/number-input.d.ts +1 -1
- package/dist/inputs/number-input/number-input.d.ts.map +1 -1
- package/dist/inputs/otp-input/otp-input.d.ts +3 -2
- package/dist/inputs/otp-input/otp-input.d.ts.map +1 -1
- package/dist/inputs/query-bar/query-bar.d.ts.map +1 -1
- package/dist/inputs/select/select.d.ts +2 -2
- package/dist/inputs/select/select.d.ts.map +1 -1
- package/dist/inputs/textarea/textarea.d.ts +4 -3
- package/dist/inputs/textarea/textarea.d.ts.map +1 -1
- package/dist/inputs/time-picker/time-picker.d.ts +2 -2
- package/dist/inputs/time-picker/time-picker.d.ts.map +1 -1
- package/dist/keyboard-key.cjs +3 -3
- package/dist/keyboard-key.cjs.map +1 -1
- package/dist/keyboard-key.js +3 -3
- package/dist/keyboard-key.js.map +1 -1
- package/dist/layout/box/box.d.ts +6 -3
- package/dist/layout/box/box.d.ts.map +1 -1
- package/dist/layout/grid/grid.d.ts +11 -6
- package/dist/layout/grid/grid.d.ts.map +1 -1
- package/dist/layout/grid-item/grid-item.d.ts +8 -4
- package/dist/layout/grid-item/grid-item.d.ts.map +1 -1
- package/dist/layout/navbar/navbar.d.ts +4 -2
- package/dist/layout/navbar/navbar.d.ts.map +1 -1
- package/dist/layout/sidebar/sidebar.d.ts +2 -1
- package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
- package/dist/list-item.cjs +1 -1
- package/dist/list-item.cjs.map +1 -1
- package/dist/list-item.js +1 -1
- package/dist/list-item.js.map +1 -1
- package/dist/list.cjs +2 -2
- package/dist/list.cjs.map +1 -1
- package/dist/list.js +2 -2
- package/dist/list.js.map +1 -1
- package/dist/marquee.cjs +3 -3
- package/dist/marquee.cjs.map +1 -1
- package/dist/marquee.js +3 -3
- package/dist/marquee.js.map +1 -1
- package/dist/menu.cjs +3 -3
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.js +3 -3
- package/dist/menu.js.map +1 -1
- package/dist/message-composer.cjs +10 -10
- package/dist/message-composer.cjs.map +1 -1
- package/dist/message-composer.js +7 -7
- package/dist/message-composer.js.map +1 -1
- package/dist/navbar.cjs +10 -10
- package/dist/navbar.cjs.map +1 -1
- package/dist/navbar.js +13 -13
- package/dist/navbar.js.map +1 -1
- package/dist/number-input.cjs +3 -3
- package/dist/number-input.cjs.map +1 -1
- package/dist/number-input.js +4 -4
- package/dist/number-input.js.map +1 -1
- package/dist/otp-input.cjs +4 -4
- package/dist/otp-input.cjs.map +1 -1
- package/dist/otp-input.js +4 -4
- package/dist/otp-input.js.map +1 -1
- package/dist/overlay/dialog/dialog.d.ts +7 -4
- package/dist/overlay/dialog/dialog.d.ts.map +1 -1
- package/dist/overlay/drawer/drawer.d.ts +4 -2
- package/dist/overlay/drawer/drawer.d.ts.map +1 -1
- package/dist/overlay/menu/menu.d.ts +2 -1
- package/dist/overlay/menu/menu.d.ts.map +1 -1
- package/dist/pagination.cjs +9 -9
- package/dist/pagination.cjs.map +1 -1
- package/dist/pagination.js +9 -9
- package/dist/pagination.js.map +1 -1
- package/dist/qr-code.cjs +2 -2
- package/dist/qr-code.cjs.map +1 -1
- package/dist/qr-code.js +2 -2
- package/dist/qr-code.js.map +1 -1
- package/dist/query-bar.cjs +7 -7
- package/dist/query-bar.cjs.map +1 -1
- package/dist/query-bar.js +8 -8
- package/dist/query-bar.js.map +1 -1
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/radio-group.js.map +1 -1
- package/dist/refine.cjs +123 -123
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +123 -123
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +124 -124
- package/dist/refine.js.map +1 -1
- package/dist/select.cjs +13 -13
- package/dist/select.cjs.map +1 -1
- package/dist/select.js +15 -15
- package/dist/select.js.map +1 -1
- package/dist/separator.cjs +4 -4
- package/dist/separator.cjs.map +1 -1
- package/dist/separator.js +4 -4
- package/dist/separator.js.map +1 -1
- package/dist/shared/prop-bundles.cjs +1 -1
- package/dist/shared/prop-bundles.cjs.map +1 -1
- package/dist/shared/prop-bundles.d.ts +7 -8
- package/dist/shared/prop-bundles.d.ts.map +1 -1
- package/dist/shared/prop-bundles.js +1 -1
- package/dist/shared/prop-bundles.js.map +1 -1
- package/dist/shared/types.cjs +2 -0
- package/dist/shared/types.cjs.map +1 -0
- package/dist/shared/types.d.ts +27 -9
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/sidebar.cjs +7 -7
- package/dist/sidebar.cjs.map +1 -1
- package/dist/sidebar.js +4 -4
- package/dist/sidebar.js.map +1 -1
- package/dist/step.cjs +10 -10
- package/dist/step.cjs.map +1 -1
- package/dist/step.js +10 -10
- package/dist/step.js.map +1 -1
- package/dist/tab-item.cjs +4 -4
- package/dist/tab-item.cjs.map +1 -1
- package/dist/tab-item.js +5 -5
- package/dist/tab-item.js.map +1 -1
- package/dist/table.cjs +2 -2
- package/dist/table.cjs.map +1 -1
- package/dist/table.js +2 -2
- package/dist/table.js.map +1 -1
- package/dist/tabs.cjs +2 -2
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.js +4 -4
- package/dist/tabs.js.map +1 -1
- package/dist/text.cjs +2 -2
- package/dist/text.cjs.map +1 -1
- package/dist/text.js +2 -2
- package/dist/text.js.map +1 -1
- package/dist/textarea.cjs +7 -7
- package/dist/textarea.cjs.map +1 -1
- package/dist/textarea.js +7 -7
- package/dist/textarea.js.map +1 -1
- package/dist/time-picker.cjs +7 -7
- package/dist/time-picker.cjs.map +1 -1
- package/dist/time-picker.js +7 -7
- package/dist/time-picker.js.map +1 -1
- package/dist/tooltip.cjs +1 -1
- package/dist/tooltip.cjs.map +1 -1
- package/dist/tooltip.js +1 -1
- package/dist/tooltip.js.map +1 -1
- package/package.json +13 -13
package/dist/table.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","names":[],"sources":["../src/content/table/table.ts"],"sourcesContent":["import { bind, define, getHost, html, onMounted, prop, watchEffect } from '@vielzeug/ore';\n\nimport { reducedMotionMixin, tableBaseMixin } from '../../styles';\nimport componentStyles from './table.css?inline';\n\n/* ── Types ───────────────────────────────────────────────────────────────── */\n\n/** Table component properties */\nexport type OreTableProps = {\n /** Adds a thicker outer border */\n bordered?: boolean;\n /** Visible caption text: also used as the accessible table label via `aria-label` */\n caption?: string;\n /** Cell density: `'compact'` | `'cozy'` (default) | `'comfortable'` */\n density?: 'compact' | 'cozy' | 'comfortable';\n /** Expands the table to 100% of its container width */\n fullwidth?: boolean;\n /** Applies a busy/disabled state with reduced opacity */\n loading?: boolean;\n /** Enables sticky column headers with a vertical scroll container */\n sticky?: boolean;\n /** Alternating row stripe backgrounds */\n striped?: boolean;\n};\n\n/* ── Child element markers ───────────────────────────────────────────────── */\n// ore-tr, ore-th, ore-td are lightweight light-DOM markers.\n// ore-table reads them and constructs a fully-native shadow <table> so that\n// browser features that require real table elements (colspan/rowspan,\n// position:sticky on <thead>, table layout algorithm) all work correctly.\n// Attributes on ore-th/ore-td are mirrored to the generated native cells.\n\n/**\n * Light-DOM row marker consumed by `<ore-table>`.\n *\n * @element ore-tr\n * @attr {boolean} head - Places the row in the generated `<thead>` section\n * @attr {boolean} foot - Places the row in the generated `<tfoot>` section\n *\n * @example\n * ```html\n * <ore-table>\n * <ore-tr head><ore-th>Name</ore-th><ore-th>Role</ore-th></ore-tr>\n * <ore-tr><ore-td>Alice</ore-td><ore-td>Admin</ore-td></ore-tr>\n * </ore-table>\n * ```\n */\nif (!customElements.get('ore-tr')) customElements.define('ore-tr', class extends HTMLElement {});\n\n/**\n * Light-DOM header cell marker consumed by `<ore-table>`.\n *\n * @element ore-th\n * @attr {number} colspan - Mirrors to native `<th colspan>`\n * @attr {number} rowspan - Mirrors to native `<th rowspan>`\n * @attr {string} scope - Mirrors to native `<th scope>`: 'col' | 'row' | 'colgroup' | 'rowgroup'\n * @attr {string} headers - Mirrors to native `<th headers>`\n *\n * @example\n * ```html\n * <ore-tr head>\n * <ore-th scope=\"col\">Name</ore-th>\n * <ore-th scope=\"col\" colspan=\"2\">Address</ore-th>\n * </ore-tr>\n * ```\n */\nif (!customElements.get('ore-th')) customElements.define('ore-th', class extends HTMLElement {});\n\n/**\n * Light-DOM data cell marker consumed by `<ore-table>`.\n *\n * @element ore-td\n * @attr {number} colspan - Mirrors to native `<td colspan>`\n * @attr {number} rowspan - Mirrors to native `<td rowspan>`\n * @attr {string} headers - Mirrors to native `<td headers>`\n *\n * @example\n * ```html\n * <ore-tr>\n * <ore-td>Alice</ore-td>\n * <ore-td colspan=\"2\">123 Main St, Springfield</ore-td>\n * </ore-tr>\n * ```\n */\nif (!customElements.get('ore-td')) customElements.define('ore-td', class extends HTMLElement {});\n\n/* ── Proxy/mirror helpers ────────────────────────────────────────────────── */\n\n// Attributes forwarded from ore-th/ore-td to the generated native cell.\n// scope is intentionally excluded: it requires fallback logic and is handled separately.\nconst CELL_ATTRS = ['colspan', 'rowspan', 'headers', 'abbr'];\n\n/**\n * Sync text content and tracked attributes from a light-DOM marker to its\n * native mirror. `fallbackScope` is applied when the source element carries no\n * explicit `scope` attribute, ensuring the auto-inferred value is always\n * present and is restored if an explicit override is later removed.\n */\nfunction syncCell(source: Element, native: HTMLTableCellElement, fallbackScope?: string): void {\n if (source.childElementCount > 0) {\n // Source has element children: deep-clone them into the native cell so\n // components like ore-skeleton render correctly inside the shadow table.\n // Guard: skip re-clone if the serialised content is identical to avoid\n // redundant DOM work on every MutationObserver tick (e.g. 250+ cells\n // of skeleton loaders all cloning on each attribute change).\n const snapshot = source.innerHTML;\n\n if (native.getAttribute('data-src-html') === snapshot) return;\n\n native.setAttribute('data-src-html', snapshot);\n native.textContent = '';\n\n for (const child of source.childNodes) {\n native.appendChild(child.cloneNode(true));\n }\n } else {\n const text = source.textContent ?? '';\n\n if (native.textContent !== text) native.textContent = text;\n }\n\n for (const attr of CELL_ATTRS) {\n const val = source.getAttribute(attr);\n\n if (val !== null) native.setAttribute(attr, val);\n else native.removeAttribute(attr);\n }\n\n // scope: explicit attribute wins; otherwise restore the inferred fallback.\n const explicitScope = source.getAttribute('scope');\n\n if (explicitScope !== null) native.setAttribute('scope', explicitScope);\n else if (fallbackScope) native.scope = fallbackScope;\n else native.removeAttribute('scope');\n}\n\n/** Entry stored in the cell map: native mirror element + its inferred scope fallback. */\ntype CellEntry = { inferredScope: string | undefined; native: HTMLTableCellElement };\n\n/**\n * (Re)build the entire native shadow table from the current light-DOM markers.\n * Returns a WeakMap of source marker → CellEntry for targeted sync by the\n * content observer. Storing `inferredScope` per cell ensures that removing an\n * explicit `scope` attribute reverts to the auto-inferred value rather than\n * dropping it entirely.\n */\nfunction buildTable(\n host: HTMLElement,\n thead: HTMLTableSectionElement,\n tbody: HTMLTableSectionElement,\n tfoot: HTMLTableSectionElement,\n): WeakMap<Element, CellEntry> {\n const cellMap = new WeakMap<Element, CellEntry>();\n\n thead.textContent = '';\n tbody.textContent = '';\n tfoot.textContent = '';\n\n for (const child of host.children) {\n if (child.localName !== 'ore-tr') continue;\n\n const section = child.hasAttribute('head') ? thead : child.hasAttribute('foot') ? tfoot : tbody;\n const tr = document.createElement('tr');\n\n for (const cell of child.children) {\n if (cell.localName !== 'ore-th' && cell.localName !== 'ore-td') continue;\n\n const isHeader = cell.localName === 'ore-th';\n const native = document.createElement(isHeader ? 'th' : 'td');\n // Auto-infer scope for <th> elements; undefined for <td>.\n const inferredScope = isHeader ? (section === thead ? 'col' : 'row') : undefined;\n\n native.setAttribute('part', section === tbody ? 'cell' : 'header-cell');\n native.removeAttribute('data-src-html');\n syncCell(cell, native, inferredScope);\n cellMap.set(cell, { inferredScope, native });\n tr.appendChild(native);\n }\n\n section.appendChild(tr);\n }\n\n return cellMap;\n}\n\n/**\n * Data table component.\n *\n * Reads light-DOM `<ore-tr>`/`<ore-th>`/`<ore-td>` markers and projects them\n * into a fully-native shadow `<table>`. Cell attributes (`colspan`, `rowspan`,\n * `scope`, etc.) are mirrored. Changes are observed and synced incrementally.\n *\n * Native table features: sticky headers, colspan/rowspan, the table layout\n * algorithm: all work because the shadow tree contains real table elements.\n *\n * @element ore-table\n *\n * @attr {boolean} bordered - Thicker outer border\n * @attr {string} caption - Caption text shown above the table\n * @attr {boolean} fullwidth - Expands to 100% container width\n * @attr {boolean} loading - Busy state: reduced opacity, no pointer events\n * @attr {string} density - Cell density: 'compact' | 'cozy' | 'comfortable' (default: 'cozy')\n * @attr {boolean} sticky - Sticky `<thead>` with scroll container\n * @attr {boolean} striped - Alternating row backgrounds\n *\n * @part scroll - Scroll container that hosts the generated native table\n * @part table - Generated native `<table>` element\n * @part head - Generated native `<thead>` section\n * @part body - Generated native `<tbody>` section\n * @part foot - Generated native `<tfoot>` section\n * @part cell - Every native `<td>` in `<tbody>` rows\n * @part header-cell - Every native `<th>` / `<td>` in `<thead>` and `<tfoot>` rows\n *\n * @cssprop --table-bg - Table background color\n * @cssprop --table-border-color - Cell separator and outer border color\n * @cssprop --table-radius - Corner radius of the table container\n * @cssprop --table-shadow - Box shadow of the table container\n * @cssprop --table-header-bg - Background of header and footer rows\n * @cssprop --table-accent - Accent color for interactive states\n * @cssprop --table-row-hover-bg - Row hover background\n * @cssprop --table-stripe-bg - Even-row stripe background\n * @cssprop --table-cell-padding-x - Cell horizontal padding\n * @cssprop --table-cell-padding-y - Cell vertical padding\n * @cssprop --table-font-size - Base font size for cells\n * @cssprop --table-sticky-max-height - Max height of the sticky scroll container\n * @cssprop --table-sticky-header-bg - Background of sticky header cells\n * @cssprop --table-sticky-blur - Backdrop blur applied to sticky headers\n *\n * @example\n * ```html\n * <ore-table caption=\"Top repositories\" striped bordered sticky>\n * <ore-tr head>\n * <ore-th>Repository</ore-th>\n * <ore-th>Stars</ore-th>\n * </ore-tr>\n * <ore-tr>\n * <ore-td>vielzeug/refine</ore-td>\n * <ore-td>1 200</ore-td>\n * </ore-tr>\n * </ore-table>\n * ```\n */\nexport const TABLE_TAG = 'ore-table' as const;\ndefine<OreTableProps>(TABLE_TAG, {\n props: {\n bordered: prop.bool(false),\n caption: prop.string(),\n density: prop.string<'compact' | 'cozy' | 'comfortable'>(),\n fullwidth: prop.bool(false),\n loading: prop.bool(false),\n sticky: prop.bool(false),\n striped: prop.bool(false),\n },\n\n setup(props) {\n const el = getHost();\n const watch = watchEffect;\n\n bind({\n attr: {\n 'aria-busy': props.loading,\n 'aria-label': props.caption,\n },\n });\n\n // Build the native shadow table via DOM APIs (not innerHTML) to avoid\n // HTML-parser foster-parenting, which ejects table section elements from\n // their intended positions in the tree.\n onMounted(() => {\n const scrollContainer = el.shadowRoot?.querySelector('.scroll-container')!;\n\n const table = document.createElement('table');\n const captionEl = document.createElement('caption');\n const thead = document.createElement('thead');\n const tbody = document.createElement('tbody');\n const tfoot = document.createElement('tfoot');\n\n scrollContainer.setAttribute('part', 'scroll');\n table.setAttribute('part', 'table');\n thead.setAttribute('part', 'head');\n tbody.setAttribute('part', 'body');\n tfoot.setAttribute('part', 'foot');\n table.append(captionEl, thead, tbody, tfoot);\n scrollContainer.appendChild(table);\n\n // Reactively sync caption text and visibility from prop.\n watch(() => {\n const text = props.caption.value ?? '';\n\n captionEl.textContent = text;\n captionEl.hidden = text === '';\n });\n\n // Initial full build.\n let cellMap = buildTable(el, thead, tbody, tfoot);\n\n // Content observer: syncs text/attribute changes inside ore-th/ore-td.\n // Remains connected throughout the component lifetime: no\n // disconnect/reconnect during structural rebuilds. Records that arrive\n // for cells no longer in cellMap (after a rebuild) are silently ignored\n // by the `if (entry)` guard, so there is no correctness risk.\n const contentObserver = new MutationObserver((records) => {\n for (const rec of records) {\n const sourceCell = (rec.target instanceof Element ? rec.target : rec.target.parentElement)?.closest(\n 'ore-th, ore-td',\n );\n\n if (sourceCell) {\n const entry = cellMap.get(sourceCell);\n\n if (entry) syncCell(sourceCell, entry.native, entry.inferredScope);\n }\n }\n });\n\n contentObserver.observe(el, {\n // Include 'scope' in attributeFilter so explicit scope changes are picked\n // up and the fallback-restore logic in syncCell runs correctly.\n attributeFilter: [...CELL_ATTRS, 'scope'],\n attributes: true,\n characterData: true,\n childList: true,\n subtree: true,\n });\n\n // Structure observer: triggers a full rebuild when ore-tr elements are\n // added, removed, or reordered. Scoped to direct children only so it\n // never fires for cell-level mutations.\n const structureObserver = new MutationObserver(() => {\n cellMap = buildTable(el, thead, tbody, tfoot);\n });\n\n structureObserver.observe(el, { childList: true });\n\n return () => {\n structureObserver.disconnect();\n contentObserver.disconnect();\n };\n });\n\n return html`\n <div class=\"scroll-container\"></div>\n `;\n },\n\n styles: [reducedMotionMixin, tableBaseMixin('table'), componentStyles],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"table.js","names":[],"sources":["../src/content/table/table.ts"],"sourcesContent":["import { bind, define, getHost, html, onMounted, prop, watchEffect } from '@vielzeug/ore';\n\nimport { reducedMotionMixin, tableBaseMixin } from '../../styles';\nimport { DENSITY_LEVELS, type DensityLevel } from '../../types';\nimport componentStyles from './table.css?inline';\n\n/* ── Types ───────────────────────────────────────────────────────────────── */\n\n/** Table component properties */\nexport type OreTableProps = {\n /** Adds a thicker outer border */\n bordered?: boolean;\n /** Visible caption text: also used as the accessible table label via `aria-label` */\n caption?: string;\n /** Cell density: `'compact'` | `'cozy'` (default) | `'comfortable'` */\n density?: DensityLevel;\n /** Expands the table to 100% of its container width */\n fullwidth?: boolean;\n /** Applies a busy/disabled state with reduced opacity */\n loading?: boolean;\n /** Enables sticky column headers with a vertical scroll container */\n sticky?: boolean;\n /** Alternating row stripe backgrounds */\n striped?: boolean;\n};\n\n/* ── Child element markers ───────────────────────────────────────────────── */\n// ore-tr, ore-th, ore-td are lightweight light-DOM markers.\n// ore-table reads them and constructs a fully-native shadow <table> so that\n// browser features that require real table elements (colspan/rowspan,\n// position:sticky on <thead>, table layout algorithm) all work correctly.\n// Attributes on ore-th/ore-td are mirrored to the generated native cells.\n\n/**\n * Light-DOM row marker consumed by `<ore-table>`.\n *\n * @element ore-tr\n * @attr {boolean} head - Places the row in the generated `<thead>` section\n * @attr {boolean} foot - Places the row in the generated `<tfoot>` section\n *\n * @example\n * ```html\n * <ore-table>\n * <ore-tr head><ore-th>Name</ore-th><ore-th>Role</ore-th></ore-tr>\n * <ore-tr><ore-td>Alice</ore-td><ore-td>Admin</ore-td></ore-tr>\n * </ore-table>\n * ```\n */\nif (!customElements.get('ore-tr')) customElements.define('ore-tr', class extends HTMLElement {});\n\n/**\n * Light-DOM header cell marker consumed by `<ore-table>`.\n *\n * @element ore-th\n * @attr {number} colspan - Mirrors to native `<th colspan>`\n * @attr {number} rowspan - Mirrors to native `<th rowspan>`\n * @attr {string} scope - Mirrors to native `<th scope>`: 'col' | 'row' | 'colgroup' | 'rowgroup'\n * @attr {string} headers - Mirrors to native `<th headers>`\n *\n * @example\n * ```html\n * <ore-tr head>\n * <ore-th scope=\"col\">Name</ore-th>\n * <ore-th scope=\"col\" colspan=\"2\">Address</ore-th>\n * </ore-tr>\n * ```\n */\nif (!customElements.get('ore-th')) customElements.define('ore-th', class extends HTMLElement {});\n\n/**\n * Light-DOM data cell marker consumed by `<ore-table>`.\n *\n * @element ore-td\n * @attr {number} colspan - Mirrors to native `<td colspan>`\n * @attr {number} rowspan - Mirrors to native `<td rowspan>`\n * @attr {string} headers - Mirrors to native `<td headers>`\n *\n * @example\n * ```html\n * <ore-tr>\n * <ore-td>Alice</ore-td>\n * <ore-td colspan=\"2\">123 Main St, Springfield</ore-td>\n * </ore-tr>\n * ```\n */\nif (!customElements.get('ore-td')) customElements.define('ore-td', class extends HTMLElement {});\n\n/* ── Proxy/mirror helpers ────────────────────────────────────────────────── */\n\n// Attributes forwarded from ore-th/ore-td to the generated native cell.\n// scope is intentionally excluded: it requires fallback logic and is handled separately.\nconst CELL_ATTRS = ['colspan', 'rowspan', 'headers', 'abbr'];\n\n/**\n * Sync text content and tracked attributes from a light-DOM marker to its\n * native mirror. `fallbackScope` is applied when the source element carries no\n * explicit `scope` attribute, ensuring the auto-inferred value is always\n * present and is restored if an explicit override is later removed.\n */\nfunction syncCell(source: Element, native: HTMLTableCellElement, fallbackScope?: string): void {\n if (source.childElementCount > 0) {\n // Source has element children: deep-clone them into the native cell so\n // components like ore-skeleton render correctly inside the shadow table.\n // Guard: skip re-clone if the serialised content is identical to avoid\n // redundant DOM work on every MutationObserver tick (e.g. 250+ cells\n // of skeleton loaders all cloning on each attribute change).\n const snapshot = source.innerHTML;\n\n if (native.getAttribute('data-src-html') === snapshot) return;\n\n native.setAttribute('data-src-html', snapshot);\n native.textContent = '';\n\n for (const child of source.childNodes) {\n native.appendChild(child.cloneNode(true));\n }\n } else {\n const text = source.textContent ?? '';\n\n if (native.textContent !== text) native.textContent = text;\n }\n\n for (const attr of CELL_ATTRS) {\n const val = source.getAttribute(attr);\n\n if (val !== null) native.setAttribute(attr, val);\n else native.removeAttribute(attr);\n }\n\n // scope: explicit attribute wins; otherwise restore the inferred fallback.\n const explicitScope = source.getAttribute('scope');\n\n if (explicitScope !== null) native.setAttribute('scope', explicitScope);\n else if (fallbackScope) native.scope = fallbackScope;\n else native.removeAttribute('scope');\n}\n\n/** Entry stored in the cell map: native mirror element + its inferred scope fallback. */\ntype CellEntry = { inferredScope: string | undefined; native: HTMLTableCellElement };\n\n/**\n * (Re)build the entire native shadow table from the current light-DOM markers.\n * Returns a WeakMap of source marker → CellEntry for targeted sync by the\n * content observer. Storing `inferredScope` per cell ensures that removing an\n * explicit `scope` attribute reverts to the auto-inferred value rather than\n * dropping it entirely.\n */\nfunction buildTable(\n host: HTMLElement,\n thead: HTMLTableSectionElement,\n tbody: HTMLTableSectionElement,\n tfoot: HTMLTableSectionElement,\n): WeakMap<Element, CellEntry> {\n const cellMap = new WeakMap<Element, CellEntry>();\n\n thead.textContent = '';\n tbody.textContent = '';\n tfoot.textContent = '';\n\n for (const child of host.children) {\n if (child.localName !== 'ore-tr') continue;\n\n const section = child.hasAttribute('head') ? thead : child.hasAttribute('foot') ? tfoot : tbody;\n const tr = document.createElement('tr');\n\n for (const cell of child.children) {\n if (cell.localName !== 'ore-th' && cell.localName !== 'ore-td') continue;\n\n const isHeader = cell.localName === 'ore-th';\n const native = document.createElement(isHeader ? 'th' : 'td');\n // Auto-infer scope for <th> elements; undefined for <td>.\n const inferredScope = isHeader ? (section === thead ? 'col' : 'row') : undefined;\n\n native.setAttribute('part', section === tbody ? 'cell' : 'header-cell');\n native.removeAttribute('data-src-html');\n syncCell(cell, native, inferredScope);\n cellMap.set(cell, { inferredScope, native });\n tr.appendChild(native);\n }\n\n section.appendChild(tr);\n }\n\n return cellMap;\n}\n\n/**\n * Data table component.\n *\n * Reads light-DOM `<ore-tr>`/`<ore-th>`/`<ore-td>` markers and projects them\n * into a fully-native shadow `<table>`. Cell attributes (`colspan`, `rowspan`,\n * `scope`, etc.) are mirrored. Changes are observed and synced incrementally.\n *\n * Native table features: sticky headers, colspan/rowspan, the table layout\n * algorithm: all work because the shadow tree contains real table elements.\n *\n * @element ore-table\n *\n * @attr {boolean} bordered - Thicker outer border\n * @attr {string} caption - Caption text shown above the table\n * @attr {boolean} fullwidth - Expands to 100% container width\n * @attr {boolean} loading - Busy state: reduced opacity, no pointer events\n * @attr {string} density - Cell density: 'compact' | 'cozy' | 'comfortable' (default: 'cozy')\n * @attr {boolean} sticky - Sticky `<thead>` with scroll container\n * @attr {boolean} striped - Alternating row backgrounds\n *\n * @part scroll - Scroll container that hosts the generated native table\n * @part table - Generated native `<table>` element\n * @part head - Generated native `<thead>` section\n * @part body - Generated native `<tbody>` section\n * @part foot - Generated native `<tfoot>` section\n * @part cell - Every native `<td>` in `<tbody>` rows\n * @part header-cell - Every native `<th>` / `<td>` in `<thead>` and `<tfoot>` rows\n *\n * @cssprop --table-bg - Table background color\n * @cssprop --table-border-color - Cell separator and outer border color\n * @cssprop --table-radius - Corner radius of the table container\n * @cssprop --table-shadow - Box shadow of the table container\n * @cssprop --table-header-bg - Background of header and footer rows\n * @cssprop --table-accent - Accent color for interactive states\n * @cssprop --table-row-hover-bg - Row hover background\n * @cssprop --table-stripe-bg - Even-row stripe background\n * @cssprop --table-cell-padding-x - Cell horizontal padding\n * @cssprop --table-cell-padding-y - Cell vertical padding\n * @cssprop --table-font-size - Base font size for cells\n * @cssprop --table-sticky-max-height - Max height of the sticky scroll container\n * @cssprop --table-sticky-header-bg - Background of sticky header cells\n * @cssprop --table-sticky-blur - Backdrop blur applied to sticky headers\n *\n * @example\n * ```html\n * <ore-table caption=\"Top repositories\" striped bordered sticky>\n * <ore-tr head>\n * <ore-th>Repository</ore-th>\n * <ore-th>Stars</ore-th>\n * </ore-tr>\n * <ore-tr>\n * <ore-td>vielzeug/refine</ore-td>\n * <ore-td>1 200</ore-td>\n * </ore-tr>\n * </ore-table>\n * ```\n */\nexport const TABLE_TAG = 'ore-table' as const;\ndefine<OreTableProps>(TABLE_TAG, {\n props: {\n bordered: prop.bool(false),\n caption: prop.string(),\n density: prop.oneOf(DENSITY_LEVELS),\n fullwidth: prop.bool(false),\n loading: prop.bool(false),\n sticky: prop.bool(false),\n striped: prop.bool(false),\n },\n\n setup(props) {\n const el = getHost();\n const watch = watchEffect;\n\n bind({\n attr: {\n 'aria-busy': props.loading,\n 'aria-label': props.caption,\n },\n });\n\n // Build the native shadow table via DOM APIs (not innerHTML) to avoid\n // HTML-parser foster-parenting, which ejects table section elements from\n // their intended positions in the tree.\n onMounted(() => {\n const scrollContainer = el.shadowRoot?.querySelector('.scroll-container')!;\n\n const table = document.createElement('table');\n const captionEl = document.createElement('caption');\n const thead = document.createElement('thead');\n const tbody = document.createElement('tbody');\n const tfoot = document.createElement('tfoot');\n\n scrollContainer.setAttribute('part', 'scroll');\n table.setAttribute('part', 'table');\n thead.setAttribute('part', 'head');\n tbody.setAttribute('part', 'body');\n tfoot.setAttribute('part', 'foot');\n table.append(captionEl, thead, tbody, tfoot);\n scrollContainer.appendChild(table);\n\n // Reactively sync caption text and visibility from prop.\n watch(() => {\n const text = props.caption.value ?? '';\n\n captionEl.textContent = text;\n captionEl.hidden = text === '';\n });\n\n // Initial full build.\n let cellMap = buildTable(el, thead, tbody, tfoot);\n\n // Content observer: syncs text/attribute changes inside ore-th/ore-td.\n // Remains connected throughout the component lifetime: no\n // disconnect/reconnect during structural rebuilds. Records that arrive\n // for cells no longer in cellMap (after a rebuild) are silently ignored\n // by the `if (entry)` guard, so there is no correctness risk.\n const contentObserver = new MutationObserver((records) => {\n for (const rec of records) {\n const sourceCell = (rec.target instanceof Element ? rec.target : rec.target.parentElement)?.closest(\n 'ore-th, ore-td',\n );\n\n if (sourceCell) {\n const entry = cellMap.get(sourceCell);\n\n if (entry) syncCell(sourceCell, entry.native, entry.inferredScope);\n }\n }\n });\n\n contentObserver.observe(el, {\n // Include 'scope' in attributeFilter so explicit scope changes are picked\n // up and the fallback-restore logic in syncCell runs correctly.\n attributeFilter: [...CELL_ATTRS, 'scope'],\n attributes: true,\n characterData: true,\n childList: true,\n subtree: true,\n });\n\n // Structure observer: triggers a full rebuild when ore-tr elements are\n // added, removed, or reordered. Scoped to direct children only so it\n // never fires for cell-level mutations.\n const structureObserver = new MutationObserver(() => {\n cellMap = buildTable(el, thead, tbody, tfoot);\n });\n\n structureObserver.observe(el, { childList: true });\n\n return () => {\n structureObserver.disconnect();\n contentObserver.disconnect();\n };\n });\n\n return html`\n <div class=\"scroll-container\"></div>\n `;\n },\n\n styles: [reducedMotionMixin, tableBaseMixin('table'), componentStyles],\n});\n"],"mappings":"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"}
|
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"),
|
|
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/types.cjs"),o=require("./shared/prop-bundles.cjs"),s=require("./disclosure/tabs/tabs.css.cjs");let c=require("@vielzeug/ripple"),l=require("@vielzeug/ore");var u=(0,l.createContext)(`TabsContext`),d=`ore-tabs`;(0,l.define)(d,{props:{...o.themableBundle,...o.sizableBundle,activation:l.prop.oneOf([`auto`,`manual`],`auto`),density:l.prop.oneOf([`compact`,`default`],`default`),label:l.prop.string(),orientation:l.prop.oneOf([`horizontal`,`vertical`],`horizontal`),value:l.prop.string(),variant:l.prop.oneOf(a.VISUAL_VARIANTS)},setup(e){let a=(0,l.getHost)(),o=(0,l.useEmit)(),s=a.shadowRoot,d=(0,l.ref)(),f=(0,l.ref)(),p=(0,c.signal)(e.value.value),m=(0,c.signal)(!1),isManualActivation=()=>e.activation.value===`manual`,isVertical=()=>e.orientation.value===`vertical`;(0,l.bind)({attr:{"data-has-panels":()=>m.value?``:null,value:()=>p.value??null}});let getTabs=()=>[...a.querySelectorAll(`:scope > ore-tab-item[slot="tabs"]`)],getEnabledTabs=()=>getTabs().filter(e=>!e.hasAttribute(`disabled`)),focusTab=e=>{e&&(e.shadowRoot?.querySelector(`[role="tab"]`)??e).focus()},setSelection=(e,t=!1)=>{p.value=e,t&&e&&o(`change`,{value:e})},ensureSelection=()=>{let e=getTabs();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`)&&e.getAttribute(`value`));n&&setSelection(n.getAttribute(`value`)??void 0,!1)};(0,c.watch)(e.value,e=>{p.value=e,ensureSelection()});let h=i.createListControl({direction:()=>r.elementDirection(a),getItems:()=>getEnabledTabs(),loop:!0,onNavigate:({item:e})=>{if(focusTab(e),!isManualActivation()){let t=e.getAttribute(`value`);t&&setSelection(t,!0)}},orientation:()=>isVertical()?`vertical`:`both`,signal:n.lifecycleSignal(l.onCleanup)});(0,l.provide)(u,{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 moveIndicator=e=>{let t=f.value,n=d.value;if(!t||!n||!e)return;let r=e.getBoundingClientRect(),i=n.getBoundingClientRect();if(isVertical()){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)`}},updateIndicator=()=>{let e=p.value;if(!e)return;let t=getTabs().find(t=>t.getAttribute(`value`)===e);moveIndicator(t)};(0,c.watch)(p,()=>{requestAnimationFrame(updateIndicator)});let handleTabClick=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&&setSelection(n,!0)},activateFocusedTab=()=>{let e=getEnabledTabs().find(e=>e===document.activeElement||e.shadowRoot?.activeElement===document.activeElement)?.getAttribute(`value`);e&&e!==p.value&&setSelection(e,!0)},g=t.createInteraction({disabled:(0,c.computed)(()=>!isManualActivation()),onPress:activateFocusedTab}),handleKeydown=e=>{let t=getEnabledTabs();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&&h.set(r),!h.handleKeydown(e)&&g.handleKeydown(e)};return(0,l.bind)({on:{click:handleTabClick,keydown:handleKeydown}}),(0,l.onMounted)(()=>{let e=s?.querySelector(`slot[name="tabs"]`),t=s?.querySelector(`slot:not([name])`),sync=()=>{ensureSelection(),m.value=t?.assignedElements().some(e=>e.localName===`ore-tab-panel`)??!1,updateIndicator()};return e?.addEventListener(`slotchange`,sync),t?.addEventListener(`slotchange`,sync),sync(),requestAnimationFrame(sync),()=>{e?.removeEventListener(`slotchange`,sync),t?.removeEventListener(`slotchange`,sync)}}),l.html`
|
|
2
2
|
<div class="tablist-wrapper">
|
|
3
3
|
<div
|
|
4
4
|
role="tablist"
|
|
@@ -13,5 +13,5 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
13
13
|
<div class="panels" part="panels" ?hidden="${()=>!m.value}">
|
|
14
14
|
<slot></slot>
|
|
15
15
|
</div>
|
|
16
|
-
`},styles:[e.colorThemeMixin,
|
|
16
|
+
`},styles:[e.colorThemeMixin,s.default]}),exports.TABS_CTX=u,exports.TABS_TAG=d;
|
|
17
17
|
//# sourceMappingURL=tabs.cjs.map
|
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, ThemeColor, VisualVariant } 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<VisualVariant | 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?: VisualVariant;\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' | '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/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<VisualVariant>(),\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 hasPanels = signal(false);\n const isManualActivation = () => props.activation.value === 'manual';\n const isVertical = () => props.orientation.value === 'vertical';\n\n bind({\n attr: {\n 'data-has-panels': () => (hasPanels.value ? '' : null),\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 // Fall back only to a tab that carries a value: slot bindings land after\n // mount, and locking an empty first value here would later override a\n // bound host `value`. No valued tab yet means defer until a later sync.\n const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled') && tab.getAttribute('value'));\n\n if (!firstEnabled) return;\n\n setSelection(firstEnabled.getAttribute('value') ?? undefined, 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 tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n const panelsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n const sync = () => {\n ensureSelection();\n hasPanels.value = panelsSlot?.assignedElements().some((child) => child.localName === 'ore-tab-panel') ?? false;\n updateIndicator();\n };\n\n tabsSlot?.addEventListener('slotchange', sync);\n panelsSlot?.addEventListener('slotchange', sync);\n\n sync();\n requestAnimationFrame(sync);\n\n return () => {\n tabsSlot?.removeEventListener('slotchange', sync);\n panelsSlot?.removeEventListener('slotchange', sync);\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\" ?hidden=\"${() => !hasPanels.value}\">\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, type ThemeColor, VISUAL_VARIANTS, type VisualVariant } 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<VisualVariant | 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?: VisualVariant;\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' | '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/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.oneOf(VISUAL_VARIANTS),\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 hasPanels = signal(false);\n const isManualActivation = () => props.activation.value === 'manual';\n const isVertical = () => props.orientation.value === 'vertical';\n\n bind({\n attr: {\n 'data-has-panels': () => (hasPanels.value ? '' : null),\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 // Fall back only to a tab that carries a value: slot bindings land after\n // mount, and locking an empty first value here would later override a\n // bound host `value`. No valued tab yet means defer until a later sync.\n const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled') && tab.getAttribute('value'));\n\n if (!firstEnabled) return;\n\n setSelection(firstEnabled.getAttribute('value') ?? undefined, 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 tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n const panelsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n const sync = () => {\n ensureSelection();\n hasPanels.value = panelsSlot?.assignedElements().some((child) => child.localName === 'ore-tab-panel') ?? false;\n updateIndicator();\n };\n\n tabsSlot?.addEventListener('slotchange', sync);\n panelsSlot?.addEventListener('slotchange', sync);\n\n sync();\n requestAnimationFrame(sync);\n\n return () => {\n tabsSlot?.removeEventListener('slotchange', sync);\n panelsSlot?.removeEventListener('slotchange', sync);\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\" ?hidden=\"${() => !hasPanels.value}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [colorThemeMixin, styles],\n});\n"],"mappings":"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"}
|
package/dist/tabs.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{colorThemeMixin as e}from"./styles/mixins/theme.css.js";import{createInteraction as t}from"./core/keyboard.js";import{lifecycleSignal as n}from"./core/ore.js";import{elementDirection as r}from"./core/direction.js";import{createListControl as i}from"./core/nav.js";import{sizableBundle as
|
|
1
|
+
import{colorThemeMixin as e}from"./styles/mixins/theme.css.js";import{createInteraction as t}from"./core/keyboard.js";import{lifecycleSignal as n}from"./core/ore.js";import{elementDirection as r}from"./core/direction.js";import{createListControl as i}from"./core/nav.js";import{VISUAL_VARIANTS as a}from"./shared/types.js";import{sizableBundle as o,themableBundle as s}from"./shared/prop-bundles.js";import c from"./disclosure/tabs/tabs.css.js";import{computed as l,signal as u,watch as d}from"@vielzeug/ripple";import{bind as f,createContext as p,define as m,getHost as h,html as g,onCleanup as _,onMounted as v,prop as y,provide as b,ref as x,useEmit as S}from"@vielzeug/ore";var C=p(`TabsContext`),w=`ore-tabs`;m(w,{props:{...s,...o,activation:y.oneOf([`auto`,`manual`],`auto`),density:y.oneOf([`compact`,`default`],`default`),label:y.string(),orientation:y.oneOf([`horizontal`,`vertical`],`horizontal`),value:y.string(),variant:y.oneOf(a)},setup(e){let a=h(),o=S(),s=a.shadowRoot,c=x(),p=x(),m=u(e.value.value),y=u(!1),isManualActivation=()=>e.activation.value===`manual`,isVertical=()=>e.orientation.value===`vertical`;f({attr:{"data-has-panels":()=>y.value?``:null,value:()=>m.value??null}});let getTabs=()=>[...a.querySelectorAll(`:scope > ore-tab-item[slot="tabs"]`)],getEnabledTabs=()=>getTabs().filter(e=>!e.hasAttribute(`disabled`)),focusTab=e=>{e&&(e.shadowRoot?.querySelector(`[role="tab"]`)??e).focus()},setSelection=(e,t=!1)=>{m.value=e,t&&e&&o(`change`,{value:e})},ensureSelection=()=>{let e=getTabs();if(e.length===0)return;let t=m.value;if(t&&e.some(e=>e.getAttribute(`value`)===t&&!e.hasAttribute(`disabled`)))return;let n=e.find(e=>!e.hasAttribute(`disabled`)&&e.getAttribute(`value`));n&&setSelection(n.getAttribute(`value`)??void 0,!1)};d(e.value,e=>{m.value=e,ensureSelection()});let w=i({direction:()=>r(a),getItems:()=>getEnabledTabs(),loop:!0,onNavigate:({item:e})=>{if(focusTab(e),!isManualActivation()){let t=e.getAttribute(`value`);t&&setSelection(t,!0)}},orientation:()=>isVertical()?`vertical`:`both`,signal:n(_)});b(C,{color:e.color,density:l(()=>e.density.value??`default`),orientation:l(()=>e.orientation.value??`horizontal`),size:e.size,value:m,variant:e.variant});let moveIndicator=e=>{let t=p.value,n=c.value;if(!t||!n||!e)return;let r=e.getBoundingClientRect(),i=n.getBoundingClientRect();if(isVertical()){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)`}},updateIndicator=()=>{let e=m.value;if(!e)return;let t=getTabs().find(t=>t.getAttribute(`value`)===e);moveIndicator(t)};d(m,()=>{requestAnimationFrame(updateIndicator)});let handleTabClick=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!==m.value&&setSelection(n,!0)},activateFocusedTab=()=>{let e=getEnabledTabs().find(e=>e===document.activeElement||e.shadowRoot?.activeElement===document.activeElement)?.getAttribute(`value`);e&&e!==m.value&&setSelection(e,!0)},T=t({disabled:l(()=>!isManualActivation()),onPress:activateFocusedTab}),handleKeydown=e=>{let t=getEnabledTabs();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`)===m.value),r=n?t.indexOf(n):-1;r>=0&&w.set(r),!w.handleKeydown(e)&&T.handleKeydown(e)};return f({on:{click:handleTabClick,keydown:handleKeydown}}),v(()=>{let e=s?.querySelector(`slot[name="tabs"]`),t=s?.querySelector(`slot:not([name])`),sync=()=>{ensureSelection(),y.value=t?.assignedElements().some(e=>e.localName===`ore-tab-panel`)??!1,updateIndicator()};return e?.addEventListener(`slotchange`,sync),t?.addEventListener(`slotchange`,sync),sync(),requestAnimationFrame(sync),()=>{e?.removeEventListener(`slotchange`,sync),t?.removeEventListener(`slotchange`,sync)}}),g`
|
|
2
2
|
<div class="tablist-wrapper">
|
|
3
3
|
<div
|
|
4
4
|
role="tablist"
|
|
5
|
-
ref="${
|
|
5
|
+
ref="${c}"
|
|
6
6
|
part="tablist"
|
|
7
7
|
aria-orientation="${e.orientation}"
|
|
8
8
|
aria-label="${e.label}">
|
|
@@ -10,8 +10,8 @@ import{colorThemeMixin as e}from"./styles/mixins/theme.css.js";import{createInte
|
|
|
10
10
|
</div>
|
|
11
11
|
<div class="indicator" ref="${p}" part="indicator"></div>
|
|
12
12
|
</div>
|
|
13
|
-
<div class="panels" part="panels" ?hidden="${()=>!
|
|
13
|
+
<div class="panels" part="panels" ?hidden="${()=>!y.value}">
|
|
14
14
|
<slot></slot>
|
|
15
15
|
</div>
|
|
16
|
-
`},styles:[e,
|
|
16
|
+
`},styles:[e,c]});export{C as TABS_CTX,w as TABS_TAG};
|
|
17
17
|
//# sourceMappingURL=tabs.js.map
|
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, ThemeColor, VisualVariant } 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<VisualVariant | 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?: VisualVariant;\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' | '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/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<VisualVariant>(),\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 hasPanels = signal(false);\n const isManualActivation = () => props.activation.value === 'manual';\n const isVertical = () => props.orientation.value === 'vertical';\n\n bind({\n attr: {\n 'data-has-panels': () => (hasPanels.value ? '' : null),\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 // Fall back only to a tab that carries a value: slot bindings land after\n // mount, and locking an empty first value here would later override a\n // bound host `value`. No valued tab yet means defer until a later sync.\n const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled') && tab.getAttribute('value'));\n\n if (!firstEnabled) return;\n\n setSelection(firstEnabled.getAttribute('value') ?? undefined, 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 tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n const panelsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n const sync = () => {\n ensureSelection();\n hasPanels.value = panelsSlot?.assignedElements().some((child) => child.localName === 'ore-tab-panel') ?? false;\n updateIndicator();\n };\n\n tabsSlot?.addEventListener('slotchange', sync);\n panelsSlot?.addEventListener('slotchange', sync);\n\n sync();\n requestAnimationFrame(sync);\n\n return () => {\n tabsSlot?.removeEventListener('slotchange', sync);\n panelsSlot?.removeEventListener('slotchange', sync);\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\" ?hidden=\"${() => !hasPanels.value}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [colorThemeMixin, styles],\n});\n"],"mappings":"knBA8BA,IAAa,EAAW,EAA2B,aAAa,EAkEnD,EAAW,WACxB,EAAqB,EAAU,CAC7B,MAAO,CACL,GAAG,EACH,GAAG,EACH,WAAY,EAAK,MAAM,CAAC,OAAQ,QAAQ,EAAY,MAAM,EAC1D,QAAS,EAAK,MAAM,CAAC,UAAW,SAAS,EAAY,SAAS,EAC9D,MAAO,EAAK,OAAO,EACnB,YAAa,EAAK,MAAM,CAAC,aAAc,UAAU,EAAY,YAAY,EACzE,MAAO,EAAK,OAAO,EACnB,QAAS,EAAK,OAAsB,CACtC,EACA,MAAM,EAAO,CACX,IAAM,EAAK,EAAQ,EACb,EAAO,EAAuB,EAE9B,EAAa,EAAG,WAChB,EAAa,EAAiB,EAC9B,EAAe,EAAiB,EAChC,EAAgB,EAA2B,EAAM,MAAM,KAAK,EAC5D,EAAY,EAAO,EAAK,EACxB,uBAA2B,EAAM,WAAW,QAAU,SACtD,eAAmB,EAAM,YAAY,QAAU,WAErD,EAAK,CACH,KAAM,CACJ,sBAA0B,EAAU,MAAQ,GAAK,KACjD,UAAa,EAAc,OAAS,IACtC,CACF,CAAC,EAED,IAAM,YAAgB,CAAC,GAAG,EAAG,iBAA8B,oCAAoC,CAAC,EAC1F,mBAAuB,QAAQ,CAAC,CAAC,OAAQ,GAAM,CAAC,EAAE,aAAa,UAAU,CAAC,EAC1E,SAAY,GAAiC,CAC5C,IAEa,EAAI,YAAY,cAA2B,cAAc,GAAK,EAAA,CAEtE,MAAM,CAClB,EAMM,cAAgB,EAA2B,EAAa,KAAU,CACtE,EAAc,MAAQ,EAElB,GAAc,GAAO,EAAK,SAAU,CAAE,OAAM,CAAC,CACnD,EAEM,oBAAwB,CAC5B,IAAM,EAAO,QAAQ,EAErB,GAAI,EAAK,SAAW,EAAG,OAEvB,IAAM,EAAU,EAAc,MAK9B,GAJmB,GACf,EAAK,KAAM,GAAQ,EAAI,aAAa,OAAO,IAAM,GAAW,CAAC,EAAI,aAAa,UAAU,CAAC,EAG7E,OAKhB,IAAM,EAAe,EAAK,KAAM,GAAQ,CAAC,EAAI,aAAa,UAAU,GAAK,EAAI,aAAa,OAAO,CAAC,EAE7F,GAEL,aAAa,EAAa,aAAa,OAAO,GAAK,IAAA,GAAW,EAAK,CACrE,EAEA,EAAM,EAAM,MAAQ,GAAU,CAC5B,EAAc,MAAQ,EACtB,gBAAgB,CAClB,CAAC,EAMD,IAAM,EAAc,EAAkB,CACpC,cAAiB,EAAiB,CAAE,EACpC,aAAgB,eAAe,EAC/B,KAAM,GACN,YAAa,CAAE,UAAW,CAGxB,GAFA,SAAS,CAAI,EAET,CAAC,mBAAmB,EAAG,CACzB,IAAM,EAAQ,EAAK,aAAa,OAAO,EAEnC,GAAO,aAAa,EAAO,EAAI,CACrC,CACF,EACA,gBAAoB,WAAW,EAAI,WAAa,OAChD,OAAQ,EAAgB,CAAS,CACnC,CAAC,EAMD,EAAQ,EAAU,CAChB,MAAO,EAAM,MACb,QAAS,MAAe,EAAM,QAAQ,OAAS,SAAS,EACxD,YAAa,MAAe,EAAM,YAAY,OAAS,YAAY,EACnE,KAAM,EAAM,KACZ,MAAO,EACP,QAAS,EAAM,OACjB,CAAC,EAED,IAAM,cAAiB,GAAuC,CAC5D,IAAM,EAAY,EAAa,MACzB,EAAU,EAAW,MAE3B,GAAI,CAAC,GAAa,CAAC,GAAW,CAAC,EAAW,OAE1C,IAAM,EAAU,EAAU,sBAAsB,EAC1C,EAAW,EAAQ,sBAAsB,EAE/C,GAAI,WAAW,EAAG,CAChB,IAAM,EAAI,EAAQ,IAAM,EAAS,IAAM,EAAQ,UAE/C,EAAU,MAAM,WAAa,OAC7B,EAAU,MAAM,OAAS,GAAG,EAAQ,OAAO,IAC3C,EAAU,MAAM,MAAQ,GACxB,EAAU,MAAM,KAAO,IACvB,EAAU,MAAM,IAAM,IACtB,EAAe,YACf,EAAU,MAAM,WAAa,GAC7B,EAAU,MAAM,UAAY,cAAc,EAAE,IAC9C,KAAO,CACL,IAAM,EAAI,EAAQ,KAAO,EAAS,KAAO,EAAQ,WAEjD,EAAU,MAAM,WAAa,OAC7B,EAAU,MAAM,MAAQ,GAAG,EAAQ,MAAM,IACzC,EAAU,MAAM,OAAS,GACzB,EAAU,MAAM,IAAM,GACtB,EAAU,MAAM,KAAO,IACvB,EAAe,YACf,EAAU,MAAM,WAAa,GAC7B,EAAU,MAAM,UAAY,cAAc,EAAE,IAC9C,CACF,EAEM,oBAAwB,CAC5B,IAAM,EAAQ,EAAc,MAE5B,GAAI,CAAC,EAAO,OAEZ,IAAM,EAAY,QAAQ,CAAC,CAAC,KAAM,GAAM,EAAE,aAAa,OAAO,IAAM,CAAK,EAEzE,cAAc,CAAS,CACzB,EAEA,EAAM,MAAqB,CACzB,sBAAsB,eAAe,CACvC,CAAC,EAMD,IAAM,eAAkB,GAAa,CACnC,IAAM,EAAM,EACT,aAAa,CAAC,CACd,KAAM,GAA8B,aAAgB,aAAe,EAAK,YAAc,cAAc,EAKvG,GAHI,CAAC,GAAO,EAAI,aAAa,UAAU,GAGnC,EAAI,QAAQ,UAAU,IAAM,EAAI,OAEpC,IAAM,EAAQ,EAAI,aAAa,OAAO,EAEjC,GAAS,IAAU,EAAc,OAEtC,aAAa,EAAO,EAAI,CAC1B,EAEM,uBAAiC,CAKrC,IAAM,EAJO,eACM,CAAA,CAAK,KACrB,GAAQ,IAAQ,SAAS,eAAiB,EAAI,YAAY,gBAAkB,SAAS,aAEnE,CAAA,EAAY,aAAa,OAAO,EAEjD,GAAgB,IAAiB,EAAc,OAAO,aAAa,EAAc,EAAI,CAC3F,EAEM,EAAwB,EAAkB,CAC9C,SAAU,MAAe,CAAC,mBAAmB,CAAC,EAC9C,QAAS,kBACX,CAAC,EAEK,cAAiB,GAAqB,CAC1C,IAAM,EAAO,eAAe,EAE5B,GAAI,EAAK,SAAW,EAAG,OAOvB,IAAM,EALO,EAAE,aACY,CAAA,CAAK,KAC7B,GAA8B,aAAgB,aAAe,EAAK,YAAc,cAG9D,GAAsB,EAAK,KAAM,GAAM,EAAE,aAAa,OAAO,IAAM,EAAc,KAAK,EACrG,EAAU,EAAe,EAAK,QAAQ,CAAY,EAAI,GAExD,GAAW,GAAG,EAAY,IAAI,CAAO,EAErC,GAAY,cAAc,CAAC,GAE/B,EAAsB,cAAc,CAAC,CACvC,EAkCA,OAhCA,EAAK,CACH,GAAI,CACF,MAAO,eACP,QAAS,aACX,CACF,CAAC,EAMD,MAAgB,CACd,IAAM,EAAW,GAAY,cAA+B,mBAAmB,EACzE,EAAa,GAAY,cAA+B,kBAAkB,EAC1E,SAAa,CACjB,gBAAgB,EAChB,EAAU,MAAQ,GAAY,iBAAiB,CAAC,CAAC,KAAM,GAAU,EAAM,YAAc,eAAe,GAAK,GACzG,gBAAgB,CAClB,EAQA,OANA,GAAU,iBAAiB,aAAc,IAAI,EAC7C,GAAY,iBAAiB,aAAc,IAAI,EAE/C,KAAK,EACL,sBAAsB,IAAI,MAEb,CACX,GAAU,oBAAoB,aAAc,IAAI,EAChD,GAAY,oBAAoB,aAAc,IAAI,CACpD,CACF,CAAC,EAEM,CAAI;;;;iBAIE,EAAW;;8BAEE,EAAM,YAAY;wBACxB,EAAM,MAAM;;;sCAGE,EAAa;;uDAEM,CAAC,EAAU,MAAM;;;KAIxE,EACA,OAAQ,CAAC,EAAiB,CAAM,CAClC,CAAC"}
|
|
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, type ThemeColor, VISUAL_VARIANTS, type VisualVariant } 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<VisualVariant | 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?: VisualVariant;\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' | '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/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.oneOf(VISUAL_VARIANTS),\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 hasPanels = signal(false);\n const isManualActivation = () => props.activation.value === 'manual';\n const isVertical = () => props.orientation.value === 'vertical';\n\n bind({\n attr: {\n 'data-has-panels': () => (hasPanels.value ? '' : null),\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 // Fall back only to a tab that carries a value: slot bindings land after\n // mount, and locking an empty first value here would later override a\n // bound host `value`. No valued tab yet means defer until a later sync.\n const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled') && tab.getAttribute('value'));\n\n if (!firstEnabled) return;\n\n setSelection(firstEnabled.getAttribute('value') ?? undefined, 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 tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n const panelsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n const sync = () => {\n ensureSelection();\n hasPanels.value = panelsSlot?.assignedElements().some((child) => child.localName === 'ore-tab-panel') ?? false;\n updateIndicator();\n };\n\n tabsSlot?.addEventListener('slotchange', sync);\n panelsSlot?.addEventListener('slotchange', sync);\n\n sync();\n requestAnimationFrame(sync);\n\n return () => {\n tabsSlot?.removeEventListener('slotchange', sync);\n panelsSlot?.removeEventListener('slotchange', sync);\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\" ?hidden=\"${() => !hasPanels.value}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [colorThemeMixin, styles],\n});\n"],"mappings":"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"}
|
package/dist/text.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./content/text/text.css.cjs");let t=require("@vielzeug/ore");var n=`ore-text`;(0,t.define)(
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./content/text/text.css.cjs");let t=require("@vielzeug/ore");var n=[`left`,`center`,`right`,`justify`],r=[`span`,`p`,`div`,`h1`,`h2`,`h3`,`h4`,`h5`,`h6`,`label`,`code`],i=[`primary`,`secondary`,`info`,`success`,`warning`,`error`,`heading`,`body`,`muted`,`tertiary`,`disabled`,`contrast`],a=[`2xs`,`xs`,`sm`,`md`,`lg`,`xl`,`2xl`],o=[`body`,`heading`,`label`,`caption`,`overline`,`code`],s=[`normal`,`medium`,`semibold`,`bold`],c=`ore-text`;(0,t.define)(c,{props:{align:t.prop.oneOf(n),as:t.prop.oneOf(r),color:t.prop.oneOf(i),italic:t.prop.bool(),lines:t.prop.number(),size:t.prop.oneOf(a),truncate:t.prop.bool(),variant:t.prop.oneOf(o),weight:t.prop.oneOf(s)},setup(e){return(0,t.bind)({attr:{"aria-level":()=>{let t=/^h([1-6])$/.exec(e.as.value??``);return t?t[1]:null},role:()=>/^h([1-6])$/.test(e.as.value??``)?`heading`:null},style:{"--_lines":()=>e.lines.value==null?null:String(e.lines.value)}}),t.html`
|
|
2
2
|
<slot></slot>
|
|
3
|
-
`},styles:[e.default]}),exports.TEXT_TAG=
|
|
3
|
+
`},styles:[e.default]}),exports.TEXT_TAG=c;
|
|
4
4
|
//# sourceMappingURL=text.cjs.map
|
package/dist/text.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.cjs","names":[],"sources":["../src/content/text/text.ts"],"sourcesContent":["import { bind, define, html, prop } from '@vielzeug/ore';\n\nimport styles from './text.css?inline';\n\
|
|
1
|
+
{"version":3,"file":"text.cjs","names":[],"sources":["../src/content/text/text.ts"],"sourcesContent":["import { bind, define, html, prop } from '@vielzeug/ore';\n\nimport styles from './text.css?inline';\n\nconst TEXT_ALIGNMENTS = ['left', 'center', 'right', 'justify'] as const;\nconst TEXT_ELEMENTS = ['span', 'p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'label', 'code'] as const;\nconst TEXT_COLORS = [\n 'primary',\n 'secondary',\n 'info',\n 'success',\n 'warning',\n 'error',\n 'heading',\n 'body',\n 'muted',\n 'tertiary',\n 'disabled',\n 'contrast',\n] as const;\nconst TEXT_SIZES = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const;\nconst TEXT_VARIANTS = ['body', 'heading', 'label', 'caption', 'overline', 'code'] as const;\nconst TEXT_WEIGHTS = ['normal', 'medium', 'semibold', 'bold'] as const;\n\n/** Text component properties */\nexport type OreTextProps = {\n /** Text alignment */\n align?: (typeof TEXT_ALIGNMENTS)[number];\n /** Semantic HTML element to render as: sets the correct ARIA role/level on the host */\n as?: (typeof TEXT_ELEMENTS)[number];\n /** Text color (semantic + theme colors) */\n color?: (typeof TEXT_COLORS)[number];\n /** Italic text style */\n italic?: boolean;\n /** Clamp text to N lines with an ellipsis (multi-line truncation) */\n lines?: number;\n /** Text size: maps to --text-* tokens for body variants and --heading-* tokens for the heading variant */\n size?: (typeof TEXT_SIZES)[number];\n /** Enable single-line text truncation with ellipsis */\n truncate?: boolean;\n /** Text semantic variant */\n variant?: (typeof TEXT_VARIANTS)[number];\n /** Font weight */\n weight?: (typeof TEXT_WEIGHTS)[number];\n};\n\n/**\n * A typography component with semantic variants and responsive sizing.\n *\n * @element ore-text\n *\n * @attr {string} variant - Text variant: 'body' | 'heading' | 'label' | 'caption' | 'overline' | 'code'\n * @attr {string} size - Font size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'. Maps to --text-* tokens for body variants and --heading-* tokens for the heading variant.\n * @attr {string} weight - Font weight: 'normal' | 'medium' | 'semibold' | 'bold'\n * @attr {string} color - Text color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error' | 'heading' | 'body' | 'muted' | 'tertiary' | 'disabled' | 'contrast'\n * @attr {string} align - Text alignment: 'left' | 'center' | 'right' | 'justify'\n * @attr {boolean} truncate - Truncate text with ellipsis when it overflows (single-line)\n * @attr {number} lines - Clamp to N lines with ellipsis (multi-line truncation)\n * @attr {boolean} italic - Italic text style\n * @attr {string} as - Semantic HTML element: 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'code'\n *\n * @slot - Text content\n *\n * @cssprop --text-size - Font size\n * @cssprop --text-weight - Font weight\n * @cssprop --text-color - Text color\n * @cssprop --text-line-height - Line height\n * @cssprop --text-letter-spacing - Letter spacing\n *\n * @example\n * ```html\n * <ore-text variant=\"heading\" size=\"2xl\" weight=\"bold\">Welcome</ore-text>\n * <ore-text as=\"h2\" variant=\"heading\" size=\"xl\">Section title</ore-text>\n * <ore-text color=\"primary\" weight=\"semibold\">Important notice</ore-text>\n * <ore-text variant=\"code\">npm install</ore-text>\n * <ore-text lines=\"2\">Long paragraph that clamps at two lines…</ore-text>\n * ```\n */\n\nexport const TEXT_TAG = 'ore-text' as const;\ndefine<OreTextProps>(TEXT_TAG, {\n props: {\n align: prop.oneOf(TEXT_ALIGNMENTS),\n as: prop.oneOf(TEXT_ELEMENTS),\n color: prop.oneOf(TEXT_COLORS),\n italic: prop.bool(),\n lines: prop.number(),\n size: prop.oneOf(TEXT_SIZES),\n truncate: prop.bool(),\n variant: prop.oneOf(TEXT_VARIANTS),\n weight: prop.oneOf(TEXT_WEIGHTS),\n },\n setup(props) {\n bind({\n attr: {\n 'aria-level': () => {\n const match = /^h([1-6])$/.exec((props.as.value as string) ?? '');\n\n return match ? match[1] : null;\n },\n role: () => (/^h([1-6])$/.test((props.as.value as string) ?? '') ? 'heading' : null),\n },\n style: {\n '--_lines': () => (props.lines.value != null ? String(props.lines.value) : null),\n },\n });\n\n return html`\n <slot></slot>\n `;\n },\n styles: [styles],\n});\n"],"mappings":"iJAIA,IAAM,EAAkB,CAAC,OAAQ,SAAU,QAAS,SAAS,EACvD,EAAgB,CAAC,OAAQ,IAAK,MAAO,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,QAAS,MAAM,EACxF,EAAc,CAClB,UACA,YACA,OACA,UACA,UACA,QACA,UACA,OACA,QACA,WACA,WACA,UACF,EACM,EAAa,CAAC,MAAO,KAAM,KAAM,KAAM,KAAM,KAAM,KAAK,EACxD,EAAgB,CAAC,OAAQ,UAAW,QAAS,UAAW,WAAY,MAAM,EAC1E,EAAe,CAAC,SAAU,SAAU,WAAY,MAAM,EAyD/C,EAAW,YACxB,EAAA,EAAA,OAAA,CAAqB,EAAU,CAC7B,MAAO,CACL,MAAO,EAAA,KAAK,MAAM,CAAe,EACjC,GAAI,EAAA,KAAK,MAAM,CAAa,EAC5B,MAAO,EAAA,KAAK,MAAM,CAAW,EAC7B,OAAQ,EAAA,KAAK,KAAK,EAClB,MAAO,EAAA,KAAK,OAAO,EACnB,KAAM,EAAA,KAAK,MAAM,CAAU,EAC3B,SAAU,EAAA,KAAK,KAAK,EACpB,QAAS,EAAA,KAAK,MAAM,CAAa,EACjC,OAAQ,EAAA,KAAK,MAAM,CAAY,CACjC,EACA,MAAM,EAAO,CAeX,OAdA,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,iBAAoB,CAClB,IAAM,EAAQ,aAAa,KAAM,EAAM,GAAG,OAAoB,EAAE,EAEhE,OAAO,EAAQ,EAAM,GAAK,IAC5B,EACA,SAAa,aAAa,KAAM,EAAM,GAAG,OAAoB,EAAE,EAAI,UAAY,IACjF,EACA,MAAO,CACL,eAAmB,EAAM,MAAM,OAAS,KAAmC,KAA5B,OAAO,EAAM,MAAM,KAAK,CACzE,CACF,CAAC,EAEM,EAAA,IAAI;;KAGb,EACA,OAAQ,CAAC,EAAA,OAAM,CACjB,CAAC"}
|
package/dist/text.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e from"./content/text/text.css.js";import{bind as t,define as n,html as r,prop as i}from"@vielzeug/ore";var a=`ore-text`;n(
|
|
1
|
+
import e from"./content/text/text.css.js";import{bind as t,define as n,html as r,prop as i}from"@vielzeug/ore";var a=[`left`,`center`,`right`,`justify`],o=[`span`,`p`,`div`,`h1`,`h2`,`h3`,`h4`,`h5`,`h6`,`label`,`code`],s=[`primary`,`secondary`,`info`,`success`,`warning`,`error`,`heading`,`body`,`muted`,`tertiary`,`disabled`,`contrast`],c=[`2xs`,`xs`,`sm`,`md`,`lg`,`xl`,`2xl`],l=[`body`,`heading`,`label`,`caption`,`overline`,`code`],u=[`normal`,`medium`,`semibold`,`bold`],d=`ore-text`;n(d,{props:{align:i.oneOf(a),as:i.oneOf(o),color:i.oneOf(s),italic:i.bool(),lines:i.number(),size:i.oneOf(c),truncate:i.bool(),variant:i.oneOf(l),weight:i.oneOf(u)},setup(e){return t({attr:{"aria-level":()=>{let t=/^h([1-6])$/.exec(e.as.value??``);return t?t[1]:null},role:()=>/^h([1-6])$/.test(e.as.value??``)?`heading`:null},style:{"--_lines":()=>e.lines.value==null?null:String(e.lines.value)}}),r`
|
|
2
2
|
<slot></slot>
|
|
3
|
-
`},styles:[e]});export{
|
|
3
|
+
`},styles:[e]});export{d as TEXT_TAG};
|
|
4
4
|
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","names":[],"sources":["../src/content/text/text.ts"],"sourcesContent":["import { bind, define, html, prop } from '@vielzeug/ore';\n\nimport styles from './text.css?inline';\n\
|
|
1
|
+
{"version":3,"file":"text.js","names":[],"sources":["../src/content/text/text.ts"],"sourcesContent":["import { bind, define, html, prop } from '@vielzeug/ore';\n\nimport styles from './text.css?inline';\n\nconst TEXT_ALIGNMENTS = ['left', 'center', 'right', 'justify'] as const;\nconst TEXT_ELEMENTS = ['span', 'p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'label', 'code'] as const;\nconst TEXT_COLORS = [\n 'primary',\n 'secondary',\n 'info',\n 'success',\n 'warning',\n 'error',\n 'heading',\n 'body',\n 'muted',\n 'tertiary',\n 'disabled',\n 'contrast',\n] as const;\nconst TEXT_SIZES = ['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const;\nconst TEXT_VARIANTS = ['body', 'heading', 'label', 'caption', 'overline', 'code'] as const;\nconst TEXT_WEIGHTS = ['normal', 'medium', 'semibold', 'bold'] as const;\n\n/** Text component properties */\nexport type OreTextProps = {\n /** Text alignment */\n align?: (typeof TEXT_ALIGNMENTS)[number];\n /** Semantic HTML element to render as: sets the correct ARIA role/level on the host */\n as?: (typeof TEXT_ELEMENTS)[number];\n /** Text color (semantic + theme colors) */\n color?: (typeof TEXT_COLORS)[number];\n /** Italic text style */\n italic?: boolean;\n /** Clamp text to N lines with an ellipsis (multi-line truncation) */\n lines?: number;\n /** Text size: maps to --text-* tokens for body variants and --heading-* tokens for the heading variant */\n size?: (typeof TEXT_SIZES)[number];\n /** Enable single-line text truncation with ellipsis */\n truncate?: boolean;\n /** Text semantic variant */\n variant?: (typeof TEXT_VARIANTS)[number];\n /** Font weight */\n weight?: (typeof TEXT_WEIGHTS)[number];\n};\n\n/**\n * A typography component with semantic variants and responsive sizing.\n *\n * @element ore-text\n *\n * @attr {string} variant - Text variant: 'body' | 'heading' | 'label' | 'caption' | 'overline' | 'code'\n * @attr {string} size - Font size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'. Maps to --text-* tokens for body variants and --heading-* tokens for the heading variant.\n * @attr {string} weight - Font weight: 'normal' | 'medium' | 'semibold' | 'bold'\n * @attr {string} color - Text color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error' | 'heading' | 'body' | 'muted' | 'tertiary' | 'disabled' | 'contrast'\n * @attr {string} align - Text alignment: 'left' | 'center' | 'right' | 'justify'\n * @attr {boolean} truncate - Truncate text with ellipsis when it overflows (single-line)\n * @attr {number} lines - Clamp to N lines with ellipsis (multi-line truncation)\n * @attr {boolean} italic - Italic text style\n * @attr {string} as - Semantic HTML element: 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'code'\n *\n * @slot - Text content\n *\n * @cssprop --text-size - Font size\n * @cssprop --text-weight - Font weight\n * @cssprop --text-color - Text color\n * @cssprop --text-line-height - Line height\n * @cssprop --text-letter-spacing - Letter spacing\n *\n * @example\n * ```html\n * <ore-text variant=\"heading\" size=\"2xl\" weight=\"bold\">Welcome</ore-text>\n * <ore-text as=\"h2\" variant=\"heading\" size=\"xl\">Section title</ore-text>\n * <ore-text color=\"primary\" weight=\"semibold\">Important notice</ore-text>\n * <ore-text variant=\"code\">npm install</ore-text>\n * <ore-text lines=\"2\">Long paragraph that clamps at two lines…</ore-text>\n * ```\n */\n\nexport const TEXT_TAG = 'ore-text' as const;\ndefine<OreTextProps>(TEXT_TAG, {\n props: {\n align: prop.oneOf(TEXT_ALIGNMENTS),\n as: prop.oneOf(TEXT_ELEMENTS),\n color: prop.oneOf(TEXT_COLORS),\n italic: prop.bool(),\n lines: prop.number(),\n size: prop.oneOf(TEXT_SIZES),\n truncate: prop.bool(),\n variant: prop.oneOf(TEXT_VARIANTS),\n weight: prop.oneOf(TEXT_WEIGHTS),\n },\n setup(props) {\n bind({\n attr: {\n 'aria-level': () => {\n const match = /^h([1-6])$/.exec((props.as.value as string) ?? '');\n\n return match ? match[1] : null;\n },\n role: () => (/^h([1-6])$/.test((props.as.value as string) ?? '') ? 'heading' : null),\n },\n style: {\n '--_lines': () => (props.lines.value != null ? String(props.lines.value) : null),\n },\n });\n\n return html`\n <slot></slot>\n `;\n },\n styles: [styles],\n});\n"],"mappings":"+GAIA,IAAM,EAAkB,CAAC,OAAQ,SAAU,QAAS,SAAS,EACvD,EAAgB,CAAC,OAAQ,IAAK,MAAO,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,QAAS,MAAM,EACxF,EAAc,CAClB,UACA,YACA,OACA,UACA,UACA,QACA,UACA,OACA,QACA,WACA,WACA,UACF,EACM,EAAa,CAAC,MAAO,KAAM,KAAM,KAAM,KAAM,KAAM,KAAK,EACxD,EAAgB,CAAC,OAAQ,UAAW,QAAS,UAAW,WAAY,MAAM,EAC1E,EAAe,CAAC,SAAU,SAAU,WAAY,MAAM,EAyD/C,EAAW,WACxB,EAAqB,EAAU,CAC7B,MAAO,CACL,MAAO,EAAK,MAAM,CAAe,EACjC,GAAI,EAAK,MAAM,CAAa,EAC5B,MAAO,EAAK,MAAM,CAAW,EAC7B,OAAQ,EAAK,KAAK,EAClB,MAAO,EAAK,OAAO,EACnB,KAAM,EAAK,MAAM,CAAU,EAC3B,SAAU,EAAK,KAAK,EACpB,QAAS,EAAK,MAAM,CAAa,EACjC,OAAQ,EAAK,MAAM,CAAY,CACjC,EACA,MAAM,EAAO,CAeX,OAdA,EAAK,CACH,KAAM,CACJ,iBAAoB,CAClB,IAAM,EAAQ,aAAa,KAAM,EAAM,GAAG,OAAoB,EAAE,EAEhE,OAAO,EAAQ,EAAM,GAAK,IAC5B,EACA,SAAa,aAAa,KAAM,EAAM,GAAG,OAAoB,EAAE,EAAI,UAAY,IACjF,EACA,MAAO,CACL,eAAmB,EAAM,MAAM,OAAS,KAAmC,KAA5B,OAAO,EAAM,MAAM,KAAK,CACzE,CACF,CAAC,EAEM,CAAI;;KAGb,EACA,OAAQ,CAAC,CAAM,CACjB,CAAC"}
|
package/dist/textarea.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./inputs/shared/native-field-event.cjs"),t=require("./styles/mixins/accessibility.css.cjs"),n=require("./styles/mixins/field-variants.css.cjs"),r=require("./styles/mixins/shape.css.cjs"),i=require("./styles/mixins/states.css.cjs"),a=require("./styles/mixins/theme.css.cjs"),o=require("./core/auto-resize.cjs"),s=require("./core/ore.cjs"),c=require("./core/ref-callback.cjs"),l=require("./core/text-field.cjs"),u=require("./shared/prop-bundles.cjs"),
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./inputs/shared/native-field-event.cjs"),t=require("./styles/mixins/accessibility.css.cjs"),n=require("./styles/mixins/field-variants.css.cjs"),r=require("./styles/mixins/shape.css.cjs"),i=require("./styles/mixins/states.css.cjs"),a=require("./styles/mixins/theme.css.cjs"),o=require("./core/auto-resize.cjs"),s=require("./core/ore.cjs"),c=require("./core/ref-callback.cjs"),l=require("./core/text-field.cjs"),u=require("./shared/types.cjs"),d=require("./shared/prop-bundles.cjs"),f=require("./shared/size-presets.cjs"),p=require("./icon.cjs"),m=require("./inputs/shared/field-binding.cjs"),h=require("./inputs/shared/templates.cjs"),g=require("./inputs/textarea/textarea.css.cjs");let _=require("@vielzeug/ripple"),v=require("@vielzeug/ore");var y=[`none`,`both`,`horizontal`,`vertical`],b=`ore-textarea`;(0,v.define)(b,{formAssociated:!0,props:{...d.themableBundle,...d.sizableBundle,...d.disablableBundle,...d.roundableBundle,"auto-resize":v.prop.bool(!1),error:v.prop.string(),fullwidth:v.prop.bool(!1),helper:v.prop.string(),label:v.prop.string(),"label-placement":v.prop.oneOf([`inset`,`outside`],`inset`),loading:v.prop.bool(!1),maxlength:v.prop.json(void 0),name:v.prop.string(),"no-resize":v.prop.bool(!1),placeholder:v.prop.string(),readonly:v.prop.bool(!1),ref:v.prop.data(),required:v.prop.bool(!1),resize:v.prop.oneOf(y),rows:v.prop.json(void 0),success:v.prop.bool(!1),value:v.prop.string(),variant:v.prop.oneOf(u.FIELD_VARIANTS)},setup(t){let n=(0,v.getHost)(),r=v.watchEffect,i=(0,v.ref)(),a=o.createAutoResize({enabled:t[`auto-resize`]}),u=(0,_.computed)(()=>t.disabled.value||t.loading.value),d=s.lifecycleSignal(v.onCleanup),f=l.createTextField({disabled:u,error:t.error,helper:t.helper,label:t.label,labelPlacement:t[`label-placement`],maxLength:t.maxlength,onChange:(t,r)=>{e.setFieldValue(n,r),e.dispatchNativeFieldEvent(n,`change`)},onInput:(t,r)=>{e.setFieldValue(n,r),e.dispatchNativeFieldEvent(n,`input`)},prefix:`textarea`,readonly:t.readonly,required:t.required,signal:d,value:t.value});e.defineFieldValue(n,()=>f.value.value,e=>{f.value.value=e}),f.attachFormField((0,v.useField)({disabled:f.disabled,onReset:()=>{f.reset(),requestAnimationFrame(()=>a.recompute())},toFormValue:e=>e,validity:f.validity,value:f.value}));let{ariaDescribedBy:p,ariaErrorMessage:g,ariaInvalid:y,ariaLabelledBy:b,assistiveId:x,counter:S,errorId:C,errorText:w,fieldId:T,helperText:E,labelId:D,labelVisible:O}=f;return(0,v.onElement)(i,e=>{let n=f.wire(e),i=a.wire(e),o=c.bindRefCallback(t.ref,e),s=r(()=>{e.style.resize=t[`auto-resize`].value||t[`no-resize`].value?`none`:t.resize.value||`vertical`,requestAnimationFrame(()=>a.recompute())});return()=>{o(),n(),i(),s()}}),(0,v.bind)({attr:{error:m.errorAttr(w),size:t.size,success:()=>t.success.value&&!w.value?!0:void 0,variant:t.variant}}),v.html`
|
|
2
2
|
<div class="textarea-wrapper" part="wrapper">
|
|
3
3
|
<label class="label" part="label" for="${T}" id="${D}" ?hidden="${()=>!O.value}">
|
|
4
4
|
${t.label}
|
|
@@ -15,16 +15,16 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
15
15
|
?disabled="${u}"
|
|
16
16
|
?readonly="${t.readonly}"
|
|
17
17
|
?required="${t.required}"
|
|
18
|
-
value="${(0,
|
|
19
|
-
aria-describedby="${
|
|
20
|
-
aria-errormessage="${
|
|
18
|
+
value="${(0,v.model)(f.value)}"
|
|
19
|
+
aria-describedby="${p}"
|
|
20
|
+
aria-errormessage="${g}"
|
|
21
21
|
aria-invalid="${y}"
|
|
22
22
|
aria-labelledby="${b}"
|
|
23
23
|
aria-busy="${()=>t.loading.value?`true`:null}"></textarea>
|
|
24
|
-
${
|
|
24
|
+
${h.renderStatusIcon(w)}
|
|
25
25
|
<span class="field-spinner" part="spinner" role="status" aria-label="Loading"></span>
|
|
26
26
|
</div>
|
|
27
|
-
${
|
|
27
|
+
${h.renderFieldStatusRegion({assistiveId:x,counter:S,errorId:C,errorText:w,helperText:E})}
|
|
28
28
|
</div>
|
|
29
|
-
`},shadow:{delegatesFocus:!0},styles:[a.colorThemeMixin,t.coarsePointerMixin,t.reducedMotionMixin,r.roundedVariantMixin,i.disabledLoadingMixin,r.sizeVariantMixin(
|
|
29
|
+
`},shadow:{delegatesFocus:!0},styles:[a.colorThemeMixin,t.coarsePointerMixin,t.reducedMotionMixin,r.roundedVariantMixin,i.disabledLoadingMixin,r.sizeVariantMixin(f.TEXTAREA_SIZE_PRESET),a.forcedColorsFocusMixin(`textarea`),g.default,n.fieldVariantMixin({container:`.field`,text:`textarea`,tokenPrefix:`textarea`})]}),exports.ICON_TAG=p.ICON_TAG,exports.TEXTAREA_TAG=b;
|
|
30
30
|
//# sourceMappingURL=textarea.cjs.map
|