@funstack/ui 0.1.0-alpha.0 → 0.1.0-alpha.2
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/README.md +1 -1
- package/dist/checkbox-group/checkbox-group-context.mjs +24 -0
- package/dist/checkbox-group/checkbox-group-context.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-item.d.mts +55 -0
- package/dist/checkbox-group/checkbox-group-item.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-item.mjs +90 -0
- package/dist/checkbox-group/checkbox-group-item.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-label.d.mts +21 -0
- package/dist/checkbox-group/checkbox-group-label.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-label.mjs +30 -0
- package/dist/checkbox-group/checkbox-group-label.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-parent.d.mts +68 -0
- package/dist/checkbox-group/checkbox-group-parent.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-parent.mjs +145 -0
- package/dist/checkbox-group/checkbox-group-parent.mjs.map +1 -0
- package/dist/checkbox-group/checkbox-group-root.d.mts +100 -0
- package/dist/checkbox-group/checkbox-group-root.d.mts.map +1 -0
- package/dist/checkbox-group/checkbox-group-root.mjs +291 -0
- package/dist/checkbox-group/checkbox-group-root.mjs.map +1 -0
- package/dist/checkbox-group/index.d.mts +23 -0
- package/dist/checkbox-group/index.d.mts.map +1 -0
- package/dist/checkbox-group/index.mjs +23 -0
- package/dist/checkbox-group/index.mjs.map +1 -0
- package/dist/combobox/combobox-clear.d.mts +34 -0
- package/dist/combobox/combobox-clear.d.mts.map +1 -0
- package/dist/combobox/combobox-clear.mjs +58 -0
- package/dist/combobox/combobox-clear.mjs.map +1 -0
- package/dist/combobox/combobox-context.mjs +23 -0
- package/dist/combobox/combobox-context.mjs.map +1 -0
- package/dist/combobox/combobox-group.d.mts +72 -0
- package/dist/combobox/combobox-group.d.mts.map +1 -0
- package/dist/combobox/combobox-group.mjs +104 -0
- package/dist/combobox/combobox-group.mjs.map +1 -0
- package/dist/combobox/combobox-input.d.mts +40 -0
- package/dist/combobox/combobox-input.d.mts.map +1 -0
- package/dist/combobox/combobox-input.mjs +88 -0
- package/dist/combobox/combobox-input.mjs.map +1 -0
- package/dist/combobox/combobox-label.d.mts +22 -0
- package/dist/combobox/combobox-label.d.mts.map +1 -0
- package/dist/combobox/combobox-label.mjs +32 -0
- package/dist/combobox/combobox-label.mjs.map +1 -0
- package/dist/combobox/combobox-list.d.mts +37 -0
- package/dist/combobox/combobox-list.d.mts.map +1 -0
- package/dist/combobox/combobox-list.mjs +70 -0
- package/dist/combobox/combobox-list.mjs.map +1 -0
- package/dist/combobox/combobox-option.d.mts +45 -0
- package/dist/combobox/combobox-option.d.mts.map +1 -0
- package/dist/combobox/combobox-option.mjs +88 -0
- package/dist/combobox/combobox-option.mjs.map +1 -0
- package/dist/combobox/combobox-popup.d.mts +49 -0
- package/dist/combobox/combobox-popup.d.mts.map +1 -0
- package/dist/combobox/combobox-popup.mjs +86 -0
- package/dist/combobox/combobox-popup.mjs.map +1 -0
- package/dist/combobox/combobox-root.d.mts +135 -0
- package/dist/combobox/combobox-root.d.mts.map +1 -0
- package/dist/combobox/combobox-root.mjs +508 -0
- package/dist/combobox/combobox-root.mjs.map +1 -0
- package/dist/combobox/combobox-trigger.d.mts +36 -0
- package/dist/combobox/combobox-trigger.d.mts.map +1 -0
- package/dist/combobox/combobox-trigger.mjs +92 -0
- package/dist/combobox/combobox-trigger.mjs.map +1 -0
- package/dist/combobox/index.d.mts +36 -0
- package/dist/combobox/index.d.mts.map +1 -0
- package/dist/combobox/index.mjs +36 -0
- package/dist/combobox/index.mjs.map +1 -0
- package/dist/dialog/dialog-close.d.mts.map +1 -1
- package/dist/dialog/dialog-close.mjs +4 -4
- package/dist/dialog/dialog-close.mjs.map +1 -1
- package/dist/dialog/dialog-context.mjs.map +1 -1
- package/dist/dialog/dialog-popup.mjs +1 -0
- package/dist/dialog/dialog-popup.mjs.map +1 -1
- package/dist/dialog/dialog-root.mjs +9 -1
- package/dist/dialog/dialog-root.mjs.map +1 -1
- package/dist/index.d.mts +42 -2
- package/dist/index.mjs +6 -1
- package/dist/internal/anchor-position.d.mts +7 -0
- package/dist/internal/anchor-position.d.mts.map +1 -0
- package/dist/internal/anchor-position.mjs +16 -0
- package/dist/internal/anchor-position.mjs.map +1 -0
- package/dist/internal/platform-attributes.d.mts +18 -1
- package/dist/internal/platform-attributes.d.mts.map +1 -1
- package/dist/internal/sync-presence.mjs +19 -0
- package/dist/internal/sync-presence.mjs.map +1 -0
- package/dist/internal/types.d.mts +8 -0
- package/dist/internal/types.d.mts.map +1 -1
- package/dist/internal/use-action-trigger.mjs +58 -0
- package/dist/internal/use-action-trigger.mjs.map +1 -0
- package/dist/internal/use-controllable-action.mjs +5 -1
- package/dist/internal/use-controllable-action.mjs.map +1 -1
- package/dist/internal/use-delayed-pair.mjs +40 -0
- package/dist/internal/use-delayed-pair.mjs.map +1 -0
- package/dist/internal/use-exit-animation.mjs +55 -0
- package/dist/internal/use-exit-animation.mjs.map +1 -0
- package/dist/internal/use-form-reset.mjs +5 -1
- package/dist/internal/use-form-reset.mjs.map +1 -1
- package/dist/internal/use-hydrated-mount.mjs +21 -0
- package/dist/internal/use-hydrated-mount.mjs.map +1 -0
- package/dist/internal/use-native-group-reconciliation.mjs +2 -2
- package/dist/internal/use-native-group-reconciliation.mjs.map +1 -1
- package/dist/internal/use-native-select-value.mjs +182 -0
- package/dist/internal/use-native-select-value.mjs.map +1 -0
- package/dist/internal/use-popover-popup.mjs +274 -0
- package/dist/internal/use-popover-popup.mjs.map +1 -0
- package/dist/internal/use-popover-root.d.mts +2 -0
- package/dist/internal/use-popover-root.mjs +112 -0
- package/dist/internal/use-popover-root.mjs.map +1 -0
- package/dist/internal/use-popover-trigger.mjs +86 -0
- package/dist/internal/use-popover-trigger.mjs.map +1 -0
- package/dist/menu/index.d.mts +34 -0
- package/dist/menu/index.d.mts.map +1 -0
- package/dist/menu/index.mjs +34 -0
- package/dist/menu/index.mjs.map +1 -0
- package/dist/menu/menu-checkbox-item.d.mts +42 -0
- package/dist/menu/menu-checkbox-item.d.mts.map +1 -0
- package/dist/menu/menu-checkbox-item.mjs +71 -0
- package/dist/menu/menu-checkbox-item.mjs.map +1 -0
- package/dist/menu/menu-context.d.mts +1 -0
- package/dist/menu/menu-context.mjs +28 -0
- package/dist/menu/menu-context.mjs.map +1 -0
- package/dist/menu/menu-dom.mjs +37 -0
- package/dist/menu/menu-dom.mjs.map +1 -0
- package/dist/menu/menu-group.d.mts +48 -0
- package/dist/menu/menu-group.d.mts.map +1 -0
- package/dist/menu/menu-group.mjs +69 -0
- package/dist/menu/menu-group.mjs.map +1 -0
- package/dist/menu/menu-hover.mjs +29 -0
- package/dist/menu/menu-hover.mjs.map +1 -0
- package/dist/menu/menu-item.d.mts +45 -0
- package/dist/menu/menu-item.d.mts.map +1 -0
- package/dist/menu/menu-item.mjs +54 -0
- package/dist/menu/menu-item.mjs.map +1 -0
- package/dist/menu/menu-popup.d.mts +69 -0
- package/dist/menu/menu-popup.d.mts.map +1 -0
- package/dist/menu/menu-popup.mjs +207 -0
- package/dist/menu/menu-popup.mjs.map +1 -0
- package/dist/menu/menu-radio-group.d.mts +37 -0
- package/dist/menu/menu-radio-group.d.mts.map +1 -0
- package/dist/menu/menu-radio-group.mjs +72 -0
- package/dist/menu/menu-radio-group.mjs.map +1 -0
- package/dist/menu/menu-radio-item.d.mts +33 -0
- package/dist/menu/menu-radio-item.d.mts.map +1 -0
- package/dist/menu/menu-radio-item.mjs +62 -0
- package/dist/menu/menu-radio-item.mjs.map +1 -0
- package/dist/menu/menu-root.d.mts +54 -0
- package/dist/menu/menu-root.d.mts.map +1 -0
- package/dist/menu/menu-root.mjs +102 -0
- package/dist/menu/menu-root.mjs.map +1 -0
- package/dist/menu/menu-submenu-trigger.d.mts +31 -0
- package/dist/menu/menu-submenu-trigger.d.mts.map +1 -0
- package/dist/menu/menu-submenu-trigger.mjs +92 -0
- package/dist/menu/menu-submenu-trigger.mjs.map +1 -0
- package/dist/menu/menu-trigger.d.mts +36 -0
- package/dist/menu/menu-trigger.d.mts.map +1 -0
- package/dist/menu/menu-trigger.mjs +81 -0
- package/dist/menu/menu-trigger.mjs.map +1 -0
- package/dist/menu/use-menu-item.mjs +54 -0
- package/dist/menu/use-menu-item.mjs.map +1 -0
- package/dist/popover/index.d.mts +28 -0
- package/dist/popover/index.d.mts.map +1 -0
- package/dist/popover/index.mjs +28 -0
- package/dist/popover/index.mjs.map +1 -0
- package/dist/popover/popover-close.d.mts +41 -0
- package/dist/popover/popover-close.d.mts.map +1 -0
- package/dist/popover/popover-close.mjs +90 -0
- package/dist/popover/popover-close.mjs.map +1 -0
- package/dist/popover/popover-context.mjs +26 -0
- package/dist/popover/popover-context.mjs.map +1 -0
- package/dist/popover/popover-description.d.mts +22 -0
- package/dist/popover/popover-description.d.mts.map +1 -0
- package/dist/popover/popover-description.mjs +28 -0
- package/dist/popover/popover-description.mjs.map +1 -0
- package/dist/popover/popover-popup.d.mts +82 -0
- package/dist/popover/popover-popup.d.mts.map +1 -0
- package/dist/popover/popover-popup.mjs +93 -0
- package/dist/popover/popover-popup.mjs.map +1 -0
- package/dist/popover/popover-root.d.mts +64 -0
- package/dist/popover/popover-root.d.mts.map +1 -0
- package/dist/popover/popover-root.mjs +39 -0
- package/dist/popover/popover-root.mjs.map +1 -0
- package/dist/popover/popover-title.d.mts +23 -0
- package/dist/popover/popover-title.d.mts.map +1 -0
- package/dist/popover/popover-title.mjs +28 -0
- package/dist/popover/popover-title.mjs.map +1 -0
- package/dist/popover/popover-trigger.d.mts +37 -0
- package/dist/popover/popover-trigger.d.mts.map +1 -0
- package/dist/popover/popover-trigger.mjs +40 -0
- package/dist/popover/popover-trigger.mjs.map +1 -0
- package/dist/radio-group/radio-group-item.d.mts.map +1 -1
- package/dist/radio-group/radio-group-item.mjs +1 -6
- package/dist/radio-group/radio-group-item.mjs.map +1 -1
- package/dist/select/index.d.mts +30 -0
- package/dist/select/index.d.mts.map +1 -0
- package/dist/select/index.mjs +30 -0
- package/dist/select/index.mjs.map +1 -0
- package/dist/select/select-context.mjs +13 -0
- package/dist/select/select-context.mjs.map +1 -0
- package/dist/select/select-dom.mjs +44 -0
- package/dist/select/select-dom.mjs.map +1 -0
- package/dist/select/select-group-label.d.mts +22 -0
- package/dist/select/select-group-label.d.mts.map +1 -0
- package/dist/select/select-group-label.mjs +39 -0
- package/dist/select/select-group-label.mjs.map +1 -0
- package/dist/select/select-group.d.mts +28 -0
- package/dist/select/select-group.d.mts.map +1 -0
- package/dist/select/select-group.mjs +45 -0
- package/dist/select/select-group.mjs.map +1 -0
- package/dist/select/select-option.d.mts +42 -0
- package/dist/select/select-option.d.mts.map +1 -0
- package/dist/select/select-option.mjs +65 -0
- package/dist/select/select-option.mjs.map +1 -0
- package/dist/select/select-root.d.mts +66 -0
- package/dist/select/select-root.d.mts.map +1 -0
- package/dist/select/select-root.mjs +94 -0
- package/dist/select/select-root.mjs.map +1 -0
- package/dist/select/select-separator.d.mts +20 -0
- package/dist/select/select-separator.d.mts.map +1 -0
- package/dist/select/select-separator.mjs +30 -0
- package/dist/select/select-separator.mjs.map +1 -0
- package/dist/select/select-trigger.d.mts +29 -0
- package/dist/select/select-trigger.d.mts.map +1 -0
- package/dist/select/select-trigger.mjs +48 -0
- package/dist/select/select-trigger.mjs.map +1 -0
- package/dist/select/select-value.d.mts +31 -0
- package/dist/select/select-value.d.mts.map +1 -0
- package/dist/select/select-value.mjs +101 -0
- package/dist/select/select-value.mjs.map +1 -0
- package/dist/tabs/tabs-context.mjs +2 -1
- package/dist/tabs/tabs-context.mjs.map +1 -1
- package/dist/tabs/tabs-list.d.mts +1 -1
- package/dist/tabs/tabs-list.d.mts.map +1 -1
- package/dist/tabs/tabs-list.mjs +17 -9
- package/dist/tabs/tabs-list.mjs.map +1 -1
- package/dist/tabs/tabs-tab.d.mts +1 -1
- package/dist/tabs/tabs-tab.d.mts.map +1 -1
- package/dist/tabs/tabs-tab.mjs +6 -3
- package/dist/tabs/tabs-tab.mjs.map +1 -1
- package/dist/tooltip/tooltip-popup.d.mts.map +1 -1
- package/dist/tooltip/tooltip-popup.mjs +2 -7
- package/dist/tooltip/tooltip-popup.mjs.map +1 -1
- package/dist/tooltip/use-tooltip-interaction.mjs +5 -17
- package/dist/tooltip/use-tooltip-interaction.mjs.map +1 -1
- package/package.json +6 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import "./internal/platform-attributes.mjs";
|
|
2
|
-
import { Action, ActionDetails, ChangeAction, ChangeReason, RenderProp, StateValue } from "./internal/types.mjs";
|
|
2
|
+
import { Action, ActionDetails, ChangeAction, ChangeReason, RenderProp, StateValue, UrgentChangeAction } from "./internal/types.mjs";
|
|
3
3
|
import { AccordionItemProps, AccordionItemState } from "./accordion/accordion-item.mjs";
|
|
4
4
|
import { AccordionPanelProps, AccordionPanelState } from "./accordion/accordion-panel.mjs";
|
|
5
5
|
import { AccordionRootExclusiveProps, AccordionRootMultipleProps, AccordionRootProps, AccordionRootState, AccordionValueChangeDetails } from "./accordion/accordion-root.mjs";
|
|
6
6
|
import { AccordionTriggerProps, AccordionTriggerState } from "./accordion/accordion-trigger.mjs";
|
|
7
7
|
import { Accordion } from "./accordion/index.mjs";
|
|
8
|
+
import { CheckboxGroupItemProps, CheckboxGroupItemState } from "./checkbox-group/checkbox-group-item.mjs";
|
|
9
|
+
import { CheckboxGroupRootProps, CheckboxGroupRootState, CheckboxGroupValueChangeDetails } from "./checkbox-group/checkbox-group-root.mjs";
|
|
10
|
+
import { CheckboxGroupLabelProps } from "./checkbox-group/checkbox-group-label.mjs";
|
|
11
|
+
import { CheckboxGroupParentProps, CheckboxGroupParentState } from "./checkbox-group/checkbox-group-parent.mjs";
|
|
12
|
+
import { CheckboxGroup } from "./checkbox-group/index.mjs";
|
|
8
13
|
import { Checkbox, CheckboxCheckedChangeDetails, CheckboxProps, CheckboxState } from "./checkbox/checkbox.mjs";
|
|
9
14
|
import "./checkbox/index.mjs";
|
|
15
|
+
import { ComboboxClearProps, ComboboxClearState } from "./combobox/combobox-clear.mjs";
|
|
16
|
+
import { ComboboxListProps, ComboboxListState } from "./combobox/combobox-list.mjs";
|
|
17
|
+
import { ComboboxEmptyProps, ComboboxEmptyState, ComboboxGroupLabelProps, ComboboxGroupLabelState, ComboboxGroupProps, ComboboxGroupState, ComboboxStatusProps, ComboboxStatusState } from "./combobox/combobox-group.mjs";
|
|
18
|
+
import { ComboboxInputProps, ComboboxInputState } from "./combobox/combobox-input.mjs";
|
|
19
|
+
import { ComboboxLabelProps, ComboboxLabelState } from "./combobox/combobox-label.mjs";
|
|
20
|
+
import { ComboboxOptionProps, ComboboxOptionState } from "./combobox/combobox-option.mjs";
|
|
21
|
+
import { ComboboxPopupProps, ComboboxPopupState } from "./combobox/combobox-popup.mjs";
|
|
22
|
+
import { ComboboxInputValueChangeDetails, ComboboxOpenChangeDetails, ComboboxRootProps, ComboboxRootState, ComboboxValueChangeDetails } from "./combobox/combobox-root.mjs";
|
|
23
|
+
import { ComboboxTriggerProps, ComboboxTriggerState } from "./combobox/combobox-trigger.mjs";
|
|
24
|
+
import { Combobox } from "./combobox/index.mjs";
|
|
10
25
|
import { DialogCloseDetails, DialogCloseProps, DialogCloseState } from "./dialog/dialog-close.mjs";
|
|
11
26
|
import { DialogPopupProps, DialogPopupState } from "./dialog/dialog-popup.mjs";
|
|
12
27
|
import { DialogDescriptionProps, DialogDescriptionState } from "./dialog/dialog-description.mjs";
|
|
@@ -31,8 +46,33 @@ import { TooltipTriggerProps, TooltipTriggerState } from "./tooltip/tooltip-trig
|
|
|
31
46
|
import { Tooltip } from "./tooltip/index.mjs";
|
|
32
47
|
import { Switch, SwitchCheckedChangeDetails, SwitchProps, SwitchState } from "./switch/switch.mjs";
|
|
33
48
|
import "./switch/index.mjs";
|
|
49
|
+
import { PopoverCloseProps, PopoverCloseState } from "./popover/popover-close.mjs";
|
|
50
|
+
import { PopoverAlign, PopoverPopupProps, PopoverPopupState, PopoverSide } from "./popover/popover-popup.mjs";
|
|
51
|
+
import { PopoverDescriptionProps, PopoverDescriptionState } from "./popover/popover-description.mjs";
|
|
52
|
+
import { PopoverOpenChangeDetails, PopoverRootProps, PopoverRootState } from "./popover/popover-root.mjs";
|
|
53
|
+
import { PopoverTitleProps, PopoverTitleState } from "./popover/popover-title.mjs";
|
|
54
|
+
import { PopoverTriggerProps, PopoverTriggerState } from "./popover/popover-trigger.mjs";
|
|
55
|
+
import { Popover } from "./popover/index.mjs";
|
|
56
|
+
import { MenuItemDetails, MenuItemProps, MenuItemState } from "./menu/menu-item.mjs";
|
|
57
|
+
import { MenuCheckboxItemProps, MenuCheckboxItemState } from "./menu/menu-checkbox-item.mjs";
|
|
58
|
+
import { MenuGroupLabelProps, MenuGroupLabelState, MenuGroupProps, MenuGroupState, MenuSeparatorProps, MenuSeparatorState } from "./menu/menu-group.mjs";
|
|
59
|
+
import { MenuAlign, MenuPopupProps, MenuPopupState, MenuSide } from "./menu/menu-popup.mjs";
|
|
60
|
+
import { MenuRadioGroupProps, MenuRadioGroupState } from "./menu/menu-radio-group.mjs";
|
|
61
|
+
import { MenuRadioItemProps, MenuRadioItemState } from "./menu/menu-radio-item.mjs";
|
|
62
|
+
import { MenuOpenChangeDetails, MenuRootProps, MenuRootState } from "./menu/menu-root.mjs";
|
|
63
|
+
import { MenuTriggerProps, MenuTriggerState } from "./menu/menu-trigger.mjs";
|
|
64
|
+
import { MenuSubmenuTriggerProps, MenuSubmenuTriggerState } from "./menu/menu-submenu-trigger.mjs";
|
|
65
|
+
import { Menu } from "./menu/index.mjs";
|
|
34
66
|
import { RadioGroupItemProps, RadioGroupItemState } from "./radio-group/radio-group-item.mjs";
|
|
35
67
|
import { RadioGroupRootProps, RadioGroupRootState, RadioGroupValueChangeDetails } from "./radio-group/radio-group-root.mjs";
|
|
36
68
|
import { RadioGroupLabelProps } from "./radio-group/radio-group-label.mjs";
|
|
37
69
|
import { RadioGroup } from "./radio-group/index.mjs";
|
|
38
|
-
|
|
70
|
+
import { SelectRootProps, SelectRootState, SelectValueChangeDetails } from "./select/select-root.mjs";
|
|
71
|
+
import { SelectGroupProps } from "./select/select-group.mjs";
|
|
72
|
+
import { SelectGroupLabelProps } from "./select/select-group-label.mjs";
|
|
73
|
+
import { SelectOptionProps, SelectOptionState } from "./select/select-option.mjs";
|
|
74
|
+
import { SelectSeparatorProps } from "./select/select-separator.mjs";
|
|
75
|
+
import { SelectTriggerProps } from "./select/select-trigger.mjs";
|
|
76
|
+
import { SelectValueProps } from "./select/select-value.mjs";
|
|
77
|
+
import { Select } from "./select/index.mjs";
|
|
78
|
+
export { Accordion, type AccordionItemProps, type AccordionItemState, type AccordionPanelProps, type AccordionPanelState, type AccordionRootExclusiveProps, type AccordionRootMultipleProps, type AccordionRootProps, type AccordionRootState, type AccordionTriggerProps, type AccordionTriggerState, type AccordionValueChangeDetails, type Action, type ActionDetails, type ChangeAction, type ChangeReason, Checkbox, type CheckboxCheckedChangeDetails, CheckboxGroup, type CheckboxGroupItemProps, type CheckboxGroupItemState, type CheckboxGroupLabelProps, type CheckboxGroupParentProps, type CheckboxGroupParentState, type CheckboxGroupRootProps, type CheckboxGroupRootState, type CheckboxGroupValueChangeDetails, type CheckboxProps, type CheckboxState, Combobox, type ComboboxClearProps, type ComboboxClearState, type ComboboxEmptyProps, type ComboboxEmptyState, type ComboboxGroupLabelProps, type ComboboxGroupLabelState, type ComboboxGroupProps, type ComboboxGroupState, type ComboboxInputProps, type ComboboxInputState, type ComboboxInputValueChangeDetails, type ComboboxLabelProps, type ComboboxLabelState, type ComboboxListProps, type ComboboxListState, type ComboboxOpenChangeDetails, type ComboboxOptionProps, type ComboboxOptionState, type ComboboxPopupProps, type ComboboxPopupState, type ComboboxRootProps, type ComboboxRootState, type ComboboxStatusProps, type ComboboxStatusState, type ComboboxTriggerProps, type ComboboxTriggerState, type ComboboxValueChangeDetails, Dialog, type DialogCloseDetails, type DialogCloseProps, type DialogCloseState, type DialogDescriptionProps, type DialogDescriptionState, type DialogFormProps, type DialogFormState, type DialogOpenChangeDetails, type DialogPopupProps, type DialogPopupState, type DialogRootProps, type DialogRootState, type DialogTitleProps, type DialogTitleState, type DialogTriggerProps, type DialogTriggerState, Menu, type MenuAlign, type MenuCheckboxItemProps, type MenuCheckboxItemState, type MenuGroupLabelProps, type MenuGroupLabelState, type MenuGroupProps, type MenuGroupState, type MenuItemDetails, type MenuItemProps, type MenuItemState, type MenuOpenChangeDetails, type MenuPopupProps, type MenuPopupState, type MenuRadioGroupProps, type MenuRadioGroupState, type MenuRadioItemProps, type MenuRadioItemState, type MenuRootProps, type MenuRootState, type MenuSeparatorProps, type MenuSeparatorState, type MenuSide, type MenuSubmenuTriggerProps, type MenuSubmenuTriggerState, type MenuTriggerProps, type MenuTriggerState, Popover, type PopoverAlign, type PopoverCloseProps, type PopoverCloseState, type PopoverDescriptionProps, type PopoverDescriptionState, type PopoverOpenChangeDetails, type PopoverPopupProps, type PopoverPopupState, type PopoverRootProps, type PopoverRootState, type PopoverSide, type PopoverTitleProps, type PopoverTitleState, type PopoverTriggerProps, type PopoverTriggerState, RadioGroup, type RadioGroupItemProps, type RadioGroupItemState, type RadioGroupLabelProps, type RadioGroupRootProps, type RadioGroupRootState, type RadioGroupValueChangeDetails, type RenderProp, Select, type SelectGroupLabelProps, type SelectGroupProps, type SelectOptionProps, type SelectOptionState, type SelectRootProps, type SelectRootState, type SelectSeparatorProps, type SelectTriggerProps, type SelectValueChangeDetails, type SelectValueProps, type StateValue, Switch, type SwitchCheckedChangeDetails, type SwitchProps, type SwitchState, Tabs, type TabsActivationMode, type TabsIndicatorProps, type TabsIndicatorState, type TabsListProps, type TabsListState, type TabsOrientation, type TabsPanelProps, type TabsPanelState, type TabsRootProps, type TabsRootState, type TabsTabProps, type TabsTabState, type TabsValueChangeDetails, Toggle, type TogglePressedChangeDetails, type ToggleProps, type ToggleState, Tooltip, type TooltipAlign, type TooltipPopupProps, type TooltipPopupState, type TooltipProvides, type TooltipRootProps, type TooltipRootState, type TooltipSide, type TooltipTriggerProps, type TooltipTriggerState, type UrgentChangeAction };
|
package/dist/index.mjs
CHANGED
|
@@ -5,5 +5,10 @@ import { Tooltip } from "./tooltip/index.mjs";
|
|
|
5
5
|
import { Switch } from "./switch/switch.mjs";
|
|
6
6
|
import { Checkbox } from "./checkbox/checkbox.mjs";
|
|
7
7
|
import { Dialog } from "./dialog/index.mjs";
|
|
8
|
+
import { Popover } from "./popover/index.mjs";
|
|
9
|
+
import { Menu } from "./menu/index.mjs";
|
|
8
10
|
import { RadioGroup } from "./radio-group/index.mjs";
|
|
9
|
-
|
|
11
|
+
import { CheckboxGroup } from "./checkbox-group/index.mjs";
|
|
12
|
+
import { Select } from "./select/index.mjs";
|
|
13
|
+
import { Combobox } from "./combobox/index.mjs";
|
|
14
|
+
export { Accordion, Checkbox, CheckboxGroup, Combobox, Dialog, Menu, Popover, RadioGroup, Select, Switch, Tabs, Toggle, Tooltip };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/internal/anchor-position.d.ts
|
|
2
|
+
/** Physical side of the anchor the popup is placed on. */
|
|
3
|
+
export type AnchorSide = "top" | "bottom" | "left" | "right";
|
|
4
|
+
/** Alignment along that side. */
|
|
5
|
+
export type AnchorAlign = "start" | "center" | "end";
|
|
6
|
+
//#endregion
|
|
7
|
+
//# sourceMappingURL=anchor-position.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-position.d.mts","names":[],"sources":["../../src/internal/anchor-position.ts"],"mappings":";;YAQY;;YAEA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/internal/anchor-position.ts
|
|
2
|
+
/** Flip to the opposite side, then the other axis, then both, when the popup does not fit. */
|
|
3
|
+
const POSITION_TRY_FALLBACKS = "flip-block, flip-inline, flip-block flip-inline";
|
|
4
|
+
/**
|
|
5
|
+
* The `position-area` for a side and an alignment (physical keywords): `bottom` + `center` is
|
|
6
|
+
* `bottom`, `bottom` + `start` is `bottom span-right`, `right` + `start` is `right span-bottom`,
|
|
7
|
+
* and so on.
|
|
8
|
+
*/
|
|
9
|
+
function positionArea(side, align) {
|
|
10
|
+
if (align === "center") return side;
|
|
11
|
+
return `${side} ${side === "top" || side === "bottom" ? align === "start" ? "span-right" : "span-left" : align === "start" ? "span-bottom" : "span-top"}`;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { POSITION_TRY_FALLBACKS, positionArea };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=anchor-position.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-position.mjs","names":[],"sources":["../../src/internal/anchor-position.ts"],"sourcesContent":["// Placement of a popover against its implicit anchor (the invoker set by `commandfor`,\n// `interestfor` or `showPopover({ source })`): inline `position-area` from a side and an\n// alignment, plus `position-try-fallbacks` that flip it when it does not fit. Shared by Tooltip,\n// Popover and Menu (design/components/tooltip.md 7.1, popover.md 8).\n//\n// No \"use client\" directive: pure functions, importable from anywhere (conventions 7.1).\n\n/** Physical side of the anchor the popup is placed on. */\nexport type AnchorSide = \"top\" | \"bottom\" | \"left\" | \"right\";\n/** Alignment along that side. */\nexport type AnchorAlign = \"start\" | \"center\" | \"end\";\n\n/** Flip to the opposite side, then the other axis, then both, when the popup does not fit. */\nexport const POSITION_TRY_FALLBACKS = \"flip-block, flip-inline, flip-block flip-inline\";\n\n/**\n * The `position-area` for a side and an alignment (physical keywords): `bottom` + `center` is\n * `bottom`, `bottom` + `start` is `bottom span-right`, `right` + `start` is `right span-bottom`,\n * and so on.\n */\nexport function positionArea(side: AnchorSide, align: AnchorAlign): string {\n if (align === \"center\") return side;\n const vertical = side === \"top\" || side === \"bottom\";\n const span = vertical\n ? align === \"start\"\n ? \"span-right\"\n : \"span-left\"\n : align === \"start\"\n ? \"span-bottom\"\n : \"span-top\";\n return `${side} ${span}`;\n}\n"],"mappings":";;AAaA,MAAa,yBAAyB;;;;;;AAOtC,SAAgB,aAAa,MAAkB,OAA4B;CACzE,IAAI,UAAU,UAAU,OAAO;CAS/B,OAAO,GAAG,KAAK,GARE,SAAS,SAAS,SAAS,WAExC,UAAU,UACR,eACA,cACF,UAAU,UACR,gBACA;AAER"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
//#region src/internal/platform-attributes.d.ts
|
|
2
3
|
declare module "react" {
|
|
3
4
|
interface HTMLAttributes<T> {
|
|
@@ -7,6 +8,12 @@ declare module "react" {
|
|
|
7
8
|
* Not in `@types/react` 19.3.0 (no DefinitelyTyped issue filed as of 2026-10-16).
|
|
8
9
|
*/
|
|
9
10
|
focusgroup?: string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* `focusgroupstart` (Chromium 150+): marks the item a `focusgroup` enters on when focus
|
|
13
|
+
* arrives from outside (with `nomemory`, always). Boolean attribute: pass `""`.
|
|
14
|
+
* Not in `@types/react` 19.3.0 (no DefinitelyTyped issue filed as of 2026-10-17).
|
|
15
|
+
*/
|
|
16
|
+
focusgroupstart?: "" | undefined;
|
|
10
17
|
}
|
|
11
18
|
interface ButtonHTMLAttributes<T> {
|
|
12
19
|
/**
|
|
@@ -35,6 +42,17 @@ declare module "react" {
|
|
|
35
42
|
/** `interestfor` on `<area href>` (Chromium 142+). Not in `@types/react` 19.3.0 (DefinitelyTyped #74664). */
|
|
36
43
|
interestfor?: string | undefined;
|
|
37
44
|
}
|
|
45
|
+
namespace JSX {
|
|
46
|
+
interface IntrinsicElements {
|
|
47
|
+
/**
|
|
48
|
+
* `<selectedcontent>` (customizable select: Chromium 135+, Safari 27): inside a select's
|
|
49
|
+
* `<button>`, the browser clones the selected option's content into it. React renders the
|
|
50
|
+
* unknown lowercase tag verbatim on client and server. `Select.Value` renders it.
|
|
51
|
+
* Not in `@types/react` 19.3.0 (no DefinitelyTyped issue filed as of 2026-10-17).
|
|
52
|
+
*/
|
|
53
|
+
selectedcontent: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
38
56
|
interface CSSProperties {
|
|
39
57
|
/** `interest-delay` shorthand (Chromium 142+). Not in `csstype` 3.2.3. */
|
|
40
58
|
interestDelay?: string | undefined;
|
|
@@ -45,5 +63,4 @@ declare module "react" {
|
|
|
45
63
|
}
|
|
46
64
|
}
|
|
47
65
|
//#endregion
|
|
48
|
-
export {};
|
|
49
66
|
//# sourceMappingURL=platform-attributes.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-attributes.d.mts","names":[],"sources":["../../src/internal/platform-attributes.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"platform-attributes.d.mts","names":[],"sources":["../../src/internal/platform-attributes.ts"],"mappings":";;;YAkBY,eAAe;;;;;;IAMvB;;;;;;IAMA;;YAGQ,qBAAqB;;;;;;IAM7B;;;;;IAKA;;;;;;IAMA;;YAWQ,qBAAqB;;IAE7B;;YAGQ,mBAAmB;;IAE3B;;YAGQ;cACE;;;;;;;MAOR,iBAAiB,MAAM,kBAAkB,MAAM,eAAe,cAAc;;;YAItE;;IAER;;IAEA;;IAEA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/internal/sync-presence.ts
|
|
2
|
+
/**
|
|
3
|
+
* Makes a boolean attribute's presence match `present`. For group members that may hydrate after
|
|
4
|
+
* their Root (`RadioGroup.Item`, `CheckboxGroup.Item` / `Parent`): hydration keeps server
|
|
5
|
+
* attributes that differ from the client render (React only warns), and a member hydrating late
|
|
6
|
+
* can render a different state than the server did.
|
|
7
|
+
*
|
|
8
|
+
* No `"use client"` directive: no client-only API (conventions 7.1).
|
|
9
|
+
*/
|
|
10
|
+
function syncPresence(el, name, present) {
|
|
11
|
+
if (el.hasAttribute(name) !== present) {
|
|
12
|
+
if (present) el.setAttribute(name, "");
|
|
13
|
+
else el.removeAttribute(name);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { syncPresence };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=sync-presence.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-presence.mjs","names":[],"sources":["../../src/internal/sync-presence.ts"],"sourcesContent":["/**\n * Makes a boolean attribute's presence match `present`. For group members that may hydrate after\n * their Root (`RadioGroup.Item`, `CheckboxGroup.Item` / `Parent`): hydration keeps server\n * attributes that differ from the client render (React only warns), and a member hydrating late\n * can render a different state than the server did.\n *\n * No `\"use client\"` directive: no client-only API (conventions 7.1).\n */\nexport function syncPresence(el: Element, name: string, present: boolean): void {\n if (el.hasAttribute(name) !== present) {\n if (present) el.setAttribute(name, \"\");\n else el.removeAttribute(name);\n }\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,aAAa,IAAa,MAAc,SAAwB;CAC9E,IAAI,GAAG,aAAa,IAAI,MAAM,SAAS;EACrC,IAAI,SAAS,GAAG,aAAa,MAAM,EAAE;OAChC,GAAG,gBAAgB,IAAI;CAC9B;AACF"}
|
|
@@ -13,6 +13,14 @@ export interface ActionDetails {
|
|
|
13
13
|
}
|
|
14
14
|
/** An action called with the next value of a controllable state. */
|
|
15
15
|
export type ChangeAction<T, Details extends ActionDetails = ActionDetails> = Action<[next: T, details: Details]>;
|
|
16
|
+
/**
|
|
17
|
+
* A change action the library calls **synchronously, outside any Transition, and does not
|
|
18
|
+
* await**: the urgent lane (conventions 2.6; first used by Combobox's `inputValueChangeAction`).
|
|
19
|
+
* The body runs during the DOM event, so `(next) => setText(next)` is an urgent update, which is
|
|
20
|
+
* what a controlled text input needs. Nothing is pending or optimistic for it; a returned promise
|
|
21
|
+
* is ignored (development warns once).
|
|
22
|
+
*/
|
|
23
|
+
export type UrgentChangeAction<T, Details extends ActionDetails = ActionDetails> = (next: T, details: Details) => void;
|
|
16
24
|
/**
|
|
17
25
|
* Why a change action was dispatched (conventions 2.2). One library-wide vocabulary: every
|
|
18
26
|
* `<Component><State>ChangeDetails` that exposes `reason` declares it with this type.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/internal/types.ts"],"mappings":";;;;;;;;YAQY,OAAO,4CAA4C,MAAM;iBAEpD;;WAEN,OAAO;;;YAIN,aAAa,GAAG,gBAAgB,gBAAgB,iBAAiB,QAC1E,MAAM,GAAG,SAAS;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/internal/types.ts"],"mappings":";;;;;;;;YAQY,OAAO,4CAA4C,MAAM;iBAEpD;;WAEN,OAAO;;;YAIN,aAAa,GAAG,gBAAgB,gBAAgB,iBAAiB,QAC1E,MAAM,GAAG,SAAS;;;;;;;;YAUT,mBAAmB,GAAG,gBAAgB,gBAAgB,kBAChE,MAAM,GACN,SAAS;;;;;;;;;;;;;YAeC;;YAGA,WAAW,GAAG,SAAS,MAAM,OAAO,UAAU;;;;;;YAO9C,WAAW,OAAO,SAC1B,aAAa,QAAQ,YACnB,OAAO,OAAO,OAAO,UAAU"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef, useState, useTransition } from "react";
|
|
3
|
+
//#region src/internal/use-action-trigger.ts
|
|
4
|
+
/** Reports an error that cannot reach a boundary (conventions 2.4: never swallow). */
|
|
5
|
+
function report(error) {
|
|
6
|
+
if (typeof reportError === "function") reportError(error);
|
|
7
|
+
else console.error(error);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The transition of a pure-trigger part: an `action` with no state of its own (`Dialog.Close`,
|
|
11
|
+
* `Popover.Close`, `Menu.Item`; conventions 3.4). Same superseded-rejection surfacing as
|
|
12
|
+
* `useControllableAction`: React keeps only the newest thenable per `useTransition`, so the
|
|
13
|
+
* rejection of a run that a later run superseded would be dropped. The hook catches it and throws
|
|
14
|
+
* it from render, so it reaches the same boundary as the latest run's rejection does.
|
|
15
|
+
*/
|
|
16
|
+
function useActionTrigger(openGeneration) {
|
|
17
|
+
const [supersededError, setSupersededError] = useState(null);
|
|
18
|
+
const hasSupersededError = useRef(false);
|
|
19
|
+
if (supersededError !== null) throw supersededError.error;
|
|
20
|
+
const [pending, startOwnTransition] = useTransition();
|
|
21
|
+
const latest = useRef(0);
|
|
22
|
+
const mounted = useRef(false);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
mounted.current = true;
|
|
25
|
+
return () => {
|
|
26
|
+
mounted.current = false;
|
|
27
|
+
};
|
|
28
|
+
}, []);
|
|
29
|
+
const run = (work, then) => {
|
|
30
|
+
const id = ++latest.current;
|
|
31
|
+
const generation = openGeneration?.();
|
|
32
|
+
startOwnTransition(async () => {
|
|
33
|
+
try {
|
|
34
|
+
await work();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (id === latest.current) throw error;
|
|
37
|
+
if (!mounted.current) return;
|
|
38
|
+
if (hasSupersededError.current) {
|
|
39
|
+
report(error);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
hasSupersededError.current = true;
|
|
43
|
+
setSupersededError({ error });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (openGeneration !== void 0 && openGeneration() !== generation) return;
|
|
47
|
+
then?.();
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
pending,
|
|
52
|
+
run
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { useActionTrigger };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=use-action-trigger.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-action-trigger.mjs","names":[],"sources":["../../src/internal/use-action-trigger.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useRef, useState, useTransition } from \"react\";\n\nexport interface ActionTrigger {\n /** A run started through this hook is in flight (the part's own transition). */\n readonly pending: boolean;\n /**\n * Runs `work` inside the part's own transition and, once it resolves, `then` (still inside the\n * transition: a Close part's close, a menu item's close). A rejection skips `then` and reaches\n * the nearest Error Boundary above the part, also when a later run superseded this one. With\n * `openGeneration` (the hook's argument), `then` is skipped when a newer open of the overlay\n * started after the run began.\n */\n readonly run: (work: () => unknown, then?: () => void) => void;\n}\n\n/** Reports an error that cannot reach a boundary (conventions 2.4: never swallow). */\nfunction report(error: unknown): void {\n if (typeof reportError === \"function\") reportError(error);\n else console.error(error);\n}\n\n/**\n * The transition of a pure-trigger part: an `action` with no state of its own (`Dialog.Close`,\n * `Popover.Close`, `Menu.Item`; conventions 3.4). Same superseded-rejection surfacing as\n * `useControllableAction`: React keeps only the newest thenable per `useTransition`, so the\n * rejection of a run that a later run superseded would be dropped. The hook catches it and throws\n * it from render, so it reaches the same boundary as the latest run's rejection does.\n */\nexport function useActionTrigger(openGeneration?: () => number): ActionTrigger {\n // Thrown before `useTransition` on purpose (see use-controllable-action.ts): in the retry of\n // an errored render `useTransition` would suspend on the newer run's pending thenable.\n const [supersededError, setSupersededError] = useState<{ readonly error: unknown } | null>(null);\n const hasSupersededError = useRef(false);\n if (supersededError !== null) throw supersededError.error;\n\n const [pending, startOwnTransition] = useTransition();\n const latest = useRef(0);\n const mounted = useRef(false);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n const run = (work: () => unknown, then?: () => void): void => {\n const id = ++latest.current;\n // The open this run belongs to (design/components/popover.md, review M2 / PO rule): if the\n // user dismisses and re-opens while the action runs, its close must not close the new open.\n const generation = openGeneration?.();\n startOwnTransition(async () => {\n try {\n await work();\n } catch (error) {\n // The latest run's rejection takes React's own route to the boundary.\n if (id === latest.current) throw error;\n if (!mounted.current) return;\n if (hasSupersededError.current) {\n report(error);\n return;\n }\n hasSupersededError.current = true;\n // After an await, outside the transition: urgent, so the part re-renders and throws.\n setSupersededError({ error });\n return;\n }\n if (openGeneration !== undefined && openGeneration() !== generation) return;\n then?.();\n });\n };\n\n return { pending, run };\n}\n"],"mappings":";;;;AAiBA,SAAS,OAAO,OAAsB;CACpC,IAAI,OAAO,gBAAgB,YAAY,YAAY,KAAK;MACnD,QAAQ,MAAM,KAAK;AAC1B;;;;;;;;AASA,SAAgB,iBAAiB,gBAA8C;CAG7E,MAAM,CAAC,iBAAiB,sBAAsB,SAA6C,IAAI;CAC/F,MAAM,qBAAqB,OAAO,KAAK;CACvC,IAAI,oBAAoB,MAAM,MAAM,gBAAgB;CAEpD,MAAM,CAAC,SAAS,sBAAsB,cAAc;CACpD,MAAM,SAAS,OAAO,CAAC;CACvB,MAAM,UAAU,OAAO,KAAK;CAE5B,gBAAgB;EACd,QAAQ,UAAU;EAClB,aAAa;GACX,QAAQ,UAAU;EACpB;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,OAAO,MAAqB,SAA4B;EAC5D,MAAM,KAAK,EAAE,OAAO;EAGpB,MAAM,aAAa,iBAAiB;EACpC,mBAAmB,YAAY;GAC7B,IAAI;IACF,MAAM,KAAK;GACb,SAAS,OAAO;IAEd,IAAI,OAAO,OAAO,SAAS,MAAM;IACjC,IAAI,CAAC,QAAQ,SAAS;IACtB,IAAI,mBAAmB,SAAS;KAC9B,OAAO,KAAK;KACZ;IACF;IACA,mBAAmB,UAAU;IAE7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B;GACF;GACA,IAAI,mBAAmB,KAAA,KAAa,eAAe,MAAM,YAAY;GACrE,OAAO;EACT,CAAC;CACH;CAEA,OAAO;EAAE;EAAS;CAAI;AACxB"}
|
|
@@ -76,12 +76,16 @@ function useControllableAction(options) {
|
|
|
76
76
|
const { count, next } = inFlight.current;
|
|
77
77
|
return count > 0 ? { next } : null;
|
|
78
78
|
}
|
|
79
|
+
function adopt(next) {
|
|
80
|
+
if (!isControlled) setInternal(next);
|
|
81
|
+
}
|
|
79
82
|
return {
|
|
80
83
|
committed,
|
|
81
84
|
optimistic,
|
|
82
85
|
pending,
|
|
83
86
|
dispatch,
|
|
84
|
-
latestInFlight
|
|
87
|
+
latestInFlight,
|
|
88
|
+
adopt
|
|
85
89
|
};
|
|
86
90
|
}
|
|
87
91
|
function useDevWarnings(isControlled, hasDefault, warn) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-controllable-action.mjs","names":[],"sources":["../../src/internal/use-controllable-action.ts"],"sourcesContent":["\"use client\";\nimport {\n addTransitionType,\n startTransition,\n useEffect,\n useOptimistic,\n useRef,\n useState,\n useTransition,\n} from \"react\";\nimport type { ActionDetails, ChangeAction } from \"./types.js\";\n\nexport interface UseControllableActionOptions<T, Details extends ActionDetails> {\n /** The controlled prop (`x`). `undefined` means uncontrolled. */\n controlled: T | undefined;\n /** The `defaultX` prop as the consumer passed it; `undefined` when omitted. */\n defaultValue: T | undefined;\n /** Initial uncontrolled value when neither `x` nor `defaultX` is given. */\n initialValue: T;\n /** The `xChangeAction` prop. Awaited inside this hook's transition. */\n action: ChangeAction<T, Details> | undefined;\n /**\n * View Transition types to attach to the commit that ends a dispatch. Receives the previous\n * committed value and the details object (Tabs derives next/prev direction from them).\n */\n transitionTypes?: (next: T, prev: T, details: Details) => readonly string[];\n /**\n * Optimistic reducer (conventions 3.4). Default: replace the value with `next`. Multi-item\n * states (Accordion's open set) pass a reducer keyed by item, reading the item from `details`,\n * so stacked optimistic patches re-base onto a changed committed value.\n */\n reducer?: (prev: T, next: T, details: Details) => T;\n /** Names used in development warnings. */\n warn?: { readonly component: string; readonly prop: string };\n}\n\nexport interface ControllableAction<T, Details extends ActionDetails> {\n /** Committed value (`x ?? internal`). Content regions render this. */\n readonly committed: T;\n /** Optimistic value; trigger parts render this. */\n readonly optimistic: T;\n /** An action dispatched through this hook is in flight. */\n readonly pending: boolean;\n /** Runs the action inside the hook's transition with optimistic feedback. */\n readonly dispatch: (next: T, details: Details) => void;\n /**\n * The `next` of the most recent dispatch while any dispatch's action is still unsettled, else\n * `null`. Lets a component with a browser-owned path (Accordion's find-in-page reveal) build on\n * what the user already intends instead of on the DOM. Read it from handlers and effects only.\n */\n readonly latestInFlight: () => { readonly next: T } | null;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n/** Reports an error that cannot reach a boundary (conventions 2.4: never swallow). */\nfunction report(error: unknown): void {\n if (typeof reportError === \"function\") reportError(error);\n else console.error(error);\n}\n\n/**\n * The one place the state model of conventions section 3 is implemented: committed state,\n * optimistic state (`useOptimistic`, reducer form), the component's own transition, the\n * post-`await` write of uncontrolled state, the latest-dispatch guard, and the surfacing of\n * superseded rejections (conventions 2.4).\n */\nexport function useControllableAction<T, Details extends ActionDetails = ActionDetails>(\n options: UseControllableActionOptions<T, Details>,\n): ControllableAction<T, Details> {\n const { controlled, defaultValue, initialValue, action, transitionTypes, reducer, warn } =\n options;\n\n // React keeps only the newest thenable per `useTransition`, so a rejection from a dispatch that\n // was superseded by a later one would be dropped entirely (no boundary, no `reportError`). The\n // hook catches those itself, keeps the first, and throws it from render so it reaches the same\n // nearest Error Boundary as the latest dispatch's rejection does through React.\n //\n // The throw sits before `useTransition` on purpose: React retries an errored render with every\n // pending lane, and in that retry `useTransition` unwraps the still-pending action thenable and\n // suspends, which would hold the boundary commit until the newer action settles.\n const [supersededError, setSupersededError] = useState<{ readonly error: unknown } | null>(null);\n const hasSupersededError = useRef(false);\n if (supersededError !== null) throw supersededError.error;\n\n const [internal, setInternal] = useState<T>(\n defaultValue === undefined ? initialValue : defaultValue,\n );\n const isControlled = controlled !== undefined;\n const committed = isControlled ? controlled : internal;\n const [optimistic, setOptimistic] = useOptimistic(\n committed,\n (prev: T, patch: { next: T; details: Details }) =>\n reducer ? reducer(prev, patch.next, patch.details) : patch.next,\n );\n const [pending, startOwnTransition] = useTransition();\n const latest = useRef(0);\n const inFlight = useRef<{ count: number; next: T | undefined }>({ count: 0, next: undefined });\n const mounted = useRef(false);\n\n useDevWarnings(isControlled, defaultValue !== undefined, warn);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n function dispatch(next: T, details: Details): void {\n const prev = committed;\n const id = ++latest.current;\n inFlight.current.count += 1;\n inFlight.current.next = next;\n startOwnTransition(async () => {\n for (const type of transitionTypes?.(next, prev, details) ?? []) addTransitionType(type);\n setOptimistic({ next, details });\n try {\n await action?.(next, details);\n } catch (error) {\n settle();\n // The latest dispatch's rejection takes React's own route to the boundary.\n if (id === latest.current) throw error;\n // Nothing to throw to once unmounted (React drops its own thenables the same way).\n if (!mounted.current) return;\n if (hasSupersededError.current) {\n // One boundary reset per burst is enough; later errors are still reported.\n report(error);\n return;\n }\n hasSupersededError.current = true;\n // Outside the transition by now (after an await), so this is an urgent update: the owner\n // re-renders promptly and throws.\n setSupersededError({ error });\n return;\n }\n settle();\n if (!isControlled && id === latest.current) {\n startTransition(() => setInternal(next));\n }\n });\n }\n\n function settle(): void {\n inFlight.current.count -= 1;\n if (inFlight.current.count === 0) inFlight.current.next = undefined;\n }\n\n function latestInFlight(): { readonly next: T } | null {\n const { count, next } = inFlight.current;\n return count > 0 ? { next: next as T } : null;\n }\n\n return { committed, optimistic, pending, dispatch, latestInFlight };\n}\n\nfunction useDevWarnings(\n isControlled: boolean,\n hasDefault: boolean,\n warn: { readonly component: string; readonly prop: string } | undefined,\n): void {\n const initialMode = useRef(isControlled);\n const warnedBoth = useRef(false);\n const warnedSwitch = useRef(false);\n const component = warn?.component ?? \"A component\";\n const prop = warn?.prop ?? \"value\";\n\n useEffect(() => {\n if (!isDev) return;\n const defaultProp = `default${capitalize(prop)}`;\n\n if (isControlled && hasDefault && !warnedBoth.current) {\n warnedBoth.current = true;\n console.error(\n `${component}: both \\`${prop}\\` and \\`${defaultProp}\\` are set. ` +\n `Use \\`${prop}\\` for controlled mode or \\`${defaultProp}\\` for uncontrolled mode, not both.`,\n );\n }\n\n if (initialMode.current !== isControlled && !warnedSwitch.current) {\n warnedSwitch.current = true;\n const from = initialMode.current ? \"controlled\" : \"uncontrolled\";\n const to = isControlled ? \"controlled\" : \"uncontrolled\";\n console.error(\n `${component}: \\`${prop}\\` changed from ${from} to ${to}. Switching modes after mount ` +\n `is not supported; keep \\`${prop}\\` either always defined or always undefined.`,\n );\n }\n }, [component, prop, isControlled, hasDefault]);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-controllable-action.mjs","names":[],"sources":["../../src/internal/use-controllable-action.ts"],"sourcesContent":["\"use client\";\nimport {\n addTransitionType,\n startTransition,\n useEffect,\n useOptimistic,\n useRef,\n useState,\n useTransition,\n} from \"react\";\nimport type { ActionDetails, ChangeAction } from \"./types.js\";\n\nexport interface UseControllableActionOptions<T, Details extends ActionDetails> {\n /** The controlled prop (`x`). `undefined` means uncontrolled. */\n controlled: T | undefined;\n /** The `defaultX` prop as the consumer passed it; `undefined` when omitted. */\n defaultValue: T | undefined;\n /** Initial uncontrolled value when neither `x` nor `defaultX` is given. */\n initialValue: T;\n /** The `xChangeAction` prop. Awaited inside this hook's transition. */\n action: ChangeAction<T, Details> | undefined;\n /**\n * View Transition types to attach to the commit that ends a dispatch. Receives the previous\n * committed value and the details object (Tabs derives next/prev direction from them).\n */\n transitionTypes?: (next: T, prev: T, details: Details) => readonly string[];\n /**\n * Optimistic reducer (conventions 3.4). Default: replace the value with `next`. Multi-item\n * states (Accordion's open set) pass a reducer keyed by item, reading the item from `details`,\n * so stacked optimistic patches re-base onto a changed committed value.\n */\n reducer?: (prev: T, next: T, details: Details) => T;\n /** Names used in development warnings. */\n warn?: { readonly component: string; readonly prop: string };\n}\n\nexport interface ControllableAction<T, Details extends ActionDetails> {\n /** Committed value (`x ?? internal`). Content regions render this. */\n readonly committed: T;\n /** Optimistic value; trigger parts render this. */\n readonly optimistic: T;\n /** An action dispatched through this hook is in flight. */\n readonly pending: boolean;\n /** Runs the action inside the hook's transition with optimistic feedback. */\n readonly dispatch: (next: T, details: Details) => void;\n /**\n * The `next` of the most recent dispatch while any dispatch's action is still unsettled, else\n * `null`. Lets a component with a browser-owned path (Accordion's find-in-page reveal) build on\n * what the user already intends instead of on the DOM. Read it from handlers and effects only.\n */\n readonly latestInFlight: () => { readonly next: T } | null;\n /**\n * Uncontrolled mode only (a no-op when controlled): writes the committed value without calling\n * the action, without an optimistic step and without a transition. For a component that adopts\n * a default the browser chose (Select with neither `value` nor `defaultValue` takes the first\n * enabled option, design/components/select.md 5). Call it from a layout effect so the write is\n * flushed before paint.\n */\n readonly adopt: (next: T) => void;\n}\n\nconst isDev = process.env.NODE_ENV !== \"production\";\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n/** Reports an error that cannot reach a boundary (conventions 2.4: never swallow). */\nfunction report(error: unknown): void {\n if (typeof reportError === \"function\") reportError(error);\n else console.error(error);\n}\n\n/**\n * The one place the state model of conventions section 3 is implemented: committed state,\n * optimistic state (`useOptimistic`, reducer form), the component's own transition, the\n * post-`await` write of uncontrolled state, the latest-dispatch guard, and the surfacing of\n * superseded rejections (conventions 2.4).\n */\nexport function useControllableAction<T, Details extends ActionDetails = ActionDetails>(\n options: UseControllableActionOptions<T, Details>,\n): ControllableAction<T, Details> {\n const { controlled, defaultValue, initialValue, action, transitionTypes, reducer, warn } =\n options;\n\n // React keeps only the newest thenable per `useTransition`, so a rejection from a dispatch that\n // was superseded by a later one would be dropped entirely (no boundary, no `reportError`). The\n // hook catches those itself, keeps the first, and throws it from render so it reaches the same\n // nearest Error Boundary as the latest dispatch's rejection does through React.\n //\n // The throw sits before `useTransition` on purpose: React retries an errored render with every\n // pending lane, and in that retry `useTransition` unwraps the still-pending action thenable and\n // suspends, which would hold the boundary commit until the newer action settles.\n const [supersededError, setSupersededError] = useState<{ readonly error: unknown } | null>(null);\n const hasSupersededError = useRef(false);\n if (supersededError !== null) throw supersededError.error;\n\n const [internal, setInternal] = useState<T>(\n defaultValue === undefined ? initialValue : defaultValue,\n );\n const isControlled = controlled !== undefined;\n const committed = isControlled ? controlled : internal;\n const [optimistic, setOptimistic] = useOptimistic(\n committed,\n (prev: T, patch: { next: T; details: Details }) =>\n reducer ? reducer(prev, patch.next, patch.details) : patch.next,\n );\n const [pending, startOwnTransition] = useTransition();\n const latest = useRef(0);\n const inFlight = useRef<{ count: number; next: T | undefined }>({ count: 0, next: undefined });\n const mounted = useRef(false);\n\n useDevWarnings(isControlled, defaultValue !== undefined, warn);\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n function dispatch(next: T, details: Details): void {\n const prev = committed;\n const id = ++latest.current;\n inFlight.current.count += 1;\n inFlight.current.next = next;\n startOwnTransition(async () => {\n for (const type of transitionTypes?.(next, prev, details) ?? []) addTransitionType(type);\n setOptimistic({ next, details });\n try {\n await action?.(next, details);\n } catch (error) {\n settle();\n // The latest dispatch's rejection takes React's own route to the boundary.\n if (id === latest.current) throw error;\n // Nothing to throw to once unmounted (React drops its own thenables the same way).\n if (!mounted.current) return;\n if (hasSupersededError.current) {\n // One boundary reset per burst is enough; later errors are still reported.\n report(error);\n return;\n }\n hasSupersededError.current = true;\n // Outside the transition by now (after an await), so this is an urgent update: the owner\n // re-renders promptly and throws.\n setSupersededError({ error });\n return;\n }\n settle();\n if (!isControlled && id === latest.current) {\n startTransition(() => setInternal(next));\n }\n });\n }\n\n function settle(): void {\n inFlight.current.count -= 1;\n if (inFlight.current.count === 0) inFlight.current.next = undefined;\n }\n\n function latestInFlight(): { readonly next: T } | null {\n const { count, next } = inFlight.current;\n return count > 0 ? { next: next as T } : null;\n }\n\n function adopt(next: T): void {\n if (!isControlled) setInternal(next);\n }\n\n return { committed, optimistic, pending, dispatch, latestInFlight, adopt };\n}\n\nfunction useDevWarnings(\n isControlled: boolean,\n hasDefault: boolean,\n warn: { readonly component: string; readonly prop: string } | undefined,\n): void {\n const initialMode = useRef(isControlled);\n const warnedBoth = useRef(false);\n const warnedSwitch = useRef(false);\n const component = warn?.component ?? \"A component\";\n const prop = warn?.prop ?? \"value\";\n\n useEffect(() => {\n if (!isDev) return;\n const defaultProp = `default${capitalize(prop)}`;\n\n if (isControlled && hasDefault && !warnedBoth.current) {\n warnedBoth.current = true;\n console.error(\n `${component}: both \\`${prop}\\` and \\`${defaultProp}\\` are set. ` +\n `Use \\`${prop}\\` for controlled mode or \\`${defaultProp}\\` for uncontrolled mode, not both.`,\n );\n }\n\n if (initialMode.current !== isControlled && !warnedSwitch.current) {\n warnedSwitch.current = true;\n const from = initialMode.current ? \"controlled\" : \"uncontrolled\";\n const to = isControlled ? \"controlled\" : \"uncontrolled\";\n console.error(\n `${component}: \\`${prop}\\` changed from ${from} to ${to}. Switching modes after mount ` +\n `is not supported; keep \\`${prop}\\` either always defined or always undefined.`,\n );\n }\n }, [component, prop, isControlled, hasDefault]);\n}\n"],"mappings":";;;AA6DA,MAAM,QAAQ,QAAQ,IAAI,aAAa;AAEvC,SAAS,WAAW,GAAmB;CACrC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAE,MAAM,CAAC;AAC9C;;AAGA,SAAS,OAAO,OAAsB;CACpC,IAAI,OAAO,gBAAgB,YAAY,YAAY,KAAK;MACnD,QAAQ,MAAM,KAAK;AAC1B;;;;;;;AAQA,SAAgB,sBACd,SACgC;CAChC,MAAM,EAAE,YAAY,cAAc,cAAc,QAAQ,iBAAiB,SAAS,SAChF;CAUF,MAAM,CAAC,iBAAiB,sBAAsB,SAA6C,IAAI;CAC/F,MAAM,qBAAqB,OAAO,KAAK;CACvC,IAAI,oBAAoB,MAAM,MAAM,gBAAgB;CAEpD,MAAM,CAAC,UAAU,eAAe,SAC9B,iBAAiB,KAAA,IAAY,eAAe,YAC9C;CACA,MAAM,eAAe,eAAe,KAAA;CACpC,MAAM,YAAY,eAAe,aAAa;CAC9C,MAAM,CAAC,YAAY,iBAAiB,cAClC,YACC,MAAS,UACR,UAAU,QAAQ,MAAM,MAAM,MAAM,MAAM,OAAO,IAAI,MAAM,IAC/D;CACA,MAAM,CAAC,SAAS,sBAAsB,cAAc;CACpD,MAAM,SAAS,OAAO,CAAC;CACvB,MAAM,WAAW,OAA+C;EAAE,OAAO;EAAG,MAAM,KAAA;CAAU,CAAC;CAC7F,MAAM,UAAU,OAAO,KAAK;CAE5B,eAAe,cAAc,iBAAiB,KAAA,GAAW,IAAI;CAE7D,gBAAgB;EACd,QAAQ,UAAU;EAClB,aAAa;GACX,QAAQ,UAAU;EACpB;CACF,GAAG,CAAC,CAAC;CAEL,SAAS,SAAS,MAAS,SAAwB;EACjD,MAAM,OAAO;EACb,MAAM,KAAK,EAAE,OAAO;EACpB,SAAS,QAAQ,SAAS;EAC1B,SAAS,QAAQ,OAAO;EACxB,mBAAmB,YAAY;GAC7B,KAAK,MAAM,QAAQ,kBAAkB,MAAM,MAAM,OAAO,KAAK,CAAC,GAAG,kBAAkB,IAAI;GACvF,cAAc;IAAE;IAAM;GAAQ,CAAC;GAC/B,IAAI;IACF,MAAM,SAAS,MAAM,OAAO;GAC9B,SAAS,OAAO;IACd,OAAO;IAEP,IAAI,OAAO,OAAO,SAAS,MAAM;IAEjC,IAAI,CAAC,QAAQ,SAAS;IACtB,IAAI,mBAAmB,SAAS;KAE9B,OAAO,KAAK;KACZ;IACF;IACA,mBAAmB,UAAU;IAG7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B;GACF;GACA,OAAO;GACP,IAAI,CAAC,gBAAgB,OAAO,OAAO,SACjC,sBAAsB,YAAY,IAAI,CAAC;EAE3C,CAAC;CACH;CAEA,SAAS,SAAe;EACtB,SAAS,QAAQ,SAAS;EAC1B,IAAI,SAAS,QAAQ,UAAU,GAAG,SAAS,QAAQ,OAAO,KAAA;CAC5D;CAEA,SAAS,iBAA8C;EACrD,MAAM,EAAE,OAAO,SAAS,SAAS;EACjC,OAAO,QAAQ,IAAI,EAAQ,KAAU,IAAI;CAC3C;CAEA,SAAS,MAAM,MAAe;EAC5B,IAAI,CAAC,cAAc,YAAY,IAAI;CACrC;CAEA,OAAO;EAAE;EAAW;EAAY;EAAS;EAAU;EAAgB;CAAM;AAC3E;AAEA,SAAS,eACP,cACA,YACA,MACM;CACN,MAAM,cAAc,OAAO,YAAY;CACvC,MAAM,aAAa,OAAO,KAAK;CAC/B,MAAM,eAAe,OAAO,KAAK;CACjC,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,OAAO,MAAM,QAAQ;CAE3B,gBAAgB;EACd,IAAI,CAAC,OAAO;EACZ,MAAM,cAAc,UAAU,WAAW,IAAI;EAE7C,IAAI,gBAAgB,cAAc,CAAC,WAAW,SAAS;GACrD,WAAW,UAAU;GACrB,QAAQ,MACN,GAAG,UAAU,WAAW,KAAK,WAAW,YAAY,oBACzC,KAAK,8BAA8B,YAAY,oCAC5D;EACF;EAEA,IAAI,YAAY,YAAY,gBAAgB,CAAC,aAAa,SAAS;GACjE,aAAa,UAAU;GACvB,MAAM,OAAO,YAAY,UAAU,eAAe;GAElD,QAAQ,MACN,GAAG,UAAU,MAAM,KAAK,kBAAkB,KAAK,MAFtC,eAAe,eAAe,eAEiB,yDAC1B,KAAK,8CACrC;EACF;CACF,GAAG;EAAC;EAAW;EAAM;EAAc;CAAU,CAAC;AAChD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
//#region src/internal/use-delayed-pair.ts
|
|
4
|
+
/** Plain factory, for code that owns its timers inside an effect (Tooltip). */
|
|
5
|
+
function createDelayedPair() {
|
|
6
|
+
let timer;
|
|
7
|
+
let kind;
|
|
8
|
+
const cancel = () => {
|
|
9
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
10
|
+
timer = void 0;
|
|
11
|
+
kind = void 0;
|
|
12
|
+
};
|
|
13
|
+
const schedule = (next, delay, callback) => {
|
|
14
|
+
cancel();
|
|
15
|
+
kind = next;
|
|
16
|
+
timer = setTimeout(() => {
|
|
17
|
+
timer = void 0;
|
|
18
|
+
kind = void 0;
|
|
19
|
+
callback();
|
|
20
|
+
}, delay);
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
open: (delay, callback) => schedule("open", delay, callback),
|
|
24
|
+
close: (delay, callback) => schedule("close", delay, callback),
|
|
25
|
+
cancelClose: () => {
|
|
26
|
+
if (kind === "close") cancel();
|
|
27
|
+
},
|
|
28
|
+
cancel
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** One `DelayedPair` per component instance, cancelled on unmount. */
|
|
32
|
+
function useDelayedPair() {
|
|
33
|
+
const [pair] = useState(createDelayedPair);
|
|
34
|
+
useEffect(() => pair.cancel, [pair]);
|
|
35
|
+
return pair;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { createDelayedPair, useDelayedPair };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=use-delayed-pair.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-delayed-pair.mjs","names":[],"sources":["../../src/internal/use-delayed-pair.ts"],"sourcesContent":["\"use client\";\nimport { useEffect, useState } from \"react\";\n\n/**\n * An open timer and a close timer that exclude each other: scheduling one cancels the other, so a\n * hover that leaves before the open delay elapses never opens, and coming back before the close\n * delay elapses never closes. Shared by Tooltip's JS path and Menu's submenu hover intent\n * (design/components/menu.md 7).\n */\nexport interface DelayedPair {\n /** Run `callback` after `delay` ms, replacing any pending open or close. */\n readonly open: (delay: number, callback: () => void) => void;\n /** Run `callback` after `delay` ms, replacing any pending open or close. */\n readonly close: (delay: number, callback: () => void) => void;\n /** Cancel a pending close only (a pending open keeps running). */\n readonly cancelClose: () => void;\n /** Cancel whatever is pending. */\n readonly cancel: () => void;\n}\n\n/** Plain factory, for code that owns its timers inside an effect (Tooltip). */\nexport function createDelayedPair(): DelayedPair {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let kind: \"open\" | \"close\" | undefined;\n\n const cancel = (): void => {\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n kind = undefined;\n };\n const schedule = (next: \"open\" | \"close\", delay: number, callback: () => void): void => {\n cancel();\n kind = next;\n timer = setTimeout(() => {\n timer = undefined;\n kind = undefined;\n callback();\n }, delay);\n };\n\n return {\n open: (delay, callback) => schedule(\"open\", delay, callback),\n close: (delay, callback) => schedule(\"close\", delay, callback),\n cancelClose: () => {\n if (kind === \"close\") cancel();\n },\n cancel,\n };\n}\n\n/** One `DelayedPair` per component instance, cancelled on unmount. */\nexport function useDelayedPair(): DelayedPair {\n const [pair] = useState(createDelayedPair);\n useEffect(() => pair.cancel, [pair]);\n return pair;\n}\n"],"mappings":";;;;AAqBA,SAAgB,oBAAiC;CAC/C,IAAI;CACJ,IAAI;CAEJ,MAAM,eAAqB;EACzB,IAAI,UAAU,KAAA,GAAW,aAAa,KAAK;EAC3C,QAAQ,KAAA;EACR,OAAO,KAAA;CACT;CACA,MAAM,YAAY,MAAwB,OAAe,aAA+B;EACtF,OAAO;EACP,OAAO;EACP,QAAQ,iBAAiB;GACvB,QAAQ,KAAA;GACR,OAAO,KAAA;GACP,SAAS;EACX,GAAG,KAAK;CACV;CAEA,OAAO;EACL,OAAO,OAAO,aAAa,SAAS,QAAQ,OAAO,QAAQ;EAC3D,QAAQ,OAAO,aAAa,SAAS,SAAS,OAAO,QAAQ;EAC7D,mBAAmB;GACjB,IAAI,SAAS,SAAS,OAAO;EAC/B;EACA;CACF;AACF;;AAGA,SAAgB,iBAA8B;CAC5C,MAAM,CAAC,QAAQ,SAAS,iBAAiB;CACzC,gBAAgB,KAAK,QAAQ,CAAC,IAAI,CAAC;CACnC,OAAO;AACT"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
//#region src/internal/use-exit-animation.ts
|
|
4
|
+
/** An animation that ends: infinite ones (a spinner) would hold the content forever. */
|
|
5
|
+
function ends(animation) {
|
|
6
|
+
const end = animation.effect?.getComputedTiming().endTime;
|
|
7
|
+
return typeof end === "number" && Number.isFinite(end);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The running exit animations of a top-layer element that has just been hidden, read after a
|
|
11
|
+
* style flush so that `transition-behavior: allow-discrete` exits exist. Empty where the engine
|
|
12
|
+
* does not animate the exit (Firefox) or there is no CSS exit.
|
|
13
|
+
*/
|
|
14
|
+
function exitAnimations(element) {
|
|
15
|
+
if (!element.isConnected || typeof element.getAnimations !== "function") return [];
|
|
16
|
+
getComputedStyle(element).opacity;
|
|
17
|
+
return element.getAnimations({ subtree: true }).filter(ends);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The deferred unmount of a top-layer popup's content while its hide animates. `held` is cleared
|
|
21
|
+
* with a normal (urgent) update, never in a transition: the popup is already hidden, nothing
|
|
22
|
+
* visible changes, and only transition commits start a document view transition, so a dismissal
|
|
23
|
+
* runs one (its notification commit) instead of two (design 4.4).
|
|
24
|
+
*/
|
|
25
|
+
function useExitAnimation() {
|
|
26
|
+
const [held, setHeld] = useState(false);
|
|
27
|
+
const wait = useRef(null);
|
|
28
|
+
const generation = useRef(0);
|
|
29
|
+
const holdUntil = (animations) => {
|
|
30
|
+
const current = ++generation.current;
|
|
31
|
+
wait.current = current;
|
|
32
|
+
setHeld(true);
|
|
33
|
+
Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {
|
|
34
|
+
if (generation.current !== current) return;
|
|
35
|
+
wait.current = null;
|
|
36
|
+
setHeld(false);
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const release = () => {
|
|
40
|
+
generation.current++;
|
|
41
|
+
wait.current = null;
|
|
42
|
+
setHeld(false);
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
held,
|
|
46
|
+
holdUntil,
|
|
47
|
+
release,
|
|
48
|
+
holdDuringRender: () => setHeld(true),
|
|
49
|
+
waiting: () => wait.current !== null
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { exitAnimations, useExitAnimation };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=use-exit-animation.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-exit-animation.mjs","names":[],"sources":["../../src/internal/use-exit-animation.ts"],"sourcesContent":["\"use client\";\nimport { useRef, useState } from \"react\";\n\n/** An animation that ends: infinite ones (a spinner) would hold the content forever. */\nfunction ends(animation: Animation): boolean {\n const end = animation.effect?.getComputedTiming().endTime;\n return typeof end === \"number\" && Number.isFinite(end);\n}\n\n/**\n * The running exit animations of a top-layer element that has just been hidden, read after a\n * style flush so that `transition-behavior: allow-discrete` exits exist. Empty where the engine\n * does not animate the exit (Firefox) or there is no CSS exit.\n */\nexport function exitAnimations(element: Element): Animation[] {\n if (!element.isConnected || typeof element.getAnimations !== \"function\") return [];\n void getComputedStyle(element).opacity;\n return element.getAnimations({ subtree: true }).filter(ends);\n}\n\nexport interface ExitAnimation {\n /** Keep the content mounted (design/components/popover.md 4.4). */\n readonly held: boolean;\n /** Set `held` now (urgent, library-internal) and clear it when `animations` settle. */\n readonly holdUntil: (animations: readonly Animation[]) => void;\n /** Clear `held` now (a normal update, never a transition), cancelling any wait in progress. */\n readonly release: () => void;\n /** Set `held` during render (the \"previous value in state\" pattern). */\n readonly holdDuringRender: () => void;\n /** Whether a `holdUntil` wait is still running. Handlers and effects only. */\n readonly waiting: () => boolean;\n}\n\n/**\n * The deferred unmount of a top-layer popup's content while its hide animates. `held` is cleared\n * with a normal (urgent) update, never in a transition: the popup is already hidden, nothing\n * visible changes, and only transition commits start a document view transition, so a dismissal\n * runs one (its notification commit) instead of two (design 4.4).\n */\nexport function useExitAnimation(): ExitAnimation {\n const [held, setHeld] = useState(false);\n // The generation of the wait in progress, or null; a newer wait supersedes an older one.\n const wait = useRef<number | null>(null);\n const generation = useRef(0);\n\n const holdUntil = (animations: readonly Animation[]): void => {\n const current = ++generation.current;\n wait.current = current;\n setHeld(true);\n void Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {\n if (generation.current !== current) return;\n wait.current = null;\n setHeld(false);\n });\n };\n\n const release = (): void => {\n generation.current++;\n wait.current = null;\n setHeld(false);\n };\n\n return {\n held,\n holdUntil,\n release,\n holdDuringRender: () => setHeld(true),\n waiting: () => wait.current !== null,\n };\n}\n"],"mappings":";;;;AAIA,SAAS,KAAK,WAA+B;CAC3C,MAAM,MAAM,UAAU,QAAQ,kBAAkB,CAAC,CAAC;CAClD,OAAO,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG;AACvD;;;;;;AAOA,SAAgB,eAAe,SAA+B;CAC5D,IAAI,CAAC,QAAQ,eAAe,OAAO,QAAQ,kBAAkB,YAAY,OAAO,CAAC;CACjF,iBAAsB,OAAO,CAAC,CAAC;CAC/B,OAAO,QAAQ,cAAc,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI;AAC7D;;;;;;;AAqBA,SAAgB,mBAAkC;CAChD,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,MAAM,OAAO,OAAsB,IAAI;CACvC,MAAM,aAAa,OAAO,CAAC;CAE3B,MAAM,aAAa,eAA2C;EAC5D,MAAM,UAAU,EAAE,WAAW;EAC7B,KAAK,UAAU;EACf,QAAQ,IAAI;EACZ,QAAa,WAAW,WAAW,KAAK,cAAc,UAAU,QAAQ,CAAC,CAAC,CAAC,WAAW;GACpF,IAAI,WAAW,YAAY,SAAS;GACpC,KAAK,UAAU;GACf,QAAQ,KAAK;EACf,CAAC;CACH;CAEA,MAAM,gBAAsB;EAC1B,WAAW;EACX,KAAK,UAAU;EACf,QAAQ,KAAK;CACf;CAEA,OAAO;EACL;EACA;EACA;EACA,wBAAwB,QAAQ,IAAI;EACpC,eAAe,KAAK,YAAY;CAClC;AACF"}
|
|
@@ -14,10 +14,13 @@ import { useEffectEvent, useLayoutEffect, useRef } from "react";
|
|
|
14
14
|
* (rule 7).
|
|
15
15
|
* - `onReset` always sees the latest committed render's values (`useEffectEvent`).
|
|
16
16
|
*/
|
|
17
|
-
function useFormReset(ref, onReset) {
|
|
17
|
+
function useFormReset(ref, onReset, onResetEvent) {
|
|
18
18
|
const handleReset = useEffectEvent((event) => {
|
|
19
19
|
onReset(event);
|
|
20
20
|
});
|
|
21
|
+
const handleResetEvent = useEffectEvent((event) => {
|
|
22
|
+
onResetEvent?.(event);
|
|
23
|
+
});
|
|
21
24
|
const subscription = useRef(null);
|
|
22
25
|
useLayoutEffect(() => {
|
|
23
26
|
const form = ref.current?.form ?? null;
|
|
@@ -27,6 +30,7 @@ function useFormReset(ref, onReset) {
|
|
|
27
30
|
subscription.current = null;
|
|
28
31
|
if (form === null) return;
|
|
29
32
|
const listener = (event) => {
|
|
33
|
+
handleResetEvent(event);
|
|
30
34
|
queueMicrotask(() => {
|
|
31
35
|
if (event.defaultPrevented) return;
|
|
32
36
|
handleReset(event);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-form-reset.mjs","names":[],"sources":["../../src/internal/use-form-reset.ts"],"sourcesContent":["\"use client\";\nimport { useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/** A form-associated element whose form owner the hook follows. */\nexport type FormAssociatedElement = HTMLInputElement | HTMLFieldSetElement;\n\ninterface Subscription {\n readonly form: HTMLFormElement;\n readonly listener: (event: Event) => void;\n}\n\n/**\n * Stable `reset` subscription on the form owner of `ref.current` (conventions 3.8).\n *\n * - The owner (`element.form`, which honours the `form` attribute) is re-read after every commit,\n * but the listener is moved only when the owner changed. It is never removed and re-added on an\n * ordinary re-render: React's automatic reset after a successful `<form action>` runs in the\n * commit's mutation phase, between an effect's cleanup and its next run, so a subscription that\n * followed every commit would miss it (rule 4).\n * - The `reset` event fires before the reset algorithm, so `onReset` is called in a microtask,\n * when the inputs hold their reset values (rule 5), and only if no listener cancelled the reset\n * (rule 7).\n * - `onReset` always sees the latest committed render's values (`useEffectEvent`).\n */\nexport function useFormReset(\n ref: RefObject<FormAssociatedElement | null>,\n onReset: (event: Event) => void,\n): void {\n const handleReset = useEffectEvent((event: Event) => {\n onReset(event);\n });\n const subscription = useRef<Subscription | null>(null);\n\n // No deps on purpose: follows the form owner after every commit (a changed `form` attribute, the\n // element moved to another form), but re-subscribes only when the owner actually changed.\n useLayoutEffect(() => {\n const form = ref.current?.form ?? null;\n const current = subscription.current;\n if (current !== null && current.form === form) return;\n if (current !== null) current.form.removeEventListener(\"reset\", current.listener);\n subscription.current = null;\n if (form === null) return;\n const listener = (event: Event) => {\n queueMicrotask(() => {\n if (event.defaultPrevented) return;\n handleReset(event);\n });\n };\n form.addEventListener(\"reset\", listener);\n subscription.current = { form, listener };\n });\n\n // The only place the subscription is torn down: unmount (and StrictMode's simulated unmount,\n // after which the effect above subscribes again).\n useLayoutEffect(\n () => () => {\n const current = subscription.current;\n if (current !== null) current.form.removeEventListener(\"reset\", current.listener);\n subscription.current = null;\n },\n [],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,SAAgB,aACd,KACA,
|
|
1
|
+
{"version":3,"file":"use-form-reset.mjs","names":[],"sources":["../../src/internal/use-form-reset.ts"],"sourcesContent":["\"use client\";\nimport { useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/** A form-associated element whose form owner the hook follows. */\nexport type FormAssociatedElement = HTMLInputElement | HTMLFieldSetElement | HTMLSelectElement;\n\ninterface Subscription {\n readonly form: HTMLFormElement;\n readonly listener: (event: Event) => void;\n}\n\n/**\n * Stable `reset` subscription on the form owner of `ref.current` (conventions 3.8).\n *\n * - The owner (`element.form`, which honours the `form` attribute) is re-read after every commit,\n * but the listener is moved only when the owner changed. It is never removed and re-added on an\n * ordinary re-render: React's automatic reset after a successful `<form action>` runs in the\n * commit's mutation phase, between an effect's cleanup and its next run, so a subscription that\n * followed every commit would miss it (rule 4).\n * - The `reset` event fires before the reset algorithm, so `onReset` is called in a microtask,\n * when the inputs hold their reset values (rule 5), and only if no listener cancelled the reset\n * (rule 7).\n * - `onReset` always sees the latest committed render's values (`useEffectEvent`).\n */\nexport function useFormReset(\n ref: RefObject<FormAssociatedElement | null>,\n onReset: (event: Event) => void,\n /**\n * Called synchronously from the `reset` event, before the reset algorithm runs: the last\n * moment to write a reset target the browser will restore (Combobox re-applies its mirrored\n * `defaultValue`, which react-dom's attribute syncing overwrites after every keystroke).\n */\n onResetEvent?: (event: Event) => void,\n): void {\n const handleReset = useEffectEvent((event: Event) => {\n onReset(event);\n });\n const handleResetEvent = useEffectEvent((event: Event) => {\n onResetEvent?.(event);\n });\n const subscription = useRef<Subscription | null>(null);\n\n // No deps on purpose: follows the form owner after every commit (a changed `form` attribute, the\n // element moved to another form), but re-subscribes only when the owner actually changed.\n useLayoutEffect(() => {\n const form = ref.current?.form ?? null;\n const current = subscription.current;\n if (current !== null && current.form === form) return;\n if (current !== null) current.form.removeEventListener(\"reset\", current.listener);\n subscription.current = null;\n if (form === null) return;\n const listener = (event: Event) => {\n handleResetEvent(event);\n queueMicrotask(() => {\n if (event.defaultPrevented) return;\n handleReset(event);\n });\n };\n form.addEventListener(\"reset\", listener);\n subscription.current = { form, listener };\n });\n\n // The only place the subscription is torn down: unmount (and StrictMode's simulated unmount,\n // after which the effect above subscribes again).\n useLayoutEffect(\n () => () => {\n const current = subscription.current;\n if (current !== null) current.form.removeEventListener(\"reset\", current.listener);\n subscription.current = null;\n },\n [],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,SAAgB,aACd,KACA,SAMA,cACM;CACN,MAAM,cAAc,gBAAgB,UAAiB;EACnD,QAAQ,KAAK;CACf,CAAC;CACD,MAAM,mBAAmB,gBAAgB,UAAiB;EACxD,eAAe,KAAK;CACtB,CAAC;CACD,MAAM,eAAe,OAA4B,IAAI;CAIrD,sBAAsB;EACpB,MAAM,OAAO,IAAI,SAAS,QAAQ;EAClC,MAAM,UAAU,aAAa;EAC7B,IAAI,YAAY,QAAQ,QAAQ,SAAS,MAAM;EAC/C,IAAI,YAAY,MAAM,QAAQ,KAAK,oBAAoB,SAAS,QAAQ,QAAQ;EAChF,aAAa,UAAU;EACvB,IAAI,SAAS,MAAM;EACnB,MAAM,YAAY,UAAiB;GACjC,iBAAiB,KAAK;GACtB,qBAAqB;IACnB,IAAI,MAAM,kBAAkB;IAC5B,YAAY,KAAK;GACnB,CAAC;EACH;EACA,KAAK,iBAAiB,SAAS,QAAQ;EACvC,aAAa,UAAU;GAAE;GAAM;EAAS;CAC1C,CAAC;CAID,4BACc;EACV,MAAM,UAAU,aAAa;EAC7B,IAAI,YAAY,MAAM,QAAQ,KAAK,oBAAoB,SAAS,QAAQ,QAAQ;EAChF,aAAa,UAAU;CACzB,GACA,CAAC,CACH;AACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
//#region src/internal/use-hydrated-mount.ts
|
|
4
|
+
const subscribe = () => () => {};
|
|
5
|
+
const clientSnapshot = () => false;
|
|
6
|
+
const serverSnapshot = () => true;
|
|
7
|
+
/**
|
|
8
|
+
* Whether this render hydrates server HTML: `true` in the render that hydrates the component
|
|
9
|
+
* (React uses `useSyncExternalStore`'s server snapshot there, also inside a Suspense boundary that
|
|
10
|
+
* hydrates after its parent) and `false` in a client-created render, including a hydration that
|
|
11
|
+
* fell back to client rendering. After a hydration React re-renders once with the client snapshot
|
|
12
|
+
* (scheduled from a passive effect), so read the value captured by the **mount layout effect**,
|
|
13
|
+
* which belongs to the hydration render (design/components/select.md 6.2).
|
|
14
|
+
*/
|
|
15
|
+
function useHydratedMount() {
|
|
16
|
+
return useSyncExternalStore(subscribe, clientSnapshot, serverSnapshot);
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { useHydratedMount };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=use-hydrated-mount.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-hydrated-mount.mjs","names":[],"sources":["../../src/internal/use-hydrated-mount.ts"],"sourcesContent":["\"use client\";\nimport { useSyncExternalStore } from \"react\";\n\nconst subscribe = () => () => {};\nconst clientSnapshot = () => false;\nconst serverSnapshot = () => true;\n\n/**\n * Whether this render hydrates server HTML: `true` in the render that hydrates the component\n * (React uses `useSyncExternalStore`'s server snapshot there, also inside a Suspense boundary that\n * hydrates after its parent) and `false` in a client-created render, including a hydration that\n * fell back to client rendering. After a hydration React re-renders once with the client snapshot\n * (scheduled from a passive effect), so read the value captured by the **mount layout effect**,\n * which belongs to the hydration render (design/components/select.md 6.2).\n */\nexport function useHydratedMount(): boolean {\n return useSyncExternalStore(subscribe, clientSnapshot, serverSnapshot);\n}\n"],"mappings":";;;AAGA,MAAM,wBAAwB,CAAC;AAC/B,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;;;;;;;;;AAU7B,SAAgB,mBAA4B;CAC1C,OAAO,qBAAqB,WAAW,gBAAgB,cAAc;AACvE"}
|
|
@@ -15,7 +15,7 @@ import { useCallback, useEffectEvent, useLayoutEffect, useRef } from "react";
|
|
|
15
15
|
* - Form reset through `useFormReset` on the container: in a microtask after the reset,
|
|
16
16
|
* `readDefault()` vs `base()`.
|
|
17
17
|
*
|
|
18
|
-
* Values are compared with `Object.is
|
|
18
|
+
* Values are compared with `equals` (default `Object.is`).
|
|
19
19
|
*/
|
|
20
20
|
function useNativeGroupReconciliation(options) {
|
|
21
21
|
const { element } = options;
|
|
@@ -56,7 +56,7 @@ function useNativeGroupReconciliation(options) {
|
|
|
56
56
|
return { onItemMounted };
|
|
57
57
|
}
|
|
58
58
|
function reconcile(options, next, event) {
|
|
59
|
-
if (!Object.is(next, options.base())) options.onPlatformChange(next, event);
|
|
59
|
+
if (!(options.equals ?? Object.is)(next, options.base())) options.onPlatformChange(next, event);
|
|
60
60
|
}
|
|
61
61
|
//#endregion
|
|
62
62
|
export { useNativeGroupReconciliation };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-native-group-reconciliation.mjs","names":[],"sources":["../../src/internal/use-native-group-reconciliation.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\nimport { useFormReset } from \"./use-form-reset.js\";\n\nexport interface UseNativeGroupReconciliationOptions<T> {\n
|
|
1
|
+
{"version":3,"file":"use-native-group-reconciliation.mjs","names":[],"sources":["../../src/internal/use-native-group-reconciliation.ts"],"sourcesContent":["\"use client\";\nimport { useCallback, useEffectEvent, useLayoutEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\nimport { useFormReset } from \"./use-form-reset.js\";\n\nexport interface UseNativeGroupReconciliationOptions<T> {\n /**\n * The group's form-associated element: `RadioGroup.Root`'s / `CheckboxGroup.Root`'s\n * `<fieldset>`, or `Select.Root`'s `<select>` (whose options are the members).\n */\n readonly element: RefObject<HTMLFieldSetElement | HTMLSelectElement | null>;\n /** The group's value as the DOM shows it now (`checked` of the members). */\n readonly readCurrent: () => T;\n /** The value the browser resets the group to (`defaultChecked` of the members, rule 5). */\n readonly readDefault: () => T;\n /**\n * What the user intends (rule 1): the latest in-flight dispatch's `next`, else the optimistic\n * value. Use an explicit null check, `const f = latestInFlight(); return f === null ?\n * optimistic : f.next;`, not `?.next ?? optimistic`, which drops an in-flight dispatch of a\n * nullish value (a pending reset to \"nothing checked\").\n */\n readonly base: () => T;\n /**\n * The browser changed the group's value on a residual path and it differs from `base()`.\n * `event` is the `reset` event, or `undefined` for mount reconciliation (the Root's mount, or a\n * member mounting later). The Root dispatches once for the whole group with\n * `reason: \"platform\"`.\n */\n readonly onPlatformChange: (next: T, event: Event | undefined) => void;\n /**\n * Equality between a read value and `base()`. Default `Object.is`, right for a primitive value\n * (RadioGroup's `string | null`); a group with a collection value (CheckboxGroup's\n * `ReadonlySet<string>`) passes structural equality, since every read builds a new object.\n */\n readonly equals?: ((a: T, b: T) => boolean) | undefined;\n}\n\nexport interface UseNativeGroupReconciliationResult {\n /**\n * Stable. Pass it to every member, which hands it to\n * `useNativeChecked({ group: true, onItemMounted })`; the hook calls it from the member's mount\n * layout effect. A member that mounts after the Root's own mount reconciliation (inside a\n * Suspense boundary that hydrates later, selective hydration, or a member added on the client)\n * makes the adapter reconcile the group again: `readCurrent()` vs `base()`, in a microtask, once\n * for all members mounted in the same commit. Members mounting in the Root's own commit are\n * covered by the Root's mount reconciliation (their effects run before the Root's) and are\n * ignored.\n *\n * RadioGroup wiring: `const { onItemMounted } = useNativeGroupReconciliation(...)` in the Root,\n * put `onItemMounted` in the group context, and in each Item\n * `useNativeChecked({ type: \"radio\", group: true, onItemMounted: ctx.onItemMounted, ... })`.\n * The member's element argument is not used here; the Root may use it (dev warnings).\n */\n readonly onItemMounted: (element?: Element | null) => void;\n}\n\n/**\n * Group adapter of the native-input machinery (conventions 3.8): one dispatch per group for the\n * residual browser paths, from the Root rather than once per member.\n *\n * - Mount reconciliation, once, in a layout effect: `readCurrent()` vs `base()`. The members'\n * `useNativeChecked({ group: true })` leaves `checked` alone in their mount commit, so this\n * effect, which runs after the members' (children first), still reads the hydrated DOM.\n * - Late members: a member mounting in a later commit (a Suspense boundary that hydrates after the\n * Root, where a click before hydration changed the DOM) reports through `onItemMounted`, and the\n * group is reconciled again the same way (see `UseNativeGroupReconciliationResult`).\n * - Form reset through `useFormReset` on the container: in a microtask after the reset,\n * `readDefault()` vs `base()`.\n *\n * Values are compared with `equals` (default `Object.is`).\n */\nexport function useNativeGroupReconciliation<T>(\n options: UseNativeGroupReconciliationOptions<T>,\n): UseNativeGroupReconciliationResult {\n const { element } = options;\n\n // The latest committed options, for the late reconciliation, which runs in a microtask outside\n // any effect (so it cannot go through an Effect Event).\n const latest = useRef(options);\n useLayoutEffect(() => {\n latest.current = options;\n });\n\n /** The Root's own mount reconciliation has run (reset by its cleanup, StrictMode included). */\n const rootReconciled = useRef(false);\n /** A late reconciliation is queued; members mounting in the same commit share it. */\n const lateQueued = useRef(false);\n\n const reconcileMount = useEffectEvent(() => {\n if (element.current === null) return;\n reconcile(options, options.readCurrent());\n });\n useLayoutEffect(() => {\n reconcileMount();\n rootReconciled.current = true;\n return () => {\n rootReconciled.current = false;\n };\n }, []);\n\n const onItemMounted = useCallback(() => {\n if (!rootReconciled.current || lateQueued.current) return;\n lateQueued.current = true;\n queueMicrotask(() => {\n lateQueued.current = false;\n if (!rootReconciled.current) return;\n const current = latest.current;\n if (current.element.current === null) return;\n reconcile(current, current.readCurrent());\n });\n }, []);\n\n useFormReset(element, (event) => {\n if (element.current === null) return;\n reconcile(options, options.readDefault(), event);\n });\n\n return { onItemMounted };\n}\n\nfunction reconcile<T>(options: UseNativeGroupReconciliationOptions<T>, next: T, event?: Event) {\n const equals = options.equals ?? Object.is;\n if (!equals(next, options.base())) options.onPlatformChange(next, event);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,6BACd,SACoC;CACpC,MAAM,EAAE,YAAY;CAIpB,MAAM,SAAS,OAAO,OAAO;CAC7B,sBAAsB;EACpB,OAAO,UAAU;CACnB,CAAC;;CAGD,MAAM,iBAAiB,OAAO,KAAK;;CAEnC,MAAM,aAAa,OAAO,KAAK;CAE/B,MAAM,iBAAiB,qBAAqB;EAC1C,IAAI,QAAQ,YAAY,MAAM;EAC9B,UAAU,SAAS,QAAQ,YAAY,CAAC;CAC1C,CAAC;CACD,sBAAsB;EACpB,eAAe;EACf,eAAe,UAAU;EACzB,aAAa;GACX,eAAe,UAAU;EAC3B;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,CAAC,eAAe,WAAW,WAAW,SAAS;EACnD,WAAW,UAAU;EACrB,qBAAqB;GACnB,WAAW,UAAU;GACrB,IAAI,CAAC,eAAe,SAAS;GAC7B,MAAM,UAAU,OAAO;GACvB,IAAI,QAAQ,QAAQ,YAAY,MAAM;GACtC,UAAU,SAAS,QAAQ,YAAY,CAAC;EAC1C,CAAC;CACH,GAAG,CAAC,CAAC;CAEL,aAAa,UAAU,UAAU;EAC/B,IAAI,QAAQ,YAAY,MAAM;EAC9B,UAAU,SAAS,QAAQ,YAAY,GAAG,KAAK;CACjD,CAAC;CAED,OAAO,EAAE,cAAc;AACzB;AAEA,SAAS,UAAa,SAAiD,MAAS,OAAe;CAE7F,IAAI,EADW,QAAQ,UAAU,OAAO,GAAA,CAC5B,MAAM,QAAQ,KAAK,CAAC,GAAG,QAAQ,iBAAiB,MAAM,KAAK;AACzE"}
|