@instructure/ui-a11y-utils 11.7.3-snapshot-7 → 11.7.3-snapshot-26
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 +5 -2
- package/es/FocusRegion.js +77 -82
- package/es/FocusRegionManager.js +118 -114
- package/es/KeyboardFocusRegion.js +63 -58
- package/es/ScreenReaderFocusRegion.js +28 -30
- package/lib/FocusRegion.js +79 -84
- package/lib/FocusRegionManager.js +120 -115
- package/lib/KeyboardFocusRegion.js +63 -58
- package/lib/ScreenReaderFocusRegion.js +28 -30
- package/package.json +10 -10
- package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [11.7.3-snapshot-
|
|
6
|
+
## [11.7.3-snapshot-26](https://github.com/instructure/instructure-ui/compare/v11.7.2...v11.7.3-snapshot-26) (2026-05-05)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **many:** update dependencies, remove lots of Babel plugins, remove Webpack 4 support ([f916fca](https://github.com/instructure/instructure-ui/commit/f916fcafdddcb2d7de401f93e8ff92cfdfa47bba))
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
|
package/es/FocusRegion.js
CHANGED
|
@@ -41,70 +41,19 @@ import { KeyboardFocusRegion } from "./KeyboardFocusRegion.js";
|
|
|
41
41
|
* @module FocusRegion
|
|
42
42
|
*/
|
|
43
43
|
class FocusRegion {
|
|
44
|
+
_contextElement = null;
|
|
45
|
+
_options;
|
|
46
|
+
_screenReaderFocusRegion;
|
|
47
|
+
_keyboardFocusRegion;
|
|
48
|
+
_id;
|
|
49
|
+
_mouseDownListener;
|
|
50
|
+
_clickListener;
|
|
51
|
+
_mouseUpListener;
|
|
52
|
+
_keyUpListener;
|
|
53
|
+
_active = false;
|
|
54
|
+
_documentClickTarget = null;
|
|
55
|
+
_contextContainsTarget = false;
|
|
44
56
|
constructor(element, options) {
|
|
45
|
-
this._contextElement = null;
|
|
46
|
-
this._options = void 0;
|
|
47
|
-
this._screenReaderFocusRegion = void 0;
|
|
48
|
-
this._keyboardFocusRegion = void 0;
|
|
49
|
-
this._id = void 0;
|
|
50
|
-
this._mouseDownListener = void 0;
|
|
51
|
-
this._clickListener = void 0;
|
|
52
|
-
this._mouseUpListener = void 0;
|
|
53
|
-
this._keyUpListener = void 0;
|
|
54
|
-
this._active = false;
|
|
55
|
-
this._documentClickTarget = null;
|
|
56
|
-
this._contextContainsTarget = false;
|
|
57
|
-
this.handleDismiss = (event, documentClick) => {
|
|
58
|
-
var _this$_options$onDism, _this$_options;
|
|
59
|
-
(_this$_options$onDism = (_this$_options = this._options).onDismiss) === null || _this$_options$onDism === void 0 ? void 0 : _this$_options$onDism.call(_this$_options, event, documentClick);
|
|
60
|
-
};
|
|
61
|
-
this.captureDocumentMousedown = event => {
|
|
62
|
-
this._documentClickTarget = event.target;
|
|
63
|
-
this._contextContainsTarget = contains(this._contextElement, this._documentClickTarget);
|
|
64
|
-
// Preemptively remove aria-hidden before focus moves to the clicked element
|
|
65
|
-
// outside the region, preventing the "aria-hidden on focused ancestor" warning.
|
|
66
|
-
if (!this._contextContainsTarget && this._options.shouldCloseOnDocumentClick) {
|
|
67
|
-
this._screenReaderFocusRegion.deactivate();
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
this.handleDocumentClick = event => {
|
|
71
|
-
// we used event.pointerType === 'mouse' here, but it is not supported in Safari
|
|
72
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType
|
|
73
|
-
// TODO: Check this in the future, because the linked Webkit bug is marked as fixed in 2025-09
|
|
74
|
-
if (this._options.shouldCloseOnDocumentClick && event.button === 0 && event.detail > 0 &&
|
|
75
|
-
// if event.detail is 0 then this is a keyboard and not a mouse press
|
|
76
|
-
!this._contextContainsTarget &&
|
|
77
|
-
//this prevents clicking on Tooltip from closing the parent dialog
|
|
78
|
-
!(canUseDOM && this._documentClickTarget instanceof HTMLElement && this._documentClickTarget.closest('[role="tooltip"]'))) {
|
|
79
|
-
this.handleDismiss(event, true);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
this.handleFrameClick = (event, frame) => {
|
|
83
|
-
if (!contains(this._contextElement, frame)) {
|
|
84
|
-
// dismiss if frame is not within the region
|
|
85
|
-
this.handleDismiss(event, true);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
this.handleKeyUp = event => {
|
|
89
|
-
if (this._options.shouldCloseOnEscape && event.keyCode === keycode.codes.esc && !event.defaultPrevented) {
|
|
90
|
-
var _ownerDocument;
|
|
91
|
-
// If a dialog contains an <input type="file"/> element and the user opens the file picker and closes it with the
|
|
92
|
-
// escape key then Firefox passes through that event which could close the parent dialog. This code prevents that
|
|
93
|
-
// from happening (listening for a `cancel` event doesn't seem to work in firefox)
|
|
94
|
-
const activeElement = (_ownerDocument = ownerDocument(this._contextElement)) === null || _ownerDocument === void 0 ? void 0 : _ownerDocument.activeElement;
|
|
95
|
-
const fileInputFocused = (activeElement === null || activeElement === void 0 ? void 0 : activeElement.tagName) === 'INPUT' && activeElement.type === 'file';
|
|
96
|
-
if (fileInputFocused) {
|
|
97
|
-
;
|
|
98
|
-
activeElement.blur();
|
|
99
|
-
} else {
|
|
100
|
-
//This should prevent a Tooltip from closing when inside of a Modal
|
|
101
|
-
if (this._options.isTooltip) {
|
|
102
|
-
event.stopPropagation();
|
|
103
|
-
}
|
|
104
|
-
this.handleDismiss(event);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
57
|
this._options = options || {
|
|
109
58
|
shouldCloseOnDocumentClick: true,
|
|
110
59
|
shouldCloseOnEscape: true,
|
|
@@ -132,6 +81,55 @@ class FocusRegion {
|
|
|
132
81
|
this._screenReaderFocusRegion.updateElement(element);
|
|
133
82
|
}
|
|
134
83
|
}
|
|
84
|
+
handleDismiss = (event, documentClick) => {
|
|
85
|
+
this._options.onDismiss?.(event, documentClick);
|
|
86
|
+
};
|
|
87
|
+
captureDocumentMousedown = event => {
|
|
88
|
+
this._documentClickTarget = event.target;
|
|
89
|
+
this._contextContainsTarget = contains(this._contextElement, this._documentClickTarget);
|
|
90
|
+
// Preemptively remove aria-hidden before focus moves to the clicked element
|
|
91
|
+
// outside the region, preventing the "aria-hidden on focused ancestor" warning.
|
|
92
|
+
if (!this._contextContainsTarget && this._options.shouldCloseOnDocumentClick) {
|
|
93
|
+
this._screenReaderFocusRegion.deactivate();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
handleDocumentClick = event => {
|
|
97
|
+
// we used event.pointerType === 'mouse' here, but it is not supported in Safari
|
|
98
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType
|
|
99
|
+
// TODO: Check this in the future, because the linked Webkit bug is marked as fixed in 2025-09
|
|
100
|
+
if (this._options.shouldCloseOnDocumentClick && event.button === 0 && event.detail > 0 &&
|
|
101
|
+
// if event.detail is 0 then this is a keyboard and not a mouse press
|
|
102
|
+
!this._contextContainsTarget &&
|
|
103
|
+
//this prevents clicking on Tooltip from closing the parent dialog
|
|
104
|
+
!(canUseDOM && this._documentClickTarget instanceof HTMLElement && this._documentClickTarget.closest('[role="tooltip"]'))) {
|
|
105
|
+
this.handleDismiss(event, true);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
handleFrameClick = (event, frame) => {
|
|
109
|
+
if (!contains(this._contextElement, frame)) {
|
|
110
|
+
// dismiss if frame is not within the region
|
|
111
|
+
this.handleDismiss(event, true);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
handleKeyUp = event => {
|
|
115
|
+
if (this._options.shouldCloseOnEscape && event.keyCode === keycode.codes.esc && !event.defaultPrevented) {
|
|
116
|
+
// If a dialog contains an <input type="file"/> element and the user opens the file picker and closes it with the
|
|
117
|
+
// escape key then Firefox passes through that event which could close the parent dialog. This code prevents that
|
|
118
|
+
// from happening (listening for a `cancel` event doesn't seem to work in firefox)
|
|
119
|
+
const activeElement = ownerDocument(this._contextElement)?.activeElement;
|
|
120
|
+
const fileInputFocused = activeElement?.tagName === 'INPUT' && activeElement.type === 'file';
|
|
121
|
+
if (fileInputFocused) {
|
|
122
|
+
;
|
|
123
|
+
activeElement.blur();
|
|
124
|
+
} else {
|
|
125
|
+
//This should prevent a Tooltip from closing when inside of a Modal
|
|
126
|
+
if (this._options.isTooltip) {
|
|
127
|
+
event.stopPropagation();
|
|
128
|
+
}
|
|
129
|
+
this.handleDismiss(event);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
135
133
|
get id() {
|
|
136
134
|
return this._id;
|
|
137
135
|
}
|
|
@@ -169,13 +167,12 @@ class FocusRegion {
|
|
|
169
167
|
}
|
|
170
168
|
});
|
|
171
169
|
} else {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
this.
|
|
177
|
-
|
|
178
|
-
this._mouseUpListener = void 0;
|
|
170
|
+
this._mouseDownListener?.remove();
|
|
171
|
+
this._mouseDownListener = undefined;
|
|
172
|
+
this._clickListener?.remove();
|
|
173
|
+
this._clickListener = undefined;
|
|
174
|
+
this._mouseUpListener?.remove();
|
|
175
|
+
this._mouseUpListener = undefined;
|
|
179
176
|
}
|
|
180
177
|
if (shouldCloseOnEscape) {
|
|
181
178
|
if (!this._keyUpListener) {
|
|
@@ -186,9 +183,8 @@ class FocusRegion {
|
|
|
186
183
|
this._keyUpListener = addEventListener(doc, 'keyup', this.handleKeyUp, useCapture);
|
|
187
184
|
}
|
|
188
185
|
} else {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this._keyUpListener = void 0;
|
|
186
|
+
this._keyUpListener?.remove();
|
|
187
|
+
this._keyUpListener = undefined;
|
|
192
188
|
}
|
|
193
189
|
}
|
|
194
190
|
activate() {
|
|
@@ -202,15 +198,14 @@ class FocusRegion {
|
|
|
202
198
|
deactivate({
|
|
203
199
|
keyboard = true
|
|
204
200
|
} = {}) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.
|
|
208
|
-
|
|
209
|
-
this.
|
|
210
|
-
|
|
211
|
-
this.
|
|
212
|
-
|
|
213
|
-
this._keyUpListener = void 0;
|
|
201
|
+
this._mouseDownListener?.remove();
|
|
202
|
+
this._mouseDownListener = undefined;
|
|
203
|
+
this._clickListener?.remove();
|
|
204
|
+
this._clickListener = undefined;
|
|
205
|
+
this._mouseUpListener?.remove();
|
|
206
|
+
this._mouseUpListener = undefined;
|
|
207
|
+
this._keyUpListener?.remove();
|
|
208
|
+
this._keyUpListener = undefined;
|
|
214
209
|
if (keyboard) {
|
|
215
210
|
this._keyboardFocusRegion.deactivate();
|
|
216
211
|
}
|
package/es/FocusRegionManager.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
var _FocusRegionManager;
|
|
2
1
|
/*
|
|
3
2
|
* The MIT License (MIT)
|
|
4
3
|
*
|
|
@@ -38,123 +37,128 @@ let ENTRIES = [];
|
|
|
38
37
|
* - Return focus to the marked element
|
|
39
38
|
* @module FocusManager
|
|
40
39
|
*/
|
|
41
|
-
class FocusRegionManager {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
entry = _FocusRegionManager.addEntry(element, idOrOptions);
|
|
49
|
-
}
|
|
50
|
-
if (entry && entry.region && typeof entry.region.focus === 'function') {
|
|
51
|
-
entry.region.focus();
|
|
52
|
-
return entry.region;
|
|
53
|
-
} else {
|
|
54
|
-
error(false, `[FocusRegionManager] Could not focus region with element: ${element}`);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
};
|
|
58
|
-
FocusRegionManager.activateRegion = (element, options) => {
|
|
59
|
-
const _FocusRegionManager$a = _FocusRegionManager.addEntry(element, options),
|
|
60
|
-
region = _FocusRegionManager$a.region;
|
|
61
|
-
return region;
|
|
62
|
-
};
|
|
63
|
-
FocusRegionManager.getActiveEntry = () => {
|
|
64
|
-
return ENTRIES.find(({
|
|
65
|
-
region
|
|
66
|
-
}) => region.focused);
|
|
67
|
-
};
|
|
68
|
-
FocusRegionManager.findEntry = (element, id) => {
|
|
69
|
-
let index;
|
|
70
|
-
if (id) {
|
|
71
|
-
index = ENTRIES.findIndex(entry => entry.id === id);
|
|
72
|
-
} else {
|
|
73
|
-
index = ENTRIES.findIndex(entry => entry.element === element);
|
|
74
|
-
}
|
|
75
|
-
return index;
|
|
76
|
-
};
|
|
77
|
-
FocusRegionManager.getEntry = (element, id) => {
|
|
78
|
-
return ENTRIES[_FocusRegionManager.findEntry(element, id)];
|
|
79
|
-
};
|
|
80
|
-
FocusRegionManager.addEntry = (element, options = {}) => {
|
|
81
|
-
const region = new FocusRegion(element, options);
|
|
82
|
-
const activeEntry = _FocusRegionManager.getActiveEntry();
|
|
83
|
-
const keyboardFocusable = region.keyboardFocusable;
|
|
84
|
-
ENTRIES.forEach(({
|
|
85
|
-
region
|
|
86
|
-
}) => {
|
|
87
|
-
if (region) {
|
|
88
|
-
// If the active region is triggering a new focus region that does not have
|
|
89
|
-
// keyboard focusable content, don't deactivate the active region's keyboard
|
|
90
|
-
// focus region
|
|
91
|
-
const keyboard = region.focused && !keyboardFocusable ? {
|
|
92
|
-
keyboard: false
|
|
93
|
-
} : void 0;
|
|
94
|
-
region.deactivate(keyboard);
|
|
40
|
+
class FocusRegionManager {
|
|
41
|
+
static focusRegion = (element, idOrOptions = {}) => {
|
|
42
|
+
let entry;
|
|
43
|
+
if (typeof idOrOptions === 'string') {
|
|
44
|
+
entry = FocusRegionManager.getEntry(element, idOrOptions);
|
|
45
|
+
} else {
|
|
46
|
+
entry = FocusRegionManager.addEntry(element, idOrOptions);
|
|
95
47
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
48
|
+
if (entry && entry.region && typeof entry.region.focus === 'function') {
|
|
49
|
+
entry.region.focus();
|
|
50
|
+
return entry.region;
|
|
51
|
+
} else {
|
|
52
|
+
error(false, `[FocusRegionManager] Could not focus region with element: ${element}`);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
};
|
|
56
|
+
static activateRegion = (element, options) => {
|
|
57
|
+
const {
|
|
58
|
+
region
|
|
59
|
+
} = FocusRegionManager.addEntry(element, options);
|
|
60
|
+
return region;
|
|
61
|
+
};
|
|
62
|
+
static getActiveEntry = () => {
|
|
63
|
+
return ENTRIES.find(({
|
|
64
|
+
region
|
|
65
|
+
}) => region.focused);
|
|
66
|
+
};
|
|
67
|
+
static findEntry = (element, id) => {
|
|
68
|
+
let index;
|
|
69
|
+
if (id) {
|
|
70
|
+
index = ENTRIES.findIndex(entry => entry.id === id);
|
|
71
|
+
} else {
|
|
72
|
+
index = ENTRIES.findIndex(entry => entry.element === element);
|
|
73
|
+
}
|
|
74
|
+
return index;
|
|
75
|
+
};
|
|
76
|
+
static getEntry = (element, id) => {
|
|
77
|
+
return ENTRIES[FocusRegionManager.findEntry(element, id)];
|
|
78
|
+
};
|
|
79
|
+
static addEntry = (element, options = {}) => {
|
|
80
|
+
const region = new FocusRegion(element, options);
|
|
81
|
+
const activeEntry = FocusRegionManager.getActiveEntry();
|
|
82
|
+
const {
|
|
83
|
+
keyboardFocusable
|
|
84
|
+
} = region;
|
|
85
|
+
ENTRIES.forEach(({
|
|
86
|
+
region
|
|
87
|
+
}) => {
|
|
88
|
+
if (region) {
|
|
89
|
+
// If the active region is triggering a new focus region that does not have
|
|
90
|
+
// keyboard focusable content, don't deactivate the active region's keyboard
|
|
91
|
+
// focus region
|
|
92
|
+
const keyboard = region.focused && !keyboardFocusable ? {
|
|
93
|
+
keyboard: false
|
|
94
|
+
} : undefined;
|
|
95
|
+
region.deactivate(keyboard);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
region.activate();
|
|
99
|
+
if (options.shouldFocusOnOpen) {
|
|
100
|
+
region.focus();
|
|
101
|
+
}
|
|
102
|
+
const entry = {
|
|
103
|
+
id: region.id,
|
|
104
|
+
element,
|
|
105
|
+
region,
|
|
106
|
+
children: [],
|
|
107
|
+
parent: activeEntry
|
|
108
|
+
};
|
|
109
|
+
ENTRIES.push(entry);
|
|
110
|
+
if (activeEntry) {
|
|
111
|
+
activeEntry.children.push(entry);
|
|
112
|
+
}
|
|
113
|
+
return entry;
|
|
107
114
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (entry) {
|
|
136
|
-
const children = entry.children,
|
|
137
|
-
region = entry.region,
|
|
138
|
-
parent = entry.parent;
|
|
115
|
+
static removeEntry = (element, id) => {
|
|
116
|
+
const index = FocusRegionManager.findEntry(element, id);
|
|
117
|
+
const entry = ENTRIES[index];
|
|
118
|
+
if (index > -1) {
|
|
119
|
+
ENTRIES.splice(index, 1);
|
|
120
|
+
}
|
|
121
|
+
return entry;
|
|
122
|
+
};
|
|
123
|
+
static isFocused = (element, id) => {
|
|
124
|
+
const entry = FocusRegionManager.getActiveEntry();
|
|
125
|
+
if (id) {
|
|
126
|
+
return entry && entry.region && entry.id === id;
|
|
127
|
+
} else {
|
|
128
|
+
return entry && entry.region && entry.element === element;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
static clearEntries = () => {
|
|
132
|
+
ENTRIES = [];
|
|
133
|
+
};
|
|
134
|
+
static blurRegion = (element, id) => {
|
|
135
|
+
const entry = FocusRegionManager.removeEntry(element, id);
|
|
136
|
+
if (entry) {
|
|
137
|
+
const {
|
|
138
|
+
children,
|
|
139
|
+
region,
|
|
140
|
+
parent
|
|
141
|
+
} = entry;
|
|
139
142
|
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
// deactivate the region...
|
|
144
|
+
region && region.deactivate();
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
// and any regions created from it
|
|
147
|
+
if (children) {
|
|
148
|
+
children.forEach(({
|
|
149
|
+
id,
|
|
150
|
+
element
|
|
151
|
+
}) => {
|
|
152
|
+
const entry = FocusRegionManager.removeEntry(element, id);
|
|
153
|
+
entry && entry.region && entry.region.deactivate();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
};
|
|
157
|
+
// activate the region's parent if it exists
|
|
158
|
+
parent && parent.region && parent.region.activate();
|
|
159
|
+
region && region.blur(); // this should focus the parent region
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
159
163
|
export default FocusRegionManager;
|
|
160
164
|
export { FocusRegionManager };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "core-js/modules/es.array.includes.js";
|
|
1
2
|
/*
|
|
2
3
|
* The MIT License (MIT)
|
|
3
4
|
*
|
|
@@ -38,61 +39,15 @@ import { scopeTab } from "./scopeTab.js";
|
|
|
38
39
|
* @module KeyboardFocusRegion
|
|
39
40
|
*/
|
|
40
41
|
class KeyboardFocusRegion {
|
|
42
|
+
_options;
|
|
43
|
+
_focusLaterElement = null;
|
|
44
|
+
_needToFocus = false;
|
|
45
|
+
_listeners = [];
|
|
46
|
+
_raf = [];
|
|
47
|
+
_active = false;
|
|
48
|
+
_wasDocumentClick;
|
|
49
|
+
_contextElement;
|
|
41
50
|
constructor(element, options) {
|
|
42
|
-
this._options = void 0;
|
|
43
|
-
this._focusLaterElement = null;
|
|
44
|
-
this._needToFocus = false;
|
|
45
|
-
this._listeners = [];
|
|
46
|
-
this._raf = [];
|
|
47
|
-
this._active = false;
|
|
48
|
-
this._wasDocumentClick = void 0;
|
|
49
|
-
this._contextElement = void 0;
|
|
50
|
-
this.handleKeyDown = event => {
|
|
51
|
-
if (event.keyCode === keycode.codes.tab) {
|
|
52
|
-
scopeTab(this._contextElement, event);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
this.handleClick = () => {
|
|
56
|
-
this._wasDocumentClick = true;
|
|
57
|
-
};
|
|
58
|
-
this.handleWindowBlur = () => {
|
|
59
|
-
if (this._wasDocumentClick) {
|
|
60
|
-
this._wasDocumentClick = false;
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
this._needToFocus = true;
|
|
64
|
-
};
|
|
65
|
-
this.handleFocus = () => {
|
|
66
|
-
if (this._needToFocus) {
|
|
67
|
-
this._needToFocus = false;
|
|
68
|
-
if (!this._contextElement) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
// need to see how jQuery shims document.on('focusin') so we don't need the
|
|
72
|
-
// setTimeout, firefox doesn't support focusin, if it did, we could focus
|
|
73
|
-
// the element outside of a setTimeout. Side-effect of this implementation
|
|
74
|
-
// is that the document.body gets focus, and then we focus our element right
|
|
75
|
-
// after, seems fine.
|
|
76
|
-
this._raf.push(requestAnimationFrame(() => {
|
|
77
|
-
if (containsActiveElement(this._contextElement)) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
this.focusDefaultElement();
|
|
81
|
-
}));
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
this.handleFirstTabbableKeyDown = event => {
|
|
85
|
-
if (event.keyCode === keycode.codes.tab && event.shiftKey) {
|
|
86
|
-
var _this$_options$onBlur, _this$_options;
|
|
87
|
-
(_this$_options$onBlur = (_this$_options = this._options).onBlur) === null || _this$_options$onBlur === void 0 ? void 0 : _this$_options$onBlur.call(_this$_options, event);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
this.handleLastTabbableKeyDown = event => {
|
|
91
|
-
if (event.keyCode === keycode.codes.tab && !event.shiftKey) {
|
|
92
|
-
var _this$_options$onBlur2, _this$_options2;
|
|
93
|
-
(_this$_options$onBlur2 = (_this$_options2 = this._options).onBlur) === null || _this$_options$onBlur2 === void 0 ? void 0 : _this$_options$onBlur2.call(_this$_options2, event);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
51
|
this._contextElement = findDOMNode(element);
|
|
97
52
|
this._options = options || {
|
|
98
53
|
shouldContainFocus: true,
|
|
@@ -107,7 +62,9 @@ class KeyboardFocusRegion {
|
|
|
107
62
|
return containsActiveElement(this._contextElement);
|
|
108
63
|
}
|
|
109
64
|
get shouldContainFocus() {
|
|
110
|
-
const
|
|
65
|
+
const {
|
|
66
|
+
shouldContainFocus
|
|
67
|
+
} = this._options;
|
|
111
68
|
return shouldContainFocus === true || Array.isArray(shouldContainFocus) && shouldContainFocus.includes('keyboard');
|
|
112
69
|
}
|
|
113
70
|
get focusable() {
|
|
@@ -135,7 +92,9 @@ class KeyboardFocusRegion {
|
|
|
135
92
|
return ownerWindow(this._contextElement);
|
|
136
93
|
}
|
|
137
94
|
get defaultFocusElement() {
|
|
138
|
-
const
|
|
95
|
+
const {
|
|
96
|
+
defaultFocusElement
|
|
97
|
+
} = this._options;
|
|
139
98
|
const element = findDOMNode(typeof defaultFocusElement === 'function' ? defaultFocusElement() : defaultFocusElement);
|
|
140
99
|
if (element && this._contextElement && this._contextElement.contains(element)) {
|
|
141
100
|
return element;
|
|
@@ -215,9 +174,55 @@ class KeyboardFocusRegion {
|
|
|
215
174
|
this._focusLaterElement = null;
|
|
216
175
|
}
|
|
217
176
|
}
|
|
177
|
+
handleKeyDown = event => {
|
|
178
|
+
if (event.keyCode === keycode.codes.tab) {
|
|
179
|
+
scopeTab(this._contextElement, event);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
handleClick = () => {
|
|
183
|
+
this._wasDocumentClick = true;
|
|
184
|
+
};
|
|
185
|
+
handleWindowBlur = () => {
|
|
186
|
+
if (this._wasDocumentClick) {
|
|
187
|
+
this._wasDocumentClick = false;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this._needToFocus = true;
|
|
191
|
+
};
|
|
192
|
+
handleFocus = () => {
|
|
193
|
+
if (this._needToFocus) {
|
|
194
|
+
this._needToFocus = false;
|
|
195
|
+
if (!this._contextElement) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// need to see how jQuery shims document.on('focusin') so we don't need the
|
|
199
|
+
// setTimeout, firefox doesn't support focusin, if it did, we could focus
|
|
200
|
+
// the element outside of a setTimeout. Side-effect of this implementation
|
|
201
|
+
// is that the document.body gets focus, and then we focus our element right
|
|
202
|
+
// after, seems fine.
|
|
203
|
+
this._raf.push(requestAnimationFrame(() => {
|
|
204
|
+
if (containsActiveElement(this._contextElement)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.focusDefaultElement();
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
handleFirstTabbableKeyDown = event => {
|
|
212
|
+
if (event.keyCode === keycode.codes.tab && event.shiftKey) {
|
|
213
|
+
this._options.onBlur?.(event);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
handleLastTabbableKeyDown = event => {
|
|
217
|
+
if (event.keyCode === keycode.codes.tab && !event.shiftKey) {
|
|
218
|
+
this._options.onBlur?.(event);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
218
221
|
activate() {
|
|
219
|
-
const
|
|
220
|
-
|
|
222
|
+
const {
|
|
223
|
+
defaultFocusElement,
|
|
224
|
+
shouldContainFocus
|
|
225
|
+
} = this;
|
|
221
226
|
if (!this._active) {
|
|
222
227
|
if (defaultFocusElement || shouldContainFocus) {
|
|
223
228
|
if (shouldContainFocus) {
|