@primitiv-ui/react 0.1.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 +79 -0
- package/package.json +59 -0
- package/src/AccessibleIcon/AccessibleIcon.tsx +40 -0
- package/src/AccessibleIcon/README.md +42 -0
- package/src/AccessibleIcon/__tests__/AccessibleIcon.test.tsx +47 -0
- package/src/AccessibleIcon/index.ts +2 -0
- package/src/AccessibleIcon/types.ts +8 -0
- package/src/Accordion/Accordion.tsx +412 -0
- package/src/Accordion/AccordionContext.ts +12 -0
- package/src/Accordion/README.md +202 -0
- package/src/Accordion/__tests__/Accordion.asChild.test.tsx +237 -0
- package/src/Accordion/__tests__/Accordion.basic-rendering.test.tsx +333 -0
- package/src/Accordion/__tests__/Accordion.controlled-state.test.tsx +175 -0
- package/src/Accordion/__tests__/Accordion.data-attributes.test.tsx +272 -0
- package/src/Accordion/__tests__/Accordion.disabled-items.test.tsx +311 -0
- package/src/Accordion/__tests__/Accordion.error-handling.test.tsx +119 -0
- package/src/Accordion/__tests__/Accordion.forceMount.test.tsx +119 -0
- package/src/Accordion/__tests__/Accordion.keyboard-interaction.test.tsx +736 -0
- package/src/Accordion/__tests__/Accordion.mouse-interaction.test.tsx +212 -0
- package/src/Accordion/__tests__/Accordion.multiple-mode.test.tsx +90 -0
- package/src/Accordion/__tests__/Accordion.reading-direction.test.tsx +139 -0
- package/src/Accordion/__tests__/Accordion.uncontrolled-state.test.tsx +154 -0
- package/src/Accordion/hooks/index.ts +6 -0
- package/src/Accordion/hooks/useAccordionContext.ts +1 -0
- package/src/Accordion/hooks/useAccordionHeaderContext.ts +10 -0
- package/src/Accordion/hooks/useAccordionItem.ts +22 -0
- package/src/Accordion/hooks/useAccordionItemContext.ts +1 -0
- package/src/Accordion/hooks/useAccordionRoot.ts +151 -0
- package/src/Accordion/hooks/useAccordionTrigger.ts +90 -0
- package/src/Accordion/index.ts +1 -0
- package/src/Accordion/types.ts +81 -0
- package/src/Alert/Alert.tsx +43 -0
- package/src/Alert/README.md +54 -0
- package/src/Alert/__tests__/Alert.test.tsx +28 -0
- package/src/Alert/index.ts +2 -0
- package/src/Alert/types.ts +5 -0
- package/src/Avatar/Avatar.tsx +149 -0
- package/src/Avatar/AvatarContext.ts +20 -0
- package/src/Avatar/README.md +116 -0
- package/src/Avatar/__tests__/Avatar.asChild.test.tsx +53 -0
- package/src/Avatar/__tests__/Avatar.basic-rendering.test.tsx +14 -0
- package/src/Avatar/__tests__/Avatar.error-handling.test.tsx +30 -0
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +75 -0
- package/src/Avatar/__tests__/Avatar.image-loading.test.tsx +81 -0
- package/src/Avatar/hooks/index.ts +2 -0
- package/src/Avatar/hooks/useAvatarContext.ts +1 -0
- package/src/Avatar/hooks/useAvatarImage.ts +40 -0
- package/src/Avatar/index.ts +3 -0
- package/src/Avatar/types.ts +44 -0
- package/src/Breadcrumb/Breadcrumb.tsx +234 -0
- package/src/Breadcrumb/README.md +111 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.asChild.test.tsx +33 -0
- package/src/Breadcrumb/__tests__/Breadcrumb.basic-rendering.test.tsx +132 -0
- package/src/Breadcrumb/index.ts +2 -0
- package/src/Breadcrumb/types.ts +22 -0
- package/src/Button/Button.tsx +95 -0
- package/src/Button/README.md +112 -0
- package/src/Button/__tests__/Button.asChild.test.tsx +91 -0
- package/src/Button/__tests__/Button.basic-rendering.test.tsx +126 -0
- package/src/Button/__tests__/Button.contract.test.tsx +72 -0
- package/src/Button/__tests__/Button.disabled.test.tsx +52 -0
- package/src/Button/__tests__/Button.icon-usage.test.tsx +57 -0
- package/src/Button/__tests__/Button.keyboard-interaction.test.tsx +70 -0
- package/src/Button/index.ts +2 -0
- package/src/Button/types.ts +8 -0
- package/src/Carousel/Carousel.tsx +708 -0
- package/src/Carousel/CarouselContext.ts +11 -0
- package/src/Carousel/README.md +848 -0
- package/src/Carousel/__tests__/Carousel.asChild.test.tsx +178 -0
- package/src/Carousel/__tests__/Carousel.auto-play.test.tsx +617 -0
- package/src/Carousel/__tests__/Carousel.basic-rendering.test.tsx +569 -0
- package/src/Carousel/__tests__/Carousel.controlled-state.test.tsx +137 -0
- package/src/Carousel/__tests__/Carousel.error-handling.test.tsx +81 -0
- package/src/Carousel/__tests__/Carousel.ids.test.tsx +111 -0
- package/src/Carousel/__tests__/Carousel.imperative-api.test.tsx +213 -0
- package/src/Carousel/__tests__/Carousel.indicators.test.tsx +560 -0
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +276 -0
- package/src/Carousel/__tests__/Carousel.keyboard-navigation.test.tsx +158 -0
- package/src/Carousel/__tests__/Carousel.play-pause.test.tsx +232 -0
- package/src/Carousel/__tests__/Carousel.prev-next.test.tsx +68 -0
- package/src/Carousel/__tests__/Carousel.reduced-motion.test.tsx +49 -0
- package/src/Carousel/__tests__/Carousel.refresh-progress.test.tsx +87 -0
- package/src/Carousel/__tests__/Carousel.scroll-snap-change.test.tsx +179 -0
- package/src/Carousel/__tests__/Carousel.scroll-sync.test.tsx +109 -0
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +151 -0
- package/src/Carousel/__tests__/Carousel.slides-per-page.test.tsx +183 -0
- package/src/Carousel/__tests__/Carousel.touch-interaction.test.tsx +96 -0
- package/src/Carousel/__tests__/Carousel.transition-modes.test.tsx +70 -0
- package/src/Carousel/__tests__/Carousel.translations.test.tsx +157 -0
- package/src/Carousel/__tests__/Carousel.uncontrolled-state.test.tsx +146 -0
- package/src/Carousel/hooks/index.ts +4 -0
- package/src/Carousel/hooks/useCarouselContext.ts +13 -0
- package/src/Carousel/hooks/useCarouselRoot.ts +450 -0
- package/src/Carousel/hooks/useCarouselSlide.ts +45 -0
- package/src/Carousel/hooks/useCarouselViewport.ts +290 -0
- package/src/Carousel/index.ts +3 -0
- package/src/Carousel/types.ts +400 -0
- package/src/Checkbox/Checkbox.tsx +228 -0
- package/src/Checkbox/CheckboxContext.ts +12 -0
- package/src/Checkbox/README.md +156 -0
- package/src/Checkbox/__tests__/Checkbox.asChild.test.tsx +69 -0
- package/src/Checkbox/__tests__/Checkbox.basic-rendering.test.tsx +41 -0
- package/src/Checkbox/__tests__/Checkbox.controlled-state.test.tsx +82 -0
- package/src/Checkbox/__tests__/Checkbox.disabled.test.tsx +15 -0
- package/src/Checkbox/__tests__/Checkbox.indeterminate.test.tsx +82 -0
- package/src/Checkbox/__tests__/Checkbox.indicator.test.tsx +117 -0
- package/src/Checkbox/__tests__/Checkbox.uncontrolled-state.test.tsx +89 -0
- package/src/Checkbox/hooks/index.ts +2 -0
- package/src/Checkbox/hooks/useCheckboxContext.ts +1 -0
- package/src/Checkbox/hooks/useCheckboxRoot.ts +32 -0
- package/src/Checkbox/index.ts +1 -0
- package/src/Checkbox/types.ts +33 -0
- package/src/CheckboxCard/CheckboxCard.tsx +208 -0
- package/src/CheckboxCard/CheckboxCardContext.ts +12 -0
- package/src/CheckboxCard/README.md +114 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.asChild.test.tsx +54 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.basic-rendering.test.tsx +58 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.controlled-state.test.tsx +77 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.disabled.test.tsx +55 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.error-handling.test.tsx +20 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.indeterminate.test.tsx +60 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.indicator.test.tsx +136 -0
- package/src/CheckboxCard/__tests__/CheckboxCard.uncontrolled-state.test.tsx +73 -0
- package/src/CheckboxCard/hooks/index.ts +2 -0
- package/src/CheckboxCard/hooks/useCheckboxCardContext.ts +1 -0
- package/src/CheckboxCard/hooks/useCheckboxCardRoot.ts +30 -0
- package/src/CheckboxCard/index.ts +3 -0
- package/src/CheckboxCard/types.ts +33 -0
- package/src/Collapsible/Collapsible.tsx +316 -0
- package/src/Collapsible/CollapsibleContext.ts +7 -0
- package/src/Collapsible/README.md +174 -0
- package/src/Collapsible/__tests__/Collapsible.asChild.test.tsx +240 -0
- package/src/Collapsible/__tests__/Collapsible.basic-rendering.test.tsx +118 -0
- package/src/Collapsible/__tests__/Collapsible.controlled-state.test.tsx +134 -0
- package/src/Collapsible/__tests__/Collapsible.disabled.test.tsx +132 -0
- package/src/Collapsible/__tests__/Collapsible.error-handling.test.tsx +40 -0
- package/src/Collapsible/__tests__/Collapsible.forceMount.test.tsx +111 -0
- package/src/Collapsible/__tests__/Collapsible.triggerIcon.test.tsx +93 -0
- package/src/Collapsible/__tests__/Collapsible.uncontrolled-state.test.tsx +125 -0
- package/src/Collapsible/hooks/index.ts +2 -0
- package/src/Collapsible/hooks/useCollapsibleRoot.ts +34 -0
- package/src/Collapsible/hooks/useCollapsibleTrigger.ts +49 -0
- package/src/Collapsible/index.ts +1 -0
- package/src/Collapsible/types.ts +48 -0
- package/src/ContextMenu/ContextMenu.tsx +1004 -0
- package/src/ContextMenu/ContextMenuContentContext.ts +15 -0
- package/src/ContextMenu/ContextMenuContext.ts +21 -0
- package/src/ContextMenu/ContextMenuGroupContext.ts +8 -0
- package/src/ContextMenu/ContextMenuItemIndicatorContext.ts +8 -0
- package/src/ContextMenu/ContextMenuRadioGroupContext.ts +9 -0
- package/src/ContextMenu/ContextMenuSubContext.ts +15 -0
- package/src/ContextMenu/README.md +275 -0
- package/src/ContextMenu/__tests__/ContextMenu.asChild.test.tsx +186 -0
- package/src/ContextMenu/__tests__/ContextMenu.basic-rendering.test.tsx +39 -0
- package/src/ContextMenu/__tests__/ContextMenu.checkbox-item.test.tsx +145 -0
- package/src/ContextMenu/__tests__/ContextMenu.error-handling.test.tsx +113 -0
- package/src/ContextMenu/__tests__/ContextMenu.group-label.test.tsx +48 -0
- package/src/ContextMenu/__tests__/ContextMenu.item-indicator.test.tsx +88 -0
- package/src/ContextMenu/__tests__/ContextMenu.item.test.tsx +106 -0
- package/src/ContextMenu/__tests__/ContextMenu.keyboard-interaction.test.tsx +172 -0
- package/src/ContextMenu/__tests__/ContextMenu.mouse-interaction.test.tsx +227 -0
- package/src/ContextMenu/__tests__/ContextMenu.radio-item.test.tsx +127 -0
- package/src/ContextMenu/__tests__/ContextMenu.reading-direction.test.tsx +152 -0
- package/src/ContextMenu/__tests__/ContextMenu.separator.test.tsx +47 -0
- package/src/ContextMenu/__tests__/ContextMenu.state-modes.test.tsx +119 -0
- package/src/ContextMenu/__tests__/ContextMenu.sub.test.tsx +262 -0
- package/src/ContextMenu/__tests__/ContextMenu.typeahead.test.tsx +89 -0
- package/src/ContextMenu/constants.ts +4 -0
- package/src/ContextMenu/index.ts +1 -0
- package/src/ContextMenu/types.ts +199 -0
- package/src/DirectionProvider/DirectionContext.ts +21 -0
- package/src/DirectionProvider/DirectionProvider.tsx +31 -0
- package/src/DirectionProvider/README.md +62 -0
- package/src/DirectionProvider/__tests__/DirectionProvider.test.tsx +29 -0
- package/src/DirectionProvider/index.ts +3 -0
- package/src/DirectionProvider/types.ts +10 -0
- package/src/Divider/Divider.tsx +57 -0
- package/src/Divider/README.md +57 -0
- package/src/Divider/__tests__/Divider.test.tsx +41 -0
- package/src/Divider/index.ts +1 -0
- package/src/Divider/types.ts +5 -0
- package/src/Dropdown/Dropdown.tsx +842 -0
- package/src/Dropdown/DropdownContentContext.ts +15 -0
- package/src/Dropdown/DropdownContext.ts +17 -0
- package/src/Dropdown/DropdownGroupContext.ts +8 -0
- package/src/Dropdown/DropdownItemIndicatorContext.ts +13 -0
- package/src/Dropdown/DropdownRadioGroupContext.ts +9 -0
- package/src/Dropdown/DropdownSubContext.ts +15 -0
- package/src/Dropdown/README.md +284 -0
- package/src/Dropdown/__tests__/Dropdown.asChild.test.tsx +286 -0
- package/src/Dropdown/__tests__/Dropdown.basic-rendering.test.tsx +43 -0
- package/src/Dropdown/__tests__/Dropdown.checkbox-item.test.tsx +121 -0
- package/src/Dropdown/__tests__/Dropdown.disabled.test.tsx +143 -0
- package/src/Dropdown/__tests__/Dropdown.error-handling.test.tsx +85 -0
- package/src/Dropdown/__tests__/Dropdown.group-label.test.tsx +68 -0
- package/src/Dropdown/__tests__/Dropdown.item-indicator.test.tsx +260 -0
- package/src/Dropdown/__tests__/Dropdown.item.test.tsx +72 -0
- package/src/Dropdown/__tests__/Dropdown.keyboard-edge-cases.test.tsx +77 -0
- package/src/Dropdown/__tests__/Dropdown.keyboard-interaction.test.tsx +310 -0
- package/src/Dropdown/__tests__/Dropdown.mouse-interaction.test.tsx +347 -0
- package/src/Dropdown/__tests__/Dropdown.radio-item.test.tsx +134 -0
- package/src/Dropdown/__tests__/Dropdown.reading-direction.test.tsx +153 -0
- package/src/Dropdown/__tests__/Dropdown.separator.test.tsx +46 -0
- package/src/Dropdown/__tests__/Dropdown.state-modes.test.tsx +100 -0
- package/src/Dropdown/__tests__/Dropdown.sub.test.tsx +185 -0
- package/src/Dropdown/__tests__/Dropdown.trigger.test.tsx +110 -0
- package/src/Dropdown/__tests__/Dropdown.typeahead.test.tsx +133 -0
- package/src/Dropdown/constants.ts +4 -0
- package/src/Dropdown/hooks/index.ts +9 -0
- package/src/Dropdown/hooks/useCloseSiblingSub.ts +13 -0
- package/src/Dropdown/hooks/useDropdownContent.ts +162 -0
- package/src/Dropdown/hooks/useDropdownContext.ts +1 -0
- package/src/Dropdown/hooks/useDropdownGroup.ts +18 -0
- package/src/Dropdown/hooks/useDropdownItem.ts +49 -0
- package/src/Dropdown/hooks/useDropdownLabel.ts +15 -0
- package/src/Dropdown/hooks/useDropdownRoot.ts +57 -0
- package/src/Dropdown/hooks/useDropdownSubContext.ts +1 -0
- package/src/Dropdown/hooks/useDropdownTrigger.ts +31 -0
- package/src/Dropdown/index.ts +1 -0
- package/src/Dropdown/types.ts +200 -0
- package/src/EmptyState/EmptyState.tsx +245 -0
- package/src/EmptyState/README.md +129 -0
- package/src/EmptyState/__tests__/EmptyState.Actions.test.tsx +32 -0
- package/src/EmptyState/__tests__/EmptyState.Description.test.tsx +30 -0
- package/src/EmptyState/__tests__/EmptyState.Media.test.tsx +34 -0
- package/src/EmptyState/__tests__/EmptyState.Root.test.tsx +28 -0
- package/src/EmptyState/__tests__/EmptyState.Title.test.tsx +26 -0
- package/src/EmptyState/index.ts +2 -0
- package/src/EmptyState/types.ts +21 -0
- package/src/Field/Field.tsx +239 -0
- package/src/Field/FieldContext.ts +22 -0
- package/src/Field/README.md +167 -0
- package/src/Field/__tests__/Field.asChild.test.tsx +83 -0
- package/src/Field/__tests__/Field.basic-rendering.test.tsx +104 -0
- package/src/Field/__tests__/Field.state-cascade.test.tsx +75 -0
- package/src/Field/hooks/index.ts +2 -0
- package/src/Field/hooks/useFieldContext.ts +1 -0
- package/src/Field/hooks/useFieldProps.ts +57 -0
- package/src/Field/index.ts +2 -0
- package/src/Field/types.ts +33 -0
- package/src/Fieldset/Fieldset.tsx +104 -0
- package/src/Fieldset/README.md +74 -0
- package/src/Fieldset/__tests__/Fieldset.basic-rendering.test.tsx +81 -0
- package/src/Fieldset/__tests__/Fieldset.disabled.test.tsx +41 -0
- package/src/Fieldset/index.ts +2 -0
- package/src/Fieldset/types.ts +5 -0
- package/src/Input/Input.tsx +120 -0
- package/src/Input/README.md +180 -0
- package/src/Input/__tests__/Input.asChild.test.tsx +85 -0
- package/src/Input/__tests__/Input.basic-rendering.test.tsx +118 -0
- package/src/Input/__tests__/Input.disabled.test.tsx +49 -0
- package/src/Input/__tests__/Input.field-integration.test.tsx +148 -0
- package/src/Input/index.ts +2 -0
- package/src/Input/types.ts +7 -0
- package/src/InputGroup/InputGroup.tsx +228 -0
- package/src/InputGroup/README.md +178 -0
- package/src/InputGroup/__tests__/InputGroup.asChild.test.tsx +109 -0
- package/src/InputGroup/__tests__/InputGroup.basic-rendering.test.tsx +106 -0
- package/src/InputGroup/index.ts +2 -0
- package/src/InputGroup/types.ts +13 -0
- package/src/MillerColumns/MillerColumns.tsx +329 -0
- package/src/MillerColumns/MillerColumnsContext.ts +25 -0
- package/src/MillerColumns/README.md +278 -0
- package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +82 -0
- package/src/MillerColumns/__tests__/MillerColumns.asChild.test.tsx +106 -0
- package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +68 -0
- package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +52 -0
- package/src/MillerColumns/__tests__/MillerColumns.column-projection.test.tsx +161 -0
- package/src/MillerColumns/__tests__/MillerColumns.controlled-state.test.tsx +90 -0
- package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +77 -0
- package/src/MillerColumns/__tests__/MillerColumns.disabled-items.test.tsx +65 -0
- package/src/MillerColumns/__tests__/MillerColumns.error-handling.test.tsx +57 -0
- package/src/MillerColumns/__tests__/MillerColumns.fixtures.ts +15 -0
- package/src/MillerColumns/__tests__/MillerColumns.item-indicator.test.tsx +57 -0
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +181 -0
- package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +47 -0
- package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +137 -0
- package/src/MillerColumns/__tests__/MillerColumns.roving-tabindex.test.tsx +91 -0
- package/src/MillerColumns/__tests__/MillerColumns.selection.test.tsx +54 -0
- package/src/MillerColumns/__tests__/MillerColumns.uncontrolled-state.test.tsx +70 -0
- package/src/MillerColumns/hooks/index.ts +7 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +23 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumnContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsItem.ts +157 -0
- package/src/MillerColumns/hooks/useMillerColumnsItemContext.ts +1 -0
- package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +76 -0
- package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
- package/src/MillerColumns/index.ts +3 -0
- package/src/MillerColumns/types.ts +93 -0
- package/src/MillerColumns/useMillerColumnsSelection.ts +31 -0
- package/src/MillerColumns/utils.ts +75 -0
- package/src/Modal/Modal.tsx +474 -0
- package/src/Modal/ModalContext.ts +13 -0
- package/src/Modal/README.md +207 -0
- package/src/Modal/__tests__/Modal.accessibility.test.tsx +167 -0
- package/src/Modal/__tests__/Modal.asChild.test.tsx +162 -0
- package/src/Modal/__tests__/Modal.click-outside.test.tsx +115 -0
- package/src/Modal/__tests__/Modal.content.test.tsx +193 -0
- package/src/Modal/__tests__/Modal.controlled-state.test.tsx +120 -0
- package/src/Modal/__tests__/Modal.error-handling.test.tsx +30 -0
- package/src/Modal/__tests__/Modal.escape-hatches.test.tsx +99 -0
- package/src/Modal/__tests__/Modal.imperative-api.test.tsx +119 -0
- package/src/Modal/__tests__/Modal.nested.test.tsx +106 -0
- package/src/Modal/__tests__/Modal.overlay.test.tsx +99 -0
- package/src/Modal/__tests__/Modal.portal.test.tsx +90 -0
- package/src/Modal/__tests__/Modal.presence.test.tsx +111 -0
- package/src/Modal/__tests__/Modal.trigger.test.tsx +70 -0
- package/src/Modal/__tests__/Modal.uncontrolled-state.test.tsx +72 -0
- package/src/Modal/__tests__/dialog-polyfill.ts +40 -0
- package/src/Modal/hooks/index.ts +4 -0
- package/src/Modal/hooks/useModalContent.ts +62 -0
- package/src/Modal/hooks/useModalContext.ts +1 -0
- package/src/Modal/hooks/useModalRoot.ts +81 -0
- package/src/Modal/hooks/useModalTrigger.ts +25 -0
- package/src/Modal/index.ts +3 -0
- package/src/Modal/types.ts +76 -0
- package/src/Portal/Portal.tsx +28 -0
- package/src/Portal/README.md +70 -0
- package/src/Portal/__tests__/Portal.basic-rendering.test.tsx +17 -0
- package/src/Portal/index.ts +2 -0
- package/src/Portal/types.ts +6 -0
- package/src/Progress/Progress.tsx +178 -0
- package/src/Progress/ProgressContext.ts +15 -0
- package/src/Progress/README.md +112 -0
- package/src/Progress/__tests__/Progress.asChild.test.tsx +37 -0
- package/src/Progress/__tests__/Progress.basic-rendering.test.tsx +65 -0
- package/src/Progress/__tests__/Progress.error-handling.test.tsx +40 -0
- package/src/Progress/__tests__/Progress.fixtures.ts +7 -0
- package/src/Progress/__tests__/Progress.value.test.tsx +83 -0
- package/src/Progress/hooks/index.ts +2 -0
- package/src/Progress/hooks/useProgressContext.ts +1 -0
- package/src/Progress/hooks/useProgressRoot.ts +45 -0
- package/src/Progress/index.ts +3 -0
- package/src/Progress/types.ts +43 -0
- package/src/RadioCard/README.md +133 -0
- package/src/RadioCard/RadioCard.tsx +334 -0
- package/src/RadioCard/RadioCardContext.ts +23 -0
- package/src/RadioCard/RadioCardItemContext.ts +10 -0
- package/src/RadioCard/__tests__/RadioCard.asChild.test.tsx +76 -0
- package/src/RadioCard/__tests__/RadioCard.basic-rendering.test.tsx +87 -0
- package/src/RadioCard/__tests__/RadioCard.controlled-state.test.tsx +107 -0
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +61 -0
- package/src/RadioCard/__tests__/RadioCard.error-handling.test.tsx +35 -0
- package/src/RadioCard/__tests__/RadioCard.indicator.test.tsx +119 -0
- package/src/RadioCard/__tests__/RadioCard.keyboard-interaction.test.tsx +158 -0
- package/src/RadioCard/__tests__/RadioCard.orientation.test.tsx +90 -0
- package/src/RadioCard/__tests__/RadioCard.reading-direction.test.tsx +65 -0
- package/src/RadioCard/__tests__/RadioCard.uncontrolled-state.test.tsx +108 -0
- package/src/RadioCard/hooks/index.ts +3 -0
- package/src/RadioCard/hooks/useRadioCardContext.ts +1 -0
- package/src/RadioCard/hooks/useRadioCardItemContext.ts +1 -0
- package/src/RadioCard/hooks/useRadioCardRoot.ts +77 -0
- package/src/RadioCard/index.ts +4 -0
- package/src/RadioCard/types.ts +51 -0
- package/src/RadioGroup/README.md +185 -0
- package/src/RadioGroup/RadioGroup.tsx +353 -0
- package/src/RadioGroup/RadioGroupContext.ts +23 -0
- package/src/RadioGroup/RadioGroupItemContext.ts +10 -0
- package/src/RadioGroup/__tests__/RadioGroup.asChild.test.tsx +105 -0
- package/src/RadioGroup/__tests__/RadioGroup.basic-rendering.test.tsx +72 -0
- package/src/RadioGroup/__tests__/RadioGroup.controlled-state.test.tsx +109 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled-keydown-guards.test.tsx +68 -0
- package/src/RadioGroup/__tests__/RadioGroup.disabled.test.tsx +79 -0
- package/src/RadioGroup/__tests__/RadioGroup.error-handling.test.tsx +33 -0
- package/src/RadioGroup/__tests__/RadioGroup.indicator.test.tsx +85 -0
- package/src/RadioGroup/__tests__/RadioGroup.keyboard-interaction.test.tsx +135 -0
- package/src/RadioGroup/__tests__/RadioGroup.orientation.test.tsx +90 -0
- package/src/RadioGroup/__tests__/RadioGroup.reading-direction.test.tsx +65 -0
- package/src/RadioGroup/__tests__/RadioGroup.ref-forwarding.test.tsx +45 -0
- package/src/RadioGroup/__tests__/RadioGroup.roving-tabindex.test.tsx +105 -0
- package/src/RadioGroup/__tests__/RadioGroup.uncontrolled-state.test.tsx +96 -0
- package/src/RadioGroup/hooks/index.ts +3 -0
- package/src/RadioGroup/hooks/useRadioGroupContext.ts +1 -0
- package/src/RadioGroup/hooks/useRadioGroupItemContext.ts +1 -0
- package/src/RadioGroup/hooks/useRadioGroupRoot.ts +87 -0
- package/src/RadioGroup/index.ts +1 -0
- package/src/RadioGroup/types.ts +51 -0
- package/src/Select/README.md +203 -0
- package/src/Select/Select.tsx +204 -0
- package/src/Select/__tests__/Select.asChild.test.tsx +36 -0
- package/src/Select/__tests__/Select.basic-rendering.test.tsx +17 -0
- package/src/Select/__tests__/Select.controlled-state.test.tsx +69 -0
- package/src/Select/__tests__/Select.data-attributes.test.tsx +29 -0
- package/src/Select/__tests__/Select.field-integration.test.tsx +150 -0
- package/src/Select/__tests__/Select.group.test.tsx +42 -0
- package/src/Select/__tests__/Select.placeholder.test.tsx +32 -0
- package/src/Select/index.ts +2 -0
- package/src/Select/types.ts +89 -0
- package/src/SkipNav/README.md +87 -0
- package/src/SkipNav/SkipNav.tsx +116 -0
- package/src/SkipNav/__tests__/SkipNav.basic-rendering.test.tsx +23 -0
- package/src/SkipNav/__tests__/SkipNav.ids.test.tsx +19 -0
- package/src/SkipNav/index.ts +1 -0
- package/src/SkipNav/types.ts +26 -0
- package/src/Slider/README.md +215 -0
- package/src/Slider/Slider.tsx +308 -0
- package/src/Slider/SliderContext.ts +24 -0
- package/src/Slider/__tests__/Slider.asChild.test.tsx +119 -0
- package/src/Slider/__tests__/Slider.basic-rendering.test.tsx +157 -0
- package/src/Slider/__tests__/Slider.controlled-state.test.tsx +78 -0
- package/src/Slider/__tests__/Slider.disabled.test.tsx +82 -0
- package/src/Slider/__tests__/Slider.error-handling.test.tsx +45 -0
- package/src/Slider/__tests__/Slider.fixtures.ts +53 -0
- package/src/Slider/__tests__/Slider.form.test.tsx +67 -0
- package/src/Slider/__tests__/Slider.inverted.test.tsx +112 -0
- package/src/Slider/__tests__/Slider.keyboard-interaction.test.tsx +118 -0
- package/src/Slider/__tests__/Slider.multiple-thumbs.test.tsx +84 -0
- package/src/Slider/__tests__/Slider.orientation.test.tsx +101 -0
- package/src/Slider/__tests__/Slider.pointer-interaction.test.tsx +205 -0
- package/src/Slider/__tests__/Slider.reading-direction.test.tsx +99 -0
- package/src/Slider/__tests__/Slider.uncontrolled-state.test.tsx +69 -0
- package/src/Slider/__tests__/Slider.value-commit.test.tsx +103 -0
- package/src/Slider/hooks/index.ts +3 -0
- package/src/Slider/hooks/useSliderContext.ts +1 -0
- package/src/Slider/hooks/useSliderRoot.ts +197 -0
- package/src/Slider/hooks/useSliderThumb.ts +77 -0
- package/src/Slider/index.ts +3 -0
- package/src/Slider/types.ts +48 -0
- package/src/Slider/utils.ts +155 -0
- package/src/Slot/Slot.tsx +158 -0
- package/src/Slot/__tests__/Slot.test.tsx +163 -0
- package/src/Slot/__tests__/composeEventHandlers.test.ts +74 -0
- package/src/Slot/composeEventHandlers.ts +38 -0
- package/src/Slot/index.ts +3 -0
- package/src/Slot/types.ts +5 -0
- package/src/Status/README.md +50 -0
- package/src/Status/Status.tsx +44 -0
- package/src/Status/__tests__/Status.test.tsx +28 -0
- package/src/Status/index.ts +2 -0
- package/src/Status/types.ts +5 -0
- package/src/Switch/README.md +121 -0
- package/src/Switch/Switch.tsx +167 -0
- package/src/Switch/SwitchContext.ts +10 -0
- package/src/Switch/__tests__/Switch.asChild.test.tsx +56 -0
- package/src/Switch/__tests__/Switch.basic-rendering.test.tsx +76 -0
- package/src/Switch/__tests__/Switch.contract.test.tsx +109 -0
- package/src/Switch/__tests__/Switch.controlled-state.test.tsx +79 -0
- package/src/Switch/__tests__/Switch.disabled.test.tsx +60 -0
- package/src/Switch/__tests__/Switch.error-handling.test.tsx +20 -0
- package/src/Switch/__tests__/Switch.keyboard-interaction.test.tsx +56 -0
- package/src/Switch/__tests__/Switch.thumb.test.tsx +84 -0
- package/src/Switch/__tests__/Switch.uncontrolled-state.test.tsx +83 -0
- package/src/Switch/hooks/index.ts +2 -0
- package/src/Switch/hooks/useSwitchContext.ts +1 -0
- package/src/Switch/hooks/useSwitchRoot.ts +28 -0
- package/src/Switch/index.ts +3 -0
- package/src/Switch/types.ts +37 -0
- package/src/Table/README.md +205 -0
- package/src/Table/Table.tsx +380 -0
- package/src/Table/__tests__/Table.Body.test.tsx +61 -0
- package/src/Table/__tests__/Table.Caption.test.tsx +70 -0
- package/src/Table/__tests__/Table.Cell.test.tsx +73 -0
- package/src/Table/__tests__/Table.Footer.test.tsx +61 -0
- package/src/Table/__tests__/Table.Head.test.tsx +61 -0
- package/src/Table/__tests__/Table.Header.test.tsx +73 -0
- package/src/Table/__tests__/Table.Root.test.tsx +49 -0
- package/src/Table/__tests__/Table.Row.test.tsx +67 -0
- package/src/Table/__tests__/Table.ScrollArea.test.tsx +83 -0
- package/src/Table/index.ts +1 -0
- package/src/Table/types.ts +63 -0
- package/src/Tabs/README.md +110 -0
- package/src/Tabs/Tabs.tsx +434 -0
- package/src/Tabs/TabsContext.ts +13 -0
- package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +114 -0
- package/src/Tabs/__tests__/Tabs.asChild.test.tsx +91 -0
- package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +483 -0
- package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +133 -0
- package/src/Tabs/__tests__/Tabs.controlled-state.test.tsx +152 -0
- package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +203 -0
- package/src/Tabs/__tests__/Tabs.error-handling.test.tsx +82 -0
- package/src/Tabs/__tests__/Tabs.fixtures.ts +171 -0
- package/src/Tabs/__tests__/Tabs.imperative-api.test.tsx +118 -0
- package/src/Tabs/__tests__/Tabs.keyboard-interaction.test.tsx +192 -0
- package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +61 -0
- package/src/Tabs/__tests__/Tabs.mouse-interaction.test.tsx +216 -0
- package/src/Tabs/__tests__/Tabs.reading-direction.test.tsx +58 -0
- package/src/Tabs/__tests__/Tabs.uncontrolled-state.test.tsx +197 -0
- package/src/Tabs/hooks/index.ts +4 -0
- package/src/Tabs/hooks/useTabsContent.ts +27 -0
- package/src/Tabs/hooks/useTabsContext.ts +1 -0
- package/src/Tabs/hooks/useTabsRoot.ts +148 -0
- package/src/Tabs/hooks/useTabsTrigger.ts +111 -0
- package/src/Tabs/index.ts +3 -0
- package/src/Tabs/types.ts +99 -0
- package/src/Tabs/utils.ts +8 -0
- package/src/Textarea/README.md +98 -0
- package/src/Textarea/Textarea.tsx +93 -0
- package/src/Textarea/__tests__/Textarea.asChild.test.tsx +85 -0
- package/src/Textarea/__tests__/Textarea.basic-rendering.test.tsx +107 -0
- package/src/Textarea/__tests__/Textarea.disabled.test.tsx +49 -0
- package/src/Textarea/__tests__/Textarea.field-integration.test.tsx +134 -0
- package/src/Textarea/index.ts +2 -0
- package/src/Textarea/types.ts +7 -0
- package/src/Toggle/README.md +97 -0
- package/src/Toggle/Toggle.tsx +81 -0
- package/src/Toggle/__tests__/Toggle.asChild.test.tsx +42 -0
- package/src/Toggle/__tests__/Toggle.basic-rendering.test.tsx +28 -0
- package/src/Toggle/__tests__/Toggle.controlled-state.test.tsx +60 -0
- package/src/Toggle/__tests__/Toggle.disabled.test.tsx +34 -0
- package/src/Toggle/__tests__/Toggle.keyboard-interaction.test.tsx +42 -0
- package/src/Toggle/__tests__/Toggle.uncontrolled-state.test.tsx +40 -0
- package/src/Toggle/index.ts +2 -0
- package/src/Toggle/types.ts +23 -0
- package/src/ToggleGroup/README.md +137 -0
- package/src/ToggleGroup/ToggleGroup.tsx +298 -0
- package/src/ToggleGroup/ToggleGroupContext.ts +9 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.asChild.test.tsx +65 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.basic-rendering.test.tsx +50 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.disabled.test.tsx +54 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.keyboard-interaction.test.tsx +151 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.multiple-mode.test.tsx +144 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.reading-direction.test.tsx +28 -0
- package/src/ToggleGroup/__tests__/ToggleGroup.single-mode.test.tsx +139 -0
- package/src/ToggleGroup/hooks/index.ts +2 -0
- package/src/ToggleGroup/hooks/useToggleGroupContext.ts +1 -0
- package/src/ToggleGroup/hooks/useToggleGroupRoot.ts +110 -0
- package/src/ToggleGroup/index.ts +2 -0
- package/src/ToggleGroup/types.ts +72 -0
- package/src/Tooltip/README.md +214 -0
- package/src/Tooltip/Tooltip.tsx +260 -0
- package/src/Tooltip/TooltipContext.ts +20 -0
- package/src/Tooltip/__tests__/Tooltip.asChild.test.tsx +77 -0
- package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +180 -0
- package/src/Tooltip/__tests__/Tooltip.controlled-state.test.tsx +128 -0
- package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
- package/src/Tooltip/__tests__/Tooltip.focus-interaction.test.tsx +88 -0
- package/src/Tooltip/__tests__/Tooltip.hover-interaction.test.tsx +179 -0
- package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +85 -0
- package/src/Tooltip/__tests__/Tooltip.uncontrolled-state.test.tsx +67 -0
- package/src/Tooltip/hooks/index.ts +4 -0
- package/src/Tooltip/hooks/useTooltipContent.ts +53 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +41 -0
- package/src/Tooltip/hooks/useTooltipRoot.ts +106 -0
- package/src/Tooltip/hooks/useTooltipTrigger.ts +44 -0
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/types.ts +64 -0
- package/src/Tree/README.md +339 -0
- package/src/Tree/Tree.tsx +571 -0
- package/src/Tree/TreeContext.ts +24 -0
- package/src/Tree/__tests__/Tree.aria.test.tsx +53 -0
- package/src/Tree/__tests__/Tree.asChild.test.tsx +134 -0
- package/src/Tree/__tests__/Tree.basic-rendering.test.tsx +111 -0
- package/src/Tree/__tests__/Tree.branch-behaviour.test.tsx +87 -0
- package/src/Tree/__tests__/Tree.controlled-expansion.test.tsx +92 -0
- package/src/Tree/__tests__/Tree.data-attributes.test.tsx +88 -0
- package/src/Tree/__tests__/Tree.disabled-items.test.tsx +196 -0
- package/src/Tree/__tests__/Tree.error-handling.test.tsx +71 -0
- package/src/Tree/__tests__/Tree.forceMount.test.tsx +72 -0
- package/src/Tree/__tests__/Tree.keyboard-interaction.test.tsx +150 -0
- package/src/Tree/__tests__/Tree.multiple-selection.test.tsx +151 -0
- package/src/Tree/__tests__/Tree.range-selection.test.tsx +200 -0
- package/src/Tree/__tests__/Tree.recursion-depth.test.tsx +73 -0
- package/src/Tree/__tests__/Tree.roving-tabindex.test.tsx +117 -0
- package/src/Tree/__tests__/Tree.selection-path.test.tsx +404 -0
- package/src/Tree/__tests__/Tree.single-selection.test.tsx +108 -0
- package/src/Tree/__tests__/Tree.uncontrolled-expansion.test.tsx +69 -0
- package/src/Tree/hooks/index.ts +3 -0
- package/src/Tree/hooks/useTreeItemKeyboard.ts +86 -0
- package/src/Tree/hooks/useTreePath.ts +68 -0
- package/src/Tree/hooks/useTreeRoot.ts +279 -0
- package/src/Tree/index.ts +3 -0
- package/src/Tree/types.ts +224 -0
- package/src/Tree/utils.ts +59 -0
- package/src/VisuallyHidden/README.md +58 -0
- package/src/VisuallyHidden/VisuallyHidden.tsx +67 -0
- package/src/VisuallyHidden/__tests__/VisuallyHidden.test.tsx +59 -0
- package/src/VisuallyHidden/index.ts +2 -0
- package/src/VisuallyHidden/types.ts +5 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useCollection.ts +74 -0
- package/src/hooks/useControllableState.ts +81 -0
- package/src/hooks/useRovingTabindex.ts +178 -0
- package/src/index.ts +38 -0
- package/src/test/intersectionObserverPolyfill.ts +83 -0
- package/src/test/popoverPolyfill.ts +86 -0
- package/src/test/scrollPolyfill.ts +23 -0
- package/src/types.ts +13 -0
- package/src/utils/__tests__/createStrictContext.test.tsx +69 -0
- package/src/utils/__tests__/deriveId.test.ts +28 -0
- package/src/utils/__tests__/getKeyToActionMap.test.ts +106 -0
- package/src/utils/createStrictContext.ts +49 -0
- package/src/utils/deriveId.ts +31 -0
- package/src/utils/getKeyToActionMap.ts +95 -0
- package/src/utils/index.ts +3 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useId,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
import { useCheckboxRoot } from "../Checkbox/hooks";
|
|
12
|
+
import { useDirection } from "../DirectionProvider";
|
|
13
|
+
import { useRadioGroupRoot } from "../RadioGroup/hooks";
|
|
14
|
+
import { useControllableState } from "../hooks";
|
|
15
|
+
import { Slot, composeEventHandlers } from "../Slot";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
ContextMenuContext,
|
|
19
|
+
ContextMenuPosition,
|
|
20
|
+
useContextMenuContext,
|
|
21
|
+
} from "./ContextMenuContext";
|
|
22
|
+
import { ContextMenuContentContext } from "./ContextMenuContentContext";
|
|
23
|
+
import { ContextMenuGroupContext } from "./ContextMenuGroupContext";
|
|
24
|
+
import { ContextMenuItemIndicatorContext } from "./ContextMenuItemIndicatorContext";
|
|
25
|
+
import { ContextMenuRadioGroupContext } from "./ContextMenuRadioGroupContext";
|
|
26
|
+
import {
|
|
27
|
+
ContextMenuSubContext,
|
|
28
|
+
useContextMenuSubContext,
|
|
29
|
+
} from "./ContextMenuSubContext";
|
|
30
|
+
import {
|
|
31
|
+
ContextMenuCheckboxItemProps,
|
|
32
|
+
ContextMenuContentProps,
|
|
33
|
+
ContextMenuGroupProps,
|
|
34
|
+
ContextMenuItemIndicatorProps,
|
|
35
|
+
ContextMenuItemProps,
|
|
36
|
+
ContextMenuLabelProps,
|
|
37
|
+
ContextMenuRadioGroupProps,
|
|
38
|
+
ContextMenuRadioItemProps,
|
|
39
|
+
ContextMenuRootProps,
|
|
40
|
+
ContextMenuSeparatorProps,
|
|
41
|
+
ContextMenuSubContentProps,
|
|
42
|
+
ContextMenuSubProps,
|
|
43
|
+
ContextMenuSubTriggerProps,
|
|
44
|
+
ContextMenuTriggerProps,
|
|
45
|
+
} from "./types";
|
|
46
|
+
import { MENUITEM_SELECTOR, TYPEAHEAD_RESET_MS } from "./constants";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The root of a ContextMenu — owns the open state and the position the
|
|
50
|
+
* menu should open at, and provides context to descendants. Renders no
|
|
51
|
+
* DOM of its own; it is a context boundary.
|
|
52
|
+
*
|
|
53
|
+
* Supports two state modes, statically discriminated at the type level so
|
|
54
|
+
* only one shape is accepted by TypeScript:
|
|
55
|
+
*
|
|
56
|
+
* - **Uncontrolled** — pass {@link ContextMenuRootProps.defaultOpen | `defaultOpen`}
|
|
57
|
+
* (or omit it to start closed). The component owns and updates the open
|
|
58
|
+
* state internally. Optional {@link ContextMenuRootProps.onOpenChange | `onOpenChange`}
|
|
59
|
+
* observes transitions.
|
|
60
|
+
* - **Controlled** — pass {@link ContextMenuRootProps.open | `open`} *and*
|
|
61
|
+
* {@link ContextMenuRootProps.onOpenChange | `onOpenChange`} together. The
|
|
62
|
+
* parent owns the state; the component defers every transition back through
|
|
63
|
+
* the callback.
|
|
64
|
+
*
|
|
65
|
+
* **Reading direction.** Pass {@link ContextMenuRootProps.dir | `dir`} to
|
|
66
|
+
* set `"ltr"` or `"rtl"`, which inverts the submenu open / close arrow
|
|
67
|
+
* keys (`ArrowRight` ↔ `ArrowLeft`). When omitted, the component reads
|
|
68
|
+
* the inherited {@link DirectionProvider} value, falling back to `"ltr"`.
|
|
69
|
+
*/
|
|
70
|
+
function ContextMenuRoot({
|
|
71
|
+
defaultOpen = false,
|
|
72
|
+
open: controlledOpen,
|
|
73
|
+
onOpenChange,
|
|
74
|
+
dir,
|
|
75
|
+
children,
|
|
76
|
+
}: ContextMenuRootProps) {
|
|
77
|
+
const contentId = useId();
|
|
78
|
+
const triggerRef = useRef<HTMLElement | null>(null);
|
|
79
|
+
const [position, setPosition] = useState<ContextMenuPosition | null>(null);
|
|
80
|
+
const inheritedDir = useDirection();
|
|
81
|
+
const resolvedDir = dir ?? inheritedDir;
|
|
82
|
+
const [open, setOpenBase] = useControllableState<boolean>(
|
|
83
|
+
controlledOpen,
|
|
84
|
+
defaultOpen,
|
|
85
|
+
onOpenChange,
|
|
86
|
+
);
|
|
87
|
+
const openRef = useRef(open);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
openRef.current = open;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const setOpen = useCallback(
|
|
93
|
+
(next: boolean) => {
|
|
94
|
+
if (openRef.current === next) return;
|
|
95
|
+
openRef.current = next;
|
|
96
|
+
setOpenBase(next);
|
|
97
|
+
},
|
|
98
|
+
[setOpenBase],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const contextValue = useMemo(
|
|
102
|
+
() => ({
|
|
103
|
+
open,
|
|
104
|
+
setOpen,
|
|
105
|
+
position,
|
|
106
|
+
setPosition,
|
|
107
|
+
contentId,
|
|
108
|
+
triggerRef,
|
|
109
|
+
dir: resolvedDir,
|
|
110
|
+
}),
|
|
111
|
+
[open, setOpen, position, contentId, resolvedDir],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<ContextMenuContext.Provider value={contextValue}>
|
|
116
|
+
{children}
|
|
117
|
+
</ContextMenuContext.Provider>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
ContextMenuRoot.displayName = "ContextMenuRoot";
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns a callback that closes any direct-child sub-menu registered with
|
|
125
|
+
* the enclosing Content / SubContent. Items invoke this on mouseEnter so
|
|
126
|
+
* hovering a sibling dismisses an open sub, mirroring the keyboard contract.
|
|
127
|
+
*/
|
|
128
|
+
function useCloseSiblingSub() {
|
|
129
|
+
const content = useContext(ContextMenuContentContext);
|
|
130
|
+
return () => content?.closeOpenSubRef.current?.();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The area that responds to right-click. Renders a `<span>` by default
|
|
135
|
+
* (a non-button host so the wrapped content keeps its native semantics);
|
|
136
|
+
* pass `asChild` to render any element.
|
|
137
|
+
*
|
|
138
|
+
* When the user opens the platform context menu over this element (via
|
|
139
|
+
* right-click, long-press on touch, or the keyboard context-menu key),
|
|
140
|
+
* the native menu is suppressed and the ContextMenu opens, positioned at
|
|
141
|
+
* the pointer.
|
|
142
|
+
*/
|
|
143
|
+
function ContextMenuTrigger({
|
|
144
|
+
children,
|
|
145
|
+
onContextMenu,
|
|
146
|
+
asChild = false,
|
|
147
|
+
disabled,
|
|
148
|
+
...rest
|
|
149
|
+
}: ContextMenuTriggerProps) {
|
|
150
|
+
const { setOpen, setPosition, triggerRef } = useContextMenuContext();
|
|
151
|
+
|
|
152
|
+
const handleContextMenu = (event: React.MouseEvent<HTMLElement>) => {
|
|
153
|
+
if (disabled) return;
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
setPosition({ x: event.clientX, y: event.clientY });
|
|
156
|
+
setOpen(true);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const triggerProps = {
|
|
160
|
+
...rest,
|
|
161
|
+
ref: triggerRef,
|
|
162
|
+
"data-disabled": disabled ? "" : undefined,
|
|
163
|
+
"data-state": disabled ? undefined : ("closed" as const),
|
|
164
|
+
onContextMenu: composeEventHandlers(onContextMenu, handleContextMenu),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
if (asChild) {
|
|
168
|
+
return <Slot {...triggerProps}>{children}</Slot>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return <span {...triggerProps}>{children}</span>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
ContextMenuTrigger.displayName = "ContextMenuTrigger";
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The menu panel rendered with the native HTML
|
|
178
|
+
* [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API)
|
|
179
|
+
* in **manual** mode (`popover="manual"`) — no portal, no floating-ui. The
|
|
180
|
+
* browser still layers the menu via the top layer, but the component owns
|
|
181
|
+
* the close flow rather than the browser's light-dismiss algorithm.
|
|
182
|
+
*
|
|
183
|
+
* Manual mode is required because the right-click gesture's pointerdown
|
|
184
|
+
* fires before the popover exists; with `popover="auto"`, the browser
|
|
185
|
+
* treats the matching pointerup as an outside click and closes the menu
|
|
186
|
+
* the instant the user releases the button. Outside-click close and
|
|
187
|
+
* Escape are handled explicitly here instead.
|
|
188
|
+
*
|
|
189
|
+
* Renders a `<menu role="menu">` positioned at the pointer coordinates
|
|
190
|
+
* captured when the Trigger fired its `contextmenu` event. The cursor
|
|
191
|
+
* position is exposed twice on the element style: as explicit `left` /
|
|
192
|
+
* `top` insets (so the menu renders at the cursor with zero consumer
|
|
193
|
+
* CSS), and as `--primitiv-context-menu-x` / `--primitiv-context-menu-y`
|
|
194
|
+
* custom properties so consumer CSS can write `@position-try` fallbacks
|
|
195
|
+
* that flip the menu to the opposite side when it would overflow the
|
|
196
|
+
* viewport. Pass `asChild` to render any element with menu semantics.
|
|
197
|
+
*/
|
|
198
|
+
function ContextMenuContent({
|
|
199
|
+
children,
|
|
200
|
+
style,
|
|
201
|
+
onKeyDown,
|
|
202
|
+
asChild = false,
|
|
203
|
+
...rest
|
|
204
|
+
}: ContextMenuContentProps) {
|
|
205
|
+
const { open, setOpen, position, contentId, triggerRef } =
|
|
206
|
+
useContextMenuContext();
|
|
207
|
+
const menuRef = useRef<HTMLMenuElement | null>(null);
|
|
208
|
+
const typeaheadRef = useRef<{ query: string; timer: number | null }>({
|
|
209
|
+
query: "",
|
|
210
|
+
timer: null,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
const menu = menuRef.current!;
|
|
215
|
+
if (open) {
|
|
216
|
+
menu.showPopover();
|
|
217
|
+
if (!menu.contains(document.activeElement)) {
|
|
218
|
+
const firstItem = menu.querySelector<HTMLElement>(MENUITEM_SELECTOR);
|
|
219
|
+
firstItem?.focus();
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
try {
|
|
223
|
+
menu.hidePopover();
|
|
224
|
+
} catch {
|
|
225
|
+
// already hidden — no-op
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}, [open]);
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
const menu = menuRef.current!;
|
|
232
|
+
const handleToggle = (event: Event) => {
|
|
233
|
+
if ((event as ToggleEvent).newState === "closed") setOpen(false);
|
|
234
|
+
};
|
|
235
|
+
menu.addEventListener("toggle", handleToggle);
|
|
236
|
+
return () => menu.removeEventListener("toggle", handleToggle);
|
|
237
|
+
}, [setOpen]);
|
|
238
|
+
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (!open) return;
|
|
241
|
+
const handleClick = (event: MouseEvent) => {
|
|
242
|
+
const target = event.target as Element;
|
|
243
|
+
if (triggerRef.current?.contains(target)) return;
|
|
244
|
+
if (target.closest?.("[popover]")) return;
|
|
245
|
+
setOpen(false);
|
|
246
|
+
};
|
|
247
|
+
document.addEventListener("click", handleClick);
|
|
248
|
+
return () => document.removeEventListener("click", handleClick);
|
|
249
|
+
}, [open, setOpen, triggerRef]);
|
|
250
|
+
|
|
251
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLMenuElement>) => {
|
|
252
|
+
const menu = menuRef.current!;
|
|
253
|
+
const focused = document.activeElement as HTMLElement | null;
|
|
254
|
+
const scope = (focused?.closest("[popover]") as HTMLElement | null) ?? menu;
|
|
255
|
+
const items = Array.from(
|
|
256
|
+
scope.querySelectorAll<HTMLElement>(MENUITEM_SELECTOR),
|
|
257
|
+
).filter((el) => el.closest("[popover]") === scope);
|
|
258
|
+
if (items.length === 0) return;
|
|
259
|
+
const currentIndex = items.indexOf(document.activeElement as HTMLElement);
|
|
260
|
+
|
|
261
|
+
let nextIndex: number | null = null;
|
|
262
|
+
if (event.key === "ArrowDown") {
|
|
263
|
+
nextIndex = (currentIndex + 1) % items.length;
|
|
264
|
+
} else if (event.key === "ArrowUp") {
|
|
265
|
+
nextIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1;
|
|
266
|
+
} else if (event.key === "Home") {
|
|
267
|
+
nextIndex = 0;
|
|
268
|
+
} else if (event.key === "End") {
|
|
269
|
+
nextIndex = items.length - 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (nextIndex !== null) {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
items[nextIndex].focus();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
279
|
+
if (currentIndex < 0) return;
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
items[currentIndex].click();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (event.key === "Escape") {
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
setOpen(false);
|
|
288
|
+
triggerRef.current?.focus();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (event.key.length === 1 && event.key !== " ") {
|
|
293
|
+
const state = typeaheadRef.current;
|
|
294
|
+
if (state.timer !== null) window.clearTimeout(state.timer);
|
|
295
|
+
state.query = (state.query + event.key).toLowerCase();
|
|
296
|
+
state.timer = window.setTimeout(() => {
|
|
297
|
+
state.query = "";
|
|
298
|
+
state.timer = null;
|
|
299
|
+
}, TYPEAHEAD_RESET_MS);
|
|
300
|
+
|
|
301
|
+
const isRepeat =
|
|
302
|
+
state.query.length > 1 &&
|
|
303
|
+
state.query.split("").every((c) => c === state.query[0]);
|
|
304
|
+
const searchQuery = isRepeat ? state.query[0] : state.query;
|
|
305
|
+
const startIndex = currentIndex < 0 ? 0 : currentIndex;
|
|
306
|
+
const offset = searchQuery.length === 1 || isRepeat ? 1 : 0;
|
|
307
|
+
for (let i = 0; i < items.length; i++) {
|
|
308
|
+
const index = (startIndex + offset + i) % items.length;
|
|
309
|
+
const text = items[index].textContent!.trim().toLowerCase();
|
|
310
|
+
if (text.startsWith(searchQuery)) {
|
|
311
|
+
event.preventDefault();
|
|
312
|
+
items[index].focus();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// Both `left`/`top` and `--primitiv-context-menu-x`/`-y` are set: the
|
|
320
|
+
// explicit insets position the menu at the cursor by default, and the
|
|
321
|
+
// custom properties let consumer CSS write `@position-try` fallbacks
|
|
322
|
+
// that flip the menu (e.g. `right: calc(100vw - var(--primitiv-context-menu-x))`)
|
|
323
|
+
// when the primary position would overflow the viewport.
|
|
324
|
+
const positionedStyle = position
|
|
325
|
+
? ({
|
|
326
|
+
position: "fixed" as const,
|
|
327
|
+
left: position.x,
|
|
328
|
+
top: position.y,
|
|
329
|
+
margin: 0,
|
|
330
|
+
"--primitiv-context-menu-x": `${position.x}px`,
|
|
331
|
+
"--primitiv-context-menu-y": `${position.y}px`,
|
|
332
|
+
...style,
|
|
333
|
+
} as React.CSSProperties)
|
|
334
|
+
: style;
|
|
335
|
+
|
|
336
|
+
const closeOpenSubRef = useRef<(() => void) | null>(null);
|
|
337
|
+
const contentContextValue = useMemo(() => ({ closeOpenSubRef }), []);
|
|
338
|
+
|
|
339
|
+
// Cascade-close: when our own popover closes, also close any registered
|
|
340
|
+
// direct-child sub so its state doesn't leak into the next open. Without
|
|
341
|
+
// this, clicking an item inside a SubContent closes Root but leaves the
|
|
342
|
+
// child Sub.open=true, which then briefly drives a stale popover render
|
|
343
|
+
// the next time the menu opens. Each SubContent runs the same cascade
|
|
344
|
+
// against its own child sub, so the chain unwinds bottom-up.
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
if (!open) closeOpenSubRef.current?.();
|
|
347
|
+
}, [open]);
|
|
348
|
+
|
|
349
|
+
const contentProps = {
|
|
350
|
+
...rest,
|
|
351
|
+
ref: menuRef,
|
|
352
|
+
id: contentId,
|
|
353
|
+
role: "menu" as const,
|
|
354
|
+
// Manual mode: the right-click gesture's pointerdown fires before the
|
|
355
|
+
// popover opens, so popover="auto"'s light-dismiss algorithm treats
|
|
356
|
+
// the matching pointerup as an outside click and closes the menu the
|
|
357
|
+
// instant the user releases the button. We close on outside click
|
|
358
|
+
// (document listener below) and Escape (key handler) ourselves.
|
|
359
|
+
popover: "manual" as const,
|
|
360
|
+
"data-state": (open ? "open" : "closed") as "open" | "closed",
|
|
361
|
+
style: positionedStyle,
|
|
362
|
+
onKeyDown: composeEventHandlers(onKeyDown, handleKeyDown),
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<ContextMenuContentContext.Provider value={contentContextValue}>
|
|
367
|
+
{asChild ? (
|
|
368
|
+
<Slot {...contentProps}>{children}</Slot>
|
|
369
|
+
) : (
|
|
370
|
+
<menu {...contentProps}>{children}</menu>
|
|
371
|
+
)}
|
|
372
|
+
</ContextMenuContentContext.Provider>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
ContextMenuContent.displayName = "ContextMenuContent";
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* A standard menu item. Renders a `<li role="menuitem">` by default; pass
|
|
380
|
+
* `asChild` to render any element with menuitem semantics.
|
|
381
|
+
*
|
|
382
|
+
* Clicking the item (or pressing Enter / Space while focused) fires
|
|
383
|
+
* {@link ContextMenuItemProps.onSelect | `onSelect`} with a cancellable
|
|
384
|
+
* `Event`. The menu auto-closes after selection; call
|
|
385
|
+
* `event.preventDefault()` inside `onSelect` to keep it open.
|
|
386
|
+
*
|
|
387
|
+
* Disabled items receive `aria-disabled="true"` and no-op on activation.
|
|
388
|
+
*/
|
|
389
|
+
function ContextMenuItem({
|
|
390
|
+
children,
|
|
391
|
+
onClick,
|
|
392
|
+
onSelect,
|
|
393
|
+
disabled,
|
|
394
|
+
asChild = false,
|
|
395
|
+
...rest
|
|
396
|
+
}: ContextMenuItemProps) {
|
|
397
|
+
const { setOpen } = useContextMenuContext();
|
|
398
|
+
const closeSiblingSub = useCloseSiblingSub();
|
|
399
|
+
const [highlighted, setHighlighted] = useState(false);
|
|
400
|
+
|
|
401
|
+
const handleClick = () => {
|
|
402
|
+
if (disabled) return;
|
|
403
|
+
const event = new Event("contextmenu.select", { cancelable: true });
|
|
404
|
+
onSelect?.(event);
|
|
405
|
+
if (!event.defaultPrevented) {
|
|
406
|
+
setOpen(false);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const itemProps = {
|
|
411
|
+
...rest,
|
|
412
|
+
role: "menuitem" as const,
|
|
413
|
+
tabIndex: -1,
|
|
414
|
+
"aria-disabled": disabled || undefined,
|
|
415
|
+
"data-highlighted": highlighted ? "" : undefined,
|
|
416
|
+
onClick: composeEventHandlers(onClick, handleClick),
|
|
417
|
+
onMouseEnter: composeEventHandlers(rest.onMouseEnter, () => {
|
|
418
|
+
setHighlighted(true);
|
|
419
|
+
closeSiblingSub();
|
|
420
|
+
}),
|
|
421
|
+
onMouseLeave: composeEventHandlers(rest.onMouseLeave, () =>
|
|
422
|
+
setHighlighted(false),
|
|
423
|
+
),
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
if (asChild) {
|
|
427
|
+
return <Slot {...itemProps}>{children}</Slot>;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return <li {...itemProps}>{children}</li>;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
ContextMenuItem.displayName = "ContextMenuItem";
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* A visual separator between groups of items. Renders a `<li role="separator">`
|
|
437
|
+
* by default. Non-interactive — skipped by focus, arrow navigation, and
|
|
438
|
+
* typeahead.
|
|
439
|
+
*/
|
|
440
|
+
function ContextMenuSeparator({
|
|
441
|
+
asChild = false,
|
|
442
|
+
children,
|
|
443
|
+
...rest
|
|
444
|
+
}: ContextMenuSeparatorProps) {
|
|
445
|
+
const separatorProps = { ...rest, role: "separator" as const };
|
|
446
|
+
|
|
447
|
+
if (asChild) {
|
|
448
|
+
return <Slot {...separatorProps}>{children}</Slot>;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return <li {...separatorProps} />;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
ContextMenuSeparator.displayName = "ContextMenuSeparator";
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* A semantic grouping of related items. Renders as a `<li role="group">`
|
|
458
|
+
* wrapping an inner `<ul role="none">`, or — with `asChild` — a single
|
|
459
|
+
* grouping element composed onto the provided child.
|
|
460
|
+
*
|
|
461
|
+
* Generates a stable id for its accompanying {@link ContextMenuLabel |
|
|
462
|
+
* `ContextMenu.Label`}, wired automatically via `aria-labelledby`.
|
|
463
|
+
*/
|
|
464
|
+
function ContextMenuGroup({
|
|
465
|
+
children,
|
|
466
|
+
asChild = false,
|
|
467
|
+
...rest
|
|
468
|
+
}: ContextMenuGroupProps) {
|
|
469
|
+
const labelId = useId();
|
|
470
|
+
const contextValue = useMemo(() => ({ labelId }), [labelId]);
|
|
471
|
+
const groupProps = {
|
|
472
|
+
...rest,
|
|
473
|
+
role: "group" as const,
|
|
474
|
+
"aria-labelledby": labelId,
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
return (
|
|
478
|
+
<ContextMenuGroupContext.Provider value={contextValue}>
|
|
479
|
+
{asChild ? (
|
|
480
|
+
<Slot {...groupProps}>{children}</Slot>
|
|
481
|
+
) : (
|
|
482
|
+
<li {...groupProps}>
|
|
483
|
+
<ul role="none">{children}</ul>
|
|
484
|
+
</li>
|
|
485
|
+
)}
|
|
486
|
+
</ContextMenuGroupContext.Provider>
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
ContextMenuGroup.displayName = "ContextMenuGroup";
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* A non-interactive label, typically used inside a {@link ContextMenuGroup |
|
|
494
|
+
* `ContextMenu.Group`} to give that group an accessible name. When nested in
|
|
495
|
+
* a group, the label's `id` is auto-wired to the group's `aria-labelledby` —
|
|
496
|
+
* consumers don't need to thread ids manually. A caller-supplied `id` takes
|
|
497
|
+
* precedence over the auto-generated one.
|
|
498
|
+
*/
|
|
499
|
+
function ContextMenuLabel({
|
|
500
|
+
id,
|
|
501
|
+
children,
|
|
502
|
+
asChild = false,
|
|
503
|
+
...rest
|
|
504
|
+
}: ContextMenuLabelProps) {
|
|
505
|
+
const group = useContext(ContextMenuGroupContext);
|
|
506
|
+
const labelProps = { ...rest, id: id ?? group?.labelId };
|
|
507
|
+
|
|
508
|
+
if (asChild) {
|
|
509
|
+
return <Slot {...labelProps}>{children}</Slot>;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return <li {...labelProps}>{children}</li>;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
ContextMenuLabel.displayName = "ContextMenuLabel";
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* A toggleable menu item. Renders a `<li role="menuitemcheckbox">` with
|
|
519
|
+
* `aria-checked` reflecting the current state. Supports a WAI-ARIA tri-state:
|
|
520
|
+
* `true`, `false`, or `"indeterminate"` (encoded as `aria-checked="mixed"`).
|
|
521
|
+
* An indeterminate item resolves to `true` on the next activation.
|
|
522
|
+
*
|
|
523
|
+
* Activation (click) toggles the checked state, then fires
|
|
524
|
+
* {@link ContextMenuCheckboxItemProps.onSelect | `onSelect`} with a
|
|
525
|
+
* cancellable `Event`. Call `event.preventDefault()` to keep the menu open.
|
|
526
|
+
*
|
|
527
|
+
* Disabled items receive `aria-disabled="true"` and no-op on activation.
|
|
528
|
+
*/
|
|
529
|
+
function ContextMenuCheckboxItem({
|
|
530
|
+
children,
|
|
531
|
+
onClick,
|
|
532
|
+
onSelect,
|
|
533
|
+
disabled,
|
|
534
|
+
defaultChecked,
|
|
535
|
+
checked: controlledChecked,
|
|
536
|
+
onCheckedChange,
|
|
537
|
+
asChild = false,
|
|
538
|
+
...rest
|
|
539
|
+
}: ContextMenuCheckboxItemProps) {
|
|
540
|
+
const { setOpen } = useContextMenuContext();
|
|
541
|
+
const closeSiblingSub = useCloseSiblingSub();
|
|
542
|
+
const [highlighted, setHighlighted] = useState(false);
|
|
543
|
+
const { checked, toggle } = useCheckboxRoot({
|
|
544
|
+
defaultChecked,
|
|
545
|
+
checked: controlledChecked,
|
|
546
|
+
onCheckedChange,
|
|
547
|
+
});
|
|
548
|
+
const ariaChecked: "mixed" | "true" | "false" =
|
|
549
|
+
checked === "indeterminate" ? "mixed" : checked ? "true" : "false";
|
|
550
|
+
|
|
551
|
+
const handleClick = () => {
|
|
552
|
+
if (disabled) return;
|
|
553
|
+
toggle();
|
|
554
|
+
const event = new Event("contextmenu.select", { cancelable: true });
|
|
555
|
+
onSelect?.(event);
|
|
556
|
+
if (!event.defaultPrevented) {
|
|
557
|
+
setOpen(false);
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const itemProps = {
|
|
562
|
+
...rest,
|
|
563
|
+
role: "menuitemcheckbox" as const,
|
|
564
|
+
tabIndex: -1,
|
|
565
|
+
"aria-checked": ariaChecked,
|
|
566
|
+
"aria-disabled": disabled || undefined,
|
|
567
|
+
"data-highlighted": highlighted ? "" : undefined,
|
|
568
|
+
onClick: composeEventHandlers(onClick, handleClick),
|
|
569
|
+
onMouseEnter: composeEventHandlers(rest.onMouseEnter, () => {
|
|
570
|
+
setHighlighted(true);
|
|
571
|
+
closeSiblingSub();
|
|
572
|
+
}),
|
|
573
|
+
onMouseLeave: composeEventHandlers(rest.onMouseLeave, () =>
|
|
574
|
+
setHighlighted(false),
|
|
575
|
+
),
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const indicatorContextValue = useMemo(() => ({ checked }), [checked]);
|
|
579
|
+
const content = asChild ? (
|
|
580
|
+
<Slot {...itemProps}>{children}</Slot>
|
|
581
|
+
) : (
|
|
582
|
+
<li {...itemProps}>{children}</li>
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
return (
|
|
586
|
+
<ContextMenuItemIndicatorContext.Provider value={indicatorContextValue}>
|
|
587
|
+
{content}
|
|
588
|
+
</ContextMenuItemIndicatorContext.Provider>
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
ContextMenuCheckboxItem.displayName = "ContextMenuCheckboxItem";
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* The visible mark (usually a checkmark) rendered inside a
|
|
596
|
+
* {@link ContextMenuCheckboxItem | `ContextMenu.CheckboxItem`} (or radio item).
|
|
597
|
+
* Must be a descendant of one; rendering it anywhere else throws a
|
|
598
|
+
* descriptive error.
|
|
599
|
+
*
|
|
600
|
+
* Renders a `<span>` by default. Exposes `data-state` reflecting the parent
|
|
601
|
+
* item's live state: `"checked"`, `"unchecked"`, or `"indeterminate"`.
|
|
602
|
+
*
|
|
603
|
+
* By default the indicator unmounts when its parent is unchecked. Pass
|
|
604
|
+
* {@link ContextMenuItemIndicatorProps.forceMount | `forceMount`} to keep
|
|
605
|
+
* the DOM node mounted in both states for animation use cases.
|
|
606
|
+
*/
|
|
607
|
+
function ContextMenuItemIndicator({
|
|
608
|
+
children,
|
|
609
|
+
asChild = false,
|
|
610
|
+
forceMount = false,
|
|
611
|
+
...rest
|
|
612
|
+
}: ContextMenuItemIndicatorProps) {
|
|
613
|
+
const context = useContext(ContextMenuItemIndicatorContext);
|
|
614
|
+
if (!context) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
"ContextMenu.ItemIndicator must be rendered inside a <ContextMenu.CheckboxItem> or <ContextMenu.RadioItem>.",
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const { checked } = context;
|
|
621
|
+
const dataState =
|
|
622
|
+
checked === "indeterminate"
|
|
623
|
+
? "indeterminate"
|
|
624
|
+
: checked
|
|
625
|
+
? "checked"
|
|
626
|
+
: "unchecked";
|
|
627
|
+
|
|
628
|
+
if (!forceMount && checked === false) return null;
|
|
629
|
+
|
|
630
|
+
const indicatorProps = { ...rest, "data-state": dataState };
|
|
631
|
+
|
|
632
|
+
if (asChild) {
|
|
633
|
+
return <Slot {...indicatorProps}>{children}</Slot>;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return <span {...indicatorProps}>{children}</span>;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
ContextMenuItemIndicator.displayName = "ContextMenuItemIndicator";
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* A single-selection group of menu items. Children must be
|
|
643
|
+
* {@link ContextMenuRadioItem | `ContextMenu.RadioItem`} elements. Renders a
|
|
644
|
+
* `<li role="group">` wrapping `<ul role="none">`.
|
|
645
|
+
*/
|
|
646
|
+
function ContextMenuRadioGroup({
|
|
647
|
+
defaultValue,
|
|
648
|
+
value: controlledValue,
|
|
649
|
+
onValueChange,
|
|
650
|
+
children,
|
|
651
|
+
asChild = false,
|
|
652
|
+
...rest
|
|
653
|
+
}: ContextMenuRadioGroupProps) {
|
|
654
|
+
const { value, select } = useRadioGroupRoot({
|
|
655
|
+
defaultValue,
|
|
656
|
+
value: controlledValue,
|
|
657
|
+
onValueChange,
|
|
658
|
+
});
|
|
659
|
+
const contextValue = useMemo(() => ({ value, select }), [value, select]);
|
|
660
|
+
const groupProps = { ...rest, role: "group" as const };
|
|
661
|
+
|
|
662
|
+
return (
|
|
663
|
+
<ContextMenuRadioGroupContext.Provider value={contextValue}>
|
|
664
|
+
{asChild ? (
|
|
665
|
+
<Slot {...groupProps}>{children}</Slot>
|
|
666
|
+
) : (
|
|
667
|
+
<li {...groupProps}>
|
|
668
|
+
<ul role="none">{children}</ul>
|
|
669
|
+
</li>
|
|
670
|
+
)}
|
|
671
|
+
</ContextMenuRadioGroupContext.Provider>
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
ContextMenuRadioGroup.displayName = "ContextMenuRadioGroup";
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* A single radio choice. Must be rendered inside a {@link ContextMenuRadioGroup |
|
|
679
|
+
* `ContextMenu.RadioGroup`}; rendering it outside one throws a descriptive
|
|
680
|
+
* error.
|
|
681
|
+
*
|
|
682
|
+
* Renders a `<li role="menuitemradio">` with `aria-checked` reflecting whether
|
|
683
|
+
* this item's `value` matches the group's active value.
|
|
684
|
+
*
|
|
685
|
+
* Activation (click) selects this item, updating the group's value, then fires
|
|
686
|
+
* {@link ContextMenuRadioItemProps.onSelect | `onSelect`} with a cancellable
|
|
687
|
+
* `Event`. Call `event.preventDefault()` to keep the menu open.
|
|
688
|
+
*/
|
|
689
|
+
function ContextMenuRadioItem({
|
|
690
|
+
children,
|
|
691
|
+
onClick,
|
|
692
|
+
onSelect,
|
|
693
|
+
disabled,
|
|
694
|
+
value: itemValue,
|
|
695
|
+
asChild = false,
|
|
696
|
+
...rest
|
|
697
|
+
}: ContextMenuRadioItemProps) {
|
|
698
|
+
const { setOpen } = useContextMenuContext();
|
|
699
|
+
const closeSiblingSub = useCloseSiblingSub();
|
|
700
|
+
const [highlighted, setHighlighted] = useState(false);
|
|
701
|
+
const group = useContext(ContextMenuRadioGroupContext);
|
|
702
|
+
if (!group) {
|
|
703
|
+
throw new Error(
|
|
704
|
+
"ContextMenu.RadioItem must be rendered inside a <ContextMenu.RadioGroup>.",
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
const checked = group.value === itemValue;
|
|
708
|
+
|
|
709
|
+
const handleClick = () => {
|
|
710
|
+
if (disabled) return;
|
|
711
|
+
group.select(itemValue);
|
|
712
|
+
const event = new Event("contextmenu.select", { cancelable: true });
|
|
713
|
+
onSelect?.(event);
|
|
714
|
+
if (!event.defaultPrevented) {
|
|
715
|
+
setOpen(false);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const itemProps = {
|
|
720
|
+
...rest,
|
|
721
|
+
role: "menuitemradio" as const,
|
|
722
|
+
tabIndex: -1,
|
|
723
|
+
"aria-checked": checked,
|
|
724
|
+
"aria-disabled": disabled || undefined,
|
|
725
|
+
"data-highlighted": highlighted ? "" : undefined,
|
|
726
|
+
onClick: composeEventHandlers(onClick, handleClick),
|
|
727
|
+
onMouseEnter: composeEventHandlers(rest.onMouseEnter, () => {
|
|
728
|
+
setHighlighted(true);
|
|
729
|
+
closeSiblingSub();
|
|
730
|
+
}),
|
|
731
|
+
onMouseLeave: composeEventHandlers(rest.onMouseLeave, () =>
|
|
732
|
+
setHighlighted(false),
|
|
733
|
+
),
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const indicatorContextValue = useMemo(() => ({ checked }), [checked]);
|
|
737
|
+
const content = asChild ? (
|
|
738
|
+
<Slot {...itemProps}>{children}</Slot>
|
|
739
|
+
) : (
|
|
740
|
+
<li {...itemProps}>{children}</li>
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
return (
|
|
744
|
+
<ContextMenuItemIndicatorContext.Provider value={indicatorContextValue}>
|
|
745
|
+
{content}
|
|
746
|
+
</ContextMenuItemIndicatorContext.Provider>
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
ContextMenuRadioItem.displayName = "ContextMenuRadioItem";
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* A submenu boundary. Wrap a {@link ContextMenuSubTrigger | `ContextMenu.SubTrigger`}
|
|
754
|
+
* and its {@link ContextMenuSubContent | `ContextMenu.SubContent`} in a
|
|
755
|
+
* `ContextMenu.Sub` to establish an independent open state for the nested
|
|
756
|
+
* menu. Supports uncontrolled (`defaultOpen`) and controlled (`open` +
|
|
757
|
+
* `onOpenChange`) modes.
|
|
758
|
+
*/
|
|
759
|
+
function ContextMenuSub({
|
|
760
|
+
defaultOpen,
|
|
761
|
+
open: controlledOpen,
|
|
762
|
+
onOpenChange,
|
|
763
|
+
children,
|
|
764
|
+
}: ContextMenuSubProps) {
|
|
765
|
+
const contentId = useId();
|
|
766
|
+
const triggerRef = useRef<HTMLLIElement | null>(null);
|
|
767
|
+
const [open, setOpenBase] = useControllableState<boolean>(
|
|
768
|
+
controlledOpen,
|
|
769
|
+
defaultOpen ?? false,
|
|
770
|
+
onOpenChange,
|
|
771
|
+
);
|
|
772
|
+
const openRef = useRef(open);
|
|
773
|
+
useEffect(() => {
|
|
774
|
+
openRef.current = open;
|
|
775
|
+
});
|
|
776
|
+
const setOpen = useCallback(
|
|
777
|
+
(next: boolean) => {
|
|
778
|
+
if (openRef.current === next) return;
|
|
779
|
+
openRef.current = next;
|
|
780
|
+
setOpenBase(next);
|
|
781
|
+
},
|
|
782
|
+
[setOpenBase],
|
|
783
|
+
);
|
|
784
|
+
const contextValue = useMemo(
|
|
785
|
+
() => ({ open, setOpen, contentId, triggerRef }),
|
|
786
|
+
[open, setOpen, contentId],
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
// Register with the enclosing Content/SubContent so sibling items can close
|
|
790
|
+
// this sub on hover (mirroring the keyboard behaviour where focus returning
|
|
791
|
+
// to the parent dismisses it). If another sibling sub is already
|
|
792
|
+
// registered as open, close it first so a hover-to-open transition onto our
|
|
793
|
+
// SubTrigger supplants the prior sub rather than stacking it.
|
|
794
|
+
const parentContent = useContext(ContextMenuContentContext);
|
|
795
|
+
useEffect(() => {
|
|
796
|
+
if (!open || !parentContent) return;
|
|
797
|
+
const close = () => setOpen(false);
|
|
798
|
+
const prev = parentContent.closeOpenSubRef.current;
|
|
799
|
+
if (prev && prev !== close) prev();
|
|
800
|
+
parentContent.closeOpenSubRef.current = close;
|
|
801
|
+
return () => {
|
|
802
|
+
if (parentContent.closeOpenSubRef.current === close) {
|
|
803
|
+
parentContent.closeOpenSubRef.current = null;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
}, [open, parentContent, setOpen]);
|
|
807
|
+
|
|
808
|
+
return (
|
|
809
|
+
<ContextMenuSubContext.Provider value={contextValue}>
|
|
810
|
+
{children}
|
|
811
|
+
</ContextMenuSubContext.Provider>
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
ContextMenuSub.displayName = "ContextMenuSub";
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* The submenu trigger. Must be rendered inside a {@link ContextMenuSub |
|
|
819
|
+
* `ContextMenu.Sub`}.
|
|
820
|
+
*
|
|
821
|
+
* Renders a `<li role="menuitem">` with `aria-haspopup="menu"`,
|
|
822
|
+
* `aria-expanded`, and `aria-controls` wiring it to the sibling
|
|
823
|
+
* {@link ContextMenuSubContent | `ContextMenu.SubContent`}.
|
|
824
|
+
*
|
|
825
|
+
* Opens the submenu on click, the inline-forward arrow key, or pointer
|
|
826
|
+
* hover. The open key follows the resolved reading direction —
|
|
827
|
+
* `ArrowRight` in `"ltr"`, `ArrowLeft` in `"rtl"`. Disabled triggers
|
|
828
|
+
* ignore both click and the open arrow key.
|
|
829
|
+
*/
|
|
830
|
+
function ContextMenuSubTrigger({
|
|
831
|
+
children,
|
|
832
|
+
onClick,
|
|
833
|
+
onKeyDown,
|
|
834
|
+
disabled,
|
|
835
|
+
asChild = false,
|
|
836
|
+
...rest
|
|
837
|
+
}: ContextMenuSubTriggerProps) {
|
|
838
|
+
const sub = useContextMenuSubContext();
|
|
839
|
+
const { dir } = useContextMenuContext();
|
|
840
|
+
const openKey = dir === "rtl" ? "ArrowLeft" : "ArrowRight";
|
|
841
|
+
const closeSiblingSub = useCloseSiblingSub();
|
|
842
|
+
const [hovered, setHovered] = useState(false);
|
|
843
|
+
const toggle = () => {
|
|
844
|
+
if (disabled) return;
|
|
845
|
+
sub.setOpen(true);
|
|
846
|
+
};
|
|
847
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLLIElement>) => {
|
|
848
|
+
if (disabled) return;
|
|
849
|
+
if (event.key !== openKey) return;
|
|
850
|
+
event.preventDefault();
|
|
851
|
+
event.stopPropagation();
|
|
852
|
+
sub.setOpen(true);
|
|
853
|
+
};
|
|
854
|
+
const subTriggerProps = {
|
|
855
|
+
...rest,
|
|
856
|
+
ref: sub.triggerRef,
|
|
857
|
+
role: "menuitem" as const,
|
|
858
|
+
tabIndex: -1,
|
|
859
|
+
"aria-haspopup": "menu" as const,
|
|
860
|
+
"aria-expanded": sub.open,
|
|
861
|
+
"aria-controls": sub.contentId,
|
|
862
|
+
"aria-disabled": disabled || undefined,
|
|
863
|
+
"data-highlighted": hovered || sub.open ? "" : undefined,
|
|
864
|
+
onClick: composeEventHandlers(onClick, toggle),
|
|
865
|
+
onKeyDown: composeEventHandlers(onKeyDown, handleKeyDown),
|
|
866
|
+
onMouseEnter: composeEventHandlers(rest.onMouseEnter, () => {
|
|
867
|
+
setHovered(true);
|
|
868
|
+
closeSiblingSub();
|
|
869
|
+
if (!disabled) sub.setOpen(true);
|
|
870
|
+
}),
|
|
871
|
+
onMouseLeave: composeEventHandlers(rest.onMouseLeave, () =>
|
|
872
|
+
setHovered(false),
|
|
873
|
+
),
|
|
874
|
+
};
|
|
875
|
+
if (asChild) {
|
|
876
|
+
return <Slot {...subTriggerProps}>{children}</Slot>;
|
|
877
|
+
}
|
|
878
|
+
return <li {...subTriggerProps}>{children}</li>;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
ContextMenuSubTrigger.displayName = "ContextMenuSubTrigger";
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* The submenu panel. Must be rendered inside a {@link ContextMenuSub |
|
|
885
|
+
* `ContextMenu.Sub`}.
|
|
886
|
+
*
|
|
887
|
+
* Renders a `<menu role="menu" popover="auto">` by default. When the submenu
|
|
888
|
+
* opens, focus moves to its first enabled item. The inline-backward arrow
|
|
889
|
+
* key closes the submenu and returns focus to the SubTrigger —
|
|
890
|
+
* `ArrowLeft` in `"ltr"`, `ArrowRight` in `"rtl"`.
|
|
891
|
+
*/
|
|
892
|
+
function ContextMenuSubContent({
|
|
893
|
+
children,
|
|
894
|
+
onKeyDown,
|
|
895
|
+
asChild = false,
|
|
896
|
+
...rest
|
|
897
|
+
}: ContextMenuSubContentProps) {
|
|
898
|
+
const sub = useContextMenuSubContext();
|
|
899
|
+
const { dir } = useContextMenuContext();
|
|
900
|
+
const closeKey = dir === "rtl" ? "ArrowRight" : "ArrowLeft";
|
|
901
|
+
const menuRef = useRef<HTMLMenuElement | null>(null);
|
|
902
|
+
|
|
903
|
+
useEffect(() => {
|
|
904
|
+
const menu = menuRef.current!;
|
|
905
|
+
if (sub.open) {
|
|
906
|
+
menu.showPopover();
|
|
907
|
+
const firstItem = menu.querySelector<HTMLElement>(MENUITEM_SELECTOR);
|
|
908
|
+
firstItem?.focus();
|
|
909
|
+
} else {
|
|
910
|
+
try {
|
|
911
|
+
menu.hidePopover();
|
|
912
|
+
} catch {
|
|
913
|
+
// already hidden — no-op
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}, [sub.open]);
|
|
917
|
+
|
|
918
|
+
useEffect(() => {
|
|
919
|
+
const menu = menuRef.current!;
|
|
920
|
+
const handleToggle = (event: Event) => {
|
|
921
|
+
if ((event as ToggleEvent).newState === "closed") sub.setOpen(false);
|
|
922
|
+
};
|
|
923
|
+
menu.addEventListener("toggle", handleToggle);
|
|
924
|
+
return () => menu.removeEventListener("toggle", handleToggle);
|
|
925
|
+
}, [sub.setOpen]);
|
|
926
|
+
|
|
927
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLMenuElement>) => {
|
|
928
|
+
if (event.key !== closeKey) return;
|
|
929
|
+
event.preventDefault();
|
|
930
|
+
event.stopPropagation();
|
|
931
|
+
sub.setOpen(false);
|
|
932
|
+
sub.triggerRef.current?.focus();
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
const closeOpenSubRef = useRef<(() => void) | null>(null);
|
|
936
|
+
const contentContextValue = useMemo(() => ({ closeOpenSubRef }), []);
|
|
937
|
+
|
|
938
|
+
// Cascade-close — see the matching effect on ContextMenuContent.
|
|
939
|
+
useEffect(() => {
|
|
940
|
+
if (!sub.open) closeOpenSubRef.current?.();
|
|
941
|
+
}, [sub.open]);
|
|
942
|
+
|
|
943
|
+
const subContentProps = {
|
|
944
|
+
...rest,
|
|
945
|
+
ref: menuRef,
|
|
946
|
+
id: sub.contentId,
|
|
947
|
+
role: "menu" as const,
|
|
948
|
+
popover: "auto" as const,
|
|
949
|
+
onKeyDown: composeEventHandlers(onKeyDown, handleKeyDown),
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
return (
|
|
953
|
+
<ContextMenuContentContext.Provider value={contentContextValue}>
|
|
954
|
+
{asChild ? (
|
|
955
|
+
<Slot {...subContentProps}>{children}</Slot>
|
|
956
|
+
) : (
|
|
957
|
+
<menu {...subContentProps}>{children}</menu>
|
|
958
|
+
)}
|
|
959
|
+
</ContextMenuContentContext.Provider>
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
ContextMenuSubContent.displayName = "ContextMenuSubContent";
|
|
964
|
+
|
|
965
|
+
type TContextMenuCompound = typeof ContextMenuRoot & {
|
|
966
|
+
Root: typeof ContextMenuRoot;
|
|
967
|
+
Trigger: typeof ContextMenuTrigger;
|
|
968
|
+
Content: typeof ContextMenuContent;
|
|
969
|
+
Item: typeof ContextMenuItem;
|
|
970
|
+
Separator: typeof ContextMenuSeparator;
|
|
971
|
+
Group: typeof ContextMenuGroup;
|
|
972
|
+
Label: typeof ContextMenuLabel;
|
|
973
|
+
CheckboxItem: typeof ContextMenuCheckboxItem;
|
|
974
|
+
ItemIndicator: typeof ContextMenuItemIndicator;
|
|
975
|
+
RadioGroup: typeof ContextMenuRadioGroup;
|
|
976
|
+
RadioItem: typeof ContextMenuRadioItem;
|
|
977
|
+
Sub: typeof ContextMenuSub;
|
|
978
|
+
SubTrigger: typeof ContextMenuSubTrigger;
|
|
979
|
+
SubContent: typeof ContextMenuSubContent;
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
const ContextMenuCompound: TContextMenuCompound = Object.assign(
|
|
983
|
+
ContextMenuRoot,
|
|
984
|
+
{
|
|
985
|
+
Root: ContextMenuRoot,
|
|
986
|
+
Trigger: ContextMenuTrigger,
|
|
987
|
+
Content: ContextMenuContent,
|
|
988
|
+
Item: ContextMenuItem,
|
|
989
|
+
Separator: ContextMenuSeparator,
|
|
990
|
+
Group: ContextMenuGroup,
|
|
991
|
+
Label: ContextMenuLabel,
|
|
992
|
+
CheckboxItem: ContextMenuCheckboxItem,
|
|
993
|
+
ItemIndicator: ContextMenuItemIndicator,
|
|
994
|
+
RadioGroup: ContextMenuRadioGroup,
|
|
995
|
+
RadioItem: ContextMenuRadioItem,
|
|
996
|
+
Sub: ContextMenuSub,
|
|
997
|
+
SubTrigger: ContextMenuSubTrigger,
|
|
998
|
+
SubContent: ContextMenuSubContent,
|
|
999
|
+
},
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
ContextMenuCompound.displayName = "ContextMenu";
|
|
1003
|
+
|
|
1004
|
+
export { ContextMenuCompound as ContextMenu };
|