@rc-component/util 1.8.2 → 1.9.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/es/Dom/focus.d.ts +4 -2
- package/es/Dom/focus.js +44 -3
- package/lib/Dom/focus.d.ts +4 -2
- package/lib/Dom/focus.js +44 -3
- package/package.json +1 -1
package/es/Dom/focus.d.ts
CHANGED
|
@@ -9,11 +9,13 @@ export declare function triggerFocus(element?: HTMLElement, option?: InputFocusO
|
|
|
9
9
|
/**
|
|
10
10
|
* Lock focus in the element.
|
|
11
11
|
* It will force back to the first focusable element when focus leaves the element.
|
|
12
|
+
* @param id - A stable ID for this lock instance
|
|
12
13
|
*/
|
|
13
|
-
export declare function lockFocus(element: HTMLElement): VoidFunction;
|
|
14
|
+
export declare function lockFocus(element: HTMLElement, id: string): VoidFunction;
|
|
14
15
|
/**
|
|
15
16
|
* Lock focus within an element.
|
|
16
17
|
* When locked, focus will be restricted to focusable elements within the specified element.
|
|
17
18
|
* If multiple elements are locked, only the last locked element will be effective.
|
|
19
|
+
* @returns A function to mark an element as ignored, which will temporarily allow focus on that element even if it's outside the locked area.
|
|
18
20
|
*/
|
|
19
|
-
export declare function useLockFocus(lock: boolean, getElement: () => HTMLElement | null): void;
|
|
21
|
+
export declare function useLockFocus(lock: boolean, getElement: () => HTMLElement | null): [ignoreElement: (ele: HTMLElement) => void];
|
package/es/Dom/focus.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import isVisible from "./isVisible";
|
|
3
|
+
import useId from "../hooks/useId";
|
|
3
4
|
function focusable(node, includePositive = false) {
|
|
4
5
|
if (isVisible(node)) {
|
|
5
6
|
const nodeName = node.nodeName.toLowerCase();
|
|
@@ -72,9 +73,29 @@ export function triggerFocus(element, option) {
|
|
|
72
73
|
// ======================================================
|
|
73
74
|
let lastFocusElement = null;
|
|
74
75
|
let focusElements = [];
|
|
76
|
+
// Map stable ID to lock element
|
|
77
|
+
const idToElementMap = new Map();
|
|
78
|
+
// Map stable ID to ignored element
|
|
79
|
+
const ignoredElementMap = new Map();
|
|
75
80
|
function getLastElement() {
|
|
76
81
|
return focusElements[focusElements.length - 1];
|
|
77
82
|
}
|
|
83
|
+
function isIgnoredElement(element) {
|
|
84
|
+
const lastElement = getLastElement();
|
|
85
|
+
if (element && lastElement) {
|
|
86
|
+
// Find the ID that maps to the last element
|
|
87
|
+
let lockId;
|
|
88
|
+
for (const [id, ele] of idToElementMap.entries()) {
|
|
89
|
+
if (ele === lastElement) {
|
|
90
|
+
lockId = id;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const ignoredEle = ignoredElementMap.get(lockId);
|
|
95
|
+
return !!ignoredEle && (ignoredEle === element || ignoredEle.contains(element));
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
78
99
|
function hasFocus(element) {
|
|
79
100
|
const {
|
|
80
101
|
activeElement
|
|
@@ -86,6 +107,11 @@ function syncFocus() {
|
|
|
86
107
|
const {
|
|
87
108
|
activeElement
|
|
88
109
|
} = document;
|
|
110
|
+
|
|
111
|
+
// If current focus is on an ignored element, don't force it back
|
|
112
|
+
if (isIgnoredElement(activeElement)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
89
115
|
if (lastElement && !hasFocus(lastElement)) {
|
|
90
116
|
const focusableList = getFocusNodeList(lastElement);
|
|
91
117
|
const matchElement = focusableList.includes(lastFocusElement) ? lastFocusElement : focusableList[0];
|
|
@@ -117,9 +143,13 @@ function onWindowKeyDown(e) {
|
|
|
117
143
|
/**
|
|
118
144
|
* Lock focus in the element.
|
|
119
145
|
* It will force back to the first focusable element when focus leaves the element.
|
|
146
|
+
* @param id - A stable ID for this lock instance
|
|
120
147
|
*/
|
|
121
|
-
export function lockFocus(element) {
|
|
148
|
+
export function lockFocus(element, id) {
|
|
122
149
|
if (element) {
|
|
150
|
+
// Store the mapping between ID and element
|
|
151
|
+
idToElementMap.set(id, element);
|
|
152
|
+
|
|
123
153
|
// Refresh focus elements
|
|
124
154
|
focusElements = focusElements.filter(ele => ele !== element);
|
|
125
155
|
focusElements.push(element);
|
|
@@ -134,6 +164,8 @@ export function lockFocus(element) {
|
|
|
134
164
|
return () => {
|
|
135
165
|
lastFocusElement = null;
|
|
136
166
|
focusElements = focusElements.filter(ele => ele !== element);
|
|
167
|
+
idToElementMap.delete(id);
|
|
168
|
+
ignoredElementMap.delete(id);
|
|
137
169
|
if (focusElements.length === 0) {
|
|
138
170
|
window.removeEventListener('focusin', syncFocus);
|
|
139
171
|
window.removeEventListener('keydown', onWindowKeyDown, true);
|
|
@@ -145,14 +177,23 @@ export function lockFocus(element) {
|
|
|
145
177
|
* Lock focus within an element.
|
|
146
178
|
* When locked, focus will be restricted to focusable elements within the specified element.
|
|
147
179
|
* If multiple elements are locked, only the last locked element will be effective.
|
|
180
|
+
* @returns A function to mark an element as ignored, which will temporarily allow focus on that element even if it's outside the locked area.
|
|
148
181
|
*/
|
|
149
182
|
export function useLockFocus(lock, getElement) {
|
|
183
|
+
const id = useId();
|
|
150
184
|
useEffect(() => {
|
|
151
185
|
if (lock) {
|
|
152
186
|
const element = getElement();
|
|
153
187
|
if (element) {
|
|
154
|
-
return lockFocus(element);
|
|
188
|
+
return lockFocus(element, id);
|
|
155
189
|
}
|
|
156
190
|
}
|
|
157
|
-
}, [lock]);
|
|
191
|
+
}, [lock, id]);
|
|
192
|
+
const ignoreElement = ele => {
|
|
193
|
+
if (ele) {
|
|
194
|
+
// Set the ignored element using stable ID
|
|
195
|
+
ignoredElementMap.set(id, ele);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
return [ignoreElement];
|
|
158
199
|
}
|
package/lib/Dom/focus.d.ts
CHANGED
|
@@ -9,11 +9,13 @@ export declare function triggerFocus(element?: HTMLElement, option?: InputFocusO
|
|
|
9
9
|
/**
|
|
10
10
|
* Lock focus in the element.
|
|
11
11
|
* It will force back to the first focusable element when focus leaves the element.
|
|
12
|
+
* @param id - A stable ID for this lock instance
|
|
12
13
|
*/
|
|
13
|
-
export declare function lockFocus(element: HTMLElement): VoidFunction;
|
|
14
|
+
export declare function lockFocus(element: HTMLElement, id: string): VoidFunction;
|
|
14
15
|
/**
|
|
15
16
|
* Lock focus within an element.
|
|
16
17
|
* When locked, focus will be restricted to focusable elements within the specified element.
|
|
17
18
|
* If multiple elements are locked, only the last locked element will be effective.
|
|
19
|
+
* @returns A function to mark an element as ignored, which will temporarily allow focus on that element even if it's outside the locked area.
|
|
18
20
|
*/
|
|
19
|
-
export declare function useLockFocus(lock: boolean, getElement: () => HTMLElement | null): void;
|
|
21
|
+
export declare function useLockFocus(lock: boolean, getElement: () => HTMLElement | null): [ignoreElement: (ele: HTMLElement) => void];
|
package/lib/Dom/focus.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.triggerFocus = triggerFocus;
|
|
|
9
9
|
exports.useLockFocus = useLockFocus;
|
|
10
10
|
var _react = require("react");
|
|
11
11
|
var _isVisible = _interopRequireDefault(require("./isVisible"));
|
|
12
|
+
var _useId = _interopRequireDefault(require("../hooks/useId"));
|
|
12
13
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
14
|
function focusable(node, includePositive = false) {
|
|
14
15
|
if ((0, _isVisible.default)(node)) {
|
|
@@ -82,9 +83,29 @@ function triggerFocus(element, option) {
|
|
|
82
83
|
// ======================================================
|
|
83
84
|
let lastFocusElement = null;
|
|
84
85
|
let focusElements = [];
|
|
86
|
+
// Map stable ID to lock element
|
|
87
|
+
const idToElementMap = new Map();
|
|
88
|
+
// Map stable ID to ignored element
|
|
89
|
+
const ignoredElementMap = new Map();
|
|
85
90
|
function getLastElement() {
|
|
86
91
|
return focusElements[focusElements.length - 1];
|
|
87
92
|
}
|
|
93
|
+
function isIgnoredElement(element) {
|
|
94
|
+
const lastElement = getLastElement();
|
|
95
|
+
if (element && lastElement) {
|
|
96
|
+
// Find the ID that maps to the last element
|
|
97
|
+
let lockId;
|
|
98
|
+
for (const [id, ele] of idToElementMap.entries()) {
|
|
99
|
+
if (ele === lastElement) {
|
|
100
|
+
lockId = id;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const ignoredEle = ignoredElementMap.get(lockId);
|
|
105
|
+
return !!ignoredEle && (ignoredEle === element || ignoredEle.contains(element));
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
88
109
|
function hasFocus(element) {
|
|
89
110
|
const {
|
|
90
111
|
activeElement
|
|
@@ -96,6 +117,11 @@ function syncFocus() {
|
|
|
96
117
|
const {
|
|
97
118
|
activeElement
|
|
98
119
|
} = document;
|
|
120
|
+
|
|
121
|
+
// If current focus is on an ignored element, don't force it back
|
|
122
|
+
if (isIgnoredElement(activeElement)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
99
125
|
if (lastElement && !hasFocus(lastElement)) {
|
|
100
126
|
const focusableList = getFocusNodeList(lastElement);
|
|
101
127
|
const matchElement = focusableList.includes(lastFocusElement) ? lastFocusElement : focusableList[0];
|
|
@@ -127,9 +153,13 @@ function onWindowKeyDown(e) {
|
|
|
127
153
|
/**
|
|
128
154
|
* Lock focus in the element.
|
|
129
155
|
* It will force back to the first focusable element when focus leaves the element.
|
|
156
|
+
* @param id - A stable ID for this lock instance
|
|
130
157
|
*/
|
|
131
|
-
function lockFocus(element) {
|
|
158
|
+
function lockFocus(element, id) {
|
|
132
159
|
if (element) {
|
|
160
|
+
// Store the mapping between ID and element
|
|
161
|
+
idToElementMap.set(id, element);
|
|
162
|
+
|
|
133
163
|
// Refresh focus elements
|
|
134
164
|
focusElements = focusElements.filter(ele => ele !== element);
|
|
135
165
|
focusElements.push(element);
|
|
@@ -144,6 +174,8 @@ function lockFocus(element) {
|
|
|
144
174
|
return () => {
|
|
145
175
|
lastFocusElement = null;
|
|
146
176
|
focusElements = focusElements.filter(ele => ele !== element);
|
|
177
|
+
idToElementMap.delete(id);
|
|
178
|
+
ignoredElementMap.delete(id);
|
|
147
179
|
if (focusElements.length === 0) {
|
|
148
180
|
window.removeEventListener('focusin', syncFocus);
|
|
149
181
|
window.removeEventListener('keydown', onWindowKeyDown, true);
|
|
@@ -155,14 +187,23 @@ function lockFocus(element) {
|
|
|
155
187
|
* Lock focus within an element.
|
|
156
188
|
* When locked, focus will be restricted to focusable elements within the specified element.
|
|
157
189
|
* If multiple elements are locked, only the last locked element will be effective.
|
|
190
|
+
* @returns A function to mark an element as ignored, which will temporarily allow focus on that element even if it's outside the locked area.
|
|
158
191
|
*/
|
|
159
192
|
function useLockFocus(lock, getElement) {
|
|
193
|
+
const id = (0, _useId.default)();
|
|
160
194
|
(0, _react.useEffect)(() => {
|
|
161
195
|
if (lock) {
|
|
162
196
|
const element = getElement();
|
|
163
197
|
if (element) {
|
|
164
|
-
return lockFocus(element);
|
|
198
|
+
return lockFocus(element, id);
|
|
165
199
|
}
|
|
166
200
|
}
|
|
167
|
-
}, [lock]);
|
|
201
|
+
}, [lock, id]);
|
|
202
|
+
const ignoreElement = ele => {
|
|
203
|
+
if (ele) {
|
|
204
|
+
// Set the ignored element using stable ID
|
|
205
|
+
ignoredElementMap.set(id, ele);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
return [ignoreElement];
|
|
168
209
|
}
|