@funstack/ui 0.1.0-alpha.2 → 0.1.0-alpha.4
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/accordion-context.mjs.map +1 -1
- package/dist/accordion/accordion-item.mjs +18 -4
- package/dist/accordion/accordion-item.mjs.map +1 -1
- package/dist/accordion/accordion-panel.mjs +2 -3
- package/dist/accordion/accordion-panel.mjs.map +1 -1
- package/dist/accordion/accordion-root.d.mts.map +1 -1
- package/dist/accordion/accordion-root.mjs +34 -15
- package/dist/accordion/accordion-root.mjs.map +1 -1
- package/dist/accordion/accordion-trigger.mjs +4 -5
- package/dist/accordion/accordion-trigger.mjs.map +1 -1
- package/dist/checkbox/checkbox.d.mts +5 -3
- package/dist/checkbox/checkbox.d.mts.map +1 -1
- package/dist/checkbox/checkbox.mjs +25 -9
- package/dist/checkbox/checkbox.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-item.d.mts +2 -1
- package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -1
- package/dist/checkbox-group/checkbox-group-item.mjs +3 -1
- package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -1
- package/dist/checkbox-group/checkbox-group-root.d.mts +5 -2
- package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -1
- package/dist/checkbox-group/checkbox-group-root.mjs +24 -5
- package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -1
- package/dist/combobox/combobox-clear.mjs +2 -1
- package/dist/combobox/combobox-clear.mjs.map +1 -1
- package/dist/combobox/combobox-context.mjs.map +1 -1
- package/dist/combobox/combobox-input.d.mts +3 -1
- package/dist/combobox/combobox-input.d.mts.map +1 -1
- package/dist/combobox/combobox-input.mjs +16 -2
- package/dist/combobox/combobox-input.mjs.map +1 -1
- package/dist/combobox/combobox-list.mjs +3 -2
- package/dist/combobox/combobox-list.mjs.map +1 -1
- package/dist/combobox/combobox-popup.d.mts.map +1 -1
- package/dist/combobox/combobox-popup.mjs +7 -2
- package/dist/combobox/combobox-popup.mjs.map +1 -1
- package/dist/combobox/combobox-root.d.mts.map +1 -1
- package/dist/combobox/combobox-root.mjs +20 -5
- package/dist/combobox/combobox-root.mjs.map +1 -1
- package/dist/combobox/combobox-trigger.mjs +2 -1
- package/dist/combobox/combobox-trigger.mjs.map +1 -1
- package/dist/dialog/dialog-close.d.mts +17 -10
- package/dist/dialog/dialog-close.d.mts.map +1 -1
- package/dist/dialog/dialog-close.mjs +33 -21
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-context.mjs.map +1 -1
- package/dist/dialog/dialog-form.d.mts +23 -10
- package/dist/dialog/dialog-form.d.mts.map +1 -1
- package/dist/dialog/dialog-form.mjs +51 -32
- package/dist/dialog/dialog-form.mjs.map +1 -1
- package/dist/dialog/dialog-popup.d.mts +10 -4
- package/dist/dialog/dialog-popup.d.mts.map +1 -1
- package/dist/dialog/dialog-popup.mjs +117 -44
- package/dist/dialog/dialog-popup.mjs.map +1 -1
- package/dist/dialog/dialog-root.d.mts +14 -8
- package/dist/dialog/dialog-root.d.mts.map +1 -1
- package/dist/dialog/dialog-root.mjs +67 -12
- package/dist/dialog/dialog-root.mjs.map +1 -1
- package/dist/dialog/dialog-trigger.d.mts +3 -2
- package/dist/dialog/dialog-trigger.d.mts.map +1 -1
- package/dist/dialog/dialog-trigger.mjs +2 -1
- package/dist/dialog/dialog-trigger.mjs.map +1 -1
- package/dist/dialog/index.d.mts +4 -2
- package/dist/dialog/index.d.mts.map +1 -1
- package/dist/dialog/index.mjs +4 -2
- package/dist/dialog/index.mjs.map +1 -1
- package/dist/field/field-control.d.mts +33 -0
- package/dist/field/field-control.d.mts.map +1 -0
- package/dist/field/field-control.mjs +68 -0
- package/dist/field/field-control.mjs.map +1 -0
- package/dist/field/field-description.d.mts +24 -0
- package/dist/field/field-description.d.mts.map +1 -0
- package/dist/field/field-description.mjs +34 -0
- package/dist/field/field-description.mjs.map +1 -0
- package/dist/field/field-error.d.mts +30 -0
- package/dist/field/field-error.d.mts.map +1 -0
- package/dist/field/field-error.mjs +42 -0
- package/dist/field/field-error.mjs.map +1 -0
- package/dist/field/field-label.d.mts +25 -0
- package/dist/field/field-label.d.mts.map +1 -0
- package/dist/field/field-label.mjs +46 -0
- package/dist/field/field-label.mjs.map +1 -0
- package/dist/field/field-root.d.mts +53 -0
- package/dist/field/field-root.d.mts.map +1 -0
- package/dist/field/field-root.mjs +319 -0
- package/dist/field/field-root.mjs.map +1 -0
- package/dist/field/index.d.mts +18 -0
- package/dist/field/index.d.mts.map +1 -0
- package/dist/field/index.mjs +17 -0
- package/dist/field/index.mjs.map +1 -0
- package/dist/form/form-root.d.mts +58 -0
- package/dist/form/form-root.d.mts.map +1 -0
- package/dist/form/form-root.mjs +147 -0
- package/dist/form/form-root.mjs.map +1 -0
- package/dist/form/form-submit.d.mts +31 -0
- package/dist/form/form-submit.d.mts.map +1 -0
- package/dist/form/form-submit.mjs +53 -0
- package/dist/form/form-submit.mjs.map +1 -0
- package/dist/form/index.d.mts +11 -0
- package/dist/form/index.d.mts.map +1 -0
- package/dist/form/index.mjs +11 -0
- package/dist/form/index.mjs.map +1 -0
- package/dist/index.d.mts +53 -1
- package/dist/index.mjs +11 -1
- package/dist/internal/announce.mjs +53 -0
- package/dist/internal/announce.mjs.map +1 -0
- package/dist/internal/content-focus-probe.mjs +42 -0
- package/dist/internal/content-focus-probe.mjs.map +1 -0
- package/dist/internal/decimal.mjs +27 -0
- package/dist/internal/decimal.mjs.map +1 -0
- package/dist/internal/dev-warn.mjs +17 -0
- package/dist/internal/dev-warn.mjs.map +1 -0
- package/dist/internal/field-context.d.mts +19 -0
- package/dist/internal/field-context.d.mts.map +1 -0
- package/dist/internal/field-context.mjs +14 -0
- package/dist/internal/field-context.mjs.map +1 -0
- package/dist/internal/focus-first-invalid.mjs +67 -0
- package/dist/internal/focus-first-invalid.mjs.map +1 -0
- package/dist/internal/form-status-watcher.mjs +31 -0
- package/dist/internal/form-status-watcher.mjs.map +1 -0
- package/dist/internal/modal-registry.mjs +39 -0
- package/dist/internal/modal-registry.mjs.map +1 -0
- package/dist/internal/range-value.mjs +51 -0
- package/dist/internal/range-value.mjs.map +1 -0
- package/dist/internal/render-element.mjs +54 -0
- package/dist/internal/render-element.mjs.map +1 -0
- package/dist/internal/toolbar-context.mjs +17 -0
- package/dist/internal/toolbar-context.mjs.map +1 -0
- package/dist/internal/types.d.mts +3 -1
- package/dist/internal/types.d.mts.map +1 -1
- package/dist/internal/use-controllable-action.mjs +33 -15
- package/dist/internal/use-controllable-action.mjs.map +1 -1
- package/dist/internal/use-exit-animation.mjs +26 -6
- package/dist/internal/use-exit-animation.mjs.map +1 -1
- package/dist/internal/use-field-control.mjs +109 -0
- package/dist/internal/use-field-control.mjs.map +1 -0
- package/dist/internal/use-form-owner-event.mjs +44 -0
- package/dist/internal/use-form-owner-event.mjs.map +1 -0
- package/dist/internal/use-form-reset.mjs +10 -33
- package/dist/internal/use-form-reset.mjs.map +1 -1
- package/dist/internal/use-latest.mjs +32 -0
- package/dist/internal/use-latest.mjs.map +1 -0
- package/dist/internal/use-modal-registration.mjs +47 -0
- package/dist/internal/use-modal-registration.mjs.map +1 -0
- package/dist/internal/use-native-group-reconciliation.mjs +6 -5
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
- package/dist/internal/use-native-select-value.mjs +7 -5
- package/dist/internal/use-native-select-value.mjs.map +1 -1
- package/dist/internal/use-popover-popup.mjs +11 -24
- package/dist/internal/use-popover-popup.mjs.map +1 -1
- package/dist/internal/use-popover-root.mjs +58 -23
- package/dist/internal/use-popover-root.mjs.map +1 -1
- package/dist/internal/use-popover-trigger.mjs +1 -1
- package/dist/internal/use-render.mjs +11 -34
- package/dist/internal/use-render.mjs.map +1 -1
- package/dist/internal/use-roving-focus.mjs +255 -0
- package/dist/internal/use-roving-focus.mjs.map +1 -0
- package/dist/internal/validity.d.mts +24 -0
- package/dist/internal/validity.d.mts.map +1 -0
- package/dist/internal/validity.mjs +108 -0
- package/dist/internal/validity.mjs.map +1 -0
- package/dist/menu/menu-checkbox-item.mjs +2 -1
- package/dist/menu/menu-checkbox-item.mjs.map +1 -1
- package/dist/menu/menu-group.d.mts.map +1 -1
- package/dist/menu/menu-group.mjs +3 -2
- package/dist/menu/menu-group.mjs.map +1 -1
- package/dist/menu/menu-item.mjs +2 -1
- package/dist/menu/menu-item.mjs.map +1 -1
- package/dist/menu/menu-popup.d.mts.map +1 -1
- package/dist/menu/menu-popup.mjs +7 -2
- package/dist/menu/menu-popup.mjs.map +1 -1
- package/dist/menu/menu-radio-group.mjs +9 -4
- package/dist/menu/menu-radio-group.mjs.map +1 -1
- package/dist/menu/menu-radio-item.mjs +2 -1
- package/dist/menu/menu-radio-item.mjs.map +1 -1
- package/dist/menu/menu-root.mjs +1 -1
- package/dist/menu/menu-root.mjs.map +1 -1
- package/dist/menu/menu-trigger.d.mts +2 -1
- package/dist/menu/menu-trigger.d.mts.map +1 -1
- package/dist/menu/menu-trigger.mjs +15 -4
- package/dist/menu/menu-trigger.mjs.map +1 -1
- package/dist/meter/index.d.mts +24 -0
- package/dist/meter/index.d.mts.map +1 -0
- package/dist/meter/index.mjs +23 -0
- package/dist/meter/index.mjs.map +1 -0
- package/dist/meter/meter-context.mjs +13 -0
- package/dist/meter/meter-context.mjs.map +1 -0
- package/dist/meter/meter-indicator.d.mts +19 -0
- package/dist/meter/meter-indicator.d.mts.map +1 -0
- package/dist/meter/meter-indicator.mjs +25 -0
- package/dist/meter/meter-indicator.mjs.map +1 -0
- package/dist/meter/meter-label.d.mts +19 -0
- package/dist/meter/meter-label.d.mts.map +1 -0
- package/dist/meter/meter-label.mjs +28 -0
- package/dist/meter/meter-label.mjs.map +1 -0
- package/dist/meter/meter-region.d.mts +4 -0
- package/dist/meter/meter-region.d.mts.map +1 -0
- package/dist/meter/meter-region.mjs +39 -0
- package/dist/meter/meter-region.mjs.map +1 -0
- package/dist/meter/meter-root.d.mts +71 -0
- package/dist/meter/meter-root.d.mts.map +1 -0
- package/dist/meter/meter-root.mjs +83 -0
- package/dist/meter/meter-root.mjs.map +1 -0
- package/dist/meter/meter-track.d.mts +19 -0
- package/dist/meter/meter-track.d.mts.map +1 -0
- package/dist/meter/meter-track.mjs +25 -0
- package/dist/meter/meter-track.mjs.map +1 -0
- package/dist/meter/meter-value.d.mts +20 -0
- package/dist/meter/meter-value.d.mts.map +1 -0
- package/dist/meter/meter-value.mjs +29 -0
- package/dist/meter/meter-value.mjs.map +1 -0
- package/dist/number-field/index.d.mts +26 -0
- package/dist/number-field/index.d.mts.map +1 -0
- package/dist/number-field/index.mjs +26 -0
- package/dist/number-field/index.mjs.map +1 -0
- package/dist/number-field/number-field-arithmetic.mjs +78 -0
- package/dist/number-field/number-field-arithmetic.mjs.map +1 -0
- package/dist/number-field/number-field-context.d.mts +2 -0
- package/dist/number-field/number-field-context.mjs +13 -0
- package/dist/number-field/number-field-context.mjs.map +1 -0
- package/dist/number-field/number-field-decrement.d.mts +16 -0
- package/dist/number-field/number-field-decrement.d.mts.map +1 -0
- package/dist/number-field/number-field-decrement.mjs +19 -0
- package/dist/number-field/number-field-decrement.mjs.map +1 -0
- package/dist/number-field/number-field-format.mjs +279 -0
- package/dist/number-field/number-field-format.mjs.map +1 -0
- package/dist/number-field/number-field-increment.d.mts +32 -0
- package/dist/number-field/number-field-increment.d.mts.map +1 -0
- package/dist/number-field/number-field-increment.mjs +68 -0
- package/dist/number-field/number-field-increment.mjs.map +1 -0
- package/dist/number-field/number-field-input.d.mts +29 -0
- package/dist/number-field/number-field-input.d.mts.map +1 -0
- package/dist/number-field/number-field-input.mjs +163 -0
- package/dist/number-field/number-field-input.mjs.map +1 -0
- package/dist/number-field/number-field-label.d.mts +23 -0
- package/dist/number-field/number-field-label.d.mts.map +1 -0
- package/dist/number-field/number-field-label.mjs +47 -0
- package/dist/number-field/number-field-label.mjs.map +1 -0
- package/dist/number-field/number-field-root.d.mts +108 -0
- package/dist/number-field/number-field-root.d.mts.map +1 -0
- package/dist/number-field/number-field-root.mjs +523 -0
- package/dist/number-field/number-field-root.mjs.map +1 -0
- package/dist/number-field/use-step-button.mjs +107 -0
- package/dist/number-field/use-step-button.mjs.map +1 -0
- package/dist/popover/popover-close.mjs +2 -1
- package/dist/popover/popover-close.mjs.map +1 -1
- package/dist/popover/popover-popup.d.mts.map +1 -1
- package/dist/popover/popover-popup.mjs +7 -2
- package/dist/popover/popover-popup.mjs.map +1 -1
- package/dist/popover/popover-root.mjs +1 -1
- package/dist/progress/index.d.mts +24 -0
- package/dist/progress/index.d.mts.map +1 -0
- package/dist/progress/index.mjs +24 -0
- package/dist/progress/index.mjs.map +1 -0
- package/dist/progress/progress-context.mjs +13 -0
- package/dist/progress/progress-context.mjs.map +1 -0
- package/dist/progress/progress-indicator.d.mts +19 -0
- package/dist/progress/progress-indicator.d.mts.map +1 -0
- package/dist/progress/progress-indicator.mjs +29 -0
- package/dist/progress/progress-indicator.mjs.map +1 -0
- package/dist/progress/progress-label.d.mts +19 -0
- package/dist/progress/progress-label.d.mts.map +1 -0
- package/dist/progress/progress-label.mjs +30 -0
- package/dist/progress/progress-label.mjs.map +1 -0
- package/dist/progress/progress-root.d.mts +68 -0
- package/dist/progress/progress-root.d.mts.map +1 -0
- package/dist/progress/progress-root.mjs +80 -0
- package/dist/progress/progress-root.mjs.map +1 -0
- package/dist/progress/progress-track.d.mts +19 -0
- package/dist/progress/progress-track.d.mts.map +1 -0
- package/dist/progress/progress-track.mjs +29 -0
- package/dist/progress/progress-track.mjs.map +1 -0
- package/dist/progress/progress-value.d.mts +20 -0
- package/dist/progress/progress-value.d.mts.map +1 -0
- package/dist/progress/progress-value.mjs +33 -0
- package/dist/progress/progress-value.mjs.map +1 -0
- package/dist/radio-group/radio-group-root.d.mts +4 -2
- package/dist/radio-group/radio-group-root.d.mts.map +1 -1
- package/dist/radio-group/radio-group-root.mjs +34 -8
- package/dist/radio-group/radio-group-root.mjs.map +1 -1
- package/dist/select/select-group.d.mts.map +1 -1
- package/dist/select/select-group.mjs +6 -4
- package/dist/select/select-group.mjs.map +1 -1
- package/dist/select/select-root.d.mts +4 -2
- package/dist/select/select-root.d.mts.map +1 -1
- package/dist/select/select-root.mjs +26 -6
- package/dist/select/select-root.mjs.map +1 -1
- package/dist/slider/index.d.mts +32 -0
- package/dist/slider/index.d.mts.map +1 -0
- package/dist/slider/index.mjs +27 -0
- package/dist/slider/index.mjs.map +1 -0
- package/dist/slider/slider-arithmetic.mjs +104 -0
- package/dist/slider/slider-arithmetic.mjs.map +1 -0
- package/dist/slider/slider-context.mjs +13 -0
- package/dist/slider/slider-context.mjs.map +1 -0
- package/dist/slider/slider-indicator.d.mts +22 -0
- package/dist/slider/slider-indicator.d.mts.map +1 -0
- package/dist/slider/slider-indicator.mjs +36 -0
- package/dist/slider/slider-indicator.mjs.map +1 -0
- package/dist/slider/slider-label.d.mts +23 -0
- package/dist/slider/slider-label.d.mts.map +1 -0
- package/dist/slider/slider-label.mjs +44 -0
- package/dist/slider/slider-label.mjs.map +1 -0
- package/dist/slider/slider-root.d.mts +111 -0
- package/dist/slider/slider-root.d.mts.map +1 -0
- package/dist/slider/slider-root.mjs +583 -0
- package/dist/slider/slider-root.mjs.map +1 -0
- package/dist/slider/slider-thumb.d.mts +58 -0
- package/dist/slider/slider-thumb.d.mts.map +1 -0
- package/dist/slider/slider-thumb.mjs +146 -0
- package/dist/slider/slider-thumb.mjs.map +1 -0
- package/dist/slider/slider-track.d.mts +29 -0
- package/dist/slider/slider-track.d.mts.map +1 -0
- package/dist/slider/slider-track.mjs +55 -0
- package/dist/slider/slider-track.mjs.map +1 -0
- package/dist/slider/slider-value.d.mts +34 -0
- package/dist/slider/slider-value.d.mts.map +1 -0
- package/dist/slider/slider-value.mjs +38 -0
- package/dist/slider/slider-value.mjs.map +1 -0
- package/dist/switch/switch.d.mts +5 -3
- package/dist/switch/switch.d.mts.map +1 -1
- package/dist/switch/switch.mjs +24 -8
- package/dist/switch/switch.mjs.map +1 -1
- package/dist/tabs/tabs-indicator.mjs +2 -1
- package/dist/tabs/tabs-indicator.mjs.map +1 -1
- package/dist/tabs/tabs-root.mjs +13 -3
- package/dist/tabs/tabs-root.mjs.map +1 -1
- package/dist/tabs/tabs-tab.mjs +2 -1
- package/dist/tabs/tabs-tab.mjs.map +1 -1
- package/dist/toast/index.d.mts +25 -0
- package/dist/toast/index.d.mts.map +1 -0
- package/dist/toast/index.mjs +26 -0
- package/dist/toast/index.mjs.map +1 -0
- package/dist/toast/toast-action.d.mts +39 -0
- package/dist/toast/toast-action.d.mts.map +1 -0
- package/dist/toast/toast-action.mjs +65 -0
- package/dist/toast/toast-action.mjs.map +1 -0
- package/dist/toast/toast-close.d.mts +25 -0
- package/dist/toast/toast-close.d.mts.map +1 -0
- package/dist/toast/toast-close.mjs +47 -0
- package/dist/toast/toast-close.mjs.map +1 -0
- package/dist/toast/toast-context.mjs +19 -0
- package/dist/toast/toast-context.mjs.map +1 -0
- package/dist/toast/toast-description.d.mts +24 -0
- package/dist/toast/toast-description.d.mts.map +1 -0
- package/dist/toast/toast-description.mjs +35 -0
- package/dist/toast/toast-description.mjs.map +1 -0
- package/dist/toast/toast-root.d.mts +43 -0
- package/dist/toast/toast-root.d.mts.map +1 -0
- package/dist/toast/toast-root.mjs +205 -0
- package/dist/toast/toast-root.mjs.map +1 -0
- package/dist/toast/toast-title.d.mts +24 -0
- package/dist/toast/toast-title.d.mts.map +1 -0
- package/dist/toast/toast-title.mjs +33 -0
- package/dist/toast/toast-title.mjs.map +1 -0
- package/dist/toast/toast-viewport.d.mts +52 -0
- package/dist/toast/toast-viewport.d.mts.map +1 -0
- package/dist/toast/toast-viewport.mjs +644 -0
- package/dist/toast/toast-viewport.mjs.map +1 -0
- package/dist/toast/toast.d.mts +119 -0
- package/dist/toast/toast.d.mts.map +1 -0
- package/dist/toast/toast.mjs +157 -0
- package/dist/toast/toast.mjs.map +1 -0
- package/dist/toggle/toggle.mjs +2 -1
- package/dist/toggle/toggle.mjs.map +1 -1
- package/dist/toggle-group/index.d.mts +16 -0
- package/dist/toggle-group/index.d.mts.map +1 -0
- package/dist/toggle-group/index.mjs +15 -0
- package/dist/toggle-group/index.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-context.d.mts +4 -0
- package/dist/toggle-group/toggle-group-context.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-context.mjs +13 -0
- package/dist/toggle-group/toggle-group-context.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-item.d.mts +47 -0
- package/dist/toggle-group/toggle-group-item.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-item.mjs +63 -0
- package/dist/toggle-group/toggle-group-item.mjs.map +1 -0
- package/dist/toggle-group/toggle-group-root.d.mts +95 -0
- package/dist/toggle-group/toggle-group-root.d.mts.map +1 -0
- package/dist/toggle-group/toggle-group-root.mjs +211 -0
- package/dist/toggle-group/toggle-group-root.mjs.map +1 -0
- package/dist/toolbar/index.d.mts +18 -0
- package/dist/toolbar/index.d.mts.map +1 -0
- package/dist/toolbar/index.mjs +18 -0
- package/dist/toolbar/index.mjs.map +1 -0
- package/dist/toolbar/toolbar-button.d.mts +42 -0
- package/dist/toolbar/toolbar-button.d.mts.map +1 -0
- package/dist/toolbar/toolbar-button.mjs +50 -0
- package/dist/toolbar/toolbar-button.mjs.map +1 -0
- package/dist/toolbar/toolbar-root.d.mts +38 -0
- package/dist/toolbar/toolbar-root.d.mts.map +1 -0
- package/dist/toolbar/toolbar-root.mjs +66 -0
- package/dist/toolbar/toolbar-root.mjs.map +1 -0
- package/dist/toolbar/toolbar-separator.d.mts +25 -0
- package/dist/toolbar/toolbar-separator.d.mts.map +1 -0
- package/dist/toolbar/toolbar-separator.mjs +31 -0
- package/dist/toolbar/toolbar-separator.mjs.map +1 -0
- package/dist/tooltip/tooltip-popup.mjs +2 -1
- package/dist/tooltip/tooltip-popup.mjs.map +1 -1
- package/dist/tooltip/tooltip-root.mjs +16 -2
- package/dist/tooltip/tooltip-root.mjs.map +1 -1
- package/dist/tooltip/tooltip-trigger.mjs +2 -1
- package/dist/tooltip/tooltip-trigger.mjs.map +1 -1
- package/package.json +10 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//#region src/internal/validity.ts
|
|
2
|
+
const VALIDITY_KEYS = [
|
|
3
|
+
"valueMissing",
|
|
4
|
+
"typeMismatch",
|
|
5
|
+
"patternMismatch",
|
|
6
|
+
"tooLong",
|
|
7
|
+
"tooShort",
|
|
8
|
+
"rangeUnderflow",
|
|
9
|
+
"rangeOverflow",
|
|
10
|
+
"stepMismatch",
|
|
11
|
+
"badInput",
|
|
12
|
+
"customError",
|
|
13
|
+
"valid"
|
|
14
|
+
];
|
|
15
|
+
const VALID = Object.freeze({
|
|
16
|
+
valueMissing: false,
|
|
17
|
+
typeMismatch: false,
|
|
18
|
+
patternMismatch: false,
|
|
19
|
+
tooLong: false,
|
|
20
|
+
tooShort: false,
|
|
21
|
+
rangeUnderflow: false,
|
|
22
|
+
rangeOverflow: false,
|
|
23
|
+
stepMismatch: false,
|
|
24
|
+
badInput: false,
|
|
25
|
+
customError: false,
|
|
26
|
+
valid: true
|
|
27
|
+
});
|
|
28
|
+
function snapshotValidity(validity) {
|
|
29
|
+
const result = {};
|
|
30
|
+
for (const key of VALIDITY_KEYS) result[key] = validity[key];
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
function validityEquals(a, b) {
|
|
34
|
+
return VALIDITY_KEYS.every((key) => a[key] === b[key]);
|
|
35
|
+
}
|
|
36
|
+
function arraysEqual(a, b) {
|
|
37
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
38
|
+
}
|
|
39
|
+
const NONE = Object.freeze([]);
|
|
40
|
+
/** `""`, `[]`, `null` and `undefined` mean no error; empty strings inside an array are dropped. */
|
|
41
|
+
function normaliseErrorValue(value) {
|
|
42
|
+
if (value === null || value === void 0 || value === "") return NONE;
|
|
43
|
+
if (typeof value === "string") return [value];
|
|
44
|
+
const list = value.filter((v) => typeof v === "string" && v !== "");
|
|
45
|
+
return list.length === 0 ? NONE : list;
|
|
46
|
+
}
|
|
47
|
+
/** Some entry of `errors` is a non-empty error. */
|
|
48
|
+
function hasAnyError(errors) {
|
|
49
|
+
if (errors === void 0) return false;
|
|
50
|
+
return Object.values(errors).some((v) => normaliseErrorValue(v).length > 0);
|
|
51
|
+
}
|
|
52
|
+
let userInvalidSupport;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the environment implements `:user-invalid` (every engine the library supports does).
|
|
55
|
+
* Decided once per module, so the read is not on a hot path.
|
|
56
|
+
*
|
|
57
|
+
* Detected with `CSS.supports("selector(:user-invalid)")` rather than by `matches()` throwing:
|
|
58
|
+
* jsdom 30's selector engine knows the name and silently answers `false` (it does not throw), and
|
|
59
|
+
* its `CSS.supports` reports `false`, so the fallback engages there and nowhere else
|
|
60
|
+
* (memo/developer/field-form.md).
|
|
61
|
+
*/
|
|
62
|
+
function supportsUserInvalid() {
|
|
63
|
+
if (userInvalidSupport === void 0) try {
|
|
64
|
+
userInvalidSupport = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("selector(:user-invalid)");
|
|
65
|
+
} catch {
|
|
66
|
+
userInvalidSupport = false;
|
|
67
|
+
}
|
|
68
|
+
return userInvalidSupport;
|
|
69
|
+
}
|
|
70
|
+
function isValidatable(el) {
|
|
71
|
+
return el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Where `validity`, `validationMessage`, `:user-invalid` and `setCustomValidity` are read and
|
|
75
|
+
* written (design 7.1): the element itself when it is a form control, else the first named
|
|
76
|
+
* `willValidate` member of a `<fieldset>` (a group's first enabled item, not a CheckboxGroup
|
|
77
|
+
* Parent, which has no name), else the first `input` / `select` / `textarea` inside.
|
|
78
|
+
*/
|
|
79
|
+
function validityElementOf(el) {
|
|
80
|
+
if (el === null) return null;
|
|
81
|
+
if (isValidatable(el)) return el;
|
|
82
|
+
const controls = (el instanceof HTMLFieldSetElement ? [...el.elements] : [...el.querySelectorAll("input, select, textarea")]).filter(isValidatable);
|
|
83
|
+
return controls.find((c) => c.willValidate && c.name !== "") ?? controls.find((c) => c.willValidate) ?? controls[0] ?? null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* What the Field focuses (design 2.5): the control itself, or for a group the checked radio if
|
|
87
|
+
* any, else the first enabled named member (a group's first item, not a CheckboxGroup Parent,
|
|
88
|
+
* which has no name), else the first enabled member.
|
|
89
|
+
*/
|
|
90
|
+
function focusTargetOf(el) {
|
|
91
|
+
if (el === null) return null;
|
|
92
|
+
if (isValidatable(el)) return el;
|
|
93
|
+
const members = (el instanceof HTMLFieldSetElement ? [...el.elements] : [...el.querySelectorAll("input, select, textarea, button")]).filter((m) => m instanceof HTMLElement && !m.disabled && m.type !== "hidden");
|
|
94
|
+
return members.find((m) => m instanceof HTMLInputElement && m.type === "radio" && m.checked) ?? members.find((m) => isValidatable(m) && m.name !== "") ?? members.find(isValidatable) ?? members[0] ?? null;
|
|
95
|
+
}
|
|
96
|
+
/** Sorts elements into document order. */
|
|
97
|
+
function sortByDocumentOrder(items, elementOf) {
|
|
98
|
+
return [...items].sort((a, b) => {
|
|
99
|
+
const position = elementOf(a).compareDocumentPosition(elementOf(b));
|
|
100
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
|
101
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
102
|
+
return 0;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { VALID, arraysEqual, focusTargetOf, hasAnyError, normaliseErrorValue, snapshotValidity, sortByDocumentOrder, supportsUserInvalid, validityElementOf, validityEquals };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=validity.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validity.mjs","names":[],"sources":["../../src/internal/validity.ts"],"sourcesContent":["// Directive-free helpers for Field and Form (design/components/field-form.md 2, 14): the validity\n// snapshot, error-value normalisation, the `:user-invalid` read and the element resolution.\n\n/** A field error: a message, several messages, or nothing (`\"\"`, `[]`, `null`, `undefined`). */\nexport type FieldErrorValue = string | readonly string[] | null | undefined;\n\n/** Field errors keyed by field `name` (usually `state.errors` from `useActionState`). */\nexport type FormErrors = Readonly<Record<string, FieldErrorValue>>;\n\n/**\n * A plain, serialisable snapshot of the control's `ValidityState` (all `false` and\n * `valid: true` on the server and before the first read).\n */\nexport interface FieldValidity {\n readonly valueMissing: boolean;\n readonly typeMismatch: boolean;\n readonly patternMismatch: boolean;\n readonly tooLong: boolean;\n readonly tooShort: boolean;\n readonly rangeUnderflow: boolean;\n readonly rangeOverflow: boolean;\n readonly stepMismatch: boolean;\n readonly badInput: boolean;\n readonly customError: boolean;\n readonly valid: boolean;\n}\n\nconst VALIDITY_KEYS = [\n \"valueMissing\",\n \"typeMismatch\",\n \"patternMismatch\",\n \"tooLong\",\n \"tooShort\",\n \"rangeUnderflow\",\n \"rangeOverflow\",\n \"stepMismatch\",\n \"badInput\",\n \"customError\",\n \"valid\",\n] as const satisfies readonly (keyof FieldValidity)[];\n\nexport const VALID: FieldValidity = Object.freeze({\n valueMissing: false,\n typeMismatch: false,\n patternMismatch: false,\n tooLong: false,\n tooShort: false,\n rangeUnderflow: false,\n rangeOverflow: false,\n stepMismatch: false,\n badInput: false,\n customError: false,\n valid: true,\n});\n\nexport function snapshotValidity(validity: ValidityState): FieldValidity {\n const result = {} as Record<keyof FieldValidity, boolean>;\n for (const key of VALIDITY_KEYS) result[key] = validity[key];\n return result;\n}\n\nexport function validityEquals(a: FieldValidity, b: FieldValidity): boolean {\n return VALIDITY_KEYS.every((key) => a[key] === b[key]);\n}\n\nexport function arraysEqual(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i]);\n}\n\nconst NONE: readonly string[] = Object.freeze([]);\n\n/** `\"\"`, `[]`, `null` and `undefined` mean no error; empty strings inside an array are dropped. */\nexport function normaliseErrorValue(value: FieldErrorValue): readonly string[] {\n if (value === null || value === undefined || value === \"\") return NONE;\n if (typeof value === \"string\") return [value];\n const list = value.filter((v) => typeof v === \"string\" && v !== \"\");\n return list.length === 0 ? NONE : list;\n}\n\n/** Some entry of `errors` is a non-empty error. */\nexport function hasAnyError(errors: FormErrors | undefined): boolean {\n if (errors === undefined) return false;\n return Object.values(errors).some((v) => normaliseErrorValue(v).length > 0);\n}\n\nlet userInvalidSupport: boolean | undefined;\n\n/**\n * Whether the environment implements `:user-invalid` (every engine the library supports does).\n * Decided once per module, so the read is not on a hot path.\n *\n * Detected with `CSS.supports(\"selector(:user-invalid)\")` rather than by `matches()` throwing:\n * jsdom 30's selector engine knows the name and silently answers `false` (it does not throw), and\n * its `CSS.supports` reports `false`, so the fallback engages there and nowhere else\n * (memo/developer/field-form.md).\n */\nexport function supportsUserInvalid(): boolean {\n if (userInvalidSupport === undefined) {\n try {\n userInvalidSupport =\n typeof CSS !== \"undefined\" &&\n typeof CSS.supports === \"function\" &&\n CSS.supports(\"selector(:user-invalid)\");\n } catch {\n userInvalidSupport = false;\n }\n }\n return userInvalidSupport;\n}\n\n/** An element that takes part in constraint validation. */\nexport type ValidatableElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\n\nfunction isValidatable(el: Element): el is ValidatableElement {\n return (\n el instanceof HTMLInputElement ||\n el instanceof HTMLSelectElement ||\n el instanceof HTMLTextAreaElement\n );\n}\n\n/**\n * Where `validity`, `validationMessage`, `:user-invalid` and `setCustomValidity` are read and\n * written (design 7.1): the element itself when it is a form control, else the first named\n * `willValidate` member of a `<fieldset>` (a group's first enabled item, not a CheckboxGroup\n * Parent, which has no name), else the first `input` / `select` / `textarea` inside.\n */\nexport function validityElementOf(el: Element | null): ValidatableElement | null {\n if (el === null) return null;\n if (isValidatable(el)) return el;\n const members =\n el instanceof HTMLFieldSetElement\n ? [...el.elements]\n : [...el.querySelectorAll(\"input, select, textarea\")];\n const controls = members.filter(isValidatable);\n return (\n controls.find((c) => c.willValidate && c.name !== \"\") ??\n controls.find((c) => c.willValidate) ??\n controls[0] ??\n null\n );\n}\n\n/**\n * What the Field focuses (design 2.5): the control itself, or for a group the checked radio if\n * any, else the first enabled named member (a group's first item, not a CheckboxGroup Parent,\n * which has no name), else the first enabled member.\n */\nexport function focusTargetOf(el: Element | null): HTMLElement | null {\n if (el === null) return null;\n if (isValidatable(el)) return el;\n const members = (\n el instanceof HTMLFieldSetElement\n ? [...el.elements]\n : [...el.querySelectorAll(\"input, select, textarea, button\")]\n ).filter(\n (m): m is HTMLElement =>\n m instanceof HTMLElement &&\n !(m as HTMLInputElement).disabled &&\n (m as HTMLInputElement).type !== \"hidden\",\n );\n const checkedRadio = members.find(\n (m) => m instanceof HTMLInputElement && m.type === \"radio\" && m.checked,\n );\n return (\n checkedRadio ??\n members.find((m) => isValidatable(m) && m.name !== \"\") ??\n members.find(isValidatable) ??\n members[0] ??\n null\n );\n}\n\n/** Sorts elements into document order. */\nexport function sortByDocumentOrder<T>(items: readonly T[], elementOf: (item: T) => Node): T[] {\n return [...items].sort((a, b) => {\n const position = elementOf(a).compareDocumentPosition(elementOf(b));\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;\n if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;\n return 0;\n });\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,8 @@ import { createActionDetails } from "../internal/action-details.mjs";
|
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { transitionTypes } from "../internal/transition-types.mjs";
|
|
5
5
|
import { useControllableAction } from "../internal/use-controllable-action.mjs";
|
|
6
|
-
import { rendersButton
|
|
6
|
+
import { rendersButton } from "../internal/render-element.mjs";
|
|
7
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
7
8
|
import { closeStackLater, useMenuContext } from "./menu-context.mjs";
|
|
8
9
|
import { useMenuItem } from "./use-menu-item.mjs";
|
|
9
10
|
import { useRef } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-checkbox-item.mjs","names":[],"sources":["../../src/menu/menu-checkbox-item.tsx"],"sourcesContent":["\"use client\";\nimport { useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { closeStackLater, useMenuContext } from \"./menu-context.js\";\nimport type { MenuItemProps } from \"./menu-item.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\nexport interface MenuCheckboxItemState {\n /** Optimistic checked state (`aria-checked`). */\n readonly checked: boolean;\n /** `checkedChangeAction` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuCheckboxItemProps extends Omit<\n MenuItemProps,\n \"action\" | \"closeOnActivate\" | \"className\" | \"style\" | \"children\" | \"render\"\n> {\n /** Controlled checked state. `undefined` means uncontrolled. */\n checked?: boolean;\n /** Initial checked state when uncontrolled. Default `false`. */\n defaultChecked?: boolean;\n /**\n * Awaited inside the item's transition; `aria-checked` flips at once and rolls back on\n * rejection (which reaches the nearest Error Boundary above the item). May be a Server\n * Function.\n */\n checkedChangeAction?: ChangeAction<boolean>;\n /** Close the whole menu once the change settles. Default `false`: the menu stays open. */\n closeOnActivate?: boolean;\n className?: StateValue<string | undefined, MenuCheckboxItemState>;\n style?: StateValue<CSSProperties | undefined, MenuCheckboxItemState>;\n children?: StateValue<ReactNode, MenuCheckboxItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuCheckboxItemState>;\n}\n\n/**\n * A checkable menu item: `<button type=\"button\" role=\"menuitemcheckbox\" aria-checked>`. Click,\n * Enter or Space toggles it through its own transition with optimistic `aria-checked` /\n * `data-checked` and `data-pending`; the menu stays open unless `closeOnActivate`.\n *\n * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked` and `data-checked`.\n */\nexport function MenuCheckboxItem({\n checked,\n defaultChecked,\n checkedChangeAction,\n closeOnActivate = false,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuCheckboxItemProps) {\n const menu = useMenuContext(\"Menu.CheckboxItem\");\n // The close to run after a dispatch's action resolved, keyed by the dispatch's details.\n const after = useRef(new WeakMap<object, () => void>());\n const { optimistic, pending, dispatch } = useControllableAction<boolean>({\n controlled: checked,\n defaultValue: defaultChecked,\n initialValue: false,\n action: async (next, details) => {\n await checkedChangeAction?.(next, details);\n after.current.get(details)?.();\n },\n transitionTypes: (next) => transitionTypes(\"menu\", next ? \"check\" : \"uncheck\"),\n warn: { component: \"Menu.CheckboxItem\", prop: \"checked\" },\n });\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.CheckboxItem\",\n role: \"menuitemcheckbox\",\n disabled,\n label,\n activate: (event) => {\n const details = createActionDetails(event, {}) as ActionDetails;\n if (closeOnActivate) after.current.set(details, closeStackLater(menu, event));\n dispatch(!optimistic, details);\n },\n });\n\n const state: MenuCheckboxItemState = { checked: optimistic, pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuCheckboxItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n \"aria-checked\": optimistic,\n \"data-checked\": dataAttr(optimistic),\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuCheckboxItem.displayName = \"MenuCheckboxItem\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-checkbox-item.mjs","names":[],"sources":["../../src/menu/menu-checkbox-item.tsx"],"sourcesContent":["\"use client\";\nimport { useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { closeStackLater, useMenuContext } from \"./menu-context.js\";\nimport type { MenuItemProps } from \"./menu-item.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\nexport interface MenuCheckboxItemState {\n /** Optimistic checked state (`aria-checked`). */\n readonly checked: boolean;\n /** `checkedChangeAction` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuCheckboxItemProps extends Omit<\n MenuItemProps,\n \"action\" | \"closeOnActivate\" | \"className\" | \"style\" | \"children\" | \"render\"\n> {\n /** Controlled checked state. `undefined` means uncontrolled. */\n checked?: boolean;\n /** Initial checked state when uncontrolled. Default `false`. */\n defaultChecked?: boolean;\n /**\n * Awaited inside the item's transition; `aria-checked` flips at once and rolls back on\n * rejection (which reaches the nearest Error Boundary above the item). May be a Server\n * Function.\n */\n checkedChangeAction?: ChangeAction<boolean>;\n /** Close the whole menu once the change settles. Default `false`: the menu stays open. */\n closeOnActivate?: boolean;\n className?: StateValue<string | undefined, MenuCheckboxItemState>;\n style?: StateValue<CSSProperties | undefined, MenuCheckboxItemState>;\n children?: StateValue<ReactNode, MenuCheckboxItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuCheckboxItemState>;\n}\n\n/**\n * A checkable menu item: `<button type=\"button\" role=\"menuitemcheckbox\" aria-checked>`. Click,\n * Enter or Space toggles it through its own transition with optimistic `aria-checked` /\n * `data-checked` and `data-pending`; the menu stays open unless `closeOnActivate`.\n *\n * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked` and `data-checked`.\n */\nexport function MenuCheckboxItem({\n checked,\n defaultChecked,\n checkedChangeAction,\n closeOnActivate = false,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuCheckboxItemProps) {\n const menu = useMenuContext(\"Menu.CheckboxItem\");\n // The close to run after a dispatch's action resolved, keyed by the dispatch's details.\n const after = useRef(new WeakMap<object, () => void>());\n const { optimistic, pending, dispatch } = useControllableAction<boolean>({\n controlled: checked,\n defaultValue: defaultChecked,\n initialValue: false,\n action: async (next, details) => {\n await checkedChangeAction?.(next, details);\n after.current.get(details)?.();\n },\n transitionTypes: (next) => transitionTypes(\"menu\", next ? \"check\" : \"uncheck\"),\n warn: { component: \"Menu.CheckboxItem\", prop: \"checked\" },\n });\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.CheckboxItem\",\n role: \"menuitemcheckbox\",\n disabled,\n label,\n activate: (event) => {\n const details = createActionDetails(event, {}) as ActionDetails;\n if (closeOnActivate) after.current.set(details, closeStackLater(menu, event));\n dispatch(!optimistic, details);\n },\n });\n\n const state: MenuCheckboxItemState = { checked: optimistic, pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuCheckboxItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n \"aria-checked\": optimistic,\n \"data-checked\": dataAttr(optimistic),\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuCheckboxItem.displayName = \"MenuCheckboxItem\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkDA,SAAgB,iBAAiB,EAC/B,SACA,gBACA,qBACA,kBAAkB,OAClB,WAAW,OACX,OACA,WACA,OACA,UACA,QACA,GAAG,QACqB;CACxB,MAAM,OAAO,eAAe,mBAAmB;CAE/C,MAAM,QAAQ,uBAAO,IAAI,QAA4B,CAAC;CACtD,MAAM,EAAE,YAAY,SAAS,aAAa,sBAA+B;EACvE,YAAY;EACZ,cAAc;EACd,cAAc;EACd,QAAQ,OAAO,MAAM,YAAY;GAC/B,MAAM,sBAAsB,MAAM,OAAO;GACzC,MAAM,QAAQ,IAAI,OAAO,CAAC,GAAG;EAC/B;EACA,kBAAkB,SAAS,gBAAgB,QAAQ,OAAO,UAAU,SAAS;EAC7E,MAAM;GAAE,WAAW;GAAqB,MAAM;EAAU;CAC1D,CAAC;CAED,MAAM,EAAE,iBAAiB,YAAY;EACnC,MAAM;EACN,MAAM;EACN;EACA;EACA,WAAW,UAAU;GACnB,MAAM,UAAU,oBAAoB,OAAO,CAAC,CAAC;GAC7C,IAAI,iBAAiB,MAAM,QAAQ,IAAI,SAAS,gBAAgB,MAAM,KAAK,CAAC;GAC5E,SAAS,CAAC,YAAY,OAAO;EAC/B;CACF,CAAC;CAID,OAAO,UAAkE;EACvE,gBAAgB;EAChB;EACA,OAAA;GALqC,SAAS;GAAY;GAAS;EAK/D;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD,GAAG;GACH,gBAAgB;GAChB,gBAAgB,SAAS,UAAU;GACnC,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;AACH;AAEA,iBAAiB,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-group.d.mts","names":[],"sources":["../../src/menu/menu-group.tsx"],"mappings":";;;;YASY,iBAAiB;YACjB,sBAAsB;YACtB,qBAAqB;iBAIhB,uBAAuB,KACtC;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;wBAOpC,YAAY,WAAW,OAAO,UAAU,WAAW,QAAQ,iCAAc,IAAA;yBAAzE;;;
|
|
1
|
+
{"version":3,"file":"menu-group.d.mts","names":[],"sources":["../../src/menu/menu-group.tsx"],"mappings":";;;;YASY,iBAAiB;YACjB,sBAAsB;YACtB,qBAAqB;iBAIhB,uBAAuB,KACtC;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;wBAOpC,YAAY,WAAW,OAAO,UAAU,WAAW,QAAQ,iCAAc,IAAA;yBAAzE;;;iBAkBC,4BAA4B,KAC3C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;;;;;wBAOpC,iBACd,WACA,OACA,UACA,WACG,QACF,sBAAmB;yBANN;;;iBA0BC,2BAA2B,KAC1C;EAGA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,SAAS,WAAW,6BAA6B;;;wBAInC,gBAAgB,WAAW,OAAO,WAAW,QAAQ,qBAAkB;yBAAvE"}
|
package/dist/menu/menu-group.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useRender } from "../internal/use-render.mjs";
|
|
3
3
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
4
4
|
import { MenuGroupContext } from "./menu-context.mjs";
|
|
5
|
-
import { use, useId } from "react";
|
|
5
|
+
import { use, useId, useMemo } from "react";
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
//#region src/menu/menu-group.tsx
|
|
8
8
|
const EMPTY = {};
|
|
@@ -25,8 +25,9 @@ function MenuGroup({ className, style, children, render, ...rest }) {
|
|
|
25
25
|
"aria-labelledby": labelId
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
|
+
const context = useMemo(() => ({ labelId }), [labelId]);
|
|
28
29
|
return /* @__PURE__ */ jsx(MenuGroupContext, {
|
|
29
|
-
value:
|
|
30
|
+
value: context,
|
|
30
31
|
children: element
|
|
31
32
|
});
|
|
32
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-group.mjs","names":[],"sources":["../../src/menu/menu-group.tsx"],"sourcesContent":["\"use client\";\nimport { use, useId } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { MenuGroupContext } from \"./menu-context.js\";\n\n/** `Menu.Group`, `Menu.GroupLabel` and `Menu.Separator` have no state. */\nexport type MenuGroupState = Record<string, never>;\nexport type MenuGroupLabelState = Record<string, never>;\nexport type MenuSeparatorState = Record<string, never>;\n\nconst EMPTY: Record<string, never> = {};\n\nexport interface MenuGroupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"role\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuGroupState>;\n style?: StateValue<CSSProperties | undefined, MenuGroupState>;\n children?: StateValue<ReactNode, MenuGroupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuGroupState>;\n}\n\n/**\n * A labelled set of items: `<div role=\"group\" aria-labelledby>` named by the `Menu.GroupLabel`\n * inside it. Owned keys: `role`, `aria-labelledby` (token-merged).\n */\nexport function MenuGroup({ className, style, children, render, ...rest }: MenuGroupProps) {\n const labelId = `${useId()}-label`;\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuGroupState>({\n defaultElement: \"div\",\n render,\n state: EMPTY,\n className,\n style,\n children,\n props: rest,\n libraryProps: { role: \"group\", \"aria-labelledby\": labelId },\n });\n return <MenuGroupContext value={
|
|
1
|
+
{"version":3,"file":"menu-group.mjs","names":[],"sources":["../../src/menu/menu-group.tsx"],"sourcesContent":["\"use client\";\nimport { use, useId, useMemo } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { MenuGroupContext } from \"./menu-context.js\";\n\n/** `Menu.Group`, `Menu.GroupLabel` and `Menu.Separator` have no state. */\nexport type MenuGroupState = Record<string, never>;\nexport type MenuGroupLabelState = Record<string, never>;\nexport type MenuSeparatorState = Record<string, never>;\n\nconst EMPTY: Record<string, never> = {};\n\nexport interface MenuGroupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"role\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuGroupState>;\n style?: StateValue<CSSProperties | undefined, MenuGroupState>;\n children?: StateValue<ReactNode, MenuGroupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuGroupState>;\n}\n\n/**\n * A labelled set of items: `<div role=\"group\" aria-labelledby>` named by the `Menu.GroupLabel`\n * inside it. Owned keys: `role`, `aria-labelledby` (token-merged).\n */\nexport function MenuGroup({ className, style, children, render, ...rest }: MenuGroupProps) {\n const labelId = `${useId()}-label`;\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuGroupState>({\n defaultElement: \"div\",\n render,\n state: EMPTY,\n className,\n style,\n children,\n props: rest,\n libraryProps: { role: \"group\", \"aria-labelledby\": labelId },\n });\n const context = useMemo(() => ({ labelId }), [labelId]);\n return <MenuGroupContext value={context}>{element}</MenuGroupContext>;\n}\n\nMenuGroup.displayName = \"MenuGroup\";\n\nexport interface MenuGroupLabelProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"id\" | \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuGroupLabelState>;\n style?: StateValue<CSSProperties | undefined, MenuGroupLabelState>;\n children?: StateValue<ReactNode, MenuGroupLabelState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuGroupLabelState>;\n}\n\n/**\n * The name of the enclosing `Menu.Group` or `Menu.RadioGroup`: a `<div>` with the id the group's\n * `aria-labelledby` references. Owned key: `id`.\n */\nexport function MenuGroupLabel({\n className,\n style,\n children,\n render,\n ...rest\n}: MenuGroupLabelProps) {\n const group = use(MenuGroupContext);\n useDevWarning(\n group === null,\n \"Menu.GroupLabel: rendered outside a Menu.Group or Menu.RadioGroup; it names nothing.\",\n );\n return useRender<ComponentPropsWithRef<\"div\">, MenuGroupLabelState>({\n defaultElement: \"div\",\n render,\n state: EMPTY,\n className,\n style,\n children,\n props: rest,\n libraryProps: group === null ? {} : { id: group.labelId },\n });\n}\n\nMenuGroupLabel.displayName = \"MenuGroupLabel\";\n\nexport interface MenuSeparatorProps extends Omit<\n ComponentPropsWithRef<\"hr\">,\n \"className\" | \"style\" | \"children\"\n> {\n className?: StateValue<string | undefined, MenuSeparatorState>;\n style?: StateValue<CSSProperties | undefined, MenuSeparatorState>;\n /** Another element must carry `role=\"separator\"` itself. */\n render?: RenderProp<ComponentPropsWithRef<\"hr\">, MenuSeparatorState>;\n}\n\n/** A divider between items: `<hr>` (implicit `role=\"separator\"`). Owns no keys. */\nexport function MenuSeparator({ className, style, render, ...rest }: MenuSeparatorProps) {\n return useRender<ComponentPropsWithRef<\"hr\">, MenuSeparatorState>({\n defaultElement: \"hr\",\n render,\n state: EMPTY,\n className,\n style,\n children: undefined,\n props: rest,\n });\n}\n\nMenuSeparator.displayName = \"MenuSeparator\";\n"],"mappings":";;;;;;;AAaA,MAAM,QAA+B,CAAC;;;;;AAgBtC,SAAgB,UAAU,EAAE,WAAW,OAAO,UAAU,QAAQ,GAAG,QAAwB;CACzF,MAAM,UAAU,GAAG,MAAM,EAAE;CAC3B,MAAM,UAAU,UAAwD;EACtE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA;EACA,OAAO;EACP,cAAc;GAAE,MAAM;GAAS,mBAAmB;EAAQ;CAC5D,CAAC;CACD,MAAM,UAAU,eAAe,EAAE,QAAQ,IAAI,CAAC,OAAO,CAAC;CACtD,OAAO,oBAAC,kBAAD;EAAkB,OAAO;EAAU,UAAA;CAA0B,CAAA;AACtE;AAEA,UAAU,cAAc;;;;;AAgBxB,SAAgB,eAAe,EAC7B,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,QAAQ,IAAI,gBAAgB;CAClC,cACE,UAAU,MACV,sFACF;CACA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA;EACA,OAAO;EACP,cAAc,UAAU,OAAO,CAAC,IAAI,EAAE,IAAI,MAAM,QAAQ;CAC1D,CAAC;AACH;AAEA,eAAe,cAAc;;AAa7B,SAAgB,cAAc,EAAE,WAAW,OAAO,QAAQ,GAAG,QAA4B;CACvF,OAAO,UAA2D;EAChE,gBAAgB;EAChB;EACA,OAAO;EACP;EACA;EACA,UAAU,KAAA;EACV,OAAO;CACT,CAAC;AACH;AAEA,cAAc,cAAc"}
|
package/dist/menu/menu-item.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createActionDetails } from "../internal/action-details.mjs";
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
|
-
import { rendersButton
|
|
4
|
+
import { rendersButton } from "../internal/render-element.mjs";
|
|
5
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
5
6
|
import { useActionTrigger } from "../internal/use-action-trigger.mjs";
|
|
6
7
|
import { useMenuContext } from "./menu-context.mjs";
|
|
7
8
|
import { useMenuItem } from "./use-menu-item.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-item.mjs","names":[],"sources":["../../src/menu/menu-item.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\n/** Details passed to `Menu.Item`'s `action`. */\nexport interface MenuItemDetails extends ActionDetails {}\n\nexport interface MenuItemState {\n /** This item's `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuItemProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"onPointerMove\" | \"disabled\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this item's own transition (`data-pending` on the item); the menu stays open\n * until it settles, then the whole menu closes (`closeOnActivate`). May be a Server Function\n * (`details.event` is virtual). A rejection leaves the menu open and reaches the nearest Error\n * Boundary above the item. Without `action`, activation only closes.\n */\n action?: Action<[details: MenuItemDetails]>;\n /** Close the whole menu once `action` settles. Default `true`. */\n closeOnActivate?: boolean;\n /** Renders `aria-disabled`: the item stays focusable and never activates. */\n disabled?: boolean;\n /** Text typeahead matches when the item's text content is not representative (an icon). */\n label?: string;\n className?: StateValue<string | undefined, MenuItemState>;\n style?: StateValue<CSSProperties | undefined, MenuItemState>;\n children?: StateValue<ReactNode, MenuItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuItemState>;\n}\n\n/**\n * A menu command: `<button type=\"button\" role=\"menuitem\" tabindex=\"-1\">`. Click, Enter or Space\n * runs `action` in the item's own transition; once it resolves the whole menu stack closes\n * through the top-level Root (`openChangeAction(false, { reason: \"activate\" })` awaited, view\n * transition if set). Hovering the item focuses it.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `role`, `tabIndex`, `disabled`\n * (always absent), `aria-disabled`, `onClick`, `onKeyDown`, `onPointerMove`, `data-pending`,\n * `data-disabled`, `data-typeahead`.\n */\nexport function MenuItem({\n action,\n closeOnActivate = true,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuItemProps) {\n const menu = useMenuContext(\"Menu.Item\");\n // The close belongs to the top-level menu, so its open generation guards it.\n const { pending, run } = useActionTrigger(menu.root.popover.openGeneration);\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.Item\",\n role: \"menuitem\",\n disabled,\n label,\n activate: (event) => {\n const details: MenuItemDetails = createActionDetails(event, {});\n // Repeated activations re-run the action; the first close wins (design 2.3).\n run(\n () => action?.(details),\n closeOnActivate ? () => menu.root.popover.requestClose(event, \"activate\") : undefined,\n );\n },\n });\n\n const state: MenuItemState = { pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuItem.displayName = \"MenuItem\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-item.mjs","names":[],"sources":["../../src/menu/menu-item.tsx"],"sourcesContent":["\"use client\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport { useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\n/** Details passed to `Menu.Item`'s `action`. */\nexport interface MenuItemDetails extends ActionDetails {}\n\nexport interface MenuItemState {\n /** This item's `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuItemProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"onKeyDown\" | \"onPointerMove\" | \"disabled\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this item's own transition (`data-pending` on the item); the menu stays open\n * until it settles, then the whole menu closes (`closeOnActivate`). May be a Server Function\n * (`details.event` is virtual). A rejection leaves the menu open and reaches the nearest Error\n * Boundary above the item. Without `action`, activation only closes.\n */\n action?: Action<[details: MenuItemDetails]>;\n /** Close the whole menu once `action` settles. Default `true`. */\n closeOnActivate?: boolean;\n /** Renders `aria-disabled`: the item stays focusable and never activates. */\n disabled?: boolean;\n /** Text typeahead matches when the item's text content is not representative (an icon). */\n label?: string;\n className?: StateValue<string | undefined, MenuItemState>;\n style?: StateValue<CSSProperties | undefined, MenuItemState>;\n children?: StateValue<ReactNode, MenuItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuItemState>;\n}\n\n/**\n * A menu command: `<button type=\"button\" role=\"menuitem\" tabindex=\"-1\">`. Click, Enter or Space\n * runs `action` in the item's own transition; once it resolves the whole menu stack closes\n * through the top-level Root (`openChangeAction(false, { reason: \"activate\" })` awaited, view\n * transition if set). Hovering the item focuses it.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>`), `role`, `tabIndex`, `disabled`\n * (always absent), `aria-disabled`, `onClick`, `onKeyDown`, `onPointerMove`, `data-pending`,\n * `data-disabled`, `data-typeahead`.\n */\nexport function MenuItem({\n action,\n closeOnActivate = true,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuItemProps) {\n const menu = useMenuContext(\"Menu.Item\");\n // The close belongs to the top-level menu, so its open generation guards it.\n const { pending, run } = useActionTrigger(menu.root.popover.openGeneration);\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.Item\",\n role: \"menuitem\",\n disabled,\n label,\n activate: (event) => {\n const details: MenuItemDetails = createActionDetails(event, {});\n // Repeated activations re-run the action; the first close wins (design 2.3).\n run(\n () => action?.(details),\n closeOnActivate ? () => menu.root.popover.requestClose(event, \"activate\") : undefined,\n );\n },\n });\n\n const state: MenuItemState = { pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuItem.displayName = \"MenuItem\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,SAAS,EACvB,QACA,kBAAkB,MAClB,WAAW,OACX,OACA,WACA,OACA,UACA,QACA,GAAG,QACa;CAChB,MAAM,OAAO,eAAe,WAAW;CAEvC,MAAM,EAAE,SAAS,QAAQ,iBAAiB,KAAK,KAAK,QAAQ,cAAc;CAE1E,MAAM,EAAE,iBAAiB,YAAY;EACnC,MAAM;EACN,MAAM;EACN;EACA;EACA,WAAW,UAAU;GACnB,MAAM,UAA2B,oBAAoB,OAAO,CAAC,CAAC;GAE9D,UACQ,SAAS,OAAO,GACtB,wBAAwB,KAAK,KAAK,QAAQ,aAAa,OAAO,UAAU,IAAI,KAAA,CAC9E;EACF;CACF,CAAC;CAID,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA;GAL6B;GAAS;EAKlC;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD,GAAG;GACH,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;AACH;AAEA,SAAS,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-popup.d.mts","names":[],"sources":["../../src/menu/menu-popup.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"menu-popup.d.mts","names":[],"sources":["../../src/menu/menu-popup.tsx"],"mappings":";;;;YAkCY,WAAW;YACX,YAAY;iBAEP;;WAEN;WACA,MAAM;WACN,OAAO;;WAEP;;iBAGM,uBAAuB,KACtC;;;;;;;EAuBA,OAAO;;EAEP,QAAQ;;;;;;EAMR;;;;;;;EAOA;;;;;;;EAOA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,8BAA8B;;;;;;;;;;;;;;;wBA4BpC,YACd,MAAM,UACN,OACA,aACA,yBACA,YACA,WACA,OACA,UACA,WACG,QACF,iBAAc;yBAXD"}
|
package/dist/menu/menu-popup.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
3
3
|
import { mergeRefs } from "../internal/merge-props.mjs";
|
|
4
|
-
import { resolveStateValue
|
|
4
|
+
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
5
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
6
|
+
import { ToolbarContext } from "../internal/toolbar-context.mjs";
|
|
5
7
|
import { POSITION_TRY_FALLBACKS, positionArea } from "../internal/anchor-position.mjs";
|
|
6
8
|
import { usePopoverPopup } from "../internal/use-popover-popup.mjs";
|
|
7
9
|
import { MenuListContext, useMenuContext } from "./menu-context.mjs";
|
|
@@ -180,7 +182,10 @@ function MenuPopup({ side: sideProp, align = "start", keepMounted, experimental_
|
|
|
180
182
|
else node = visible ? wrapContent(resolveStateValue(children, s)) : null;
|
|
181
183
|
return /* @__PURE__ */ jsx(MenuListContext, {
|
|
182
184
|
value: listContext,
|
|
183
|
-
children:
|
|
185
|
+
children: /* @__PURE__ */ jsx(ToolbarContext, {
|
|
186
|
+
value: null,
|
|
187
|
+
children: node
|
|
188
|
+
})
|
|
184
189
|
});
|
|
185
190
|
};
|
|
186
191
|
const element = useRender({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-popup.mjs","names":[],"sources":["../../src/menu/menu-popup.tsx"],"sourcesContent":["\"use client\";\nimport { Activity, useCallback, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n FocusEvent,\n KeyboardEvent,\n MouseEvent,\n PointerEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { POSITION_TRY_FALLBACKS, positionArea } from \"../internal/anchor-position.js\";\nimport type { AnchorAlign, AnchorSide } from \"../internal/anchor-position.js\";\nimport { mergeRefs } from \"../internal/merge-props.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverPopup } from \"../internal/use-popover-popup.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { MenuListContext, useMenuContext } from \"./menu-context.js\";\nimport type { MenuListContextValue } from \"./menu-context.js\";\nimport {\n edgeItem,\n focusItem,\n hasModifier,\n isMenuItem,\n isRtl,\n menuItems,\n typeaheadText,\n} from \"./menu-dom.js\";\nimport { scheduleHoverClose } from \"./menu-hover.js\";\n\nexport type MenuSide = AnchorSide;\nexport type MenuAlign = AnchorAlign;\n\nexport interface MenuPopupState {\n /** Committed open state. Read `:popover-open` for the DOM truth. */\n readonly open: boolean;\n readonly side: MenuSide | null;\n readonly align: MenuAlign;\n /** This popup belongs to a submenu. */\n readonly submenu: boolean;\n}\n\nexport interface MenuPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"id\"\n | \"popover\"\n | \"role\"\n | \"aria-labelledby\"\n | \"onKeyDown\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onMouseDown\"\n | \"onPointerEnter\"\n | \"onPointerLeave\"\n | \"onToggle\"\n | \"onBeforeToggle\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /**\n * Placement against the Trigger through inline `position-area`, with `position-try-fallbacks`\n * flipping it when it does not fit. Default `\"bottom\"` for a top-level menu, `\"right\"` for a\n * submenu (whose fallback is `flip-inline` only). `null` emits no inline positioning (and no\n * `data-side` / `data-align`): the stylesheet owns placement.\n */\n side?: MenuSide | null;\n /** Alignment along the side. Default `\"start\"`. Ignored when `side` is `null`. */\n align?: MenuAlign;\n /**\n * `false` (default): the items are not rendered while the menu is closed; mounting them is part\n * of the opening transition, so items that suspend hold the open (and the Trigger pending)\n * until they are ready. `true`: they stay mounted inside `<Activity mode=\"hidden\">`.\n */\n keepMounted?: boolean;\n /**\n * Experimental (Chromium 150+). Renders `focusgroup=\"menu wrap|nowrap\"` and, where the engine\n * implements `focusGroup`, lets the browser handle ArrowUp / ArrowDown / Home / End: items then\n * render no `tabindex` and the library's arrow-key handling steps aside. Typeahead, activation,\n * submenu keys and Tab stay in JS. No effect in other engines. Default `false`.\n */\n experimental_focusgroup?: boolean;\n /**\n * Forwarded to the `<ViewTransition default>` around the popup element. Animates the opening\n * commit and every library-driven close (an item's activation, the Trigger, ArrowLeft, hover\n * leaving a submenu, controlled `open={false}`) in all engines; dismissals (Escape, a click\n * outside, Tab) animate with CSS only. Without it the part renders no `<ViewTransition>`.\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, MenuPopupState>;\n style?: StateValue<CSSProperties | undefined, MenuPopupState>;\n children?: StateValue<ReactNode, MenuPopupState>;\n /** Should render a `<div>`; a `<dialog>` would run the dialog focusing steps. */\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuPopupState>;\n}\n\n/** Engine capabilities never change while the page lives. */\nconst subscribeNever = () => () => {};\n\n/** Typeahead buffer lifetime after the last keystroke (design 5.1). */\nconst TYPEAHEAD_RESET_MS = 500;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** What a press may focus inside a popup: items, the popup itself, the consumer's controls. */\nconst FOCUSABLE_SELECTOR =\n 'a[href], button, input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable=\"false\"])';\n\n/**\n * The menu itself: `<div popover=\"auto\" role=\"menu\" aria-labelledby={trigger} tabindex=\"-1\">`,\n * placed against its Trigger with CSS Anchor Positioning. Owns the keyboard model (design 5):\n * ArrowUp / ArrowDown / Home / End move focus between items (or the browser does, with\n * `experimental_focusgroup` where supported), printable characters run typeahead, ArrowLeft\n * closes a submenu. The top-level popup closes the whole menu at once when focus leaves it (Tab).\n * Style it with `:popover-open`; it emits `data-side` / `data-align` but no `data-open`.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `role`, `aria-labelledby` (token-merged),\n * `tabIndex`, `focusgroup` (with the flag), `onKeyDown`, `onBlur`, `onMouseDown` (a press on\n * non-item space keeps focus on the current item), `onPointerEnter` / `onPointerLeave`, and\n * `data-side` / `data-align` unless `side` is `null`.\n */\nexport function MenuPopup({\n side: sideProp,\n align = \"start\",\n keepMounted,\n experimental_focusgroup = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuPopupProps) {\n const menu = useMenuContext(\"Menu.Popup\");\n const { popover, submenu, loop } = menu;\n const side = sideProp === undefined ? (submenu ? \"right\" : \"bottom\") : sideProp;\n\n // As Tabs.List (#43): detected only with the flag, never during SSR or hydration.\n const nativeFocusgroup = useSyncExternalStore(\n subscribeNever,\n () => experimental_focusgroup && \"focusGroup\" in HTMLElement.prototype,\n () => false,\n );\n\n const typeahead = useRef({ text: \"\", at: 0 });\n const typingAhead = useCallback(() => {\n const { text, at } = typeahead.current;\n return text !== \"\" && Date.now() - at <= TYPEAHEAD_RESET_MS;\n }, []);\n\n const { setElement, visible, wrapContent } = usePopoverPopup(popover, {\n part: \"Menu.Popup\",\n initialFocus: undefined,\n // Design 6.1: the first (or last) enabled item by open cause, nothing for a hover open, the\n // popup itself when there are no items.\n // A pointer open focuses without the focus ring where the engine can be told so (H1).\n resolveInitialFocus: (popup) => {\n const { cause, pointer } = menu.takeOpenCause();\n if (cause === \"none\") return null;\n const element = edgeItem(popup, cause) ?? popup;\n return pointer ? { element, focusVisible: false } : element;\n },\n keepMounted,\n transition,\n });\n\n const devRef = useCallback((element: HTMLElement) => {\n if (isDev && element.tagName === \"DIALOG\") {\n console.error(\n \"Menu.Popup: rendered a <dialog>. Render a <div>: a <dialog popover> runs the dialog \" +\n \"focusing steps, which fight the menu's item focus.\",\n );\n }\n return () => {};\n }, []);\n const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);\n\n const listContext = useMemo<MenuListContextValue>(\n () => ({ menu, nativeFocusgroup, typingAhead }),\n [menu, nativeFocusgroup, typingAhead],\n );\n\n const state: MenuPopupState = { open: popover.committed, side, align, submenu };\n\n const runTypeahead = (popup: HTMLElement, current: HTMLElement | null, key: string): boolean => {\n const now = Date.now();\n const buffer = typeahead.current;\n if (buffer.text === \"\" || now - buffer.at > TYPEAHEAD_RESET_MS) buffer.text = \"\";\n if (key === \" \" && buffer.text === \"\") return false;\n buffer.text += key.toLowerCase();\n buffer.at = now;\n const items = menuItems(popup);\n if (items.length === 0) return true;\n // A repeated single character cycles through the items starting with it; a longer buffer\n // keeps the current item while it still matches (design 5.1).\n const repeated = [...buffer.text].every((c) => c === buffer.text[0]);\n const search = repeated ? buffer.text[0]! : buffer.text;\n const index = current === null ? -1 : items.indexOf(current);\n const start = repeated || index < 0 ? index + 1 : index;\n for (let i = 0; i < items.length; i++) {\n const item = items[(start + i) % items.length]!;\n if (typeaheadText(item).startsWith(search)) {\n if (item !== current) focusItem(item);\n break;\n }\n }\n return true;\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.defaultPrevented || hasModifier(event)) return;\n const popup = event.currentTarget;\n const target = event.target as HTMLElement;\n const onItem = isMenuItem(target, popup);\n if (!onItem && target !== popup) return;\n const key = event.key;\n\n // ArrowLeft (ArrowRight in RTL) closes a submenu; focus returns to its SubmenuTrigger\n // through the popover hook's nested focus rule (design 6.3).\n if (submenu && key === (isRtl(popup) ? \"ArrowRight\" : \"ArrowLeft\")) {\n event.preventDefault();\n popover.requestClose(event.nativeEvent, \"activate\");\n return;\n }\n\n if (key === \"ArrowDown\" || key === \"ArrowUp\" || key === \"Home\" || key === \"End\") {\n if (!onItem) {\n // The popup itself has focus (no items at open, or focus landed on it some other way):\n // into the list from its edge (review M1).\n event.preventDefault();\n const items = menuItems(popup);\n const item = key === \"ArrowDown\" || key === \"Home\" ? items[0] : items.at(-1);\n if (item !== undefined) focusItem(item);\n return;\n }\n // The browser moves focus itself where `focusgroup` is implemented.\n if (nativeFocusgroup) return;\n event.preventDefault();\n const items = menuItems(popup);\n const index = items.indexOf(target);\n const last = items.length - 1;\n let next: number;\n if (key === \"Home\") next = 0;\n else if (key === \"End\") next = last;\n else if (key === \"ArrowDown\") next = index >= last ? (loop ? 0 : last) : index + 1;\n else next = index <= 0 ? (loop ? last : 0) : index - 1;\n const item = items[next];\n if (item !== undefined && item !== target) focusItem(item);\n return;\n }\n\n if (key.length === 1 && runTypeahead(popup, onItem ? target : null, key)) {\n event.preventDefault();\n }\n };\n\n // Tab / Shift+Tab or any focus move out of the tree closes the whole stack at once (design\n // 2.2): submenus are DOM descendants of the top-level popup, the Trigger is outside on purpose.\n // A null `relatedTarget` (window blur, a removed element) closes nothing.\n const onBlur = (event: FocusEvent<HTMLElement>) => {\n if (submenu) return;\n const popup = event.currentTarget;\n const next = event.relatedTarget;\n if (!(next instanceof Node) || popup.contains(next)) return;\n if (popup.matches(\":popover-open\")) popup.hidePopover();\n };\n\n // A press on non-item space (padding, a Separator, a GroupLabel) would focus the popup itself\n // (`tabindex=\"-1\"`) and take focus off the current item; keep it where it is (review M1, the\n // pointer-focus model of design 6.2). Focusable content of the consumer's (a text field) still\n // takes focus.\n const onMouseDown = (event: MouseEvent<HTMLElement>) => {\n if (event.button !== 0) return;\n const target = event.target as Element;\n const focusable = target.closest(FOCUSABLE_SELECTOR);\n if (focusable === null || focusable.getAttribute(\"role\") === \"menu\") event.preventDefault();\n };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: popover.ids.popup,\n popover: \"auto\",\n role: \"menu\",\n \"aria-labelledby\": menu.triggerId,\n // The popup itself takes focus as the last resort when a menu has no items.\n tabIndex: -1,\n ...(experimental_focusgroup && { focusgroup: `menu ${loop ? \"wrap\" : \"nowrap\"}` }),\n onKeyDown,\n onBlur,\n onMouseDown,\n ...(submenu && {\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") menu.timers.cancelClose();\n },\n onPointerLeave: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") scheduleHoverClose(menu, event.nativeEvent);\n },\n }),\n ...(side !== null && { \"data-side\": side, \"data-align\": align }),\n };\n\n const content = (s: MenuPopupState): ReactNode => {\n let node: ReactNode;\n if (keepMounted === true) {\n node = (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n } else {\n node = visible ? wrapContent(resolveStateValue(children, s)) : null;\n }\n return <MenuListContext value={listContext}>{node}</MenuListContext>;\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // The inline placement sits under the consumer's style: the consumer wins.\n style:\n side === null\n ? style\n : (s) => ({\n positionArea: positionArea(side, align),\n positionTryFallbacks: submenu ? \"flip-inline\" : POSITION_TRY_FALLBACKS,\n ...resolveStateValue(style, s),\n }),\n children: content,\n props: rest,\n libraryProps,\n ref,\n });\n\n return useTransitionBoundary(\"Menu.Popup\", transition, element);\n}\n\nMenuPopup.displayName = \"MenuPopup\";\n"],"mappings":";;;;;;;;;;;;;AAoGA,MAAM,6BAA6B,CAAC;;AAGpC,MAAM,qBAAqB;AAE3B,MAAM,QAAQ,QAAQ,IAAI,aAAa;;AAGvC,MAAM,qBACJ;;;;;;;;;;;;;;AAeF,SAAgB,UAAU,EACxB,MAAM,UACN,QAAQ,SACR,aACA,0BAA0B,OAC1B,YACA,WACA,OACA,UACA,QACA,GAAG,QACc;CACjB,MAAM,OAAO,eAAe,YAAY;CACxC,MAAM,EAAE,SAAS,SAAS,SAAS;CACnC,MAAM,OAAO,aAAa,KAAA,IAAa,UAAU,UAAU,WAAY;CAGvE,MAAM,mBAAmB,qBACvB,sBACM,2BAA2B,gBAAgB,YAAY,iBACvD,KACR;CAEA,MAAM,YAAY,OAAO;EAAE,MAAM;EAAI,IAAI;CAAE,CAAC;CAC5C,MAAM,cAAc,kBAAkB;EACpC,MAAM,EAAE,MAAM,OAAO,UAAU;EAC/B,OAAO,SAAS,MAAM,KAAK,IAAI,IAAI,MAAM;CAC3C,GAAG,CAAC,CAAC;CAEL,MAAM,EAAE,YAAY,SAAS,gBAAgB,gBAAgB,SAAS;EACpE,MAAM;EACN,cAAc,KAAA;EAId,sBAAsB,UAAU;GAC9B,MAAM,EAAE,OAAO,YAAY,KAAK,cAAc;GAC9C,IAAI,UAAU,QAAQ,OAAO;GAC7B,MAAM,UAAU,SAAS,OAAO,KAAK,KAAK;GAC1C,OAAO,UAAU;IAAE;IAAS,cAAc;GAAM,IAAI;EACtD;EACA;EACA;CACF,CAAC;CAED,MAAM,SAAS,aAAa,YAAyB;EACnD,IAAI,SAAS,QAAQ,YAAY,UAC/B,QAAQ,MACN,wIAEF;EAEF,aAAa,CAAC;CAChB,GAAG,CAAC,CAAC;CACL,MAAM,MAAM,cAAc,UAAU,YAAY,MAAM,GAAG,CAAC,YAAY,MAAM,CAAC;CAE7E,MAAM,cAAc,eACX;EAAE;EAAM;EAAkB;CAAY,IAC7C;EAAC;EAAM;EAAkB;CAAW,CACtC;CAEA,MAAM,QAAwB;EAAE,MAAM,QAAQ;EAAW;EAAM;EAAO;CAAQ;CAE9E,MAAM,gBAAgB,OAAoB,SAA6B,QAAyB;EAC9F,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,SAAS,UAAU;EACzB,IAAI,OAAO,SAAS,MAAM,MAAM,OAAO,KAAK,oBAAoB,OAAO,OAAO;EAC9E,IAAI,QAAQ,OAAO,OAAO,SAAS,IAAI,OAAO;EAC9C,OAAO,QAAQ,IAAI,YAAY;EAC/B,OAAO,KAAK;EACZ,MAAM,QAAQ,UAAU,KAAK;EAC7B,IAAI,MAAM,WAAW,GAAG,OAAO;EAG/B,MAAM,WAAW,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,OAAO,MAAM,MAAM,OAAO,KAAK,EAAE;EACnE,MAAM,SAAS,WAAW,OAAO,KAAK,KAAM,OAAO;EACnD,MAAM,QAAQ,YAAY,OAAO,KAAK,MAAM,QAAQ,OAAO;EAC3D,MAAM,QAAQ,YAAY,QAAQ,IAAI,QAAQ,IAAI;EAClD,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,OAAO,OAAO,QAAQ,KAAK,MAAM;GACvC,IAAI,cAAc,IAAI,CAAC,CAAC,WAAW,MAAM,GAAG;IAC1C,IAAI,SAAS,SAAS,UAAU,IAAI;IACpC;GACF;EACF;EACA,OAAO;CACT;CAEA,MAAM,aAAa,UAAsC;EACvD,IAAI,MAAM,oBAAoB,YAAY,KAAK,GAAG;EAClD,MAAM,QAAQ,MAAM;EACpB,MAAM,SAAS,MAAM;EACrB,MAAM,SAAS,WAAW,QAAQ,KAAK;EACvC,IAAI,CAAC,UAAU,WAAW,OAAO;EACjC,MAAM,MAAM,MAAM;EAIlB,IAAI,WAAW,SAAS,MAAM,KAAK,IAAI,eAAe,cAAc;GAClE,MAAM,eAAe;GACrB,QAAQ,aAAa,MAAM,aAAa,UAAU;GAClD;EACF;EAEA,IAAI,QAAQ,eAAe,QAAQ,aAAa,QAAQ,UAAU,QAAQ,OAAO;GAC/E,IAAI,CAAC,QAAQ;IAGX,MAAM,eAAe;IACrB,MAAM,QAAQ,UAAU,KAAK;IAC7B,MAAM,OAAO,QAAQ,eAAe,QAAQ,SAAS,MAAM,KAAK,MAAM,GAAG,EAAE;IAC3E,IAAI,SAAS,KAAA,GAAW,UAAU,IAAI;IACtC;GACF;GAEA,IAAI,kBAAkB;GACtB,MAAM,eAAe;GACrB,MAAM,QAAQ,UAAU,KAAK;GAC7B,MAAM,QAAQ,MAAM,QAAQ,MAAM;GAClC,MAAM,OAAO,MAAM,SAAS;GAC5B,IAAI;GACJ,IAAI,QAAQ,QAAQ,OAAO;QACtB,IAAI,QAAQ,OAAO,OAAO;QAC1B,IAAI,QAAQ,aAAa,OAAO,SAAS,OAAQ,OAAO,IAAI,OAAQ,QAAQ;QAC5E,OAAO,SAAS,IAAK,OAAO,OAAO,IAAK,QAAQ;GACrD,MAAM,OAAO,MAAM;GACnB,IAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,UAAU,IAAI;GACzD;EACF;EAEA,IAAI,IAAI,WAAW,KAAK,aAAa,OAAO,SAAS,SAAS,MAAM,GAAG,GACrE,MAAM,eAAe;CAEzB;CAKA,MAAM,UAAU,UAAmC;EACjD,IAAI,SAAS;EACb,MAAM,QAAQ,MAAM;EACpB,MAAM,OAAO,MAAM;EACnB,IAAI,EAAE,gBAAgB,SAAS,MAAM,SAAS,IAAI,GAAG;EACrD,IAAI,MAAM,QAAQ,eAAe,GAAG,MAAM,YAAY;CACxD;CAMA,MAAM,eAAe,UAAmC;EACtD,IAAI,MAAM,WAAW,GAAG;EAExB,MAAM,YADS,MAAM,OACI,QAAQ,kBAAkB;EACnD,IAAI,cAAc,QAAQ,UAAU,aAAa,MAAM,MAAM,QAAQ,MAAM,eAAe;CAC5F;CAEA,MAAM,eAA8D;EAClE,IAAI,QAAQ,IAAI;EAChB,SAAS;EACT,MAAM;EACN,mBAAmB,KAAK;EAExB,UAAU;EACV,GAAI,2BAA2B,EAAE,YAAY,QAAQ,OAAO,SAAS,WAAW;EAChF;EACA;EACA;EACA,GAAI,WAAW;GACb,iBAAiB,UAAqC;IACpD,IAAI,MAAM,gBAAgB,SAAS,KAAK,OAAO,YAAY;GAC7D;GACA,iBAAiB,UAAqC;IACpD,IAAI,MAAM,gBAAgB,SAAS,mBAAmB,MAAM,MAAM,WAAW;GAC/E;EACF;EACA,GAAI,SAAS,QAAQ;GAAE,aAAa;GAAM,cAAc;EAAM;CAChE;CAEA,MAAM,WAAW,MAAiC;EAChD,IAAI;EACJ,IAAI,gBAAgB,MAClB,OACE,oBAAC,UAAD;GAAU,MAAM,UAAU,YAAY;GACnC,UAAA,YAAY,kBAAkB,UAAU,CAAC,CAAC;EACnC,CAAA;OAGZ,OAAO,UAAU,YAAY,kBAAkB,UAAU,CAAC,CAAC,IAAI;EAEjE,OAAO,oBAAC,iBAAD;GAAiB,OAAO;GAAc,UAAA;EAAsB,CAAA;CACrE;CAEA,MAAM,UAAU,UAAwD;EACtE,gBAAgB;EAChB;EACA;EACA;EAEA,OACE,SAAS,OACL,SACC,OAAO;GACN,cAAc,aAAa,MAAM,KAAK;GACtC,sBAAsB,UAAU,gBAAgB;GAChD,GAAG,kBAAkB,OAAO,CAAC;EAC/B;EACN,UAAU;EACV,OAAO;EACP;EACA;CACF,CAAC;CAED,OAAO,sBAAsB,cAAc,YAAY,OAAO;AAChE;AAEA,UAAU,cAAc"}
|
|
1
|
+
{"version":3,"file":"menu-popup.mjs","names":[],"sources":["../../src/menu/menu-popup.tsx"],"sourcesContent":["\"use client\";\nimport { Activity, useCallback, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n FocusEvent,\n KeyboardEvent,\n MouseEvent,\n PointerEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { POSITION_TRY_FALLBACKS, positionArea } from \"../internal/anchor-position.js\";\nimport type { AnchorAlign, AnchorSide } from \"../internal/anchor-position.js\";\nimport { mergeRefs } from \"../internal/merge-props.js\";\nimport { ToolbarContext } from \"../internal/toolbar-context.js\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { usePopoverPopup } from \"../internal/use-popover-popup.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { MenuListContext, useMenuContext } from \"./menu-context.js\";\nimport type { MenuListContextValue } from \"./menu-context.js\";\nimport {\n edgeItem,\n focusItem,\n hasModifier,\n isMenuItem,\n isRtl,\n menuItems,\n typeaheadText,\n} from \"./menu-dom.js\";\nimport { scheduleHoverClose } from \"./menu-hover.js\";\n\nexport type MenuSide = AnchorSide;\nexport type MenuAlign = AnchorAlign;\n\nexport interface MenuPopupState {\n /** Committed open state. Read `:popover-open` for the DOM truth. */\n readonly open: boolean;\n readonly side: MenuSide | null;\n readonly align: MenuAlign;\n /** This popup belongs to a submenu. */\n readonly submenu: boolean;\n}\n\nexport interface MenuPopupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n | \"id\"\n | \"popover\"\n | \"role\"\n | \"aria-labelledby\"\n | \"onKeyDown\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onMouseDown\"\n | \"onPointerEnter\"\n | \"onPointerLeave\"\n | \"onToggle\"\n | \"onBeforeToggle\"\n | \"className\"\n | \"style\"\n | \"children\"\n> {\n /**\n * Placement against the Trigger through inline `position-area`, with `position-try-fallbacks`\n * flipping it when it does not fit. Default `\"bottom\"` for a top-level menu, `\"right\"` for a\n * submenu (whose fallback is `flip-inline` only). `null` emits no inline positioning (and no\n * `data-side` / `data-align`): the stylesheet owns placement.\n */\n side?: MenuSide | null;\n /** Alignment along the side. Default `\"start\"`. Ignored when `side` is `null`. */\n align?: MenuAlign;\n /**\n * `false` (default): the items are not rendered while the menu is closed; mounting them is part\n * of the opening transition, so items that suspend hold the open (and the Trigger pending)\n * until they are ready. `true`: they stay mounted inside `<Activity mode=\"hidden\">`.\n */\n keepMounted?: boolean;\n /**\n * Experimental (Chromium 150+). Renders `focusgroup=\"menu wrap|nowrap\"` and, where the engine\n * implements `focusGroup`, lets the browser handle ArrowUp / ArrowDown / Home / End: items then\n * render no `tabindex` and the library's arrow-key handling steps aside. Typeahead, activation,\n * submenu keys and Tab stay in JS. No effect in other engines. Default `false`.\n */\n experimental_focusgroup?: boolean;\n /**\n * Forwarded to the `<ViewTransition default>` around the popup element. Animates the opening\n * commit and every library-driven close (an item's activation, the Trigger, ArrowLeft, hover\n * leaving a submenu, controlled `open={false}`) in all engines; dismissals (Escape, a click\n * outside, Tab) animate with CSS only. Without it the part renders no `<ViewTransition>`.\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, MenuPopupState>;\n style?: StateValue<CSSProperties | undefined, MenuPopupState>;\n children?: StateValue<ReactNode, MenuPopupState>;\n /** Should render a `<div>`; a `<dialog>` would run the dialog focusing steps. */\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuPopupState>;\n}\n\n/** Engine capabilities never change while the page lives. */\nconst subscribeNever = () => () => {};\n\n/** Typeahead buffer lifetime after the last keystroke (design 5.1). */\nconst TYPEAHEAD_RESET_MS = 500;\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/** What a press may focus inside a popup: items, the popup itself, the consumer's controls. */\nconst FOCUSABLE_SELECTOR =\n 'a[href], button, input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable=\"false\"])';\n\n/**\n * The menu itself: `<div popover=\"auto\" role=\"menu\" aria-labelledby={trigger} tabindex=\"-1\">`,\n * placed against its Trigger with CSS Anchor Positioning. Owns the keyboard model (design 5):\n * ArrowUp / ArrowDown / Home / End move focus between items (or the browser does, with\n * `experimental_focusgroup` where supported), printable characters run typeahead, ArrowLeft\n * closes a submenu. The top-level popup closes the whole menu at once when focus leaves it (Tab).\n * Style it with `:popover-open`; it emits `data-side` / `data-align` but no `data-open`.\n *\n * Owned keys (conventions 1.2): `id`, `popover`, `role`, `aria-labelledby` (token-merged),\n * `tabIndex`, `focusgroup` (with the flag), `onKeyDown`, `onBlur`, `onMouseDown` (a press on\n * non-item space keeps focus on the current item), `onPointerEnter` / `onPointerLeave`, and\n * `data-side` / `data-align` unless `side` is `null`.\n */\nexport function MenuPopup({\n side: sideProp,\n align = \"start\",\n keepMounted,\n experimental_focusgroup = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuPopupProps) {\n const menu = useMenuContext(\"Menu.Popup\");\n const { popover, submenu, loop } = menu;\n const side = sideProp === undefined ? (submenu ? \"right\" : \"bottom\") : sideProp;\n\n // As Tabs.List (#43): detected only with the flag, never during SSR or hydration.\n const nativeFocusgroup = useSyncExternalStore(\n subscribeNever,\n () => experimental_focusgroup && \"focusGroup\" in HTMLElement.prototype,\n () => false,\n );\n\n const typeahead = useRef({ text: \"\", at: 0 });\n const typingAhead = useCallback(() => {\n const { text, at } = typeahead.current;\n return text !== \"\" && Date.now() - at <= TYPEAHEAD_RESET_MS;\n }, []);\n\n const { setElement, visible, wrapContent } = usePopoverPopup(popover, {\n part: \"Menu.Popup\",\n initialFocus: undefined,\n // Design 6.1: the first (or last) enabled item by open cause, nothing for a hover open, the\n // popup itself when there are no items.\n // A pointer open focuses without the focus ring where the engine can be told so (H1).\n resolveInitialFocus: (popup) => {\n const { cause, pointer } = menu.takeOpenCause();\n if (cause === \"none\") return null;\n const element = edgeItem(popup, cause) ?? popup;\n return pointer ? { element, focusVisible: false } : element;\n },\n keepMounted,\n transition,\n });\n\n const devRef = useCallback((element: HTMLElement) => {\n if (isDev && element.tagName === \"DIALOG\") {\n console.error(\n \"Menu.Popup: rendered a <dialog>. Render a <div>: a <dialog popover> runs the dialog \" +\n \"focusing steps, which fight the menu's item focus.\",\n );\n }\n return () => {};\n }, []);\n const ref = useMemo(() => mergeRefs(setElement, devRef), [setElement, devRef]);\n\n const listContext = useMemo<MenuListContextValue>(\n () => ({ menu, nativeFocusgroup, typingAhead }),\n [menu, nativeFocusgroup, typingAhead],\n );\n\n const state: MenuPopupState = { open: popover.committed, side, align, submenu };\n\n const runTypeahead = (popup: HTMLElement, current: HTMLElement | null, key: string): boolean => {\n const now = Date.now();\n const buffer = typeahead.current;\n if (buffer.text === \"\" || now - buffer.at > TYPEAHEAD_RESET_MS) buffer.text = \"\";\n if (key === \" \" && buffer.text === \"\") return false;\n buffer.text += key.toLowerCase();\n buffer.at = now;\n const items = menuItems(popup);\n if (items.length === 0) return true;\n // A repeated single character cycles through the items starting with it; a longer buffer\n // keeps the current item while it still matches (design 5.1).\n const repeated = [...buffer.text].every((c) => c === buffer.text[0]);\n const search = repeated ? buffer.text[0]! : buffer.text;\n const index = current === null ? -1 : items.indexOf(current);\n const start = repeated || index < 0 ? index + 1 : index;\n for (let i = 0; i < items.length; i++) {\n const item = items[(start + i) % items.length]!;\n if (typeaheadText(item).startsWith(search)) {\n if (item !== current) focusItem(item);\n break;\n }\n }\n return true;\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (event.defaultPrevented || hasModifier(event)) return;\n const popup = event.currentTarget;\n const target = event.target as HTMLElement;\n const onItem = isMenuItem(target, popup);\n if (!onItem && target !== popup) return;\n const key = event.key;\n\n // ArrowLeft (ArrowRight in RTL) closes a submenu; focus returns to its SubmenuTrigger\n // through the popover hook's nested focus rule (design 6.3).\n if (submenu && key === (isRtl(popup) ? \"ArrowRight\" : \"ArrowLeft\")) {\n event.preventDefault();\n popover.requestClose(event.nativeEvent, \"activate\");\n return;\n }\n\n if (key === \"ArrowDown\" || key === \"ArrowUp\" || key === \"Home\" || key === \"End\") {\n if (!onItem) {\n // The popup itself has focus (no items at open, or focus landed on it some other way):\n // into the list from its edge (review M1).\n event.preventDefault();\n const items = menuItems(popup);\n const item = key === \"ArrowDown\" || key === \"Home\" ? items[0] : items.at(-1);\n if (item !== undefined) focusItem(item);\n return;\n }\n // The browser moves focus itself where `focusgroup` is implemented.\n if (nativeFocusgroup) return;\n event.preventDefault();\n const items = menuItems(popup);\n const index = items.indexOf(target);\n const last = items.length - 1;\n let next: number;\n if (key === \"Home\") next = 0;\n else if (key === \"End\") next = last;\n else if (key === \"ArrowDown\") next = index >= last ? (loop ? 0 : last) : index + 1;\n else next = index <= 0 ? (loop ? last : 0) : index - 1;\n const item = items[next];\n if (item !== undefined && item !== target) focusItem(item);\n return;\n }\n\n if (key.length === 1 && runTypeahead(popup, onItem ? target : null, key)) {\n event.preventDefault();\n }\n };\n\n // Tab / Shift+Tab or any focus move out of the tree closes the whole stack at once (design\n // 2.2): submenus are DOM descendants of the top-level popup, the Trigger is outside on purpose.\n // A null `relatedTarget` (window blur, a removed element) closes nothing.\n const onBlur = (event: FocusEvent<HTMLElement>) => {\n if (submenu) return;\n const popup = event.currentTarget;\n const next = event.relatedTarget;\n if (!(next instanceof Node) || popup.contains(next)) return;\n if (popup.matches(\":popover-open\")) popup.hidePopover();\n };\n\n // A press on non-item space (padding, a Separator, a GroupLabel) would focus the popup itself\n // (`tabindex=\"-1\"`) and take focus off the current item; keep it where it is (review M1, the\n // pointer-focus model of design 6.2). Focusable content of the consumer's (a text field) still\n // takes focus.\n const onMouseDown = (event: MouseEvent<HTMLElement>) => {\n if (event.button !== 0) return;\n const target = event.target as Element;\n const focusable = target.closest(FOCUSABLE_SELECTOR);\n if (focusable === null || focusable.getAttribute(\"role\") === \"menu\") event.preventDefault();\n };\n\n const libraryProps: ComponentPropsWithRef<\"div\"> & DataAttributes = {\n id: popover.ids.popup,\n popover: \"auto\",\n role: \"menu\",\n \"aria-labelledby\": menu.triggerId,\n // The popup itself takes focus as the last resort when a menu has no items.\n tabIndex: -1,\n ...(experimental_focusgroup && { focusgroup: `menu ${loop ? \"wrap\" : \"nowrap\"}` }),\n onKeyDown,\n onBlur,\n onMouseDown,\n ...(submenu && {\n onPointerEnter: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") menu.timers.cancelClose();\n },\n onPointerLeave: (event: PointerEvent<HTMLElement>) => {\n if (event.pointerType !== \"touch\") scheduleHoverClose(menu, event.nativeEvent);\n },\n }),\n ...(side !== null && { \"data-side\": side, \"data-align\": align }),\n };\n\n const content = (s: MenuPopupState): ReactNode => {\n let node: ReactNode;\n if (keepMounted === true) {\n node = (\n <Activity mode={visible ? \"visible\" : \"hidden\"}>\n {wrapContent(resolveStateValue(children, s))}\n </Activity>\n );\n } else {\n node = visible ? wrapContent(resolveStateValue(children, s)) : null;\n }\n // Not part of an enclosing Toolbar: a Menu.Trigger or ToggleGroup in here sees no Toolbar.\n return (\n <MenuListContext value={listContext}>\n <ToolbarContext value={null}>{node}</ToolbarContext>\n </MenuListContext>\n );\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuPopupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n // The inline placement sits under the consumer's style: the consumer wins.\n style:\n side === null\n ? style\n : (s) => ({\n positionArea: positionArea(side, align),\n positionTryFallbacks: submenu ? \"flip-inline\" : POSITION_TRY_FALLBACKS,\n ...resolveStateValue(style, s),\n }),\n children: content,\n props: rest,\n libraryProps,\n ref,\n });\n\n return useTransitionBoundary(\"Menu.Popup\", transition, element);\n}\n\nMenuPopup.displayName = \"MenuPopup\";\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { transitionTypes } from "../internal/transition-types.mjs";
|
|
|
5
5
|
import { useControllableAction } from "../internal/use-controllable-action.mjs";
|
|
6
6
|
import { useRender } from "../internal/use-render.mjs";
|
|
7
7
|
import { MenuGroupContext, MenuRadioGroupContext } from "./menu-context.mjs";
|
|
8
|
-
import { useId, useRef } from "react";
|
|
8
|
+
import { useId, useMemo, useRef } from "react";
|
|
9
9
|
import { jsx } from "react/jsx-runtime";
|
|
10
10
|
//#region src/menu/menu-radio-group.tsx
|
|
11
11
|
/**
|
|
@@ -31,7 +31,7 @@ function MenuRadioGroup({ value, defaultValue, valueChangeAction, className, sty
|
|
|
31
31
|
prop: "value"
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
-
const context = {
|
|
34
|
+
const context = useMemo(() => ({
|
|
35
35
|
value: optimistic,
|
|
36
36
|
pending,
|
|
37
37
|
select: (next, event, then) => {
|
|
@@ -39,7 +39,12 @@ function MenuRadioGroup({ value, defaultValue, valueChangeAction, className, sty
|
|
|
39
39
|
if (then !== void 0) after.current.set(details, then);
|
|
40
40
|
dispatch(next, details);
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
}), [
|
|
43
|
+
optimistic,
|
|
44
|
+
pending,
|
|
45
|
+
dispatch
|
|
46
|
+
]);
|
|
47
|
+
const groupContext = useMemo(() => ({ labelId }), [labelId]);
|
|
43
48
|
const element = useRender({
|
|
44
49
|
defaultElement: "div",
|
|
45
50
|
render,
|
|
@@ -58,7 +63,7 @@ function MenuRadioGroup({ value, defaultValue, valueChangeAction, className, sty
|
|
|
58
63
|
}
|
|
59
64
|
});
|
|
60
65
|
return /* @__PURE__ */ jsx(MenuGroupContext, {
|
|
61
|
-
value:
|
|
66
|
+
value: groupContext,
|
|
62
67
|
children: /* @__PURE__ */ jsx(MenuRadioGroupContext, {
|
|
63
68
|
value: context,
|
|
64
69
|
children: element
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-radio-group.mjs","names":[],"sources":["../../src/menu/menu-radio-group.tsx"],"sourcesContent":["\"use client\";\nimport { useId, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { MenuGroupContext, MenuRadioGroupContext } from \"./menu-context.js\";\nimport type { MenuRadioGroupContextValue } from \"./menu-context.js\";\n\nexport interface MenuRadioGroupState {\n /** Optimistic value; `null` when no item is selected. */\n readonly value: string | null;\n /** `valueChangeAction` is in flight. */\n readonly pending: boolean;\n}\n\nexport interface MenuRadioGroupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"role\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Controlled value; `null` selects nothing, `undefined` means uncontrolled. */\n value?: string | null;\n /** Initial value when uncontrolled. Default `null`. */\n defaultValue?: string | null;\n /**\n * Receives the chosen item's `value`, awaited inside the group's transition; the items'\n * `aria-checked` follow at once and roll back on rejection. Re-activating the selected item\n * dispatches nothing. May be a Server Function.\n */\n valueChangeAction?: ChangeAction<string>;\n className?: StateValue<string | undefined, MenuRadioGroupState>;\n style?: StateValue<CSSProperties | undefined, MenuRadioGroupState>;\n children?: StateValue<ReactNode, MenuRadioGroupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuRadioGroupState>;\n}\n\n/**\n * The state owner of a set of `Menu.RadioItem`s: `<div role=\"group\" aria-labelledby>` (named by\n * a `Menu.GroupLabel` inside it). Emits `data-pending` while `valueChangeAction` runs.\n *\n * Owned keys (conventions 1.2): `role`, `aria-labelledby` (token-merged), `data-pending`.\n */\nexport function MenuRadioGroup({\n value,\n defaultValue,\n valueChangeAction,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuRadioGroupProps) {\n const labelId = `${useId()}-label`;\n // The work to run after a dispatch's action resolved (an item's `closeOnActivate`), keyed by\n // the dispatch's details object.\n const after = useRef(new WeakMap<object, () => void>());\n\n const { optimistic, pending, dispatch } = useControllableAction<string | null>({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: async (next, details) => {\n if (next !== null) await valueChangeAction?.(next, details);\n after.current.get(details)?.();\n },\n transitionTypes: () => transitionTypes(\"menu\", \"select\"),\n warn: { component: \"Menu.RadioGroup\", prop: \"value\" },\n });\n\n const context
|
|
1
|
+
{"version":3,"file":"menu-radio-group.mjs","names":[],"sources":["../../src/menu/menu-radio-group.tsx"],"sourcesContent":["\"use client\";\nimport { useId, useMemo, useRef } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport { transitionTypes } from \"../internal/transition-types.js\";\nimport type { ActionDetails, ChangeAction, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useControllableAction } from \"../internal/use-controllable-action.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { MenuGroupContext, MenuRadioGroupContext } from \"./menu-context.js\";\nimport type { MenuRadioGroupContextValue } from \"./menu-context.js\";\n\nexport interface MenuRadioGroupState {\n /** Optimistic value; `null` when no item is selected. */\n readonly value: string | null;\n /** `valueChangeAction` is in flight. */\n readonly pending: boolean;\n}\n\nexport interface MenuRadioGroupProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"role\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Controlled value; `null` selects nothing, `undefined` means uncontrolled. */\n value?: string | null;\n /** Initial value when uncontrolled. Default `null`. */\n defaultValue?: string | null;\n /**\n * Receives the chosen item's `value`, awaited inside the group's transition; the items'\n * `aria-checked` follow at once and roll back on rejection. Re-activating the selected item\n * dispatches nothing. May be a Server Function.\n */\n valueChangeAction?: ChangeAction<string>;\n className?: StateValue<string | undefined, MenuRadioGroupState>;\n style?: StateValue<CSSProperties | undefined, MenuRadioGroupState>;\n children?: StateValue<ReactNode, MenuRadioGroupState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, MenuRadioGroupState>;\n}\n\n/**\n * The state owner of a set of `Menu.RadioItem`s: `<div role=\"group\" aria-labelledby>` (named by\n * a `Menu.GroupLabel` inside it). Emits `data-pending` while `valueChangeAction` runs.\n *\n * Owned keys (conventions 1.2): `role`, `aria-labelledby` (token-merged), `data-pending`.\n */\nexport function MenuRadioGroup({\n value,\n defaultValue,\n valueChangeAction,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuRadioGroupProps) {\n const labelId = `${useId()}-label`;\n // The work to run after a dispatch's action resolved (an item's `closeOnActivate`), keyed by\n // the dispatch's details object.\n const after = useRef(new WeakMap<object, () => void>());\n\n const { optimistic, pending, dispatch } = useControllableAction<string | null>({\n controlled: value,\n defaultValue,\n initialValue: null,\n action: async (next, details) => {\n if (next !== null) await valueChangeAction?.(next, details);\n after.current.get(details)?.();\n },\n transitionTypes: () => transitionTypes(\"menu\", \"select\"),\n warn: { component: \"Menu.RadioGroup\", prop: \"value\" },\n });\n\n // Memoised (#48): `dispatch` is stable.\n const context = useMemo<MenuRadioGroupContextValue>(\n () => ({\n value: optimistic,\n pending,\n select: (next, event, then) => {\n const details = createActionDetails(event, {}) as ActionDetails;\n if (then !== undefined) after.current.set(details, then);\n dispatch(next, details);\n },\n }),\n [optimistic, pending, dispatch],\n );\n const groupContext = useMemo(() => ({ labelId }), [labelId]);\n\n const state: MenuRadioGroupState = { value: optimistic, pending };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, MenuRadioGroupState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n role: \"group\",\n \"aria-labelledby\": labelId,\n \"data-pending\": dataAttr(pending),\n },\n });\n\n return (\n <MenuGroupContext value={groupContext}>\n <MenuRadioGroupContext value={context}>{element}</MenuRadioGroupContext>\n </MenuGroupContext>\n );\n}\n\nMenuRadioGroup.displayName = \"MenuRadioGroup\";\n"],"mappings":";;;;;;;;;;;;;;;;AA6CA,SAAgB,eAAe,EAC7B,OACA,cACA,mBACA,WACA,OACA,UACA,QACA,GAAG,QACmB;CACtB,MAAM,UAAU,GAAG,MAAM,EAAE;CAG3B,MAAM,QAAQ,uBAAO,IAAI,QAA4B,CAAC;CAEtD,MAAM,EAAE,YAAY,SAAS,aAAa,sBAAqC;EAC7E,YAAY;EACZ;EACA,cAAc;EACd,QAAQ,OAAO,MAAM,YAAY;GAC/B,IAAI,SAAS,MAAM,MAAM,oBAAoB,MAAM,OAAO;GAC1D,MAAM,QAAQ,IAAI,OAAO,CAAC,GAAG;EAC/B;EACA,uBAAuB,gBAAgB,QAAQ,QAAQ;EACvD,MAAM;GAAE,WAAW;GAAmB,MAAM;EAAQ;CACtD,CAAC;CAGD,MAAM,UAAU,eACP;EACL,OAAO;EACP;EACA,SAAS,MAAM,OAAO,SAAS;GAC7B,MAAM,UAAU,oBAAoB,OAAO,CAAC,CAAC;GAC7C,IAAI,SAAS,KAAA,GAAW,MAAM,QAAQ,IAAI,SAAS,IAAI;GACvD,SAAS,MAAM,OAAO;EACxB;CACF,IACA;EAAC;EAAY;EAAS;CAAQ,CAChC;CACA,MAAM,eAAe,eAAe,EAAE,QAAQ,IAAI,CAAC,OAAO,CAAC;CAI3D,MAAM,UAAU,UAA6D;EAC3E,gBAAgB;EAChB;EACA,OAAA;GALmC,OAAO;GAAY;EAKlD;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,MAAM;GACN,mBAAmB;GACnB,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;CAED,OACE,oBAAC,kBAAD;EAAkB,OAAO;EACvB,UAAA,oBAAC,uBAAD;GAAuB,OAAO;GAAU,UAAA;EAA+B,CAAA;CACvD,CAAA;AAEtB;AAEA,eAAe,cAAc"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
3
|
-
import { rendersButton
|
|
3
|
+
import { rendersButton } from "../internal/render-element.mjs";
|
|
4
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
4
5
|
import { MenuRadioGroupContext, closeStackLater, useMenuContext } from "./menu-context.mjs";
|
|
5
6
|
import { useMenuItem } from "./use-menu-item.mjs";
|
|
6
7
|
import { use } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-radio-item.mjs","names":[],"sources":["../../src/menu/menu-radio-item.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { MenuCheckboxItemProps } from \"./menu-checkbox-item.js\";\nimport { closeStackLater, MenuRadioGroupContext, useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\nexport interface MenuRadioItemState {\n /** This item is the group's optimistic value (`aria-checked`). */\n readonly checked: boolean;\n /** The group's change to this item is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuRadioItemProps extends Omit<\n MenuCheckboxItemProps,\n | \"checked\"\n | \"defaultChecked\"\n | \"checkedChangeAction\"\n | \"className\"\n | \"style\"\n | \"children\"\n | \"render\"\n> {\n /** The value the group's `valueChangeAction` receives when this item is chosen. */\n value: string;\n className?: StateValue<string | undefined, MenuRadioItemState>;\n style?: StateValue<CSSProperties | undefined, MenuRadioItemState>;\n children?: StateValue<ReactNode, MenuRadioItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuRadioItemState>;\n}\n\n/**\n * One option of a `Menu.RadioGroup`: `<button type=\"button\" role=\"menuitemradio\" aria-checked>`.\n * Activation selects it through the group's transition (nothing is dispatched when it is already\n * selected); the menu stays open unless `closeOnActivate`.\n *\n * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked`, `data-checked`, and\n * `value` (not rendered: a menu item is not a form control).\n */\nexport function MenuRadioItem({\n value,\n closeOnActivate = false,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuRadioItemProps) {\n const menu = useMenuContext(\"Menu.RadioItem\");\n const group = use(MenuRadioGroupContext);\n if (group === null) throw new Error(\"Menu.RadioItem must be rendered inside a Menu.RadioGroup.\");\n\n const checked = group.value === value;\n const pending = group.pending && checked;\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.RadioItem\",\n role: \"menuitemradio\",\n disabled,\n label,\n activate: (event) => {\n const close = closeOnActivate ? closeStackLater(menu, event) : undefined;\n // Re-activating the selected item changes nothing (conventions 2.3 item 8); with\n // `closeOnActivate` it still closes the menu, as any activation does.\n if (checked) {\n close?.();\n return;\n }\n group.select(value, event, close);\n },\n });\n\n const state: MenuRadioItemState = { checked, pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuRadioItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n value: undefined,\n \"aria-checked\": checked,\n \"data-checked\": dataAttr(checked),\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuRadioItem.displayName = \"MenuRadioItem\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-radio-item.mjs","names":[],"sources":["../../src/menu/menu-radio-item.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { MenuCheckboxItemProps } from \"./menu-checkbox-item.js\";\nimport { closeStackLater, MenuRadioGroupContext, useMenuContext } from \"./menu-context.js\";\nimport { useMenuItem } from \"./use-menu-item.js\";\n\nexport interface MenuRadioItemState {\n /** This item is the group's optimistic value (`aria-checked`). */\n readonly checked: boolean;\n /** The group's change to this item is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\nexport interface MenuRadioItemProps extends Omit<\n MenuCheckboxItemProps,\n | \"checked\"\n | \"defaultChecked\"\n | \"checkedChangeAction\"\n | \"className\"\n | \"style\"\n | \"children\"\n | \"render\"\n> {\n /** The value the group's `valueChangeAction` receives when this item is chosen. */\n value: string;\n className?: StateValue<string | undefined, MenuRadioItemState>;\n style?: StateValue<CSSProperties | undefined, MenuRadioItemState>;\n children?: StateValue<ReactNode, MenuRadioItemState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, MenuRadioItemState>;\n}\n\n/**\n * One option of a `Menu.RadioGroup`: `<button type=\"button\" role=\"menuitemradio\" aria-checked>`.\n * Activation selects it through the group's transition (nothing is dispatched when it is already\n * selected); the menu stays open unless `closeOnActivate`.\n *\n * Owned keys (conventions 1.2): those of `Menu.Item`, plus `aria-checked`, `data-checked`, and\n * `value` (not rendered: a menu item is not a form control).\n */\nexport function MenuRadioItem({\n value,\n closeOnActivate = false,\n disabled = false,\n label,\n className,\n style,\n children,\n render,\n ...rest\n}: MenuRadioItemProps) {\n const menu = useMenuContext(\"Menu.RadioItem\");\n const group = use(MenuRadioGroupContext);\n if (group === null) throw new Error(\"Menu.RadioItem must be rendered inside a Menu.RadioGroup.\");\n\n const checked = group.value === value;\n const pending = group.pending && checked;\n\n const { libraryProps } = useMenuItem({\n part: \"Menu.RadioItem\",\n role: \"menuitemradio\",\n disabled,\n label,\n activate: (event) => {\n const close = closeOnActivate ? closeStackLater(menu, event) : undefined;\n // Re-activating the selected item changes nothing (conventions 2.3 item 8); with\n // `closeOnActivate` it still closes the menu, as any activation does.\n if (checked) {\n close?.();\n return;\n }\n group.select(value, event, close);\n },\n });\n\n const state: MenuRadioItemState = { checked, pending, disabled };\n\n return useRender<ComponentPropsWithRef<\"button\">, MenuRadioItemState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n ...(rendersButton(render) && { type: \"button\" as const }),\n ...libraryProps,\n value: undefined,\n \"aria-checked\": checked,\n \"data-checked\": dataAttr(checked),\n \"data-pending\": dataAttr(pending),\n },\n });\n}\n\nMenuRadioItem.displayName = \"MenuRadioItem\";\n"],"mappings":";;;;;;;;;;;;;;;;AA4CA,SAAgB,cAAc,EAC5B,OACA,kBAAkB,OAClB,WAAW,OACX,OACA,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,OAAO,eAAe,gBAAgB;CAC5C,MAAM,QAAQ,IAAI,qBAAqB;CACvC,IAAI,UAAU,MAAM,MAAM,IAAI,MAAM,2DAA2D;CAE/F,MAAM,UAAU,MAAM,UAAU;CAChC,MAAM,UAAU,MAAM,WAAW;CAEjC,MAAM,EAAE,iBAAiB,YAAY;EACnC,MAAM;EACN,MAAM;EACN;EACA;EACA,WAAW,UAAU;GACnB,MAAM,QAAQ,kBAAkB,gBAAgB,MAAM,KAAK,IAAI,KAAA;GAG/D,IAAI,SAAS;IACX,QAAQ;IACR;GACF;GACA,MAAM,OAAO,OAAO,OAAO,KAAK;EAClC;CACF,CAAC;CAID,OAAO,UAA+D;EACpE,gBAAgB;EAChB;EACA,OAAA;GALkC;GAAS;GAAS;EAKhD;EACJ;EACA;EACA;EACA,OAAO;EACP,cAAc;GACZ,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;GACvD,GAAG;GACH,OAAO,KAAA;GACP,gBAAgB;GAChB,gBAAgB,SAAS,OAAO;GAChC,gBAAgB,SAAS,OAAO;EAClC;CACF,CAAC;AACH;AAEA,cAAc,cAAc"}
|
package/dist/menu/menu-root.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { resolveStateValue } from "../internal/
|
|
2
|
+
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
3
3
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
4
4
|
import { useDelayedPair } from "../internal/use-delayed-pair.mjs";
|
|
5
5
|
import { usePopoverRoot } from "../internal/use-popover-root.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-root.mjs","names":[],"sources":["../../src/menu/menu-root.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useMemo, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ActionDetails, ChangeAction, ChangeReason, StateValue } from \"../internal/types.js\";\nimport { useDelayedPair } from \"../internal/use-delayed-pair.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { MenuContext, MenuListContext } from \"./menu-context.js\";\nimport type { MenuContextValue, MenuOpenFocus } from \"./menu-context.js\";\n\n/** Details passed as the second argument of `Menu.Root`'s `openChangeAction`. */\nexport interface MenuOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user acted on a part: the Trigger or SubmenuTrigger (click, Enter / Space,\n * arrow keys), an item's activation (the close after its action), ArrowLeft in a submenu, or a\n * foreign invoker.\n * `\"dismiss\"`: Escape, a click outside, Tab or focus leaving the menu, the pointer leaving a\n * hover-opened submenu, the parent menu closing (a submenu when the whole stack closes), script.\n * `\"platform\"`: the menu was found open on mount (opened before hydration); `event` is\n * `undefined`.\n */\n readonly reason: ChangeReason;\n}\n\nexport interface MenuRootState {\n /** Optimistic open state. Content rendered from it must not suspend. */\n readonly open: boolean;\n /** An open or close of this Root is in flight (its `openChangeAction`). */\n readonly pending: boolean;\n /** This Root is a submenu (rendered inside a `Menu.Popup`). */\n readonly submenu: boolean;\n}\n\nexport interface MenuRootProps {\n /** Controlled open state. `undefined` means uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for `true` (the menu opens when it resolves and the items have rendered) and for the\n * closes the user asks a part for (Trigger toggle, an item's activation, ArrowLeft in a\n * submenu); a notification for dismissals (Escape, a click outside, Tab, the parent menu\n * closing). May be a Server Function. A `setState` placed after your own `await` needs\n * `startTransition` (React limitation).\n */\n openChangeAction?: ChangeAction<boolean, MenuOpenChangeDetails>;\n /** Arrow keys wrap from the last item to the first and back. Default `true`. */\n loop?: boolean;\n /** Submenu only: milliseconds from hovering the SubmenuTrigger to the open. Default 100. */\n delay?: number;\n /**\n * Submenu only: milliseconds from the pointer leaving the SubmenuTrigger and the submenu to the\n * close. Default 150.\n */\n closeDelay?: number;\n children?: StateValue<ReactNode, MenuRootState>;\n}\n\n/**\n * Owns the ids, the open state and the context of one menu level. Renders no DOM. A `Menu.Root`\n * rendered inside a `Menu.Popup` is a **submenu**: its Trigger is a `Menu.SubmenuTrigger` (an\n * item of the parent menu) and its Popup opens beside that item.\n *\n * A menu is always in library mode (design/components/menu.md 2.1): opening and the closes a\n * part asks for are transitions (pending Trigger, items render from committed state,\n * `showPopover()` / `hidePopover()` once the change has committed); dismissals are the\n * browser's and `openChangeAction(false)` is a notification.\n */\nconst DEFAULT_FOCUS: MenuOpenFocus = { cause: \"first\", pointer: false };\n\nexport function MenuRoot({\n open,\n defaultOpen,\n openChangeAction,\n loop = true,\n delay,\n closeDelay,\n children,\n}: MenuRootProps) {\n const parent = use(MenuContext);\n const list = use(MenuListContext);\n // A Root inside a Popup's content is a submenu (design 1); the enclosing menu is the Popup's.\n const parentMenu = list !== null ? list.menu : null;\n const submenu = parentMenu !== null;\n\n useDevWarning(\n !submenu && (delay !== undefined || closeDelay !== undefined),\n \"Menu.Root: `delay` and `closeDelay` only apply to a submenu (a Menu.Root inside a \" +\n \"Menu.Popup); a top-level menu does not open on hover.\",\n );\n useDevWarning(\n parent !== null && !submenu,\n \"Menu.Root: rendered inside another Menu.Root but outside its Menu.Popup. A submenu's Root \" +\n \"goes inside the parent's Menu.Popup.\",\n );\n\n const timers = useDelayedPair();\n // The cause recorded by the part about to dispatch an open, and the cause of the open being\n // shown (design 14, \"Open cause\").\n const nextCause = useRef<MenuOpenFocus | null>(null);\n const openCause = useRef<MenuOpenFocus | null>(null);\n\n const popover = usePopoverRoot<MenuOpenChangeDetails>({\n component: \"menu\",\n part: \"Menu.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n onDispatch: (next) => {\n // Any dispatch cancels hover intent (walkthrough 9).\n timers.cancel();\n if (next) openCause.current = nextCause.current ?? DEFAULT_FOCUS;\n nextCause.current = null;\n },\n });\n\n const setOpenCause = useCallback((cause: MenuOpenFocus[\"cause\"], pointer = false) => {\n nextCause.current = { cause, pointer };\n }, []);\n const takeOpenCause = useCallback(() => {\n const cause = openCause.current ?? DEFAULT_FOCUS;\n openCause.current = null;\n return cause;\n }, []);\n\n const resolvedDelay = delay ?? 100;\n const resolvedCloseDelay = closeDelay ?? 150;\n //
|
|
1
|
+
{"version":3,"file":"menu-root.mjs","names":[],"sources":["../../src/menu/menu-root.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useMemo, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { ActionDetails, ChangeAction, ChangeReason, StateValue } from \"../internal/types.js\";\nimport { useDelayedPair } from \"../internal/use-delayed-pair.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { usePopoverRoot } from \"../internal/use-popover-root.js\";\nimport { resolveStateValue } from \"../internal/use-render.js\";\nimport { MenuContext, MenuListContext } from \"./menu-context.js\";\nimport type { MenuContextValue, MenuOpenFocus } from \"./menu-context.js\";\n\n/** Details passed as the second argument of `Menu.Root`'s `openChangeAction`. */\nexport interface MenuOpenChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user acted on a part: the Trigger or SubmenuTrigger (click, Enter / Space,\n * arrow keys), an item's activation (the close after its action), ArrowLeft in a submenu, or a\n * foreign invoker.\n * `\"dismiss\"`: Escape, a click outside, Tab or focus leaving the menu, the pointer leaving a\n * hover-opened submenu, the parent menu closing (a submenu when the whole stack closes), script.\n * `\"platform\"`: the menu was found open on mount (opened before hydration); `event` is\n * `undefined`.\n */\n readonly reason: ChangeReason;\n}\n\nexport interface MenuRootState {\n /** Optimistic open state. Content rendered from it must not suspend. */\n readonly open: boolean;\n /** An open or close of this Root is in flight (its `openChangeAction`). */\n readonly pending: boolean;\n /** This Root is a submenu (rendered inside a `Menu.Popup`). */\n readonly submenu: boolean;\n}\n\nexport interface MenuRootProps {\n /** Controlled open state. `undefined` means uncontrolled. */\n open?: boolean;\n /** Initial open state when uncontrolled. Opens after hydration. */\n defaultOpen?: boolean;\n /**\n * Awaited for `true` (the menu opens when it resolves and the items have rendered) and for the\n * closes the user asks a part for (Trigger toggle, an item's activation, ArrowLeft in a\n * submenu); a notification for dismissals (Escape, a click outside, Tab, the parent menu\n * closing). May be a Server Function. A `setState` placed after your own `await` needs\n * `startTransition` (React limitation).\n */\n openChangeAction?: ChangeAction<boolean, MenuOpenChangeDetails>;\n /** Arrow keys wrap from the last item to the first and back. Default `true`. */\n loop?: boolean;\n /** Submenu only: milliseconds from hovering the SubmenuTrigger to the open. Default 100. */\n delay?: number;\n /**\n * Submenu only: milliseconds from the pointer leaving the SubmenuTrigger and the submenu to the\n * close. Default 150.\n */\n closeDelay?: number;\n children?: StateValue<ReactNode, MenuRootState>;\n}\n\n/**\n * Owns the ids, the open state and the context of one menu level. Renders no DOM. A `Menu.Root`\n * rendered inside a `Menu.Popup` is a **submenu**: its Trigger is a `Menu.SubmenuTrigger` (an\n * item of the parent menu) and its Popup opens beside that item.\n *\n * A menu is always in library mode (design/components/menu.md 2.1): opening and the closes a\n * part asks for are transitions (pending Trigger, items render from committed state,\n * `showPopover()` / `hidePopover()` once the change has committed); dismissals are the\n * browser's and `openChangeAction(false)` is a notification.\n */\nconst DEFAULT_FOCUS: MenuOpenFocus = { cause: \"first\", pointer: false };\n\nexport function MenuRoot({\n open,\n defaultOpen,\n openChangeAction,\n loop = true,\n delay,\n closeDelay,\n children,\n}: MenuRootProps) {\n const parent = use(MenuContext);\n const list = use(MenuListContext);\n // A Root inside a Popup's content is a submenu (design 1); the enclosing menu is the Popup's.\n const parentMenu = list !== null ? list.menu : null;\n const submenu = parentMenu !== null;\n\n useDevWarning(\n !submenu && (delay !== undefined || closeDelay !== undefined),\n \"Menu.Root: `delay` and `closeDelay` only apply to a submenu (a Menu.Root inside a \" +\n \"Menu.Popup); a top-level menu does not open on hover.\",\n );\n useDevWarning(\n parent !== null && !submenu,\n \"Menu.Root: rendered inside another Menu.Root but outside its Menu.Popup. A submenu's Root \" +\n \"goes inside the parent's Menu.Popup.\",\n );\n\n const timers = useDelayedPair();\n // The cause recorded by the part about to dispatch an open, and the cause of the open being\n // shown (design 14, \"Open cause\").\n const nextCause = useRef<MenuOpenFocus | null>(null);\n const openCause = useRef<MenuOpenFocus | null>(null);\n\n const popover = usePopoverRoot<MenuOpenChangeDetails>({\n component: \"menu\",\n part: \"Menu.Root\",\n mode: \"library\",\n open,\n defaultOpen,\n openChangeAction,\n onDispatch: (next) => {\n // Any dispatch cancels hover intent (walkthrough 9).\n timers.cancel();\n if (next) openCause.current = nextCause.current ?? DEFAULT_FOCUS;\n nextCause.current = null;\n },\n });\n\n const setOpenCause = useCallback((cause: MenuOpenFocus[\"cause\"], pointer = false) => {\n nextCause.current = { cause, pointer };\n }, []);\n const takeOpenCause = useCallback(() => {\n const cause = openCause.current ?? DEFAULT_FOCUS;\n openCause.current = null;\n return cause;\n }, []);\n\n const resolvedDelay = delay ?? 100;\n const resolvedCloseDelay = closeDelay ?? 150;\n // `popover` is memoised by `usePopoverRoot` (#48) and the other members are stable, so this\n // changes only with the open state, pending and the props listed below.\n const value = useMemo<MenuContextValue>(() => {\n const self: MenuContextValue = {\n popover,\n submenu,\n // The top-level menu: this one, or the parent's root (design 2.2).\n get root() {\n return parentMenu?.root ?? self;\n },\n parent: parentMenu,\n loop,\n delay: resolvedDelay,\n closeDelay: resolvedCloseDelay,\n triggerId: `${popover.ids.popup}-trigger`,\n setOpenCause,\n takeOpenCause,\n timers,\n };\n return self;\n }, [\n popover,\n submenu,\n parentMenu,\n loop,\n resolvedDelay,\n resolvedCloseDelay,\n setOpenCause,\n takeOpenCause,\n timers,\n ]);\n\n const state: MenuRootState = {\n open: popover.optimistic,\n pending: popover.pending,\n submenu,\n };\n\n return <MenuContext value={value}>{resolveStateValue(children, state)}</MenuContext>;\n}\n\nMenuRoot.displayName = \"MenuRoot\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqEA,MAAM,gBAA+B;CAAE,OAAO;CAAS,SAAS;AAAM;AAEtE,SAAgB,SAAS,EACvB,MACA,aACA,kBACA,OAAO,MACP,OACA,YACA,YACgB;CAChB,MAAM,SAAS,IAAI,WAAW;CAC9B,MAAM,OAAO,IAAI,eAAe;CAEhC,MAAM,aAAa,SAAS,OAAO,KAAK,OAAO;CAC/C,MAAM,UAAU,eAAe;CAE/B,cACE,CAAC,YAAY,UAAU,KAAA,KAAa,eAAe,KAAA,IACnD,yIAEF;CACA,cACE,WAAW,QAAQ,CAAC,SACpB,gIAEF;CAEA,MAAM,SAAS,eAAe;CAG9B,MAAM,YAAY,OAA6B,IAAI;CACnD,MAAM,YAAY,OAA6B,IAAI;CAEnD,MAAM,UAAU,eAAsC;EACpD,WAAW;EACX,MAAM;EACN,MAAM;EACN;EACA;EACA;EACA,aAAa,SAAS;GAEpB,OAAO,OAAO;GACd,IAAI,MAAM,UAAU,UAAU,UAAU,WAAW;GACnD,UAAU,UAAU;EACtB;CACF,CAAC;CAED,MAAM,eAAe,aAAa,OAA+B,UAAU,UAAU;EACnF,UAAU,UAAU;GAAE;GAAO;EAAQ;CACvC,GAAG,CAAC,CAAC;CACL,MAAM,gBAAgB,kBAAkB;EACtC,MAAM,QAAQ,UAAU,WAAW;EACnC,UAAU,UAAU;EACpB,OAAO;CACT,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,SAAS;CAC/B,MAAM,qBAAqB,cAAc;CAGzC,MAAM,QAAQ,cAAgC;EAC5C,MAAM,OAAyB;GAC7B;GACA;GAEA,IAAI,OAAO;IACT,OAAO,YAAY,QAAQ;GAC7B;GACA,QAAQ;GACR;GACA,OAAO;GACP,YAAY;GACZ,WAAW,GAAG,QAAQ,IAAI,MAAM;GAChC;GACA;GACA;EACF;EACA,OAAO;CACT,GAAG;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,MAAM,QAAuB;EAC3B,MAAM,QAAQ;EACd,SAAS,QAAQ;EACjB;CACF;CAEA,OAAO,oBAAC,aAAD;EAAoB;EAAQ,UAAA,kBAAkB,UAAU,KAAK;CAAe,CAAA;AACrF;AAEA,SAAS,cAAc"}
|
|
@@ -22,7 +22,8 @@ export interface MenuTriggerProps extends Omit<ComponentPropsWithRef<"button">,
|
|
|
22
22
|
/**
|
|
23
23
|
* The menu button: `<button type="button" commandfor command="toggle-popover"
|
|
24
24
|
* aria-haspopup="menu" aria-expanded>`. A click (Enter, Space) toggles the menu through the
|
|
25
|
-
* Root's transition; ArrowDown / ArrowUp open it with the first / last item focused
|
|
25
|
+
* Root's transition; ArrowDown / ArrowUp open it with the first / last item focused (inside a
|
|
26
|
+
* vertical `Toolbar.Root`: the inline-end arrow, with the first item focused).
|
|
26
27
|
*
|
|
27
28
|
* Owned keys (conventions 1.2): `type` (on a `<button>` only), `id`, `disabled`, `commandfor`,
|
|
28
29
|
* `command`, `aria-haspopup`, `aria-expanded`, `onClick`, `onKeyDown`, `data-open`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-trigger.d.mts","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"menu-trigger.d.mts","names":[],"sources":["../../src/menu/menu-trigger.tsx"],"mappings":";;;;iBAWiB;;WAEN;;WAEA;WACA;;iBAGM,yBAAyB,KACxC;;EAIA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;EAE9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;wBAwCvC,cACd,UACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAPH"}
|