@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-context.mjs","names":[],"sources":["../../src/accordion/accordion-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\n\n/** Why a change was dispatched (design 2.2 / 2.3). */\nexport type AccordionChangeReason = \"activate\" | \"platform\";\n\n/**\n * \"static\": JS-free mode, the browser owns `open`. \"library\": `open` is driven by React\n * committed state inside the Root's transition (design section 2).\n */\nexport type AccordionMode = \"static\" | \"library\";\n\nexport interface AccordionRootContextValue {\n readonly mode: AccordionMode;\n readonly exclusive: boolean;\n /** `name` for every `<details>`; set only in static exclusive mode. */\n readonly name: string | undefined;\n readonly disabled: boolean;\n readonly pending: boolean;\n /** Committed open set (library mode). Empty in static mode. */\n readonly committed: ReadonlySet<string>;\n /** Optimistic open set (library mode). Empty in static mode. */\n readonly optimistic: ReadonlySet<string>;\n /** A Trigger was activated: compute `next` from the optimistic set and dispatch. */\n readonly dispatch: (item: string, open: boolean, event: Event | undefined) => void;\n /** The browser changed `details.open`: compute `next` from the DOM and dispatch (design 2.3). */\n readonly dispatchFromDom: (item: string, open: boolean, event: Event | undefined) => void;\n /** Items register their `<details>` so `dispatchFromDom` can read every item's DOM state. */\n readonly registerItem: (value: string, element: HTMLDetailsElement) => () => void;\n /** Triggers register their `<summary>` for arrow-key navigation (design section 5). */\n readonly registerTrigger: (element: HTMLElement) => () => void;\n}\n\nexport const AccordionRootContext = createContext<AccordionRootContextValue | null>(null);\n\nexport interface AccordionItemContextValue {\n readonly value: string;\n readonly disabled: boolean;\n /** `undefined` in static mode (browser-owned, unknown to React). */\n readonly committedOpen: boolean | undefined;\n /** `undefined` in static mode. */\n readonly optimisticOpen: boolean | undefined;\n readonly pending: boolean;\n}\n\nexport const AccordionItemContext = createContext<AccordionItemContextValue | null>(null);\n"],"mappings":";;;AAiCA,MAAa,uBAAuB,cAAgD,IAAI;
|
|
1
|
+
{"version":3,"file":"accordion-context.mjs","names":[],"sources":["../../src/accordion/accordion-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext } from \"react\";\n\n/** Why a change was dispatched (design 2.2 / 2.3). */\nexport type AccordionChangeReason = \"activate\" | \"platform\";\n\n/**\n * \"static\": JS-free mode, the browser owns `open`. \"library\": `open` is driven by React\n * committed state inside the Root's transition (design section 2).\n */\nexport type AccordionMode = \"static\" | \"library\";\n\nexport interface AccordionRootContextValue {\n readonly mode: AccordionMode;\n readonly exclusive: boolean;\n /** `name` for every `<details>`; set only in static exclusive mode. */\n readonly name: string | undefined;\n readonly disabled: boolean;\n readonly pending: boolean;\n /** Committed open set (library mode). Empty in static mode. */\n readonly committed: ReadonlySet<string>;\n /** Optimistic open set (library mode). Empty in static mode. */\n readonly optimistic: ReadonlySet<string>;\n /** A Trigger was activated: compute `next` from the optimistic set and dispatch. */\n readonly dispatch: (item: string, open: boolean, event: Event | undefined) => void;\n /** The browser changed `details.open`: compute `next` from the DOM and dispatch (design 2.3). */\n readonly dispatchFromDom: (item: string, open: boolean, event: Event | undefined) => void;\n /** Items register their `<details>` so `dispatchFromDom` can read every item's DOM state. */\n readonly registerItem: (value: string, element: HTMLDetailsElement) => () => void;\n /** Triggers register their `<summary>` for arrow-key navigation (design section 5). */\n readonly registerTrigger: (element: HTMLElement) => () => void;\n}\n\nexport const AccordionRootContext = createContext<AccordionRootContextValue | null>(null);\n\nexport interface AccordionItemContextValue {\n readonly value: string;\n readonly disabled: boolean;\n /** `undefined` in static mode (browser-owned, unknown to React). */\n readonly committedOpen: boolean | undefined;\n /** `undefined` in static mode. */\n readonly optimisticOpen: boolean | undefined;\n readonly pending: boolean;\n /**\n * The Root's mode is `\"library\"`. The Root members the Trigger and Panel need are copied here\n * so they read only this context, which is memoised per item: opening another item re-renders\n * every Item (they read the Root's sets) but not their Triggers and Panels (#48).\n */\n readonly library: boolean;\n /** The Root's `dispatch` (stable); `undefined` outside a Root. */\n readonly dispatch: AccordionRootContextValue[\"dispatch\"] | undefined;\n /** The Root's `registerTrigger` (stable); `undefined` outside a Root. */\n readonly registerTrigger: AccordionRootContextValue[\"registerTrigger\"] | undefined;\n}\n\nexport const AccordionItemContext = createContext<AccordionItemContextValue | null>(null);\n"],"mappings":";;;AAiCA,MAAa,uBAAuB,cAAgD,IAAI;AAsBxF,MAAa,uBAAuB,cAAgD,IAAI"}
|
|
@@ -4,7 +4,7 @@ import { dataAttr } from "../internal/data-attr.mjs";
|
|
|
4
4
|
import { useRender } from "../internal/use-render.mjs";
|
|
5
5
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
6
6
|
import { AccordionItemContext, AccordionRootContext } from "./accordion-context.mjs";
|
|
7
|
-
import { use, useCallback, useState } from "react";
|
|
7
|
+
import { use, useCallback, useMemo, useState } from "react";
|
|
8
8
|
import { jsx } from "react/jsx-runtime";
|
|
9
9
|
//#region src/accordion/accordion-item.tsx
|
|
10
10
|
const isDev = process.env.NODE_ENV !== "production";
|
|
@@ -47,13 +47,27 @@ function AccordionItem({ value, defaultOpen = false, disabled: disabledProp = fa
|
|
|
47
47
|
pending,
|
|
48
48
|
disabled
|
|
49
49
|
};
|
|
50
|
-
const
|
|
50
|
+
const dispatch = root?.dispatch;
|
|
51
|
+
const registerTrigger = root?.registerTrigger;
|
|
52
|
+
const itemContext = useMemo(() => ({
|
|
51
53
|
value,
|
|
52
54
|
disabled,
|
|
53
55
|
committedOpen,
|
|
54
56
|
optimisticOpen,
|
|
55
|
-
pending
|
|
56
|
-
|
|
57
|
+
pending,
|
|
58
|
+
library: isLibrary,
|
|
59
|
+
dispatch,
|
|
60
|
+
registerTrigger
|
|
61
|
+
}), [
|
|
62
|
+
value,
|
|
63
|
+
disabled,
|
|
64
|
+
committedOpen,
|
|
65
|
+
optimisticOpen,
|
|
66
|
+
pending,
|
|
67
|
+
isLibrary,
|
|
68
|
+
dispatch,
|
|
69
|
+
registerTrigger
|
|
70
|
+
]);
|
|
57
71
|
const element = useRender({
|
|
58
72
|
defaultElement: "details",
|
|
59
73
|
render,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-item.mjs","names":[],"sources":["../../src/accordion/accordion-item.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useState } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n ReactNode,\n SyntheticEvent,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext, AccordionRootContext } from \"./accordion-context.js\";\nimport type { AccordionItemContextValue } from \"./accordion-context.js\";\n\nexport interface AccordionItemState {\n readonly value: string;\n /** Optimistic open state. `undefined` in JS-free mode (browser-owned, unknown to React). */\n readonly open: boolean | undefined;\n /** This item's open state is changing through an in-flight action. */\n readonly pending: boolean;\n /** Disabled by its own prop or by the Root. */\n readonly disabled: boolean;\n}\n\nexport interface AccordionItemProps extends Omit<\n ComponentPropsWithRef<\"details\">,\n \"open\" | \"name\" | \"onToggle\" | \"className\" | \"style\" | \"children\"\n> {\n /** Identifies the item in the Root's `value`. Required in both modes. */\n value: string;\n /**\n * JS-free mode only: renders the initial `open` attribute; the browser owns it from then on.\n * Ignored (dev warning) in library mode, where the Root's `defaultValue` / `value` decide.\n */\n defaultOpen?: boolean;\n /**\n * Disables this item: the Trigger renders `aria-disabled` and never toggles. Enforced by a\n * click handler, so in JS-free mode it does not hold before hydration or with JS off.\n */\n disabled?: boolean;\n /**\n * Library mode only. Forwarded as `default` to the `<ViewTransition>` around the `<details>`;\n * animates the commit that ends an action (the `open` attribute flip), not the optimistic\n * `data-open` flip. Ignored (dev warning) in JS-free mode.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, AccordionItemState>;\n style?: StateValue<CSSProperties | undefined, AccordionItemState>;\n children?: StateValue<ReactNode, AccordionItemState>;\n /** Must render a `<details>` element. */\n render?: RenderProp<ComponentPropsWithRef<\"details\">, AccordionItemState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * One accordion entry: the `<details>` element. In library mode its `open` attribute follows\n * committed state and `data-open` / `data-pending` follow optimistic state; in JS-free mode (or\n * outside any Root, as a standalone disclosure) the browser owns `open`.\n */\nexport function AccordionItem({\n value,\n defaultOpen = false,\n disabled: disabledProp = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: AccordionItemProps) {\n const root = use(AccordionRootContext);\n const isLibrary = root?.mode === \"library\";\n const disabled = disabledProp || (root?.disabled ?? false);\n const committedOpen = isLibrary ? root.committed.has(value) : undefined;\n const optimisticOpen = isLibrary ? root.optimistic.has(value) : undefined;\n const rootPending = root?.pending ?? false;\n const pending = isLibrary && rootPending && committedOpen !== optimisticOpen;\n // JS-free mode: the prop React writes never changes after the first render, so React never\n // rewrites the attribute the browser now owns.\n const [staticOpen] = useState(defaultOpen);\n\n useDevWarning(\n isLibrary && defaultOpen,\n \"Accordion.Item: `defaultOpen` is ignored in library mode. Use `defaultValue` or `value` on \" +\n \"Accordion.Root instead.\",\n );\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Accordion.Item: `transition` has no effect in JS-free mode; the browser toggles the item \" +\n \"outside any React transition. Animate `::details-content` with CSS instead.\",\n );\n\n const registerItem = root?.registerItem;\n const libraryRef = useCallback(\n (element: HTMLDetailsElement) => {\n if (isDev && element.tagName !== \"DETAILS\") {\n console.error(\n `Accordion.Item: expected to render a <details> element but got <${element.tagName.toLowerCase()}>. ` +\n \"The platform semantics (toggle, find-in-page, exclusive groups) need a real <details>.\",\n );\n }\n const unregister = registerItem?.(value, element);\n return () => {\n unregister?.();\n };\n },\n [registerItem, value],\n );\n\n // Platform path (design 2.3): the Root reconciles pre-hydration clicks on mount and re-applies\n // committed state once nothing is in flight; this handler reports the browser's own flips.\n const onToggle = isLibrary\n ? (event: SyntheticEvent<HTMLDetailsElement>) => {\n const element = event.currentTarget;\n // Read `details.open`, never `ToggleEvent.newState`: `toggle` is coalesced.\n if (element.open === committedOpen) return; // our own commit echoing back\n if (disabled) {\n // A disabled item cannot be opened by the browser either; close it again now or, while\n // an action is in flight, when the Root re-applies committed state.\n if (!rootPending) element.open = committedOpen === true;\n return;\n }\n root.dispatchFromDom(value, element.open, event.nativeEvent);\n }\n : undefined;\n\n const state: AccordionItemState = { value, open: optimisticOpen, pending, disabled };\n const
|
|
1
|
+
{"version":3,"file":"accordion-item.mjs","names":[],"sources":["../../src/accordion/accordion-item.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useMemo, useState } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n ReactNode,\n SyntheticEvent,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext, AccordionRootContext } from \"./accordion-context.js\";\nimport type { AccordionItemContextValue } from \"./accordion-context.js\";\n\nexport interface AccordionItemState {\n readonly value: string;\n /** Optimistic open state. `undefined` in JS-free mode (browser-owned, unknown to React). */\n readonly open: boolean | undefined;\n /** This item's open state is changing through an in-flight action. */\n readonly pending: boolean;\n /** Disabled by its own prop or by the Root. */\n readonly disabled: boolean;\n}\n\nexport interface AccordionItemProps extends Omit<\n ComponentPropsWithRef<\"details\">,\n \"open\" | \"name\" | \"onToggle\" | \"className\" | \"style\" | \"children\"\n> {\n /** Identifies the item in the Root's `value`. Required in both modes. */\n value: string;\n /**\n * JS-free mode only: renders the initial `open` attribute; the browser owns it from then on.\n * Ignored (dev warning) in library mode, where the Root's `defaultValue` / `value` decide.\n */\n defaultOpen?: boolean;\n /**\n * Disables this item: the Trigger renders `aria-disabled` and never toggles. Enforced by a\n * click handler, so in JS-free mode it does not hold before hydration or with JS off.\n */\n disabled?: boolean;\n /**\n * Library mode only. Forwarded as `default` to the `<ViewTransition>` around the `<details>`;\n * animates the commit that ends an action (the `open` attribute flip), not the optimistic\n * `data-open` flip. Ignored (dev warning) in JS-free mode.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, AccordionItemState>;\n style?: StateValue<CSSProperties | undefined, AccordionItemState>;\n children?: StateValue<ReactNode, AccordionItemState>;\n /** Must render a `<details>` element. */\n render?: RenderProp<ComponentPropsWithRef<\"details\">, AccordionItemState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * One accordion entry: the `<details>` element. In library mode its `open` attribute follows\n * committed state and `data-open` / `data-pending` follow optimistic state; in JS-free mode (or\n * outside any Root, as a standalone disclosure) the browser owns `open`.\n */\nexport function AccordionItem({\n value,\n defaultOpen = false,\n disabled: disabledProp = false,\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: AccordionItemProps) {\n const root = use(AccordionRootContext);\n const isLibrary = root?.mode === \"library\";\n const disabled = disabledProp || (root?.disabled ?? false);\n const committedOpen = isLibrary ? root.committed.has(value) : undefined;\n const optimisticOpen = isLibrary ? root.optimistic.has(value) : undefined;\n const rootPending = root?.pending ?? false;\n const pending = isLibrary && rootPending && committedOpen !== optimisticOpen;\n // JS-free mode: the prop React writes never changes after the first render, so React never\n // rewrites the attribute the browser now owns.\n const [staticOpen] = useState(defaultOpen);\n\n useDevWarning(\n isLibrary && defaultOpen,\n \"Accordion.Item: `defaultOpen` is ignored in library mode. Use `defaultValue` or `value` on \" +\n \"Accordion.Root instead.\",\n );\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Accordion.Item: `transition` has no effect in JS-free mode; the browser toggles the item \" +\n \"outside any React transition. Animate `::details-content` with CSS instead.\",\n );\n\n const registerItem = root?.registerItem;\n const libraryRef = useCallback(\n (element: HTMLDetailsElement) => {\n if (isDev && element.tagName !== \"DETAILS\") {\n console.error(\n `Accordion.Item: expected to render a <details> element but got <${element.tagName.toLowerCase()}>. ` +\n \"The platform semantics (toggle, find-in-page, exclusive groups) need a real <details>.\",\n );\n }\n const unregister = registerItem?.(value, element);\n return () => {\n unregister?.();\n };\n },\n [registerItem, value],\n );\n\n // Platform path (design 2.3): the Root reconciles pre-hydration clicks on mount and re-applies\n // committed state once nothing is in flight; this handler reports the browser's own flips.\n const onToggle = isLibrary\n ? (event: SyntheticEvent<HTMLDetailsElement>) => {\n const element = event.currentTarget;\n // Read `details.open`, never `ToggleEvent.newState`: `toggle` is coalesced.\n if (element.open === committedOpen) return; // our own commit echoing back\n if (disabled) {\n // A disabled item cannot be opened by the browser either; close it again now or, while\n // an action is in flight, when the Root re-applies committed state.\n if (!rootPending) element.open = committedOpen === true;\n return;\n }\n root.dispatchFromDom(value, element.open, event.nativeEvent);\n }\n : undefined;\n\n const state: AccordionItemState = { value, open: optimisticOpen, pending, disabled };\n const dispatch = root?.dispatch;\n const registerTrigger = root?.registerTrigger;\n // Memoised (#48): changes only with this item's own values, not with every Root change.\n const itemContext = useMemo<AccordionItemContextValue>(\n () => ({\n value,\n disabled,\n committedOpen,\n optimisticOpen,\n pending,\n library: isLibrary,\n dispatch,\n registerTrigger,\n }),\n [value, disabled, committedOpen, optimisticOpen, pending, isLibrary, dispatch, registerTrigger],\n );\n\n const element = useRender<ComponentPropsWithRef<\"details\">, AccordionItemState>({\n defaultElement: \"details\",\n render,\n state,\n className,\n style,\n children,\n ref: libraryRef,\n props: rest,\n // Library-controlled layer: `open`, `name` and `onToggle` are owned by the library in at\n // least one mode, so a `render` element cannot override them either (conventions 1.2).\n libraryProps: {\n open: isLibrary ? committedOpen : staticOpen,\n // A click before hydration changes `open` under React's feet in both modes; the mismatch is\n // expected and reconciled (library mode) or the browser's business (JS-free mode).\n suppressHydrationWarning: true,\n // Owned in both modes: in library mode there is one writer of `open`, so a render element's\n // `name` (a second, platform-level writer) is removed.\n name: !isLibrary ? root?.name : undefined,\n // Only installed in library mode; owning the key in static mode would strip a render\n // element's own `onToggle`.\n ...(isLibrary && { onToggle }),\n \"data-open\": dataAttr(optimisticOpen === true),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n },\n });\n\n return useTransitionBoundary(\n \"Accordion.Item\",\n isLibrary ? transition : undefined,\n <AccordionItemContext value={itemContext}>{element}</AccordionItemContext>,\n );\n}\n\nAccordionItem.displayName = \"AccordionItem\";\n"],"mappings":";;;;;;;;;AA6DA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;AAOvC,SAAgB,cAAc,EAC5B,OACA,cAAc,OACd,UAAU,eAAe,OACzB,YACA,WACA,OACA,UACA,QACA,GAAG,QACkB;CACrB,MAAM,OAAO,IAAI,oBAAoB;CACrC,MAAM,YAAY,MAAM,SAAS;CACjC,MAAM,WAAW,iBAAiB,MAAM,YAAY;CACpD,MAAM,gBAAgB,YAAY,KAAK,UAAU,IAAI,KAAK,IAAI,KAAA;CAC9D,MAAM,iBAAiB,YAAY,KAAK,WAAW,IAAI,KAAK,IAAI,KAAA;CAChE,MAAM,cAAc,MAAM,WAAW;CACrC,MAAM,UAAU,aAAa,eAAe,kBAAkB;CAG9D,MAAM,CAAC,cAAc,SAAS,WAAW;CAEzC,cACE,aAAa,aACb,oHAEF;CACA,cACE,CAAC,aAAa,eAAe,KAAA,GAC7B,sKAEF;CAEA,MAAM,eAAe,MAAM;CAC3B,MAAM,aAAa,aAChB,YAAgC;EAC/B,IAAI,SAAS,QAAQ,YAAY,WAC/B,QAAQ,MACN,mEAAmE,QAAQ,QAAQ,YAAY,EAAE,0FAEnG;EAEF,MAAM,aAAa,eAAe,OAAO,OAAO;EAChD,aAAa;GACX,aAAa;EACf;CACF,GACA,CAAC,cAAc,KAAK,CACtB;CAIA,MAAM,WAAW,aACZ,UAA8C;EAC7C,MAAM,UAAU,MAAM;EAEtB,IAAI,QAAQ,SAAS,eAAe;EACpC,IAAI,UAAU;GAGZ,IAAI,CAAC,aAAa,QAAQ,OAAO,kBAAkB;GACnD;EACF;EACA,KAAK,gBAAgB,OAAO,QAAQ,MAAM,MAAM,WAAW;CAC7D,IACA,KAAA;CAEJ,MAAM,QAA4B;EAAE;EAAO,MAAM;EAAgB;EAAS;CAAS;CACnF,MAAM,WAAW,MAAM;CACvB,MAAM,kBAAkB,MAAM;CAE9B,MAAM,cAAc,eACX;EACL;EACA;EACA;EACA;EACA;EACA,SAAS;EACT;EACA;CACF,IACA;EAAC;EAAO;EAAU;EAAe;EAAgB;EAAS;EAAW;EAAU;CAAe,CAChG;CAEA,MAAM,UAAU,UAAgE;EAC9E,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,KAAK;EACL,OAAO;EAGP,cAAc;GACZ,MAAM,YAAY,gBAAgB;GAGlC,0BAA0B;GAG1B,MAAM,CAAC,YAAY,MAAM,OAAO,KAAA;GAGhC,GAAI,aAAa,EAAE,SAAS;GAC5B,aAAa,SAAS,mBAAmB,IAAI;GAC7C,gBAAgB,SAAS,OAAO;GAChC,iBAAiB,SAAS,QAAQ;EACpC;CACF,CAAC;CAED,OAAO,sBACL,kBACA,YAAY,aAAa,KAAA,GACzB,oBAAC,sBAAD;EAAsB,OAAO;EAAc,UAAA;CAA8B,CAAA,CAC3E;AACF;AAEA,cAAc,cAAc"}
|
|
@@ -3,7 +3,7 @@ import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { useRender } from "../internal/use-render.mjs";
|
|
5
5
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
6
|
-
import { AccordionItemContext
|
|
6
|
+
import { AccordionItemContext } from "./accordion-context.mjs";
|
|
7
7
|
import { use } from "react";
|
|
8
8
|
//#region src/accordion/accordion-panel.tsx
|
|
9
9
|
/**
|
|
@@ -11,10 +11,9 @@ import { use } from "react";
|
|
|
11
11
|
* is the parent's `open` attribute (`details:open > .panel`); it renders committed state.
|
|
12
12
|
*/
|
|
13
13
|
function AccordionPanel({ transition, className, style, children, render, ...rest }) {
|
|
14
|
-
const root = use(AccordionRootContext);
|
|
15
14
|
const item = use(AccordionItemContext);
|
|
16
15
|
if (item === null) throw new Error("Accordion.Panel must be rendered inside an Accordion.Item.");
|
|
17
|
-
const isLibrary =
|
|
16
|
+
const isLibrary = item.library;
|
|
18
17
|
useDevWarning(!isLibrary && transition !== void 0, "Accordion.Panel: `transition` has no effect in JS-free mode; the browser toggles the item outside any React transition.");
|
|
19
18
|
const state = {
|
|
20
19
|
value: item.value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-panel.mjs","names":[],"sources":["../../src/accordion/accordion-panel.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode, ViewTransitionClass } from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext
|
|
1
|
+
{"version":3,"file":"accordion-panel.mjs","names":[],"sources":["../../src/accordion/accordion-panel.tsx"],"sourcesContent":["\"use client\";\nimport { use } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode, ViewTransitionClass } from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext } from \"./accordion-context.js\";\n\nexport interface AccordionPanelState {\n readonly value: string;\n /** Committed open state. `undefined` in JS-free mode. */\n readonly open: boolean | undefined;\n readonly disabled: boolean;\n}\n\nexport interface AccordionPanelProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"className\" | \"style\" | \"children\"\n> {\n /**\n * Library mode only. Forwarded as `default` to the `<ViewTransition>` around the panel's\n * `div`; animates content that mounts or changes in the commit that ends an action.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, AccordionPanelState>;\n style?: StateValue<CSSProperties | undefined, AccordionPanelState>;\n /**\n * Always mounted (closed `<details>` content stays in the DOM, indexable and searchable). The\n * function form receives committed `open`, which is how content is mounted lazily and how\n * suspending content keeps the item closed until it is ready.\n */\n children?: StateValue<ReactNode, AccordionPanelState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, AccordionPanelState>;\n}\n\n/**\n * The item's content region: a `div` inside the `<details>`, after the Trigger. Its visibility\n * is the parent's `open` attribute (`details:open > .panel`); it renders committed state.\n */\nexport function AccordionPanel({\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: AccordionPanelProps) {\n // Only the Item's context (memoised per item, #48).\n const item = use(AccordionItemContext);\n if (item === null) {\n throw new Error(\"Accordion.Panel must be rendered inside an Accordion.Item.\");\n }\n const isLibrary = item.library;\n\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Accordion.Panel: `transition` has no effect in JS-free mode; the browser toggles the item \" +\n \"outside any React transition.\",\n );\n\n const state: AccordionPanelState = {\n value: item.value,\n open: item.committedOpen,\n disabled: item.disabled,\n };\n\n const element = useRender<ComponentPropsWithRef<\"div\">, AccordionPanelState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps: {\n \"data-disabled\": dataAttr(item.disabled),\n },\n });\n\n return useTransitionBoundary(\"Accordion.Panel\", isLibrary ? transition : undefined, element);\n}\n\nAccordionPanel.displayName = \"AccordionPanel\";\n"],"mappings":";;;;;;;;;;;;AA8CA,SAAgB,eAAe,EAC7B,YACA,WACA,OACA,UACA,QACA,GAAG,QACmB;CAEtB,MAAM,OAAO,IAAI,oBAAoB;CACrC,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,4DAA4D;CAE9E,MAAM,YAAY,KAAK;CAEvB,cACE,CAAC,aAAa,eAAe,KAAA,GAC7B,yHAEF;CAEA,MAAM,QAA6B;EACjC,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,UAAU,KAAK;CACjB;CAEA,MAAM,UAAU,UAA6D;EAC3E,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP,cAAc,EACZ,iBAAiB,SAAS,KAAK,QAAQ,EACzC;CACF,CAAC;CAED,OAAO,sBAAsB,mBAAmB,YAAY,aAAa,KAAA,GAAW,OAAO;AAC7F;AAEA,eAAe,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-root.d.mts","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"accordion-root.d.mts","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"mappings":";;;;iBA0BiB;;WAEN;;WAEA;;;iBAIM,oCAAoC;;;;;;WAM1C,QAAQ;;WAER;;WAEA;;UAGD,+BAA+B,KACvC;;EAKA;;;;;;EAMA;EACA,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,8BAA8B;;iBAGnC,mCAAmC;;EAElD;;EAEA;;EAEA;;;;;;;;;;;;EAYA,oBAAoB,uBAAuB;;iBAG5B,oCAAoC;;;;;EAKnD;;EAEA;;EAEA;;EAEA,oBAAoB,4BAA4B;;;;;;;;;;YAWtC,qBAAqB,6BAA6B;;;;;wBAqF9C,cAAc,OAAO,qCAAkB,IAAA;yBAAvC"}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
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
|
+
import { useLatest } from "../internal/use-latest.mjs";
|
|
5
6
|
import { useControllableAction } from "../internal/use-controllable-action.mjs";
|
|
6
|
-
import { resolveStateValue
|
|
7
|
+
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
8
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
7
9
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
8
10
|
import { AccordionRootContext } from "./accordion-context.mjs";
|
|
9
11
|
import { useCallback, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
@@ -84,7 +86,8 @@ function AccordionRoot(props) {
|
|
|
84
86
|
const [mode] = useState(isLibraryNow ? "library" : "static");
|
|
85
87
|
useDevWarning(mode === "library" !== isLibraryNow, "Accordion.Root: the mode changed after mount (`value`, `defaultValue` and `valueChangeAction` were all absent on one render and present on another). Switching between JS-free and library mode is not supported.");
|
|
86
88
|
const action = valueChangeAction;
|
|
87
|
-
const
|
|
89
|
+
const controlledKey = value === void 0 ? void 0 : JSON.stringify(value);
|
|
90
|
+
const controlledSet = useMemo(() => controlledKey === void 0 ? void 0 : toSet(JSON.parse(controlledKey)), [controlledKey]);
|
|
88
91
|
const items = useRef(/* @__PURE__ */ new Map());
|
|
89
92
|
const triggers = useRef(/* @__PURE__ */ new Set());
|
|
90
93
|
const registeredValues = () => [...items.current.values()];
|
|
@@ -119,14 +122,16 @@ function AccordionRoot(props) {
|
|
|
119
122
|
open
|
|
120
123
|
}));
|
|
121
124
|
}, [dispatch]);
|
|
122
|
-
const
|
|
123
|
-
dispatchChange(reduce(optimistic, item, open, exclusive), item, open, "activate", event);
|
|
124
|
-
}, [
|
|
125
|
-
dispatchChange,
|
|
125
|
+
const latest = useLatest({
|
|
126
126
|
optimistic,
|
|
127
127
|
exclusive
|
|
128
|
-
|
|
128
|
+
});
|
|
129
|
+
const dispatchFromTrigger = useCallback((item, open, event) => {
|
|
130
|
+
const { optimistic, exclusive } = latest.current;
|
|
131
|
+
dispatchChange(reduce(optimistic, item, open, exclusive), item, open, "activate", event);
|
|
132
|
+
}, [dispatchChange, latest]);
|
|
129
133
|
const dispatchFromDom = useCallback((item, open, event) => {
|
|
134
|
+
const { optimistic, exclusive } = latest.current;
|
|
130
135
|
const base = latestInFlight()?.next ?? optimistic;
|
|
131
136
|
const next = reduce(base, item, open, exclusive);
|
|
132
137
|
if (setsEqual(next, base)) return;
|
|
@@ -134,8 +139,7 @@ function AccordionRoot(props) {
|
|
|
134
139
|
}, [
|
|
135
140
|
dispatchChange,
|
|
136
141
|
latestInFlight,
|
|
137
|
-
|
|
138
|
-
exclusive
|
|
142
|
+
latest
|
|
139
143
|
]);
|
|
140
144
|
const isLibrary = mode === "library";
|
|
141
145
|
const reconcileOnMount = useEffectEvent(() => {
|
|
@@ -181,23 +185,38 @@ function AccordionRoot(props) {
|
|
|
181
185
|
}
|
|
182
186
|
const id = useId();
|
|
183
187
|
const name = !isLibrary && exclusive ? id : void 0;
|
|
188
|
+
const rootPending = isLibrary && pending;
|
|
184
189
|
const state = {
|
|
185
|
-
pending:
|
|
190
|
+
pending: rootPending,
|
|
186
191
|
disabled
|
|
187
192
|
};
|
|
188
|
-
const
|
|
193
|
+
const contextCommitted = isLibrary ? committed : EMPTY;
|
|
194
|
+
const contextOptimistic = isLibrary ? optimistic : EMPTY;
|
|
195
|
+
const context = useMemo(() => ({
|
|
189
196
|
mode,
|
|
190
197
|
exclusive,
|
|
191
198
|
name,
|
|
192
199
|
disabled,
|
|
193
|
-
pending:
|
|
194
|
-
committed:
|
|
195
|
-
optimistic:
|
|
200
|
+
pending: rootPending,
|
|
201
|
+
committed: contextCommitted,
|
|
202
|
+
optimistic: contextOptimistic,
|
|
196
203
|
dispatch: dispatchFromTrigger,
|
|
197
204
|
dispatchFromDom,
|
|
198
205
|
registerItem,
|
|
199
206
|
registerTrigger
|
|
200
|
-
}
|
|
207
|
+
}), [
|
|
208
|
+
mode,
|
|
209
|
+
exclusive,
|
|
210
|
+
name,
|
|
211
|
+
disabled,
|
|
212
|
+
rootPending,
|
|
213
|
+
contextCommitted,
|
|
214
|
+
contextOptimistic,
|
|
215
|
+
dispatchFromTrigger,
|
|
216
|
+
dispatchFromDom,
|
|
217
|
+
registerItem,
|
|
218
|
+
registerTrigger
|
|
219
|
+
]);
|
|
201
220
|
const resolvedStyle = resolveStateValue(style, state);
|
|
202
221
|
const finalStyle = experimental_interpolateSize ? {
|
|
203
222
|
interpolateSize: "allow-keywords",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-root.mjs","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffectEvent,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, 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 { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { AccordionRootContext } from \"./accordion-context.js\";\nimport type {\n AccordionChangeReason,\n AccordionMode,\n AccordionRootContextValue,\n} from \"./accordion-context.js\";\n\nexport interface AccordionRootState {\n /** An action dispatched through this Accordion is in flight (library mode only). */\n readonly pending: boolean;\n /** The Root's `disabled` prop. */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface AccordionValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user activated a Trigger. `\"platform\"`: the browser changed `open` itself\n * (find-in-page, a text fragment, a click before hydration); `event` is then the `toggle`\n * event, or `undefined` when the mismatch was found on mount.\n */\n readonly reason: AccordionChangeReason;\n /** The item whose open state changed. */\n readonly item: string;\n /** That item's new open state. */\n readonly open: boolean;\n}\n\ninterface AccordionRootBaseProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n // `defaultValue` is also a native HTMLAttributes member; the Accordion's own definition wins.\n \"onKeyDown\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Disables every item: triggers render `aria-disabled` and never dispatch. Default `false`. */\n disabled?: boolean;\n /**\n * Renders `interpolate-size: allow-keywords` on the root so `::details-content` can transition\n * `height` to `auto`. Chromium-only polish today; use the `grid-template-rows` form for a\n * cross-engine animation. Merged with `style`; the consumer's declaration wins.\n */\n experimental_interpolateSize?: boolean;\n className?: StateValue<string | undefined, AccordionRootState>;\n style?: StateValue<CSSProperties | undefined, AccordionRootState>;\n children?: StateValue<ReactNode, AccordionRootState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, AccordionRootState>;\n}\n\nexport interface AccordionRootMultipleProps extends AccordionRootBaseProps {\n /** Any number of items may be open. */\n exclusive?: false;\n /** Controlled open items. `undefined` means uncontrolled; `[]` means none open. */\n value?: string[];\n /** Initial open items in uncontrolled library mode. */\n defaultValue?: string[];\n /**\n * Called with the next open items on every change. Runs inside the Root's transition; may be\n * sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n *\n * May also be called with `details.reason === \"platform\"` when the browser opened an item\n * itself (find-in-page, a click before hydration); committed state is re-applied to the DOM\n * once the action settles.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n valueChangeAction?: ChangeAction<string[], AccordionValueChangeDetails>;\n}\n\nexport interface AccordionRootExclusiveProps extends AccordionRootBaseProps {\n /**\n * At most one item open. JS-free mode: renders the same `name` on every `<details>` (the\n * platform's exclusive group). Library mode: the value is a single item or `null`.\n */\n exclusive: true;\n /** Controlled open item. `undefined` means uncontrolled; `null` means none open. */\n value?: string | null;\n /** Initial open item in uncontrolled library mode. */\n defaultValue?: string | null;\n /** See {@link AccordionRootMultipleProps.valueChangeAction}. */\n valueChangeAction?: ChangeAction<string | null, AccordionValueChangeDetails>;\n}\n\n/**\n * Props of `Accordion.Root`, discriminated on `exclusive`.\n *\n * Without `value`, `defaultValue` and `valueChangeAction` the Accordion runs in **JS-free mode**:\n * the browser owns each `<details open>`, nothing runs on toggle, and `exclusive` renders `name`.\n * With any of them it runs in **library mode**: the summary click is intercepted, `open` is driven\n * by committed state inside a transition, and triggers show optimistic `data-open`.\n */\nexport type AccordionRootProps = AccordionRootMultipleProps | AccordionRootExclusiveProps;\n\ntype AccordionValue = string[] | string | null;\ntype OpenSet = ReadonlySet<string>;\n\nconst EMPTY: OpenSet = new Set();\n\nfunction toSet(value: AccordionValue): OpenSet {\n if (value === null) return EMPTY;\n return typeof value === \"string\" ? new Set([value]) : new Set(value);\n}\n\n/**\n * Converts the open set back to the public value. Multiple mode lists the items in registration\n * order (the order the Items mounted, which is document order for a list rendered top to bottom),\n * not in the order they were opened; unregistered values keep their insertion order at the end.\n */\nfunction fromSet(set: OpenSet, exclusive: boolean, order: readonly string[]): AccordionValue {\n if (exclusive) {\n const [first] = set;\n return first ?? null;\n }\n const index = new Map(order.map((v, i) => [v, i] as const));\n return [...set].sort((a, b) => (index.get(a) ?? Infinity) - (index.get(b) ?? Infinity));\n}\n\n/** The optimistic reducer of design 4.1: add/remove one item, or replace when exclusive. */\nfunction reduce(set: OpenSet, item: string, open: boolean, exclusive: boolean): OpenSet {\n if (!open) {\n if (!set.has(item)) return set;\n const next = new Set(set);\n next.delete(item);\n return next;\n }\n if (exclusive) return new Set([item]);\n return set.has(item) ? set : new Set(set).add(item);\n}\n\nfunction setsEqual(a: OpenSet, b: OpenSet): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction byDocumentOrder<T extends Node>(a: T, b: T): number {\n if (a === b) return 0;\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n}\n\nconst NAV_KEYS = new Set([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"]);\n\n/** Writes `open` to the DOM; it fires `toggle`, whose handler sees DOM === committed and returns. */\nfunction applyOpen(element: HTMLDetailsElement, open: boolean): void {\n if (element.open !== open) element.open = open;\n}\n\nfunction isEnabledTrigger(el: HTMLElement): boolean {\n return el.getAttribute(\"aria-disabled\") !== \"true\";\n}\n\n/**\n * Picks the trigger to focus for an arrow/Home/End key: `sorted` is every registered trigger in\n * document order; disabled ones are skipped; arrows wrap. Works when `current` is itself disabled\n * (disabled triggers stay focusable).\n */\nfunction nextTrigger(\n sorted: readonly HTMLElement[],\n current: HTMLElement,\n key: string,\n): HTMLElement | undefined {\n const enabled = sorted.filter(isEnabledTrigger);\n if (enabled.length === 0) return undefined;\n if (key === \"Home\") return enabled[0];\n if (key === \"End\") return enabled[enabled.length - 1];\n const position = sorted.indexOf(current);\n if (key === \"ArrowDown\") {\n return enabled.find((el) => sorted.indexOf(el) > position) ?? enabled[0];\n }\n return enabled.findLast((el) => sorted.indexOf(el) < position) ?? enabled[enabled.length - 1];\n}\n\n/**\n * The accordion container: a `div` that owns the open state, the transition, the context the\n * other parts read, and ArrowUp/ArrowDown/Home/End navigation between triggers.\n */\nexport function AccordionRoot(props: AccordionRootProps) {\n const {\n exclusive = false,\n value,\n defaultValue,\n valueChangeAction,\n disabled = false,\n experimental_interpolateSize = false,\n className,\n style,\n children,\n render,\n ...rest\n } = props;\n\n // Mode is decided once, from the first render's props (design 12).\n const isLibraryNow =\n value !== undefined || defaultValue !== undefined || valueChangeAction !== undefined;\n const [mode] = useState<AccordionMode>(isLibraryNow ? \"library\" : \"static\");\n useDevWarning(\n (mode === \"library\") !== isLibraryNow,\n \"Accordion.Root: the mode changed after mount (`value`, `defaultValue` and `valueChangeAction` \" +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Both prop variants are narrowed by `exclusive` at the call site; internally one signature.\n const action = valueChangeAction as\n | ChangeAction<AccordionValue, AccordionValueChangeDetails>\n | undefined;\n\n // Keep the committed set's identity stable while the consumer's `value` is, so `useOptimistic`\n // only re-bases when the controlled value really changed.\n const controlledSet = useMemo(() => (value === undefined ? undefined : toSet(value)), [value]);\n\n const items = useRef(new Map<HTMLDetailsElement, string>());\n const triggers = useRef(new Set<HTMLElement>());\n const registeredValues = () => [...items.current.values()];\n\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n OpenSet,\n AccordionValueChangeDetails\n >({\n controlled: controlledSet,\n defaultValue: defaultValue === undefined ? undefined : toSet(defaultValue),\n initialValue: EMPTY,\n action:\n action && ((next, details) => action(fromSet(next, exclusive, registeredValues()), details)),\n transitionTypes: (_next, _prev, details) =>\n transitionTypes(\"accordion\", details.open ? \"expand\" : \"collapse\"),\n // One item per dispatch, re-based on the committed set, except the mount reconciliation\n // (reason \"platform\", no event), which folds every pre-hydration click into one `next`.\n reducer: (prev, next, details) =>\n details.reason === \"platform\" && details.event === undefined\n ? next\n : reduce(prev, details.item, details.open, exclusive),\n warn: { component: \"Accordion.Root\", prop: \"value\" },\n });\n\n const registerItem = useCallback((itemValue: string, element: HTMLDetailsElement) => {\n items.current.set(element, itemValue);\n return () => {\n items.current.delete(element);\n };\n }, []);\n\n const registerTrigger = useCallback((element: HTMLElement) => {\n triggers.current.add(element);\n return () => {\n triggers.current.delete(element);\n };\n }, []);\n\n const dispatchChange = useCallback(\n (\n next: OpenSet,\n item: string,\n open: boolean,\n reason: AccordionChangeReason,\n event: Event | undefined,\n ): void => {\n dispatch(next, createActionDetails(event, { reason, item, open }));\n },\n [dispatch],\n );\n\n const dispatchFromTrigger = useCallback<AccordionRootContextValue[\"dispatch\"]>(\n (item, open, event) => {\n // `next` comes from the optimistic set: what the user sees and intends (design 2.2).\n dispatchChange(reduce(optimistic, item, open, exclusive), item, open, \"activate\", event);\n },\n // `dispatchChange` closes over the hook's `dispatch`, which is new on every render.\n [dispatchChange, optimistic, exclusive],\n );\n\n const dispatchFromDom = useCallback<AccordionRootContextValue[\"dispatchFromDom\"]>(\n (item, open, event) => {\n // The browser flipped one item (design 2.3). `next` builds on what the user already\n // intends (the latest in-flight dispatch, else the optimistic set), never on the DOM: a\n // pending optimistic open would otherwise be lost to latest-wins. Exclusive mode therefore\n // lets the toggled item win when it opened (walkthrough 9.6).\n const base = latestInFlight()?.next ?? optimistic;\n const next = reduce(base, item, open, exclusive);\n if (setsEqual(next, base)) return;\n dispatchChange(next, item, open, \"platform\", event);\n },\n [dispatchChange, latestInFlight, optimistic, exclusive],\n );\n\n const isLibrary = mode === \"library\";\n\n // Mount reconciliation (design 2.3): clicks before hydration left some `<details open>` out of\n // step with committed state. Every mismatch is folded into ONE dispatch with reason \"platform\"\n // and no event; disabled items are simply put back. Runs once per mount; the StrictMode re-run\n // is deduplicated against the dispatch just made.\n const reconcileOnMount = useEffectEvent(() => {\n const base = latestInFlight()?.next ?? optimistic;\n const mismatches: { item: string; open: boolean }[] = [];\n for (const [element, item] of items.current) {\n const want = committed.has(item);\n if (element.open === want) continue;\n if (element.hasAttribute(\"data-disabled\")) applyOpen(element, want);\n else mismatches.push({ item, open: element.open });\n }\n if (mismatches.length === 0) return;\n // Exclusive mode: the first opened item in registration order wins (the others are re-closed\n // by the re-apply effect); otherwise fold every flip onto the base.\n const opened = exclusive ? mismatches.find((m) => m.open) : undefined;\n const chosen = opened ?? mismatches[0]!;\n const next = opened\n ? new Set([opened.item])\n : mismatches.reduce((set, m) => reduce(set, m.item, m.open, exclusive), base);\n if (setsEqual(next, base)) return;\n dispatchChange(next, chosen.item, chosen.open, \"platform\", undefined);\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n // Re-apply committed state to the DOM once nothing is in flight: closes what find-in-page\n // revealed and the parent refused, or what a rejected action left open. Writing `open` fires\n // `toggle`, whose handler sees DOM === committed and does nothing.\n useLayoutEffect(() => {\n if (!isLibrary || pending || latestInFlight() !== null) return;\n for (const [element, item] of items.current) applyOpen(element, committed.has(item));\n }, [isLibrary, pending, committed, latestInFlight]);\n\n function onKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (!NAV_KEYS.has(event.key)) return;\n const target = event.target;\n if (!(target instanceof HTMLElement) || !triggers.current.has(target)) return;\n const sorted = [...triggers.current].sort(byDocumentOrder);\n const next = nextTrigger(sorted, target, event.key);\n if (next === undefined) return;\n event.preventDefault();\n // A nested Root handles its own triggers; the outer one must not move focus again.\n event.stopPropagation();\n next.focus();\n }\n\n const id = useId();\n const name = !isLibrary && exclusive ? id : undefined;\n const state: AccordionRootState = { pending: isLibrary && pending, disabled };\n const rootPending = state.pending;\n const contextCommitted = isLibrary ? committed : EMPTY;\n const contextOptimistic = isLibrary ? optimistic : EMPTY;\n\n // Not memoised, deliberately: `dispatchChange` closes over the hook's `dispatch`, which is a\n // new function on every render of the Root, so a `useMemo` here (design 12) could never reuse\n // its value and the React Compiler lint flags it as unpreservable. Parts re-render with the\n // Root, which is what happens today anyway whenever its state changes.\n const context: AccordionRootContextValue = {\n mode,\n exclusive,\n name,\n disabled,\n pending: rootPending,\n committed: contextCommitted,\n optimistic: contextOptimistic,\n dispatch: dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n };\n\n const resolvedStyle = resolveStateValue(style, state);\n const finalStyle: CSSProperties | undefined = experimental_interpolateSize\n ? { interpolateSize: \"allow-keywords\", ...resolvedStyle }\n : resolvedStyle;\n\n const element = useRender<ComponentPropsWithRef<\"div\">, AccordionRootState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style: finalStyle,\n children,\n props: rest,\n // Library-controlled layer: wins over a `render` element's props (conventions 1.2).\n libraryProps: {\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(disabled),\n onKeyDown,\n },\n });\n\n return <AccordionRootContext value={context}>{element}</AccordionRootContext>;\n}\n\nAccordionRoot.displayName = \"AccordionRoot\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accordion-root.mjs","names":[],"sources":["../../src/accordion/accordion-root.tsx"],"sourcesContent":["\"use client\";\nimport {\n useCallback,\n useEffectEvent,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, KeyboardEvent, 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 { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useLatest } from \"../internal/use-latest.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { AccordionRootContext } from \"./accordion-context.js\";\nimport type {\n AccordionChangeReason,\n AccordionMode,\n AccordionRootContextValue,\n} from \"./accordion-context.js\";\n\nexport interface AccordionRootState {\n /** An action dispatched through this Accordion is in flight (library mode only). */\n readonly pending: boolean;\n /** The Root's `disabled` prop. */\n readonly disabled: boolean;\n}\n\n/** Details passed as the second argument of `valueChangeAction`. */\nexport interface AccordionValueChangeDetails extends ActionDetails {\n /**\n * `\"activate\"`: the user activated a Trigger. `\"platform\"`: the browser changed `open` itself\n * (find-in-page, a text fragment, a click before hydration); `event` is then the `toggle`\n * event, or `undefined` when the mismatch was found on mount.\n */\n readonly reason: AccordionChangeReason;\n /** The item whose open state changed. */\n readonly item: string;\n /** That item's new open state. */\n readonly open: boolean;\n}\n\ninterface AccordionRootBaseProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n // `defaultValue` is also a native HTMLAttributes member; the Accordion's own definition wins.\n \"onKeyDown\" | \"defaultValue\" | \"className\" | \"style\" | \"children\"\n> {\n /** Disables every item: triggers render `aria-disabled` and never dispatch. Default `false`. */\n disabled?: boolean;\n /**\n * Renders `interpolate-size: allow-keywords` on the root so `::details-content` can transition\n * `height` to `auto`. Chromium-only polish today; use the `grid-template-rows` form for a\n * cross-engine animation. Merged with `style`; the consumer's declaration wins.\n */\n experimental_interpolateSize?: boolean;\n className?: StateValue<string | undefined, AccordionRootState>;\n style?: StateValue<CSSProperties | undefined, AccordionRootState>;\n children?: StateValue<ReactNode, AccordionRootState>;\n render?: RenderProp<ComponentPropsWithRef<\"div\">, AccordionRootState>;\n}\n\nexport interface AccordionRootMultipleProps extends AccordionRootBaseProps {\n /** Any number of items may be open. */\n exclusive?: false;\n /** Controlled open items. `undefined` means uncontrolled; `[]` means none open. */\n value?: string[];\n /** Initial open items in uncontrolled library mode. */\n defaultValue?: string[];\n /**\n * Called with the next open items on every change. Runs inside the Root's transition; may be\n * sync or async (also a Server Function). Rejections reach the nearest Error Boundary.\n *\n * May also be called with `details.reason === \"platform\"` when the browser opened an item\n * itself (find-in-page, a click before hydration); committed state is re-applied to the DOM\n * once the action settles.\n *\n * A `setState` placed after your own `await` is not a Transition update; wrap it in\n * `startTransition` (React limitation).\n */\n valueChangeAction?: ChangeAction<string[], AccordionValueChangeDetails>;\n}\n\nexport interface AccordionRootExclusiveProps extends AccordionRootBaseProps {\n /**\n * At most one item open. JS-free mode: renders the same `name` on every `<details>` (the\n * platform's exclusive group). Library mode: the value is a single item or `null`.\n */\n exclusive: true;\n /** Controlled open item. `undefined` means uncontrolled; `null` means none open. */\n value?: string | null;\n /** Initial open item in uncontrolled library mode. */\n defaultValue?: string | null;\n /** See {@link AccordionRootMultipleProps.valueChangeAction}. */\n valueChangeAction?: ChangeAction<string | null, AccordionValueChangeDetails>;\n}\n\n/**\n * Props of `Accordion.Root`, discriminated on `exclusive`.\n *\n * Without `value`, `defaultValue` and `valueChangeAction` the Accordion runs in **JS-free mode**:\n * the browser owns each `<details open>`, nothing runs on toggle, and `exclusive` renders `name`.\n * With any of them it runs in **library mode**: the summary click is intercepted, `open` is driven\n * by committed state inside a transition, and triggers show optimistic `data-open`.\n */\nexport type AccordionRootProps = AccordionRootMultipleProps | AccordionRootExclusiveProps;\n\ntype AccordionValue = string[] | string | null;\ntype OpenSet = ReadonlySet<string>;\n\nconst EMPTY: OpenSet = new Set();\n\nfunction toSet(value: AccordionValue): OpenSet {\n if (value === null) return EMPTY;\n return typeof value === \"string\" ? new Set([value]) : new Set(value);\n}\n\n/**\n * Converts the open set back to the public value. Multiple mode lists the items in registration\n * order (the order the Items mounted, which is document order for a list rendered top to bottom),\n * not in the order they were opened; unregistered values keep their insertion order at the end.\n */\nfunction fromSet(set: OpenSet, exclusive: boolean, order: readonly string[]): AccordionValue {\n if (exclusive) {\n const [first] = set;\n return first ?? null;\n }\n const index = new Map(order.map((v, i) => [v, i] as const));\n return [...set].sort((a, b) => (index.get(a) ?? Infinity) - (index.get(b) ?? Infinity));\n}\n\n/** The optimistic reducer of design 4.1: add/remove one item, or replace when exclusive. */\nfunction reduce(set: OpenSet, item: string, open: boolean, exclusive: boolean): OpenSet {\n if (!open) {\n if (!set.has(item)) return set;\n const next = new Set(set);\n next.delete(item);\n return next;\n }\n if (exclusive) return new Set([item]);\n return set.has(item) ? set : new Set(set).add(item);\n}\n\nfunction setsEqual(a: OpenSet, b: OpenSet): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction byDocumentOrder<T extends Node>(a: T, b: T): number {\n if (a === b) return 0;\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;\n}\n\nconst NAV_KEYS = new Set([\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"]);\n\n/** Writes `open` to the DOM; it fires `toggle`, whose handler sees DOM === committed and returns. */\nfunction applyOpen(element: HTMLDetailsElement, open: boolean): void {\n if (element.open !== open) element.open = open;\n}\n\nfunction isEnabledTrigger(el: HTMLElement): boolean {\n return el.getAttribute(\"aria-disabled\") !== \"true\";\n}\n\n/**\n * Picks the trigger to focus for an arrow/Home/End key: `sorted` is every registered trigger in\n * document order; disabled ones are skipped; arrows wrap. Works when `current` is itself disabled\n * (disabled triggers stay focusable).\n */\nfunction nextTrigger(\n sorted: readonly HTMLElement[],\n current: HTMLElement,\n key: string,\n): HTMLElement | undefined {\n const enabled = sorted.filter(isEnabledTrigger);\n if (enabled.length === 0) return undefined;\n if (key === \"Home\") return enabled[0];\n if (key === \"End\") return enabled[enabled.length - 1];\n const position = sorted.indexOf(current);\n if (key === \"ArrowDown\") {\n return enabled.find((el) => sorted.indexOf(el) > position) ?? enabled[0];\n }\n return enabled.findLast((el) => sorted.indexOf(el) < position) ?? enabled[enabled.length - 1];\n}\n\n/**\n * The accordion container: a `div` that owns the open state, the transition, the context the\n * other parts read, and ArrowUp/ArrowDown/Home/End navigation between triggers.\n */\nexport function AccordionRoot(props: AccordionRootProps) {\n const {\n exclusive = false,\n value,\n defaultValue,\n valueChangeAction,\n disabled = false,\n experimental_interpolateSize = false,\n className,\n style,\n children,\n render,\n ...rest\n } = props;\n\n // Mode is decided once, from the first render's props (design 12).\n const isLibraryNow =\n value !== undefined || defaultValue !== undefined || valueChangeAction !== undefined;\n const [mode] = useState<AccordionMode>(isLibraryNow ? \"library\" : \"static\");\n useDevWarning(\n (mode === \"library\") !== isLibraryNow,\n \"Accordion.Root: the mode changed after mount (`value`, `defaultValue` and `valueChangeAction` \" +\n \"were all absent on one render and present on another). Switching between JS-free and \" +\n \"library mode is not supported.\",\n );\n\n // Both prop variants are narrowed by `exclusive` at the call site; internally one signature.\n const action = valueChangeAction as\n | ChangeAction<AccordionValue, AccordionValueChangeDetails>\n | undefined;\n\n // Keep the committed set's identity stable while the consumer's `value` has the same contents,\n // so `useOptimistic` only re-bases, and the context only changes, when the controlled value\n // really changed. Keyed on the contents, not the array: an inline `value={[\"a\"]}` is a new\n // array on every render. (Order-sensitive; a reordered array only costs one new set.)\n const controlledKey = value === undefined ? undefined : JSON.stringify(value);\n const controlledSet = useMemo(\n () =>\n controlledKey === undefined ? undefined : toSet(JSON.parse(controlledKey) as AccordionValue),\n [controlledKey],\n );\n\n const items = useRef(new Map<HTMLDetailsElement, string>());\n const triggers = useRef(new Set<HTMLElement>());\n const registeredValues = () => [...items.current.values()];\n\n const { committed, optimistic, pending, dispatch, latestInFlight } = useControllableAction<\n OpenSet,\n AccordionValueChangeDetails\n >({\n controlled: controlledSet,\n defaultValue: defaultValue === undefined ? undefined : toSet(defaultValue),\n initialValue: EMPTY,\n action:\n action && ((next, details) => action(fromSet(next, exclusive, registeredValues()), details)),\n transitionTypes: (_next, _prev, details) =>\n transitionTypes(\"accordion\", details.open ? \"expand\" : \"collapse\"),\n // One item per dispatch, re-based on the committed set, except the mount reconciliation\n // (reason \"platform\", no event), which folds every pre-hydration click into one `next`.\n reducer: (prev, next, details) =>\n details.reason === \"platform\" && details.event === undefined\n ? next\n : reduce(prev, details.item, details.open, exclusive),\n warn: { component: \"Accordion.Root\", prop: \"value\" },\n });\n\n const registerItem = useCallback((itemValue: string, element: HTMLDetailsElement) => {\n items.current.set(element, itemValue);\n return () => {\n items.current.delete(element);\n };\n }, []);\n\n const registerTrigger = useCallback((element: HTMLElement) => {\n triggers.current.add(element);\n return () => {\n triggers.current.delete(element);\n };\n }, []);\n\n const dispatchChange = useCallback(\n (\n next: OpenSet,\n item: string,\n open: boolean,\n reason: AccordionChangeReason,\n event: Event | undefined,\n ): void => {\n dispatch(next, createActionDetails(event, { reason, item, open }));\n },\n [dispatch],\n );\n\n // The two dispatchers below read the last committed optimistic set through `latest`, so they\n // keep one identity and the Items' contexts are reused across open-state changes (#48): a\n // handler only ever runs against the committed UI, so this is the set it closed over before.\n const latest = useLatest({ optimistic, exclusive });\n\n const dispatchFromTrigger = useCallback<AccordionRootContextValue[\"dispatch\"]>(\n (item, open, event) => {\n const { optimistic, exclusive } = latest.current;\n // `next` comes from the optimistic set: what the user sees and intends (design 2.2).\n dispatchChange(reduce(optimistic, item, open, exclusive), item, open, \"activate\", event);\n },\n [dispatchChange, latest],\n );\n\n const dispatchFromDom = useCallback<AccordionRootContextValue[\"dispatchFromDom\"]>(\n (item, open, event) => {\n const { optimistic, exclusive } = latest.current;\n // The browser flipped one item (design 2.3). `next` builds on what the user already\n // intends (the latest in-flight dispatch, else the optimistic set), never on the DOM: a\n // pending optimistic open would otherwise be lost to latest-wins. Exclusive mode therefore\n // lets the toggled item win when it opened (walkthrough 9.6).\n const base = latestInFlight()?.next ?? optimistic;\n const next = reduce(base, item, open, exclusive);\n if (setsEqual(next, base)) return;\n dispatchChange(next, item, open, \"platform\", event);\n },\n [dispatchChange, latestInFlight, latest],\n );\n\n const isLibrary = mode === \"library\";\n\n // Mount reconciliation (design 2.3): clicks before hydration left some `<details open>` out of\n // step with committed state. Every mismatch is folded into ONE dispatch with reason \"platform\"\n // and no event; disabled items are simply put back. Runs once per mount; the StrictMode re-run\n // is deduplicated against the dispatch just made.\n const reconcileOnMount = useEffectEvent(() => {\n const base = latestInFlight()?.next ?? optimistic;\n const mismatches: { item: string; open: boolean }[] = [];\n for (const [element, item] of items.current) {\n const want = committed.has(item);\n if (element.open === want) continue;\n if (element.hasAttribute(\"data-disabled\")) applyOpen(element, want);\n else mismatches.push({ item, open: element.open });\n }\n if (mismatches.length === 0) return;\n // Exclusive mode: the first opened item in registration order wins (the others are re-closed\n // by the re-apply effect); otherwise fold every flip onto the base.\n const opened = exclusive ? mismatches.find((m) => m.open) : undefined;\n const chosen = opened ?? mismatches[0]!;\n const next = opened\n ? new Set([opened.item])\n : mismatches.reduce((set, m) => reduce(set, m.item, m.open, exclusive), base);\n if (setsEqual(next, base)) return;\n dispatchChange(next, chosen.item, chosen.open, \"platform\", undefined);\n });\n useLayoutEffect(() => {\n if (isLibrary) reconcileOnMount();\n }, [isLibrary]);\n\n // Re-apply committed state to the DOM once nothing is in flight: closes what find-in-page\n // revealed and the parent refused, or what a rejected action left open. Writing `open` fires\n // `toggle`, whose handler sees DOM === committed and does nothing.\n useLayoutEffect(() => {\n if (!isLibrary || pending || latestInFlight() !== null) return;\n for (const [element, item] of items.current) applyOpen(element, committed.has(item));\n }, [isLibrary, pending, committed, latestInFlight]);\n\n function onKeyDown(event: KeyboardEvent<HTMLDivElement>): void {\n if (!NAV_KEYS.has(event.key)) return;\n const target = event.target;\n if (!(target instanceof HTMLElement) || !triggers.current.has(target)) return;\n const sorted = [...triggers.current].sort(byDocumentOrder);\n const next = nextTrigger(sorted, target, event.key);\n if (next === undefined) return;\n event.preventDefault();\n // A nested Root handles its own triggers; the outer one must not move focus again.\n event.stopPropagation();\n next.focus();\n }\n\n const id = useId();\n const name = !isLibrary && exclusive ? id : undefined;\n const rootPending = isLibrary && pending;\n const state: AccordionRootState = { pending: rootPending, disabled };\n const contextCommitted = isLibrary ? committed : EMPTY;\n const contextOptimistic = isLibrary ? optimistic : EMPTY;\n\n // Memoised (design 12, #48): the dispatchers are stable, so this changes only with the open\n // sets, pending and the props below.\n const context = useMemo<AccordionRootContextValue>(\n () => ({\n mode,\n exclusive,\n name,\n disabled,\n pending: rootPending,\n committed: contextCommitted,\n optimistic: contextOptimistic,\n dispatch: dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n }),\n [\n mode,\n exclusive,\n name,\n disabled,\n rootPending,\n contextCommitted,\n contextOptimistic,\n dispatchFromTrigger,\n dispatchFromDom,\n registerItem,\n registerTrigger,\n ],\n );\n\n const resolvedStyle = resolveStateValue(style, state);\n const finalStyle: CSSProperties | undefined = experimental_interpolateSize\n ? { interpolateSize: \"allow-keywords\", ...resolvedStyle }\n : resolvedStyle;\n\n const element = useRender<ComponentPropsWithRef<\"div\">, AccordionRootState>({\n defaultElement: \"div\",\n render,\n state,\n className,\n style: finalStyle,\n children,\n props: rest,\n // Library-controlled layer: wins over a `render` element's props (conventions 1.2).\n libraryProps: {\n \"data-pending\": dataAttr(state.pending),\n \"data-disabled\": dataAttr(disabled),\n onKeyDown,\n },\n });\n\n return <AccordionRootContext value={context}>{element}</AccordionRootContext>;\n}\n\nAccordionRoot.displayName = \"AccordionRoot\";\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
4
|
import { useRender } from "../internal/use-render.mjs";
|
|
5
5
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
6
|
-
import { AccordionItemContext
|
|
6
|
+
import { AccordionItemContext } from "./accordion-context.mjs";
|
|
7
7
|
import { use, useCallback } from "react";
|
|
8
8
|
//#region src/accordion/accordion-trigger.tsx
|
|
9
9
|
const isDev = process.env.NODE_ENV !== "production";
|
|
@@ -26,12 +26,11 @@ function nestedInteractive(target, summary) {
|
|
|
26
26
|
* on a summary, so assistive technology hears the committed state (from `<details open>`).
|
|
27
27
|
*/
|
|
28
28
|
function AccordionTrigger({ transition, className, style, children, render, ...rest }) {
|
|
29
|
-
const root = use(AccordionRootContext);
|
|
30
29
|
const item = use(AccordionItemContext);
|
|
31
30
|
if (item === null) throw new Error("Accordion.Trigger must be rendered inside an Accordion.Item.");
|
|
32
|
-
const isLibrary =
|
|
31
|
+
const isLibrary = item.library;
|
|
33
32
|
useDevWarning(!isLibrary && transition !== void 0, "Accordion.Trigger: `transition` has no effect in JS-free mode; the browser toggles the item outside any React transition.");
|
|
34
|
-
const registerTrigger =
|
|
33
|
+
const registerTrigger = item.registerTrigger;
|
|
35
34
|
const libraryRef = useCallback((element) => {
|
|
36
35
|
if (isDev && element.tagName !== "SUMMARY") console.error(`Accordion.Trigger: expected to render a <summary> element but got <${element.tagName.toLowerCase()}>. Only a <summary> toggles its parent <details>.`);
|
|
37
36
|
const unregister = registerTrigger?.(element);
|
|
@@ -53,7 +52,7 @@ function AccordionTrigger({ transition, className, style, children, render, ...r
|
|
|
53
52
|
}
|
|
54
53
|
event.preventDefault();
|
|
55
54
|
if (item.disabled || !isLibrary) return;
|
|
56
|
-
|
|
55
|
+
item.dispatch?.(item.value, !item.optimisticOpen, event.nativeEvent);
|
|
57
56
|
} : void 0;
|
|
58
57
|
const element = useRender({
|
|
59
58
|
defaultElement: "summary",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accordion-trigger.mjs","names":[],"sources":["../../src/accordion/accordion-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext
|
|
1
|
+
{"version":3,"file":"accordion-trigger.mjs","names":[],"sources":["../../src/accordion/accordion-trigger.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback } from \"react\";\nimport type {\n ComponentPropsWithRef,\n CSSProperties,\n MouseEvent,\n ReactNode,\n ViewTransitionClass,\n} from \"react\";\nimport { useTransitionBoundary } from \"../internal/transition-boundary.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { useRender } from \"../internal/use-render.js\";\nimport { AccordionItemContext } from \"./accordion-context.js\";\nimport type { AccordionItemState } from \"./accordion-item.js\";\n\n/** The trigger is the item's control; it renders the item's state object. */\nexport type AccordionTriggerState = AccordionItemState;\n\nexport interface AccordionTriggerProps extends Omit<\n ComponentPropsWithRef<\"summary\">,\n \"onClick\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Library mode only. Forwarded as `default` to the `<ViewTransition>` around the `<summary>`.\n * Animates only the commit that ends an action (for example a spinner rendered from `pending`\n * disappearing); the optimistic `data-open` flip is a CSS job.\n *\n * Static: without `transition` the part renders no `<ViewTransition>`; changing it between\n * `undefined` and a value remounts the element. Any value, `\"none\"` included, makes a mount or\n * `<Activity>` reveal of this part inside a transition start a document view transition: the\n * page cross-fades for about 250 ms and the next transition commit waits for it (issue #35).\n */\n transition?: ViewTransitionClass;\n className?: StateValue<string | undefined, AccordionTriggerState>;\n style?: StateValue<CSSProperties | undefined, AccordionTriggerState>;\n /** Rendered from optimistic state; must not suspend. A heading inside is recommended. */\n children?: StateValue<ReactNode, AccordionTriggerState>;\n /** Must render a `<summary>` element. */\n render?: RenderProp<ComponentPropsWithRef<\"summary\">, AccordionTriggerState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nconst NESTED_INTERACTIVE =\n \"a[href], button, label, input, select, textarea, [contenteditable]:not([contenteditable='false'])\";\n\n/**\n * Nested controls with an activation behaviour of their own. The browser runs theirs instead of\n * the summary's (no toggle), so their click must keep its default: navigation, check, submit.\n */\nconst SELF_ACTIVATING =\n \"a[href], button, label, input:is([type='button'], [type='submit'], [type='reset'], \" +\n \"[type='image'], [type='checkbox'], [type='radio'], [type='file'], [type='color'])\";\n\n/** The interactive descendant of the summary that was clicked (a link in the heading), if any. */\nfunction nestedInteractive(target: EventTarget | null, summary: HTMLElement): Element | null {\n if (!(target instanceof Element) || target === summary) return null;\n const interactive = target.closest(NESTED_INTERACTIVE);\n return interactive !== null && interactive !== summary && summary.contains(interactive)\n ? interactive\n : null;\n}\n\n/**\n * The item's disclosure control: the `<summary>`, which must be the first child of the Item. In\n * library mode the click is intercepted and dispatched through the Root's transition; the\n * element shows optimistic `data-open` and `data-pending`. The platform forbids `aria-expanded`\n * on a summary, so assistive technology hears the committed state (from `<details open>`).\n */\nexport function AccordionTrigger({\n transition,\n className,\n style,\n children,\n render,\n ...rest\n}: AccordionTriggerProps) {\n // Only the Item's context (memoised per item, #48), which carries what is needed of the Root's.\n const item = use(AccordionItemContext);\n if (item === null) {\n throw new Error(\"Accordion.Trigger must be rendered inside an Accordion.Item.\");\n }\n const isLibrary = item.library;\n\n useDevWarning(\n !isLibrary && transition !== undefined,\n \"Accordion.Trigger: `transition` has no effect in JS-free mode; the browser toggles the item \" +\n \"outside any React transition.\",\n );\n\n const registerTrigger = item.registerTrigger;\n const libraryRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && element.tagName !== \"SUMMARY\") {\n console.error(\n `Accordion.Trigger: expected to render a <summary> element but got <${element.tagName.toLowerCase()}>. ` +\n \"Only a <summary> toggles its parent <details>.\",\n );\n }\n const unregister = registerTrigger?.(element);\n // Always return a cleanup so the merged ref never calls back with `null`.\n return () => {\n unregister?.();\n };\n },\n [registerTrigger],\n );\n\n const state: AccordionTriggerState = {\n value: item.value,\n open: item.optimisticOpen,\n pending: item.pending,\n disabled: item.disabled,\n };\n\n // JS-free mode attaches no click handler unless the item is disabled (design 2.1 / section 6).\n const onClick =\n isLibrary || item.disabled\n ? (event: MouseEvent<HTMLElement>) => {\n const nested = nestedInteractive(event.target, event.currentTarget);\n if (nested !== null) {\n // A nested control never toggles. Controls without their own activation behaviour\n // (text input, select, textarea, contenteditable) would let the click fall through to\n // the summary's toggle, so cancel it; focus and pickers happen on mousedown and are\n // unaffected. Self-activating controls already pre-empt the summary's behaviour.\n if (!nested.matches(SELF_ACTIVATING)) event.preventDefault();\n return;\n }\n // Cancelling the click is what keeps the browser from touching `open`.\n event.preventDefault();\n if (item.disabled || !isLibrary) return;\n item.dispatch?.(item.value, !item.optimisticOpen, event.nativeEvent);\n }\n : undefined;\n\n const element = useRender<ComponentPropsWithRef<\"summary\">, AccordionTriggerState>({\n defaultElement: \"summary\",\n render,\n state,\n className,\n style,\n children,\n ref: libraryRef,\n props: rest,\n // Library-controlled layer: wins over a `render` element's props (conventions 1.2).\n libraryProps: {\n \"aria-disabled\": item.disabled ? true : undefined,\n \"data-open\": dataAttr(item.optimisticOpen === true),\n \"data-pending\": dataAttr(item.pending),\n \"data-disabled\": dataAttr(item.disabled),\n // Only owned when installed, so a render element keeps its own `onClick` in JS-free mode.\n ...(onClick && { onClick }),\n },\n });\n\n return useTransitionBoundary(\"Accordion.Trigger\", isLibrary ? transition : undefined, element);\n}\n\nAccordionTrigger.displayName = \"AccordionTrigger\";\n"],"mappings":";;;;;;;;AA2CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;AAEvC,MAAM,qBACJ;;;;;AAMF,MAAM,kBACJ;;AAIF,SAAS,kBAAkB,QAA4B,SAAsC;CAC3F,IAAI,EAAE,kBAAkB,YAAY,WAAW,SAAS,OAAO;CAC/D,MAAM,cAAc,OAAO,QAAQ,kBAAkB;CACrD,OAAO,gBAAgB,QAAQ,gBAAgB,WAAW,QAAQ,SAAS,WAAW,IAClF,cACA;AACN;;;;;;;AAQA,SAAgB,iBAAiB,EAC/B,YACA,WACA,OACA,UACA,QACA,GAAG,QACqB;CAExB,MAAM,OAAO,IAAI,oBAAoB;CACrC,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,8DAA8D;CAEhF,MAAM,YAAY,KAAK;CAEvB,cACE,CAAC,aAAa,eAAe,KAAA,GAC7B,2HAEF;CAEA,MAAM,kBAAkB,KAAK;CAC7B,MAAM,aAAa,aAChB,YAAyB;EACxB,IAAI,SAAS,QAAQ,YAAY,WAC/B,QAAQ,MACN,sEAAsE,QAAQ,QAAQ,YAAY,EAAE,kDAEtG;EAEF,MAAM,aAAa,kBAAkB,OAAO;EAE5C,aAAa;GACX,aAAa;EACf;CACF,GACA,CAAC,eAAe,CAClB;CAEA,MAAM,QAA+B;EACnC,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,SAAS,KAAK;EACd,UAAU,KAAK;CACjB;CAGA,MAAM,UACJ,aAAa,KAAK,YACb,UAAmC;EAClC,MAAM,SAAS,kBAAkB,MAAM,QAAQ,MAAM,aAAa;EAClE,IAAI,WAAW,MAAM;GAKnB,IAAI,CAAC,OAAO,QAAQ,eAAe,GAAG,MAAM,eAAe;GAC3D;EACF;EAEA,MAAM,eAAe;EACrB,IAAI,KAAK,YAAY,CAAC,WAAW;EACjC,KAAK,WAAW,KAAK,OAAO,CAAC,KAAK,gBAAgB,MAAM,WAAW;CACrE,IACA,KAAA;CAEN,MAAM,UAAU,UAAmE;EACjF,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,KAAK;EACL,OAAO;EAEP,cAAc;GACZ,iBAAiB,KAAK,WAAW,OAAO,KAAA;GACxC,aAAa,SAAS,KAAK,mBAAmB,IAAI;GAClD,gBAAgB,SAAS,KAAK,OAAO;GACrC,iBAAiB,SAAS,KAAK,QAAQ;GAEvC,GAAI,WAAW,EAAE,QAAQ;EAC3B;CACF,CAAC;CAED,OAAO,sBAAsB,qBAAqB,YAAY,aAAa,KAAA,GAAW,OAAO;AAC/F;AAEA,iBAAiB,cAAc"}
|
|
@@ -11,7 +11,7 @@ export interface CheckboxState {
|
|
|
11
11
|
readonly indeterminate: boolean;
|
|
12
12
|
/** An action dispatched by this Checkbox is in flight (including the resulting render). */
|
|
13
13
|
readonly pending: boolean;
|
|
14
|
-
/** The `disabled` prop. */
|
|
14
|
+
/** The `disabled` prop (or the enclosing `Field.Root`'s). */
|
|
15
15
|
readonly disabled: boolean;
|
|
16
16
|
}
|
|
17
17
|
/** Details passed as the second argument of `checkedChangeAction`. */
|
|
@@ -79,12 +79,14 @@ export interface CheckboxProps extends Omit<ComponentPropsWithRef<"input">, "typ
|
|
|
79
79
|
*
|
|
80
80
|
* Owned keys (a `render` element's value is overridden): `type`, `checked`, `defaultChecked`,
|
|
81
81
|
* `indeterminate` (stripped), `aria-checked` (always removed), `disabled`, `data-checked`,
|
|
82
|
-
* `data-indeterminate`, `data-pending`, `data-disabled
|
|
82
|
+
* `data-indeterminate`, `data-pending`, `data-disabled`; inside a `Field.Root` also `id`,
|
|
83
|
+
* `name`, `required`, `aria-invalid`, `aria-describedby` (token-merged) and `data-invalid`, and
|
|
84
|
+
* `disabled` / `name` / `required` default from the Field. `role` passes through. Event handlers
|
|
83
85
|
* are merged rather than owned: a `render` element's own `onChange` (or `onClick`) runs first,
|
|
84
86
|
* then the library's; an `onClick` that calls `preventDefault()` cancels the change (nothing is
|
|
85
87
|
* dispatched).
|
|
86
88
|
*/
|
|
87
|
-
export declare function Checkbox({ checked, defaultChecked, indeterminate, checkedChangeAction, disabled, transition, className, style, render, ...rest }: CheckboxProps): import("react").ReactNode;
|
|
89
|
+
export declare function Checkbox({ checked, defaultChecked, indeterminate, checkedChangeAction, disabled: disabledProp, transition, className, style, render, ...rest }: CheckboxProps): import("react").ReactNode;
|
|
88
90
|
export declare namespace Checkbox {
|
|
89
91
|
var displayName: string;
|
|
90
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox.d.mts","names":[],"sources":["../../src/checkbox/checkbox.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"checkbox.d.mts","names":[],"sources":["../../src/checkbox/checkbox.tsx"],"mappings":";;;iBAuBiB;;WAEN;;;;;WAKA;;WAEA;;WAEA;;;iBAIM,qCAAqC;;;;;;;WAO3C,QAAQ;;iBAGF,sBAAsB,KACrC;;EAWA;;;;;EAKA;;;;;;;;EAQA;;;;;;;;;;EAUA,sBAAsB,sBAAsB;;EAE5C;;EAEA;;;;;;;;;;EAUA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;;;;;EAK9C,SAAS,WAAW,gCAAgC;;;;;;;;;;;;;;;;wBAiBtC,WACd,SACA,gBACA,eACA,qBACA,UAAU,cACV,YACA,WACA,OACA,WACG,QACF,gCAAa;yBAXA"}
|
|
@@ -5,6 +5,7 @@ import { dataAttr } from "../internal/data-attr.mjs";
|
|
|
5
5
|
import { transitionTypes } from "../internal/transition-types.mjs";
|
|
6
6
|
import { useControllableAction } from "../internal/use-controllable-action.mjs";
|
|
7
7
|
import { useRender } from "../internal/use-render.mjs";
|
|
8
|
+
import { fieldInputProps, useFieldControl, useFieldValueChange, useMergedRef } from "../internal/use-field-control.mjs";
|
|
8
9
|
import { guardCheckableChange, useNativeChecked } from "../internal/use-native-checked.mjs";
|
|
9
10
|
//#region src/checkbox/checkbox.tsx
|
|
10
11
|
/**
|
|
@@ -14,12 +15,23 @@ import { guardCheckableChange, useNativeChecked } from "../internal/use-native-c
|
|
|
14
15
|
*
|
|
15
16
|
* Owned keys (a `render` element's value is overridden): `type`, `checked`, `defaultChecked`,
|
|
16
17
|
* `indeterminate` (stripped), `aria-checked` (always removed), `disabled`, `data-checked`,
|
|
17
|
-
* `data-indeterminate`, `data-pending`, `data-disabled
|
|
18
|
+
* `data-indeterminate`, `data-pending`, `data-disabled`; inside a `Field.Root` also `id`,
|
|
19
|
+
* `name`, `required`, `aria-invalid`, `aria-describedby` (token-merged) and `data-invalid`, and
|
|
20
|
+
* `disabled` / `name` / `required` default from the Field. `role` passes through. Event handlers
|
|
18
21
|
* are merged rather than owned: a `render` element's own `onChange` (or `onClick`) runs first,
|
|
19
22
|
* then the library's; an `onClick` that calls `preventDefault()` cancels the change (nothing is
|
|
20
23
|
* dispatched).
|
|
21
24
|
*/
|
|
22
|
-
function Checkbox({ checked, defaultChecked, indeterminate = false, checkedChangeAction, disabled
|
|
25
|
+
function Checkbox({ checked, defaultChecked, indeterminate = false, checkedChangeAction, disabled: disabledProp, transition, className, style, render, ...rest }) {
|
|
26
|
+
const field = useFieldControl({
|
|
27
|
+
labelable: true,
|
|
28
|
+
name: rest.name,
|
|
29
|
+
disabled: disabledProp,
|
|
30
|
+
required: rest.required,
|
|
31
|
+
id: rest.id,
|
|
32
|
+
component: "Checkbox"
|
|
33
|
+
});
|
|
34
|
+
const disabled = field.disabled;
|
|
23
35
|
const { optimistic, pending, dispatch, latestInFlight } = useControllableAction({
|
|
24
36
|
controlled: checked,
|
|
25
37
|
defaultValue: defaultChecked,
|
|
@@ -31,6 +43,7 @@ function Checkbox({ checked, defaultChecked, indeterminate = false, checkedChang
|
|
|
31
43
|
prop: "checked"
|
|
32
44
|
}
|
|
33
45
|
});
|
|
46
|
+
useFieldValueChange(optimistic);
|
|
34
47
|
const shownIndeterminate = indeterminate && !pending;
|
|
35
48
|
const inputRef = useNativeChecked({
|
|
36
49
|
type: "checkbox",
|
|
@@ -41,21 +54,24 @@ function Checkbox({ checked, defaultChecked, indeterminate = false, checkedChang
|
|
|
41
54
|
latestInFlight,
|
|
42
55
|
onPlatformChange: (next, event) => dispatch(next, createActionDetails(event, { reason: "platform" }))
|
|
43
56
|
});
|
|
57
|
+
const state = {
|
|
58
|
+
checked: optimistic,
|
|
59
|
+
indeterminate: shownIndeterminate,
|
|
60
|
+
pending,
|
|
61
|
+
disabled
|
|
62
|
+
};
|
|
63
|
+
const ref = useMergedRef(inputRef, field.ref);
|
|
44
64
|
const element = useRender({
|
|
45
65
|
defaultElement: "input",
|
|
46
66
|
render,
|
|
47
|
-
state
|
|
48
|
-
checked: optimistic,
|
|
49
|
-
indeterminate: shownIndeterminate,
|
|
50
|
-
pending,
|
|
51
|
-
disabled
|
|
52
|
-
},
|
|
67
|
+
state,
|
|
53
68
|
className,
|
|
54
69
|
style,
|
|
55
70
|
children: void 0,
|
|
56
|
-
ref
|
|
71
|
+
ref,
|
|
57
72
|
props: rest,
|
|
58
73
|
libraryProps: {
|
|
74
|
+
...fieldInputProps(field),
|
|
59
75
|
type: "checkbox",
|
|
60
76
|
checked: optimistic,
|
|
61
77
|
defaultChecked: void 0,
|