@khanacademy/wonder-blocks-popover 3.2.8 → 3.2.10
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 +14 -0
- package/dist/components/popover.d.ts +16 -0
- package/dist/es/index.js +82 -55
- package/dist/index.js +82 -55
- package/package.json +2 -2
- package/src/components/__tests__/popover.test.tsx +43 -5
- package/src/components/popover-dialog.tsx +2 -0
- package/src/components/popover.tsx +83 -31
- package/tsconfig-build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-popover
|
|
2
2
|
|
|
3
|
+
## 3.2.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 68dd6059: adds optional aria label for popover
|
|
8
|
+
- Updated dependencies [be540444]
|
|
9
|
+
- @khanacademy/wonder-blocks-tooltip@2.3.8
|
|
10
|
+
|
|
11
|
+
## 3.2.9
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 47d680f4: Removed usage of focus management in Popover component, in favor of a similar implementation that won't override custom keyboard navigation.
|
|
16
|
+
|
|
3
17
|
## 3.2.8
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -81,6 +81,20 @@ type Props = AriaProps & Readonly<{
|
|
|
81
81
|
* Whether to show the popover tail or not. Defaults to true.
|
|
82
82
|
*/
|
|
83
83
|
showTail: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Optional property to enable the portal functionality of popover.
|
|
86
|
+
* This is very handy in cases where the Popover can't be easily
|
|
87
|
+
* injected into the DOM structure and requires portaling to
|
|
88
|
+
* the trigger location.
|
|
89
|
+
*
|
|
90
|
+
* Set to "true" by default.
|
|
91
|
+
*
|
|
92
|
+
* CAUTION: Turning off portal could cause some clipping issues
|
|
93
|
+
* especially around legacy code with usage of z-indexing,
|
|
94
|
+
* Use caution when turning this functionality off and ensure
|
|
95
|
+
* your content does not get clipped or hidden.
|
|
96
|
+
*/
|
|
97
|
+
portal?: boolean;
|
|
84
98
|
}>;
|
|
85
99
|
type State = Readonly<{
|
|
86
100
|
/**
|
|
@@ -99,6 +113,7 @@ type State = Readonly<{
|
|
|
99
113
|
type DefaultProps = Readonly<{
|
|
100
114
|
placement: Props["placement"];
|
|
101
115
|
showTail: Props["showTail"];
|
|
116
|
+
portal: Props["portal"];
|
|
102
117
|
}>;
|
|
103
118
|
/**
|
|
104
119
|
* Popovers provide additional information that is related to a particular
|
|
@@ -151,6 +166,7 @@ export default class Popover extends React.Component<Props, State> {
|
|
|
151
166
|
renderContent(uniqueId: string): PopoverContents;
|
|
152
167
|
renderPopper(uniqueId: string): React.ReactNode;
|
|
153
168
|
getHost(): Element | null | undefined;
|
|
169
|
+
renderPortal(uniqueId: string, opened: boolean): React.ReactNode;
|
|
154
170
|
render(): React.ReactNode;
|
|
155
171
|
}
|
|
156
172
|
export {};
|
package/dist/es/index.js
CHANGED
|
@@ -72,11 +72,13 @@ class PopoverDialog extends React.Component {
|
|
|
72
72
|
style,
|
|
73
73
|
showTail,
|
|
74
74
|
"aria-describedby": ariaDescribedby,
|
|
75
|
-
"aria-labelledby": ariaLabelledBy
|
|
75
|
+
"aria-labelledby": ariaLabelledBy,
|
|
76
|
+
"aria-label": ariaLabel
|
|
76
77
|
} = this.props;
|
|
77
78
|
const contentProps = children.props;
|
|
78
79
|
const color = contentProps.emphasized ? "blue" : contentProps.color;
|
|
79
80
|
return React.createElement(React.Fragment, null, React.createElement(View, {
|
|
81
|
+
"aria-label": ariaLabel,
|
|
80
82
|
"aria-describedby": ariaDescribedby,
|
|
81
83
|
"aria-labelledby": ariaLabelledBy,
|
|
82
84
|
id: id,
|
|
@@ -122,6 +124,49 @@ function isFocusable(element) {
|
|
|
122
124
|
return element.matches(FOCUSABLE_ELEMENTS);
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
class PopoverEventListener extends React.Component {
|
|
128
|
+
constructor(...args) {
|
|
129
|
+
super(...args);
|
|
130
|
+
this.state = {
|
|
131
|
+
isFirstClick: true
|
|
132
|
+
};
|
|
133
|
+
this._handleKeyup = e => {
|
|
134
|
+
if (e.key === "Escape") {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
this.props.onClose(true);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
this._handleClick = e => {
|
|
141
|
+
var _this$props$contentRe;
|
|
142
|
+
if (this.state.isFirstClick) {
|
|
143
|
+
this.setState({
|
|
144
|
+
isFirstClick: false
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const node = ReactDOM.findDOMNode((_this$props$contentRe = this.props.contentRef) == null ? void 0 : _this$props$contentRe.current);
|
|
149
|
+
if (node && !node.contains(e.target)) {
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
e.stopPropagation();
|
|
152
|
+
const shouldReturnFocus = !isFocusable(e.target);
|
|
153
|
+
this.props.onClose(shouldReturnFocus);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
componentDidMount() {
|
|
158
|
+
window.addEventListener("keyup", this._handleKeyup);
|
|
159
|
+
window.addEventListener("click", this._handleClick);
|
|
160
|
+
}
|
|
161
|
+
componentWillUnmount() {
|
|
162
|
+
window.removeEventListener("keyup", this._handleKeyup);
|
|
163
|
+
window.removeEventListener("click", this._handleClick);
|
|
164
|
+
}
|
|
165
|
+
render() {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
125
170
|
class InitialFocus extends React.Component {
|
|
126
171
|
constructor(...args) {
|
|
127
172
|
super(...args);
|
|
@@ -327,49 +372,6 @@ class FocusManager extends React.Component {
|
|
|
327
372
|
}
|
|
328
373
|
}
|
|
329
374
|
|
|
330
|
-
class PopoverEventListener extends React.Component {
|
|
331
|
-
constructor(...args) {
|
|
332
|
-
super(...args);
|
|
333
|
-
this.state = {
|
|
334
|
-
isFirstClick: true
|
|
335
|
-
};
|
|
336
|
-
this._handleKeyup = e => {
|
|
337
|
-
if (e.key === "Escape") {
|
|
338
|
-
e.preventDefault();
|
|
339
|
-
e.stopPropagation();
|
|
340
|
-
this.props.onClose(true);
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
this._handleClick = e => {
|
|
344
|
-
var _this$props$contentRe;
|
|
345
|
-
if (this.state.isFirstClick) {
|
|
346
|
-
this.setState({
|
|
347
|
-
isFirstClick: false
|
|
348
|
-
});
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const node = ReactDOM.findDOMNode((_this$props$contentRe = this.props.contentRef) == null ? void 0 : _this$props$contentRe.current);
|
|
352
|
-
if (node && !node.contains(e.target)) {
|
|
353
|
-
e.preventDefault();
|
|
354
|
-
e.stopPropagation();
|
|
355
|
-
const shouldReturnFocus = !isFocusable(e.target);
|
|
356
|
-
this.props.onClose(shouldReturnFocus);
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
componentDidMount() {
|
|
361
|
-
window.addEventListener("keyup", this._handleKeyup);
|
|
362
|
-
window.addEventListener("click", this._handleClick);
|
|
363
|
-
}
|
|
364
|
-
componentWillUnmount() {
|
|
365
|
-
window.removeEventListener("keyup", this._handleKeyup);
|
|
366
|
-
window.removeEventListener("click", this._handleClick);
|
|
367
|
-
}
|
|
368
|
-
render() {
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
375
|
class Popover extends React.Component {
|
|
374
376
|
constructor(...args) {
|
|
375
377
|
super(...args);
|
|
@@ -443,30 +445,55 @@ class Popover extends React.Component {
|
|
|
443
445
|
const {
|
|
444
446
|
initialFocusId,
|
|
445
447
|
placement,
|
|
446
|
-
showTail
|
|
448
|
+
showTail,
|
|
449
|
+
portal,
|
|
450
|
+
"aria-label": ariaLabel
|
|
447
451
|
} = this.props;
|
|
448
452
|
const {
|
|
449
453
|
anchorElement
|
|
450
454
|
} = this.state;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}, React.createElement(TooltipPopper, {
|
|
455
|
+
const ariaDescribedBy = ariaLabel ? undefined : `${uniqueId}-content`;
|
|
456
|
+
const ariaLabelledBy = ariaLabel ? undefined : `${uniqueId}-title`;
|
|
457
|
+
const popperContent = React.createElement(TooltipPopper, {
|
|
455
458
|
anchorElement: anchorElement,
|
|
456
459
|
placement: placement
|
|
457
460
|
}, props => React.createElement(PopoverDialog, _extends({}, props, {
|
|
458
|
-
"aria-
|
|
459
|
-
"aria-
|
|
461
|
+
"aria-label": ariaLabel,
|
|
462
|
+
"aria-describedby": ariaDescribedBy,
|
|
463
|
+
"aria-labelledby": ariaLabelledBy,
|
|
460
464
|
id: uniqueId,
|
|
461
465
|
onUpdate: placement => this.setState({
|
|
462
466
|
placement
|
|
463
467
|
}),
|
|
464
468
|
showTail: showTail
|
|
465
|
-
}), this.renderContent(uniqueId)))
|
|
469
|
+
}), this.renderContent(uniqueId)));
|
|
470
|
+
if (portal) {
|
|
471
|
+
return React.createElement(FocusManager, {
|
|
472
|
+
anchorElement: anchorElement,
|
|
473
|
+
initialFocusId: initialFocusId
|
|
474
|
+
}, popperContent);
|
|
475
|
+
} else {
|
|
476
|
+
return React.createElement(InitialFocus, {
|
|
477
|
+
initialFocusId: initialFocusId
|
|
478
|
+
}, popperContent);
|
|
479
|
+
}
|
|
466
480
|
}
|
|
467
481
|
getHost() {
|
|
468
482
|
return maybeGetPortalMountedModalHostElement(this.state.anchorElement) || document.body;
|
|
469
483
|
}
|
|
484
|
+
renderPortal(uniqueId, opened) {
|
|
485
|
+
if (!opened) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
const {
|
|
489
|
+
portal
|
|
490
|
+
} = this.props;
|
|
491
|
+
const popperHost = this.getHost();
|
|
492
|
+
if (portal && popperHost) {
|
|
493
|
+
return ReactDOM.createPortal(this.renderPopper(uniqueId), popperHost);
|
|
494
|
+
}
|
|
495
|
+
return this.renderPopper(uniqueId);
|
|
496
|
+
}
|
|
470
497
|
render() {
|
|
471
498
|
const {
|
|
472
499
|
children,
|
|
@@ -477,7 +504,6 @@ class Popover extends React.Component {
|
|
|
477
504
|
opened,
|
|
478
505
|
placement
|
|
479
506
|
} = this.state;
|
|
480
|
-
const popperHost = this.getHost();
|
|
481
507
|
return React.createElement(PopoverContext.Provider, {
|
|
482
508
|
value: {
|
|
483
509
|
close: this.handleClose,
|
|
@@ -492,7 +518,7 @@ class Popover extends React.Component {
|
|
|
492
518
|
"aria-controls": uniqueId,
|
|
493
519
|
"aria-expanded": opened ? "true" : "false",
|
|
494
520
|
onClick: this.handleOpen
|
|
495
|
-
}, children),
|
|
521
|
+
}, children), this.renderPortal(uniqueId, opened))), dismissEnabled && opened && React.createElement(PopoverEventListener, {
|
|
496
522
|
onClose: this.handleClose,
|
|
497
523
|
contentRef: this.contentRef
|
|
498
524
|
}));
|
|
@@ -500,7 +526,8 @@ class Popover extends React.Component {
|
|
|
500
526
|
}
|
|
501
527
|
Popover.defaultProps = {
|
|
502
528
|
placement: "top",
|
|
503
|
-
showTail: true
|
|
529
|
+
showTail: true,
|
|
530
|
+
portal: true
|
|
504
531
|
};
|
|
505
532
|
|
|
506
533
|
class CloseButton extends React.Component {
|
package/dist/index.js
CHANGED
|
@@ -102,11 +102,13 @@ class PopoverDialog extends React__namespace.Component {
|
|
|
102
102
|
style,
|
|
103
103
|
showTail,
|
|
104
104
|
"aria-describedby": ariaDescribedby,
|
|
105
|
-
"aria-labelledby": ariaLabelledBy
|
|
105
|
+
"aria-labelledby": ariaLabelledBy,
|
|
106
|
+
"aria-label": ariaLabel
|
|
106
107
|
} = this.props;
|
|
107
108
|
const contentProps = children.props;
|
|
108
109
|
const color = contentProps.emphasized ? "blue" : contentProps.color;
|
|
109
110
|
return React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement(wonderBlocksCore.View, {
|
|
111
|
+
"aria-label": ariaLabel,
|
|
110
112
|
"aria-describedby": ariaDescribedby,
|
|
111
113
|
"aria-labelledby": ariaLabelledBy,
|
|
112
114
|
id: id,
|
|
@@ -152,6 +154,49 @@ function isFocusable(element) {
|
|
|
152
154
|
return element.matches(FOCUSABLE_ELEMENTS);
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
class PopoverEventListener extends React__namespace.Component {
|
|
158
|
+
constructor(...args) {
|
|
159
|
+
super(...args);
|
|
160
|
+
this.state = {
|
|
161
|
+
isFirstClick: true
|
|
162
|
+
};
|
|
163
|
+
this._handleKeyup = e => {
|
|
164
|
+
if (e.key === "Escape") {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
e.stopPropagation();
|
|
167
|
+
this.props.onClose(true);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
this._handleClick = e => {
|
|
171
|
+
var _this$props$contentRe;
|
|
172
|
+
if (this.state.isFirstClick) {
|
|
173
|
+
this.setState({
|
|
174
|
+
isFirstClick: false
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const node = ReactDOM__namespace.findDOMNode((_this$props$contentRe = this.props.contentRef) == null ? void 0 : _this$props$contentRe.current);
|
|
179
|
+
if (node && !node.contains(e.target)) {
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
e.stopPropagation();
|
|
182
|
+
const shouldReturnFocus = !isFocusable(e.target);
|
|
183
|
+
this.props.onClose(shouldReturnFocus);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
componentDidMount() {
|
|
188
|
+
window.addEventListener("keyup", this._handleKeyup);
|
|
189
|
+
window.addEventListener("click", this._handleClick);
|
|
190
|
+
}
|
|
191
|
+
componentWillUnmount() {
|
|
192
|
+
window.removeEventListener("keyup", this._handleKeyup);
|
|
193
|
+
window.removeEventListener("click", this._handleClick);
|
|
194
|
+
}
|
|
195
|
+
render() {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
155
200
|
class InitialFocus extends React__namespace.Component {
|
|
156
201
|
constructor(...args) {
|
|
157
202
|
super(...args);
|
|
@@ -357,49 +402,6 @@ class FocusManager extends React__namespace.Component {
|
|
|
357
402
|
}
|
|
358
403
|
}
|
|
359
404
|
|
|
360
|
-
class PopoverEventListener extends React__namespace.Component {
|
|
361
|
-
constructor(...args) {
|
|
362
|
-
super(...args);
|
|
363
|
-
this.state = {
|
|
364
|
-
isFirstClick: true
|
|
365
|
-
};
|
|
366
|
-
this._handleKeyup = e => {
|
|
367
|
-
if (e.key === "Escape") {
|
|
368
|
-
e.preventDefault();
|
|
369
|
-
e.stopPropagation();
|
|
370
|
-
this.props.onClose(true);
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
this._handleClick = e => {
|
|
374
|
-
var _this$props$contentRe;
|
|
375
|
-
if (this.state.isFirstClick) {
|
|
376
|
-
this.setState({
|
|
377
|
-
isFirstClick: false
|
|
378
|
-
});
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
const node = ReactDOM__namespace.findDOMNode((_this$props$contentRe = this.props.contentRef) == null ? void 0 : _this$props$contentRe.current);
|
|
382
|
-
if (node && !node.contains(e.target)) {
|
|
383
|
-
e.preventDefault();
|
|
384
|
-
e.stopPropagation();
|
|
385
|
-
const shouldReturnFocus = !isFocusable(e.target);
|
|
386
|
-
this.props.onClose(shouldReturnFocus);
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
componentDidMount() {
|
|
391
|
-
window.addEventListener("keyup", this._handleKeyup);
|
|
392
|
-
window.addEventListener("click", this._handleClick);
|
|
393
|
-
}
|
|
394
|
-
componentWillUnmount() {
|
|
395
|
-
window.removeEventListener("keyup", this._handleKeyup);
|
|
396
|
-
window.removeEventListener("click", this._handleClick);
|
|
397
|
-
}
|
|
398
|
-
render() {
|
|
399
|
-
return null;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
405
|
class Popover extends React__namespace.Component {
|
|
404
406
|
constructor(...args) {
|
|
405
407
|
super(...args);
|
|
@@ -473,30 +475,55 @@ class Popover extends React__namespace.Component {
|
|
|
473
475
|
const {
|
|
474
476
|
initialFocusId,
|
|
475
477
|
placement,
|
|
476
|
-
showTail
|
|
478
|
+
showTail,
|
|
479
|
+
portal,
|
|
480
|
+
"aria-label": ariaLabel
|
|
477
481
|
} = this.props;
|
|
478
482
|
const {
|
|
479
483
|
anchorElement
|
|
480
484
|
} = this.state;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}, React__namespace.createElement(wonderBlocksTooltip.TooltipPopper, {
|
|
485
|
+
const ariaDescribedBy = ariaLabel ? undefined : `${uniqueId}-content`;
|
|
486
|
+
const ariaLabelledBy = ariaLabel ? undefined : `${uniqueId}-title`;
|
|
487
|
+
const popperContent = React__namespace.createElement(wonderBlocksTooltip.TooltipPopper, {
|
|
485
488
|
anchorElement: anchorElement,
|
|
486
489
|
placement: placement
|
|
487
490
|
}, props => React__namespace.createElement(PopoverDialog, _extends__default["default"]({}, props, {
|
|
488
|
-
"aria-
|
|
489
|
-
"aria-
|
|
491
|
+
"aria-label": ariaLabel,
|
|
492
|
+
"aria-describedby": ariaDescribedBy,
|
|
493
|
+
"aria-labelledby": ariaLabelledBy,
|
|
490
494
|
id: uniqueId,
|
|
491
495
|
onUpdate: placement => this.setState({
|
|
492
496
|
placement
|
|
493
497
|
}),
|
|
494
498
|
showTail: showTail
|
|
495
|
-
}), this.renderContent(uniqueId)))
|
|
499
|
+
}), this.renderContent(uniqueId)));
|
|
500
|
+
if (portal) {
|
|
501
|
+
return React__namespace.createElement(FocusManager, {
|
|
502
|
+
anchorElement: anchorElement,
|
|
503
|
+
initialFocusId: initialFocusId
|
|
504
|
+
}, popperContent);
|
|
505
|
+
} else {
|
|
506
|
+
return React__namespace.createElement(InitialFocus, {
|
|
507
|
+
initialFocusId: initialFocusId
|
|
508
|
+
}, popperContent);
|
|
509
|
+
}
|
|
496
510
|
}
|
|
497
511
|
getHost() {
|
|
498
512
|
return wonderBlocksModal.maybeGetPortalMountedModalHostElement(this.state.anchorElement) || document.body;
|
|
499
513
|
}
|
|
514
|
+
renderPortal(uniqueId, opened) {
|
|
515
|
+
if (!opened) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
const {
|
|
519
|
+
portal
|
|
520
|
+
} = this.props;
|
|
521
|
+
const popperHost = this.getHost();
|
|
522
|
+
if (portal && popperHost) {
|
|
523
|
+
return ReactDOM__namespace.createPortal(this.renderPopper(uniqueId), popperHost);
|
|
524
|
+
}
|
|
525
|
+
return this.renderPopper(uniqueId);
|
|
526
|
+
}
|
|
500
527
|
render() {
|
|
501
528
|
const {
|
|
502
529
|
children,
|
|
@@ -507,7 +534,6 @@ class Popover extends React__namespace.Component {
|
|
|
507
534
|
opened,
|
|
508
535
|
placement
|
|
509
536
|
} = this.state;
|
|
510
|
-
const popperHost = this.getHost();
|
|
511
537
|
return React__namespace.createElement(PopoverContext.Provider, {
|
|
512
538
|
value: {
|
|
513
539
|
close: this.handleClose,
|
|
@@ -522,7 +548,7 @@ class Popover extends React__namespace.Component {
|
|
|
522
548
|
"aria-controls": uniqueId,
|
|
523
549
|
"aria-expanded": opened ? "true" : "false",
|
|
524
550
|
onClick: this.handleOpen
|
|
525
|
-
}, children),
|
|
551
|
+
}, children), this.renderPortal(uniqueId, opened))), dismissEnabled && opened && React__namespace.createElement(PopoverEventListener, {
|
|
526
552
|
onClose: this.handleClose,
|
|
527
553
|
contentRef: this.contentRef
|
|
528
554
|
}));
|
|
@@ -530,7 +556,8 @@ class Popover extends React__namespace.Component {
|
|
|
530
556
|
}
|
|
531
557
|
Popover.defaultProps = {
|
|
532
558
|
placement: "top",
|
|
533
|
-
showTail: true
|
|
559
|
+
showTail: true,
|
|
560
|
+
portal: true
|
|
534
561
|
};
|
|
535
562
|
|
|
536
563
|
class CloseButton extends React__namespace.Component {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-popover",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.10",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@khanacademy/wonder-blocks-icon-button": "^5.3.3",
|
|
21
21
|
"@khanacademy/wonder-blocks-modal": "^5.1.8",
|
|
22
22
|
"@khanacademy/wonder-blocks-tokens": "^1.3.1",
|
|
23
|
-
"@khanacademy/wonder-blocks-tooltip": "^2.3.
|
|
23
|
+
"@khanacademy/wonder-blocks-tooltip": "^2.3.8",
|
|
24
24
|
"@khanacademy/wonder-blocks-typography": "^2.1.14"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
@@ -583,6 +583,40 @@ describe("Popover", () => {
|
|
|
583
583
|
).toBeInTheDocument();
|
|
584
584
|
});
|
|
585
585
|
|
|
586
|
+
it("should announce a popover correctly by reading the aria label", async () => {
|
|
587
|
+
// Arrange
|
|
588
|
+
render(
|
|
589
|
+
<Popover
|
|
590
|
+
onClose={jest.fn()}
|
|
591
|
+
aria-label="Popover Aria Label"
|
|
592
|
+
content={
|
|
593
|
+
<PopoverContentCore>
|
|
594
|
+
<button data-close-button onClick={close}>
|
|
595
|
+
Close Popover
|
|
596
|
+
</button>
|
|
597
|
+
</PopoverContentCore>
|
|
598
|
+
}
|
|
599
|
+
>
|
|
600
|
+
<Button>Open default popover</Button>
|
|
601
|
+
</Popover>,
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
// Act
|
|
605
|
+
// Open the popover
|
|
606
|
+
const openButton = await screen.findByRole("button", {
|
|
607
|
+
name: "Open default popover",
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
await userEvent.click(openButton);
|
|
611
|
+
const popover = await screen.findByRole("dialog");
|
|
612
|
+
|
|
613
|
+
// Assert
|
|
614
|
+
|
|
615
|
+
expect(popover).toHaveAttribute("aria-label", "Popover Aria Label");
|
|
616
|
+
expect(popover).not.toHaveAttribute("aria-labelledby");
|
|
617
|
+
expect(popover).not.toHaveAttribute("aria-describedby");
|
|
618
|
+
});
|
|
619
|
+
|
|
586
620
|
it("should correctly describe the popover content core's aria label", async () => {
|
|
587
621
|
// Arrange
|
|
588
622
|
render(
|
|
@@ -621,14 +655,15 @@ describe("Popover", () => {
|
|
|
621
655
|
});
|
|
622
656
|
});
|
|
623
657
|
|
|
624
|
-
describe("keyboard navigation", () => {
|
|
625
|
-
it(
|
|
658
|
+
describe.each([true, false])("keyboard navigation", (portal) => {
|
|
659
|
+
it(`when portal=${portal}, should move focus to the first focusable element after popover is open`, async () => {
|
|
626
660
|
// Arrange
|
|
627
661
|
render(
|
|
628
662
|
<>
|
|
629
663
|
<Button>Prev focusable element outside</Button>
|
|
630
664
|
<Popover
|
|
631
665
|
onClose={jest.fn()}
|
|
666
|
+
portal={portal}
|
|
632
667
|
content={
|
|
633
668
|
<PopoverContent
|
|
634
669
|
title="Popover title"
|
|
@@ -667,13 +702,14 @@ describe("Popover", () => {
|
|
|
667
702
|
).toHaveFocus();
|
|
668
703
|
});
|
|
669
704
|
|
|
670
|
-
it(
|
|
705
|
+
it(`when portal=${portal}, should allow flowing focus correctly even if the popover remains open`, async () => {
|
|
671
706
|
// Arrange
|
|
672
707
|
render(
|
|
673
708
|
<>
|
|
674
709
|
<Button>Prev focusable element outside</Button>
|
|
675
710
|
<Popover
|
|
676
711
|
onClose={jest.fn()}
|
|
712
|
+
portal={portal}
|
|
677
713
|
content={
|
|
678
714
|
<PopoverContent
|
|
679
715
|
title="Popover title"
|
|
@@ -709,13 +745,14 @@ describe("Popover", () => {
|
|
|
709
745
|
).toHaveFocus();
|
|
710
746
|
});
|
|
711
747
|
|
|
712
|
-
it(
|
|
748
|
+
it(`when portal=${portal}, should allow circular navigation when the popover is open`, async () => {
|
|
713
749
|
// Arrange
|
|
714
750
|
render(
|
|
715
751
|
<>
|
|
716
752
|
<Button>Prev focusable element outside</Button>
|
|
717
753
|
<Popover
|
|
718
754
|
onClose={jest.fn()}
|
|
755
|
+
portal={portal}
|
|
719
756
|
content={
|
|
720
757
|
<PopoverContent
|
|
721
758
|
title="Popover title"
|
|
@@ -757,13 +794,14 @@ describe("Popover", () => {
|
|
|
757
794
|
).toHaveFocus();
|
|
758
795
|
});
|
|
759
796
|
|
|
760
|
-
it(
|
|
797
|
+
it(`when portal=${portal}, should allow navigating backwards when the popover is open`, async () => {
|
|
761
798
|
// Arrange
|
|
762
799
|
render(
|
|
763
800
|
<>
|
|
764
801
|
<Button>Prev focusable element outside</Button>
|
|
765
802
|
<Popover
|
|
766
803
|
onClose={jest.fn()}
|
|
804
|
+
portal={portal}
|
|
767
805
|
content={
|
|
768
806
|
<PopoverContent
|
|
769
807
|
title="Popover title"
|
|
@@ -78,6 +78,7 @@ export default class PopoverDialog extends React.Component<Props> {
|
|
|
78
78
|
showTail,
|
|
79
79
|
"aria-describedby": ariaDescribedby,
|
|
80
80
|
"aria-labelledby": ariaLabelledBy,
|
|
81
|
+
"aria-label": ariaLabel,
|
|
81
82
|
} = this.props;
|
|
82
83
|
|
|
83
84
|
const contentProps = children.props as any;
|
|
@@ -90,6 +91,7 @@ export default class PopoverDialog extends React.Component<Props> {
|
|
|
90
91
|
return (
|
|
91
92
|
<React.Fragment>
|
|
92
93
|
<View
|
|
94
|
+
aria-label={ariaLabel}
|
|
93
95
|
aria-describedby={ariaDescribedby}
|
|
94
96
|
aria-labelledby={ariaLabelledBy}
|
|
95
97
|
id={id}
|