@primitiv-ui/react 0.1.29 → 0.1.30

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.
Files changed (213) hide show
  1. package/README.md +15 -2
  2. package/package.json +1 -1
  3. package/src/AccessibleIcon/AccessibleIcon.tsx +1 -1
  4. package/src/Accordion/Accordion.tsx +7 -7
  5. package/src/Accordion/README.md +2 -2
  6. package/src/Avatar/__tests__/Avatar.fallback.test.tsx +3 -3
  7. package/src/Breadcrumb/Breadcrumb.tsx +7 -7
  8. package/src/Breadcrumb/README.md +2 -2
  9. package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +1 -1
  10. package/src/Breadcrumb/types.ts +1 -1
  11. package/src/Button/__tests__/Button.contract.test.tsx +1 -1
  12. package/src/Carousel/Carousel.tsx +6 -6
  13. package/src/Carousel/README.md +22 -22
  14. package/src/Carousel/__tests__/Carousel.infinite.test.tsx +5 -5
  15. package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +2 -2
  16. package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +1 -1
  17. package/src/Carousel/loopEngine.ts +1 -1
  18. package/src/Carousel/types.ts +1 -1
  19. package/src/Checkbox/Checkbox.tsx +2 -2
  20. package/src/CheckboxCard/CheckboxCard.tsx +1 -1
  21. package/src/CheckboxCard/README.md +1 -1
  22. package/src/Collapsible/README.md +2 -2
  23. package/src/Combobox/Combobox.tsx +508 -0
  24. package/src/Combobox/ComboboxContext.ts +23 -0
  25. package/src/Combobox/README.md +170 -0
  26. package/src/Combobox/__tests__/Combobox.asChild.test.tsx +62 -0
  27. package/src/Combobox/__tests__/Combobox.basic-rendering.test.tsx +16 -0
  28. package/src/Combobox/__tests__/Combobox.content.test.tsx +36 -0
  29. package/src/Combobox/__tests__/Combobox.controlled-state.test.tsx +88 -0
  30. package/src/Combobox/__tests__/Combobox.empty.test.tsx +42 -0
  31. package/src/Combobox/__tests__/Combobox.ids.test.tsx +66 -0
  32. package/src/Combobox/__tests__/Combobox.item.test.tsx +76 -0
  33. package/src/Combobox/__tests__/Combobox.keyboard-interaction.test.tsx +196 -0
  34. package/src/Combobox/__tests__/Combobox.light-dismiss.test.tsx +101 -0
  35. package/src/Combobox/__tests__/Combobox.query.test.tsx +105 -0
  36. package/src/Combobox/hooks/index.ts +1 -0
  37. package/src/Combobox/hooks/useComboboxRoot.ts +2 -0
  38. package/src/Combobox/index.ts +3 -0
  39. package/src/Combobox/types.ts +133 -0
  40. package/src/ContextMenu/ContextMenu.tsx +3 -3
  41. package/src/ContextMenu/README.md +1 -1
  42. package/src/DirectionProvider/DirectionProvider.tsx +5 -5
  43. package/src/DirectionProvider/README.md +4 -4
  44. package/src/Divider/__tests__/Divider.contract.test.tsx +1 -1
  45. package/src/Drawer/Drawer.tsx +5 -5
  46. package/src/Drawer/README.md +2 -2
  47. package/src/Dropdown/Dropdown.tsx +2 -2
  48. package/src/Dropdown/README.md +7 -5
  49. package/src/EmptyState/README.md +2 -2
  50. package/src/Field/README.md +1 -1
  51. package/src/Field/hooks/useFieldProps.ts +1 -1
  52. package/src/Fieldset/Fieldset.tsx +3 -3
  53. package/src/Fieldset/README.md +2 -2
  54. package/src/Input/Input.tsx +1 -1
  55. package/src/Input/README.md +2 -2
  56. package/src/Listbox/Listbox.tsx +475 -0
  57. package/src/Listbox/ListboxContext.ts +48 -0
  58. package/src/Listbox/README.md +248 -0
  59. package/src/Listbox/__tests__/Listbox.active-descendant.test.tsx +128 -0
  60. package/src/Listbox/__tests__/Listbox.asChild.test.tsx +91 -0
  61. package/src/Listbox/__tests__/Listbox.basic-rendering.test.tsx +16 -0
  62. package/src/Listbox/__tests__/Listbox.controlled-state.test.tsx +114 -0
  63. package/src/Listbox/__tests__/Listbox.disabled.test.tsx +90 -0
  64. package/src/Listbox/__tests__/Listbox.fixtures.ts +27 -0
  65. package/src/Listbox/__tests__/Listbox.group-label.test.tsx +97 -0
  66. package/src/Listbox/__tests__/Listbox.group.test.tsx +45 -0
  67. package/src/Listbox/__tests__/Listbox.keyboard-edge-cases.test.tsx +144 -0
  68. package/src/Listbox/__tests__/Listbox.keyboard-interaction.test.tsx +112 -0
  69. package/src/Listbox/__tests__/Listbox.modifier-keys.test.tsx +86 -0
  70. package/src/Listbox/__tests__/Listbox.multi-select-keyboard.test.tsx +368 -0
  71. package/src/Listbox/__tests__/Listbox.multiple-mode.test.tsx +91 -0
  72. package/src/Listbox/__tests__/Listbox.mutation-hardening.test.tsx +480 -0
  73. package/src/Listbox/__tests__/Listbox.orientation.test.tsx +59 -0
  74. package/src/Listbox/__tests__/Listbox.reading-direction.test.tsx +65 -0
  75. package/src/Listbox/__tests__/Listbox.reorder.test.tsx +78 -0
  76. package/src/Listbox/__tests__/Listbox.selection-follows-focus.test.tsx +64 -0
  77. package/src/Listbox/__tests__/Listbox.strict-context.test.tsx +21 -0
  78. package/src/Listbox/__tests__/Listbox.typeahead.test.tsx +123 -0
  79. package/src/Listbox/__tests__/Listbox.uncontrolled-state.test.tsx +48 -0
  80. package/src/Listbox/constants.ts +2 -0
  81. package/src/Listbox/hooks/index.ts +1 -0
  82. package/src/Listbox/hooks/useListboxRoot.ts +407 -0
  83. package/src/Listbox/index.ts +3 -0
  84. package/src/Listbox/types.ts +238 -0
  85. package/src/MillerColumns/MillerColumns.tsx +104 -30
  86. package/src/MillerColumns/README.md +90 -20
  87. package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +45 -0
  88. package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +4 -2
  89. package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +18 -3
  90. package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +48 -0
  91. package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +35 -2
  92. package/src/MillerColumns/__tests__/MillerColumns.keyboard-selection-state.test.tsx +96 -0
  93. package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +29 -4
  94. package/src/MillerColumns/__tests__/MillerColumns.preview-visibility.test.tsx +79 -0
  95. package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +189 -0
  96. package/src/MillerColumns/__tests__/MillerColumns.styled-parts.test.tsx +143 -0
  97. package/src/MillerColumns/__tests__/MillerColumns.typeahead.test.tsx +104 -0
  98. package/src/MillerColumns/constants.ts +2 -0
  99. package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +5 -2
  100. package/src/MillerColumns/hooks/useMillerColumnsItem.ts +29 -7
  101. package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +80 -10
  102. package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
  103. package/src/MillerColumns/index.ts +4 -0
  104. package/src/MillerColumns/types.ts +56 -6
  105. package/src/MillerColumns/utils.ts +69 -3
  106. package/src/Modal/Modal.tsx +4 -4
  107. package/src/Modal/README.md +5 -5
  108. package/src/Modal/types.ts +1 -1
  109. package/src/NavigationMenu/NavigationMenu.tsx +6 -6
  110. package/src/NavigationMenu/README.md +3 -3
  111. package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +1 -1
  112. package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +1 -1
  113. package/src/Popover/Popover.tsx +2 -2
  114. package/src/Popover/README.md +1 -1
  115. package/src/Portal/Portal.tsx +2 -2
  116. package/src/Portal/README.md +2 -2
  117. package/src/Progress/README.md +1 -1
  118. package/src/Radio/Radio.tsx +1 -1
  119. package/src/RadioGroup/README.md +2 -2
  120. package/src/RadioGroup/RadioGroup.tsx +1 -1
  121. package/src/SegmentedControl/README.md +2 -2
  122. package/src/SegmentedControl/SegmentedControl.tsx +1 -1
  123. package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +1 -0
  124. package/src/Select/README.md +2 -2
  125. package/src/Select/Select.tsx +8 -8
  126. package/src/Select/__tests__/Select.placeholder.test.tsx +3 -3
  127. package/src/Select/__tests__/Select.typeahead.test.tsx +3 -3
  128. package/src/Select/__tests__/Select.value-mirror.test.tsx +1 -1
  129. package/src/Select/types.ts +2 -2
  130. package/src/SkipNav/README.md +3 -3
  131. package/src/SkipNav/SkipNav.tsx +5 -5
  132. package/src/Slider/README.md +37 -7
  133. package/src/Slider/Slider.tsx +9 -3
  134. package/src/Slot/Slot.tsx +2 -2
  135. package/src/Slot/__tests__/Slot.test.tsx +2 -2
  136. package/src/Slot/types.ts +1 -1
  137. package/src/SplitButton/README.md +225 -0
  138. package/src/SplitButton/SplitButton.tsx +457 -0
  139. package/src/SplitButton/SplitButtonContext.ts +26 -0
  140. package/src/SplitButton/__tests__/SplitButton.accessibility.test.tsx +55 -0
  141. package/src/SplitButton/__tests__/SplitButton.asChild.test.tsx +51 -0
  142. package/src/SplitButton/__tests__/SplitButton.basic-rendering.test.tsx +57 -0
  143. package/src/SplitButton/__tests__/SplitButton.data-attributes.test.tsx +28 -0
  144. package/src/SplitButton/__tests__/SplitButton.disabled.test.tsx +79 -0
  145. package/src/SplitButton/__tests__/SplitButton.error-handling.test.tsx +29 -0
  146. package/src/SplitButton/__tests__/SplitButton.keyboard-interaction.test.tsx +56 -0
  147. package/src/SplitButton/__tests__/SplitButton.state-modes.test.tsx +68 -0
  148. package/src/SplitButton/index.ts +2 -0
  149. package/src/SplitButton/types.ts +186 -0
  150. package/src/Switch/README.md +1 -1
  151. package/src/Switch/Switch.tsx +2 -2
  152. package/src/Switch/types.ts +1 -1
  153. package/src/Table/README.md +110 -8
  154. package/src/Table/Table.tsx +206 -10
  155. package/src/Table/TableExpandableContext.ts +7 -0
  156. package/src/Table/__tests__/Table.DetailRow.test.tsx +83 -0
  157. package/src/Table/__tests__/Table.ExpandTrigger.test.tsx +102 -0
  158. package/src/Table/__tests__/Table.Expandable.test.tsx +121 -0
  159. package/src/Table/types.ts +125 -0
  160. package/src/Tabs/README.md +4 -4
  161. package/src/Tabs/Tabs.tsx +5 -4
  162. package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +58 -0
  163. package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +15 -0
  164. package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +24 -0
  165. package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +36 -0
  166. package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +17 -0
  167. package/src/Tabs/hooks/useTabsContent.ts +5 -0
  168. package/src/Tabs/hooks/useTabsRoot.ts +29 -5
  169. package/src/Tabs/hooks/useTabsTrigger.ts +7 -2
  170. package/src/ToggleGroup/README.md +4 -4
  171. package/src/Tooltip/README.md +7 -7
  172. package/src/Tooltip/Tooltip.tsx +2 -2
  173. package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +50 -0
  174. package/src/Tooltip/__tests__/Tooltip.error-handling.test.tsx +37 -0
  175. package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
  176. package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +134 -0
  177. package/src/Tooltip/__tests__/Tooltip.skip-delay.test.tsx +216 -0
  178. package/src/Tooltip/__tests__/Tooltip.timer-coordination.test.tsx +223 -0
  179. package/src/Tooltip/hooks/useTooltipContent.ts +3 -0
  180. package/src/Tooltip/hooks/useTooltipProvider.ts +40 -14
  181. package/src/Tooltip/hooks/useTooltipRoot.ts +30 -6
  182. package/src/Tree/README.md +69 -5
  183. package/src/Tree/Tree.tsx +1 -1
  184. package/src/Tree/__tests__/Tree.styled-wrapper.test.tsx +159 -0
  185. package/src/Tree/hooks/useTreePath.ts +33 -1
  186. package/src/Tree/index.ts +6 -1
  187. package/src/Tree/utils.ts +46 -4
  188. package/src/VisuallyHidden/VisuallyHidden.tsx +1 -1
  189. package/src/hooks/index.ts +1 -0
  190. package/src/hooks/useTypeahead.ts +95 -0
  191. package/src/index.ts +6 -0
  192. package/src/useLocalStorage/__tests__/useLocalStorage.reading.test.tsx +31 -0
  193. package/src/useLocalStorage/__tests__/useLocalStorage.resilience.test.tsx +97 -0
  194. package/src/useLocalStorage/__tests__/useLocalStorage.ssr.test.tsx +20 -0
  195. package/src/useLocalStorage/__tests__/useLocalStorage.writing.test.tsx +72 -0
  196. package/src/useLocalStorage/index.ts +1 -0
  197. package/src/useLocalStorage/useLocalStorage.ts +140 -0
  198. package/src/useMediaQuery/README.md +66 -0
  199. package/src/useMediaQuery/__tests__/useMediaQuery.matching.test.tsx +42 -0
  200. package/src/useMediaQuery/__tests__/useMediaQuery.ssr.test.tsx +19 -0
  201. package/src/useMediaQuery/__tests__/useMediaQuery.subscription.test.tsx +89 -0
  202. package/src/useMediaQuery/index.ts +1 -0
  203. package/src/useMediaQuery/useMediaQuery.ts +49 -0
  204. package/src/usePagination/README.md +170 -0
  205. package/src/usePagination/__tests__/usePagination.navigation.test.tsx +109 -0
  206. package/src/usePagination/__tests__/usePagination.page-count.test.tsx +56 -0
  207. package/src/usePagination/__tests__/usePagination.page-state.test.tsx +87 -0
  208. package/src/usePagination/__tests__/usePagination.range.test.tsx +82 -0
  209. package/src/usePagination/__tests__/usePagination.slice.test.tsx +59 -0
  210. package/src/usePagination/__tests__/usePagination.windowing.test.tsx +68 -0
  211. package/src/usePagination/index.ts +1 -0
  212. package/src/usePagination/usePagination.ts +292 -0
  213. package/src/utils/getKeyToActionMap.ts +1 -1
@@ -23,11 +23,77 @@ export function selectAtDepth(
23
23
  }
24
24
 
25
25
  /**
26
- * Whether `node` is a `MillerColumns.Column` element — the identity
27
- * check used to separate an Item's cell content from its child column.
26
+ * Splits a props bag into its `aria-*` entries and everything else.
27
+ *
28
+ * `Root` renders two elements — a plain strip container and the inner
29
+ * `role="tree"` widget — so one props bag has two possible homes. ARIA
30
+ * belongs on the widget (an `aria-label` must name the tree, not its
31
+ * scroll container); `className`, `id`, `style` and the rest belong on
32
+ * the container the consumer actually lays out.
33
+ */
34
+ export function partitionAriaProps(props: Record<string, unknown>): {
35
+ aria: Record<string, unknown>;
36
+ rest: Record<string, unknown>;
37
+ } {
38
+ const aria: Record<string, unknown> = {};
39
+ const rest: Record<string, unknown> = {};
40
+
41
+ for (const [key, value] of Object.entries(props)) {
42
+ if (key.startsWith("aria-")) {
43
+ aria[key] = value;
44
+ } else {
45
+ rest[key] = value;
46
+ }
47
+ }
48
+
49
+ return { aria, rest };
50
+ }
51
+
52
+ /**
53
+ * Marker a styling layer sets on a component that renders a `MillerColumns`
54
+ * part, so this module recognises the wrapper as that part.
55
+ *
56
+ * `Item` inspects its children **before** rendering them, so it sees the
57
+ * wrapper component rather than the `MillerColumns.Column` the wrapper
58
+ * returns. Without a marker a wrapped child column is mis-read as cell
59
+ * content: the item stops being a branch (no `aria-expanded`, no chevron, no
60
+ * ArrowRight) and — worse — the column projects into whatever depth slot
61
+ * happens to be open, so selecting *one* branch reveals *every* sibling's
62
+ * children at once.
63
+ *
64
+ * ```tsx
65
+ * function StyledColumn(props: MillerColumnsColumnProps) {
66
+ * return <MillerColumns.Column {...props} className="column" />;
67
+ * }
68
+ * StyledColumn[MILLER_COLUMNS_PART] = "column";
69
+ * ```
70
+ */
71
+ export const MILLER_COLUMNS_PART: "__primitivMillerColumnsPart" =
72
+ "__primitivMillerColumnsPart";
73
+
74
+ /** The part names {@link MILLER_COLUMNS_PART} accepts. */
75
+ export type MillerColumnsPartName = "column";
76
+
77
+ /** Read a component's {@link MILLER_COLUMNS_PART} marker, if it declares one. */
78
+ function millerColumnsPartOf(
79
+ type: unknown,
80
+ ): MillerColumnsPartName | undefined {
81
+ return (type as Record<string, MillerColumnsPartName | undefined> | null)?.[
82
+ MILLER_COLUMNS_PART
83
+ ];
84
+ }
85
+
86
+ /**
87
+ * Whether `node` is a `MillerColumns.Column` element — either the part itself,
88
+ * or a component that marks itself as standing in for it (see
89
+ * {@link MILLER_COLUMNS_PART}).
28
90
  */
29
91
  export function isColumnElement(node: ReactNode): node is ReactElement {
30
- return isValidElement(node) && node.type === MillerColumnsColumn;
92
+ return (
93
+ isValidElement(node) &&
94
+ (node.type === MillerColumnsColumn ||
95
+ millerColumnsPartOf(node.type) === "column")
96
+ );
31
97
  }
32
98
 
33
99
  /**
@@ -51,7 +51,7 @@ import {
51
51
  * @example Uncontrolled
52
52
  * ```tsx
53
53
  * <Modal.Root defaultOpen>
54
- * <Modal.Content>…</Modal.Content>
54
+ * <Modal.Content>...</Modal.Content>
55
55
  * </Modal.Root>
56
56
  * ```
57
57
  *
@@ -63,7 +63,7 @@ import {
63
63
  * <Modal.Trigger>Open</Modal.Trigger>
64
64
  * <Modal.Portal>
65
65
  * <Modal.Overlay />
66
- * <Modal.Content>…</Modal.Content>
66
+ * <Modal.Content>...</Modal.Content>
67
67
  * </Modal.Portal>
68
68
  * </Modal.Root>
69
69
  * ```
@@ -140,7 +140,7 @@ ModalTrigger.displayName = "ModalTrigger";
140
140
  * ```tsx
141
141
  * <Modal.Portal container={document.getElementById("modal-root")!}>
142
142
  * <Modal.Overlay />
143
- * <Modal.Content>…</Modal.Content>
143
+ * <Modal.Content>...</Modal.Content>
144
144
  * </Modal.Portal>
145
145
  * ```
146
146
  */
@@ -267,7 +267,7 @@ ModalOverlay.displayName = "ModalOverlay";
267
267
  * if (hasUnsavedChanges) event.preventDefault();
268
268
  * }}
269
269
  * >
270
- * …
270
+ * ...
271
271
  * </Modal.Content>
272
272
  * ```
273
273
  */
@@ -90,12 +90,12 @@ rejects mixing them.
90
90
 
91
91
  ```tsx
92
92
  // Uncontrolled
93
- <Modal.Root defaultOpen>…</Modal.Root>;
93
+ <Modal.Root defaultOpen>...</Modal.Root>;
94
94
 
95
95
  // Controlled
96
96
  const [open, setOpen] = useState(false);
97
97
  <Modal.Root open={open} onOpenChange={setOpen}>
98
- …
98
+ ...
99
99
  </Modal.Root>;
100
100
  ```
101
101
 
@@ -115,7 +115,7 @@ open. `onPointerDownOutside` receives the native `PointerEvent`
115
115
  if (isRequiredFlow) event.preventDefault();
116
116
  }}
117
117
  >
118
- …
118
+ ...
119
119
  </Modal.Content>
120
120
  ```
121
121
 
@@ -126,7 +126,7 @@ import { Modal, type ModalImperativeApi } from "@primitiv-ui/react";
126
126
 
127
127
  const ref = useRef<ModalImperativeApi>(null);
128
128
 
129
- <Modal.Root ref={ref}>…</Modal.Root>;
129
+ <Modal.Root ref={ref}>...</Modal.Root>;
130
130
 
131
131
  ref.current?.open();
132
132
  ref.current?.close();
@@ -177,7 +177,7 @@ animation can play against the `data-state="closed"` attribute:
177
177
  ```tsx
178
178
  <Modal.Portal forceMount>
179
179
  <Modal.Overlay forceMount />
180
- <Modal.Content>…</Modal.Content>
180
+ <Modal.Content>...</Modal.Content>
181
181
  </Modal.Portal>
182
182
  ```
183
183
 
@@ -120,7 +120,7 @@ export type ControlledModalRootProps = {
120
120
  * TypeScript accepts only one shape at a time.
121
121
  */
122
122
  export type ModalRootProps = {
123
- /** The modal's sub-components (Trigger, Portal, Overlay, Content, …). */
123
+ /** The modal's sub-components (Trigger, Portal, Overlay, Content, ...). */
124
124
  children?: ReactNode;
125
125
  } & (UncontrolledModalRootProps | ControlledModalRootProps) & {
126
126
  /** Ref receiving the imperative {@link ModalImperativeApi | open/close handle}. */
@@ -115,14 +115,14 @@ import type {
115
115
  * useEffect(() => setOpen(""), [pathname]);
116
116
  *
117
117
  * <NavigationMenu.Root value={open} onValueChange={setOpen}>
118
- * …
118
+ * ...
119
119
  * </NavigationMenu.Root>
120
120
  * ```
121
121
  *
122
122
  * @example Click-only, for a nav that also has to work under touch
123
123
  * ```tsx
124
124
  * <NavigationMenu.Root openOnHover={false} aria-label="Docs">
125
- * …
125
+ * ...
126
126
  * </NavigationMenu.Root>
127
127
  * ```
128
128
  */
@@ -230,7 +230,7 @@ NavigationMenuRoot.displayName = "NavigationMenuRoot";
230
230
  * @example
231
231
  * ```tsx
232
232
  * <NavigationMenu.List>
233
- * <NavigationMenu.Item value="concepts">…</NavigationMenu.Item>
233
+ * <NavigationMenu.Item value="concepts">...</NavigationMenu.Item>
234
234
  * </NavigationMenu.List>
235
235
  * ```
236
236
  */
@@ -268,7 +268,7 @@ NavigationMenuList.displayName = "NavigationMenuList";
268
268
  * ```tsx
269
269
  * <NavigationMenu.Item value="concepts">
270
270
  * <NavigationMenu.Trigger>Concepts</NavigationMenu.Trigger>
271
- * <NavigationMenu.Content>…</NavigationMenu.Content>
271
+ * <NavigationMenu.Content>...</NavigationMenu.Content>
272
272
  * </NavigationMenu.Item>
273
273
  * ```
274
274
  *
@@ -483,7 +483,7 @@ NavigationMenuTrigger.displayName = "NavigationMenuTrigger";
483
483
  *
484
484
  * @example forceMount, for a CSS-animated panel
485
485
  * ```tsx
486
- * <NavigationMenu.Content forceMount className="panel">…</NavigationMenu.Content>
486
+ * <NavigationMenu.Content forceMount className="panel">...</NavigationMenu.Content>
487
487
  * ```
488
488
  * ```css
489
489
  * .panel { opacity: 0; transition: opacity 150ms; }
@@ -566,7 +566,7 @@ NavigationMenuContent.displayName = "NavigationMenuContent";
566
566
  * @example
567
567
  * ```tsx
568
568
  * <NavigationMenu.Root>
569
- * <NavigationMenu.List>…</NavigationMenu.List>
569
+ * <NavigationMenu.List>...</NavigationMenu.List>
570
570
  * <NavigationMenu.Viewport className="viewport" />
571
571
  * </NavigationMenu.Root>
572
572
  * ```
@@ -69,7 +69,7 @@ duplicate `id`s in the accessibility tree. See RFC 0019 §3–4a.
69
69
  {/* A disclosure — has a panel */}
70
70
  <NavigationMenu.Item value="concepts">
71
71
  <NavigationMenu.Trigger>Concepts</NavigationMenu.Trigger>
72
- <NavigationMenu.Content>…</NavigationMenu.Content>
72
+ <NavigationMenu.Content>...</NavigationMenu.Content>
73
73
  </NavigationMenu.Item>
74
74
 
75
75
  {/* A plain link — no panel, no value */}
@@ -102,7 +102,7 @@ const [open, setOpen] = useState("");
102
102
  const { pathname } = useLocation();
103
103
  useEffect(() => setOpen(""), [pathname]);
104
104
 
105
- <NavigationMenu.Root value={open} onValueChange={setOpen}>…</NavigationMenu.Root>;
105
+ <NavigationMenu.Root value={open} onValueChange={setOpen}>...</NavigationMenu.Root>;
106
106
  ```
107
107
 
108
108
  ## Hover intent
@@ -167,7 +167,7 @@ animates between panels instead of each entry expanding separately:
167
167
 
168
168
  ```tsx
169
169
  <NavigationMenu.Root>
170
- <NavigationMenu.List>…</NavigationMenu.List>
170
+ <NavigationMenu.List>...</NavigationMenu.List>
171
171
  <NavigationMenu.Viewport className="viewport" />
172
172
  </NavigationMenu.Root>
173
173
  ```
@@ -228,7 +228,7 @@ export function useNavigationMenuRoot({
228
228
  cancelOpen();
229
229
  cancelClose();
230
230
  },
231
- // Both are `useCallback(…, [])`, so their identities are fixed for the
231
+ // Both are `useCallback(..., [])`, so their identities are fixed for the
232
232
  // component's lifetime: emptying this array subscribes the cleanup exactly
233
233
  // once either way.
234
234
  // Stryker disable next-line ArrayDeclaration: equivalent — stable dependencies.
@@ -73,7 +73,7 @@ export function useNavigationMenuTrigger({
73
73
  openOnPointerEnterRef.current = undefined;
74
74
  cancelOpen();
75
75
  },
76
- // `cancelOpen` is a `useCallback(…, [])` on the Root, so its identity is
76
+ // `cancelOpen` is a `useCallback(..., [])` on the Root, so its identity is
77
77
  // fixed for the nav's lifetime — emptying this array yields the identical
78
78
  // memoised function.
79
79
  // Stryker disable next-line ArrayDeclaration: equivalent — stable dependency.
@@ -38,7 +38,7 @@ import type {
38
38
  * ```tsx
39
39
  * <Popover.Root>
40
40
  * <Popover.Trigger>Open</Popover.Trigger>
41
- * <Popover.Content>…</Popover.Content>
41
+ * <Popover.Content>...</Popover.Content>
42
42
  * </Popover.Root>
43
43
  * ```
44
44
  *
@@ -48,7 +48,7 @@ import type {
48
48
  *
49
49
  * <Popover.Root open={open} onOpenChange={setOpen}>
50
50
  * <Popover.Trigger>Open</Popover.Trigger>
51
- * <Popover.Content>…</Popover.Content>
51
+ * <Popover.Content>...</Popover.Content>
52
52
  * </Popover.Root>
53
53
  * ```
54
54
  */
@@ -77,7 +77,7 @@ input group whose trigger is a small button.
77
77
  <Popover.Trigger aria-label="Options">▾</Popover.Trigger>
78
78
  </div>
79
79
  </Popover.Anchor>
80
- <Popover.Content /* position-anchor: --popover in CSS */>…</Popover.Content>
80
+ <Popover.Content /* position-anchor: --popover in CSS */>...</Popover.Content>
81
81
  </Popover.Root>;
82
82
  ```
83
83
 
@@ -22,7 +22,7 @@ import type { PortalProps } from "./types";
22
22
  * ```tsx
23
23
  * {isOpen && (
24
24
  * <Portal>
25
- * <div role="dialog">…</div>
25
+ * <div role="dialog">...</div>
26
26
  * </Portal>
27
27
  * )}
28
28
  * ```
@@ -32,7 +32,7 @@ import type { PortalProps } from "./types";
32
32
  *
33
33
  * ```tsx
34
34
  * <Portal>
35
- * <div data-state={isOpen ? "open" : "closed"}>…</div>
35
+ * <div data-state={isOpen ? "open" : "closed"}>...</div>
36
36
  * </Portal>
37
37
  * ```
38
38
  *
@@ -41,7 +41,7 @@ rendering the `Portal` itself, or by wrapping it with open/closed logic:
41
41
  ```tsx
42
42
  {isOpen && (
43
43
  <Portal>
44
- <div role="dialog">…</div>
44
+ <div role="dialog">...</div>
45
45
  </Portal>
46
46
  )}
47
47
  ```
@@ -51,7 +51,7 @@ from a `data-state` attribute:
51
51
 
52
52
  ```tsx
53
53
  <Portal>
54
- <div data-state={isOpen ? "open" : "closed"}>…</div>
54
+ <div data-state={isOpen ? "open" : "closed"}>...</div>
55
55
  </Portal>
56
56
  ```
57
57
 
@@ -99,7 +99,7 @@ Both sub-components accept `asChild`. The library's ARIA attributes and
99
99
 
100
100
  ```tsx
101
101
  <Progress.Root asChild value={50} aria-label="Upload progress">
102
- <section>…</section>
102
+ <section>...</section>
103
103
  </Progress.Root>
104
104
  ```
105
105
 
@@ -28,7 +28,7 @@ function dataStateOf(checked: boolean) {
28
28
  *
29
29
  * Props routing: `className` / `style` style the **box** (the `<label>` you
30
30
  * see — the input is hidden); every other prop (`name`, `value`, `id`,
31
- * `aria-*`, `required`, `disabled`, `ref`, …) spreads onto the input, because
31
+ * `aria-*`, `required`, `disabled`, `ref`, ...) spreads onto the input, because
32
32
  * semantically the Root *is* the radio.
33
33
  *
34
34
  * Supports two state modes, statically discriminated at the type level:
@@ -44,13 +44,13 @@ TypeScript rejects mixing them.
44
44
  ```tsx
45
45
  // Uncontrolled
46
46
  <RadioGroup.Root defaultValue="compact" aria-label="Density">
47
- …
47
+ ...
48
48
  </RadioGroup.Root>;
49
49
 
50
50
  // Controlled
51
51
  const [value, setValue] = useState("compact");
52
52
  <RadioGroup.Root value={value} onValueChange={setValue} aria-label="Density">
53
- …
53
+ ...
54
54
  </RadioGroup.Root>;
55
55
  ```
56
56
 
@@ -70,7 +70,7 @@ import {
70
70
  * ```tsx
71
71
  * const [value, setValue] = useState("comfortable");
72
72
  *
73
- * <RadioGroup.Root value={value} onValueChange={setValue} aria-label="…">
73
+ * <RadioGroup.Root value={value} onValueChange={setValue} aria-label="...">
74
74
  * <RadioGroup.Item value="compact">Compact</RadioGroup.Item>
75
75
  * <RadioGroup.Item value="comfortable">Comfortable</RadioGroup.Item>
76
76
  * </RadioGroup.Root>
@@ -53,13 +53,13 @@ rejects mixing them.
53
53
  ```tsx
54
54
  // Uncontrolled
55
55
  <SegmentedControl.Root defaultValue="headless" aria-label="Mode">
56
- …
56
+ ...
57
57
  </SegmentedControl.Root>;
58
58
 
59
59
  // Controlled
60
60
  const [value, setValue] = useState("headless");
61
61
  <SegmentedControl.Root value={value} onValueChange={setValue} aria-label="Mode">
62
- …
62
+ ...
63
63
  </SegmentedControl.Root>;
64
64
  ```
65
65
 
@@ -71,7 +71,7 @@ import {
71
71
  * ```tsx
72
72
  * const [value, setValue] = useState("headless");
73
73
  *
74
- * <SegmentedControl.Root value={value} onValueChange={setValue} aria-label="…">
74
+ * <SegmentedControl.Root value={value} onValueChange={setValue} aria-label="...">
75
75
  * <SegmentedControl.Item value="headless">Headless</SegmentedControl.Item>
76
76
  * <SegmentedControl.Item value="styled">Styled</SegmentedControl.Item>
77
77
  * </SegmentedControl.Root>
@@ -72,6 +72,7 @@ export function useSegmentedControlRoot({
72
72
  // Stryker disable next-line OptionalChaining: unreachable given that invariant.
73
73
  itemsRef.current.get(itemValue)?.element.focus();
74
74
  },
75
+ // Stryker disable next-line ArrayDeclaration: equivalent — itemsRef is a stable RefObject.
75
76
  [itemsRef],
76
77
  );
77
78
 
@@ -20,7 +20,7 @@ import { Select } from "@primitiv-ui/react";
20
20
  // Rich (default)
21
21
  <Select.Root value={framework} onValueChange={setFramework}>
22
22
  <Select.Trigger>
23
- <Select.Value placeholder="Pick a framework…" />
23
+ <Select.Value placeholder="Pick a framework..." />
24
24
  </Select.Trigger>
25
25
  <Select.Content>
26
26
  <Select.Item value="react">
@@ -115,7 +115,7 @@ icon-only item with no text renders an empty, unlabelled `<option>`.
115
115
 
116
116
  ```tsx
117
117
  <Select.Root native defaultValue="apple" aria-label="Pick a fruit">
118
- <Select.Placeholder>Choose a fruit…</Select.Placeholder>
118
+ <Select.Placeholder>Choose a fruit...</Select.Placeholder>
119
119
  <Select.Group label="Fruits">
120
120
  <Select.Item value="apple">Apple</Select.Item>
121
121
  <Select.Item value="banana">Banana</Select.Item>
@@ -121,7 +121,7 @@ function hasPlaceholderChild(children: ReactNode): boolean {
121
121
  * ```tsx
122
122
  * <Select.Root value={framework} onValueChange={setFramework}>
123
123
  * <Select.Trigger>
124
- * <Select.Value placeholder="Pick a framework…" />
124
+ * <Select.Value placeholder="Pick a framework..." />
125
125
  * </Select.Trigger>
126
126
  * <Select.Content>
127
127
  * <Select.Item value="react">
@@ -141,7 +141,7 @@ function hasPlaceholderChild(children: ReactNode): boolean {
141
141
  * @example Native
142
142
  * ```tsx
143
143
  * <Select.Root native defaultValue="apple" aria-label="Pick a fruit">
144
- * <Select.Placeholder>Choose…</Select.Placeholder>
144
+ * <Select.Placeholder>Choose...</Select.Placeholder>
145
145
  * <Select.Group label="Fruits">
146
146
  * <Select.Item value="apple">Apple</Select.Item>
147
147
  * <Select.Item value="banana">Banana</Select.Item>
@@ -155,7 +155,7 @@ function hasPlaceholderChild(children: ReactNode): boolean {
155
155
  * <Field.Label>Framework</Field.Label>
156
156
  * <Select.Root name="framework" value={value} onValueChange={onChange}>
157
157
  * <Select.Trigger>
158
- * <Select.Value placeholder="Choose…" />
158
+ * <Select.Value placeholder="Choose..." />
159
159
  * </Select.Trigger>
160
160
  * <Select.Content>
161
161
  * <Select.Item value="react">React</Select.Item>
@@ -365,7 +365,7 @@ SelectItem.displayName = "SelectItem";
365
365
  * @example
366
366
  * ```tsx
367
367
  * <Select.Trigger>
368
- * <Select.Value placeholder="Pick a framework…" />
368
+ * <Select.Value placeholder="Pick a framework..." />
369
369
  * </Select.Trigger>
370
370
  * ```
371
371
  */
@@ -415,7 +415,7 @@ SelectTrigger.displayName = "SelectTrigger";
415
415
  *
416
416
  * @example
417
417
  * ```tsx
418
- * <Select.Value placeholder="Select a framework…" />
418
+ * <Select.Value placeholder="Select a framework..." />
419
419
  * ```
420
420
  */
421
421
  export function SelectValue({
@@ -639,7 +639,7 @@ SelectSeparator.displayName = "SelectSeparator";
639
639
  * @example
640
640
  * ```tsx
641
641
  * <Select.Root native required aria-label="Pick a fruit">
642
- * <Select.Placeholder>Choose a fruit…</Select.Placeholder>
642
+ * <Select.Placeholder>Choose a fruit...</Select.Placeholder>
643
643
  * <Select.Item value="apple">Apple</Select.Item>
644
644
  * <Select.Item value="banana">Banana</Select.Item>
645
645
  * </Select.Root>
@@ -698,7 +698,7 @@ export type TSelectCompound = typeof SelectRoot & {
698
698
  *
699
699
  * <Select.Root value={framework} onValueChange={setFramework}>
700
700
  * <Select.Trigger>
701
- * <Select.Value placeholder="Pick a framework…" />
701
+ * <Select.Value placeholder="Pick a framework..." />
702
702
  * </Select.Trigger>
703
703
  * <Select.Content>
704
704
  * <Select.Item value="react">
@@ -716,7 +716,7 @@ export type TSelectCompound = typeof SelectRoot & {
716
716
  * @example Native
717
717
  * ```tsx
718
718
  * <Select.Root native defaultValue="apple" aria-label="Pick a fruit">
719
- * <Select.Placeholder>Choose…</Select.Placeholder>
719
+ * <Select.Placeholder>Choose...</Select.Placeholder>
720
720
  * <Select.Group label="Fruits">
721
721
  * <Select.Item value="apple">Apple</Select.Item>
722
722
  * <Select.Item value="banana">Banana</Select.Item>
@@ -7,7 +7,7 @@ describe("Select placeholder", () => {
7
7
  // Arrange & Act
8
8
  const { container } = render(
9
9
  <Select.Root native>
10
- <Select.Placeholder>Choose a fruit…</Select.Placeholder>
10
+ <Select.Placeholder>Choose a fruit...</Select.Placeholder>
11
11
  <Select.Item value="apple">Apple</Select.Item>
12
12
  </Select.Root>,
13
13
  );
@@ -22,9 +22,9 @@ describe("Select placeholder", () => {
22
22
  expect(placeholder).not.toBeNull();
23
23
  expect(placeholder).toBeDisabled();
24
24
  expect(placeholder).toHaveAttribute("value", "");
25
- expect(placeholder).toHaveTextContent("Choose a fruit…");
25
+ expect(placeholder).toHaveTextContent("Choose a fruit...");
26
26
 
27
- // …and is the initial selection because it's the first option with an
27
+ // ...and is the initial selection because it's the first option with an
28
28
  // empty value.
29
29
  const select = screen.getByRole("combobox") as HTMLSelectElement;
30
30
  expect(select.value).toBe("");
@@ -41,11 +41,11 @@ describe("Select typeahead", () => {
41
41
  await user.keyboard("a");
42
42
  expect(option("Apricot")).toHaveFocus();
43
43
 
44
- // Repeated same char keeps cycling through the "a" matches…
44
+ // Repeated same char keeps cycling through the "a" matches...
45
45
  await user.keyboard("a");
46
46
  expect(option("Avocado")).toHaveFocus();
47
47
 
48
- // …and wraps back to the first "a" match (past the non-matching Banana).
48
+ // ...and wraps back to the first "a" match (past the non-matching Banana).
49
49
  await user.keyboard("a");
50
50
  expect(option("Apple")).toHaveFocus();
51
51
  });
@@ -56,7 +56,7 @@ describe("Select typeahead", () => {
56
56
  await user.click(screen.getByRole("button")); // Apple focused
57
57
 
58
58
  // "av" is not a repeat; it searches from the current index without the
59
- // single-char skip, so it stays put on the only "av…" match.
59
+ // single-char skip, so it stays put on the only "av..." match.
60
60
  await user.keyboard("av");
61
61
  expect(option("Avocado")).toHaveFocus();
62
62
  });
@@ -131,7 +131,7 @@ describe("Select.Value mirroring", () => {
131
131
  const trigger = screen.getByRole("button");
132
132
  expect(trigger).toHaveTextContent("React");
133
133
  expect(within(trigger).queryByTestId("mark")).not.toBeInTheDocument();
134
- // …while the row's own copy stays mounted.
134
+ // ...while the row's own copy stays mounted.
135
135
  expect(screen.getByTestId("mark")).toBeInTheDocument();
136
136
  });
137
137
 
@@ -172,7 +172,7 @@ export type SelectPlaceholderProps = Omit<
172
172
  "value" | "disabled" | "hidden"
173
173
  > & {
174
174
  /** The placeholder hint text shown in the closed select before the user
175
- * makes a selection (e.g. `"Choose a fruit…"`). */
175
+ * makes a selection (e.g. `"Choose a fruit..."`). */
176
176
  children?: ReactNode;
177
177
  /** Forwarded to the underlying `HTMLOptionElement`. */
178
178
  ref?: Ref<HTMLOptionElement>;
@@ -204,7 +204,7 @@ export type SelectTriggerProps = ComponentProps<"button"> & {
204
204
  * `children`, which the component owns.
205
205
  */
206
206
  export type SelectValueProps = Omit<ComponentProps<"span">, "children"> & {
207
- /** Shown when no value is selected (e.g. `"Select a framework…"`). */
207
+ /** Shown when no value is selected (e.g. `"Select a framework..."`). */
208
208
  placeholder?: ReactNode;
209
209
  /** Forwarded to the underlying `HTMLSpanElement`. */
210
210
  ref?: Ref<HTMLSpanElement>;
@@ -10,9 +10,9 @@ import { SkipNav } from "@primitiv-ui/react";
10
10
 
11
11
  <>
12
12
  <SkipNav.Link>Skip to main content</SkipNav.Link>
13
- <header>…site navigation…</header>
13
+ <header>...site navigation...</header>
14
14
  <SkipNav.Content>
15
- <main>…</main>
15
+ <main>...</main>
16
16
  </SkipNav.Content>
17
17
  </>;
18
18
  ```
@@ -60,7 +60,7 @@ custom id, set it on both sides:
60
60
  ```tsx
61
61
  <SkipNav.Link contentId="main">Skip to main content</SkipNav.Link>
62
62
  <SkipNav.Content id="main">
63
- <main>…</main>
63
+ <main>...</main>
64
64
  </SkipNav.Content>
65
65
  ```
66
66
 
@@ -80,14 +80,14 @@ SkipNavLink.displayName = "SkipNavLink";
80
80
  * @example Default — wraps the main page content
81
81
  * ```tsx
82
82
  * <SkipNav.Content>
83
- * <main>…</main>
83
+ * <main>...</main>
84
84
  * </SkipNav.Content>
85
85
  * ```
86
86
  *
87
87
  * @example Custom id (must match `contentId` on `SkipNav.Link`)
88
88
  * ```tsx
89
89
  * <SkipNav.Content id="main">
90
- * <main>…</main>
90
+ * <main>...</main>
91
91
  * </SkipNav.Content>
92
92
  * ```
93
93
  */
@@ -132,9 +132,9 @@ SkipNavContent.displayName = "SkipNavContent";
132
132
  * return (
133
133
  * <>
134
134
  * <SkipNav.Link>Skip to main content</SkipNav.Link>
135
- * <header>…site navigation…</header>
135
+ * <header>...site navigation...</header>
136
136
  * <SkipNav.Content>
137
- * <main>…</main>
137
+ * <main>...</main>
138
138
  * </SkipNav.Content>
139
139
  * </>
140
140
  * );
@@ -145,7 +145,7 @@ SkipNavContent.displayName = "SkipNavContent";
145
145
  * ```tsx
146
146
  * <SkipNav.Link contentId="main">Skip to main content</SkipNav.Link>
147
147
  * <SkipNav.Content id="main">
148
- * <main>…</main>
148
+ * <main>...</main>
149
149
  * </SkipNav.Content>
150
150
  * ```
151
151
  *