@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 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
  }
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rc-component/util",
3
- "version": "1.8.2",
3
+ "version": "1.9.0",
4
4
  "description": "Common Utils For React Component",
5
5
  "keywords": [
6
6
  "react",