@mantine/hooks 9.7.2 → 9.7.3

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.
@@ -2,23 +2,15 @@
2
2
  let react = require("react");
3
3
  //#region packages/@mantine/hooks/src/use-event-listener/use-event-listener.ts
4
4
  function useEventListener(type, listener, options) {
5
- const previousListener = (0, react.useRef)(null);
6
- const previousNode = (0, react.useRef)(null);
7
- const callbackRef = (0, react.useCallback)((node) => {
5
+ return (0, react.useCallback)((node) => {
8
6
  if (!node) return;
9
- if (previousNode.current && previousListener.current) previousNode.current.removeEventListener(type, previousListener.current, options);
10
7
  node.addEventListener(type, listener, options);
11
- previousNode.current = node;
12
- previousListener.current = listener;
8
+ return () => node.removeEventListener(type, listener, options);
13
9
  }, [
14
10
  type,
15
11
  listener,
16
12
  options
17
13
  ]);
18
- (0, react.useEffect)(() => () => {
19
- if (previousNode.current && previousListener.current) previousNode.current.removeEventListener(type, previousListener.current, options);
20
- }, [type, options]);
21
- return callbackRef;
22
14
  }
23
15
  //#endregion
24
16
  exports.useEventListener = useEventListener;
@@ -1 +1 @@
1
- {"version":3,"file":"use-event-listener.cjs","names":["useRef","useCallback"],"sources":["../../src/use-event-listener/use-event-listener.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\nexport function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = any>(\n type: K,\n listener: (this: T, ev: HTMLElementEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions\n): React.RefCallback<T | null> {\n const previousListener = useRef<Function | null>(null);\n const previousNode = useRef<T | null>(null);\n\n const callbackRef: React.RefCallback<T | null> = useCallback(\n (node) => {\n if (!node) {\n return;\n }\n\n if (previousNode.current && previousListener.current) {\n previousNode.current.removeEventListener(type, previousListener.current as any, options);\n }\n\n node.addEventListener(type, listener as any, options);\n previousNode.current = node;\n previousListener.current = listener;\n },\n [type, listener, options]\n );\n\n useEffect(\n () => () => {\n if (previousNode.current && previousListener.current) {\n previousNode.current.removeEventListener(type, previousListener.current as any, options);\n }\n },\n [type, options]\n );\n\n return callbackRef;\n}\n"],"mappings":";;;AAEA,SAAgB,iBACd,MACA,UACA,SAC6B;CAC7B,MAAM,oBAAA,GAAmBA,MAAAA,OAAAA,CAAwB,IAAI;CACrD,MAAM,gBAAA,GAAeA,MAAAA,OAAAA,CAAiB,IAAI;CAE1C,MAAM,eAAA,GAA2CC,MAAAA,YAAAA,EAC9C,SAAS;EACR,IAAI,CAAC,MACH;EAGF,IAAI,aAAa,WAAW,iBAAiB,SAC3C,aAAa,QAAQ,oBAAoB,MAAM,iBAAiB,SAAgB,OAAO;EAGzF,KAAK,iBAAiB,MAAM,UAAiB,OAAO;EACpD,aAAa,UAAU;EACvB,iBAAiB,UAAU;CAC7B,GACA;EAAC;EAAM;EAAU;CAAO,CAC1B;CAEA,CAAA,GAAA,MAAA,UAAA,aACc;EACV,IAAI,aAAa,WAAW,iBAAiB,SAC3C,aAAa,QAAQ,oBAAoB,MAAM,iBAAiB,SAAgB,OAAO;CAE3F,GACA,CAAC,MAAM,OAAO,CAChB;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"use-event-listener.cjs","names":["useCallback"],"sources":["../../src/use-event-listener/use-event-listener.ts"],"sourcesContent":["import { useCallback } from 'react';\n\nexport function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = any>(\n type: K,\n listener: (this: T, ev: HTMLElementEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions\n): React.RefCallback<T | null> {\n return useCallback(\n (node) => {\n if (!node) {\n return;\n }\n\n node.addEventListener(type, listener as any, options);\n return () => node.removeEventListener(type, listener as any, options);\n },\n [type, listener, options]\n );\n}\n"],"mappings":";;;AAEA,SAAgB,iBACd,MACA,UACA,SAC6B;CAC7B,QAAA,GAAOA,MAAAA,YAAAA,EACJ,SAAS;EACR,IAAI,CAAC,MACH;EAGF,KAAK,iBAAiB,MAAM,UAAiB,OAAO;EACpD,aAAa,KAAK,oBAAoB,MAAM,UAAiB,OAAO;CACtE,GACA;EAAC;EAAM;EAAU;CAAO,CAC1B;AACF"}
@@ -1,21 +1,74 @@
1
1
  "use client";
2
2
  const require_tabbable = require("./tabbable.cjs");
3
3
  //#region packages/@mantine/hooks/src/use-focus-trap/scope-tab.ts
4
- function scopeTab(node, event) {
4
+ const NATIVE_TAB_STOPS_INPUT_TYPES = [
5
+ "date",
6
+ "time",
7
+ "datetime-local",
8
+ "month",
9
+ "week"
10
+ ];
11
+ const NATIVE_TAB_STOPS_NODES = [
12
+ "audio",
13
+ "video",
14
+ "iframe",
15
+ "object",
16
+ "embed"
17
+ ];
18
+ function isRadio(element) {
19
+ return !!element && element.tagName === "INPUT" && element.type === "radio";
20
+ }
21
+ function isSameRadioGroup(a, b) {
22
+ return a.name !== "" && a.name === b.name && a.form === b.form;
23
+ }
24
+ function hasNativeTabStops(element) {
25
+ const nodeName = element.nodeName.toLowerCase();
26
+ if (nodeName === "input") return NATIVE_TAB_STOPS_INPUT_TYPES.includes(element.type);
27
+ return NATIVE_TAB_STOPS_NODES.includes(nodeName);
28
+ }
29
+ function getTabSequence(node, activeElement) {
5
30
  const tabbable = require_tabbable.findTabbableDescendants(node);
6
- if (!tabbable.length) {
7
- event.preventDefault();
8
- return;
31
+ return tabbable.filter((element) => {
32
+ if (element === activeElement || !isRadio(element)) return true;
33
+ if (isRadio(activeElement) && isSameRadioGroup(element, activeElement)) return false;
34
+ if (element.checked) return true;
35
+ return !tabbable.some((item) => isRadio(item) && item.checked && isSameRadioGroup(item, element));
36
+ });
37
+ }
38
+ function getStartIndex(sequence, activeElement, node, backwards) {
39
+ const activeIndex = sequence.indexOf(activeElement);
40
+ if (activeIndex !== -1) return activeIndex + 1;
41
+ if (activeElement === node) return 0;
42
+ const position = backwards ? Node.DOCUMENT_POSITION_PRECEDING : Node.DOCUMENT_POSITION_FOLLOWING;
43
+ const nextIndex = sequence.findIndex((element) => (activeElement.compareDocumentPosition(element) & position) !== 0);
44
+ return nextIndex === -1 ? 0 : nextIndex;
45
+ }
46
+ function getCandidates(node, activeElement, backwards) {
47
+ const sequence = getTabSequence(node, activeElement);
48
+ if (backwards) sequence.reverse();
49
+ const startIndex = getStartIndex(sequence, activeElement, node, backwards);
50
+ return [...sequence.slice(startIndex), ...sequence.slice(0, startIndex)].filter((element) => element !== activeElement);
51
+ }
52
+ function focusFirstAvailable(candidates, root) {
53
+ for (const candidate of candidates) {
54
+ candidate.focus();
55
+ if (root.activeElement === candidate) return;
9
56
  }
10
- const finalTabbable = tabbable[event.shiftKey ? 0 : tabbable.length - 1];
57
+ }
58
+ function scopeTab(node, event) {
11
59
  const root = node.getRootNode();
12
- let leavingFinalTabbable = finalTabbable === root.activeElement || node === root.activeElement;
13
60
  const activeElement = root.activeElement;
14
- if (activeElement.tagName === "INPUT" && activeElement.getAttribute("type") === "radio") leavingFinalTabbable = tabbable.filter((element) => element.getAttribute("type") === "radio" && element.getAttribute("name") === activeElement.getAttribute("name")).includes(finalTabbable);
15
- if (!leavingFinalTabbable) return;
61
+ if (!activeElement || activeElement !== node && !node.contains(activeElement)) return;
62
+ const candidates = getCandidates(node, activeElement, event.shiftKey);
63
+ if (hasNativeTabStops(activeElement)) {
64
+ setTimeout(() => {
65
+ const current = root.activeElement;
66
+ if (node.isConnected && current !== activeElement && current !== candidates[0]) focusFirstAvailable([...candidates, activeElement], root);
67
+ });
68
+ return;
69
+ }
16
70
  event.preventDefault();
17
- const target = tabbable[event.shiftKey ? tabbable.length - 1 : 0];
18
- if (target) target.focus();
71
+ focusFirstAvailable(candidates, root);
19
72
  }
20
73
  //#endregion
21
74
  exports.scopeTab = scopeTab;
@@ -1 +1 @@
1
- {"version":3,"file":"scope-tab.cjs","names":["findTabbableDescendants"],"sources":["../../src/use-focus-trap/scope-tab.ts"],"sourcesContent":["import { findTabbableDescendants } from './tabbable';\n\nexport function scopeTab(node: HTMLElement, event: KeyboardEvent) {\n const tabbable = findTabbableDescendants(node);\n if (!tabbable.length) {\n event.preventDefault();\n return;\n }\n const finalTabbable = tabbable[event.shiftKey ? 0 : tabbable.length - 1];\n const root = node.getRootNode() as unknown as DocumentOrShadowRoot;\n let leavingFinalTabbable = finalTabbable === root.activeElement || node === root.activeElement;\n\n const activeElement = root.activeElement as Element;\n const activeElementIsRadio =\n activeElement.tagName === 'INPUT' && activeElement.getAttribute('type') === 'radio';\n if (activeElementIsRadio) {\n const activeRadioGroup = tabbable.filter(\n (element) =>\n element.getAttribute('type') === 'radio' &&\n element.getAttribute('name') === activeElement.getAttribute('name')\n );\n leavingFinalTabbable = activeRadioGroup.includes(finalTabbable);\n }\n\n if (!leavingFinalTabbable) {\n return;\n }\n\n event.preventDefault();\n\n const target = tabbable[event.shiftKey ? tabbable.length - 1 : 0];\n\n if (target) {\n target.focus();\n }\n}\n"],"mappings":";;;AAEA,SAAgB,SAAS,MAAmB,OAAsB;CAChE,MAAM,WAAWA,iBAAAA,wBAAwB,IAAI;CAC7C,IAAI,CAAC,SAAS,QAAQ;EACpB,MAAM,eAAe;EACrB;CACF;CACA,MAAM,gBAAgB,SAAS,MAAM,WAAW,IAAI,SAAS,SAAS;CACtE,MAAM,OAAO,KAAK,YAAY;CAC9B,IAAI,uBAAuB,kBAAkB,KAAK,iBAAiB,SAAS,KAAK;CAEjF,MAAM,gBAAgB,KAAK;CAG3B,IADE,cAAc,YAAY,WAAW,cAAc,aAAa,MAAM,MAAM,SAO5E,uBALyB,SAAS,QAC/B,YACC,QAAQ,aAAa,MAAM,MAAM,WACjC,QAAQ,aAAa,MAAM,MAAM,cAAc,aAAa,MAAM,CAEhC,CAAC,CAAC,SAAS,aAAa;CAGhE,IAAI,CAAC,sBACH;CAGF,MAAM,eAAe;CAErB,MAAM,SAAS,SAAS,MAAM,WAAW,SAAS,SAAS,IAAI;CAE/D,IAAI,QACF,OAAO,MAAM;AAEjB"}
1
+ {"version":3,"file":"scope-tab.cjs","names":["findTabbableDescendants"],"sources":["../../src/use-focus-trap/scope-tab.ts"],"sourcesContent":["import { findTabbableDescendants } from './tabbable';\n\nconst NATIVE_TAB_STOPS_INPUT_TYPES = ['date', 'time', 'datetime-local', 'month', 'week'];\nconst NATIVE_TAB_STOPS_NODES = ['audio', 'video', 'iframe', 'object', 'embed'];\n\nfunction isRadio(element: Element | null): element is HTMLInputElement {\n return !!element && element.tagName === 'INPUT' && (element as HTMLInputElement).type === 'radio';\n}\n\nfunction isSameRadioGroup(a: HTMLInputElement, b: HTMLInputElement) {\n return a.name !== '' && a.name === b.name && a.form === b.form;\n}\n\nfunction hasNativeTabStops(element: Element) {\n const nodeName = element.nodeName.toLowerCase();\n\n if (nodeName === 'input') {\n return NATIVE_TAB_STOPS_INPUT_TYPES.includes((element as HTMLInputElement).type);\n }\n\n return NATIVE_TAB_STOPS_NODES.includes(nodeName);\n}\n\nfunction getTabSequence(node: HTMLElement, activeElement: Element) {\n const tabbable = findTabbableDescendants(node);\n\n return tabbable.filter((element) => {\n if (element === activeElement || !isRadio(element)) {\n return true;\n }\n\n if (isRadio(activeElement) && isSameRadioGroup(element, activeElement)) {\n return false;\n }\n\n if (element.checked) {\n return true;\n }\n\n return !tabbable.some(\n (item) => isRadio(item) && item.checked && isSameRadioGroup(item, element)\n );\n });\n}\n\nfunction getStartIndex(\n sequence: HTMLElement[],\n activeElement: Element,\n node: HTMLElement,\n backwards: boolean\n) {\n const activeIndex = sequence.indexOf(activeElement as HTMLElement);\n if (activeIndex !== -1) {\n return activeIndex + 1;\n }\n\n if (activeElement === node) {\n return 0;\n }\n\n const position = backwards ? Node.DOCUMENT_POSITION_PRECEDING : Node.DOCUMENT_POSITION_FOLLOWING;\n const nextIndex = sequence.findIndex(\n (element) => (activeElement.compareDocumentPosition(element) & position) !== 0\n );\n\n return nextIndex === -1 ? 0 : nextIndex;\n}\n\nfunction getCandidates(node: HTMLElement, activeElement: Element, backwards: boolean) {\n const sequence = getTabSequence(node, activeElement);\n if (backwards) {\n sequence.reverse();\n }\n\n const startIndex = getStartIndex(sequence, activeElement, node, backwards);\n return [...sequence.slice(startIndex), ...sequence.slice(0, startIndex)].filter(\n (element) => element !== activeElement\n );\n}\n\nfunction focusFirstAvailable(candidates: HTMLElement[], root: DocumentOrShadowRoot) {\n for (const candidate of candidates) {\n candidate.focus();\n\n if (root.activeElement === candidate) {\n return;\n }\n }\n}\n\nexport function scopeTab(node: HTMLElement, event: KeyboardEvent) {\n const root = node.getRootNode() as unknown as DocumentOrShadowRoot;\n const activeElement = root.activeElement;\n\n if (!activeElement || (activeElement !== node && !node.contains(activeElement))) {\n return;\n }\n\n const candidates = getCandidates(node, activeElement, event.shiftKey);\n\n if (hasNativeTabStops(activeElement)) {\n setTimeout(() => {\n const current = root.activeElement;\n if (node.isConnected && current !== activeElement && current !== candidates[0]) {\n focusFirstAvailable([...candidates, activeElement as HTMLElement], root);\n }\n });\n return;\n }\n\n event.preventDefault();\n focusFirstAvailable(candidates, root);\n}\n"],"mappings":";;;AAEA,MAAM,+BAA+B;CAAC;CAAQ;CAAQ;CAAkB;CAAS;AAAM;AACvF,MAAM,yBAAyB;CAAC;CAAS;CAAS;CAAU;CAAU;AAAO;AAE7E,SAAS,QAAQ,SAAsD;CACrE,OAAO,CAAC,CAAC,WAAW,QAAQ,YAAY,WAAY,QAA6B,SAAS;AAC5F;AAEA,SAAS,iBAAiB,GAAqB,GAAqB;CAClE,OAAO,EAAE,SAAS,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE;AAC5D;AAEA,SAAS,kBAAkB,SAAkB;CAC3C,MAAM,WAAW,QAAQ,SAAS,YAAY;CAE9C,IAAI,aAAa,SACf,OAAO,6BAA6B,SAAU,QAA6B,IAAI;CAGjF,OAAO,uBAAuB,SAAS,QAAQ;AACjD;AAEA,SAAS,eAAe,MAAmB,eAAwB;CACjE,MAAM,WAAWA,iBAAAA,wBAAwB,IAAI;CAE7C,OAAO,SAAS,QAAQ,YAAY;EAClC,IAAI,YAAY,iBAAiB,CAAC,QAAQ,OAAO,GAC/C,OAAO;EAGT,IAAI,QAAQ,aAAa,KAAK,iBAAiB,SAAS,aAAa,GACnE,OAAO;EAGT,IAAI,QAAQ,SACV,OAAO;EAGT,OAAO,CAAC,SAAS,MACd,SAAS,QAAQ,IAAI,KAAK,KAAK,WAAW,iBAAiB,MAAM,OAAO,CAC3E;CACF,CAAC;AACH;AAEA,SAAS,cACP,UACA,eACA,MACA,WACA;CACA,MAAM,cAAc,SAAS,QAAQ,aAA4B;CACjE,IAAI,gBAAgB,IAClB,OAAO,cAAc;CAGvB,IAAI,kBAAkB,MACpB,OAAO;CAGT,MAAM,WAAW,YAAY,KAAK,8BAA8B,KAAK;CACrE,MAAM,YAAY,SAAS,WACxB,aAAa,cAAc,wBAAwB,OAAO,IAAI,cAAc,CAC/E;CAEA,OAAO,cAAc,KAAK,IAAI;AAChC;AAEA,SAAS,cAAc,MAAmB,eAAwB,WAAoB;CACpF,MAAM,WAAW,eAAe,MAAM,aAAa;CACnD,IAAI,WACF,SAAS,QAAQ;CAGnB,MAAM,aAAa,cAAc,UAAU,eAAe,MAAM,SAAS;CACzE,OAAO,CAAC,GAAG,SAAS,MAAM,UAAU,GAAG,GAAG,SAAS,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,QACtE,YAAY,YAAY,aAC3B;AACF;AAEA,SAAS,oBAAoB,YAA2B,MAA4B;CAClF,KAAK,MAAM,aAAa,YAAY;EAClC,UAAU,MAAM;EAEhB,IAAI,KAAK,kBAAkB,WACzB;CAEJ;AACF;AAEA,SAAgB,SAAS,MAAmB,OAAsB;CAChE,MAAM,OAAO,KAAK,YAAY;CAC9B,MAAM,gBAAgB,KAAK;CAE3B,IAAI,CAAC,iBAAkB,kBAAkB,QAAQ,CAAC,KAAK,SAAS,aAAa,GAC3E;CAGF,MAAM,aAAa,cAAc,MAAM,eAAe,MAAM,QAAQ;CAEpE,IAAI,kBAAkB,aAAa,GAAG;EACpC,iBAAiB;GACf,MAAM,UAAU,KAAK;GACrB,IAAI,KAAK,eAAe,YAAY,iBAAiB,YAAY,WAAW,IAC1E,oBAAoB,CAAC,GAAG,YAAY,aAA4B,GAAG,IAAI;EAE3E,CAAC;EACD;CACF;CAEA,MAAM,eAAe;CACrB,oBAAoB,YAAY,IAAI;AACtC"}
@@ -1,7 +1,15 @@
1
1
  "use client";
2
2
  //#region packages/@mantine/hooks/src/use-focus-trap/tabbable.ts
3
- const TABBABLE_NODES = /input|select|textarea|button|object/;
4
- const FOCUS_SELECTOR = "a, input, select, textarea, button, object, [tabindex]";
3
+ const TABBABLE_NODES = /input|select|textarea|button|object|summary|iframe/;
4
+ const FOCUS_SELECTOR = "a, input, select, textarea, button, object, summary, iframe, audio[controls], video[controls], [contenteditable], [tabindex]";
5
+ function isContentEditable(element) {
6
+ const value = element.getAttribute("contenteditable");
7
+ return value !== null && value !== "false";
8
+ }
9
+ function hasMediaControls(element) {
10
+ const nodeName = element.nodeName.toLowerCase();
11
+ return (nodeName === "audio" || nodeName === "video") && element.hasAttribute("controls");
12
+ }
5
13
  function hidden(element) {
6
14
  if (process.env.NODE_ENV === "test") return false;
7
15
  return element.style.display === "none";
@@ -24,14 +32,17 @@ function getElementTabIndex(element) {
24
32
  function focusable(element) {
25
33
  const nodeName = element.nodeName.toLowerCase();
26
34
  const isTabIndexNotNaN = !Number.isNaN(getElementTabIndex(element));
27
- return (TABBABLE_NODES.test(nodeName) && !element.disabled || (element instanceof HTMLAnchorElement ? element.href || isTabIndexNotNaN : isTabIndexNotNaN)) && visible(element);
35
+ return (TABBABLE_NODES.test(nodeName) && !element.disabled || isContentEditable(element) || hasMediaControls(element) || (element instanceof HTMLAnchorElement ? element.href || isTabIndexNotNaN : isTabIndexNotNaN)) && visible(element);
28
36
  }
29
37
  function tabbable(element) {
30
38
  const tabIndex = getElementTabIndex(element);
31
39
  return (Number.isNaN(tabIndex) || tabIndex >= 0) && focusable(element);
32
40
  }
33
41
  function findTabbableDescendants(element) {
34
- return Array.from(element.querySelectorAll(FOCUS_SELECTOR)).filter(tabbable);
42
+ const elements = Array.from(element.querySelectorAll(FOCUS_SELECTOR)).filter(tabbable);
43
+ const positive = elements.filter((item) => getElementTabIndex(item) > 0).sort((a, b) => getElementTabIndex(a) - getElementTabIndex(b));
44
+ const regular = elements.filter((item) => !(getElementTabIndex(item) > 0));
45
+ return [...positive, ...regular];
35
46
  }
36
47
  //#endregion
37
48
  exports.FOCUS_SELECTOR = FOCUS_SELECTOR;
@@ -1 +1 @@
1
- {"version":3,"file":"tabbable.cjs","names":[],"sources":["../../src/use-focus-trap/tabbable.ts"],"sourcesContent":["const TABBABLE_NODES = /input|select|textarea|button|object/;\nexport const FOCUS_SELECTOR = 'a, input, select, textarea, button, object, [tabindex]';\n\nfunction hidden(element: HTMLElement) {\n if (process.env.NODE_ENV === 'test') {\n return false;\n }\n\n return element.style.display === 'none';\n}\n\nfunction visible(element: HTMLElement) {\n const isHidden =\n element.getAttribute('aria-hidden') ||\n element.getAttribute('hidden') ||\n element.getAttribute('type') === 'hidden';\n\n if (isHidden) {\n return false;\n }\n\n let parentElement: HTMLElement = element;\n while (parentElement) {\n if (parentElement === document.body || parentElement.nodeType === 11) {\n break;\n }\n\n if (hidden(parentElement)) {\n return false;\n }\n\n parentElement = parentElement.parentNode as HTMLElement;\n }\n\n return true;\n}\n\nfunction getElementTabIndex(element: HTMLElement) {\n let tabIndex: string | null | undefined = element.getAttribute('tabindex');\n if (tabIndex === null) {\n tabIndex = undefined;\n }\n return parseInt(tabIndex as string, 10);\n}\n\nexport function focusable(element: HTMLElement) {\n const nodeName = element.nodeName.toLowerCase();\n const isTabIndexNotNaN = !Number.isNaN(getElementTabIndex(element));\n const res =\n // @ts-expect-error function accepts any html element but if it is a button, it should not be disabled to trigger the condition\n (TABBABLE_NODES.test(nodeName) && !element.disabled) ||\n (element instanceof HTMLAnchorElement ? element.href || isTabIndexNotNaN : isTabIndexNotNaN);\n\n return res && visible(element);\n}\n\nexport function tabbable(element: HTMLElement) {\n const tabIndex = getElementTabIndex(element);\n const isTabIndexNaN = Number.isNaN(tabIndex);\n return (isTabIndexNaN || tabIndex >= 0) && focusable(element);\n}\n\nexport function findTabbableDescendants(element: HTMLElement): HTMLElement[] {\n return Array.from(element.querySelectorAll<HTMLElement>(FOCUS_SELECTOR)).filter(tabbable);\n}\n"],"mappings":";;AAAA,MAAM,iBAAiB;AACvB,MAAa,iBAAiB;AAE9B,SAAS,OAAO,SAAsB;CACpC,IAAI,QAAQ,IAAI,aAAa,QAC3B,OAAO;CAGT,OAAO,QAAQ,MAAM,YAAY;AACnC;AAEA,SAAS,QAAQ,SAAsB;CAMrC,IAJE,QAAQ,aAAa,aAAa,KAClC,QAAQ,aAAa,QAAQ,KAC7B,QAAQ,aAAa,MAAM,MAAM,UAGjC,OAAO;CAGT,IAAI,gBAA6B;CACjC,OAAO,eAAe;EACpB,IAAI,kBAAkB,SAAS,QAAQ,cAAc,aAAa,IAChE;EAGF,IAAI,OAAO,aAAa,GACtB,OAAO;EAGT,gBAAgB,cAAc;CAChC;CAEA,OAAO;AACT;AAEA,SAAS,mBAAmB,SAAsB;CAChD,IAAI,WAAsC,QAAQ,aAAa,UAAU;CACzE,IAAI,aAAa,MACf,WAAW,KAAA;CAEb,OAAO,SAAS,UAAoB,EAAE;AACxC;AAEA,SAAgB,UAAU,SAAsB;CAC9C,MAAM,WAAW,QAAQ,SAAS,YAAY;CAC9C,MAAM,mBAAmB,CAAC,OAAO,MAAM,mBAAmB,OAAO,CAAC;CAMlE,QAHG,eAAe,KAAK,QAAQ,KAAK,CAAC,QAAQ,aAC1C,mBAAmB,oBAAoB,QAAQ,QAAQ,mBAAmB,sBAE/D,QAAQ,OAAO;AAC/B;AAEA,SAAgB,SAAS,SAAsB;CAC7C,MAAM,WAAW,mBAAmB,OAAO;CAE3C,QADsB,OAAO,MAAM,QACf,KAAK,YAAY,MAAM,UAAU,OAAO;AAC9D;AAEA,SAAgB,wBAAwB,SAAqC;CAC3E,OAAO,MAAM,KAAK,QAAQ,iBAA8B,cAAc,CAAC,CAAC,CAAC,OAAO,QAAQ;AAC1F"}
1
+ {"version":3,"file":"tabbable.cjs","names":[],"sources":["../../src/use-focus-trap/tabbable.ts"],"sourcesContent":["const TABBABLE_NODES = /input|select|textarea|button|object|summary|iframe/;\nexport const FOCUS_SELECTOR =\n 'a, input, select, textarea, button, object, summary, iframe, audio[controls], video[controls], [contenteditable], [tabindex]';\n\nfunction isContentEditable(element: HTMLElement) {\n const value = element.getAttribute('contenteditable');\n return value !== null && value !== 'false';\n}\n\nfunction hasMediaControls(element: HTMLElement) {\n const nodeName = element.nodeName.toLowerCase();\n return (nodeName === 'audio' || nodeName === 'video') && element.hasAttribute('controls');\n}\n\nfunction hidden(element: HTMLElement) {\n if (process.env.NODE_ENV === 'test') {\n return false;\n }\n\n return element.style.display === 'none';\n}\n\nfunction visible(element: HTMLElement) {\n const isHidden =\n element.getAttribute('aria-hidden') ||\n element.getAttribute('hidden') ||\n element.getAttribute('type') === 'hidden';\n\n if (isHidden) {\n return false;\n }\n\n let parentElement: HTMLElement = element;\n while (parentElement) {\n if (parentElement === document.body || parentElement.nodeType === 11) {\n break;\n }\n\n if (hidden(parentElement)) {\n return false;\n }\n\n parentElement = parentElement.parentNode as HTMLElement;\n }\n\n return true;\n}\n\nfunction getElementTabIndex(element: HTMLElement) {\n let tabIndex: string | null | undefined = element.getAttribute('tabindex');\n if (tabIndex === null) {\n tabIndex = undefined;\n }\n return parseInt(tabIndex as string, 10);\n}\n\nexport function focusable(element: HTMLElement) {\n const nodeName = element.nodeName.toLowerCase();\n const isTabIndexNotNaN = !Number.isNaN(getElementTabIndex(element));\n const res =\n // @ts-expect-error function accepts any html element but if it is a button, it should not be disabled to trigger the condition\n (TABBABLE_NODES.test(nodeName) && !element.disabled) ||\n isContentEditable(element) ||\n hasMediaControls(element) ||\n (element instanceof HTMLAnchorElement ? element.href || isTabIndexNotNaN : isTabIndexNotNaN);\n\n return res && visible(element);\n}\n\nexport function tabbable(element: HTMLElement) {\n const tabIndex = getElementTabIndex(element);\n const isTabIndexNaN = Number.isNaN(tabIndex);\n return (isTabIndexNaN || tabIndex >= 0) && focusable(element);\n}\n\nexport function findTabbableDescendants(element: HTMLElement): HTMLElement[] {\n const elements = Array.from(element.querySelectorAll<HTMLElement>(FOCUS_SELECTOR)).filter(\n tabbable\n );\n const positive = elements\n .filter((item) => getElementTabIndex(item) > 0)\n .sort((a, b) => getElementTabIndex(a) - getElementTabIndex(b));\n const regular = elements.filter((item) => !(getElementTabIndex(item) > 0));\n return [...positive, ...regular];\n}\n"],"mappings":";;AAAA,MAAM,iBAAiB;AACvB,MAAa,iBACX;AAEF,SAAS,kBAAkB,SAAsB;CAC/C,MAAM,QAAQ,QAAQ,aAAa,iBAAiB;CACpD,OAAO,UAAU,QAAQ,UAAU;AACrC;AAEA,SAAS,iBAAiB,SAAsB;CAC9C,MAAM,WAAW,QAAQ,SAAS,YAAY;CAC9C,QAAQ,aAAa,WAAW,aAAa,YAAY,QAAQ,aAAa,UAAU;AAC1F;AAEA,SAAS,OAAO,SAAsB;CACpC,IAAI,QAAQ,IAAI,aAAa,QAC3B,OAAO;CAGT,OAAO,QAAQ,MAAM,YAAY;AACnC;AAEA,SAAS,QAAQ,SAAsB;CAMrC,IAJE,QAAQ,aAAa,aAAa,KAClC,QAAQ,aAAa,QAAQ,KAC7B,QAAQ,aAAa,MAAM,MAAM,UAGjC,OAAO;CAGT,IAAI,gBAA6B;CACjC,OAAO,eAAe;EACpB,IAAI,kBAAkB,SAAS,QAAQ,cAAc,aAAa,IAChE;EAGF,IAAI,OAAO,aAAa,GACtB,OAAO;EAGT,gBAAgB,cAAc;CAChC;CAEA,OAAO;AACT;AAEA,SAAS,mBAAmB,SAAsB;CAChD,IAAI,WAAsC,QAAQ,aAAa,UAAU;CACzE,IAAI,aAAa,MACf,WAAW,KAAA;CAEb,OAAO,SAAS,UAAoB,EAAE;AACxC;AAEA,SAAgB,UAAU,SAAsB;CAC9C,MAAM,WAAW,QAAQ,SAAS,YAAY;CAC9C,MAAM,mBAAmB,CAAC,OAAO,MAAM,mBAAmB,OAAO,CAAC;CAQlE,QALG,eAAe,KAAK,QAAQ,KAAK,CAAC,QAAQ,YAC3C,kBAAkB,OAAO,KACzB,iBAAiB,OAAO,MACvB,mBAAmB,oBAAoB,QAAQ,QAAQ,mBAAmB,sBAE/D,QAAQ,OAAO;AAC/B;AAEA,SAAgB,SAAS,SAAsB;CAC7C,MAAM,WAAW,mBAAmB,OAAO;CAE3C,QADsB,OAAO,MAAM,QACf,KAAK,YAAY,MAAM,UAAU,OAAO;AAC9D;AAEA,SAAgB,wBAAwB,SAAqC;CAC3E,MAAM,WAAW,MAAM,KAAK,QAAQ,iBAA8B,cAAc,CAAC,CAAC,CAAC,OACjF,QACF;CACA,MAAM,WAAW,SACd,QAAQ,SAAS,mBAAmB,IAAI,IAAI,CAAC,CAAC,CAC9C,MAAM,GAAG,MAAM,mBAAmB,CAAC,IAAI,mBAAmB,CAAC,CAAC;CAC/D,MAAM,UAAU,SAAS,QAAQ,SAAS,EAAE,mBAAmB,IAAI,IAAI,EAAE;CACzE,OAAO,CAAC,GAAG,UAAU,GAAG,OAAO;AACjC"}
@@ -3,6 +3,16 @@ const require_tabbable = require("./tabbable.cjs");
3
3
  const require_scope_tab = require("./scope-tab.cjs");
4
4
  let react = require("react");
5
5
  //#region packages/@mantine/hooks/src/use-focus-trap/use-focus-trap.ts
6
+ const activeTraps = /* @__PURE__ */ new Set();
7
+ function hasNestedActiveTrap(ref, node) {
8
+ const activeElement = node.getRootNode().activeElement;
9
+ if (!activeElement) return false;
10
+ for (const trap of activeTraps) {
11
+ const nested = trap.current;
12
+ if (trap !== ref && nested && nested !== node && node.contains(nested) && nested.contains(activeElement)) return true;
13
+ }
14
+ return false;
15
+ }
6
16
  function useFocusTrap(active = true) {
7
17
  const ref = (0, react.useRef)(null);
8
18
  const focusNode = (node) => {
@@ -34,10 +44,15 @@ function useFocusTrap(active = true) {
34
44
  if (ref.current) focusNode(ref.current);
35
45
  });
36
46
  const handleKeyDown = (event) => {
37
- if (event.key === "Tab" && ref.current) require_scope_tab.scopeTab(ref.current, event);
47
+ if (event.key !== "Tab" || event.defaultPrevented || !ref.current || hasNestedActiveTrap(ref, ref.current)) return;
48
+ require_scope_tab.scopeTab(ref.current, event);
38
49
  };
50
+ activeTraps.add(ref);
39
51
  document.addEventListener("keydown", handleKeyDown);
40
- return () => document.removeEventListener("keydown", handleKeyDown);
52
+ return () => {
53
+ activeTraps.delete(ref);
54
+ document.removeEventListener("keydown", handleKeyDown);
55
+ };
41
56
  }, [active]);
42
57
  return setRef;
43
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-focus-trap.cjs","names":["useRef","FOCUS_SELECTOR","tabbable","focusable","useCallback"],"sources":["../../src/use-focus-trap/use-focus-trap.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { scopeTab } from './scope-tab';\nimport { FOCUS_SELECTOR, focusable, tabbable } from './tabbable';\n\nexport function useFocusTrap(active = true): React.RefCallback<HTMLElement | null> {\n const ref = useRef<HTMLElement>(null);\n\n const focusNode = (node: HTMLElement) => {\n let focusElement: HTMLElement | null = node.querySelector('[data-autofocus]');\n\n if (!focusElement) {\n const children = Array.from<HTMLElement>(node.querySelectorAll(FOCUS_SELECTOR));\n focusElement = children.find(tabbable) || children.find(focusable) || null;\n if (!focusElement && focusable(node)) {\n focusElement = node;\n }\n }\n\n if (focusElement) {\n focusElement.focus({ preventScroll: true });\n } else if (process.env.NODE_ENV === 'development') {\n // oxlint-disable-next-line no-console\n console.warn(\n '[@mantine/hooks/use-focus-trap] Failed to find focusable element within provided node',\n node\n );\n }\n };\n\n const setRef = useCallback(\n (node: HTMLElement | null) => {\n if (!active) {\n return;\n }\n\n if (node === null) {\n ref.current = null;\n return;\n }\n\n if (ref.current === node) {\n return;\n }\n\n // Delay processing the HTML node by a frame. This ensures focus is assigned correctly.\n setTimeout(() => {\n if (node.getRootNode()) {\n focusNode(node);\n } else if (process.env.NODE_ENV === 'development') {\n // oxlint-disable-next-line no-console\n console.warn('[@mantine/hooks/use-focus-trap] Ref node is not part of the dom', node);\n }\n });\n\n ref.current = node;\n },\n [active]\n );\n\n useEffect(() => {\n if (!active) {\n return undefined;\n }\n\n if (ref.current) {\n setTimeout(() => {\n if (ref.current) {\n focusNode(ref.current);\n }\n });\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Tab' && ref.current) {\n scopeTab(ref.current, event);\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [active]);\n\n return setRef;\n}\n"],"mappings":";;;;;AAIA,SAAgB,aAAa,SAAS,MAA6C;CACjF,MAAM,OAAA,GAAMA,MAAAA,OAAAA,CAAoB,IAAI;CAEpC,MAAM,aAAa,SAAsB;EACvC,IAAI,eAAmC,KAAK,cAAc,kBAAkB;EAE5E,IAAI,CAAC,cAAc;GACjB,MAAM,WAAW,MAAM,KAAkB,KAAK,iBAAiBC,iBAAAA,cAAc,CAAC;GAC9E,eAAe,SAAS,KAAKC,iBAAAA,QAAQ,KAAK,SAAS,KAAKC,iBAAAA,SAAS,KAAK;GACtE,IAAI,CAAC,gBAAgBA,iBAAAA,UAAU,IAAI,GACjC,eAAe;EAEnB;EAEA,IAAI,cACF,aAAa,MAAM,EAAE,eAAe,KAAK,CAAC;OACrC,IAAI,QAAQ,IAAI,aAAa,eAElC,QAAQ,KACN,yFACA,IACF;CAEJ;CAEA,MAAM,UAAA,GAASC,MAAAA,YAAAA,EACZ,SAA6B;EAC5B,IAAI,CAAC,QACH;EAGF,IAAI,SAAS,MAAM;GACjB,IAAI,UAAU;GACd;EACF;EAEA,IAAI,IAAI,YAAY,MAClB;EAIF,iBAAiB;GACf,IAAI,KAAK,YAAY,GACnB,UAAU,IAAI;QACT,IAAI,QAAQ,IAAI,aAAa,eAElC,QAAQ,KAAK,mEAAmE,IAAI;EAExF,CAAC;EAED,IAAI,UAAU;CAChB,GACA,CAAC,MAAM,CACT;CAEA,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,CAAC,QACH;EAGF,IAAI,IAAI,SACN,iBAAiB;GACf,IAAI,IAAI,SACN,UAAU,IAAI,OAAO;EAEzB,CAAC;EAGH,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,QAAQ,SAAS,IAAI,SAC7B,kBAAA,SAAS,IAAI,SAAS,KAAK;EAE/B;EAEA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa,SAAS,oBAAoB,WAAW,aAAa;CACpE,GAAG,CAAC,MAAM,CAAC;CAEX,OAAO;AACT"}
1
+ {"version":3,"file":"use-focus-trap.cjs","names":["useRef","FOCUS_SELECTOR","tabbable","focusable","useCallback"],"sources":["../../src/use-focus-trap/use-focus-trap.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { scopeTab } from './scope-tab';\nimport { FOCUS_SELECTOR, focusable, tabbable } from './tabbable';\n\nconst activeTraps = new Set<React.RefObject<HTMLElement | null>>();\n\nfunction hasNestedActiveTrap(ref: React.RefObject<HTMLElement | null>, node: HTMLElement) {\n const activeElement = (node.getRootNode() as unknown as DocumentOrShadowRoot).activeElement;\n if (!activeElement) {\n return false;\n }\n\n for (const trap of activeTraps) {\n const nested = trap.current;\n if (\n trap !== ref &&\n nested &&\n nested !== node &&\n node.contains(nested) &&\n nested.contains(activeElement)\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function useFocusTrap(active = true): React.RefCallback<HTMLElement | null> {\n const ref = useRef<HTMLElement>(null);\n\n const focusNode = (node: HTMLElement) => {\n let focusElement: HTMLElement | null = node.querySelector('[data-autofocus]');\n\n if (!focusElement) {\n const children = Array.from<HTMLElement>(node.querySelectorAll(FOCUS_SELECTOR));\n focusElement = children.find(tabbable) || children.find(focusable) || null;\n if (!focusElement && focusable(node)) {\n focusElement = node;\n }\n }\n\n if (focusElement) {\n focusElement.focus({ preventScroll: true });\n } else if (process.env.NODE_ENV === 'development') {\n // oxlint-disable-next-line no-console\n console.warn(\n '[@mantine/hooks/use-focus-trap] Failed to find focusable element within provided node',\n node\n );\n }\n };\n\n const setRef = useCallback(\n (node: HTMLElement | null) => {\n if (!active) {\n return;\n }\n\n if (node === null) {\n ref.current = null;\n return;\n }\n\n if (ref.current === node) {\n return;\n }\n\n // Delay processing the HTML node by a frame. This ensures focus is assigned correctly.\n setTimeout(() => {\n if (node.getRootNode()) {\n focusNode(node);\n } else if (process.env.NODE_ENV === 'development') {\n // oxlint-disable-next-line no-console\n console.warn('[@mantine/hooks/use-focus-trap] Ref node is not part of the dom', node);\n }\n });\n\n ref.current = node;\n },\n [active]\n );\n\n useEffect(() => {\n if (!active) {\n return undefined;\n }\n\n if (ref.current) {\n setTimeout(() => {\n if (ref.current) {\n focusNode(ref.current);\n }\n });\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (\n event.key !== 'Tab' ||\n event.defaultPrevented ||\n !ref.current ||\n hasNestedActiveTrap(ref, ref.current)\n ) {\n return;\n }\n\n scopeTab(ref.current, event);\n };\n\n activeTraps.add(ref);\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n activeTraps.delete(ref);\n document.removeEventListener('keydown', handleKeyDown);\n };\n }, [active]);\n\n return setRef;\n}\n"],"mappings":";;;;;AAIA,MAAM,8BAAc,IAAI,IAAyC;AAEjE,SAAS,oBAAoB,KAA0C,MAAmB;CACxF,MAAM,gBAAiB,KAAK,YAAY,CAAC,CAAqC;CAC9E,IAAI,CAAC,eACH,OAAO;CAGT,KAAK,MAAM,QAAQ,aAAa;EAC9B,MAAM,SAAS,KAAK;EACpB,IACE,SAAS,OACT,UACA,WAAW,QACX,KAAK,SAAS,MAAM,KACpB,OAAO,SAAS,aAAa,GAE7B,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,SAAgB,aAAa,SAAS,MAA6C;CACjF,MAAM,OAAA,GAAMA,MAAAA,OAAAA,CAAoB,IAAI;CAEpC,MAAM,aAAa,SAAsB;EACvC,IAAI,eAAmC,KAAK,cAAc,kBAAkB;EAE5E,IAAI,CAAC,cAAc;GACjB,MAAM,WAAW,MAAM,KAAkB,KAAK,iBAAiBC,iBAAAA,cAAc,CAAC;GAC9E,eAAe,SAAS,KAAKC,iBAAAA,QAAQ,KAAK,SAAS,KAAKC,iBAAAA,SAAS,KAAK;GACtE,IAAI,CAAC,gBAAgBA,iBAAAA,UAAU,IAAI,GACjC,eAAe;EAEnB;EAEA,IAAI,cACF,aAAa,MAAM,EAAE,eAAe,KAAK,CAAC;OACrC,IAAI,QAAQ,IAAI,aAAa,eAElC,QAAQ,KACN,yFACA,IACF;CAEJ;CAEA,MAAM,UAAA,GAASC,MAAAA,YAAAA,EACZ,SAA6B;EAC5B,IAAI,CAAC,QACH;EAGF,IAAI,SAAS,MAAM;GACjB,IAAI,UAAU;GACd;EACF;EAEA,IAAI,IAAI,YAAY,MAClB;EAIF,iBAAiB;GACf,IAAI,KAAK,YAAY,GACnB,UAAU,IAAI;QACT,IAAI,QAAQ,IAAI,aAAa,eAElC,QAAQ,KAAK,mEAAmE,IAAI;EAExF,CAAC;EAED,IAAI,UAAU;CAChB,GACA,CAAC,MAAM,CACT;CAEA,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,IAAI,CAAC,QACH;EAGF,IAAI,IAAI,SACN,iBAAiB;GACf,IAAI,IAAI,SACN,UAAU,IAAI,OAAO;EAEzB,CAAC;EAGH,MAAM,iBAAiB,UAAyB;GAC9C,IACE,MAAM,QAAQ,SACd,MAAM,oBACN,CAAC,IAAI,WACL,oBAAoB,KAAK,IAAI,OAAO,GAEpC;GAGF,kBAAA,SAAS,IAAI,SAAS,KAAK;EAC7B;EAEA,YAAY,IAAI,GAAG;EACnB,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa;GACX,YAAY,OAAO,GAAG;GACtB,SAAS,oBAAoB,WAAW,aAAa;EACvD;CACF,GAAG,CAAC,MAAM,CAAC;CAEX,OAAO;AACT"}
@@ -11,6 +11,7 @@ function useScroller(options = {}) {
11
11
  const hasDraggedRef = (0, react.useRef)(false);
12
12
  const startX = (0, react.useRef)(0);
13
13
  const scrollLeftStart = (0, react.useRef)(0);
14
+ const removeClickSuppressorRef = (0, react.useRef)(null);
14
15
  const onScrollStateChangeRef = (0, react.useRef)(onScrollStateChange);
15
16
  onScrollStateChangeRef.current = onScrollStateChange;
16
17
  const updateScrollState = (0, react.useCallback)(() => {
@@ -64,6 +65,7 @@ function useScroller(options = {}) {
64
65
  const scrollStart = (0, react.useCallback)(() => scroll("start"), [scroll]);
65
66
  const scrollEnd = (0, react.useCallback)(() => scroll("end"), [scroll]);
66
67
  const handleMouseDown = (0, react.useCallback)((event) => {
68
+ removeClickSuppressorRef.current?.();
67
69
  if (!draggable) return;
68
70
  const container = containerRef.current;
69
71
  if (container) {
@@ -96,12 +98,18 @@ function useScroller(options = {}) {
96
98
  container.style.cursor = "";
97
99
  container.style.userSelect = "";
98
100
  if (wasDragged) {
101
+ removeClickSuppressorRef.current?.();
102
+ const removeSuppressor = () => {
103
+ container.removeEventListener("click", suppressClick, true);
104
+ removeClickSuppressorRef.current = null;
105
+ };
99
106
  const suppressClick = (event) => {
100
107
  event.stopPropagation();
101
108
  event.preventDefault();
102
- container.removeEventListener("click", suppressClick, true);
109
+ removeSuppressor();
103
110
  };
104
111
  container.addEventListener("click", suppressClick, true);
112
+ removeClickSuppressorRef.current = removeSuppressor;
105
113
  }
106
114
  }
107
115
  }, []);
@@ -1 +1 @@
1
- {"version":3,"file":"use-scroller.cjs","names":["useRef","useState","useCallback"],"sources":["../../src/use-scroller/use-scroller.ts"],"sourcesContent":["import { RefCallback, useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface UseScrollerOptions {\n /** Amount of pixels to scroll when calling scroll functions, `200` by default */\n scrollAmount?: number;\n\n /** Determines whether content can be scrolled by dragging with mouse, `true` by default */\n draggable?: boolean;\n\n /** Called when scroll state changes (canScrollStart or canScrollEnd) */\n onScrollStateChange?: (state: UseScrollerScrollState) => void;\n}\n\nexport interface UseScrollerScrollState {\n /** Whether content can be scrolled towards the start (left in LTR, right in RTL) */\n canScrollStart: boolean;\n\n /** Whether content can be scrolled towards the end (right in LTR, left in RTL) */\n canScrollEnd: boolean;\n}\n\nexport interface UseScrollerReturnValue<T extends HTMLElement = HTMLDivElement> {\n /** Ref callback to attach to the scrollable container element */\n ref: RefCallback<T | null>;\n\n /** Whether content can be scrolled towards the start */\n canScrollStart: boolean;\n\n /** Whether content can be scrolled towards the end */\n canScrollEnd: boolean;\n\n /** Scrolls towards the start direction */\n scrollStart: () => void;\n\n /** Scrolls towards the end direction */\n scrollEnd: () => void;\n\n /** `true` if the user is currently dragging the content */\n isDragging: boolean;\n\n /** Props to spread on the scrollable container for drag functionality */\n dragHandlers: {\n onMouseDown: (e: React.MouseEvent) => void;\n onMouseMove: (e: React.MouseEvent) => void;\n onMouseUp: () => void;\n onMouseLeave: () => void;\n };\n}\n\nexport function useScroller<T extends HTMLElement = HTMLDivElement>(\n options: UseScrollerOptions = {}\n): UseScrollerReturnValue<T> {\n const { scrollAmount = 200, draggable = true, onScrollStateChange } = options;\n\n const containerRef = useRef<T | null>(null);\n\n const [canScrollStart, setCanScrollStart] = useState(false);\n const [canScrollEnd, setCanScrollEnd] = useState(false);\n const [isDragging, setIsDragging] = useState(false);\n\n const isDraggingRef = useRef(false);\n const hasDraggedRef = useRef(false);\n const startX = useRef(0);\n const scrollLeftStart = useRef(0);\n\n const onScrollStateChangeRef = useRef(onScrollStateChange);\n onScrollStateChangeRef.current = onScrollStateChange;\n\n const updateScrollState = useCallback(() => {\n const container = containerRef.current;\n if (container) {\n const { scrollLeft, scrollWidth, clientWidth } = container;\n const isRtl = getComputedStyle(container).direction === 'rtl';\n\n let newCanScrollStart: boolean;\n let newCanScrollEnd: boolean;\n\n if (isRtl) {\n newCanScrollStart = scrollLeft < -1;\n newCanScrollEnd = scrollLeft > -(scrollWidth - clientWidth) + 1;\n } else {\n newCanScrollStart = scrollLeft > 1;\n newCanScrollEnd = scrollLeft < scrollWidth - clientWidth - 1;\n }\n\n setCanScrollStart(newCanScrollStart);\n setCanScrollEnd(newCanScrollEnd);\n\n onScrollStateChangeRef.current?.({\n canScrollStart: newCanScrollStart,\n canScrollEnd: newCanScrollEnd,\n });\n }\n }, []);\n\n useEffect(() => {\n updateScrollState();\n const container = containerRef.current;\n if (container) {\n container.addEventListener('scroll', updateScrollState);\n const resizeObserver = new ResizeObserver(updateScrollState);\n resizeObserver.observe(container);\n return () => {\n container.removeEventListener('scroll', updateScrollState);\n resizeObserver.disconnect();\n };\n }\n return undefined;\n }, [updateScrollState]);\n\n const scroll = useCallback(\n (direction: 'start' | 'end') => {\n const container = containerRef.current;\n if (container) {\n const isRtl = getComputedStyle(container).direction === 'rtl';\n const amount = scrollAmount;\n const scrollBy = direction === 'end' ? amount : -amount;\n const adjustedScrollBy = isRtl ? -scrollBy : scrollBy;\n\n container.scrollBy({\n left: adjustedScrollBy,\n behavior: 'smooth',\n });\n }\n },\n [scrollAmount]\n );\n\n const scrollStart = useCallback(() => scroll('start'), [scroll]);\n const scrollEnd = useCallback(() => scroll('end'), [scroll]);\n\n const handleMouseDown = useCallback(\n (event: React.MouseEvent) => {\n if (!draggable) {\n return;\n }\n const container = containerRef.current;\n if (container) {\n isDraggingRef.current = true;\n hasDraggedRef.current = false;\n setIsDragging(true);\n startX.current = event.pageX - container.offsetLeft;\n scrollLeftStart.current = container.scrollLeft;\n container.style.cursor = 'grabbing';\n container.style.userSelect = 'none';\n }\n },\n [draggable]\n );\n\n const handleMouseMove = useCallback((event: React.MouseEvent) => {\n if (!isDraggingRef.current) {\n return;\n }\n event.preventDefault();\n const container = containerRef.current;\n if (container) {\n const x = event.pageX - container.offsetLeft;\n const walk = x - startX.current;\n if (Math.abs(walk) > 5) {\n hasDraggedRef.current = true;\n }\n container.scrollLeft = scrollLeftStart.current - walk;\n }\n }, []);\n\n const handleMouseUp = useCallback(() => {\n const wasDragged = hasDraggedRef.current;\n isDraggingRef.current = false;\n hasDraggedRef.current = false;\n setIsDragging(false);\n const container = containerRef.current;\n if (container) {\n container.style.cursor = '';\n container.style.userSelect = '';\n\n if (wasDragged) {\n const suppressClick = (event: MouseEvent) => {\n event.stopPropagation();\n event.preventDefault();\n container.removeEventListener('click', suppressClick, true);\n };\n container.addEventListener('click', suppressClick, true);\n }\n }\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n if (isDraggingRef.current) {\n handleMouseUp();\n }\n }, [handleMouseUp]);\n\n const assignRef: RefCallback<T | null> = useCallback(\n (node) => {\n containerRef.current = node;\n if (node) {\n updateScrollState();\n }\n },\n [updateScrollState]\n );\n\n return {\n ref: assignRef,\n canScrollStart,\n canScrollEnd,\n scrollStart,\n scrollEnd,\n isDragging,\n dragHandlers: {\n onMouseDown: handleMouseDown,\n onMouseMove: handleMouseMove,\n onMouseUp: handleMouseUp,\n onMouseLeave: handleMouseLeave,\n },\n };\n}\n\nexport namespace useScroller {\n export type Options = UseScrollerOptions;\n export type ReturnValue<T extends HTMLElement = HTMLDivElement> = UseScrollerReturnValue<T>;\n export type ScrollState = UseScrollerScrollState;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-scroller.cjs","names":["useRef","useState","useCallback"],"sources":["../../src/use-scroller/use-scroller.ts"],"sourcesContent":["import { RefCallback, useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface UseScrollerOptions {\n /** Amount of pixels to scroll when calling scroll functions, `200` by default */\n scrollAmount?: number;\n\n /** Determines whether content can be scrolled by dragging with mouse, `true` by default */\n draggable?: boolean;\n\n /** Called when scroll state changes (canScrollStart or canScrollEnd) */\n onScrollStateChange?: (state: UseScrollerScrollState) => void;\n}\n\nexport interface UseScrollerScrollState {\n /** Whether content can be scrolled towards the start (left in LTR, right in RTL) */\n canScrollStart: boolean;\n\n /** Whether content can be scrolled towards the end (right in LTR, left in RTL) */\n canScrollEnd: boolean;\n}\n\nexport interface UseScrollerReturnValue<T extends HTMLElement = HTMLDivElement> {\n /** Ref callback to attach to the scrollable container element */\n ref: RefCallback<T | null>;\n\n /** Whether content can be scrolled towards the start */\n canScrollStart: boolean;\n\n /** Whether content can be scrolled towards the end */\n canScrollEnd: boolean;\n\n /** Scrolls towards the start direction */\n scrollStart: () => void;\n\n /** Scrolls towards the end direction */\n scrollEnd: () => void;\n\n /** `true` if the user is currently dragging the content */\n isDragging: boolean;\n\n /** Props to spread on the scrollable container for drag functionality */\n dragHandlers: {\n onMouseDown: (e: React.MouseEvent) => void;\n onMouseMove: (e: React.MouseEvent) => void;\n onMouseUp: () => void;\n onMouseLeave: () => void;\n };\n}\n\nexport function useScroller<T extends HTMLElement = HTMLDivElement>(\n options: UseScrollerOptions = {}\n): UseScrollerReturnValue<T> {\n const { scrollAmount = 200, draggable = true, onScrollStateChange } = options;\n\n const containerRef = useRef<T | null>(null);\n\n const [canScrollStart, setCanScrollStart] = useState(false);\n const [canScrollEnd, setCanScrollEnd] = useState(false);\n const [isDragging, setIsDragging] = useState(false);\n\n const isDraggingRef = useRef(false);\n const hasDraggedRef = useRef(false);\n const startX = useRef(0);\n const scrollLeftStart = useRef(0);\n const removeClickSuppressorRef = useRef<(() => void) | null>(null);\n\n const onScrollStateChangeRef = useRef(onScrollStateChange);\n onScrollStateChangeRef.current = onScrollStateChange;\n\n const updateScrollState = useCallback(() => {\n const container = containerRef.current;\n if (container) {\n const { scrollLeft, scrollWidth, clientWidth } = container;\n const isRtl = getComputedStyle(container).direction === 'rtl';\n\n let newCanScrollStart: boolean;\n let newCanScrollEnd: boolean;\n\n if (isRtl) {\n newCanScrollStart = scrollLeft < -1;\n newCanScrollEnd = scrollLeft > -(scrollWidth - clientWidth) + 1;\n } else {\n newCanScrollStart = scrollLeft > 1;\n newCanScrollEnd = scrollLeft < scrollWidth - clientWidth - 1;\n }\n\n setCanScrollStart(newCanScrollStart);\n setCanScrollEnd(newCanScrollEnd);\n\n onScrollStateChangeRef.current?.({\n canScrollStart: newCanScrollStart,\n canScrollEnd: newCanScrollEnd,\n });\n }\n }, []);\n\n useEffect(() => {\n updateScrollState();\n const container = containerRef.current;\n if (container) {\n container.addEventListener('scroll', updateScrollState);\n const resizeObserver = new ResizeObserver(updateScrollState);\n resizeObserver.observe(container);\n return () => {\n container.removeEventListener('scroll', updateScrollState);\n resizeObserver.disconnect();\n };\n }\n return undefined;\n }, [updateScrollState]);\n\n const scroll = useCallback(\n (direction: 'start' | 'end') => {\n const container = containerRef.current;\n if (container) {\n const isRtl = getComputedStyle(container).direction === 'rtl';\n const amount = scrollAmount;\n const scrollBy = direction === 'end' ? amount : -amount;\n const adjustedScrollBy = isRtl ? -scrollBy : scrollBy;\n\n container.scrollBy({\n left: adjustedScrollBy,\n behavior: 'smooth',\n });\n }\n },\n [scrollAmount]\n );\n\n const scrollStart = useCallback(() => scroll('start'), [scroll]);\n const scrollEnd = useCallback(() => scroll('end'), [scroll]);\n\n const handleMouseDown = useCallback(\n (event: React.MouseEvent) => {\n removeClickSuppressorRef.current?.();\n if (!draggable) {\n return;\n }\n const container = containerRef.current;\n if (container) {\n isDraggingRef.current = true;\n hasDraggedRef.current = false;\n setIsDragging(true);\n startX.current = event.pageX - container.offsetLeft;\n scrollLeftStart.current = container.scrollLeft;\n container.style.cursor = 'grabbing';\n container.style.userSelect = 'none';\n }\n },\n [draggable]\n );\n\n const handleMouseMove = useCallback((event: React.MouseEvent) => {\n if (!isDraggingRef.current) {\n return;\n }\n event.preventDefault();\n const container = containerRef.current;\n if (container) {\n const x = event.pageX - container.offsetLeft;\n const walk = x - startX.current;\n if (Math.abs(walk) > 5) {\n hasDraggedRef.current = true;\n }\n container.scrollLeft = scrollLeftStart.current - walk;\n }\n }, []);\n\n const handleMouseUp = useCallback(() => {\n const wasDragged = hasDraggedRef.current;\n isDraggingRef.current = false;\n hasDraggedRef.current = false;\n setIsDragging(false);\n const container = containerRef.current;\n if (container) {\n container.style.cursor = '';\n container.style.userSelect = '';\n\n if (wasDragged) {\n removeClickSuppressorRef.current?.();\n const removeSuppressor = () => {\n container.removeEventListener('click', suppressClick, true);\n removeClickSuppressorRef.current = null;\n };\n const suppressClick = (event: MouseEvent) => {\n event.stopPropagation();\n event.preventDefault();\n removeSuppressor();\n };\n container.addEventListener('click', suppressClick, true);\n removeClickSuppressorRef.current = removeSuppressor;\n }\n }\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n if (isDraggingRef.current) {\n handleMouseUp();\n }\n }, [handleMouseUp]);\n\n const assignRef: RefCallback<T | null> = useCallback(\n (node) => {\n containerRef.current = node;\n if (node) {\n updateScrollState();\n }\n },\n [updateScrollState]\n );\n\n return {\n ref: assignRef,\n canScrollStart,\n canScrollEnd,\n scrollStart,\n scrollEnd,\n isDragging,\n dragHandlers: {\n onMouseDown: handleMouseDown,\n onMouseMove: handleMouseMove,\n onMouseUp: handleMouseUp,\n onMouseLeave: handleMouseLeave,\n },\n };\n}\n\nexport namespace useScroller {\n export type Options = UseScrollerOptions;\n export type ReturnValue<T extends HTMLElement = HTMLDivElement> = UseScrollerReturnValue<T>;\n export type ScrollState = UseScrollerScrollState;\n}\n"],"mappings":"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"}
@@ -17,10 +17,15 @@ function useThrottledCallbackWithClearTimeout(callback, wait) {
17
17
  latestOutArgsRef.current = null;
18
18
  };
19
19
  const callThrottledCallback = (0, react.useCallback)((...args) => {
20
- handleCallback(...args);
21
20
  latestInArgsRef.current = args;
22
21
  latestOutArgsRef.current = args;
23
22
  active.current = false;
23
+ try {
24
+ handleCallback(...args);
25
+ } catch (error) {
26
+ active.current = true;
27
+ throw error;
28
+ }
24
29
  }, [handleCallback]);
25
30
  const timerCallback = (0, react.useCallback)(() => {
26
31
  if (latestInArgsRef.current && latestInArgsRef.current !== latestOutArgsRef.current) {
@@ -1 +1 @@
1
- {"version":3,"file":"use-throttled-callback.cjs","names":["useCallbackRef","useRef","useCallback"],"sources":["../../src/use-throttled-callback/use-throttled-callback.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport function useThrottledCallbackWithClearTimeout<T extends (...args: any[]) => any>(\n callback: T,\n wait: number\n) {\n const handleCallback = useCallbackRef(callback);\n const latestInArgsRef = useRef<Parameters<T>>(null);\n const latestOutArgsRef = useRef<Parameters<T>>(null);\n const active = useRef(true);\n const waitRef = useRef(wait);\n const timeoutRef = useRef<number>(-1);\n\n const clearTimeout = () => {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = -1;\n active.current = true;\n latestInArgsRef.current = null;\n latestOutArgsRef.current = null;\n };\n\n const callThrottledCallback = useCallback(\n (...args: Parameters<T>) => {\n handleCallback(...args);\n latestInArgsRef.current = args;\n latestOutArgsRef.current = args;\n active.current = false;\n },\n [handleCallback]\n );\n\n const timerCallback = useCallback(() => {\n if (latestInArgsRef.current && latestInArgsRef.current !== latestOutArgsRef.current) {\n callThrottledCallback(...latestInArgsRef.current);\n\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n active.current = true;\n }\n }, [callThrottledCallback]);\n\n const throttled = useCallback(\n (...args: Parameters<T>) => {\n if (active.current) {\n callThrottledCallback(...args);\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n latestInArgsRef.current = args;\n }\n },\n [callThrottledCallback, timerCallback]\n );\n\n useEffect(() => {\n waitRef.current = wait;\n }, [wait]);\n\n return [throttled, clearTimeout] as const;\n}\n\nexport function useThrottledCallback<T extends (...args: any[]) => any>(callback: T, wait: number) {\n const [throttled, clearTimeout] = useThrottledCallbackWithClearTimeout(callback, wait);\n useEffect(() => clearTimeout, []);\n return throttled;\n}\n"],"mappings":";;;;AAGA,SAAgB,qCACd,UACA,MACA;CACA,MAAM,iBAAiBA,yBAAAA,eAAe,QAAQ;CAC9C,MAAM,mBAAA,GAAkBC,MAAAA,OAAAA,CAAsB,IAAI;CAClD,MAAM,oBAAA,GAAmBA,MAAAA,OAAAA,CAAsB,IAAI;CACnD,MAAM,UAAA,GAASA,MAAAA,OAAAA,CAAO,IAAI;CAC1B,MAAM,WAAA,GAAUA,MAAAA,OAAAA,CAAO,IAAI;CAC3B,MAAM,cAAA,GAAaA,MAAAA,OAAAA,CAAe,EAAE;CAEpC,MAAM,qBAAqB;EACzB,OAAO,aAAa,WAAW,OAAO;EACtC,WAAW,UAAU;EACrB,OAAO,UAAU;EACjB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;CAC7B;CAEA,MAAM,yBAAA,GAAwBC,MAAAA,YAAAA,EAC3B,GAAG,SAAwB;EAC1B,eAAe,GAAG,IAAI;EACtB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,OAAO,UAAU;CACnB,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,iBAAA,GAAgBA,MAAAA,YAAAA,OAAkB;EACtC,IAAI,gBAAgB,WAAW,gBAAgB,YAAY,iBAAiB,SAAS;GACnF,sBAAsB,GAAG,gBAAgB,OAAO;GAEhD,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,OAAO,UAAU;CAErB,GAAG,CAAC,qBAAqB,CAAC;CAE1B,MAAM,aAAA,GAAYA,MAAAA,YAAAA,EACf,GAAG,SAAwB;EAC1B,IAAI,OAAO,SAAS;GAClB,sBAAsB,GAAG,IAAI;GAC7B,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,gBAAgB,UAAU;CAE9B,GACA,CAAC,uBAAuB,aAAa,CACvC;CAEA,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,QAAQ,UAAU;CACpB,GAAG,CAAC,IAAI,CAAC;CAET,OAAO,CAAC,WAAW,YAAY;AACjC;AAEA,SAAgB,qBAAwD,UAAa,MAAc;CACjG,MAAM,CAAC,WAAW,gBAAgB,qCAAqC,UAAU,IAAI;CACrF,CAAA,GAAA,MAAA,UAAA,OAAgB,cAAc,CAAC,CAAC;CAChC,OAAO;AACT"}
1
+ {"version":3,"file":"use-throttled-callback.cjs","names":["useCallbackRef","useRef","useCallback"],"sources":["../../src/use-throttled-callback/use-throttled-callback.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport function useThrottledCallbackWithClearTimeout<T extends (...args: any[]) => any>(\n callback: T,\n wait: number\n) {\n const handleCallback = useCallbackRef(callback);\n const latestInArgsRef = useRef<Parameters<T>>(null);\n const latestOutArgsRef = useRef<Parameters<T>>(null);\n const active = useRef(true);\n const waitRef = useRef(wait);\n const timeoutRef = useRef<number>(-1);\n\n const clearTimeout = () => {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = -1;\n active.current = true;\n latestInArgsRef.current = null;\n latestOutArgsRef.current = null;\n };\n\n const callThrottledCallback = useCallback(\n (...args: Parameters<T>) => {\n latestInArgsRef.current = args;\n latestOutArgsRef.current = args;\n active.current = false;\n try {\n handleCallback(...args);\n } catch (error) {\n active.current = true;\n throw error;\n }\n },\n [handleCallback]\n );\n\n const timerCallback = useCallback(() => {\n if (latestInArgsRef.current && latestInArgsRef.current !== latestOutArgsRef.current) {\n callThrottledCallback(...latestInArgsRef.current);\n\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n active.current = true;\n }\n }, [callThrottledCallback]);\n\n const throttled = useCallback(\n (...args: Parameters<T>) => {\n if (active.current) {\n callThrottledCallback(...args);\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n latestInArgsRef.current = args;\n }\n },\n [callThrottledCallback, timerCallback]\n );\n\n useEffect(() => {\n waitRef.current = wait;\n }, [wait]);\n\n return [throttled, clearTimeout] as const;\n}\n\nexport function useThrottledCallback<T extends (...args: any[]) => any>(callback: T, wait: number) {\n const [throttled, clearTimeout] = useThrottledCallbackWithClearTimeout(callback, wait);\n useEffect(() => clearTimeout, []);\n return throttled;\n}\n"],"mappings":";;;;AAGA,SAAgB,qCACd,UACA,MACA;CACA,MAAM,iBAAiBA,yBAAAA,eAAe,QAAQ;CAC9C,MAAM,mBAAA,GAAkBC,MAAAA,OAAAA,CAAsB,IAAI;CAClD,MAAM,oBAAA,GAAmBA,MAAAA,OAAAA,CAAsB,IAAI;CACnD,MAAM,UAAA,GAASA,MAAAA,OAAAA,CAAO,IAAI;CAC1B,MAAM,WAAA,GAAUA,MAAAA,OAAAA,CAAO,IAAI;CAC3B,MAAM,cAAA,GAAaA,MAAAA,OAAAA,CAAe,EAAE;CAEpC,MAAM,qBAAqB;EACzB,OAAO,aAAa,WAAW,OAAO;EACtC,WAAW,UAAU;EACrB,OAAO,UAAU;EACjB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;CAC7B;CAEA,MAAM,yBAAA,GAAwBC,MAAAA,YAAAA,EAC3B,GAAG,SAAwB;EAC1B,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,OAAO,UAAU;EACjB,IAAI;GACF,eAAe,GAAG,IAAI;EACxB,SAAS,OAAO;GACd,OAAO,UAAU;GACjB,MAAM;EACR;CACF,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,iBAAA,GAAgBA,MAAAA,YAAAA,OAAkB;EACtC,IAAI,gBAAgB,WAAW,gBAAgB,YAAY,iBAAiB,SAAS;GACnF,sBAAsB,GAAG,gBAAgB,OAAO;GAEhD,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,OAAO,UAAU;CAErB,GAAG,CAAC,qBAAqB,CAAC;CAE1B,MAAM,aAAA,GAAYA,MAAAA,YAAAA,EACf,GAAG,SAAwB;EAC1B,IAAI,OAAO,SAAS;GAClB,sBAAsB,GAAG,IAAI;GAC7B,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,gBAAgB,UAAU;CAE9B,GACA,CAAC,uBAAuB,aAAa,CACvC;CAEA,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,QAAQ,UAAU;CACpB,GAAG,CAAC,IAAI,CAAC;CAET,OAAO,CAAC,WAAW,YAAY;AACjC;AAEA,SAAgB,qBAAwD,UAAa,MAAc;CACjG,MAAM,CAAC,WAAW,gBAAgB,qCAAqC,UAAU,IAAI;CACrF,CAAA,GAAA,MAAA,UAAA,OAAgB,cAAc,CAAC,CAAC;CAChC,OAAO;AACT"}
@@ -1,24 +1,16 @@
1
1
  "use client";
2
- import { useCallback, useEffect, useRef } from "react";
2
+ import { useCallback } from "react";
3
3
  //#region packages/@mantine/hooks/src/use-event-listener/use-event-listener.ts
4
4
  function useEventListener(type, listener, options) {
5
- const previousListener = useRef(null);
6
- const previousNode = useRef(null);
7
- const callbackRef = useCallback((node) => {
5
+ return useCallback((node) => {
8
6
  if (!node) return;
9
- if (previousNode.current && previousListener.current) previousNode.current.removeEventListener(type, previousListener.current, options);
10
7
  node.addEventListener(type, listener, options);
11
- previousNode.current = node;
12
- previousListener.current = listener;
8
+ return () => node.removeEventListener(type, listener, options);
13
9
  }, [
14
10
  type,
15
11
  listener,
16
12
  options
17
13
  ]);
18
- useEffect(() => () => {
19
- if (previousNode.current && previousListener.current) previousNode.current.removeEventListener(type, previousListener.current, options);
20
- }, [type, options]);
21
- return callbackRef;
22
14
  }
23
15
  //#endregion
24
16
  export { useEventListener };
@@ -1 +1 @@
1
- {"version":3,"file":"use-event-listener.mjs","names":[],"sources":["../../src/use-event-listener/use-event-listener.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\nexport function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = any>(\n type: K,\n listener: (this: T, ev: HTMLElementEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions\n): React.RefCallback<T | null> {\n const previousListener = useRef<Function | null>(null);\n const previousNode = useRef<T | null>(null);\n\n const callbackRef: React.RefCallback<T | null> = useCallback(\n (node) => {\n if (!node) {\n return;\n }\n\n if (previousNode.current && previousListener.current) {\n previousNode.current.removeEventListener(type, previousListener.current as any, options);\n }\n\n node.addEventListener(type, listener as any, options);\n previousNode.current = node;\n previousListener.current = listener;\n },\n [type, listener, options]\n );\n\n useEffect(\n () => () => {\n if (previousNode.current && previousListener.current) {\n previousNode.current.removeEventListener(type, previousListener.current as any, options);\n }\n },\n [type, options]\n );\n\n return callbackRef;\n}\n"],"mappings":";;;AAEA,SAAgB,iBACd,MACA,UACA,SAC6B;CAC7B,MAAM,mBAAmB,OAAwB,IAAI;CACrD,MAAM,eAAe,OAAiB,IAAI;CAE1C,MAAM,cAA2C,aAC9C,SAAS;EACR,IAAI,CAAC,MACH;EAGF,IAAI,aAAa,WAAW,iBAAiB,SAC3C,aAAa,QAAQ,oBAAoB,MAAM,iBAAiB,SAAgB,OAAO;EAGzF,KAAK,iBAAiB,MAAM,UAAiB,OAAO;EACpD,aAAa,UAAU;EACvB,iBAAiB,UAAU;CAC7B,GACA;EAAC;EAAM;EAAU;CAAO,CAC1B;CAEA,sBACc;EACV,IAAI,aAAa,WAAW,iBAAiB,SAC3C,aAAa,QAAQ,oBAAoB,MAAM,iBAAiB,SAAgB,OAAO;CAE3F,GACA,CAAC,MAAM,OAAO,CAChB;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"use-event-listener.mjs","names":[],"sources":["../../src/use-event-listener/use-event-listener.ts"],"sourcesContent":["import { useCallback } from 'react';\n\nexport function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = any>(\n type: K,\n listener: (this: T, ev: HTMLElementEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions\n): React.RefCallback<T | null> {\n return useCallback(\n (node) => {\n if (!node) {\n return;\n }\n\n node.addEventListener(type, listener as any, options);\n return () => node.removeEventListener(type, listener as any, options);\n },\n [type, listener, options]\n );\n}\n"],"mappings":";;;AAEA,SAAgB,iBACd,MACA,UACA,SAC6B;CAC7B,OAAO,aACJ,SAAS;EACR,IAAI,CAAC,MACH;EAGF,KAAK,iBAAiB,MAAM,UAAiB,OAAO;EACpD,aAAa,KAAK,oBAAoB,MAAM,UAAiB,OAAO;CACtE,GACA;EAAC;EAAM;EAAU;CAAO,CAC1B;AACF"}
@@ -1,21 +1,74 @@
1
1
  "use client";
2
2
  import { findTabbableDescendants } from "./tabbable.mjs";
3
3
  //#region packages/@mantine/hooks/src/use-focus-trap/scope-tab.ts
4
- function scopeTab(node, event) {
4
+ const NATIVE_TAB_STOPS_INPUT_TYPES = [
5
+ "date",
6
+ "time",
7
+ "datetime-local",
8
+ "month",
9
+ "week"
10
+ ];
11
+ const NATIVE_TAB_STOPS_NODES = [
12
+ "audio",
13
+ "video",
14
+ "iframe",
15
+ "object",
16
+ "embed"
17
+ ];
18
+ function isRadio(element) {
19
+ return !!element && element.tagName === "INPUT" && element.type === "radio";
20
+ }
21
+ function isSameRadioGroup(a, b) {
22
+ return a.name !== "" && a.name === b.name && a.form === b.form;
23
+ }
24
+ function hasNativeTabStops(element) {
25
+ const nodeName = element.nodeName.toLowerCase();
26
+ if (nodeName === "input") return NATIVE_TAB_STOPS_INPUT_TYPES.includes(element.type);
27
+ return NATIVE_TAB_STOPS_NODES.includes(nodeName);
28
+ }
29
+ function getTabSequence(node, activeElement) {
5
30
  const tabbable = findTabbableDescendants(node);
6
- if (!tabbable.length) {
7
- event.preventDefault();
8
- return;
31
+ return tabbable.filter((element) => {
32
+ if (element === activeElement || !isRadio(element)) return true;
33
+ if (isRadio(activeElement) && isSameRadioGroup(element, activeElement)) return false;
34
+ if (element.checked) return true;
35
+ return !tabbable.some((item) => isRadio(item) && item.checked && isSameRadioGroup(item, element));
36
+ });
37
+ }
38
+ function getStartIndex(sequence, activeElement, node, backwards) {
39
+ const activeIndex = sequence.indexOf(activeElement);
40
+ if (activeIndex !== -1) return activeIndex + 1;
41
+ if (activeElement === node) return 0;
42
+ const position = backwards ? Node.DOCUMENT_POSITION_PRECEDING : Node.DOCUMENT_POSITION_FOLLOWING;
43
+ const nextIndex = sequence.findIndex((element) => (activeElement.compareDocumentPosition(element) & position) !== 0);
44
+ return nextIndex === -1 ? 0 : nextIndex;
45
+ }
46
+ function getCandidates(node, activeElement, backwards) {
47
+ const sequence = getTabSequence(node, activeElement);
48
+ if (backwards) sequence.reverse();
49
+ const startIndex = getStartIndex(sequence, activeElement, node, backwards);
50
+ return [...sequence.slice(startIndex), ...sequence.slice(0, startIndex)].filter((element) => element !== activeElement);
51
+ }
52
+ function focusFirstAvailable(candidates, root) {
53
+ for (const candidate of candidates) {
54
+ candidate.focus();
55
+ if (root.activeElement === candidate) return;
9
56
  }
10
- const finalTabbable = tabbable[event.shiftKey ? 0 : tabbable.length - 1];
57
+ }
58
+ function scopeTab(node, event) {
11
59
  const root = node.getRootNode();
12
- let leavingFinalTabbable = finalTabbable === root.activeElement || node === root.activeElement;
13
60
  const activeElement = root.activeElement;
14
- if (activeElement.tagName === "INPUT" && activeElement.getAttribute("type") === "radio") leavingFinalTabbable = tabbable.filter((element) => element.getAttribute("type") === "radio" && element.getAttribute("name") === activeElement.getAttribute("name")).includes(finalTabbable);
15
- if (!leavingFinalTabbable) return;
61
+ if (!activeElement || activeElement !== node && !node.contains(activeElement)) return;
62
+ const candidates = getCandidates(node, activeElement, event.shiftKey);
63
+ if (hasNativeTabStops(activeElement)) {
64
+ setTimeout(() => {
65
+ const current = root.activeElement;
66
+ if (node.isConnected && current !== activeElement && current !== candidates[0]) focusFirstAvailable([...candidates, activeElement], root);
67
+ });
68
+ return;
69
+ }
16
70
  event.preventDefault();
17
- const target = tabbable[event.shiftKey ? tabbable.length - 1 : 0];
18
- if (target) target.focus();
71
+ focusFirstAvailable(candidates, root);
19
72
  }
20
73
  //#endregion
21
74
  export { scopeTab };
@@ -1 +1 @@
1
- {"version":3,"file":"scope-tab.mjs","names":[],"sources":["../../src/use-focus-trap/scope-tab.ts"],"sourcesContent":["import { findTabbableDescendants } from './tabbable';\n\nexport function scopeTab(node: HTMLElement, event: KeyboardEvent) {\n const tabbable = findTabbableDescendants(node);\n if (!tabbable.length) {\n event.preventDefault();\n return;\n }\n const finalTabbable = tabbable[event.shiftKey ? 0 : tabbable.length - 1];\n const root = node.getRootNode() as unknown as DocumentOrShadowRoot;\n let leavingFinalTabbable = finalTabbable === root.activeElement || node === root.activeElement;\n\n const activeElement = root.activeElement as Element;\n const activeElementIsRadio =\n activeElement.tagName === 'INPUT' && activeElement.getAttribute('type') === 'radio';\n if (activeElementIsRadio) {\n const activeRadioGroup = tabbable.filter(\n (element) =>\n element.getAttribute('type') === 'radio' &&\n element.getAttribute('name') === activeElement.getAttribute('name')\n );\n leavingFinalTabbable = activeRadioGroup.includes(finalTabbable);\n }\n\n if (!leavingFinalTabbable) {\n return;\n }\n\n event.preventDefault();\n\n const target = tabbable[event.shiftKey ? tabbable.length - 1 : 0];\n\n if (target) {\n target.focus();\n }\n}\n"],"mappings":";;;AAEA,SAAgB,SAAS,MAAmB,OAAsB;CAChE,MAAM,WAAW,wBAAwB,IAAI;CAC7C,IAAI,CAAC,SAAS,QAAQ;EACpB,MAAM,eAAe;EACrB;CACF;CACA,MAAM,gBAAgB,SAAS,MAAM,WAAW,IAAI,SAAS,SAAS;CACtE,MAAM,OAAO,KAAK,YAAY;CAC9B,IAAI,uBAAuB,kBAAkB,KAAK,iBAAiB,SAAS,KAAK;CAEjF,MAAM,gBAAgB,KAAK;CAG3B,IADE,cAAc,YAAY,WAAW,cAAc,aAAa,MAAM,MAAM,SAO5E,uBALyB,SAAS,QAC/B,YACC,QAAQ,aAAa,MAAM,MAAM,WACjC,QAAQ,aAAa,MAAM,MAAM,cAAc,aAAa,MAAM,CAEhC,CAAC,CAAC,SAAS,aAAa;CAGhE,IAAI,CAAC,sBACH;CAGF,MAAM,eAAe;CAErB,MAAM,SAAS,SAAS,MAAM,WAAW,SAAS,SAAS,IAAI;CAE/D,IAAI,QACF,OAAO,MAAM;AAEjB"}
1
+ {"version":3,"file":"scope-tab.mjs","names":[],"sources":["../../src/use-focus-trap/scope-tab.ts"],"sourcesContent":["import { findTabbableDescendants } from './tabbable';\n\nconst NATIVE_TAB_STOPS_INPUT_TYPES = ['date', 'time', 'datetime-local', 'month', 'week'];\nconst NATIVE_TAB_STOPS_NODES = ['audio', 'video', 'iframe', 'object', 'embed'];\n\nfunction isRadio(element: Element | null): element is HTMLInputElement {\n return !!element && element.tagName === 'INPUT' && (element as HTMLInputElement).type === 'radio';\n}\n\nfunction isSameRadioGroup(a: HTMLInputElement, b: HTMLInputElement) {\n return a.name !== '' && a.name === b.name && a.form === b.form;\n}\n\nfunction hasNativeTabStops(element: Element) {\n const nodeName = element.nodeName.toLowerCase();\n\n if (nodeName === 'input') {\n return NATIVE_TAB_STOPS_INPUT_TYPES.includes((element as HTMLInputElement).type);\n }\n\n return NATIVE_TAB_STOPS_NODES.includes(nodeName);\n}\n\nfunction getTabSequence(node: HTMLElement, activeElement: Element) {\n const tabbable = findTabbableDescendants(node);\n\n return tabbable.filter((element) => {\n if (element === activeElement || !isRadio(element)) {\n return true;\n }\n\n if (isRadio(activeElement) && isSameRadioGroup(element, activeElement)) {\n return false;\n }\n\n if (element.checked) {\n return true;\n }\n\n return !tabbable.some(\n (item) => isRadio(item) && item.checked && isSameRadioGroup(item, element)\n );\n });\n}\n\nfunction getStartIndex(\n sequence: HTMLElement[],\n activeElement: Element,\n node: HTMLElement,\n backwards: boolean\n) {\n const activeIndex = sequence.indexOf(activeElement as HTMLElement);\n if (activeIndex !== -1) {\n return activeIndex + 1;\n }\n\n if (activeElement === node) {\n return 0;\n }\n\n const position = backwards ? Node.DOCUMENT_POSITION_PRECEDING : Node.DOCUMENT_POSITION_FOLLOWING;\n const nextIndex = sequence.findIndex(\n (element) => (activeElement.compareDocumentPosition(element) & position) !== 0\n );\n\n return nextIndex === -1 ? 0 : nextIndex;\n}\n\nfunction getCandidates(node: HTMLElement, activeElement: Element, backwards: boolean) {\n const sequence = getTabSequence(node, activeElement);\n if (backwards) {\n sequence.reverse();\n }\n\n const startIndex = getStartIndex(sequence, activeElement, node, backwards);\n return [...sequence.slice(startIndex), ...sequence.slice(0, startIndex)].filter(\n (element) => element !== activeElement\n );\n}\n\nfunction focusFirstAvailable(candidates: HTMLElement[], root: DocumentOrShadowRoot) {\n for (const candidate of candidates) {\n candidate.focus();\n\n if (root.activeElement === candidate) {\n return;\n }\n }\n}\n\nexport function scopeTab(node: HTMLElement, event: KeyboardEvent) {\n const root = node.getRootNode() as unknown as DocumentOrShadowRoot;\n const activeElement = root.activeElement;\n\n if (!activeElement || (activeElement !== node && !node.contains(activeElement))) {\n return;\n }\n\n const candidates = getCandidates(node, activeElement, event.shiftKey);\n\n if (hasNativeTabStops(activeElement)) {\n setTimeout(() => {\n const current = root.activeElement;\n if (node.isConnected && current !== activeElement && current !== candidates[0]) {\n focusFirstAvailable([...candidates, activeElement as HTMLElement], root);\n }\n });\n return;\n }\n\n event.preventDefault();\n focusFirstAvailable(candidates, root);\n}\n"],"mappings":";;;AAEA,MAAM,+BAA+B;CAAC;CAAQ;CAAQ;CAAkB;CAAS;AAAM;AACvF,MAAM,yBAAyB;CAAC;CAAS;CAAS;CAAU;CAAU;AAAO;AAE7E,SAAS,QAAQ,SAAsD;CACrE,OAAO,CAAC,CAAC,WAAW,QAAQ,YAAY,WAAY,QAA6B,SAAS;AAC5F;AAEA,SAAS,iBAAiB,GAAqB,GAAqB;CAClE,OAAO,EAAE,SAAS,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE;AAC5D;AAEA,SAAS,kBAAkB,SAAkB;CAC3C,MAAM,WAAW,QAAQ,SAAS,YAAY;CAE9C,IAAI,aAAa,SACf,OAAO,6BAA6B,SAAU,QAA6B,IAAI;CAGjF,OAAO,uBAAuB,SAAS,QAAQ;AACjD;AAEA,SAAS,eAAe,MAAmB,eAAwB;CACjE,MAAM,WAAW,wBAAwB,IAAI;CAE7C,OAAO,SAAS,QAAQ,YAAY;EAClC,IAAI,YAAY,iBAAiB,CAAC,QAAQ,OAAO,GAC/C,OAAO;EAGT,IAAI,QAAQ,aAAa,KAAK,iBAAiB,SAAS,aAAa,GACnE,OAAO;EAGT,IAAI,QAAQ,SACV,OAAO;EAGT,OAAO,CAAC,SAAS,MACd,SAAS,QAAQ,IAAI,KAAK,KAAK,WAAW,iBAAiB,MAAM,OAAO,CAC3E;CACF,CAAC;AACH;AAEA,SAAS,cACP,UACA,eACA,MACA,WACA;CACA,MAAM,cAAc,SAAS,QAAQ,aAA4B;CACjE,IAAI,gBAAgB,IAClB,OAAO,cAAc;CAGvB,IAAI,kBAAkB,MACpB,OAAO;CAGT,MAAM,WAAW,YAAY,KAAK,8BAA8B,KAAK;CACrE,MAAM,YAAY,SAAS,WACxB,aAAa,cAAc,wBAAwB,OAAO,IAAI,cAAc,CAC/E;CAEA,OAAO,cAAc,KAAK,IAAI;AAChC;AAEA,SAAS,cAAc,MAAmB,eAAwB,WAAoB;CACpF,MAAM,WAAW,eAAe,MAAM,aAAa;CACnD,IAAI,WACF,SAAS,QAAQ;CAGnB,MAAM,aAAa,cAAc,UAAU,eAAe,MAAM,SAAS;CACzE,OAAO,CAAC,GAAG,SAAS,MAAM,UAAU,GAAG,GAAG,SAAS,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,QACtE,YAAY,YAAY,aAC3B;AACF;AAEA,SAAS,oBAAoB,YAA2B,MAA4B;CAClF,KAAK,MAAM,aAAa,YAAY;EAClC,UAAU,MAAM;EAEhB,IAAI,KAAK,kBAAkB,WACzB;CAEJ;AACF;AAEA,SAAgB,SAAS,MAAmB,OAAsB;CAChE,MAAM,OAAO,KAAK,YAAY;CAC9B,MAAM,gBAAgB,KAAK;CAE3B,IAAI,CAAC,iBAAkB,kBAAkB,QAAQ,CAAC,KAAK,SAAS,aAAa,GAC3E;CAGF,MAAM,aAAa,cAAc,MAAM,eAAe,MAAM,QAAQ;CAEpE,IAAI,kBAAkB,aAAa,GAAG;EACpC,iBAAiB;GACf,MAAM,UAAU,KAAK;GACrB,IAAI,KAAK,eAAe,YAAY,iBAAiB,YAAY,WAAW,IAC1E,oBAAoB,CAAC,GAAG,YAAY,aAA4B,GAAG,IAAI;EAE3E,CAAC;EACD;CACF;CAEA,MAAM,eAAe;CACrB,oBAAoB,YAAY,IAAI;AACtC"}
@@ -1,7 +1,15 @@
1
1
  "use client";
2
2
  //#region packages/@mantine/hooks/src/use-focus-trap/tabbable.ts
3
- const TABBABLE_NODES = /input|select|textarea|button|object/;
4
- const FOCUS_SELECTOR = "a, input, select, textarea, button, object, [tabindex]";
3
+ const TABBABLE_NODES = /input|select|textarea|button|object|summary|iframe/;
4
+ const FOCUS_SELECTOR = "a, input, select, textarea, button, object, summary, iframe, audio[controls], video[controls], [contenteditable], [tabindex]";
5
+ function isContentEditable(element) {
6
+ const value = element.getAttribute("contenteditable");
7
+ return value !== null && value !== "false";
8
+ }
9
+ function hasMediaControls(element) {
10
+ const nodeName = element.nodeName.toLowerCase();
11
+ return (nodeName === "audio" || nodeName === "video") && element.hasAttribute("controls");
12
+ }
5
13
  function hidden(element) {
6
14
  return element.style.display === "none";
7
15
  }
@@ -23,14 +31,17 @@ function getElementTabIndex(element) {
23
31
  function focusable(element) {
24
32
  const nodeName = element.nodeName.toLowerCase();
25
33
  const isTabIndexNotNaN = !Number.isNaN(getElementTabIndex(element));
26
- return (TABBABLE_NODES.test(nodeName) && !element.disabled || (element instanceof HTMLAnchorElement ? element.href || isTabIndexNotNaN : isTabIndexNotNaN)) && visible(element);
34
+ return (TABBABLE_NODES.test(nodeName) && !element.disabled || isContentEditable(element) || hasMediaControls(element) || (element instanceof HTMLAnchorElement ? element.href || isTabIndexNotNaN : isTabIndexNotNaN)) && visible(element);
27
35
  }
28
36
  function tabbable(element) {
29
37
  const tabIndex = getElementTabIndex(element);
30
38
  return (Number.isNaN(tabIndex) || tabIndex >= 0) && focusable(element);
31
39
  }
32
40
  function findTabbableDescendants(element) {
33
- return Array.from(element.querySelectorAll(FOCUS_SELECTOR)).filter(tabbable);
41
+ const elements = Array.from(element.querySelectorAll(FOCUS_SELECTOR)).filter(tabbable);
42
+ const positive = elements.filter((item) => getElementTabIndex(item) > 0).sort((a, b) => getElementTabIndex(a) - getElementTabIndex(b));
43
+ const regular = elements.filter((item) => !(getElementTabIndex(item) > 0));
44
+ return [...positive, ...regular];
34
45
  }
35
46
  //#endregion
36
47
  export { FOCUS_SELECTOR, findTabbableDescendants, focusable, tabbable };
@@ -1 +1 @@
1
- {"version":3,"file":"tabbable.mjs","names":[],"sources":["../../src/use-focus-trap/tabbable.ts"],"sourcesContent":["const TABBABLE_NODES = /input|select|textarea|button|object/;\nexport const FOCUS_SELECTOR = 'a, input, select, textarea, button, object, [tabindex]';\n\nfunction hidden(element: HTMLElement) {\n if (process.env.NODE_ENV === 'test') {\n return false;\n }\n\n return element.style.display === 'none';\n}\n\nfunction visible(element: HTMLElement) {\n const isHidden =\n element.getAttribute('aria-hidden') ||\n element.getAttribute('hidden') ||\n element.getAttribute('type') === 'hidden';\n\n if (isHidden) {\n return false;\n }\n\n let parentElement: HTMLElement = element;\n while (parentElement) {\n if (parentElement === document.body || parentElement.nodeType === 11) {\n break;\n }\n\n if (hidden(parentElement)) {\n return false;\n }\n\n parentElement = parentElement.parentNode as HTMLElement;\n }\n\n return true;\n}\n\nfunction getElementTabIndex(element: HTMLElement) {\n let tabIndex: string | null | undefined = element.getAttribute('tabindex');\n if (tabIndex === null) {\n tabIndex = undefined;\n }\n return parseInt(tabIndex as string, 10);\n}\n\nexport function focusable(element: HTMLElement) {\n const nodeName = element.nodeName.toLowerCase();\n const isTabIndexNotNaN = !Number.isNaN(getElementTabIndex(element));\n const res =\n // @ts-expect-error function accepts any html element but if it is a button, it should not be disabled to trigger the condition\n (TABBABLE_NODES.test(nodeName) && !element.disabled) ||\n (element instanceof HTMLAnchorElement ? element.href || isTabIndexNotNaN : isTabIndexNotNaN);\n\n return res && visible(element);\n}\n\nexport function tabbable(element: HTMLElement) {\n const tabIndex = getElementTabIndex(element);\n const isTabIndexNaN = Number.isNaN(tabIndex);\n return (isTabIndexNaN || tabIndex >= 0) && focusable(element);\n}\n\nexport function findTabbableDescendants(element: HTMLElement): HTMLElement[] {\n return Array.from(element.querySelectorAll<HTMLElement>(FOCUS_SELECTOR)).filter(tabbable);\n}\n"],"mappings":";;AAAA,MAAM,iBAAiB;AACvB,MAAa,iBAAiB;AAE9B,SAAS,OAAO,SAAsB;CAKpC,OAAO,QAAQ,MAAM,YAAY;AACnC;AAEA,SAAS,QAAQ,SAAsB;CAMrC,IAJE,QAAQ,aAAa,aAAa,KAClC,QAAQ,aAAa,QAAQ,KAC7B,QAAQ,aAAa,MAAM,MAAM,UAGjC,OAAO;CAGT,IAAI,gBAA6B;CACjC,OAAO,eAAe;EACpB,IAAI,kBAAkB,SAAS,QAAQ,cAAc,aAAa,IAChE;EAGF,IAAI,OAAO,aAAa,GACtB,OAAO;EAGT,gBAAgB,cAAc;CAChC;CAEA,OAAO;AACT;AAEA,SAAS,mBAAmB,SAAsB;CAChD,IAAI,WAAsC,QAAQ,aAAa,UAAU;CACzE,IAAI,aAAa,MACf,WAAW,KAAA;CAEb,OAAO,SAAS,UAAoB,EAAE;AACxC;AAEA,SAAgB,UAAU,SAAsB;CAC9C,MAAM,WAAW,QAAQ,SAAS,YAAY;CAC9C,MAAM,mBAAmB,CAAC,OAAO,MAAM,mBAAmB,OAAO,CAAC;CAMlE,QAHG,eAAe,KAAK,QAAQ,KAAK,CAAC,QAAQ,aAC1C,mBAAmB,oBAAoB,QAAQ,QAAQ,mBAAmB,sBAE/D,QAAQ,OAAO;AAC/B;AAEA,SAAgB,SAAS,SAAsB;CAC7C,MAAM,WAAW,mBAAmB,OAAO;CAE3C,QADsB,OAAO,MAAM,QACf,KAAK,YAAY,MAAM,UAAU,OAAO;AAC9D;AAEA,SAAgB,wBAAwB,SAAqC;CAC3E,OAAO,MAAM,KAAK,QAAQ,iBAA8B,cAAc,CAAC,CAAC,CAAC,OAAO,QAAQ;AAC1F"}
1
+ {"version":3,"file":"tabbable.mjs","names":[],"sources":["../../src/use-focus-trap/tabbable.ts"],"sourcesContent":["const TABBABLE_NODES = /input|select|textarea|button|object|summary|iframe/;\nexport const FOCUS_SELECTOR =\n 'a, input, select, textarea, button, object, summary, iframe, audio[controls], video[controls], [contenteditable], [tabindex]';\n\nfunction isContentEditable(element: HTMLElement) {\n const value = element.getAttribute('contenteditable');\n return value !== null && value !== 'false';\n}\n\nfunction hasMediaControls(element: HTMLElement) {\n const nodeName = element.nodeName.toLowerCase();\n return (nodeName === 'audio' || nodeName === 'video') && element.hasAttribute('controls');\n}\n\nfunction hidden(element: HTMLElement) {\n if (process.env.NODE_ENV === 'test') {\n return false;\n }\n\n return element.style.display === 'none';\n}\n\nfunction visible(element: HTMLElement) {\n const isHidden =\n element.getAttribute('aria-hidden') ||\n element.getAttribute('hidden') ||\n element.getAttribute('type') === 'hidden';\n\n if (isHidden) {\n return false;\n }\n\n let parentElement: HTMLElement = element;\n while (parentElement) {\n if (parentElement === document.body || parentElement.nodeType === 11) {\n break;\n }\n\n if (hidden(parentElement)) {\n return false;\n }\n\n parentElement = parentElement.parentNode as HTMLElement;\n }\n\n return true;\n}\n\nfunction getElementTabIndex(element: HTMLElement) {\n let tabIndex: string | null | undefined = element.getAttribute('tabindex');\n if (tabIndex === null) {\n tabIndex = undefined;\n }\n return parseInt(tabIndex as string, 10);\n}\n\nexport function focusable(element: HTMLElement) {\n const nodeName = element.nodeName.toLowerCase();\n const isTabIndexNotNaN = !Number.isNaN(getElementTabIndex(element));\n const res =\n // @ts-expect-error function accepts any html element but if it is a button, it should not be disabled to trigger the condition\n (TABBABLE_NODES.test(nodeName) && !element.disabled) ||\n isContentEditable(element) ||\n hasMediaControls(element) ||\n (element instanceof HTMLAnchorElement ? element.href || isTabIndexNotNaN : isTabIndexNotNaN);\n\n return res && visible(element);\n}\n\nexport function tabbable(element: HTMLElement) {\n const tabIndex = getElementTabIndex(element);\n const isTabIndexNaN = Number.isNaN(tabIndex);\n return (isTabIndexNaN || tabIndex >= 0) && focusable(element);\n}\n\nexport function findTabbableDescendants(element: HTMLElement): HTMLElement[] {\n const elements = Array.from(element.querySelectorAll<HTMLElement>(FOCUS_SELECTOR)).filter(\n tabbable\n );\n const positive = elements\n .filter((item) => getElementTabIndex(item) > 0)\n .sort((a, b) => getElementTabIndex(a) - getElementTabIndex(b));\n const regular = elements.filter((item) => !(getElementTabIndex(item) > 0));\n return [...positive, ...regular];\n}\n"],"mappings":";;AAAA,MAAM,iBAAiB;AACvB,MAAa,iBACX;AAEF,SAAS,kBAAkB,SAAsB;CAC/C,MAAM,QAAQ,QAAQ,aAAa,iBAAiB;CACpD,OAAO,UAAU,QAAQ,UAAU;AACrC;AAEA,SAAS,iBAAiB,SAAsB;CAC9C,MAAM,WAAW,QAAQ,SAAS,YAAY;CAC9C,QAAQ,aAAa,WAAW,aAAa,YAAY,QAAQ,aAAa,UAAU;AAC1F;AAEA,SAAS,OAAO,SAAsB;CAKpC,OAAO,QAAQ,MAAM,YAAY;AACnC;AAEA,SAAS,QAAQ,SAAsB;CAMrC,IAJE,QAAQ,aAAa,aAAa,KAClC,QAAQ,aAAa,QAAQ,KAC7B,QAAQ,aAAa,MAAM,MAAM,UAGjC,OAAO;CAGT,IAAI,gBAA6B;CACjC,OAAO,eAAe;EACpB,IAAI,kBAAkB,SAAS,QAAQ,cAAc,aAAa,IAChE;EAGF,IAAI,OAAO,aAAa,GACtB,OAAO;EAGT,gBAAgB,cAAc;CAChC;CAEA,OAAO;AACT;AAEA,SAAS,mBAAmB,SAAsB;CAChD,IAAI,WAAsC,QAAQ,aAAa,UAAU;CACzE,IAAI,aAAa,MACf,WAAW,KAAA;CAEb,OAAO,SAAS,UAAoB,EAAE;AACxC;AAEA,SAAgB,UAAU,SAAsB;CAC9C,MAAM,WAAW,QAAQ,SAAS,YAAY;CAC9C,MAAM,mBAAmB,CAAC,OAAO,MAAM,mBAAmB,OAAO,CAAC;CAQlE,QALG,eAAe,KAAK,QAAQ,KAAK,CAAC,QAAQ,YAC3C,kBAAkB,OAAO,KACzB,iBAAiB,OAAO,MACvB,mBAAmB,oBAAoB,QAAQ,QAAQ,mBAAmB,sBAE/D,QAAQ,OAAO;AAC/B;AAEA,SAAgB,SAAS,SAAsB;CAC7C,MAAM,WAAW,mBAAmB,OAAO;CAE3C,QADsB,OAAO,MAAM,QACf,KAAK,YAAY,MAAM,UAAU,OAAO;AAC9D;AAEA,SAAgB,wBAAwB,SAAqC;CAC3E,MAAM,WAAW,MAAM,KAAK,QAAQ,iBAA8B,cAAc,CAAC,CAAC,CAAC,OACjF,QACF;CACA,MAAM,WAAW,SACd,QAAQ,SAAS,mBAAmB,IAAI,IAAI,CAAC,CAAC,CAC9C,MAAM,GAAG,MAAM,mBAAmB,CAAC,IAAI,mBAAmB,CAAC,CAAC;CAC/D,MAAM,UAAU,SAAS,QAAQ,SAAS,EAAE,mBAAmB,IAAI,IAAI,EAAE;CACzE,OAAO,CAAC,GAAG,UAAU,GAAG,OAAO;AACjC"}
@@ -3,6 +3,16 @@ import { FOCUS_SELECTOR, focusable, tabbable } from "./tabbable.mjs";
3
3
  import { scopeTab } from "./scope-tab.mjs";
4
4
  import { useCallback, useEffect, useRef } from "react";
5
5
  //#region packages/@mantine/hooks/src/use-focus-trap/use-focus-trap.ts
6
+ const activeTraps = /* @__PURE__ */ new Set();
7
+ function hasNestedActiveTrap(ref, node) {
8
+ const activeElement = node.getRootNode().activeElement;
9
+ if (!activeElement) return false;
10
+ for (const trap of activeTraps) {
11
+ const nested = trap.current;
12
+ if (trap !== ref && nested && nested !== node && node.contains(nested) && nested.contains(activeElement)) return true;
13
+ }
14
+ return false;
15
+ }
6
16
  function useFocusTrap(active = true) {
7
17
  const ref = useRef(null);
8
18
  const focusNode = (node) => {
@@ -34,10 +44,15 @@ function useFocusTrap(active = true) {
34
44
  if (ref.current) focusNode(ref.current);
35
45
  });
36
46
  const handleKeyDown = (event) => {
37
- if (event.key === "Tab" && ref.current) scopeTab(ref.current, event);
47
+ if (event.key !== "Tab" || event.defaultPrevented || !ref.current || hasNestedActiveTrap(ref, ref.current)) return;
48
+ scopeTab(ref.current, event);
38
49
  };
50
+ activeTraps.add(ref);
39
51
  document.addEventListener("keydown", handleKeyDown);
40
- return () => document.removeEventListener("keydown", handleKeyDown);
52
+ return () => {
53
+ activeTraps.delete(ref);
54
+ document.removeEventListener("keydown", handleKeyDown);
55
+ };
41
56
  }, [active]);
42
57
  return setRef;
43
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"use-focus-trap.mjs","names":[],"sources":["../../src/use-focus-trap/use-focus-trap.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { scopeTab } from './scope-tab';\nimport { FOCUS_SELECTOR, focusable, tabbable } from './tabbable';\n\nexport function useFocusTrap(active = true): React.RefCallback<HTMLElement | null> {\n const ref = useRef<HTMLElement>(null);\n\n const focusNode = (node: HTMLElement) => {\n let focusElement: HTMLElement | null = node.querySelector('[data-autofocus]');\n\n if (!focusElement) {\n const children = Array.from<HTMLElement>(node.querySelectorAll(FOCUS_SELECTOR));\n focusElement = children.find(tabbable) || children.find(focusable) || null;\n if (!focusElement && focusable(node)) {\n focusElement = node;\n }\n }\n\n if (focusElement) {\n focusElement.focus({ preventScroll: true });\n } else if (process.env.NODE_ENV === 'development') {\n // oxlint-disable-next-line no-console\n console.warn(\n '[@mantine/hooks/use-focus-trap] Failed to find focusable element within provided node',\n node\n );\n }\n };\n\n const setRef = useCallback(\n (node: HTMLElement | null) => {\n if (!active) {\n return;\n }\n\n if (node === null) {\n ref.current = null;\n return;\n }\n\n if (ref.current === node) {\n return;\n }\n\n // Delay processing the HTML node by a frame. This ensures focus is assigned correctly.\n setTimeout(() => {\n if (node.getRootNode()) {\n focusNode(node);\n } else if (process.env.NODE_ENV === 'development') {\n // oxlint-disable-next-line no-console\n console.warn('[@mantine/hooks/use-focus-trap] Ref node is not part of the dom', node);\n }\n });\n\n ref.current = node;\n },\n [active]\n );\n\n useEffect(() => {\n if (!active) {\n return undefined;\n }\n\n if (ref.current) {\n setTimeout(() => {\n if (ref.current) {\n focusNode(ref.current);\n }\n });\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Tab' && ref.current) {\n scopeTab(ref.current, event);\n }\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [active]);\n\n return setRef;\n}\n"],"mappings":";;;;;AAIA,SAAgB,aAAa,SAAS,MAA6C;CACjF,MAAM,MAAM,OAAoB,IAAI;CAEpC,MAAM,aAAa,SAAsB;EACvC,IAAI,eAAmC,KAAK,cAAc,kBAAkB;EAE5E,IAAI,CAAC,cAAc;GACjB,MAAM,WAAW,MAAM,KAAkB,KAAK,iBAAiB,cAAc,CAAC;GAC9E,eAAe,SAAS,KAAK,QAAQ,KAAK,SAAS,KAAK,SAAS,KAAK;GACtE,IAAI,CAAC,gBAAgB,UAAU,IAAI,GACjC,eAAe;EAEnB;EAEA,IAAI,cACF,aAAa,MAAM,EAAE,eAAe,KAAK,CAAC;OAG1C,QAAQ,KACN,yFACA,IACF;CAEJ;CAEA,MAAM,SAAS,aACZ,SAA6B;EAC5B,IAAI,CAAC,QACH;EAGF,IAAI,SAAS,MAAM;GACjB,IAAI,UAAU;GACd;EACF;EAEA,IAAI,IAAI,YAAY,MAClB;EAIF,iBAAiB;GACf,IAAI,KAAK,YAAY,GACnB,UAAU,IAAI;QAGd,QAAQ,KAAK,mEAAmE,IAAI;EAExF,CAAC;EAED,IAAI,UAAU;CAChB,GACA,CAAC,MAAM,CACT;CAEA,gBAAgB;EACd,IAAI,CAAC,QACH;EAGF,IAAI,IAAI,SACN,iBAAiB;GACf,IAAI,IAAI,SACN,UAAU,IAAI,OAAO;EAEzB,CAAC;EAGH,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,QAAQ,SAAS,IAAI,SAC7B,SAAS,IAAI,SAAS,KAAK;EAE/B;EAEA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa,SAAS,oBAAoB,WAAW,aAAa;CACpE,GAAG,CAAC,MAAM,CAAC;CAEX,OAAO;AACT"}
1
+ {"version":3,"file":"use-focus-trap.mjs","names":[],"sources":["../../src/use-focus-trap/use-focus-trap.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { scopeTab } from './scope-tab';\nimport { FOCUS_SELECTOR, focusable, tabbable } from './tabbable';\n\nconst activeTraps = new Set<React.RefObject<HTMLElement | null>>();\n\nfunction hasNestedActiveTrap(ref: React.RefObject<HTMLElement | null>, node: HTMLElement) {\n const activeElement = (node.getRootNode() as unknown as DocumentOrShadowRoot).activeElement;\n if (!activeElement) {\n return false;\n }\n\n for (const trap of activeTraps) {\n const nested = trap.current;\n if (\n trap !== ref &&\n nested &&\n nested !== node &&\n node.contains(nested) &&\n nested.contains(activeElement)\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function useFocusTrap(active = true): React.RefCallback<HTMLElement | null> {\n const ref = useRef<HTMLElement>(null);\n\n const focusNode = (node: HTMLElement) => {\n let focusElement: HTMLElement | null = node.querySelector('[data-autofocus]');\n\n if (!focusElement) {\n const children = Array.from<HTMLElement>(node.querySelectorAll(FOCUS_SELECTOR));\n focusElement = children.find(tabbable) || children.find(focusable) || null;\n if (!focusElement && focusable(node)) {\n focusElement = node;\n }\n }\n\n if (focusElement) {\n focusElement.focus({ preventScroll: true });\n } else if (process.env.NODE_ENV === 'development') {\n // oxlint-disable-next-line no-console\n console.warn(\n '[@mantine/hooks/use-focus-trap] Failed to find focusable element within provided node',\n node\n );\n }\n };\n\n const setRef = useCallback(\n (node: HTMLElement | null) => {\n if (!active) {\n return;\n }\n\n if (node === null) {\n ref.current = null;\n return;\n }\n\n if (ref.current === node) {\n return;\n }\n\n // Delay processing the HTML node by a frame. This ensures focus is assigned correctly.\n setTimeout(() => {\n if (node.getRootNode()) {\n focusNode(node);\n } else if (process.env.NODE_ENV === 'development') {\n // oxlint-disable-next-line no-console\n console.warn('[@mantine/hooks/use-focus-trap] Ref node is not part of the dom', node);\n }\n });\n\n ref.current = node;\n },\n [active]\n );\n\n useEffect(() => {\n if (!active) {\n return undefined;\n }\n\n if (ref.current) {\n setTimeout(() => {\n if (ref.current) {\n focusNode(ref.current);\n }\n });\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (\n event.key !== 'Tab' ||\n event.defaultPrevented ||\n !ref.current ||\n hasNestedActiveTrap(ref, ref.current)\n ) {\n return;\n }\n\n scopeTab(ref.current, event);\n };\n\n activeTraps.add(ref);\n document.addEventListener('keydown', handleKeyDown);\n return () => {\n activeTraps.delete(ref);\n document.removeEventListener('keydown', handleKeyDown);\n };\n }, [active]);\n\n return setRef;\n}\n"],"mappings":";;;;;AAIA,MAAM,8BAAc,IAAI,IAAyC;AAEjE,SAAS,oBAAoB,KAA0C,MAAmB;CACxF,MAAM,gBAAiB,KAAK,YAAY,CAAC,CAAqC;CAC9E,IAAI,CAAC,eACH,OAAO;CAGT,KAAK,MAAM,QAAQ,aAAa;EAC9B,MAAM,SAAS,KAAK;EACpB,IACE,SAAS,OACT,UACA,WAAW,QACX,KAAK,SAAS,MAAM,KACpB,OAAO,SAAS,aAAa,GAE7B,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,SAAgB,aAAa,SAAS,MAA6C;CACjF,MAAM,MAAM,OAAoB,IAAI;CAEpC,MAAM,aAAa,SAAsB;EACvC,IAAI,eAAmC,KAAK,cAAc,kBAAkB;EAE5E,IAAI,CAAC,cAAc;GACjB,MAAM,WAAW,MAAM,KAAkB,KAAK,iBAAiB,cAAc,CAAC;GAC9E,eAAe,SAAS,KAAK,QAAQ,KAAK,SAAS,KAAK,SAAS,KAAK;GACtE,IAAI,CAAC,gBAAgB,UAAU,IAAI,GACjC,eAAe;EAEnB;EAEA,IAAI,cACF,aAAa,MAAM,EAAE,eAAe,KAAK,CAAC;OAG1C,QAAQ,KACN,yFACA,IACF;CAEJ;CAEA,MAAM,SAAS,aACZ,SAA6B;EAC5B,IAAI,CAAC,QACH;EAGF,IAAI,SAAS,MAAM;GACjB,IAAI,UAAU;GACd;EACF;EAEA,IAAI,IAAI,YAAY,MAClB;EAIF,iBAAiB;GACf,IAAI,KAAK,YAAY,GACnB,UAAU,IAAI;QAGd,QAAQ,KAAK,mEAAmE,IAAI;EAExF,CAAC;EAED,IAAI,UAAU;CAChB,GACA,CAAC,MAAM,CACT;CAEA,gBAAgB;EACd,IAAI,CAAC,QACH;EAGF,IAAI,IAAI,SACN,iBAAiB;GACf,IAAI,IAAI,SACN,UAAU,IAAI,OAAO;EAEzB,CAAC;EAGH,MAAM,iBAAiB,UAAyB;GAC9C,IACE,MAAM,QAAQ,SACd,MAAM,oBACN,CAAC,IAAI,WACL,oBAAoB,KAAK,IAAI,OAAO,GAEpC;GAGF,SAAS,IAAI,SAAS,KAAK;EAC7B;EAEA,YAAY,IAAI,GAAG;EACnB,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa;GACX,YAAY,OAAO,GAAG;GACtB,SAAS,oBAAoB,WAAW,aAAa;EACvD;CACF,GAAG,CAAC,MAAM,CAAC;CAEX,OAAO;AACT"}
@@ -11,6 +11,7 @@ function useScroller(options = {}) {
11
11
  const hasDraggedRef = useRef(false);
12
12
  const startX = useRef(0);
13
13
  const scrollLeftStart = useRef(0);
14
+ const removeClickSuppressorRef = useRef(null);
14
15
  const onScrollStateChangeRef = useRef(onScrollStateChange);
15
16
  onScrollStateChangeRef.current = onScrollStateChange;
16
17
  const updateScrollState = useCallback(() => {
@@ -64,6 +65,7 @@ function useScroller(options = {}) {
64
65
  const scrollStart = useCallback(() => scroll("start"), [scroll]);
65
66
  const scrollEnd = useCallback(() => scroll("end"), [scroll]);
66
67
  const handleMouseDown = useCallback((event) => {
68
+ removeClickSuppressorRef.current?.();
67
69
  if (!draggable) return;
68
70
  const container = containerRef.current;
69
71
  if (container) {
@@ -96,12 +98,18 @@ function useScroller(options = {}) {
96
98
  container.style.cursor = "";
97
99
  container.style.userSelect = "";
98
100
  if (wasDragged) {
101
+ removeClickSuppressorRef.current?.();
102
+ const removeSuppressor = () => {
103
+ container.removeEventListener("click", suppressClick, true);
104
+ removeClickSuppressorRef.current = null;
105
+ };
99
106
  const suppressClick = (event) => {
100
107
  event.stopPropagation();
101
108
  event.preventDefault();
102
- container.removeEventListener("click", suppressClick, true);
109
+ removeSuppressor();
103
110
  };
104
111
  container.addEventListener("click", suppressClick, true);
112
+ removeClickSuppressorRef.current = removeSuppressor;
105
113
  }
106
114
  }
107
115
  }, []);
@@ -1 +1 @@
1
- {"version":3,"file":"use-scroller.mjs","names":[],"sources":["../../src/use-scroller/use-scroller.ts"],"sourcesContent":["import { RefCallback, useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface UseScrollerOptions {\n /** Amount of pixels to scroll when calling scroll functions, `200` by default */\n scrollAmount?: number;\n\n /** Determines whether content can be scrolled by dragging with mouse, `true` by default */\n draggable?: boolean;\n\n /** Called when scroll state changes (canScrollStart or canScrollEnd) */\n onScrollStateChange?: (state: UseScrollerScrollState) => void;\n}\n\nexport interface UseScrollerScrollState {\n /** Whether content can be scrolled towards the start (left in LTR, right in RTL) */\n canScrollStart: boolean;\n\n /** Whether content can be scrolled towards the end (right in LTR, left in RTL) */\n canScrollEnd: boolean;\n}\n\nexport interface UseScrollerReturnValue<T extends HTMLElement = HTMLDivElement> {\n /** Ref callback to attach to the scrollable container element */\n ref: RefCallback<T | null>;\n\n /** Whether content can be scrolled towards the start */\n canScrollStart: boolean;\n\n /** Whether content can be scrolled towards the end */\n canScrollEnd: boolean;\n\n /** Scrolls towards the start direction */\n scrollStart: () => void;\n\n /** Scrolls towards the end direction */\n scrollEnd: () => void;\n\n /** `true` if the user is currently dragging the content */\n isDragging: boolean;\n\n /** Props to spread on the scrollable container for drag functionality */\n dragHandlers: {\n onMouseDown: (e: React.MouseEvent) => void;\n onMouseMove: (e: React.MouseEvent) => void;\n onMouseUp: () => void;\n onMouseLeave: () => void;\n };\n}\n\nexport function useScroller<T extends HTMLElement = HTMLDivElement>(\n options: UseScrollerOptions = {}\n): UseScrollerReturnValue<T> {\n const { scrollAmount = 200, draggable = true, onScrollStateChange } = options;\n\n const containerRef = useRef<T | null>(null);\n\n const [canScrollStart, setCanScrollStart] = useState(false);\n const [canScrollEnd, setCanScrollEnd] = useState(false);\n const [isDragging, setIsDragging] = useState(false);\n\n const isDraggingRef = useRef(false);\n const hasDraggedRef = useRef(false);\n const startX = useRef(0);\n const scrollLeftStart = useRef(0);\n\n const onScrollStateChangeRef = useRef(onScrollStateChange);\n onScrollStateChangeRef.current = onScrollStateChange;\n\n const updateScrollState = useCallback(() => {\n const container = containerRef.current;\n if (container) {\n const { scrollLeft, scrollWidth, clientWidth } = container;\n const isRtl = getComputedStyle(container).direction === 'rtl';\n\n let newCanScrollStart: boolean;\n let newCanScrollEnd: boolean;\n\n if (isRtl) {\n newCanScrollStart = scrollLeft < -1;\n newCanScrollEnd = scrollLeft > -(scrollWidth - clientWidth) + 1;\n } else {\n newCanScrollStart = scrollLeft > 1;\n newCanScrollEnd = scrollLeft < scrollWidth - clientWidth - 1;\n }\n\n setCanScrollStart(newCanScrollStart);\n setCanScrollEnd(newCanScrollEnd);\n\n onScrollStateChangeRef.current?.({\n canScrollStart: newCanScrollStart,\n canScrollEnd: newCanScrollEnd,\n });\n }\n }, []);\n\n useEffect(() => {\n updateScrollState();\n const container = containerRef.current;\n if (container) {\n container.addEventListener('scroll', updateScrollState);\n const resizeObserver = new ResizeObserver(updateScrollState);\n resizeObserver.observe(container);\n return () => {\n container.removeEventListener('scroll', updateScrollState);\n resizeObserver.disconnect();\n };\n }\n return undefined;\n }, [updateScrollState]);\n\n const scroll = useCallback(\n (direction: 'start' | 'end') => {\n const container = containerRef.current;\n if (container) {\n const isRtl = getComputedStyle(container).direction === 'rtl';\n const amount = scrollAmount;\n const scrollBy = direction === 'end' ? amount : -amount;\n const adjustedScrollBy = isRtl ? -scrollBy : scrollBy;\n\n container.scrollBy({\n left: adjustedScrollBy,\n behavior: 'smooth',\n });\n }\n },\n [scrollAmount]\n );\n\n const scrollStart = useCallback(() => scroll('start'), [scroll]);\n const scrollEnd = useCallback(() => scroll('end'), [scroll]);\n\n const handleMouseDown = useCallback(\n (event: React.MouseEvent) => {\n if (!draggable) {\n return;\n }\n const container = containerRef.current;\n if (container) {\n isDraggingRef.current = true;\n hasDraggedRef.current = false;\n setIsDragging(true);\n startX.current = event.pageX - container.offsetLeft;\n scrollLeftStart.current = container.scrollLeft;\n container.style.cursor = 'grabbing';\n container.style.userSelect = 'none';\n }\n },\n [draggable]\n );\n\n const handleMouseMove = useCallback((event: React.MouseEvent) => {\n if (!isDraggingRef.current) {\n return;\n }\n event.preventDefault();\n const container = containerRef.current;\n if (container) {\n const x = event.pageX - container.offsetLeft;\n const walk = x - startX.current;\n if (Math.abs(walk) > 5) {\n hasDraggedRef.current = true;\n }\n container.scrollLeft = scrollLeftStart.current - walk;\n }\n }, []);\n\n const handleMouseUp = useCallback(() => {\n const wasDragged = hasDraggedRef.current;\n isDraggingRef.current = false;\n hasDraggedRef.current = false;\n setIsDragging(false);\n const container = containerRef.current;\n if (container) {\n container.style.cursor = '';\n container.style.userSelect = '';\n\n if (wasDragged) {\n const suppressClick = (event: MouseEvent) => {\n event.stopPropagation();\n event.preventDefault();\n container.removeEventListener('click', suppressClick, true);\n };\n container.addEventListener('click', suppressClick, true);\n }\n }\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n if (isDraggingRef.current) {\n handleMouseUp();\n }\n }, [handleMouseUp]);\n\n const assignRef: RefCallback<T | null> = useCallback(\n (node) => {\n containerRef.current = node;\n if (node) {\n updateScrollState();\n }\n },\n [updateScrollState]\n );\n\n return {\n ref: assignRef,\n canScrollStart,\n canScrollEnd,\n scrollStart,\n scrollEnd,\n isDragging,\n dragHandlers: {\n onMouseDown: handleMouseDown,\n onMouseMove: handleMouseMove,\n onMouseUp: handleMouseUp,\n onMouseLeave: handleMouseLeave,\n },\n };\n}\n\nexport namespace useScroller {\n export type Options = UseScrollerOptions;\n export type ReturnValue<T extends HTMLElement = HTMLDivElement> = UseScrollerReturnValue<T>;\n export type ScrollState = UseScrollerScrollState;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-scroller.mjs","names":[],"sources":["../../src/use-scroller/use-scroller.ts"],"sourcesContent":["import { RefCallback, useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface UseScrollerOptions {\n /** Amount of pixels to scroll when calling scroll functions, `200` by default */\n scrollAmount?: number;\n\n /** Determines whether content can be scrolled by dragging with mouse, `true` by default */\n draggable?: boolean;\n\n /** Called when scroll state changes (canScrollStart or canScrollEnd) */\n onScrollStateChange?: (state: UseScrollerScrollState) => void;\n}\n\nexport interface UseScrollerScrollState {\n /** Whether content can be scrolled towards the start (left in LTR, right in RTL) */\n canScrollStart: boolean;\n\n /** Whether content can be scrolled towards the end (right in LTR, left in RTL) */\n canScrollEnd: boolean;\n}\n\nexport interface UseScrollerReturnValue<T extends HTMLElement = HTMLDivElement> {\n /** Ref callback to attach to the scrollable container element */\n ref: RefCallback<T | null>;\n\n /** Whether content can be scrolled towards the start */\n canScrollStart: boolean;\n\n /** Whether content can be scrolled towards the end */\n canScrollEnd: boolean;\n\n /** Scrolls towards the start direction */\n scrollStart: () => void;\n\n /** Scrolls towards the end direction */\n scrollEnd: () => void;\n\n /** `true` if the user is currently dragging the content */\n isDragging: boolean;\n\n /** Props to spread on the scrollable container for drag functionality */\n dragHandlers: {\n onMouseDown: (e: React.MouseEvent) => void;\n onMouseMove: (e: React.MouseEvent) => void;\n onMouseUp: () => void;\n onMouseLeave: () => void;\n };\n}\n\nexport function useScroller<T extends HTMLElement = HTMLDivElement>(\n options: UseScrollerOptions = {}\n): UseScrollerReturnValue<T> {\n const { scrollAmount = 200, draggable = true, onScrollStateChange } = options;\n\n const containerRef = useRef<T | null>(null);\n\n const [canScrollStart, setCanScrollStart] = useState(false);\n const [canScrollEnd, setCanScrollEnd] = useState(false);\n const [isDragging, setIsDragging] = useState(false);\n\n const isDraggingRef = useRef(false);\n const hasDraggedRef = useRef(false);\n const startX = useRef(0);\n const scrollLeftStart = useRef(0);\n const removeClickSuppressorRef = useRef<(() => void) | null>(null);\n\n const onScrollStateChangeRef = useRef(onScrollStateChange);\n onScrollStateChangeRef.current = onScrollStateChange;\n\n const updateScrollState = useCallback(() => {\n const container = containerRef.current;\n if (container) {\n const { scrollLeft, scrollWidth, clientWidth } = container;\n const isRtl = getComputedStyle(container).direction === 'rtl';\n\n let newCanScrollStart: boolean;\n let newCanScrollEnd: boolean;\n\n if (isRtl) {\n newCanScrollStart = scrollLeft < -1;\n newCanScrollEnd = scrollLeft > -(scrollWidth - clientWidth) + 1;\n } else {\n newCanScrollStart = scrollLeft > 1;\n newCanScrollEnd = scrollLeft < scrollWidth - clientWidth - 1;\n }\n\n setCanScrollStart(newCanScrollStart);\n setCanScrollEnd(newCanScrollEnd);\n\n onScrollStateChangeRef.current?.({\n canScrollStart: newCanScrollStart,\n canScrollEnd: newCanScrollEnd,\n });\n }\n }, []);\n\n useEffect(() => {\n updateScrollState();\n const container = containerRef.current;\n if (container) {\n container.addEventListener('scroll', updateScrollState);\n const resizeObserver = new ResizeObserver(updateScrollState);\n resizeObserver.observe(container);\n return () => {\n container.removeEventListener('scroll', updateScrollState);\n resizeObserver.disconnect();\n };\n }\n return undefined;\n }, [updateScrollState]);\n\n const scroll = useCallback(\n (direction: 'start' | 'end') => {\n const container = containerRef.current;\n if (container) {\n const isRtl = getComputedStyle(container).direction === 'rtl';\n const amount = scrollAmount;\n const scrollBy = direction === 'end' ? amount : -amount;\n const adjustedScrollBy = isRtl ? -scrollBy : scrollBy;\n\n container.scrollBy({\n left: adjustedScrollBy,\n behavior: 'smooth',\n });\n }\n },\n [scrollAmount]\n );\n\n const scrollStart = useCallback(() => scroll('start'), [scroll]);\n const scrollEnd = useCallback(() => scroll('end'), [scroll]);\n\n const handleMouseDown = useCallback(\n (event: React.MouseEvent) => {\n removeClickSuppressorRef.current?.();\n if (!draggable) {\n return;\n }\n const container = containerRef.current;\n if (container) {\n isDraggingRef.current = true;\n hasDraggedRef.current = false;\n setIsDragging(true);\n startX.current = event.pageX - container.offsetLeft;\n scrollLeftStart.current = container.scrollLeft;\n container.style.cursor = 'grabbing';\n container.style.userSelect = 'none';\n }\n },\n [draggable]\n );\n\n const handleMouseMove = useCallback((event: React.MouseEvent) => {\n if (!isDraggingRef.current) {\n return;\n }\n event.preventDefault();\n const container = containerRef.current;\n if (container) {\n const x = event.pageX - container.offsetLeft;\n const walk = x - startX.current;\n if (Math.abs(walk) > 5) {\n hasDraggedRef.current = true;\n }\n container.scrollLeft = scrollLeftStart.current - walk;\n }\n }, []);\n\n const handleMouseUp = useCallback(() => {\n const wasDragged = hasDraggedRef.current;\n isDraggingRef.current = false;\n hasDraggedRef.current = false;\n setIsDragging(false);\n const container = containerRef.current;\n if (container) {\n container.style.cursor = '';\n container.style.userSelect = '';\n\n if (wasDragged) {\n removeClickSuppressorRef.current?.();\n const removeSuppressor = () => {\n container.removeEventListener('click', suppressClick, true);\n removeClickSuppressorRef.current = null;\n };\n const suppressClick = (event: MouseEvent) => {\n event.stopPropagation();\n event.preventDefault();\n removeSuppressor();\n };\n container.addEventListener('click', suppressClick, true);\n removeClickSuppressorRef.current = removeSuppressor;\n }\n }\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n if (isDraggingRef.current) {\n handleMouseUp();\n }\n }, [handleMouseUp]);\n\n const assignRef: RefCallback<T | null> = useCallback(\n (node) => {\n containerRef.current = node;\n if (node) {\n updateScrollState();\n }\n },\n [updateScrollState]\n );\n\n return {\n ref: assignRef,\n canScrollStart,\n canScrollEnd,\n scrollStart,\n scrollEnd,\n isDragging,\n dragHandlers: {\n onMouseDown: handleMouseDown,\n onMouseMove: handleMouseMove,\n onMouseUp: handleMouseUp,\n onMouseLeave: handleMouseLeave,\n },\n };\n}\n\nexport namespace useScroller {\n export type Options = UseScrollerOptions;\n export type ReturnValue<T extends HTMLElement = HTMLDivElement> = UseScrollerReturnValue<T>;\n export type ScrollState = UseScrollerScrollState;\n}\n"],"mappings":"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"}
@@ -17,10 +17,15 @@ function useThrottledCallbackWithClearTimeout(callback, wait) {
17
17
  latestOutArgsRef.current = null;
18
18
  };
19
19
  const callThrottledCallback = useCallback((...args) => {
20
- handleCallback(...args);
21
20
  latestInArgsRef.current = args;
22
21
  latestOutArgsRef.current = args;
23
22
  active.current = false;
23
+ try {
24
+ handleCallback(...args);
25
+ } catch (error) {
26
+ active.current = true;
27
+ throw error;
28
+ }
24
29
  }, [handleCallback]);
25
30
  const timerCallback = useCallback(() => {
26
31
  if (latestInArgsRef.current && latestInArgsRef.current !== latestOutArgsRef.current) {
@@ -1 +1 @@
1
- {"version":3,"file":"use-throttled-callback.mjs","names":[],"sources":["../../src/use-throttled-callback/use-throttled-callback.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport function useThrottledCallbackWithClearTimeout<T extends (...args: any[]) => any>(\n callback: T,\n wait: number\n) {\n const handleCallback = useCallbackRef(callback);\n const latestInArgsRef = useRef<Parameters<T>>(null);\n const latestOutArgsRef = useRef<Parameters<T>>(null);\n const active = useRef(true);\n const waitRef = useRef(wait);\n const timeoutRef = useRef<number>(-1);\n\n const clearTimeout = () => {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = -1;\n active.current = true;\n latestInArgsRef.current = null;\n latestOutArgsRef.current = null;\n };\n\n const callThrottledCallback = useCallback(\n (...args: Parameters<T>) => {\n handleCallback(...args);\n latestInArgsRef.current = args;\n latestOutArgsRef.current = args;\n active.current = false;\n },\n [handleCallback]\n );\n\n const timerCallback = useCallback(() => {\n if (latestInArgsRef.current && latestInArgsRef.current !== latestOutArgsRef.current) {\n callThrottledCallback(...latestInArgsRef.current);\n\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n active.current = true;\n }\n }, [callThrottledCallback]);\n\n const throttled = useCallback(\n (...args: Parameters<T>) => {\n if (active.current) {\n callThrottledCallback(...args);\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n latestInArgsRef.current = args;\n }\n },\n [callThrottledCallback, timerCallback]\n );\n\n useEffect(() => {\n waitRef.current = wait;\n }, [wait]);\n\n return [throttled, clearTimeout] as const;\n}\n\nexport function useThrottledCallback<T extends (...args: any[]) => any>(callback: T, wait: number) {\n const [throttled, clearTimeout] = useThrottledCallbackWithClearTimeout(callback, wait);\n useEffect(() => clearTimeout, []);\n return throttled;\n}\n"],"mappings":";;;;AAGA,SAAgB,qCACd,UACA,MACA;CACA,MAAM,iBAAiB,eAAe,QAAQ;CAC9C,MAAM,kBAAkB,OAAsB,IAAI;CAClD,MAAM,mBAAmB,OAAsB,IAAI;CACnD,MAAM,SAAS,OAAO,IAAI;CAC1B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,aAAa,OAAe,EAAE;CAEpC,MAAM,qBAAqB;EACzB,OAAO,aAAa,WAAW,OAAO;EACtC,WAAW,UAAU;EACrB,OAAO,UAAU;EACjB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;CAC7B;CAEA,MAAM,wBAAwB,aAC3B,GAAG,SAAwB;EAC1B,eAAe,GAAG,IAAI;EACtB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,OAAO,UAAU;CACnB,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,gBAAgB,WAAW,gBAAgB,YAAY,iBAAiB,SAAS;GACnF,sBAAsB,GAAG,gBAAgB,OAAO;GAEhD,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,OAAO,UAAU;CAErB,GAAG,CAAC,qBAAqB,CAAC;CAE1B,MAAM,YAAY,aACf,GAAG,SAAwB;EAC1B,IAAI,OAAO,SAAS;GAClB,sBAAsB,GAAG,IAAI;GAC7B,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,gBAAgB,UAAU;CAE9B,GACA,CAAC,uBAAuB,aAAa,CACvC;CAEA,gBAAgB;EACd,QAAQ,UAAU;CACpB,GAAG,CAAC,IAAI,CAAC;CAET,OAAO,CAAC,WAAW,YAAY;AACjC;AAEA,SAAgB,qBAAwD,UAAa,MAAc;CACjG,MAAM,CAAC,WAAW,gBAAgB,qCAAqC,UAAU,IAAI;CACrF,gBAAgB,cAAc,CAAC,CAAC;CAChC,OAAO;AACT"}
1
+ {"version":3,"file":"use-throttled-callback.mjs","names":[],"sources":["../../src/use-throttled-callback/use-throttled-callback.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport function useThrottledCallbackWithClearTimeout<T extends (...args: any[]) => any>(\n callback: T,\n wait: number\n) {\n const handleCallback = useCallbackRef(callback);\n const latestInArgsRef = useRef<Parameters<T>>(null);\n const latestOutArgsRef = useRef<Parameters<T>>(null);\n const active = useRef(true);\n const waitRef = useRef(wait);\n const timeoutRef = useRef<number>(-1);\n\n const clearTimeout = () => {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = -1;\n active.current = true;\n latestInArgsRef.current = null;\n latestOutArgsRef.current = null;\n };\n\n const callThrottledCallback = useCallback(\n (...args: Parameters<T>) => {\n latestInArgsRef.current = args;\n latestOutArgsRef.current = args;\n active.current = false;\n try {\n handleCallback(...args);\n } catch (error) {\n active.current = true;\n throw error;\n }\n },\n [handleCallback]\n );\n\n const timerCallback = useCallback(() => {\n if (latestInArgsRef.current && latestInArgsRef.current !== latestOutArgsRef.current) {\n callThrottledCallback(...latestInArgsRef.current);\n\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n active.current = true;\n }\n }, [callThrottledCallback]);\n\n const throttled = useCallback(\n (...args: Parameters<T>) => {\n if (active.current) {\n callThrottledCallback(...args);\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n latestInArgsRef.current = args;\n }\n },\n [callThrottledCallback, timerCallback]\n );\n\n useEffect(() => {\n waitRef.current = wait;\n }, [wait]);\n\n return [throttled, clearTimeout] as const;\n}\n\nexport function useThrottledCallback<T extends (...args: any[]) => any>(callback: T, wait: number) {\n const [throttled, clearTimeout] = useThrottledCallbackWithClearTimeout(callback, wait);\n useEffect(() => clearTimeout, []);\n return throttled;\n}\n"],"mappings":";;;;AAGA,SAAgB,qCACd,UACA,MACA;CACA,MAAM,iBAAiB,eAAe,QAAQ;CAC9C,MAAM,kBAAkB,OAAsB,IAAI;CAClD,MAAM,mBAAmB,OAAsB,IAAI;CACnD,MAAM,SAAS,OAAO,IAAI;CAC1B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,aAAa,OAAe,EAAE;CAEpC,MAAM,qBAAqB;EACzB,OAAO,aAAa,WAAW,OAAO;EACtC,WAAW,UAAU;EACrB,OAAO,UAAU;EACjB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;CAC7B;CAEA,MAAM,wBAAwB,aAC3B,GAAG,SAAwB;EAC1B,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,OAAO,UAAU;EACjB,IAAI;GACF,eAAe,GAAG,IAAI;EACxB,SAAS,OAAO;GACd,OAAO,UAAU;GACjB,MAAM;EACR;CACF,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,gBAAgB,WAAW,gBAAgB,YAAY,iBAAiB,SAAS;GACnF,sBAAsB,GAAG,gBAAgB,OAAO;GAEhD,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,OAAO,UAAU;CAErB,GAAG,CAAC,qBAAqB,CAAC;CAE1B,MAAM,YAAY,aACf,GAAG,SAAwB;EAC1B,IAAI,OAAO,SAAS;GAClB,sBAAsB,GAAG,IAAI;GAC7B,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,gBAAgB,UAAU;CAE9B,GACA,CAAC,uBAAuB,aAAa,CACvC;CAEA,gBAAgB;EACd,QAAQ,UAAU;CACpB,GAAG,CAAC,IAAI,CAAC;CAET,OAAO,CAAC,WAAW,YAAY;AACjC;AAEA,SAAgB,qBAAwD,UAAa,MAAc;CACjG,MAAM,CAAC,WAAW,gBAAgB,qCAAqC,UAAU,IAAI;CACrF,gBAAgB,cAAc,CAAC,CAAC;CAChC,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- export declare const FOCUS_SELECTOR = "a, input, select, textarea, button, object, [tabindex]";
1
+ export declare const FOCUS_SELECTOR = "a, input, select, textarea, button, object, summary, iframe, audio[controls], video[controls], [contenteditable], [tabindex]";
2
2
  export declare function focusable(element: HTMLElement): boolean | "";
3
3
  export declare function tabbable(element: HTMLElement): boolean | "";
4
4
  export declare function findTabbableDescendants(element: HTMLElement): HTMLElement[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantine/hooks",
3
- "version": "9.7.2",
3
+ "version": "9.7.3",
4
4
  "description": "A collection of 50+ hooks for state and UI management",
5
5
  "homepage": "https://mantine.dev",
6
6
  "license": "MIT",