@vielzeug/refine 1.5.4 → 1.7.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/dist/accordion.cjs +1 -1
- package/dist/accordion.cjs.map +1 -1
- package/dist/accordion.js +30 -28
- package/dist/accordion.js.map +1 -1
- package/dist/button-group.cjs.map +1 -1
- package/dist/button-group.js.map +1 -1
- package/dist/button.cjs +1 -1
- package/dist/button.cjs.map +1 -1
- package/dist/button.js +1 -0
- package/dist/button.js.map +1 -1
- package/dist/checkbox-group.cjs +3 -3
- package/dist/checkbox-group.cjs.map +1 -1
- package/dist/checkbox-group.js +51 -58
- package/dist/checkbox-group.js.map +1 -1
- package/dist/combobox.cjs +29 -28
- package/dist/combobox.cjs.map +1 -1
- package/dist/combobox.js +201 -195
- package/dist/combobox.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/chat-message/chat-message.css.cjs +1 -1
- package/dist/content/chat-message/chat-message.css.cjs.map +1 -1
- package/dist/content/chat-message/chat-message.css.js +1 -1
- package/dist/content/chat-message/chat-message.css.js.map +1 -1
- package/dist/content/icon/icon.d.ts.map +1 -1
- package/dist/content/index.d.ts +4 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/list/_list-item-reveal.cjs +2 -0
- package/dist/content/list/_list-item-reveal.cjs.map +1 -0
- package/dist/content/list/_list-item-reveal.d.ts +25 -0
- package/dist/content/list/_list-item-reveal.d.ts.map +1 -0
- package/dist/content/list/_list-item-reveal.js +33 -0
- package/dist/content/list/_list-item-reveal.js.map +1 -0
- package/dist/content/list/list-item.css.cjs +2 -0
- package/dist/content/list/list-item.css.cjs.map +1 -0
- package/dist/content/list/list-item.css.js +6 -0
- package/dist/content/list/list-item.css.js.map +1 -0
- package/dist/content/list/list-item.d.ts +111 -0
- package/dist/content/list/list-item.d.ts.map +1 -0
- package/dist/content/list/list.css.cjs +2 -0
- package/dist/content/list/list.css.cjs.map +1 -0
- package/dist/content/list/list.css.js +6 -0
- package/dist/content/list/list.css.js.map +1 -0
- package/dist/content/list/list.d.ts +93 -0
- package/dist/content/list/list.d.ts.map +1 -0
- package/dist/custom-elements.json +6052 -5449
- package/dist/date-picker.cjs +2 -1
- package/dist/date-picker.cjs.map +1 -1
- package/dist/date-picker.js +2 -0
- package/dist/date-picker.js.map +1 -1
- package/dist/disclosure/tab-item/tab-item.d.ts.map +1 -1
- package/dist/disclosure/tab-panel/tab-panel.d.ts.map +1 -1
- 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 +47 -48
- package/dist/drawer.js.map +1 -1
- package/dist/file-input.cjs +111 -45
- package/dist/file-input.cjs.map +1 -1
- package/dist/file-input.js +216 -156
- package/dist/file-input.js.map +1 -1
- package/dist/headless/aria-reflection.cjs +2 -0
- package/dist/headless/aria-reflection.cjs.map +1 -0
- package/dist/headless/aria-reflection.d.ts +27 -0
- package/dist/headless/aria-reflection.d.ts.map +1 -0
- package/dist/headless/aria-reflection.js +18 -0
- package/dist/headless/aria-reflection.js.map +1 -0
- package/dist/headless/checkable.cjs +1 -1
- package/dist/headless/checkable.cjs.map +1 -1
- package/dist/headless/checkable.d.ts +2 -10
- package/dist/headless/checkable.d.ts.map +1 -1
- package/dist/headless/checkable.js +29 -29
- package/dist/headless/checkable.js.map +1 -1
- package/dist/headless/choice-field.cjs +1 -1
- package/dist/headless/choice-field.cjs.map +1 -1
- package/dist/headless/choice-field.d.ts +2 -7
- package/dist/headless/choice-field.d.ts.map +1 -1
- package/dist/headless/choice-field.js +37 -37
- package/dist/headless/choice-field.js.map +1 -1
- package/dist/headless/field-base.cjs +1 -1
- package/dist/headless/field-base.cjs.map +1 -1
- package/dist/headless/field-base.d.ts +17 -0
- package/dist/headless/field-base.d.ts.map +1 -1
- package/dist/headless/field-base.js +21 -6
- package/dist/headless/field-base.js.map +1 -1
- package/dist/headless/focus.cjs +1 -1
- package/dist/headless/focus.cjs.map +1 -1
- package/dist/headless/focus.d.ts +7 -0
- package/dist/headless/focus.d.ts.map +1 -1
- package/dist/headless/focus.js +6 -6
- package/dist/headless/focus.js.map +1 -1
- package/dist/headless/index.d.ts +4 -2
- package/dist/headless/index.d.ts.map +1 -1
- package/dist/headless/nav.cjs +1 -1
- package/dist/headless/nav.cjs.map +1 -1
- package/dist/headless/nav.d.ts +12 -0
- package/dist/headless/nav.d.ts.map +1 -1
- package/dist/headless/nav.js +68 -50
- package/dist/headless/nav.js.map +1 -1
- package/dist/headless/overlay.cjs +1 -1
- package/dist/headless/overlay.cjs.map +1 -1
- package/dist/headless/overlay.d.ts +2 -0
- package/dist/headless/overlay.d.ts.map +1 -1
- package/dist/headless/overlay.js +8 -5
- package/dist/headless/overlay.js.map +1 -1
- package/dist/headless/ref-callback.cjs +2 -0
- package/dist/headless/ref-callback.cjs.map +1 -0
- package/dist/headless/ref-callback.d.ts +26 -0
- package/dist/headless/ref-callback.d.ts.map +1 -0
- package/dist/headless/ref-callback.js +13 -0
- package/dist/headless/ref-callback.js.map +1 -0
- package/dist/headless/swipe.cjs +1 -1
- package/dist/headless/swipe.cjs.map +1 -1
- package/dist/headless/swipe.d.ts +2 -0
- package/dist/headless/swipe.d.ts.map +1 -1
- package/dist/headless/swipe.js +26 -21
- package/dist/headless/swipe.js.map +1 -1
- package/dist/headless.cjs +1 -1
- package/dist/headless.js +28 -26
- package/dist/icon.cjs +1 -1
- package/dist/icon.cjs.map +1 -1
- package/dist/icon.js +36 -38
- package/dist/icon.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +63 -61
- package/dist/input.cjs +29 -26
- package/dist/input.cjs.map +1 -1
- package/dist/input.js +91 -79
- package/dist/input.js.map +1 -1
- package/dist/inputs/button/button.css.cjs +1 -1
- package/dist/inputs/button/button.css.cjs.map +1 -1
- package/dist/inputs/button/button.css.js +1 -1
- package/dist/inputs/button/button.css.js.map +1 -1
- package/dist/inputs/button/button.d.ts +8 -0
- package/dist/inputs/button/button.d.ts.map +1 -1
- package/dist/inputs/checkbox/checkbox.css.cjs +1 -1
- package/dist/inputs/checkbox/checkbox.css.cjs.map +1 -1
- package/dist/inputs/checkbox/checkbox.css.js +1 -1
- package/dist/inputs/checkbox/checkbox.css.js.map +1 -1
- package/dist/inputs/combobox/combobox.d.ts +1 -0
- package/dist/inputs/combobox/combobox.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/date-picker/date-picker.d.ts +6 -0
- package/dist/inputs/date-picker/date-picker.d.ts.map +1 -1
- package/dist/inputs/file-input/file-input-upload.cjs +2 -0
- package/dist/inputs/file-input/file-input-upload.cjs.map +1 -0
- package/dist/inputs/file-input/file-input-upload.d.ts +76 -0
- package/dist/inputs/file-input/file-input-upload.d.ts.map +1 -0
- package/dist/inputs/file-input/file-input-upload.js +145 -0
- package/dist/inputs/file-input/file-input-upload.js.map +1 -0
- package/dist/inputs/file-input/file-input.css.cjs +1 -1
- package/dist/inputs/file-input/file-input.css.cjs.map +1 -1
- package/dist/inputs/file-input/file-input.css.js +1 -1
- package/dist/inputs/file-input/file-input.css.js.map +1 -1
- package/dist/inputs/file-input/file-input.d.ts +54 -1
- package/dist/inputs/file-input/file-input.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/input/input.d.ts +13 -0
- package/dist/inputs/input/input.d.ts.map +1 -1
- package/dist/inputs/message-composer/message-composer.css.cjs +1 -1
- package/dist/inputs/message-composer/message-composer.css.cjs.map +1 -1
- package/dist/inputs/message-composer/message-composer.css.js +1 -1
- package/dist/inputs/message-composer/message-composer.css.js.map +1 -1
- package/dist/inputs/message-composer/message-composer.d.ts +7 -0
- package/dist/inputs/message-composer/message-composer.d.ts.map +1 -1
- package/dist/inputs/number-input/number-input.css.cjs +1 -1
- package/dist/inputs/number-input/number-input.css.cjs.map +1 -1
- package/dist/inputs/number-input/number-input.css.js +1 -1
- package/dist/inputs/number-input/number-input.css.js.map +1 -1
- package/dist/inputs/number-input/number-input.d.ts +15 -0
- package/dist/inputs/number-input/number-input.d.ts.map +1 -1
- package/dist/inputs/otp-input/otp-input.css.cjs +1 -1
- package/dist/inputs/otp-input/otp-input.css.cjs.map +1 -1
- package/dist/inputs/otp-input/otp-input.css.js +1 -1
- package/dist/inputs/otp-input/otp-input.css.js.map +1 -1
- package/dist/inputs/radio/radio.d.ts.map +1 -1
- package/dist/inputs/select/select.d.ts +1 -0
- package/dist/inputs/select/select.d.ts.map +1 -1
- package/dist/inputs/shared/index.d.ts +1 -1
- package/dist/inputs/shared/index.d.ts.map +1 -1
- package/dist/inputs/shared/templates.cjs +26 -3
- package/dist/inputs/shared/templates.cjs.map +1 -1
- package/dist/inputs/shared/templates.d.ts +20 -0
- package/dist/inputs/shared/templates.d.ts.map +1 -1
- package/dist/inputs/shared/templates.js +29 -5
- package/dist/inputs/shared/templates.js.map +1 -1
- package/dist/inputs/switch/switch.css.cjs +1 -1
- package/dist/inputs/switch/switch.css.cjs.map +1 -1
- package/dist/inputs/switch/switch.css.js +1 -1
- package/dist/inputs/switch/switch.css.js.map +1 -1
- package/dist/inputs/switch/switch.d.ts.map +1 -1
- package/dist/inputs/textarea/textarea.css.cjs +1 -1
- package/dist/inputs/textarea/textarea.css.cjs.map +1 -1
- package/dist/inputs/textarea/textarea.css.js +1 -1
- package/dist/inputs/textarea/textarea.css.js.map +1 -1
- package/dist/inputs/textarea/textarea.d.ts +14 -0
- package/dist/inputs/textarea/textarea.d.ts.map +1 -1
- package/dist/inputs/time-picker/time-picker.css.cjs +1 -1
- package/dist/inputs/time-picker/time-picker.css.cjs.map +1 -1
- package/dist/inputs/time-picker/time-picker.css.js +1 -1
- package/dist/inputs/time-picker/time-picker.css.js.map +1 -1
- package/dist/inputs/time-picker/time-picker.d.ts +6 -0
- package/dist/inputs/time-picker/time-picker.d.ts.map +1 -1
- package/dist/list-item.cjs +22 -0
- package/dist/list-item.cjs.map +1 -0
- package/dist/list-item.js +91 -0
- package/dist/list-item.js.map +1 -0
- package/dist/list.cjs +2 -0
- package/dist/list.cjs.map +1 -0
- package/dist/list.js +64 -0
- package/dist/list.js.map +1 -0
- package/dist/menu.cjs +4 -4
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.js +60 -59
- package/dist/menu.js.map +1 -1
- package/dist/message-composer.cjs +14 -20
- package/dist/message-composer.cjs.map +1 -1
- package/dist/message-composer.js +63 -64
- package/dist/message-composer.js.map +1 -1
- package/dist/number-input.cjs +5 -3
- package/dist/number-input.cjs.map +1 -1
- package/dist/number-input.js +8 -4
- package/dist/number-input.js.map +1 -1
- package/dist/otp-input.cjs +9 -9
- package/dist/otp-input.cjs.map +1 -1
- package/dist/otp-input.js +70 -62
- package/dist/otp-input.js.map +1 -1
- package/dist/overlay/menu/menu-item.css.cjs +1 -1
- package/dist/overlay/menu/menu-item.css.cjs.map +1 -1
- package/dist/overlay/menu/menu-item.css.js +1 -1
- package/dist/overlay/menu/menu-item.css.js.map +1 -1
- package/dist/overlay/menu/menu-separator.css.cjs +1 -1
- package/dist/overlay/menu/menu-separator.css.cjs.map +1 -1
- package/dist/overlay/menu/menu-separator.css.js +1 -1
- package/dist/overlay/menu/menu-separator.css.js.map +1 -1
- package/dist/overlay/menu/menu.css.cjs +1 -1
- package/dist/overlay/menu/menu.css.cjs.map +1 -1
- package/dist/overlay/menu/menu.css.js +1 -1
- package/dist/overlay/menu/menu.css.js.map +1 -1
- package/dist/overlay/shared/use-dialog.cjs +1 -1
- package/dist/overlay/shared/use-dialog.cjs.map +1 -1
- package/dist/overlay/shared/use-dialog.d.ts.map +1 -1
- package/dist/overlay/shared/use-dialog.js +7 -6
- package/dist/overlay/shared/use-dialog.js.map +1 -1
- package/dist/radio-group.cjs +3 -3
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +64 -70
- package/dist/radio-group.js.map +1 -1
- package/dist/radio.cjs +4 -4
- package/dist/radio.cjs.map +1 -1
- package/dist/radio.js +58 -58
- package/dist/radio.js.map +1 -1
- package/dist/rating.cjs.map +1 -1
- package/dist/rating.js.map +1 -1
- package/dist/refine.cjs +426 -233
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +431 -238
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +452 -259
- package/dist/refine.js.map +1 -1
- package/dist/select.cjs +21 -21
- package/dist/select.cjs.map +1 -1
- package/dist/select.js +135 -127
- package/dist/select.js.map +1 -1
- package/dist/shared/size-presets.cjs +1 -1
- package/dist/shared/size-presets.cjs.map +1 -1
- package/dist/shared/size-presets.d.ts +74 -0
- package/dist/shared/size-presets.d.ts.map +1 -1
- package/dist/shared/size-presets.js +48 -1
- package/dist/shared/size-presets.js.map +1 -1
- package/dist/shared/types.d.ts +5 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/styles/index.cjs +1 -1
- package/dist/styles/index.cjs.map +1 -1
- package/dist/styles/index.d.ts +1 -0
- package/dist/styles/index.d.ts.map +1 -1
- package/dist/styles/index.js +5 -4
- package/dist/styles/index.js.map +1 -1
- package/dist/styles/mixins/field-variants.css.cjs +85 -0
- package/dist/styles/mixins/field-variants.css.cjs.map +1 -0
- package/dist/styles/mixins/field-variants.css.d.ts +66 -0
- package/dist/styles/mixins/field-variants.css.d.ts.map +1 -0
- package/dist/styles/mixins/field-variants.css.js +90 -0
- package/dist/styles/mixins/field-variants.css.js.map +1 -0
- package/dist/switch.cjs +3 -3
- package/dist/switch.cjs.map +1 -1
- package/dist/switch.js +38 -36
- package/dist/switch.js.map +1 -1
- package/dist/tab-item.cjs +8 -8
- package/dist/tab-item.cjs.map +1 -1
- package/dist/tab-item.js +38 -28
- package/dist/tab-item.js.map +1 -1
- package/dist/tab-panel.cjs +7 -7
- package/dist/tab-panel.cjs.map +1 -1
- package/dist/tab-panel.js +34 -26
- package/dist/tab-panel.js.map +1 -1
- package/dist/tabs.cjs +4 -4
- package/dist/tabs.cjs.map +1 -1
- package/dist/tabs.js +64 -60
- package/dist/tabs.js.map +1 -1
- package/dist/textarea.cjs +15 -18
- package/dist/textarea.cjs.map +1 -1
- package/dist/textarea.js +83 -74
- package/dist/textarea.js.map +1 -1
- package/dist/time-picker.cjs +34 -31
- package/dist/time-picker.cjs.map +1 -1
- package/dist/time-picker.js +121 -109
- package/dist/time-picker.js.map +1 -1
- package/package.json +21 -9
- package/dist/headless/dialog-focus.cjs +0 -2
- package/dist/headless/dialog-focus.cjs.map +0 -1
- package/dist/headless/dialog-focus.d.ts +0 -40
- package/dist/headless/dialog-focus.d.ts.map +0 -1
- package/dist/headless/dialog-focus.js +0 -22
- package/dist/headless/dialog-focus.js.map +0 -1
- package/dist/headless/focus-trap.cjs +0 -2
- package/dist/headless/focus-trap.cjs.map +0 -1
- package/dist/headless/focus-trap.d.ts +0 -45
- package/dist/headless/focus-trap.d.ts.map +0 -1
- package/dist/headless/focus-trap.js +0 -51
- package/dist/headless/focus-trap.js.map +0 -1
package/dist/accordion.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./headless/
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./headless/ore.cjs"),t=require("./headless/nav.cjs"),n=require("./disclosure/accordion/accordion.css.cjs");let r=require("@vielzeug/ore"),i=require("@vielzeug/ripple");var a=(0,r.createContext)(`AccordionContext`),o=`ore-accordion`;(0,r.define)(o,{props:{selectionMode:r.prop.string(),size:r.prop.string(),variant:r.prop.string()},setup(n){let o=(0,r.getHost)(),s=(0,r.useEmit)(),c=e=>{n.selectionMode.value===`single`&&(o.querySelectorAll(`:scope > ore-accordion-item[expanded]`).forEach(t=>{t!==e&&t.hasAttribute(`expanded`)&&t.removeAttribute(`expanded`)}),s(`change`,{expandedItem:e}))},l=()=>[...o.querySelectorAll(`:scope > ore-accordion-item:not([disabled])`)],u=()=>l().map(e=>e.shadowRoot?.querySelector(`summary`)).filter(Boolean),d=t.createListControl({getItems:()=>l(),loop:!0,onNavigate:(e,t)=>{u()[t]?.focus()},signal:e.lifecycleSignal(r.onCleanup)});return(0,r.provide)(a,{selectionMode:(0,i.computed)(()=>n.selectionMode.value),size:n.size,variant:n.variant}),(0,r.bind)({on:{expand:e=>{let t=e.composedPath().find(e=>e instanceof HTMLElement),n=e.detail?.item??t;!n||n.localName!==`ore-accordion-item`||n.closest(`ore-accordion`)===o&&c(n)},keydown:e=>{let t=u();if(!t.length)return;let n=e.composedPath().find(e=>e instanceof HTMLElement&&e.localName===`summary`),r=n?t.indexOf(n):-1;r!==-1&&(d.set(r),d.handleKeydown(e))}}}),r.html`<slot></slot>`},styles:[n.default]}),exports.ACCORDION_CTX=a,exports.ACCORDION_TAG=o;
|
|
2
2
|
//# sourceMappingURL=accordion.cjs.map
|
package/dist/accordion.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.cjs","names":[],"sources":["../src/disclosure/accordion/accordion.ts"],"sourcesContent":["import { createContext, define, html, prop, bind, getHost, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport type { ComponentSize, SurfaceVariant } from '../../types';\n\nimport { createListControl } from '../../headless';\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<SurfaceVariant | 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?: SurfaceVariant;\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' | 'glass' | '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/glass/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<SurfaceVariant>(),\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: (_action, index) => {\n const summaries = getSummaryElements();\n\n summaries[index]?.focus();\n },\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 || 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`<slot></slot>`;\n },\n\n styles: [styles],\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"accordion.cjs","names":[],"sources":["../src/disclosure/accordion/accordion.ts"],"sourcesContent":["import { createContext, define, html, prop, bind, getHost, onCleanup, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport type { ComponentSize, SurfaceVariant } from '../../types';\n\nimport { createListControl, lifecycleSignal } from '../../headless';\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<SurfaceVariant | 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?: SurfaceVariant;\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' | 'glass' | '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/glass/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<SurfaceVariant>(),\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: (_action, 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 || 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`<slot></slot>`;\n },\n\n styles: [styles],\n});\n"],"mappings":"4PAeA,IAAa,GAAA,EAAA,EAAA,cAAA,CAAgD,kBAAkB,EAsDlE,EAAgB,6BACH,EAAe,CACvC,MAAO,CACL,cAAe,EAAA,KAAK,OAA8B,EAClD,KAAM,EAAA,KAAK,OAAsB,EACjC,QAAS,EAAA,KAAK,OAAuB,CACvC,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAA,EAAA,QAAA,CAAa,EACb,GAAA,EAAA,EAAA,QAAA,CAAmC,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,EAAS,IAAU,CAG9B,EAAA,CAAA,CAAU,EAAM,EAAE,MAAM,CAC1B,EACA,OAAQ,EAAA,gBAAgB,EAAA,SAAS,CACnC,CAAC,EAyCD,OAvCA,EAAA,EAAA,QAAA,CAAQ,EAAe,CACrB,eAAA,EAAA,EAAA,SAAA,KAA8B,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,CAAC,GAAgB,EAAa,YAAc,sBAG5C,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,eACb,EAEA,OAAQ,CAAC,EAAA,OAAM,CACjB,CAAC"}
|
package/dist/accordion.js
CHANGED
|
@@ -1,47 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import t from "./
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import { lifecycleSignal as e } from "./headless/ore.js";
|
|
2
|
+
import { createListControl as t } from "./headless/nav.js";
|
|
3
|
+
import n from "./disclosure/accordion/accordion.css.js";
|
|
4
|
+
import { bind as r, createContext as i, define as a, getHost as o, html as s, onCleanup as c, prop as l, provide as u, useEmit as d } from "@vielzeug/ore";
|
|
5
|
+
import { computed as f } from "@vielzeug/ripple";
|
|
5
6
|
//#region src/disclosure/accordion/accordion.ts
|
|
6
|
-
var
|
|
7
|
-
|
|
7
|
+
var p = i("AccordionContext"), m = "ore-accordion";
|
|
8
|
+
a(m, {
|
|
8
9
|
props: {
|
|
9
|
-
selectionMode:
|
|
10
|
-
size:
|
|
11
|
-
variant:
|
|
10
|
+
selectionMode: l.string(),
|
|
11
|
+
size: l.string(),
|
|
12
|
+
variant: l.string()
|
|
12
13
|
},
|
|
13
|
-
setup(
|
|
14
|
-
let
|
|
15
|
-
|
|
14
|
+
setup(n) {
|
|
15
|
+
let i = o(), a = d(), l = (e) => {
|
|
16
|
+
n.selectionMode.value === "single" && (i.querySelectorAll(":scope > ore-accordion-item[expanded]").forEach((t) => {
|
|
16
17
|
t !== e && t.hasAttribute("expanded") && t.removeAttribute("expanded");
|
|
17
|
-
}),
|
|
18
|
-
},
|
|
19
|
-
getItems: () =>
|
|
18
|
+
}), a("change", { expandedItem: e }));
|
|
19
|
+
}, m = () => [...i.querySelectorAll(":scope > ore-accordion-item:not([disabled])")], h = () => m().map((e) => e.shadowRoot?.querySelector("summary")).filter(Boolean), g = t({
|
|
20
|
+
getItems: () => m(),
|
|
20
21
|
loop: !0,
|
|
21
22
|
onNavigate: (e, t) => {
|
|
22
|
-
|
|
23
|
-
}
|
|
23
|
+
h()[t]?.focus();
|
|
24
|
+
},
|
|
25
|
+
signal: e(c)
|
|
24
26
|
});
|
|
25
|
-
return
|
|
26
|
-
selectionMode:
|
|
27
|
-
size:
|
|
28
|
-
variant:
|
|
29
|
-
}),
|
|
27
|
+
return u(p, {
|
|
28
|
+
selectionMode: f(() => n.selectionMode.value),
|
|
29
|
+
size: n.size,
|
|
30
|
+
variant: n.variant
|
|
31
|
+
}), r({ on: {
|
|
30
32
|
expand: (e) => {
|
|
31
33
|
let t = e.composedPath().find((e) => e instanceof HTMLElement), n = e.detail?.item ?? t;
|
|
32
|
-
!n || n.localName !== "ore-accordion-item" || n.closest("ore-accordion") ===
|
|
34
|
+
!n || n.localName !== "ore-accordion-item" || n.closest("ore-accordion") === i && l(n);
|
|
33
35
|
},
|
|
34
36
|
keydown: (e) => {
|
|
35
|
-
let t =
|
|
37
|
+
let t = h();
|
|
36
38
|
if (!t.length) return;
|
|
37
39
|
let n = e.composedPath().find((e) => e instanceof HTMLElement && e.localName === "summary"), r = n ? t.indexOf(n) : -1;
|
|
38
|
-
r !== -1 && (
|
|
40
|
+
r !== -1 && (g.set(r), g.handleKeydown(e));
|
|
39
41
|
}
|
|
40
|
-
} }),
|
|
42
|
+
} }), s`<slot></slot>`;
|
|
41
43
|
},
|
|
42
|
-
styles: [
|
|
44
|
+
styles: [n]
|
|
43
45
|
});
|
|
44
46
|
//#endregion
|
|
45
|
-
export {
|
|
47
|
+
export { p as ACCORDION_CTX, m as ACCORDION_TAG };
|
|
46
48
|
|
|
47
49
|
//# sourceMappingURL=accordion.js.map
|
package/dist/accordion.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion.js","names":[],"sources":["../src/disclosure/accordion/accordion.ts"],"sourcesContent":["import { createContext, define, html, prop, bind, getHost, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport type { ComponentSize, SurfaceVariant } from '../../types';\n\nimport { createListControl } from '../../headless';\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<SurfaceVariant | 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?: SurfaceVariant;\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' | 'glass' | '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/glass/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<SurfaceVariant>(),\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: (_action, index) => {\n const summaries = getSummaryElements();\n\n summaries[index]?.focus();\n },\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 || 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`<slot></slot>`;\n },\n\n styles: [styles],\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"accordion.js","names":[],"sources":["../src/disclosure/accordion/accordion.ts"],"sourcesContent":["import { createContext, define, html, prop, bind, getHost, onCleanup, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable } from '@vielzeug/ripple';\n\nimport type { ComponentSize, SurfaceVariant } from '../../types';\n\nimport { createListControl, lifecycleSignal } from '../../headless';\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<SurfaceVariant | 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?: SurfaceVariant;\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' | 'glass' | '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/glass/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<SurfaceVariant>(),\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: (_action, 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 || 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`<slot></slot>`;\n },\n\n styles: [styles],\n});\n"],"mappings":";;;;;;AAeA,IAAa,IAAgB,EAAgC,kBAAkB,GAsDlE,IAAgB;AAC7B,EAA0B,GAAe;CACvC,OAAO;EACL,eAAe,EAAK,OAA8B;EAClD,MAAM,EAAK,OAAsB;EACjC,SAAS,EAAK,OAAuB;CACvC;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,GAAS,MAAU;IAG9B,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,CAAC,KAAgB,EAAa,cAAc,wBAG5C,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;CACb;CAEA,QAAQ,CAAC,CAAM;AACjB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button-group.cjs","names":[],"sources":["../src/inputs/button-group/button-group.ts"],"sourcesContent":["import { createContext, define, html, prop, provide } from '@vielzeug/ore';\nimport { type Readable } from '@vielzeug/ripple';\n\nimport type { ComponentSize, ThemeColor } from '../../shared';\nimport type { ButtonVariant } from '../button/button';\n\nimport { sizableBundle, themableBundle } from '../../shared';\nimport
|
|
1
|
+
{"version":3,"file":"button-group.cjs","names":[],"sources":["../src/inputs/button-group/button-group.ts"],"sourcesContent":["import { createContext, define, html, prop, provide } from '@vielzeug/ore';\nimport { type Readable } from '@vielzeug/ripple';\n\nimport type { ComponentSize, ThemeColor } from '../../shared';\nimport type { ButtonVariant } from '../button/button';\n\nimport { sizableBundle, themableBundle } from '../../shared';\nimport componentStyles from './button-group.css?inline';\n\n/** Button group properties */\nexport type OreButtonGroupProps = {\n /** Join buttons together into a single unit */\n attached?: boolean;\n /** Theme color tint for all child buttons */\n color?: ThemeColor;\n /** Group children span full width */\n fullwidth?: boolean;\n /** Label for screen readers */\n label?: string;\n /** Layout direction */\n orientation?: 'horizontal' | 'vertical';\n /** Shared size for all child buttons */\n size?: ComponentSize;\n /** Shared visual variant for all child buttons */\n variant?: ButtonVariant;\n};\n\n/** Shared context propagated from ore-button-group to child ore-button elements */\nexport type ButtonGroupContext = {\n color: Readable<ThemeColor | undefined>;\n size: Readable<ComponentSize | undefined>;\n variant: Readable<ButtonVariant | undefined>;\n};\n\nexport const BUTTON_GROUP_CTX = createContext<ButtonGroupContext | undefined>('OreButtonGroup');\n\n/**\n * A container for grouping related buttons.\n * Child `ore-button` components automatically inherit the group's color, size, and variant.\n *\n * @element ore-button-group\n *\n * @attr {boolean} attached - Join buttons together into a unit\n * @attr {string} color - Shared color tint: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {boolean} fullwidth - Group spans full width\n * @attr {string} label - Accessible label\n * @attr {string} orientation - 'horizontal' | 'vertical'\n * @attr {string} size - Shared size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Shared visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'frost'\n *\n * @slot - Place ore-button elements here\n *\n * @cssprop --group-gap - Gap between buttons (non-attached mode)\n * @cssprop --group-radius - Border radius of the group container\n * @cssprop --button-radius - Passed through to child buttons to control corner radius\n * @cssprop --button-border-start - Passed through to suppress start borders in attached mode\n * @cssprop --button-border-top - Passed through to suppress top borders in vertical attached mode\n * @part group - Group container.\n * @example\n * ```html\n * <ore-button-group attached>\n * <ore-button variant=\"solid\" color=\"primary\">Save</ore-button>\n * <ore-button variant=\"solid\" color=\"primary\">Save & Continue</ore-button>\n * </ore-button-group>\n * <ore-button-group orientation=\"vertical\" size=\"sm\">\n * <ore-button>Top</ore-button>\n * <ore-button>Middle</ore-button>\n * <ore-button>Bottom</ore-button>\n * </ore-button-group>\n * ```\n */\nexport const BUTTON_GROUP_TAG = 'ore-button-group' as const;\ndefine<OreButtonGroupProps>(BUTTON_GROUP_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n attached: prop.bool(false),\n fullwidth: prop.bool(false),\n label: prop.string(),\n orientation: prop.string<'horizontal' | 'vertical'>(),\n variant: prop.string(),\n },\n setup(props) {\n provide(BUTTON_GROUP_CTX, {\n color: props.color!,\n size: props.size!,\n variant: props.variant!,\n });\n\n return html`\n <div class=\"button-group\" part=\"group\" role=\"group\" :aria-label=\"${props.label}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":"mOAkCA,IAAa,GAAA,EAAA,EAAA,cAAA,CAAiE,gBAAgB,EAqCjF,EAAmB,gCACJ,EAAkB,CAC5C,MAAO,CACL,GAAG,EAAA,eACH,GAAG,EAAA,cACH,SAAU,EAAA,KAAK,KAAK,EAAK,EACzB,UAAW,EAAA,KAAK,KAAK,EAAK,EAC1B,MAAO,EAAA,KAAK,OAAO,EACnB,YAAa,EAAA,KAAK,OAAkC,EACpD,QAAS,EAAA,KAAK,OAAO,CACvB,EACA,MAAM,EAAO,CAOX,OANA,EAAA,EAAA,QAAA,CAAQ,EAAkB,CACxB,MAAO,EAAM,MACb,KAAM,EAAM,KACZ,QAAS,EAAM,OACjB,CAAC,EAEM,EAAA,IAAI;yEAC0D,EAAM,MAAM;;;KAInF,EACA,OAAQ,CAAC,EAAA,OAAe,CAC1B,CAAC"}
|
package/dist/button-group.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button-group.js","names":[],"sources":["../src/inputs/button-group/button-group.ts"],"sourcesContent":["import { createContext, define, html, prop, provide } from '@vielzeug/ore';\nimport { type Readable } from '@vielzeug/ripple';\n\nimport type { ComponentSize, ThemeColor } from '../../shared';\nimport type { ButtonVariant } from '../button/button';\n\nimport { sizableBundle, themableBundle } from '../../shared';\nimport
|
|
1
|
+
{"version":3,"file":"button-group.js","names":[],"sources":["../src/inputs/button-group/button-group.ts"],"sourcesContent":["import { createContext, define, html, prop, provide } from '@vielzeug/ore';\nimport { type Readable } from '@vielzeug/ripple';\n\nimport type { ComponentSize, ThemeColor } from '../../shared';\nimport type { ButtonVariant } from '../button/button';\n\nimport { sizableBundle, themableBundle } from '../../shared';\nimport componentStyles from './button-group.css?inline';\n\n/** Button group properties */\nexport type OreButtonGroupProps = {\n /** Join buttons together into a single unit */\n attached?: boolean;\n /** Theme color tint for all child buttons */\n color?: ThemeColor;\n /** Group children span full width */\n fullwidth?: boolean;\n /** Label for screen readers */\n label?: string;\n /** Layout direction */\n orientation?: 'horizontal' | 'vertical';\n /** Shared size for all child buttons */\n size?: ComponentSize;\n /** Shared visual variant for all child buttons */\n variant?: ButtonVariant;\n};\n\n/** Shared context propagated from ore-button-group to child ore-button elements */\nexport type ButtonGroupContext = {\n color: Readable<ThemeColor | undefined>;\n size: Readable<ComponentSize | undefined>;\n variant: Readable<ButtonVariant | undefined>;\n};\n\nexport const BUTTON_GROUP_CTX = createContext<ButtonGroupContext | undefined>('OreButtonGroup');\n\n/**\n * A container for grouping related buttons.\n * Child `ore-button` components automatically inherit the group's color, size, and variant.\n *\n * @element ore-button-group\n *\n * @attr {boolean} attached - Join buttons together into a unit\n * @attr {string} color - Shared color tint: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {boolean} fullwidth - Group spans full width\n * @attr {string} label - Accessible label\n * @attr {string} orientation - 'horizontal' | 'vertical'\n * @attr {string} size - Shared size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Shared visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'frost'\n *\n * @slot - Place ore-button elements here\n *\n * @cssprop --group-gap - Gap between buttons (non-attached mode)\n * @cssprop --group-radius - Border radius of the group container\n * @cssprop --button-radius - Passed through to child buttons to control corner radius\n * @cssprop --button-border-start - Passed through to suppress start borders in attached mode\n * @cssprop --button-border-top - Passed through to suppress top borders in vertical attached mode\n * @part group - Group container.\n * @example\n * ```html\n * <ore-button-group attached>\n * <ore-button variant=\"solid\" color=\"primary\">Save</ore-button>\n * <ore-button variant=\"solid\" color=\"primary\">Save & Continue</ore-button>\n * </ore-button-group>\n * <ore-button-group orientation=\"vertical\" size=\"sm\">\n * <ore-button>Top</ore-button>\n * <ore-button>Middle</ore-button>\n * <ore-button>Bottom</ore-button>\n * </ore-button-group>\n * ```\n */\nexport const BUTTON_GROUP_TAG = 'ore-button-group' as const;\ndefine<OreButtonGroupProps>(BUTTON_GROUP_TAG, {\n props: {\n ...themableBundle,\n ...sizableBundle,\n attached: prop.bool(false),\n fullwidth: prop.bool(false),\n label: prop.string(),\n orientation: prop.string<'horizontal' | 'vertical'>(),\n variant: prop.string(),\n },\n setup(props) {\n provide(BUTTON_GROUP_CTX, {\n color: props.color!,\n size: props.size!,\n variant: props.variant!,\n });\n\n return html`\n <div class=\"button-group\" part=\"group\" role=\"group\" :aria-label=\"${props.label}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles],\n});\n"],"mappings":";;;;;AAkCA,IAAa,IAAmB,EAA8C,gBAAgB,GAqCjF,IAAmB;AAChC,EAA4B,GAAkB;CAC5C,OAAO;EACL,GAAG;EACH,GAAG;EACH,UAAU,EAAK,KAAK,EAAK;EACzB,WAAW,EAAK,KAAK,EAAK;EAC1B,OAAO,EAAK,OAAO;EACnB,aAAa,EAAK,OAAkC;EACpD,SAAS,EAAK,OAAO;CACvB;CACA,MAAM,GAAO;EAOX,OANA,EAAQ,GAAkB;GACxB,OAAO,EAAM;GACb,MAAM,EAAM;GACZ,SAAS,EAAM;EACjB,CAAC,GAEM,CAAI;yEAC0D,EAAM,MAAM;;;;CAInF;CACA,QAAQ,CAAC,CAAe;AAC1B,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/shape.css.cjs"),n=require("./styles/mixins/states.css.cjs"),r=require("./styles/mixins/theme.css.cjs"),i=require("./styles/mixins/animation.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"),p=require("@vielzeug/ore/forms");var m=[`solid`,`flat`,`bordered`,`outline`,`ghost`,`text`,`frost`],h=`ore-button`;(0,d.define)(h,{formAssociated:!0,props:{...o.commonProps,effect:d.prop.string(),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(m,`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:m}=s.useLinkProps(e.href,e.rel,e.target),h=(0,p.useField)({disabled:o,toFormValue:()=>null,value:(0,f.computed)(()=>``)}),g=n=>{if(o.value){n.preventDefault();return}l.useFormAction(()=>m.value?null:h.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`)??(m.value?`link`:`button`)),size:i,tabindex:(0,f.computed)(()=>o.value?`-1`:`0`),variant:a},on:{keydown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),(t.shadowRoot?.querySelector(`[part="button"]`))?.click())}}});let _=d.html`
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/shape.css.cjs"),n=require("./styles/mixins/states.css.cjs"),r=require("./styles/mixins/theme.css.cjs"),i=require("./styles/mixins/animation.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"),p=require("@vielzeug/ore/forms");var m=[`solid`,`flat`,`bordered`,`outline`,`ghost`,`text`,`frost`],h=`ore-button`;(0,d.define)(h,{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(m,`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:m}=s.useLinkProps(e.href,e.rel,e.target),h=(0,p.useField)({disabled:o,toFormValue:()=>null,value:(0,f.computed)(()=>``)}),g=n=>{if(o.value){n.preventDefault();return}l.useFormAction(()=>m.value?null:h.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`)??(m.value?`link`:`button`)),size:i,tabindex:(0,f.computed)(()=>o.value?`-1`:`0`),variant:a},on:{keydown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),(t.shadowRoot?.querySelector(`[part="button"]`))?.click())}}});let _=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>
|
package/dist/button.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.cjs","names":[],"sources":["../src/inputs/button/button.ts"],"sourcesContent":["import { define, html, inject, prop, bind, getHost } from '@vielzeug/ore';\nimport { useField } from '@vielzeug/ore/forms';\nimport { computed } from '@vielzeug/ripple';\n\nimport type { ButtonType, ComponentSize, LinkTarget, RoundedSize, ThemeColor } from '../../types';\n\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 { 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 /** 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 *\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 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`<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 : html`<span part=\"button\" role=\"presentation\" @click=\"${handleClick}\">${buttonContent}</span>`}\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 { define, html, inject, prop, bind, getHost } from '@vielzeug/ore';\nimport { useField } from '@vielzeug/ore/forms';\nimport { computed } from '@vielzeug/ripple';\n\nimport type { ButtonType, ComponentSize, LinkTarget, RoundedSize, ThemeColor } from '../../types';\n\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 { 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`<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 : html`<span part=\"button\" role=\"presentation\" @click=\"${handleClick}\">${buttonContent}</span>`}\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
package/dist/button.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.js","names":[],"sources":["../src/inputs/button/button.ts"],"sourcesContent":["import { define, html, inject, prop, bind, getHost } from '@vielzeug/ore';\nimport { useField } from '@vielzeug/ore/forms';\nimport { computed } from '@vielzeug/ripple';\n\nimport type { ButtonType, ComponentSize, LinkTarget, RoundedSize, ThemeColor } from '../../types';\n\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 { 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 /** 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 *\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 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`<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 : html`<span part=\"button\" role=\"presentation\" @click=\"${handleClick}\">${buttonContent}</span>`}\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":";;;;;;;;;;;;;;;AAwBA,IAAa,IAAkB;CAAC;CAAS;CAAQ;CAAY;CAAW;CAAS;CAAQ;AAAO,GAsFnF,IAAa;AAC1B,EAAuB,GAAY;CACjC,gBAAgB;CAChB,OAAO;EACL,GAAG;EACH,QAAQ,EAAK,OAAqB;EAClC,WAAW,EAAK,KAAK,EAAK;EAC1B,MAAM,EAAK,OAAO;EAClB,UAAU,EAAK,KAAK,EAAK;EACzB,OAAO,EAAK,OAAO;EACnB,KAAK,EAAK,OAAO;EACjB,QAAQ,EAAK,OAAmB;EAChC,MAAM,EAAK,MAAM;GAAC;GAAU;GAAU;EAAO,GAAY,QAAQ;EACjE,SAAS,EAAK,MAAM,GAAiB,OAAO;CAC9C;CACA,MAAM,GAAO;EACX,IAAM,IAAK,EAAQ,GAGb,IAAW,EAAO,CAAgB,GAClC,IAAiB,QAAe,GAAU,MAAM,SAAS,EAAM,MAAM,KAAK,GAC1E,IAAgB,QAAe,GAAU,KAAK,SAAS,EAAM,KAAK,KAAK,GACvE,IAAmB,QAAe,GAAU,QAAQ,SAAS,EAAM,QAAQ,KAAK,GAEhF,IAAa,QAAe,CAAC,EAAE,EAAM,SAAS,SAAS,EAAM,QAAQ,MAAM,GAG3E,EAAE,iBAAc,cAAW,EAAa,EAAM,MAAM,EAAM,KAAK,EAAM,MAAM,GAK3E,IAAY,EAAS;GACzB,UAAU;GACV,mBAAmB;GACnB,OAAO,QAAe,EAAE;EAC1B,CAAC,GAEK,KAAe,MAAkB;GACrC,IAAI,EAAW,OAAO;IACpB,EAAE,eAAe;IAEjB;GACF;GAIA,QAAqB,EAAO,QAAQ,OAAO,EAAU,UAAU,MAAO,EAAM,MAAM,GAAY,CAAE,CAAC,CAAC,CAAC;EACrG;EAUA,EAAK;GACH,MAAM;IACJ,aAAa,EAAM;IACnB,iBAAiB;IACjB,cAAc,EAAM;IACpB,OAAO;IACP,QAAQ,EAAM;IACd,MAAM,QAAe,EAAG,aAAa,MAAM,MAAM,EAAO,QAAQ,SAAS,SAAS;IAClF,MAAM;IACN,UAAU,QAAgB,EAAW,QAAQ,OAAO,GAAI;IACxD,SAAS;GACX;GACA,IAAI,EACF,UArBmB,MAAqB;IAC1C,CAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,eAAe,IAChB,EAAG,YAAY,cAAc,mBAAiB,EAAA,EAAmB,MAAM;GAE5E,EAiBE;EACF,CAAC;EAED,IAAM,IAAgB,CAAI;8EACgD,CAAC,EAAM,QAAQ,MAAM;;;;;EAY/F,OAAO,CAAI;cAEP,EAAO,QACH,CAAI;;;;sBAIM,EAAM,KAAK;sBACX,EAAa;yBACV,EAAM,OAAO;wBACd,EAAY;gBACpB,EAAc;oBAElB,CAAI,mDAAmD,EAAY,IAAI,EAAc,SAAS;;CAExG;CACA,QAAQ,EAAE,gBAAgB,GAAM;CAChC,QAAQ;EACN;EACA;EACA;EACA;EACA;EACA,EAAiB;GACf,IAAI;IACF,UAAU;IACV,KAAK;IACL,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,SAAS;GACX;GACA,IAAI;IACF,UAAU;IACV,KAAK;IACL,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,SAAS;GACX;EACF,CAAC;EACD,EAAkB,mBAAiB;EACnC,EAAmB,mBAAiB;EACpC,EAAiB,mBAAiB;EAClC;EACA;CACF;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"button.js","names":[],"sources":["../src/inputs/button/button.ts"],"sourcesContent":["import { define, html, inject, prop, bind, getHost } from '@vielzeug/ore';\nimport { useField } from '@vielzeug/ore/forms';\nimport { computed } from '@vielzeug/ripple';\n\nimport type { ButtonType, ComponentSize, LinkTarget, RoundedSize, ThemeColor } from '../../types';\n\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 { 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`<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 : html`<span part=\"button\" role=\"presentation\" @click=\"${handleClick}\">${buttonContent}</span>`}\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":";;;;;;;;;;;;;;;AAwBA,IAAa,IAAkB;CAAC;CAAS;CAAQ;CAAY;CAAW;CAAS;CAAQ;AAAO,GA8FnF,IAAa;AAC1B,EAAuB,GAAY;CACjC,gBAAgB;CAChB,OAAO;EACL,GAAG;EACH,QAAQ,EAAK,OAAqB;EAClC,YAAY,EAAK,KAAK,EAAK;EAC3B,WAAW,EAAK,KAAK,EAAK;EAC1B,MAAM,EAAK,OAAO;EAClB,UAAU,EAAK,KAAK,EAAK;EACzB,OAAO,EAAK,OAAO;EACnB,KAAK,EAAK,OAAO;EACjB,QAAQ,EAAK,OAAmB;EAChC,MAAM,EAAK,MAAM;GAAC;GAAU;GAAU;EAAO,GAAY,QAAQ;EACjE,SAAS,EAAK,MAAM,GAAiB,OAAO;CAC9C;CACA,MAAM,GAAO;EACX,IAAM,IAAK,EAAQ,GAGb,IAAW,EAAO,CAAgB,GAClC,IAAiB,QAAe,GAAU,MAAM,SAAS,EAAM,MAAM,KAAK,GAC1E,IAAgB,QAAe,GAAU,KAAK,SAAS,EAAM,KAAK,KAAK,GACvE,IAAmB,QAAe,GAAU,QAAQ,SAAS,EAAM,QAAQ,KAAK,GAEhF,IAAa,QAAe,CAAC,EAAE,EAAM,SAAS,SAAS,EAAM,QAAQ,MAAM,GAG3E,EAAE,iBAAc,cAAW,EAAa,EAAM,MAAM,EAAM,KAAK,EAAM,MAAM,GAK3E,IAAY,EAAS;GACzB,UAAU;GACV,mBAAmB;GACnB,OAAO,QAAe,EAAE;EAC1B,CAAC,GAEK,KAAe,MAAkB;GACrC,IAAI,EAAW,OAAO;IACpB,EAAE,eAAe;IAEjB;GACF;GAIA,QAAqB,EAAO,QAAQ,OAAO,EAAU,UAAU,MAAO,EAAM,MAAM,GAAY,CAAE,CAAC,CAAC,CAAC;EACrG;EAUA,EAAK;GACH,MAAM;IACJ,aAAa,EAAM;IACnB,iBAAiB;IACjB,cAAc,EAAM;IACpB,OAAO;IACP,QAAQ,EAAM;IACd,MAAM,QAAe,EAAG,aAAa,MAAM,MAAM,EAAO,QAAQ,SAAS,SAAS;IAClF,MAAM;IACN,UAAU,QAAgB,EAAW,QAAQ,OAAO,GAAI;IACxD,SAAS;GACX;GACA,IAAI,EACF,UArBmB,MAAqB;IAC1C,CAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,eAAe,IAChB,EAAG,YAAY,cAAc,mBAAiB,EAAA,EAAmB,MAAM;GAE5E,EAiBE;EACF,CAAC;EAED,IAAM,IAAgB,CAAI;8EACgD,CAAC,EAAM,QAAQ,MAAM;;;;;EAY/F,OAAO,CAAI;cAEP,EAAO,QACH,CAAI;;;;sBAIM,EAAM,KAAK;sBACX,EAAa;yBACV,EAAM,OAAO;wBACd,EAAY;gBACpB,EAAc;oBAElB,CAAI,mDAAmD,EAAY,IAAI,EAAc,SAAS;;CAExG;CACA,QAAQ,EAAE,gBAAgB,GAAM;CAChC,QAAQ;EACN;EACA;EACA;EACA;EACA;EACA,EAAiB;GACf,IAAI;IACF,UAAU;IACV,KAAK;IACL,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,SAAS;GACX;GACA,IAAI;IACF,UAAU;IACV,KAAK;IACL,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,SAAS;GACX;EACF,CAAC;EACD,EAAkB,mBAAiB;EACnC,EAAmB,mBAAiB;EACpC,EAAiB,mBAAiB;EAClC;EACA;CACF;AACF,CAAC"}
|
package/dist/checkbox-group.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/states.css.cjs"),t=require("./shared/prop-bundles.cjs"),n=require("./headless/ore.cjs"),r=require("./headless/choice-field.cjs"),i=require("./headless/light-dom.cjs"),a=require("./inputs/shared/form-context.cjs"),o=require("./inputs/checkbox-group/checkbox-group.css.cjs");let s=require("@vielzeug/ore"),c=require("@vielzeug/ore/directives"),l=require("@vielzeug/ripple"),u=require("@vielzeug/ore/forms");var d=(0,s.createContext)(`CheckboxGroupContext`),f=`ore-checkbox-group`;(0,s.define)(f,{formAssociated:!0,props:{...t.themableBundle,...t.sizableBundle,...t.disablableBundle,error:s.prop.string(),helper:s.prop.string(),label:s.prop.string(),name:s.prop.string(),orientation:s.prop.string(`vertical`),required:s.prop.bool(!1),values:{...s.prop.string(),reflect:!1}},setup(e){let t=(0,s.getHost)(),o=(0,s.useEmit)(),f=(0,s.useSlots)(),p=s.watchEffect,m=(0,s.inject)(a.FORM_CTX),h=a.useFormContext(e,m),g=r.createChoiceField({disabled:h.disabled,error:e.error,helper:e.helper,multiple:(0,l.signal)(!0),prefix:`checkbox-group`,required:e.required,signal:n.lifecycleSignal(s.onCleanup),validateOn:m?.validateOn,value:e.values});g.attachFormField((0,u.useField)({disabled:g.disabled,onReset:g.reset,toFormValue:e=>e,validationMessage:g.validationMessage,validity:g.validity,value:g.formValue}));let _=g.selectedValues,v=()=>i.getLightChildrenByTag(t,`ore-checkbox`),y=e=>i.getChoiceLabel(v(),e),b=e=>{let t=_.value,n=t.map(y);o(`change`,{labels:n,originalEvent:e,values:t})},x=(e,t)=>{g.toggleValue(e),g.triggerValidation(`change`),b(t)};(0,s.provide)(d,{color:e.color,disabled:(0,l.computed)(()=>!!e.disabled.value),size:e.size,toggle:x,values:_});let S=()=>{let t=_.value,n=e.color.value,r=e.size.value,i=e.disabled.value,a=v();for(let e of a){let a=e.getAttribute(`value`)??``;e.toggleAttribute(`checked`,t.includes(a)),n?e.setAttribute(`color`,n):e.removeAttribute(`color`),r?e.setAttribute(`size`,r):e.removeAttribute(`size`),e.toggleAttribute(`disabled`,!!i)}};p(()=>{f.elements().value,S()}),p(()=>{f.elements().value;let e=v().map(e=>{let t=t=>{t.stopPropagation();let n=(e.getAttribute(`value`)??``).trim();n&&x(n,t)};return e.addEventListener(`change`,t),()=>{e.removeEventListener(`change`,t)}});return()=>{for(let t of e)t()}});let C=(0,s.createStableId)(`checkbox-group-legend`),w=`${C}-error`,T=`${C}-helper`,E=()=>!!e.error.value,D=()=>!!e.helper.value&&!E();return(0,s.bind)({attr:{size:h.size,values:g.formValue}}),s.html`
|
|
2
2
|
<fieldset
|
|
3
3
|
role="group"
|
|
4
4
|
aria-required="${()=>String(!!e.required.value)}"
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
6
6
|
aria-errormessage="${()=>E()?w:null}"
|
|
7
7
|
aria-describedby="${()=>E()?w:D()?T:null}">
|
|
8
8
|
<legend id="${C}" ?hidden=${()=>!e.label.value}>
|
|
9
|
-
${e.label}${(0,
|
|
9
|
+
${e.label}${(0,c.when)(()=>!!e.required.value,()=>s.html`<span aria-hidden="true"> *</span>`)}
|
|
10
10
|
</legend>
|
|
11
11
|
<div class="checkbox-group-items" part="items">
|
|
12
12
|
<slot></slot>
|
|
@@ -14,5 +14,5 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
14
14
|
<div class="error-text" id="${w}" role="alert" ?hidden=${()=>!E()}>${e.error}</div>
|
|
15
15
|
<div class="helper-text" id="${T}" ?hidden=${()=>!D()}>${e.helper}</div>
|
|
16
16
|
</fieldset>
|
|
17
|
-
`},styles:[
|
|
17
|
+
`},styles:[e.disabledStateMixin,o.default]}),exports.CHECKBOX_GROUP_CTX=d,exports.CHECKBOX_GROUP_TAG=f;
|
|
18
18
|
//# sourceMappingURL=checkbox-group.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox-group.cjs","names":[],"sources":["../src/inputs/checkbox-group/checkbox-group.ts"],"sourcesContent":["import {\n createContext,\n createStableId,\n define,\n html,\n inject,\n prop,\n bind,\n getHost,\n onCleanup,\n provide,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { when } from '@vielzeug/ore/directives';\nimport { useField } from '@vielzeug/ore/forms';\nimport { computed, type Readable, signal } from '@vielzeug/ripple';\n\nimport type { ComponentSize, ThemeColor } from '../../types';\n\nimport {\n type ChoiceChangeDetail,\n lifecycleSignal,\n createChoiceField,\n getChoiceLabel,\n getLightChildrenByTag,\n} from '../../headless';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin, disabledStateMixin, sizeVariantMixin } from '../../styles';\nimport { FORM_CTX, useFormContext } from '../shared/form-context';\nimport componentStyles from './checkbox-group.css?inline';\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport type CheckboxGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n size: Readable<ComponentSize | undefined>;\n toggle: (value: string, originalEvent?: Event) => void;\n values: Readable<string[]>;\n};\n\nexport const CHECKBOX_GROUP_CTX = createContext<CheckboxGroupContext>('CheckboxGroupContext');\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type OreCheckboxGroupProps = {\n /** Theme color — propagated to all child ore-checkbox elements */\n color?: ThemeColor;\n /** Disable all checkboxes in the group */\n disabled?: boolean;\n /** Error message shown below the group */\n error?: string;\n /** Helper text shown below the group */\n helper?: string;\n /** Legend / label for the fieldset. Required for accessibility. */\n label?: string;\n /** Form field name used during submission */\n name?: string;\n /** Layout direction of the checkbox options */\n orientation?: 'vertical' | 'horizontal';\n /** Mark the group as required */\n required?: boolean;\n /** Size — propagated to all child ore-checkbox elements */\n size?: ComponentSize;\n /** Comma-separated list of currently checked values */\n values?: string;\n};\n\nexport type OreCheckboxGroupEvents = {\n change: ChoiceChangeDetail;\n};\n\n/**\n * A fieldset wrapper that groups `ore-checkbox` elements, provides shared\n * `color` and `size` via context, and manages multi-value selection state.\n *\n * @element ore-checkbox-group\n *\n * @attr {string} label - Legend text (required for a11y)\n * @attr {string} values - Comma-separated list of checked values\n * @attr {boolean} disabled - Disable all checkboxes in the group\n * @attr {string} error - Error message\n * @attr {string} helper - Helper text\n * @attr {string} name - Form field name\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Component size: 'sm' | 'md' | 'lg'\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when selection changes. detail: { value: string, values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-checkbox` elements here\n *\n * @cssprop --checkbox-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --checkbox-group-gap - Gap between checkbox items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-checkbox-group name=\"fruits\" label=\"Favourite fruits\" required>\n * <ore-checkbox value=\"apple\">Apple</ore-checkbox>\n * <ore-checkbox value=\"banana\">Banana</ore-checkbox>\n * <ore-checkbox value=\"cherry\">Cherry</ore-checkbox>\n * </ore-checkbox-group>\n * <ore-checkbox-group name=\"options\" orientation=\"horizontal\" color=\"primary\">\n * <ore-checkbox value=\"a\">Option A</ore-checkbox>\n * <ore-checkbox value=\"b\">Option B</ore-checkbox>\n * </ore-checkbox-group>\n * ```\n */\nexport const CHECKBOX_GROUP_TAG = 'ore-checkbox-group' as const;\ndefine<OreCheckboxGroupProps>(CHECKBOX_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.string('vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n // (`choice.formValue`) is the single writer for this attribute, via `bind()` below;\n // letting `prop.string()`'s own default reflection also write the raw incoming value\n // would leave two effects racing to set the same attribute from different sources.\n values: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreCheckboxGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const formCtx = inject(FORM_CTX);\n const fCtxProps = useFormContext(props, formCtx);\n\n const choice = createChoiceField({\n disabled: fCtxProps.disabled,\n error: props.error,\n helper: props.helper,\n multiple: signal(true),\n prefix: 'checkbox-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n validateOn: formCtx?.validateOn,\n value: props.values,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const checkedValues = choice.selectedValues;\n\n const getCheckboxes = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-checkbox');\n const getLabelForValue = (value: string): string => getChoiceLabel(getCheckboxes(), value);\n const emitChange = (originalEvent?: Event) => {\n const values = checkedValues.value;\n\n const labels = values.map(getLabelForValue);\n\n emit('change', { labels, originalEvent, values });\n };\n\n const toggleCheckbox = (val: string, originalEvent?: Event) => {\n choice.toggleValue(val);\n choice.triggerValidation('change');\n emitChange(originalEvent);\n };\n\n provide(CHECKBOX_GROUP_CTX, {\n color: props.color,\n disabled: computed(() => Boolean(props.disabled.value)),\n size: props.size,\n toggle: toggleCheckbox,\n values: checkedValues,\n });\n\n // Sync checked state + color/size/disabled onto slotted ore-checkbox children\n const syncChildren = () => {\n const values = checkedValues.value;\n const color = props.color.value;\n const size = props.size.value;\n const disabled = props.disabled.value;\n const checkboxes = getCheckboxes();\n\n for (const checkbox of checkboxes) {\n const val = checkbox.getAttribute('value') ?? '';\n\n checkbox.toggleAttribute('checked', values.includes(val));\n\n if (color) checkbox.setAttribute('color', color);\n else checkbox.removeAttribute('color');\n\n if (size) checkbox.setAttribute('size', size);\n else checkbox.removeAttribute('size');\n\n checkbox.toggleAttribute('disabled', Boolean(disabled));\n }\n };\n\n watch(() => {\n void slots.elements().value;\n syncChildren();\n });\n\n watch(() => {\n void slots.elements().value;\n\n const listeners = getCheckboxes().map((checkbox) => {\n const handler = (event: Event) => {\n event.stopPropagation();\n\n const val = (checkbox.getAttribute('value') ?? '').trim();\n\n if (!val) return;\n\n toggleCheckbox(val, event);\n };\n\n checkbox.addEventListener('change', handler);\n\n return () => {\n checkbox.removeEventListener('change', handler);\n };\n });\n\n return () => {\n for (const dispose of listeners) dispose();\n };\n });\n\n const legendId = createStableId('checkbox-group-legend');\n const errorId = `${legendId}-error`;\n const helperId = `${legendId}-helper`;\n const hasError = () => Boolean(props.error.value);\n const hasHelper = () => Boolean(props.helper.value) && !hasError();\n\n // Reactive, not a one-off `el.setAttribute()` inside the click handler: the host's `values`\n // attribute must stay in sync with the selection regardless of *why* it changed (a click,\n // or `reset()` on ancestor form reset — neither of which should need its own copy of this).\n bind({ attr: { size: fCtxProps.size, values: choice.formValue } });\n\n return html`\n <fieldset\n role=\"group\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${() => String(hasError())}\"\n aria-errormessage=\"${() => (hasError() ? errorId : null)}\"\n aria-describedby=\"${() => (hasError() ? errorId : hasHelper() ? helperId : null)}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`<span aria-hidden=\"true\"> *</span>`,\n )}\n </legend>\n <div class=\"checkbox-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div class=\"error-text\" id=\"${errorId}\" role=\"alert\" ?hidden=${() => !hasError()}>${props.error}</div>\n <div class=\"helper-text\" id=\"${helperId}\" ?hidden=${() => !hasHelper()}>${props.helper}</div>\n </fieldset>\n `;\n },\n styles: [colorThemeMixin, sizeVariantMixin(), disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checkbox-group.cjs","names":[],"sources":["../src/inputs/checkbox-group/checkbox-group.ts"],"sourcesContent":["import {\n createContext,\n createStableId,\n define,\n html,\n inject,\n prop,\n bind,\n getHost,\n onCleanup,\n provide,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { when } from '@vielzeug/ore/directives';\nimport { useField } from '@vielzeug/ore/forms';\nimport { computed, type Readable, signal } from '@vielzeug/ripple';\n\nimport type { ComponentSize, ThemeColor } from '../../types';\n\nimport {\n type ChoiceChangeDetail,\n lifecycleSignal,\n createChoiceField,\n getChoiceLabel,\n getLightChildrenByTag,\n} from '../../headless';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport { FORM_CTX, useFormContext } from '../shared/form-context';\nimport componentStyles from './checkbox-group.css?inline';\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\nexport type CheckboxGroupContext = {\n color: Readable<ThemeColor | undefined>;\n disabled: Readable<boolean>;\n size: Readable<ComponentSize | undefined>;\n toggle: (value: string, originalEvent?: Event) => void;\n values: Readable<string[]>;\n};\n\nexport const CHECKBOX_GROUP_CTX = createContext<CheckboxGroupContext>('CheckboxGroupContext');\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type OreCheckboxGroupProps = {\n /** Theme color — propagated to all child ore-checkbox elements */\n color?: ThemeColor;\n /** Disable all checkboxes in the group */\n disabled?: boolean;\n /** Error message shown below the group */\n error?: string;\n /** Helper text shown below the group */\n helper?: string;\n /** Legend / label for the fieldset. Required for accessibility. */\n label?: string;\n /** Form field name used during submission */\n name?: string;\n /** Layout direction of the checkbox options */\n orientation?: 'vertical' | 'horizontal';\n /** Mark the group as required */\n required?: boolean;\n /** Size — propagated to all child ore-checkbox elements */\n size?: ComponentSize;\n /** Comma-separated list of currently checked values */\n values?: string;\n};\n\nexport type OreCheckboxGroupEvents = {\n change: ChoiceChangeDetail;\n};\n\n/**\n * A fieldset wrapper that groups `ore-checkbox` elements, provides shared\n * `color` and `size` via context, and manages multi-value selection state.\n *\n * @element ore-checkbox-group\n *\n * @attr {string} label - Legend text (required for a11y)\n * @attr {string} values - Comma-separated list of checked values\n * @attr {boolean} disabled - Disable all checkboxes in the group\n * @attr {string} error - Error message\n * @attr {string} helper - Helper text\n * @attr {string} name - Form field name\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Component size: 'sm' | 'md' | 'lg'\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when selection changes. detail: { value: string, values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-checkbox` elements here\n *\n * @cssprop --checkbox-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --checkbox-group-gap - Gap between checkbox items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-checkbox-group name=\"fruits\" label=\"Favourite fruits\" required>\n * <ore-checkbox value=\"apple\">Apple</ore-checkbox>\n * <ore-checkbox value=\"banana\">Banana</ore-checkbox>\n * <ore-checkbox value=\"cherry\">Cherry</ore-checkbox>\n * </ore-checkbox-group>\n * <ore-checkbox-group name=\"options\" orientation=\"horizontal\" color=\"primary\">\n * <ore-checkbox value=\"a\">Option A</ore-checkbox>\n * <ore-checkbox value=\"b\">Option B</ore-checkbox>\n * </ore-checkbox-group>\n * ```\n */\nexport const CHECKBOX_GROUP_TAG = 'ore-checkbox-group' as const;\ndefine<OreCheckboxGroupProps>(CHECKBOX_GROUP_TAG, {\n formAssociated: true,\n props: {\n ...themableBundle,\n ...sizableBundle,\n ...disablableBundle,\n error: prop.string(),\n helper: prop.string(),\n label: prop.string(),\n name: prop.string(),\n orientation: prop.string('vertical'),\n required: prop.bool(false),\n // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n // (`choice.formValue`) is the single writer for this attribute, via `bind()` below;\n // letting `prop.string()`'s own default reflection also write the raw incoming value\n // would leave two effects racing to set the same attribute from different sources.\n values: { ...prop.string(), reflect: false },\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreCheckboxGroupEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const formCtx = inject(FORM_CTX);\n const fCtxProps = useFormContext(props, formCtx);\n\n const choice = createChoiceField({\n disabled: fCtxProps.disabled,\n error: props.error,\n helper: props.helper,\n multiple: signal(true),\n prefix: 'checkbox-group',\n required: props.required,\n signal: lifecycleSignal(onCleanup),\n validateOn: formCtx?.validateOn,\n value: props.values,\n });\n\n choice.attachFormField(\n useField<string>({\n disabled: choice.disabled,\n onReset: choice.reset,\n toFormValue: (v) => v,\n validationMessage: choice.validationMessage,\n validity: choice.validity,\n value: choice.formValue,\n }),\n );\n\n const checkedValues = choice.selectedValues;\n\n const getCheckboxes = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-checkbox');\n const getLabelForValue = (value: string): string => getChoiceLabel(getCheckboxes(), value);\n const emitChange = (originalEvent?: Event) => {\n const values = checkedValues.value;\n\n const labels = values.map(getLabelForValue);\n\n emit('change', { labels, originalEvent, values });\n };\n\n const toggleCheckbox = (val: string, originalEvent?: Event) => {\n choice.toggleValue(val);\n choice.triggerValidation('change');\n emitChange(originalEvent);\n };\n\n provide(CHECKBOX_GROUP_CTX, {\n color: props.color,\n disabled: computed(() => Boolean(props.disabled.value)),\n size: props.size,\n toggle: toggleCheckbox,\n values: checkedValues,\n });\n\n // Sync checked state + color/size/disabled onto slotted ore-checkbox children\n const syncChildren = () => {\n const values = checkedValues.value;\n const color = props.color.value;\n const size = props.size.value;\n const disabled = props.disabled.value;\n const checkboxes = getCheckboxes();\n\n for (const checkbox of checkboxes) {\n const val = checkbox.getAttribute('value') ?? '';\n\n checkbox.toggleAttribute('checked', values.includes(val));\n\n if (color) checkbox.setAttribute('color', color);\n else checkbox.removeAttribute('color');\n\n if (size) checkbox.setAttribute('size', size);\n else checkbox.removeAttribute('size');\n\n checkbox.toggleAttribute('disabled', Boolean(disabled));\n }\n };\n\n watch(() => {\n void slots.elements().value;\n syncChildren();\n });\n\n watch(() => {\n void slots.elements().value;\n\n const listeners = getCheckboxes().map((checkbox) => {\n const handler = (event: Event) => {\n event.stopPropagation();\n\n const val = (checkbox.getAttribute('value') ?? '').trim();\n\n if (!val) return;\n\n toggleCheckbox(val, event);\n };\n\n checkbox.addEventListener('change', handler);\n\n return () => {\n checkbox.removeEventListener('change', handler);\n };\n });\n\n return () => {\n for (const dispose of listeners) dispose();\n };\n });\n\n const legendId = createStableId('checkbox-group-legend');\n const errorId = `${legendId}-error`;\n const helperId = `${legendId}-helper`;\n const hasError = () => Boolean(props.error.value);\n const hasHelper = () => Boolean(props.helper.value) && !hasError();\n\n // Reactive, not a one-off `el.setAttribute()` inside the click handler: the host's `values`\n // attribute must stay in sync with the selection regardless of *why* it changed (a click,\n // or `reset()` on ancestor form reset — neither of which should need its own copy of this).\n bind({ attr: { size: fCtxProps.size, values: choice.formValue } });\n\n return html`\n <fieldset\n role=\"group\"\n aria-required=\"${() => String(Boolean(props.required.value))}\"\n aria-invalid=\"${() => String(hasError())}\"\n aria-errormessage=\"${() => (hasError() ? errorId : null)}\"\n aria-describedby=\"${() => (hasError() ? errorId : hasHelper() ? helperId : null)}\">\n <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n ${props.label}${when(\n () => Boolean(props.required.value),\n () => html`<span aria-hidden=\"true\"> *</span>`,\n )}\n </legend>\n <div class=\"checkbox-group-items\" part=\"items\">\n <slot></slot>\n </div>\n <div class=\"error-text\" id=\"${errorId}\" role=\"alert\" ?hidden=${() => !hasError()}>${props.error}</div>\n <div class=\"helper-text\" id=\"${helperId}\" ?hidden=${() => !hasHelper()}>${props.helper}</div>\n </fieldset>\n `;\n },\n styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"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"}
|