@multiplatform.one/components 7.28.1 → 7.29.1
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/dist/cjs/Accordion/index.cjs +26 -17
- package/dist/cjs/Accordion/index.native.js +36 -27
- package/dist/cjs/Accordion/index.native.js.map +1 -1
- package/dist/cjs/Heading.cjs +74 -30
- package/dist/cjs/Heading.native.js +81 -30
- package/dist/cjs/Heading.native.js.map +1 -1
- package/dist/cjs/ProgressSteps/index.native.js.map +1 -1
- package/dist/cjs/ScrollView/index.native.js +4 -0
- package/dist/cjs/ScrollView/index.native.js.map +1 -1
- package/dist/cjs/SortableList/SortableHandle.cjs +1 -1
- package/dist/cjs/SortableList/SortableHandle.native.js +1 -1
- package/dist/cjs/Text.cjs +58 -21
- package/dist/cjs/Text.native.js +62 -21
- package/dist/cjs/Text.native.js.map +1 -1
- package/dist/cjs/Toast/index.native.js.map +1 -1
- package/dist/cjs/charts/ChartDataView.cjs +2 -1
- package/dist/cjs/charts/ChartDataView.native.js +2 -1
- package/dist/cjs/charts/ChartDataView.native.js.map +1 -1
- package/dist/cjs/code/CodeBlock/index.cjs +8 -8
- package/dist/cjs/code/CodeBlock/index.native.js +8 -8
- package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.native.js +1 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/layouts/EmptyState.cjs +4 -3
- package/dist/cjs/layouts/EmptyState.native.js +14 -9
- package/dist/cjs/layouts/EmptyState.native.js.map +1 -1
- package/dist/cjs/layouts/page.cjs +2 -1
- package/dist/cjs/layouts/page.native.js +2 -1
- package/dist/cjs/layouts/page.native.js.map +1 -1
- package/dist/cjs/layouts/sidebar.cjs +2 -1
- package/dist/cjs/layouts/sidebar.native.js +2 -1
- package/dist/cjs/layouts/sidebar.native.js.map +1 -1
- package/dist/cjs/layouts/tags.cjs +2 -1
- package/dist/cjs/layouts/tags.native.js +2 -1
- package/dist/cjs/layouts/tags.native.js.map +1 -1
- package/dist/cjs/layouts/toolbar.cjs +2 -1
- package/dist/cjs/layouts/toolbar.native.js +2 -1
- package/dist/cjs/layouts/toolbar.native.js.map +1 -1
- package/dist/cjs/shared/useReturnFocusOnClose.cjs +1 -0
- package/dist/cjs/shared/useReturnFocusOnClose.native.js +1 -0
- package/dist/cjs/shared/useReturnFocusOnClose.native.js.map +1 -1
- package/dist/cjs/surfaces.cjs +61 -0
- package/dist/cjs/surfaces.native.js +73 -0
- package/dist/cjs/surfaces.native.js.map +1 -1
- package/dist/cjs/views/Calendar.cjs +13 -10
- package/dist/cjs/views/Calendar.native.js +13 -10
- package/dist/cjs/views/Calendar.native.js.map +1 -1
- package/dist/cjs/views/Dashboard.cjs +2 -1
- package/dist/cjs/views/Dashboard.native.js +2 -1
- package/dist/cjs/views/Dashboard.native.js.map +1 -1
- package/dist/cjs/views/Gantt.cjs +39 -27
- package/dist/cjs/views/Gantt.native.js +38 -23
- package/dist/cjs/views/Gantt.native.js.map +1 -1
- package/dist/cjs/views/Kanban.cjs +2 -1
- package/dist/cjs/views/Kanban.native.js +1 -0
- package/dist/cjs/views/Kanban.native.js.map +1 -1
- package/dist/cjs/views/List.cjs +1 -0
- package/dist/cjs/views/List.native.js +1 -0
- package/dist/cjs/views/List.native.js.map +1 -1
- package/dist/cjs/views/Matrix.cjs +5 -3
- package/dist/cjs/views/Matrix.native.js +5 -3
- package/dist/cjs/views/Matrix.native.js.map +1 -1
- package/dist/cjs/views/ReportBuilder.cjs +2 -1
- package/dist/cjs/views/ReportBuilder.native.js +2 -1
- package/dist/cjs/views/ReportBuilder.native.js.map +1 -1
- package/dist/cjs/views/TreeView.cjs +2 -1
- package/dist/cjs/views/TreeView.native.js +2 -1
- package/dist/cjs/views/TreeView.native.js.map +1 -1
- package/dist/esm/Accordion/index.mjs +26 -18
- package/dist/esm/Accordion/index.mjs.map +1 -1
- package/dist/esm/Accordion/index.native.js +36 -28
- package/dist/esm/Accordion/index.native.js.map +1 -1
- package/dist/esm/Heading.mjs +74 -30
- package/dist/esm/Heading.mjs.map +1 -1
- package/dist/esm/Heading.native.js +81 -30
- package/dist/esm/Heading.native.js.map +1 -1
- package/dist/esm/ProgressSteps/index.mjs.map +1 -1
- package/dist/esm/ProgressSteps/index.native.js.map +1 -1
- package/dist/esm/ScrollView/index.native.js +4 -0
- package/dist/esm/ScrollView/index.native.js.map +1 -1
- package/dist/esm/SortableList/SortableHandle.mjs +1 -1
- package/dist/esm/SortableList/SortableHandle.native.js +1 -1
- package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
- package/dist/esm/Text.mjs +58 -21
- package/dist/esm/Text.mjs.map +1 -1
- package/dist/esm/Text.native.js +62 -21
- package/dist/esm/Text.native.js.map +1 -1
- package/dist/esm/Toast/index.mjs.map +1 -1
- package/dist/esm/Toast/index.native.js.map +1 -1
- package/dist/esm/charts/ChartDataView.mjs +2 -1
- package/dist/esm/charts/ChartDataView.mjs.map +1 -1
- package/dist/esm/charts/ChartDataView.native.js +2 -1
- package/dist/esm/charts/ChartDataView.native.js.map +1 -1
- package/dist/esm/code/CodeBlock/index.mjs +1 -1
- package/dist/esm/code/CodeBlock/index.native.js +1 -1
- package/dist/esm/index.mjs +2 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -2
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/layouts/EmptyState.mjs +4 -3
- package/dist/esm/layouts/EmptyState.mjs.map +1 -1
- package/dist/esm/layouts/EmptyState.native.js +14 -9
- package/dist/esm/layouts/EmptyState.native.js.map +1 -1
- package/dist/esm/layouts/page.mjs +2 -1
- package/dist/esm/layouts/page.mjs.map +1 -1
- package/dist/esm/layouts/page.native.js +2 -1
- package/dist/esm/layouts/page.native.js.map +1 -1
- package/dist/esm/layouts/sidebar.mjs +2 -1
- package/dist/esm/layouts/sidebar.mjs.map +1 -1
- package/dist/esm/layouts/sidebar.native.js +2 -1
- package/dist/esm/layouts/sidebar.native.js.map +1 -1
- package/dist/esm/layouts/tags.mjs +2 -1
- package/dist/esm/layouts/tags.mjs.map +1 -1
- package/dist/esm/layouts/tags.native.js +2 -1
- package/dist/esm/layouts/tags.native.js.map +1 -1
- package/dist/esm/layouts/toolbar.mjs +2 -1
- package/dist/esm/layouts/toolbar.mjs.map +1 -1
- package/dist/esm/layouts/toolbar.native.js +2 -1
- package/dist/esm/layouts/toolbar.native.js.map +1 -1
- package/dist/esm/shared/useReturnFocusOnClose.mjs +1 -0
- package/dist/esm/shared/useReturnFocusOnClose.mjs.map +1 -1
- package/dist/esm/shared/useReturnFocusOnClose.native.js +1 -0
- package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
- package/dist/esm/surfaces.mjs +63 -3
- package/dist/esm/surfaces.mjs.map +1 -1
- package/dist/esm/surfaces.native.js +75 -3
- package/dist/esm/surfaces.native.js.map +1 -1
- package/dist/esm/views/Calendar.mjs +12 -9
- package/dist/esm/views/Calendar.mjs.map +1 -1
- package/dist/esm/views/Calendar.native.js +12 -9
- package/dist/esm/views/Calendar.native.js.map +1 -1
- package/dist/esm/views/Dashboard.mjs +2 -1
- package/dist/esm/views/Dashboard.mjs.map +1 -1
- package/dist/esm/views/Dashboard.native.js +2 -1
- package/dist/esm/views/Dashboard.native.js.map +1 -1
- package/dist/esm/views/Gantt.mjs +37 -25
- package/dist/esm/views/Gantt.mjs.map +1 -1
- package/dist/esm/views/Gantt.native.js +36 -21
- package/dist/esm/views/Gantt.native.js.map +1 -1
- package/dist/esm/views/Kanban.mjs +2 -1
- package/dist/esm/views/Kanban.mjs.map +1 -1
- package/dist/esm/views/Kanban.native.js +1 -0
- package/dist/esm/views/Kanban.native.js.map +1 -1
- package/dist/esm/views/List.mjs +1 -0
- package/dist/esm/views/List.mjs.map +1 -1
- package/dist/esm/views/List.native.js +1 -0
- package/dist/esm/views/List.native.js.map +1 -1
- package/dist/esm/views/Matrix.mjs +3 -1
- package/dist/esm/views/Matrix.mjs.map +1 -1
- package/dist/esm/views/Matrix.native.js +3 -1
- package/dist/esm/views/Matrix.native.js.map +1 -1
- package/dist/esm/views/ReportBuilder.mjs +2 -1
- package/dist/esm/views/ReportBuilder.mjs.map +1 -1
- package/dist/esm/views/ReportBuilder.native.js +2 -1
- package/dist/esm/views/ReportBuilder.native.js.map +1 -1
- package/dist/esm/views/TreeView.mjs +2 -1
- package/dist/esm/views/TreeView.mjs.map +1 -1
- package/dist/esm/views/TreeView.native.js +2 -1
- package/dist/esm/views/TreeView.native.js.map +1 -1
- package/dist/jsx/Accordion/index.mjs +26 -18
- package/dist/jsx/Accordion/index.mjs.map +1 -1
- package/dist/jsx/Accordion/index.native.js +36 -27
- package/dist/jsx/Accordion/index.native.js.map +1 -1
- package/dist/jsx/Heading.mjs +74 -30
- package/dist/jsx/Heading.mjs.map +1 -1
- package/dist/jsx/Heading.native.js +81 -30
- package/dist/jsx/Heading.native.js.map +1 -1
- package/dist/jsx/ProgressSteps/index.mjs.map +1 -1
- package/dist/jsx/ProgressSteps/index.native.js.map +1 -1
- package/dist/jsx/ScrollView/index.native.js +4 -0
- package/dist/jsx/ScrollView/index.native.js.map +1 -1
- package/dist/jsx/SortableList/SortableHandle.mjs +1 -1
- package/dist/jsx/SortableList/SortableHandle.native.js +1 -1
- package/dist/jsx/Text.mjs +58 -21
- package/dist/jsx/Text.mjs.map +1 -1
- package/dist/jsx/Text.native.js +62 -21
- package/dist/jsx/Text.native.js.map +1 -1
- package/dist/jsx/Toast/index.mjs.map +1 -1
- package/dist/jsx/Toast/index.native.js.map +1 -1
- package/dist/jsx/charts/ChartDataView.mjs +2 -1
- package/dist/jsx/charts/ChartDataView.mjs.map +1 -1
- package/dist/jsx/charts/ChartDataView.native.js +2 -1
- package/dist/jsx/charts/ChartDataView.native.js.map +1 -1
- package/dist/jsx/code/CodeBlock/index.mjs +1 -1
- package/dist/jsx/code/CodeBlock/index.native.js +8 -8
- package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
- package/dist/jsx/index.js +2 -2
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +2 -2
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +1 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/layouts/EmptyState.mjs +4 -3
- package/dist/jsx/layouts/EmptyState.mjs.map +1 -1
- package/dist/jsx/layouts/EmptyState.native.js +14 -9
- package/dist/jsx/layouts/EmptyState.native.js.map +1 -1
- package/dist/jsx/layouts/page.mjs +2 -1
- package/dist/jsx/layouts/page.mjs.map +1 -1
- package/dist/jsx/layouts/page.native.js +2 -1
- package/dist/jsx/layouts/page.native.js.map +1 -1
- package/dist/jsx/layouts/sidebar.mjs +2 -1
- package/dist/jsx/layouts/sidebar.mjs.map +1 -1
- package/dist/jsx/layouts/sidebar.native.js +2 -1
- package/dist/jsx/layouts/sidebar.native.js.map +1 -1
- package/dist/jsx/layouts/tags.mjs +2 -1
- package/dist/jsx/layouts/tags.mjs.map +1 -1
- package/dist/jsx/layouts/tags.native.js +2 -1
- package/dist/jsx/layouts/tags.native.js.map +1 -1
- package/dist/jsx/layouts/toolbar.mjs +2 -1
- package/dist/jsx/layouts/toolbar.mjs.map +1 -1
- package/dist/jsx/layouts/toolbar.native.js +2 -1
- package/dist/jsx/layouts/toolbar.native.js.map +1 -1
- package/dist/jsx/shared/useReturnFocusOnClose.mjs +1 -0
- package/dist/jsx/shared/useReturnFocusOnClose.mjs.map +1 -1
- package/dist/jsx/shared/useReturnFocusOnClose.native.js +1 -0
- package/dist/jsx/shared/useReturnFocusOnClose.native.js.map +1 -1
- package/dist/jsx/surfaces.mjs +63 -3
- package/dist/jsx/surfaces.mjs.map +1 -1
- package/dist/jsx/surfaces.native.js +73 -0
- package/dist/jsx/surfaces.native.js.map +1 -1
- package/dist/jsx/views/Calendar.mjs +12 -9
- package/dist/jsx/views/Calendar.mjs.map +1 -1
- package/dist/jsx/views/Calendar.native.js +13 -10
- package/dist/jsx/views/Calendar.native.js.map +1 -1
- package/dist/jsx/views/Dashboard.mjs +2 -1
- package/dist/jsx/views/Dashboard.mjs.map +1 -1
- package/dist/jsx/views/Dashboard.native.js +2 -1
- package/dist/jsx/views/Dashboard.native.js.map +1 -1
- package/dist/jsx/views/Gantt.mjs +37 -25
- package/dist/jsx/views/Gantt.mjs.map +1 -1
- package/dist/jsx/views/Gantt.native.js +38 -23
- package/dist/jsx/views/Gantt.native.js.map +1 -1
- package/dist/jsx/views/Kanban.mjs +2 -1
- package/dist/jsx/views/Kanban.mjs.map +1 -1
- package/dist/jsx/views/Kanban.native.js +1 -0
- package/dist/jsx/views/Kanban.native.js.map +1 -1
- package/dist/jsx/views/List.mjs +1 -0
- package/dist/jsx/views/List.mjs.map +1 -1
- package/dist/jsx/views/List.native.js +1 -0
- package/dist/jsx/views/List.native.js.map +1 -1
- package/dist/jsx/views/Matrix.mjs +3 -1
- package/dist/jsx/views/Matrix.mjs.map +1 -1
- package/dist/jsx/views/Matrix.native.js +5 -3
- package/dist/jsx/views/Matrix.native.js.map +1 -1
- package/dist/jsx/views/ReportBuilder.mjs +2 -1
- package/dist/jsx/views/ReportBuilder.mjs.map +1 -1
- package/dist/jsx/views/ReportBuilder.native.js +2 -1
- package/dist/jsx/views/ReportBuilder.native.js.map +1 -1
- package/dist/jsx/views/TreeView.mjs +2 -1
- package/dist/jsx/views/TreeView.mjs.map +1 -1
- package/dist/jsx/views/TreeView.native.js +2 -1
- package/dist/jsx/views/TreeView.native.js.map +1 -1
- package/package.json +9 -9
- package/src/Accordion/index.tsx +34 -15
- package/src/Heading.stories.tsx +13 -0
- package/src/Heading.tsx +62 -21
- package/src/ProgressSteps/ProgressSteps.spec.tsx +2 -3
- package/src/ProgressSteps/index.tsx +3 -4
- package/src/ScrollView/ScrollView.native.spec.tsx +97 -1
- package/src/ScrollView/index.native.tsx +4 -0
- package/src/SortableList/SortableHandle.tsx +2 -2
- package/src/Tabs/Tabs.lifecycle.spec.tsx +3 -1
- package/src/Text.tsx +32 -20
- package/src/Toast/index.tsx +4 -3
- package/src/charts/ChartDataView.tsx +2 -1
- package/src/code/CodeBlock/index.tsx +1 -1
- package/src/feedback/ConfirmDialog.spec.tsx +2 -2
- package/src/icons/phosphorTokenColor.spec.tsx +86 -0
- package/src/index.ts +1 -1
- package/src/layouts/EmptyState.spec.tsx +17 -0
- package/src/layouts/EmptyState.tsx +10 -3
- package/src/layouts/Screen.keyboardTaps.spec.tsx +57 -0
- package/src/layouts/Screen.stories.tsx +55 -2
- package/src/layouts/page.tsx +2 -14
- package/src/layouts/sidebar.tsx +2 -1
- package/src/layouts/tags.tsx +1 -1
- package/src/layouts/toolbar.tsx +2 -1
- package/src/shared/useReturnFocusOnClose.spec.tsx +39 -0
- package/src/shared/useReturnFocusOnClose.ts +4 -0
- package/src/surfaces.spec.tsx +272 -3
- package/src/surfaces.tsx +97 -4
- package/src/textWeight.houseFonts.spec.tsx +127 -0
- package/src/textWeight.native.spec.tsx +27 -0
- package/src/textWeight.spec.tsx +15 -0
- package/src/views/Calendar.tsx +37 -29
- package/src/views/Dashboard.tsx +2 -13
- package/src/views/Gantt.scroll.spec.tsx +4 -1
- package/src/views/Gantt.tsx +40 -25
- package/src/views/Kanban.native.tsx +1 -0
- package/src/views/Kanban.tsx +2 -10
- package/src/views/List.tsx +1 -0
- package/src/views/Matrix.tsx +3 -1
- package/src/views/ReportBuilder.tsx +2 -1
- package/src/views/TreeView.tsx +2 -1
- package/src/views/views.spec.tsx +14 -2
- package/types/Accordion/index.d.ts +8 -0
- package/types/Accordion/index.d.ts.map +1 -1
- package/types/Chip/index.d.ts +1 -1
- package/types/Heading.d.ts +7 -7
- package/types/Heading.d.ts.map +1 -1
- package/types/ProgressSteps/index.d.ts.map +1 -1
- package/types/ScrollView/index.native.d.ts.map +1 -1
- package/types/SortableList/SortableHandle.d.ts +1 -1
- package/types/Tabs/index.d.ts +1 -1
- package/types/Text.d.ts +4 -4
- package/types/Text.d.ts.map +1 -1
- package/types/Toast/index.d.ts.map +1 -1
- package/types/charts/ChartDataView.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/layouts/EmptyState.d.ts.map +1 -1
- package/types/layouts/page.d.ts.map +1 -1
- package/types/layouts/sidebar.d.ts.map +1 -1
- package/types/layouts/tags.d.ts.map +1 -1
- package/types/layouts/toolbar.d.ts.map +1 -1
- package/types/shared/useReturnFocusOnClose.d.ts.map +1 -1
- package/types/surfaces.d.ts +14 -0
- package/types/surfaces.d.ts.map +1 -1
- package/types/views/Calendar.d.ts +18 -1
- package/types/views/Calendar.d.ts.map +1 -1
- package/types/views/Dashboard.d.ts.map +1 -1
- package/types/views/Gantt.d.ts.map +1 -1
- package/types/views/Kanban.d.ts.map +1 -1
- package/types/views/List.d.ts.map +1 -1
- package/types/views/Matrix.d.ts.map +1 -1
- package/types/views/ReportBuilder.d.ts.map +1 -1
- package/types/views/TreeView.d.ts.map +1 -1
|
@@ -62,6 +62,12 @@ export function EmptyState({
|
|
|
62
62
|
// Polaris/Primer: icon | copy | actions are three bands; title+description
|
|
63
63
|
// sit tighter than the bands. Compact (table/column) stays on the stepped gap.
|
|
64
64
|
const frameGap = compact ? knobProps.gap : knobProps.gapLg;
|
|
65
|
+
// One paragraph per line: a web paragraph folds a newline into a space, and
|
|
66
|
+
// a multi-line server reason (LC-96) then reads as one run-on sentence.
|
|
67
|
+
const descriptionLines = (description ?? "")
|
|
68
|
+
.split("\n")
|
|
69
|
+
.map((line) => line.trim())
|
|
70
|
+
.filter(Boolean);
|
|
65
71
|
|
|
66
72
|
if (isError) {
|
|
67
73
|
warnBannedErrorWords(title, { component: "EmptyState" });
|
|
@@ -145,8 +151,9 @@ export function EmptyState({
|
|
|
145
151
|
{well && isError ? <Intent name="error">{well}</Intent> : well}
|
|
146
152
|
<YStack alignItems="center" gap="$1" {...readingWidthStyle()}>
|
|
147
153
|
{titleNode}
|
|
148
|
-
{
|
|
154
|
+
{descriptionLines.map((line, index) => (
|
|
149
155
|
<Paragraph
|
|
156
|
+
key={`${index}:${line}`}
|
|
150
157
|
{...knobProps.body}
|
|
151
158
|
color={knobProps.textAccentColor as any}
|
|
152
159
|
fontSize={knobProps.label.fontSize}
|
|
@@ -154,9 +161,9 @@ export function EmptyState({
|
|
|
154
161
|
margin={0}
|
|
155
162
|
width="100%"
|
|
156
163
|
>
|
|
157
|
-
{
|
|
164
|
+
{line}
|
|
158
165
|
</Paragraph>
|
|
159
|
-
)
|
|
166
|
+
))}
|
|
160
167
|
</YStack>
|
|
161
168
|
{action ? (
|
|
162
169
|
<XStack
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPO-457: a native sheet is an RN Modal, so the Screen's ScrollView is a
|
|
3
|
+
* React ancestor of every row in it. At RN's default keyboardShouldPersistTaps
|
|
4
|
+
* ("never") that ScrollView claims the first tap in the sheet while the
|
|
5
|
+
* keyboard is up and spends it on dismissing the keyboard. The house
|
|
6
|
+
* ScrollView carries "handled" on native (ScrollView.native.spec.tsx), so
|
|
7
|
+
* Screen must scroll through it.
|
|
8
|
+
*/
|
|
9
|
+
import { cleanup } from "@testing-library/react";
|
|
10
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
11
|
+
import { afterEach, expect, it, vi } from "vitest";
|
|
12
|
+
import { Screen } from "./page";
|
|
13
|
+
|
|
14
|
+
const houseScrollViews = vi.hoisted(() => [] as Array<Record<string, unknown>>);
|
|
15
|
+
|
|
16
|
+
vi.mock("../ScrollView", async (importOriginal) => {
|
|
17
|
+
const { forwardRef, createElement } = await import("react");
|
|
18
|
+
const { ScrollView } = await importOriginal<typeof import("../ScrollView")>();
|
|
19
|
+
return {
|
|
20
|
+
ScrollView: forwardRef<unknown, Record<string, unknown>>(function HouseScrollProbe(props, ref) {
|
|
21
|
+
houseScrollViews.push(props);
|
|
22
|
+
return createElement(ScrollView as never, { ...props, ref });
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
cleanup();
|
|
29
|
+
houseScrollViews.length = 0;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("scrolls through the house ScrollView, which defaults keyboard taps to handled on native", () => {
|
|
33
|
+
renderWithProviders(
|
|
34
|
+
<Screen>
|
|
35
|
+
<span>Page body</span>
|
|
36
|
+
</Screen>,
|
|
37
|
+
);
|
|
38
|
+
expect(houseScrollViews.length).toBeGreaterThan(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("still hands scrollViewProps to that ScrollView, so a screen can opt back out", () => {
|
|
42
|
+
renderWithProviders(
|
|
43
|
+
<Screen scrollViewProps={{ keyboardShouldPersistTaps: "never" }}>
|
|
44
|
+
<span>Page body</span>
|
|
45
|
+
</Screen>,
|
|
46
|
+
);
|
|
47
|
+
expect(houseScrollViews.at(-1)?.keyboardShouldPersistTaps).toBe("never");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("renders no ScrollView when scroll is off", () => {
|
|
51
|
+
renderWithProviders(
|
|
52
|
+
<Screen scroll={false}>
|
|
53
|
+
<span>Page body</span>
|
|
54
|
+
</Screen>,
|
|
55
|
+
);
|
|
56
|
+
expect(houseScrollViews).toEqual([]);
|
|
57
|
+
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
2
2
|
// Catalog Button (G-15 story honesty / LC-18, UX-P06).
|
|
3
|
-
import { Button } from "@multiplatform.one/forms";
|
|
4
|
-
import {
|
|
3
|
+
import { Button, Input } from "@multiplatform.one/forms";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { Paragraph, SizableText, YStack } from "tamagui";
|
|
6
|
+
import { SheetModal } from "../SheetModal";
|
|
5
7
|
import { CONTENT_MAX_WIDTH, EmptyState, PageHeader, PageSection, Screen } from "./page";
|
|
6
8
|
|
|
7
9
|
const meta: Meta<typeof Screen> = {
|
|
@@ -106,3 +108,54 @@ export const NestedScreens: Story = {
|
|
|
106
108
|
</Screen>
|
|
107
109
|
),
|
|
108
110
|
};
|
|
111
|
+
|
|
112
|
+
const PROJECTS = ["Alpha", "Beta", "Gamma", "Delta"] as const;
|
|
113
|
+
|
|
114
|
+
function SheetOverKeyboardHarness(args: Story["args"]) {
|
|
115
|
+
const [open, setOpen] = useState(false);
|
|
116
|
+
const [picks, setPicks] = useState<string[]>([]);
|
|
117
|
+
const readout = `Picks: ${picks.length}${picks.length ? `, last ${picks[picks.length - 1]}` : ""}`;
|
|
118
|
+
return (
|
|
119
|
+
<Screen {...args}>
|
|
120
|
+
<PageHeader
|
|
121
|
+
title="Sheet over the keyboard"
|
|
122
|
+
subtitle="The Screen scrolls at its defaults and the sheet opens over it"
|
|
123
|
+
/>
|
|
124
|
+
<Paragraph testID="screen-plain-text">
|
|
125
|
+
With a field focused, a tap on this text dismisses the keyboard.
|
|
126
|
+
</Paragraph>
|
|
127
|
+
<Input label="Screen field" placeholder="Focus to raise the keyboard" />
|
|
128
|
+
<Button onPress={() => setOpen(true)}>Open sheet</Button>
|
|
129
|
+
<Paragraph testID="screen-picks">{readout}</Paragraph>
|
|
130
|
+
<SheetModal
|
|
131
|
+
open={open}
|
|
132
|
+
onOpenChange={setOpen}
|
|
133
|
+
header={<SizableText fontWeight="600">Pick a project</SizableText>}
|
|
134
|
+
>
|
|
135
|
+
<YStack gap="$2">
|
|
136
|
+
<Input label="Search" placeholder="Focus to raise the keyboard" />
|
|
137
|
+
<Paragraph testID="sheet-picks">{readout}</Paragraph>
|
|
138
|
+
{PROJECTS.map((name) => (
|
|
139
|
+
<Button key={name} onPress={() => setPicks((previous) => [...previous, name])}>
|
|
140
|
+
{name}
|
|
141
|
+
</Button>
|
|
142
|
+
))}
|
|
143
|
+
</YStack>
|
|
144
|
+
</SheetModal>
|
|
145
|
+
</Screen>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const SheetOverKeyboard: Story = {
|
|
150
|
+
name: "Sheet over the keyboard",
|
|
151
|
+
parameters: {
|
|
152
|
+
nativeGallery: { scroll: false },
|
|
153
|
+
docs: {
|
|
154
|
+
description: {
|
|
155
|
+
story:
|
|
156
|
+
"MPO-457: the sheet is an RN Modal, so the Screen's ScrollView is a React ancestor of every row in it. Focus Search, then tap a project: the first tap picks it while the keyboard stays up.",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
render: (args) => <SheetOverKeyboardHarness {...args} />,
|
|
161
|
+
};
|
package/src/layouts/page.tsx
CHANGED
|
@@ -28,21 +28,9 @@ import {
|
|
|
28
28
|
} from "@multiplatform.one/theme";
|
|
29
29
|
import type { ReactNode } from "react";
|
|
30
30
|
import type { ScrollViewProps as TamaguiScrollViewProps, XStackProps, YStackProps } from "tamagui";
|
|
31
|
-
import {
|
|
32
|
-
H1,
|
|
33
|
-
H2,
|
|
34
|
-
H3,
|
|
35
|
-
H4,
|
|
36
|
-
H5,
|
|
37
|
-
H6,
|
|
38
|
-
Paragraph,
|
|
39
|
-
ScrollView,
|
|
40
|
-
Separator,
|
|
41
|
-
XStack,
|
|
42
|
-
YStack,
|
|
43
|
-
isWeb,
|
|
44
|
-
} from "tamagui";
|
|
31
|
+
import { H1, H2, H3, H4, H5, H6, Paragraph, Separator, XStack, YStack, isWeb } from "tamagui";
|
|
45
32
|
import { componentColors, sectionHeading } from "../componentColors";
|
|
33
|
+
import { ScrollView } from "../ScrollView";
|
|
46
34
|
import {
|
|
47
35
|
PageHeadingScope,
|
|
48
36
|
usePageChromeInset,
|
package/src/layouts/sidebar.tsx
CHANGED
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
} from "@multiplatform.one/theme";
|
|
13
13
|
import type React from "react";
|
|
14
14
|
import { Fragment, useId, useState } from "react";
|
|
15
|
-
import { Label,
|
|
15
|
+
import { Label, Separator, Text, XStack, YStack, isWeb } from "tamagui";
|
|
16
|
+
import { ScrollView } from "../ScrollView";
|
|
16
17
|
import { componentColors } from "../componentColors";
|
|
17
18
|
|
|
18
19
|
export interface SidebarSection {
|
package/src/layouts/tags.tsx
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
Input,
|
|
15
15
|
Label,
|
|
16
16
|
Popover,
|
|
17
|
-
ScrollView,
|
|
18
17
|
Text,
|
|
19
18
|
View,
|
|
20
19
|
XStack,
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
type InputProps,
|
|
25
24
|
type YStackProps,
|
|
26
25
|
} from "tamagui";
|
|
26
|
+
import { ScrollView } from "../ScrollView";
|
|
27
27
|
import {
|
|
28
28
|
Chip,
|
|
29
29
|
ChipFrame,
|
package/src/layouts/toolbar.tsx
CHANGED
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
import type React from "react";
|
|
11
11
|
import { useTranslation } from "../shared/i18n";
|
|
12
12
|
import { Button } from "@multiplatform.one/forms";
|
|
13
|
-
import { Group,
|
|
13
|
+
import { Group, Separator, Text, XStack, isWeb } from "tamagui";
|
|
14
|
+
import { ScrollView } from "../ScrollView";
|
|
14
15
|
|
|
15
16
|
export interface ToolbarAction {
|
|
16
17
|
label: string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { act, cleanup, renderHook } from "@testing-library/react";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { useReturnFocusOnClose } from "./useReturnFocusOnClose";
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.unstubAllGlobals();
|
|
7
|
+
cleanup();
|
|
8
|
+
vi.useRealTimers();
|
|
9
|
+
document.body.replaceChildren();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("useReturnFocusOnClose", () => {
|
|
13
|
+
function setup() {
|
|
14
|
+
vi.useFakeTimers();
|
|
15
|
+
const trigger = document.createElement("button");
|
|
16
|
+
document.body.append(trigger);
|
|
17
|
+
trigger.focus();
|
|
18
|
+
const hook = renderHook(({ open }) => useReturnFocusOnClose(open), {
|
|
19
|
+
initialProps: { open: true },
|
|
20
|
+
});
|
|
21
|
+
return { ...hook, trigger };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
it("restores dropped focus after the close animation", async () => {
|
|
25
|
+
const h = setup();
|
|
26
|
+
h.trigger.blur();
|
|
27
|
+
h.rerender({ open: false });
|
|
28
|
+
await act(() => vi.advanceTimersByTimeAsync(400));
|
|
29
|
+
expect(document.activeElement).toBe(h.trigger);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("leaves the delayed restore inert once the document is gone", () => {
|
|
33
|
+
const h = setup();
|
|
34
|
+
h.trigger.blur();
|
|
35
|
+
h.rerender({ open: false });
|
|
36
|
+
vi.stubGlobal("document", undefined);
|
|
37
|
+
expect(() => vi.advanceTimersByTime(400)).not.toThrow();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -39,6 +39,10 @@ export function useReturnFocusOnClose(open: boolean) {
|
|
|
39
39
|
if (!canUseDom() || !open) return;
|
|
40
40
|
return () => {
|
|
41
41
|
const restore = () => {
|
|
42
|
+
// Nothing cancels this after unmount, so the document can be gone by
|
|
43
|
+
// now (a test environment torn down inside the retry window), and
|
|
44
|
+
// `isConnected` stays true on the detached node.
|
|
45
|
+
if (!canUseDom()) return;
|
|
42
46
|
const target = prevRef.current;
|
|
43
47
|
if (!target || !target.isConnected) return;
|
|
44
48
|
if (focusIsDropped()) {
|
package/src/surfaces.spec.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
1
|
+
import { modelFirstFocusableShow, renderWithProviders } from "@multiplatform.one/test-utils";
|
|
2
2
|
import { Preset, type Knobs } from "@multiplatform.one/theme";
|
|
3
3
|
import { defaultConfig } from "@tamagui/config/v5";
|
|
4
|
-
import { cleanup, render } from "@testing-library/react";
|
|
4
|
+
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
Sheet,
|
|
15
15
|
TamaguiProvider,
|
|
16
16
|
} from "tamagui";
|
|
17
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
17
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
18
|
+
import { Input } from "@multiplatform.one/forms";
|
|
18
19
|
import { Button } from "./Button";
|
|
19
20
|
import * as pkg from "./index";
|
|
20
21
|
import {
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
CardFooter,
|
|
23
24
|
CardHeader,
|
|
24
25
|
DialogContent,
|
|
26
|
+
DialogShowFocus,
|
|
25
27
|
AlertDialogContent,
|
|
26
28
|
AlertDialogOverlay,
|
|
27
29
|
KnobCard,
|
|
@@ -280,3 +282,270 @@ describe("overlay containers take the Axiom 1 child-clip cap (MPO-150)", () => {
|
|
|
280
282
|
}
|
|
281
283
|
}
|
|
282
284
|
});
|
|
285
|
+
|
|
286
|
+
// MPO-452: tamagui's FocusScope checks "focus already inside?" before an idle
|
|
287
|
+
// wait of up to 200 ms, then focuses the first tabbable that does not hold
|
|
288
|
+
// focus. A click into the name field during that wait had focus moved on to
|
|
289
|
+
// the Save button, so every keystroke after it missed the field.
|
|
290
|
+
describe("PopoverContent open focus (MPO-452)", () => {
|
|
291
|
+
function SaveAsPopover() {
|
|
292
|
+
return (
|
|
293
|
+
<Popover>
|
|
294
|
+
<Popover.Trigger asChild>
|
|
295
|
+
<button type="button">Save As</button>
|
|
296
|
+
</Popover.Trigger>
|
|
297
|
+
<PopoverContent>
|
|
298
|
+
<input aria-label="Report name" />
|
|
299
|
+
<button type="button">Save</button>
|
|
300
|
+
</PopoverContent>
|
|
301
|
+
</Popover>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
it("keeps focus on a field focused right after open", async () => {
|
|
306
|
+
renderWithProviders(<SaveAsPopover />);
|
|
307
|
+
fireEvent.click(screen.getByRole("button", { name: "Save As" }));
|
|
308
|
+
const field = await waitFor(() => screen.getByLabelText("Report name"));
|
|
309
|
+
field.focus();
|
|
310
|
+
expect(document.activeElement).toBe(field);
|
|
311
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
312
|
+
expect(document.activeElement).toBe(field);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("focuses the first tabbable on open", async () => {
|
|
316
|
+
renderWithProviders(<SaveAsPopover />);
|
|
317
|
+
fireEvent.click(screen.getByRole("button", { name: "Save As" }));
|
|
318
|
+
const field = await waitFor(() => screen.getByLabelText("Report name"));
|
|
319
|
+
await waitFor(() => expect(document.activeElement).toBe(field));
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// MPO-466: DialogContent mounts the same FocusScope. In browsers its native
|
|
324
|
+
// <dialog>.show() focuses inside first; jsdom has no show(), so the deferred
|
|
325
|
+
// pass ran and moved focus off a field focused right after open.
|
|
326
|
+
describe("DialogContent open focus (MPO-466)", () => {
|
|
327
|
+
function RenameDialog({ onOpenAutoFocus }: { onOpenAutoFocus?: (event: Event) => void }) {
|
|
328
|
+
return (
|
|
329
|
+
<Dialog modal>
|
|
330
|
+
<Dialog.Trigger asChild>
|
|
331
|
+
<button type="button">Rename</button>
|
|
332
|
+
</Dialog.Trigger>
|
|
333
|
+
<Dialog.Portal>
|
|
334
|
+
<DialogContent onOpenAutoFocus={onOpenAutoFocus}>
|
|
335
|
+
<input aria-label="Name" />
|
|
336
|
+
<input aria-label="Notes" />
|
|
337
|
+
<button type="button">Save</button>
|
|
338
|
+
</DialogContent>
|
|
339
|
+
</Dialog.Portal>
|
|
340
|
+
</Dialog>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const settle = () => new Promise((resolve) => setTimeout(resolve, 300));
|
|
345
|
+
|
|
346
|
+
it.each(["Name", "Notes"])(
|
|
347
|
+
"keeps focus on the %s field focused right after open",
|
|
348
|
+
async (label) => {
|
|
349
|
+
renderWithProviders(<RenameDialog />);
|
|
350
|
+
fireEvent.click(screen.getByRole("button", { name: "Rename" }));
|
|
351
|
+
const field = await waitFor(() => screen.getByLabelText(label));
|
|
352
|
+
field.focus();
|
|
353
|
+
expect(document.activeElement).toBe(field);
|
|
354
|
+
await settle();
|
|
355
|
+
expect(document.activeElement).toBe(field);
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
it("focuses the first tabbable on open", async () => {
|
|
360
|
+
renderWithProviders(<RenameDialog />);
|
|
361
|
+
fireEvent.click(screen.getByRole("button", { name: "Rename" }));
|
|
362
|
+
const field = await waitFor(() => screen.getByLabelText("Name"));
|
|
363
|
+
await waitFor(() => expect(document.activeElement).toBe(field));
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("runs the caller's onOpenAutoFocus first and lets it opt out", async () => {
|
|
367
|
+
const seen: boolean[] = [];
|
|
368
|
+
renderWithProviders(
|
|
369
|
+
<RenameDialog
|
|
370
|
+
onOpenAutoFocus={(event) => {
|
|
371
|
+
seen.push(event.defaultPrevented);
|
|
372
|
+
event.preventDefault();
|
|
373
|
+
}}
|
|
374
|
+
/>,
|
|
375
|
+
);
|
|
376
|
+
const trigger = screen.getByRole("button", { name: "Rename" });
|
|
377
|
+
trigger.focus();
|
|
378
|
+
fireEvent.click(trigger);
|
|
379
|
+
await waitFor(() => screen.getByLabelText("Name"));
|
|
380
|
+
await settle();
|
|
381
|
+
expect(seen).toEqual([false]);
|
|
382
|
+
expect(document.activeElement).toBe(trigger);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// AlertDialogContent needs no house handler: tamagui's AlertDialog.Content
|
|
387
|
+
// prevents the FocusScope open pass and focuses its Cancel action in the same
|
|
388
|
+
// step, so there is no deferred focus to race.
|
|
389
|
+
describe("AlertDialogContent open focus (MPO-466)", () => {
|
|
390
|
+
function RenameAlertDialog() {
|
|
391
|
+
return (
|
|
392
|
+
<AlertDialog>
|
|
393
|
+
<AlertDialog.Trigger asChild>
|
|
394
|
+
<button type="button">Rename</button>
|
|
395
|
+
</AlertDialog.Trigger>
|
|
396
|
+
<AlertDialog.Portal>
|
|
397
|
+
<AlertDialogContent>
|
|
398
|
+
<input aria-label="Name" />
|
|
399
|
+
<AlertDialog.Cancel asChild>
|
|
400
|
+
<button type="button">Cancel</button>
|
|
401
|
+
</AlertDialog.Cancel>
|
|
402
|
+
<button type="button">Save</button>
|
|
403
|
+
</AlertDialogContent>
|
|
404
|
+
</AlertDialog.Portal>
|
|
405
|
+
</AlertDialog>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
it("focuses its Cancel action on open", async () => {
|
|
410
|
+
renderWithProviders(<RenameAlertDialog />);
|
|
411
|
+
fireEvent.click(screen.getByRole("button", { name: "Rename" }));
|
|
412
|
+
const cancel = await waitFor(() => screen.getByText("Cancel"));
|
|
413
|
+
await waitFor(() => expect(document.activeElement).toBe(cancel));
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("keeps focus on a field focused right after open", async () => {
|
|
417
|
+
renderWithProviders(<RenameAlertDialog />);
|
|
418
|
+
fireEvent.click(screen.getByRole("button", { name: "Rename" }));
|
|
419
|
+
const field = await waitFor(() => screen.getByLabelText("Name"));
|
|
420
|
+
field.focus();
|
|
421
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
422
|
+
expect(document.activeElement).toBe(field);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// MPO-470: in Chromium and WebKit, <dialog>.show() focuses the first focusable
|
|
427
|
+
// descendant, a tabindex=-1 frame included, where Firefox and the HTML spec
|
|
428
|
+
// take the first tab stop. The house Input's group frame is tabindex=-1, so it
|
|
429
|
+
// took focus, FocusScope then saw focus inside and never ran the open focus,
|
|
430
|
+
// and typing went nowhere. jsdom has no show(), so modelFirstFocusableShow
|
|
431
|
+
// stands in for Chromium's.
|
|
432
|
+
|
|
433
|
+
describe("DialogContent open focus behind <dialog>.show() (MPO-470)", () => {
|
|
434
|
+
let restoreShow = () => {};
|
|
435
|
+
beforeEach(() => {
|
|
436
|
+
restoreShow = modelFirstFocusableShow();
|
|
437
|
+
});
|
|
438
|
+
afterEach(() => restoreShow());
|
|
439
|
+
|
|
440
|
+
function NameDialog({
|
|
441
|
+
helpFirst,
|
|
442
|
+
onOpenAutoFocus,
|
|
443
|
+
}: {
|
|
444
|
+
helpFirst?: boolean;
|
|
445
|
+
onOpenAutoFocus?: (event: Event) => void;
|
|
446
|
+
}) {
|
|
447
|
+
return (
|
|
448
|
+
<Dialog modal>
|
|
449
|
+
<Dialog.Trigger asChild>
|
|
450
|
+
<button type="button">Rename</button>
|
|
451
|
+
</Dialog.Trigger>
|
|
452
|
+
<Dialog.Portal>
|
|
453
|
+
<DialogContent onOpenAutoFocus={onOpenAutoFocus}>
|
|
454
|
+
{helpFirst ? <button type="button">Help</button> : null}
|
|
455
|
+
<Input inputProps={{ "aria-label": "Name" }} />
|
|
456
|
+
<button type="button">Save</button>
|
|
457
|
+
</DialogContent>
|
|
458
|
+
</Dialog.Portal>
|
|
459
|
+
</Dialog>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
it("moves focus off the house Input's group frame onto its field", async () => {
|
|
464
|
+
renderWithProviders(<NameDialog />);
|
|
465
|
+
fireEvent.click(screen.getByRole("button", { name: "Rename" }));
|
|
466
|
+
const field = await waitFor(() => screen.getByLabelText("Name"));
|
|
467
|
+
expect(field.closest("[tabindex='-1']")).not.toBeNull();
|
|
468
|
+
await waitFor(() => expect(document.activeElement).toBe(field));
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("runs the caller's onOpenAutoFocus, which can pick another target", async () => {
|
|
472
|
+
renderWithProviders(
|
|
473
|
+
<NameDialog
|
|
474
|
+
onOpenAutoFocus={(event) => {
|
|
475
|
+
event.preventDefault();
|
|
476
|
+
screen.getByRole("button", { name: "Save" }).focus();
|
|
477
|
+
}}
|
|
478
|
+
/>,
|
|
479
|
+
);
|
|
480
|
+
fireEvent.click(screen.getByRole("button", { name: "Rename" }));
|
|
481
|
+
const save = await waitFor(() => screen.getByRole("button", { name: "Save" }));
|
|
482
|
+
await waitFor(() => expect(document.activeElement).toBe(save));
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it("leaves focus on a tab stop the browser picked", async () => {
|
|
486
|
+
renderWithProviders(<NameDialog helpFirst />);
|
|
487
|
+
fireEvent.click(screen.getByRole("button", { name: "Rename" }));
|
|
488
|
+
const help = await waitFor(() => screen.getByRole("button", { name: "Help" }));
|
|
489
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
490
|
+
expect(document.activeElement).toBe(help);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
describe("AlertDialogContent open focus behind <dialog>.show() (MPO-470)", () => {
|
|
495
|
+
let restoreShow = () => {};
|
|
496
|
+
beforeEach(() => {
|
|
497
|
+
restoreShow = modelFirstFocusableShow();
|
|
498
|
+
});
|
|
499
|
+
afterEach(() => restoreShow());
|
|
500
|
+
|
|
501
|
+
it("moves focus off the house Input's group frame onto its field", async () => {
|
|
502
|
+
renderWithProviders(
|
|
503
|
+
<AlertDialog>
|
|
504
|
+
<AlertDialog.Trigger asChild>
|
|
505
|
+
<button type="button">Rename</button>
|
|
506
|
+
</AlertDialog.Trigger>
|
|
507
|
+
<AlertDialog.Portal>
|
|
508
|
+
<AlertDialogContent>
|
|
509
|
+
<Input inputProps={{ "aria-label": "Name" }} />
|
|
510
|
+
<AlertDialog.Cancel asChild>
|
|
511
|
+
<button type="button">Cancel</button>
|
|
512
|
+
</AlertDialog.Cancel>
|
|
513
|
+
</AlertDialogContent>
|
|
514
|
+
</AlertDialog.Portal>
|
|
515
|
+
</AlertDialog>,
|
|
516
|
+
);
|
|
517
|
+
fireEvent.click(screen.getByRole("button", { name: "Rename" }));
|
|
518
|
+
const field = await waitFor(() => screen.getByLabelText("Name"));
|
|
519
|
+
expect(field.closest("[tabindex='-1']")).not.toBeNull();
|
|
520
|
+
await waitFor(() => expect(document.activeElement).toBe(field));
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
describe("DialogShowFocus in a raw Dialog.Content (MPO-470)", () => {
|
|
525
|
+
let restoreShow = () => {};
|
|
526
|
+
beforeEach(() => {
|
|
527
|
+
restoreShow = modelFirstFocusableShow();
|
|
528
|
+
});
|
|
529
|
+
afterEach(() => restoreShow());
|
|
530
|
+
|
|
531
|
+
it("moves focus off the house Input's group frame onto its field", async () => {
|
|
532
|
+
renderWithProviders(
|
|
533
|
+
<Dialog modal>
|
|
534
|
+
<Dialog.Trigger asChild>
|
|
535
|
+
<button type="button">Save Filter</button>
|
|
536
|
+
</Dialog.Trigger>
|
|
537
|
+
<Dialog.Portal>
|
|
538
|
+
<Dialog.Content>
|
|
539
|
+
<Input inputProps={{ "aria-label": "Filter Name" }} />
|
|
540
|
+
<button type="button">Save</button>
|
|
541
|
+
<DialogShowFocus />
|
|
542
|
+
</Dialog.Content>
|
|
543
|
+
</Dialog.Portal>
|
|
544
|
+
</Dialog>,
|
|
545
|
+
);
|
|
546
|
+
fireEvent.click(screen.getByRole("button", { name: "Save Filter" }));
|
|
547
|
+
const field = await waitFor(() => screen.getByLabelText("Filter Name"));
|
|
548
|
+
expect(field.closest("[tabindex='-1']")).not.toBeNull();
|
|
549
|
+
await waitFor(() => expect(document.activeElement).toBe(field));
|
|
550
|
+
});
|
|
551
|
+
});
|