@rc-component/util 1.5.0 → 1.6.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
@@ -1,11 +1,4 @@
1
1
  export declare function getFocusNodeList(node: HTMLElement, includePositive?: boolean): HTMLElement[];
2
- /** @deprecated Do not use since this may failed when used in async */
3
- export declare function saveLastFocusNode(): void;
4
- /** @deprecated Do not use since this may failed when used in async */
5
- export declare function clearLastFocusNode(): void;
6
- /** @deprecated Do not use since this may failed when used in async */
7
- export declare function backLastFocusNode(): void;
8
- export declare function limitTabRange(node: HTMLElement, e: KeyboardEvent): void;
9
2
  export interface InputFocusOptions extends FocusOptions {
10
3
  cursor?: 'start' | 'end' | 'all';
11
4
  }
@@ -13,3 +6,14 @@ export interface InputFocusOptions extends FocusOptions {
13
6
  * Focus element and set cursor position for input/textarea elements.
14
7
  */
15
8
  export declare function triggerFocus(element?: HTMLElement, option?: InputFocusOptions): void;
9
+ /**
10
+ * Lock focus in the element.
11
+ * It will force back to the first focusable element when focus leaves the element.
12
+ */
13
+ export declare function lockFocus(element: HTMLElement): VoidFunction;
14
+ /**
15
+ * Lock focus within an element.
16
+ * When locked, focus will be restricted to focusable elements within the specified element.
17
+ * If multiple elements are locked, only the last locked element will be effective.
18
+ */
19
+ export declare function useLockFocus(lock: boolean, getElement: () => HTMLElement | null): void;
package/es/Dom/focus.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { useEffect } from 'react';
1
2
  import isVisible from "./isVisible";
2
3
  function focusable(node, includePositive = false) {
3
4
  if (isVisible(node)) {
@@ -39,44 +40,6 @@ export function getFocusNodeList(node, includePositive = false) {
39
40
  }
40
41
  return res;
41
42
  }
42
- let lastFocusElement = null;
43
-
44
- /** @deprecated Do not use since this may failed when used in async */
45
- export function saveLastFocusNode() {
46
- lastFocusElement = document.activeElement;
47
- }
48
-
49
- /** @deprecated Do not use since this may failed when used in async */
50
- export function clearLastFocusNode() {
51
- lastFocusElement = null;
52
- }
53
-
54
- /** @deprecated Do not use since this may failed when used in async */
55
- export function backLastFocusNode() {
56
- if (lastFocusElement) {
57
- try {
58
- // 元素可能已经被移动了
59
- lastFocusElement.focus();
60
-
61
- /* eslint-disable no-empty */
62
- } catch (e) {
63
- // empty
64
- }
65
- /* eslint-enable no-empty */
66
- }
67
- }
68
- export function limitTabRange(node, e) {
69
- if (e.keyCode === 9) {
70
- const tabNodeList = getFocusNodeList(node);
71
- const lastTabNode = tabNodeList[e.shiftKey ? 0 : tabNodeList.length - 1];
72
- const leavingTab = lastTabNode === document.activeElement || node === document.activeElement;
73
- if (leavingTab) {
74
- const target = tabNodeList[e.shiftKey ? tabNodeList.length - 1 : 0];
75
- target.focus();
76
- e.preventDefault();
77
- }
78
- }
79
- }
80
43
  // Used for `rc-input` `rc-textarea` `rc-input-number`
81
44
  /**
82
45
  * Focus element and set cursor position for input/textarea elements.
@@ -102,4 +65,92 @@ export function triggerFocus(element, option) {
102
65
  element.setSelectionRange(0, len);
103
66
  }
104
67
  }
68
+ }
69
+
70
+ // ======================================================
71
+ // == Lock Focus ==
72
+ // ======================================================
73
+ let lastFocusElement = null;
74
+ let focusElements = [];
75
+ function getLastElement() {
76
+ return focusElements[focusElements.length - 1];
77
+ }
78
+ function hasFocus(element) {
79
+ const {
80
+ activeElement
81
+ } = document;
82
+ return element === activeElement || element.contains(activeElement);
83
+ }
84
+ function syncFocus() {
85
+ const lastElement = getLastElement();
86
+ const {
87
+ activeElement
88
+ } = document;
89
+ if (lastElement && !hasFocus(lastElement)) {
90
+ const focusableList = getFocusNodeList(lastElement);
91
+ const matchElement = focusableList.includes(lastFocusElement) ? lastFocusElement : focusableList[0];
92
+ matchElement?.focus();
93
+ } else {
94
+ lastFocusElement = activeElement;
95
+ }
96
+ }
97
+ function onWindowKeyDown(e) {
98
+ if (e.key === 'Tab') {
99
+ const {
100
+ activeElement
101
+ } = document;
102
+ const lastElement = getLastElement();
103
+ const focusableList = getFocusNodeList(lastElement);
104
+ const last = focusableList[focusableList.length - 1];
105
+ if (e.shiftKey && activeElement === focusableList[0]) {
106
+ // Tab backward on first focusable element
107
+ lastFocusElement = last;
108
+ } else if (!e.shiftKey && activeElement === last) {
109
+ // Tab forward on last focusable element
110
+ lastFocusElement = focusableList[0];
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Lock focus in the element.
117
+ * It will force back to the first focusable element when focus leaves the element.
118
+ */
119
+ export function lockFocus(element) {
120
+ if (element) {
121
+ // Refresh focus elements
122
+ focusElements = focusElements.filter(ele => ele !== element);
123
+ focusElements.push(element);
124
+
125
+ // Just add event since it will de-duplicate
126
+ window.addEventListener('focusin', syncFocus);
127
+ window.addEventListener('keydown', onWindowKeyDown, true);
128
+ syncFocus();
129
+ }
130
+
131
+ // Always return unregister function
132
+ return () => {
133
+ lastFocusElement = null;
134
+ focusElements = focusElements.filter(ele => ele !== element);
135
+ if (focusElements.length === 0) {
136
+ window.removeEventListener('focusin', syncFocus);
137
+ window.removeEventListener('keydown', onWindowKeyDown, true);
138
+ }
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Lock focus within an element.
144
+ * When locked, focus will be restricted to focusable elements within the specified element.
145
+ * If multiple elements are locked, only the last locked element will be effective.
146
+ */
147
+ export function useLockFocus(lock, getElement) {
148
+ useEffect(() => {
149
+ if (lock) {
150
+ const element = getElement();
151
+ if (element) {
152
+ return lockFocus(element);
153
+ }
154
+ }
155
+ }, [lock]);
105
156
  }
@@ -1,7 +1,5 @@
1
- type ScrollBarSize = {
1
+ export default function getScrollBarSize(fresh?: boolean): number;
2
+ export declare function getTargetScrollBarSize(target: HTMLElement): {
2
3
  width: number;
3
4
  height: number;
4
5
  };
5
- export default function getScrollBarSize(fresh?: boolean): number;
6
- export declare function getTargetScrollBarSize(target: HTMLElement): ScrollBarSize;
7
- export {};
@@ -1,11 +1,4 @@
1
1
  export declare function getFocusNodeList(node: HTMLElement, includePositive?: boolean): HTMLElement[];
2
- /** @deprecated Do not use since this may failed when used in async */
3
- export declare function saveLastFocusNode(): void;
4
- /** @deprecated Do not use since this may failed when used in async */
5
- export declare function clearLastFocusNode(): void;
6
- /** @deprecated Do not use since this may failed when used in async */
7
- export declare function backLastFocusNode(): void;
8
- export declare function limitTabRange(node: HTMLElement, e: KeyboardEvent): void;
9
2
  export interface InputFocusOptions extends FocusOptions {
10
3
  cursor?: 'start' | 'end' | 'all';
11
4
  }
@@ -13,3 +6,14 @@ export interface InputFocusOptions extends FocusOptions {
13
6
  * Focus element and set cursor position for input/textarea elements.
14
7
  */
15
8
  export declare function triggerFocus(element?: HTMLElement, option?: InputFocusOptions): void;
9
+ /**
10
+ * Lock focus in the element.
11
+ * It will force back to the first focusable element when focus leaves the element.
12
+ */
13
+ export declare function lockFocus(element: HTMLElement): VoidFunction;
14
+ /**
15
+ * Lock focus within an element.
16
+ * When locked, focus will be restricted to focusable elements within the specified element.
17
+ * If multiple elements are locked, only the last locked element will be effective.
18
+ */
19
+ export declare function useLockFocus(lock: boolean, getElement: () => HTMLElement | null): void;
package/lib/Dom/focus.js CHANGED
@@ -3,12 +3,11 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.backLastFocusNode = backLastFocusNode;
7
- exports.clearLastFocusNode = clearLastFocusNode;
8
6
  exports.getFocusNodeList = getFocusNodeList;
9
- exports.limitTabRange = limitTabRange;
10
- exports.saveLastFocusNode = saveLastFocusNode;
7
+ exports.lockFocus = lockFocus;
11
8
  exports.triggerFocus = triggerFocus;
9
+ exports.useLockFocus = useLockFocus;
10
+ var _react = require("react");
12
11
  var _isVisible = _interopRequireDefault(require("./isVisible"));
13
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
13
  function focusable(node, includePositive = false) {
@@ -51,44 +50,6 @@ function getFocusNodeList(node, includePositive = false) {
51
50
  }
52
51
  return res;
53
52
  }
54
- let lastFocusElement = null;
55
-
56
- /** @deprecated Do not use since this may failed when used in async */
57
- function saveLastFocusNode() {
58
- lastFocusElement = document.activeElement;
59
- }
60
-
61
- /** @deprecated Do not use since this may failed when used in async */
62
- function clearLastFocusNode() {
63
- lastFocusElement = null;
64
- }
65
-
66
- /** @deprecated Do not use since this may failed when used in async */
67
- function backLastFocusNode() {
68
- if (lastFocusElement) {
69
- try {
70
- // 元素可能已经被移动了
71
- lastFocusElement.focus();
72
-
73
- /* eslint-disable no-empty */
74
- } catch (e) {
75
- // empty
76
- }
77
- /* eslint-enable no-empty */
78
- }
79
- }
80
- function limitTabRange(node, e) {
81
- if (e.keyCode === 9) {
82
- const tabNodeList = getFocusNodeList(node);
83
- const lastTabNode = tabNodeList[e.shiftKey ? 0 : tabNodeList.length - 1];
84
- const leavingTab = lastTabNode === document.activeElement || node === document.activeElement;
85
- if (leavingTab) {
86
- const target = tabNodeList[e.shiftKey ? tabNodeList.length - 1 : 0];
87
- target.focus();
88
- e.preventDefault();
89
- }
90
- }
91
- }
92
53
  // Used for `rc-input` `rc-textarea` `rc-input-number`
93
54
  /**
94
55
  * Focus element and set cursor position for input/textarea elements.
@@ -114,4 +75,92 @@ function triggerFocus(element, option) {
114
75
  element.setSelectionRange(0, len);
115
76
  }
116
77
  }
78
+ }
79
+
80
+ // ======================================================
81
+ // == Lock Focus ==
82
+ // ======================================================
83
+ let lastFocusElement = null;
84
+ let focusElements = [];
85
+ function getLastElement() {
86
+ return focusElements[focusElements.length - 1];
87
+ }
88
+ function hasFocus(element) {
89
+ const {
90
+ activeElement
91
+ } = document;
92
+ return element === activeElement || element.contains(activeElement);
93
+ }
94
+ function syncFocus() {
95
+ const lastElement = getLastElement();
96
+ const {
97
+ activeElement
98
+ } = document;
99
+ if (lastElement && !hasFocus(lastElement)) {
100
+ const focusableList = getFocusNodeList(lastElement);
101
+ const matchElement = focusableList.includes(lastFocusElement) ? lastFocusElement : focusableList[0];
102
+ matchElement?.focus();
103
+ } else {
104
+ lastFocusElement = activeElement;
105
+ }
106
+ }
107
+ function onWindowKeyDown(e) {
108
+ if (e.key === 'Tab') {
109
+ const {
110
+ activeElement
111
+ } = document;
112
+ const lastElement = getLastElement();
113
+ const focusableList = getFocusNodeList(lastElement);
114
+ const last = focusableList[focusableList.length - 1];
115
+ if (e.shiftKey && activeElement === focusableList[0]) {
116
+ // Tab backward on first focusable element
117
+ lastFocusElement = last;
118
+ } else if (!e.shiftKey && activeElement === last) {
119
+ // Tab forward on last focusable element
120
+ lastFocusElement = focusableList[0];
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Lock focus in the element.
127
+ * It will force back to the first focusable element when focus leaves the element.
128
+ */
129
+ function lockFocus(element) {
130
+ if (element) {
131
+ // Refresh focus elements
132
+ focusElements = focusElements.filter(ele => ele !== element);
133
+ focusElements.push(element);
134
+
135
+ // Just add event since it will de-duplicate
136
+ window.addEventListener('focusin', syncFocus);
137
+ window.addEventListener('keydown', onWindowKeyDown, true);
138
+ syncFocus();
139
+ }
140
+
141
+ // Always return unregister function
142
+ return () => {
143
+ lastFocusElement = null;
144
+ focusElements = focusElements.filter(ele => ele !== element);
145
+ if (focusElements.length === 0) {
146
+ window.removeEventListener('focusin', syncFocus);
147
+ window.removeEventListener('keydown', onWindowKeyDown, true);
148
+ }
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Lock focus within an element.
154
+ * When locked, focus will be restricted to focusable elements within the specified element.
155
+ * If multiple elements are locked, only the last locked element will be effective.
156
+ */
157
+ function useLockFocus(lock, getElement) {
158
+ (0, _react.useEffect)(() => {
159
+ if (lock) {
160
+ const element = getElement();
161
+ if (element) {
162
+ return lockFocus(element);
163
+ }
164
+ }
165
+ }, [lock]);
117
166
  }
@@ -1,7 +1,5 @@
1
- type ScrollBarSize = {
1
+ export default function getScrollBarSize(fresh?: boolean): number;
2
+ export declare function getTargetScrollBarSize(target: HTMLElement): {
2
3
  width: number;
3
4
  height: number;
4
5
  };
5
- export default function getScrollBarSize(fresh?: boolean): number;
6
- export declare function getTargetScrollBarSize(target: HTMLElement): ScrollBarSize;
7
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rc-component/util",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Common Utils For React Component",
5
5
  "keywords": [
6
6
  "react",