@atlaskit/dropdown-menu 16.10.6 → 17.0.0
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 +26 -0
- package/__tests__/playwright/top-layer-focus.spec.tsx +165 -0
- package/package.json +30 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @atlaskit/dropdown-menu
|
|
2
2
|
|
|
3
|
+
## 17.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [`f2dc9097319f0`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f2dc9097319f0) - ###
|
|
8
|
+
Dropped support for _legacy_ Typescript 4 types. **Typescript 5 is now the new minimum**.
|
|
9
|
+
|
|
10
|
+
Removes the `typesVersions` property and `dist/types-ts4.5` directory from the dist.
|
|
11
|
+
|
|
12
|
+
Types are now exclusively via the `"types": "dist/types/index.d.ts"` property.
|
|
13
|
+
|
|
14
|
+
```diff
|
|
15
|
+
- "typesVersions": {
|
|
16
|
+
- ">=4.5 <4.9": {
|
|
17
|
+
- "*": [
|
|
18
|
+
- "dist/types-ts4.5/*",
|
|
19
|
+
- "dist/types-ts4.5/index.d.ts"
|
|
20
|
+
- ]
|
|
21
|
+
- }
|
|
22
|
+
- },
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies
|
|
28
|
+
|
|
3
29
|
## 16.10.6
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import invariant from 'tiny-invariant';
|
|
2
|
+
|
|
3
|
+
import { expect, test } from '@af/integration-testing';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Dropdown menu: focus contract on the top-layer code path.
|
|
7
|
+
*
|
|
8
|
+
* `DropdownMenu` renders a `role="menu"` popover. The focus contract is:
|
|
9
|
+
*
|
|
10
|
+
* 1. Initial focus moves to the first menu item on open.
|
|
11
|
+
* 2. Escape closes the menu and restores focus to the trigger.
|
|
12
|
+
* 3. ArrowDown moves focus to the next menu item within the menu.
|
|
13
|
+
*
|
|
14
|
+
* `DropdownMenu` also exposes an `autoFocus` boolean prop that overrides
|
|
15
|
+
* the trigger-source heuristic (focus the first item only when the
|
|
16
|
+
* trigger was activated via the keyboard) and forces focus to the first
|
|
17
|
+
* menu item whenever the menu opens. This spec exercises both variants.
|
|
18
|
+
*
|
|
19
|
+
* See: `platform/packages/design-system/top-layer/notes/architecture/focus.md`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const featureFlag = 'platform-dst-top-layer';
|
|
23
|
+
|
|
24
|
+
test.describe('Dropdown menu: top-layer focus contract', () => {
|
|
25
|
+
test('initial focus: focus moves to the first menu item on open', async ({ page }) => {
|
|
26
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
27
|
+
'design-system',
|
|
28
|
+
'dropdown-menu',
|
|
29
|
+
'testing-top-layer-focus',
|
|
30
|
+
{ featureFlag },
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
await page.getByTestId('dropdown--trigger').click();
|
|
34
|
+
|
|
35
|
+
await expect(page.getByTestId('dropdown-item-1')).toBeFocused();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('focus restoration: Escape restores focus to the trigger', async ({ page }) => {
|
|
39
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
40
|
+
'design-system',
|
|
41
|
+
'dropdown-menu',
|
|
42
|
+
'testing-top-layer-focus',
|
|
43
|
+
{ featureFlag },
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const trigger = page.getByTestId('dropdown--trigger');
|
|
47
|
+
await trigger.click();
|
|
48
|
+
await expect(page.getByTestId('dropdown-item-1')).toBeFocused();
|
|
49
|
+
|
|
50
|
+
await page.keyboard.press('Escape');
|
|
51
|
+
await expect(trigger).toBeFocused();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('focus movement: ArrowDown moves focus between menu items', async ({ page }) => {
|
|
55
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
56
|
+
'design-system',
|
|
57
|
+
'dropdown-menu',
|
|
58
|
+
'testing-top-layer-focus',
|
|
59
|
+
{ featureFlag },
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
await page.getByTestId('dropdown--trigger').click();
|
|
63
|
+
await expect(page.getByTestId('dropdown-item-1')).toBeFocused();
|
|
64
|
+
|
|
65
|
+
await page.keyboard.press('ArrowDown');
|
|
66
|
+
await expect(page.getByTestId('dropdown-item-2')).toBeFocused();
|
|
67
|
+
|
|
68
|
+
await page.keyboard.press('ArrowDown');
|
|
69
|
+
await expect(page.getByTestId('dropdown-item-3')).toBeFocused();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// WCAG 2.4.3 Focus Order + WAI-ARIA APG Menu Button pattern
|
|
73
|
+
// (https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/).
|
|
74
|
+
// "Enter / Space / Down Arrow: Opens the menu and moves focus to the
|
|
75
|
+
// first menu item." Keyboard activation must move focus to the
|
|
76
|
+
// first menu item even when `autoFocus` is left at its default.
|
|
77
|
+
test('initial focus: keyboard-triggered open focuses the first menu item (default `autoFocus={false}`)', async ({
|
|
78
|
+
page,
|
|
79
|
+
}) => {
|
|
80
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
81
|
+
'design-system',
|
|
82
|
+
'dropdown-menu',
|
|
83
|
+
'testing-top-layer-focus',
|
|
84
|
+
{ featureFlag },
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const trigger = page.getByTestId('dropdown--trigger');
|
|
88
|
+
await trigger.focus();
|
|
89
|
+
await page.keyboard.press('Enter');
|
|
90
|
+
|
|
91
|
+
await expect(page.getByTestId('dropdown-item-1')).toBeFocused();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// WCAG 2.4.3 Focus Order + WAI-ARIA APG Menu Button pattern. The
|
|
95
|
+
// `defaultOpen` prop mounts the menu in the open state, so there is
|
|
96
|
+
// no trigger interaction to source focus from. The menu must still
|
|
97
|
+
// move focus to the first menu item on mount, exercising the
|
|
98
|
+
// mount-time open path of `useInitialFocus`.
|
|
99
|
+
test('initial focus: `defaultOpen` focuses the first menu item on mount', async ({ page }) => {
|
|
100
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
101
|
+
'design-system',
|
|
102
|
+
'dropdown-menu',
|
|
103
|
+
'testing-top-layer-focus',
|
|
104
|
+
{ featureFlag },
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
await expect(page.getByTestId('dropdown-default-open-item-1')).toBeFocused();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// WCAG 2.4.3 Focus Order. `returnFocusRef` lets a consumer redirect
|
|
111
|
+
// Escape-restoration to a different element than the trigger. The
|
|
112
|
+
// dropdown's focus-restoration code runs after the browser's native
|
|
113
|
+
// trigger restoration, so the consumer-supplied target must win.
|
|
114
|
+
test('focus restoration: `returnFocusRef` redirects Escape focus to a sibling element', async ({
|
|
115
|
+
page,
|
|
116
|
+
}) => {
|
|
117
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
118
|
+
'design-system',
|
|
119
|
+
'dropdown-menu',
|
|
120
|
+
'testing-top-layer-focus',
|
|
121
|
+
{ featureFlag },
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const trigger = page.getByTestId('dropdown-return-focus-ref--trigger');
|
|
125
|
+
await trigger.focus();
|
|
126
|
+
await page.keyboard.press('Enter');
|
|
127
|
+
|
|
128
|
+
await expect(page.getByTestId('dropdown-return-focus-ref-item-1')).toBeFocused();
|
|
129
|
+
|
|
130
|
+
await page.keyboard.press('Escape');
|
|
131
|
+
|
|
132
|
+
// Focus must land on the consumer-supplied target, not on the
|
|
133
|
+
// trigger that opened the menu.
|
|
134
|
+
await expect(page.getByTestId('dropdown-return-focus-ref-target')).toBeFocused();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// WCAG 2.4.3 Focus Order + WAI-ARIA APG Menu Button pattern. The
|
|
138
|
+
// `autoFocus` prop is an opt-in override of the default
|
|
139
|
+
// trigger-source heuristic (focus the first item only on keyboard
|
|
140
|
+
// open). With `autoFocus={true}`, focus must move to the first
|
|
141
|
+
// menu item regardless of how the trigger was activated, which is
|
|
142
|
+
// the strict APG behaviour.
|
|
143
|
+
test('initial focus: `autoFocus={true}` focuses the first menu item even on real mouse open', async ({
|
|
144
|
+
page,
|
|
145
|
+
}) => {
|
|
146
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
147
|
+
'design-system',
|
|
148
|
+
'dropdown-menu',
|
|
149
|
+
'testing-top-layer-focus',
|
|
150
|
+
{ featureFlag },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const trigger = page.getByTestId('dropdown-autofocus--trigger');
|
|
154
|
+
const box = await trigger.boundingBox();
|
|
155
|
+
invariant(box, 'autoFocus dropdown trigger should have a bounding box');
|
|
156
|
+
|
|
157
|
+
// Drive a real mouse click with non-zero coordinates so the
|
|
158
|
+
// dropdown's trigger-source heuristic classifies it as a mouse
|
|
159
|
+
// (not keyboard) activation. `autoFocus={true}` must still
|
|
160
|
+
// pull focus onto the first menu item.
|
|
161
|
+
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
|
162
|
+
|
|
163
|
+
await expect(page.getByTestId('dropdown-autofocus-item-1')).toBeFocused();
|
|
164
|
+
});
|
|
165
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/dropdown-menu",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.0",
|
|
4
4
|
"description": "A dropdown menu displays a list of actions or options to a user.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@atlaskit/button": "^
|
|
35
|
-
"@atlaskit/css": "^0.
|
|
36
|
-
"@atlaskit/ds-lib": "^
|
|
37
|
-
"@atlaskit/icon": "^
|
|
38
|
-
"@atlaskit/layering": "^
|
|
39
|
-
"@atlaskit/menu": "^
|
|
40
|
-
"@atlaskit/platform-feature-flags": "^
|
|
41
|
-
"@atlaskit/popup": "^
|
|
42
|
-
"@atlaskit/primitives": "^
|
|
43
|
-
"@atlaskit/spinner": "^
|
|
44
|
-
"@atlaskit/theme": "^
|
|
45
|
-
"@atlaskit/tokens": "^
|
|
46
|
-
"@atlaskit/top-layer": "^0.
|
|
47
|
-
"@atlaskit/visually-hidden": "^
|
|
34
|
+
"@atlaskit/button": "^24.0.0",
|
|
35
|
+
"@atlaskit/css": "^1.0.0",
|
|
36
|
+
"@atlaskit/ds-lib": "^8.0.0",
|
|
37
|
+
"@atlaskit/icon": "^36.0.0",
|
|
38
|
+
"@atlaskit/layering": "^4.0.0",
|
|
39
|
+
"@atlaskit/menu": "^9.0.0",
|
|
40
|
+
"@atlaskit/platform-feature-flags": "^2.0.0",
|
|
41
|
+
"@atlaskit/popup": "^5.0.0",
|
|
42
|
+
"@atlaskit/primitives": "^20.0.0",
|
|
43
|
+
"@atlaskit/spinner": "^20.0.0",
|
|
44
|
+
"@atlaskit/theme": "^26.0.0",
|
|
45
|
+
"@atlaskit/tokens": "^14.0.0",
|
|
46
|
+
"@atlaskit/top-layer": "^1.0.0",
|
|
47
|
+
"@atlaskit/visually-hidden": "^4.0.0",
|
|
48
48
|
"@babel/runtime": "^7.0.0",
|
|
49
49
|
"@compiled/react": "^0.20.0",
|
|
50
50
|
"bind-event-listener": "^3.0.0"
|
|
@@ -57,19 +57,19 @@
|
|
|
57
57
|
"@af/accessibility-testing": "workspace:^",
|
|
58
58
|
"@af/integration-testing": "workspace:^",
|
|
59
59
|
"@af/visual-regression": "workspace:^",
|
|
60
|
-
"@atlaskit/app-provider": "^
|
|
61
|
-
"@atlaskit/atlassian-navigation": "^
|
|
62
|
-
"@atlaskit/avatar": "^
|
|
63
|
-
"@atlaskit/checkbox": "^
|
|
64
|
-
"@atlaskit/docs": "^
|
|
65
|
-
"@atlaskit/form": "^
|
|
66
|
-
"@atlaskit/heading": "^
|
|
67
|
-
"@atlaskit/link": "^
|
|
68
|
-
"@atlaskit/lozenge": "^
|
|
69
|
-
"@atlaskit/modal-dialog": "^
|
|
70
|
-
"@atlaskit/section-message": "^
|
|
71
|
-
"@atlaskit/textfield": "^
|
|
72
|
-
"@atlaskit/toggle": "^
|
|
60
|
+
"@atlaskit/app-provider": "^5.0.0",
|
|
61
|
+
"@atlaskit/atlassian-navigation": "^6.0.0",
|
|
62
|
+
"@atlaskit/avatar": "^26.0.0",
|
|
63
|
+
"@atlaskit/checkbox": "^18.0.0",
|
|
64
|
+
"@atlaskit/docs": "^12.0.0",
|
|
65
|
+
"@atlaskit/form": "^16.0.0",
|
|
66
|
+
"@atlaskit/heading": "^6.0.0",
|
|
67
|
+
"@atlaskit/link": "^4.0.0",
|
|
68
|
+
"@atlaskit/lozenge": "^14.0.0",
|
|
69
|
+
"@atlaskit/modal-dialog": "^16.0.0",
|
|
70
|
+
"@atlaskit/section-message": "^9.0.0",
|
|
71
|
+
"@atlaskit/textfield": "^9.0.0",
|
|
72
|
+
"@atlaskit/toggle": "^17.0.0",
|
|
73
73
|
"@atlassian/a11y-jest-testing": "^0.12.0",
|
|
74
74
|
"@atlassian/feature-flags-test-utils": "^1.1.0",
|
|
75
75
|
"@atlassian/react-compiler-gating": "workspace:^",
|
|
@@ -79,7 +79,8 @@
|
|
|
79
79
|
"jest-in-case": "^1.0.2",
|
|
80
80
|
"raf-stub": "^2.0.1",
|
|
81
81
|
"react": "^18.2.0",
|
|
82
|
-
"react-dom": "^18.2.0"
|
|
82
|
+
"react-dom": "^18.2.0",
|
|
83
|
+
"tiny-invariant": "^1.2.0"
|
|
83
84
|
},
|
|
84
85
|
"keywords": [
|
|
85
86
|
"atlaskit",
|
|
@@ -129,13 +130,5 @@
|
|
|
129
130
|
"deprecation": "no-deprecated-imports"
|
|
130
131
|
}
|
|
131
132
|
},
|
|
132
|
-
"typesVersions": {
|
|
133
|
-
">=4.5 <4.9": {
|
|
134
|
-
"*": [
|
|
135
|
-
"dist/types-ts4.5/*",
|
|
136
|
-
"dist/types-ts4.5/index.d.ts"
|
|
137
|
-
]
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
133
|
"homepage": "https://atlassian.design/components/dropdown-menu/"
|
|
141
134
|
}
|