@mortenbrudvik/waveui 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1221 -173
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/components/button/Button.cjs +2 -0
- package/dist/components/button/Button.cjs.map +1 -0
- package/dist/components/button/Button.mjs +50 -0
- package/dist/components/button/Button.mjs.map +1 -0
- package/dist/components/button/Button.semantics.cjs +2 -0
- package/dist/components/button/Button.semantics.cjs.map +1 -0
- package/dist/components/button/Button.semantics.mjs +61 -0
- package/dist/components/button/Button.semantics.mjs.map +1 -0
- package/dist/components/button/Button.slots.cjs +2 -0
- package/dist/components/button/Button.slots.cjs.map +1 -0
- package/dist/components/button/Button.slots.mjs +47 -0
- package/dist/components/button/Button.slots.mjs.map +1 -0
- package/dist/components/button/CompoundButton.cjs +2 -0
- package/dist/components/button/CompoundButton.cjs.map +1 -0
- package/dist/components/button/CompoundButton.mjs +53 -0
- package/dist/components/button/CompoundButton.mjs.map +1 -0
- package/dist/components/button/Link.cjs +2 -0
- package/dist/components/button/Link.cjs.map +1 -0
- package/dist/components/button/Link.mjs +37 -0
- package/dist/components/button/Link.mjs.map +1 -0
- package/dist/components/button/MenuButton.cjs +2 -0
- package/dist/components/button/MenuButton.cjs.map +1 -0
- package/dist/components/button/MenuButton.mjs +45 -0
- package/dist/components/button/MenuButton.mjs.map +1 -0
- package/dist/components/button/SplitButton.cjs +2 -0
- package/dist/components/button/SplitButton.cjs.map +1 -0
- package/dist/components/button/SplitButton.mjs +66 -0
- package/dist/components/button/SplitButton.mjs.map +1 -0
- package/dist/components/button/ToggleButton.cjs +2 -0
- package/dist/components/button/ToggleButton.cjs.map +1 -0
- package/dist/components/button/ToggleButton.mjs +65 -0
- package/dist/components/button/ToggleButton.mjs.map +1 -0
- package/dist/components/button/Toolbar.cjs +2 -0
- package/dist/components/button/Toolbar.cjs.map +1 -0
- package/dist/components/button/Toolbar.context.cjs +2 -0
- package/dist/components/button/Toolbar.context.cjs.map +1 -0
- package/dist/components/button/Toolbar.context.mjs +31 -0
- package/dist/components/button/Toolbar.context.mjs.map +1 -0
- package/dist/components/button/Toolbar.mjs +63 -0
- package/dist/components/button/Toolbar.mjs.map +1 -0
- package/dist/components/button/Toolbar.parts.cjs +2 -0
- package/dist/components/button/Toolbar.parts.cjs.map +1 -0
- package/dist/components/button/Toolbar.parts.mjs +131 -0
- package/dist/components/button/Toolbar.parts.mjs.map +1 -0
- package/dist/components/button/buttonStyles.cjs +2 -0
- package/dist/components/button/buttonStyles.cjs.map +1 -0
- package/dist/components/button/buttonStyles.mjs +39 -0
- package/dist/components/button/buttonStyles.mjs.map +1 -0
- package/dist/components/data-display/Avatar.cjs +2 -0
- package/dist/components/data-display/Avatar.cjs.map +1 -0
- package/dist/components/data-display/Avatar.mjs +157 -0
- package/dist/components/data-display/Avatar.mjs.map +1 -0
- package/dist/components/data-display/AvatarGroup.cjs +2 -0
- package/dist/components/data-display/AvatarGroup.cjs.map +1 -0
- package/dist/components/data-display/AvatarGroup.mjs +134 -0
- package/dist/components/data-display/AvatarGroup.mjs.map +1 -0
- package/dist/components/data-display/Badge.cjs +2 -0
- package/dist/components/data-display/Badge.cjs.map +1 -0
- package/dist/components/data-display/Badge.colors.cjs +2 -0
- package/dist/components/data-display/Badge.colors.cjs.map +1 -0
- package/dist/components/data-display/Badge.colors.mjs +58 -0
- package/dist/components/data-display/Badge.colors.mjs.map +1 -0
- package/dist/components/data-display/Badge.mjs +32 -0
- package/dist/components/data-display/Badge.mjs.map +1 -0
- package/dist/components/data-display/CounterBadge.cjs +2 -0
- package/dist/components/data-display/CounterBadge.cjs.map +1 -0
- package/dist/components/data-display/CounterBadge.mjs +28 -0
- package/dist/components/data-display/CounterBadge.mjs.map +1 -0
- package/dist/components/data-display/Divider.cjs +2 -0
- package/dist/components/data-display/Divider.cjs.map +1 -0
- package/dist/components/data-display/Divider.mjs +52 -0
- package/dist/components/data-display/Divider.mjs.map +1 -0
- package/dist/components/data-display/Image.cjs +2 -0
- package/dist/components/data-display/Image.cjs.map +1 -0
- package/dist/components/data-display/Image.mjs +32 -0
- package/dist/components/data-display/Image.mjs.map +1 -0
- package/dist/components/data-display/InfoLabel.cjs +2 -0
- package/dist/components/data-display/InfoLabel.cjs.map +1 -0
- package/dist/components/data-display/InfoLabel.mjs +104 -0
- package/dist/components/data-display/InfoLabel.mjs.map +1 -0
- package/dist/components/data-display/List.cjs +2 -0
- package/dist/components/data-display/List.cjs.map +1 -0
- package/dist/components/data-display/List.mjs +381 -0
- package/dist/components/data-display/List.mjs.map +1 -0
- package/dist/components/data-display/List.registry.cjs +2 -0
- package/dist/components/data-display/List.registry.cjs.map +1 -0
- package/dist/components/data-display/List.registry.mjs +101 -0
- package/dist/components/data-display/List.registry.mjs.map +1 -0
- package/dist/components/data-display/Persona.cjs +2 -0
- package/dist/components/data-display/Persona.cjs.map +1 -0
- package/dist/components/data-display/Persona.mjs +48 -0
- package/dist/components/data-display/Persona.mjs.map +1 -0
- package/dist/components/data-display/PresenceBadge.cjs +2 -0
- package/dist/components/data-display/PresenceBadge.cjs.map +1 -0
- package/dist/components/data-display/PresenceBadge.mjs +71 -0
- package/dist/components/data-display/PresenceBadge.mjs.map +1 -0
- package/dist/components/data-display/Tag.cjs +2 -0
- package/dist/components/data-display/Tag.cjs.map +1 -0
- package/dist/components/data-display/Tag.mjs +147 -0
- package/dist/components/data-display/Tag.mjs.map +1 -0
- package/dist/components/feedback/MessageBar.cjs +2 -0
- package/dist/components/feedback/MessageBar.cjs.map +1 -0
- package/dist/components/feedback/MessageBar.mjs +144 -0
- package/dist/components/feedback/MessageBar.mjs.map +1 -0
- package/dist/components/feedback/MessageBar.status.cjs +2 -0
- package/dist/components/feedback/MessageBar.status.cjs.map +1 -0
- package/dist/components/feedback/MessageBar.status.mjs +45 -0
- package/dist/components/feedback/MessageBar.status.mjs.map +1 -0
- package/dist/components/feedback/ProgressBar.cjs +2 -0
- package/dist/components/feedback/ProgressBar.cjs.map +1 -0
- package/dist/components/feedback/ProgressBar.mjs +72 -0
- package/dist/components/feedback/ProgressBar.mjs.map +1 -0
- package/dist/components/feedback/Skeleton.cjs +2 -0
- package/dist/components/feedback/Skeleton.cjs.map +1 -0
- package/dist/components/feedback/Skeleton.mjs +47 -0
- package/dist/components/feedback/Skeleton.mjs.map +1 -0
- package/dist/components/feedback/Spinner.cjs +2 -0
- package/dist/components/feedback/Spinner.cjs.map +1 -0
- package/dist/components/feedback/Spinner.mjs +60 -0
- package/dist/components/feedback/Spinner.mjs.map +1 -0
- package/dist/components/feedback/Toast.cjs +2 -0
- package/dist/components/feedback/Toast.cjs.map +1 -0
- package/dist/components/feedback/Toast.mjs +347 -0
- package/dist/components/feedback/Toast.mjs.map +1 -0
- package/dist/components/input/Checkbox.cjs +2 -0
- package/dist/components/input/Checkbox.cjs.map +1 -0
- package/dist/components/input/Checkbox.mjs +96 -0
- package/dist/components/input/Checkbox.mjs.map +1 -0
- package/dist/components/input/ColorPicker.cjs +6 -0
- package/dist/components/input/ColorPicker.cjs.map +1 -0
- package/dist/components/input/ColorPicker.mjs +236 -0
- package/dist/components/input/ColorPicker.mjs.map +1 -0
- package/dist/components/input/Combobox.cjs +2 -0
- package/dist/components/input/Combobox.cjs.map +1 -0
- package/dist/components/input/Combobox.expand.cjs +2 -0
- package/dist/components/input/Combobox.expand.cjs.map +1 -0
- package/dist/components/input/Combobox.expand.mjs +38 -0
- package/dist/components/input/Combobox.expand.mjs.map +1 -0
- package/dist/components/input/Combobox.mjs +187 -0
- package/dist/components/input/Combobox.mjs.map +1 -0
- package/dist/components/input/DatePicker.cjs +2 -0
- package/dist/components/input/DatePicker.cjs.map +1 -0
- package/dist/components/input/DatePicker.mjs +340 -0
- package/dist/components/input/DatePicker.mjs.map +1 -0
- package/dist/components/input/Dropdown.cjs +2 -0
- package/dist/components/input/Dropdown.cjs.map +1 -0
- package/dist/components/input/Dropdown.mjs +128 -0
- package/dist/components/input/Dropdown.mjs.map +1 -0
- package/dist/components/input/Field.cjs +2 -0
- package/dist/components/input/Field.cjs.map +1 -0
- package/dist/components/input/Field.mjs +200 -0
- package/dist/components/input/Field.mjs.map +1 -0
- package/dist/components/input/Input.cjs +2 -0
- package/dist/components/input/Input.cjs.map +1 -0
- package/dist/components/input/Input.mjs +81 -0
- package/dist/components/input/Input.mjs.map +1 -0
- package/dist/components/input/Label.cjs +2 -0
- package/dist/components/input/Label.cjs.map +1 -0
- package/dist/components/input/Label.mjs +28 -0
- package/dist/components/input/Label.mjs.map +1 -0
- package/dist/components/input/Option.cjs +2 -0
- package/dist/components/input/Option.cjs.map +1 -0
- package/dist/components/input/Option.mjs +169 -0
- package/dist/components/input/Option.mjs.map +1 -0
- package/dist/components/input/RadioGroup.cjs +2 -0
- package/dist/components/input/RadioGroup.cjs.map +1 -0
- package/dist/components/input/RadioGroup.mjs +139 -0
- package/dist/components/input/RadioGroup.mjs.map +1 -0
- package/dist/components/input/Rating.cjs +2 -0
- package/dist/components/input/Rating.cjs.map +1 -0
- package/dist/components/input/Rating.mjs +196 -0
- package/dist/components/input/Rating.mjs.map +1 -0
- package/dist/components/input/SearchBox.cjs +2 -0
- package/dist/components/input/SearchBox.cjs.map +1 -0
- package/dist/components/input/SearchBox.mjs +205 -0
- package/dist/components/input/SearchBox.mjs.map +1 -0
- package/dist/components/input/Select.cjs +2 -0
- package/dist/components/input/Select.cjs.map +1 -0
- package/dist/components/input/Select.mjs +33 -0
- package/dist/components/input/Select.mjs.map +1 -0
- package/dist/components/input/Slider.cjs +2 -0
- package/dist/components/input/Slider.cjs.map +1 -0
- package/dist/components/input/Slider.mjs +79 -0
- package/dist/components/input/Slider.mjs.map +1 -0
- package/dist/components/input/SpinButton.cjs +2 -0
- package/dist/components/input/SpinButton.cjs.map +1 -0
- package/dist/components/input/SpinButton.mjs +157 -0
- package/dist/components/input/SpinButton.mjs.map +1 -0
- package/dist/components/input/SwatchPicker.cjs +2 -0
- package/dist/components/input/SwatchPicker.cjs.map +1 -0
- package/dist/components/input/SwatchPicker.mjs +113 -0
- package/dist/components/input/SwatchPicker.mjs.map +1 -0
- package/dist/components/input/Switch.cjs +2 -0
- package/dist/components/input/Switch.cjs.map +1 -0
- package/dist/components/input/Switch.mjs +92 -0
- package/dist/components/input/Switch.mjs.map +1 -0
- package/dist/components/input/TagPicker.cjs +2 -0
- package/dist/components/input/TagPicker.cjs.map +1 -0
- package/dist/components/input/TagPicker.mjs +249 -0
- package/dist/components/input/TagPicker.mjs.map +1 -0
- package/dist/components/input/Textarea.cjs +2 -0
- package/dist/components/input/Textarea.cjs.map +1 -0
- package/dist/components/input/Textarea.mjs +32 -0
- package/dist/components/input/Textarea.mjs.map +1 -0
- package/dist/components/input/TimePicker.cjs +2 -0
- package/dist/components/input/TimePicker.cjs.map +1 -0
- package/dist/components/input/TimePicker.mjs +292 -0
- package/dist/components/input/TimePicker.mjs.map +1 -0
- package/dist/components/input/colorUtils.cjs +2 -0
- package/dist/components/input/colorUtils.cjs.map +1 -0
- package/dist/components/input/colorUtils.mjs +96 -0
- package/dist/components/input/colorUtils.mjs.map +1 -0
- package/dist/components/input/dateUtils.cjs +2 -0
- package/dist/components/input/dateUtils.cjs.map +1 -0
- package/dist/components/input/dateUtils.mjs +250 -0
- package/dist/components/input/dateUtils.mjs.map +1 -0
- package/dist/components/input/pickerStyles.cjs +2 -0
- package/dist/components/input/pickerStyles.cjs.map +1 -0
- package/dist/components/input/pickerStyles.mjs +10 -0
- package/dist/components/input/pickerStyles.mjs.map +1 -0
- package/dist/components/internal/HiddenInput.cjs +2 -0
- package/dist/components/internal/HiddenInput.cjs.map +1 -0
- package/dist/components/internal/HiddenInput.mjs +88 -0
- package/dist/components/internal/HiddenInput.mjs.map +1 -0
- package/dist/components/layout/Accordion.cjs +2 -0
- package/dist/components/layout/Accordion.cjs.map +1 -0
- package/dist/components/layout/Accordion.mjs +236 -0
- package/dist/components/layout/Accordion.mjs.map +1 -0
- package/dist/components/layout/Card.cjs +2 -0
- package/dist/components/layout/Card.cjs.map +1 -0
- package/dist/components/layout/Card.mjs +121 -0
- package/dist/components/layout/Card.mjs.map +1 -0
- package/dist/components/layout/Carousel.cjs +2 -0
- package/dist/components/layout/Carousel.cjs.map +1 -0
- package/dist/components/layout/Carousel.mjs +205 -0
- package/dist/components/layout/Carousel.mjs.map +1 -0
- package/dist/components/layout/Flex.cjs +2 -0
- package/dist/components/layout/Flex.cjs.map +1 -0
- package/dist/components/layout/Flex.mjs +54 -0
- package/dist/components/layout/Flex.mjs.map +1 -0
- package/dist/components/layout/Grid.cjs +2 -0
- package/dist/components/layout/Grid.cjs.map +1 -0
- package/dist/components/layout/Grid.mjs +62 -0
- package/dist/components/layout/Grid.mjs.map +1 -0
- package/dist/components/layout/Overflow.cjs +2 -0
- package/dist/components/layout/Overflow.cjs.map +1 -0
- package/dist/components/layout/Overflow.mjs +269 -0
- package/dist/components/layout/Overflow.mjs.map +1 -0
- package/dist/components/layout/Stack.cjs +2 -0
- package/dist/components/layout/Stack.cjs.map +1 -0
- package/dist/components/layout/Stack.mjs +39 -0
- package/dist/components/layout/Stack.mjs.map +1 -0
- package/dist/components/layout/TabList.cjs +2 -0
- package/dist/components/layout/TabList.cjs.map +1 -0
- package/dist/components/layout/TabList.mjs +238 -0
- package/dist/components/layout/TabList.mjs.map +1 -0
- package/dist/components/layout/Tree.cjs +2 -0
- package/dist/components/layout/Tree.cjs.map +1 -0
- package/dist/components/layout/Tree.mjs +220 -0
- package/dist/components/layout/Tree.mjs.map +1 -0
- package/dist/components/layout/disclosureIds.cjs +2 -0
- package/dist/components/layout/disclosureIds.cjs.map +1 -0
- package/dist/components/layout/disclosureIds.mjs +12 -0
- package/dist/components/layout/disclosureIds.mjs.map +1 -0
- package/dist/components/motion/Presence.cjs +2 -0
- package/dist/components/motion/Presence.cjs.map +1 -0
- package/dist/components/motion/Presence.mjs +35 -0
- package/dist/components/motion/Presence.mjs.map +1 -0
- package/dist/components/navigation/Breadcrumb.cjs +2 -0
- package/dist/components/navigation/Breadcrumb.cjs.map +1 -0
- package/dist/components/navigation/Breadcrumb.mjs +144 -0
- package/dist/components/navigation/Breadcrumb.mjs.map +1 -0
- package/dist/components/navigation/Menu.cjs +2 -0
- package/dist/components/navigation/Menu.cjs.map +1 -0
- package/dist/components/navigation/Menu.context.cjs +2 -0
- package/dist/components/navigation/Menu.context.cjs.map +1 -0
- package/dist/components/navigation/Menu.context.mjs +82 -0
- package/dist/components/navigation/Menu.context.mjs.map +1 -0
- package/dist/components/navigation/Menu.group.cjs +2 -0
- package/dist/components/navigation/Menu.group.cjs.map +1 -0
- package/dist/components/navigation/Menu.group.mjs +58 -0
- package/dist/components/navigation/Menu.group.mjs.map +1 -0
- package/dist/components/navigation/Menu.items.cjs +2 -0
- package/dist/components/navigation/Menu.items.cjs.map +1 -0
- package/dist/components/navigation/Menu.items.mjs +127 -0
- package/dist/components/navigation/Menu.items.mjs.map +1 -0
- package/dist/components/navigation/Menu.link.cjs +2 -0
- package/dist/components/navigation/Menu.link.cjs.map +1 -0
- package/dist/components/navigation/Menu.link.mjs +41 -0
- package/dist/components/navigation/Menu.link.mjs.map +1 -0
- package/dist/components/navigation/Menu.mjs +27 -0
- package/dist/components/navigation/Menu.mjs.map +1 -0
- package/dist/components/navigation/Menu.popover.cjs +2 -0
- package/dist/components/navigation/Menu.popover.cjs.map +1 -0
- package/dist/components/navigation/Menu.popover.mjs +231 -0
- package/dist/components/navigation/Menu.popover.mjs.map +1 -0
- package/dist/components/navigation/Menu.root.cjs +2 -0
- package/dist/components/navigation/Menu.root.cjs.map +1 -0
- package/dist/components/navigation/Menu.root.mjs +308 -0
- package/dist/components/navigation/Menu.root.mjs.map +1 -0
- package/dist/components/navigation/Menu.selectable.cjs +2 -0
- package/dist/components/navigation/Menu.selectable.cjs.map +1 -0
- package/dist/components/navigation/Menu.selectable.mjs +89 -0
- package/dist/components/navigation/Menu.selectable.mjs.map +1 -0
- package/dist/components/navigation/Menu.shared.cjs +2 -0
- package/dist/components/navigation/Menu.shared.cjs.map +1 -0
- package/dist/components/navigation/Menu.shared.mjs +23 -0
- package/dist/components/navigation/Menu.shared.mjs.map +1 -0
- package/dist/components/navigation/Menu.splitGroup.cjs +2 -0
- package/dist/components/navigation/Menu.splitGroup.cjs.map +1 -0
- package/dist/components/navigation/Menu.splitGroup.mjs +51 -0
- package/dist/components/navigation/Menu.splitGroup.mjs.map +1 -0
- package/dist/components/navigation/Menu.trigger.cjs +2 -0
- package/dist/components/navigation/Menu.trigger.cjs.map +1 -0
- package/dist/components/navigation/Menu.trigger.mjs +79 -0
- package/dist/components/navigation/Menu.trigger.mjs.map +1 -0
- package/dist/components/navigation/Nav.cjs +2 -0
- package/dist/components/navigation/Nav.cjs.map +1 -0
- package/dist/components/navigation/Nav.mjs +276 -0
- package/dist/components/navigation/Nav.mjs.map +1 -0
- package/dist/components/navigation/Pagination.cjs +2 -0
- package/dist/components/navigation/Pagination.cjs.map +1 -0
- package/dist/components/navigation/Pagination.mjs +110 -0
- package/dist/components/navigation/Pagination.mjs.map +1 -0
- package/dist/components/navigation/Stepper.cjs +2 -0
- package/dist/components/navigation/Stepper.cjs.map +1 -0
- package/dist/components/navigation/Stepper.mjs +204 -0
- package/dist/components/navigation/Stepper.mjs.map +1 -0
- package/dist/components/overlays/Dialog.cjs +2 -0
- package/dist/components/overlays/Dialog.cjs.map +1 -0
- package/dist/components/overlays/Dialog.mjs +180 -0
- package/dist/components/overlays/Dialog.mjs.map +1 -0
- package/dist/components/overlays/Dialog.shared.cjs +2 -0
- package/dist/components/overlays/Dialog.shared.cjs.map +1 -0
- package/dist/components/overlays/Dialog.shared.mjs +227 -0
- package/dist/components/overlays/Dialog.shared.mjs.map +1 -0
- package/dist/components/overlays/Drawer.cjs +2 -0
- package/dist/components/overlays/Drawer.cjs.map +1 -0
- package/dist/components/overlays/Drawer.mjs +192 -0
- package/dist/components/overlays/Drawer.mjs.map +1 -0
- package/dist/components/overlays/Popover.cjs +2 -0
- package/dist/components/overlays/Popover.cjs.map +1 -0
- package/dist/components/overlays/Popover.mjs +365 -0
- package/dist/components/overlays/Popover.mjs.map +1 -0
- package/dist/components/overlays/Popover.shared.cjs +2 -0
- package/dist/components/overlays/Popover.shared.cjs.map +1 -0
- package/dist/components/overlays/Popover.shared.mjs +182 -0
- package/dist/components/overlays/Popover.shared.mjs.map +1 -0
- package/dist/components/overlays/TeachingPopover.cjs +2 -0
- package/dist/components/overlays/TeachingPopover.cjs.map +1 -0
- package/dist/components/overlays/TeachingPopover.mjs +181 -0
- package/dist/components/overlays/TeachingPopover.mjs.map +1 -0
- package/dist/components/overlays/Tooltip.cjs +2 -0
- package/dist/components/overlays/Tooltip.cjs.map +1 -0
- package/dist/components/overlays/Tooltip.mjs +160 -0
- package/dist/components/overlays/Tooltip.mjs.map +1 -0
- package/dist/components/portal/Portal.cjs +2 -0
- package/dist/components/portal/Portal.cjs.map +1 -0
- package/dist/components/portal/Portal.mjs +45 -0
- package/dist/components/portal/Portal.mjs.map +1 -0
- package/dist/components/provider/WaveProvider.cjs +2 -0
- package/dist/components/provider/WaveProvider.cjs.map +1 -0
- package/dist/components/provider/WaveProvider.mjs +51 -0
- package/dist/components/provider/WaveProvider.mjs.map +1 -0
- package/dist/components/table/DataGrid.cjs +2 -0
- package/dist/components/table/DataGrid.cjs.map +1 -0
- package/dist/components/table/DataGrid.mjs +489 -0
- package/dist/components/table/DataGrid.mjs.map +1 -0
- package/dist/components/table/Table.cjs +2 -0
- package/dist/components/table/Table.cjs.map +1 -0
- package/dist/components/table/Table.mjs +99 -0
- package/dist/components/table/Table.mjs.map +1 -0
- package/dist/components/table/useGridNavigation.cjs +2 -0
- package/dist/components/table/useGridNavigation.cjs.map +1 -0
- package/dist/components/table/useGridNavigation.mjs +336 -0
- package/dist/components/table/useGridNavigation.mjs.map +1 -0
- package/dist/components/typography/Text.cjs +2 -0
- package/dist/components/typography/Text.cjs.map +1 -0
- package/dist/components/typography/Text.mjs +38 -0
- package/dist/components/typography/Text.mjs.map +1 -0
- package/dist/hooks/useAnnounce.cjs +2 -0
- package/dist/hooks/useAnnounce.cjs.map +1 -0
- package/dist/hooks/useAnnounce.mjs +88 -0
- package/dist/hooks/useAnnounce.mjs.map +1 -0
- package/dist/hooks/useCheckedValues.cjs +2 -0
- package/dist/hooks/useCheckedValues.cjs.map +1 -0
- package/dist/hooks/useCheckedValues.mjs +102 -0
- package/dist/hooks/useCheckedValues.mjs.map +1 -0
- package/dist/hooks/useContextMenuAnchor.cjs +2 -0
- package/dist/hooks/useContextMenuAnchor.cjs.map +1 -0
- package/dist/hooks/useContextMenuAnchor.mjs +132 -0
- package/dist/hooks/useContextMenuAnchor.mjs.map +1 -0
- package/dist/hooks/useControllable.cjs +2 -0
- package/dist/hooks/useControllable.cjs.map +1 -0
- package/dist/hooks/useControllable.mjs +37 -0
- package/dist/hooks/useControllable.mjs.map +1 -0
- package/dist/hooks/useDirection.cjs +2 -0
- package/dist/hooks/useDirection.cjs.map +1 -0
- package/dist/hooks/useDirection.mjs +57 -0
- package/dist/hooks/useDirection.mjs.map +1 -0
- package/dist/hooks/useDismiss.cjs +2 -0
- package/dist/hooks/useDismiss.cjs.map +1 -0
- package/dist/hooks/useDismiss.mjs +57 -0
- package/dist/hooks/useDismiss.mjs.map +1 -0
- package/dist/hooks/useEventCallback.cjs +2 -0
- package/dist/hooks/useEventCallback.cjs.map +1 -0
- package/dist/hooks/useEventCallback.mjs +16 -0
- package/dist/hooks/useEventCallback.mjs.map +1 -0
- package/dist/hooks/useFieldControl.cjs +2 -0
- package/dist/hooks/useFieldControl.cjs.map +1 -0
- package/dist/hooks/useFieldControl.mjs +62 -0
- package/dist/hooks/useFieldControl.mjs.map +1 -0
- package/dist/hooks/useFocusTrap.cjs +2 -0
- package/dist/hooks/useFocusTrap.cjs.map +1 -0
- package/dist/hooks/useFocusTrap.mjs +266 -0
- package/dist/hooks/useFocusTrap.mjs.map +1 -0
- package/dist/hooks/useFormReset.cjs +2 -0
- package/dist/hooks/useFormReset.cjs.map +1 -0
- package/dist/hooks/useFormReset.mjs +33 -0
- package/dist/hooks/useFormReset.mjs.map +1 -0
- package/dist/hooks/useHoverIntent.cjs +2 -0
- package/dist/hooks/useHoverIntent.cjs.map +1 -0
- package/dist/hooks/useHoverIntent.mjs +201 -0
- package/dist/hooks/useHoverIntent.mjs.map +1 -0
- package/dist/hooks/useId.cjs +2 -0
- package/dist/hooks/useId.cjs.map +1 -0
- package/dist/hooks/useId.mjs +11 -0
- package/dist/hooks/useId.mjs.map +1 -0
- package/dist/hooks/useIsClient.cjs +2 -0
- package/dist/hooks/useIsClient.cjs.map +1 -0
- package/dist/hooks/useIsClient.mjs +19 -0
- package/dist/hooks/useIsClient.mjs.map +1 -0
- package/dist/hooks/useListbox.cjs +2 -0
- package/dist/hooks/useListbox.cjs.map +1 -0
- package/dist/hooks/useListbox.mjs +492 -0
- package/dist/hooks/useListbox.mjs.map +1 -0
- package/dist/hooks/useMergedRefs.cjs +2 -0
- package/dist/hooks/useMergedRefs.cjs.map +1 -0
- package/dist/hooks/useMergedRefs.mjs +57 -0
- package/dist/hooks/useMergedRefs.mjs.map +1 -0
- package/dist/hooks/useModalIsolation.cjs +2 -0
- package/dist/hooks/useModalIsolation.cjs.map +1 -0
- package/dist/hooks/useModalIsolation.mjs +87 -0
- package/dist/hooks/useModalIsolation.mjs.map +1 -0
- package/dist/hooks/useModalLayer.cjs +2 -0
- package/dist/hooks/useModalLayer.cjs.map +1 -0
- package/dist/hooks/useModalLayer.mjs +30 -0
- package/dist/hooks/useModalLayer.mjs.map +1 -0
- package/dist/hooks/usePopupPosition.cjs +2 -0
- package/dist/hooks/usePopupPosition.cjs.map +1 -0
- package/dist/hooks/usePopupPosition.mjs +117 -0
- package/dist/hooks/usePopupPosition.mjs.map +1 -0
- package/dist/hooks/usePrefersReducedMotion.cjs +2 -0
- package/dist/hooks/usePrefersReducedMotion.cjs.map +1 -0
- package/dist/hooks/usePrefersReducedMotion.mjs +24 -0
- package/dist/hooks/usePrefersReducedMotion.mjs.map +1 -0
- package/dist/hooks/usePresence.cjs +2 -0
- package/dist/hooks/usePresence.cjs.map +1 -0
- package/dist/hooks/usePresence.mjs +136 -0
- package/dist/hooks/usePresence.mjs.map +1 -0
- package/dist/hooks/usePreserveFocus.cjs +2 -0
- package/dist/hooks/usePreserveFocus.cjs.map +1 -0
- package/dist/hooks/usePreserveFocus.mjs +50 -0
- package/dist/hooks/usePreserveFocus.mjs.map +1 -0
- package/dist/hooks/useRestoreFocus.cjs +2 -0
- package/dist/hooks/useRestoreFocus.cjs.map +1 -0
- package/dist/hooks/useRestoreFocus.mjs +271 -0
- package/dist/hooks/useRestoreFocus.mjs.map +1 -0
- package/dist/hooks/useRovingTabIndex.cjs +2 -0
- package/dist/hooks/useRovingTabIndex.cjs.map +1 -0
- package/dist/hooks/useRovingTabIndex.mjs +389 -0
- package/dist/hooks/useRovingTabIndex.mjs.map +1 -0
- package/dist/hooks/useScrollLock.cjs +2 -0
- package/dist/hooks/useScrollLock.cjs.map +1 -0
- package/dist/hooks/useScrollLock.mjs +71 -0
- package/dist/hooks/useScrollLock.mjs.map +1 -0
- package/dist/hooks/useTriggerElement.cjs +2 -0
- package/dist/hooks/useTriggerElement.cjs.map +1 -0
- package/dist/hooks/useTriggerElement.mjs +126 -0
- package/dist/hooks/useTriggerElement.mjs.map +1 -0
- package/dist/hooks/useTypeahead.cjs +2 -0
- package/dist/hooks/useTypeahead.cjs.map +1 -0
- package/dist/hooks/useTypeahead.mjs +38 -0
- package/dist/hooks/useTypeahead.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +9099 -0
- package/dist/index.d.ts +7688 -1027
- package/dist/index.mjs +91 -5695
- package/dist/lib/aria.cjs +2 -0
- package/dist/lib/aria.cjs.map +1 -0
- package/dist/lib/aria.mjs +29 -0
- package/dist/lib/aria.mjs.map +1 -0
- package/dist/lib/children.cjs +2 -0
- package/dist/lib/children.cjs.map +1 -0
- package/dist/lib/children.mjs +37 -0
- package/dist/lib/children.mjs.map +1 -0
- package/dist/lib/cn.cjs +2 -0
- package/dist/lib/cn.cjs.map +1 -0
- package/dist/lib/cn.mjs +64 -0
- package/dist/lib/cn.mjs.map +1 -0
- package/dist/lib/composeEventHandlers.cjs +2 -0
- package/dist/lib/composeEventHandlers.cjs.map +1 -0
- package/dist/lib/composeEventHandlers.mjs +11 -0
- package/dist/lib/composeEventHandlers.mjs.map +1 -0
- package/dist/lib/dev.cjs +2 -0
- package/dist/lib/dev.cjs.map +1 -0
- package/dist/lib/dev.mjs +42 -0
- package/dist/lib/dev.mjs.map +1 -0
- package/dist/lib/direction.cjs +2 -0
- package/dist/lib/direction.cjs.map +1 -0
- package/dist/lib/direction.mjs +35 -0
- package/dist/lib/direction.mjs.map +1 -0
- package/dist/lib/events.cjs +2 -0
- package/dist/lib/events.cjs.map +1 -0
- package/dist/lib/events.mjs +31 -0
- package/dist/lib/events.mjs.map +1 -0
- package/dist/lib/focus.cjs +2 -0
- package/dist/lib/focus.cjs.map +1 -0
- package/dist/lib/focus.mjs +119 -0
- package/dist/lib/focus.mjs.map +1 -0
- package/dist/lib/globalRegistry.cjs +2 -0
- package/dist/lib/globalRegistry.cjs.map +1 -0
- package/dist/lib/globalRegistry.mjs +13 -0
- package/dist/lib/globalRegistry.mjs.map +1 -0
- package/dist/lib/icons.cjs +2 -0
- package/dist/lib/icons.cjs.map +1 -0
- package/dist/lib/icons.mjs +254 -0
- package/dist/lib/icons.mjs.map +1 -0
- package/dist/lib/labelInName.cjs +2 -0
- package/dist/lib/labelInName.cjs.map +1 -0
- package/dist/lib/labelInName.mjs +54 -0
- package/dist/lib/labelInName.mjs.map +1 -0
- package/dist/lib/layers.cjs +2 -0
- package/dist/lib/layers.cjs.map +1 -0
- package/dist/lib/layers.mjs +224 -0
- package/dist/lib/layers.mjs.map +1 -0
- package/dist/lib/mergeProps.cjs +2 -0
- package/dist/lib/mergeProps.cjs.map +1 -0
- package/dist/lib/mergeProps.mjs +21 -0
- package/dist/lib/mergeProps.mjs.map +1 -0
- package/dist/lib/mergeRefs.cjs +2 -0
- package/dist/lib/mergeRefs.cjs.map +1 -0
- package/dist/lib/mergeRefs.mjs +29 -0
- package/dist/lib/mergeRefs.mjs.map +1 -0
- package/dist/lib/renderTrigger.cjs +2 -0
- package/dist/lib/renderTrigger.cjs.map +1 -0
- package/dist/lib/renderTrigger.mjs +29 -0
- package/dist/lib/renderTrigger.mjs.map +1 -0
- package/dist/lib/slot.cjs +2 -0
- package/dist/lib/slot.cjs.map +1 -0
- package/dist/lib/slot.mjs +122 -0
- package/dist/lib/slot.mjs.map +1 -0
- package/dist/lib/styles.cjs +2 -0
- package/dist/lib/styles.cjs.map +1 -0
- package/dist/lib/styles.mjs +16 -0
- package/dist/lib/styles.mjs.map +1 -0
- package/dist/lib/theme.cjs +2 -0
- package/dist/lib/theme.cjs.map +1 -0
- package/dist/lib/theme.mjs +16 -0
- package/dist/lib/theme.mjs.map +1 -0
- package/dist/preflight.css +2 -0
- package/dist/styles.css +2 -0
- package/package.json +149 -100
- package/src/styles/animations.css +7 -39
- package/src/styles/base.css +103 -0
- package/src/styles/globals.css +10 -22
- package/src/styles/legacy-tokens.css +184 -0
- package/src/styles/preflight.css +7 -0
- package/src/styles/styles.css +32 -0
- package/src/styles/tailwind.css +17 -0
- package/src/styles/tokens.css +561 -221
- package/src/styles/variants.css +29 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("../lib/dev.cjs"),t=require("./useControllable.cjs");let n=require("react");var r=Object.freeze({});function i(e,t){return(Object.hasOwn(e,t)?e[t]:void 0)??[]}function a(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,writable:!0,configurable:!0})}function o(e,t,n){let r={};for(let t of Object.keys(e))a(r,t,[...i(e,t)]);return a(r,t,[...n]),r}function s(e,t){return t?(n,r)=>{e(n,r),t(n,r)}:e}function c(e,t){return{values:e.values,isChecked:e.isChecked,toggle:(n,r,i,a)=>e.toggle(n,r,i,s(t,a)),select:(n,r,i,a)=>e.select(n,r,i,s(t,a))}}function l(e,a,s){let c=(0,n.useRef)(void 0),[l,u]=t.useControllable(e,a??r,e=>{let t=c.current;if(!t)return;let n=o(e,t.name,i(e,t.name)),r={name:t.name,checkedItems:n[t.name],event:t.event};s?.(n,r),t.listener?.(n,r)}),d=(0,n.useCallback)((e,t)=>{c.current=e;try{u(t)}finally{c.current=void 0}},[u]),f=(0,n.useCallback)((e,t,n,r)=>{d({name:e,event:n,listener:r},n=>{let r=i(n,e);return o(n,e,r.includes(t)?r.filter(e=>e!==t):[...r,t])})},[d]),p=(0,n.useCallback)((e,t,n,r)=>{d({name:e,event:n,listener:r},n=>{let r=i(n,e);return r.length===1&&r[0]===t?n:o(n,e,[t])})},[d]);return(0,n.useMemo)(()=>({values:l,isChecked:(e,t)=>i(l,e).includes(t),toggle:f,select:p}),[l,f,p])}var u=()=>{};function d(t,r){let[i]=(0,n.useState)(()=>new Map);return(0,n.useCallback)((n,a)=>{if(!e.isDev)return u;let o=JSON.stringify([n,a]),s=(i.get(o)??0)+1;return i.set(o,s),s>1&&e.warnOnce(t,r(n,a)),()=>{let e=(i.get(o)??1)-1;e>0?i.set(o,e):i.delete(o)}},[i,t,r])}exports.useCheckedValues=l,exports.useDuplicatePairRegistry=d,exports.withCheckedValuesListener=c;
|
|
2
|
+
//# sourceMappingURL=useCheckedValues.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCheckedValues.cjs","names":["useRef","useControllable","useCallback","useMemo","useState","isDev","require_dev","require_useControllable","react","EMPTY","groupOf","values","name","setGroup","items","withGroup","next","key","chainListeners","second","first","details","withCheckedValuesListener","api","value","event","listener","inner","useCheckedValues","requestRef","setValues","checkedValues","defaultCheckedValues","request","emitted","onCheckedValuesChange","change","update","toggle","previous","item","select","noop","useDuplicatePairRegistry","counts","count","warnKey","message","remaining"],"sources":["../../src/hooks/useCheckedValues.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\nimport { isDev, warnOnce } from '../lib/dev';\nimport type {\n CheckedValues,\n CheckedValuesChangeDetails,\n CheckedValuesChangeHandler,\n} from '../lib/types';\nimport { useControllable } from './useControllable';\n\n/** The checked-values state of a Menu tree or a Toolbar. */\nexport interface CheckedValuesApi {\n /** The rendered checked values (controlled, or the internal state). */\n readonly values: CheckedValues;\n /** Whether `value` is checked in group `name`. */\n isChecked(name: string, value: string): boolean;\n /**\n * Adds `value` to group `name` (at the end) or removes it: checkbox and switch items, toggles.\n * `listener` (a sharing submenu's `onCheckedValuesChange`) is called after the owner's callback\n * with the same arguments, only on change.\n */\n toggle(name: string, value: string, event: Event, listener?: CheckedValuesChangeHandler): void;\n /** Makes `value` the only checked value of group `name` (radio items); no change when it is. */\n select(name: string, value: string, event: Event, listener?: CheckedValuesChangeHandler): void;\n}\n\n/** The empty state: one frozen object, so an absent default keeps a stable identity. */\nconst EMPTY: CheckedValues = Object.freeze({});\n\n/** The change being made: the group, the event behind it and the request's listener. */\ninterface ChangeRequest {\n name: string;\n event: Event;\n listener: CheckedValuesChangeHandler | undefined;\n}\n\n/**\n * The items of group `name`: an own property only, so a name that is an `Object.prototype` key\n * (`constructor`, `toString`, `__proto__`) reads as an empty group like any other unknown name.\n */\nfunction groupOf(values: CheckedValues, name: string): readonly string[] {\n return (Object.hasOwn(values, name) ? values[name] : undefined) ?? [];\n}\n\n/** Sets group `name` as an own property (an assignment to `__proto__` would set the prototype). */\nfunction setGroup(values: Record<string, string[]>, name: string, items: string[]): void {\n Object.defineProperty(values, name, {\n value: items,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n}\n\n/** A copy of `values` in which every group is a new array, and group `name` holds `items`. */\nfunction withGroup(\n values: CheckedValues,\n name: string,\n items: readonly string[],\n): Record<string, string[]> {\n const next: Record<string, string[]> = {};\n for (const key of Object.keys(values)) setGroup(next, key, [...groupOf(values, key)]);\n setGroup(next, name, [...items]);\n return next;\n}\n\n/** `second` after `first`, or `first` alone. */\nfunction chainListeners(\n first: CheckedValuesChangeHandler,\n second: CheckedValuesChangeHandler | undefined,\n): CheckedValuesChangeHandler {\n if (!second) return first;\n return (values, details) => {\n first(values, details);\n second(values, details);\n };\n}\n\n/**\n * The API a submenu that shares its parent's state passes to its items: `toggle` and `select`\n * forward to `api` with a listener that calls `listener` first, then any listener the caller\n * passed, so a nested chain calls the owner's callback, then the outer submenu's, then the inner\n * one's. Memoize the result on `api` and `listener`.\n */\nexport function withCheckedValuesListener(\n api: CheckedValuesApi,\n listener: CheckedValuesChangeHandler,\n): CheckedValuesApi {\n return {\n values: api.values,\n isChecked: api.isChecked,\n toggle: (name, value, event, inner) =>\n api.toggle(name, value, event, chainListeners(listener, inner)),\n select: (name, value, event, inner) =>\n api.select(name, value, event, chainListeners(listener, inner)),\n };\n}\n\n/**\n * The checked-values state of a Menu tree or a Toolbar (`checkedValues`, `defaultCheckedValues`,\n * `onCheckedValuesChange`), through `useControllable`: controlled or uncontrolled, the callback\n * fires only on change, once per change (StrictMode included), and two changes made in one event\n * chain.\n *\n * - `toggle` and `select` compute the next value from the value the user sees. The next value is\n * a new object in which every group is a new array; an unknown group reads as empty (a name\n * that is an `Object.prototype` key, such as `constructor` or `__proto__`, included: groups are\n * own properties), and removing a value keeps the order of the others. `select` of the value\n * that already is the group's only value changes nothing and calls nothing.\n * - The callback receives a copy of the new values (every array copied again), so a consumer who\n * mutates it, or stores it with `setState`, never changes the state the hook renders; `details`\n * is `{ name, checkedItems, event }`, `checkedItems` being `checkedValues[name]` of that copy.\n * A request's `listener` is then called with the same two arguments.\n * - The returned object is memoized on the rendered values (C-MEMO); `toggle` and `select` keep\n * their identity.\n *\n * Internal (Menu, Toolbar).\n */\nexport function useCheckedValues(\n checkedValues: CheckedValues | undefined,\n defaultCheckedValues: CheckedValues | undefined,\n onCheckedValuesChange: CheckedValuesChangeHandler | undefined,\n): CheckedValuesApi {\n // The request behind the change being made, set around the setter: useControllable calls its\n // onChange synchronously inside it, and only on change (the useModalOpenState pattern).\n const requestRef = useRef<ChangeRequest | undefined>(undefined);\n\n const [values, setValues] = useControllable<CheckedValues>(\n checkedValues,\n defaultCheckedValues ?? EMPTY,\n (next) => {\n const request = requestRef.current;\n if (!request) return;\n const emitted = withGroup(next, request.name, groupOf(next, request.name));\n const details: CheckedValuesChangeDetails = {\n name: request.name,\n checkedItems: emitted[request.name],\n event: request.event,\n };\n onCheckedValuesChange?.(emitted, details);\n request.listener?.(emitted, details);\n },\n );\n\n const change = useCallback(\n (request: ChangeRequest, update: (previous: CheckedValues) => CheckedValues) => {\n requestRef.current = request;\n try {\n setValues(update);\n } finally {\n requestRef.current = undefined;\n }\n },\n [setValues],\n );\n\n const toggle = useCallback<CheckedValuesApi['toggle']>(\n (name, value, event, listener) => {\n change({ name, event, listener }, (previous) => {\n const items = groupOf(previous, name);\n const next = items.includes(value)\n ? items.filter((item) => item !== value)\n : [...items, value];\n return withGroup(previous, name, next);\n });\n },\n [change],\n );\n\n const select = useCallback<CheckedValuesApi['select']>(\n (name, value, event, listener) => {\n change({ name, event, listener }, (previous) => {\n const items = groupOf(previous, name);\n if (items.length === 1 && items[0] === value) return previous;\n return withGroup(previous, name, [value]);\n });\n },\n [change],\n );\n\n return useMemo<CheckedValuesApi>(\n () => ({\n values,\n isChecked: (name, value) => groupOf(values, name).includes(value),\n toggle,\n select,\n }),\n [values, toggle, select],\n );\n}\n\nconst noop = () => {};\n\n/**\n * The duplicate check of one owner of checked values (a menu list, a toolbar): each item\n * registers its `name`/`value` pair from an effect and unregisters in the cleanup (the returned\n * function). Registering a pair that is already registered warns once under `warnKey` with\n * `message(name, value)`, since both items would show as checked.\n *\n * - The counts live as long as the caller, per owner: the same pair in two owners is fine, and\n * StrictMode's effect, cleanup, effect counts an item once.\n * - The function keeps its identity while `warnKey` and `message` do, so pass a module-level\n * `message` (the owner puts the function in a memoized context value, C-MEMO).\n * - Development only: in production registering counts nothing and returns a no-op.\n *\n * Internal (Menu, Toolbar).\n */\nexport function useDuplicatePairRegistry(\n warnKey: string,\n message: (name: string, value: string) => string,\n): (name: string, value: string) => () => void {\n const [counts] = useState(() => new Map<string, number>());\n return useCallback(\n (name: string, value: string) => {\n if (!isDev) return noop;\n const key = JSON.stringify([name, value]);\n const count = (counts.get(key) ?? 0) + 1;\n counts.set(key, count);\n if (count > 1) warnOnce(warnKey, message(name, value));\n return () => {\n const remaining = (counts.get(key) ?? 1) - 1;\n if (remaining > 0) counts.set(key, remaining);\n else counts.delete(key);\n };\n },\n [counts, warnKey, message],\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { isDev as e, warnOnce as t } from "../lib/dev.mjs";
|
|
3
|
+
import { useControllable as n } from "./useControllable.mjs";
|
|
4
|
+
import { useCallback as r, useMemo as i, useRef as a, useState as o } from "react";
|
|
5
|
+
//#region src/hooks/useCheckedValues.ts
|
|
6
|
+
var s = Object.freeze({});
|
|
7
|
+
function c(e, t) {
|
|
8
|
+
return (Object.hasOwn(e, t) ? e[t] : void 0) ?? [];
|
|
9
|
+
}
|
|
10
|
+
function l(e, t, n) {
|
|
11
|
+
Object.defineProperty(e, t, {
|
|
12
|
+
value: n,
|
|
13
|
+
enumerable: !0,
|
|
14
|
+
writable: !0,
|
|
15
|
+
configurable: !0
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function u(e, t, n) {
|
|
19
|
+
let r = {};
|
|
20
|
+
for (let t of Object.keys(e)) l(r, t, [...c(e, t)]);
|
|
21
|
+
return l(r, t, [...n]), r;
|
|
22
|
+
}
|
|
23
|
+
function d(e, t) {
|
|
24
|
+
return t ? (n, r) => {
|
|
25
|
+
e(n, r), t(n, r);
|
|
26
|
+
} : e;
|
|
27
|
+
}
|
|
28
|
+
function f(e, t) {
|
|
29
|
+
return {
|
|
30
|
+
values: e.values,
|
|
31
|
+
isChecked: e.isChecked,
|
|
32
|
+
toggle: (n, r, i, a) => e.toggle(n, r, i, d(t, a)),
|
|
33
|
+
select: (n, r, i, a) => e.select(n, r, i, d(t, a))
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function p(e, t, o) {
|
|
37
|
+
let l = a(void 0), [d, f] = n(e, t ?? s, (e) => {
|
|
38
|
+
let t = l.current;
|
|
39
|
+
if (!t) return;
|
|
40
|
+
let n = u(e, t.name, c(e, t.name)), r = {
|
|
41
|
+
name: t.name,
|
|
42
|
+
checkedItems: n[t.name],
|
|
43
|
+
event: t.event
|
|
44
|
+
};
|
|
45
|
+
o?.(n, r), t.listener?.(n, r);
|
|
46
|
+
}), p = r((e, t) => {
|
|
47
|
+
l.current = e;
|
|
48
|
+
try {
|
|
49
|
+
f(t);
|
|
50
|
+
} finally {
|
|
51
|
+
l.current = void 0;
|
|
52
|
+
}
|
|
53
|
+
}, [f]), m = r((e, t, n, r) => {
|
|
54
|
+
p({
|
|
55
|
+
name: e,
|
|
56
|
+
event: n,
|
|
57
|
+
listener: r
|
|
58
|
+
}, (n) => {
|
|
59
|
+
let r = c(n, e);
|
|
60
|
+
return u(n, e, r.includes(t) ? r.filter((e) => e !== t) : [...r, t]);
|
|
61
|
+
});
|
|
62
|
+
}, [p]), h = r((e, t, n, r) => {
|
|
63
|
+
p({
|
|
64
|
+
name: e,
|
|
65
|
+
event: n,
|
|
66
|
+
listener: r
|
|
67
|
+
}, (n) => {
|
|
68
|
+
let r = c(n, e);
|
|
69
|
+
return r.length === 1 && r[0] === t ? n : u(n, e, [t]);
|
|
70
|
+
});
|
|
71
|
+
}, [p]);
|
|
72
|
+
return i(() => ({
|
|
73
|
+
values: d,
|
|
74
|
+
isChecked: (e, t) => c(d, e).includes(t),
|
|
75
|
+
toggle: m,
|
|
76
|
+
select: h
|
|
77
|
+
}), [
|
|
78
|
+
d,
|
|
79
|
+
m,
|
|
80
|
+
h
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
var m = () => {};
|
|
84
|
+
function h(n, i) {
|
|
85
|
+
let [a] = o(() => /* @__PURE__ */ new Map());
|
|
86
|
+
return r((r, o) => {
|
|
87
|
+
if (!e) return m;
|
|
88
|
+
let s = JSON.stringify([r, o]), c = (a.get(s) ?? 0) + 1;
|
|
89
|
+
return a.set(s, c), c > 1 && t(n, i(r, o)), () => {
|
|
90
|
+
let e = (a.get(s) ?? 1) - 1;
|
|
91
|
+
e > 0 ? a.set(s, e) : a.delete(s);
|
|
92
|
+
};
|
|
93
|
+
}, [
|
|
94
|
+
a,
|
|
95
|
+
n,
|
|
96
|
+
i
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { p as useCheckedValues, h as useDuplicatePairRegistry, f as withCheckedValuesListener };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=useCheckedValues.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCheckedValues.mjs","names":["EMPTY","groupOf","values","name","setGroup","items","withGroup","next","key","chainListeners","second","first","details","withCheckedValuesListener","api","value","event","listener","inner","useCheckedValues","requestRef","useRef","setValues","useControllable","checkedValues","defaultCheckedValues","request","emitted","onCheckedValuesChange","change","useCallback","update","toggle","previous","item","select","useMemo","noop","useDuplicatePairRegistry","counts","useState","isDev","count","warnOnce","warnKey","message","remaining"],"sources":["../../src/hooks/useCheckedValues.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react';\nimport { isDev, warnOnce } from '../lib/dev';\nimport type {\n CheckedValues,\n CheckedValuesChangeDetails,\n CheckedValuesChangeHandler,\n} from '../lib/types';\nimport { useControllable } from './useControllable';\n\n/** The checked-values state of a Menu tree or a Toolbar. */\nexport interface CheckedValuesApi {\n /** The rendered checked values (controlled, or the internal state). */\n readonly values: CheckedValues;\n /** Whether `value` is checked in group `name`. */\n isChecked(name: string, value: string): boolean;\n /**\n * Adds `value` to group `name` (at the end) or removes it: checkbox and switch items, toggles.\n * `listener` (a sharing submenu's `onCheckedValuesChange`) is called after the owner's callback\n * with the same arguments, only on change.\n */\n toggle(name: string, value: string, event: Event, listener?: CheckedValuesChangeHandler): void;\n /** Makes `value` the only checked value of group `name` (radio items); no change when it is. */\n select(name: string, value: string, event: Event, listener?: CheckedValuesChangeHandler): void;\n}\n\n/** The empty state: one frozen object, so an absent default keeps a stable identity. */\nconst EMPTY: CheckedValues = Object.freeze({});\n\n/** The change being made: the group, the event behind it and the request's listener. */\ninterface ChangeRequest {\n name: string;\n event: Event;\n listener: CheckedValuesChangeHandler | undefined;\n}\n\n/**\n * The items of group `name`: an own property only, so a name that is an `Object.prototype` key\n * (`constructor`, `toString`, `__proto__`) reads as an empty group like any other unknown name.\n */\nfunction groupOf(values: CheckedValues, name: string): readonly string[] {\n return (Object.hasOwn(values, name) ? values[name] : undefined) ?? [];\n}\n\n/** Sets group `name` as an own property (an assignment to `__proto__` would set the prototype). */\nfunction setGroup(values: Record<string, string[]>, name: string, items: string[]): void {\n Object.defineProperty(values, name, {\n value: items,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n}\n\n/** A copy of `values` in which every group is a new array, and group `name` holds `items`. */\nfunction withGroup(\n values: CheckedValues,\n name: string,\n items: readonly string[],\n): Record<string, string[]> {\n const next: Record<string, string[]> = {};\n for (const key of Object.keys(values)) setGroup(next, key, [...groupOf(values, key)]);\n setGroup(next, name, [...items]);\n return next;\n}\n\n/** `second` after `first`, or `first` alone. */\nfunction chainListeners(\n first: CheckedValuesChangeHandler,\n second: CheckedValuesChangeHandler | undefined,\n): CheckedValuesChangeHandler {\n if (!second) return first;\n return (values, details) => {\n first(values, details);\n second(values, details);\n };\n}\n\n/**\n * The API a submenu that shares its parent's state passes to its items: `toggle` and `select`\n * forward to `api` with a listener that calls `listener` first, then any listener the caller\n * passed, so a nested chain calls the owner's callback, then the outer submenu's, then the inner\n * one's. Memoize the result on `api` and `listener`.\n */\nexport function withCheckedValuesListener(\n api: CheckedValuesApi,\n listener: CheckedValuesChangeHandler,\n): CheckedValuesApi {\n return {\n values: api.values,\n isChecked: api.isChecked,\n toggle: (name, value, event, inner) =>\n api.toggle(name, value, event, chainListeners(listener, inner)),\n select: (name, value, event, inner) =>\n api.select(name, value, event, chainListeners(listener, inner)),\n };\n}\n\n/**\n * The checked-values state of a Menu tree or a Toolbar (`checkedValues`, `defaultCheckedValues`,\n * `onCheckedValuesChange`), through `useControllable`: controlled or uncontrolled, the callback\n * fires only on change, once per change (StrictMode included), and two changes made in one event\n * chain.\n *\n * - `toggle` and `select` compute the next value from the value the user sees. The next value is\n * a new object in which every group is a new array; an unknown group reads as empty (a name\n * that is an `Object.prototype` key, such as `constructor` or `__proto__`, included: groups are\n * own properties), and removing a value keeps the order of the others. `select` of the value\n * that already is the group's only value changes nothing and calls nothing.\n * - The callback receives a copy of the new values (every array copied again), so a consumer who\n * mutates it, or stores it with `setState`, never changes the state the hook renders; `details`\n * is `{ name, checkedItems, event }`, `checkedItems` being `checkedValues[name]` of that copy.\n * A request's `listener` is then called with the same two arguments.\n * - The returned object is memoized on the rendered values (C-MEMO); `toggle` and `select` keep\n * their identity.\n *\n * Internal (Menu, Toolbar).\n */\nexport function useCheckedValues(\n checkedValues: CheckedValues | undefined,\n defaultCheckedValues: CheckedValues | undefined,\n onCheckedValuesChange: CheckedValuesChangeHandler | undefined,\n): CheckedValuesApi {\n // The request behind the change being made, set around the setter: useControllable calls its\n // onChange synchronously inside it, and only on change (the useModalOpenState pattern).\n const requestRef = useRef<ChangeRequest | undefined>(undefined);\n\n const [values, setValues] = useControllable<CheckedValues>(\n checkedValues,\n defaultCheckedValues ?? EMPTY,\n (next) => {\n const request = requestRef.current;\n if (!request) return;\n const emitted = withGroup(next, request.name, groupOf(next, request.name));\n const details: CheckedValuesChangeDetails = {\n name: request.name,\n checkedItems: emitted[request.name],\n event: request.event,\n };\n onCheckedValuesChange?.(emitted, details);\n request.listener?.(emitted, details);\n },\n );\n\n const change = useCallback(\n (request: ChangeRequest, update: (previous: CheckedValues) => CheckedValues) => {\n requestRef.current = request;\n try {\n setValues(update);\n } finally {\n requestRef.current = undefined;\n }\n },\n [setValues],\n );\n\n const toggle = useCallback<CheckedValuesApi['toggle']>(\n (name, value, event, listener) => {\n change({ name, event, listener }, (previous) => {\n const items = groupOf(previous, name);\n const next = items.includes(value)\n ? items.filter((item) => item !== value)\n : [...items, value];\n return withGroup(previous, name, next);\n });\n },\n [change],\n );\n\n const select = useCallback<CheckedValuesApi['select']>(\n (name, value, event, listener) => {\n change({ name, event, listener }, (previous) => {\n const items = groupOf(previous, name);\n if (items.length === 1 && items[0] === value) return previous;\n return withGroup(previous, name, [value]);\n });\n },\n [change],\n );\n\n return useMemo<CheckedValuesApi>(\n () => ({\n values,\n isChecked: (name, value) => groupOf(values, name).includes(value),\n toggle,\n select,\n }),\n [values, toggle, select],\n );\n}\n\nconst noop = () => {};\n\n/**\n * The duplicate check of one owner of checked values (a menu list, a toolbar): each item\n * registers its `name`/`value` pair from an effect and unregisters in the cleanup (the returned\n * function). Registering a pair that is already registered warns once under `warnKey` with\n * `message(name, value)`, since both items would show as checked.\n *\n * - The counts live as long as the caller, per owner: the same pair in two owners is fine, and\n * StrictMode's effect, cleanup, effect counts an item once.\n * - The function keeps its identity while `warnKey` and `message` do, so pass a module-level\n * `message` (the owner puts the function in a memoized context value, C-MEMO).\n * - Development only: in production registering counts nothing and returns a no-op.\n *\n * Internal (Menu, Toolbar).\n */\nexport function useDuplicatePairRegistry(\n warnKey: string,\n message: (name: string, value: string) => string,\n): (name: string, value: string) => () => void {\n const [counts] = useState(() => new Map<string, number>());\n return useCallback(\n (name: string, value: string) => {\n if (!isDev) return noop;\n const key = JSON.stringify([name, value]);\n const count = (counts.get(key) ?? 0) + 1;\n counts.set(key, count);\n if (count > 1) warnOnce(warnKey, message(name, value));\n return () => {\n const remaining = (counts.get(key) ?? 1) - 1;\n if (remaining > 0) counts.set(key, remaining);\n else counts.delete(key);\n };\n },\n [counts, warnKey, message],\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("./useEventCallback.cjs"),t=require("../lib/layers.cjs"),n=require("../lib/events.cjs");let r=require("react");var i=2;function a(e){return e.ctrlKey||e.altKey||e.metaKey?!1:e.key===`ContextMenu`||e.key===`F10`&&e.shiftKey}function o(e,t,n){let r={x:e,y:t,width:0,height:0,top:t,right:e,bottom:t,left:e};return{getBoundingClientRect:()=>r,contextElement:n}}function s(e){if(!e.isConnected)return!0;let t=e.getBoundingClientRect();return t.width===0&&t.height===0}function c(c){let{enabled:l,open:u,trigger:d,layerId:f,onOpen:p,onClose:m}=c,[h,g]=(0,r.useState)(null),[_,v]=(0,r.useState)(!1),[y,b]=(0,r.useState)(u),x=h?.pending??!1,S=_;u===y?u&&x&&(S=!0):(b(u),u&&(S=x)),S!==_&&v(S),h&&x&&g({...h,pending:!1});let C=(0,r.useRef)(null),w=(0,r.useRef)(null),T=(0,r.useRef)([]),E=e.useEventCallback(p),D=e.useEventCallback(m),O=(0,r.useCallback)(()=>{w.current?.clear(),w.current=null},[]),k=(0,r.useCallback)(e=>{O();let t=()=>O(),n=e=>{(e.ctrlKey||e.altKey||e.metaKey||e.key!==`ContextMenu`&&(e.key!==`F10`||!e.shiftKey))&&O()};e.addEventListener(`pointerdown`,t,!0),e.addEventListener(`keydown`,n,!0),w.current={clear:()=>{e.removeEventListener(`pointerdown`,t,!0),e.removeEventListener(`keydown`,n,!0)}}},[O]),A=(0,r.useCallback)(e=>{let n=e.activeElement;C.current=n&&n!==e.body&&!t.isInsideLayerTree(f,n)?n:null},[f]),j=(0,r.useCallback)((e,t,n,r)=>{let i=(e,t)=>({element:e,rect:e.getBoundingClientRect(),optional:t});T.current=n.map(e=>i(e,!1)),r&&T.current.push(i(r,!0)),g({anchor:e,origin:t,pending:!0})},[]),M=(0,r.useCallback)(e=>{if(!l||e.defaultPrevented||!n.isOwnEvent(e)||n.isDisabledTrigger(e)||n.isEditableTarget(e.target)||(e.preventDefault(),w.current))return;let t=e.currentTarget;A(t.ownerDocument);let r=e.target;j(o(e.clientX,e.clientY,t),`pointer`,[t],r===t?void 0:r),E(`pointer`,e.nativeEvent)},[l,A,j,E]),N=(0,r.useCallback)(e=>{if(!l||!a(e)||e.defaultPrevented||!n.isOwnEvent(e)||n.isDisabledTrigger(e)||n.isEditableTarget(e.target))return;e.preventDefault();let t=e.currentTarget;k(t.ownerDocument),A(t.ownerDocument);let r=e.target,i=r===t?null:r;j(i,`keyboard`,[i??t]),E(`keyboard`,e.nativeEvent)},[l,k,A,j,E]),P=(0,r.useCallback)(e=>{l&&e.preventDefault()},[l]);(0,r.useLayoutEffect)(()=>{u&&!S&&(C.current=null)},[u,S]),(0,r.useEffect)(()=>{u||O()},[u,O]),(0,r.useEffect)(()=>O,[O]),(0,r.useEffect)(()=>{if(!l||!u||!_||!d)return;let e=d.ownerDocument,n=e=>!(e&&typeof e.nodeType==`number`)||!t.isInsideLayerTree(f,e),r=e=>{let t=e.target;!n(t)||t&&d.contains(t)||D(`outside-context-menu`,e)},a=e=>{n(e.target)&&T.current.some(({element:e,rect:t,optional:n})=>{if(n&&s(e))return!1;let r=e.getBoundingClientRect();return Math.abs(r.left-t.left)>i||Math.abs(r.top-t.top)>i})&&D(`scroll`,e)};return e.addEventListener(`contextmenu`,r,!0),e.addEventListener(`scroll`,a,!0),()=>{e.removeEventListener(`contextmenu`,r,!0),e.removeEventListener(`scroll`,a,!0)}},[l,u,_,d,f,D]);let F=(0,r.useMemo)(()=>({onContextMenu:M,onKeyDown:N}),[M,N]),I=(0,r.useMemo)(()=>({onContextMenu:P}),[P]);return{anchor:h?.anchor??null,fromContext:S,origin:h?.origin??null,opener:C,triggerHandlers:F,surfaceHandlers:I}}exports.useContextMenuAnchor=c;
|
|
2
|
+
//# sourceMappingURL=useContextMenuAnchor.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useContextMenuAnchor.cjs","names":["useState","useRef","useEventCallback","useCallback","isInsideLayerTree","isOwnEvent","isDisabledTrigger","isEditableTarget","useMemo","require_useEventCallback","require_layers","require_events","react","SCROLL_TOLERANCE","isContextKey","event","pointAnchor","rect","y","x","hasNoBox","element","useContextMenuAnchor","options","gesture","setGesture","fromContext","setFromContext","previousOpen","setPreviousOpen","open","pending","nextFromContext","opener","keyboardFlagRef","referencesRef","requestOpen","onOpen","requestClose","onClose","clearKeyboardFlag","setKeyboardFlag","clearOnPointer","clearOnKey","doc","recordOpener","active","layerId","startGesture","record","references","optionalReference","onTriggerContextMenu","enabled","region","target","onTriggerKeyDown","anchor","onSurfaceContextMenu","trigger","isOutside","onContextMenu","onScroll","optional","now","triggerHandlers","surfaceHandlers"],"sources":["../../src/hooks/useContextMenuAnchor.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type * as React from 'react';\nimport { isDisabledTrigger, isEditableTarget, isOwnEvent } from '../lib/events';\nimport { isInsideLayerTree } from '../lib/layers';\nimport type { PopupRect, VirtualElement } from '../lib/types';\nimport { useEventCallback } from './useEventCallback';\n\n/** Where a context gesture came from. */\nexport type ContextOrigin = 'pointer' | 'keyboard';\n\n/** Options of {@link useContextMenuAnchor}. */\nexport interface UseContextMenuAnchorOptions {\n /** Whether context gestures on the trigger open the surface (`openOnContext`). */\n enabled: boolean;\n /** Whether the surface is open. */\n open: boolean;\n /** The trigger element (the context region), held in state: the point's `contextElement`. */\n trigger: HTMLElement | null;\n /** The surface's dismiss layer: context menus and scrolls inside its tree are inside. */\n layerId: string;\n /** A context gesture on the trigger asks to open (or, while open, to move to the new anchor). */\n onOpen: (origin: ContextOrigin, event: Event) => void;\n /** A context menu or an anchor-moving scroll outside asks a context-opened surface to close. */\n onClose: (reason: 'outside-context-menu' | 'scroll', event: Event) => void;\n}\n\n/** Returned by {@link useContextMenuAnchor}. Destructure it (react-hooks/refs, like usePresence). */\nexport interface ContextMenuAnchor {\n /**\n * The positioning anchor of the last context gesture: a zero-size `VirtualElement` at the\n * pointer (pointer origin), or the element that had focus when the key was pressed (keyboard\n * origin; `null` when that was the trigger itself). Kept until the next gesture, so an exiting\n * surface does not jump.\n */\n anchor: HTMLElement | VirtualElement | null;\n /** Whether the open surface was opened (last) by a context gesture; kept like `anchor`. */\n fromContext: boolean;\n /** The origin of the last gesture. */\n origin: ContextOrigin | null;\n /**\n * The element focused at the gesture that opened the surface (not `<body>`, not inside the\n * surface's layer tree), else `null`: the first focus-return target of a context-opened surface.\n * Kept through its close; an open without a gesture (a controlled open) forgets it.\n */\n opener: React.RefObject<HTMLElement | null>;\n /** Compose onto the trigger. */\n triggerHandlers: {\n onContextMenu: React.MouseEventHandler<HTMLElement>;\n onKeyDown: React.KeyboardEventHandler<HTMLElement>;\n };\n /**\n * Compose onto the surface: while `enabled`, the browser's context menu never opens over it.\n * With `enabled: false` the handler does nothing, so a component may compose it in every mode\n * and only its context mode suppresses the browser's menu.\n */\n surfaceHandlers: { onContextMenu: React.MouseEventHandler<HTMLElement> };\n}\n\n/** Pixels a gesture's reference element may move before an outside scroll closes the surface. */\nconst SCROLL_TOLERANCE = 2;\n\n/** Whether a keydown is the keyboard context gesture: Shift+F10 or the ContextMenu key. */\nfunction isContextKey(event: React.KeyboardEvent): boolean {\n if (event.ctrlKey || event.altKey || event.metaKey) return false;\n return event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey);\n}\n\n/** A zero-size anchor at a viewport point, scrolled with `contextElement`'s containers. */\nfunction pointAnchor(x: number, y: number, contextElement: Element): VirtualElement {\n const rect: PopupRect = { x, y, width: 0, height: 0, top: y, right: x, bottom: y, left: x };\n return { getBoundingClientRect: () => rect, contextElement };\n}\n\n/** An element whose movement since the gesture closes the surface, with its rectangle then. */\ninterface ScrollReference {\n element: Element;\n rect: DOMRect;\n /**\n * The element a pointer gesture landed on: once it has no box (removed, or `display: none`,\n * such as a row's hover-only action), it no longer counts, and the region alone decides.\n */\n optional: boolean;\n}\n\n/** Whether an element has left the document or the layout (it has no box). */\nfunction hasNoBox(element: Element): boolean {\n if (!element.isConnected) return true;\n const rect = element.getBoundingClientRect();\n return rect.width === 0 && rect.height === 0;\n}\n\n/** The last gesture: its anchor and origin, and whether it has yet to decide `fromContext`. */\ninterface Gesture {\n anchor: HTMLElement | VirtualElement | null;\n origin: ContextOrigin;\n /** A gesture of the hook asked to open in this update (decides `fromContext`). */\n pending: boolean;\n}\n\n/**\n * Context-menu gestures on a trigger region (`openOnContext` on Menu and Popover): a right click\n * (a macOS Ctrl+click, a long press where the browser reports one) opens at the pointer, Shift+F10\n * and the ContextMenu key open at the focused element inside the region. Internal until the\n * positioning, dismiss and hover pieces become public (ROADMAP P7-01).\n *\n * 1. **Origin.** The trigger's keydown of Shift+F10 or ContextMenu sets a keyboard flag, which the\n * next `pointerdown` anywhere, the next keydown of another key or the surface closing clear. A\n * `contextmenu` on the trigger while the flag is set is the browser's event for that key press:\n * it is prevented and changes nothing. Every other `contextmenu` is a pointer gesture at its\n * point, whatever its `button` or pointer type.\n * 2. **Trigger `contextmenu`** (not default-prevented, not from a portal rendered inside the\n * trigger, not from an `aria-disabled` element at or inside it, not from an editable field:\n * those keep the browser's menu and open nothing): prevented; the focused element is recorded\n * as `opener`; a pointer gesture stores a zero-size `VirtualElement` at the point (its\n * `contextElement` is the trigger) as `anchor`; then `onOpen('pointer', event)`. A second\n * gesture while open does the same (the surface moves).\n * 3. **Trigger keydown** of Shift+F10 or ContextMenu (same filters): prevented, the flag is set,\n * `opener` recorded, `anchor` = the focused element inside the trigger (else `null`), then\n * `onOpen('keyboard', event)`.\n * 4. **Surface `contextmenu`** is prevented while `enabled`, whether or not a gesture opened the\n * surface. With `enabled: false` it is left to the browser (the surface handler is inert).\n * 5. **While open from a context gesture:** a `contextmenu` outside the layer tree and the trigger\n * calls `onClose('outside-context-menu', event)` without preventing it; a scroll outside the\n * layer tree calls `onClose('scroll', event)` only when a reference element of the gesture moved\n * more than 2 px since the gesture. For a pointer gesture those are the trigger and the element\n * the gesture landed on (the row under the pointer, which a scroll of the region's content\n * moves), the latter only while it has a box: once it is removed or `display: none` (a row's\n * hover-only action), the trigger alone counts. For a keyboard gesture the reference is the\n * keyboard anchor, else the trigger. A scroll that moves none of them (a scroll in an unrelated\n * panel) is ignored; inertial scrolling of the region's content that is still running at the\n * right click moves the row, so it closes the surface.\n * 6. `anchor` and `origin` are replaced at the next gesture, not reset on close. `fromContext` is\n * decided when `open` becomes `true`: `true` when a gesture of the hook caused that open, else\n * `false`; a gesture while open makes it `true` (the surface moves to the gesture). It keeps its\n * value while the surface is closed or exiting. `opener` is kept through the close too, and\n * forgotten (`null`) when the surface opens without a gesture, so a close or Tab after that\n * open never returns to an earlier gesture's element.\n */\nexport function useContextMenuAnchor(options: UseContextMenuAnchorOptions): ContextMenuAnchor {\n const { enabled, open, trigger, layerId, onOpen, onClose } = options;\n\n const [gesture, setGesture] = useState<Gesture | null>(null);\n const [fromContext, setFromContext] = useState(false);\n const [previousOpen, setPreviousOpen] = useState(open);\n\n // `fromContext` during render (C-HOOKS: previous-value state): decided when `open` becomes\n // true, from the gesture requested in the same update.\n const pending = gesture?.pending ?? false;\n let nextFromContext = fromContext;\n if (open !== previousOpen) {\n setPreviousOpen(open);\n if (open) nextFromContext = pending;\n } else if (open && pending) {\n nextFromContext = true;\n }\n if (nextFromContext !== fromContext) setFromContext(nextFromContext);\n if (gesture && pending) setGesture({ ...gesture, pending: false });\n\n const opener = useRef<HTMLElement | null>(null);\n // The keyboard flag and the reference elements' rectangles at the gesture: written in handlers\n // and effects only.\n const keyboardFlagRef = useRef<{ clear: () => void } | null>(null);\n const referencesRef = useRef<ScrollReference[]>([]);\n\n const requestOpen = useEventCallback(onOpen);\n const requestClose = useEventCallback(onClose);\n\n const clearKeyboardFlag = useCallback(() => {\n keyboardFlagRef.current?.clear();\n keyboardFlagRef.current = null;\n }, []);\n\n const setKeyboardFlag = useCallback(\n (doc: Document) => {\n clearKeyboardFlag();\n // Added during the keydown, after its capture phase: they see only later events.\n const clearOnPointer = () => clearKeyboardFlag();\n const clearOnKey = (event: KeyboardEvent) => {\n const again =\n !event.ctrlKey &&\n !event.altKey &&\n !event.metaKey &&\n (event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey));\n if (!again) clearKeyboardFlag();\n };\n doc.addEventListener('pointerdown', clearOnPointer, true);\n doc.addEventListener('keydown', clearOnKey, true);\n keyboardFlagRef.current = {\n clear: () => {\n doc.removeEventListener('pointerdown', clearOnPointer, true);\n doc.removeEventListener('keydown', clearOnKey, true);\n },\n };\n },\n [clearKeyboardFlag],\n );\n\n /** The focused element, unless it is `<body>` or inside the surface's layer tree. */\n const recordOpener = useCallback(\n (doc: Document) => {\n const active = doc.activeElement as HTMLElement | null;\n opener.current =\n active && active !== doc.body && !isInsideLayerTree(layerId, active) ? active : null;\n },\n [layerId],\n );\n\n /**\n * Records the gesture and the rectangles of the elements whose movement closes the surface: the\n * required ones, then an optional one (see `ScrollReference.optional`).\n */\n const startGesture = useCallback(\n (\n anchor: HTMLElement | VirtualElement | null,\n origin: ContextOrigin,\n references: readonly Element[],\n optionalReference?: Element,\n ) => {\n const record = (element: Element, optional: boolean): ScrollReference => ({\n element,\n rect: element.getBoundingClientRect(),\n optional,\n });\n referencesRef.current = references.map((element) => record(element, false));\n if (optionalReference) referencesRef.current.push(record(optionalReference, true));\n setGesture({ anchor, origin, pending: true });\n },\n [],\n );\n\n const onTriggerContextMenu = useCallback(\n (event: React.MouseEvent<HTMLElement>) => {\n if (!enabled || event.defaultPrevented || !isOwnEvent(event)) return;\n if (isDisabledTrigger(event) || isEditableTarget(event.target)) return;\n event.preventDefault();\n // The browser's contextmenu for the key press: the surface stays at its keyboard anchor.\n if (keyboardFlagRef.current) return;\n const region = event.currentTarget;\n recordOpener(region.ownerDocument);\n // The region, and the element the gesture landed on (a row, which scrolls inside the\n // region; `isOwnEvent` keeps it inside) while it keeps a box.\n const target = event.target as Element;\n startGesture(\n pointAnchor(event.clientX, event.clientY, region),\n 'pointer',\n [region],\n target === region ? undefined : target,\n );\n requestOpen('pointer', event.nativeEvent);\n },\n [enabled, recordOpener, startGesture, requestOpen],\n );\n\n const onTriggerKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (!enabled || !isContextKey(event) || event.defaultPrevented || !isOwnEvent(event)) return;\n if (isDisabledTrigger(event) || isEditableTarget(event.target)) return;\n event.preventDefault();\n const region = event.currentTarget;\n setKeyboardFlag(region.ownerDocument);\n recordOpener(region.ownerDocument);\n const target = event.target as HTMLElement;\n const anchor = target !== region ? target : null;\n startGesture(anchor, 'keyboard', [anchor ?? region]);\n requestOpen('keyboard', event.nativeEvent);\n },\n [enabled, setKeyboardFlag, recordOpener, startGesture, requestOpen],\n );\n\n // Only in context mode: a surface that composes this unconditionally keeps the browser's menu\n // (paste, spelling suggestions in its fields) while the hook is disabled.\n const onSurfaceContextMenu = useCallback(\n (event: React.MouseEvent<HTMLElement>) => {\n if (enabled) event.preventDefault();\n },\n [enabled],\n );\n\n // The opener belongs to the gesture: an open that no gesture caused (a controlled open from a\n // toolbar button) forgets the last gesture's, before any close or Tab can return to it. It is\n // kept through the close of a gesture-opened surface, which returns focus to it.\n useLayoutEffect(() => {\n if (open && !nextFromContext) opener.current = null;\n }, [open, nextFromContext]);\n\n // The flag clears when the surface closes; nothing is left behind on unmount.\n useEffect(() => {\n if (!open) clearKeyboardFlag();\n }, [open, clearKeyboardFlag]);\n useEffect(() => clearKeyboardFlag, [clearKeyboardFlag]);\n\n // While open from a context gesture: an outside context menu, or a scroll that moved the anchor.\n useEffect(() => {\n if (!enabled || !open || !fromContext || !trigger) return undefined;\n const doc = trigger.ownerDocument;\n const isOutside = (target: EventTarget | null) =>\n !(target && typeof (target as Node).nodeType === 'number') ||\n !isInsideLayerTree(layerId, target as Node);\n const onContextMenu = (event: MouseEvent) => {\n const target = event.target as Node | null;\n if (!isOutside(target) || (target && trigger.contains(target))) return;\n requestClose('outside-context-menu', event);\n };\n const onScroll = (event: Event) => {\n if (!isOutside(event.target)) return;\n const moved = referencesRef.current.some(({ element, rect, optional }) => {\n if (optional && hasNoBox(element)) return false;\n const now = element.getBoundingClientRect();\n return (\n Math.abs(now.left - rect.left) > SCROLL_TOLERANCE ||\n Math.abs(now.top - rect.top) > SCROLL_TOLERANCE\n );\n });\n if (moved) requestClose('scroll', event);\n };\n doc.addEventListener('contextmenu', onContextMenu, true);\n doc.addEventListener('scroll', onScroll, true);\n return () => {\n doc.removeEventListener('contextmenu', onContextMenu, true);\n doc.removeEventListener('scroll', onScroll, true);\n };\n }, [enabled, open, fromContext, trigger, layerId, requestClose]);\n\n const triggerHandlers = useMemo(\n () => ({ onContextMenu: onTriggerContextMenu, onKeyDown: onTriggerKeyDown }),\n [onTriggerContextMenu, onTriggerKeyDown],\n );\n const surfaceHandlers = useMemo(\n () => ({ onContextMenu: onSurfaceContextMenu }),\n [onSurfaceContextMenu],\n );\n\n return {\n anchor: gesture?.anchor ?? null,\n fromContext: nextFromContext,\n origin: gesture?.origin ?? null,\n opener,\n triggerHandlers,\n surfaceHandlers,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEventCallback as e } from "./useEventCallback.mjs";
|
|
3
|
+
import { isInsideLayerTree as t } from "../lib/layers.mjs";
|
|
4
|
+
import { isDisabledTrigger as n, isEditableTarget as r, isOwnEvent as i } from "../lib/events.mjs";
|
|
5
|
+
import { useCallback as a, useEffect as o, useLayoutEffect as s, useMemo as c, useRef as l, useState as u } from "react";
|
|
6
|
+
//#region src/hooks/useContextMenuAnchor.ts
|
|
7
|
+
var d = 2;
|
|
8
|
+
function f(e) {
|
|
9
|
+
return e.ctrlKey || e.altKey || e.metaKey ? !1 : e.key === "ContextMenu" || e.key === "F10" && e.shiftKey;
|
|
10
|
+
}
|
|
11
|
+
function p(e, t, n) {
|
|
12
|
+
let r = {
|
|
13
|
+
x: e,
|
|
14
|
+
y: t,
|
|
15
|
+
width: 0,
|
|
16
|
+
height: 0,
|
|
17
|
+
top: t,
|
|
18
|
+
right: e,
|
|
19
|
+
bottom: t,
|
|
20
|
+
left: e
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
getBoundingClientRect: () => r,
|
|
24
|
+
contextElement: n
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function m(e) {
|
|
28
|
+
if (!e.isConnected) return !0;
|
|
29
|
+
let t = e.getBoundingClientRect();
|
|
30
|
+
return t.width === 0 && t.height === 0;
|
|
31
|
+
}
|
|
32
|
+
function h(h) {
|
|
33
|
+
let { enabled: g, open: _, trigger: v, layerId: y, onOpen: b, onClose: x } = h, [S, C] = u(null), [w, T] = u(!1), [E, D] = u(_), O = S?.pending ?? !1, k = w;
|
|
34
|
+
_ === E ? _ && O && (k = !0) : (D(_), _ && (k = O)), k !== w && T(k), S && O && C({
|
|
35
|
+
...S,
|
|
36
|
+
pending: !1
|
|
37
|
+
});
|
|
38
|
+
let A = l(null), j = l(null), M = l([]), N = e(b), P = e(x), F = a(() => {
|
|
39
|
+
j.current?.clear(), j.current = null;
|
|
40
|
+
}, []), I = a((e) => {
|
|
41
|
+
F();
|
|
42
|
+
let t = () => F(), n = (e) => {
|
|
43
|
+
(e.ctrlKey || e.altKey || e.metaKey || e.key !== "ContextMenu" && (e.key !== "F10" || !e.shiftKey)) && F();
|
|
44
|
+
};
|
|
45
|
+
e.addEventListener("pointerdown", t, !0), e.addEventListener("keydown", n, !0), j.current = { clear: () => {
|
|
46
|
+
e.removeEventListener("pointerdown", t, !0), e.removeEventListener("keydown", n, !0);
|
|
47
|
+
} };
|
|
48
|
+
}, [F]), L = a((e) => {
|
|
49
|
+
let n = e.activeElement;
|
|
50
|
+
A.current = n && n !== e.body && !t(y, n) ? n : null;
|
|
51
|
+
}, [y]), R = a((e, t, n, r) => {
|
|
52
|
+
let i = (e, t) => ({
|
|
53
|
+
element: e,
|
|
54
|
+
rect: e.getBoundingClientRect(),
|
|
55
|
+
optional: t
|
|
56
|
+
});
|
|
57
|
+
M.current = n.map((e) => i(e, !1)), r && M.current.push(i(r, !0)), C({
|
|
58
|
+
anchor: e,
|
|
59
|
+
origin: t,
|
|
60
|
+
pending: !0
|
|
61
|
+
});
|
|
62
|
+
}, []), z = a((e) => {
|
|
63
|
+
if (!g || e.defaultPrevented || !i(e) || n(e) || r(e.target) || (e.preventDefault(), j.current)) return;
|
|
64
|
+
let t = e.currentTarget;
|
|
65
|
+
L(t.ownerDocument);
|
|
66
|
+
let a = e.target;
|
|
67
|
+
R(p(e.clientX, e.clientY, t), "pointer", [t], a === t ? void 0 : a), N("pointer", e.nativeEvent);
|
|
68
|
+
}, [
|
|
69
|
+
g,
|
|
70
|
+
L,
|
|
71
|
+
R,
|
|
72
|
+
N
|
|
73
|
+
]), B = a((e) => {
|
|
74
|
+
if (!g || !f(e) || e.defaultPrevented || !i(e) || n(e) || r(e.target)) return;
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
let t = e.currentTarget;
|
|
77
|
+
I(t.ownerDocument), L(t.ownerDocument);
|
|
78
|
+
let a = e.target, o = a === t ? null : a;
|
|
79
|
+
R(o, "keyboard", [o ?? t]), N("keyboard", e.nativeEvent);
|
|
80
|
+
}, [
|
|
81
|
+
g,
|
|
82
|
+
I,
|
|
83
|
+
L,
|
|
84
|
+
R,
|
|
85
|
+
N
|
|
86
|
+
]), V = a((e) => {
|
|
87
|
+
g && e.preventDefault();
|
|
88
|
+
}, [g]);
|
|
89
|
+
s(() => {
|
|
90
|
+
_ && !k && (A.current = null);
|
|
91
|
+
}, [_, k]), o(() => {
|
|
92
|
+
_ || F();
|
|
93
|
+
}, [_, F]), o(() => F, [F]), o(() => {
|
|
94
|
+
if (!g || !_ || !w || !v) return;
|
|
95
|
+
let e = v.ownerDocument, n = (e) => !(e && typeof e.nodeType == "number") || !t(y, e), r = (e) => {
|
|
96
|
+
let t = e.target;
|
|
97
|
+
!n(t) || t && v.contains(t) || P("outside-context-menu", e);
|
|
98
|
+
}, i = (e) => {
|
|
99
|
+
n(e.target) && M.current.some(({ element: e, rect: t, optional: n }) => {
|
|
100
|
+
if (n && m(e)) return !1;
|
|
101
|
+
let r = e.getBoundingClientRect();
|
|
102
|
+
return Math.abs(r.left - t.left) > d || Math.abs(r.top - t.top) > d;
|
|
103
|
+
}) && P("scroll", e);
|
|
104
|
+
};
|
|
105
|
+
return e.addEventListener("contextmenu", r, !0), e.addEventListener("scroll", i, !0), () => {
|
|
106
|
+
e.removeEventListener("contextmenu", r, !0), e.removeEventListener("scroll", i, !0);
|
|
107
|
+
};
|
|
108
|
+
}, [
|
|
109
|
+
g,
|
|
110
|
+
_,
|
|
111
|
+
w,
|
|
112
|
+
v,
|
|
113
|
+
y,
|
|
114
|
+
P
|
|
115
|
+
]);
|
|
116
|
+
let H = c(() => ({
|
|
117
|
+
onContextMenu: z,
|
|
118
|
+
onKeyDown: B
|
|
119
|
+
}), [z, B]), U = c(() => ({ onContextMenu: V }), [V]);
|
|
120
|
+
return {
|
|
121
|
+
anchor: S?.anchor ?? null,
|
|
122
|
+
fromContext: k,
|
|
123
|
+
origin: S?.origin ?? null,
|
|
124
|
+
opener: A,
|
|
125
|
+
triggerHandlers: H,
|
|
126
|
+
surfaceHandlers: U
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { h as useContextMenuAnchor };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=useContextMenuAnchor.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useContextMenuAnchor.mjs","names":["SCROLL_TOLERANCE","isContextKey","event","pointAnchor","rect","y","x","hasNoBox","element","useContextMenuAnchor","options","gesture","setGesture","useState","fromContext","setFromContext","previousOpen","setPreviousOpen","open","pending","nextFromContext","opener","useRef","keyboardFlagRef","referencesRef","requestOpen","useEventCallback","onOpen","requestClose","onClose","clearKeyboardFlag","useCallback","setKeyboardFlag","clearOnPointer","clearOnKey","doc","recordOpener","active","isInsideLayerTree","layerId","startGesture","record","references","optionalReference","onTriggerContextMenu","enabled","isOwnEvent","isDisabledTrigger","isEditableTarget","region","target","onTriggerKeyDown","anchor","onSurfaceContextMenu","useLayoutEffect","useEffect","trigger","isOutside","onContextMenu","onScroll","optional","now","triggerHandlers","useMemo","surfaceHandlers"],"sources":["../../src/hooks/useContextMenuAnchor.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type * as React from 'react';\nimport { isDisabledTrigger, isEditableTarget, isOwnEvent } from '../lib/events';\nimport { isInsideLayerTree } from '../lib/layers';\nimport type { PopupRect, VirtualElement } from '../lib/types';\nimport { useEventCallback } from './useEventCallback';\n\n/** Where a context gesture came from. */\nexport type ContextOrigin = 'pointer' | 'keyboard';\n\n/** Options of {@link useContextMenuAnchor}. */\nexport interface UseContextMenuAnchorOptions {\n /** Whether context gestures on the trigger open the surface (`openOnContext`). */\n enabled: boolean;\n /** Whether the surface is open. */\n open: boolean;\n /** The trigger element (the context region), held in state: the point's `contextElement`. */\n trigger: HTMLElement | null;\n /** The surface's dismiss layer: context menus and scrolls inside its tree are inside. */\n layerId: string;\n /** A context gesture on the trigger asks to open (or, while open, to move to the new anchor). */\n onOpen: (origin: ContextOrigin, event: Event) => void;\n /** A context menu or an anchor-moving scroll outside asks a context-opened surface to close. */\n onClose: (reason: 'outside-context-menu' | 'scroll', event: Event) => void;\n}\n\n/** Returned by {@link useContextMenuAnchor}. Destructure it (react-hooks/refs, like usePresence). */\nexport interface ContextMenuAnchor {\n /**\n * The positioning anchor of the last context gesture: a zero-size `VirtualElement` at the\n * pointer (pointer origin), or the element that had focus when the key was pressed (keyboard\n * origin; `null` when that was the trigger itself). Kept until the next gesture, so an exiting\n * surface does not jump.\n */\n anchor: HTMLElement | VirtualElement | null;\n /** Whether the open surface was opened (last) by a context gesture; kept like `anchor`. */\n fromContext: boolean;\n /** The origin of the last gesture. */\n origin: ContextOrigin | null;\n /**\n * The element focused at the gesture that opened the surface (not `<body>`, not inside the\n * surface's layer tree), else `null`: the first focus-return target of a context-opened surface.\n * Kept through its close; an open without a gesture (a controlled open) forgets it.\n */\n opener: React.RefObject<HTMLElement | null>;\n /** Compose onto the trigger. */\n triggerHandlers: {\n onContextMenu: React.MouseEventHandler<HTMLElement>;\n onKeyDown: React.KeyboardEventHandler<HTMLElement>;\n };\n /**\n * Compose onto the surface: while `enabled`, the browser's context menu never opens over it.\n * With `enabled: false` the handler does nothing, so a component may compose it in every mode\n * and only its context mode suppresses the browser's menu.\n */\n surfaceHandlers: { onContextMenu: React.MouseEventHandler<HTMLElement> };\n}\n\n/** Pixels a gesture's reference element may move before an outside scroll closes the surface. */\nconst SCROLL_TOLERANCE = 2;\n\n/** Whether a keydown is the keyboard context gesture: Shift+F10 or the ContextMenu key. */\nfunction isContextKey(event: React.KeyboardEvent): boolean {\n if (event.ctrlKey || event.altKey || event.metaKey) return false;\n return event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey);\n}\n\n/** A zero-size anchor at a viewport point, scrolled with `contextElement`'s containers. */\nfunction pointAnchor(x: number, y: number, contextElement: Element): VirtualElement {\n const rect: PopupRect = { x, y, width: 0, height: 0, top: y, right: x, bottom: y, left: x };\n return { getBoundingClientRect: () => rect, contextElement };\n}\n\n/** An element whose movement since the gesture closes the surface, with its rectangle then. */\ninterface ScrollReference {\n element: Element;\n rect: DOMRect;\n /**\n * The element a pointer gesture landed on: once it has no box (removed, or `display: none`,\n * such as a row's hover-only action), it no longer counts, and the region alone decides.\n */\n optional: boolean;\n}\n\n/** Whether an element has left the document or the layout (it has no box). */\nfunction hasNoBox(element: Element): boolean {\n if (!element.isConnected) return true;\n const rect = element.getBoundingClientRect();\n return rect.width === 0 && rect.height === 0;\n}\n\n/** The last gesture: its anchor and origin, and whether it has yet to decide `fromContext`. */\ninterface Gesture {\n anchor: HTMLElement | VirtualElement | null;\n origin: ContextOrigin;\n /** A gesture of the hook asked to open in this update (decides `fromContext`). */\n pending: boolean;\n}\n\n/**\n * Context-menu gestures on a trigger region (`openOnContext` on Menu and Popover): a right click\n * (a macOS Ctrl+click, a long press where the browser reports one) opens at the pointer, Shift+F10\n * and the ContextMenu key open at the focused element inside the region. Internal until the\n * positioning, dismiss and hover pieces become public (ROADMAP P7-01).\n *\n * 1. **Origin.** The trigger's keydown of Shift+F10 or ContextMenu sets a keyboard flag, which the\n * next `pointerdown` anywhere, the next keydown of another key or the surface closing clear. A\n * `contextmenu` on the trigger while the flag is set is the browser's event for that key press:\n * it is prevented and changes nothing. Every other `contextmenu` is a pointer gesture at its\n * point, whatever its `button` or pointer type.\n * 2. **Trigger `contextmenu`** (not default-prevented, not from a portal rendered inside the\n * trigger, not from an `aria-disabled` element at or inside it, not from an editable field:\n * those keep the browser's menu and open nothing): prevented; the focused element is recorded\n * as `opener`; a pointer gesture stores a zero-size `VirtualElement` at the point (its\n * `contextElement` is the trigger) as `anchor`; then `onOpen('pointer', event)`. A second\n * gesture while open does the same (the surface moves).\n * 3. **Trigger keydown** of Shift+F10 or ContextMenu (same filters): prevented, the flag is set,\n * `opener` recorded, `anchor` = the focused element inside the trigger (else `null`), then\n * `onOpen('keyboard', event)`.\n * 4. **Surface `contextmenu`** is prevented while `enabled`, whether or not a gesture opened the\n * surface. With `enabled: false` it is left to the browser (the surface handler is inert).\n * 5. **While open from a context gesture:** a `contextmenu` outside the layer tree and the trigger\n * calls `onClose('outside-context-menu', event)` without preventing it; a scroll outside the\n * layer tree calls `onClose('scroll', event)` only when a reference element of the gesture moved\n * more than 2 px since the gesture. For a pointer gesture those are the trigger and the element\n * the gesture landed on (the row under the pointer, which a scroll of the region's content\n * moves), the latter only while it has a box: once it is removed or `display: none` (a row's\n * hover-only action), the trigger alone counts. For a keyboard gesture the reference is the\n * keyboard anchor, else the trigger. A scroll that moves none of them (a scroll in an unrelated\n * panel) is ignored; inertial scrolling of the region's content that is still running at the\n * right click moves the row, so it closes the surface.\n * 6. `anchor` and `origin` are replaced at the next gesture, not reset on close. `fromContext` is\n * decided when `open` becomes `true`: `true` when a gesture of the hook caused that open, else\n * `false`; a gesture while open makes it `true` (the surface moves to the gesture). It keeps its\n * value while the surface is closed or exiting. `opener` is kept through the close too, and\n * forgotten (`null`) when the surface opens without a gesture, so a close or Tab after that\n * open never returns to an earlier gesture's element.\n */\nexport function useContextMenuAnchor(options: UseContextMenuAnchorOptions): ContextMenuAnchor {\n const { enabled, open, trigger, layerId, onOpen, onClose } = options;\n\n const [gesture, setGesture] = useState<Gesture | null>(null);\n const [fromContext, setFromContext] = useState(false);\n const [previousOpen, setPreviousOpen] = useState(open);\n\n // `fromContext` during render (C-HOOKS: previous-value state): decided when `open` becomes\n // true, from the gesture requested in the same update.\n const pending = gesture?.pending ?? false;\n let nextFromContext = fromContext;\n if (open !== previousOpen) {\n setPreviousOpen(open);\n if (open) nextFromContext = pending;\n } else if (open && pending) {\n nextFromContext = true;\n }\n if (nextFromContext !== fromContext) setFromContext(nextFromContext);\n if (gesture && pending) setGesture({ ...gesture, pending: false });\n\n const opener = useRef<HTMLElement | null>(null);\n // The keyboard flag and the reference elements' rectangles at the gesture: written in handlers\n // and effects only.\n const keyboardFlagRef = useRef<{ clear: () => void } | null>(null);\n const referencesRef = useRef<ScrollReference[]>([]);\n\n const requestOpen = useEventCallback(onOpen);\n const requestClose = useEventCallback(onClose);\n\n const clearKeyboardFlag = useCallback(() => {\n keyboardFlagRef.current?.clear();\n keyboardFlagRef.current = null;\n }, []);\n\n const setKeyboardFlag = useCallback(\n (doc: Document) => {\n clearKeyboardFlag();\n // Added during the keydown, after its capture phase: they see only later events.\n const clearOnPointer = () => clearKeyboardFlag();\n const clearOnKey = (event: KeyboardEvent) => {\n const again =\n !event.ctrlKey &&\n !event.altKey &&\n !event.metaKey &&\n (event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey));\n if (!again) clearKeyboardFlag();\n };\n doc.addEventListener('pointerdown', clearOnPointer, true);\n doc.addEventListener('keydown', clearOnKey, true);\n keyboardFlagRef.current = {\n clear: () => {\n doc.removeEventListener('pointerdown', clearOnPointer, true);\n doc.removeEventListener('keydown', clearOnKey, true);\n },\n };\n },\n [clearKeyboardFlag],\n );\n\n /** The focused element, unless it is `<body>` or inside the surface's layer tree. */\n const recordOpener = useCallback(\n (doc: Document) => {\n const active = doc.activeElement as HTMLElement | null;\n opener.current =\n active && active !== doc.body && !isInsideLayerTree(layerId, active) ? active : null;\n },\n [layerId],\n );\n\n /**\n * Records the gesture and the rectangles of the elements whose movement closes the surface: the\n * required ones, then an optional one (see `ScrollReference.optional`).\n */\n const startGesture = useCallback(\n (\n anchor: HTMLElement | VirtualElement | null,\n origin: ContextOrigin,\n references: readonly Element[],\n optionalReference?: Element,\n ) => {\n const record = (element: Element, optional: boolean): ScrollReference => ({\n element,\n rect: element.getBoundingClientRect(),\n optional,\n });\n referencesRef.current = references.map((element) => record(element, false));\n if (optionalReference) referencesRef.current.push(record(optionalReference, true));\n setGesture({ anchor, origin, pending: true });\n },\n [],\n );\n\n const onTriggerContextMenu = useCallback(\n (event: React.MouseEvent<HTMLElement>) => {\n if (!enabled || event.defaultPrevented || !isOwnEvent(event)) return;\n if (isDisabledTrigger(event) || isEditableTarget(event.target)) return;\n event.preventDefault();\n // The browser's contextmenu for the key press: the surface stays at its keyboard anchor.\n if (keyboardFlagRef.current) return;\n const region = event.currentTarget;\n recordOpener(region.ownerDocument);\n // The region, and the element the gesture landed on (a row, which scrolls inside the\n // region; `isOwnEvent` keeps it inside) while it keeps a box.\n const target = event.target as Element;\n startGesture(\n pointAnchor(event.clientX, event.clientY, region),\n 'pointer',\n [region],\n target === region ? undefined : target,\n );\n requestOpen('pointer', event.nativeEvent);\n },\n [enabled, recordOpener, startGesture, requestOpen],\n );\n\n const onTriggerKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (!enabled || !isContextKey(event) || event.defaultPrevented || !isOwnEvent(event)) return;\n if (isDisabledTrigger(event) || isEditableTarget(event.target)) return;\n event.preventDefault();\n const region = event.currentTarget;\n setKeyboardFlag(region.ownerDocument);\n recordOpener(region.ownerDocument);\n const target = event.target as HTMLElement;\n const anchor = target !== region ? target : null;\n startGesture(anchor, 'keyboard', [anchor ?? region]);\n requestOpen('keyboard', event.nativeEvent);\n },\n [enabled, setKeyboardFlag, recordOpener, startGesture, requestOpen],\n );\n\n // Only in context mode: a surface that composes this unconditionally keeps the browser's menu\n // (paste, spelling suggestions in its fields) while the hook is disabled.\n const onSurfaceContextMenu = useCallback(\n (event: React.MouseEvent<HTMLElement>) => {\n if (enabled) event.preventDefault();\n },\n [enabled],\n );\n\n // The opener belongs to the gesture: an open that no gesture caused (a controlled open from a\n // toolbar button) forgets the last gesture's, before any close or Tab can return to it. It is\n // kept through the close of a gesture-opened surface, which returns focus to it.\n useLayoutEffect(() => {\n if (open && !nextFromContext) opener.current = null;\n }, [open, nextFromContext]);\n\n // The flag clears when the surface closes; nothing is left behind on unmount.\n useEffect(() => {\n if (!open) clearKeyboardFlag();\n }, [open, clearKeyboardFlag]);\n useEffect(() => clearKeyboardFlag, [clearKeyboardFlag]);\n\n // While open from a context gesture: an outside context menu, or a scroll that moved the anchor.\n useEffect(() => {\n if (!enabled || !open || !fromContext || !trigger) return undefined;\n const doc = trigger.ownerDocument;\n const isOutside = (target: EventTarget | null) =>\n !(target && typeof (target as Node).nodeType === 'number') ||\n !isInsideLayerTree(layerId, target as Node);\n const onContextMenu = (event: MouseEvent) => {\n const target = event.target as Node | null;\n if (!isOutside(target) || (target && trigger.contains(target))) return;\n requestClose('outside-context-menu', event);\n };\n const onScroll = (event: Event) => {\n if (!isOutside(event.target)) return;\n const moved = referencesRef.current.some(({ element, rect, optional }) => {\n if (optional && hasNoBox(element)) return false;\n const now = element.getBoundingClientRect();\n return (\n Math.abs(now.left - rect.left) > SCROLL_TOLERANCE ||\n Math.abs(now.top - rect.top) > SCROLL_TOLERANCE\n );\n });\n if (moved) requestClose('scroll', event);\n };\n doc.addEventListener('contextmenu', onContextMenu, true);\n doc.addEventListener('scroll', onScroll, true);\n return () => {\n doc.removeEventListener('contextmenu', onContextMenu, true);\n doc.removeEventListener('scroll', onScroll, true);\n };\n }, [enabled, open, fromContext, trigger, layerId, requestClose]);\n\n const triggerHandlers = useMemo(\n () => ({ onContextMenu: onTriggerContextMenu, onKeyDown: onTriggerKeyDown }),\n [onTriggerContextMenu, onTriggerKeyDown],\n );\n const surfaceHandlers = useMemo(\n () => ({ onContextMenu: onSurfaceContextMenu }),\n [onSurfaceContextMenu],\n );\n\n return {\n anchor: gesture?.anchor ?? null,\n fromContext: nextFromContext,\n origin: gesture?.origin ?? null,\n opener,\n triggerHandlers,\n surfaceHandlers,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("../lib/dev.cjs"),t=require("./useEventCallback.cjs");let n=require("react");function r(t,n){e.warnOnce(`useControllable:${t}->${n}`,`A component is changing from ${t} to ${n}. Components should not switch between controlled and uncontrolled: pass \`undefined\` only when the component is uncontrolled, and the empty value (for example \`[]\`, \`null\` or \`""\`) to clear a controlled value.`)}function i(e,i,a){let[o]=(0,n.useState)(e!==void 0),[s,c]=(0,n.useState)(o);e!==void 0&&!s&&c(!0);let l=s||e!==void 0,[u,d]=(0,n.useState)(i),f=l?e===void 0?i:e:u,p=(0,n.useRef)(f),m=(0,n.useRef)(l),h=(0,n.useRef)(u),g=(0,n.useRef)(null);(0,n.useInsertionEffect)(()=>{p.current=f,m.current=l,h.current=u}),(0,n.useEffect)(()=>{e===void 0&&s?r(`controlled`,`uncontrolled`):e!==void 0&&!o&&r(`uncontrolled`,`controlled`)},[e,s,o]);let _=t.useEventCallback(a);return[f,(0,n.useCallback)(e=>{let t=m.current,n=t?g.current?g.current.value:p.current:h.current,r=typeof e==`function`?e(n):e;Object.is(r,n)||(t?(g.current||queueMicrotask(()=>{g.current=null}),g.current={value:r}):(h.current=r,d(r)),_(r))},[_]),l]}exports.useControllable=i;
|
|
2
|
+
//# sourceMappingURL=useControllable.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useControllable.cjs","names":["useState","useRef","useEventCallback","useCallback","require_dev","require_useEventCallback","react","warnModeSwitch","from","to","useControllable","initiallyControlled","controlledValue","wasControlled","setWasControlled","isControlled","internalValue","setInternalValue","defaultValue","value","renderedRef","isControlledRef","latestRef","pendingRef","emitChange","onChange","controlled","base","next","valueOrUpdater"],"sources":["../../src/hooks/useControllable.ts"],"sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useRef, useState } from 'react';\nimport { useEventCallback } from './useEventCallback';\nimport { warnOnce } from '../lib/dev';\n\n/** Setter returned by {@link useControllable}: a direct value or a functional updater. */\nexport type SetValue<T> = (valueOrUpdater: T | ((prev: T) => T)) => void;\n\ntype Mode = 'controlled' | 'uncontrolled';\n\n/**\n * Warns once per page and direction, through `src/lib/dev.ts` (C-DEV). `warnOnce(key, message)`\n * logs one prefixed string, so the directions are part of the message text (`from ${from} to\n * ${to}`) rather than separate console arguments.\n */\nfunction warnModeSwitch(from: Mode, to: Mode): void {\n warnOnce(\n `useControllable:${from}->${to}`,\n `A component is changing from ${from} to ${to}. Components should not switch between ` +\n 'controlled and uncontrolled: pass `undefined` only when the component is uncontrolled, and ' +\n 'the empty value (for example `[]`, `null` or `\"\"`) to clear a controlled value.',\n );\n}\n\n/**\n * State for components that can be controlled (`value` + `onChange` from the parent) or\n * uncontrolled (internal state seeded from `defaultValue`). Returns `[value, setValue]` like\n * `useState`, plus the current mode as a third element (`isControlled`) for components that treat\n * the two modes differently (e.g. store a clamp only while uncontrolled).\n *\n * - **Sticky controlled mode.** The component is controlled from the first render in which\n * `controlledValue !== undefined` on. A value that arrives after mount (data loaded later) takes\n * over immediately. A controlled value that later becomes `undefined` keeps the component\n * controlled and returns the `defaultValue` argument — pass the component's empty value there\n * (`defaultValueProp ?? []`), so `value={undefined}` clears it. Each direction warns once in\n * development.\n * - **`setValue(valueOrUpdater)`** computes the next value from the value the user sees — the last\n * rendered controlled value (plus a value already emitted earlier in the same event, so two\n * functional updates in one handler chain) or the latest uncontrolled value — and calls\n * `onChange` exactly once from the event path (never inside a state updater, so StrictMode does\n * not double it). An update that does not change the value (`Object.is`) is skipped, including\n * its `onChange`. A controlled parent that ignores `onChange` never leaves a stale value behind:\n * the next event starts from the rendered value again.\n * - **What \"the same event\" means** (controlled mode). A value emitted by `setValue` stays pending\n * until the next microtask checkpoint, and `setValue` calls made before then chain from it. Real\n * user events are separate tasks, so each one starts from the rendered value. Everything\n * dispatched synchronously within one task counts as one interaction and chains exactly like the\n * uncontrolled mode does, whether the parent accepts the value or not: a nested `el.focus()` or\n * `el.click()` from a handler, two `el.click()` calls from one timer callback, back-to-back\n * `fireEvent` calls, or several `setValue` calls in one `act()`. Tests that need two separate\n * interactions await a microtask between them (`await act(async () => {})`) or use `userEvent`.\n * - `setValue` has a stable identity; the latest `onChange` is always called. The rendered value\n * and mode it reads are synced before any layout effect of a commit, so a layout or passive\n * effect (also a child's, which React runs first) that calls `setValue` starts from the value\n * committed in that same commit.\n *\n * Event-named callbacks that must fire on every activation (e.g. `onPageChange` on the current\n * page) are called by the component from its handler, not through this hook.\n *\n * @typeParam T - The type of the state value.\n * @param controlledValue - The controlled value, or `undefined` while the component is uncontrolled.\n * @param defaultValue - Initial uncontrolled value; also the value returned while a once-controlled\n * value is `undefined`.\n * @param onChange - Called with the next value whenever `setValue` changes it (both modes).\n * @returns A `[value, setValue, isControlled]` tuple. `isControlled` is the sticky mode described\n * above: `true` from the first render with a defined `controlledValue` on (also in that render\n * itself and after the value becomes `undefined` again), `false` while the component has only\n * ever been uncontrolled. Read the mode from here rather than tracking it in the component.\n */\nexport function useControllable<T>(\n controlledValue: T | undefined,\n defaultValue: T,\n onChange?: (value: T) => void,\n): [value: T, setValue: SetValue<T>, isControlled: boolean] {\n const [initiallyControlled] = useState(controlledValue !== undefined);\n const [wasControlled, setWasControlled] = useState(initiallyControlled);\n if (controlledValue !== undefined && !wasControlled) {\n setWasControlled(true);\n }\n const isControlled = wasControlled || controlledValue !== undefined;\n\n const [internalValue, setInternalValue] = useState(defaultValue);\n const value: T = isControlled\n ? controlledValue !== undefined\n ? controlledValue\n : defaultValue\n : internalValue;\n\n // Value and mode of the last commit. Written only by the insertion effect below, never by setValue.\n const renderedRef = useRef(value);\n const isControlledRef = useRef(isControlled);\n // Uncontrolled: the latest value, updated optimistically by setValue (nobody can reject it).\n const latestRef = useRef(internalValue);\n // Controlled: the value emitted earlier in the same event; cleared in a microtask, never persisted\n // across events.\n const pendingRef = useRef<{ value: T } | null>(null);\n\n // An insertion effect runs in the commit's mutation phase, before every layout effect of that\n // commit, so a child's layout effect that calls setValue already sees the committed value and mode\n // (React runs a child's layout effects before its parent's). Not run on the server, where setValue\n // is never called.\n useInsertionEffect(() => {\n renderedRef.current = value;\n isControlledRef.current = isControlled;\n latestRef.current = internalValue;\n });\n\n useEffect(() => {\n if (controlledValue === undefined && wasControlled) {\n warnModeSwitch('controlled', 'uncontrolled');\n } else if (controlledValue !== undefined && !initiallyControlled) {\n warnModeSwitch('uncontrolled', 'controlled');\n }\n }, [controlledValue, wasControlled, initiallyControlled]);\n\n const emitChange = useEventCallback(onChange);\n\n const setValue = useCallback<SetValue<T>>(\n (valueOrUpdater) => {\n const controlled = isControlledRef.current;\n const base = controlled\n ? pendingRef.current\n ? pendingRef.current.value\n : renderedRef.current\n : latestRef.current;\n const next =\n typeof valueOrUpdater === 'function'\n ? (valueOrUpdater as (prev: T) => T)(base)\n : valueOrUpdater;\n if (Object.is(next, base)) return;\n\n if (controlled) {\n if (!pendingRef.current) {\n queueMicrotask(() => {\n pendingRef.current = null;\n });\n }\n pendingRef.current = { value: next };\n } else {\n latestRef.current = next;\n setInternalValue(next);\n }\n emitChange(next);\n },\n [emitChange],\n );\n\n return [value, setValue, isControlled];\n}\n"],"mappings":"0GAcA,SAASO,EAAe,EAAY,EAAgB,CAClDH,EAAA,SACE,mBAAmBI,EAAK,IAAIC,IAC5B,gCAAgCD,EAAK,MAAMC,EAAG,0NAGhD,CACF,CA+CA,SAAgBC,EACd,EACA,EACA,EAC0D,CAC1D,GAAM,CAACC,IAAA,EAAuBX,EAAAA,SAAAA,CAASY,IAAoB,IAAA,EAAS,EAC9D,CAACC,EAAeC,IAAA,EAAoBd,EAAAA,SAAAA,CAASW,CAAmB,EAClEC,IAAoB,IAAA,IAAa,CAACC,GACpCC,EAAiB,EAAI,EAEvB,IAAMC,EAAeF,GAAiBD,IAAoB,IAAA,GAEpD,CAACI,EAAeC,IAAA,EAAoBjB,EAAAA,SAAAA,CAASkB,CAAY,EACzDC,EAAWJ,EACbH,IAAoB,IAAA,GAElBM,EADAN,EAEFI,EAGEI,GAAA,EAAcnB,EAAAA,OAAAA,CAAOkB,CAAK,EAC1BE,GAAA,EAAkBpB,EAAAA,OAAAA,CAAOc,CAAY,EAErCO,GAAA,EAAYrB,EAAAA,OAAAA,CAAOe,CAAa,EAGhCO,GAAA,EAAatB,EAAAA,OAAAA,CAA4B,IAAI,GAMnD,EAAAK,EAAA,mBAAA,KAAyB,CACvB,EAAY,QAAUa,EACtB,EAAgB,QAAUJ,EAC1B,EAAU,QAAUC,CACtB,CAAC,GAED,EAAAV,EAAA,UAAA,KAAgB,CACVM,IAAoB,IAAA,IAAaC,EACnCN,EAAe,aAAc,cAAc,EAClCK,IAAoB,IAAA,IAAa,CAACD,GAC3CJ,EAAe,eAAgB,YAAY,CAE/C,EAAG,CAACK,EAAiBC,EAAeF,CAAmB,CAAC,EAExD,IAAMa,EAAatB,EAAAA,iBAAiBuB,CAAQ,EAgC5C,MAAO,CAACN,GA9BShB,EAAAA,EAAAA,YAAAA,CACd,GAAmB,CAClB,IAAMuB,EAAaL,EAAgB,QAC7BM,EAAOD,EACTH,EAAW,QACTA,EAAW,QAAQ,MACnBH,EAAY,QACdE,EAAU,QACRM,EACJ,OAAOC,GAAmB,WACrBA,EAAkCF,CAAI,EACvCE,EACF,OAAO,GAAGD,EAAMD,CAAI,IAEpBD,GACGH,EAAW,SACd,mBAAqB,CACnB,EAAW,QAAU,IACvB,CAAC,EAEH,EAAW,QAAU,CAAE,MAAOK,CAAK,IAEnC,EAAU,QAAUA,EACpBX,EAAiBW,CAAI,GAEvBJ,EAAWI,CAAI,EACjB,EACA,CAACJ,CAAU,CAGE,EAAUT,CAAY,CACvC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { warnOnce as e } from "../lib/dev.mjs";
|
|
3
|
+
import { useEventCallback as t } from "./useEventCallback.mjs";
|
|
4
|
+
import { useCallback as n, useEffect as r, useInsertionEffect as i, useRef as a, useState as o } from "react";
|
|
5
|
+
//#region src/hooks/useControllable.ts
|
|
6
|
+
function s(t, n) {
|
|
7
|
+
e(`useControllable:${t}->${n}`, `A component is changing from ${t} to ${n}. Components should not switch between controlled and uncontrolled: pass \`undefined\` only when the component is uncontrolled, and the empty value (for example \`[]\`, \`null\` or \`""\`) to clear a controlled value.`);
|
|
8
|
+
}
|
|
9
|
+
function c(e, c, l) {
|
|
10
|
+
let [u] = o(e !== void 0), [d, f] = o(u);
|
|
11
|
+
e !== void 0 && !d && f(!0);
|
|
12
|
+
let p = d || e !== void 0, [m, h] = o(c), g = p ? e === void 0 ? c : e : m, _ = a(g), v = a(p), y = a(m), b = a(null);
|
|
13
|
+
i(() => {
|
|
14
|
+
_.current = g, v.current = p, y.current = m;
|
|
15
|
+
}), r(() => {
|
|
16
|
+
e === void 0 && d ? s("controlled", "uncontrolled") : e !== void 0 && !u && s("uncontrolled", "controlled");
|
|
17
|
+
}, [
|
|
18
|
+
e,
|
|
19
|
+
d,
|
|
20
|
+
u
|
|
21
|
+
]);
|
|
22
|
+
let x = t(l);
|
|
23
|
+
return [
|
|
24
|
+
g,
|
|
25
|
+
n((e) => {
|
|
26
|
+
let t = v.current, n = t ? b.current ? b.current.value : _.current : y.current, r = typeof e == "function" ? e(n) : e;
|
|
27
|
+
Object.is(r, n) || (t ? (b.current || queueMicrotask(() => {
|
|
28
|
+
b.current = null;
|
|
29
|
+
}), b.current = { value: r }) : (y.current = r, h(r)), x(r));
|
|
30
|
+
}, [x]),
|
|
31
|
+
p
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { c as useControllable };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=useControllable.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useControllable.mjs","names":["warnModeSwitch","from","to","useControllable","initiallyControlled","useState","controlledValue","wasControlled","setWasControlled","isControlled","internalValue","setInternalValue","defaultValue","value","renderedRef","useRef","isControlledRef","latestRef","pendingRef","useInsertionEffect","useEffect","emitChange","useEventCallback","onChange","useCallback","controlled","base","next","valueOrUpdater"],"sources":["../../src/hooks/useControllable.ts"],"sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useRef, useState } from 'react';\nimport { useEventCallback } from './useEventCallback';\nimport { warnOnce } from '../lib/dev';\n\n/** Setter returned by {@link useControllable}: a direct value or a functional updater. */\nexport type SetValue<T> = (valueOrUpdater: T | ((prev: T) => T)) => void;\n\ntype Mode = 'controlled' | 'uncontrolled';\n\n/**\n * Warns once per page and direction, through `src/lib/dev.ts` (C-DEV). `warnOnce(key, message)`\n * logs one prefixed string, so the directions are part of the message text (`from ${from} to\n * ${to}`) rather than separate console arguments.\n */\nfunction warnModeSwitch(from: Mode, to: Mode): void {\n warnOnce(\n `useControllable:${from}->${to}`,\n `A component is changing from ${from} to ${to}. Components should not switch between ` +\n 'controlled and uncontrolled: pass `undefined` only when the component is uncontrolled, and ' +\n 'the empty value (for example `[]`, `null` or `\"\"`) to clear a controlled value.',\n );\n}\n\n/**\n * State for components that can be controlled (`value` + `onChange` from the parent) or\n * uncontrolled (internal state seeded from `defaultValue`). Returns `[value, setValue]` like\n * `useState`, plus the current mode as a third element (`isControlled`) for components that treat\n * the two modes differently (e.g. store a clamp only while uncontrolled).\n *\n * - **Sticky controlled mode.** The component is controlled from the first render in which\n * `controlledValue !== undefined` on. A value that arrives after mount (data loaded later) takes\n * over immediately. A controlled value that later becomes `undefined` keeps the component\n * controlled and returns the `defaultValue` argument — pass the component's empty value there\n * (`defaultValueProp ?? []`), so `value={undefined}` clears it. Each direction warns once in\n * development.\n * - **`setValue(valueOrUpdater)`** computes the next value from the value the user sees — the last\n * rendered controlled value (plus a value already emitted earlier in the same event, so two\n * functional updates in one handler chain) or the latest uncontrolled value — and calls\n * `onChange` exactly once from the event path (never inside a state updater, so StrictMode does\n * not double it). An update that does not change the value (`Object.is`) is skipped, including\n * its `onChange`. A controlled parent that ignores `onChange` never leaves a stale value behind:\n * the next event starts from the rendered value again.\n * - **What \"the same event\" means** (controlled mode). A value emitted by `setValue` stays pending\n * until the next microtask checkpoint, and `setValue` calls made before then chain from it. Real\n * user events are separate tasks, so each one starts from the rendered value. Everything\n * dispatched synchronously within one task counts as one interaction and chains exactly like the\n * uncontrolled mode does, whether the parent accepts the value or not: a nested `el.focus()` or\n * `el.click()` from a handler, two `el.click()` calls from one timer callback, back-to-back\n * `fireEvent` calls, or several `setValue` calls in one `act()`. Tests that need two separate\n * interactions await a microtask between them (`await act(async () => {})`) or use `userEvent`.\n * - `setValue` has a stable identity; the latest `onChange` is always called. The rendered value\n * and mode it reads are synced before any layout effect of a commit, so a layout or passive\n * effect (also a child's, which React runs first) that calls `setValue` starts from the value\n * committed in that same commit.\n *\n * Event-named callbacks that must fire on every activation (e.g. `onPageChange` on the current\n * page) are called by the component from its handler, not through this hook.\n *\n * @typeParam T - The type of the state value.\n * @param controlledValue - The controlled value, or `undefined` while the component is uncontrolled.\n * @param defaultValue - Initial uncontrolled value; also the value returned while a once-controlled\n * value is `undefined`.\n * @param onChange - Called with the next value whenever `setValue` changes it (both modes).\n * @returns A `[value, setValue, isControlled]` tuple. `isControlled` is the sticky mode described\n * above: `true` from the first render with a defined `controlledValue` on (also in that render\n * itself and after the value becomes `undefined` again), `false` while the component has only\n * ever been uncontrolled. Read the mode from here rather than tracking it in the component.\n */\nexport function useControllable<T>(\n controlledValue: T | undefined,\n defaultValue: T,\n onChange?: (value: T) => void,\n): [value: T, setValue: SetValue<T>, isControlled: boolean] {\n const [initiallyControlled] = useState(controlledValue !== undefined);\n const [wasControlled, setWasControlled] = useState(initiallyControlled);\n if (controlledValue !== undefined && !wasControlled) {\n setWasControlled(true);\n }\n const isControlled = wasControlled || controlledValue !== undefined;\n\n const [internalValue, setInternalValue] = useState(defaultValue);\n const value: T = isControlled\n ? controlledValue !== undefined\n ? controlledValue\n : defaultValue\n : internalValue;\n\n // Value and mode of the last commit. Written only by the insertion effect below, never by setValue.\n const renderedRef = useRef(value);\n const isControlledRef = useRef(isControlled);\n // Uncontrolled: the latest value, updated optimistically by setValue (nobody can reject it).\n const latestRef = useRef(internalValue);\n // Controlled: the value emitted earlier in the same event; cleared in a microtask, never persisted\n // across events.\n const pendingRef = useRef<{ value: T } | null>(null);\n\n // An insertion effect runs in the commit's mutation phase, before every layout effect of that\n // commit, so a child's layout effect that calls setValue already sees the committed value and mode\n // (React runs a child's layout effects before its parent's). Not run on the server, where setValue\n // is never called.\n useInsertionEffect(() => {\n renderedRef.current = value;\n isControlledRef.current = isControlled;\n latestRef.current = internalValue;\n });\n\n useEffect(() => {\n if (controlledValue === undefined && wasControlled) {\n warnModeSwitch('controlled', 'uncontrolled');\n } else if (controlledValue !== undefined && !initiallyControlled) {\n warnModeSwitch('uncontrolled', 'controlled');\n }\n }, [controlledValue, wasControlled, initiallyControlled]);\n\n const emitChange = useEventCallback(onChange);\n\n const setValue = useCallback<SetValue<T>>(\n (valueOrUpdater) => {\n const controlled = isControlledRef.current;\n const base = controlled\n ? pendingRef.current\n ? pendingRef.current.value\n : renderedRef.current\n : latestRef.current;\n const next =\n typeof valueOrUpdater === 'function'\n ? (valueOrUpdater as (prev: T) => T)(base)\n : valueOrUpdater;\n if (Object.is(next, base)) return;\n\n if (controlled) {\n if (!pendingRef.current) {\n queueMicrotask(() => {\n pendingRef.current = null;\n });\n }\n pendingRef.current = { value: next };\n } else {\n latestRef.current = next;\n setInternalValue(next);\n }\n emitChange(next);\n },\n [emitChange],\n );\n\n return [value, setValue, isControlled];\n}\n"],"mappings":";;;;;AAcA,SAASA,EAAe,GAAY,GAAgB;CAClD,EACE,mBAAmBC,EAAK,IAAIC,KAC5B,gCAAgCD,EAAK,MAAMC,EAAG,0NAGhD;AACF;AA+CA,SAAgBC,EACd,GACA,GACA,GAC0D;CAC1D,IAAM,CAACC,KAAuBC,EAASC,MAAoB,KAAA,CAAS,GAC9D,CAACC,GAAeC,KAAoBH,EAASD,CAAmB;CACtE,AAAIE,MAAoB,KAAA,KAAa,CAACC,KACpCC,EAAiB,EAAI;CAEvB,IAAMC,IAAeF,KAAiBD,MAAoB,KAAA,GAEpD,CAACI,GAAeC,KAAoBN,EAASO,CAAY,GACzDC,IAAWJ,IACbH,MAAoB,KAAA,IAElBM,IADAN,IAEFI,GAGEI,IAAcC,EAAOF,CAAK,GAC1BG,IAAkBD,EAAON,CAAY,GAErCQ,IAAYF,EAAOL,CAAa,GAGhCQ,IAAaH,EAA4B,IAAI;CAYnD,AANAI,QAAyB;EAGvB,AAFA,EAAY,UAAUN,GACtB,EAAgB,UAAUJ,GAC1B,EAAU,UAAUC;CACtB,CAAC,GAEDU,QAAgB;EACd,AAAId,MAAoB,KAAA,KAAaC,IACnCP,EAAe,cAAc,cAAc,IAClCM,MAAoB,KAAA,KAAa,CAACF,KAC3CJ,EAAe,gBAAgB,YAAY;CAE/C,GAAG;EAACM;EAAiBC;EAAeH;CAAmB,CAAC;CAExD,IAAMiB,IAAaC,EAAiBC,CAAQ;CAgC5C,OAAO;EAACV;EA9BSW,GACd,MAAmB;GAClB,IAAMC,IAAaT,EAAgB,SAC7BU,IAAOD,IACTP,EAAW,UACTA,EAAW,QAAQ,QACnBJ,EAAY,UACdG,EAAU,SACRU,IACJ,OAAOC,KAAmB,aACrBA,EAAkCF,CAAI,IACvCE;GACN,AAAI,OAAO,GAAGD,GAAMD,CAAI,MAEpBD,KACGP,EAAW,WACd,qBAAqB;IACnB,EAAW,UAAU;GACvB,CAAC,GAEH,EAAW,UAAU,EAAE,OAAOS,EAAK,MAEnC,EAAU,UAAUA,GACpBhB,EAAiBgB,CAAI,IAEvBN,EAAWM,CAAI;EACjB,GACA,CAACN,CAAU,CAGE;EAAUZ;CAAY;AACvC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";const e=require("../lib/direction.cjs"),t=require("../components/provider/WaveProvider.cjs");let n=require("react");var r=new Map,i=0,a=new WeakMap;function o(){}function s(e,t){if(typeof MutationObserver>`u`)return o;let n=r.get(e);if(!n){let t=new Set,a=new MutationObserver(()=>{i+=1;for(let e of Array.from(t))e()});a.observe(e.documentElement,{attributes:!0,attributeFilter:[`dir`],subtree:!0}),i+=1,n={observer:a,listeners:t},r.set(e,n)}let a=n,s=()=>t();return a.listeners.add(s),()=>{a.listeners.delete(s),a.listeners.size===0&&r.get(e)===a&&(a.observer.disconnect(),r.delete(e))}}function c(t){if(!t.isConnected||!r.has(t.ownerDocument))return e.getDirection(t);let n=a.get(t);if(n&&n.version===i)return n.direction;let o=e.getDirection(t);return a.set(t,{version:i,direction:o}),o}function l(){return c(document.body??document.documentElement)}function u(){return`ltr`}function d(e){let{dir:r,hasProvider:i}=t.useWaveTheme(),a=e??null,d=(0,n.useCallback)(e=>a?s(a.ownerDocument,e):i||typeof document>`u`?o:s(document,e),[a,i]),f=(0,n.useCallback)(()=>a?c(a):i?r:l(),[a,i,r]),p=(0,n.useCallback)(()=>i?r:u(),[i,r]);return(0,n.useSyncExternalStore)(d,f,p)}exports.useDirection=d;
|
|
2
|
+
//# sourceMappingURL=useDirection.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDirection.cjs","names":["getDirection","useWaveTheme","useCallback","useSyncExternalStore","require_direction","require_WaveProvider","react","sharedObservers","cacheVersion","directionCache","noopUnsubscribe","subscribeToDirectionChanges","shared","doc","listeners","observer","listener","current","notify","readDirection","el","cached","direction","getDocumentDirection","getServerDirection","useDirection","element","subscribe","hasProvider","getSnapshot","dir","getServerSnapshot"],"sources":["../../src/hooks/useDirection.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport { getDirection, type Direction } from '../lib/direction';\nimport { useWaveTheme } from '../components/provider/WaveProvider';\n\n/**\n * One MutationObserver per document, shared by every subscriber (ref-counted): it watches every\n * `dir` attribute of the document (`<html>`, WaveProvider roots, `dir` sections, the elements\n * themselves) and invalidates the cached directions before it notifies the subscribers.\n */\ninterface SharedDirectionObserver {\n observer: MutationObserver;\n listeners: Set<() => void>;\n}\n\nconst sharedObservers = new Map<Document, SharedDirectionObserver>();\n\n/**\n * Bumped whenever a shared observer reports a `dir` change (and when an observer starts, since\n * nothing watched the document before). A cached direction is valid only for the version it was\n * read at.\n */\nlet cacheVersion = 0;\nconst directionCache = new WeakMap<Element, { version: number; direction: Direction }>();\n\nfunction noopUnsubscribe(): void {}\n\nfunction subscribeToDirectionChanges(doc: Document, notify: () => void): () => void {\n if (typeof MutationObserver === 'undefined') return noopUnsubscribe;\n let shared = sharedObservers.get(doc);\n if (!shared) {\n const listeners = new Set<() => void>();\n const observer = new MutationObserver(() => {\n cacheVersion += 1;\n for (const listener of Array.from(listeners)) listener();\n });\n observer.observe(doc.documentElement, {\n attributes: true,\n attributeFilter: ['dir'],\n subtree: true,\n });\n cacheVersion += 1;\n shared = { observer, listeners };\n sharedObservers.set(doc, shared);\n }\n const current = shared;\n // A wrapper per subscription, so two subscriptions with the same callback are counted twice.\n const listener = () => notify();\n current.listeners.add(listener);\n return () => {\n current.listeners.delete(listener);\n if (current.listeners.size === 0 && sharedObservers.get(doc) === current) {\n current.observer.disconnect();\n sharedObservers.delete(doc);\n }\n };\n}\n\n/**\n * `getDirection(el)`, cached per element while the shared observer of its document runs (that is,\n * while anything subscribes), so a render does not walk `getComputedStyle` up the ancestors.\n * Without an observer, or for a detached element, it is read directly.\n */\nfunction readDirection(el: Element): Direction {\n if (!el.isConnected || !sharedObservers.has(el.ownerDocument)) return getDirection(el);\n const cached = directionCache.get(el);\n if (cached && cached.version === cacheVersion) return cached.direction;\n const direction = getDirection(el);\n directionCache.set(el, { version: cacheVersion, direction });\n return direction;\n}\n\nfunction getDocumentDirection(): Direction {\n return readDirection(document.body ?? document.documentElement);\n}\n\nfunction getServerDirection(): Direction {\n return 'ltr';\n}\n\n/**\n * The writing direction for the caller.\n *\n * - **Without an element** (`useDirection()`): the `dir` of the nearest {@link WaveProvider};\n * outside a provider the document's direction (`<html dir=\"rtl\">`, updated when it changes).\n * Callers inside a provider do not read or observe the document.\n * - **With an element** (`useDirection(el)`): the direction that applies to that element — its own\n * `dir`, else the nearest ancestor's (an RTL section inside an LTR provider and the reverse), else\n * the computed direction (`getDirection(el)`). It updates when any `dir` attribute of the\n * element's document changes, including a WaveProvider root. While `el` is `null` (before the\n * element mounts) and on the server it falls back to the provider or document direction, as\n * without an element.\n * - `'ltr'` on the server outside a provider.\n *\n * All callers that observe the document share one ref-counted MutationObserver per document\n * (`dir` attributes, whole subtree). An element's direction is cached until that observer reports\n * a `dir` change, so re-renders do not read computed styles. Changes the observer cannot see are\n * not picked up until the next `dir` change: moving the element under a different `dir` ancestor,\n * or a stylesheet `direction` change without a `dir` attribute.\n *\n * Use it during render (placement of start/end popups, slide offsets, directional glyphs). Keyboard\n * handlers resolve direction at event time with `getDirection(event.currentTarget)` instead.\n *\n * @param el - The rendered element whose direction applies (typically from a callback ref kept in\n * state), or `null`/omitted for the provider or document direction.\n *\n * @example\n * const [root, setRoot] = React.useState<HTMLDivElement | null>(null);\n * const dir = useDirection(root);\n * return <div ref={setRoot} style={{ transform: `translateX(${dir === 'rtl' ? '' : '-'}${index * 100}%)` }} />;\n */\nexport function useDirection(el?: Element | null): Direction {\n const { dir, hasProvider } = useWaveTheme();\n const element = el ?? null;\n\n const subscribe = useCallback(\n (notify: () => void): (() => void) => {\n if (element) return subscribeToDirectionChanges(element.ownerDocument, notify);\n if (hasProvider || typeof document === 'undefined') return noopUnsubscribe;\n return subscribeToDirectionChanges(document, notify);\n },\n [element, hasProvider],\n );\n\n const getSnapshot = useCallback((): Direction => {\n if (element) return readDirection(element);\n return hasProvider ? dir : getDocumentDirection();\n }, [element, hasProvider, dir]);\n\n const getServerSnapshot = useCallback(\n (): Direction => (hasProvider ? dir : getServerDirection()),\n [hasProvider, dir],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n"],"mappings":"iIAcA,IAAMO,EAAkB,IAAI,IAOxBC,EAAe,EACbC,EAAiB,IAAI,QAE3B,SAASC,GAAwB,CAAC,CAElC,SAASC,EAA4B,EAAe,EAAgC,CAClF,GAAI,OAAO,iBAAqB,IAAa,OAAOD,EACpD,IAAIE,EAASL,EAAgB,IAAIM,CAAG,EACpC,GAAI,CAACD,EAAQ,CACX,IAAME,EAAY,IAAI,IAChBC,EAAW,IAAI,qBAAuB,CAC1C,GAAgB,EAChB,IAAK,IAAMC,KAAY,MAAM,KAAKF,CAAS,EAAGE,EAAS,CACzD,CAAC,EACDD,EAAS,QAAQF,EAAI,gBAAiB,CACpC,WAAY,GACZ,gBAAiB,CAAC,KAAK,EACvB,QAAS,EACX,CAAC,EACD,GAAgB,EAChB,EAAS,CAAE,WAAU,WAAU,EAC/BN,EAAgB,IAAIM,EAAKD,CAAM,CACjC,CACA,IAAMK,EAAUL,EAEVI,MAAiBE,EAAO,EAE9B,OADAD,EAAQ,UAAU,IAAID,CAAQ,MACjB,CACXC,EAAQ,UAAU,OAAOD,CAAQ,EAC7BC,EAAQ,UAAU,OAAS,GAAKV,EAAgB,IAAIM,CAAG,IAAMI,IAC/DA,EAAQ,SAAS,WAAW,EAC5BV,EAAgB,OAAOM,CAAG,EAE9B,CACF,CAOA,SAASM,EAAc,EAAwB,CAC7C,GAAI,CAACC,EAAG,aAAe,CAACb,EAAgB,IAAIa,EAAG,aAAa,EAAG,OAAOpB,EAAAA,aAAaoB,CAAE,EACrF,IAAMC,EAASZ,EAAe,IAAIW,CAAE,EACpC,GAAIC,GAAUA,EAAO,UAAYb,EAAc,OAAOa,EAAO,UAC7D,IAAMC,EAAYtB,EAAAA,aAAaoB,CAAE,EAEjC,OADAX,EAAe,IAAIW,EAAI,CAAE,QAASZ,EAAc,WAAU,CAAC,EACpDc,CACT,CAEA,SAASC,GAAkC,CACzC,OAAOJ,EAAc,SAAS,MAAQ,SAAS,eAAe,CAChE,CAEA,SAASK,GAAgC,CACvC,MAAO,KACT,CAiCA,SAAgBC,EAAa,EAAgC,CAC3D,GAAM,CAAE,MAAK,eAAgBxB,EAAAA,aAAa,EACpCyB,EAAUN,GAAM,KAEhBO,GAAA,EAAYzB,EAAAA,YAAAA,CACf,GACKwB,EAAgBf,EAA4Be,EAAQ,cAAeR,CAAM,EACzEU,GAAe,OAAO,SAAa,IAAoBlB,EACpDC,EAA4B,SAAUO,CAAM,EAErD,CAACQ,EAASE,CAAW,CACvB,EAEMC,GAAA,EAAc3B,EAAAA,YAAAA,KACdwB,EAAgBP,EAAcO,CAAO,EAClCE,EAAcE,EAAMP,EAAqB,EAC/C,CAACG,EAASE,EAAaE,CAAG,CAAC,EAExBC,GAAA,EAAoB7B,EAAAA,YAAAA,KACN0B,EAAcE,EAAMN,EAAmB,EACzD,CAACI,EAAaE,CAAG,CACnB,EAEA,OAAA,EAAO3B,EAAAA,qBAAAA,CAAqBwB,EAAWE,EAAaE,CAAiB,CACvE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { getDirection as e } from "../lib/direction.mjs";
|
|
3
|
+
import { useWaveTheme as t } from "../components/provider/WaveProvider.mjs";
|
|
4
|
+
import { useCallback as n, useSyncExternalStore as r } from "react";
|
|
5
|
+
//#region src/hooks/useDirection.ts
|
|
6
|
+
var i = /* @__PURE__ */ new Map(), a = 0, o = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function s() {}
|
|
8
|
+
function c(e, t) {
|
|
9
|
+
if (typeof MutationObserver > "u") return s;
|
|
10
|
+
let n = i.get(e);
|
|
11
|
+
if (!n) {
|
|
12
|
+
let t = /* @__PURE__ */ new Set(), r = new MutationObserver(() => {
|
|
13
|
+
a += 1;
|
|
14
|
+
for (let e of Array.from(t)) e();
|
|
15
|
+
});
|
|
16
|
+
r.observe(e.documentElement, {
|
|
17
|
+
attributes: !0,
|
|
18
|
+
attributeFilter: ["dir"],
|
|
19
|
+
subtree: !0
|
|
20
|
+
}), a += 1, n = {
|
|
21
|
+
observer: r,
|
|
22
|
+
listeners: t
|
|
23
|
+
}, i.set(e, n);
|
|
24
|
+
}
|
|
25
|
+
let r = n, o = () => t();
|
|
26
|
+
return r.listeners.add(o), () => {
|
|
27
|
+
r.listeners.delete(o), r.listeners.size === 0 && i.get(e) === r && (r.observer.disconnect(), i.delete(e));
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function l(t) {
|
|
31
|
+
if (!t.isConnected || !i.has(t.ownerDocument)) return e(t);
|
|
32
|
+
let n = o.get(t);
|
|
33
|
+
if (n && n.version === a) return n.direction;
|
|
34
|
+
let r = e(t);
|
|
35
|
+
return o.set(t, {
|
|
36
|
+
version: a,
|
|
37
|
+
direction: r
|
|
38
|
+
}), r;
|
|
39
|
+
}
|
|
40
|
+
function u() {
|
|
41
|
+
return l(document.body ?? document.documentElement);
|
|
42
|
+
}
|
|
43
|
+
function d() {
|
|
44
|
+
return "ltr";
|
|
45
|
+
}
|
|
46
|
+
function f(e) {
|
|
47
|
+
let { dir: i, hasProvider: a } = t(), o = e ?? null, f = n((e) => o ? c(o.ownerDocument, e) : a || typeof document > "u" ? s : c(document, e), [o, a]), p = n(() => o ? l(o) : a ? i : u(), [
|
|
48
|
+
o,
|
|
49
|
+
a,
|
|
50
|
+
i
|
|
51
|
+
]), m = n(() => a ? i : d(), [a, i]);
|
|
52
|
+
return r(f, p, m);
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { f as useDirection };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=useDirection.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDirection.mjs","names":["sharedObservers","cacheVersion","directionCache","noopUnsubscribe","subscribeToDirectionChanges","shared","doc","listeners","observer","listener","current","notify","readDirection","el","getDirection","cached","direction","getDocumentDirection","getServerDirection","useDirection","useWaveTheme","element","subscribe","useCallback","hasProvider","getSnapshot","dir","getServerSnapshot","useSyncExternalStore"],"sources":["../../src/hooks/useDirection.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\nimport { getDirection, type Direction } from '../lib/direction';\nimport { useWaveTheme } from '../components/provider/WaveProvider';\n\n/**\n * One MutationObserver per document, shared by every subscriber (ref-counted): it watches every\n * `dir` attribute of the document (`<html>`, WaveProvider roots, `dir` sections, the elements\n * themselves) and invalidates the cached directions before it notifies the subscribers.\n */\ninterface SharedDirectionObserver {\n observer: MutationObserver;\n listeners: Set<() => void>;\n}\n\nconst sharedObservers = new Map<Document, SharedDirectionObserver>();\n\n/**\n * Bumped whenever a shared observer reports a `dir` change (and when an observer starts, since\n * nothing watched the document before). A cached direction is valid only for the version it was\n * read at.\n */\nlet cacheVersion = 0;\nconst directionCache = new WeakMap<Element, { version: number; direction: Direction }>();\n\nfunction noopUnsubscribe(): void {}\n\nfunction subscribeToDirectionChanges(doc: Document, notify: () => void): () => void {\n if (typeof MutationObserver === 'undefined') return noopUnsubscribe;\n let shared = sharedObservers.get(doc);\n if (!shared) {\n const listeners = new Set<() => void>();\n const observer = new MutationObserver(() => {\n cacheVersion += 1;\n for (const listener of Array.from(listeners)) listener();\n });\n observer.observe(doc.documentElement, {\n attributes: true,\n attributeFilter: ['dir'],\n subtree: true,\n });\n cacheVersion += 1;\n shared = { observer, listeners };\n sharedObservers.set(doc, shared);\n }\n const current = shared;\n // A wrapper per subscription, so two subscriptions with the same callback are counted twice.\n const listener = () => notify();\n current.listeners.add(listener);\n return () => {\n current.listeners.delete(listener);\n if (current.listeners.size === 0 && sharedObservers.get(doc) === current) {\n current.observer.disconnect();\n sharedObservers.delete(doc);\n }\n };\n}\n\n/**\n * `getDirection(el)`, cached per element while the shared observer of its document runs (that is,\n * while anything subscribes), so a render does not walk `getComputedStyle` up the ancestors.\n * Without an observer, or for a detached element, it is read directly.\n */\nfunction readDirection(el: Element): Direction {\n if (!el.isConnected || !sharedObservers.has(el.ownerDocument)) return getDirection(el);\n const cached = directionCache.get(el);\n if (cached && cached.version === cacheVersion) return cached.direction;\n const direction = getDirection(el);\n directionCache.set(el, { version: cacheVersion, direction });\n return direction;\n}\n\nfunction getDocumentDirection(): Direction {\n return readDirection(document.body ?? document.documentElement);\n}\n\nfunction getServerDirection(): Direction {\n return 'ltr';\n}\n\n/**\n * The writing direction for the caller.\n *\n * - **Without an element** (`useDirection()`): the `dir` of the nearest {@link WaveProvider};\n * outside a provider the document's direction (`<html dir=\"rtl\">`, updated when it changes).\n * Callers inside a provider do not read or observe the document.\n * - **With an element** (`useDirection(el)`): the direction that applies to that element — its own\n * `dir`, else the nearest ancestor's (an RTL section inside an LTR provider and the reverse), else\n * the computed direction (`getDirection(el)`). It updates when any `dir` attribute of the\n * element's document changes, including a WaveProvider root. While `el` is `null` (before the\n * element mounts) and on the server it falls back to the provider or document direction, as\n * without an element.\n * - `'ltr'` on the server outside a provider.\n *\n * All callers that observe the document share one ref-counted MutationObserver per document\n * (`dir` attributes, whole subtree). An element's direction is cached until that observer reports\n * a `dir` change, so re-renders do not read computed styles. Changes the observer cannot see are\n * not picked up until the next `dir` change: moving the element under a different `dir` ancestor,\n * or a stylesheet `direction` change without a `dir` attribute.\n *\n * Use it during render (placement of start/end popups, slide offsets, directional glyphs). Keyboard\n * handlers resolve direction at event time with `getDirection(event.currentTarget)` instead.\n *\n * @param el - The rendered element whose direction applies (typically from a callback ref kept in\n * state), or `null`/omitted for the provider or document direction.\n *\n * @example\n * const [root, setRoot] = React.useState<HTMLDivElement | null>(null);\n * const dir = useDirection(root);\n * return <div ref={setRoot} style={{ transform: `translateX(${dir === 'rtl' ? '' : '-'}${index * 100}%)` }} />;\n */\nexport function useDirection(el?: Element | null): Direction {\n const { dir, hasProvider } = useWaveTheme();\n const element = el ?? null;\n\n const subscribe = useCallback(\n (notify: () => void): (() => void) => {\n if (element) return subscribeToDirectionChanges(element.ownerDocument, notify);\n if (hasProvider || typeof document === 'undefined') return noopUnsubscribe;\n return subscribeToDirectionChanges(document, notify);\n },\n [element, hasProvider],\n );\n\n const getSnapshot = useCallback((): Direction => {\n if (element) return readDirection(element);\n return hasProvider ? dir : getDocumentDirection();\n }, [element, hasProvider, dir]);\n\n const getServerSnapshot = useCallback(\n (): Direction => (hasProvider ? dir : getServerDirection()),\n [hasProvider, dir],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n"],"mappings":";;;;;AAcA,IAAMA,oBAAkB,IAAI,IAAuC,GAO/DC,IAAe,GACbC,oBAAiB,IAAI,QAA4D;AAEvF,SAASC,IAAwB,CAAC;AAElC,SAASC,EAA4B,GAAe,GAAgC;CAClF,IAAI,OAAO,mBAAqB,KAAa,OAAOD;CACpD,IAAIE,IAASL,EAAgB,IAAIM,CAAG;CACpC,IAAI,CAACD,GAAQ;EACX,IAAME,oBAAY,IAAI,IAAgB,GAChCC,IAAW,IAAI,uBAAuB;GAC1C,KAAgB;GAChB,KAAK,IAAMC,KAAY,MAAM,KAAKF,CAAS,GAAG,EAAS;EACzD,CAAC;EAQD,AAPAC,EAAS,QAAQF,EAAI,iBAAiB;GACpC,YAAY;GACZ,iBAAiB,CAAC,KAAK;GACvB,SAAS;EACX,CAAC,GACD,KAAgB,GAChB,IAAS;GAAE;GAAU;EAAU,GAC/BN,EAAgB,IAAIM,GAAKD,CAAM;CACjC;CACA,IAAMK,IAAUL,GAEVI,UAAiBE,EAAO;CAE9B,OADAD,EAAQ,UAAU,IAAID,CAAQ,SACjB;EAEX,AADAC,EAAQ,UAAU,OAAOD,CAAQ,GAC7BC,EAAQ,UAAU,SAAS,KAAKV,EAAgB,IAAIM,CAAG,MAAMI,MAC/DA,EAAQ,SAAS,WAAW,GAC5BV,EAAgB,OAAOM,CAAG;CAE9B;AACF;AAOA,SAASM,EAAc,GAAwB;CAC7C,IAAI,CAACC,EAAG,eAAe,CAACb,EAAgB,IAAIa,EAAG,aAAa,GAAG,OAAOC,EAAaD,CAAE;CACrF,IAAME,IAASb,EAAe,IAAIW,CAAE;CACpC,IAAIE,KAAUA,EAAO,YAAYd,GAAc,OAAOc,EAAO;CAC7D,IAAMC,IAAYF,EAAaD,CAAE;CAEjC,OADAX,EAAe,IAAIW,GAAI;EAAE,SAASZ;EAAc;CAAU,CAAC,GACpDe;AACT;AAEA,SAASC,IAAkC;CACzC,OAAOL,EAAc,SAAS,QAAQ,SAAS,eAAe;AAChE;AAEA,SAASM,IAAgC;CACvC,OAAO;AACT;AAiCA,SAAgBC,EAAa,GAAgC;CAC3D,IAAM,EAAE,QAAK,mBAAgBC,EAAa,GACpCC,IAAUR,KAAM,MAEhBS,IAAYC,GACf,MACKF,IAAgBjB,EAA4BiB,EAAQ,eAAeV,CAAM,IACzEa,KAAe,OAAO,WAAa,MAAoBrB,IACpDC,EAA4B,UAAUO,CAAM,GAErD,CAACU,GAASG,CAAW,CACvB,GAEMC,IAAcF,QACdF,IAAgBT,EAAcS,CAAO,IAClCG,IAAcE,IAAMT,EAAqB,GAC/C;EAACI;EAASG;EAAaE;CAAG,CAAC,GAExBC,IAAoBJ,QACNC,IAAcE,IAAMR,EAAmB,GACzD,CAACM,GAAaE,CAAG,CACnB;CAEA,OAAOE,EAAqBN,GAAWG,GAAaE,CAAiB;AACvE"}
|