@mantine/hooks 9.7.1 → 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.
- package/cjs/use-debounced-callback/use-debounced-callback.cjs +3 -5
- package/cjs/use-debounced-callback/use-debounced-callback.cjs.map +1 -1
- package/cjs/use-event-listener/use-event-listener.cjs +2 -10
- package/cjs/use-event-listener/use-event-listener.cjs.map +1 -1
- package/cjs/use-focus-trap/scope-tab.cjs +63 -10
- package/cjs/use-focus-trap/scope-tab.cjs.map +1 -1
- package/cjs/use-focus-trap/tabbable.cjs +15 -4
- package/cjs/use-focus-trap/tabbable.cjs.map +1 -1
- package/cjs/use-focus-trap/use-focus-trap.cjs +17 -2
- package/cjs/use-focus-trap/use-focus-trap.cjs.map +1 -1
- package/cjs/use-scroll-into-view/use-scroll-into-view.cjs +1 -0
- package/cjs/use-scroll-into-view/use-scroll-into-view.cjs.map +1 -1
- package/cjs/use-scroller/use-scroller.cjs +9 -1
- package/cjs/use-scroller/use-scroller.cjs.map +1 -1
- package/cjs/use-throttled-callback/use-throttled-callback.cjs +6 -1
- package/cjs/use-throttled-callback/use-throttled-callback.cjs.map +1 -1
- package/esm/use-debounced-callback/use-debounced-callback.mjs +3 -5
- package/esm/use-debounced-callback/use-debounced-callback.mjs.map +1 -1
- package/esm/use-event-listener/use-event-listener.mjs +3 -11
- package/esm/use-event-listener/use-event-listener.mjs.map +1 -1
- package/esm/use-focus-trap/scope-tab.mjs +63 -10
- package/esm/use-focus-trap/scope-tab.mjs.map +1 -1
- package/esm/use-focus-trap/tabbable.mjs +15 -4
- package/esm/use-focus-trap/tabbable.mjs.map +1 -1
- package/esm/use-focus-trap/use-focus-trap.mjs +17 -2
- package/esm/use-focus-trap/use-focus-trap.mjs.map +1 -1
- package/esm/use-scroll-into-view/use-scroll-into-view.mjs +1 -0
- package/esm/use-scroll-into-view/use-scroll-into-view.mjs.map +1 -1
- package/esm/use-scroller/use-scroller.mjs +9 -1
- package/esm/use-scroller/use-scroller.mjs.map +1 -1
- package/esm/use-throttled-callback/use-throttled-callback.mjs +6 -1
- package/esm/use-throttled-callback/use-throttled-callback.mjs.map +1 -1
- package/lib/use-focus-trap/tabbable.d.ts +1 -1
- package/package.json +1 -1
|
@@ -29,7 +29,8 @@ function useDebouncedCallback(callback, options) {
|
|
|
29
29
|
}
|
|
30
30
|
function startMaxWaitTimer() {
|
|
31
31
|
if (maxWait !== void 0 && maxWaitTimerRef.current === 0) maxWaitTimerRef.current = window.setTimeout(() => {
|
|
32
|
-
|
|
32
|
+
maxWaitTimerRef.current = 0;
|
|
33
|
+
if (debounceTimerRef.current !== 0 && currentCallback._hasPendingCallback) {
|
|
33
34
|
const latestArgs = latestArgsRef.current;
|
|
34
35
|
clearTimeoutAndLeadingRef();
|
|
35
36
|
handleCallback(...latestArgs);
|
|
@@ -42,10 +43,7 @@ function useDebouncedCallback(callback, options) {
|
|
|
42
43
|
clearTimeoutAndLeadingRef();
|
|
43
44
|
};
|
|
44
45
|
const flush = () => {
|
|
45
|
-
if (debounceTimerRef.current !== 0)
|
|
46
|
-
clearTimeoutAndLeadingRef();
|
|
47
|
-
handleCallback(...args);
|
|
48
|
-
}
|
|
46
|
+
if (debounceTimerRef.current !== 0) clearTimeoutAndLeadingRef();
|
|
49
47
|
};
|
|
50
48
|
const cancel = () => {
|
|
51
49
|
clearTimeoutAndLeadingRef();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-debounced-callback.cjs","names":["useCallbackRef","useRef","useMemo"],"sources":["../../src/use-debounced-callback/use-debounced-callback.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport interface UseDebouncedCallbackOptions {\n delay: number;\n flushOnUnmount?: boolean;\n leading?: boolean;\n maxWait?: number;\n}\n\nexport type UseDebouncedCallbackReturnValue<T extends (...args: any[]) => any> = ((\n ...args: Parameters<T>\n) => void) & { flush: () => void; cancel: () => void; isPending: () => boolean };\n\nexport function useDebouncedCallback<T extends (...args: any[]) => any>(\n callback: T,\n options: number | UseDebouncedCallbackOptions\n) {\n const { delay, flushOnUnmount, leading, maxWait } =\n typeof options === 'number'\n ? {\n delay: options,\n flushOnUnmount: false,\n leading: false,\n maxWait: undefined as number | undefined,\n }\n : options;\n\n const handleCallback = useCallbackRef(callback);\n const debounceTimerRef = useRef(0);\n const maxWaitTimerRef = useRef(0);\n const latestArgsRef = useRef<Parameters<T> | null>(null);\n\n const lastCallback = useMemo(() => {\n const currentCallback = Object.assign(\n (...args: Parameters<T>) => {\n window.clearTimeout(debounceTimerRef.current);\n latestArgsRef.current = args;\n\n const isFirstCall = currentCallback._isFirstCall;\n currentCallback._isFirstCall = false;\n\n function clearTimeoutAndLeadingRef() {\n window.clearTimeout(debounceTimerRef.current);\n window.clearTimeout(maxWaitTimerRef.current);\n debounceTimerRef.current = 0;\n maxWaitTimerRef.current = 0;\n currentCallback._isFirstCall = true;\n currentCallback._hasPendingCallback = false;\n }\n\n function startMaxWaitTimer() {\n if (maxWait !== undefined && maxWaitTimerRef.current === 0) {\n maxWaitTimerRef.current = window.setTimeout(() => {\n if (debounceTimerRef.current !== 0) {\n const latestArgs = latestArgsRef.current!;\n clearTimeoutAndLeadingRef();\n handleCallback(...latestArgs);\n }\n }, maxWait);\n }\n }\n\n if (leading && isFirstCall) {\n handleCallback(...args);\n\n const resetLeadingState = () => {\n clearTimeoutAndLeadingRef();\n };\n\n const flush = () => {\n if (debounceTimerRef.current !== 0) {\n clearTimeoutAndLeadingRef();\n
|
|
1
|
+
{"version":3,"file":"use-debounced-callback.cjs","names":["useCallbackRef","useRef","useMemo"],"sources":["../../src/use-debounced-callback/use-debounced-callback.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport interface UseDebouncedCallbackOptions {\n delay: number;\n flushOnUnmount?: boolean;\n leading?: boolean;\n maxWait?: number;\n}\n\nexport type UseDebouncedCallbackReturnValue<T extends (...args: any[]) => any> = ((\n ...args: Parameters<T>\n) => void) & { flush: () => void; cancel: () => void; isPending: () => boolean };\n\nexport function useDebouncedCallback<T extends (...args: any[]) => any>(\n callback: T,\n options: number | UseDebouncedCallbackOptions\n) {\n const { delay, flushOnUnmount, leading, maxWait } =\n typeof options === 'number'\n ? {\n delay: options,\n flushOnUnmount: false,\n leading: false,\n maxWait: undefined as number | undefined,\n }\n : options;\n\n const handleCallback = useCallbackRef(callback);\n const debounceTimerRef = useRef(0);\n const maxWaitTimerRef = useRef(0);\n const latestArgsRef = useRef<Parameters<T> | null>(null);\n\n const lastCallback = useMemo(() => {\n const currentCallback = Object.assign(\n (...args: Parameters<T>) => {\n window.clearTimeout(debounceTimerRef.current);\n latestArgsRef.current = args;\n\n const isFirstCall = currentCallback._isFirstCall;\n currentCallback._isFirstCall = false;\n\n function clearTimeoutAndLeadingRef() {\n window.clearTimeout(debounceTimerRef.current);\n window.clearTimeout(maxWaitTimerRef.current);\n debounceTimerRef.current = 0;\n maxWaitTimerRef.current = 0;\n currentCallback._isFirstCall = true;\n currentCallback._hasPendingCallback = false;\n }\n\n function startMaxWaitTimer() {\n if (maxWait !== undefined && maxWaitTimerRef.current === 0) {\n maxWaitTimerRef.current = window.setTimeout(() => {\n maxWaitTimerRef.current = 0;\n if (debounceTimerRef.current !== 0 && currentCallback._hasPendingCallback) {\n const latestArgs = latestArgsRef.current!;\n clearTimeoutAndLeadingRef();\n handleCallback(...latestArgs);\n }\n }, maxWait);\n }\n }\n\n if (leading && isFirstCall) {\n handleCallback(...args);\n\n const resetLeadingState = () => {\n clearTimeoutAndLeadingRef();\n };\n\n const flush = () => {\n if (debounceTimerRef.current !== 0) {\n clearTimeoutAndLeadingRef();\n }\n };\n\n const cancel = () => {\n clearTimeoutAndLeadingRef();\n };\n\n currentCallback.flush = flush;\n currentCallback.cancel = cancel;\n debounceTimerRef.current = window.setTimeout(resetLeadingState, delay);\n startMaxWaitTimer();\n return;\n }\n\n if (leading && !isFirstCall) {\n currentCallback._hasPendingCallback = true;\n const flush = () => {\n if (debounceTimerRef.current !== 0) {\n clearTimeoutAndLeadingRef();\n handleCallback(...args);\n }\n };\n\n const cancel = () => {\n clearTimeoutAndLeadingRef();\n };\n\n currentCallback.flush = flush;\n currentCallback.cancel = cancel;\n\n const resetLeadingState = () => {\n clearTimeoutAndLeadingRef();\n };\n debounceTimerRef.current = window.setTimeout(resetLeadingState, delay);\n startMaxWaitTimer();\n return;\n }\n\n currentCallback._hasPendingCallback = true;\n\n const flush = () => {\n if (debounceTimerRef.current !== 0) {\n clearTimeoutAndLeadingRef();\n handleCallback(...args);\n }\n };\n\n const cancel = () => {\n clearTimeoutAndLeadingRef();\n };\n\n currentCallback.flush = flush;\n currentCallback.cancel = cancel;\n debounceTimerRef.current = window.setTimeout(flush, delay);\n startMaxWaitTimer();\n },\n {\n flush: () => {},\n cancel: () => {},\n isPending: () => currentCallback._hasPendingCallback,\n _isFirstCall: true,\n _hasPendingCallback: false,\n }\n );\n return currentCallback;\n }, [handleCallback, delay, leading, maxWait]);\n\n useEffect(\n () => () => {\n if (flushOnUnmount) {\n lastCallback.flush();\n } else {\n lastCallback.cancel();\n }\n },\n [lastCallback, flushOnUnmount]\n );\n\n return lastCallback;\n}\n\nexport namespace useDebouncedCallback {\n export type Options = UseDebouncedCallbackOptions;\n export type ReturnValue<T extends (...args: any[]) => any> = UseDebouncedCallbackReturnValue<T>;\n}\n"],"mappings":";;;;AAcA,SAAgB,qBACd,UACA,SACA;CACA,MAAM,EAAE,OAAO,gBAAgB,SAAS,YACtC,OAAO,YAAY,WACf;EACE,OAAO;EACP,gBAAgB;EAChB,SAAS;EACT,SAAS,KAAA;CACX,IACA;CAEN,MAAM,iBAAiBA,yBAAAA,eAAe,QAAQ;CAC9C,MAAM,oBAAA,GAAmBC,MAAAA,OAAAA,CAAO,CAAC;CACjC,MAAM,mBAAA,GAAkBA,MAAAA,OAAAA,CAAO,CAAC;CAChC,MAAM,iBAAA,GAAgBA,MAAAA,OAAAA,CAA6B,IAAI;CAEvD,MAAM,gBAAA,GAAeC,MAAAA,QAAAA,OAAc;EACjC,MAAM,kBAAkB,OAAO,QAC5B,GAAG,SAAwB;GAC1B,OAAO,aAAa,iBAAiB,OAAO;GAC5C,cAAc,UAAU;GAExB,MAAM,cAAc,gBAAgB;GACpC,gBAAgB,eAAe;GAE/B,SAAS,4BAA4B;IACnC,OAAO,aAAa,iBAAiB,OAAO;IAC5C,OAAO,aAAa,gBAAgB,OAAO;IAC3C,iBAAiB,UAAU;IAC3B,gBAAgB,UAAU;IAC1B,gBAAgB,eAAe;IAC/B,gBAAgB,sBAAsB;GACxC;GAEA,SAAS,oBAAoB;IAC3B,IAAI,YAAY,KAAA,KAAa,gBAAgB,YAAY,GACvD,gBAAgB,UAAU,OAAO,iBAAiB;KAChD,gBAAgB,UAAU;KAC1B,IAAI,iBAAiB,YAAY,KAAK,gBAAgB,qBAAqB;MACzE,MAAM,aAAa,cAAc;MACjC,0BAA0B;MAC1B,eAAe,GAAG,UAAU;KAC9B;IACF,GAAG,OAAO;GAEd;GAEA,IAAI,WAAW,aAAa;IAC1B,eAAe,GAAG,IAAI;IAEtB,MAAM,0BAA0B;KAC9B,0BAA0B;IAC5B;IAEA,MAAM,cAAc;KAClB,IAAI,iBAAiB,YAAY,GAC/B,0BAA0B;IAE9B;IAEA,MAAM,eAAe;KACnB,0BAA0B;IAC5B;IAEA,gBAAgB,QAAQ;IACxB,gBAAgB,SAAS;IACzB,iBAAiB,UAAU,OAAO,WAAW,mBAAmB,KAAK;IACrE,kBAAkB;IAClB;GACF;GAEA,IAAI,WAAW,CAAC,aAAa;IAC3B,gBAAgB,sBAAsB;IACtC,MAAM,cAAc;KAClB,IAAI,iBAAiB,YAAY,GAAG;MAClC,0BAA0B;MAC1B,eAAe,GAAG,IAAI;KACxB;IACF;IAEA,MAAM,eAAe;KACnB,0BAA0B;IAC5B;IAEA,gBAAgB,QAAQ;IACxB,gBAAgB,SAAS;IAEzB,MAAM,0BAA0B;KAC9B,0BAA0B;IAC5B;IACA,iBAAiB,UAAU,OAAO,WAAW,mBAAmB,KAAK;IACrE,kBAAkB;IAClB;GACF;GAEA,gBAAgB,sBAAsB;GAEtC,MAAM,cAAc;IAClB,IAAI,iBAAiB,YAAY,GAAG;KAClC,0BAA0B;KAC1B,eAAe,GAAG,IAAI;IACxB;GACF;GAEA,MAAM,eAAe;IACnB,0BAA0B;GAC5B;GAEA,gBAAgB,QAAQ;GACxB,gBAAgB,SAAS;GACzB,iBAAiB,UAAU,OAAO,WAAW,OAAO,KAAK;GACzD,kBAAkB;EACpB,GACA;GACE,aAAa,CAAC;GACd,cAAc,CAAC;GACf,iBAAiB,gBAAgB;GACjC,cAAc;GACd,qBAAqB;EACvB,CACF;EACA,OAAO;CACT,GAAG;EAAC;EAAgB;EAAO;EAAS;CAAO,CAAC;CAE5C,CAAA,GAAA,MAAA,UAAA,aACc;EACV,IAAI,gBACF,aAAa,MAAM;OAEnB,aAAa,OAAO;CAExB,GACA,CAAC,cAAc,cAAc,CAC/B;CAEA,OAAO;AACT"}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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":["
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
|
|
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
|
-
|
|
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\
|
|
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
|
-
|
|
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
|
|
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
|
|
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 () =>
|
|
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
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-scroll-into-view.cjs","names":["useRef","useState","useReducedMotion","useCallback"],"sources":["../../src/use-scroll-into-view/use-scroll-into-view.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useReducedMotion } from '../use-reduced-motion/use-reduced-motion';\nimport { useWindowEvent } from '../use-window-event/use-window-event';\n\ninterface UseScrollIntoViewAnimation {\n /** Target element alignment relatively to parent based on current axis */\n alignment?: 'start' | 'end' | 'center';\n}\n\nexport interface UseScrollIntoViewOptions {\n /** Callback fired after scroll */\n onScrollFinish?: () => void;\n\n /** Callback fired when scroll animation is canceled by user interaction */\n onScrollCancel?: () => void;\n\n /** Duration of scroll in milliseconds */\n duration?: number;\n\n /** Axis of scroll */\n axis?: 'x' | 'y';\n\n /** Custom mathematical easing function */\n easing?: (t: number) => number;\n\n /** Additional distance between nearest edge and element */\n offset?: number;\n\n /** Indicator if animation may be interrupted by user scrolling */\n cancelable?: boolean;\n\n /** Prevents content jumping in scrolling lists with multiple targets */\n isList?: boolean;\n}\n\nexport interface UseScrollIntoViewReturnValue<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n> {\n scrollableRef: React.RefObject<Parent | null>;\n targetRef: React.RefObject<Target | null>;\n scrollIntoView: (params?: UseScrollIntoViewAnimation) => void;\n cancel: () => void;\n scrolling: boolean;\n}\n\nexport function useScrollIntoView<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n>({\n duration = 1250,\n axis = 'y',\n onScrollFinish,\n onScrollCancel,\n easing,\n offset = 0,\n cancelable = true,\n isList = false,\n}: UseScrollIntoViewOptions = {}): UseScrollIntoViewReturnValue<Target, Parent> {\n const frameID = useRef(0);\n const startTime = useRef(0);\n const shouldStop = useRef(false);\n const [scrolling, setScrolling] = useState(false);\n\n const scrollableRef = useRef<Parent | null>(null);\n const targetRef = useRef<Target | null>(null);\n\n const reducedMotion = useReducedMotion();\n\n const _easing = easing ?? easeInOutQuad;\n\n const cancel = (): void => {\n if (frameID.current) {\n cancelAnimationFrame(frameID.current);\n frameID.current = 0;\n setScrolling(false);\n }\n };\n\n const scrollIntoView = useCallback(\n ({ alignment = 'start' }: UseScrollIntoViewAnimation = {}) => {\n shouldStop.current = false;\n\n if (frameID.current) {\n cancel();\n }\n\n const start = getScrollStart({ parent: scrollableRef.current, axis }) ?? 0;\n\n const change =\n getRelativePosition({\n parent: scrollableRef.current,\n target: targetRef.current,\n axis,\n alignment,\n offset,\n isList,\n }) - (scrollableRef.current ? 0 : start);\n\n setScrolling(true);\n\n function animateScroll() {\n if (startTime.current === 0) {\n startTime.current = performance.now();\n }\n\n const now = performance.now();\n const elapsed = now - startTime.current;\n\n // Easing timing progress\n const t = reducedMotion || duration === 0 ? 1 : elapsed / duration;\n\n const distance = start + change * _easing(t);\n\n setScrollParam({\n parent: scrollableRef.current,\n axis,\n distance,\n });\n\n if (!shouldStop.current && t < 1) {\n frameID.current = requestAnimationFrame(animateScroll);\n } else {\n if (shouldStop.current) {\n typeof onScrollCancel === 'function' && onScrollCancel();\n } else {\n typeof onScrollFinish === 'function' && onScrollFinish();\n }\n startTime.current = 0;\n frameID.current = 0;\n setScrolling(false);\n cancel();\n }\n }\n animateScroll();\n },\n [axis, duration, _easing, isList, offset, onScrollFinish, onScrollCancel, reducedMotion]\n );\n\n const handleStop = () => {\n if (cancelable) {\n shouldStop.current = true;\n }\n };\n\n /**\n * Detection of one of these events stops scroll animation\n * wheel - mouse wheel / touch pad\n * touchmove - any touchable device\n */\n\n useWindowEvent('wheel', handleStop, {\n passive: true,\n });\n\n useWindowEvent('touchmove', handleStop, {\n passive: true,\n });\n\n // Cleanup requestAnimationFrame\n useEffect(() => cancel, []);\n\n return {\n scrollableRef,\n targetRef,\n scrollIntoView,\n cancel,\n scrolling,\n };\n}\n\n// ---------------------------------------------------\n// Helpers\n// ---------------------------------------------------\n\nfunction easeInOutQuad(t: number) {\n return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n}\n\nfunction getRelativePosition({ axis, target, parent, alignment, offset, isList }: any): number {\n if (!target || (!parent && typeof document === 'undefined')) {\n return 0;\n }\n const isCustomParent = !!parent;\n const parentElement = parent || document.body;\n const parentPosition = parentElement.getBoundingClientRect();\n const targetPosition = target.getBoundingClientRect();\n\n const getDiff = (property: 'top' | 'left'): number =>\n targetPosition[property] - parentPosition[property];\n\n if (axis === 'y') {\n const diff = getDiff('top');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentHeight = isCustomParent ? parentPosition.height : window.innerHeight;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentHeight + targetPosition.height;\n const shouldScroll = distance >= -targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentHeight / 2 + targetPosition.height / 2;\n }\n\n return 0;\n }\n\n if (axis === 'x') {\n const diff = getDiff('left');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentWidth = isCustomParent ? parentPosition.width : window.innerWidth;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentWidth + targetPosition.width;\n const shouldScroll = distance >= -targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentWidth / 2 + targetPosition.width / 2;\n }\n\n return 0;\n }\n\n return 0;\n}\n\nfunction getScrollStart({ axis, parent }: any) {\n if (!parent && typeof document === 'undefined') {\n return 0;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n return parent[method];\n }\n\n const { body, documentElement } = document;\n\n // While one of it has a value the second is equal 0\n return body[method] + documentElement[method];\n}\n\nfunction setScrollParam({ axis, parent, distance }: any) {\n if (!parent && typeof document === 'undefined') {\n return;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n parent[method] = distance;\n } else {\n const { body, documentElement } = document;\n body[method] = distance;\n documentElement[method] = distance;\n }\n}\n\nexport namespace useScrollIntoView {\n export type Options = UseScrollIntoViewOptions;\n export type ReturnValue<\n Target extends HTMLElement,\n Parent extends HTMLElement | null,\n > = UseScrollIntoViewReturnValue<Target, Parent>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-scroll-into-view.cjs","names":["useRef","useState","useReducedMotion","useCallback"],"sources":["../../src/use-scroll-into-view/use-scroll-into-view.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useReducedMotion } from '../use-reduced-motion/use-reduced-motion';\nimport { useWindowEvent } from '../use-window-event/use-window-event';\n\ninterface UseScrollIntoViewAnimation {\n /** Target element alignment relatively to parent based on current axis */\n alignment?: 'start' | 'end' | 'center';\n}\n\nexport interface UseScrollIntoViewOptions {\n /** Callback fired after scroll */\n onScrollFinish?: () => void;\n\n /** Callback fired when scroll animation is canceled by user interaction */\n onScrollCancel?: () => void;\n\n /** Duration of scroll in milliseconds */\n duration?: number;\n\n /** Axis of scroll */\n axis?: 'x' | 'y';\n\n /** Custom mathematical easing function */\n easing?: (t: number) => number;\n\n /** Additional distance between nearest edge and element */\n offset?: number;\n\n /** Indicator if animation may be interrupted by user scrolling */\n cancelable?: boolean;\n\n /** Prevents content jumping in scrolling lists with multiple targets */\n isList?: boolean;\n}\n\nexport interface UseScrollIntoViewReturnValue<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n> {\n scrollableRef: React.RefObject<Parent | null>;\n targetRef: React.RefObject<Target | null>;\n scrollIntoView: (params?: UseScrollIntoViewAnimation) => void;\n cancel: () => void;\n scrolling: boolean;\n}\n\nexport function useScrollIntoView<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n>({\n duration = 1250,\n axis = 'y',\n onScrollFinish,\n onScrollCancel,\n easing,\n offset = 0,\n cancelable = true,\n isList = false,\n}: UseScrollIntoViewOptions = {}): UseScrollIntoViewReturnValue<Target, Parent> {\n const frameID = useRef(0);\n const startTime = useRef(0);\n const shouldStop = useRef(false);\n const [scrolling, setScrolling] = useState(false);\n\n const scrollableRef = useRef<Parent | null>(null);\n const targetRef = useRef<Target | null>(null);\n\n const reducedMotion = useReducedMotion();\n\n const _easing = easing ?? easeInOutQuad;\n\n const cancel = (): void => {\n if (frameID.current) {\n cancelAnimationFrame(frameID.current);\n frameID.current = 0;\n startTime.current = 0;\n setScrolling(false);\n }\n };\n\n const scrollIntoView = useCallback(\n ({ alignment = 'start' }: UseScrollIntoViewAnimation = {}) => {\n shouldStop.current = false;\n\n if (frameID.current) {\n cancel();\n }\n\n const start = getScrollStart({ parent: scrollableRef.current, axis }) ?? 0;\n\n const change =\n getRelativePosition({\n parent: scrollableRef.current,\n target: targetRef.current,\n axis,\n alignment,\n offset,\n isList,\n }) - (scrollableRef.current ? 0 : start);\n\n setScrolling(true);\n\n function animateScroll() {\n if (startTime.current === 0) {\n startTime.current = performance.now();\n }\n\n const now = performance.now();\n const elapsed = now - startTime.current;\n\n // Easing timing progress\n const t = reducedMotion || duration === 0 ? 1 : elapsed / duration;\n\n const distance = start + change * _easing(t);\n\n setScrollParam({\n parent: scrollableRef.current,\n axis,\n distance,\n });\n\n if (!shouldStop.current && t < 1) {\n frameID.current = requestAnimationFrame(animateScroll);\n } else {\n if (shouldStop.current) {\n typeof onScrollCancel === 'function' && onScrollCancel();\n } else {\n typeof onScrollFinish === 'function' && onScrollFinish();\n }\n startTime.current = 0;\n frameID.current = 0;\n setScrolling(false);\n cancel();\n }\n }\n animateScroll();\n },\n [axis, duration, _easing, isList, offset, onScrollFinish, onScrollCancel, reducedMotion]\n );\n\n const handleStop = () => {\n if (cancelable) {\n shouldStop.current = true;\n }\n };\n\n /**\n * Detection of one of these events stops scroll animation\n * wheel - mouse wheel / touch pad\n * touchmove - any touchable device\n */\n\n useWindowEvent('wheel', handleStop, {\n passive: true,\n });\n\n useWindowEvent('touchmove', handleStop, {\n passive: true,\n });\n\n // Cleanup requestAnimationFrame\n useEffect(() => cancel, []);\n\n return {\n scrollableRef,\n targetRef,\n scrollIntoView,\n cancel,\n scrolling,\n };\n}\n\n// ---------------------------------------------------\n// Helpers\n// ---------------------------------------------------\n\nfunction easeInOutQuad(t: number) {\n return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n}\n\nfunction getRelativePosition({ axis, target, parent, alignment, offset, isList }: any): number {\n if (!target || (!parent && typeof document === 'undefined')) {\n return 0;\n }\n const isCustomParent = !!parent;\n const parentElement = parent || document.body;\n const parentPosition = parentElement.getBoundingClientRect();\n const targetPosition = target.getBoundingClientRect();\n\n const getDiff = (property: 'top' | 'left'): number =>\n targetPosition[property] - parentPosition[property];\n\n if (axis === 'y') {\n const diff = getDiff('top');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentHeight = isCustomParent ? parentPosition.height : window.innerHeight;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentHeight + targetPosition.height;\n const shouldScroll = distance >= -targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentHeight / 2 + targetPosition.height / 2;\n }\n\n return 0;\n }\n\n if (axis === 'x') {\n const diff = getDiff('left');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentWidth = isCustomParent ? parentPosition.width : window.innerWidth;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentWidth + targetPosition.width;\n const shouldScroll = distance >= -targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentWidth / 2 + targetPosition.width / 2;\n }\n\n return 0;\n }\n\n return 0;\n}\n\nfunction getScrollStart({ axis, parent }: any) {\n if (!parent && typeof document === 'undefined') {\n return 0;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n return parent[method];\n }\n\n const { body, documentElement } = document;\n\n // While one of it has a value the second is equal 0\n return body[method] + documentElement[method];\n}\n\nfunction setScrollParam({ axis, parent, distance }: any) {\n if (!parent && typeof document === 'undefined') {\n return;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n parent[method] = distance;\n } else {\n const { body, documentElement } = document;\n body[method] = distance;\n documentElement[method] = distance;\n }\n}\n\nexport namespace useScrollIntoView {\n export type Options = UseScrollIntoViewOptions;\n export type ReturnValue<\n Target extends HTMLElement,\n Parent extends HTMLElement | null,\n > = UseScrollIntoViewReturnValue<Target, Parent>;\n}\n"],"mappings":"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"}
|
|
@@ -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
|
-
|
|
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
|
|
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"}
|
|
@@ -29,7 +29,8 @@ function useDebouncedCallback(callback, options) {
|
|
|
29
29
|
}
|
|
30
30
|
function startMaxWaitTimer() {
|
|
31
31
|
if (maxWait !== void 0 && maxWaitTimerRef.current === 0) maxWaitTimerRef.current = window.setTimeout(() => {
|
|
32
|
-
|
|
32
|
+
maxWaitTimerRef.current = 0;
|
|
33
|
+
if (debounceTimerRef.current !== 0 && currentCallback._hasPendingCallback) {
|
|
33
34
|
const latestArgs = latestArgsRef.current;
|
|
34
35
|
clearTimeoutAndLeadingRef();
|
|
35
36
|
handleCallback(...latestArgs);
|
|
@@ -42,10 +43,7 @@ function useDebouncedCallback(callback, options) {
|
|
|
42
43
|
clearTimeoutAndLeadingRef();
|
|
43
44
|
};
|
|
44
45
|
const flush = () => {
|
|
45
|
-
if (debounceTimerRef.current !== 0)
|
|
46
|
-
clearTimeoutAndLeadingRef();
|
|
47
|
-
handleCallback(...args);
|
|
48
|
-
}
|
|
46
|
+
if (debounceTimerRef.current !== 0) clearTimeoutAndLeadingRef();
|
|
49
47
|
};
|
|
50
48
|
const cancel = () => {
|
|
51
49
|
clearTimeoutAndLeadingRef();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-debounced-callback.mjs","names":[],"sources":["../../src/use-debounced-callback/use-debounced-callback.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport interface UseDebouncedCallbackOptions {\n delay: number;\n flushOnUnmount?: boolean;\n leading?: boolean;\n maxWait?: number;\n}\n\nexport type UseDebouncedCallbackReturnValue<T extends (...args: any[]) => any> = ((\n ...args: Parameters<T>\n) => void) & { flush: () => void; cancel: () => void; isPending: () => boolean };\n\nexport function useDebouncedCallback<T extends (...args: any[]) => any>(\n callback: T,\n options: number | UseDebouncedCallbackOptions\n) {\n const { delay, flushOnUnmount, leading, maxWait } =\n typeof options === 'number'\n ? {\n delay: options,\n flushOnUnmount: false,\n leading: false,\n maxWait: undefined as number | undefined,\n }\n : options;\n\n const handleCallback = useCallbackRef(callback);\n const debounceTimerRef = useRef(0);\n const maxWaitTimerRef = useRef(0);\n const latestArgsRef = useRef<Parameters<T> | null>(null);\n\n const lastCallback = useMemo(() => {\n const currentCallback = Object.assign(\n (...args: Parameters<T>) => {\n window.clearTimeout(debounceTimerRef.current);\n latestArgsRef.current = args;\n\n const isFirstCall = currentCallback._isFirstCall;\n currentCallback._isFirstCall = false;\n\n function clearTimeoutAndLeadingRef() {\n window.clearTimeout(debounceTimerRef.current);\n window.clearTimeout(maxWaitTimerRef.current);\n debounceTimerRef.current = 0;\n maxWaitTimerRef.current = 0;\n currentCallback._isFirstCall = true;\n currentCallback._hasPendingCallback = false;\n }\n\n function startMaxWaitTimer() {\n if (maxWait !== undefined && maxWaitTimerRef.current === 0) {\n maxWaitTimerRef.current = window.setTimeout(() => {\n if (debounceTimerRef.current !== 0) {\n const latestArgs = latestArgsRef.current!;\n clearTimeoutAndLeadingRef();\n handleCallback(...latestArgs);\n }\n }, maxWait);\n }\n }\n\n if (leading && isFirstCall) {\n handleCallback(...args);\n\n const resetLeadingState = () => {\n clearTimeoutAndLeadingRef();\n };\n\n const flush = () => {\n if (debounceTimerRef.current !== 0) {\n clearTimeoutAndLeadingRef();\n
|
|
1
|
+
{"version":3,"file":"use-debounced-callback.mjs","names":[],"sources":["../../src/use-debounced-callback/use-debounced-callback.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport interface UseDebouncedCallbackOptions {\n delay: number;\n flushOnUnmount?: boolean;\n leading?: boolean;\n maxWait?: number;\n}\n\nexport type UseDebouncedCallbackReturnValue<T extends (...args: any[]) => any> = ((\n ...args: Parameters<T>\n) => void) & { flush: () => void; cancel: () => void; isPending: () => boolean };\n\nexport function useDebouncedCallback<T extends (...args: any[]) => any>(\n callback: T,\n options: number | UseDebouncedCallbackOptions\n) {\n const { delay, flushOnUnmount, leading, maxWait } =\n typeof options === 'number'\n ? {\n delay: options,\n flushOnUnmount: false,\n leading: false,\n maxWait: undefined as number | undefined,\n }\n : options;\n\n const handleCallback = useCallbackRef(callback);\n const debounceTimerRef = useRef(0);\n const maxWaitTimerRef = useRef(0);\n const latestArgsRef = useRef<Parameters<T> | null>(null);\n\n const lastCallback = useMemo(() => {\n const currentCallback = Object.assign(\n (...args: Parameters<T>) => {\n window.clearTimeout(debounceTimerRef.current);\n latestArgsRef.current = args;\n\n const isFirstCall = currentCallback._isFirstCall;\n currentCallback._isFirstCall = false;\n\n function clearTimeoutAndLeadingRef() {\n window.clearTimeout(debounceTimerRef.current);\n window.clearTimeout(maxWaitTimerRef.current);\n debounceTimerRef.current = 0;\n maxWaitTimerRef.current = 0;\n currentCallback._isFirstCall = true;\n currentCallback._hasPendingCallback = false;\n }\n\n function startMaxWaitTimer() {\n if (maxWait !== undefined && maxWaitTimerRef.current === 0) {\n maxWaitTimerRef.current = window.setTimeout(() => {\n maxWaitTimerRef.current = 0;\n if (debounceTimerRef.current !== 0 && currentCallback._hasPendingCallback) {\n const latestArgs = latestArgsRef.current!;\n clearTimeoutAndLeadingRef();\n handleCallback(...latestArgs);\n }\n }, maxWait);\n }\n }\n\n if (leading && isFirstCall) {\n handleCallback(...args);\n\n const resetLeadingState = () => {\n clearTimeoutAndLeadingRef();\n };\n\n const flush = () => {\n if (debounceTimerRef.current !== 0) {\n clearTimeoutAndLeadingRef();\n }\n };\n\n const cancel = () => {\n clearTimeoutAndLeadingRef();\n };\n\n currentCallback.flush = flush;\n currentCallback.cancel = cancel;\n debounceTimerRef.current = window.setTimeout(resetLeadingState, delay);\n startMaxWaitTimer();\n return;\n }\n\n if (leading && !isFirstCall) {\n currentCallback._hasPendingCallback = true;\n const flush = () => {\n if (debounceTimerRef.current !== 0) {\n clearTimeoutAndLeadingRef();\n handleCallback(...args);\n }\n };\n\n const cancel = () => {\n clearTimeoutAndLeadingRef();\n };\n\n currentCallback.flush = flush;\n currentCallback.cancel = cancel;\n\n const resetLeadingState = () => {\n clearTimeoutAndLeadingRef();\n };\n debounceTimerRef.current = window.setTimeout(resetLeadingState, delay);\n startMaxWaitTimer();\n return;\n }\n\n currentCallback._hasPendingCallback = true;\n\n const flush = () => {\n if (debounceTimerRef.current !== 0) {\n clearTimeoutAndLeadingRef();\n handleCallback(...args);\n }\n };\n\n const cancel = () => {\n clearTimeoutAndLeadingRef();\n };\n\n currentCallback.flush = flush;\n currentCallback.cancel = cancel;\n debounceTimerRef.current = window.setTimeout(flush, delay);\n startMaxWaitTimer();\n },\n {\n flush: () => {},\n cancel: () => {},\n isPending: () => currentCallback._hasPendingCallback,\n _isFirstCall: true,\n _hasPendingCallback: false,\n }\n );\n return currentCallback;\n }, [handleCallback, delay, leading, maxWait]);\n\n useEffect(\n () => () => {\n if (flushOnUnmount) {\n lastCallback.flush();\n } else {\n lastCallback.cancel();\n }\n },\n [lastCallback, flushOnUnmount]\n );\n\n return lastCallback;\n}\n\nexport namespace useDebouncedCallback {\n export type Options = UseDebouncedCallbackOptions;\n export type ReturnValue<T extends (...args: any[]) => any> = UseDebouncedCallbackReturnValue<T>;\n}\n"],"mappings":";;;;AAcA,SAAgB,qBACd,UACA,SACA;CACA,MAAM,EAAE,OAAO,gBAAgB,SAAS,YACtC,OAAO,YAAY,WACf;EACE,OAAO;EACP,gBAAgB;EAChB,SAAS;EACT,SAAS,KAAA;CACX,IACA;CAEN,MAAM,iBAAiB,eAAe,QAAQ;CAC9C,MAAM,mBAAmB,OAAO,CAAC;CACjC,MAAM,kBAAkB,OAAO,CAAC;CAChC,MAAM,gBAAgB,OAA6B,IAAI;CAEvD,MAAM,eAAe,cAAc;EACjC,MAAM,kBAAkB,OAAO,QAC5B,GAAG,SAAwB;GAC1B,OAAO,aAAa,iBAAiB,OAAO;GAC5C,cAAc,UAAU;GAExB,MAAM,cAAc,gBAAgB;GACpC,gBAAgB,eAAe;GAE/B,SAAS,4BAA4B;IACnC,OAAO,aAAa,iBAAiB,OAAO;IAC5C,OAAO,aAAa,gBAAgB,OAAO;IAC3C,iBAAiB,UAAU;IAC3B,gBAAgB,UAAU;IAC1B,gBAAgB,eAAe;IAC/B,gBAAgB,sBAAsB;GACxC;GAEA,SAAS,oBAAoB;IAC3B,IAAI,YAAY,KAAA,KAAa,gBAAgB,YAAY,GACvD,gBAAgB,UAAU,OAAO,iBAAiB;KAChD,gBAAgB,UAAU;KAC1B,IAAI,iBAAiB,YAAY,KAAK,gBAAgB,qBAAqB;MACzE,MAAM,aAAa,cAAc;MACjC,0BAA0B;MAC1B,eAAe,GAAG,UAAU;KAC9B;IACF,GAAG,OAAO;GAEd;GAEA,IAAI,WAAW,aAAa;IAC1B,eAAe,GAAG,IAAI;IAEtB,MAAM,0BAA0B;KAC9B,0BAA0B;IAC5B;IAEA,MAAM,cAAc;KAClB,IAAI,iBAAiB,YAAY,GAC/B,0BAA0B;IAE9B;IAEA,MAAM,eAAe;KACnB,0BAA0B;IAC5B;IAEA,gBAAgB,QAAQ;IACxB,gBAAgB,SAAS;IACzB,iBAAiB,UAAU,OAAO,WAAW,mBAAmB,KAAK;IACrE,kBAAkB;IAClB;GACF;GAEA,IAAI,WAAW,CAAC,aAAa;IAC3B,gBAAgB,sBAAsB;IACtC,MAAM,cAAc;KAClB,IAAI,iBAAiB,YAAY,GAAG;MAClC,0BAA0B;MAC1B,eAAe,GAAG,IAAI;KACxB;IACF;IAEA,MAAM,eAAe;KACnB,0BAA0B;IAC5B;IAEA,gBAAgB,QAAQ;IACxB,gBAAgB,SAAS;IAEzB,MAAM,0BAA0B;KAC9B,0BAA0B;IAC5B;IACA,iBAAiB,UAAU,OAAO,WAAW,mBAAmB,KAAK;IACrE,kBAAkB;IAClB;GACF;GAEA,gBAAgB,sBAAsB;GAEtC,MAAM,cAAc;IAClB,IAAI,iBAAiB,YAAY,GAAG;KAClC,0BAA0B;KAC1B,eAAe,GAAG,IAAI;IACxB;GACF;GAEA,MAAM,eAAe;IACnB,0BAA0B;GAC5B;GAEA,gBAAgB,QAAQ;GACxB,gBAAgB,SAAS;GACzB,iBAAiB,UAAU,OAAO,WAAW,OAAO,KAAK;GACzD,kBAAkB;EACpB,GACA;GACE,aAAa,CAAC;GACd,cAAc,CAAC;GACf,iBAAiB,gBAAgB;GACjC,cAAc;GACd,qBAAqB;EACvB,CACF;EACA,OAAO;CACT,GAAG;EAAC;EAAgB;EAAO;EAAS;CAAO,CAAC;CAE5C,sBACc;EACV,IAAI,gBACF,aAAa,MAAM;OAEnB,aAAa,OAAO;CAExB,GACA,CAAC,cAAc,cAAc,CAC/B;CAEA,OAAO;AACT"}
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useCallback
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
|
|
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
|
-
|
|
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\
|
|
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
|
-
|
|
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
|
|
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
|
|
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 () =>
|
|
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
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-scroll-into-view.mjs","names":[],"sources":["../../src/use-scroll-into-view/use-scroll-into-view.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useReducedMotion } from '../use-reduced-motion/use-reduced-motion';\nimport { useWindowEvent } from '../use-window-event/use-window-event';\n\ninterface UseScrollIntoViewAnimation {\n /** Target element alignment relatively to parent based on current axis */\n alignment?: 'start' | 'end' | 'center';\n}\n\nexport interface UseScrollIntoViewOptions {\n /** Callback fired after scroll */\n onScrollFinish?: () => void;\n\n /** Callback fired when scroll animation is canceled by user interaction */\n onScrollCancel?: () => void;\n\n /** Duration of scroll in milliseconds */\n duration?: number;\n\n /** Axis of scroll */\n axis?: 'x' | 'y';\n\n /** Custom mathematical easing function */\n easing?: (t: number) => number;\n\n /** Additional distance between nearest edge and element */\n offset?: number;\n\n /** Indicator if animation may be interrupted by user scrolling */\n cancelable?: boolean;\n\n /** Prevents content jumping in scrolling lists with multiple targets */\n isList?: boolean;\n}\n\nexport interface UseScrollIntoViewReturnValue<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n> {\n scrollableRef: React.RefObject<Parent | null>;\n targetRef: React.RefObject<Target | null>;\n scrollIntoView: (params?: UseScrollIntoViewAnimation) => void;\n cancel: () => void;\n scrolling: boolean;\n}\n\nexport function useScrollIntoView<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n>({\n duration = 1250,\n axis = 'y',\n onScrollFinish,\n onScrollCancel,\n easing,\n offset = 0,\n cancelable = true,\n isList = false,\n}: UseScrollIntoViewOptions = {}): UseScrollIntoViewReturnValue<Target, Parent> {\n const frameID = useRef(0);\n const startTime = useRef(0);\n const shouldStop = useRef(false);\n const [scrolling, setScrolling] = useState(false);\n\n const scrollableRef = useRef<Parent | null>(null);\n const targetRef = useRef<Target | null>(null);\n\n const reducedMotion = useReducedMotion();\n\n const _easing = easing ?? easeInOutQuad;\n\n const cancel = (): void => {\n if (frameID.current) {\n cancelAnimationFrame(frameID.current);\n frameID.current = 0;\n setScrolling(false);\n }\n };\n\n const scrollIntoView = useCallback(\n ({ alignment = 'start' }: UseScrollIntoViewAnimation = {}) => {\n shouldStop.current = false;\n\n if (frameID.current) {\n cancel();\n }\n\n const start = getScrollStart({ parent: scrollableRef.current, axis }) ?? 0;\n\n const change =\n getRelativePosition({\n parent: scrollableRef.current,\n target: targetRef.current,\n axis,\n alignment,\n offset,\n isList,\n }) - (scrollableRef.current ? 0 : start);\n\n setScrolling(true);\n\n function animateScroll() {\n if (startTime.current === 0) {\n startTime.current = performance.now();\n }\n\n const now = performance.now();\n const elapsed = now - startTime.current;\n\n // Easing timing progress\n const t = reducedMotion || duration === 0 ? 1 : elapsed / duration;\n\n const distance = start + change * _easing(t);\n\n setScrollParam({\n parent: scrollableRef.current,\n axis,\n distance,\n });\n\n if (!shouldStop.current && t < 1) {\n frameID.current = requestAnimationFrame(animateScroll);\n } else {\n if (shouldStop.current) {\n typeof onScrollCancel === 'function' && onScrollCancel();\n } else {\n typeof onScrollFinish === 'function' && onScrollFinish();\n }\n startTime.current = 0;\n frameID.current = 0;\n setScrolling(false);\n cancel();\n }\n }\n animateScroll();\n },\n [axis, duration, _easing, isList, offset, onScrollFinish, onScrollCancel, reducedMotion]\n );\n\n const handleStop = () => {\n if (cancelable) {\n shouldStop.current = true;\n }\n };\n\n /**\n * Detection of one of these events stops scroll animation\n * wheel - mouse wheel / touch pad\n * touchmove - any touchable device\n */\n\n useWindowEvent('wheel', handleStop, {\n passive: true,\n });\n\n useWindowEvent('touchmove', handleStop, {\n passive: true,\n });\n\n // Cleanup requestAnimationFrame\n useEffect(() => cancel, []);\n\n return {\n scrollableRef,\n targetRef,\n scrollIntoView,\n cancel,\n scrolling,\n };\n}\n\n// ---------------------------------------------------\n// Helpers\n// ---------------------------------------------------\n\nfunction easeInOutQuad(t: number) {\n return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n}\n\nfunction getRelativePosition({ axis, target, parent, alignment, offset, isList }: any): number {\n if (!target || (!parent && typeof document === 'undefined')) {\n return 0;\n }\n const isCustomParent = !!parent;\n const parentElement = parent || document.body;\n const parentPosition = parentElement.getBoundingClientRect();\n const targetPosition = target.getBoundingClientRect();\n\n const getDiff = (property: 'top' | 'left'): number =>\n targetPosition[property] - parentPosition[property];\n\n if (axis === 'y') {\n const diff = getDiff('top');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentHeight = isCustomParent ? parentPosition.height : window.innerHeight;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentHeight + targetPosition.height;\n const shouldScroll = distance >= -targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentHeight / 2 + targetPosition.height / 2;\n }\n\n return 0;\n }\n\n if (axis === 'x') {\n const diff = getDiff('left');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentWidth = isCustomParent ? parentPosition.width : window.innerWidth;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentWidth + targetPosition.width;\n const shouldScroll = distance >= -targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentWidth / 2 + targetPosition.width / 2;\n }\n\n return 0;\n }\n\n return 0;\n}\n\nfunction getScrollStart({ axis, parent }: any) {\n if (!parent && typeof document === 'undefined') {\n return 0;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n return parent[method];\n }\n\n const { body, documentElement } = document;\n\n // While one of it has a value the second is equal 0\n return body[method] + documentElement[method];\n}\n\nfunction setScrollParam({ axis, parent, distance }: any) {\n if (!parent && typeof document === 'undefined') {\n return;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n parent[method] = distance;\n } else {\n const { body, documentElement } = document;\n body[method] = distance;\n documentElement[method] = distance;\n }\n}\n\nexport namespace useScrollIntoView {\n export type Options = UseScrollIntoViewOptions;\n export type ReturnValue<\n Target extends HTMLElement,\n Parent extends HTMLElement | null,\n > = UseScrollIntoViewReturnValue<Target, Parent>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-scroll-into-view.mjs","names":[],"sources":["../../src/use-scroll-into-view/use-scroll-into-view.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useReducedMotion } from '../use-reduced-motion/use-reduced-motion';\nimport { useWindowEvent } from '../use-window-event/use-window-event';\n\ninterface UseScrollIntoViewAnimation {\n /** Target element alignment relatively to parent based on current axis */\n alignment?: 'start' | 'end' | 'center';\n}\n\nexport interface UseScrollIntoViewOptions {\n /** Callback fired after scroll */\n onScrollFinish?: () => void;\n\n /** Callback fired when scroll animation is canceled by user interaction */\n onScrollCancel?: () => void;\n\n /** Duration of scroll in milliseconds */\n duration?: number;\n\n /** Axis of scroll */\n axis?: 'x' | 'y';\n\n /** Custom mathematical easing function */\n easing?: (t: number) => number;\n\n /** Additional distance between nearest edge and element */\n offset?: number;\n\n /** Indicator if animation may be interrupted by user scrolling */\n cancelable?: boolean;\n\n /** Prevents content jumping in scrolling lists with multiple targets */\n isList?: boolean;\n}\n\nexport interface UseScrollIntoViewReturnValue<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n> {\n scrollableRef: React.RefObject<Parent | null>;\n targetRef: React.RefObject<Target | null>;\n scrollIntoView: (params?: UseScrollIntoViewAnimation) => void;\n cancel: () => void;\n scrolling: boolean;\n}\n\nexport function useScrollIntoView<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n>({\n duration = 1250,\n axis = 'y',\n onScrollFinish,\n onScrollCancel,\n easing,\n offset = 0,\n cancelable = true,\n isList = false,\n}: UseScrollIntoViewOptions = {}): UseScrollIntoViewReturnValue<Target, Parent> {\n const frameID = useRef(0);\n const startTime = useRef(0);\n const shouldStop = useRef(false);\n const [scrolling, setScrolling] = useState(false);\n\n const scrollableRef = useRef<Parent | null>(null);\n const targetRef = useRef<Target | null>(null);\n\n const reducedMotion = useReducedMotion();\n\n const _easing = easing ?? easeInOutQuad;\n\n const cancel = (): void => {\n if (frameID.current) {\n cancelAnimationFrame(frameID.current);\n frameID.current = 0;\n startTime.current = 0;\n setScrolling(false);\n }\n };\n\n const scrollIntoView = useCallback(\n ({ alignment = 'start' }: UseScrollIntoViewAnimation = {}) => {\n shouldStop.current = false;\n\n if (frameID.current) {\n cancel();\n }\n\n const start = getScrollStart({ parent: scrollableRef.current, axis }) ?? 0;\n\n const change =\n getRelativePosition({\n parent: scrollableRef.current,\n target: targetRef.current,\n axis,\n alignment,\n offset,\n isList,\n }) - (scrollableRef.current ? 0 : start);\n\n setScrolling(true);\n\n function animateScroll() {\n if (startTime.current === 0) {\n startTime.current = performance.now();\n }\n\n const now = performance.now();\n const elapsed = now - startTime.current;\n\n // Easing timing progress\n const t = reducedMotion || duration === 0 ? 1 : elapsed / duration;\n\n const distance = start + change * _easing(t);\n\n setScrollParam({\n parent: scrollableRef.current,\n axis,\n distance,\n });\n\n if (!shouldStop.current && t < 1) {\n frameID.current = requestAnimationFrame(animateScroll);\n } else {\n if (shouldStop.current) {\n typeof onScrollCancel === 'function' && onScrollCancel();\n } else {\n typeof onScrollFinish === 'function' && onScrollFinish();\n }\n startTime.current = 0;\n frameID.current = 0;\n setScrolling(false);\n cancel();\n }\n }\n animateScroll();\n },\n [axis, duration, _easing, isList, offset, onScrollFinish, onScrollCancel, reducedMotion]\n );\n\n const handleStop = () => {\n if (cancelable) {\n shouldStop.current = true;\n }\n };\n\n /**\n * Detection of one of these events stops scroll animation\n * wheel - mouse wheel / touch pad\n * touchmove - any touchable device\n */\n\n useWindowEvent('wheel', handleStop, {\n passive: true,\n });\n\n useWindowEvent('touchmove', handleStop, {\n passive: true,\n });\n\n // Cleanup requestAnimationFrame\n useEffect(() => cancel, []);\n\n return {\n scrollableRef,\n targetRef,\n scrollIntoView,\n cancel,\n scrolling,\n };\n}\n\n// ---------------------------------------------------\n// Helpers\n// ---------------------------------------------------\n\nfunction easeInOutQuad(t: number) {\n return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n}\n\nfunction getRelativePosition({ axis, target, parent, alignment, offset, isList }: any): number {\n if (!target || (!parent && typeof document === 'undefined')) {\n return 0;\n }\n const isCustomParent = !!parent;\n const parentElement = parent || document.body;\n const parentPosition = parentElement.getBoundingClientRect();\n const targetPosition = target.getBoundingClientRect();\n\n const getDiff = (property: 'top' | 'left'): number =>\n targetPosition[property] - parentPosition[property];\n\n if (axis === 'y') {\n const diff = getDiff('top');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentHeight = isCustomParent ? parentPosition.height : window.innerHeight;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentHeight + targetPosition.height;\n const shouldScroll = distance >= -targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentHeight / 2 + targetPosition.height / 2;\n }\n\n return 0;\n }\n\n if (axis === 'x') {\n const diff = getDiff('left');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentWidth = isCustomParent ? parentPosition.width : window.innerWidth;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentWidth + targetPosition.width;\n const shouldScroll = distance >= -targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentWidth / 2 + targetPosition.width / 2;\n }\n\n return 0;\n }\n\n return 0;\n}\n\nfunction getScrollStart({ axis, parent }: any) {\n if (!parent && typeof document === 'undefined') {\n return 0;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n return parent[method];\n }\n\n const { body, documentElement } = document;\n\n // While one of it has a value the second is equal 0\n return body[method] + documentElement[method];\n}\n\nfunction setScrollParam({ axis, parent, distance }: any) {\n if (!parent && typeof document === 'undefined') {\n return;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n parent[method] = distance;\n } else {\n const { body, documentElement } = document;\n body[method] = distance;\n documentElement[method] = distance;\n }\n}\n\nexport namespace useScrollIntoView {\n export type Options = UseScrollIntoViewOptions;\n export type ReturnValue<\n Target extends HTMLElement,\n Parent extends HTMLElement | null,\n > = UseScrollIntoViewReturnValue<Target, Parent>;\n}\n"],"mappings":"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"}
|
|
@@ -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
|
-
|
|
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
|
|
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[];
|