@atlaskit/modal-dialog 15.3.1 → 16.0.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/CHANGELOG.md +35 -0
- package/__tests__/playwright/top-layer-focus.spec.tsx +141 -0
- package/dist/cjs/internal/components/modal-wrapper.js +1 -1
- package/dist/es2019/internal/components/modal-wrapper.js +1 -1
- package/dist/esm/internal/components/modal-wrapper.js +1 -1
- package/package.json +38 -46
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @atlaskit/modal-dialog
|
|
2
2
|
|
|
3
|
+
## 16.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`ee28cf33718b0`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ee28cf33718b0) -
|
|
8
|
+
Add @atlassian/react-compiler-gating as a runtime dependency to enable React Compiler platform
|
|
9
|
+
gating.
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
|
|
12
|
+
## 16.0.0
|
|
13
|
+
|
|
14
|
+
### Major Changes
|
|
15
|
+
|
|
16
|
+
- [`f2dc9097319f0`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f2dc9097319f0) - ###
|
|
17
|
+
Dropped support for _legacy_ Typescript 4 types. **Typescript 5 is now the new minimum**.
|
|
18
|
+
|
|
19
|
+
Removes the `typesVersions` property and `dist/types-ts4.5` directory from the dist.
|
|
20
|
+
|
|
21
|
+
Types are now exclusively via the `"types": "dist/types/index.d.ts"` property.
|
|
22
|
+
|
|
23
|
+
```diff
|
|
24
|
+
- "typesVersions": {
|
|
25
|
+
- ">=4.5 <4.9": {
|
|
26
|
+
- "*": [
|
|
27
|
+
- "dist/types-ts4.5/*",
|
|
28
|
+
- "dist/types-ts4.5/index.d.ts"
|
|
29
|
+
- ]
|
|
30
|
+
- }
|
|
31
|
+
- },
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- Updated dependencies
|
|
37
|
+
|
|
3
38
|
## 15.3.1
|
|
4
39
|
|
|
5
40
|
### Patch Changes
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { expect, test } from '@af/integration-testing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Modal dialog: focus contract on the top-layer code path.
|
|
5
|
+
*
|
|
6
|
+
* `ModalDialog` renders as a `role="dialog"` modal. Per WCAG 2.4.3 (Focus
|
|
7
|
+
* Order) and the top-layer focus rules:
|
|
8
|
+
*
|
|
9
|
+
* 1. Initial focus moves to the first focusable element on open (or to the
|
|
10
|
+
* element marked with the native HTML `autofocus` attribute when present).
|
|
11
|
+
* 2. Closing the modal (Escape) restores focus to the trigger.
|
|
12
|
+
* 3. Tab cycles focus within the modal (focus does not escape to elements
|
|
13
|
+
* behind the modal).
|
|
14
|
+
*
|
|
15
|
+
* See: `platform/packages/design-system/top-layer/notes/architecture/focus.md`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const featureFlag = 'platform-dst-top-layer';
|
|
19
|
+
|
|
20
|
+
test.describe('Modal dialog: top-layer focus contract', () => {
|
|
21
|
+
test('initial focus: focus moves to first focusable element on open', async ({ page }) => {
|
|
22
|
+
await page.visitExample<typeof import('../../examples/98-testing-initial-focus-matrix.tsx')>(
|
|
23
|
+
'design-system',
|
|
24
|
+
'modal-dialog',
|
|
25
|
+
'testing-initial-focus-matrix',
|
|
26
|
+
{ featureFlag },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
await page.getByTestId('default-modal-trigger').click();
|
|
30
|
+
|
|
31
|
+
const modal = page.getByTestId('default-modal');
|
|
32
|
+
await expect(modal).toBeVisible();
|
|
33
|
+
|
|
34
|
+
// The first focusable inside the modal is the header's close button.
|
|
35
|
+
await expect(page.getByTestId('default-modal--close-button')).toBeFocused();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('focus restoration: Escape restores focus to the trigger', async ({ page }) => {
|
|
39
|
+
await page.visitExample<typeof import('../../examples/98-testing-initial-focus-matrix.tsx')>(
|
|
40
|
+
'design-system',
|
|
41
|
+
'modal-dialog',
|
|
42
|
+
'testing-initial-focus-matrix',
|
|
43
|
+
{ featureFlag },
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const trigger = page.getByTestId('default-modal-trigger');
|
|
47
|
+
await trigger.click();
|
|
48
|
+
await expect(page.getByTestId('default-modal')).toBeVisible();
|
|
49
|
+
|
|
50
|
+
await page.keyboard.press('Escape');
|
|
51
|
+
await expect(page.getByTestId('default-modal')).toBeHidden();
|
|
52
|
+
await expect(trigger).toBeFocused();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// WCAG 2.4.3 Focus Order + HTML `<dialog>` focusing steps
|
|
56
|
+
// (https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-focusing-steps).
|
|
57
|
+
// When a descendant of the dialog carries the native HTML `autofocus`
|
|
58
|
+
// attribute, focus must land on that element instead of the first
|
|
59
|
+
// focusable. This matches both `<dialog>.showModal()` and the
|
|
60
|
+
// WAI-ARIA APG Dialog pattern.
|
|
61
|
+
test('initial focus: native [autofocus] element wins over the first focusable element', async ({
|
|
62
|
+
page,
|
|
63
|
+
}) => {
|
|
64
|
+
await page.visitExample<typeof import('../../examples/98-testing-initial-focus-matrix.tsx')>(
|
|
65
|
+
'design-system',
|
|
66
|
+
'modal-dialog',
|
|
67
|
+
'testing-initial-focus-matrix',
|
|
68
|
+
{ featureFlag },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
await page.getByTestId('native-autofocus-modal-trigger').click();
|
|
72
|
+
|
|
73
|
+
const modal = page.getByTestId('native-autofocus-modal');
|
|
74
|
+
await expect(modal).toBeVisible();
|
|
75
|
+
|
|
76
|
+
// The first focusable inside the modal is the header close
|
|
77
|
+
// button, but the body input carries `autofocus`, so focus must
|
|
78
|
+
// land on the input.
|
|
79
|
+
await expect(page.getByTestId('native-autofocus-input')).toBeFocused();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// WCAG 2.4.3 Focus Order. `Modal`'s `autoFocus` prop accepts a
|
|
83
|
+
// `RefObject` that points at a specific descendant to focus on
|
|
84
|
+
// open. This is the consumer-level override of the default
|
|
85
|
+
// first-focusable behaviour and is implemented in `modal-wrapper`
|
|
86
|
+
// outside `useInitialFocus`, but it shares the same WCAG / APG
|
|
87
|
+
// contract: the chosen element receives focus instead of the
|
|
88
|
+
// natural first focusable.
|
|
89
|
+
test('initial focus: `autoFocus` ref overrides the first focusable element', async ({ page }) => {
|
|
90
|
+
await page.visitExample<typeof import('../../examples/98-testing-initial-focus-matrix.tsx')>(
|
|
91
|
+
'design-system',
|
|
92
|
+
'modal-dialog',
|
|
93
|
+
'testing-initial-focus-matrix',
|
|
94
|
+
{ featureFlag },
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await page.getByTestId('auto-focus-ref-modal-trigger').click();
|
|
98
|
+
|
|
99
|
+
const modal = page.getByTestId('auto-focus-ref-modal');
|
|
100
|
+
await expect(modal).toBeVisible();
|
|
101
|
+
|
|
102
|
+
// The header close button is the first focusable, but the
|
|
103
|
+
// consumer pointed `autoFocus` at the body input, so focus must
|
|
104
|
+
// land on the input.
|
|
105
|
+
await expect(page.getByTestId('auto-focus-ref-input')).toBeFocused();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('focus movement: Tab cycles focus within the modal', async ({ page }) => {
|
|
109
|
+
await page.visitExample<typeof import('../../examples/98-testing-initial-focus-matrix.tsx')>(
|
|
110
|
+
'design-system',
|
|
111
|
+
'modal-dialog',
|
|
112
|
+
'testing-initial-focus-matrix',
|
|
113
|
+
{ featureFlag },
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
await page.getByTestId('default-modal-trigger').click();
|
|
117
|
+
const modal = page.getByTestId('default-modal');
|
|
118
|
+
await expect(modal).toBeVisible();
|
|
119
|
+
|
|
120
|
+
// Walk forward through every focusable element in the modal and verify
|
|
121
|
+
// focus never escapes the dialog surface.
|
|
122
|
+
const focusables = [
|
|
123
|
+
page.getByTestId('default-modal--close-button'),
|
|
124
|
+
page.getByTestId('default-modal-secondary'),
|
|
125
|
+
page.getByTestId('default-modal-primary'),
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
for (const target of focusables) {
|
|
129
|
+
await expect(target).toBeFocused();
|
|
130
|
+
await page.keyboard.press('Tab');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// After cycling past the last focusable, focus must remain inside the
|
|
134
|
+
// modal (focus wrap), never on a node outside it.
|
|
135
|
+
const activeWithinModal = await modal.evaluate(
|
|
136
|
+
(modalElement) =>
|
|
137
|
+
document.activeElement !== null && modalElement.contains(document.activeElement),
|
|
138
|
+
);
|
|
139
|
+
expect(activeWithinModal).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -114,7 +114,7 @@ var InternalModalWrapper = /*#__PURE__*/(0, _react.forwardRef)(function (props,
|
|
|
114
114
|
action: 'closed',
|
|
115
115
|
componentName: 'modalDialog',
|
|
116
116
|
packageName: "@atlaskit/modal-dialog",
|
|
117
|
-
packageVersion: "
|
|
117
|
+
packageVersion: "16.0.0"
|
|
118
118
|
});
|
|
119
119
|
var onBlanketClicked = (0, _react.useCallback)(function (e) {
|
|
120
120
|
if (shouldCloseOnOverlayClick) {
|
|
@@ -100,7 +100,7 @@ const InternalModalWrapper = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
100
100
|
action: 'closed',
|
|
101
101
|
componentName: 'modalDialog',
|
|
102
102
|
packageName: "@atlaskit/modal-dialog",
|
|
103
|
-
packageVersion: "
|
|
103
|
+
packageVersion: "16.0.0"
|
|
104
104
|
});
|
|
105
105
|
const onBlanketClicked = useCallback(e => {
|
|
106
106
|
if (shouldCloseOnOverlayClick) {
|
|
@@ -105,7 +105,7 @@ var InternalModalWrapper = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
|
105
105
|
action: 'closed',
|
|
106
106
|
componentName: 'modalDialog',
|
|
107
107
|
packageName: "@atlaskit/modal-dialog",
|
|
108
|
-
packageVersion: "
|
|
108
|
+
packageVersion: "16.0.0"
|
|
109
109
|
});
|
|
110
110
|
var onBlanketClicked = useCallback(function (e) {
|
|
111
111
|
if (shouldCloseOnOverlayClick) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/modal-dialog",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.0.1",
|
|
4
4
|
"description": "A modal dialog displays content that requires user interaction, in a layer above the page.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -34,21 +34,22 @@
|
|
|
34
34
|
"watch": "tsc --watch --noEmit --project './tsconfig.json'"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@atlaskit/analytics-next": "^
|
|
38
|
-
"@atlaskit/blanket": "^
|
|
39
|
-
"@atlaskit/button": "^
|
|
40
|
-
"@atlaskit/css": "^0.
|
|
41
|
-
"@atlaskit/ds-lib": "^
|
|
42
|
-
"@atlaskit/icon": "^
|
|
43
|
-
"@atlaskit/layering": "^
|
|
44
|
-
"@atlaskit/motion": "^
|
|
45
|
-
"@atlaskit/platform-feature-flags": "^
|
|
46
|
-
"@atlaskit/portal": "^
|
|
47
|
-
"@atlaskit/pragmatic-drag-and-drop": "^
|
|
48
|
-
"@atlaskit/primitives": "^
|
|
49
|
-
"@atlaskit/theme": "^
|
|
50
|
-
"@atlaskit/tokens": "^
|
|
51
|
-
"@atlaskit/top-layer": "^0.
|
|
37
|
+
"@atlaskit/analytics-next": "^12.0.0",
|
|
38
|
+
"@atlaskit/blanket": "^16.0.0",
|
|
39
|
+
"@atlaskit/button": "^24.1.0",
|
|
40
|
+
"@atlaskit/css": "^1.0.0",
|
|
41
|
+
"@atlaskit/ds-lib": "^8.0.0",
|
|
42
|
+
"@atlaskit/icon": "^36.0.0",
|
|
43
|
+
"@atlaskit/layering": "^4.0.0",
|
|
44
|
+
"@atlaskit/motion": "^7.1.0",
|
|
45
|
+
"@atlaskit/platform-feature-flags": "^2.0.0",
|
|
46
|
+
"@atlaskit/portal": "^6.0.0",
|
|
47
|
+
"@atlaskit/pragmatic-drag-and-drop": "^2.0.0",
|
|
48
|
+
"@atlaskit/primitives": "^20.0.0",
|
|
49
|
+
"@atlaskit/theme": "^26.0.0",
|
|
50
|
+
"@atlaskit/tokens": "^15.0.0",
|
|
51
|
+
"@atlaskit/top-layer": "^1.0.0",
|
|
52
|
+
"@atlassian/react-compiler-gating": "^0.2.0",
|
|
52
53
|
"@babel/runtime": "^7.0.0",
|
|
53
54
|
"@compiled/react": "^0.20.0",
|
|
54
55
|
"bind-event-listener": "^3.0.0",
|
|
@@ -63,28 +64,27 @@
|
|
|
63
64
|
"@af/accessibility-testing": "workspace:^",
|
|
64
65
|
"@af/integration-testing": "workspace:^",
|
|
65
66
|
"@af/visual-regression": "workspace:^",
|
|
66
|
-
"@atlaskit/avatar": "^
|
|
67
|
-
"@atlaskit/avatar-group": "^
|
|
68
|
-
"@atlaskit/banner": "^
|
|
69
|
-
"@atlaskit/breadcrumbs": "^
|
|
70
|
-
"@atlaskit/checkbox": "^
|
|
71
|
-
"@atlaskit/code": "^
|
|
72
|
-
"@atlaskit/datetime-picker": "^
|
|
73
|
-
"@atlaskit/docs": "^
|
|
74
|
-
"@atlaskit/dropdown-menu": "^
|
|
75
|
-
"@atlaskit/flag": "^
|
|
76
|
-
"@atlaskit/form": "^
|
|
77
|
-
"@atlaskit/heading": "^
|
|
78
|
-
"@atlaskit/link": "^
|
|
79
|
-
"@atlaskit/popup": "^
|
|
80
|
-
"@atlaskit/radio": "^
|
|
81
|
-
"@atlaskit/section-message": "^
|
|
82
|
-
"@atlaskit/select": "^
|
|
83
|
-
"@atlaskit/spotlight": "^0.
|
|
84
|
-
"@atlaskit/textfield": "^
|
|
85
|
-
"@atlaskit/tooltip": "^
|
|
67
|
+
"@atlaskit/avatar": "^26.0.0",
|
|
68
|
+
"@atlaskit/avatar-group": "^13.0.0",
|
|
69
|
+
"@atlaskit/banner": "^15.0.0",
|
|
70
|
+
"@atlaskit/breadcrumbs": "^17.0.0",
|
|
71
|
+
"@atlaskit/checkbox": "^18.0.0",
|
|
72
|
+
"@atlaskit/code": "^18.0.0",
|
|
73
|
+
"@atlaskit/datetime-picker": "^18.0.0",
|
|
74
|
+
"@atlaskit/docs": "^12.0.0",
|
|
75
|
+
"@atlaskit/dropdown-menu": "^17.0.0",
|
|
76
|
+
"@atlaskit/flag": "^18.0.0",
|
|
77
|
+
"@atlaskit/form": "^16.0.0",
|
|
78
|
+
"@atlaskit/heading": "^6.0.0",
|
|
79
|
+
"@atlaskit/link": "^4.0.0",
|
|
80
|
+
"@atlaskit/popup": "^5.0.0",
|
|
81
|
+
"@atlaskit/radio": "^9.0.0",
|
|
82
|
+
"@atlaskit/section-message": "^9.1.0",
|
|
83
|
+
"@atlaskit/select": "^22.1.0",
|
|
84
|
+
"@atlaskit/spotlight": "^2.0.0",
|
|
85
|
+
"@atlaskit/textfield": "^9.0.0",
|
|
86
|
+
"@atlaskit/tooltip": "^23.0.0",
|
|
86
87
|
"@atlassian/feature-flags-test-utils": "^1.1.0",
|
|
87
|
-
"@atlassian/react-compiler-gating": "workspace:^",
|
|
88
88
|
"@atlassian/ssr-tests": "workspace:^",
|
|
89
89
|
"@atlassian/structured-docs-types": "workspace:^",
|
|
90
90
|
"@atlassian/testing-library": "^0.6.0",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"react-lorem-component": "^0.13.0",
|
|
99
99
|
"react-sweet-state": "^2.6.5",
|
|
100
100
|
"tiny-invariant": "^1.2.0",
|
|
101
|
-
"typescript": "5.9.
|
|
101
|
+
"typescript": "5.9.3"
|
|
102
102
|
},
|
|
103
103
|
"keywords": [
|
|
104
104
|
"atlaskit",
|
|
@@ -125,14 +125,6 @@
|
|
|
125
125
|
]
|
|
126
126
|
}
|
|
127
127
|
},
|
|
128
|
-
"typesVersions": {
|
|
129
|
-
">=4.5 <4.9": {
|
|
130
|
-
"*": [
|
|
131
|
-
"dist/types-ts4.5/*",
|
|
132
|
-
"dist/types-ts4.5/index.d.ts"
|
|
133
|
-
]
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
128
|
"platform-feature-flags": {
|
|
137
129
|
"platform_modal-dialog-heading-icon-a11y-fix": {
|
|
138
130
|
"type": "boolean"
|