From 626fbc70d8d151ad56809d472f44b73d349747f0 Mon Sep 17 00:00:00 2001 From: robonen Date: Sun, 7 Jun 2026 16:29:56 +0700 Subject: [PATCH] fix(primitives): eslint/tsconfig migration, asChild refactor, type fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migrate to eslint flat config + composite tsconfig. - Complete the asChild→as="template" refactor (remove asChild prop + :as-child bindings across components, matching Primitive's slot model). - Fix test type errors and source type-safety (useGraceArea hull/point math, FocusScope/util ref typing). Note: ~53 vue-tsc errors remain (HTML attr/event passthrough typing on transparent wrapper components + a couple of duplicate-export naming collisions) — not gated by CI (build/lint/test green); pending a component-attribute-typing design decision. --- ...dbc42b176f989336a161e4f129aeb48944d8d4.png | Bin 0 -> 4774 bytes vue/primitives/AGENTS.md | 447 ++++ vue/primitives/REKA_COMPARISON.md | 2056 +++++++++++++++++ vue/primitives/eslint.config.ts | 9 + vue/primitives/oxlint.config.ts | 13 - vue/primitives/package.json | 26 +- vue/primitives/playground/.gitignore | 3 + vue/primitives/playground/README.md | 32 + vue/primitives/playground/index.html | 12 + vue/primitives/playground/package.json | 26 + vue/primitives/playground/src/App.vue | 91 + .../playground/src/demos/Accordion.vue | 65 + .../playground/src/demos/Checkbox.vue | 40 + vue/primitives/playground/src/main.ts | 10 + vue/primitives/playground/src/router.ts | 37 + vue/primitives/playground/src/styles.css | 12 + vue/primitives/playground/src/views/Home.vue | 32 + .../playground/src/views/NotFound.vue | 19 + vue/primitives/playground/tsconfig.json | 12 + vue/primitives/playground/vite.config.ts | 31 + .../src/accordion/AccordionContent.vue | 39 + .../src/accordion/AccordionItem.vue | 49 + .../src/accordion/AccordionRoot.vue | 153 ++ .../src/accordion/AccordionTrigger.vue | 52 + .../src/accordion/__test__/Accordion.test.ts | 242 ++ vue/primitives/src/accordion/context.ts | 33 + vue/primitives/src/accordion/index.ts | 12 + .../src/alert-dialog/AlertDialogAction.vue | 17 + .../src/alert-dialog/AlertDialogCancel.vue | 17 + .../src/alert-dialog/AlertDialogContent.vue | 43 + .../src/alert-dialog/AlertDialogRoot.vue | 25 + .../alert-dialog/__test__/AlertDialog.test.ts | 118 + vue/primitives/src/alert-dialog/index.ts | 11 + .../src/aspect-ratio/AspectRatio.vue | 41 + .../aspect-ratio/__test__/AspectRatio.test.ts | 24 + vue/primitives/src/aspect-ratio/index.ts | 2 + vue/primitives/src/avatar/AvatarFallback.vue | 57 + vue/primitives/src/avatar/AvatarImage.vue | 83 + vue/primitives/src/avatar/AvatarRoot.vue | 30 + .../src/avatar/__test__/Avatar.test.ts | 93 + vue/primitives/src/avatar/context.ts | 14 + vue/primitives/src/avatar/index.ts | 8 + vue/primitives/src/calendar/CalendarCell.vue | 43 + .../src/calendar/CalendarCellTrigger.vue | 198 ++ vue/primitives/src/calendar/CalendarGrid.vue | 40 + .../src/calendar/CalendarGridBody.vue | 17 + .../src/calendar/CalendarGridHead.vue | 17 + .../src/calendar/CalendarGridRow.vue | 17 + .../src/calendar/CalendarHeadCell.vue | 31 + .../src/calendar/CalendarHeader.vue | 17 + .../src/calendar/CalendarHeading.vue | 31 + vue/primitives/src/calendar/CalendarNext.vue | 45 + vue/primitives/src/calendar/CalendarPrev.vue | 45 + vue/primitives/src/calendar/CalendarRoot.vue | 324 +++ .../src/calendar/__test__/date-utils.test.ts | 46 + vue/primitives/src/calendar/context.ts | 67 + vue/primitives/src/calendar/date-utils.ts | 125 + vue/primitives/src/calendar/index.ts | 42 + vue/primitives/src/calendar/utils.ts | 64 + .../src/checkbox/CheckboxIndicator.vue | 32 + vue/primitives/src/checkbox/CheckboxRoot.vue | 99 + .../src/checkbox/__test__/Checkbox.test.ts | 109 + vue/primitives/src/checkbox/context.ts | 14 + vue/primitives/src/checkbox/index.ts | 5 + .../src/collapsible/CollapsibleContent.vue | 36 + .../src/collapsible/CollapsibleRoot.vue | 56 + .../src/collapsible/CollapsibleTrigger.vue | 32 + .../collapsible/__test__/Collapsible.test.ts | 65 + vue/primitives/src/collapsible/context.ts | 16 + vue/primitives/src/collapsible/index.ts | 8 + vue/primitives/src/collection/index.ts | 6 + .../src/collection/useCollection.ts | 185 ++ .../src/combobox/ComboboxAnchor.vue | 33 + vue/primitives/src/combobox/ComboboxArrow.vue | 24 + .../src/combobox/ComboboxCancel.vue | 40 + .../src/combobox/ComboboxContent.vue | 30 + .../src/combobox/ComboboxContentImpl.vue | 139 ++ vue/primitives/src/combobox/ComboboxEmpty.vue | 37 + vue/primitives/src/combobox/ComboboxGroup.vue | 39 + vue/primitives/src/combobox/ComboboxInput.vue | 221 ++ vue/primitives/src/combobox/ComboboxItem.vue | 120 + .../src/combobox/ComboboxItemIndicator.vue | 27 + vue/primitives/src/combobox/ComboboxLabel.vue | 26 + .../src/combobox/ComboboxPortal.vue | 17 + vue/primitives/src/combobox/ComboboxRoot.vue | 400 ++++ .../src/combobox/ComboboxSeparator.vue | 26 + .../src/combobox/ComboboxTrigger.vue | 53 + .../src/combobox/ComboboxViewport.vue | 32 + .../__test__/Combobox.live-region.test.ts | 81 + ...lecting-the-unfiltered-count-on-open-1.png | Bin 0 -> 2380 bytes ...unt-as-the-search-term-filters-items-1.png | Bin 0 -> 3071 bytes vue/primitives/src/combobox/context.ts | 112 + vue/primitives/src/combobox/index.ts | 51 + vue/primitives/src/combobox/utils.ts | 57 + vue/primitives/src/command/CommandEmpty.vue | 39 + vue/primitives/src/command/CommandGroup.vue | 73 + vue/primitives/src/command/CommandInput.vue | 165 ++ vue/primitives/src/command/CommandItem.vue | 127 + vue/primitives/src/command/CommandList.vue | 92 + vue/primitives/src/command/CommandLoading.vue | 40 + vue/primitives/src/command/CommandRoot.vue | 287 +++ .../src/command/CommandSeparator.vue | 37 + vue/primitives/src/command/context.ts | 71 + vue/primitives/src/command/index.ts | 30 + vue/primitives/src/command/utils.ts | 34 + .../__test__/config-provider.test.ts | 69 +- vue/primitives/src/config-provider/context.ts | 18 +- vue/primitives/src/config-provider/index.ts | 2 + vue/primitives/src/config-provider/useId.ts | 16 + .../src/context-menu/ContextMenuTrigger.vue | 5 +- .../src/date-picker/DatePickerAnchor.vue | 28 + .../src/date-picker/DatePickerArrow.vue | 17 + .../src/date-picker/DatePickerCalendar.vue | 17 + .../src/date-picker/DatePickerClose.vue | 25 + .../src/date-picker/DatePickerContent.vue | 75 + .../src/date-picker/DatePickerField.vue | 67 + .../src/date-picker/DatePickerPortal.vue | 17 + .../src/date-picker/DatePickerRoot.vue | 333 +++ .../src/date-picker/DatePickerTrigger.vue | 41 + vue/primitives/src/date-picker/context.ts | 20 + vue/primitives/src/date-picker/index.ts | 36 + vue/primitives/src/dialog/DialogClose.vue | 21 + vue/primitives/src/dialog/DialogContent.vue | 55 + .../src/dialog/DialogContentImpl.vue | 68 + .../src/dialog/DialogContentModal.vue | 54 + .../src/dialog/DialogContentNonModal.vue | 36 + .../src/dialog/DialogDescription.vue | 32 + vue/primitives/src/dialog/DialogOverlay.vue | 36 + vue/primitives/src/dialog/DialogPortal.vue | 35 + vue/primitives/src/dialog/DialogRoot.vue | 66 + vue/primitives/src/dialog/DialogTitle.vue | 32 + vue/primitives/src/dialog/DialogTrigger.vue | 36 + .../src/dialog/__test__/Dialog.test.ts | 385 +++ vue/primitives/src/dialog/context.ts | 32 + vue/primitives/src/dialog/index.ts | 20 + .../dismissable-layer/DismissableLayer.vue | 132 ++ .../__test__/DismissableLayer.test.ts | 121 + vue/primitives/src/dismissable-layer/index.ts | 7 + vue/primitives/src/dismissable-layer/stack.ts | 40 + .../src/dropdown-menu/DropdownMenuTrigger.vue | 5 +- vue/primitives/src/editable/EditableArea.vue | 30 + .../src/editable/EditableCancelTrigger.vue | 31 + .../src/editable/EditableEditTrigger.vue | 31 + vue/primitives/src/editable/EditableInput.vue | 85 + .../src/editable/EditablePreview.vue | 53 + vue/primitives/src/editable/EditableRoot.vue | 164 ++ .../src/editable/EditableSubmitTrigger.vue | 31 + .../src/editable/__test__/Editable.test.ts | 172 ++ vue/primitives/src/editable/context.ts | 44 + vue/primitives/src/editable/index.ts | 24 + vue/primitives/src/env.d.ts | 18 +- vue/primitives/src/focus-scope/FocusScope.vue | 25 +- .../focus-scope/__test__/FocusScope.test.ts | 139 ++ .../src/focus-scope/useAutoFocus.ts | 4 +- .../src/focus-scope/useFocusTrap.ts | 4 +- .../src/hover-card/HoverCardArrow.vue | 17 + .../src/hover-card/HoverCardContent.vue | 34 + .../src/hover-card/HoverCardContentImpl.vue | 170 ++ .../src/hover-card/HoverCardPortal.vue | 17 + .../src/hover-card/HoverCardRoot.vue | 113 + .../src/hover-card/HoverCardTrigger.vue | 49 + .../src/hover-card/__test__/HoverCard.test.ts | 176 ++ vue/primitives/src/hover-card/context.ts | 20 + vue/primitives/src/hover-card/index.ts | 17 + vue/primitives/src/hover-card/utils.ts | 20 + vue/primitives/src/index.ts | 11 + vue/primitives/src/label/Label.vue | 28 + .../src/label/__test__/Label.test.ts | 33 + vue/primitives/src/label/index.ts | 2 + vue/primitives/src/listbox/ListboxContent.vue | 72 + vue/primitives/src/listbox/ListboxFilter.vue | 95 + vue/primitives/src/listbox/ListboxGroup.vue | 30 + .../src/listbox/ListboxGroupLabel.vue | 26 + vue/primitives/src/listbox/ListboxItem.vue | 85 + .../src/listbox/ListboxItemIndicator.vue | 27 + vue/primitives/src/listbox/ListboxRoot.vue | 299 +++ .../src/listbox/__test__/Listbox.test.ts | 232 ++ vue/primitives/src/listbox/context.ts | 53 + vue/primitives/src/listbox/index.ts | 32 + vue/primitives/src/listbox/utils.ts | 27 + vue/primitives/src/menu/MenuGroup.vue | 4 +- vue/primitives/src/menu/MenuItemImpl.vue | 4 +- vue/primitives/src/menu/MenuItemIndicator.vue | 3 +- vue/primitives/src/menu/MenuLabel.vue | 4 +- vue/primitives/src/menu/MenuRadioGroup.vue | 4 +- vue/primitives/src/menu/MenuSeparator.vue | 4 +- vue/primitives/src/menu/MenuSub.vue | 2 +- vue/primitives/src/menu/MenuSubTrigger.vue | 4 +- vue/primitives/src/menubar/MenubarRoot.vue | 54 +- vue/primitives/src/menubar/MenubarTrigger.vue | 117 +- .../__test__/Menubar.regression.test.ts | 146 ++ vue/primitives/src/menubar/context.ts | 5 + .../navigation-menu/NavigationMenuContent.vue | 72 + .../NavigationMenuContentImpl.vue | 171 ++ .../NavigationMenuIndicator.vue | 87 + .../navigation-menu/NavigationMenuItem.vue | 80 + .../navigation-menu/NavigationMenuLink.vue | 55 + .../navigation-menu/NavigationMenuList.vue | 42 + .../navigation-menu/NavigationMenuRoot.vue | 224 ++ .../src/navigation-menu/NavigationMenuSub.vue | 117 + .../navigation-menu/NavigationMenuTrigger.vue | 152 ++ .../NavigationMenuViewport.vue | 121 + .../__test__/NavigationMenu.landmark.test.ts | 56 + vue/primitives/src/navigation-menu/context.ts | 65 + vue/primitives/src/navigation-menu/index.ts | 31 + vue/primitives/src/navigation-menu/utils.ts | 51 + .../src/number-field/NumberFieldDecrement.vue | 33 + .../src/number-field/NumberFieldIncrement.vue | 33 + .../src/number-field/NumberFieldInput.vue | 87 + .../src/number-field/NumberFieldRoot.vue | 89 + .../number-field/__test__/NumberField.test.ts | 130 ++ vue/primitives/src/number-field/context.ts | 20 + vue/primitives/src/number-field/index.ts | 8 + .../src/pagination/PaginationEllipsis.vue | 2 +- .../src/pagination/PaginationFirst.vue | 4 +- .../src/pagination/PaginationLast.vue | 4 +- .../src/pagination/PaginationList.vue | 10 +- .../src/pagination/PaginationListItem.vue | 9 +- .../src/pagination/PaginationNext.vue | 4 +- .../src/pagination/PaginationPrev.vue | 4 +- .../src/pagination/PaginationRoot.vue | 2 +- .../src/pin-input/PinInputInput.vue | 133 ++ vue/primitives/src/pin-input/PinInputRoot.vue | 148 ++ .../src/pin-input/__test__/PinInput.test.ts | 125 + vue/primitives/src/pin-input/context.ts | 23 + vue/primitives/src/pin-input/index.ts | 3 + vue/primitives/src/popover/PopoverAnchor.vue | 28 + vue/primitives/src/popover/PopoverArrow.vue | 17 + vue/primitives/src/popover/PopoverClose.vue | 26 + vue/primitives/src/popover/PopoverContent.vue | 53 + .../src/popover/PopoverContentImpl.vue | 78 + .../src/popover/PopoverContentModal.vue | 39 + .../src/popover/PopoverContentNonModal.vue | 47 + vue/primitives/src/popover/PopoverPortal.vue | 17 + vue/primitives/src/popover/PopoverRoot.vue | 56 + vue/primitives/src/popover/PopoverTrigger.vue | 40 + .../src/popover/__test__/Popover.test.ts | 169 ++ .../Popover-closes-on-Escape-key-1.png | Bin 0 -> 4774 bytes vue/primitives/src/popover/context.ts | 18 + vue/primitives/src/popover/index.ts | 18 + vue/primitives/src/popper/PopperAnchor.vue | 31 + vue/primitives/src/popper/PopperArrow.vue | 76 + vue/primitives/src/popper/PopperContent.vue | 280 +++ vue/primitives/src/popper/PopperRoot.vue | 18 + .../src/popper/__test__/Popper.test.ts | 108 + vue/primitives/src/popper/context.ts | 25 + vue/primitives/src/popper/index.ts | 12 + vue/primitives/src/popper/utils.ts | 59 + vue/primitives/src/primitive/Primitive.ts | 12 +- vue/primitives/src/primitive/Slot.ts | 38 +- .../src/primitive/__test__/Primitive.bench.ts | 45 +- .../src/primitive/__test__/Primitive.test.ts | 2 +- .../src/progress/ProgressIndicator.vue | 29 + vue/primitives/src/progress/ProgressRoot.vue | 65 + .../src/progress/__test__/Progress.test.ts | 50 + vue/primitives/src/progress/context.ts | 15 + vue/primitives/src/progress/index.ts | 6 + .../src/radio-group/RadioGroupIndicator.vue | 30 + .../src/radio-group/RadioGroupItem.vue | 76 + .../src/radio-group/RadioGroupRoot.vue | 137 ++ .../__test__/RadioGroup.form.test.ts | 75 + .../radio-group/__test__/RadioGroup.test.ts | 118 + ...lected-value-into-FormData-on-submit-1.png | Bin 0 -> 3322 bytes vue/primitives/src/radio-group/context.ts | 33 + vue/primitives/src/radio-group/index.ts | 6 + .../src/roving-focus/RovingFocusGroup.vue | 156 ++ .../src/roving-focus/RovingFocusItem.vue | 116 + vue/primitives/src/roving-focus/index.ts | 20 + vue/primitives/src/roving-focus/utils.ts | 78 + .../src/scroll-area/ScrollAreaCorner.vue | 78 + .../src/scroll-area/ScrollAreaRoot.vue | 97 + .../src/scroll-area/ScrollAreaScrollbar.vue | 81 + .../scroll-area/ScrollAreaScrollbarAuto.vue | 80 + .../scroll-area/ScrollAreaScrollbarHover.vue | 80 + .../scroll-area/ScrollAreaScrollbarImpl.vue | 246 ++ .../scroll-area/ScrollAreaScrollbarScroll.vue | 88 + .../ScrollAreaScrollbarVisible.vue | 117 + .../src/scroll-area/ScrollAreaThumb.vue | 82 + .../src/scroll-area/ScrollAreaViewport.vue | 67 + .../__test__/ScrollArea.a11y.test.ts | 200 ++ .../scroll-area/__test__/ScrollArea.test.ts | 182 ++ ...viewport-forward--visually-reversed--1.png | Bin 0 -> 2968 bytes ...lt-when-scrollbar-is-non-interactive-1.png | Bin 0 -> 2082 bytes ...no-op-when-content-does-not-overflow-1.png | Bin 0 -> 2082 bytes vue/primitives/src/scroll-area/context.ts | 42 + vue/primitives/src/scroll-area/index.ts | 14 + vue/primitives/src/scroll-area/types.ts | 13 + vue/primitives/src/scroll-area/utils.ts | 98 + vue/primitives/src/select/SelectArrow.vue | 21 + vue/primitives/src/select/SelectContent.vue | 27 + .../src/select/SelectContentImpl.vue | 151 ++ vue/primitives/src/select/SelectGroup.vue | 31 + vue/primitives/src/select/SelectIcon.vue | 24 + vue/primitives/src/select/SelectItem.vue | 95 + .../src/select/SelectItemAlignedPosition.vue | 58 + .../src/select/SelectItemIndicator.vue | 27 + vue/primitives/src/select/SelectItemText.vue | 32 + vue/primitives/src/select/SelectLabel.vue | 26 + .../src/select/SelectPopperPosition.vue | 30 + vue/primitives/src/select/SelectPortal.vue | 17 + vue/primitives/src/select/SelectRoot.vue | 159 ++ .../src/select/SelectScrollButtonImpl.vue | 55 + .../src/select/SelectScrollDownButton.vue | 55 + .../src/select/SelectScrollUpButton.vue | 53 + vue/primitives/src/select/SelectSeparator.vue | 25 + vue/primitives/src/select/SelectTrigger.vue | 65 + vue/primitives/src/select/SelectValue.vue | 35 + vue/primitives/src/select/SelectViewport.vue | 32 + vue/primitives/src/select/context.ts | 84 + vue/primitives/src/select/index.ts | 47 + vue/primitives/src/select/utils.ts | 7 + vue/primitives/src/separator/Separator.vue | 38 + .../src/separator/__test__/Separator.test.ts | 29 + vue/primitives/src/separator/index.ts | 2 + vue/primitives/src/slider/SliderRange.vue | 75 + vue/primitives/src/slider/SliderRoot.vue | 263 +++ vue/primitives/src/slider/SliderThumb.vue | 137 ++ vue/primitives/src/slider/SliderTrack.vue | 41 + .../src/slider/__test__/Slider.test.ts | 227 ++ vue/primitives/src/slider/context.ts | 36 + vue/primitives/src/slider/index.ts | 9 + vue/primitives/src/slider/utils.ts | 70 + .../src/stepper/StepperDescription.vue | 26 + .../src/stepper/StepperIndicator.vue | 28 + vue/primitives/src/stepper/StepperItem.vue | 63 + vue/primitives/src/stepper/StepperRoot.vue | 140 ++ .../src/stepper/StepperSeparator.vue | 30 + vue/primitives/src/stepper/StepperTitle.vue | 26 + vue/primitives/src/stepper/StepperTrigger.vue | 59 + .../src/stepper/__test__/Stepper.test.ts | 176 ++ vue/primitives/src/stepper/context.ts | 43 + vue/primitives/src/stepper/index.ts | 27 + vue/primitives/src/switch/Switch.vue | 114 + .../src/switch/__test__/Switch.test.ts | 188 ++ vue/primitives/src/switch/index.ts | 2 + vue/primitives/src/tabs/TabsContent.vue | 38 + vue/primitives/src/tabs/TabsList.vue | 28 + vue/primitives/src/tabs/TabsRoot.vue | 111 + vue/primitives/src/tabs/TabsTrigger.vue | 59 + vue/primitives/src/tabs/__test__/Tabs.test.ts | 225 ++ vue/primitives/src/tabs/context.ts | 20 + vue/primitives/src/tabs/index.ts | 12 + .../src/tags-input/TagsInputClear.vue | 38 + .../src/tags-input/TagsInputInput.vue | 136 ++ .../src/tags-input/TagsInputItem.vue | 54 + .../src/tags-input/TagsInputItemDelete.vue | 40 + .../src/tags-input/TagsInputItemText.vue | 32 + .../src/tags-input/TagsInputRoot.vue | 249 ++ .../src/tags-input/__test__/TagsInput.test.ts | 171 ++ vue/primitives/src/tags-input/context.ts | 53 + vue/primitives/src/tags-input/index.ts | 23 + vue/primitives/src/teleport/Teleport.vue | 65 + .../src/teleport/__test__/Teleport.test.ts | 73 + vue/primitives/src/teleport/index.ts | 2 + vue/primitives/src/toast/ToastAction.vue | 31 + vue/primitives/src/toast/ToastClose.vue | 27 + vue/primitives/src/toast/ToastDescription.vue | 24 + vue/primitives/src/toast/ToastProvider.vue | 55 + vue/primitives/src/toast/ToastRoot.vue | 140 ++ vue/primitives/src/toast/ToastTitle.vue | 24 + vue/primitives/src/toast/ToastViewport.vue | 84 + .../toast/__test__/Toast.regression.test.ts | 103 + vue/primitives/src/toast/context.ts | 43 + vue/primitives/src/toast/index.ts | 21 + vue/primitives/src/toast/utils.ts | 2 + .../src/toggle-group/ToggleGroupItem.vue | 66 + .../src/toggle-group/ToggleGroupRoot.vue | 149 ++ .../toggle-group/__test__/ToggleGroup.test.ts | 104 + vue/primitives/src/toggle-group/context.ts | 25 + vue/primitives/src/toggle-group/index.ts | 5 + vue/primitives/src/toggle/Toggle.vue | 67 + .../src/toggle/__test__/Toggle.test.ts | 101 + vue/primitives/src/toggle/index.ts | 2 + vue/primitives/src/toolbar/ToolbarButton.vue | 48 + vue/primitives/src/toolbar/ToolbarRoot.vue | 75 + .../src/toolbar/ToolbarSeparator.vue | 31 + .../src/toolbar/__test__/Toolbar.test.ts | 102 + vue/primitives/src/toolbar/context.ts | 19 + vue/primitives/src/toolbar/index.ts | 6 + vue/primitives/src/tooltip/TooltipArrow.vue | 17 + vue/primitives/src/tooltip/TooltipContent.vue | 33 + .../src/tooltip/TooltipContentImpl.vue | 149 ++ vue/primitives/src/tooltip/TooltipPortal.vue | 17 + .../src/tooltip/TooltipProvider.vue | 89 + vue/primitives/src/tooltip/TooltipRoot.vue | 157 ++ vue/primitives/src/tooltip/TooltipTrigger.vue | 95 + .../src/tooltip/__test__/Tooltip.test.ts | 291 +++ vue/primitives/src/tooltip/context.ts | 50 + vue/primitives/src/tooltip/index.ts | 26 + vue/primitives/src/tooltip/utils.ts | 8 + vue/primitives/src/tree/TreeItem.vue | 69 + vue/primitives/src/tree/TreeRoot.vue | 341 +++ vue/primitives/src/tree/__test__/Tree.test.ts | 262 +++ vue/primitives/src/tree/context.ts | 35 + vue/primitives/src/tree/index.ts | 10 + vue/primitives/src/tree/utils.ts | 87 + vue/primitives/src/utils/getRawChildren.ts | 4 +- vue/primitives/src/utils/roving-focus.ts | 58 + vue/primitives/src/utils/useGraceArea.ts | 227 ++ vue/primitives/src/utils/useHideOthers.ts | 59 + .../src/visually-hidden/VisuallyHidden.vue | 50 + .../__test__/VisuallyHidden.test.ts | 36 + vue/primitives/src/visually-hidden/index.ts | 2 + vue/primitives/tsconfig.json | 22 +- vue/primitives/tsconfig.node.json | 8 + vue/primitives/tsconfig.src.json | 12 + vue/primitives/tsdown.config.ts | 9 +- vue/primitives/vitest.config.ts | 10 +- 408 files changed, 27367 insertions(+), 154 deletions(-) create mode 100644 vue/primitives/.vitest-attachments/12dbc42b176f989336a161e4f129aeb48944d8d4.png create mode 100644 vue/primitives/AGENTS.md create mode 100644 vue/primitives/REKA_COMPARISON.md create mode 100644 vue/primitives/eslint.config.ts delete mode 100644 vue/primitives/oxlint.config.ts create mode 100644 vue/primitives/playground/.gitignore create mode 100644 vue/primitives/playground/README.md create mode 100644 vue/primitives/playground/index.html create mode 100644 vue/primitives/playground/package.json create mode 100644 vue/primitives/playground/src/App.vue create mode 100644 vue/primitives/playground/src/demos/Accordion.vue create mode 100644 vue/primitives/playground/src/demos/Checkbox.vue create mode 100644 vue/primitives/playground/src/main.ts create mode 100644 vue/primitives/playground/src/router.ts create mode 100644 vue/primitives/playground/src/styles.css create mode 100644 vue/primitives/playground/src/views/Home.vue create mode 100644 vue/primitives/playground/src/views/NotFound.vue create mode 100644 vue/primitives/playground/tsconfig.json create mode 100644 vue/primitives/playground/vite.config.ts create mode 100644 vue/primitives/src/accordion/AccordionContent.vue create mode 100644 vue/primitives/src/accordion/AccordionItem.vue create mode 100644 vue/primitives/src/accordion/AccordionRoot.vue create mode 100644 vue/primitives/src/accordion/AccordionTrigger.vue create mode 100644 vue/primitives/src/accordion/__test__/Accordion.test.ts create mode 100644 vue/primitives/src/accordion/context.ts create mode 100644 vue/primitives/src/accordion/index.ts create mode 100644 vue/primitives/src/alert-dialog/AlertDialogAction.vue create mode 100644 vue/primitives/src/alert-dialog/AlertDialogCancel.vue create mode 100644 vue/primitives/src/alert-dialog/AlertDialogContent.vue create mode 100644 vue/primitives/src/alert-dialog/AlertDialogRoot.vue create mode 100644 vue/primitives/src/alert-dialog/__test__/AlertDialog.test.ts create mode 100644 vue/primitives/src/alert-dialog/index.ts create mode 100644 vue/primitives/src/aspect-ratio/AspectRatio.vue create mode 100644 vue/primitives/src/aspect-ratio/__test__/AspectRatio.test.ts create mode 100644 vue/primitives/src/aspect-ratio/index.ts create mode 100644 vue/primitives/src/avatar/AvatarFallback.vue create mode 100644 vue/primitives/src/avatar/AvatarImage.vue create mode 100644 vue/primitives/src/avatar/AvatarRoot.vue create mode 100644 vue/primitives/src/avatar/__test__/Avatar.test.ts create mode 100644 vue/primitives/src/avatar/context.ts create mode 100644 vue/primitives/src/avatar/index.ts create mode 100644 vue/primitives/src/calendar/CalendarCell.vue create mode 100644 vue/primitives/src/calendar/CalendarCellTrigger.vue create mode 100644 vue/primitives/src/calendar/CalendarGrid.vue create mode 100644 vue/primitives/src/calendar/CalendarGridBody.vue create mode 100644 vue/primitives/src/calendar/CalendarGridHead.vue create mode 100644 vue/primitives/src/calendar/CalendarGridRow.vue create mode 100644 vue/primitives/src/calendar/CalendarHeadCell.vue create mode 100644 vue/primitives/src/calendar/CalendarHeader.vue create mode 100644 vue/primitives/src/calendar/CalendarHeading.vue create mode 100644 vue/primitives/src/calendar/CalendarNext.vue create mode 100644 vue/primitives/src/calendar/CalendarPrev.vue create mode 100644 vue/primitives/src/calendar/CalendarRoot.vue create mode 100644 vue/primitives/src/calendar/__test__/date-utils.test.ts create mode 100644 vue/primitives/src/calendar/context.ts create mode 100644 vue/primitives/src/calendar/date-utils.ts create mode 100644 vue/primitives/src/calendar/index.ts create mode 100644 vue/primitives/src/calendar/utils.ts create mode 100644 vue/primitives/src/checkbox/CheckboxIndicator.vue create mode 100644 vue/primitives/src/checkbox/CheckboxRoot.vue create mode 100644 vue/primitives/src/checkbox/__test__/Checkbox.test.ts create mode 100644 vue/primitives/src/checkbox/context.ts create mode 100644 vue/primitives/src/checkbox/index.ts create mode 100644 vue/primitives/src/collapsible/CollapsibleContent.vue create mode 100644 vue/primitives/src/collapsible/CollapsibleRoot.vue create mode 100644 vue/primitives/src/collapsible/CollapsibleTrigger.vue create mode 100644 vue/primitives/src/collapsible/__test__/Collapsible.test.ts create mode 100644 vue/primitives/src/collapsible/context.ts create mode 100644 vue/primitives/src/collapsible/index.ts create mode 100644 vue/primitives/src/collection/index.ts create mode 100644 vue/primitives/src/collection/useCollection.ts create mode 100644 vue/primitives/src/combobox/ComboboxAnchor.vue create mode 100644 vue/primitives/src/combobox/ComboboxArrow.vue create mode 100644 vue/primitives/src/combobox/ComboboxCancel.vue create mode 100644 vue/primitives/src/combobox/ComboboxContent.vue create mode 100644 vue/primitives/src/combobox/ComboboxContentImpl.vue create mode 100644 vue/primitives/src/combobox/ComboboxEmpty.vue create mode 100644 vue/primitives/src/combobox/ComboboxGroup.vue create mode 100644 vue/primitives/src/combobox/ComboboxInput.vue create mode 100644 vue/primitives/src/combobox/ComboboxItem.vue create mode 100644 vue/primitives/src/combobox/ComboboxItemIndicator.vue create mode 100644 vue/primitives/src/combobox/ComboboxLabel.vue create mode 100644 vue/primitives/src/combobox/ComboboxPortal.vue create mode 100644 vue/primitives/src/combobox/ComboboxRoot.vue create mode 100644 vue/primitives/src/combobox/ComboboxSeparator.vue create mode 100644 vue/primitives/src/combobox/ComboboxTrigger.vue create mode 100644 vue/primitives/src/combobox/ComboboxViewport.vue create mode 100644 vue/primitives/src/combobox/__test__/Combobox.live-region.test.ts create mode 100644 vue/primitives/src/combobox/__test__/__screenshots__/Combobox.live-region.test.ts/Combobox---filtered-results-live-region-announces--N-results-available---reflecting-the-unfiltered-count-on-open-1.png create mode 100644 vue/primitives/src/combobox/__test__/__screenshots__/Combobox.live-region.test.ts/Combobox---filtered-results-live-region-updates-the-count-as-the-search-term-filters-items-1.png create mode 100644 vue/primitives/src/combobox/context.ts create mode 100644 vue/primitives/src/combobox/index.ts create mode 100644 vue/primitives/src/combobox/utils.ts create mode 100644 vue/primitives/src/command/CommandEmpty.vue create mode 100644 vue/primitives/src/command/CommandGroup.vue create mode 100644 vue/primitives/src/command/CommandInput.vue create mode 100644 vue/primitives/src/command/CommandItem.vue create mode 100644 vue/primitives/src/command/CommandList.vue create mode 100644 vue/primitives/src/command/CommandLoading.vue create mode 100644 vue/primitives/src/command/CommandRoot.vue create mode 100644 vue/primitives/src/command/CommandSeparator.vue create mode 100644 vue/primitives/src/command/context.ts create mode 100644 vue/primitives/src/command/index.ts create mode 100644 vue/primitives/src/command/utils.ts create mode 100644 vue/primitives/src/config-provider/useId.ts create mode 100644 vue/primitives/src/date-picker/DatePickerAnchor.vue create mode 100644 vue/primitives/src/date-picker/DatePickerArrow.vue create mode 100644 vue/primitives/src/date-picker/DatePickerCalendar.vue create mode 100644 vue/primitives/src/date-picker/DatePickerClose.vue create mode 100644 vue/primitives/src/date-picker/DatePickerContent.vue create mode 100644 vue/primitives/src/date-picker/DatePickerField.vue create mode 100644 vue/primitives/src/date-picker/DatePickerPortal.vue create mode 100644 vue/primitives/src/date-picker/DatePickerRoot.vue create mode 100644 vue/primitives/src/date-picker/DatePickerTrigger.vue create mode 100644 vue/primitives/src/date-picker/context.ts create mode 100644 vue/primitives/src/date-picker/index.ts create mode 100644 vue/primitives/src/dialog/DialogClose.vue create mode 100644 vue/primitives/src/dialog/DialogContent.vue create mode 100644 vue/primitives/src/dialog/DialogContentImpl.vue create mode 100644 vue/primitives/src/dialog/DialogContentModal.vue create mode 100644 vue/primitives/src/dialog/DialogContentNonModal.vue create mode 100644 vue/primitives/src/dialog/DialogDescription.vue create mode 100644 vue/primitives/src/dialog/DialogOverlay.vue create mode 100644 vue/primitives/src/dialog/DialogPortal.vue create mode 100644 vue/primitives/src/dialog/DialogRoot.vue create mode 100644 vue/primitives/src/dialog/DialogTitle.vue create mode 100644 vue/primitives/src/dialog/DialogTrigger.vue create mode 100644 vue/primitives/src/dialog/__test__/Dialog.test.ts create mode 100644 vue/primitives/src/dialog/context.ts create mode 100644 vue/primitives/src/dialog/index.ts create mode 100644 vue/primitives/src/dismissable-layer/DismissableLayer.vue create mode 100644 vue/primitives/src/dismissable-layer/__test__/DismissableLayer.test.ts create mode 100644 vue/primitives/src/dismissable-layer/index.ts create mode 100644 vue/primitives/src/dismissable-layer/stack.ts create mode 100644 vue/primitives/src/editable/EditableArea.vue create mode 100644 vue/primitives/src/editable/EditableCancelTrigger.vue create mode 100644 vue/primitives/src/editable/EditableEditTrigger.vue create mode 100644 vue/primitives/src/editable/EditableInput.vue create mode 100644 vue/primitives/src/editable/EditablePreview.vue create mode 100644 vue/primitives/src/editable/EditableRoot.vue create mode 100644 vue/primitives/src/editable/EditableSubmitTrigger.vue create mode 100644 vue/primitives/src/editable/__test__/Editable.test.ts create mode 100644 vue/primitives/src/editable/context.ts create mode 100644 vue/primitives/src/editable/index.ts create mode 100644 vue/primitives/src/hover-card/HoverCardArrow.vue create mode 100644 vue/primitives/src/hover-card/HoverCardContent.vue create mode 100644 vue/primitives/src/hover-card/HoverCardContentImpl.vue create mode 100644 vue/primitives/src/hover-card/HoverCardPortal.vue create mode 100644 vue/primitives/src/hover-card/HoverCardRoot.vue create mode 100644 vue/primitives/src/hover-card/HoverCardTrigger.vue create mode 100644 vue/primitives/src/hover-card/__test__/HoverCard.test.ts create mode 100644 vue/primitives/src/hover-card/context.ts create mode 100644 vue/primitives/src/hover-card/index.ts create mode 100644 vue/primitives/src/hover-card/utils.ts create mode 100644 vue/primitives/src/label/Label.vue create mode 100644 vue/primitives/src/label/__test__/Label.test.ts create mode 100644 vue/primitives/src/label/index.ts create mode 100644 vue/primitives/src/listbox/ListboxContent.vue create mode 100644 vue/primitives/src/listbox/ListboxFilter.vue create mode 100644 vue/primitives/src/listbox/ListboxGroup.vue create mode 100644 vue/primitives/src/listbox/ListboxGroupLabel.vue create mode 100644 vue/primitives/src/listbox/ListboxItem.vue create mode 100644 vue/primitives/src/listbox/ListboxItemIndicator.vue create mode 100644 vue/primitives/src/listbox/ListboxRoot.vue create mode 100644 vue/primitives/src/listbox/__test__/Listbox.test.ts create mode 100644 vue/primitives/src/listbox/context.ts create mode 100644 vue/primitives/src/listbox/index.ts create mode 100644 vue/primitives/src/listbox/utils.ts create mode 100644 vue/primitives/src/menubar/__test__/Menubar.regression.test.ts create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuContent.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuContentImpl.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuIndicator.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuItem.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuLink.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuList.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuRoot.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuSub.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuTrigger.vue create mode 100644 vue/primitives/src/navigation-menu/NavigationMenuViewport.vue create mode 100644 vue/primitives/src/navigation-menu/__test__/NavigationMenu.landmark.test.ts create mode 100644 vue/primitives/src/navigation-menu/context.ts create mode 100644 vue/primitives/src/navigation-menu/index.ts create mode 100644 vue/primitives/src/navigation-menu/utils.ts create mode 100644 vue/primitives/src/number-field/NumberFieldDecrement.vue create mode 100644 vue/primitives/src/number-field/NumberFieldIncrement.vue create mode 100644 vue/primitives/src/number-field/NumberFieldInput.vue create mode 100644 vue/primitives/src/number-field/NumberFieldRoot.vue create mode 100644 vue/primitives/src/number-field/__test__/NumberField.test.ts create mode 100644 vue/primitives/src/number-field/context.ts create mode 100644 vue/primitives/src/number-field/index.ts create mode 100644 vue/primitives/src/pin-input/PinInputInput.vue create mode 100644 vue/primitives/src/pin-input/PinInputRoot.vue create mode 100644 vue/primitives/src/pin-input/__test__/PinInput.test.ts create mode 100644 vue/primitives/src/pin-input/context.ts create mode 100644 vue/primitives/src/pin-input/index.ts create mode 100644 vue/primitives/src/popover/PopoverAnchor.vue create mode 100644 vue/primitives/src/popover/PopoverArrow.vue create mode 100644 vue/primitives/src/popover/PopoverClose.vue create mode 100644 vue/primitives/src/popover/PopoverContent.vue create mode 100644 vue/primitives/src/popover/PopoverContentImpl.vue create mode 100644 vue/primitives/src/popover/PopoverContentModal.vue create mode 100644 vue/primitives/src/popover/PopoverContentNonModal.vue create mode 100644 vue/primitives/src/popover/PopoverPortal.vue create mode 100644 vue/primitives/src/popover/PopoverRoot.vue create mode 100644 vue/primitives/src/popover/PopoverTrigger.vue create mode 100644 vue/primitives/src/popover/__test__/Popover.test.ts create mode 100644 vue/primitives/src/popover/__test__/__screenshots__/Popover.test.ts/Popover-closes-on-Escape-key-1.png create mode 100644 vue/primitives/src/popover/context.ts create mode 100644 vue/primitives/src/popover/index.ts create mode 100644 vue/primitives/src/popper/PopperAnchor.vue create mode 100644 vue/primitives/src/popper/PopperArrow.vue create mode 100644 vue/primitives/src/popper/PopperContent.vue create mode 100644 vue/primitives/src/popper/PopperRoot.vue create mode 100644 vue/primitives/src/popper/__test__/Popper.test.ts create mode 100644 vue/primitives/src/popper/context.ts create mode 100644 vue/primitives/src/popper/index.ts create mode 100644 vue/primitives/src/popper/utils.ts create mode 100644 vue/primitives/src/progress/ProgressIndicator.vue create mode 100644 vue/primitives/src/progress/ProgressRoot.vue create mode 100644 vue/primitives/src/progress/__test__/Progress.test.ts create mode 100644 vue/primitives/src/progress/context.ts create mode 100644 vue/primitives/src/progress/index.ts create mode 100644 vue/primitives/src/radio-group/RadioGroupIndicator.vue create mode 100644 vue/primitives/src/radio-group/RadioGroupItem.vue create mode 100644 vue/primitives/src/radio-group/RadioGroupRoot.vue create mode 100644 vue/primitives/src/radio-group/__test__/RadioGroup.form.test.ts create mode 100644 vue/primitives/src/radio-group/__test__/RadioGroup.test.ts create mode 100644 vue/primitives/src/radio-group/__test__/__screenshots__/RadioGroup.form.test.ts/RadioGroup---form-submission-hidden-input-forwards-the-selected-value-into-FormData-on-submit-1.png create mode 100644 vue/primitives/src/radio-group/context.ts create mode 100644 vue/primitives/src/radio-group/index.ts create mode 100644 vue/primitives/src/roving-focus/RovingFocusGroup.vue create mode 100644 vue/primitives/src/roving-focus/RovingFocusItem.vue create mode 100644 vue/primitives/src/roving-focus/index.ts create mode 100644 vue/primitives/src/roving-focus/utils.ts create mode 100644 vue/primitives/src/scroll-area/ScrollAreaCorner.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaRoot.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaScrollbar.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaScrollbarAuto.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaScrollbarHover.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaScrollbarImpl.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaScrollbarScroll.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaScrollbarVisible.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaThumb.vue create mode 100644 vue/primitives/src/scroll-area/ScrollAreaViewport.vue create mode 100644 vue/primitives/src/scroll-area/__test__/ScrollArea.a11y.test.ts create mode 100644 vue/primitives/src/scroll-area/__test__/ScrollArea.test.ts create mode 100644 vue/primitives/src/scroll-area/__test__/__screenshots__/ScrollArea.a11y.test.ts/scroll-area---scrollbar-keyboard-support-RTL--ArrowLeft-scrolls-the-horizontal-viewport-forward--visually-reversed--1.png create mode 100644 vue/primitives/src/scroll-area/__test__/__screenshots__/ScrollArea.a11y.test.ts/scroll-area---scrollbar-keyboard-support-keydown-handler-does-not-call-preventDefault-when-scrollbar-is-non-interactive-1.png create mode 100644 vue/primitives/src/scroll-area/__test__/__screenshots__/ScrollArea.a11y.test.ts/scroll-area---scrollbar-keyboard-support-keydown-handler-is-a-no-op-when-content-does-not-overflow-1.png create mode 100644 vue/primitives/src/scroll-area/context.ts create mode 100644 vue/primitives/src/scroll-area/index.ts create mode 100644 vue/primitives/src/scroll-area/types.ts create mode 100644 vue/primitives/src/scroll-area/utils.ts create mode 100644 vue/primitives/src/select/SelectArrow.vue create mode 100644 vue/primitives/src/select/SelectContent.vue create mode 100644 vue/primitives/src/select/SelectContentImpl.vue create mode 100644 vue/primitives/src/select/SelectGroup.vue create mode 100644 vue/primitives/src/select/SelectIcon.vue create mode 100644 vue/primitives/src/select/SelectItem.vue create mode 100644 vue/primitives/src/select/SelectItemAlignedPosition.vue create mode 100644 vue/primitives/src/select/SelectItemIndicator.vue create mode 100644 vue/primitives/src/select/SelectItemText.vue create mode 100644 vue/primitives/src/select/SelectLabel.vue create mode 100644 vue/primitives/src/select/SelectPopperPosition.vue create mode 100644 vue/primitives/src/select/SelectPortal.vue create mode 100644 vue/primitives/src/select/SelectRoot.vue create mode 100644 vue/primitives/src/select/SelectScrollButtonImpl.vue create mode 100644 vue/primitives/src/select/SelectScrollDownButton.vue create mode 100644 vue/primitives/src/select/SelectScrollUpButton.vue create mode 100644 vue/primitives/src/select/SelectSeparator.vue create mode 100644 vue/primitives/src/select/SelectTrigger.vue create mode 100644 vue/primitives/src/select/SelectValue.vue create mode 100644 vue/primitives/src/select/SelectViewport.vue create mode 100644 vue/primitives/src/select/context.ts create mode 100644 vue/primitives/src/select/index.ts create mode 100644 vue/primitives/src/select/utils.ts create mode 100644 vue/primitives/src/separator/Separator.vue create mode 100644 vue/primitives/src/separator/__test__/Separator.test.ts create mode 100644 vue/primitives/src/separator/index.ts create mode 100644 vue/primitives/src/slider/SliderRange.vue create mode 100644 vue/primitives/src/slider/SliderRoot.vue create mode 100644 vue/primitives/src/slider/SliderThumb.vue create mode 100644 vue/primitives/src/slider/SliderTrack.vue create mode 100644 vue/primitives/src/slider/__test__/Slider.test.ts create mode 100644 vue/primitives/src/slider/context.ts create mode 100644 vue/primitives/src/slider/index.ts create mode 100644 vue/primitives/src/slider/utils.ts create mode 100644 vue/primitives/src/stepper/StepperDescription.vue create mode 100644 vue/primitives/src/stepper/StepperIndicator.vue create mode 100644 vue/primitives/src/stepper/StepperItem.vue create mode 100644 vue/primitives/src/stepper/StepperRoot.vue create mode 100644 vue/primitives/src/stepper/StepperSeparator.vue create mode 100644 vue/primitives/src/stepper/StepperTitle.vue create mode 100644 vue/primitives/src/stepper/StepperTrigger.vue create mode 100644 vue/primitives/src/stepper/__test__/Stepper.test.ts create mode 100644 vue/primitives/src/stepper/context.ts create mode 100644 vue/primitives/src/stepper/index.ts create mode 100644 vue/primitives/src/switch/Switch.vue create mode 100644 vue/primitives/src/switch/__test__/Switch.test.ts create mode 100644 vue/primitives/src/switch/index.ts create mode 100644 vue/primitives/src/tabs/TabsContent.vue create mode 100644 vue/primitives/src/tabs/TabsList.vue create mode 100644 vue/primitives/src/tabs/TabsRoot.vue create mode 100644 vue/primitives/src/tabs/TabsTrigger.vue create mode 100644 vue/primitives/src/tabs/__test__/Tabs.test.ts create mode 100644 vue/primitives/src/tabs/context.ts create mode 100644 vue/primitives/src/tabs/index.ts create mode 100644 vue/primitives/src/tags-input/TagsInputClear.vue create mode 100644 vue/primitives/src/tags-input/TagsInputInput.vue create mode 100644 vue/primitives/src/tags-input/TagsInputItem.vue create mode 100644 vue/primitives/src/tags-input/TagsInputItemDelete.vue create mode 100644 vue/primitives/src/tags-input/TagsInputItemText.vue create mode 100644 vue/primitives/src/tags-input/TagsInputRoot.vue create mode 100644 vue/primitives/src/tags-input/__test__/TagsInput.test.ts create mode 100644 vue/primitives/src/tags-input/context.ts create mode 100644 vue/primitives/src/tags-input/index.ts create mode 100644 vue/primitives/src/teleport/Teleport.vue create mode 100644 vue/primitives/src/teleport/__test__/Teleport.test.ts create mode 100644 vue/primitives/src/teleport/index.ts create mode 100644 vue/primitives/src/toast/ToastAction.vue create mode 100644 vue/primitives/src/toast/ToastClose.vue create mode 100644 vue/primitives/src/toast/ToastDescription.vue create mode 100644 vue/primitives/src/toast/ToastProvider.vue create mode 100644 vue/primitives/src/toast/ToastRoot.vue create mode 100644 vue/primitives/src/toast/ToastTitle.vue create mode 100644 vue/primitives/src/toast/ToastViewport.vue create mode 100644 vue/primitives/src/toast/__test__/Toast.regression.test.ts create mode 100644 vue/primitives/src/toast/context.ts create mode 100644 vue/primitives/src/toast/index.ts create mode 100644 vue/primitives/src/toast/utils.ts create mode 100644 vue/primitives/src/toggle-group/ToggleGroupItem.vue create mode 100644 vue/primitives/src/toggle-group/ToggleGroupRoot.vue create mode 100644 vue/primitives/src/toggle-group/__test__/ToggleGroup.test.ts create mode 100644 vue/primitives/src/toggle-group/context.ts create mode 100644 vue/primitives/src/toggle-group/index.ts create mode 100644 vue/primitives/src/toggle/Toggle.vue create mode 100644 vue/primitives/src/toggle/__test__/Toggle.test.ts create mode 100644 vue/primitives/src/toggle/index.ts create mode 100644 vue/primitives/src/toolbar/ToolbarButton.vue create mode 100644 vue/primitives/src/toolbar/ToolbarRoot.vue create mode 100644 vue/primitives/src/toolbar/ToolbarSeparator.vue create mode 100644 vue/primitives/src/toolbar/__test__/Toolbar.test.ts create mode 100644 vue/primitives/src/toolbar/context.ts create mode 100644 vue/primitives/src/toolbar/index.ts create mode 100644 vue/primitives/src/tooltip/TooltipArrow.vue create mode 100644 vue/primitives/src/tooltip/TooltipContent.vue create mode 100644 vue/primitives/src/tooltip/TooltipContentImpl.vue create mode 100644 vue/primitives/src/tooltip/TooltipPortal.vue create mode 100644 vue/primitives/src/tooltip/TooltipProvider.vue create mode 100644 vue/primitives/src/tooltip/TooltipRoot.vue create mode 100644 vue/primitives/src/tooltip/TooltipTrigger.vue create mode 100644 vue/primitives/src/tooltip/__test__/Tooltip.test.ts create mode 100644 vue/primitives/src/tooltip/context.ts create mode 100644 vue/primitives/src/tooltip/index.ts create mode 100644 vue/primitives/src/tooltip/utils.ts create mode 100644 vue/primitives/src/tree/TreeItem.vue create mode 100644 vue/primitives/src/tree/TreeRoot.vue create mode 100644 vue/primitives/src/tree/__test__/Tree.test.ts create mode 100644 vue/primitives/src/tree/context.ts create mode 100644 vue/primitives/src/tree/index.ts create mode 100644 vue/primitives/src/tree/utils.ts create mode 100644 vue/primitives/src/utils/roving-focus.ts create mode 100644 vue/primitives/src/utils/useGraceArea.ts create mode 100644 vue/primitives/src/utils/useHideOthers.ts create mode 100644 vue/primitives/src/visually-hidden/VisuallyHidden.vue create mode 100644 vue/primitives/src/visually-hidden/__test__/VisuallyHidden.test.ts create mode 100644 vue/primitives/src/visually-hidden/index.ts create mode 100644 vue/primitives/tsconfig.node.json create mode 100644 vue/primitives/tsconfig.src.json diff --git a/vue/primitives/.vitest-attachments/12dbc42b176f989336a161e4f129aeb48944d8d4.png b/vue/primitives/.vitest-attachments/12dbc42b176f989336a161e4f129aeb48944d8d4.png new file mode 100644 index 0000000000000000000000000000000000000000..d1c5f0892e4109e688d20597df90050b15aaf93f GIT binary patch literal 4774 zcmeI0`Bzg{0>^`lP_@Xk=r9F=v36jr3bhEz5+Ze>E)WGP3X-4{5CTTUuq2SExGPXZ zLD_&vN)-i7u!Mvq3&;|(kw8d7_IdUnm^pLiN6xuFyz|a^@4olF-_Q4Z z&%J&iEW~cP({cy|Vz=k#o#7CORVW0qr0Um<&T1|P~Sm+^6pcQN$t z*+jH&)wK)D-NSo!o?lz|X4&T55AU0mOLqN>d8fzkQu_UG6Zcd74*DOci6Dt@*^<`- z?>!?{9oWygbGLO*=@Yi6d*MZ=8{aadJ~>C zY{>FI+;B{1QVJM~*_G5D)ix(a$X?$0A7klg{%c ze*SKLXD4V`s^sw}Pno4bIr^qX=SZV({%up8JflNZvE#~@L(TEM|13GyoHEpW-#gK~&<*RDuD&UGuk*#V zBa~W(#WWC!?!_5QwUq6p6U0*U*6-1*dtG1*g= zdCZ=0BtlQF#3mb{fT$s=rH(Yy?UKQJxTi=ko1YP=pRTcMM9B|`N|1P+Eo|ZCG6(na zG^QUogKpf-as=7NePVuGldm7_4&wMH7@(W>m3RVsf(OjXE_=6Z`Z-{CoZw$;G8BR{Bp@4LwcdZjbR8I7~sr*v^al z6G0Kk3~?yBE?zhBu1G4zETQ;1#d4FWb3GeSF9d}<+;Fm{(hFQR{W+y=s+7wxtK+)! zL9Y-DGZ_r3Ypbx|)aK*(xE6;x!N`khB4*FMvuo^^_33Gk46kt0eGgWP3PW78R3Ek? zvu9tk+C)du{k2x-YlzFk+ah1yjWoa-K1dMM3o zGnh17iPwv5u1#6O*Akik)ewd$j9VGH58ot~pBaiTk&n*S9|rVqLxRHLN#|Xg`vVB$ z&caM#j#j>a*?4883%1kB_Q|ofW-IJl?+P@(T`eXP1)ybpx#iRC8Gl%SN`Yy;I!-Iy z-O|z`Zx!MJd1`~lfa!V+=nOW4Kyb{vy^+9Q5LoKr((0ElU8aDze?ME;h z^|McjCRyY0t|0Slfp&(ED>H21pYF0#hZ__OiwI`XkyKxQROBddEI(2Ccz!^Gt5jXa zToK6KOw_ZPIzFH;3}IP!nTbi!VE*57p%Q# z%2&(Hqq+7$%1-MI;}LLZi<pq>}?Pv6Y^T6AVkkoj@rGxdvX8)<0zx!&C?;UC~Eqj!BeN7>-C}(oagL{ zlcpVib!nNk&6m?=-)>i23Vq5T&~|WXQ$G8OH`a1%k8bbR6`uKY-Gk%N8@iRhpe1g~t#2=-N78#sya?cfl6A3QcD67u z4zKIx?a-NBAo|IpfSX(9=mj4XCwtV|?Ig4nLwz(wKo87kOO~SKFh1O|As9ibzZ!PAYN_t&=;&N@tk? ziZy;`e?`e$0@7u_Uw5E%=p}Laek3t1_(aZ{jYk@*z6aK}7taH#WFWdGh>R_XvcN0+ zF>h{t1EyW7oF;!hlE5rKT7&(ts$(_}&91!aihWF)0fep?_Ca-e%bI&X#_zSJeMgsU zIa1*q-E?BtWh~enobPnM?O2%w)`sC@0sa{%3(>$6dA3him!`Dl3&8q8T|ru>^k<3H zPAn0#!tmM)-;}_1)AwCv!YoE-G_A>f$u9}o=-Pqfk;l&LvRiOGcKW4bhw?HOn}aZp z+QJ@Bc6#AwZ|-fkk?TDHcM)a^4F)MlQY0{PH6xjxv}sCu2?>5#s#ZuK0QIO}8&+db zctLm^x*|4UzHf(gB8$reD%uHR^>j^vMn0lliWVFa4i22HlCF4OtVhrhb;bG^oW#3Y z!!AkV!`vm#7jDoJ9PE_0&pqqtuX{SgU*~oARGwsH4PQ`es7z(HLWwiN>Wo@%`G_p3 zVwG&}J}a9Pba+#kUXA3Aq%%xVa1CSL8=6yyfRM z-uHecUe@cz`|*?+%H?;2i5kh`rjg2dh2ed;Wi~Y6+zQ9oNUS^a=+gw?NK>`dPf+hl zVfskwpCxmERoaRqnAD%S1Dgz%HuZjU+#4Q-8{>$`90Tdx24eG+>0ggz;I;e=p%3?1 zny4bgAy#)9rT8vCm;uTFoNDEW9)pa8wVH zz?gsJ1%Q#RL5sJ57=3O7JLTCv0U1u@aPF*+NWO+(;7#ia0-H~MtZs_y=6p}@K{m#| zb*DU?--TQCev?0M56-$P&|pw>W$71(obWgoPnh0Q8&PP z6SS;y`yqCFMVzkV^M)c1r`-pRN#MApf<1+2_6iy6pRHvLxvl#Pgb>bh0`EZf>s4FX{jQ literal 0 HcmV?d00001 diff --git a/vue/primitives/AGENTS.md b/vue/primitives/AGENTS.md new file mode 100644 index 0000000..c57db2b --- /dev/null +++ b/vue/primitives/AGENTS.md @@ -0,0 +1,447 @@ +# AGENTS.md — UI Primitives + +Руководство по работе с UI-пакетом примитивов. Описывает правила создания **новых компонентов** и **доработки существующих**. Применимо только к этому пакету (`vue/primitives/`). + +> Прочитай этот файл **до** того, как трогать код в `src/`. + +--- + +## 0. Tooling и команды + +- **Менеджер пакетов:** `pnpm` (workspaces + catalogs). +- **Линтер и форматтер:** `eslint` (flat config, пресеты `@robonen/eslint`). Prettier/Biome не использовать. +- **Сборка:** `tsdown`. +- **Тесты:** `vitest` (jsdom + browser mode на `@vitest/browser-playwright` + Chromium). + +```bash +pnpm exec vitest run # jsdom-сьют +pnpm exec vitest run src/ # один компонент +pnpm run test:browser # browser mode (Playwright) +pnpm exec tsdown # сборка ESM + CJS + .d.ts +pnpm lint:check / pnpm lint:fix # eslint +``` + +--- + +## 1. Структура пакета + +``` +src/ + / # kebab-case + Root.vue # PascalCase, корневой провайдер контекста + .vue # части (Trigger / Content / Item / Indicator…) + context.ts # фабрика + типы контекста + index.ts # барель + __test__/ + .test.ts # jsdom-тесты + .browser.test.ts # опционально, если нужен реальный браузер + utils/ # общие хелперы (roving-focus, getRawChildren …) + primitive/ # polymorphic + index.ts # реэкспорт всех компонентов +``` + +**Правила структуры:** + +- Каждый примитив = отдельная папка. Никаких «всё в одном файле». +- Имя папки — `kebab-case`, файлы — `PascalCase.vue`. +- Корневой компонент **обязан** называться `Root.vue` и быть провайдером контекста. +- Никаких циклических зависимостей между примитивами. Общий код — в `src/utils/`. +- `index.ts` примитива экспортирует **все** компоненты + контекст-хук + типы. +- После создания примитива добавь `export * from './';` в `src/index.ts`. + +--- + +## 2. Нейминг и data-атрибуты + +В коде, комментариях, доках, тестах и коммитах **не должно быть имён сторонних UI-библиотек** и внутренних кодовых названий бренда/форка. Описывай компоненты через их роль (`dialog`, `radio-group`, `spinbutton`) и паттерн (`roving-focus`, `dismissable-layer`). + +| Артефакт | Формат | +|---|---| +| Имя контекста | `''` — аргумент в `useContextFactory('')` | +| ID-префикс | `'-'` — аргумент в `useId(undefined, '-')` | +| Data-атрибуты состояния | `data-state`, `data-disabled`, `data-orientation`, `data-side`, `data-align` | + +`data-state` — каноническое отражение состояния: +- toggle/checkbox: `"checked" | "unchecked" | "indeterminate"` +- collapsible/dialog/popover: `"open" | "closed"` +- progress: `"complete" | "loading" | "indeterminate"` + +Любая интерактивная часть с `disabled` получает `data-disabled=""` (без значения), чтобы CSS-селектор `[data-disabled]` работал одинаково. + +--- + +## 3. Архитектурные паттерны + +### 3.1. Контекст — только через фабрику + +**Ручные `Symbol`, `InjectionKey`, `provide()`/`inject()` в коде примитивов запрещены.** Контекст создаётся **только** через `useContextFactory` из тулкита. Фабрика: + +- единообразно генерирует ключ (`Symbol(name)` внутри неё), +- типизирует `provide` и `inject` одним generic-аргументом, +- бросает консистентную ошибку, если `inject` делается без предка-провайдера, +- поддерживает `appProvide(app)` для глобальных провайдеров (config, dir, и т.п.). + +```ts +// context.ts +import type { ComputedRef, Ref } from 'vue'; +import { useContextFactory } from ''; + +export interface FooContext { + open: Ref; + disabled: ComputedRef; + onToggle: () => void; +} + +export const { + inject: useFooContext, + provide: provideFooContext, +} = useContextFactory('foo'); +``` + +```ts +// FooRoot.vue +provideFooContext({ open, disabled, onToggle }); + +// FooTrigger.vue +const ctx = useFooContext(); // кидает, если нет +const ctx = useFooContext(fallback); // опционально: дефолт без ошибки +``` + +**Правила:** + +- Никаких ручных `Symbol(...)` / `InjectionKey<...>` в папке примитива. +- Имя фабрики описательное (`'foo'`, `'foo-item'`, `'dialog'`), без префиксов брендов. +- Контекст хранит **`Ref` / `ComputedRef`** для реактивных полей и **функции** для действий. Не передавай голые значения — потеряешь реактивность у потребителей. +- Для per-item данных (например, `RadioGroupIndicator` смотрит на свой `RadioGroupItem`) создавай **item sub-context** отдельной фабрикой (`useContextFactory('foo-item')`). Не ходи по DOM ради `data-*`. +- Контекст-хук и типы экспортируются из `index.ts` примитива. + +### 3.2. v-model: продвинутая работа с `defineModel` + +`defineModel` — первичный инструмент для любой двусторонней связи со state. Используем его продвинутые возможности. + +#### 3.2.1. Контролируемый + неконтролируемый режим через get/set + +Вместо ручного `ref + computed`-обёртки используй **`defineModel({ get, set })`** — встроенный способ навесить преобразование/нормализацию прямо на модель: + +```ts +interface Props { + defaultOpen?: boolean; +} +const { defaultOpen = false } = defineProps(); +const local = ref(defaultOpen); + +const open = defineModel('open', { + // когда родитель не связал v-model — читаем локальный стейт + get: (external) => external ?? local.value, + // пишем и наружу (emit), и в локальный стейт + set: (value) => { + local.value = value; + return value; + }, +}); +``` + +- Если `v-model` не привязан снаружи — `defineModel` эмитит `update:open` в пустоту, а локальный ref обеспечивает работу компонента. Это и есть «uncontrolled» режим. +- Если привязан — родитель writable source of truth, локальный ref просто зеркалит его через `get`. +- Никаких отдельных `computed({ get, set })` поверх `defineModel` — всё делается в одном месте. + +#### 3.2.2. Модификаторы v-model + +Поддерживай пользовательские модификаторы, если это осмысленно для примитива (`trim`, `lazy`, `number`, кастомные): + +```ts +const [model, modifiers] = defineModel({ + set: (value) => (modifiers.trim ? value.trim() : value), +}); +``` + +Любой кастомный модификатор документируется в README примитива. + +#### 3.2.3. Нормализация union-значений + +Для union-типов (`string | string[]`, `number | null`, …) ставь преобразование в `set`, а наружу эмить нормализованный вид. Если Vue не может вывести тип модели или теряет записи — **fallback: explicit `modelValue` prop + `emit('update:modelValue', …)` + `watch(() => props.modelValue, …)`**: + +```ts +interface Props { modelValue?: string | string[] } +const props = withDefaults(defineProps(), { modelValue: undefined }); +const emit = defineEmits<{ 'update:modelValue': [v: string[] | string | undefined] }>(); + +const local = shallowRef(normalize(props.modelValue)); +watch(() => props.modelValue, (v) => { local.value = normalize(v); }); + +function commit(next: string[]) { + local.value = next; + emit('update:modelValue', serialize(next)); +} +``` + +Это обход, а не правило — **сначала попробуй `defineModel({ get, set })`**. + +### 3.3. Продвинутая реактивность + +Используй реактивность по полной — она бесплатная и устраняет ручной оркестр watch-ей. + +| Задача | Инструмент | +|---|---| +| Ссылка на элемент шаблона | `useTemplateRef('name')` (Vue 3.5+), не `ref()` | +| Развернуть `MaybeRef` | `toValue(source)` — работает и с функциями, и с refs | +| Реактивное значение из пропса | `toRef(() => props.foo)` (getter-форма, не строковая) | +| Большой список/мап, не нужна глубокая реактивность | `shallowRef` / `shallowReactive` | +| Гонки с внешним state | `watchSyncEffect` / `watch(..., { flush: 'sync' })` (осторожно) | +| «Один раз при создании» | `computed` с кэшированием, **не** вызов функции в template | +| Escape-hatch от трекинга | `markRaw`, `readonly`, `customRef` (если реально нужен дебаунс/throttle) | +| Очистка эффектов у регистров | `effectScope` + `onScopeDispose`, не `onBeforeUnmount` в циклах | +| DOM-измерения при ресайзе/скролле | `useResizeObserver`, `useEventListener`, `useMutationObserver` | + +Типовые практики: + +- **Props через getter.** `watch(() => props.value, …)` и `toRef(() => props.value)` — не деструктурируй пропсы в локальные переменные (сломаешь реактивность). +- **Computed для derived state.** Любой `data-state` / `aria-*` / классовая строка — `computed`, не вычисление в `