@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
|
@@ -67,6 +67,51 @@ describe("MillerColumns — aria", () => {
|
|
|
67
67
|
);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
it("puts the tree role on an inner element, not the strip container", () => {
|
|
71
|
+
const { container } = render(<Tree />);
|
|
72
|
+
|
|
73
|
+
const strip = container.querySelector("[data-miller-columns-strip]");
|
|
74
|
+
const tree = screen.getByRole("tree");
|
|
75
|
+
|
|
76
|
+
expect(strip).not.toBe(tree);
|
|
77
|
+
expect(strip).toContainElement(tree);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("keeps the preview panel outside the tree", () => {
|
|
81
|
+
render(
|
|
82
|
+
<MillerColumns.Root defaultValue={["a"]}>
|
|
83
|
+
<MillerColumns.Column>
|
|
84
|
+
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
85
|
+
</MillerColumns.Column>
|
|
86
|
+
<MillerColumns.PreviewPanel data-testid="preview">
|
|
87
|
+
Preview
|
|
88
|
+
</MillerColumns.PreviewPanel>
|
|
89
|
+
</MillerColumns.Root>,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// A tree may only own treeitem and group; the panel is neither.
|
|
93
|
+
expect(screen.getByRole("tree")).not.toContainElement(
|
|
94
|
+
screen.getByTestId("preview"),
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("splits aria-* onto the tree widget and everything else onto the strip", () => {
|
|
99
|
+
const { container } = render(
|
|
100
|
+
<MillerColumns.Root aria-label="Files" className="strip" id="picker">
|
|
101
|
+
<MillerColumns.Column>
|
|
102
|
+
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
103
|
+
</MillerColumns.Column>
|
|
104
|
+
</MillerColumns.Root>,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const strip = container.querySelector(".strip")!;
|
|
108
|
+
|
|
109
|
+
// The widget is what needs naming; the container is what needs styling.
|
|
110
|
+
expect(screen.getByRole("tree", { name: "Files" })).toBeInTheDocument();
|
|
111
|
+
expect(strip).not.toHaveAttribute("aria-label");
|
|
112
|
+
expect(strip).toHaveAttribute("id", "picker");
|
|
113
|
+
});
|
|
114
|
+
|
|
70
115
|
it("keeps a treeitem's accessible name to its own cell content", async () => {
|
|
71
116
|
const user = userEvent.setup();
|
|
72
117
|
|
|
@@ -32,9 +32,11 @@ describe("MillerColumns — auto-scroll", () => {
|
|
|
32
32
|
it("scrolls the strip to reveal a newly opened column", async () => {
|
|
33
33
|
const user = userEvent.setup();
|
|
34
34
|
|
|
35
|
-
render(<Tree />);
|
|
35
|
+
const { container } = render(<Tree />);
|
|
36
36
|
|
|
37
|
-
const strip =
|
|
37
|
+
const strip = container.querySelector<HTMLElement>(
|
|
38
|
+
"[data-miller-columns-strip]",
|
|
39
|
+
)!;
|
|
38
40
|
const scrollTo = vi.spyOn(strip, "scrollTo");
|
|
39
41
|
|
|
40
42
|
await user.click(screen.getByRole("treeitem", { name: "Fruit" }));
|
|
@@ -3,8 +3,8 @@ import { render, screen } from "@testing-library/react";
|
|
|
3
3
|
import { MillerColumns } from "../MillerColumns";
|
|
4
4
|
|
|
5
5
|
describe("MillerColumns — basic rendering", () => {
|
|
6
|
-
it("renders
|
|
7
|
-
render(
|
|
6
|
+
it("renders a strip container with a horizontal orientation hook", () => {
|
|
7
|
+
const { container } = render(
|
|
8
8
|
<MillerColumns.Root>
|
|
9
9
|
<MillerColumns.Column>
|
|
10
10
|
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
@@ -12,12 +12,27 @@ describe("MillerColumns — basic rendering", () => {
|
|
|
12
12
|
</MillerColumns.Root>,
|
|
13
13
|
);
|
|
14
14
|
|
|
15
|
-
const strip =
|
|
15
|
+
const strip = container.querySelector("[data-miller-columns-strip]");
|
|
16
16
|
|
|
17
17
|
expect(strip).toBeInTheDocument();
|
|
18
18
|
expect(strip).toHaveAttribute("data-orientation", "horizontal");
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
it("renders the tree widget inside the strip container", () => {
|
|
22
|
+
const { container } = render(
|
|
23
|
+
<MillerColumns.Root>
|
|
24
|
+
<MillerColumns.Column>
|
|
25
|
+
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
26
|
+
</MillerColumns.Column>
|
|
27
|
+
</MillerColumns.Root>,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const tree = screen.getByRole("tree");
|
|
31
|
+
|
|
32
|
+
expect(tree).toHaveAttribute("data-miller-columns-tree");
|
|
33
|
+
expect(container.querySelector("[data-miller-columns-strip]")).toContainElement(tree);
|
|
34
|
+
});
|
|
35
|
+
|
|
21
36
|
it("projects the top-level column into the strip as a group", () => {
|
|
22
37
|
render(
|
|
23
38
|
<MillerColumns.Root>
|
|
@@ -33,6 +33,27 @@ describe("MillerColumns — data attributes", () => {
|
|
|
33
33
|
expect(groups[1]).toHaveAttribute("data-depth", "1");
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
it("flags a column with no items as empty", async () => {
|
|
37
|
+
const user = userEvent.setup();
|
|
38
|
+
|
|
39
|
+
render(
|
|
40
|
+
<MillerColumns.Root>
|
|
41
|
+
<MillerColumns.Column>
|
|
42
|
+
<MillerColumns.Item value="empty-folder">
|
|
43
|
+
Empty folder
|
|
44
|
+
<MillerColumns.Column />
|
|
45
|
+
</MillerColumns.Item>
|
|
46
|
+
</MillerColumns.Column>
|
|
47
|
+
</MillerColumns.Root>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
await user.click(screen.getByRole("treeitem", { name: "Empty folder" }));
|
|
51
|
+
|
|
52
|
+
const [root, revealed] = screen.getAllByRole("group");
|
|
53
|
+
expect(root).not.toHaveAttribute("data-empty");
|
|
54
|
+
expect(revealed).toHaveAttribute("data-empty");
|
|
55
|
+
});
|
|
56
|
+
|
|
36
57
|
it("reflects selection through data-state", async () => {
|
|
37
58
|
const user = userEvent.setup();
|
|
38
59
|
|
|
@@ -46,6 +67,33 @@ describe("MillerColumns — data attributes", () => {
|
|
|
46
67
|
expect(veg).toHaveAttribute("data-state", "selected");
|
|
47
68
|
});
|
|
48
69
|
|
|
70
|
+
it("marks only the deepest selected item with data-terminal", async () => {
|
|
71
|
+
const user = userEvent.setup();
|
|
72
|
+
|
|
73
|
+
render(<Tree />);
|
|
74
|
+
|
|
75
|
+
const fruit = screen.getByRole("treeitem", { name: "Fruit" });
|
|
76
|
+
|
|
77
|
+
// Selected and deepest — it is the terminal selection.
|
|
78
|
+
await user.click(fruit);
|
|
79
|
+
expect(fruit).toHaveAttribute("data-terminal");
|
|
80
|
+
|
|
81
|
+
// Still selected, but now an ancestor of a deeper selection.
|
|
82
|
+
await user.click(screen.getByRole("treeitem", { name: "Apple" }));
|
|
83
|
+
|
|
84
|
+
expect(fruit).toHaveAttribute("data-state", "selected");
|
|
85
|
+
expect(fruit).not.toHaveAttribute("data-terminal");
|
|
86
|
+
expect(screen.getByRole("treeitem", { name: "Apple" })).toHaveAttribute(
|
|
87
|
+
"data-terminal",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Unselected, but sitting at the same depth the terminal item once
|
|
91
|
+
// occupied — depth alone must not be what earns the attribute.
|
|
92
|
+
expect(
|
|
93
|
+
screen.getByRole("treeitem", { name: "Veg" }),
|
|
94
|
+
).not.toHaveAttribute("data-terminal");
|
|
95
|
+
});
|
|
96
|
+
|
|
49
97
|
it("flags branch items with data-has-children and omits it on leaves", () => {
|
|
50
98
|
render(<Tree />);
|
|
51
99
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
|
|
4
|
+
import { DirectionProvider } from "../../DirectionProvider/index.ts";
|
|
4
5
|
import { MillerColumns } from "../MillerColumns";
|
|
5
6
|
import { verticalArrowCases } from "./MillerColumns.fixtures";
|
|
6
7
|
|
|
@@ -16,9 +17,9 @@ function FlatList() {
|
|
|
16
17
|
);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
function Tree() {
|
|
20
|
+
function Tree({ dir }: { dir?: "ltr" | "rtl" } = {}) {
|
|
20
21
|
return (
|
|
21
|
-
<MillerColumns.Root>
|
|
22
|
+
<MillerColumns.Root dir={dir}>
|
|
22
23
|
<MillerColumns.Column>
|
|
23
24
|
<MillerColumns.Item value="fruit">
|
|
24
25
|
Fruit
|
|
@@ -178,4 +179,36 @@ describe("MillerColumns — keyboard interaction", () => {
|
|
|
178
179
|
expect(veg).toHaveFocus();
|
|
179
180
|
});
|
|
180
181
|
});
|
|
182
|
+
|
|
183
|
+
describe("reading direction", () => {
|
|
184
|
+
it("swaps the horizontal arrow pair under rtl", async () => {
|
|
185
|
+
const user = userEvent.setup();
|
|
186
|
+
|
|
187
|
+
render(<Tree dir="rtl" />);
|
|
188
|
+
screen.getByRole("treeitem", { name: "Fruit" }).focus();
|
|
189
|
+
|
|
190
|
+
// The child column sits to the LEFT in rtl, so ArrowLeft steps in.
|
|
191
|
+
await user.keyboard("{ArrowLeft}");
|
|
192
|
+
expect(screen.getByRole("treeitem", { name: "Apple" })).toHaveFocus();
|
|
193
|
+
|
|
194
|
+
// ...and ArrowRight walks back out to the parent.
|
|
195
|
+
await user.keyboard("{ArrowRight}");
|
|
196
|
+
expect(screen.getByRole("treeitem", { name: "Fruit" })).toHaveFocus();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("inherits the direction from a DirectionProvider", async () => {
|
|
200
|
+
const user = userEvent.setup();
|
|
201
|
+
|
|
202
|
+
render(
|
|
203
|
+
<DirectionProvider dir="rtl">
|
|
204
|
+
<Tree />
|
|
205
|
+
</DirectionProvider>,
|
|
206
|
+
);
|
|
207
|
+
screen.getByRole("treeitem", { name: "Fruit" }).focus();
|
|
208
|
+
|
|
209
|
+
await user.keyboard("{ArrowLeft}");
|
|
210
|
+
|
|
211
|
+
expect(screen.getByRole("treeitem", { name: "Apple" })).toHaveFocus();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
181
214
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
|
|
4
|
+
import { MillerColumns } from "../MillerColumns";
|
|
5
|
+
|
|
6
|
+
function Tree() {
|
|
7
|
+
return (
|
|
8
|
+
<MillerColumns.Root>
|
|
9
|
+
<MillerColumns.Column>
|
|
10
|
+
<MillerColumns.Item value="documents">
|
|
11
|
+
Documents
|
|
12
|
+
<MillerColumns.Column>
|
|
13
|
+
<MillerColumns.Item value="work">Work</MillerColumns.Item>
|
|
14
|
+
</MillerColumns.Column>
|
|
15
|
+
</MillerColumns.Item>
|
|
16
|
+
<MillerColumns.Item value="pictures">
|
|
17
|
+
Pictures
|
|
18
|
+
<MillerColumns.Column>
|
|
19
|
+
<MillerColumns.Item value="holiday">
|
|
20
|
+
Holiday
|
|
21
|
+
<MillerColumns.Column>
|
|
22
|
+
<MillerColumns.Item value="beach">beach.jpg</MillerColumns.Item>
|
|
23
|
+
<MillerColumns.Item value="sunset">sunset.jpg</MillerColumns.Item>
|
|
24
|
+
</MillerColumns.Column>
|
|
25
|
+
</MillerColumns.Item>
|
|
26
|
+
</MillerColumns.Column>
|
|
27
|
+
</MillerColumns.Item>
|
|
28
|
+
</MillerColumns.Column>
|
|
29
|
+
</MillerColumns.Root>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const state = (name: string) =>
|
|
34
|
+
screen.getByRole("treeitem", { name }).getAttribute("data-state");
|
|
35
|
+
const terminal = (name: string) =>
|
|
36
|
+
screen.getByRole("treeitem", { name }).hasAttribute("data-terminal");
|
|
37
|
+
|
|
38
|
+
describe("MillerColumns — selection state under keyboard navigation", () => {
|
|
39
|
+
it("deselects the previous branch when arrowing to a sibling and stepping in", async () => {
|
|
40
|
+
const user = userEvent.setup();
|
|
41
|
+
|
|
42
|
+
render(<Tree />);
|
|
43
|
+
|
|
44
|
+
await user.click(screen.getByRole("treeitem", { name: "Documents" }));
|
|
45
|
+
expect(state("Documents")).toBe("selected");
|
|
46
|
+
|
|
47
|
+
// Down to Pictures, then right to open it — Documents must let go.
|
|
48
|
+
await user.keyboard("{ArrowDown}{ArrowRight}");
|
|
49
|
+
|
|
50
|
+
expect(state("Pictures")).toBe("selected");
|
|
51
|
+
expect(state("Documents")).toBe("unselected");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps exactly one terminal item as focus moves deeper", async () => {
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
|
|
57
|
+
render(<Tree />);
|
|
58
|
+
|
|
59
|
+
await user.click(screen.getByRole("treeitem", { name: "Pictures" }));
|
|
60
|
+
await user.click(screen.getByRole("treeitem", { name: "Holiday" }));
|
|
61
|
+
|
|
62
|
+
expect(state("Pictures")).toBe("selected");
|
|
63
|
+
expect(terminal("Pictures")).toBe(false);
|
|
64
|
+
expect(terminal("Holiday")).toBe(true);
|
|
65
|
+
|
|
66
|
+
// Arrowing within the revealed column moves focus only — it must not
|
|
67
|
+
// change which row is terminal.
|
|
68
|
+
await user.keyboard("{ArrowRight}{ArrowDown}");
|
|
69
|
+
|
|
70
|
+
expect(screen.getByRole("treeitem", { name: "sunset.jpg" })).toHaveFocus();
|
|
71
|
+
expect(terminal("Holiday")).toBe(true);
|
|
72
|
+
expect(terminal("sunset.jpg")).toBe(false);
|
|
73
|
+
expect(state("Documents")).toBe("unselected");
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("MillerColumns — a selected leaf deep in the path", () => {
|
|
78
|
+
it("marks the leaf terminal and its ancestors selected", async () => {
|
|
79
|
+
const user = userEvent.setup();
|
|
80
|
+
|
|
81
|
+
render(<Tree />);
|
|
82
|
+
|
|
83
|
+
await user.click(screen.getByRole("treeitem", { name: "Pictures" }));
|
|
84
|
+
await user.click(screen.getByRole("treeitem", { name: "Holiday" }));
|
|
85
|
+
await user.click(screen.getByRole("treeitem", { name: "beach.jpg" }));
|
|
86
|
+
|
|
87
|
+
expect(state("Pictures")).toBe("selected");
|
|
88
|
+
expect(terminal("Pictures")).toBe(false);
|
|
89
|
+
expect(state("Holiday")).toBe("selected");
|
|
90
|
+
expect(terminal("Holiday")).toBe(false);
|
|
91
|
+
|
|
92
|
+
// The row the user actually landed on.
|
|
93
|
+
expect(state("beach.jpg")).toBe("selected");
|
|
94
|
+
expect(terminal("beach.jpg")).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
2
3
|
|
|
3
4
|
import { MillerColumns } from "../MillerColumns";
|
|
4
5
|
|
|
5
6
|
describe("MillerColumns — preview panel", () => {
|
|
6
7
|
it("renders a panel at the trailing edge of the strip", () => {
|
|
7
|
-
render(
|
|
8
|
-
<MillerColumns.Root>
|
|
8
|
+
const { container } = render(
|
|
9
|
+
<MillerColumns.Root defaultValue={["a"]}>
|
|
9
10
|
<MillerColumns.Column>
|
|
10
11
|
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
11
12
|
</MillerColumns.Column>
|
|
@@ -13,7 +14,7 @@ describe("MillerColumns — preview panel", () => {
|
|
|
13
14
|
</MillerColumns.Root>,
|
|
14
15
|
);
|
|
15
16
|
|
|
16
|
-
const strip =
|
|
17
|
+
const strip = container.querySelector("[data-miller-columns-strip]")!;
|
|
17
18
|
const panel = screen.getByText("preview");
|
|
18
19
|
|
|
19
20
|
expect(panel).toHaveAttribute("data-miller-columns-preview");
|
|
@@ -21,9 +22,33 @@ describe("MillerColumns — preview panel", () => {
|
|
|
21
22
|
expect(strip.lastElementChild).toBe(panel);
|
|
22
23
|
});
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
// Only reachable under forceMount: without it the panel does not exist while
|
|
26
|
+
// nothing is selected, so there is nothing to flag.
|
|
27
|
+
it("flags a force-mounted panel as empty until something is selected", async () => {
|
|
28
|
+
const user = userEvent.setup();
|
|
29
|
+
|
|
25
30
|
render(
|
|
26
31
|
<MillerColumns.Root>
|
|
32
|
+
<MillerColumns.Column>
|
|
33
|
+
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
34
|
+
</MillerColumns.Column>
|
|
35
|
+
<MillerColumns.PreviewPanel forceMount>
|
|
36
|
+
preview
|
|
37
|
+
</MillerColumns.PreviewPanel>
|
|
38
|
+
</MillerColumns.Root>,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const panel = screen.getByText("preview");
|
|
42
|
+
expect(panel).toHaveAttribute("data-empty");
|
|
43
|
+
|
|
44
|
+
await user.click(screen.getByRole("treeitem", { name: "A" }));
|
|
45
|
+
|
|
46
|
+
expect(panel).not.toHaveAttribute("data-empty");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("forwards props to the panel element", () => {
|
|
50
|
+
render(
|
|
51
|
+
<MillerColumns.Root defaultValue={["a"]}>
|
|
27
52
|
<MillerColumns.Column>
|
|
28
53
|
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
29
54
|
</MillerColumns.Column>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
|
|
4
|
+
import { MillerColumns } from "../MillerColumns";
|
|
5
|
+
|
|
6
|
+
function Tree({ forceMount }: { forceMount?: boolean } = {}) {
|
|
7
|
+
return (
|
|
8
|
+
<MillerColumns.Root>
|
|
9
|
+
<MillerColumns.Column>
|
|
10
|
+
<MillerColumns.Item value="fruit">
|
|
11
|
+
Fruit
|
|
12
|
+
<MillerColumns.Column>
|
|
13
|
+
<MillerColumns.Item value="apple">Apple</MillerColumns.Item>
|
|
14
|
+
</MillerColumns.Column>
|
|
15
|
+
</MillerColumns.Item>
|
|
16
|
+
<MillerColumns.Item value="veg">Veg</MillerColumns.Item>
|
|
17
|
+
</MillerColumns.Column>
|
|
18
|
+
<MillerColumns.PreviewPanel forceMount={forceMount}>
|
|
19
|
+
preview
|
|
20
|
+
</MillerColumns.PreviewPanel>
|
|
21
|
+
</MillerColumns.Root>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Finder's rule: the preview occupies the slot *after* the selected item, so it
|
|
27
|
+
* exists only when that item has no children of its own. Select a folder and
|
|
28
|
+
* you get its contents there instead; select nothing and there is no preview
|
|
29
|
+
* at all.
|
|
30
|
+
*/
|
|
31
|
+
describe("MillerColumns — preview panel visibility", () => {
|
|
32
|
+
it("renders no preview until something is selected", () => {
|
|
33
|
+
render(<Tree />);
|
|
34
|
+
|
|
35
|
+
expect(screen.queryByText("preview")).not.toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("renders no preview while a branch is selected", async () => {
|
|
39
|
+
const user = userEvent.setup();
|
|
40
|
+
|
|
41
|
+
render(<Tree />);
|
|
42
|
+
await user.click(screen.getByRole("treeitem", { name: "Fruit" }));
|
|
43
|
+
|
|
44
|
+
// The slot after Fruit belongs to its children, not to a preview.
|
|
45
|
+
expect(screen.getByRole("treeitem", { name: "Apple" })).toBeInTheDocument();
|
|
46
|
+
expect(screen.queryByText("preview")).not.toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("renders the preview once a leaf is selected", async () => {
|
|
50
|
+
const user = userEvent.setup();
|
|
51
|
+
|
|
52
|
+
render(<Tree />);
|
|
53
|
+
await user.click(screen.getByRole("treeitem", { name: "Veg" }));
|
|
54
|
+
|
|
55
|
+
expect(screen.getByText("preview")).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("renders the preview for a leaf nested inside a branch", async () => {
|
|
59
|
+
const user = userEvent.setup();
|
|
60
|
+
|
|
61
|
+
render(<Tree />);
|
|
62
|
+
await user.click(screen.getByRole("treeitem", { name: "Fruit" }));
|
|
63
|
+
await user.click(screen.getByRole("treeitem", { name: "Apple" }));
|
|
64
|
+
|
|
65
|
+
expect(screen.getByText("preview")).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("keeps the panel mounted throughout when forceMount is set", async () => {
|
|
69
|
+
const user = userEvent.setup();
|
|
70
|
+
|
|
71
|
+
render(<Tree forceMount />);
|
|
72
|
+
|
|
73
|
+
expect(screen.getByText("preview")).toBeInTheDocument();
|
|
74
|
+
|
|
75
|
+
await user.click(screen.getByRole("treeitem", { name: "Fruit" }));
|
|
76
|
+
|
|
77
|
+
expect(screen.getByText("preview")).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
2
3
|
|
|
3
4
|
import { MillerColumns } from "../MillerColumns";
|
|
4
5
|
|
|
6
|
+
function Resizable({
|
|
7
|
+
dir,
|
|
8
|
+
...handleProps
|
|
9
|
+
}: {
|
|
10
|
+
dir?: "ltr" | "rtl";
|
|
11
|
+
minWidth?: number;
|
|
12
|
+
maxWidth?: number;
|
|
13
|
+
step?: number;
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<MillerColumns.Root dir={dir}>
|
|
17
|
+
<MillerColumns.Column>
|
|
18
|
+
<MillerColumns.ResizeHandle aria-label="Resize" {...handleProps} />
|
|
19
|
+
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
20
|
+
</MillerColumns.Column>
|
|
21
|
+
</MillerColumns.Root>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
describe("MillerColumns — resize handle", () => {
|
|
6
26
|
it("renders a vertical separator inside a column", () => {
|
|
7
27
|
render(
|
|
@@ -19,6 +39,175 @@ describe("MillerColumns — resize handle", () => {
|
|
|
19
39
|
expect(handle).toHaveAttribute("data-miller-columns-resize-handle");
|
|
20
40
|
});
|
|
21
41
|
|
|
42
|
+
it("is a focusable splitter carrying its value range", () => {
|
|
43
|
+
render(
|
|
44
|
+
<MillerColumns.Root>
|
|
45
|
+
<MillerColumns.Column>
|
|
46
|
+
<MillerColumns.ResizeHandle
|
|
47
|
+
aria-label="Resize"
|
|
48
|
+
minWidth={120}
|
|
49
|
+
maxWidth={400}
|
|
50
|
+
/>
|
|
51
|
+
<MillerColumns.Item value="a">A</MillerColumns.Item>
|
|
52
|
+
</MillerColumns.Column>
|
|
53
|
+
</MillerColumns.Root>,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const handle = screen.getByRole("separator", { name: "Resize" });
|
|
57
|
+
|
|
58
|
+
expect(handle).toHaveAttribute("tabindex", "0");
|
|
59
|
+
expect(handle).toHaveAttribute("aria-valuemin", "120");
|
|
60
|
+
expect(handle).toHaveAttribute("aria-valuemax", "400");
|
|
61
|
+
expect(handle).toHaveAttribute("aria-valuenow");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("steps the column width with the arrow keys", async () => {
|
|
65
|
+
const user = userEvent.setup();
|
|
66
|
+
|
|
67
|
+
render(<Resizable minWidth={100} maxWidth={400} />);
|
|
68
|
+
|
|
69
|
+
const handle = screen.getByRole("separator");
|
|
70
|
+
const column = screen.getByRole("group");
|
|
71
|
+
|
|
72
|
+
// Seed a known width so stepping is measured from something.
|
|
73
|
+
fireEvent.pointerDown(handle, { clientX: 0 });
|
|
74
|
+
fireEvent.pointerMove(window, { clientX: 200 });
|
|
75
|
+
fireEvent.pointerUp(window);
|
|
76
|
+
|
|
77
|
+
handle.focus();
|
|
78
|
+
await user.keyboard("{ArrowRight}");
|
|
79
|
+
expect(column).toHaveStyle({ width: "210px" });
|
|
80
|
+
|
|
81
|
+
await user.keyboard("{ArrowLeft}{ArrowLeft}");
|
|
82
|
+
expect(column).toHaveStyle({ width: "190px" });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("honours a custom step", async () => {
|
|
86
|
+
const user = userEvent.setup();
|
|
87
|
+
|
|
88
|
+
render(<Resizable minWidth={100} maxWidth={400} step={25} />);
|
|
89
|
+
|
|
90
|
+
const handle = screen.getByRole("separator");
|
|
91
|
+
const column = screen.getByRole("group");
|
|
92
|
+
|
|
93
|
+
fireEvent.pointerDown(handle, { clientX: 0 });
|
|
94
|
+
fireEvent.pointerMove(window, { clientX: 200 });
|
|
95
|
+
fireEvent.pointerUp(window);
|
|
96
|
+
|
|
97
|
+
handle.focus();
|
|
98
|
+
await user.keyboard("{ArrowRight}");
|
|
99
|
+
|
|
100
|
+
expect(column).toHaveStyle({ width: "225px" });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("clamps stepping to the min and max width", async () => {
|
|
104
|
+
const user = userEvent.setup();
|
|
105
|
+
|
|
106
|
+
render(<Resizable minWidth={100} maxWidth={220} />);
|
|
107
|
+
|
|
108
|
+
const handle = screen.getByRole("separator");
|
|
109
|
+
const column = screen.getByRole("group");
|
|
110
|
+
|
|
111
|
+
fireEvent.pointerDown(handle, { clientX: 0 });
|
|
112
|
+
fireEvent.pointerMove(window, { clientX: 215 });
|
|
113
|
+
fireEvent.pointerUp(window);
|
|
114
|
+
|
|
115
|
+
handle.focus();
|
|
116
|
+
await user.keyboard("{ArrowRight}{ArrowRight}");
|
|
117
|
+
|
|
118
|
+
expect(column).toHaveStyle({ width: "220px" });
|
|
119
|
+
expect(handle).toHaveAttribute("aria-valuenow", "220");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("jumps to the min and max width with Home and End", async () => {
|
|
123
|
+
const user = userEvent.setup();
|
|
124
|
+
|
|
125
|
+
render(<Resizable minWidth={100} maxWidth={400} />);
|
|
126
|
+
|
|
127
|
+
const handle = screen.getByRole("separator");
|
|
128
|
+
const column = screen.getByRole("group");
|
|
129
|
+
|
|
130
|
+
handle.focus();
|
|
131
|
+
await user.keyboard("{End}");
|
|
132
|
+
expect(column).toHaveStyle({ width: "400px" });
|
|
133
|
+
|
|
134
|
+
await user.keyboard("{Home}");
|
|
135
|
+
expect(column).toHaveStyle({ width: "100px" });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("mirrors the stepping arrows under rtl", async () => {
|
|
139
|
+
const user = userEvent.setup();
|
|
140
|
+
|
|
141
|
+
render(<Resizable dir="rtl" minWidth={100} maxWidth={400} />);
|
|
142
|
+
|
|
143
|
+
const handle = screen.getByRole("separator");
|
|
144
|
+
const column = screen.getByRole("group");
|
|
145
|
+
|
|
146
|
+
fireEvent.pointerDown(handle, { clientX: 0 });
|
|
147
|
+
fireEvent.pointerMove(window, { clientX: 200 });
|
|
148
|
+
fireEvent.pointerUp(window);
|
|
149
|
+
|
|
150
|
+
handle.focus();
|
|
151
|
+
// The column grows leftwards in rtl, so ArrowLeft widens it.
|
|
152
|
+
await user.keyboard("{ArrowLeft}");
|
|
153
|
+
|
|
154
|
+
expect(column).toHaveStyle({ width: "210px" });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("leaves the width alone for keys it does not handle", async () => {
|
|
158
|
+
const user = userEvent.setup();
|
|
159
|
+
|
|
160
|
+
render(<Resizable minWidth={100} maxWidth={400} />);
|
|
161
|
+
|
|
162
|
+
const handle = screen.getByRole("separator");
|
|
163
|
+
const column = screen.getByRole("group");
|
|
164
|
+
|
|
165
|
+
fireEvent.pointerDown(handle, { clientX: 0 });
|
|
166
|
+
fireEvent.pointerMove(window, { clientX: 200 });
|
|
167
|
+
fireEvent.pointerUp(window);
|
|
168
|
+
|
|
169
|
+
handle.focus();
|
|
170
|
+
await user.keyboard("{ArrowUp}{ArrowDown}a");
|
|
171
|
+
|
|
172
|
+
expect(column).toHaveStyle({ width: "200px" });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("ignores End when no maxWidth bounds the column", async () => {
|
|
176
|
+
const user = userEvent.setup();
|
|
177
|
+
|
|
178
|
+
// Without a maxWidth there is no largest width to jump to, so End
|
|
179
|
+
// must not resize to Infinity.
|
|
180
|
+
render(<Resizable minWidth={100} />);
|
|
181
|
+
|
|
182
|
+
const handle = screen.getByRole("separator");
|
|
183
|
+
const column = screen.getByRole("group");
|
|
184
|
+
|
|
185
|
+
fireEvent.pointerDown(handle, { clientX: 0 });
|
|
186
|
+
fireEvent.pointerMove(window, { clientX: 200 });
|
|
187
|
+
fireEvent.pointerUp(window);
|
|
188
|
+
|
|
189
|
+
handle.focus();
|
|
190
|
+
await user.keyboard("{End}");
|
|
191
|
+
|
|
192
|
+
expect(handle).toHaveAttribute("aria-valuenow", "200");
|
|
193
|
+
expect(column).toHaveStyle({ width: "200px" });
|
|
194
|
+
expect(handle).not.toHaveAttribute("aria-valuemax");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("clamps a pointer drag to the min and max width", () => {
|
|
198
|
+
render(<Resizable minWidth={120} maxWidth={260} />);
|
|
199
|
+
|
|
200
|
+
const handle = screen.getByRole("separator");
|
|
201
|
+
const column = screen.getByRole("group");
|
|
202
|
+
|
|
203
|
+
fireEvent.pointerDown(handle, { clientX: 0 });
|
|
204
|
+
fireEvent.pointerMove(window, { clientX: 1000 });
|
|
205
|
+
expect(column).toHaveStyle({ width: "260px" });
|
|
206
|
+
|
|
207
|
+
fireEvent.pointerMove(window, { clientX: -1000 });
|
|
208
|
+
expect(column).toHaveStyle({ width: "120px" });
|
|
209
|
+
});
|
|
210
|
+
|
|
22
211
|
it("throws when ResizeHandle is rendered outside Column", () => {
|
|
23
212
|
expect(() =>
|
|
24
213
|
render(
|