@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":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition, then closes the dialog with `
|
|
1
|
+
{"version":3,"file":"dialog-close.mjs","names":[],"sources":["../../src/dialog/dialog-close.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { createActionDetails } from \"../internal/action-details.js\";\nimport { dataAttr } from \"../internal/data-attr.js\";\nimport type { Action, ActionDetails, RenderProp, StateValue } from \"../internal/types.js\";\nimport { useActionTrigger } from \"../internal/use-action-trigger.js\";\nimport { rendersButton, useRender } from \"../internal/use-render.js\";\nimport type { DataAttributes } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogCloseState {\n /** The `action` is in flight. */\n readonly pending: boolean;\n readonly disabled: boolean;\n}\n\n/** Details passed to `Dialog.Close`'s `action`. */\nexport interface DialogCloseDetails extends ActionDetails {\n /** The `value` the dialog closes with (`dialog.returnValue`); `\"\"` without `value`. */\n readonly returnValue: string;\n}\n\nexport interface DialogCloseProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"onClick\" | \"value\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * Runs inside this part's own transition (`data-pending` on the button), then closes the\n * dialog with `value` as `returnValue`: in library mode through the Root\n * (`openChangeAction(false)` awaited, view transition if set), in JS-free mode with\n * `close(value)`. A rejection reaches the nearest Error Boundary above the Close part and\n * leaves the dialog open. If the dialog was dismissed and opened again while the action ran,\n * the close is dropped: it belonged to the earlier open. Without `action` in JS-free mode the\n * button is a native `command=\"request-close\"` invoker.\n */\n action?: Action<[details: DialogCloseDetails]>;\n /** Becomes `dialog.returnValue` (and `details.returnValue` of `openChangeAction(false)`). */\n value?: string;\n /** Native `disabled`; owned, so a `render` element cannot re-enable the button. */\n disabled?: boolean;\n className?: StateValue<string | undefined, DialogCloseState>;\n style?: StateValue<CSSProperties | undefined, DialogCloseState>;\n children?: StateValue<ReactNode, DialogCloseState>;\n render?: RenderProp<ComponentPropsWithRef<\"button\">, DialogCloseState>;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\n/**\n * A close button: `<button type=\"button\" commandfor command=\"request-close\">`. In JS-free mode\n * without `action` the browser closes the dialog (a close request: `cancel`, then `close`), with\n * `value` as the `returnValue`. In library mode the click is always cancelled and the close goes\n * through the Root (`openChangeAction(false)` awaited, then `close(value)` in the commit, inside\n * the view transition if the Popup's `transition` is set). With `action` the action runs first in\n * this part's own transition (`data-pending` on the button); a rejection leaves the dialog open.\n *\n * Owned keys (conventions 1.2): `type` (on a `<button>` only), `disabled`, `commandfor`,\n * `command`, `value`, `onClick` (library mode, or with `action`), `data-pending`,\n * `data-disabled`.\n */\nexport function DialogClose({\n action,\n value,\n disabled = false,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogCloseProps) {\n const ctx = useDialogContext(\"Dialog.Close\");\n // The open-generation guard: a close from a previous opening must not close a new one.\n const { pending, run } = useActionTrigger(ctx.openGeneration);\n const { ids, dialogRef, recordCloseIntent } = ctx;\n const isLibrary = ctx.mode === \"library\";\n const hasAction = action !== undefined;\n const installClick = isLibrary || hasAction;\n\n const state: DialogCloseState = { pending, disabled };\n\n const devRef = useCallback(\n (element: HTMLElement) => {\n if (isDev && !installClick && element.tagName !== \"BUTTON\") {\n console.error(\n `Dialog.Close: rendered a <${element.tagName.toLowerCase()}> in JS-free mode without ` +\n '`action`. Only a <button> runs `command=\"request-close\"`; this element will not ' +\n \"close the dialog.\",\n );\n }\n // A cleanup, so the merged ref never calls back with `null`.\n return () => {};\n },\n [installClick],\n );\n\n const close = (event: Event, returnValue: string): void => {\n if (isLibrary) {\n // A library-owned close (design 2.3): awaited, then `close(returnValue)` in the commit. A\n // no-op when `false` is already intended (a repeated click, or a dismissal meanwhile).\n ctx.requestClose(event, \"activate\", returnValue);\n return;\n }\n // JS-free mode with `action`: a browser-owned close; there is no Root state to go through.\n const dialog = dialogRef.current;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event });\n dialog.close(returnValue);\n };\n\n const onClick = (event: MouseEvent<HTMLElement>) => {\n // Cancelling the click cancels the command.\n event.preventDefault();\n if (disabled) return;\n const native = event.nativeEvent;\n const returnValue = value ?? \"\";\n if (action === undefined) {\n close(native, returnValue);\n return;\n }\n const details = createActionDetails(native, { returnValue });\n // Repeated clicks re-run the action; the first close wins (design 3.4). A close whose open\n // was superseded by a newer one (dismissed and re-opened meanwhile) is dropped by the hook.\n run(\n () => action(details),\n () => close(native, returnValue),\n );\n };\n\n const libraryProps: ComponentPropsWithRef<\"button\"> & DataAttributes = {\n ...(rendersButton(render) && { type: \"button\" as const }),\n // Owned: a render element must not re-enable a disabled Close.\n disabled,\n commandfor: ids.dialog,\n command: \"request-close\",\n value,\n ...(installClick && { onClick }),\n \"data-pending\": dataAttr(pending),\n \"data-disabled\": dataAttr(disabled),\n };\n\n return useRender<ComponentPropsWithRef<\"button\">, DialogCloseState>({\n defaultElement: \"button\",\n render,\n state,\n className,\n style,\n children,\n props: rest,\n libraryProps,\n ref: devRef,\n });\n}\n\nDialogClose.displayName = \"DialogClose\";\n"],"mappings":";;;;;;;;;AA+CA,MAAM,QAAQ,QAAQ,IAAI,aAAa;;;;;;;;;;;;;AAcvC,SAAgB,YAAY,EAC1B,QACA,OACA,WAAW,OACX,WACA,OACA,UACA,QACA,GAAG,QACgB;CACnB,MAAM,MAAM,iBAAiB,cAAc;CAE3C,MAAM,EAAE,SAAS,QAAQ,iBAAiB,IAAI,cAAc;CAC5D,MAAM,EAAE,KAAK,WAAW,sBAAsB;CAC9C,MAAM,YAAY,IAAI,SAAS;CAE/B,MAAM,eAAe,aADH,WAAW,KAAA;CAG7B,MAAM,QAA0B;EAAE;EAAS;CAAS;CAEpD,MAAM,SAAS,aACZ,YAAyB;EACxB,IAAI,SAAS,CAAC,gBAAgB,QAAQ,YAAY,UAChD,QAAQ,MACN,6BAA6B,QAAQ,QAAQ,YAAY,EAAE,gIAG7D;EAGF,aAAa,CAAC;CAChB,GACA,CAAC,YAAY,CACf;CAEA,MAAM,SAAS,OAAc,gBAA8B;EACzD,IAAI,WAAW;GAGb,IAAI,aAAa,OAAO,YAAY,WAAW;GAC/C;EACF;EAEA,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY;EAAM,CAAC;EAC/C,OAAO,MAAM,WAAW;CAC1B;CAEA,MAAM,WAAW,UAAmC;EAElD,MAAM,eAAe;EACrB,IAAI,UAAU;EACd,MAAM,SAAS,MAAM;EACrB,MAAM,cAAc,SAAS;EAC7B,IAAI,WAAW,KAAA,GAAW;GACxB,MAAM,QAAQ,WAAW;GACzB;EACF;EACA,MAAM,UAAU,oBAAoB,QAAQ,EAAE,YAAY,CAAC;EAG3D,UACQ,OAAO,OAAO,SACd,MAAM,QAAQ,WAAW,CACjC;CACF;CAEA,MAAM,eAAiE;EACrE,GAAI,cAAc,MAAM,KAAK,EAAE,MAAM,SAAkB;EAEvD;EACA,YAAY,IAAI;EAChB,SAAS;EACT;EACA,GAAI,gBAAgB,EAAE,QAAQ;EAC9B,gBAAgB,SAAS,OAAO;EAChC,iBAAiB,SAAS,QAAQ;CACpC;CAEA,OAAO,UAA6D;EAClE,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA,OAAO;EACP;EACA,KAAK;CACP,CAAC;AACH;AAEA,YAAY,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-context.mjs","names":[],"sources":["../../src/dialog/dialog-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { ChangeReason } from \"../internal/types.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n *
|
|
1
|
+
{"version":3,"file":"dialog-context.mjs","names":[],"sources":["../../src/dialog/dialog-context.ts"],"sourcesContent":["\"use client\";\nimport { createContext, use } from \"react\";\nimport type { RefObject } from \"react\";\nimport type { ChangeReason } from \"../internal/types.js\";\n\n/**\n * \"static\": JS-free mode, the browser owns open and close through Invoker Commands. \"library\":\n * opening and the closes a part asks for are library-owned (transitions keyed on committed\n * state); dismissals are browser-owned and notified (design section 2).\n */\nexport type DialogMode = \"static\" | \"library\";\n\n/**\n * What caused the next `close` event, recorded just before the close happens (design 2.3). The\n * `close` event carries no source; without an intent the close is a `\"dismiss\"`.\n */\nexport interface DialogCloseIntent {\n readonly reason: ChangeReason;\n readonly event: Event | undefined;\n}\n\nexport interface DialogContextValue {\n readonly mode: DialogMode;\n /** `useId`-based ids: the `<dialog>`, its Title and its Description. */\n readonly ids: { readonly dialog: string; readonly title: string; readonly description: string };\n readonly modal: boolean;\n /** Committed open state (library mode); `false` in static mode. */\n readonly committed: boolean;\n /** Optimistic open state (library mode); `false` in static mode. */\n readonly optimistic: boolean;\n /** The Root's transition is pending (library mode). */\n readonly pending: boolean;\n /**\n * Open the dialog through the Root's transition (library mode): a Trigger click, a foreign\n * `show-modal` command, or mount reconciliation. A no-op when the user already intends `true`.\n */\n readonly requestOpen: (event: Event | undefined, reason: ChangeReason) => void;\n /**\n * The dialog closed natively (a dismissal, not the library's own `close()`): dispatch the\n * `false` notification unless the user already intends `false`.\n */\n readonly notifyClosed: (event: Event, reason: ChangeReason, returnValue: string) => void;\n /**\n * What the user already intends: the latest in-flight dispatch's `next`, else the optimistic\n * value of the last commit. Safe to call from handlers and effects (layout effects included,\n * also in the commit that changed it), and after an `await`. Not during render.\n */\n readonly intended: () => boolean;\n /**\n * A library-owned close (library mode, design 2.3): a Close part, a `method=\"dialog\"` submit,\n * a Form's settle close, a foreign close command. Records `returnValue` for the apply effect's\n * `close()` and dispatches `false` (`openChangeAction(false)` awaited). A no-op when `false` is\n * already intended.\n */\n readonly requestClose: (\n event: Event | undefined,\n reason: ChangeReason,\n returnValue: string,\n ) => void;\n /**\n * Read and clear the `returnValue` of the pending part-initiated close, for the apply effect's\n * `close()`; `\"\"` when there is none (a controlled close).\n */\n readonly takeCloseReturnValue: () => string;\n /** From `useControllableAction`: the latest in-flight intent, or `null`. */\n readonly latestInFlight: () => { readonly next: boolean } | null;\n /** The registered Trigger element, for the focus-return fallback (design 6.2). */\n readonly triggerRef: RefObject<HTMLElement | null>;\n /** The `<dialog>`, set by the Popup's ref; how Close and Form reach `close(value)`. */\n readonly dialogRef: RefObject<HTMLDialogElement | null>;\n /** Record what causes the next close. Cleared after a task if the dialog did not close. */\n readonly recordCloseIntent: (intent: DialogCloseIntent | undefined) => void;\n /** Read and clear the recorded intent (the Popup's `close` listener). */\n readonly takeCloseIntent: () => DialogCloseIntent | undefined;\n /**\n * Counts the opens of this dialog. A Close action reads it when it starts and skips its close\n * if a newer open started meanwhile (the dialog was dismissed and opened again).\n */\n readonly openGeneration: () => number;\n /** Advance `openGeneration` (an open dispatch, the `<dialog>`'s native `beforetoggle` to open). */\n readonly nextOpenGeneration: () => void;\n}\n\nexport const DialogContext = createContext<DialogContextValue | null>(null);\n\nexport function useDialogContext(part: string): DialogContextValue {\n const context = use(DialogContext);\n if (context === null) {\n throw new Error(`${part} must be rendered inside a Dialog.Root.`);\n }\n return context;\n}\n"],"mappings":";;;AAmFA,MAAa,gBAAgB,cAAyC,IAAI;AAE1E,SAAgB,iBAAiB,MAAkC;CACjE,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,GAAG,KAAK,wCAAwC;CAElE,OAAO;AACT"}
|
|
@@ -9,19 +9,29 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
|
|
|
9
9
|
/**
|
|
10
10
|
* React form action (`(formData) => unknown`, may be a Server Function). Passed to the form
|
|
11
11
|
* unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's
|
|
12
|
-
* `value` as `returnValue` once the action's transition settles (see `closeOnSettle`)
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* `value` as `returnValue` once the action's transition settles (see `closeOnSettle`): in
|
|
13
|
+
* library mode through the Root (`openChangeAction(false)` awaited, view transition if set),
|
|
14
|
+
* in JS-free mode with `close(value)`. A rejection leaves the dialog open. Without `action`
|
|
15
|
+
* the form renders `method="dialog"`: submitting closes the dialog, natively in JS-free mode
|
|
16
|
+
* and through the Root in library mode (a `preventDefault()` in your own `onSubmit` blocks
|
|
17
|
+
* both; `stopPropagation()` there leaves the close to the browser, notified as a dismissal).
|
|
18
|
+
* A string `action` (a URL) is a plain navigating form.
|
|
15
19
|
*/
|
|
16
20
|
action?: string | ((formData: FormData) => unknown);
|
|
17
21
|
/**
|
|
18
22
|
* With a function `action`: close the dialog once the action's transition settles without
|
|
19
|
-
* throwing
|
|
23
|
+
* throwing and, when this form is rendered as a `Form.Root`
|
|
24
|
+
* (`render={<Form.Root errors={state.errors} />}`), with no field error: a settle whose
|
|
25
|
+
* `errors` has a non-empty entry (hidden stale ones included, the `autoReset` rule) keeps
|
|
26
|
+
* the dialog open and the Form focuses the first errored field. Default `true`.
|
|
20
27
|
*
|
|
21
|
-
*
|
|
22
|
-
* that reports
|
|
23
|
-
*
|
|
24
|
-
* a `Dialog.Close`, a
|
|
28
|
+
* Without a `Form.Root` the library never sees a result, so any settle that does not throw
|
|
29
|
+
* counts, including a `useActionState` result that reports errors: render the form as a
|
|
30
|
+
* `Form.Root` to route them, throw to keep the dialog open, or set `closeOnSettle={false}`
|
|
31
|
+
* and close it yourself (a `Dialog.Close`, a submit button in a separate `method="dialog"`
|
|
32
|
+
* form, or a controlled `open`). A `formMethod="dialog"` submitter inside this form cannot
|
|
33
|
+
* close it: React prevents every submit of a form with a function `action` and runs the
|
|
34
|
+
* action again.
|
|
25
35
|
*
|
|
26
36
|
* The dialog is not closed either when it was closed (Escape, a Close part) while the action
|
|
27
37
|
* ran, also if the user re-opened it meanwhile.
|
|
@@ -39,10 +49,13 @@ export interface DialogFormProps extends Omit<ComponentPropsWithRef<"form">, "me
|
|
|
39
49
|
}
|
|
40
50
|
/**
|
|
41
51
|
* A form that completes the dialog. Without `action`: `<form method="dialog">`, so a submit
|
|
42
|
-
* closes the dialog
|
|
52
|
+
* closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;
|
|
53
|
+
* in library mode the Popup cancels the submission and closes through the Root). With a
|
|
43
54
|
* function `action`: `<form action={action}>` (React overrides the method) and a status watcher
|
|
44
55
|
* that closes the dialog once the action has settled, with the submitter's `value`, unless
|
|
45
|
-
* `closeOnSettle` is `false
|
|
56
|
+
* `closeOnSettle` is `false`, the dialog was closed while the action ran, or the form is rendered
|
|
57
|
+
* as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the
|
|
58
|
+
* recommended composition for validation).
|
|
46
59
|
*
|
|
47
60
|
* `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and
|
|
48
61
|
* is not folded into any control's `pending` (conventions 2.7). Submit buttons read
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-form.d.mts","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"dialog-form.d.mts","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"mappings":";;;iBAWiB;;WAEN;;iBAGM,wBAAwB,KACvC;;;;;;;;;;;;EAcA,oBAAoB,UAAU;;;;;;;;;;;;;;;;;;;EAmB9B;;;;;EAKA,SAAS;EACT,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;EACjC,SAAS,WAAW,+BAA+B;;;;;;;;;;;;;;;;;;wBAmBrC,aACd,QACA,eACA,QACA,WACA,OACA,UACA,WACG,QACF,kBAAe;yBATF"}
|
|
@@ -1,38 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { resolveStateValue
|
|
2
|
+
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
3
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
4
|
+
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
5
|
+
import { FormContext } from "../internal/field-context.mjs";
|
|
3
6
|
import { useDialogContext } from "./dialog-context.mjs";
|
|
4
|
-
import {
|
|
7
|
+
import { FormStatusWatcher } from "../internal/form-status-watcher.mjs";
|
|
8
|
+
import { hasAnyError } from "../internal/validity.mjs";
|
|
9
|
+
import { use, useCallback, useRef, useState } from "react";
|
|
5
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { useFormStatus } from "react-dom";
|
|
7
11
|
//#region src/dialog/dialog-form.tsx
|
|
8
12
|
/**
|
|
9
|
-
* Observes React's form channel (design 8.1): `useFormStatus().pending` covers the action's
|
|
10
|
-
* whole transition, including the Suspense wait of what it re-renders. On `true → false` the
|
|
11
|
-
* submission succeeded (a rejection unmounts the form through the nearest Error Boundary, so the
|
|
12
|
-
* edge is never seen).
|
|
13
|
-
*/
|
|
14
|
-
function FormStatusWatcher({ onPendingChange, onSettled }) {
|
|
15
|
-
const { pending } = useFormStatus();
|
|
16
|
-
const previous = useRef(false);
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const was = previous.current;
|
|
19
|
-
previous.current = pending;
|
|
20
|
-
if (was === pending) return;
|
|
21
|
-
onPendingChange(pending);
|
|
22
|
-
if (was && !pending) onSettled();
|
|
23
|
-
}, [
|
|
24
|
-
pending,
|
|
25
|
-
onPendingChange,
|
|
26
|
-
onSettled
|
|
27
|
-
]);
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
13
|
* A form that completes the dialog. Without `action`: `<form method="dialog">`, so a submit
|
|
32
|
-
* closes the dialog
|
|
14
|
+
* closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;
|
|
15
|
+
* in library mode the Popup cancels the submission and closes through the Root). With a
|
|
33
16
|
* function `action`: `<form action={action}>` (React overrides the method) and a status watcher
|
|
34
17
|
* that closes the dialog once the action has settled, with the submitter's `value`, unless
|
|
35
|
-
* `closeOnSettle` is `false
|
|
18
|
+
* `closeOnSettle` is `false`, the dialog was closed while the action ran, or the form is rendered
|
|
19
|
+
* as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the
|
|
20
|
+
* recommended composition for validation).
|
|
36
21
|
*
|
|
37
22
|
* `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and
|
|
38
23
|
* is not folded into any control's `pending` (conventions 2.7). Submit buttons read
|
|
@@ -41,7 +26,9 @@ function FormStatusWatcher({ onPendingChange, onSettled }) {
|
|
|
41
26
|
* Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.
|
|
42
27
|
*/
|
|
43
28
|
function DialogForm({ action, closeOnSettle = true, method, className, style, children, render, ...rest }) {
|
|
44
|
-
const
|
|
29
|
+
const ctx = useDialogContext("Dialog.Form");
|
|
30
|
+
const { dialogRef, recordCloseIntent, requestClose } = ctx;
|
|
31
|
+
const isLibrary = ctx.mode === "library";
|
|
45
32
|
const [pending, setPending] = useState(false);
|
|
46
33
|
const submission = useRef(null);
|
|
47
34
|
const closedSinceSubmit = useRef(false);
|
|
@@ -67,21 +54,29 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
|
|
|
67
54
|
dialog?.removeEventListener("close", onClose);
|
|
68
55
|
};
|
|
69
56
|
}, []);
|
|
70
|
-
const onSettled = useCallback(() => {
|
|
57
|
+
const onSettled = useCallback((clean) => {
|
|
71
58
|
const dialog = dialogRef.current;
|
|
72
59
|
const submitted = submission.current;
|
|
73
60
|
submission.current = null;
|
|
74
61
|
if (!closeOnSettle || closedSinceSubmit.current) return;
|
|
62
|
+
if (!clean) return;
|
|
75
63
|
if (dialog === null || !dialog.open) return;
|
|
64
|
+
const returnValue = submitted?.returnValue ?? "";
|
|
65
|
+
if (isLibrary) {
|
|
66
|
+
requestClose(submitted?.event, "activate", returnValue);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
76
69
|
recordCloseIntent({
|
|
77
70
|
reason: "activate",
|
|
78
71
|
event: submitted?.event
|
|
79
72
|
});
|
|
80
|
-
dialog.close(
|
|
73
|
+
dialog.close(returnValue);
|
|
81
74
|
}, [
|
|
82
75
|
closeOnSettle,
|
|
83
76
|
dialogRef,
|
|
84
|
-
|
|
77
|
+
isLibrary,
|
|
78
|
+
recordCloseIntent,
|
|
79
|
+
requestClose
|
|
85
80
|
]);
|
|
86
81
|
return useRender({
|
|
87
82
|
defaultElement: "form",
|
|
@@ -89,7 +84,9 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
|
|
|
89
84
|
state: { pending },
|
|
90
85
|
className,
|
|
91
86
|
style,
|
|
92
|
-
children: (s) => /* @__PURE__ */ jsxs(Fragment, { children: [resolveStateValue(children, s),
|
|
87
|
+
children: (s) => /* @__PURE__ */ jsxs(Fragment, { children: [resolveStateValue(children, s), /* @__PURE__ */ jsx(DialogFormSettle, {
|
|
88
|
+
watches,
|
|
89
|
+
missingAction: action === void 0,
|
|
93
90
|
onPendingChange: setPending,
|
|
94
91
|
onSettled
|
|
95
92
|
})] }),
|
|
@@ -102,6 +99,28 @@ function DialogForm({ action, closeOnSettle = true, method, className, style, ch
|
|
|
102
99
|
});
|
|
103
100
|
}
|
|
104
101
|
DialogForm.displayName = "DialogForm";
|
|
102
|
+
/**
|
|
103
|
+
* Rendered among the form's children, so it reads the `FormContext` exactly when the form is a
|
|
104
|
+
* `Form.Root` (either orientation of the `render` composition): the settle close then waits for
|
|
105
|
+
* a result with no field error (`hasAnyError`, the `autoReset` predicate, hidden stale entries
|
|
106
|
+
* included).
|
|
107
|
+
*/
|
|
108
|
+
function DialogFormSettle({ watches, missingAction, onPendingChange, onSettled }) {
|
|
109
|
+
const form = use(FormContext);
|
|
110
|
+
useDevWarning(form !== null && missingAction, "`Dialog.Form` rendered as a `Form.Root` needs a function `action`; without one the form navigates instead of closing the dialog. Render a plain `<Dialog.Form>` for a `method=\"dialog\"` form.");
|
|
111
|
+
const isForm = form !== null;
|
|
112
|
+
const errors = form?.errors;
|
|
113
|
+
const settled = useCallback(() => onSettled(!isForm || !hasAnyError(errors)), [
|
|
114
|
+
isForm,
|
|
115
|
+
errors,
|
|
116
|
+
onSettled
|
|
117
|
+
]);
|
|
118
|
+
if (!watches) return null;
|
|
119
|
+
return /* @__PURE__ */ jsx(FormStatusWatcher, {
|
|
120
|
+
onPendingChange,
|
|
121
|
+
onSettled: settled
|
|
122
|
+
});
|
|
123
|
+
}
|
|
105
124
|
//#endregion
|
|
106
125
|
export { DialogForm };
|
|
107
126
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-form.mjs","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { useFormStatus } from \"react-dom\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogFormState {\n /** `useFormStatus().pending` of this form, exposed for `className` / `children` functions. */\n readonly pending: boolean;\n}\n\nexport interface DialogFormProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form\n * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's\n * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`). A\n * rejection leaves the dialog open. Without `action` the form renders `method=\"dialog\"`:\n * submitting closes the dialog natively. A string `action` (a URL) is a plain navigating form.\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * With a function `action`: close the dialog once the action's transition settles without\n * throwing. Default `true`.\n *\n * Any result that does not throw counts as success, including a `useActionState`-style result\n * that reports validation errors: with the default the dialog closes and those errors are never\n * seen. Throw to keep the dialog open, or set `closeOnSettle={false}` and close it yourself:\n * a `Dialog.Close`, a submitter with `formMethod=\"dialog\"`, or a controlled `open`.\n *\n * The dialog is not closed either when it was closed (Escape, a Close part) while the action\n * ran, also if the user re-opened it meanwhile.\n */\n closeOnSettle?: boolean;\n /**\n * Only with a string `action` (a navigating form, for example `\"post\"`). Owned otherwise:\n * `\"dialog\"` without `action`, React's own with a function `action`.\n */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, DialogFormState>;\n style?: StateValue<CSSProperties | undefined, DialogFormState>;\n children?: StateValue<ReactNode, DialogFormState>;\n render?: RenderProp<ComponentPropsWithRef<\"form\">, DialogFormState>;\n}\n\ninterface FormStatusWatcherProps {\n readonly onPendingChange: (pending: boolean) => void;\n readonly onSettled: () => void;\n}\n\n/**\n * Observes React's form channel (design 8.1): `useFormStatus().pending` covers the action's\n * whole transition, including the Suspense wait of what it re-renders. On `true → false` the\n * submission succeeded (a rejection unmounts the form through the nearest Error Boundary, so the\n * edge is never seen).\n */\nfunction FormStatusWatcher({ onPendingChange, onSettled }: FormStatusWatcherProps) {\n const { pending } = useFormStatus();\n const previous = useRef(false);\n useEffect(() => {\n const was = previous.current;\n previous.current = pending;\n if (was === pending) return;\n onPendingChange(pending);\n if (was && !pending) onSettled();\n }, [pending, onPendingChange, onSettled]);\n return null;\n}\n\n/**\n * A form that completes the dialog. Without `action`: `<form method=\"dialog\">`, so a submit\n * closes the dialog natively (JS-free) with the submitter's `value` as `returnValue`. With a\n * function `action`: `<form action={action}>` (React overrides the method) and a status watcher\n * that closes the dialog once the action has settled, with the submitter's `value`, unless\n * `closeOnSettle` is `false` or the dialog was closed while the action ran.\n *\n * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and\n * is not folded into any control's `pending` (conventions 2.7). Submit buttons read\n * `useFormStatus()` themselves.\n *\n * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.\n */\nexport function DialogForm({\n action,\n closeOnSettle = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogFormProps) {\n const ctx = useDialogContext(\"Dialog.Form\");\n const { dialogRef, recordCloseIntent } = ctx;\n const [pending, setPending] = useState(false);\n const submission = useRef<{ event: Event; returnValue: string } | null>(null);\n // The dialog closed after the last submit (Escape, a Close part): the settle must not close it\n // again, also when the user re-opened it meanwhile (the review's walkthrough 11.8 variant).\n const closedSinceSubmit = useRef(false);\n const watches = typeof action === \"function\";\n\n // The submitter's `value` comes from the native `submit` event (capture, before React's\n // handler runs the action).\n const libraryRef = useCallback((form: HTMLFormElement) => {\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n const submitter = (event as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n submission.current = { event, returnValue: submitter?.value ?? \"\" };\n closedSinceSubmit.current = false;\n };\n const dialog = form.closest(\"dialog\");\n const onClose = (event: Event) => {\n if (event.target === dialog) closedSinceSubmit.current = true;\n };\n form.addEventListener(\"submit\", onSubmit, true);\n dialog?.addEventListener(\"close\", onClose);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n dialog?.removeEventListener(\"close\", onClose);\n };\n }, []);\n\n const onSettled = useCallback(() => {\n const dialog = dialogRef.current;\n const submitted = submission.current;\n submission.current = null;\n if (!closeOnSettle || closedSinceSubmit.current) return;\n if (dialog === null || !dialog.open) return;\n recordCloseIntent({ reason: \"activate\", event: submitted?.event });\n dialog.close(submitted?.returnValue ?? \"\");\n }, [closeOnSettle, dialogRef, recordCloseIntent]);\n\n const state: DialogFormState = { pending };\n\n return useRender<ComponentPropsWithRef<\"form\">, DialogFormState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n {watches && <FormStatusWatcher onPendingChange={setPending} onSettled={onSettled} />}\n </>\n ),\n props: rest,\n libraryProps: {\n method: action === undefined ? \"dialog\" : typeof action === \"string\" ? method : undefined,\n // React's own type wants `void | Promise<void>`; the library's actions return `unknown`\n // (conventions 2.2) and React ignores the result.\n action: action as ComponentPropsWithRef<\"form\">[\"action\"],\n },\n ref: libraryRef,\n });\n}\n\nDialogForm.displayName = \"DialogForm\";\n"],"mappings":";;;;;;;;;;;;;AA4DA,SAAS,kBAAkB,EAAE,iBAAiB,aAAqC;CACjF,MAAM,EAAE,YAAY,cAAc;CAClC,MAAM,WAAW,OAAO,KAAK;CAC7B,gBAAgB;EACd,MAAM,MAAM,SAAS;EACrB,SAAS,UAAU;EACnB,IAAI,QAAQ,SAAS;EACrB,gBAAgB,OAAO;EACvB,IAAI,OAAO,CAAC,SAAS,UAAU;CACjC,GAAG;EAAC;EAAS;EAAiB;CAAS,CAAC;CACxC,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAgB,WAAW,EACzB,QACA,gBAAgB,MAChB,QACA,WACA,OACA,UACA,QACA,GAAG,QACe;CAElB,MAAM,EAAE,WAAW,sBADP,iBAAiB,aACc;CAC3C,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,aAAa,OAAqD,IAAI;CAG5E,MAAM,oBAAoB,OAAO,KAAK;CACtC,MAAM,UAAU,OAAO,WAAW;CAIlC,MAAM,aAAa,aAAa,SAA0B;EACxD,MAAM,YAAY,UAAiB;GACjC,IAAI,MAAM,WAAW,MAAM;GAC3B,MAAM,YAAa,MAAsB;GAIzC,WAAW,UAAU;IAAE;IAAO,aAAa,WAAW,SAAS;GAAG;GAClE,kBAAkB,UAAU;EAC9B;EACA,MAAM,SAAS,KAAK,QAAQ,QAAQ;EACpC,MAAM,WAAW,UAAiB;GAChC,IAAI,MAAM,WAAW,QAAQ,kBAAkB,UAAU;EAC3D;EACA,KAAK,iBAAiB,UAAU,UAAU,IAAI;EAC9C,QAAQ,iBAAiB,SAAS,OAAO;EACzC,aAAa;GACX,KAAK,oBAAoB,UAAU,UAAU,IAAI;GACjD,QAAQ,oBAAoB,SAAS,OAAO;EAC9C;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,YAAY,kBAAkB;EAClC,MAAM,SAAS,UAAU;EACzB,MAAM,YAAY,WAAW;EAC7B,WAAW,UAAU;EACrB,IAAI,CAAC,iBAAiB,kBAAkB,SAAS;EACjD,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,kBAAkB;GAAE,QAAQ;GAAY,OAAO,WAAW;EAAM,CAAC;EACjE,OAAO,MAAM,WAAW,eAAe,EAAE;CAC3C,GAAG;EAAC;EAAe;EAAW;CAAiB,CAAC;CAIhD,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA,EAL+B,QAK3B;EACJ;EACA;EACA,WAAW,MACT,qBAAA,UAAA,EAAA,UAAA,CACG,kBAAkB,UAAU,CAAC,GAC7B,WAAW,oBAAC,mBAAD;GAAmB,iBAAiB;GAAuB;EAAY,CAAA,CACnF,EAAA,CAAA;EAEJ,OAAO;EACP,cAAc;GACZ,QAAQ,WAAW,KAAA,IAAY,WAAW,OAAO,WAAW,WAAW,SAAS,KAAA;GAGxE;EACV;EACA,KAAK;CACP,CAAC;AACH;AAEA,WAAW,cAAc"}
|
|
1
|
+
{"version":3,"file":"dialog-form.mjs","names":[],"sources":["../../src/dialog/dialog-form.tsx"],"sourcesContent":["\"use client\";\nimport { use, useCallback, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, CSSProperties, ReactNode } from \"react\";\nimport { FormContext } from \"../internal/field-context.js\";\nimport { FormStatusWatcher } from \"../internal/form-status-watcher.js\";\nimport type { RenderProp, StateValue } from \"../internal/types.js\";\nimport { useDevWarning } from \"../internal/use-dev-warning.js\";\nimport { resolveStateValue, useRender } from \"../internal/use-render.js\";\nimport { hasAnyError } from \"../internal/validity.js\";\nimport { useDialogContext } from \"./dialog-context.js\";\n\nexport interface DialogFormState {\n /** `useFormStatus().pending` of this form, exposed for `className` / `children` functions. */\n readonly pending: boolean;\n}\n\nexport interface DialogFormProps extends Omit<\n ComponentPropsWithRef<\"form\">,\n \"method\" | \"action\" | \"className\" | \"style\" | \"children\"\n> {\n /**\n * React form action (`(formData) => unknown`, may be a Server Function). Passed to the form\n * unwrapped so React's progressive enhancement is kept; the dialog closes with the submitter's\n * `value` as `returnValue` once the action's transition settles (see `closeOnSettle`): in\n * library mode through the Root (`openChangeAction(false)` awaited, view transition if set),\n * in JS-free mode with `close(value)`. A rejection leaves the dialog open. Without `action`\n * the form renders `method=\"dialog\"`: submitting closes the dialog, natively in JS-free mode\n * and through the Root in library mode (a `preventDefault()` in your own `onSubmit` blocks\n * both; `stopPropagation()` there leaves the close to the browser, notified as a dismissal).\n * A string `action` (a URL) is a plain navigating form.\n */\n action?: string | ((formData: FormData) => unknown);\n /**\n * With a function `action`: close the dialog once the action's transition settles without\n * throwing and, when this form is rendered as a `Form.Root`\n * (`render={<Form.Root errors={state.errors} />}`), with no field error: a settle whose\n * `errors` has a non-empty entry (hidden stale ones included, the `autoReset` rule) keeps\n * the dialog open and the Form focuses the first errored field. Default `true`.\n *\n * Without a `Form.Root` the library never sees a result, so any settle that does not throw\n * counts, including a `useActionState` result that reports errors: render the form as a\n * `Form.Root` to route them, throw to keep the dialog open, or set `closeOnSettle={false}`\n * and close it yourself (a `Dialog.Close`, a submit button in a separate `method=\"dialog\"`\n * form, or a controlled `open`). A `formMethod=\"dialog\"` submitter inside this form cannot\n * close it: React prevents every submit of a form with a function `action` and runs the\n * action again.\n *\n * The dialog is not closed either when it was closed (Escape, a Close part) while the action\n * ran, also if the user re-opened it meanwhile.\n */\n closeOnSettle?: boolean;\n /**\n * Only with a string `action` (a navigating form, for example `\"post\"`). Owned otherwise:\n * `\"dialog\"` without `action`, React's own with a function `action`.\n */\n method?: ComponentPropsWithRef<\"form\">[\"method\"];\n className?: StateValue<string | undefined, DialogFormState>;\n style?: StateValue<CSSProperties | undefined, DialogFormState>;\n children?: StateValue<ReactNode, DialogFormState>;\n render?: RenderProp<ComponentPropsWithRef<\"form\">, DialogFormState>;\n}\n\n/**\n * A form that completes the dialog. Without `action`: `<form method=\"dialog\">`, so a submit\n * closes the dialog with the submitter's `value` as `returnValue` (natively in JS-free mode;\n * in library mode the Popup cancels the submission and closes through the Root). With a\n * function `action`: `<form action={action}>` (React overrides the method) and a status watcher\n * that closes the dialog once the action has settled, with the submitter's `value`, unless\n * `closeOnSettle` is `false`, the dialog was closed while the action ran, or the form is rendered\n * as a `Form.Root` and the result has a field error (`render={<Form.Root errors={...} />}`, the\n * recommended composition for validation).\n *\n * `DialogFormState.pending` is this form's own status; it is not emitted as `data-pending` and\n * is not folded into any control's `pending` (conventions 2.7). Submit buttons read\n * `useFormStatus()` themselves.\n *\n * Owned keys (conventions 1.2): `method` (except with a string `action`), `action`.\n */\nexport function DialogForm({\n action,\n closeOnSettle = true,\n method,\n className,\n style,\n children,\n render,\n ...rest\n}: DialogFormProps) {\n const ctx = useDialogContext(\"Dialog.Form\");\n const { dialogRef, recordCloseIntent, requestClose } = ctx;\n const isLibrary = ctx.mode === \"library\";\n const [pending, setPending] = useState(false);\n const submission = useRef<{ event: Event; returnValue: string } | null>(null);\n // The dialog closed after the last submit (Escape, a Close part): the settle must not close it\n // again, also when the user re-opened it meanwhile (the review's walkthrough 11.8 variant).\n const closedSinceSubmit = useRef(false);\n const watches = typeof action === \"function\";\n\n // The submitter's `value` comes from the native `submit` event (capture, before React's\n // handler runs the action).\n const libraryRef = useCallback((form: HTMLFormElement) => {\n const onSubmit = (event: Event) => {\n if (event.target !== form) return;\n const submitter = (event as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n submission.current = { event, returnValue: submitter?.value ?? \"\" };\n closedSinceSubmit.current = false;\n };\n const dialog = form.closest(\"dialog\");\n const onClose = (event: Event) => {\n if (event.target === dialog) closedSinceSubmit.current = true;\n };\n form.addEventListener(\"submit\", onSubmit, true);\n dialog?.addEventListener(\"close\", onClose);\n return () => {\n form.removeEventListener(\"submit\", onSubmit, true);\n dialog?.removeEventListener(\"close\", onClose);\n };\n }, []);\n\n // `clean`: no field error in the settle's result (always `true` without a `Form.Root`).\n const onSettled = useCallback(\n (clean: boolean) => {\n const dialog = dialogRef.current;\n const submitted = submission.current;\n submission.current = null;\n if (!closeOnSettle || closedSinceSubmit.current) return;\n // A result with field errors keeps the dialog open; the Form.Root focuses the first errored\n // field (design/components/dialog.md, \"Dialog.Form and Form.Root\").\n if (!clean) return;\n if (dialog === null || !dialog.open) return;\n const returnValue = submitted?.returnValue ?? \"\";\n if (isLibrary) {\n // A library-owned close (design 2.3): `openChangeAction(false)` awaited, then\n // `close(returnValue)` in the commit, inside the view transition if set.\n requestClose(submitted?.event, \"activate\", returnValue);\n return;\n }\n recordCloseIntent({ reason: \"activate\", event: submitted?.event });\n dialog.close(returnValue);\n },\n [closeOnSettle, dialogRef, isLibrary, recordCloseIntent, requestClose],\n );\n\n const state: DialogFormState = { pending };\n\n return useRender<ComponentPropsWithRef<\"form\">, DialogFormState>({\n defaultElement: \"form\",\n render,\n state,\n className,\n style,\n children: (s) => (\n <>\n {resolveStateValue(children, s)}\n <DialogFormSettle\n watches={watches}\n missingAction={action === undefined}\n onPendingChange={setPending}\n onSettled={onSettled}\n />\n </>\n ),\n props: rest,\n libraryProps: {\n method: action === undefined ? \"dialog\" : typeof action === \"string\" ? method : undefined,\n // React's own type wants `void | Promise<void>`; the library's actions return `unknown`\n // (conventions 2.2) and React ignores the result.\n action: action as ComponentPropsWithRef<\"form\">[\"action\"],\n },\n ref: libraryRef,\n });\n}\n\nDialogForm.displayName = \"DialogForm\";\n\n/**\n * Rendered among the form's children, so it reads the `FormContext` exactly when the form is a\n * `Form.Root` (either orientation of the `render` composition): the settle close then waits for\n * a result with no field error (`hasAnyError`, the `autoReset` predicate, hidden stale entries\n * included).\n */\nfunction DialogFormSettle({\n watches,\n missingAction,\n onPendingChange,\n onSettled,\n}: {\n readonly watches: boolean;\n readonly missingAction: boolean;\n readonly onPendingChange: (pending: boolean) => void;\n readonly onSettled: (clean: boolean) => void;\n}) {\n const form = use(FormContext);\n useDevWarning(\n form !== null && missingAction,\n \"`Dialog.Form` rendered as a `Form.Root` needs a function `action`; without one the form \" +\n \"navigates instead of closing the dialog. Render a plain `<Dialog.Form>` for a \" +\n '`method=\"dialog\"` form.',\n );\n const isForm = form !== null;\n const errors = form?.errors;\n const settled = useCallback(\n () => onSettled(!isForm || !hasAnyError(errors)),\n [isForm, errors, onSettled],\n );\n if (!watches) return null;\n return <FormStatusWatcher onPendingChange={onPendingChange} onSettled={settled} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA8EA,SAAgB,WAAW,EACzB,QACA,gBAAgB,MAChB,QACA,WACA,OACA,UACA,QACA,GAAG,QACe;CAClB,MAAM,MAAM,iBAAiB,aAAa;CAC1C,MAAM,EAAE,WAAW,mBAAmB,iBAAiB;CACvD,MAAM,YAAY,IAAI,SAAS;CAC/B,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,aAAa,OAAqD,IAAI;CAG5E,MAAM,oBAAoB,OAAO,KAAK;CACtC,MAAM,UAAU,OAAO,WAAW;CAIlC,MAAM,aAAa,aAAa,SAA0B;EACxD,MAAM,YAAY,UAAiB;GACjC,IAAI,MAAM,WAAW,MAAM;GAC3B,MAAM,YAAa,MAAsB;GAIzC,WAAW,UAAU;IAAE;IAAO,aAAa,WAAW,SAAS;GAAG;GAClE,kBAAkB,UAAU;EAC9B;EACA,MAAM,SAAS,KAAK,QAAQ,QAAQ;EACpC,MAAM,WAAW,UAAiB;GAChC,IAAI,MAAM,WAAW,QAAQ,kBAAkB,UAAU;EAC3D;EACA,KAAK,iBAAiB,UAAU,UAAU,IAAI;EAC9C,QAAQ,iBAAiB,SAAS,OAAO;EACzC,aAAa;GACX,KAAK,oBAAoB,UAAU,UAAU,IAAI;GACjD,QAAQ,oBAAoB,SAAS,OAAO;EAC9C;CACF,GAAG,CAAC,CAAC;CAGL,MAAM,YAAY,aACf,UAAmB;EAClB,MAAM,SAAS,UAAU;EACzB,MAAM,YAAY,WAAW;EAC7B,WAAW,UAAU;EACrB,IAAI,CAAC,iBAAiB,kBAAkB,SAAS;EAGjD,IAAI,CAAC,OAAO;EACZ,IAAI,WAAW,QAAQ,CAAC,OAAO,MAAM;EACrC,MAAM,cAAc,WAAW,eAAe;EAC9C,IAAI,WAAW;GAGb,aAAa,WAAW,OAAO,YAAY,WAAW;GACtD;EACF;EACA,kBAAkB;GAAE,QAAQ;GAAY,OAAO,WAAW;EAAM,CAAC;EACjE,OAAO,MAAM,WAAW;CAC1B,GACA;EAAC;EAAe;EAAW;EAAW;EAAmB;CAAY,CACvE;CAIA,OAAO,UAA0D;EAC/D,gBAAgB;EAChB;EACA,OAAA,EAL+B,QAK3B;EACJ;EACA;EACA,WAAW,MACT,qBAAA,UAAA,EAAA,UAAA,CACG,kBAAkB,UAAU,CAAC,GAC9B,oBAAC,kBAAD;GACW;GACT,eAAe,WAAW,KAAA;GAC1B,iBAAiB;GACN;EACZ,CAAA,CACD,EAAA,CAAA;EAEJ,OAAO;EACP,cAAc;GACZ,QAAQ,WAAW,KAAA,IAAY,WAAW,OAAO,WAAW,WAAW,SAAS,KAAA;GAGxE;EACV;EACA,KAAK;CACP,CAAC;AACH;AAEA,WAAW,cAAc;;;;;;;AAQzB,SAAS,iBAAiB,EACxB,SACA,eACA,iBACA,aAMC;CACD,MAAM,OAAO,IAAI,WAAW;CAC5B,cACE,SAAS,QAAQ,eACjB,iMAGF;CACA,MAAM,SAAS,SAAS;CACxB,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,kBACR,UAAU,CAAC,UAAU,CAAC,YAAY,MAAM,CAAC,GAC/C;EAAC;EAAQ;EAAQ;CAAS,CAC5B;CACA,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO,oBAAC,mBAAD;EAAoC;EAAiB,WAAW;CAAU,CAAA;AACnF"}
|
|
@@ -44,8 +44,10 @@ export interface DialogPopupProps extends Omit<ComponentPropsWithRef<"dialog">,
|
|
|
44
44
|
experimental_closedBy?: boolean;
|
|
45
45
|
/**
|
|
46
46
|
* Library mode only. Forwarded to the `<ViewTransition default>` around the dialog. Animates
|
|
47
|
-
* the opening commit
|
|
48
|
-
*
|
|
47
|
+
* the opening commit, content changes while open, and every library-driven close (a Close
|
|
48
|
+
* part, a Form submit or settled action, a foreign close command, a controlled
|
|
49
|
+
* `open={false}`), in all engines; dismissals (Escape, a click outside) are the browser's and
|
|
50
|
+
* animate with CSS only. Ignored (dev warning) in JS-free mode.
|
|
49
51
|
*
|
|
50
52
|
* Static: without `transition` the part renders no `<ViewTransition>`; changing it between
|
|
51
53
|
* `undefined` and a value remounts the element. Any value, `"none"` included, makes a mount or
|
|
@@ -67,8 +69,12 @@ export interface DialogPopupProps extends Omit<ComponentPropsWithRef<"dialog">,
|
|
|
67
69
|
*
|
|
68
70
|
* In library mode the children render from committed state, so content that suspends keeps the
|
|
69
71
|
* dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout
|
|
70
|
-
* effect of the commit that mounted them.
|
|
71
|
-
*
|
|
72
|
+
* effect of the commit that mounted them. A close a part asks for (Close, a `method="dialog"`
|
|
73
|
+
* submit, a Form's settle close, a foreign close command) is cancelled and goes through the
|
|
74
|
+
* Root: `openChangeAction(false)` is awaited and `close(returnValue)` runs in the layout effect
|
|
75
|
+
* of the commit that ends it, inside the view transition if `transition` is set. A dismissal
|
|
76
|
+
* (Escape, a click outside) is the browser's: the native `close` event becomes
|
|
77
|
+
* `openChangeAction(false, { reason: "dismiss", returnValue })`.
|
|
72
78
|
*
|
|
73
79
|
* Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and
|
|
74
80
|
* `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-popup.d.mts","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"dialog-popup.d.mts","names":[],"sources":["../../src/dialog/dialog-popup.tsx"],"mappings":";;;iBA6BiB;;WAEN;WACA;;iBAGM,yBAAyB,KACxC;;;;;;;;;;;EAaA;;;;;;;;;;EAUA,eAAe,UAAU;;;;;;;EAOzB;;;;;;;EAOA;;;;;;;;;;;;;EAaA,aAAa;EACb,YAAY,+BAA+B;EAC3C,QAAQ,WAAW,2BAA2B;EAC9C,WAAW,WAAW,WAAW;;EAEjC,SAAS,WAAW,iCAAiC;;;;;;;;;;;;;;;;;;;;wBAiGvC,cACd,aACA,cACA,cACA,uBACA,YACA,WACA,OACA,UACA,WACG,QACF,mBAAgB;yBAXH"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useTransitionBoundary } from "../internal/transition-boundary.mjs";
|
|
3
3
|
import { dataAttr } from "../internal/data-attr.mjs";
|
|
4
|
-
import { resolveStateValue
|
|
4
|
+
import { resolveStateValue } from "../internal/render-element.mjs";
|
|
5
|
+
import { useRender } from "../internal/use-render.mjs";
|
|
6
|
+
import { ToolbarContext } from "../internal/toolbar-context.mjs";
|
|
5
7
|
import { useDevWarning } from "../internal/use-dev-warning.mjs";
|
|
6
8
|
import { useDialogContext } from "./dialog-context.mjs";
|
|
7
|
-
import {
|
|
9
|
+
import { useContentFocusProbe } from "../internal/content-focus-probe.mjs";
|
|
10
|
+
import { useExitAnimation } from "../internal/use-exit-animation.mjs";
|
|
11
|
+
import { useModalRegistration } from "../internal/use-modal-registration.mjs";
|
|
12
|
+
import { Activity, useCallback, useEffect, useEffectEvent, useLayoutEffect, useRef, useState } from "react";
|
|
8
13
|
import { jsx } from "react/jsx-runtime";
|
|
9
14
|
//#region src/dialog/dialog-popup.tsx
|
|
10
15
|
const isDev = process.env.NODE_ENV !== "production";
|
|
@@ -52,10 +57,10 @@ function onBackdrop(dialog, event) {
|
|
|
52
57
|
const rect = dialog.getBoundingClientRect();
|
|
53
58
|
return !(rect.top <= clientY && clientY <= rect.bottom && rect.left <= clientX && clientX <= rect.right);
|
|
54
59
|
}
|
|
55
|
-
/**
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
return
|
|
60
|
+
/** The submitter's or invoker's `value` (`""` without one, or where the engine gives no source). */
|
|
61
|
+
function valueOf(element) {
|
|
62
|
+
if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) return element.value;
|
|
63
|
+
return "";
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
66
|
* The dialog itself: a `<dialog>` with the id the Trigger's `commandfor` names, wired to Title
|
|
@@ -65,8 +70,12 @@ function ends(animation) {
|
|
|
65
70
|
*
|
|
66
71
|
* In library mode the children render from committed state, so content that suspends keeps the
|
|
67
72
|
* dialog closed (and the Trigger pending) until it is ready; `showModal()` runs in the layout
|
|
68
|
-
* effect of the commit that mounted them.
|
|
69
|
-
*
|
|
73
|
+
* effect of the commit that mounted them. A close a part asks for (Close, a `method="dialog"`
|
|
74
|
+
* submit, a Form's settle close, a foreign close command) is cancelled and goes through the
|
|
75
|
+
* Root: `openChangeAction(false)` is awaited and `close(returnValue)` runs in the layout effect
|
|
76
|
+
* of the commit that ends it, inside the view transition if `transition` is set. A dismissal
|
|
77
|
+
* (Escape, a click outside) is the browser's: the native `close` event becomes
|
|
78
|
+
* `openChangeAction(false, { reason: "dismiss", returnValue })`.
|
|
70
79
|
*
|
|
71
80
|
* Owned keys (conventions 1.2): `id`, `open`, `closedby`, `aria-labelledby` and
|
|
72
81
|
* `aria-describedby` (token-merged), `data-modal`, `suppressHydrationWarning`.
|
|
@@ -76,10 +85,19 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
|
|
|
76
85
|
const isLibrary = ctx.mode === "library";
|
|
77
86
|
const { modal, ids, dialogRef, triggerRef } = ctx;
|
|
78
87
|
const [dialog, setDialog] = useState(null);
|
|
79
|
-
const [exiting, setExiting] = useState(false);
|
|
80
|
-
const exitGeneration = useRef(0);
|
|
81
88
|
const awaitingClose = useRef(false);
|
|
89
|
+
const libraryClose = useRef(false);
|
|
90
|
+
const dismissedWhilePending = useRef(null);
|
|
82
91
|
const warnedFocus = useRef(false);
|
|
92
|
+
const exit = useExitAnimation();
|
|
93
|
+
const [prevCommitted, setPrevCommitted] = useState(ctx.committed);
|
|
94
|
+
if (prevCommitted !== ctx.committed) {
|
|
95
|
+
setPrevCommitted(ctx.committed);
|
|
96
|
+
if (isLibrary && prevCommitted && transition === void 0) exit.holdDuringRender();
|
|
97
|
+
}
|
|
98
|
+
const contentProbe = useContentFocusProbe(dialogRef);
|
|
99
|
+
const { wrapContent } = contentProbe;
|
|
100
|
+
useModalRegistration(dialog);
|
|
83
101
|
useDevWarning(!isLibrary && transition !== void 0, "Dialog.Popup: `transition` has no effect in JS-free mode; the browser opens and closes the dialog outside any React transition. Animate it with CSS (`@starting-style`).");
|
|
84
102
|
useDevWarning(!isLibrary && keepMounted === false, "Dialog.Popup: `keepMounted={false}` cannot be honoured in JS-free mode; the content is always mounted so that the server HTML is complete.");
|
|
85
103
|
const libraryRef = useCallback((element) => {
|
|
@@ -120,18 +138,45 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
|
|
|
120
138
|
useLayoutEffect(() => {
|
|
121
139
|
if (isLibrary) reconcileOnMount();
|
|
122
140
|
}, [isLibrary]);
|
|
141
|
+
/**
|
|
142
|
+
* Design 6.2 rule 2, after a close whose commit unmounted the focused content: when focus is
|
|
143
|
+
* now on `<body>` or still inside the closed dialog, focus the Trigger (if connected), else
|
|
144
|
+
* blur what holds focus. A modal dialog never needs it (the platform restores focus on
|
|
145
|
+
* `close()` unconditionally); a non-modal one restores only when focus is inside.
|
|
146
|
+
*/
|
|
147
|
+
const returnFocus = (element) => {
|
|
148
|
+
const doc = element.ownerDocument;
|
|
149
|
+
const active = doc.activeElement;
|
|
150
|
+
if (active !== null && active !== doc.body && !element.contains(active)) return;
|
|
151
|
+
const trigger = triggerRef.current;
|
|
152
|
+
if (trigger !== null && trigger.isConnected) trigger.focus();
|
|
153
|
+
else if (active instanceof HTMLElement && active !== doc.body) active.blur();
|
|
154
|
+
};
|
|
123
155
|
useLayoutEffect(() => {
|
|
124
156
|
if (!isLibrary) return;
|
|
125
157
|
const element = dialogRef.current;
|
|
126
158
|
if (element === null || !element.isConnected) return;
|
|
127
|
-
if (ctx.pending
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
if (!ctx.pending && ctx.latestInFlight() === null) {
|
|
160
|
+
const dismissed = dismissedWhilePending.current;
|
|
161
|
+
if (ctx.committed && !element.open) {
|
|
162
|
+
if (!awaitingClose.current) {
|
|
163
|
+
dismissedWhilePending.current = null;
|
|
164
|
+
if (dismissed !== null && dismissed.generation === ctx.openGeneration()) ctx.notifyClosed(dismissed.event, "dismiss", dismissed.returnValue);
|
|
165
|
+
else openDialog(element);
|
|
166
|
+
}
|
|
167
|
+
} else if (!ctx.committed && element.open) {
|
|
168
|
+
ctx.recordCloseIntent(void 0);
|
|
169
|
+
libraryClose.current = true;
|
|
170
|
+
element.close(ctx.takeCloseReturnValue());
|
|
171
|
+
if (contentProbe.hadFocus()) {
|
|
172
|
+
contentProbe.forget();
|
|
173
|
+
returnFocus(element);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
134
176
|
}
|
|
177
|
+
if (!ctx.committed && dismissedWhilePending.current !== null && ctx.latestInFlight() === null) dismissedWhilePending.current = null;
|
|
178
|
+
if (ctx.committed && element.open) contentProbe.forget();
|
|
179
|
+
if (exit.held && !ctx.committed && !exit.waiting() && !awaitingClose.current && !element.open) exit.release();
|
|
135
180
|
});
|
|
136
181
|
useEffect(() => {
|
|
137
182
|
if (isLibrary || initialFocus === void 0 || dialog === null) return;
|
|
@@ -151,22 +196,36 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
|
|
|
151
196
|
if (!isLibrary) return;
|
|
152
197
|
event.preventDefault();
|
|
153
198
|
ctx.requestOpen(event, "activate");
|
|
154
|
-
} else if (command === "request-close" || command === "close")
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
199
|
+
} else if (command === "request-close" || command === "close") {
|
|
200
|
+
if (isLibrary) {
|
|
201
|
+
event.preventDefault();
|
|
202
|
+
const source = event.source;
|
|
203
|
+
ctx.requestClose(event, "activate", valueOf(source));
|
|
204
|
+
} else ctx.recordCloseIntent({
|
|
205
|
+
reason: "activate",
|
|
206
|
+
event
|
|
207
|
+
});
|
|
208
|
+
}
|
|
158
209
|
});
|
|
159
210
|
const onSubmit = useEffectEvent((event) => {
|
|
160
211
|
const element = dialogRef.current;
|
|
161
212
|
const form = event.target;
|
|
162
213
|
if (element === null || !(form instanceof HTMLFormElement)) return;
|
|
163
214
|
if (form.closest("dialog") !== element) return;
|
|
164
|
-
const
|
|
215
|
+
const submitter = event.submitter;
|
|
216
|
+
const formMethod = submitter?.getAttribute("formmethod");
|
|
165
217
|
if ((formMethod != null ? formMethod.toLowerCase() : form.method) !== "dialog") return;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
218
|
+
if (!isLibrary) {
|
|
219
|
+
ctx.recordCloseIntent({
|
|
220
|
+
reason: "activate",
|
|
221
|
+
event
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (event.defaultPrevented) return;
|
|
226
|
+
if (event.cancelBubble) return;
|
|
227
|
+
event.preventDefault();
|
|
228
|
+
ctx.requestClose(event, "activate", valueOf(submitter));
|
|
170
229
|
});
|
|
171
230
|
const onClose = useEffectEvent((event) => {
|
|
172
231
|
const element = dialogRef.current;
|
|
@@ -180,29 +239,39 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
|
|
|
180
239
|
else if (active instanceof HTMLElement) active.blur();
|
|
181
240
|
}
|
|
182
241
|
const intent = ctx.takeCloseIntent();
|
|
242
|
+
const echo = libraryClose.current;
|
|
243
|
+
libraryClose.current = false;
|
|
183
244
|
if (!isLibrary) return;
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const generation = ++exitGeneration.current;
|
|
189
|
-
setExiting(true);
|
|
190
|
-
Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {
|
|
191
|
-
if (exitGeneration.current === generation) startTransition(() => setExiting(false));
|
|
192
|
-
});
|
|
193
|
-
}
|
|
245
|
+
if (!echo || transition === void 0) {
|
|
246
|
+
const animations = exit.exitAnimations(element);
|
|
247
|
+
if (animations.length > 0) exit.holdUntil(animations);
|
|
248
|
+
else exit.release();
|
|
194
249
|
}
|
|
195
|
-
|
|
250
|
+
if (echo) return;
|
|
251
|
+
ctx.takeCloseReturnValue();
|
|
252
|
+
if (ctx.intended()) ctx.notifyClosed(intent?.event ?? event, intent?.reason ?? "dismiss", element.returnValue);
|
|
253
|
+
else dismissedWhilePending.current = {
|
|
254
|
+
event: intent?.event ?? event,
|
|
255
|
+
returnValue: element.returnValue,
|
|
256
|
+
generation: ctx.openGeneration()
|
|
257
|
+
};
|
|
196
258
|
});
|
|
197
259
|
const onBeforeToggle = useEffectEvent((event) => {
|
|
198
|
-
|
|
260
|
+
const element = dialogRef.current;
|
|
261
|
+
if (element === null || event.target !== element) return;
|
|
199
262
|
const newState = event.newState;
|
|
200
263
|
awaitingClose.current = newState === "closed";
|
|
201
|
-
if (newState === "
|
|
264
|
+
if (newState === "closed") exit.recordBaseline(element);
|
|
265
|
+
if (newState === "open") {
|
|
266
|
+
dismissedWhilePending.current = null;
|
|
267
|
+
ctx.nextOpenGeneration();
|
|
268
|
+
contentProbe.forget();
|
|
269
|
+
}
|
|
202
270
|
});
|
|
203
271
|
useEffect(() => {
|
|
204
272
|
if (dialog === null) return;
|
|
205
273
|
const doc = dialog.ownerDocument;
|
|
274
|
+
const root = dialog.getRootNode();
|
|
206
275
|
const nativeClosedBy = experimental_closedBy && "closedBy" in dialog;
|
|
207
276
|
const listeners = [
|
|
208
277
|
[
|
|
@@ -212,7 +281,7 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
|
|
|
212
281
|
false
|
|
213
282
|
],
|
|
214
283
|
[
|
|
215
|
-
|
|
284
|
+
root,
|
|
216
285
|
"submit",
|
|
217
286
|
onSubmit,
|
|
218
287
|
false
|
|
@@ -329,14 +398,18 @@ function DialogPopup({ keepMounted, initialFocus, lightDismiss = false, experime
|
|
|
329
398
|
open: ctx.committed,
|
|
330
399
|
modal
|
|
331
400
|
};
|
|
332
|
-
const visible = ctx.committed ||
|
|
333
|
-
const content = (s) => {
|
|
401
|
+
const visible = ctx.committed || exit.held;
|
|
402
|
+
const content = (s) => /* @__PURE__ */ jsx(ToolbarContext, {
|
|
403
|
+
value: null,
|
|
404
|
+
children: contentOf(s)
|
|
405
|
+
});
|
|
406
|
+
const contentOf = (s) => {
|
|
334
407
|
if (!isLibrary) return resolveStateValue(children, s);
|
|
335
408
|
if (keepMounted === true) return /* @__PURE__ */ jsx(Activity, {
|
|
336
409
|
mode: visible ? "visible" : "hidden",
|
|
337
|
-
children: resolveStateValue(children, s)
|
|
410
|
+
children: wrapContent(resolveStateValue(children, s))
|
|
338
411
|
});
|
|
339
|
-
return visible ? resolveStateValue(children, s) : null;
|
|
412
|
+
return visible ? wrapContent(resolveStateValue(children, s)) : null;
|
|
340
413
|
};
|
|
341
414
|
const libraryProps = {
|
|
342
415
|
id: ids.dialog,
|