@vielzeug/refine 1.5.4 → 1.8.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/async.cjs +11 -11
- package/dist/async.cjs.map +1 -1
- package/dist/async.js +1 -2
- package/dist/async.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/calendar.cjs +26 -26
- package/dist/calendar.cjs.map +1 -1
- package/dist/calendar.js +4 -5
- package/dist/calendar.js.map +1 -1
- package/dist/checkbox-group.cjs +9 -9
- package/dist/checkbox-group.cjs.map +1 -1
- package/dist/checkbox-group.js +50 -58
- package/dist/checkbox-group.js.map +1 -1
- package/dist/code-window.cjs +4 -4
- package/dist/code-window.cjs.map +1 -1
- package/dist/code-window.js +1 -2
- package/dist/code-window.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/command-palette.cjs +6 -6
- package/dist/command-palette.cjs.map +1 -1
- package/dist/command-palette.js +46 -47
- package/dist/command-palette.js.map +1 -1
- package/dist/content/card/card.css.cjs +1 -1
- package/dist/content/card/card.css.cjs.map +1 -1
- package/dist/content/card/card.css.js +1 -1
- package/dist/content/card/card.css.js.map +1 -1
- package/dist/content/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/code-window/code-window.d.ts.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 +5693 -5090
- package/dist/datagrid.cjs +2 -2
- package/dist/datagrid.js +206 -206
- package/dist/datagrid.js.map +1 -1
- 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/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/feedback/async/async.d.ts.map +1 -1
- package/dist/feedback/progress/progress.d.ts.map +1 -1
- package/dist/file-input.cjs +108 -42
- package/dist/file-input.cjs.map +1 -1
- package/dist/file-input.js +215 -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 +35 -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/calendar/calendar.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/progress.cjs +13 -13
- package/dist/progress.cjs.map +1 -1
- package/dist/progress.js +3 -4
- package/dist/progress.js.map +1 -1
- package/dist/radio-group.cjs +11 -11
- package/dist/radio-group.cjs.map +1 -1
- package/dist/radio-group.js +63 -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 +502 -309
- package/dist/refine.cjs.map +1 -1
- package/dist/refine.iife.js +562 -369
- package/dist/refine.iife.js.map +1 -1
- package/dist/refine.js +558 -365
- 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 +33 -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/testing/index.d.ts +15 -60
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +7 -24
- package/dist/testing.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 +22 -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/keymap/dist/_dev.cjs +0 -2
- package/dist/keymap/dist/_dev.cjs.map +0 -1
- package/dist/keymap/dist/_dev.js +0 -4
- package/dist/keymap/dist/_dev.js.map +0 -1
- package/dist/keymap/dist/format.cjs +0 -1
- package/dist/keymap/dist/format.js +0 -1
- package/dist/keymap/dist/index.cjs +0 -1
- package/dist/keymap/dist/index.js +0 -4
- package/dist/keymap/dist/keymap.cjs +0 -2
- package/dist/keymap/dist/keymap.cjs.map +0 -1
- package/dist/keymap/dist/keymap.js +0 -221
- package/dist/keymap/dist/keymap.js.map +0 -1
- package/dist/keymap/dist/layer.cjs +0 -1
- package/dist/keymap/dist/layer.js +0 -1
package/dist/menu.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/shape.css.cjs"),t=require("./styles/mixins/theme.css.cjs"),n=require("./shared/prop-bundles.cjs"),r=require("./headless/ore.cjs"),
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/shape.css.cjs"),t=require("./styles/mixins/theme.css.cjs"),n=require("./shared/prop-bundles.cjs"),r=require("./shared/size-presets.cjs"),i=require("./headless/ore.cjs"),a=require("./headless/keyboard.cjs"),o=require("./headless/option-list.cjs"),s=require("./overlay/menu/menu-item.css.cjs"),c=require("./overlay/menu/menu-separator.css.cjs"),l=require("./overlay/menu/menu.css.cjs");let u=require("@vielzeug/ore"),d=require("@vielzeug/ripple");var f=`ore-menu-item`;(0,u.define)(f,{props:{checked:u.prop.bool(!1),disabled:u.prop.bool(!1),type:u.prop.string(),value:u.prop.string()},setup(e){let t=()=>e.type.value===`checkbox`||e.type.value===`radio`,n=()=>t()&&e.checked.value;return t()?u.html`
|
|
2
2
|
<div
|
|
3
3
|
class="${()=>{let t=e.type.value;return[`item`,t===`checkbox`?`is-checkbox`:``,t===`radio`?`is-radio`:``,n()?`is-checked`:``].filter(Boolean).join(` `)}}"
|
|
4
4
|
tabindex="-1"
|
|
@@ -9,12 +9,12 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
9
9
|
<span class="icon-slot"><slot name="icon"></slot></span>
|
|
10
10
|
<span class="item-label"><slot></slot></span>
|
|
11
11
|
</div>
|
|
12
|
-
`:
|
|
12
|
+
`:u.html`
|
|
13
13
|
<div class="item" tabindex="-1" role="menuitem" aria-disabled="${e.disabled}">
|
|
14
14
|
<span class="icon-slot"><slot name="icon"></slot></span>
|
|
15
15
|
<span class="item-label"><slot></slot></span>
|
|
16
16
|
</div>
|
|
17
|
-
`},styles:[
|
|
17
|
+
`},styles:[t.colorThemeMixin,s.default]});var p=`ore-menu-separator`;(0,u.define)(p,{setup(){return u.html``},styles:[c.default]});var m=e=>e===`checkbox`||e===`radio`,h=`ore-menu`;(0,u.define)(h,{props:{...n.sizableBundle,...n.disablableBundle,placement:u.prop.oneOf([`bottom`,`bottom-start`,`bottom-end`,`top`,`top-start`,`top-end`],`bottom-start`)},setup(e){let t=(0,u.getHost)(),n=(0,u.useEmit)(),r=(0,u.useSlots)(),s=u.watchEffect,c=(0,u.createStableId)(`menu`),l=(0,d.computed)(()=>!!e.disabled.value),f=i.lifecycleSignal(u.onCleanup),p=null,h=null,g=null;function _(){return Array.from(t.querySelectorAll(`ore-menu-item:not([disabled])`))}function v(e){return e?e.shadowRoot?.querySelector(`[role^="menuitem"]`)??e:null}function y(){return _().findIndex(e=>{let t=v(e);return e===document.activeElement||t===document.activeElement})}let b=o.createOptionList({getBoundary:()=>t,getItems:_,getPanel:()=>h,getReference:()=>p,getTrigger:()=>p,isDisabled:()=>l.value,isItemDisabled:e=>e.hasAttribute(`disabled`),onClose:e=>n(`close`,{reason:e}),onNavigate:(e,t)=>{let n=_()[t];v(n)?.focus()},onOpen:e=>n(`open`,{reason:e}),positioning:{getPlacement:()=>e.placement.value??`bottom-start`,matchWidth:!1,offsetPx:4,padding:6},signal:f}),{isOpen:x}=b,S=e=>{let r=e.getAttribute(`type`),i=m(r);if(r===`checkbox`)e.toggleAttribute(`checked`,!e.hasAttribute(`checked`));else if(r===`radio`)for(let n of t.querySelectorAll(`ore-menu-item[type="radio"]`))n.toggleAttribute(`checked`,n===e);let a=e.getAttribute(`value`)??``,o=i?e.hasAttribute(`checked`):void 0;n(`select`,{checked:o,value:a}),i||b.close(`programmatic`)},C=a.createInteraction({keys:[`Enter`,` `,`ArrowDown`],onPress:()=>{b.open(`keyboard`),requestAnimationFrame(()=>b.set(0))}}),w=a.createInteraction({onPress:()=>{let e=b.getActiveItem();e&&S(e)}});function T(e){if(l.value)return;if(!x.value){C.handleKeydown(e);return}let t=y();if(t>=0&&b.set(t),!b.handleKeydown(e)){if(e.key===` `||e.key===`Enter`){w.handleKeydown(e);return}e.key===`Tab`&&b.close(`programmatic`)}}(0,u.bind)({on:{click:e=>{let t=e.composedPath();if(!x.value)return;let n=t.find(e=>e instanceof HTMLElement&&e.tagName===`ORE-MENU-ITEM`)??e.target?.closest(`ore-menu-item`)??null;!n||n.hasAttribute(`disabled`)||S(n)}}}),s(()=>{let e=x.value;h&&h.toggleAttribute(`data-open`,e)});function E(){if(g?.(),g=null,p=r.elements(`trigger`).value?.[0]??null,!p)return;let e=[],t=(0,u.aria)(p,{controls:()=>c,disabled:()=>l.value,expanded:()=>String(x.value),haspopup:`menu`}),n=e=>{e.stopPropagation(),!l.value&&b.toggle()},i=e=>{T(e)};p.addEventListener(`click`,n),p.addEventListener(`keydown`,i),e.push(()=>p?.removeEventListener(`click`,n)),e.push(()=>p?.removeEventListener(`keydown`,i)),g=()=>{t();for(let t of e)t()}}return(0,d.watch)(r.elements(`trigger`),E,{immediate:!0}),(0,u.onMounted)(()=>()=>{g?.(),g=null}),u.html`
|
|
18
18
|
<slot name="trigger"></slot>
|
|
19
19
|
<div
|
|
20
20
|
class="menu-panel"
|
|
@@ -26,5 +26,5 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
26
26
|
ref="${e=>{h=e}}">
|
|
27
27
|
<slot></slot>
|
|
28
28
|
</div>
|
|
29
|
-
`},styles:[
|
|
29
|
+
`},styles:[l.default,e.sizeVariantMixin(r.MENU_SIZE_PRESET),t.forcedColorsMixin]}),exports.MENU_ITEM_TAG=f,exports.MENU_TAG=h,exports.SEPARATOR_TAG=p;
|
|
30
30
|
//# sourceMappingURL=menu.cjs.map
|
package/dist/menu.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.cjs","names":[],"sources":["../src/overlay/menu/menu.ts"],"sourcesContent":["import type { Placement } from '@vielzeug/orbit';\n\nimport {\n createStableId,\n define,\n html,\n prop,\n aria,\n bind,\n getHost,\n onCleanup,\n onMounted,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { computed, watch as rippleWatch } from '@vielzeug/ripple';\n\nimport type { ComponentSize } from '../../types';\n\nimport {\n lifecycleSignal,\n createInteraction,\n createOptionList,\n type DropdownCloseReason,\n type OverlayOpenDetail,\n} from '../../headless';\nimport { disablableBundle, sizableBundle } from '../../shared';\nimport { forcedColorsMixin, sizeVariantMixin } from '../../styles';\nimport menuItemStyles from './menu-item.css?inline';\nimport menuSeparatorStyles from './menu-separator.css?inline';\nimport componentStyles from './menu.css?inline';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport interface MenuSelectDetail {\n value: string;\n checked?: boolean;\n}\n\nexport type OreMenuItemType = 'checkbox' | 'radio';\n\nexport type OreMenuEvents = {\n close: { reason: DropdownCloseReason };\n open: OverlayOpenDetail;\n select: MenuSelectDetail;\n};\n\nexport type OreMenuItemProps = {\n checked?: boolean;\n disabled?: boolean;\n type?: OreMenuItemType;\n value?: string;\n};\n\nexport type OreMenuProps = {\n disabled?: boolean;\n placement?: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';\n size?: ComponentSize;\n};\n\n// ── Styles ─────────────────────────────────────────────────────────────\n\n// ── Menu Item Component ─────────────────────────────────────────────────────────────\n\n/**\n * A selectable action item used inside `<ore-menu>`.\n *\n * @element ore-menu-item\n *\n * @attr {boolean} checked - Checked state for `checkbox` and `radio` item types\n * @attr {boolean} disabled - Disables selection and pointer interaction\n * @attr {'checkbox'|'radio'} type - Optional checkable menu item mode\n * @attr {string} value - Value emitted by parent menu on selection\n *\n * @slot - Item label/content\n * @slot icon - Optional leading icon content\n *\n * @cssprop --menu-item-hover-bg - Background on hover\n * @cssprop --menu-item-focus-color - Text color when keyboard-focused\n * @cssprop --menu-item-focus-bg - Background when keyboard-focused\n * @cssprop --menu-item-selection-bg - Background for checkbox/radio items (unselected)\n * @cssprop --menu-item-checked-color - Text color when checked\n * @cssprop --menu-item-checked-bg - Background when checked\n *\n * @part item - Root item container element.\n * @part item-label - Label text container.\n * @part icon-slot - Leading icon slot container.\n *\n * @example\n * ```html\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\" disabled>Delete</ore-menu-item>\n * <ore-menu-item type=\"checkbox\" value=\"wrap\" checked>Word wrap</ore-menu-item>\n * <ore-menu-item type=\"radio\" value=\"left\">Align left</ore-menu-item>\n * ```\n */\nexport const MENU_ITEM_TAG = 'ore-menu-item' as const;\ndefine<OreMenuItemProps>(MENU_ITEM_TAG, {\n props: {\n checked: prop.bool(false),\n disabled: prop.bool(false),\n type: prop.string<OreMenuItemType>(),\n value: prop.string(),\n },\n setup(props) {\n const isCheckable = () => props.type.value === 'checkbox' || props.type.value === 'radio';\n const isChecked = () => isCheckable() && props.checked.value;\n const itemRole = () => {\n if (props.type.value === 'checkbox') return 'menuitemcheckbox';\n\n if (props.type.value === 'radio') return 'menuitemradio';\n\n return 'menuitem';\n };\n const itemClass = () => {\n const type = props.type.value;\n\n return [\n 'item',\n type === 'checkbox' ? 'is-checkbox' : '',\n type === 'radio' ? 'is-radio' : '',\n isChecked() ? 'is-checked' : '',\n ]\n .filter(Boolean)\n .join(' ');\n };\n\n return isCheckable()\n ? html`\n <div\n class=\"${itemClass}\"\n tabindex=\"-1\"\n role=\"${itemRole}\"\n aria-checked=\"${() => String(isChecked())}\"\n aria-disabled=\"${props.disabled}\">\n <span class=\"item-check\" aria-hidden=\"true\"></span>\n <span class=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\"><slot></slot></span>\n </div>\n `\n : html`\n <div class=\"item\" tabindex=\"-1\" role=\"menuitem\" aria-disabled=\"${props.disabled}\">\n <span class=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\"><slot></slot></span>\n </div>\n `;\n },\n styles: [menuItemStyles],\n});\n\n// ── Menu Separator ─────────────────────────────────────────────────────────────\n\n/**\n * Visual separator used to group menu items inside `<ore-menu>`.\n *\n * @element ore-menu-separator\n *\n * @example\n * ```html\n * <ore-menu-item value=\"cut\">Cut</ore-menu-item>\n * <ore-menu-separator></ore-menu-separator>\n * <ore-menu-item value=\"paste\">Paste</ore-menu-item>\n * ```\n */\nexport const SEPARATOR_TAG = 'ore-menu-separator' as const;\ndefine(SEPARATOR_TAG, {\n setup() {\n return html``;\n },\n styles: [menuSeparatorStyles],\n});\n\n// ── Menu Component ─────────────────────────────────────────────────────────────\n\nconst isCheckableItemType = (value: string | null): value is OreMenuItemType =>\n value === 'checkbox' || value === 'radio';\n\n/**\n * Action dropdown menu triggered by a slotted trigger element.\n *\n * @element ore-menu\n * @element ore-menu-item - Clickable menu option (place in default slot)\n * @element ore-menu-separator - Visual divider between menu groups\n *\n * @attr {boolean} disabled - Disables opening and keyboard interaction\n * @attr {string} placement - Panel placement: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end' (default: 'bottom-start')\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n *\n * @fires open - Fired when the menu opens. detail: { reason: 'trigger' | 'programmatic' }\n * @fires close - Fired when the menu closes. detail: { reason: 'escape' | 'outsideClick' | 'programmatic' | 'trigger' }\n * @fires select - Fired when an item is selected. detail: { value: string, checked?: boolean }\n *\n * @slot trigger - Trigger element that toggles menu visibility\n * @slot - Menu content (`<ore-menu-item>` and `<ore-menu-separator>`)\n *\n * @part panel - Floating menu panel container\n *\n * @cssprop --menu-panel-bg - Background of the floating panel\n * @cssprop --menu-panel-border-color - Border color of the floating panel\n * @cssprop --menu-panel-shadow - Box shadow of the floating panel\n * @cssprop --menu-panel-blur - Backdrop blur amount for the floating panel\n * @cssprop --menu-panel-min-width - Minimum width of the floating panel\n * @cssprop --menu-panel-max-height - Maximum height of the floating panel before it scrolls\n * @cssprop --menu-panel-radius - Border radius of the floating panel\n *\n * @example\n * ```html\n * <ore-menu>\n * <button slot=\"trigger\">Actions</button>\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\">Delete</ore-menu-item>\n * </ore-menu>\n * ```\n */\nexport const MENU_TAG = 'ore-menu' as const;\ndefine<OreMenuProps>(MENU_TAG, {\n props: {\n ...sizableBundle,\n ...disablableBundle,\n placement: prop.oneOf(\n ['bottom', 'bottom-start', 'bottom-end', 'top', 'top-start', 'top-end'] as const,\n 'bottom-start',\n ),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMenuEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const menuId = createStableId('menu');\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const abortSignal = lifecycleSignal(onCleanup);\n let triggerEl: HTMLElement | null = null;\n let panelEl: HTMLElement | null = null;\n let cleanupTrigger: (() => void) | null = null;\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n function getItems(): HTMLElement[] {\n return Array.from(el.querySelectorAll<HTMLElement>('ore-menu-item:not([disabled])'));\n }\n\n function getItemFocusable(item: HTMLElement | null | undefined): HTMLElement | null {\n if (!item) return null;\n\n return item.shadowRoot?.querySelector<HTMLElement>('[role^=\"menuitem\"]') ?? item;\n }\n\n function getFocusedItemIndex(): number {\n const items = getItems();\n\n return items.findIndex((item) => {\n const focusable = getItemFocusable(item);\n\n return item === document.activeElement || focusable === document.activeElement;\n });\n }\n\n const optionList = createOptionList<HTMLElement>({\n getBoundary: () => el,\n getItems: getItems,\n getPanel: () => panelEl,\n getReference: () => triggerEl,\n getTrigger: () => triggerEl,\n isDisabled: () => isDisabled.value,\n isItemDisabled: (item) => item.hasAttribute('disabled'),\n onClose: (reason) => emit('close', { reason }),\n onNavigate: (_action, index) => {\n const nextItem = getItems()[index];\n\n getItemFocusable(nextItem)?.focus();\n },\n onOpen: (reason) => emit('open', { reason }),\n positioning: {\n getPlacement: () => (props.placement.value ?? 'bottom-start') as Placement,\n matchWidth: false,\n offsetPx: 4,\n padding: 6,\n },\n signal: abortSignal,\n });\n const { isOpen: isOpenSignal } = optionList;\n\n const activateItem = (item: HTMLElement): void => {\n const type = item.getAttribute('type');\n const isCheckable = isCheckableItemType(type);\n\n if (type === 'checkbox') {\n item.toggleAttribute('checked', !item.hasAttribute('checked'));\n } else if (type === 'radio') {\n for (const radio of el.querySelectorAll<HTMLElement>('ore-menu-item[type=\"radio\"]')) {\n radio.toggleAttribute('checked', radio === item);\n }\n }\n\n const value = item.getAttribute('value') ?? '';\n const checked = isCheckable ? item.hasAttribute('checked') : undefined;\n\n emit('select', { checked, value });\n\n if (!isCheckable) {\n optionList.close('programmatic');\n }\n };\n\n const openFromKeyboardPress = createInteraction({\n keys: ['Enter', ' ', 'ArrowDown'],\n onPress: () => {\n optionList.open('keyboard');\n requestAnimationFrame(() => optionList.set(0));\n },\n });\n\n const activateFocusedFromKeyboardPress = createInteraction({\n onPress: () => {\n const focused = optionList.getActiveItem();\n\n if (focused) activateItem(focused);\n },\n });\n\n // ── Keyboard Navigation ───────────────────────────────────────────────────\n function handleMenuKeydown(e: KeyboardEvent) {\n if (isDisabled.value) return;\n\n const open = isOpenSignal.value;\n\n // When closed: open on Enter / Space / ArrowDown\n if (!open) {\n openFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n const currentFocusedIndex = getFocusedItemIndex();\n\n if (currentFocusedIndex >= 0) optionList.set(currentFocusedIndex);\n\n if (optionList.handleKeydown(e)) return;\n\n // When open: navigate and activate\n if (e.key === ' ' || e.key === 'Enter') {\n activateFocusedFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n if (e.key === 'Tab') {\n optionList.close('programmatic');\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n bind({\n on: {\n click: (e: MouseEvent) => {\n const path = e.composedPath();\n\n if (!isOpenSignal.value) return;\n\n const itemFromPath = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.tagName === 'ORE-MENU-ITEM',\n );\n const item = itemFromPath ?? (e.target as HTMLElement | null)?.closest<HTMLElement>('ore-menu-item') ?? null;\n\n if (!item || item.hasAttribute('disabled')) return;\n\n activateItem(item);\n },\n },\n });\n\n watch(() => {\n const open = isOpenSignal.value;\n\n if (!panelEl) return;\n\n panelEl.toggleAttribute('data-open', open);\n });\n\n function resolveTrigger() {\n cleanupTrigger?.();\n cleanupTrigger = null;\n\n const assigned = slots.elements('trigger').value;\n\n triggerEl = (assigned?.[0] as HTMLElement | undefined) ?? null;\n\n if (!triggerEl) return;\n\n const cleanups: Array<() => void> = [];\n const removeAria = aria(triggerEl, {\n controls: () => menuId,\n disabled: () => isDisabled.value,\n expanded: () => String(isOpenSignal.value),\n haspopup: 'menu',\n });\n\n const onTriggerClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled.value) return;\n\n optionList.toggle();\n };\n const onTriggerKeydown = (event: KeyboardEvent) => {\n handleMenuKeydown(event);\n };\n\n triggerEl.addEventListener('click', onTriggerClick);\n triggerEl.addEventListener('keydown', onTriggerKeydown);\n cleanups.push(() => triggerEl?.removeEventListener('click', onTriggerClick));\n cleanups.push(() => triggerEl?.removeEventListener('keydown', onTriggerKeydown));\n\n cleanupTrigger = () => {\n removeAria();\n\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n rippleWatch(slots.elements('trigger'), resolveTrigger, { immediate: true });\n\n onMounted(() => {\n return () => {\n cleanupTrigger?.();\n cleanupTrigger = null;\n };\n });\n\n return html`\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-panel\"\n part=\"panel\"\n id=\"${menuId}\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n @keydown=\"${handleMenuKeydown}\"\n ref=\"${(el: HTMLElement | null) => {\n panelEl = el;\n }}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles, sizeVariantMixin(), forcedColorsMixin],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"menu.cjs","names":[],"sources":["../src/overlay/menu/menu.ts"],"sourcesContent":["import type { Placement } from '@vielzeug/orbit';\n\nimport {\n createStableId,\n define,\n html,\n prop,\n aria,\n bind,\n getHost,\n onCleanup,\n onMounted,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { computed, watch as rippleWatch } from '@vielzeug/ripple';\n\nimport type { ComponentSize } from '../../types';\n\nimport {\n lifecycleSignal,\n createInteraction,\n createOptionList,\n type DropdownCloseReason,\n type OverlayOpenDetail,\n} from '../../headless';\nimport { disablableBundle, MENU_SIZE_PRESET, sizableBundle } from '../../shared';\nimport { colorThemeMixin, forcedColorsMixin, sizeVariantMixin } from '../../styles';\nimport menuItemStyles from './menu-item.css?inline';\nimport menuSeparatorStyles from './menu-separator.css?inline';\nimport componentStyles from './menu.css?inline';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport interface MenuSelectDetail {\n value: string;\n checked?: boolean;\n}\n\nexport type OreMenuItemType = 'checkbox' | 'radio';\n\nexport type OreMenuEvents = {\n close: { reason: DropdownCloseReason };\n open: OverlayOpenDetail;\n select: MenuSelectDetail;\n};\n\nexport type OreMenuItemProps = {\n checked?: boolean;\n disabled?: boolean;\n type?: OreMenuItemType;\n value?: string;\n};\n\nexport type OreMenuProps = {\n disabled?: boolean;\n placement?: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';\n size?: ComponentSize;\n};\n\n// ── Styles ─────────────────────────────────────────────────────────────\n\n// ── Menu Item Component ─────────────────────────────────────────────────────────────\n\n/**\n * A selectable action item used inside `<ore-menu>`.\n *\n * @element ore-menu-item\n *\n * @attr {boolean} checked - Checked state for `checkbox` and `radio` item types\n * @attr {boolean} disabled - Disables selection and pointer interaction\n * @attr {'checkbox'|'radio'} type - Optional checkable menu item mode\n * @attr {string} value - Value emitted by parent menu on selection\n *\n * @slot - Item label/content\n * @slot icon - Optional leading icon content\n *\n * @cssprop --menu-item-hover-bg - Background on hover\n * @cssprop --menu-item-focus-color - Text color when keyboard-focused\n * @cssprop --menu-item-focus-bg - Background when keyboard-focused\n * @cssprop --menu-item-selection-bg - Background for checkbox/radio items (unselected)\n * @cssprop --menu-item-checked-color - Text color when checked\n * @cssprop --menu-item-checked-bg - Background when checked\n *\n * @part item - Root item container element.\n * @part item-label - Label text container.\n * @part icon-slot - Leading icon slot container.\n *\n * @example\n * ```html\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\" disabled>Delete</ore-menu-item>\n * <ore-menu-item type=\"checkbox\" value=\"wrap\" checked>Word wrap</ore-menu-item>\n * <ore-menu-item type=\"radio\" value=\"left\">Align left</ore-menu-item>\n * ```\n */\nexport const MENU_ITEM_TAG = 'ore-menu-item' as const;\ndefine<OreMenuItemProps>(MENU_ITEM_TAG, {\n props: {\n checked: prop.bool(false),\n disabled: prop.bool(false),\n type: prop.string<OreMenuItemType>(),\n value: prop.string(),\n },\n setup(props) {\n const isCheckable = () => props.type.value === 'checkbox' || props.type.value === 'radio';\n const isChecked = () => isCheckable() && props.checked.value;\n const itemRole = () => {\n if (props.type.value === 'checkbox') return 'menuitemcheckbox';\n\n if (props.type.value === 'radio') return 'menuitemradio';\n\n return 'menuitem';\n };\n const itemClass = () => {\n const type = props.type.value;\n\n return [\n 'item',\n type === 'checkbox' ? 'is-checkbox' : '',\n type === 'radio' ? 'is-radio' : '',\n isChecked() ? 'is-checked' : '',\n ]\n .filter(Boolean)\n .join(' ');\n };\n\n return isCheckable()\n ? html`\n <div\n class=\"${itemClass}\"\n tabindex=\"-1\"\n role=\"${itemRole}\"\n aria-checked=\"${() => String(isChecked())}\"\n aria-disabled=\"${props.disabled}\">\n <span class=\"item-check\" aria-hidden=\"true\"></span>\n <span class=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\"><slot></slot></span>\n </div>\n `\n : html`\n <div class=\"item\" tabindex=\"-1\" role=\"menuitem\" aria-disabled=\"${props.disabled}\">\n <span class=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\"><slot></slot></span>\n </div>\n `;\n },\n styles: [colorThemeMixin, menuItemStyles],\n});\n\n// ── Menu Separator ─────────────────────────────────────────────────────────────\n\n/**\n * Visual separator used to group menu items inside `<ore-menu>`.\n *\n * @element ore-menu-separator\n *\n * @example\n * ```html\n * <ore-menu-item value=\"cut\">Cut</ore-menu-item>\n * <ore-menu-separator></ore-menu-separator>\n * <ore-menu-item value=\"paste\">Paste</ore-menu-item>\n * ```\n */\nexport const SEPARATOR_TAG = 'ore-menu-separator' as const;\ndefine(SEPARATOR_TAG, {\n setup() {\n return html``;\n },\n styles: [menuSeparatorStyles],\n});\n\n// ── Menu Component ─────────────────────────────────────────────────────────────\n\nconst isCheckableItemType = (value: string | null): value is OreMenuItemType =>\n value === 'checkbox' || value === 'radio';\n\n/**\n * Action dropdown menu triggered by a slotted trigger element.\n *\n * @element ore-menu\n * @element ore-menu-item - Clickable menu option (place in default slot)\n * @element ore-menu-separator - Visual divider between menu groups\n *\n * @attr {boolean} disabled - Disables opening and keyboard interaction\n * @attr {string} placement - Panel placement: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end' (default: 'bottom-start')\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n *\n * @fires open - Fired when the menu opens. detail: { reason: 'trigger' | 'programmatic' }\n * @fires close - Fired when the menu closes. detail: { reason: 'escape' | 'outsideClick' | 'programmatic' | 'trigger' }\n * @fires select - Fired when an item is selected. detail: { value: string, checked?: boolean }\n *\n * @slot trigger - Trigger element that toggles menu visibility\n * @slot - Menu content (`<ore-menu-item>` and `<ore-menu-separator>`)\n *\n * @part panel - Floating menu panel container\n *\n * @cssprop --menu-panel-bg - Background of the floating panel\n * @cssprop --menu-panel-border-color - Border color of the floating panel\n * @cssprop --menu-panel-shadow - Box shadow of the floating panel\n * @cssprop --menu-panel-blur - Backdrop blur amount for the floating panel\n * @cssprop --menu-panel-min-width - Minimum width of the floating panel\n * @cssprop --menu-panel-max-height - Maximum height of the floating panel before it scrolls\n * @cssprop --menu-panel-radius - Border radius of the floating panel\n *\n * @example\n * ```html\n * <ore-menu>\n * <button slot=\"trigger\">Actions</button>\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\">Delete</ore-menu-item>\n * </ore-menu>\n * ```\n */\nexport const MENU_TAG = 'ore-menu' as const;\ndefine<OreMenuProps>(MENU_TAG, {\n props: {\n ...sizableBundle,\n ...disablableBundle,\n placement: prop.oneOf(\n ['bottom', 'bottom-start', 'bottom-end', 'top', 'top-start', 'top-end'] as const,\n 'bottom-start',\n ),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMenuEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const menuId = createStableId('menu');\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const abortSignal = lifecycleSignal(onCleanup);\n let triggerEl: HTMLElement | null = null;\n let panelEl: HTMLElement | null = null;\n let cleanupTrigger: (() => void) | null = null;\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n function getItems(): HTMLElement[] {\n return Array.from(el.querySelectorAll<HTMLElement>('ore-menu-item:not([disabled])'));\n }\n\n function getItemFocusable(item: HTMLElement | null | undefined): HTMLElement | null {\n if (!item) return null;\n\n return item.shadowRoot?.querySelector<HTMLElement>('[role^=\"menuitem\"]') ?? item;\n }\n\n function getFocusedItemIndex(): number {\n const items = getItems();\n\n return items.findIndex((item) => {\n const focusable = getItemFocusable(item);\n\n return item === document.activeElement || focusable === document.activeElement;\n });\n }\n\n const optionList = createOptionList<HTMLElement>({\n getBoundary: () => el,\n getItems: getItems,\n getPanel: () => panelEl,\n getReference: () => triggerEl,\n getTrigger: () => triggerEl,\n isDisabled: () => isDisabled.value,\n isItemDisabled: (item) => item.hasAttribute('disabled'),\n onClose: (reason) => emit('close', { reason }),\n onNavigate: (_action, index) => {\n const nextItem = getItems()[index];\n\n getItemFocusable(nextItem)?.focus();\n },\n onOpen: (reason) => emit('open', { reason }),\n positioning: {\n getPlacement: () => (props.placement.value ?? 'bottom-start') as Placement,\n matchWidth: false,\n offsetPx: 4,\n padding: 6,\n },\n signal: abortSignal,\n });\n const { isOpen: isOpenSignal } = optionList;\n\n const activateItem = (item: HTMLElement): void => {\n const type = item.getAttribute('type');\n const isCheckable = isCheckableItemType(type);\n\n if (type === 'checkbox') {\n item.toggleAttribute('checked', !item.hasAttribute('checked'));\n } else if (type === 'radio') {\n for (const radio of el.querySelectorAll<HTMLElement>('ore-menu-item[type=\"radio\"]')) {\n radio.toggleAttribute('checked', radio === item);\n }\n }\n\n const value = item.getAttribute('value') ?? '';\n const checked = isCheckable ? item.hasAttribute('checked') : undefined;\n\n emit('select', { checked, value });\n\n if (!isCheckable) {\n optionList.close('programmatic');\n }\n };\n\n const openFromKeyboardPress = createInteraction({\n keys: ['Enter', ' ', 'ArrowDown'],\n onPress: () => {\n optionList.open('keyboard');\n requestAnimationFrame(() => optionList.set(0));\n },\n });\n\n const activateFocusedFromKeyboardPress = createInteraction({\n onPress: () => {\n const focused = optionList.getActiveItem();\n\n if (focused) activateItem(focused);\n },\n });\n\n // ── Keyboard Navigation ───────────────────────────────────────────────────\n function handleMenuKeydown(e: KeyboardEvent) {\n if (isDisabled.value) return;\n\n const open = isOpenSignal.value;\n\n // When closed: open on Enter / Space / ArrowDown\n if (!open) {\n openFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n const currentFocusedIndex = getFocusedItemIndex();\n\n if (currentFocusedIndex >= 0) optionList.set(currentFocusedIndex);\n\n if (optionList.handleKeydown(e)) return;\n\n // When open: navigate and activate\n if (e.key === ' ' || e.key === 'Enter') {\n activateFocusedFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n if (e.key === 'Tab') {\n optionList.close('programmatic');\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n bind({\n on: {\n click: (e: MouseEvent) => {\n const path = e.composedPath();\n\n if (!isOpenSignal.value) return;\n\n const itemFromPath = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.tagName === 'ORE-MENU-ITEM',\n );\n const item = itemFromPath ?? (e.target as HTMLElement | null)?.closest<HTMLElement>('ore-menu-item') ?? null;\n\n if (!item || item.hasAttribute('disabled')) return;\n\n activateItem(item);\n },\n },\n });\n\n watch(() => {\n const open = isOpenSignal.value;\n\n if (!panelEl) return;\n\n panelEl.toggleAttribute('data-open', open);\n });\n\n function resolveTrigger() {\n cleanupTrigger?.();\n cleanupTrigger = null;\n\n const assigned = slots.elements('trigger').value;\n\n triggerEl = (assigned?.[0] as HTMLElement | undefined) ?? null;\n\n if (!triggerEl) return;\n\n const cleanups: Array<() => void> = [];\n const removeAria = aria(triggerEl, {\n controls: () => menuId,\n disabled: () => isDisabled.value,\n expanded: () => String(isOpenSignal.value),\n haspopup: 'menu',\n });\n\n const onTriggerClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled.value) return;\n\n optionList.toggle();\n };\n const onTriggerKeydown = (event: KeyboardEvent) => {\n handleMenuKeydown(event);\n };\n\n triggerEl.addEventListener('click', onTriggerClick);\n triggerEl.addEventListener('keydown', onTriggerKeydown);\n cleanups.push(() => triggerEl?.removeEventListener('click', onTriggerClick));\n cleanups.push(() => triggerEl?.removeEventListener('keydown', onTriggerKeydown));\n\n cleanupTrigger = () => {\n removeAria();\n\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n rippleWatch(slots.elements('trigger'), resolveTrigger, { immediate: true });\n\n onMounted(() => {\n return () => {\n cleanupTrigger?.();\n cleanupTrigger = null;\n };\n });\n\n return html`\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-panel\"\n part=\"panel\"\n id=\"${menuId}\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n @keydown=\"${handleMenuKeydown}\"\n ref=\"${(el: HTMLElement | null) => {\n panelEl = el;\n }}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles, sizeVariantMixin(MENU_SIZE_PRESET), forcedColorsMixin],\n});\n"],"mappings":"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"}
|
package/dist/menu.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import { sizeVariantMixin as e } from "./styles/mixins/shape.css.js";
|
|
2
|
-
import {
|
|
3
|
-
import { disablableBundle as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
2
|
+
import { colorThemeMixin as t, forcedColorsMixin as n } from "./styles/mixins/theme.css.js";
|
|
3
|
+
import { disablableBundle as r, sizableBundle as i } from "./shared/prop-bundles.js";
|
|
4
|
+
import { MENU_SIZE_PRESET as a } from "./shared/size-presets.js";
|
|
5
|
+
import { lifecycleSignal as o } from "./headless/ore.js";
|
|
6
|
+
import { createInteraction as s } from "./headless/keyboard.js";
|
|
7
|
+
import { createOptionList as c } from "./headless/option-list.js";
|
|
8
|
+
import l from "./overlay/menu/menu-item.css.js";
|
|
9
|
+
import u from "./overlay/menu/menu-separator.css.js";
|
|
10
|
+
import d from "./overlay/menu/menu.css.js";
|
|
11
|
+
import { aria as f, bind as p, createStableId as m, define as h, getHost as g, html as _, onCleanup as v, onMounted as y, prop as b, useEmit as x, useSlots as S, watchEffect as C } from "@vielzeug/ore";
|
|
12
|
+
import { computed as w, watch as T } from "@vielzeug/ripple";
|
|
12
13
|
//#region src/overlay/menu/menu.ts
|
|
13
|
-
var
|
|
14
|
-
|
|
14
|
+
var E = "ore-menu-item";
|
|
15
|
+
h(E, {
|
|
15
16
|
props: {
|
|
16
|
-
checked:
|
|
17
|
-
disabled:
|
|
18
|
-
type:
|
|
19
|
-
value:
|
|
17
|
+
checked: b.bool(!1),
|
|
18
|
+
disabled: b.bool(!1),
|
|
19
|
+
type: b.string(),
|
|
20
|
+
value: b.string()
|
|
20
21
|
},
|
|
21
22
|
setup(e) {
|
|
22
23
|
let t = () => e.type.value === "checkbox" || e.type.value === "radio", n = () => t() && e.checked.value;
|
|
23
|
-
return t() ?
|
|
24
|
+
return t() ? _`
|
|
24
25
|
<div
|
|
25
26
|
class="${() => {
|
|
26
27
|
let t = e.type.value;
|
|
@@ -39,28 +40,28 @@ p(w, {
|
|
|
39
40
|
<span class="icon-slot"><slot name="icon"></slot></span>
|
|
40
41
|
<span class="item-label"><slot></slot></span>
|
|
41
42
|
</div>
|
|
42
|
-
` :
|
|
43
|
+
` : _`
|
|
43
44
|
<div class="item" tabindex="-1" role="menuitem" aria-disabled="${e.disabled}">
|
|
44
45
|
<span class="icon-slot"><slot name="icon"></slot></span>
|
|
45
46
|
<span class="item-label"><slot></slot></span>
|
|
46
47
|
</div>
|
|
47
48
|
`;
|
|
48
49
|
},
|
|
49
|
-
styles: [
|
|
50
|
+
styles: [t, l]
|
|
50
51
|
});
|
|
51
|
-
var
|
|
52
|
-
|
|
52
|
+
var D = "ore-menu-separator";
|
|
53
|
+
h(D, {
|
|
53
54
|
setup() {
|
|
54
|
-
return
|
|
55
|
+
return _``;
|
|
55
56
|
},
|
|
56
|
-
styles: [
|
|
57
|
+
styles: [u]
|
|
57
58
|
});
|
|
58
|
-
var
|
|
59
|
-
|
|
59
|
+
var O = (e) => e === "checkbox" || e === "radio", k = "ore-menu";
|
|
60
|
+
h(k, {
|
|
60
61
|
props: {
|
|
62
|
+
...i,
|
|
61
63
|
...r,
|
|
62
|
-
|
|
63
|
-
placement: v.oneOf([
|
|
64
|
+
placement: b.oneOf([
|
|
64
65
|
"bottom",
|
|
65
66
|
"bottom-start",
|
|
66
67
|
"bottom-end",
|
|
@@ -70,31 +71,31 @@ p(D, {
|
|
|
70
71
|
], "bottom-start")
|
|
71
72
|
},
|
|
72
73
|
setup(e) {
|
|
73
|
-
let t =
|
|
74
|
-
function
|
|
74
|
+
let t = g(), n = x(), r = S(), i = C, a = m("menu"), l = w(() => !!e.disabled.value), u = o(v), d = null, h = null, b = null;
|
|
75
|
+
function E() {
|
|
75
76
|
return Array.from(t.querySelectorAll("ore-menu-item:not([disabled])"));
|
|
76
77
|
}
|
|
77
|
-
function
|
|
78
|
+
function D(e) {
|
|
78
79
|
return e ? e.shadowRoot?.querySelector("[role^=\"menuitem\"]") ?? e : null;
|
|
79
80
|
}
|
|
80
81
|
function k() {
|
|
81
|
-
return
|
|
82
|
-
let t =
|
|
82
|
+
return E().findIndex((e) => {
|
|
83
|
+
let t = D(e);
|
|
83
84
|
return e === document.activeElement || t === document.activeElement;
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
|
-
let A =
|
|
87
|
+
let A = c({
|
|
87
88
|
getBoundary: () => t,
|
|
88
|
-
getItems:
|
|
89
|
-
getPanel: () =>
|
|
90
|
-
getReference: () =>
|
|
91
|
-
getTrigger: () =>
|
|
89
|
+
getItems: E,
|
|
90
|
+
getPanel: () => h,
|
|
91
|
+
getReference: () => d,
|
|
92
|
+
getTrigger: () => d,
|
|
92
93
|
isDisabled: () => l.value,
|
|
93
94
|
isItemDisabled: (e) => e.hasAttribute("disabled"),
|
|
94
95
|
onClose: (e) => n("close", { reason: e }),
|
|
95
96
|
onNavigate: (e, t) => {
|
|
96
|
-
let n =
|
|
97
|
-
|
|
97
|
+
let n = E()[t];
|
|
98
|
+
D(n)?.focus();
|
|
98
99
|
},
|
|
99
100
|
onOpen: (e) => n("open", { reason: e }),
|
|
100
101
|
positioning: {
|
|
@@ -103,9 +104,9 @@ p(D, {
|
|
|
103
104
|
offsetPx: 4,
|
|
104
105
|
padding: 6
|
|
105
106
|
},
|
|
106
|
-
signal:
|
|
107
|
+
signal: u
|
|
107
108
|
}), { isOpen: j } = A, M = (e) => {
|
|
108
|
-
let r = e.getAttribute("type"), i =
|
|
109
|
+
let r = e.getAttribute("type"), i = O(r);
|
|
109
110
|
if (r === "checkbox") e.toggleAttribute("checked", !e.hasAttribute("checked"));
|
|
110
111
|
else if (r === "radio") for (let n of t.querySelectorAll("ore-menu-item[type=\"radio\"]")) n.toggleAttribute("checked", n === e);
|
|
111
112
|
let a = e.getAttribute("value") ?? "", o = i ? e.hasAttribute("checked") : void 0;
|
|
@@ -113,7 +114,7 @@ p(D, {
|
|
|
113
114
|
checked: o,
|
|
114
115
|
value: a
|
|
115
116
|
}), i || A.close("programmatic");
|
|
116
|
-
}, N =
|
|
117
|
+
}, N = s({
|
|
117
118
|
keys: [
|
|
118
119
|
"Enter",
|
|
119
120
|
" ",
|
|
@@ -122,7 +123,7 @@ p(D, {
|
|
|
122
123
|
onPress: () => {
|
|
123
124
|
A.open("keyboard"), requestAnimationFrame(() => A.set(0));
|
|
124
125
|
}
|
|
125
|
-
}), P =
|
|
126
|
+
}), P = s({ onPress: () => {
|
|
126
127
|
let e = A.getActiveItem();
|
|
127
128
|
e && M(e);
|
|
128
129
|
} });
|
|
@@ -141,19 +142,19 @@ p(D, {
|
|
|
141
142
|
e.key === "Tab" && A.close("programmatic");
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
+
p({ on: { click: (e) => {
|
|
145
146
|
let t = e.composedPath();
|
|
146
147
|
if (!j.value) return;
|
|
147
148
|
let n = t.find((e) => e instanceof HTMLElement && e.tagName === "ORE-MENU-ITEM") ?? e.target?.closest("ore-menu-item") ?? null;
|
|
148
149
|
!n || n.hasAttribute("disabled") || M(n);
|
|
149
|
-
} } }),
|
|
150
|
+
} } }), i(() => {
|
|
150
151
|
let e = j.value;
|
|
151
|
-
|
|
152
|
+
h && h.toggleAttribute("data-open", e);
|
|
152
153
|
});
|
|
153
154
|
function I() {
|
|
154
|
-
if (
|
|
155
|
-
let e = [], t =
|
|
156
|
-
controls: () =>
|
|
155
|
+
if (b?.(), b = null, d = r.elements("trigger").value?.[0] ?? null, !d) return;
|
|
156
|
+
let e = [], t = f(d, {
|
|
157
|
+
controls: () => a,
|
|
157
158
|
disabled: () => l.value,
|
|
158
159
|
expanded: () => String(j.value),
|
|
159
160
|
haspopup: "menu"
|
|
@@ -162,36 +163,36 @@ p(D, {
|
|
|
162
163
|
}, i = (e) => {
|
|
163
164
|
F(e);
|
|
164
165
|
};
|
|
165
|
-
|
|
166
|
+
d.addEventListener("click", n), d.addEventListener("keydown", i), e.push(() => d?.removeEventListener("click", n)), e.push(() => d?.removeEventListener("keydown", i)), b = () => {
|
|
166
167
|
t();
|
|
167
168
|
for (let t of e) t();
|
|
168
169
|
};
|
|
169
170
|
}
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
}),
|
|
171
|
+
return T(r.elements("trigger"), I, { immediate: !0 }), y(() => () => {
|
|
172
|
+
b?.(), b = null;
|
|
173
|
+
}), _`
|
|
173
174
|
<slot name="trigger"></slot>
|
|
174
175
|
<div
|
|
175
176
|
class="menu-panel"
|
|
176
177
|
part="panel"
|
|
177
|
-
id="${
|
|
178
|
+
id="${a}"
|
|
178
179
|
role="menu"
|
|
179
180
|
aria-orientation="vertical"
|
|
180
181
|
@keydown="${F}"
|
|
181
182
|
ref="${(e) => {
|
|
182
|
-
|
|
183
|
+
h = e;
|
|
183
184
|
}}">
|
|
184
185
|
<slot></slot>
|
|
185
186
|
</div>
|
|
186
187
|
`;
|
|
187
188
|
},
|
|
188
189
|
styles: [
|
|
189
|
-
|
|
190
|
-
e(),
|
|
191
|
-
|
|
190
|
+
d,
|
|
191
|
+
e(a),
|
|
192
|
+
n
|
|
192
193
|
]
|
|
193
194
|
});
|
|
194
195
|
//#endregion
|
|
195
|
-
export {
|
|
196
|
+
export { E as MENU_ITEM_TAG, k as MENU_TAG, D as SEPARATOR_TAG };
|
|
196
197
|
|
|
197
198
|
//# sourceMappingURL=menu.js.map
|
package/dist/menu.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu.js","names":[],"sources":["../src/overlay/menu/menu.ts"],"sourcesContent":["import type { Placement } from '@vielzeug/orbit';\n\nimport {\n createStableId,\n define,\n html,\n prop,\n aria,\n bind,\n getHost,\n onCleanup,\n onMounted,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { computed, watch as rippleWatch } from '@vielzeug/ripple';\n\nimport type { ComponentSize } from '../../types';\n\nimport {\n lifecycleSignal,\n createInteraction,\n createOptionList,\n type DropdownCloseReason,\n type OverlayOpenDetail,\n} from '../../headless';\nimport { disablableBundle, sizableBundle } from '../../shared';\nimport { forcedColorsMixin, sizeVariantMixin } from '../../styles';\nimport menuItemStyles from './menu-item.css?inline';\nimport menuSeparatorStyles from './menu-separator.css?inline';\nimport componentStyles from './menu.css?inline';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport interface MenuSelectDetail {\n value: string;\n checked?: boolean;\n}\n\nexport type OreMenuItemType = 'checkbox' | 'radio';\n\nexport type OreMenuEvents = {\n close: { reason: DropdownCloseReason };\n open: OverlayOpenDetail;\n select: MenuSelectDetail;\n};\n\nexport type OreMenuItemProps = {\n checked?: boolean;\n disabled?: boolean;\n type?: OreMenuItemType;\n value?: string;\n};\n\nexport type OreMenuProps = {\n disabled?: boolean;\n placement?: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';\n size?: ComponentSize;\n};\n\n// ── Styles ─────────────────────────────────────────────────────────────\n\n// ── Menu Item Component ─────────────────────────────────────────────────────────────\n\n/**\n * A selectable action item used inside `<ore-menu>`.\n *\n * @element ore-menu-item\n *\n * @attr {boolean} checked - Checked state for `checkbox` and `radio` item types\n * @attr {boolean} disabled - Disables selection and pointer interaction\n * @attr {'checkbox'|'radio'} type - Optional checkable menu item mode\n * @attr {string} value - Value emitted by parent menu on selection\n *\n * @slot - Item label/content\n * @slot icon - Optional leading icon content\n *\n * @cssprop --menu-item-hover-bg - Background on hover\n * @cssprop --menu-item-focus-color - Text color when keyboard-focused\n * @cssprop --menu-item-focus-bg - Background when keyboard-focused\n * @cssprop --menu-item-selection-bg - Background for checkbox/radio items (unselected)\n * @cssprop --menu-item-checked-color - Text color when checked\n * @cssprop --menu-item-checked-bg - Background when checked\n *\n * @part item - Root item container element.\n * @part item-label - Label text container.\n * @part icon-slot - Leading icon slot container.\n *\n * @example\n * ```html\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\" disabled>Delete</ore-menu-item>\n * <ore-menu-item type=\"checkbox\" value=\"wrap\" checked>Word wrap</ore-menu-item>\n * <ore-menu-item type=\"radio\" value=\"left\">Align left</ore-menu-item>\n * ```\n */\nexport const MENU_ITEM_TAG = 'ore-menu-item' as const;\ndefine<OreMenuItemProps>(MENU_ITEM_TAG, {\n props: {\n checked: prop.bool(false),\n disabled: prop.bool(false),\n type: prop.string<OreMenuItemType>(),\n value: prop.string(),\n },\n setup(props) {\n const isCheckable = () => props.type.value === 'checkbox' || props.type.value === 'radio';\n const isChecked = () => isCheckable() && props.checked.value;\n const itemRole = () => {\n if (props.type.value === 'checkbox') return 'menuitemcheckbox';\n\n if (props.type.value === 'radio') return 'menuitemradio';\n\n return 'menuitem';\n };\n const itemClass = () => {\n const type = props.type.value;\n\n return [\n 'item',\n type === 'checkbox' ? 'is-checkbox' : '',\n type === 'radio' ? 'is-radio' : '',\n isChecked() ? 'is-checked' : '',\n ]\n .filter(Boolean)\n .join(' ');\n };\n\n return isCheckable()\n ? html`\n <div\n class=\"${itemClass}\"\n tabindex=\"-1\"\n role=\"${itemRole}\"\n aria-checked=\"${() => String(isChecked())}\"\n aria-disabled=\"${props.disabled}\">\n <span class=\"item-check\" aria-hidden=\"true\"></span>\n <span class=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\"><slot></slot></span>\n </div>\n `\n : html`\n <div class=\"item\" tabindex=\"-1\" role=\"menuitem\" aria-disabled=\"${props.disabled}\">\n <span class=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\"><slot></slot></span>\n </div>\n `;\n },\n styles: [menuItemStyles],\n});\n\n// ── Menu Separator ─────────────────────────────────────────────────────────────\n\n/**\n * Visual separator used to group menu items inside `<ore-menu>`.\n *\n * @element ore-menu-separator\n *\n * @example\n * ```html\n * <ore-menu-item value=\"cut\">Cut</ore-menu-item>\n * <ore-menu-separator></ore-menu-separator>\n * <ore-menu-item value=\"paste\">Paste</ore-menu-item>\n * ```\n */\nexport const SEPARATOR_TAG = 'ore-menu-separator' as const;\ndefine(SEPARATOR_TAG, {\n setup() {\n return html``;\n },\n styles: [menuSeparatorStyles],\n});\n\n// ── Menu Component ─────────────────────────────────────────────────────────────\n\nconst isCheckableItemType = (value: string | null): value is OreMenuItemType =>\n value === 'checkbox' || value === 'radio';\n\n/**\n * Action dropdown menu triggered by a slotted trigger element.\n *\n * @element ore-menu\n * @element ore-menu-item - Clickable menu option (place in default slot)\n * @element ore-menu-separator - Visual divider between menu groups\n *\n * @attr {boolean} disabled - Disables opening and keyboard interaction\n * @attr {string} placement - Panel placement: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end' (default: 'bottom-start')\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n *\n * @fires open - Fired when the menu opens. detail: { reason: 'trigger' | 'programmatic' }\n * @fires close - Fired when the menu closes. detail: { reason: 'escape' | 'outsideClick' | 'programmatic' | 'trigger' }\n * @fires select - Fired when an item is selected. detail: { value: string, checked?: boolean }\n *\n * @slot trigger - Trigger element that toggles menu visibility\n * @slot - Menu content (`<ore-menu-item>` and `<ore-menu-separator>`)\n *\n * @part panel - Floating menu panel container\n *\n * @cssprop --menu-panel-bg - Background of the floating panel\n * @cssprop --menu-panel-border-color - Border color of the floating panel\n * @cssprop --menu-panel-shadow - Box shadow of the floating panel\n * @cssprop --menu-panel-blur - Backdrop blur amount for the floating panel\n * @cssprop --menu-panel-min-width - Minimum width of the floating panel\n * @cssprop --menu-panel-max-height - Maximum height of the floating panel before it scrolls\n * @cssprop --menu-panel-radius - Border radius of the floating panel\n *\n * @example\n * ```html\n * <ore-menu>\n * <button slot=\"trigger\">Actions</button>\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\">Delete</ore-menu-item>\n * </ore-menu>\n * ```\n */\nexport const MENU_TAG = 'ore-menu' as const;\ndefine<OreMenuProps>(MENU_TAG, {\n props: {\n ...sizableBundle,\n ...disablableBundle,\n placement: prop.oneOf(\n ['bottom', 'bottom-start', 'bottom-end', 'top', 'top-start', 'top-end'] as const,\n 'bottom-start',\n ),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMenuEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const menuId = createStableId('menu');\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const abortSignal = lifecycleSignal(onCleanup);\n let triggerEl: HTMLElement | null = null;\n let panelEl: HTMLElement | null = null;\n let cleanupTrigger: (() => void) | null = null;\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n function getItems(): HTMLElement[] {\n return Array.from(el.querySelectorAll<HTMLElement>('ore-menu-item:not([disabled])'));\n }\n\n function getItemFocusable(item: HTMLElement | null | undefined): HTMLElement | null {\n if (!item) return null;\n\n return item.shadowRoot?.querySelector<HTMLElement>('[role^=\"menuitem\"]') ?? item;\n }\n\n function getFocusedItemIndex(): number {\n const items = getItems();\n\n return items.findIndex((item) => {\n const focusable = getItemFocusable(item);\n\n return item === document.activeElement || focusable === document.activeElement;\n });\n }\n\n const optionList = createOptionList<HTMLElement>({\n getBoundary: () => el,\n getItems: getItems,\n getPanel: () => panelEl,\n getReference: () => triggerEl,\n getTrigger: () => triggerEl,\n isDisabled: () => isDisabled.value,\n isItemDisabled: (item) => item.hasAttribute('disabled'),\n onClose: (reason) => emit('close', { reason }),\n onNavigate: (_action, index) => {\n const nextItem = getItems()[index];\n\n getItemFocusable(nextItem)?.focus();\n },\n onOpen: (reason) => emit('open', { reason }),\n positioning: {\n getPlacement: () => (props.placement.value ?? 'bottom-start') as Placement,\n matchWidth: false,\n offsetPx: 4,\n padding: 6,\n },\n signal: abortSignal,\n });\n const { isOpen: isOpenSignal } = optionList;\n\n const activateItem = (item: HTMLElement): void => {\n const type = item.getAttribute('type');\n const isCheckable = isCheckableItemType(type);\n\n if (type === 'checkbox') {\n item.toggleAttribute('checked', !item.hasAttribute('checked'));\n } else if (type === 'radio') {\n for (const radio of el.querySelectorAll<HTMLElement>('ore-menu-item[type=\"radio\"]')) {\n radio.toggleAttribute('checked', radio === item);\n }\n }\n\n const value = item.getAttribute('value') ?? '';\n const checked = isCheckable ? item.hasAttribute('checked') : undefined;\n\n emit('select', { checked, value });\n\n if (!isCheckable) {\n optionList.close('programmatic');\n }\n };\n\n const openFromKeyboardPress = createInteraction({\n keys: ['Enter', ' ', 'ArrowDown'],\n onPress: () => {\n optionList.open('keyboard');\n requestAnimationFrame(() => optionList.set(0));\n },\n });\n\n const activateFocusedFromKeyboardPress = createInteraction({\n onPress: () => {\n const focused = optionList.getActiveItem();\n\n if (focused) activateItem(focused);\n },\n });\n\n // ── Keyboard Navigation ───────────────────────────────────────────────────\n function handleMenuKeydown(e: KeyboardEvent) {\n if (isDisabled.value) return;\n\n const open = isOpenSignal.value;\n\n // When closed: open on Enter / Space / ArrowDown\n if (!open) {\n openFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n const currentFocusedIndex = getFocusedItemIndex();\n\n if (currentFocusedIndex >= 0) optionList.set(currentFocusedIndex);\n\n if (optionList.handleKeydown(e)) return;\n\n // When open: navigate and activate\n if (e.key === ' ' || e.key === 'Enter') {\n activateFocusedFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n if (e.key === 'Tab') {\n optionList.close('programmatic');\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n bind({\n on: {\n click: (e: MouseEvent) => {\n const path = e.composedPath();\n\n if (!isOpenSignal.value) return;\n\n const itemFromPath = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.tagName === 'ORE-MENU-ITEM',\n );\n const item = itemFromPath ?? (e.target as HTMLElement | null)?.closest<HTMLElement>('ore-menu-item') ?? null;\n\n if (!item || item.hasAttribute('disabled')) return;\n\n activateItem(item);\n },\n },\n });\n\n watch(() => {\n const open = isOpenSignal.value;\n\n if (!panelEl) return;\n\n panelEl.toggleAttribute('data-open', open);\n });\n\n function resolveTrigger() {\n cleanupTrigger?.();\n cleanupTrigger = null;\n\n const assigned = slots.elements('trigger').value;\n\n triggerEl = (assigned?.[0] as HTMLElement | undefined) ?? null;\n\n if (!triggerEl) return;\n\n const cleanups: Array<() => void> = [];\n const removeAria = aria(triggerEl, {\n controls: () => menuId,\n disabled: () => isDisabled.value,\n expanded: () => String(isOpenSignal.value),\n haspopup: 'menu',\n });\n\n const onTriggerClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled.value) return;\n\n optionList.toggle();\n };\n const onTriggerKeydown = (event: KeyboardEvent) => {\n handleMenuKeydown(event);\n };\n\n triggerEl.addEventListener('click', onTriggerClick);\n triggerEl.addEventListener('keydown', onTriggerKeydown);\n cleanups.push(() => triggerEl?.removeEventListener('click', onTriggerClick));\n cleanups.push(() => triggerEl?.removeEventListener('keydown', onTriggerKeydown));\n\n cleanupTrigger = () => {\n removeAria();\n\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n rippleWatch(slots.elements('trigger'), resolveTrigger, { immediate: true });\n\n onMounted(() => {\n return () => {\n cleanupTrigger?.();\n cleanupTrigger = null;\n };\n });\n\n return html`\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-panel\"\n part=\"panel\"\n id=\"${menuId}\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n @keydown=\"${handleMenuKeydown}\"\n ref=\"${(el: HTMLElement | null) => {\n panelEl = el;\n }}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles, sizeVariantMixin(), forcedColorsMixin],\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"menu.js","names":[],"sources":["../src/overlay/menu/menu.ts"],"sourcesContent":["import type { Placement } from '@vielzeug/orbit';\n\nimport {\n createStableId,\n define,\n html,\n prop,\n aria,\n bind,\n getHost,\n onCleanup,\n onMounted,\n useEmit,\n useSlots,\n watchEffect,\n} from '@vielzeug/ore';\nimport { computed, watch as rippleWatch } from '@vielzeug/ripple';\n\nimport type { ComponentSize } from '../../types';\n\nimport {\n lifecycleSignal,\n createInteraction,\n createOptionList,\n type DropdownCloseReason,\n type OverlayOpenDetail,\n} from '../../headless';\nimport { disablableBundle, MENU_SIZE_PRESET, sizableBundle } from '../../shared';\nimport { colorThemeMixin, forcedColorsMixin, sizeVariantMixin } from '../../styles';\nimport menuItemStyles from './menu-item.css?inline';\nimport menuSeparatorStyles from './menu-separator.css?inline';\nimport componentStyles from './menu.css?inline';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport interface MenuSelectDetail {\n value: string;\n checked?: boolean;\n}\n\nexport type OreMenuItemType = 'checkbox' | 'radio';\n\nexport type OreMenuEvents = {\n close: { reason: DropdownCloseReason };\n open: OverlayOpenDetail;\n select: MenuSelectDetail;\n};\n\nexport type OreMenuItemProps = {\n checked?: boolean;\n disabled?: boolean;\n type?: OreMenuItemType;\n value?: string;\n};\n\nexport type OreMenuProps = {\n disabled?: boolean;\n placement?: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end';\n size?: ComponentSize;\n};\n\n// ── Styles ─────────────────────────────────────────────────────────────\n\n// ── Menu Item Component ─────────────────────────────────────────────────────────────\n\n/**\n * A selectable action item used inside `<ore-menu>`.\n *\n * @element ore-menu-item\n *\n * @attr {boolean} checked - Checked state for `checkbox` and `radio` item types\n * @attr {boolean} disabled - Disables selection and pointer interaction\n * @attr {'checkbox'|'radio'} type - Optional checkable menu item mode\n * @attr {string} value - Value emitted by parent menu on selection\n *\n * @slot - Item label/content\n * @slot icon - Optional leading icon content\n *\n * @cssprop --menu-item-hover-bg - Background on hover\n * @cssprop --menu-item-focus-color - Text color when keyboard-focused\n * @cssprop --menu-item-focus-bg - Background when keyboard-focused\n * @cssprop --menu-item-selection-bg - Background for checkbox/radio items (unselected)\n * @cssprop --menu-item-checked-color - Text color when checked\n * @cssprop --menu-item-checked-bg - Background when checked\n *\n * @part item - Root item container element.\n * @part item-label - Label text container.\n * @part icon-slot - Leading icon slot container.\n *\n * @example\n * ```html\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\" disabled>Delete</ore-menu-item>\n * <ore-menu-item type=\"checkbox\" value=\"wrap\" checked>Word wrap</ore-menu-item>\n * <ore-menu-item type=\"radio\" value=\"left\">Align left</ore-menu-item>\n * ```\n */\nexport const MENU_ITEM_TAG = 'ore-menu-item' as const;\ndefine<OreMenuItemProps>(MENU_ITEM_TAG, {\n props: {\n checked: prop.bool(false),\n disabled: prop.bool(false),\n type: prop.string<OreMenuItemType>(),\n value: prop.string(),\n },\n setup(props) {\n const isCheckable = () => props.type.value === 'checkbox' || props.type.value === 'radio';\n const isChecked = () => isCheckable() && props.checked.value;\n const itemRole = () => {\n if (props.type.value === 'checkbox') return 'menuitemcheckbox';\n\n if (props.type.value === 'radio') return 'menuitemradio';\n\n return 'menuitem';\n };\n const itemClass = () => {\n const type = props.type.value;\n\n return [\n 'item',\n type === 'checkbox' ? 'is-checkbox' : '',\n type === 'radio' ? 'is-radio' : '',\n isChecked() ? 'is-checked' : '',\n ]\n .filter(Boolean)\n .join(' ');\n };\n\n return isCheckable()\n ? html`\n <div\n class=\"${itemClass}\"\n tabindex=\"-1\"\n role=\"${itemRole}\"\n aria-checked=\"${() => String(isChecked())}\"\n aria-disabled=\"${props.disabled}\">\n <span class=\"item-check\" aria-hidden=\"true\"></span>\n <span class=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\"><slot></slot></span>\n </div>\n `\n : html`\n <div class=\"item\" tabindex=\"-1\" role=\"menuitem\" aria-disabled=\"${props.disabled}\">\n <span class=\"icon-slot\"><slot name=\"icon\"></slot></span>\n <span class=\"item-label\"><slot></slot></span>\n </div>\n `;\n },\n styles: [colorThemeMixin, menuItemStyles],\n});\n\n// ── Menu Separator ─────────────────────────────────────────────────────────────\n\n/**\n * Visual separator used to group menu items inside `<ore-menu>`.\n *\n * @element ore-menu-separator\n *\n * @example\n * ```html\n * <ore-menu-item value=\"cut\">Cut</ore-menu-item>\n * <ore-menu-separator></ore-menu-separator>\n * <ore-menu-item value=\"paste\">Paste</ore-menu-item>\n * ```\n */\nexport const SEPARATOR_TAG = 'ore-menu-separator' as const;\ndefine(SEPARATOR_TAG, {\n setup() {\n return html``;\n },\n styles: [menuSeparatorStyles],\n});\n\n// ── Menu Component ─────────────────────────────────────────────────────────────\n\nconst isCheckableItemType = (value: string | null): value is OreMenuItemType =>\n value === 'checkbox' || value === 'radio';\n\n/**\n * Action dropdown menu triggered by a slotted trigger element.\n *\n * @element ore-menu\n * @element ore-menu-item - Clickable menu option (place in default slot)\n * @element ore-menu-separator - Visual divider between menu groups\n *\n * @attr {boolean} disabled - Disables opening and keyboard interaction\n * @attr {string} placement - Panel placement: 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end' (default: 'bottom-start')\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n *\n * @fires open - Fired when the menu opens. detail: { reason: 'trigger' | 'programmatic' }\n * @fires close - Fired when the menu closes. detail: { reason: 'escape' | 'outsideClick' | 'programmatic' | 'trigger' }\n * @fires select - Fired when an item is selected. detail: { value: string, checked?: boolean }\n *\n * @slot trigger - Trigger element that toggles menu visibility\n * @slot - Menu content (`<ore-menu-item>` and `<ore-menu-separator>`)\n *\n * @part panel - Floating menu panel container\n *\n * @cssprop --menu-panel-bg - Background of the floating panel\n * @cssprop --menu-panel-border-color - Border color of the floating panel\n * @cssprop --menu-panel-shadow - Box shadow of the floating panel\n * @cssprop --menu-panel-blur - Backdrop blur amount for the floating panel\n * @cssprop --menu-panel-min-width - Minimum width of the floating panel\n * @cssprop --menu-panel-max-height - Maximum height of the floating panel before it scrolls\n * @cssprop --menu-panel-radius - Border radius of the floating panel\n *\n * @example\n * ```html\n * <ore-menu>\n * <button slot=\"trigger\">Actions</button>\n * <ore-menu-item value=\"edit\">Edit</ore-menu-item>\n * <ore-menu-item value=\"delete\">Delete</ore-menu-item>\n * </ore-menu>\n * ```\n */\nexport const MENU_TAG = 'ore-menu' as const;\ndefine<OreMenuProps>(MENU_TAG, {\n props: {\n ...sizableBundle,\n ...disablableBundle,\n placement: prop.oneOf(\n ['bottom', 'bottom-start', 'bottom-end', 'top', 'top-start', 'top-end'] as const,\n 'bottom-start',\n ),\n },\n setup(props) {\n const el = getHost();\n const emit = useEmit<OreMenuEvents>();\n const slots = useSlots();\n const watch = watchEffect;\n\n const menuId = createStableId('menu');\n const isDisabled = computed(() => Boolean(props.disabled.value));\n const abortSignal = lifecycleSignal(onCleanup);\n let triggerEl: HTMLElement | null = null;\n let panelEl: HTMLElement | null = null;\n let cleanupTrigger: (() => void) | null = null;\n\n // ── Helpers ───────────────────────────────────────────────────────────────\n function getItems(): HTMLElement[] {\n return Array.from(el.querySelectorAll<HTMLElement>('ore-menu-item:not([disabled])'));\n }\n\n function getItemFocusable(item: HTMLElement | null | undefined): HTMLElement | null {\n if (!item) return null;\n\n return item.shadowRoot?.querySelector<HTMLElement>('[role^=\"menuitem\"]') ?? item;\n }\n\n function getFocusedItemIndex(): number {\n const items = getItems();\n\n return items.findIndex((item) => {\n const focusable = getItemFocusable(item);\n\n return item === document.activeElement || focusable === document.activeElement;\n });\n }\n\n const optionList = createOptionList<HTMLElement>({\n getBoundary: () => el,\n getItems: getItems,\n getPanel: () => panelEl,\n getReference: () => triggerEl,\n getTrigger: () => triggerEl,\n isDisabled: () => isDisabled.value,\n isItemDisabled: (item) => item.hasAttribute('disabled'),\n onClose: (reason) => emit('close', { reason }),\n onNavigate: (_action, index) => {\n const nextItem = getItems()[index];\n\n getItemFocusable(nextItem)?.focus();\n },\n onOpen: (reason) => emit('open', { reason }),\n positioning: {\n getPlacement: () => (props.placement.value ?? 'bottom-start') as Placement,\n matchWidth: false,\n offsetPx: 4,\n padding: 6,\n },\n signal: abortSignal,\n });\n const { isOpen: isOpenSignal } = optionList;\n\n const activateItem = (item: HTMLElement): void => {\n const type = item.getAttribute('type');\n const isCheckable = isCheckableItemType(type);\n\n if (type === 'checkbox') {\n item.toggleAttribute('checked', !item.hasAttribute('checked'));\n } else if (type === 'radio') {\n for (const radio of el.querySelectorAll<HTMLElement>('ore-menu-item[type=\"radio\"]')) {\n radio.toggleAttribute('checked', radio === item);\n }\n }\n\n const value = item.getAttribute('value') ?? '';\n const checked = isCheckable ? item.hasAttribute('checked') : undefined;\n\n emit('select', { checked, value });\n\n if (!isCheckable) {\n optionList.close('programmatic');\n }\n };\n\n const openFromKeyboardPress = createInteraction({\n keys: ['Enter', ' ', 'ArrowDown'],\n onPress: () => {\n optionList.open('keyboard');\n requestAnimationFrame(() => optionList.set(0));\n },\n });\n\n const activateFocusedFromKeyboardPress = createInteraction({\n onPress: () => {\n const focused = optionList.getActiveItem();\n\n if (focused) activateItem(focused);\n },\n });\n\n // ── Keyboard Navigation ───────────────────────────────────────────────────\n function handleMenuKeydown(e: KeyboardEvent) {\n if (isDisabled.value) return;\n\n const open = isOpenSignal.value;\n\n // When closed: open on Enter / Space / ArrowDown\n if (!open) {\n openFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n const currentFocusedIndex = getFocusedItemIndex();\n\n if (currentFocusedIndex >= 0) optionList.set(currentFocusedIndex);\n\n if (optionList.handleKeydown(e)) return;\n\n // When open: navigate and activate\n if (e.key === ' ' || e.key === 'Enter') {\n activateFocusedFromKeyboardPress.handleKeydown(e);\n\n return;\n }\n\n if (e.key === 'Tab') {\n optionList.close('programmatic');\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n bind({\n on: {\n click: (e: MouseEvent) => {\n const path = e.composedPath();\n\n if (!isOpenSignal.value) return;\n\n const itemFromPath = path.find(\n (node): node is HTMLElement => node instanceof HTMLElement && node.tagName === 'ORE-MENU-ITEM',\n );\n const item = itemFromPath ?? (e.target as HTMLElement | null)?.closest<HTMLElement>('ore-menu-item') ?? null;\n\n if (!item || item.hasAttribute('disabled')) return;\n\n activateItem(item);\n },\n },\n });\n\n watch(() => {\n const open = isOpenSignal.value;\n\n if (!panelEl) return;\n\n panelEl.toggleAttribute('data-open', open);\n });\n\n function resolveTrigger() {\n cleanupTrigger?.();\n cleanupTrigger = null;\n\n const assigned = slots.elements('trigger').value;\n\n triggerEl = (assigned?.[0] as HTMLElement | undefined) ?? null;\n\n if (!triggerEl) return;\n\n const cleanups: Array<() => void> = [];\n const removeAria = aria(triggerEl, {\n controls: () => menuId,\n disabled: () => isDisabled.value,\n expanded: () => String(isOpenSignal.value),\n haspopup: 'menu',\n });\n\n const onTriggerClick = (event: MouseEvent) => {\n event.stopPropagation();\n\n if (isDisabled.value) return;\n\n optionList.toggle();\n };\n const onTriggerKeydown = (event: KeyboardEvent) => {\n handleMenuKeydown(event);\n };\n\n triggerEl.addEventListener('click', onTriggerClick);\n triggerEl.addEventListener('keydown', onTriggerKeydown);\n cleanups.push(() => triggerEl?.removeEventListener('click', onTriggerClick));\n cleanups.push(() => triggerEl?.removeEventListener('keydown', onTriggerKeydown));\n\n cleanupTrigger = () => {\n removeAria();\n\n for (const cleanup of cleanups) cleanup();\n };\n }\n\n rippleWatch(slots.elements('trigger'), resolveTrigger, { immediate: true });\n\n onMounted(() => {\n return () => {\n cleanupTrigger?.();\n cleanupTrigger = null;\n };\n });\n\n return html`\n <slot name=\"trigger\"></slot>\n <div\n class=\"menu-panel\"\n part=\"panel\"\n id=\"${menuId}\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n @keydown=\"${handleMenuKeydown}\"\n ref=\"${(el: HTMLElement | null) => {\n panelEl = el;\n }}\">\n <slot></slot>\n </div>\n `;\n },\n styles: [componentStyles, sizeVariantMixin(MENU_SIZE_PRESET), forcedColorsMixin],\n});\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
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("./shared/prop-bundles.cjs"),
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./styles/mixins/accessibility.css.cjs"),t=require("./styles/mixins/shape.css.cjs"),n=require("./styles/mixins/states.css.cjs"),r=require("./styles/mixins/theme.css.cjs"),i=require("./styles/mixins/field-variants.css.cjs"),a=require("./shared/prop-bundles.cjs"),o=require("./shared/size-presets.cjs"),s=require("./headless/ore.cjs"),c=require("./headless/auto-resize.cjs"),l=require("./headless/ref-callback.cjs"),u=require("./headless/text-field.cjs"),d=require("./headless/composer.cjs"),f=require("./inputs/shared/field-binding.cjs"),p=require("./inputs/shared/form-context.cjs"),m=require("./inputs/shared/templates.cjs"),h=require("./inputs/message-composer/message-composer.css.cjs");let g=require("@vielzeug/ore"),_=require("@vielzeug/ripple"),v=require("@vielzeug/ore/forms"),y=require("@vielzeug/ore/directives");var b=`Send message`,x=`arrow-up`,S=`ore-message-composer`;(0,g.define)(S,{formAssociated:!0,props:{...a.themableBundle,...a.sizableBundle,...a.disablableBundle,...a.loadableBundle,"clear-on-send":g.prop.bool(!0),error:g.prop.string(),fullwidth:g.prop.bool(!1),helper:g.prop.string(),label:g.prop.string(),maxlength:g.prop.json(void 0),name:g.prop.string(),placeholder:g.prop.string(),readonly:g.prop.bool(!1),ref:g.prop.data(),required:g.prop.bool(!1),rows:g.prop.json(void 0),"send-icon":g.prop.string(),"send-label":g.prop.string(),"send-shortcut":g.prop.oneOf([`enter`,`mod+enter`],`enter`),success:g.prop.bool(!1),value:g.prop.string(),variant:g.prop.string()},setup(e){let t=(0,g.useEmit)(),n=(0,g.useSlots)(),r=(0,g.inject)(p.FORM_CTX),i=p.useFormContext(e,r),a=i.disabled,o=s.lifecycleSignal(g.onCleanup),h=(0,g.ref)(),S=c.createAutoResize(),C=u.createTextField({disabled:a,error:e.error,helper:e.helper,maxLength:e.maxlength,onInput:(e,n)=>t(`input`,{originalEvent:e,value:n}),prefix:`composer`,readonly:e.readonly,required:e.required,signal:o,validateOn:r?.validateOn,value:e.value});C.attachFormField((0,v.useField)({disabled:C.disabled,onReset:()=>{C.reset(),S.recompute()},toFormValue:e=>e,validationMessage:C.validationMessage,validity:C.validity,value:C.value}));function w(n){t(`send`,{originalEvent:n,value:C.value.value.trim()})&&e[`clear-on-send`].value!==!1&&(C.clear(),S.recompute(),h.value?.focus())}let T=d.createComposerControl({disabled:a,loading:(0,_.computed)(()=>!!e.loading.value),onSend:w,sendShortcut:e[`send-shortcut`],value:C.value});return(0,g.onElement)(h,t=>{let n=C.wire(t),r=S.wire(t),i=l.bindRefCallback(e.ref,t),a=e=>T.handleKeydown(e);return t.addEventListener(`keydown`,a),()=>{t.removeEventListener(`keydown`,a),n(),r(),i()}}),(0,g.bind)({attr:{error:f.errorAttr(C.errorText),size:i.size,success:()=>e.success.value&&!C.errorText.value?!0:void 0,variant:i.variant}}),g.html`
|
|
2
2
|
<div class="composer" part="composer">
|
|
3
3
|
<textarea
|
|
4
4
|
class="field"
|
|
5
5
|
part="field"
|
|
6
|
-
ref="${
|
|
6
|
+
ref="${h}"
|
|
7
7
|
:rows="${()=>e.rows.value??1}"
|
|
8
8
|
:name="${()=>e.name.value??``}"
|
|
9
9
|
:placeholder="${()=>e.placeholder.value??`Message…`}"
|
|
@@ -11,19 +11,13 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
11
11
|
?disabled="${a}"
|
|
12
12
|
?readonly="${()=>!!e.readonly.value}"
|
|
13
13
|
?required="${()=>!!e.required.value}"
|
|
14
|
-
:value="${(0,
|
|
14
|
+
:value="${(0,y.live)(C.value)}"
|
|
15
15
|
:aria-label="${()=>e.label.value||`Message`}"
|
|
16
|
-
:aria-keyshortcuts="${
|
|
17
|
-
:aria-describedby="${
|
|
18
|
-
:aria-errormessage="${
|
|
19
|
-
:aria-invalid="${
|
|
20
|
-
|
|
21
|
-
<div id="${S.assistiveId}" class="helper-text" aria-live="polite" part="helper" ?hidden="${()=>!!S.errorText.value||!S.helperText.value}">
|
|
22
|
-
${()=>S.helperText.value}
|
|
23
|
-
</div>
|
|
24
|
-
<div id="${S.errorId}" class="helper-text" role="alert" part="error" ?hidden="${()=>!S.errorText.value}">
|
|
25
|
-
${()=>S.errorText.value}
|
|
26
|
-
</div>
|
|
16
|
+
:aria-keyshortcuts="${T.keyShortcutsHint}"
|
|
17
|
+
:aria-describedby="${C.ariaDescribedBy}"
|
|
18
|
+
:aria-errormessage="${C.ariaErrorMessage}"
|
|
19
|
+
:aria-invalid="${C.ariaInvalid}"></textarea>
|
|
20
|
+
${m.renderStatusIcon(C.errorText)} ${m.renderFieldStatusRegion(C)}
|
|
27
21
|
<div class="toolbar" part="toolbar">
|
|
28
22
|
<div class="toolbar-start" part="toolbar-start">
|
|
29
23
|
<slot name="prefix"></slot>
|
|
@@ -31,7 +25,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
31
25
|
<div class="toolbar-end" part="toolbar-end">
|
|
32
26
|
<slot name="suffix"></slot>
|
|
33
27
|
<slot name="send"></slot>
|
|
34
|
-
${()=>n.has(`send`).value?``:
|
|
28
|
+
${()=>n.has(`send`).value?``:g.html`<ore-button
|
|
35
29
|
class="send-btn"
|
|
36
30
|
part="send-button"
|
|
37
31
|
type="button"
|
|
@@ -39,14 +33,14 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
39
33
|
variant="solid"
|
|
40
34
|
:color="${()=>e.color.value||`primary`}"
|
|
41
35
|
:size="${i.size}"
|
|
42
|
-
:label="${()=>e[`send-label`].value||
|
|
36
|
+
:label="${()=>e[`send-label`].value||b}"
|
|
43
37
|
?loading="${()=>!!e.loading.value}"
|
|
44
|
-
?disabled="${()=>!
|
|
45
|
-
@click="${e=>
|
|
46
|
-
<ore-icon name="${()=>e[`send-icon`].value||
|
|
38
|
+
?disabled="${()=>!T.canSend.value}"
|
|
39
|
+
@click="${e=>T.send(e)}">
|
|
40
|
+
<ore-icon name="${()=>e[`send-icon`].value||x}" size="16"></ore-icon>
|
|
47
41
|
</ore-button>`}
|
|
48
42
|
</div>
|
|
49
43
|
</div>
|
|
50
44
|
</div>
|
|
51
|
-
`},shadow:{delegatesFocus:!0},styles:[r.colorThemeMixin,e.coarsePointerMixin,e.reducedMotionMixin,n.disabledLoadingMixin,r.forcedColorsMixin,r.forcedColorsFocusMixin(`.field`),t.sizeVariantMixin(
|
|
45
|
+
`},shadow:{delegatesFocus:!0},styles:[r.colorThemeMixin,e.coarsePointerMixin,e.reducedMotionMixin,n.disabledLoadingMixin,r.forcedColorsMixin,r.forcedColorsFocusMixin(`.field`),t.sizeVariantMixin(o.MESSAGE_COMPOSER_SIZE_PRESET),h.default,i.fieldVariantMixin({container:`.composer`,text:`.field`,tokenPrefix:`message-composer`})]}),exports.MESSAGE_COMPOSER_TAG=S;
|
|
52
46
|
//# sourceMappingURL=message-composer.cjs.map
|