@rc-component/util 1.8.1 → 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,10 +107,17 @@ 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];
92
- matchElement?.focus();
118
+ matchElement?.focus({
119
+ preventScroll: true
120
+ });
93
121
  } else {
94
122
  lastFocusElement = activeElement;
95
123
  }
@@ -115,9 +143,13 @@ function onWindowKeyDown(e) {
115
143
  /**
116
144
  * Lock focus in the element.
117
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
118
147
  */
119
- export function lockFocus(element) {
148
+ export function lockFocus(element, id) {
120
149
  if (element) {
150
+ // Store the mapping between ID and element
151
+ idToElementMap.set(id, element);
152
+
121
153
  // Refresh focus elements
122
154
  focusElements = focusElements.filter(ele => ele !== element);
123
155
  focusElements.push(element);
@@ -132,6 +164,8 @@ export function lockFocus(element) {
132
164
  return () => {
133
165
  lastFocusElement = null;
134
166
  focusElements = focusElements.filter(ele => ele !== element);
167
+ idToElementMap.delete(id);
168
+ ignoredElementMap.delete(id);
135
169
  if (focusElements.length === 0) {
136
170
  window.removeEventListener('focusin', syncFocus);
137
171
  window.removeEventListener('keydown', onWindowKeyDown, true);
@@ -143,14 +177,23 @@ export function lockFocus(element) {
143
177
  * Lock focus within an element.
144
178
  * When locked, focus will be restricted to focusable elements within the specified element.
145
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.
146
181
  */
147
182
  export function useLockFocus(lock, getElement) {
183
+ const id = useId();
148
184
  useEffect(() => {
149
185
  if (lock) {
150
186
  const element = getElement();
151
187
  if (element) {
152
- return lockFocus(element);
188
+ return lockFocus(element, id);
153
189
  }
154
190
  }
155
- }, [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];
156
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,10 +117,17 @@ 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];
102
- matchElement?.focus();
128
+ matchElement?.focus({
129
+ preventScroll: true
130
+ });
103
131
  } else {
104
132
  lastFocusElement = activeElement;
105
133
  }
@@ -125,9 +153,13 @@ function onWindowKeyDown(e) {
125
153
  /**
126
154
  * Lock focus in the element.
127
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
128
157
  */
129
- function lockFocus(element) {
158
+ function lockFocus(element, id) {
130
159
  if (element) {
160
+ // Store the mapping between ID and element
161
+ idToElementMap.set(id, element);
162
+
131
163
  // Refresh focus elements
132
164
  focusElements = focusElements.filter(ele => ele !== element);
133
165
  focusElements.push(element);
@@ -142,6 +174,8 @@ function lockFocus(element) {
142
174
  return () => {
143
175
  lastFocusElement = null;
144
176
  focusElements = focusElements.filter(ele => ele !== element);
177
+ idToElementMap.delete(id);
178
+ ignoredElementMap.delete(id);
145
179
  if (focusElements.length === 0) {
146
180
  window.removeEventListener('focusin', syncFocus);
147
181
  window.removeEventListener('keydown', onWindowKeyDown, true);
@@ -153,14 +187,23 @@ function lockFocus(element) {
153
187
  * Lock focus within an element.
154
188
  * When locked, focus will be restricted to focusable elements within the specified element.
155
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.
156
191
  */
157
192
  function useLockFocus(lock, getElement) {
193
+ const id = (0, _useId.default)();
158
194
  (0, _react.useEffect)(() => {
159
195
  if (lock) {
160
196
  const element = getElement();
161
197
  if (element) {
162
- return lockFocus(element);
198
+ return lockFocus(element, id);
163
199
  }
164
200
  }
165
- }, [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];
166
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rc-component/util",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "Common Utils For React Component",
5
5
  "keywords": [
6
6
  "react",