@vielzeug/refine 2.4.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -18
- package/dist/accordion-item.cjs.map +1 -1
- package/dist/accordion-item.js.map +1 -1
- package/dist/accordion.cjs.map +1 -1
- package/dist/accordion.js.map +1 -1
- package/dist/async.cjs +1 -1
- package/dist/async.cjs.map +1 -1
- package/dist/async.js +3 -3
- package/dist/async.js.map +1 -1
- package/dist/box.cjs.map +1 -1
- package/dist/box.js.map +1 -1
- package/dist/button.cjs +6 -4
- package/dist/button.cjs.map +1 -1
- package/dist/button.js +11 -9
- package/dist/button.js.map +1 -1
- package/dist/card.cjs +1 -1
- package/dist/card.cjs.map +1 -1
- package/dist/card.js +6 -4
- package/dist/card.js.map +1 -1
- package/dist/checkbox-group.cjs +1 -1
- package/dist/checkbox-group.cjs.map +1 -1
- package/dist/checkbox-group.js +1 -1
- package/dist/checkbox-group.js.map +1 -1
- package/dist/combobox.cjs +2 -2
- package/dist/combobox.cjs.map +1 -1
- package/dist/combobox.js +7 -3
- package/dist/combobox.js.map +1 -1
- package/dist/command-palette.cjs +1 -1
- package/dist/command-palette.cjs.map +1 -1
- package/dist/command-palette.js +6 -2
- package/dist/command-palette.js.map +1 -1
- package/dist/content/card/card.css.cjs +1 -1
- package/dist/content/card/card.css.cjs.map +1 -1
- package/dist/content/card/card.css.js +1 -1
- package/dist/content/card/card.css.js.map +1 -1
- package/dist/content/card/card.d.ts +2 -2
- package/dist/content/card/card.d.ts.map +1 -1
- package/dist/content/pagination/pagination.css.cjs +1 -1
- package/dist/content/pagination/pagination.css.cjs.map +1 -1
- package/dist/content/pagination/pagination.css.js +1 -1
- package/dist/content/pagination/pagination.css.js.map +1 -1
- package/dist/core/field-base.cjs +1 -1
- package/dist/core/field-base.cjs.map +1 -1
- package/dist/core/field-base.d.ts +1 -1
- package/dist/core/field-base.js +1 -1
- package/dist/core/field-base.js.map +1 -1
- package/dist/core/nav.cjs +1 -1
- package/dist/core/nav.cjs.map +1 -1
- package/dist/core/nav.d.ts +4 -1
- package/dist/core/nav.d.ts.map +1 -1
- package/dist/core/nav.js +41 -28
- package/dist/core/nav.js.map +1 -1
- package/dist/core/option-list.cjs +1 -1
- package/dist/core/option-list.cjs.map +1 -1
- package/dist/core/option-list.d.ts.map +1 -1
- package/dist/core/option-list.js +30 -28
- package/dist/core/option-list.js.map +1 -1
- package/dist/core/slider.cjs +1 -1
- package/dist/core/slider.cjs.map +1 -1
- package/dist/core/slider.js +1 -1
- package/dist/core/slider.js.map +1 -1
- package/dist/core/spinner.cjs +1 -1
- package/dist/core/spinner.cjs.map +1 -1
- package/dist/core/spinner.js +1 -1
- package/dist/core/spinner.js.map +1 -1
- package/dist/custom-elements.json +231 -19
- package/dist/datagrid.cjs +73 -73
- package/dist/datagrid.cjs.map +1 -1
- package/dist/datagrid.js +331 -332
- package/dist/datagrid.js.map +1 -1
- package/dist/disclosure/accordion/accordion.css.cjs +1 -1
- package/dist/disclosure/accordion/accordion.css.cjs.map +1 -1
- package/dist/disclosure/accordion/accordion.css.js +1 -1
- package/dist/disclosure/accordion/accordion.css.js.map +1 -1
- package/dist/disclosure/accordion/accordion.d.ts +5 -5
- package/dist/disclosure/accordion/accordion.d.ts.map +1 -1
- package/dist/disclosure/accordion-item/accordion-item.css.cjs +1 -1
- package/dist/disclosure/accordion-item/accordion-item.css.cjs.map +1 -1
- package/dist/disclosure/accordion-item/accordion-item.css.js +1 -1
- package/dist/disclosure/accordion-item/accordion-item.css.js.map +1 -1
- package/dist/disclosure/accordion-item/accordion-item.d.ts +3 -3
- package/dist/disclosure/accordion-item/accordion-item.d.ts.map +1 -1
- package/dist/disclosure/tab-item/tab-item.css.cjs +1 -1
- package/dist/disclosure/tab-item/tab-item.css.cjs.map +1 -1
- package/dist/disclosure/tab-item/tab-item.css.js +1 -1
- package/dist/disclosure/tab-item/tab-item.css.js.map +1 -1
- package/dist/disclosure/tab-item/tab-item.d.ts +3 -3
- package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
- package/dist/disclosure/tabs/tabs.css.cjs +1 -1
- package/dist/disclosure/tabs/tabs.css.cjs.map +1 -1
- package/dist/disclosure/tabs/tabs.css.js +1 -1
- package/dist/disclosure/tabs/tabs.css.js.map +1 -1
- package/dist/disclosure/tabs/tabs.d.ts +5 -5
- package/dist/disclosure/tabs/tabs.d.ts.map +1 -1
- package/dist/drawer.cjs +1 -1
- package/dist/drawer.cjs.map +1 -1
- package/dist/drawer.js +1 -1
- package/dist/drawer.js.map +1 -1
- package/dist/feedback/skeleton/skeleton.css.cjs +1 -1
- package/dist/feedback/skeleton/skeleton.css.cjs.map +1 -1
- package/dist/feedback/skeleton/skeleton.css.js +1 -1
- package/dist/feedback/skeleton/skeleton.css.js.map +1 -1
- package/dist/feedback/skeleton/skeleton.d.ts +1 -1
- package/dist/feedback/toast/toast.d.ts.map +1 -1
- package/dist/file-input.cjs +1 -1
- package/dist/file-input.cjs.map +1 -1
- package/dist/file-input.js +2 -2
- package/dist/file-input.js.map +1 -1
- package/dist/frameworks/elements.d.ts +4 -86
- package/dist/frameworks/react.d.ts +5 -4
- package/dist/frameworks/vue.d.ts +4 -4
- package/dist/grid.cjs +2 -2
- package/dist/grid.cjs.map +1 -1
- package/dist/grid.js +21 -18
- package/dist/grid.js.map +1 -1
- package/dist/inputs/combobox/combobox.d.ts +12 -1
- package/dist/inputs/combobox/combobox.d.ts.map +1 -1
- package/dist/inputs/datagrid/datagrid.d.ts +24 -32
- package/dist/inputs/datagrid/datagrid.d.ts.map +1 -1
- package/dist/inputs/date-picker/date-picker.css.cjs +1 -1
- package/dist/inputs/date-picker/date-picker.css.cjs.map +1 -1
- package/dist/inputs/date-picker/date-picker.css.js +1 -1
- package/dist/inputs/date-picker/date-picker.css.js.map +1 -1
- package/dist/inputs/file-input/file-input-upload.cjs +1 -1
- package/dist/inputs/file-input/file-input-upload.cjs.map +1 -1
- package/dist/inputs/file-input/file-input-upload.js +1 -1
- package/dist/inputs/file-input/file-input-upload.js.map +1 -1
- package/dist/inputs/index.d.ts +2 -3
- package/dist/inputs/index.d.ts.map +1 -1
- package/dist/inputs/input/input.css.cjs +1 -1
- package/dist/inputs/input/input.css.cjs.map +1 -1
- package/dist/inputs/input/input.css.js +1 -1
- package/dist/inputs/input/input.css.js.map +1 -1
- package/dist/inputs/shared/field-binding.cjs +1 -1
- package/dist/inputs/shared/field-binding.cjs.map +1 -1
- package/dist/inputs/shared/field-binding.js +6 -6
- package/dist/inputs/shared/field-binding.js.map +1 -1
- package/dist/layout/box/box.css.cjs +1 -1
- package/dist/layout/box/box.css.cjs.map +1 -1
- package/dist/layout/box/box.css.js +1 -1
- package/dist/layout/box/box.css.js.map +1 -1
- package/dist/layout/box/box.d.ts +3 -3
- package/dist/layout/box/box.d.ts.map +1 -1
- package/dist/layout/grid/grid.d.ts.map +1 -1
- package/dist/layout/navbar/navbar.css.cjs +1 -1
- package/dist/layout/navbar/navbar.css.cjs.map +1 -1
- package/dist/layout/navbar/navbar.css.js +1 -1
- package/dist/layout/navbar/navbar.css.js.map +1 -1
- package/dist/layout/navbar/navbar.d.ts +7 -1
- package/dist/layout/navbar/navbar.d.ts.map +1 -1
- package/dist/layout/sidebar/sidebar-item.css.cjs +1 -1
- package/dist/layout/sidebar/sidebar-item.css.cjs.map +1 -1
- package/dist/layout/sidebar/sidebar-item.css.js +1 -1
- package/dist/layout/sidebar/sidebar-item.css.js.map +1 -1
- package/dist/layout/sidebar/sidebar.css.cjs +1 -1
- package/dist/layout/sidebar/sidebar.css.cjs.map +1 -1
- package/dist/layout/sidebar/sidebar.css.js +1 -1
- package/dist/layout/sidebar/sidebar.css.js.map +1 -1
- package/dist/layout/sidebar/sidebar.d.ts.map +1 -1
- package/dist/menu.cjs +1 -1
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.js +6 -6
- package/dist/menu.js.map +1 -1
- package/dist/navbar.cjs +10 -10
- package/dist/navbar.cjs.map +1 -1
- package/dist/navbar.js +99 -95
- package/dist/navbar.js.map +1 -1
- package/dist/navigation-menu.cjs +1 -1
- package/dist/navigation-menu.cjs.map +1 -1
- package/dist/navigation-menu.js +1 -1
- package/dist/navigation-menu.js.map +1 -1
- package/dist/number-input.cjs +1 -1
- package/dist/number-input.cjs.map +1 -1
- package/dist/number-input.js +1 -1
- package/dist/number-input.js.map +1 -1
- package/dist/overlay/popover/popover.d.ts.map +1 -1
- package/dist/overlay/tooltip/tooltip.d.ts.map +1 -1
- package/dist/popover.cjs +1 -1
- package/dist/popover.cjs.map +1 -1
- package/dist/popover.js +6 -6
- package/dist/popover.js.map +1 -1
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +1 -1
- package/dist/radio-group.js.map +1 -1
- package/dist/radio.cjs +1 -1
- package/dist/radio.cjs.map +1 -1
- package/dist/radio.js +5 -5
- package/dist/radio.js.map +1 -1
- package/dist/rating.cjs +1 -1
- package/dist/rating.cjs.map +1 -1
- package/dist/rating.js +1 -1
- package/dist/rating.js.map +1 -1
- package/dist/refine.cjs +241 -224
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +297 -280
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +251 -234
- package/dist/refine.js.map +1 -1
- package/dist/shared/types.d.ts +0 -2
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/sidebar.cjs +1 -1
- package/dist/sidebar.cjs.map +1 -1
- package/dist/sidebar.js +83 -83
- package/dist/sidebar.js.map +1 -1
- package/dist/skeleton.cjs +1 -1
- package/dist/skeleton.cjs.map +1 -1
- package/dist/skeleton.js +11 -9
- package/dist/skeleton.js.map +1 -1
- package/dist/slider.cjs +1 -1
- package/dist/slider.cjs.map +1 -1
- package/dist/slider.js +20 -20
- package/dist/slider.js.map +1 -1
- package/dist/stats.cjs +1 -1
- package/dist/stats.cjs.map +1 -1
- package/dist/stats.js +2 -2
- package/dist/stats.js.map +1 -1
- package/dist/styles/mixins/field-variants.css.cjs +15 -0
- package/dist/styles/mixins/field-variants.css.cjs.map +1 -1
- package/dist/styles/mixins/field-variants.css.d.ts.map +1 -1
- package/dist/styles/mixins/field-variants.css.js +15 -0
- package/dist/styles/mixins/field-variants.css.js.map +1 -1
- package/dist/styles/mixins/variants.css.cjs +4 -4
- package/dist/styles/mixins/variants.css.cjs.map +1 -1
- package/dist/styles/mixins/variants.css.js +4 -4
- package/dist/styles/mixins/variants.css.js.map +1 -1
- package/dist/tab-item.cjs.map +1 -1
- package/dist/tab-item.js.map +1 -1
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.js.map +1 -1
- package/dist/toast.cjs +8 -8
- package/dist/toast.cjs.map +1 -1
- package/dist/toast.js +32 -33
- package/dist/toast.js.map +1 -1
- package/dist/tooltip.cjs +1 -1
- package/dist/tooltip.cjs.map +1 -1
- package/dist/tooltip.js +2 -2
- package/dist/tooltip.js.map +1 -1
- package/dist/types/elements.d.ts +207 -0
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
# @vielzeug/refine
|
|
2
2
|
|
|
3
|
-
> Accessible, themeable web components built
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/@vielzeug/refine) [](https://opensource.org/licenses/MIT)
|
|
6
|
-
|
|
7
|
-
<details>
|
|
8
|
-
<summary>Quick Reference</summary>
|
|
9
|
-
|
|
10
|
-
**Package:** `@vielzeug/refine` · **Category:** UI Components
|
|
11
|
-
|
|
12
|
-
**Key exports (custom elements):** `ore-button`, `ore-button-group`, `ore-input`, `ore-select`, `ore-combobox`, `ore-dialog`, `ore-drawer`, `ore-avatar`, `ore-avatar-group`, `ore-carousel`, `ore-datagrid`, `ore-date-picker`, `ore-time-picker`, `ore-tabs`, `ore-toast`
|
|
13
|
-
|
|
14
|
-
**When to use:** Accessible, themeable web components built with Ore for framework and vanilla DOM apps.
|
|
15
|
-
|
|
16
|
-
**Related:** [@vielzeug/ore](https://vielzeug.dev/ore/) · [@vielzeug/orbit](https://vielzeug.dev/orbit/) · [@vielzeug/forge](https://vielzeug.dev/forge/)
|
|
17
|
-
|
|
18
|
-
</details>
|
|
19
|
-
|
|
20
|
-
`@vielzeug/refine` is part of Vielzeug and ships as a TypeScript package with ESM+CJS output.
|
|
3
|
+
> Accessible, themeable web components built on ore
|
|
21
4
|
|
|
22
5
|
## Installation
|
|
23
6
|
|
|
@@ -36,6 +19,9 @@ import '@vielzeug/refine/tokens.css';
|
|
|
36
19
|
// Optional browser-default reset
|
|
37
20
|
import '@vielzeug/refine/styles/preflight.css';
|
|
38
21
|
|
|
22
|
+
// Optional typed DOM tag map
|
|
23
|
+
import type {} from '@vielzeug/refine/frameworks/elements';
|
|
24
|
+
|
|
39
25
|
// Register only the components you use
|
|
40
26
|
import '@vielzeug/refine/button';
|
|
41
27
|
import '@vielzeug/refine/card';
|
|
@@ -76,6 +62,7 @@ import '@vielzeug/refine/time-picker';
|
|
|
76
62
|
- [Overview](https://vielzeug.dev/refine/)
|
|
77
63
|
- [Usage Guide](https://vielzeug.dev/refine/usage)
|
|
78
64
|
- [API Reference](https://vielzeug.dev/refine/api)
|
|
65
|
+
- [Migration Guide](https://vielzeug.dev/refine/migration)
|
|
79
66
|
|
|
80
67
|
## License
|
|
81
68
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-item.cjs","names":[],"sources":["../src/disclosure/accordion-item/accordion-item.ts"],"sourcesContent":["import { define, getHost, html, inject, onMounted, prop, ref, useEmit, watchEffect } from '@vielzeug/ore';\n\nimport type { ComponentSize, SurfaceVariant } from '../../types';\n\nimport '../../content/icon/icon';\nimport { elementDirection } from '../../core/direction';\nimport { disablableBundle } from '../../shared';\nimport { coarsePointerMixin } from '../../styles';\nimport { ACCORDION_CTX } from '../accordion/accordion';\nimport styles from './accordion-item.css?inline';\n\n/** Accordion item component properties */\n\nexport type OreAccordionItemEvents = {\n collapse: { expanded: boolean; item: HTMLElement };\n expand: { expanded: boolean; item: HTMLElement };\n};\n\nexport type OreAccordionItemProps = {\n /** Disable accordion item interaction */\n disabled?: boolean;\n /** Whether the item is expanded/open */\n expanded?: boolean;\n /** Item size */\n size?: ComponentSize;\n /** Visual style variant */\n variant?: SurfaceVariant;\n};\n\n/**\n * An individual accordion item with expand/collapse functionality using native details/summary.\n *\n * @element ore-accordion-item\n *\n * @attr {boolean} expanded - Whether the item is expanded/open\n * @attr {boolean} disabled - Disable accordion item interaction\n * @attr {string} size - Item size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'glass' | 'frost'\n *\n * @fires expand - Emitted when item expands. detail: { expanded: boolean; item: HTMLElement }\n * @fires collapse - Emitted when item collapses. detail: { expanded: boolean; item: HTMLElement }\n *\n * @slot prefix - Content before the title (e.g., icons)\n * @slot title - Main accordion item title\n * @slot subtitle - Optional subtitle text\n * @slot suffix - Content after the title (e.g., badges)\n * @slot - Accordion item content (shown when expanded)\n *\n * @cssprop --accordion-item-bg - Background color\n * @cssprop --accordion-item-hover-bg - Background color on hover\n * @cssprop --accordion-item-border-color - Border color\n * @cssprop --accordion-item-title-color - Title text color\n * @cssprop --accordion-item-subtitle-color - Subtitle text color\n * @cssprop --accordion-item-body-color - Body text color\n * @cssprop --accordion-item-radius - Border radius\n * @cssprop --accordion-item-transition - Transition duration\n * @cssprop --accordion-item-title - Title font size\n * @cssprop --accordion-item-subtitle-size - Subtitle font size\n * @cssprop --accordion-item-body - Body font size\n * @cssprop --accordion-item-details-padding - Summary/header padding\n * @cssprop --accordion-item-summary-padding - Content padding\n *\n * @part item - Item root element.\n * @part summary - Summary trigger row.\n * @part header - Header container.\n * @part title - Title text element.\n * @part subtitle - Subtitle text element.\n * @part content - Content container.\n * @example\n * ```html\n * <ore-accordion-item><span slot=\"title\">Click to expand</span><p>Content</p></ore-accordion-item>\n * <ore-accordion-item expanded variant=\"bordered\"><span slot=\"title\">Title</span><p>Content</p></ore-accordion-item>\n * ```\n */\n\nexport const ACCORDION_ITEM_TAG = 'ore-accordion-item' as const;\ndefine<OreAccordionItemProps>(ACCORDION_ITEM_TAG, {\n props: {\n ...disablableBundle,\n expanded: prop.bool(false),\n size: prop.string<ComponentSize>(),\n variant: prop.string<SurfaceVariant>(),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreAccordionItemEvents>();\n const watch = watchEffect;\n\n // Inherit size/variant from a parent ore-accordion when present.\n const accordionCtx = inject(ACCORDION_CTX);\n\n if (accordionCtx) {\n watch(() => {\n const size = accordionCtx.size.value;\n const variant = accordionCtx.variant.value;\n\n if (size !== undefined) el.setAttribute('size', size);\n\n if (variant !== undefined) el.setAttribute('variant', variant);\n });\n }\n\n const titleId = 'accordion-item-title';\n const detailsRef = ref<HTMLDetailsElement>();\n const summaryRef = ref<HTMLElement>();\n let isAnimating = false;\n\n const openItem = () => {\n const details = detailsRef.value;\n\n if (!details || details.open) return;\n\n details.classList.add('opening');\n details.open = true;\n\n el.toggleAttribute('expanded', true);\n emit('expand', { expanded: true, item: el });\n\n requestAnimationFrame(() => {\n const inner = details.querySelector<HTMLElement>('.content-inner');\n\n if (!inner) {\n details.classList.remove('opening');\n isAnimating = false;\n\n return;\n }\n\n const onDone = () => {\n details.classList.remove('opening');\n isAnimating = false;\n };\n const transitions = inner.getAnimations?.().filter((a) => a instanceof CSSTransition) ?? [];\n\n if (transitions.length > 0) {\n Promise.allSettled(transitions.map((a) => a.finished)).then(onDone);\n } else {\n onDone();\n }\n });\n };\n\n const closeItem = () => {\n const details = detailsRef.value;\n\n if (!details?.open || isAnimating) return;\n\n isAnimating = true;\n\n details.classList.add('closing');\n\n const inner = details.querySelector<HTMLElement>('.content-inner');\n const onDone = () => {\n details.classList.remove('closing');\n details.open = false;\n el.toggleAttribute('expanded', false);\n emit('collapse', { expanded: false, item: el });\n isAnimating = false;\n };\n\n if (inner) {\n const transitions = inner.getAnimations?.().filter((a) => a instanceof CSSTransition) ?? [];\n\n if (transitions.length > 0) {\n Promise.allSettled(transitions.map((a) => a.finished)).then(onDone);\n } else {\n onDone();\n }\n } else {\n onDone();\n }\n };\n\n let touchHandled = false;\n let touchStartY = 0;\n\n const toggleDetails = () => {\n if (props.disabled.value) return;\n\n const details = detailsRef.value;\n\n if (!details) return;\n\n if (details.open) {\n closeItem();\n } else {\n openItem();\n }\n };\n\n const handleSummaryTouchStart = (e: TouchEvent) => {\n touchStartY = e.touches[0]?.clientY ?? 0;\n };\n\n const handleSummaryTouchEnd = (e: TouchEvent) => {\n const dy = Math.abs((e.changedTouches[0]?.clientY ?? 0) - touchStartY);\n\n if (dy > 10) return;\n\n e.preventDefault();\n touchHandled = true;\n toggleDetails();\n };\n\n const handleSummaryClick = (e: Event) => {\n if (touchHandled) {\n touchHandled = false;\n e.preventDefault();\n\n return;\n }\n\n e.preventDefault();\n toggleDetails();\n };\n\n const handleToggle = () => {\n // Only fires for programmatic open/close (e.g. from accordion parent)\n const isOpen = detailsRef.value?.open ?? false;\n const wasExpanded = Boolean(props.expanded.value);\n\n if (isOpen && !wasExpanded) {\n el.toggleAttribute('expanded', true);\n emit('expand', { expanded: true, item: el });\n } else if (!isOpen && wasExpanded) {\n el.toggleAttribute('expanded', false);\n emit('collapse', { expanded: false, item: el });\n }\n };\n\n onMounted(() => {\n const details = detailsRef.value;\n const summary = summaryRef.value;\n\n if (!details || !summary) return;\n\n // Detect RTL so plain CSS classes can target it (see `elementDirection` for the\n // ancestor-dir / computed-style resolution rule).\n const checkRTL = () => {\n details.classList.toggle('rtl', elementDirection(el) === 'rtl');\n };\n\n // Check initially\n checkRTL();\n\n // Re-check when `dir` changes on this element's own ancestor chain (not the whole\n // document — `elementDirection` only ever looks at `el`'s ancestors, so that's all we\n // need to watch).\n const observer = new MutationObserver((mutations) => {\n const dirChanged = mutations.some((m) => m.attributeName === 'dir');\n\n if (dirChanged) {\n checkRTL();\n }\n });\n\n for (let ancestor: Element | null = el; ancestor; ancestor = ancestor.parentElement) {\n observer.observe(ancestor, { attributeFilter: ['dir'], attributes: true });\n }\n\n details.addEventListener('toggle', handleToggle);\n summary.addEventListener('touchstart', handleSummaryTouchStart, { passive: true });\n summary.addEventListener('touchend', handleSummaryTouchEnd, { passive: false });\n summary.addEventListener('click', handleSummaryClick);\n\n return () => {\n observer.disconnect();\n details.removeEventListener('toggle', handleToggle);\n summary.removeEventListener('touchstart', handleSummaryTouchStart);\n summary.removeEventListener('touchend', handleSummaryTouchEnd);\n summary.removeEventListener('click', handleSummaryClick);\n };\n });\n\n return html`\n <details part=\"item\" ?open=\"${props.expanded}\" ref=\"${detailsRef}\">\n <summary\n part=\"summary\"\n aria-expanded=\"${() => String(props.expanded.value)}\"\n aria-disabled=\"${() => (props.disabled.value ? 'true' : 'false')}\"\n tabindex=\"${() => (props.disabled.value ? '-1' : null)}\"\n ref=\"${summaryRef}\">\n <slot name=\"prefix\"></slot>\n <div class=\"header-content\" part=\"header\">\n <span class=\"title\" part=\"title\" id=\"${titleId}\">\n <slot name=\"title\"></slot>\n </span>\n <span class=\"subtitle\" part=\"subtitle\">\n <slot name=\"subtitle\"></slot>\n </span>\n </div>\n <slot name=\"suffix\"></slot>\n <ore-icon class=\"chevron\" name=\"chevron-down\" size=\"20\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </summary>\n <div class=\"content-wrapper\" part=\"content\" role=\"region\" aria-labelledby=\"${titleId}\">\n <div class=\"content-inner\">\n <slot></slot>\n </div>\n </div>\n </details>\n `;\n },\n\n styles: [coarsePointerMixin, styles],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accordion-item.cjs","names":[],"sources":["../src/disclosure/accordion-item/accordion-item.ts"],"sourcesContent":["import { define, getHost, html, inject, onMounted, prop, ref, useEmit, watchEffect } from '@vielzeug/ore';\n\nimport type { ComponentSize, VisualVariant } from '../../types';\n\nimport '../../content/icon/icon';\nimport { elementDirection } from '../../core/direction';\nimport { disablableBundle } from '../../shared';\nimport { coarsePointerMixin } from '../../styles';\nimport { ACCORDION_CTX } from '../accordion/accordion';\nimport styles from './accordion-item.css?inline';\n\n/** Accordion item component properties */\n\nexport type OreAccordionItemEvents = {\n collapse: { expanded: boolean; item: HTMLElement };\n expand: { expanded: boolean; item: HTMLElement };\n};\n\nexport type OreAccordionItemProps = {\n /** Disable accordion item interaction */\n disabled?: boolean;\n /** Whether the item is expanded/open */\n expanded?: boolean;\n /** Item size */\n size?: ComponentSize;\n /** Visual style variant */\n variant?: VisualVariant;\n};\n\n/**\n * An individual accordion item with expand/collapse functionality using native details/summary.\n *\n * @element ore-accordion-item\n *\n * @attr {boolean} expanded - Whether the item is expanded/open\n * @attr {boolean} disabled - Disable accordion item interaction\n * @attr {string} size - Item size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'frost'\n *\n * @fires expand - Emitted when item expands. detail: { expanded: boolean; item: HTMLElement }\n * @fires collapse - Emitted when item collapses. detail: { expanded: boolean; item: HTMLElement }\n *\n * @slot prefix - Content before the title (e.g., icons)\n * @slot title - Main accordion item title\n * @slot subtitle - Optional subtitle text\n * @slot suffix - Content after the title (e.g., badges)\n * @slot - Accordion item content (shown when expanded)\n *\n * @cssprop --accordion-item-bg - Background color\n * @cssprop --accordion-item-hover-bg - Background color on hover\n * @cssprop --accordion-item-border-color - Border color\n * @cssprop --accordion-item-title-color - Title text color\n * @cssprop --accordion-item-subtitle-color - Subtitle text color\n * @cssprop --accordion-item-body-color - Body text color\n * @cssprop --accordion-item-radius - Border radius\n * @cssprop --accordion-item-transition - Transition duration\n * @cssprop --accordion-item-title - Title font size\n * @cssprop --accordion-item-subtitle-size - Subtitle font size\n * @cssprop --accordion-item-body - Body font size\n * @cssprop --accordion-item-details-padding - Summary/header padding\n * @cssprop --accordion-item-summary-padding - Content padding\n *\n * @part item - Item root element.\n * @part summary - Summary trigger row.\n * @part header - Header container.\n * @part title - Title text element.\n * @part subtitle - Subtitle text element.\n * @part content - Content container.\n * @example\n * ```html\n * <ore-accordion-item><span slot=\"title\">Click to expand</span><p>Content</p></ore-accordion-item>\n * <ore-accordion-item expanded variant=\"bordered\"><span slot=\"title\">Title</span><p>Content</p></ore-accordion-item>\n * ```\n */\n\nexport const ACCORDION_ITEM_TAG = 'ore-accordion-item' as const;\ndefine<OreAccordionItemProps>(ACCORDION_ITEM_TAG, {\n props: {\n ...disablableBundle,\n expanded: prop.bool(false),\n size: prop.string<ComponentSize>(),\n variant: prop.string<VisualVariant>(),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreAccordionItemEvents>();\n const watch = watchEffect;\n\n // Inherit size/variant from a parent ore-accordion when present.\n const accordionCtx = inject(ACCORDION_CTX);\n\n if (accordionCtx) {\n watch(() => {\n const size = accordionCtx.size.value;\n const variant = accordionCtx.variant.value;\n\n if (size !== undefined) el.setAttribute('size', size);\n\n if (variant !== undefined) el.setAttribute('variant', variant);\n });\n }\n\n const titleId = 'accordion-item-title';\n const detailsRef = ref<HTMLDetailsElement>();\n const summaryRef = ref<HTMLElement>();\n let isAnimating = false;\n\n const openItem = () => {\n const details = detailsRef.value;\n\n if (!details || details.open) return;\n\n details.classList.add('opening');\n details.open = true;\n\n el.toggleAttribute('expanded', true);\n emit('expand', { expanded: true, item: el });\n\n requestAnimationFrame(() => {\n const inner = details.querySelector<HTMLElement>('.content-inner');\n\n if (!inner) {\n details.classList.remove('opening');\n isAnimating = false;\n\n return;\n }\n\n const onDone = () => {\n details.classList.remove('opening');\n isAnimating = false;\n };\n const transitions = inner.getAnimations?.().filter((a) => a instanceof CSSTransition) ?? [];\n\n if (transitions.length > 0) {\n Promise.allSettled(transitions.map((a) => a.finished)).then(onDone);\n } else {\n onDone();\n }\n });\n };\n\n const closeItem = () => {\n const details = detailsRef.value;\n\n if (!details?.open || isAnimating) return;\n\n isAnimating = true;\n\n details.classList.add('closing');\n\n const inner = details.querySelector<HTMLElement>('.content-inner');\n const onDone = () => {\n details.classList.remove('closing');\n details.open = false;\n el.toggleAttribute('expanded', false);\n emit('collapse', { expanded: false, item: el });\n isAnimating = false;\n };\n\n if (inner) {\n const transitions = inner.getAnimations?.().filter((a) => a instanceof CSSTransition) ?? [];\n\n if (transitions.length > 0) {\n Promise.allSettled(transitions.map((a) => a.finished)).then(onDone);\n } else {\n onDone();\n }\n } else {\n onDone();\n }\n };\n\n let touchHandled = false;\n let touchStartY = 0;\n\n const toggleDetails = () => {\n if (props.disabled.value) return;\n\n const details = detailsRef.value;\n\n if (!details) return;\n\n if (details.open) {\n closeItem();\n } else {\n openItem();\n }\n };\n\n const handleSummaryTouchStart = (e: TouchEvent) => {\n touchStartY = e.touches[0]?.clientY ?? 0;\n };\n\n const handleSummaryTouchEnd = (e: TouchEvent) => {\n const dy = Math.abs((e.changedTouches[0]?.clientY ?? 0) - touchStartY);\n\n if (dy > 10) return;\n\n e.preventDefault();\n touchHandled = true;\n toggleDetails();\n };\n\n const handleSummaryClick = (e: Event) => {\n if (touchHandled) {\n touchHandled = false;\n e.preventDefault();\n\n return;\n }\n\n e.preventDefault();\n toggleDetails();\n };\n\n const handleToggle = () => {\n // Only fires for programmatic open/close (e.g. from accordion parent)\n const isOpen = detailsRef.value?.open ?? false;\n const wasExpanded = Boolean(props.expanded.value);\n\n if (isOpen && !wasExpanded) {\n el.toggleAttribute('expanded', true);\n emit('expand', { expanded: true, item: el });\n } else if (!isOpen && wasExpanded) {\n el.toggleAttribute('expanded', false);\n emit('collapse', { expanded: false, item: el });\n }\n };\n\n onMounted(() => {\n const details = detailsRef.value;\n const summary = summaryRef.value;\n\n if (!details || !summary) return;\n\n // Detect RTL so plain CSS classes can target it (see `elementDirection` for the\n // ancestor-dir / computed-style resolution rule).\n const checkRTL = () => {\n details.classList.toggle('rtl', elementDirection(el) === 'rtl');\n };\n\n // Check initially\n checkRTL();\n\n // Re-check when `dir` changes on this element's own ancestor chain (not the whole\n // document — `elementDirection` only ever looks at `el`'s ancestors, so that's all we\n // need to watch).\n const observer = new MutationObserver((mutations) => {\n const dirChanged = mutations.some((m) => m.attributeName === 'dir');\n\n if (dirChanged) {\n checkRTL();\n }\n });\n\n for (let ancestor: Element | null = el; ancestor; ancestor = ancestor.parentElement) {\n observer.observe(ancestor, { attributeFilter: ['dir'], attributes: true });\n }\n\n details.addEventListener('toggle', handleToggle);\n summary.addEventListener('touchstart', handleSummaryTouchStart, { passive: true });\n summary.addEventListener('touchend', handleSummaryTouchEnd, { passive: false });\n summary.addEventListener('click', handleSummaryClick);\n\n return () => {\n observer.disconnect();\n details.removeEventListener('toggle', handleToggle);\n summary.removeEventListener('touchstart', handleSummaryTouchStart);\n summary.removeEventListener('touchend', handleSummaryTouchEnd);\n summary.removeEventListener('click', handleSummaryClick);\n };\n });\n\n return html`\n <details part=\"item\" ?open=\"${props.expanded}\" ref=\"${detailsRef}\">\n <summary\n part=\"summary\"\n aria-expanded=\"${() => String(props.expanded.value)}\"\n aria-disabled=\"${() => (props.disabled.value ? 'true' : 'false')}\"\n tabindex=\"${() => (props.disabled.value ? '-1' : null)}\"\n ref=\"${summaryRef}\">\n <slot name=\"prefix\"></slot>\n <div class=\"header-content\" part=\"header\">\n <span class=\"title\" part=\"title\" id=\"${titleId}\">\n <slot name=\"title\"></slot>\n </span>\n <span class=\"subtitle\" part=\"subtitle\">\n <slot name=\"subtitle\"></slot>\n </span>\n </div>\n <slot name=\"suffix\"></slot>\n <ore-icon class=\"chevron\" name=\"chevron-down\" size=\"20\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </summary>\n <div class=\"content-wrapper\" part=\"content\" role=\"region\" aria-labelledby=\"${titleId}\">\n <div class=\"content-inner\">\n <slot></slot>\n </div>\n </div>\n </details>\n `;\n },\n\n styles: [coarsePointerMixin, styles],\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-item.js","names":[],"sources":["../src/disclosure/accordion-item/accordion-item.ts"],"sourcesContent":["import { define, getHost, html, inject, onMounted, prop, ref, useEmit, watchEffect } from '@vielzeug/ore';\n\nimport type { ComponentSize, SurfaceVariant } from '../../types';\n\nimport '../../content/icon/icon';\nimport { elementDirection } from '../../core/direction';\nimport { disablableBundle } from '../../shared';\nimport { coarsePointerMixin } from '../../styles';\nimport { ACCORDION_CTX } from '../accordion/accordion';\nimport styles from './accordion-item.css?inline';\n\n/** Accordion item component properties */\n\nexport type OreAccordionItemEvents = {\n collapse: { expanded: boolean; item: HTMLElement };\n expand: { expanded: boolean; item: HTMLElement };\n};\n\nexport type OreAccordionItemProps = {\n /** Disable accordion item interaction */\n disabled?: boolean;\n /** Whether the item is expanded/open */\n expanded?: boolean;\n /** Item size */\n size?: ComponentSize;\n /** Visual style variant */\n variant?: SurfaceVariant;\n};\n\n/**\n * An individual accordion item with expand/collapse functionality using native details/summary.\n *\n * @element ore-accordion-item\n *\n * @attr {boolean} expanded - Whether the item is expanded/open\n * @attr {boolean} disabled - Disable accordion item interaction\n * @attr {string} size - Item size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'glass' | 'frost'\n *\n * @fires expand - Emitted when item expands. detail: { expanded: boolean; item: HTMLElement }\n * @fires collapse - Emitted when item collapses. detail: { expanded: boolean; item: HTMLElement }\n *\n * @slot prefix - Content before the title (e.g., icons)\n * @slot title - Main accordion item title\n * @slot subtitle - Optional subtitle text\n * @slot suffix - Content after the title (e.g., badges)\n * @slot - Accordion item content (shown when expanded)\n *\n * @cssprop --accordion-item-bg - Background color\n * @cssprop --accordion-item-hover-bg - Background color on hover\n * @cssprop --accordion-item-border-color - Border color\n * @cssprop --accordion-item-title-color - Title text color\n * @cssprop --accordion-item-subtitle-color - Subtitle text color\n * @cssprop --accordion-item-body-color - Body text color\n * @cssprop --accordion-item-radius - Border radius\n * @cssprop --accordion-item-transition - Transition duration\n * @cssprop --accordion-item-title - Title font size\n * @cssprop --accordion-item-subtitle-size - Subtitle font size\n * @cssprop --accordion-item-body - Body font size\n * @cssprop --accordion-item-details-padding - Summary/header padding\n * @cssprop --accordion-item-summary-padding - Content padding\n *\n * @part item - Item root element.\n * @part summary - Summary trigger row.\n * @part header - Header container.\n * @part title - Title text element.\n * @part subtitle - Subtitle text element.\n * @part content - Content container.\n * @example\n * ```html\n * <ore-accordion-item><span slot=\"title\">Click to expand</span><p>Content</p></ore-accordion-item>\n * <ore-accordion-item expanded variant=\"bordered\"><span slot=\"title\">Title</span><p>Content</p></ore-accordion-item>\n * ```\n */\n\nexport const ACCORDION_ITEM_TAG = 'ore-accordion-item' as const;\ndefine<OreAccordionItemProps>(ACCORDION_ITEM_TAG, {\n props: {\n ...disablableBundle,\n expanded: prop.bool(false),\n size: prop.string<ComponentSize>(),\n variant: prop.string<SurfaceVariant>(),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreAccordionItemEvents>();\n const watch = watchEffect;\n\n // Inherit size/variant from a parent ore-accordion when present.\n const accordionCtx = inject(ACCORDION_CTX);\n\n if (accordionCtx) {\n watch(() => {\n const size = accordionCtx.size.value;\n const variant = accordionCtx.variant.value;\n\n if (size !== undefined) el.setAttribute('size', size);\n\n if (variant !== undefined) el.setAttribute('variant', variant);\n });\n }\n\n const titleId = 'accordion-item-title';\n const detailsRef = ref<HTMLDetailsElement>();\n const summaryRef = ref<HTMLElement>();\n let isAnimating = false;\n\n const openItem = () => {\n const details = detailsRef.value;\n\n if (!details || details.open) return;\n\n details.classList.add('opening');\n details.open = true;\n\n el.toggleAttribute('expanded', true);\n emit('expand', { expanded: true, item: el });\n\n requestAnimationFrame(() => {\n const inner = details.querySelector<HTMLElement>('.content-inner');\n\n if (!inner) {\n details.classList.remove('opening');\n isAnimating = false;\n\n return;\n }\n\n const onDone = () => {\n details.classList.remove('opening');\n isAnimating = false;\n };\n const transitions = inner.getAnimations?.().filter((a) => a instanceof CSSTransition) ?? [];\n\n if (transitions.length > 0) {\n Promise.allSettled(transitions.map((a) => a.finished)).then(onDone);\n } else {\n onDone();\n }\n });\n };\n\n const closeItem = () => {\n const details = detailsRef.value;\n\n if (!details?.open || isAnimating) return;\n\n isAnimating = true;\n\n details.classList.add('closing');\n\n const inner = details.querySelector<HTMLElement>('.content-inner');\n const onDone = () => {\n details.classList.remove('closing');\n details.open = false;\n el.toggleAttribute('expanded', false);\n emit('collapse', { expanded: false, item: el });\n isAnimating = false;\n };\n\n if (inner) {\n const transitions = inner.getAnimations?.().filter((a) => a instanceof CSSTransition) ?? [];\n\n if (transitions.length > 0) {\n Promise.allSettled(transitions.map((a) => a.finished)).then(onDone);\n } else {\n onDone();\n }\n } else {\n onDone();\n }\n };\n\n let touchHandled = false;\n let touchStartY = 0;\n\n const toggleDetails = () => {\n if (props.disabled.value) return;\n\n const details = detailsRef.value;\n\n if (!details) return;\n\n if (details.open) {\n closeItem();\n } else {\n openItem();\n }\n };\n\n const handleSummaryTouchStart = (e: TouchEvent) => {\n touchStartY = e.touches[0]?.clientY ?? 0;\n };\n\n const handleSummaryTouchEnd = (e: TouchEvent) => {\n const dy = Math.abs((e.changedTouches[0]?.clientY ?? 0) - touchStartY);\n\n if (dy > 10) return;\n\n e.preventDefault();\n touchHandled = true;\n toggleDetails();\n };\n\n const handleSummaryClick = (e: Event) => {\n if (touchHandled) {\n touchHandled = false;\n e.preventDefault();\n\n return;\n }\n\n e.preventDefault();\n toggleDetails();\n };\n\n const handleToggle = () => {\n // Only fires for programmatic open/close (e.g. from accordion parent)\n const isOpen = detailsRef.value?.open ?? false;\n const wasExpanded = Boolean(props.expanded.value);\n\n if (isOpen && !wasExpanded) {\n el.toggleAttribute('expanded', true);\n emit('expand', { expanded: true, item: el });\n } else if (!isOpen && wasExpanded) {\n el.toggleAttribute('expanded', false);\n emit('collapse', { expanded: false, item: el });\n }\n };\n\n onMounted(() => {\n const details = detailsRef.value;\n const summary = summaryRef.value;\n\n if (!details || !summary) return;\n\n // Detect RTL so plain CSS classes can target it (see `elementDirection` for the\n // ancestor-dir / computed-style resolution rule).\n const checkRTL = () => {\n details.classList.toggle('rtl', elementDirection(el) === 'rtl');\n };\n\n // Check initially\n checkRTL();\n\n // Re-check when `dir` changes on this element's own ancestor chain (not the whole\n // document — `elementDirection` only ever looks at `el`'s ancestors, so that's all we\n // need to watch).\n const observer = new MutationObserver((mutations) => {\n const dirChanged = mutations.some((m) => m.attributeName === 'dir');\n\n if (dirChanged) {\n checkRTL();\n }\n });\n\n for (let ancestor: Element | null = el; ancestor; ancestor = ancestor.parentElement) {\n observer.observe(ancestor, { attributeFilter: ['dir'], attributes: true });\n }\n\n details.addEventListener('toggle', handleToggle);\n summary.addEventListener('touchstart', handleSummaryTouchStart, { passive: true });\n summary.addEventListener('touchend', handleSummaryTouchEnd, { passive: false });\n summary.addEventListener('click', handleSummaryClick);\n\n return () => {\n observer.disconnect();\n details.removeEventListener('toggle', handleToggle);\n summary.removeEventListener('touchstart', handleSummaryTouchStart);\n summary.removeEventListener('touchend', handleSummaryTouchEnd);\n summary.removeEventListener('click', handleSummaryClick);\n };\n });\n\n return html`\n <details part=\"item\" ?open=\"${props.expanded}\" ref=\"${detailsRef}\">\n <summary\n part=\"summary\"\n aria-expanded=\"${() => String(props.expanded.value)}\"\n aria-disabled=\"${() => (props.disabled.value ? 'true' : 'false')}\"\n tabindex=\"${() => (props.disabled.value ? '-1' : null)}\"\n ref=\"${summaryRef}\">\n <slot name=\"prefix\"></slot>\n <div class=\"header-content\" part=\"header\">\n <span class=\"title\" part=\"title\" id=\"${titleId}\">\n <slot name=\"title\"></slot>\n </span>\n <span class=\"subtitle\" part=\"subtitle\">\n <slot name=\"subtitle\"></slot>\n </span>\n </div>\n <slot name=\"suffix\"></slot>\n <ore-icon class=\"chevron\" name=\"chevron-down\" size=\"20\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </summary>\n <div class=\"content-wrapper\" part=\"content\" role=\"region\" aria-labelledby=\"${titleId}\">\n <div class=\"content-inner\">\n <slot></slot>\n </div>\n </div>\n </details>\n `;\n },\n\n styles: [coarsePointerMixin, styles],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accordion-item.js","names":[],"sources":["../src/disclosure/accordion-item/accordion-item.ts"],"sourcesContent":["import { define, getHost, html, inject, onMounted, prop, ref, useEmit, watchEffect } from '@vielzeug/ore';\n\nimport type { ComponentSize, VisualVariant } from '../../types';\n\nimport '../../content/icon/icon';\nimport { elementDirection } from '../../core/direction';\nimport { disablableBundle } from '../../shared';\nimport { coarsePointerMixin } from '../../styles';\nimport { ACCORDION_CTX } from '../accordion/accordion';\nimport styles from './accordion-item.css?inline';\n\n/** Accordion item component properties */\n\nexport type OreAccordionItemEvents = {\n collapse: { expanded: boolean; item: HTMLElement };\n expand: { expanded: boolean; item: HTMLElement };\n};\n\nexport type OreAccordionItemProps = {\n /** Disable accordion item interaction */\n disabled?: boolean;\n /** Whether the item is expanded/open */\n expanded?: boolean;\n /** Item size */\n size?: ComponentSize;\n /** Visual style variant */\n variant?: VisualVariant;\n};\n\n/**\n * An individual accordion item with expand/collapse functionality using native details/summary.\n *\n * @element ore-accordion-item\n *\n * @attr {boolean} expanded - Whether the item is expanded/open\n * @attr {boolean} disabled - Disable accordion item interaction\n * @attr {string} size - Item size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'frost'\n *\n * @fires expand - Emitted when item expands. detail: { expanded: boolean; item: HTMLElement }\n * @fires collapse - Emitted when item collapses. detail: { expanded: boolean; item: HTMLElement }\n *\n * @slot prefix - Content before the title (e.g., icons)\n * @slot title - Main accordion item title\n * @slot subtitle - Optional subtitle text\n * @slot suffix - Content after the title (e.g., badges)\n * @slot - Accordion item content (shown when expanded)\n *\n * @cssprop --accordion-item-bg - Background color\n * @cssprop --accordion-item-hover-bg - Background color on hover\n * @cssprop --accordion-item-border-color - Border color\n * @cssprop --accordion-item-title-color - Title text color\n * @cssprop --accordion-item-subtitle-color - Subtitle text color\n * @cssprop --accordion-item-body-color - Body text color\n * @cssprop --accordion-item-radius - Border radius\n * @cssprop --accordion-item-transition - Transition duration\n * @cssprop --accordion-item-title - Title font size\n * @cssprop --accordion-item-subtitle-size - Subtitle font size\n * @cssprop --accordion-item-body - Body font size\n * @cssprop --accordion-item-details-padding - Summary/header padding\n * @cssprop --accordion-item-summary-padding - Content padding\n *\n * @part item - Item root element.\n * @part summary - Summary trigger row.\n * @part header - Header container.\n * @part title - Title text element.\n * @part subtitle - Subtitle text element.\n * @part content - Content container.\n * @example\n * ```html\n * <ore-accordion-item><span slot=\"title\">Click to expand</span><p>Content</p></ore-accordion-item>\n * <ore-accordion-item expanded variant=\"bordered\"><span slot=\"title\">Title</span><p>Content</p></ore-accordion-item>\n * ```\n */\n\nexport const ACCORDION_ITEM_TAG = 'ore-accordion-item' as const;\ndefine<OreAccordionItemProps>(ACCORDION_ITEM_TAG, {\n props: {\n ...disablableBundle,\n expanded: prop.bool(false),\n size: prop.string<ComponentSize>(),\n variant: prop.string<VisualVariant>(),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreAccordionItemEvents>();\n const watch = watchEffect;\n\n // Inherit size/variant from a parent ore-accordion when present.\n const accordionCtx = inject(ACCORDION_CTX);\n\n if (accordionCtx) {\n watch(() => {\n const size = accordionCtx.size.value;\n const variant = accordionCtx.variant.value;\n\n if (size !== undefined) el.setAttribute('size', size);\n\n if (variant !== undefined) el.setAttribute('variant', variant);\n });\n }\n\n const titleId = 'accordion-item-title';\n const detailsRef = ref<HTMLDetailsElement>();\n const summaryRef = ref<HTMLElement>();\n let isAnimating = false;\n\n const openItem = () => {\n const details = detailsRef.value;\n\n if (!details || details.open) return;\n\n details.classList.add('opening');\n details.open = true;\n\n el.toggleAttribute('expanded', true);\n emit('expand', { expanded: true, item: el });\n\n requestAnimationFrame(() => {\n const inner = details.querySelector<HTMLElement>('.content-inner');\n\n if (!inner) {\n details.classList.remove('opening');\n isAnimating = false;\n\n return;\n }\n\n const onDone = () => {\n details.classList.remove('opening');\n isAnimating = false;\n };\n const transitions = inner.getAnimations?.().filter((a) => a instanceof CSSTransition) ?? [];\n\n if (transitions.length > 0) {\n Promise.allSettled(transitions.map((a) => a.finished)).then(onDone);\n } else {\n onDone();\n }\n });\n };\n\n const closeItem = () => {\n const details = detailsRef.value;\n\n if (!details?.open || isAnimating) return;\n\n isAnimating = true;\n\n details.classList.add('closing');\n\n const inner = details.querySelector<HTMLElement>('.content-inner');\n const onDone = () => {\n details.classList.remove('closing');\n details.open = false;\n el.toggleAttribute('expanded', false);\n emit('collapse', { expanded: false, item: el });\n isAnimating = false;\n };\n\n if (inner) {\n const transitions = inner.getAnimations?.().filter((a) => a instanceof CSSTransition) ?? [];\n\n if (transitions.length > 0) {\n Promise.allSettled(transitions.map((a) => a.finished)).then(onDone);\n } else {\n onDone();\n }\n } else {\n onDone();\n }\n };\n\n let touchHandled = false;\n let touchStartY = 0;\n\n const toggleDetails = () => {\n if (props.disabled.value) return;\n\n const details = detailsRef.value;\n\n if (!details) return;\n\n if (details.open) {\n closeItem();\n } else {\n openItem();\n }\n };\n\n const handleSummaryTouchStart = (e: TouchEvent) => {\n touchStartY = e.touches[0]?.clientY ?? 0;\n };\n\n const handleSummaryTouchEnd = (e: TouchEvent) => {\n const dy = Math.abs((e.changedTouches[0]?.clientY ?? 0) - touchStartY);\n\n if (dy > 10) return;\n\n e.preventDefault();\n touchHandled = true;\n toggleDetails();\n };\n\n const handleSummaryClick = (e: Event) => {\n if (touchHandled) {\n touchHandled = false;\n e.preventDefault();\n\n return;\n }\n\n e.preventDefault();\n toggleDetails();\n };\n\n const handleToggle = () => {\n // Only fires for programmatic open/close (e.g. from accordion parent)\n const isOpen = detailsRef.value?.open ?? false;\n const wasExpanded = Boolean(props.expanded.value);\n\n if (isOpen && !wasExpanded) {\n el.toggleAttribute('expanded', true);\n emit('expand', { expanded: true, item: el });\n } else if (!isOpen && wasExpanded) {\n el.toggleAttribute('expanded', false);\n emit('collapse', { expanded: false, item: el });\n }\n };\n\n onMounted(() => {\n const details = detailsRef.value;\n const summary = summaryRef.value;\n\n if (!details || !summary) return;\n\n // Detect RTL so plain CSS classes can target it (see `elementDirection` for the\n // ancestor-dir / computed-style resolution rule).\n const checkRTL = () => {\n details.classList.toggle('rtl', elementDirection(el) === 'rtl');\n };\n\n // Check initially\n checkRTL();\n\n // Re-check when `dir` changes on this element's own ancestor chain (not the whole\n // document — `elementDirection` only ever looks at `el`'s ancestors, so that's all we\n // need to watch).\n const observer = new MutationObserver((mutations) => {\n const dirChanged = mutations.some((m) => m.attributeName === 'dir');\n\n if (dirChanged) {\n checkRTL();\n }\n });\n\n for (let ancestor: Element | null = el; ancestor; ancestor = ancestor.parentElement) {\n observer.observe(ancestor, { attributeFilter: ['dir'], attributes: true });\n }\n\n details.addEventListener('toggle', handleToggle);\n summary.addEventListener('touchstart', handleSummaryTouchStart, { passive: true });\n summary.addEventListener('touchend', handleSummaryTouchEnd, { passive: false });\n summary.addEventListener('click', handleSummaryClick);\n\n return () => {\n observer.disconnect();\n details.removeEventListener('toggle', handleToggle);\n summary.removeEventListener('touchstart', handleSummaryTouchStart);\n summary.removeEventListener('touchend', handleSummaryTouchEnd);\n summary.removeEventListener('click', handleSummaryClick);\n };\n });\n\n return html`\n <details part=\"item\" ?open=\"${props.expanded}\" ref=\"${detailsRef}\">\n <summary\n part=\"summary\"\n aria-expanded=\"${() => String(props.expanded.value)}\"\n aria-disabled=\"${() => (props.disabled.value ? 'true' : 'false')}\"\n tabindex=\"${() => (props.disabled.value ? '-1' : null)}\"\n ref=\"${summaryRef}\">\n <slot name=\"prefix\"></slot>\n <div class=\"header-content\" part=\"header\">\n <span class=\"title\" part=\"title\" id=\"${titleId}\">\n <slot name=\"title\"></slot>\n </span>\n <span class=\"subtitle\" part=\"subtitle\">\n <slot name=\"subtitle\"></slot>\n </span>\n </div>\n <slot name=\"suffix\"></slot>\n <ore-icon class=\"chevron\" name=\"chevron-down\" size=\"20\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n </summary>\n <div class=\"content-wrapper\" part=\"content\" role=\"region\" aria-labelledby=\"${titleId}\">\n <div class=\"content-inner\">\n <slot></slot>\n </div>\n </div>\n </details>\n `;\n },\n\n styles: [coarsePointerMixin, styles],\n});\n"],"mappings":"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"}
|
package/dist/accordion.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.cjs","names":[],"sources":["../src/disclosure/accordion/accordion.ts"],"sourcesContent":["import { bind, createContext, define, getHost, html, onCleanup, prop, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport type { ComponentSize,
|
|
1
|
+
{"version":3,"file":"accordion.cjs","names":[],"sources":["../src/disclosure/accordion/accordion.ts"],"sourcesContent":["import { bind, createContext, define, getHost, html, onCleanup, prop, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport type { ComponentSize, VisualVariant } from '../../types';\nimport styles from './accordion.css?inline';\n\n/** Context provided by ore-accordion to its ore-accordion-item children. */\nexport type AccordionContext = {\n selectionMode: Readable<'single' | 'multiple' | undefined>;\n size: Readable<ComponentSize | undefined>;\n variant: Readable<VisualVariant | undefined>;\n};\n/** Injection key for the accordion context. */\nexport const ACCORDION_CTX = createContext<AccordionContext>('AccordionContext');\n\n/** Accordion component properties */\n\nexport type OreAccordionEvents = {\n change: { expandedItem: HTMLElement };\n};\n\nexport type OreAccordionProps = {\n /** Selection mode (single = only one opens, multiple = multiple can be open) */\n selectionMode?: 'single' | 'multiple';\n /** Size for all items (propagated via context) */\n size?: ComponentSize;\n /** Visual variant for all items (propagated via context) */\n variant?: VisualVariant;\n};\n\n/**\n * A container for accordion items with single or multiple selection modes.\n *\n * @element ore-accordion\n * @element ore-accordion-item - Child element for each collapsible panel\n *\n * @attr {string} selection-mode - Selection mode: 'single' | 'multiple'\n * @attr {string} size - Size for all items: 'sm' | 'md' | 'lg' (propagated to children)\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'text' | 'frost' (propagated to children)\n *\n * @fires expand - Emitted when an item expands. detail: { expanded: boolean; item: HTMLElement }\n * @fires change - Emitted when selection changes (single mode). detail: { expandedItem: HTMLElement }\n *\n * @slot - `ore-accordion-item` elements\n *\n * @cssprop --accordion-bg - Container background color (solid/flat/frost variants)\n * @cssprop --accordion-border-color - Container border color (solid/flat variants)\n * @cssprop --accordion-divider-color - Divider color between items (text variant)\n * @cssprop --accordion-shadow - Container box shadow\n * @example\n * ```html\n * <ore-accordion selection-mode=\"single\">\n * <ore-accordion-item>\n * <span slot=\"title\">What is Refine?</span>\n * <p>Refine is a headless web component library.</p>\n * </ore-accordion-item>\n * <ore-accordion-item>\n * <span slot=\"title\">How do I install it?</span>\n * <p>Run <code>npm install @vielzeug/refine</code>.</p>\n * </ore-accordion-item>\n * </ore-accordion>\n * <ore-accordion variant=\"bordered\" selection-mode=\"multiple\">\n * <ore-accordion-item expanded><span slot=\"title\">Open by default</span><p>Content</p></ore-accordion-item>\n * </ore-accordion>\n * ```\n */\n\nexport const ACCORDION_TAG = 'ore-accordion' as const;\ndefine<OreAccordionProps>(ACCORDION_TAG, {\n props: {\n selectionMode: prop.string<'single' | 'multiple'>(),\n size: prop.string<ComponentSize>(),\n variant: prop.string<VisualVariant>(),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreAccordionEvents>();\n\n const handleSelectionMode = (expandedItem: HTMLElement) => {\n if (props.selectionMode.value !== 'single') return;\n\n el.querySelectorAll(':scope > ore-accordion-item[expanded]').forEach((item) => {\n if (item !== expandedItem && item.hasAttribute('expanded')) {\n item.removeAttribute('expanded');\n }\n });\n\n emit('change', { expandedItem });\n };\n\n const getAccordionItems = () => {\n return [...el.querySelectorAll<HTMLElement>(':scope > ore-accordion-item:not([disabled])')];\n };\n\n const getSummaryElements = () => {\n return getAccordionItems()\n .map((item) => item.shadowRoot?.querySelector<HTMLElement>('summary'))\n .filter(Boolean) as HTMLElement[];\n };\n\n const listControl = createListControl({\n getItems: () => getAccordionItems(),\n loop: true,\n onNavigate: ({ index }) => {\n const summaries = getSummaryElements();\n\n summaries[index]?.focus();\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n provide(ACCORDION_CTX, {\n selectionMode: computed(() => props.selectionMode.value),\n size: props.size,\n variant: props.variant,\n });\n\n // Group-level event and keyboard handling for WAI-ARIA Accordion pattern\n\n bind({\n on: {\n expand: (event: Event) => {\n const eventTarget = event.composedPath().find((node): node is HTMLElement => node instanceof HTMLElement);\n const expandedItem = (event as CustomEvent<{ item?: HTMLElement }>).detail?.item ?? eventTarget;\n\n if (expandedItem?.localName !== 'ore-accordion-item') return;\n\n // Guard: only respond to accordion-items that belong to THIS accordion instance\n if (expandedItem.closest('ore-accordion') !== el) return;\n\n handleSelectionMode(expandedItem);\n },\n keydown: (evt: KeyboardEvent) => {\n const summaries = getSummaryElements();\n\n if (!summaries.length) return;\n\n const activeSummary = evt\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'summary');\n const focused = activeSummary ? summaries.indexOf(activeSummary) : -1;\n\n if (focused === -1) return; // focus is not on a summary — let native handling proceed\n\n listControl.set(focused);\n listControl.handleKeydown(evt);\n },\n },\n });\n\n return html`\n <slot></slot>\n `;\n },\n\n styles: [styles],\n});\n"],"mappings":"oPAaA,IAAa,GAAA,EAAgB,EAAA,cAAA,CAAgC,kBAAkB,EAsDlE,EAAgB,iBAC7B,EAAA,EAAA,OAAA,CAA0B,EAAe,CACvC,MAAO,CACL,cAAe,EAAA,KAAK,OAA8B,EAClD,KAAM,EAAA,KAAK,OAAsB,EACjC,QAAS,EAAA,KAAK,OAAsB,CACtC,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAK,EAAA,QAAA,CAAQ,EACb,GAAA,EAAO,EAAA,QAAA,CAA4B,EAEnC,EAAuB,GAA8B,CACrD,EAAM,cAAc,QAAU,WAElC,EAAG,iBAAiB,uCAAuC,CAAC,CAAC,QAAS,GAAS,CACzE,IAAS,GAAgB,EAAK,aAAa,UAAU,GACvD,EAAK,gBAAgB,UAAU,CAEnC,CAAC,EAED,EAAK,SAAU,CAAE,cAAa,CAAC,EACjC,EAEM,MACG,CAAC,GAAG,EAAG,iBAA8B,6CAA6C,CAAC,EAGtF,MACG,EAAkB,CAAC,CACvB,IAAK,GAAS,EAAK,YAAY,cAA2B,SAAS,CAAC,CAAC,CACrE,OAAO,OAAO,EAGb,EAAc,EAAA,kBAAkB,CACpC,aAAgB,EAAkB,EAClC,KAAM,GACN,YAAa,CAAE,WAAY,CAGzB,EAAA,CAAA,CAAU,EAAM,EAAE,MAAM,CAC1B,EACA,OAAQ,EAAA,gBAAgB,EAAA,SAAS,CACnC,CAAC,EAyCD,OAvCA,EAAA,EAAA,QAAA,CAAQ,EAAe,CACrB,eAAA,EAAe,EAAA,SAAA,KAAe,EAAM,cAAc,KAAK,EACvD,KAAM,EAAM,KACZ,QAAS,EAAM,OACjB,CAAC,GAID,EAAA,EAAA,KAAA,CAAK,CACH,GAAI,CACF,OAAS,GAAiB,CACxB,IAAM,EAAc,EAAM,aAAa,CAAC,CAAC,KAAM,GAA8B,aAAgB,WAAW,EAClG,EAAgB,EAA8C,QAAQ,MAAQ,EAEhF,GAAc,YAAc,sBAG5B,EAAa,QAAQ,eAAe,IAAM,GAE9C,EAAoB,CAAY,CAClC,EACA,QAAU,GAAuB,CAC/B,IAAM,EAAY,EAAmB,EAErC,GAAI,CAAC,EAAU,OAAQ,OAEvB,IAAM,EAAgB,EACnB,aAAa,CAAC,CACd,KAAM,GAA8B,aAAgB,aAAe,EAAK,YAAc,SAAS,EAC5F,EAAU,EAAgB,EAAU,QAAQ,CAAa,EAAI,GAE/D,IAAY,KAEhB,EAAY,IAAI,CAAO,EACvB,EAAY,cAAc,CAAG,EAC/B,CACF,CACF,CAAC,EAEM,EAAA,IAAI;;KAGb,EAEA,OAAQ,CAAC,EAAA,OAAM,CACjB,CAAC"}
|
package/dist/accordion.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.js","names":[],"sources":["../src/disclosure/accordion/accordion.ts"],"sourcesContent":["import { bind, createContext, define, getHost, html, onCleanup, prop, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport type { ComponentSize,
|
|
1
|
+
{"version":3,"file":"accordion.js","names":[],"sources":["../src/disclosure/accordion/accordion.ts"],"sourcesContent":["import { bind, createContext, define, getHost, html, onCleanup, prop, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport type { ComponentSize, VisualVariant } from '../../types';\nimport styles from './accordion.css?inline';\n\n/** Context provided by ore-accordion to its ore-accordion-item children. */\nexport type AccordionContext = {\n selectionMode: Readable<'single' | 'multiple' | undefined>;\n size: Readable<ComponentSize | undefined>;\n variant: Readable<VisualVariant | undefined>;\n};\n/** Injection key for the accordion context. */\nexport const ACCORDION_CTX = createContext<AccordionContext>('AccordionContext');\n\n/** Accordion component properties */\n\nexport type OreAccordionEvents = {\n change: { expandedItem: HTMLElement };\n};\n\nexport type OreAccordionProps = {\n /** Selection mode (single = only one opens, multiple = multiple can be open) */\n selectionMode?: 'single' | 'multiple';\n /** Size for all items (propagated via context) */\n size?: ComponentSize;\n /** Visual variant for all items (propagated via context) */\n variant?: VisualVariant;\n};\n\n/**\n * A container for accordion items with single or multiple selection modes.\n *\n * @element ore-accordion\n * @element ore-accordion-item - Child element for each collapsible panel\n *\n * @attr {string} selection-mode - Selection mode: 'single' | 'multiple'\n * @attr {string} size - Size for all items: 'sm' | 'md' | 'lg' (propagated to children)\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'text' | 'frost' (propagated to children)\n *\n * @fires expand - Emitted when an item expands. detail: { expanded: boolean; item: HTMLElement }\n * @fires change - Emitted when selection changes (single mode). detail: { expandedItem: HTMLElement }\n *\n * @slot - `ore-accordion-item` elements\n *\n * @cssprop --accordion-bg - Container background color (solid/flat/frost variants)\n * @cssprop --accordion-border-color - Container border color (solid/flat variants)\n * @cssprop --accordion-divider-color - Divider color between items (text variant)\n * @cssprop --accordion-shadow - Container box shadow\n * @example\n * ```html\n * <ore-accordion selection-mode=\"single\">\n * <ore-accordion-item>\n * <span slot=\"title\">What is Refine?</span>\n * <p>Refine is a headless web component library.</p>\n * </ore-accordion-item>\n * <ore-accordion-item>\n * <span slot=\"title\">How do I install it?</span>\n * <p>Run <code>npm install @vielzeug/refine</code>.</p>\n * </ore-accordion-item>\n * </ore-accordion>\n * <ore-accordion variant=\"bordered\" selection-mode=\"multiple\">\n * <ore-accordion-item expanded><span slot=\"title\">Open by default</span><p>Content</p></ore-accordion-item>\n * </ore-accordion>\n * ```\n */\n\nexport const ACCORDION_TAG = 'ore-accordion' as const;\ndefine<OreAccordionProps>(ACCORDION_TAG, {\n props: {\n selectionMode: prop.string<'single' | 'multiple'>(),\n size: prop.string<ComponentSize>(),\n variant: prop.string<VisualVariant>(),\n },\n\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreAccordionEvents>();\n\n const handleSelectionMode = (expandedItem: HTMLElement) => {\n if (props.selectionMode.value !== 'single') return;\n\n el.querySelectorAll(':scope > ore-accordion-item[expanded]').forEach((item) => {\n if (item !== expandedItem && item.hasAttribute('expanded')) {\n item.removeAttribute('expanded');\n }\n });\n\n emit('change', { expandedItem });\n };\n\n const getAccordionItems = () => {\n return [...el.querySelectorAll<HTMLElement>(':scope > ore-accordion-item:not([disabled])')];\n };\n\n const getSummaryElements = () => {\n return getAccordionItems()\n .map((item) => item.shadowRoot?.querySelector<HTMLElement>('summary'))\n .filter(Boolean) as HTMLElement[];\n };\n\n const listControl = createListControl({\n getItems: () => getAccordionItems(),\n loop: true,\n onNavigate: ({ index }) => {\n const summaries = getSummaryElements();\n\n summaries[index]?.focus();\n },\n signal: lifecycleSignal(onCleanup),\n });\n\n provide(ACCORDION_CTX, {\n selectionMode: computed(() => props.selectionMode.value),\n size: props.size,\n variant: props.variant,\n });\n\n // Group-level event and keyboard handling for WAI-ARIA Accordion pattern\n\n bind({\n on: {\n expand: (event: Event) => {\n const eventTarget = event.composedPath().find((node): node is HTMLElement => node instanceof HTMLElement);\n const expandedItem = (event as CustomEvent<{ item?: HTMLElement }>).detail?.item ?? eventTarget;\n\n if (expandedItem?.localName !== 'ore-accordion-item') return;\n\n // Guard: only respond to accordion-items that belong to THIS accordion instance\n if (expandedItem.closest('ore-accordion') !== el) return;\n\n handleSelectionMode(expandedItem);\n },\n keydown: (evt: KeyboardEvent) => {\n const summaries = getSummaryElements();\n\n if (!summaries.length) return;\n\n const activeSummary = evt\n .composedPath()\n .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'summary');\n const focused = activeSummary ? summaries.indexOf(activeSummary) : -1;\n\n if (focused === -1) return; // focus is not on a summary — let native handling proceed\n\n listControl.set(focused);\n listControl.handleKeydown(evt);\n },\n },\n });\n\n return html`\n <slot></slot>\n `;\n },\n\n styles: [styles],\n});\n"],"mappings":";;;;;;AAaA,IAAa,IAAgB,EAAgC,kBAAkB,GAsDlE,IAAgB;AAC7B,EAA0B,GAAe;CACvC,OAAO;EACL,eAAe,EAAK,OAA8B;EAClD,MAAM,EAAK,OAAsB;EACjC,SAAS,EAAK,OAAsB;CACtC;CAEA,MAAM,GAAO;EACX,IAAM,IAAK,EAAQ,GACb,IAAO,EAA4B,GAEnC,KAAuB,MAA8B;GACrD,EAAM,cAAc,UAAU,aAElC,EAAG,iBAAiB,uCAAuC,CAAC,CAAC,SAAS,MAAS;IAC7E,AAAI,MAAS,KAAgB,EAAK,aAAa,UAAU,KACvD,EAAK,gBAAgB,UAAU;GAEnC,CAAC,GAED,EAAK,UAAU,EAAE,gBAAa,CAAC;EACjC,GAEM,UACG,CAAC,GAAG,EAAG,iBAA8B,6CAA6C,CAAC,GAGtF,UACG,EAAkB,CAAC,CACvB,KAAK,MAAS,EAAK,YAAY,cAA2B,SAAS,CAAC,CAAC,CACrE,OAAO,OAAO,GAGb,IAAc,EAAkB;GACpC,gBAAgB,EAAkB;GAClC,MAAM;GACN,aAAa,EAAE,eAAY;IAGzB,EAAA,CAAA,CAAU,EAAM,EAAE,MAAM;GAC1B;GACA,QAAQ,EAAgB,CAAS;EACnC,CAAC;EAyCD,OAvCA,EAAQ,GAAe;GACrB,eAAe,QAAe,EAAM,cAAc,KAAK;GACvD,MAAM,EAAM;GACZ,SAAS,EAAM;EACjB,CAAC,GAID,EAAK,EACH,IAAI;GACF,SAAS,MAAiB;IACxB,IAAM,IAAc,EAAM,aAAa,CAAC,CAAC,MAAM,MAA8B,aAAgB,WAAW,GAClG,IAAgB,EAA8C,QAAQ,QAAQ;IAEhF,GAAc,cAAc,wBAG5B,EAAa,QAAQ,eAAe,MAAM,KAE9C,EAAoB,CAAY;GAClC;GACA,UAAU,MAAuB;IAC/B,IAAM,IAAY,EAAmB;IAErC,IAAI,CAAC,EAAU,QAAQ;IAEvB,IAAM,IAAgB,EACnB,aAAa,CAAC,CACd,MAAM,MAA8B,aAAgB,eAAe,EAAK,cAAc,SAAS,GAC5F,IAAU,IAAgB,EAAU,QAAQ,CAAa,IAAI;IAE/D,MAAY,OAEhB,EAAY,IAAI,CAAO,GACvB,EAAY,cAAc,CAAG;GAC/B;EACF,EACF,CAAC,GAEM,CAAI;;;CAGb;CAEA,QAAQ,CAAC,CAAM;AACjB,CAAC"}
|
package/dist/async.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./feedback/async/async.css.cjs");let n=require("@vielzeug/ore"),r=require("@vielzeug/ripple");var i=`ore-async`;(0,n.define)(i,{props:{"empty-description":n.prop.string(),"empty-label":n.prop.string(`No content yet`),"error-description":n.prop.string(),"error-label":n.prop.string(`Something went wrong`),retryable:n.prop.bool(!1),status:n.prop.oneOf([`idle`,`loading`,`empty`,`error`,`success`],`success`)},setup(e){let t=(0,n.useEmit)(),i=(0,r.signal)(!1),a=(0,r.signal)(!1),o=(0,r.signal)(!1);(0,n.bind)({attr:{
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./feedback/async/async.css.cjs");let n=require("@vielzeug/ore"),r=require("@vielzeug/ripple");var i=`ore-async`;(0,n.define)(i,{props:{"empty-description":n.prop.string(),"empty-label":n.prop.string(`No content yet`),"error-description":n.prop.string(),"error-label":n.prop.string(`Something went wrong`),retryable:n.prop.bool(!1),status:n.prop.oneOf([`idle`,`loading`,`empty`,`error`,`success`],`success`)},setup(e){let t=(0,n.useEmit)(),i=(0,r.signal)(!1),a=(0,r.signal)(!1),o=(0,r.signal)(!1);(0,n.bind)({attr:{"aria-busy":()=>e.status.value===`loading`?`true`:`false`,"aria-label":()=>e.status.value===`loading`?`Loading…`:null,"aria-live":()=>e.status.value===`error`?`assertive`:`polite`,status:e.status}});let s=(e,t)=>()=>t?.value?n.html`
|
|
2
2
|
<p class="${e}">${t}</p>
|
|
3
3
|
`:``;return n.html`
|
|
4
4
|
<div class="region region-idle" role="presentation"></div>
|
package/dist/async.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.cjs","names":[],"sources":["../src/feedback/async/async.ts"],"sourcesContent":["import { bind, define, html, prop, useEmit, when } from '@vielzeug/ore';\nimport { type Readable, signal } from '@vielzeug/ripple';\n\nimport { reducedMotionMixin } from '../../styles';\nimport componentStyles from './async.css?inline';\n\nexport type AsyncStatus = 'idle' | 'loading' | 'empty' | 'error' | 'success';\n\nexport type OreAsyncEvents = {\n retry: undefined;\n};\n\nexport type OreAsyncProps = {\n 'empty-description'?: string;\n 'empty-label'?: string;\n 'error-description'?: string;\n 'error-label'?: string;\n retryable?: boolean;\n status?: AsyncStatus;\n};\n\n/**\n * A container for handling asynchronous states (loading, empty, error, success).\n * Simplifies data fetching UI by providing consistent fallbacks.\n *\n * @element ore-async\n *\n * @attr {string} status - current state: 'idle' | 'loading' | 'empty' | 'error' | 'success' (default: 'success')\n * @attr {boolean} retryable - show retry button in error state (default: false)\n * @attr {string} empty-label - title for empty state (default: 'No content yet')\n * @attr {string} empty-description - optional text for empty state\n * @attr {string} error-label - title for error state (default: 'Something went wrong')\n * @attr {string} error-description - optional text for error state\n *\n * @fires retry - Emitted when the retry button is clicked (no detail payload)\n *\n * @slot - default content shown in 'success' state\n * @slot loading - custom loading UI (overrides default skeletons)\n * @slot empty - custom empty UI (overrides default icon/label)\n * @slot error - custom error UI (overrides default icon/label)\n *\n * @cssprop --async-color - Text/icon color used by default async state content\n * @cssprop --async-gap - Vertical spacing between icon, title, description, and actions\n * @cssprop --async-icon-size - Icon size for built-in loading/empty/error visuals\n * @example\n * ```html\n * <!-- Success state: default slot is shown -->\n * <ore-async status=\"success\">\n * <ul><li>Item one</li><li>Item two</li></ul>\n * </ore-async>\n *\n * <!-- Loading state: shows skeleton placeholders -->\n * <ore-async status=\"loading\"></ore-async>\n *\n * <!-- Empty state with custom message -->\n * <ore-async status=\"empty\" empty-label=\"No results\" empty-description=\"Try adjusting your filters.\"></ore-async>\n *\n * <!-- Error state with retry button -->\n * <ore-async status=\"error\" retryable error-label=\"Failed to load\" error-description=\"Check your connection.\"></ore-async>\n * ```\n */\nexport const ASYNC_TAG = 'ore-async' as const;\ndefine<OreAsyncProps>(ASYNC_TAG, {\n props: {\n 'empty-description': prop.string(),\n 'empty-label': prop.string('No content yet'),\n 'error-description': prop.string(),\n 'error-label': prop.string('Something went wrong'),\n retryable: prop.bool(false),\n status: prop.oneOf(['idle', 'loading', 'empty', 'error', 'success'] as const, 'success'),\n },\n setup(props) {\n const emit = useEmit<OreAsyncEvents>();\n\n const hasLoadingSlot = signal(false);\n const hasEmptySlot = signal(false);\n const hasErrorSlot = signal(false);\n\n // Reflect status onto the host so CSS can show/hide each region.\n // ARIA attributes are driven reactively by bind().\n bind({\n attr: {\n
|
|
1
|
+
{"version":3,"file":"async.cjs","names":[],"sources":["../src/feedback/async/async.ts"],"sourcesContent":["import { bind, define, html, prop, useEmit, when } from '@vielzeug/ore';\nimport { type Readable, signal } from '@vielzeug/ripple';\n\nimport { reducedMotionMixin } from '../../styles';\nimport componentStyles from './async.css?inline';\n\nexport type AsyncStatus = 'idle' | 'loading' | 'empty' | 'error' | 'success';\n\nexport type OreAsyncEvents = {\n retry: undefined;\n};\n\nexport type OreAsyncProps = {\n 'empty-description'?: string;\n 'empty-label'?: string;\n 'error-description'?: string;\n 'error-label'?: string;\n retryable?: boolean;\n status?: AsyncStatus;\n};\n\n/**\n * A container for handling asynchronous states (loading, empty, error, success).\n * Simplifies data fetching UI by providing consistent fallbacks.\n *\n * @element ore-async\n *\n * @attr {string} status - current state: 'idle' | 'loading' | 'empty' | 'error' | 'success' (default: 'success')\n * @attr {boolean} retryable - show retry button in error state (default: false)\n * @attr {string} empty-label - title for empty state (default: 'No content yet')\n * @attr {string} empty-description - optional text for empty state\n * @attr {string} error-label - title for error state (default: 'Something went wrong')\n * @attr {string} error-description - optional text for error state\n *\n * @fires retry - Emitted when the retry button is clicked (no detail payload)\n *\n * @slot - default content shown in 'success' state\n * @slot loading - custom loading UI (overrides default skeletons)\n * @slot empty - custom empty UI (overrides default icon/label)\n * @slot error - custom error UI (overrides default icon/label)\n *\n * @cssprop --async-color - Text/icon color used by default async state content\n * @cssprop --async-gap - Vertical spacing between icon, title, description, and actions\n * @cssprop --async-icon-size - Icon size for built-in loading/empty/error visuals\n * @example\n * ```html\n * <!-- Success state: default slot is shown -->\n * <ore-async status=\"success\">\n * <ul><li>Item one</li><li>Item two</li></ul>\n * </ore-async>\n *\n * <!-- Loading state: shows skeleton placeholders -->\n * <ore-async status=\"loading\"></ore-async>\n *\n * <!-- Empty state with custom message -->\n * <ore-async status=\"empty\" empty-label=\"No results\" empty-description=\"Try adjusting your filters.\"></ore-async>\n *\n * <!-- Error state with retry button -->\n * <ore-async status=\"error\" retryable error-label=\"Failed to load\" error-description=\"Check your connection.\"></ore-async>\n * ```\n */\nexport const ASYNC_TAG = 'ore-async' as const;\ndefine<OreAsyncProps>(ASYNC_TAG, {\n props: {\n 'empty-description': prop.string(),\n 'empty-label': prop.string('No content yet'),\n 'error-description': prop.string(),\n 'error-label': prop.string('Something went wrong'),\n retryable: prop.bool(false),\n status: prop.oneOf(['idle', 'loading', 'empty', 'error', 'success'] as const, 'success'),\n },\n setup(props) {\n const emit = useEmit<OreAsyncEvents>();\n\n const hasLoadingSlot = signal(false);\n const hasEmptySlot = signal(false);\n const hasErrorSlot = signal(false);\n\n // Reflect status onto the host so CSS can show/hide each region.\n // ARIA attributes are driven reactively by bind().\n bind({\n attr: {\n 'aria-busy': () => (props.status.value === 'loading' ? 'true' : 'false'),\n 'aria-label': () => (props.status.value === 'loading' ? 'Loading…' : null),\n 'aria-live': () => (props.status.value === 'error' ? 'assertive' : 'polite'),\n status: props.status,\n },\n });\n\n const renderText = (className: 'title' | 'description', text: Readable<string | undefined> | undefined) => () =>\n text?.value\n ? html`\n <p class=\"${className}\">${text}</p>\n `\n : '';\n\n // All four regions are always in the shadow DOM — CSS on :host([status=\"…\"])\n // toggles their visibility. This means:\n // - No DOM churn on status transitions (no teardown/rebuild of slot elements).\n // - Live regions are always present, so screen readers announce correctly.\n // - focus is never lost across status changes.\n return html`\n <div class=\"region region-idle\" role=\"presentation\"></div>\n\n <div class=\"region region-loading\" role=\"status\">\n <slot\n name=\"loading\"\n @slotchange=${(e: Event) => {\n hasLoadingSlot.value = (e.target as HTMLSlotElement).assignedNodes().length > 0;\n }}></slot>\n ${when(\n hasLoadingSlot,\n () => html``,\n () => html`\n <div class=\"loading-default\" aria-hidden=\"true\">\n <ore-skeleton variant=\"text\" lines=\"1\" width=\"40%\"></ore-skeleton>\n <ore-skeleton variant=\"text\" lines=\"3\" width=\"100%\"></ore-skeleton>\n <ore-skeleton variant=\"text\" lines=\"1\" width=\"60%\"></ore-skeleton>\n </div>\n `,\n )}\n </div>\n\n <div class=\"region region-empty\">\n <slot\n name=\"empty\"\n @slotchange=${(e: Event) => {\n hasEmptySlot.value = (e.target as HTMLSlotElement).assignedNodes().length > 0;\n }}></slot>\n ${when(\n hasEmptySlot,\n () => html``,\n () => html`\n <div class=\"empty-state\" role=\"status\">\n <div class=\"icon\">\n <ore-icon name=\"package\" size=\"100%\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n </div>\n ${renderText('title', props['empty-label'])} ${renderText('description', props['empty-description'])}\n </div>\n `,\n )}\n </div>\n\n <div class=\"region region-error\">\n <slot\n name=\"error\"\n @slotchange=${(e: Event) => {\n hasErrorSlot.value = (e.target as HTMLSlotElement).assignedNodes().length > 0;\n }}></slot>\n ${when(\n hasErrorSlot,\n () => html``,\n () => html`\n <div class=\"error-state\" role=\"alert\">\n <div class=\"icon\">\n <ore-icon name=\"triangle-alert\" size=\"100%\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n </div>\n ${renderText('title', props['error-label'])} ${renderText('description', props['error-description'])}\n ${when(\n () => Boolean(props.retryable.value),\n () => html`\n <button class=\"retry-btn\" type=\"button\" @click=${() => emit('retry')}>\n <ore-icon name=\"refresh-cw\" size=\"1em\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n Try again\n </button>\n `,\n )}\n </div>\n `,\n )}\n </div>\n\n <div class=\"region region-success\" role=\"presentation\">\n <slot></slot>\n </div>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"qOA6DA,IAAa,EAAY,aACzB,EAAA,EAAA,OAAA,CAAsB,EAAW,CAC/B,MAAO,CACL,oBAAqB,EAAA,KAAK,OAAO,EACjC,cAAe,EAAA,KAAK,OAAO,gBAAgB,EAC3C,oBAAqB,EAAA,KAAK,OAAO,EACjC,cAAe,EAAA,KAAK,OAAO,sBAAsB,EACjD,UAAW,EAAA,KAAK,KAAK,EAAK,EAC1B,OAAQ,EAAA,KAAK,MAAM,CAAC,OAAQ,UAAW,QAAS,QAAS,SAAS,EAAY,SAAS,CACzF,EACA,MAAM,EAAO,CACX,IAAM,GAAA,EAAO,EAAA,QAAA,CAAwB,EAE/B,GAAA,EAAiB,EAAA,OAAA,CAAO,EAAK,EAC7B,GAAA,EAAe,EAAA,OAAA,CAAO,EAAK,EAC3B,GAAA,EAAe,EAAA,OAAA,CAAO,EAAK,GAIjC,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,gBAAoB,EAAM,OAAO,QAAU,UAAY,OAAS,QAChE,iBAAqB,EAAM,OAAO,QAAU,UAAY,WAAa,KACrE,gBAAoB,EAAM,OAAO,QAAU,QAAU,YAAc,SACnE,OAAQ,EAAM,MAChB,CACF,CAAC,EAED,IAAM,GAAc,EAAoC,QACtD,GAAM,MACF,EAAA,IAAI;wBACU,EAAU,IAAI,EAAK;YAEjC,GAON,MAAO,GAAA,IAAI;;;;;;wBAMU,GAAa,CAC1B,EAAe,MAAS,EAAE,OAA2B,cAAc,CAAC,CAAC,OAAS,CAChF,EAAE;WACF,EAAA,EAAA,KAAA,CACA,MACM,EAAA,IAAI,OACJ,EAAA,IAAI;;;;;;WAOZ,EAAE;;;;;;wBAMe,GAAa,CAC1B,EAAa,MAAS,EAAE,OAA2B,cAAc,CAAC,CAAC,OAAS,CAC9E,EAAE;WACF,EAAA,EAAA,KAAA,CACA,MACM,EAAA,IAAI,OACJ,EAAA,IAAI;;;;;gBAKJ,EAAW,QAAS,EAAM,cAAc,EAAE,GAAG,EAAW,cAAe,EAAM,oBAAoB,EAAE;;WAG3G,EAAE;;;;;;wBAMe,GAAa,CAC1B,EAAa,MAAS,EAAE,OAA2B,cAAc,CAAC,CAAC,OAAS,CAC9E,EAAE;WACF,EAAA,EAAA,KAAA,CACA,MACM,EAAA,IAAI,OACJ,EAAA,IAAI;;;;;gBAKJ,EAAW,QAAS,EAAM,cAAc,EAAE,GAAG,EAAW,cAAe,EAAM,oBAAoB,EAAE;iBACnG,EAAA,EAAA,KAAA,KACM,EAAQ,EAAM,UAAU,UACxB,EAAA,IAAI;uEAC+C,EAAK,OAAO,EAAE;;;;iBAKzE,EAAE;;WAGR,EAAE;;;;;;KAOR,EACA,OAAQ,CAAC,EAAA,mBAAoB,EAAA,OAAe,CAC9C,CAAC"}
|
package/dist/async.js
CHANGED
|
@@ -22,9 +22,9 @@ r(l, {
|
|
|
22
22
|
setup(e) {
|
|
23
23
|
let t = o(), r = c(!1), a = c(!1), l = c(!1);
|
|
24
24
|
n({ attr: {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
"aria-busy": () => e.status.value === "loading" ? "true" : "false",
|
|
26
|
+
"aria-label": () => e.status.value === "loading" ? "Loading…" : null,
|
|
27
|
+
"aria-live": () => e.status.value === "error" ? "assertive" : "polite",
|
|
28
28
|
status: e.status
|
|
29
29
|
} });
|
|
30
30
|
let u = (e, t) => () => t?.value ? i`
|
package/dist/async.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.js","names":[],"sources":["../src/feedback/async/async.ts"],"sourcesContent":["import { bind, define, html, prop, useEmit, when } from '@vielzeug/ore';\nimport { type Readable, signal } from '@vielzeug/ripple';\n\nimport { reducedMotionMixin } from '../../styles';\nimport componentStyles from './async.css?inline';\n\nexport type AsyncStatus = 'idle' | 'loading' | 'empty' | 'error' | 'success';\n\nexport type OreAsyncEvents = {\n retry: undefined;\n};\n\nexport type OreAsyncProps = {\n 'empty-description'?: string;\n 'empty-label'?: string;\n 'error-description'?: string;\n 'error-label'?: string;\n retryable?: boolean;\n status?: AsyncStatus;\n};\n\n/**\n * A container for handling asynchronous states (loading, empty, error, success).\n * Simplifies data fetching UI by providing consistent fallbacks.\n *\n * @element ore-async\n *\n * @attr {string} status - current state: 'idle' | 'loading' | 'empty' | 'error' | 'success' (default: 'success')\n * @attr {boolean} retryable - show retry button in error state (default: false)\n * @attr {string} empty-label - title for empty state (default: 'No content yet')\n * @attr {string} empty-description - optional text for empty state\n * @attr {string} error-label - title for error state (default: 'Something went wrong')\n * @attr {string} error-description - optional text for error state\n *\n * @fires retry - Emitted when the retry button is clicked (no detail payload)\n *\n * @slot - default content shown in 'success' state\n * @slot loading - custom loading UI (overrides default skeletons)\n * @slot empty - custom empty UI (overrides default icon/label)\n * @slot error - custom error UI (overrides default icon/label)\n *\n * @cssprop --async-color - Text/icon color used by default async state content\n * @cssprop --async-gap - Vertical spacing between icon, title, description, and actions\n * @cssprop --async-icon-size - Icon size for built-in loading/empty/error visuals\n * @example\n * ```html\n * <!-- Success state: default slot is shown -->\n * <ore-async status=\"success\">\n * <ul><li>Item one</li><li>Item two</li></ul>\n * </ore-async>\n *\n * <!-- Loading state: shows skeleton placeholders -->\n * <ore-async status=\"loading\"></ore-async>\n *\n * <!-- Empty state with custom message -->\n * <ore-async status=\"empty\" empty-label=\"No results\" empty-description=\"Try adjusting your filters.\"></ore-async>\n *\n * <!-- Error state with retry button -->\n * <ore-async status=\"error\" retryable error-label=\"Failed to load\" error-description=\"Check your connection.\"></ore-async>\n * ```\n */\nexport const ASYNC_TAG = 'ore-async' as const;\ndefine<OreAsyncProps>(ASYNC_TAG, {\n props: {\n 'empty-description': prop.string(),\n 'empty-label': prop.string('No content yet'),\n 'error-description': prop.string(),\n 'error-label': prop.string('Something went wrong'),\n retryable: prop.bool(false),\n status: prop.oneOf(['idle', 'loading', 'empty', 'error', 'success'] as const, 'success'),\n },\n setup(props) {\n const emit = useEmit<OreAsyncEvents>();\n\n const hasLoadingSlot = signal(false);\n const hasEmptySlot = signal(false);\n const hasErrorSlot = signal(false);\n\n // Reflect status onto the host so CSS can show/hide each region.\n // ARIA attributes are driven reactively by bind().\n bind({\n attr: {\n
|
|
1
|
+
{"version":3,"file":"async.js","names":[],"sources":["../src/feedback/async/async.ts"],"sourcesContent":["import { bind, define, html, prop, useEmit, when } from '@vielzeug/ore';\nimport { type Readable, signal } from '@vielzeug/ripple';\n\nimport { reducedMotionMixin } from '../../styles';\nimport componentStyles from './async.css?inline';\n\nexport type AsyncStatus = 'idle' | 'loading' | 'empty' | 'error' | 'success';\n\nexport type OreAsyncEvents = {\n retry: undefined;\n};\n\nexport type OreAsyncProps = {\n 'empty-description'?: string;\n 'empty-label'?: string;\n 'error-description'?: string;\n 'error-label'?: string;\n retryable?: boolean;\n status?: AsyncStatus;\n};\n\n/**\n * A container for handling asynchronous states (loading, empty, error, success).\n * Simplifies data fetching UI by providing consistent fallbacks.\n *\n * @element ore-async\n *\n * @attr {string} status - current state: 'idle' | 'loading' | 'empty' | 'error' | 'success' (default: 'success')\n * @attr {boolean} retryable - show retry button in error state (default: false)\n * @attr {string} empty-label - title for empty state (default: 'No content yet')\n * @attr {string} empty-description - optional text for empty state\n * @attr {string} error-label - title for error state (default: 'Something went wrong')\n * @attr {string} error-description - optional text for error state\n *\n * @fires retry - Emitted when the retry button is clicked (no detail payload)\n *\n * @slot - default content shown in 'success' state\n * @slot loading - custom loading UI (overrides default skeletons)\n * @slot empty - custom empty UI (overrides default icon/label)\n * @slot error - custom error UI (overrides default icon/label)\n *\n * @cssprop --async-color - Text/icon color used by default async state content\n * @cssprop --async-gap - Vertical spacing between icon, title, description, and actions\n * @cssprop --async-icon-size - Icon size for built-in loading/empty/error visuals\n * @example\n * ```html\n * <!-- Success state: default slot is shown -->\n * <ore-async status=\"success\">\n * <ul><li>Item one</li><li>Item two</li></ul>\n * </ore-async>\n *\n * <!-- Loading state: shows skeleton placeholders -->\n * <ore-async status=\"loading\"></ore-async>\n *\n * <!-- Empty state with custom message -->\n * <ore-async status=\"empty\" empty-label=\"No results\" empty-description=\"Try adjusting your filters.\"></ore-async>\n *\n * <!-- Error state with retry button -->\n * <ore-async status=\"error\" retryable error-label=\"Failed to load\" error-description=\"Check your connection.\"></ore-async>\n * ```\n */\nexport const ASYNC_TAG = 'ore-async' as const;\ndefine<OreAsyncProps>(ASYNC_TAG, {\n props: {\n 'empty-description': prop.string(),\n 'empty-label': prop.string('No content yet'),\n 'error-description': prop.string(),\n 'error-label': prop.string('Something went wrong'),\n retryable: prop.bool(false),\n status: prop.oneOf(['idle', 'loading', 'empty', 'error', 'success'] as const, 'success'),\n },\n setup(props) {\n const emit = useEmit<OreAsyncEvents>();\n\n const hasLoadingSlot = signal(false);\n const hasEmptySlot = signal(false);\n const hasErrorSlot = signal(false);\n\n // Reflect status onto the host so CSS can show/hide each region.\n // ARIA attributes are driven reactively by bind().\n bind({\n attr: {\n 'aria-busy': () => (props.status.value === 'loading' ? 'true' : 'false'),\n 'aria-label': () => (props.status.value === 'loading' ? 'Loading…' : null),\n 'aria-live': () => (props.status.value === 'error' ? 'assertive' : 'polite'),\n status: props.status,\n },\n });\n\n const renderText = (className: 'title' | 'description', text: Readable<string | undefined> | undefined) => () =>\n text?.value\n ? html`\n <p class=\"${className}\">${text}</p>\n `\n : '';\n\n // All four regions are always in the shadow DOM — CSS on :host([status=\"…\"])\n // toggles their visibility. This means:\n // - No DOM churn on status transitions (no teardown/rebuild of slot elements).\n // - Live regions are always present, so screen readers announce correctly.\n // - focus is never lost across status changes.\n return html`\n <div class=\"region region-idle\" role=\"presentation\"></div>\n\n <div class=\"region region-loading\" role=\"status\">\n <slot\n name=\"loading\"\n @slotchange=${(e: Event) => {\n hasLoadingSlot.value = (e.target as HTMLSlotElement).assignedNodes().length > 0;\n }}></slot>\n ${when(\n hasLoadingSlot,\n () => html``,\n () => html`\n <div class=\"loading-default\" aria-hidden=\"true\">\n <ore-skeleton variant=\"text\" lines=\"1\" width=\"40%\"></ore-skeleton>\n <ore-skeleton variant=\"text\" lines=\"3\" width=\"100%\"></ore-skeleton>\n <ore-skeleton variant=\"text\" lines=\"1\" width=\"60%\"></ore-skeleton>\n </div>\n `,\n )}\n </div>\n\n <div class=\"region region-empty\">\n <slot\n name=\"empty\"\n @slotchange=${(e: Event) => {\n hasEmptySlot.value = (e.target as HTMLSlotElement).assignedNodes().length > 0;\n }}></slot>\n ${when(\n hasEmptySlot,\n () => html``,\n () => html`\n <div class=\"empty-state\" role=\"status\">\n <div class=\"icon\">\n <ore-icon name=\"package\" size=\"100%\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n </div>\n ${renderText('title', props['empty-label'])} ${renderText('description', props['empty-description'])}\n </div>\n `,\n )}\n </div>\n\n <div class=\"region region-error\">\n <slot\n name=\"error\"\n @slotchange=${(e: Event) => {\n hasErrorSlot.value = (e.target as HTMLSlotElement).assignedNodes().length > 0;\n }}></slot>\n ${when(\n hasErrorSlot,\n () => html``,\n () => html`\n <div class=\"error-state\" role=\"alert\">\n <div class=\"icon\">\n <ore-icon name=\"triangle-alert\" size=\"100%\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n </div>\n ${renderText('title', props['error-label'])} ${renderText('description', props['error-description'])}\n ${when(\n () => Boolean(props.retryable.value),\n () => html`\n <button class=\"retry-btn\" type=\"button\" @click=${() => emit('retry')}>\n <ore-icon name=\"refresh-cw\" size=\"1em\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n Try again\n </button>\n `,\n )}\n </div>\n `,\n )}\n </div>\n\n <div class=\"region region-success\" role=\"presentation\">\n <slot></slot>\n </div>\n `;\n },\n styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":";;;;;AA6DA,IAAa,IAAY;AACzB,EAAsB,GAAW;CAC/B,OAAO;EACL,qBAAqB,EAAK,OAAO;EACjC,eAAe,EAAK,OAAO,gBAAgB;EAC3C,qBAAqB,EAAK,OAAO;EACjC,eAAe,EAAK,OAAO,sBAAsB;EACjD,WAAW,EAAK,KAAK,EAAK;EAC1B,QAAQ,EAAK,MAAM;GAAC;GAAQ;GAAW;GAAS;GAAS;EAAS,GAAY,SAAS;CACzF;CACA,MAAM,GAAO;EACX,IAAM,IAAO,EAAwB,GAE/B,IAAiB,EAAO,EAAK,GAC7B,IAAe,EAAO,EAAK,GAC3B,IAAe,EAAO,EAAK;EAIjC,EAAK,EACH,MAAM;GACJ,mBAAoB,EAAM,OAAO,UAAU,YAAY,SAAS;GAChE,oBAAqB,EAAM,OAAO,UAAU,YAAY,aAAa;GACrE,mBAAoB,EAAM,OAAO,UAAU,UAAU,cAAc;GACnE,QAAQ,EAAM;EAChB,EACF,CAAC;EAED,IAAM,KAAc,GAAoC,YACtD,GAAM,QACF,CAAI;wBACU,EAAU,IAAI,EAAK;cAEjC;EAON,OAAO,CAAI;;;;;;yBAMU,MAAa;GAC1B,EAAe,QAAS,EAAE,OAA2B,cAAc,CAAC,CAAC,SAAS;EAChF,EAAE;UACF,EACA,SACM,CAAI,UACJ,CAAI;;;;;;WAOZ,EAAE;;;;;;yBAMe,MAAa;GAC1B,EAAa,QAAS,EAAE,OAA2B,cAAc,CAAC,CAAC,SAAS;EAC9E,EAAE;UACF,EACA,SACM,CAAI,UACJ,CAAI;;;;;gBAKJ,EAAW,SAAS,EAAM,cAAc,EAAE,GAAG,EAAW,eAAe,EAAM,oBAAoB,EAAE;;WAG3G,EAAE;;;;;;yBAMe,MAAa;GAC1B,EAAa,QAAS,EAAE,OAA2B,cAAc,CAAC,CAAC,SAAS;EAC9E,EAAE;UACF,EACA,SACM,CAAI,UACJ,CAAI;;;;;gBAKJ,EAAW,SAAS,EAAM,cAAc,EAAE,GAAG,EAAW,eAAe,EAAM,oBAAoB,EAAE;gBACnG,QACM,EAAQ,EAAM,UAAU,aACxB,CAAI;yEAC+C,EAAK,OAAO,EAAE;;;;iBAKzE,EAAE;;WAGR,EAAE;;;;;;;CAOR;CACA,QAAQ,CAAC,GAAoB,CAAe;AAC9C,CAAC"}
|
package/dist/box.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"box.cjs","names":[],"sources":["../src/layout/box/box.ts"],"sourcesContent":["import { define, html, prop } from '@vielzeug/ore';\nimport {\n colorThemeMixin,\n elevationMixin,\n frostVariantMixin,\n paddingMixin,\n rainbowEffectMixin,\n roundedVariantMixin,\n} from '../../styles';\nimport type { ElevationLevel, PaddingSize, RoundedSize, ThemeColor } from '../../types';\nimport componentStyles from './box.css?inline';\n\n/** Box component properties */\nexport type OreBoxProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Animated border effect */\n effect?: 'rainbow';\n /** Shadow elevation level (0–5) */\n elevation?: ElevationLevel;\n /** Full width mode (100% of container) */\n fullwidth?: boolean;\n /** Internal padding size */\n padding?: PaddingSize;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Visual style variant */\n variant?: 'solid' | 'flat' | '
|
|
1
|
+
{"version":3,"file":"box.cjs","names":[],"sources":["../src/layout/box/box.ts"],"sourcesContent":["import { define, html, prop } from '@vielzeug/ore';\nimport {\n colorThemeMixin,\n elevationMixin,\n frostVariantMixin,\n paddingMixin,\n rainbowEffectMixin,\n roundedVariantMixin,\n} from '../../styles';\nimport type { ElevationLevel, PaddingSize, RoundedSize, ThemeColor } from '../../types';\nimport componentStyles from './box.css?inline';\n\n/** Box component properties */\nexport type OreBoxProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Animated border effect */\n effect?: 'rainbow';\n /** Shadow elevation level (0–5) */\n elevation?: ElevationLevel;\n /** Full width mode (100% of container) */\n fullwidth?: boolean;\n /** Internal padding size */\n padding?: PaddingSize;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Visual style variant */\n variant?: 'solid' | 'flat' | 'frost';\n};\n\n/**\n * ore-box — A foundational layout primitive with theming support.\n *\n * @element ore-box\n *\n * @attr {string} variant - Style variant: 'solid' | 'flat' | 'frost'\n * @attr {string} color - Color theme: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} padding - Padding size: 'none' | 'sm' | 'md' | 'lg' | 'xl'\n * @attr {string} elevation - Shadow elevation: '0' | '1' | '2' | '3' | '4' | '5'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n * @attr {string} effect - Animated border effect: 'rainbow'\n * @attr {boolean} fullwidth - Expand to full width\n *\n * @slot - Default slot for content\n *\n * @part box - The inner box element\n *\n * @cssprop --box-bg - Background color\n * @cssprop --box-color - Text color\n * @cssprop --box-border - Border width\n * @cssprop --box-border-color - Border color\n * @cssprop --box-radius - Border radius\n * @cssprop --box-padding - Inner padding\n * @cssprop --box-shadow - Box shadow\n *\n * @example\n * ```html\n * <ore-box padding=\"lg\" elevation=\"2\">Simple content</ore-box>\n * <ore-box variant=\"solid\" color=\"primary\">Solid color</ore-box>\n * <ore-box variant=\"frost\" effect=\"rainbow\">Frosted glass</ore-box>\n * ```\n */\nexport const BOX_TAG = 'ore-box' as const;\ndefine<OreBoxProps>(BOX_TAG, {\n props: {\n color: prop.string<ThemeColor>(),\n effect: prop.string<'rainbow'>(),\n elevation: prop.number<ElevationLevel>(),\n fullwidth: prop.bool(),\n padding: prop.string<PaddingSize>(),\n rounded: prop.string<RoundedSize>(),\n variant: prop.string<'flat' | 'solid' | 'frost'>(),\n },\n setup() {\n return html`\n <div class=\"box\" part=\"box\"><slot></slot></div>\n `;\n },\n\n styles: [\n colorThemeMixin,\n elevationMixin,\n paddingMixin,\n roundedVariantMixin,\n frostVariantMixin('.box'),\n rainbowEffectMixin('.box'),\n componentStyles,\n ],\n});\n"],"mappings":"iUA8DA,IAAa,EAAU,WACvB,EAAA,EAAA,OAAA,CAAoB,EAAS,CAC3B,MAAO,CACL,MAAO,EAAA,KAAK,OAAmB,EAC/B,OAAQ,EAAA,KAAK,OAAkB,EAC/B,UAAW,EAAA,KAAK,OAAuB,EACvC,UAAW,EAAA,KAAK,KAAK,EACrB,QAAS,EAAA,KAAK,OAAoB,EAClC,QAAS,EAAA,KAAK,OAAoB,EAClC,QAAS,EAAA,KAAK,OAAmC,CACnD,EACA,OAAQ,CACN,MAAO,GAAA,IAAI;;KAGb,EAEA,OAAQ,CACN,EAAA,gBACA,EAAA,eACA,EAAA,aACA,EAAA,oBACA,EAAA,kBAAkB,MAAM,EACxB,EAAA,mBAAmB,MAAM,EACzB,EAAA,OACF,CACF,CAAC"}
|
package/dist/box.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"box.js","names":[],"sources":["../src/layout/box/box.ts"],"sourcesContent":["import { define, html, prop } from '@vielzeug/ore';\nimport {\n colorThemeMixin,\n elevationMixin,\n frostVariantMixin,\n paddingMixin,\n rainbowEffectMixin,\n roundedVariantMixin,\n} from '../../styles';\nimport type { ElevationLevel, PaddingSize, RoundedSize, ThemeColor } from '../../types';\nimport componentStyles from './box.css?inline';\n\n/** Box component properties */\nexport type OreBoxProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Animated border effect */\n effect?: 'rainbow';\n /** Shadow elevation level (0–5) */\n elevation?: ElevationLevel;\n /** Full width mode (100% of container) */\n fullwidth?: boolean;\n /** Internal padding size */\n padding?: PaddingSize;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Visual style variant */\n variant?: 'solid' | 'flat' | '
|
|
1
|
+
{"version":3,"file":"box.js","names":[],"sources":["../src/layout/box/box.ts"],"sourcesContent":["import { define, html, prop } from '@vielzeug/ore';\nimport {\n colorThemeMixin,\n elevationMixin,\n frostVariantMixin,\n paddingMixin,\n rainbowEffectMixin,\n roundedVariantMixin,\n} from '../../styles';\nimport type { ElevationLevel, PaddingSize, RoundedSize, ThemeColor } from '../../types';\nimport componentStyles from './box.css?inline';\n\n/** Box component properties */\nexport type OreBoxProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Animated border effect */\n effect?: 'rainbow';\n /** Shadow elevation level (0–5) */\n elevation?: ElevationLevel;\n /** Full width mode (100% of container) */\n fullwidth?: boolean;\n /** Internal padding size */\n padding?: PaddingSize;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Visual style variant */\n variant?: 'solid' | 'flat' | 'frost';\n};\n\n/**\n * ore-box — A foundational layout primitive with theming support.\n *\n * @element ore-box\n *\n * @attr {string} variant - Style variant: 'solid' | 'flat' | 'frost'\n * @attr {string} color - Color theme: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} padding - Padding size: 'none' | 'sm' | 'md' | 'lg' | 'xl'\n * @attr {string} elevation - Shadow elevation: '0' | '1' | '2' | '3' | '4' | '5'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n * @attr {string} effect - Animated border effect: 'rainbow'\n * @attr {boolean} fullwidth - Expand to full width\n *\n * @slot - Default slot for content\n *\n * @part box - The inner box element\n *\n * @cssprop --box-bg - Background color\n * @cssprop --box-color - Text color\n * @cssprop --box-border - Border width\n * @cssprop --box-border-color - Border color\n * @cssprop --box-radius - Border radius\n * @cssprop --box-padding - Inner padding\n * @cssprop --box-shadow - Box shadow\n *\n * @example\n * ```html\n * <ore-box padding=\"lg\" elevation=\"2\">Simple content</ore-box>\n * <ore-box variant=\"solid\" color=\"primary\">Solid color</ore-box>\n * <ore-box variant=\"frost\" effect=\"rainbow\">Frosted glass</ore-box>\n * ```\n */\nexport const BOX_TAG = 'ore-box' as const;\ndefine<OreBoxProps>(BOX_TAG, {\n props: {\n color: prop.string<ThemeColor>(),\n effect: prop.string<'rainbow'>(),\n elevation: prop.number<ElevationLevel>(),\n fullwidth: prop.bool(),\n padding: prop.string<PaddingSize>(),\n rounded: prop.string<RoundedSize>(),\n variant: prop.string<'flat' | 'solid' | 'frost'>(),\n },\n setup() {\n return html`\n <div class=\"box\" part=\"box\"><slot></slot></div>\n `;\n },\n\n styles: [\n colorThemeMixin,\n elevationMixin,\n paddingMixin,\n roundedVariantMixin,\n frostVariantMixin('.box'),\n rainbowEffectMixin('.box'),\n componentStyles,\n ],\n});\n"],"mappings":";;;;;;;AA8DA,IAAa,IAAU;AACvB,EAAoB,GAAS;CAC3B,OAAO;EACL,OAAO,EAAK,OAAmB;EAC/B,QAAQ,EAAK,OAAkB;EAC/B,WAAW,EAAK,OAAuB;EACvC,WAAW,EAAK,KAAK;EACrB,SAAS,EAAK,OAAoB;EAClC,SAAS,EAAK,OAAoB;EAClC,SAAS,EAAK,OAAmC;CACnD;CACA,QAAQ;EACN,OAAO,CAAI;;;CAGb;CAEA,QAAQ;EACN;EACA;EACA;EACA;EACA,EAAkB,MAAM;EACxB,EAAmB,MAAM;EACzB;CACF;AACF,CAAC"}
|
package/dist/button.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/animation.css.cjs"),n=require("./styles/mixins/shape.css.cjs"),r=require("./styles/mixins/states.css.cjs"),i=require("./styles/mixins/theme.css.cjs"),a=require("./styles/mixins/variants.css.cjs"),o=require("./shared/prop-bundles.cjs"),s=require("./utils/link.cjs"),c=require("./button-group.cjs"),l=require("./inputs/shared/use-form-action.cjs"),u=require("./inputs/button/button.css.cjs");let d=require("@vielzeug/ore"),f=require("@vielzeug/ripple");var p=[`solid`,`flat`,`bordered`,`outline`,`ghost`,`text`,`frost`],m=`ore-button`;(0,d.define)(m,{formAssociated:!0,props:{...o.commonProps,effect:d.prop.string(),fullheight:d.prop.bool(!1),fullwidth:d.prop.bool(!1),href:d.prop.string(),iconOnly:d.prop.bool(!1),label:d.prop.string(),rel:d.prop.string(),target:d.prop.string(),type:d.prop.oneOf([`button`,`submit`,`reset`],`button`),variant:d.prop.oneOf(p,`solid`)},setup(e){let t=(0,d.getHost)(),n=(0,d.inject)(c.BUTTON_GROUP_CTX),r=(0,f.computed)(()=>n?.color.value??e.color.value),i=(0,f.computed)(()=>n?.size.value??e.size.value),a=(0,f.computed)(()=>n?.variant.value??e.variant.value),o=(0,f.computed)(()=>!!(e.disabled.value||e.loading.value)),{effectiveRel:u,isLink:p}=s.useLinkProps(e.href,e.rel,e.target),m=(0,d.useField)({disabled:o,toFormValue:()=>null,value:(0,f.computed)(()=>``)}),h=n=>{if(o.value){n.preventDefault();return}l.useFormAction(()=>p.value?null:m.internals.form,e.type,o,t)(n)};(0,d.bind)({attr:{"aria-busy":e.loading,"aria-disabled":o,"aria-label":e.label,color:r,effect:e.effect,role:(0,f.computed)(()=>t.getAttribute(`role`)
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/animation.css.cjs"),n=require("./styles/mixins/shape.css.cjs"),r=require("./styles/mixins/states.css.cjs"),i=require("./styles/mixins/theme.css.cjs"),a=require("./styles/mixins/variants.css.cjs"),o=require("./shared/prop-bundles.cjs"),s=require("./utils/link.cjs"),c=require("./button-group.cjs"),l=require("./inputs/shared/use-form-action.cjs"),u=require("./inputs/button/button.css.cjs");let d=require("@vielzeug/ore"),f=require("@vielzeug/ripple");var p=[`solid`,`flat`,`bordered`,`outline`,`ghost`,`text`,`frost`],m=`ore-button`;(0,d.define)(m,{formAssociated:!0,props:{...o.commonProps,effect:d.prop.string(),fullheight:d.prop.bool(!1),fullwidth:d.prop.bool(!1),href:d.prop.string(),iconOnly:d.prop.bool(!1),label:d.prop.string(),rel:d.prop.string(),target:d.prop.string(),type:d.prop.oneOf([`button`,`submit`,`reset`],`button`),variant:d.prop.oneOf(p,`solid`)},setup(e){let t=(0,d.getHost)(),n=(0,d.inject)(c.BUTTON_GROUP_CTX),r=(0,f.computed)(()=>n?.color.value??e.color.value),i=(0,f.computed)(()=>n?.size.value??e.size.value),a=(0,f.computed)(()=>n?.variant.value??e.variant.value),o=(0,f.computed)(()=>!!(e.disabled.value||e.loading.value)),{effectiveRel:u,isLink:p}=s.useLinkProps(e.href,e.rel,e.target),m=(0,d.useField)({disabled:o,toFormValue:()=>null,value:(0,f.computed)(()=>``)}),h=n=>{if(o.value){n.preventDefault();return}l.useFormAction(()=>p.value?null:m.internals.form,e.type,o,t)(n)};(0,d.bind)({attr:{"aria-busy":(0,f.computed)(()=>p.value?null:e.loading.value),"aria-disabled":(0,f.computed)(()=>p.value?null:o.value),"aria-label":(0,f.computed)(()=>p.value?null:e.label.value),color:r,effect:e.effect,role:(0,f.computed)(()=>p.value?null:t.getAttribute(`role`)??`button`),size:i,tabindex:(0,f.computed)(()=>p.value?null:o.value?`-1`:`0`),variant:a},on:{keydown:e=>{p.value||(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),(t.shadowRoot?.querySelector(`[part="button"]`))?.click())}}});let g=d.html`
|
|
2
2
|
<span class="loader" part="loader" aria-label="Loading" ?hidden=${()=>!e.loading.value}></span>
|
|
3
3
|
<slot name="prefix"></slot>
|
|
4
4
|
<span class="content" part="content"><slot></slot></span>
|
|
@@ -7,9 +7,11 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
7
7
|
${()=>p.value?d.html`
|
|
8
8
|
<a
|
|
9
9
|
part="button"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
aria-busy="${e.loading}"
|
|
11
|
+
aria-disabled="${o}"
|
|
12
|
+
aria-label="${(0,f.computed)(()=>e.label.value||t.textContent?.trim()||null)}"
|
|
13
|
+
tabindex="${(0,f.computed)(()=>o.value?`-1`:null)}"
|
|
14
|
+
href="${(0,f.computed)(()=>o.value?null:e.href.value)}"
|
|
13
15
|
rel="${u}"
|
|
14
16
|
target="${e.target}"
|
|
15
17
|
@click="${h}">
|
package/dist/button.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.cjs","names":[],"sources":["../src/inputs/button/button.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { commonProps } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledLoadingMixin,\n forcedColorsMixin,\n frostVariantMixin,\n rainbowEffectMixin,\n reducedMotionMixin,\n roundedVariantMixin,\n shineEffectMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { ButtonType, ComponentSize, LinkTarget, RoundedSize, ThemeColor } from '../../types';\nimport { useLinkProps } from '../../utils';\nimport { BUTTON_GROUP_CTX } from '../button-group/button-group';\nimport { useFormAction } from '../shared';\nimport componentStyles from './button.css?inline';\n\nexport const BUTTON_VARIANTS = ['solid', 'flat', 'bordered', 'outline', 'ghost', 'text', 'frost'] as const;\n\n/** Visual variant for ore-button — derived from BUTTON_VARIANTS for a single source of truth. */\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number];\n\n/** Animated border effect for ore-button. */\nexport type ButtonEffect = 'shine' | 'rainbow';\n\n/** Button component properties */\nexport type OreButtonProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Animated border effect: 'shine' (color-aware neon sweep) or 'rainbow' */\n effect?: ButtonEffect;\n /**\n * Full height button (100% of container) — the inner `[part=\"button\"]` fills its host's\n * height instead of the size preset's fixed height. Useful when the button itself is the\n * whole tappable surface of a container it doesn't otherwise control the size of (e.g. a\n * swipe-revealed row action in `ore-list-item`'s `actions-left`/`actions-right` slots).\n */\n fullheight?: boolean;\n /** Full width button (100% of container) */\n fullwidth?: boolean;\n /** When set, renders an `<a>` instead of `<button>` */\n href?: string;\n /** Icon-only mode (square aspect ratio, no padding) */\n iconOnly?: boolean;\n /** Accessible label for the inner button — required for icon-only buttons */\n label?: string;\n /** Show loading state with spinner */\n loading?: boolean;\n /** Link rel attribute (requires href) */\n rel?: string;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Component size */\n size?: ComponentSize;\n /** Link target (requires href) */\n target?: LinkTarget;\n /** HTML button type attribute */\n type?: ButtonType;\n /** Visual style variant */\n variant?: ButtonVariant;\n};\n\n/**\n * A customizable button component with multiple variants, sizes, and states.\n * Supports icons, loading states, and animated border effects.\n *\n * @element ore-button\n *\n * @attr {string} type - HTML button type: 'button' | 'submit' | 'reset'\n * @attr {boolean} disabled - Disable button interaction\n * @attr {boolean} loading - Show loading state with spinner\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'frost'\n * @attr {string} size - Button size: 'sm' | 'md' | 'lg'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n * @attr {string} effect - Animated border effect: 'shine' | 'rainbow'\n * @attr {boolean} icon-only - Icon-only mode (square aspect ratio, no padding)\n * @attr {boolean} fullwidth - Full width button (100% of container)\n * @attr {boolean} fullheight - Full height button (100% of container)\n *\n * @fires click - Emitted when button is clicked (unless disabled/loading)\n *\n * @slot - Button content (text, icons, etc.)\n * @slot prefix - Content before the button text (e.g., icons)\n * @slot suffix - Content after the button text (e.g., icons, badges)\n *\n * @part button - The inner button or anchor element\n * @part loader - The loading spinner element\n * @part content - The button content wrapper\n *\n * @cssprop --button-bg - Background color override\n * @cssprop --button-color - Text color override\n * @cssprop --button-border - Border width override\n * @cssprop --button-border-color - Border color override\n * @cssprop --button-radius - Border radius override\n * @cssprop --button-padding - Inner padding override\n * @cssprop --button-gap - Gap between icon and text override\n * @cssprop --button-font-size - Font size override\n * @cssprop --button-frost-active-bg - Background when pressed (frost variant)\n * @cssprop --button-frost-active-border-color - Border color when pressed (frost variant)\n *\n * @example\n * ```html\n * <ore-button variant=\"solid\" color=\"primary\">Click me</ore-button>\n * <ore-button loading color=\"success\">Processing...</ore-button>\n * <ore-button effect=\"shine\" color=\"primary\">Shine</ore-button>\n * <ore-button effect=\"rainbow\" variant=\"frost\">Rainbow</ore-button>\n * ```\n */\nexport const BUTTON_TAG = 'ore-button' as const;\ndefine<OreButtonProps>(BUTTON_TAG, {\n formAssociated: true,\n props: {\n ...commonProps,\n effect: prop.string<ButtonEffect>(),\n fullheight: prop.bool(false),\n fullwidth: prop.bool(false),\n href: prop.string(),\n iconOnly: prop.bool(false),\n label: prop.string(),\n rel: prop.string(),\n target: prop.string<LinkTarget>(),\n type: prop.oneOf(['button', 'submit', 'reset'] as const, 'button'),\n variant: prop.oneOf(BUTTON_VARIANTS, 'solid'),\n },\n setup(props) {\n const el = getHost();\n\n // Prefer group context over own props for color/size/variant.\n const groupCtx = inject(BUTTON_GROUP_CTX);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveVariant = computed(() => groupCtx?.variant.value ?? props.variant.value);\n\n const isDisabled = computed(() => !!(props.disabled.value || props.loading.value));\n\n // isLink and effectiveRel are computed from signals — correct even if href changes at runtime.\n const { effectiveRel, isLink } = useLinkProps(props.href, props.rel, props.target);\n\n // Form association: relay submit/reset clicks to the associated form.\n // The inner <button> always has type=\"button\" so shadow DOM never drives native form actions.\n // getForm() returns null for link mode at runtime, so no form actions fire.\n const formField = useField({\n disabled: isDisabled,\n toFormValue: () => null,\n value: computed(() => ''),\n });\n\n const handleClick = (e: MouseEvent) => {\n if (isDisabled.value) {\n e.preventDefault();\n\n return;\n }\n\n // Link mode renders a real <a href> below — the browser handles left/middle/ctrl-click\n // navigation, target, and rel natively. No manual window.open/location.href needed.\n useFormAction(() => (isLink.value ? null : formField.internals.form), props.type, isDisabled, el)(e);\n };\n\n const handleKeydown = (e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (el.shadowRoot?.querySelector('[part=\"button\"]') as HTMLElement)?.click();\n }\n };\n\n // ARIA attributes live on the host.\n bind({\n attr: {\n 'aria-busy': props.loading,\n 'aria-disabled': isDisabled,\n 'aria-label': props.label,\n color: effectiveColor,\n effect: props.effect,\n role: computed(() => el.getAttribute('role') ?? (isLink.value ? 'link' : 'button')),\n size: effectiveSize,\n tabindex: computed(() => (isDisabled.value ? '-1' : '0')),\n variant: effectiveVariant,\n },\n on: {\n keydown: handleKeydown,\n },\n });\n\n const buttonContent = html`\n <span class=\"loader\" part=\"loader\" aria-label=\"Loading\" ?hidden=${() => !props.loading.value}></span>\n <slot name=\"prefix\"></slot>\n <span class=\"content\" part=\"content\"><slot></slot></span>\n <slot name=\"suffix\"></slot>\n `;\n\n // The inner element (<a> or <span>) is deliberately non-focusable/decorative — this\n // component's real interactive semantics (role, tabindex, aria-label) live on the\n // custom-element host itself (see the `bind()` block above), since ore-button is\n // formAssociated and needs the host, not an inner element, to carry ElementInternals.\n // This differs from ore-navbar-item / ore-sidebar-item, which aren't form-associated\n // and so render their <a> as the one real focusable/semantic element.\n return html`\n ${() =>\n isLink.value\n ? html`\n <a\n part=\"button\"\n role=\"presentation\"\n tabindex=\"-1\"\n href=\"${props.href}\"\n rel=\"${effectiveRel}\"\n target=\"${props.target}\"\n @click=\"${handleClick}\">\n ${buttonContent}\n </a>\n `\n : html`\n <span part=\"button\" role=\"presentation\" @click=\"${handleClick}\">${buttonContent}</span>\n `}\n `;\n },\n shadow: { delegatesFocus: false },\n styles: [\n colorThemeMixin,\n coarsePointerMixin,\n reducedMotionMixin,\n roundedVariantMixin,\n forcedColorsMixin,\n sizeVariantMixin({\n lg: {\n fontSize: 'var(--text-base)',\n gap: 'var(--size-2-5)',\n height: 'var(--size-12)',\n iconSize: 'var(--size-6)',\n lineHeight: 'var(--leading-relaxed)',\n padding: 'var(--size-2-5) var(--size-5)',\n },\n sm: {\n fontSize: 'var(--text-sm)',\n gap: 'var(--size-1-5)',\n height: 'var(--size-8)',\n iconSize: 'var(--size-4)',\n lineHeight: 'var(--leading-tight)',\n padding: 'var(--size-1-5) var(--size-3)',\n },\n }),\n frostVariantMixin('[part=\"button\"]'),\n rainbowEffectMixin('[part=\"button\"]'),\n shineEffectMixin('[part=\"button\"]'),\n disabledLoadingMixin,\n componentStyles,\n ],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"button.cjs","names":[],"sources":["../src/inputs/button/button.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { commonProps } from '../../shared';\nimport {\n coarsePointerMixin,\n colorThemeMixin,\n disabledLoadingMixin,\n forcedColorsMixin,\n frostVariantMixin,\n rainbowEffectMixin,\n reducedMotionMixin,\n roundedVariantMixin,\n shineEffectMixin,\n sizeVariantMixin,\n} from '../../styles';\nimport type { ButtonType, ComponentSize, LinkTarget, RoundedSize, ThemeColor } from '../../types';\nimport { useLinkProps } from '../../utils';\nimport { BUTTON_GROUP_CTX } from '../button-group/button-group';\nimport { useFormAction } from '../shared';\nimport componentStyles from './button.css?inline';\n\nexport const BUTTON_VARIANTS = ['solid', 'flat', 'bordered', 'outline', 'ghost', 'text', 'frost'] as const;\n\n/** Visual variant for ore-button — derived from BUTTON_VARIANTS for a single source of truth. */\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number];\n\n/** Animated border effect for ore-button. */\nexport type ButtonEffect = 'shine' | 'rainbow';\n\n/** Button component properties */\nexport type OreButtonProps = {\n /** Theme color */\n color?: ThemeColor;\n /** Disable interaction */\n disabled?: boolean;\n /** Animated border effect: 'shine' (color-aware neon sweep) or 'rainbow' */\n effect?: ButtonEffect;\n /**\n * Full height button (100% of container) — the inner `[part=\"button\"]` fills its host's\n * height instead of the size preset's fixed height. Useful when the button itself is the\n * whole tappable surface of a container it doesn't otherwise control the size of (e.g. a\n * swipe-revealed row action in `ore-list-item`'s `actions-left`/`actions-right` slots).\n */\n fullheight?: boolean;\n /** Full width button (100% of container) */\n fullwidth?: boolean;\n /** When set, renders an `<a>` instead of `<button>` */\n href?: string;\n /** Icon-only mode (square aspect ratio, no padding) */\n iconOnly?: boolean;\n /** Accessible label for the inner button — required for icon-only buttons */\n label?: string;\n /** Show loading state with spinner */\n loading?: boolean;\n /** Link rel attribute (requires href) */\n rel?: string;\n /** Border radius size */\n rounded?: RoundedSize;\n /** Component size */\n size?: ComponentSize;\n /** Link target (requires href) */\n target?: LinkTarget;\n /** HTML button type attribute */\n type?: ButtonType;\n /** Visual style variant */\n variant?: ButtonVariant;\n};\n\n/**\n * A customizable button component with multiple variants, sizes, and states.\n * Supports icons, loading states, and animated border effects.\n *\n * @element ore-button\n *\n * @attr {string} type - HTML button type: 'button' | 'submit' | 'reset'\n * @attr {boolean} disabled - Disable button interaction\n * @attr {boolean} loading - Show loading state with spinner\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'frost'\n * @attr {string} size - Button size: 'sm' | 'md' | 'lg'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n * @attr {string} effect - Animated border effect: 'shine' | 'rainbow'\n * @attr {boolean} icon-only - Icon-only mode (square aspect ratio, no padding)\n * @attr {boolean} fullwidth - Full width button (100% of container)\n * @attr {boolean} fullheight - Full height button (100% of container)\n *\n * @fires click - Emitted when button is clicked (unless disabled/loading)\n *\n * @slot - Button content (text, icons, etc.)\n * @slot prefix - Content before the button text (e.g., icons)\n * @slot suffix - Content after the button text (e.g., icons, badges)\n *\n * @part button - The inner button or anchor element\n * @part loader - The loading spinner element\n * @part content - The button content wrapper\n *\n * @cssprop --button-bg - Background color override\n * @cssprop --button-color - Text color override\n * @cssprop --button-border - Border width override\n * @cssprop --button-border-color - Border color override\n * @cssprop --button-radius - Border radius override\n * @cssprop --button-padding - Inner padding override\n * @cssprop --button-gap - Gap between icon and text override\n * @cssprop --button-font-size - Font size override\n * @cssprop --button-frost-active-bg - Background when pressed (frost variant)\n * @cssprop --button-frost-active-border-color - Border color when pressed (frost variant)\n *\n * @example\n * ```html\n * <ore-button variant=\"solid\" color=\"primary\">Click me</ore-button>\n * <ore-button loading color=\"success\">Processing...</ore-button>\n * <ore-button effect=\"shine\" color=\"primary\">Shine</ore-button>\n * <ore-button effect=\"rainbow\" variant=\"frost\">Rainbow</ore-button>\n * ```\n */\nexport const BUTTON_TAG = 'ore-button' as const;\ndefine<OreButtonProps>(BUTTON_TAG, {\n formAssociated: true,\n props: {\n ...commonProps,\n effect: prop.string<ButtonEffect>(),\n fullheight: prop.bool(false),\n fullwidth: prop.bool(false),\n href: prop.string(),\n iconOnly: prop.bool(false),\n label: prop.string(),\n rel: prop.string(),\n target: prop.string<LinkTarget>(),\n type: prop.oneOf(['button', 'submit', 'reset'] as const, 'button'),\n variant: prop.oneOf(BUTTON_VARIANTS, 'solid'),\n },\n setup(props) {\n const el = getHost();\n\n // Prefer group context over own props for color/size/variant.\n const groupCtx = inject(BUTTON_GROUP_CTX);\n const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n const effectiveVariant = computed(() => groupCtx?.variant.value ?? props.variant.value);\n\n const isDisabled = computed(() => !!(props.disabled.value || props.loading.value));\n\n // isLink and effectiveRel are computed from signals — correct even if href changes at runtime.\n const { effectiveRel, isLink } = useLinkProps(props.href, props.rel, props.target);\n\n // Form association: relay submit/reset clicks to the associated form.\n // The inner <button> always has type=\"button\" so shadow DOM never drives native form actions.\n // getForm() returns null for link mode at runtime, so no form actions fire.\n const formField = useField({\n disabled: isDisabled,\n toFormValue: () => null,\n value: computed(() => ''),\n });\n\n const handleClick = (e: MouseEvent) => {\n if (isDisabled.value) {\n e.preventDefault();\n\n return;\n }\n\n // Link mode renders a real <a href> below — the browser handles left/middle/ctrl-click\n // navigation, target, and rel natively. No manual window.open/location.href needed.\n useFormAction(() => (isLink.value ? null : formField.internals.form), props.type, isDisabled, el)(e);\n };\n\n const handleKeydown = (e: KeyboardEvent) => {\n if (isLink.value) return;\n\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (el.shadowRoot?.querySelector('[part=\"button\"]') as HTMLElement)?.click();\n }\n };\n\n // ARIA attributes live on the host.\n bind({\n attr: {\n 'aria-busy': computed(() => (isLink.value ? null : props.loading.value)),\n 'aria-disabled': computed(() => (isLink.value ? null : isDisabled.value)),\n 'aria-label': computed(() => (isLink.value ? null : props.label.value)),\n color: effectiveColor,\n effect: props.effect,\n role: computed(() => (isLink.value ? null : (el.getAttribute('role') ?? 'button'))),\n size: effectiveSize,\n tabindex: computed(() => (isLink.value ? null : isDisabled.value ? '-1' : '0')),\n variant: effectiveVariant,\n },\n on: {\n keydown: handleKeydown,\n },\n });\n\n const buttonContent = html`\n <span class=\"loader\" part=\"loader\" aria-label=\"Loading\" ?hidden=${() => !props.loading.value}></span>\n <slot name=\"prefix\"></slot>\n <span class=\"content\" part=\"content\"><slot></slot></span>\n <slot name=\"suffix\"></slot>\n `;\n\n // Link mode delegates semantics and focus to the native anchor. Button mode keeps them on\n // the form-associated host because its visual part is intentionally a non-semantic span.\n return html`\n ${() =>\n isLink.value\n ? html`\n <a\n part=\"button\"\n aria-busy=\"${props.loading}\"\n aria-disabled=\"${isDisabled}\"\n aria-label=\"${computed(() => props.label.value || el.textContent?.trim() || null)}\"\n tabindex=\"${computed(() => (isDisabled.value ? '-1' : null))}\"\n href=\"${computed(() => (isDisabled.value ? null : props.href.value))}\"\n rel=\"${effectiveRel}\"\n target=\"${props.target}\"\n @click=\"${handleClick}\">\n ${buttonContent}\n </a>\n `\n : html`\n <span part=\"button\" role=\"presentation\" @click=\"${handleClick}\">${buttonContent}</span>\n `}\n `;\n },\n shadow: { delegatesFocus: false },\n styles: [\n colorThemeMixin,\n coarsePointerMixin,\n reducedMotionMixin,\n roundedVariantMixin,\n forcedColorsMixin,\n sizeVariantMixin({\n lg: {\n fontSize: 'var(--text-base)',\n gap: 'var(--size-2-5)',\n height: 'var(--size-12)',\n iconSize: 'var(--size-6)',\n lineHeight: 'var(--leading-relaxed)',\n padding: 'var(--size-2-5) var(--size-5)',\n },\n sm: {\n fontSize: 'var(--text-sm)',\n gap: 'var(--size-1-5)',\n height: 'var(--size-8)',\n iconSize: 'var(--size-4)',\n lineHeight: 'var(--leading-tight)',\n padding: 'var(--size-1-5) var(--size-3)',\n },\n }),\n frostVariantMixin('[part=\"button\"]'),\n rainbowEffectMixin('[part=\"button\"]'),\n shineEffectMixin('[part=\"button\"]'),\n disabledLoadingMixin,\n componentStyles,\n ],\n});\n"],"mappings":"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"}
|
package/dist/button.js
CHANGED
|
@@ -54,18 +54,18 @@ g(w, {
|
|
|
54
54
|
};
|
|
55
55
|
h({
|
|
56
56
|
attr: {
|
|
57
|
-
"aria-busy": e.loading,
|
|
58
|
-
"aria-disabled": o,
|
|
59
|
-
"aria-label": e.label,
|
|
57
|
+
"aria-busy": S(() => c.value ? null : e.loading.value),
|
|
58
|
+
"aria-disabled": S(() => c.value ? null : o.value),
|
|
59
|
+
"aria-label": S(() => c.value ? null : e.label.value),
|
|
60
60
|
color: r,
|
|
61
61
|
effect: e.effect,
|
|
62
|
-
role: S(() => t.getAttribute("role") ??
|
|
62
|
+
role: S(() => c.value ? null : t.getAttribute("role") ?? "button"),
|
|
63
63
|
size: i,
|
|
64
|
-
tabindex: S(() => o.value ? "-1" : "0"),
|
|
64
|
+
tabindex: S(() => c.value ? null : o.value ? "-1" : "0"),
|
|
65
65
|
variant: a
|
|
66
66
|
},
|
|
67
67
|
on: { keydown: (e) => {
|
|
68
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), (t.shadowRoot?.querySelector("[part=\"button\"]"))?.click());
|
|
68
|
+
c.value || (e.key === "Enter" || e.key === " ") && (e.preventDefault(), (t.shadowRoot?.querySelector("[part=\"button\"]"))?.click());
|
|
69
69
|
} }
|
|
70
70
|
});
|
|
71
71
|
let m = v`
|
|
@@ -78,9 +78,11 @@ g(w, {
|
|
|
78
78
|
${() => c.value ? v`
|
|
79
79
|
<a
|
|
80
80
|
part="button"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
aria-busy="${e.loading}"
|
|
82
|
+
aria-disabled="${o}"
|
|
83
|
+
aria-label="${S(() => e.label.value || t.textContent?.trim() || null)}"
|
|
84
|
+
tabindex="${S(() => o.value ? "-1" : null)}"
|
|
85
|
+
href="${S(() => o.value ? null : e.href.value)}"
|
|
84
86
|
rel="${s}"
|
|
85
87
|
target="${e.target}"
|
|
86
88
|
@click="${u}">
|