@v-c/util 1.0.7 → 1.0.9

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.
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_isVisible = require("./isVisible.cjs");
3
+ let vue = require("vue");
3
4
  function focusable(node, includePositive = false) {
4
5
  if (require_isVisible.default(node)) {
5
6
  const nodeName = node.nodeName.toLowerCase();
@@ -26,27 +27,6 @@ function getFocusNodeList(node, includePositive = false) {
26
27
  if (focusable(node, includePositive)) res.unshift(node);
27
28
  return res;
28
29
  }
29
- var lastFocusElement = null;
30
- function saveLastFocusNode() {
31
- lastFocusElement = document.activeElement;
32
- }
33
- function clearLastFocusNode() {
34
- lastFocusElement = null;
35
- }
36
- function backLastFocusNode() {
37
- if (lastFocusElement) try {
38
- lastFocusElement.focus();
39
- } catch (_e) {}
40
- }
41
- function limitTabRange(node, e) {
42
- if (e.keyCode === 9) {
43
- const tabNodeList = getFocusNodeList(node);
44
- if (tabNodeList[e.shiftKey ? 0 : tabNodeList.length - 1] === document.activeElement || node === document.activeElement) {
45
- tabNodeList[e.shiftKey ? tabNodeList.length - 1 : 0].focus();
46
- e.preventDefault();
47
- }
48
- }
49
- }
50
30
  function triggerFocus(element, option) {
51
31
  if (!element) return;
52
32
  element.focus(option);
@@ -64,9 +44,58 @@ function triggerFocus(element, option) {
64
44
  }
65
45
  }
66
46
  }
67
- exports.backLastFocusNode = backLastFocusNode;
68
- exports.clearLastFocusNode = clearLastFocusNode;
47
+ var lastFocusElement = null;
48
+ var focusElements = [];
49
+ function getLastElement() {
50
+ return focusElements[focusElements.length - 1];
51
+ }
52
+ function hasFocus(element) {
53
+ const { activeElement } = document;
54
+ return element === activeElement || element.contains(activeElement);
55
+ }
56
+ function syncFocus() {
57
+ const lastElement = getLastElement();
58
+ const { activeElement } = document;
59
+ if (lastElement && !hasFocus(lastElement)) {
60
+ const focusableList = getFocusNodeList(lastElement);
61
+ (focusableList.includes(lastFocusElement) ? lastFocusElement : focusableList[0])?.focus();
62
+ } else lastFocusElement = activeElement;
63
+ }
64
+ function onWindowKeyDown(e) {
65
+ if (e.key === "Tab") {
66
+ const { activeElement } = document;
67
+ const focusableList = getFocusNodeList(getLastElement());
68
+ const last = focusableList[focusableList.length - 1];
69
+ if (e.shiftKey && activeElement === focusableList[0]) lastFocusElement = last;
70
+ else if (!e.shiftKey && activeElement === last) lastFocusElement = focusableList[0];
71
+ }
72
+ }
73
+ function lockFocus(element) {
74
+ if (element) {
75
+ focusElements = focusElements.filter((ele) => ele !== element);
76
+ focusElements.push(element);
77
+ window.addEventListener("focusin", syncFocus);
78
+ window.addEventListener("keydown", onWindowKeyDown, true);
79
+ syncFocus();
80
+ }
81
+ return () => {
82
+ lastFocusElement = null;
83
+ focusElements = focusElements.filter((ele) => ele !== element);
84
+ if (focusElements.length === 0) {
85
+ window.removeEventListener("focusin", syncFocus);
86
+ window.removeEventListener("keydown", onWindowKeyDown, true);
87
+ }
88
+ };
89
+ }
90
+ function useLockFocus(lock, getElement) {
91
+ (0, vue.watch)(lock, (_, _o, onCleanup) => {
92
+ if (lock.value) {
93
+ const element = getElement();
94
+ if (element) onCleanup(lockFocus(element));
95
+ }
96
+ });
97
+ }
69
98
  exports.getFocusNodeList = getFocusNodeList;
70
- exports.limitTabRange = limitTabRange;
71
- exports.saveLastFocusNode = saveLastFocusNode;
99
+ exports.lockFocus = lockFocus;
72
100
  exports.triggerFocus = triggerFocus;
101
+ exports.useLockFocus = useLockFocus;
@@ -1,12 +1,17 @@
1
+ import { Ref } from 'vue';
1
2
  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
3
  export interface InputFocusOptions extends FocusOptions {
10
4
  cursor?: 'start' | 'end' | 'all';
11
5
  }
12
6
  export declare function triggerFocus(element?: HTMLInputElement | HTMLTextAreaElement, option?: InputFocusOptions): void;
7
+ /**
8
+ * Lock focus in the element.
9
+ * It will force back to the first focusable element when focus leaves the element.
10
+ */
11
+ export declare function lockFocus(element: HTMLElement): VoidFunction;
12
+ /**
13
+ * Lock focus within an element.
14
+ * When locked, focus will be restricted to focusable elements within the specified element.
15
+ * If multiple elements are locked, only the last locked element will be effective.
16
+ */
17
+ export declare function useLockFocus(lock: Ref<boolean>, getElement: () => HTMLElement | null): void;
package/dist/Dom/focus.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import isVisible_default from "./isVisible.js";
2
+ import { watch } from "vue";
2
3
  function focusable(node, includePositive = false) {
3
4
  if (isVisible_default(node)) {
4
5
  const nodeName = node.nodeName.toLowerCase();
@@ -25,27 +26,6 @@ function getFocusNodeList(node, includePositive = false) {
25
26
  if (focusable(node, includePositive)) res.unshift(node);
26
27
  return res;
27
28
  }
28
- var lastFocusElement = null;
29
- function saveLastFocusNode() {
30
- lastFocusElement = document.activeElement;
31
- }
32
- function clearLastFocusNode() {
33
- lastFocusElement = null;
34
- }
35
- function backLastFocusNode() {
36
- if (lastFocusElement) try {
37
- lastFocusElement.focus();
38
- } catch (_e) {}
39
- }
40
- function limitTabRange(node, e) {
41
- if (e.keyCode === 9) {
42
- const tabNodeList = getFocusNodeList(node);
43
- if (tabNodeList[e.shiftKey ? 0 : tabNodeList.length - 1] === document.activeElement || node === document.activeElement) {
44
- tabNodeList[e.shiftKey ? tabNodeList.length - 1 : 0].focus();
45
- e.preventDefault();
46
- }
47
- }
48
- }
49
29
  function triggerFocus(element, option) {
50
30
  if (!element) return;
51
31
  element.focus(option);
@@ -63,4 +43,55 @@ function triggerFocus(element, option) {
63
43
  }
64
44
  }
65
45
  }
66
- export { backLastFocusNode, clearLastFocusNode, getFocusNodeList, limitTabRange, saveLastFocusNode, triggerFocus };
46
+ var lastFocusElement = null;
47
+ var focusElements = [];
48
+ function getLastElement() {
49
+ return focusElements[focusElements.length - 1];
50
+ }
51
+ function hasFocus(element) {
52
+ const { activeElement } = document;
53
+ return element === activeElement || element.contains(activeElement);
54
+ }
55
+ function syncFocus() {
56
+ const lastElement = getLastElement();
57
+ const { activeElement } = document;
58
+ if (lastElement && !hasFocus(lastElement)) {
59
+ const focusableList = getFocusNodeList(lastElement);
60
+ (focusableList.includes(lastFocusElement) ? lastFocusElement : focusableList[0])?.focus();
61
+ } else lastFocusElement = activeElement;
62
+ }
63
+ function onWindowKeyDown(e) {
64
+ if (e.key === "Tab") {
65
+ const { activeElement } = document;
66
+ const focusableList = getFocusNodeList(getLastElement());
67
+ const last = focusableList[focusableList.length - 1];
68
+ if (e.shiftKey && activeElement === focusableList[0]) lastFocusElement = last;
69
+ else if (!e.shiftKey && activeElement === last) lastFocusElement = focusableList[0];
70
+ }
71
+ }
72
+ function lockFocus(element) {
73
+ if (element) {
74
+ focusElements = focusElements.filter((ele) => ele !== element);
75
+ focusElements.push(element);
76
+ window.addEventListener("focusin", syncFocus);
77
+ window.addEventListener("keydown", onWindowKeyDown, true);
78
+ syncFocus();
79
+ }
80
+ return () => {
81
+ lastFocusElement = null;
82
+ focusElements = focusElements.filter((ele) => ele !== element);
83
+ if (focusElements.length === 0) {
84
+ window.removeEventListener("focusin", syncFocus);
85
+ window.removeEventListener("keydown", onWindowKeyDown, true);
86
+ }
87
+ };
88
+ }
89
+ function useLockFocus(lock, getElement) {
90
+ watch(lock, (_, _o, onCleanup) => {
91
+ if (lock.value) {
92
+ const element = getElement();
93
+ if (element) onCleanup(lockFocus(element));
94
+ }
95
+ });
96
+ }
97
+ export { getFocusNodeList, lockFocus, triggerFocus, useLockFocus };
@@ -0,0 +1,19 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let vue = require("vue");
3
+ function useControlledState(state, emit, updateKey = "value", defaultState, props) {
4
+ const mergedState = (0, vue.shallowRef)(defaultState ?? state.value);
5
+ function setState(nextState) {
6
+ if (emit) emit(`update:${updateKey}`, nextState);
7
+ if (props && props?.[`onUpdate:${updateKey}`]) props?.[`onUpdate:${updateKey}`](nextState);
8
+ (0, vue.nextTick)(() => {
9
+ if (state.value === void 0 && state.value !== nextState) mergedState.value = nextState;
10
+ });
11
+ }
12
+ (0, vue.watch)(() => (0, vue.toValue)(state), () => {
13
+ const prevState = (0, vue.toValue)(mergedState);
14
+ const nextState = (0, vue.toValue)(state);
15
+ if (prevState !== nextState) mergedState.value = nextState;
16
+ });
17
+ return [state, setState];
18
+ }
19
+ exports.useControlledState = useControlledState;
@@ -0,0 +1,2 @@
1
+ import { Ref } from 'vue';
2
+ export declare function useControlledState<T = any>(state: Ref<T | undefined>, emit?: any, updateKey?: string, defaultState?: T, props?: any): readonly [Ref<T, T>, (nextState: T) => void];
@@ -0,0 +1,18 @@
1
+ import { nextTick, shallowRef, toValue, watch } from "vue";
2
+ function useControlledState(state, emit, updateKey = "value", defaultState, props) {
3
+ const mergedState = shallowRef(defaultState ?? state.value);
4
+ function setState(nextState) {
5
+ if (emit) emit(`update:${updateKey}`, nextState);
6
+ if (props && props?.[`onUpdate:${updateKey}`]) props?.[`onUpdate:${updateKey}`](nextState);
7
+ nextTick(() => {
8
+ if (state.value === void 0 && state.value !== nextState) mergedState.value = nextState;
9
+ });
10
+ }
11
+ watch(() => toValue(state), () => {
12
+ const prevState = toValue(mergedState);
13
+ const nextState = toValue(state);
14
+ if (prevState !== nextState) mergedState.value = nextState;
15
+ });
16
+ return [state, setState];
17
+ }
18
+ export { useControlledState };
package/dist/index.cjs CHANGED
@@ -4,6 +4,7 @@ const require_raf = require("./raf.cjs");
4
4
  const require_omit = require("./omit.cjs");
5
5
  const require_RenderComponent = require("./RenderComponent.cjs");
6
6
  const require_classnames = require("./classnames.cjs");
7
+ const require_useControlledState = require("./hooks/useControlledState.cjs");
7
8
  const require_useId = require("./hooks/useId.cjs");
8
9
  const require_useMergedState = require("./hooks/useMergedState.cjs");
9
10
  const require_get = require("./utils/get.cjs");
@@ -17,6 +18,7 @@ exports.get = require_get.default;
17
18
  exports.omit = require_omit.default;
18
19
  exports.raf = require_raf.default;
19
20
  exports.set = require_set.default;
21
+ exports.useControlledState = require_useControlledState.useControlledState;
20
22
  exports.useId = require_useId.default;
21
23
  exports.useMergedState = require_useMergedState.default;
22
24
  exports.warning = require_warning.default;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as classNames, clsx } from './classnames';
2
+ export { useControlledState } from './hooks/useControlledState';
2
3
  export { default as useId } from './hooks/useId';
3
4
  export { default as useMergedState } from './hooks/useMergedState';
4
5
  export { default as KeyCode } from './KeyCode';
package/dist/index.js CHANGED
@@ -3,9 +3,10 @@ import raf_default from "./raf.js";
3
3
  import omit from "./omit.js";
4
4
  import RenderComponent_default from "./RenderComponent.js";
5
5
  import classNames, { clsx } from "./classnames.js";
6
+ import { useControlledState } from "./hooks/useControlledState.js";
6
7
  import useId_default from "./hooks/useId.js";
7
8
  import useMergedState from "./hooks/useMergedState.js";
8
9
  import get from "./utils/get.js";
9
10
  import set from "./utils/set.js";
10
11
  import warning_default from "./warning.js";
11
- export { KeyCode_default as KeyCode, RenderComponent_default as RenderComponent, classNames, clsx, get, omit, raf_default as raf, set, useId_default as useId, useMergedState, warning_default as warning };
12
+ export { KeyCode_default as KeyCode, RenderComponent_default as RenderComponent, classNames, clsx, get, omit, raf_default as raf, set, useControlledState, useId_default as useId, useMergedState, warning_default as warning };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@v-c/util",
3
3
  "type": "module",
4
- "version": "1.0.7",
4
+ "version": "1.0.9",
5
5
  "description": "Vue3 components utils",
6
6
  "publishConfig": {
7
7
  "access": "public"