@atlaskit/avatar-group 12.10.12 → 12.11.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 +12 -0
- package/__tests__/playwright/top-layer-focus.spec.tsx +78 -0
- package/dist/cjs/components/avatar-group-top-layer.js +15 -25
- package/dist/es2019/components/avatar-group-top-layer.js +15 -26
- package/dist/esm/components/avatar-group-top-layer.js +15 -25
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @atlaskit/avatar-group
|
|
2
2
|
|
|
3
|
+
## 12.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`032135d12186b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/032135d12186b) -
|
|
8
|
+
Top-layer: fix initial focus regressions on the top-layer adapters for `@atlaskit/avatar-group`
|
|
9
|
+
and `@atlaskit/popup`. Behind the `platform-dst-top-layer` feature gate.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies
|
|
14
|
+
|
|
3
15
|
## 12.10.12
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { expect, test } from '@af/integration-testing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Avatar group: focus contract on the top-layer code path.
|
|
5
|
+
*
|
|
6
|
+
* `AvatarGroup` renders a "more" trigger that opens a `role="menu"` popover
|
|
7
|
+
* listing avatars beyond `maxCount`. The focus contract is:
|
|
8
|
+
*
|
|
9
|
+
* 1. Initial focus moves to the first item in the avatar list on open.
|
|
10
|
+
* 2. Escape closes the popover and restores focus to the more trigger.
|
|
11
|
+
* 3. ArrowDown moves focus between items within the menu.
|
|
12
|
+
*
|
|
13
|
+
* See: `platform/packages/design-system/top-layer/notes/architecture/focus.md`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const featureFlag = 'platform-dst-top-layer';
|
|
17
|
+
|
|
18
|
+
test.describe('Avatar group: top-layer focus contract', () => {
|
|
19
|
+
test('initial focus: focus moves to the first overflow item on open', async ({ page }) => {
|
|
20
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
21
|
+
'design-system',
|
|
22
|
+
'avatar-group',
|
|
23
|
+
'testing-top-layer-focus',
|
|
24
|
+
{ featureFlag },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const moreTrigger = page.getByTestId('avatar-group--overflow-menu--trigger');
|
|
28
|
+
await moreTrigger.click();
|
|
29
|
+
|
|
30
|
+
const menu = page.getByRole('menu');
|
|
31
|
+
await expect(menu).toBeVisible();
|
|
32
|
+
// avatar-group computes `maxAvatar = total > max ? max - 1 : max` and
|
|
33
|
+
// emits overflow items with testId `${testId}--avatar-group-item-${index + maxAvatar}`.
|
|
34
|
+
// With `data.length=6` and `maxCount=3`, `maxAvatar=2`, so the first
|
|
35
|
+
// overflow item is at index 2.
|
|
36
|
+
const firstItem = page.getByTestId('avatar-group--avatar-group-item-2');
|
|
37
|
+
await expect(firstItem).toBeFocused();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('focus restoration: Escape restores focus to the more trigger', async ({ page }) => {
|
|
41
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
42
|
+
'design-system',
|
|
43
|
+
'avatar-group',
|
|
44
|
+
'testing-top-layer-focus',
|
|
45
|
+
{ featureFlag },
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const moreTrigger = page.getByTestId('avatar-group--overflow-menu--trigger');
|
|
49
|
+
await moreTrigger.focus();
|
|
50
|
+
await page.keyboard.press('ArrowDown');
|
|
51
|
+
await expect(page.getByRole('menu')).toBeVisible();
|
|
52
|
+
|
|
53
|
+
await page.keyboard.press('Escape');
|
|
54
|
+
await expect(moreTrigger).toBeFocused();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('focus movement: ArrowDown moves focus between items within the menu', async ({ page }) => {
|
|
58
|
+
await page.visitExample<typeof import('../../examples/testing-top-layer-focus.tsx')>(
|
|
59
|
+
'design-system',
|
|
60
|
+
'avatar-group',
|
|
61
|
+
'testing-top-layer-focus',
|
|
62
|
+
{ featureFlag },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const moreTrigger = page.getByTestId('avatar-group--overflow-menu--trigger');
|
|
66
|
+
await moreTrigger.focus();
|
|
67
|
+
await page.keyboard.press('ArrowDown');
|
|
68
|
+
|
|
69
|
+
const menu = page.getByRole('menu');
|
|
70
|
+
await expect(menu).toBeVisible();
|
|
71
|
+
|
|
72
|
+
const items = menu.getByRole('menuitem');
|
|
73
|
+
await expect(items.first()).toBeFocused();
|
|
74
|
+
|
|
75
|
+
await page.keyboard.press('ArrowDown');
|
|
76
|
+
await expect(items.nth(1)).toBeFocused();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -11,7 +11,6 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
|
|
|
11
11
|
var _react = _interopRequireWildcard(require("react"));
|
|
12
12
|
var _bindEventListener = require("bind-event-listener");
|
|
13
13
|
var _keycodes = require("@atlaskit/ds-lib/keycodes");
|
|
14
|
-
var _useFocusEvent = _interopRequireDefault(require("@atlaskit/ds-lib/use-focus-event"));
|
|
15
14
|
var _menu = require("@atlaskit/menu");
|
|
16
15
|
var _animations = require("@atlaskit/top-layer/animations");
|
|
17
16
|
var _getAriaForTrigger = require("@atlaskit/top-layer/get-aria-for-trigger");
|
|
@@ -107,27 +106,27 @@ function MoreDropdownTopLayer(_ref) {
|
|
|
107
106
|
|
|
108
107
|
// ArrowDown-to-open: when the trigger is focused and the menu is closed,
|
|
109
108
|
// pressing ArrowDown opens the menu (WAI-ARIA menu button pattern).
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
triggerFocusBind = _useFocus.bindFocus;
|
|
109
|
+
//
|
|
110
|
+
// Bind the keydown listener directly to the trigger element via its ref so
|
|
111
|
+
// that opening the menu does not trigger a focus-driven re-render of the
|
|
112
|
+
// host component, which would unmount and remount the menu items and drop
|
|
113
|
+
// menuitem focus.
|
|
116
114
|
(0, _react.useEffect)(function () {
|
|
117
|
-
|
|
115
|
+
var trigger = triggerRef.current;
|
|
116
|
+
if (!trigger || isOpen) {
|
|
118
117
|
return;
|
|
119
118
|
}
|
|
120
|
-
return (0, _bindEventListener.bind)(
|
|
119
|
+
return (0, _bindEventListener.bind)(trigger, {
|
|
121
120
|
type: 'keydown',
|
|
122
|
-
listener: function openOnArrowDown(
|
|
123
|
-
if (
|
|
121
|
+
listener: function openOnArrowDown(event) {
|
|
122
|
+
if (event.key === _keycodes.KEY_DOWN) {
|
|
124
123
|
// Prevent page scroll when opening the menu via ArrowDown.
|
|
125
|
-
|
|
126
|
-
handleTriggerClicked(
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
handleTriggerClicked(event);
|
|
127
126
|
}
|
|
128
127
|
}
|
|
129
128
|
});
|
|
130
|
-
}, [
|
|
129
|
+
}, [isOpen, handleTriggerClicked]);
|
|
131
130
|
var triggerAria = (0, _getAriaForTrigger.getAriaForTrigger)({
|
|
132
131
|
role: 'menu',
|
|
133
132
|
isOpen: isOpen,
|
|
@@ -136,20 +135,11 @@ function MoreDropdownTopLayer(_ref) {
|
|
|
136
135
|
var setTriggerRef = (0, _react.useCallback)(function (node) {
|
|
137
136
|
triggerRef.current = node;
|
|
138
137
|
}, []);
|
|
139
|
-
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null,
|
|
140
|
-
ref: triggerWrapperRef,
|
|
141
|
-
onFocus: triggerFocusBind.onFocus,
|
|
142
|
-
onBlur: triggerFocusBind.onBlur
|
|
143
|
-
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- display: contents is a layout-neutral wrapper; it cannot affect visual output.
|
|
144
|
-
,
|
|
145
|
-
style: {
|
|
146
|
-
display: 'contents'
|
|
147
|
-
}
|
|
148
|
-
}, renderMoreButton(_objectSpread(_objectSpread({
|
|
138
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, renderMoreButton(_objectSpread(_objectSpread({
|
|
149
139
|
ref: setTriggerRef
|
|
150
140
|
}, triggerAria), {}, {
|
|
151
141
|
onClick: handleTriggerClicked
|
|
152
|
-
}))
|
|
142
|
+
})), /*#__PURE__*/_react.default.createElement(_popover.Popover, {
|
|
153
143
|
ref: popoverRef,
|
|
154
144
|
id: popoverId,
|
|
155
145
|
role: "menu",
|
|
@@ -2,7 +2,6 @@ import _extends from "@babel/runtime/helpers/extends";
|
|
|
2
2
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
3
3
|
import { bind } from 'bind-event-listener';
|
|
4
4
|
import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
|
|
5
|
-
import useFocus from '@atlaskit/ds-lib/use-focus-event';
|
|
6
5
|
import { MenuGroup, Section } from '@atlaskit/menu';
|
|
7
6
|
import { slideAndFade } from '@atlaskit/top-layer/animations';
|
|
8
7
|
import { getAriaForTrigger } from '@atlaskit/top-layer/get-aria-for-trigger';
|
|
@@ -94,28 +93,27 @@ export function MoreDropdownTopLayer({
|
|
|
94
93
|
|
|
95
94
|
// ArrowDown-to-open: when the trigger is focused and the menu is closed,
|
|
96
95
|
// pressing ArrowDown opens the menu (WAI-ARIA menu button pattern).
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
bindFocus: triggerFocusBind
|
|
103
|
-
} = useFocus();
|
|
96
|
+
//
|
|
97
|
+
// Bind the keydown listener directly to the trigger element via its ref so
|
|
98
|
+
// that opening the menu does not trigger a focus-driven re-render of the
|
|
99
|
+
// host component, which would unmount and remount the menu items and drop
|
|
100
|
+
// menuitem focus.
|
|
104
101
|
useEffect(() => {
|
|
105
|
-
|
|
102
|
+
const trigger = triggerRef.current;
|
|
103
|
+
if (!trigger || isOpen) {
|
|
106
104
|
return;
|
|
107
105
|
}
|
|
108
|
-
return bind(
|
|
106
|
+
return bind(trigger, {
|
|
109
107
|
type: 'keydown',
|
|
110
|
-
listener: function openOnArrowDown(
|
|
111
|
-
if (
|
|
108
|
+
listener: function openOnArrowDown(event) {
|
|
109
|
+
if (event.key === KEY_DOWN) {
|
|
112
110
|
// Prevent page scroll when opening the menu via ArrowDown.
|
|
113
|
-
|
|
114
|
-
handleTriggerClicked(
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
handleTriggerClicked(event);
|
|
115
113
|
}
|
|
116
114
|
}
|
|
117
115
|
});
|
|
118
|
-
}, [
|
|
116
|
+
}, [isOpen, handleTriggerClicked]);
|
|
119
117
|
const triggerAria = getAriaForTrigger({
|
|
120
118
|
role: 'menu',
|
|
121
119
|
isOpen,
|
|
@@ -124,20 +122,11 @@ export function MoreDropdownTopLayer({
|
|
|
124
122
|
const setTriggerRef = useCallback(node => {
|
|
125
123
|
triggerRef.current = node;
|
|
126
124
|
}, []);
|
|
127
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null,
|
|
128
|
-
ref: triggerWrapperRef,
|
|
129
|
-
onFocus: triggerFocusBind.onFocus,
|
|
130
|
-
onBlur: triggerFocusBind.onBlur
|
|
131
|
-
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- display: contents is a layout-neutral wrapper; it cannot affect visual output.
|
|
132
|
-
,
|
|
133
|
-
style: {
|
|
134
|
-
display: 'contents'
|
|
135
|
-
}
|
|
136
|
-
}, renderMoreButton({
|
|
125
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, renderMoreButton({
|
|
137
126
|
ref: setTriggerRef,
|
|
138
127
|
...triggerAria,
|
|
139
128
|
onClick: handleTriggerClicked
|
|
140
|
-
})
|
|
129
|
+
}), /*#__PURE__*/React.createElement(Popover, {
|
|
141
130
|
ref: popoverRef,
|
|
142
131
|
id: popoverId,
|
|
143
132
|
role: "menu",
|
|
@@ -5,7 +5,6 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
5
5
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
6
6
|
import { bind } from 'bind-event-listener';
|
|
7
7
|
import { KEY_DOWN } from '@atlaskit/ds-lib/keycodes';
|
|
8
|
-
import useFocus from '@atlaskit/ds-lib/use-focus-event';
|
|
9
8
|
import { MenuGroup, Section } from '@atlaskit/menu';
|
|
10
9
|
import { slideAndFade } from '@atlaskit/top-layer/animations';
|
|
11
10
|
import { getAriaForTrigger } from '@atlaskit/top-layer/get-aria-for-trigger';
|
|
@@ -98,27 +97,27 @@ export function MoreDropdownTopLayer(_ref) {
|
|
|
98
97
|
|
|
99
98
|
// ArrowDown-to-open: when the trigger is focused and the menu is closed,
|
|
100
99
|
// pressing ArrowDown opens the menu (WAI-ARIA menu button pattern).
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
triggerFocusBind = _useFocus.bindFocus;
|
|
100
|
+
//
|
|
101
|
+
// Bind the keydown listener directly to the trigger element via its ref so
|
|
102
|
+
// that opening the menu does not trigger a focus-driven re-render of the
|
|
103
|
+
// host component, which would unmount and remount the menu items and drop
|
|
104
|
+
// menuitem focus.
|
|
107
105
|
useEffect(function () {
|
|
108
|
-
|
|
106
|
+
var trigger = triggerRef.current;
|
|
107
|
+
if (!trigger || isOpen) {
|
|
109
108
|
return;
|
|
110
109
|
}
|
|
111
|
-
return bind(
|
|
110
|
+
return bind(trigger, {
|
|
112
111
|
type: 'keydown',
|
|
113
|
-
listener: function openOnArrowDown(
|
|
114
|
-
if (
|
|
112
|
+
listener: function openOnArrowDown(event) {
|
|
113
|
+
if (event.key === KEY_DOWN) {
|
|
115
114
|
// Prevent page scroll when opening the menu via ArrowDown.
|
|
116
|
-
|
|
117
|
-
handleTriggerClicked(
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
handleTriggerClicked(event);
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
});
|
|
121
|
-
}, [
|
|
120
|
+
}, [isOpen, handleTriggerClicked]);
|
|
122
121
|
var triggerAria = getAriaForTrigger({
|
|
123
122
|
role: 'menu',
|
|
124
123
|
isOpen: isOpen,
|
|
@@ -127,20 +126,11 @@ export function MoreDropdownTopLayer(_ref) {
|
|
|
127
126
|
var setTriggerRef = useCallback(function (node) {
|
|
128
127
|
triggerRef.current = node;
|
|
129
128
|
}, []);
|
|
130
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null,
|
|
131
|
-
ref: triggerWrapperRef,
|
|
132
|
-
onFocus: triggerFocusBind.onFocus,
|
|
133
|
-
onBlur: triggerFocusBind.onBlur
|
|
134
|
-
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- display: contents is a layout-neutral wrapper; it cannot affect visual output.
|
|
135
|
-
,
|
|
136
|
-
style: {
|
|
137
|
-
display: 'contents'
|
|
138
|
-
}
|
|
139
|
-
}, renderMoreButton(_objectSpread(_objectSpread({
|
|
129
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, renderMoreButton(_objectSpread(_objectSpread({
|
|
140
130
|
ref: setTriggerRef
|
|
141
131
|
}, triggerAria), {}, {
|
|
142
132
|
onClick: handleTriggerClicked
|
|
143
|
-
}))
|
|
133
|
+
})), /*#__PURE__*/React.createElement(Popover, {
|
|
144
134
|
ref: popoverRef,
|
|
145
135
|
id: popoverId,
|
|
146
136
|
role: "menu",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/avatar-group",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.11.0",
|
|
4
4
|
"description": "An avatar group displays a number of avatars grouped together in a stack or grid.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@atlaskit/menu": "^8.5.0",
|
|
38
38
|
"@atlaskit/motion": "^6.2.0",
|
|
39
39
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
40
|
-
"@atlaskit/popup": "^4.
|
|
40
|
+
"@atlaskit/popup": "^4.26.0",
|
|
41
41
|
"@atlaskit/tokens": "^13.4.0",
|
|
42
42
|
"@atlaskit/tooltip": "^22.6.0",
|
|
43
43
|
"@atlaskit/top-layer": "^0.18.0",
|