@vitus-labs/hooks 2.0.0-beta.0 → 2.0.0-beta.1

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/lib/index.d.ts CHANGED
@@ -108,6 +108,9 @@ type UseFocusTrap = (ref: {
108
108
  * Traps keyboard focus within the referenced container.
109
109
  * Tab and Shift+Tab cycle through focusable elements inside.
110
110
  * Useful for modals, dialogs, and dropdown menus.
111
+ *
112
+ * Focusable elements are cached and only re-queried when DOM mutations
113
+ * inside the container add/remove nodes — not on every Tab keypress.
111
114
  */
112
115
  declare const useFocusTrap: UseFocusTrap;
113
116
  //#endregion
package/lib/index.js CHANGED
@@ -27,24 +27,21 @@ const useBreakpoint = () => {
27
27
  });
28
28
  useEffect(() => {
29
29
  if (sorted.length === 0) return void 0;
30
- const mqls = [];
30
+ let raf = 0;
31
31
  const update = () => {
32
+ raf = 0;
32
33
  const width = window.innerWidth;
33
34
  let match = sorted[0]?.[0];
34
35
  for (const [name, min] of sorted) if (width >= min) match = name;
35
36
  setCurrent(match);
36
37
  };
37
- for (const [, min] of sorted) {
38
- const mql = window.matchMedia(`(min-width: ${min}px)`);
39
- const handler = () => update();
40
- mql.addEventListener("change", handler);
41
- mqls.push({
42
- mql,
43
- handler
44
- });
45
- }
38
+ const onResize = () => {
39
+ if (raf === 0) raf = requestAnimationFrame(update);
40
+ };
41
+ window.addEventListener("resize", onResize, { passive: true });
46
42
  return () => {
47
- for (const { mql, handler } of mqls) mql.removeEventListener("change", handler);
43
+ if (raf !== 0) cancelAnimationFrame(raf);
44
+ window.removeEventListener("resize", onResize);
48
45
  };
49
46
  }, [sorted]);
50
47
  return current;
@@ -254,14 +251,27 @@ const wrapFocus = (e, target) => {
254
251
  * Traps keyboard focus within the referenced container.
255
252
  * Tab and Shift+Tab cycle through focusable elements inside.
256
253
  * Useful for modals, dialogs, and dropdown menus.
254
+ *
255
+ * Focusable elements are cached and only re-queried when DOM mutations
256
+ * inside the container add/remove nodes — not on every Tab keypress.
257
257
  */
258
258
  const useFocusTrap = (ref, enabled = true) => {
259
259
  useEffect(() => {
260
260
  if (!enabled) return void 0;
261
+ const container = ref.current;
262
+ if (!container) return void 0;
263
+ let focusable = null;
264
+ const refresh = () => {
265
+ focusable = container.querySelectorAll(FOCUSABLE);
266
+ };
267
+ refresh();
268
+ const observer = new MutationObserver(refresh);
269
+ observer.observe(container, {
270
+ childList: true,
271
+ subtree: true
272
+ });
261
273
  const handler = (e) => {
262
- if (e.key !== "Tab" || !ref.current) return;
263
- const focusable = ref.current.querySelectorAll(FOCUSABLE);
264
- if (focusable.length === 0) return;
274
+ if (e.key !== "Tab" || !focusable || focusable.length === 0) return;
265
275
  const first = focusable[0];
266
276
  const last = focusable[focusable.length - 1];
267
277
  const active = document.activeElement;
@@ -269,7 +279,10 @@ const useFocusTrap = (ref, enabled = true) => {
269
279
  else if (!e.shiftKey && active === last) wrapFocus(e, first);
270
280
  };
271
281
  document.addEventListener("keydown", handler);
272
- return () => document.removeEventListener("keydown", handler);
282
+ return () => {
283
+ observer.disconnect();
284
+ document.removeEventListener("keydown", handler);
285
+ };
273
286
  }, [ref, enabled]);
274
287
  };
275
288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitus-labs/hooks",
3
- "version": "2.0.0-beta.0",
3
+ "version": "2.0.0-beta.1",
4
4
  "license": "MIT",
5
5
  "author": "Vit Bokisch <vit@bokisch.cz>",
6
6
  "maintainers": [
@@ -52,7 +52,7 @@
52
52
  "node": ">= 18"
53
53
  },
54
54
  "peerDependencies": {
55
- "@vitus-labs/core": "2.0.0-alpha.27",
55
+ "@vitus-labs/core": "2.0.0-beta.1",
56
56
  "react": ">= 19",
57
57
  "react-native": ">= 0.76"
58
58
  },
@@ -62,11 +62,10 @@
62
62
  }
63
63
  },
64
64
  "devDependencies": {
65
- "@vitus-labs/core": "2.0.0-beta.0",
66
- "@vitus-labs/tools-rolldown": "1.10.0",
67
- "@vitus-labs/tools-storybook": "1.10.0",
68
- "@vitus-labs/tools-typescript": "1.10.0",
69
- "react-native": ">=0.76.0"
70
- },
71
- "gitHead": "dd8b9f356086ecd8bfb69c87fcad1e8bfa9ab1f4"
65
+ "@vitus-labs/core": "workspace:*",
66
+ "@vitus-labs/tools-rolldown": "2.2.0",
67
+ "@vitus-labs/tools-storybook": "2.2.0",
68
+ "@vitus-labs/tools-typescript": "2.1.0",
69
+ "react-native": ">=0.84.1"
70
+ }
72
71
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023-present Vit Bokisch
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.