@primitiv-ui/react 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/package.json +1 -1
- package/src/AccessibleIcon/AccessibleIcon.tsx +1 -1
- package/src/Accordion/Accordion.tsx +7 -7
- package/src/Accordion/README.md +2 -2
- package/src/Avatar/__tests__/Avatar.fallback.test.tsx +3 -3
- package/src/Breadcrumb/Breadcrumb.tsx +7 -7
- package/src/Breadcrumb/README.md +2 -2
- package/src/Breadcrumb/__tests__/Breadcrumb.ellipsis.test.tsx +1 -1
- package/src/Breadcrumb/types.ts +1 -1
- package/src/Button/__tests__/Button.contract.test.tsx +1 -1
- package/src/Carousel/Carousel.tsx +6 -6
- package/src/Carousel/README.md +22 -22
- package/src/Carousel/__tests__/Carousel.infinite.test.tsx +5 -5
- package/src/Carousel/__tests__/Carousel.intersection-observer.test.tsx +2 -2
- package/src/Carousel/__tests__/Carousel.slides-per-move.test.tsx +1 -1
- package/src/Carousel/loopEngine.ts +1 -1
- package/src/Carousel/types.ts +1 -1
- package/src/Checkbox/Checkbox.tsx +2 -2
- package/src/CheckboxCard/CheckboxCard.tsx +1 -1
- package/src/CheckboxCard/README.md +1 -1
- package/src/Collapsible/README.md +2 -2
- package/src/Combobox/Combobox.tsx +508 -0
- package/src/Combobox/ComboboxContext.ts +23 -0
- package/src/Combobox/README.md +170 -0
- package/src/Combobox/__tests__/Combobox.asChild.test.tsx +62 -0
- package/src/Combobox/__tests__/Combobox.basic-rendering.test.tsx +16 -0
- package/src/Combobox/__tests__/Combobox.content.test.tsx +36 -0
- package/src/Combobox/__tests__/Combobox.controlled-state.test.tsx +88 -0
- package/src/Combobox/__tests__/Combobox.empty.test.tsx +42 -0
- package/src/Combobox/__tests__/Combobox.ids.test.tsx +66 -0
- package/src/Combobox/__tests__/Combobox.item.test.tsx +76 -0
- package/src/Combobox/__tests__/Combobox.keyboard-interaction.test.tsx +196 -0
- package/src/Combobox/__tests__/Combobox.light-dismiss.test.tsx +101 -0
- package/src/Combobox/__tests__/Combobox.query.test.tsx +105 -0
- package/src/Combobox/hooks/index.ts +1 -0
- package/src/Combobox/hooks/useComboboxRoot.ts +2 -0
- package/src/Combobox/index.ts +3 -0
- package/src/Combobox/types.ts +133 -0
- package/src/ContextMenu/ContextMenu.tsx +3 -3
- package/src/ContextMenu/README.md +1 -1
- package/src/DirectionProvider/DirectionProvider.tsx +5 -5
- package/src/DirectionProvider/README.md +4 -4
- package/src/Divider/__tests__/Divider.contract.test.tsx +1 -1
- package/src/Drawer/Drawer.tsx +5 -5
- package/src/Drawer/README.md +2 -2
- package/src/Dropdown/Dropdown.tsx +2 -2
- package/src/Dropdown/README.md +7 -5
- package/src/EmptyState/README.md +2 -2
- package/src/Field/README.md +1 -1
- package/src/Field/hooks/useFieldProps.ts +1 -1
- package/src/Fieldset/Fieldset.tsx +3 -3
- package/src/Fieldset/README.md +2 -2
- package/src/Input/Input.tsx +1 -1
- package/src/Input/README.md +2 -2
- package/src/Listbox/Listbox.tsx +475 -0
- package/src/Listbox/ListboxContext.ts +48 -0
- package/src/Listbox/README.md +248 -0
- package/src/Listbox/__tests__/Listbox.active-descendant.test.tsx +128 -0
- package/src/Listbox/__tests__/Listbox.asChild.test.tsx +91 -0
- package/src/Listbox/__tests__/Listbox.basic-rendering.test.tsx +16 -0
- package/src/Listbox/__tests__/Listbox.controlled-state.test.tsx +114 -0
- package/src/Listbox/__tests__/Listbox.disabled.test.tsx +90 -0
- package/src/Listbox/__tests__/Listbox.fixtures.ts +27 -0
- package/src/Listbox/__tests__/Listbox.group-label.test.tsx +97 -0
- package/src/Listbox/__tests__/Listbox.group.test.tsx +45 -0
- package/src/Listbox/__tests__/Listbox.keyboard-edge-cases.test.tsx +144 -0
- package/src/Listbox/__tests__/Listbox.keyboard-interaction.test.tsx +112 -0
- package/src/Listbox/__tests__/Listbox.modifier-keys.test.tsx +86 -0
- package/src/Listbox/__tests__/Listbox.multi-select-keyboard.test.tsx +368 -0
- package/src/Listbox/__tests__/Listbox.multiple-mode.test.tsx +91 -0
- package/src/Listbox/__tests__/Listbox.mutation-hardening.test.tsx +480 -0
- package/src/Listbox/__tests__/Listbox.orientation.test.tsx +59 -0
- package/src/Listbox/__tests__/Listbox.reading-direction.test.tsx +65 -0
- package/src/Listbox/__tests__/Listbox.reorder.test.tsx +78 -0
- package/src/Listbox/__tests__/Listbox.selection-follows-focus.test.tsx +64 -0
- package/src/Listbox/__tests__/Listbox.strict-context.test.tsx +21 -0
- package/src/Listbox/__tests__/Listbox.typeahead.test.tsx +123 -0
- package/src/Listbox/__tests__/Listbox.uncontrolled-state.test.tsx +48 -0
- package/src/Listbox/constants.ts +2 -0
- package/src/Listbox/hooks/index.ts +1 -0
- package/src/Listbox/hooks/useListboxRoot.ts +407 -0
- package/src/Listbox/index.ts +3 -0
- package/src/Listbox/types.ts +238 -0
- package/src/MillerColumns/MillerColumns.tsx +104 -30
- package/src/MillerColumns/README.md +90 -20
- package/src/MillerColumns/__tests__/MillerColumns.aria.test.tsx +45 -0
- package/src/MillerColumns/__tests__/MillerColumns.auto-scroll.test.tsx +4 -2
- package/src/MillerColumns/__tests__/MillerColumns.basic-rendering.test.tsx +18 -3
- package/src/MillerColumns/__tests__/MillerColumns.data-attributes.test.tsx +48 -0
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-interaction.test.tsx +35 -2
- package/src/MillerColumns/__tests__/MillerColumns.keyboard-selection-state.test.tsx +96 -0
- package/src/MillerColumns/__tests__/MillerColumns.preview-panel.test.tsx +29 -4
- package/src/MillerColumns/__tests__/MillerColumns.preview-visibility.test.tsx +79 -0
- package/src/MillerColumns/__tests__/MillerColumns.resize.test.tsx +189 -0
- package/src/MillerColumns/__tests__/MillerColumns.styled-parts.test.tsx +143 -0
- package/src/MillerColumns/__tests__/MillerColumns.typeahead.test.tsx +104 -0
- package/src/MillerColumns/constants.ts +2 -0
- package/src/MillerColumns/hooks/useMillerColumnsColumn.ts +5 -2
- package/src/MillerColumns/hooks/useMillerColumnsItem.ts +29 -7
- package/src/MillerColumns/hooks/useMillerColumnsResizeHandle.ts +80 -10
- package/src/MillerColumns/hooks/useMillerColumnsRoot.ts +0 -0
- package/src/MillerColumns/index.ts +4 -0
- package/src/MillerColumns/types.ts +56 -6
- package/src/MillerColumns/utils.ts +69 -3
- package/src/Modal/Modal.tsx +4 -4
- package/src/Modal/README.md +5 -5
- package/src/Modal/types.ts +1 -1
- package/src/NavigationMenu/NavigationMenu.tsx +6 -6
- package/src/NavigationMenu/README.md +3 -3
- package/src/NavigationMenu/hooks/useNavigationMenuRoot.ts +1 -1
- package/src/NavigationMenu/hooks/useNavigationMenuTrigger.ts +1 -1
- package/src/Popover/Popover.tsx +2 -2
- package/src/Popover/README.md +1 -1
- package/src/Portal/Portal.tsx +2 -2
- package/src/Portal/README.md +2 -2
- package/src/Progress/README.md +1 -1
- package/src/Radio/Radio.tsx +1 -1
- package/src/RadioCard/README.md +2 -1
- package/src/RadioCard/RadioCard.tsx +5 -2
- package/src/RadioCard/__tests__/RadioCard.disabled-items.test.tsx +23 -0
- package/src/RadioGroup/README.md +2 -2
- package/src/RadioGroup/RadioGroup.tsx +1 -1
- package/src/SegmentedControl/README.md +2 -2
- package/src/SegmentedControl/SegmentedControl.tsx +1 -1
- package/src/SegmentedControl/hooks/useSegmentedControlRoot.ts +1 -0
- package/src/Select/README.md +2 -2
- package/src/Select/Select.tsx +8 -8
- package/src/Select/__tests__/Select.placeholder.test.tsx +3 -3
- package/src/Select/__tests__/Select.typeahead.test.tsx +3 -3
- package/src/Select/__tests__/Select.value-mirror.test.tsx +1 -1
- package/src/Select/types.ts +2 -2
- package/src/SkipNav/README.md +3 -3
- package/src/SkipNav/SkipNav.tsx +5 -5
- package/src/Slider/README.md +37 -7
- package/src/Slider/Slider.tsx +9 -3
- package/src/Slot/Slot.tsx +2 -2
- package/src/Slot/__tests__/Slot.test.tsx +2 -2
- package/src/Slot/types.ts +1 -1
- package/src/SplitButton/README.md +225 -0
- package/src/SplitButton/SplitButton.tsx +457 -0
- package/src/SplitButton/SplitButtonContext.ts +26 -0
- package/src/SplitButton/__tests__/SplitButton.accessibility.test.tsx +55 -0
- package/src/SplitButton/__tests__/SplitButton.asChild.test.tsx +51 -0
- package/src/SplitButton/__tests__/SplitButton.basic-rendering.test.tsx +57 -0
- package/src/SplitButton/__tests__/SplitButton.data-attributes.test.tsx +28 -0
- package/src/SplitButton/__tests__/SplitButton.disabled.test.tsx +79 -0
- package/src/SplitButton/__tests__/SplitButton.error-handling.test.tsx +29 -0
- package/src/SplitButton/__tests__/SplitButton.keyboard-interaction.test.tsx +56 -0
- package/src/SplitButton/__tests__/SplitButton.state-modes.test.tsx +68 -0
- package/src/SplitButton/index.ts +2 -0
- package/src/SplitButton/types.ts +186 -0
- package/src/Switch/README.md +1 -1
- package/src/Switch/Switch.tsx +2 -2
- package/src/Switch/types.ts +1 -1
- package/src/Table/README.md +110 -8
- package/src/Table/Table.tsx +206 -10
- package/src/Table/TableExpandableContext.ts +7 -0
- package/src/Table/__tests__/Table.DetailRow.test.tsx +83 -0
- package/src/Table/__tests__/Table.ExpandTrigger.test.tsx +102 -0
- package/src/Table/__tests__/Table.Expandable.test.tsx +121 -0
- package/src/Table/types.ts +125 -0
- package/src/Tabs/README.md +4 -4
- package/src/Tabs/Tabs.tsx +5 -4
- package/src/Tabs/__tests__/Tabs.activation-mode.test.tsx +58 -0
- package/src/Tabs/__tests__/Tabs.basic-rendering.test.tsx +15 -0
- package/src/Tabs/__tests__/Tabs.change-event-callbacks.test.tsx +24 -0
- package/src/Tabs/__tests__/Tabs.disabled-tabs.test.tsx +36 -0
- package/src/Tabs/__tests__/Tabs.lazy-mount.test.tsx +17 -0
- package/src/Tabs/hooks/useTabsContent.ts +5 -0
- package/src/Tabs/hooks/useTabsRoot.ts +29 -5
- package/src/Tabs/hooks/useTabsTrigger.ts +7 -2
- package/src/ToggleGroup/README.md +4 -4
- package/src/Tooltip/README.md +7 -7
- package/src/Tooltip/Tooltip.tsx +2 -2
- package/src/Tooltip/__tests__/Tooltip.basic-rendering.test.tsx +50 -0
- package/src/Tooltip/__tests__/Tooltip.error-handling.test.tsx +37 -0
- package/src/Tooltip/__tests__/Tooltip.escape-hatches.test.tsx +73 -0
- package/src/Tooltip/__tests__/Tooltip.keyboard-interaction.test.tsx +134 -0
- package/src/Tooltip/__tests__/Tooltip.skip-delay.test.tsx +216 -0
- package/src/Tooltip/__tests__/Tooltip.timer-coordination.test.tsx +223 -0
- package/src/Tooltip/hooks/useTooltipContent.ts +3 -0
- package/src/Tooltip/hooks/useTooltipProvider.ts +40 -14
- package/src/Tooltip/hooks/useTooltipRoot.ts +30 -6
- package/src/Tree/README.md +69 -5
- package/src/Tree/Tree.tsx +1 -1
- package/src/Tree/__tests__/Tree.styled-wrapper.test.tsx +159 -0
- package/src/Tree/hooks/useTreePath.ts +33 -1
- package/src/Tree/index.ts +6 -1
- package/src/Tree/utils.ts +46 -4
- package/src/VisuallyHidden/VisuallyHidden.tsx +1 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useTypeahead.ts +95 -0
- package/src/index.ts +6 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.reading.test.tsx +31 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.resilience.test.tsx +97 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.ssr.test.tsx +20 -0
- package/src/useLocalStorage/__tests__/useLocalStorage.writing.test.tsx +72 -0
- package/src/useLocalStorage/index.ts +1 -0
- package/src/useLocalStorage/useLocalStorage.ts +140 -0
- package/src/useMediaQuery/README.md +66 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.matching.test.tsx +42 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.ssr.test.tsx +19 -0
- package/src/useMediaQuery/__tests__/useMediaQuery.subscription.test.tsx +89 -0
- package/src/useMediaQuery/index.ts +1 -0
- package/src/useMediaQuery/useMediaQuery.ts +49 -0
- package/src/usePagination/README.md +170 -0
- package/src/usePagination/__tests__/usePagination.navigation.test.tsx +109 -0
- package/src/usePagination/__tests__/usePagination.page-count.test.tsx +56 -0
- package/src/usePagination/__tests__/usePagination.page-state.test.tsx +87 -0
- package/src/usePagination/__tests__/usePagination.range.test.tsx +82 -0
- package/src/usePagination/__tests__/usePagination.slice.test.tsx +59 -0
- package/src/usePagination/__tests__/usePagination.windowing.test.tsx +68 -0
- package/src/usePagination/index.ts +1 -0
- package/src/usePagination/usePagination.ts +292 -0
- package/src/utils/getKeyToActionMap.ts +1 -1
|
@@ -23,11 +23,77 @@ export function selectAtDepth(
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
92
|
+
return (
|
|
93
|
+
isValidElement(node) &&
|
|
94
|
+
(node.type === MillerColumnsColumn ||
|
|
95
|
+
millerColumnsPartOf(node.type) === "column")
|
|
96
|
+
);
|
|
31
97
|
}
|
|
32
98
|
|
|
33
99
|
/**
|
package/src/Modal/Modal.tsx
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
* @example Uncontrolled
|
|
52
52
|
* ```tsx
|
|
53
53
|
* <Modal.Root defaultOpen>
|
|
54
|
-
* <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
|
|
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
|
|
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
|
*/
|
package/src/Modal/README.md
CHANGED
|
@@ -90,12 +90,12 @@ rejects mixing them.
|
|
|
90
90
|
|
|
91
91
|
```tsx
|
|
92
92
|
// Uncontrolled
|
|
93
|
-
<Modal.Root defaultOpen
|
|
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}
|
|
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
|
|
180
|
+
<Modal.Content>...</Modal.Content>
|
|
181
181
|
</Modal.Portal>
|
|
182
182
|
```
|
|
183
183
|
|
package/src/Modal/types.ts
CHANGED
|
@@ -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"
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
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(
|
|
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(
|
|
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.
|
package/src/Popover/Popover.tsx
CHANGED
|
@@ -38,7 +38,7 @@ import type {
|
|
|
38
38
|
* ```tsx
|
|
39
39
|
* <Popover.Root>
|
|
40
40
|
* <Popover.Trigger>Open</Popover.Trigger>
|
|
41
|
-
* <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
|
|
51
|
+
* <Popover.Content>...</Popover.Content>
|
|
52
52
|
* </Popover.Root>
|
|
53
53
|
* ```
|
|
54
54
|
*/
|
package/src/Popover/README.md
CHANGED
|
@@ -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
|
|
80
|
+
<Popover.Content /* position-anchor: --popover in CSS */>...</Popover.Content>
|
|
81
81
|
</Popover.Root>;
|
|
82
82
|
```
|
|
83
83
|
|
package/src/Portal/Portal.tsx
CHANGED
|
@@ -22,7 +22,7 @@ import type { PortalProps } from "./types";
|
|
|
22
22
|
* ```tsx
|
|
23
23
|
* {isOpen && (
|
|
24
24
|
* <Portal>
|
|
25
|
-
* <div role="dialog"
|
|
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"}
|
|
35
|
+
* <div data-state={isOpen ? "open" : "closed"}>...</div>
|
|
36
36
|
* </Portal>
|
|
37
37
|
* ```
|
|
38
38
|
*
|
package/src/Portal/README.md
CHANGED
|
@@ -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"
|
|
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"}
|
|
54
|
+
<div data-state={isOpen ? "open" : "closed"}>...</div>
|
|
55
55
|
</Portal>
|
|
56
56
|
```
|
|
57
57
|
|
package/src/Progress/README.md
CHANGED
package/src/Radio/Radio.tsx
CHANGED
|
@@ -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`,
|
|
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:
|
package/src/RadioCard/README.md
CHANGED
|
@@ -27,7 +27,7 @@ import { RadioCard } from "@primitiv-ui/react";
|
|
|
27
27
|
| Export | Element | ARIA / data hooks | `asChild` |
|
|
28
28
|
|--------|---------|------------------|-----------|
|
|
29
29
|
| `RadioCard.Root` | `<div>` | `role="radiogroup"` | yes |
|
|
30
|
-
| `RadioCard.Item` | `<button>` | `role="radio"`, `aria-checked`, `data-state`, `tabIndex` | yes |
|
|
30
|
+
| `RadioCard.Item` | `<button>` | `role="radio"`, `aria-checked`, `data-state`, `data-disabled`, `tabIndex` | yes |
|
|
31
31
|
| `RadioCard.Indicator` | `<span>` | `aria-hidden="true"`, `data-state` | yes |
|
|
32
32
|
|
|
33
33
|
## State modes
|
|
@@ -131,3 +131,4 @@ to keep it in the DOM — `data-state="unchecked"` lets a CSS exit animation pla
|
|
|
131
131
|
| Attribute | Values | Set on |
|
|
132
132
|
|-----------|--------|--------|
|
|
133
133
|
| `data-state` | `"checked"` \| `"unchecked"` | `RadioCard.Item`, `RadioCard.Indicator` |
|
|
134
|
+
| `data-disabled` | `""` (present only while disabled) | `RadioCard.Item` |
|
|
@@ -157,8 +157,10 @@ RadioCardRoot.displayName = "RadioCardRoot";
|
|
|
157
157
|
* excludes the Item from arrow-key navigation and the roving-tabindex
|
|
158
158
|
* home base.
|
|
159
159
|
*
|
|
160
|
-
* **Styling
|
|
161
|
-
* selection state
|
|
160
|
+
* **Styling hooks.** `data-state="checked" | "unchecked"` mirrors the
|
|
161
|
+
* selection state, and `data-disabled=""` is present while disabled — the
|
|
162
|
+
* same pair `CheckboxCard.Root` publishes, so one stylesheet can dress
|
|
163
|
+
* both.
|
|
162
164
|
*
|
|
163
165
|
* **`asChild` prop.** Pass `asChild` to render any consumer element with
|
|
164
166
|
* the Item's ARIA, data-state, tabIndex, onClick, onKeyDown, disabled, and
|
|
@@ -223,6 +225,7 @@ export function RadioCardItem({
|
|
|
223
225
|
role: "radio" as const,
|
|
224
226
|
"aria-checked": isChecked,
|
|
225
227
|
"data-state": isChecked ? ("checked" as const) : ("unchecked" as const),
|
|
228
|
+
"data-disabled": disabled ? "" : undefined,
|
|
226
229
|
tabIndex: isTabStop ? 0 : -1,
|
|
227
230
|
disabled,
|
|
228
231
|
onClick: composeEventHandlers(onClick, () => select(value)),
|
|
@@ -18,6 +18,29 @@ describe("RadioCard disabled items", () => {
|
|
|
18
18
|
expect(screen.getByRole("radio", { name: "Starter" })).toBeDisabled();
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
/*
|
|
22
|
+
* The styling hook, not just the native attribute. `[data-disabled]` is what
|
|
23
|
+
* the registry stylesheet keys the dimmed look off — the same hook
|
|
24
|
+
* `CheckboxCard.Root` already publishes — so without it the shipped disabled
|
|
25
|
+
* treatment silently never applies (found by measuring: a disabled RadioCard
|
|
26
|
+
* rendered at opacity 1 while a disabled CheckboxCard correctly dimmed).
|
|
27
|
+
*/
|
|
28
|
+
it('sets data-disabled="" on a disabled item, and omits it otherwise', () => {
|
|
29
|
+
// Arrange & Act
|
|
30
|
+
render(
|
|
31
|
+
<RadioCard.Root aria-label="Plan">
|
|
32
|
+
<RadioCard.Item value="starter" disabled>
|
|
33
|
+
Starter
|
|
34
|
+
</RadioCard.Item>
|
|
35
|
+
<RadioCard.Item value="pro">Pro</RadioCard.Item>
|
|
36
|
+
</RadioCard.Root>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(screen.getByRole("radio", { name: "Starter" })).toHaveAttribute("data-disabled", "");
|
|
41
|
+
expect(screen.getByRole("radio", { name: "Pro" })).not.toHaveAttribute("data-disabled");
|
|
42
|
+
});
|
|
43
|
+
|
|
21
44
|
it("does not change selection when a disabled item is clicked", async () => {
|
|
22
45
|
// Arrange
|
|
23
46
|
const user = userEvent.setup();
|
package/src/RadioGroup/README.md
CHANGED
|
@@ -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
|
|
package/src/Select/README.md
CHANGED
|
@@ -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
|
|
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>
|
package/src/Select/Select.tsx
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
134
|
+
// ...while the row's own copy stays mounted.
|
|
135
135
|
expect(screen.getByTestId("mark")).toBeInTheDocument();
|
|
136
136
|
});
|
|
137
137
|
|
package/src/Select/types.ts
CHANGED
|
@@ -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>;
|