@mantine/hooks 9.6.1 → 9.6.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.
Files changed (38) hide show
  1. package/cjs/use-click-outside/use-click-outside.cjs +8 -4
  2. package/cjs/use-click-outside/use-click-outside.cjs.map +1 -1
  3. package/cjs/use-hotkeys/parse-hotkey.cjs +4 -6
  4. package/cjs/use-hotkeys/parse-hotkey.cjs.map +1 -1
  5. package/cjs/use-hotkeys/use-hotkeys.cjs +4 -6
  6. package/cjs/use-hotkeys/use-hotkeys.cjs.map +1 -1
  7. package/cjs/use-intersection/use-intersection.cjs +3 -2
  8. package/cjs/use-intersection/use-intersection.cjs.map +1 -1
  9. package/cjs/use-mask/use-mask.cjs +7 -5
  10. package/cjs/use-mask/use-mask.cjs.map +1 -1
  11. package/cjs/use-media-query/use-media-query.cjs +5 -1
  12. package/cjs/use-media-query/use-media-query.cjs.map +1 -1
  13. package/cjs/use-roving-index/use-roving-index.cjs +11 -10
  14. package/cjs/use-roving-index/use-roving-index.cjs.map +1 -1
  15. package/cjs/use-scroll-spy/use-scroll-spy.cjs +9 -3
  16. package/cjs/use-scroll-spy/use-scroll-spy.cjs.map +1 -1
  17. package/cjs/use-throttled-callback/use-throttled-callback.cjs +7 -1
  18. package/cjs/use-throttled-callback/use-throttled-callback.cjs.map +1 -1
  19. package/esm/use-click-outside/use-click-outside.mjs +8 -4
  20. package/esm/use-click-outside/use-click-outside.mjs.map +1 -1
  21. package/esm/use-hotkeys/parse-hotkey.mjs +4 -6
  22. package/esm/use-hotkeys/parse-hotkey.mjs.map +1 -1
  23. package/esm/use-hotkeys/use-hotkeys.mjs +4 -6
  24. package/esm/use-hotkeys/use-hotkeys.mjs.map +1 -1
  25. package/esm/use-intersection/use-intersection.mjs +3 -2
  26. package/esm/use-intersection/use-intersection.mjs.map +1 -1
  27. package/esm/use-mask/use-mask.mjs +7 -5
  28. package/esm/use-mask/use-mask.mjs.map +1 -1
  29. package/esm/use-media-query/use-media-query.mjs +5 -1
  30. package/esm/use-media-query/use-media-query.mjs.map +1 -1
  31. package/esm/use-roving-index/use-roving-index.mjs +11 -10
  32. package/esm/use-roving-index/use-roving-index.mjs.map +1 -1
  33. package/esm/use-scroll-spy/use-scroll-spy.mjs +9 -3
  34. package/esm/use-scroll-spy/use-scroll-spy.mjs.map +1 -1
  35. package/esm/use-throttled-callback/use-throttled-callback.mjs +7 -1
  36. package/esm/use-throttled-callback/use-throttled-callback.mjs.map +1 -1
  37. package/lib/use-click-outside/use-click-outside.d.ts +1 -1
  38. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-roving-index.mjs","names":[],"sources":["../../src/use-roving-index/use-roving-index.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useUncontrolled } from '../use-uncontrolled/use-uncontrolled';\n\nexport interface UseRovingIndexInput {\n /** Total number of items in the group */\n total: number;\n\n /** Which arrow keys navigate, `'horizontal'` by default */\n orientation?: 'horizontal' | 'vertical' | 'both';\n\n /** Whether navigation wraps at boundaries, `true` by default */\n loop?: boolean;\n\n /** Text direction, `'ltr'` by default */\n dir?: 'rtl' | 'ltr';\n\n /** Whether to click element when it receives focus via keyboard, `false` by default */\n activateOnFocus?: boolean;\n\n /** Number of columns for grid (2D) navigation. When set, enables grid mode */\n columns?: number;\n\n /** Controlled focused index */\n focusedIndex?: number;\n\n /** Initial focused index for uncontrolled mode, first non-disabled item by default */\n initialIndex?: number;\n\n /** Called when focused index changes */\n onFocusChange?: (index: number) => void;\n\n /** Function to check if item at given index is disabled, `() => false` by default */\n isItemDisabled?: (index: number) => boolean;\n}\n\nexport interface UseRovingIndexGetItemPropsInput {\n /** Index of the item in the group */\n index: number;\n\n /** Called when item is clicked */\n onClick?: React.MouseEventHandler;\n\n /** Called when keydown event fires on item */\n onKeyDown?: React.KeyboardEventHandler;\n}\n\nexport interface UseRovingIndexReturnValue {\n /** Get props to spread on each navigable item */\n getItemProps: (options: UseRovingIndexGetItemPropsInput) => {\n tabIndex: 0 | -1;\n onKeyDown: React.KeyboardEventHandler;\n onClick: React.MouseEventHandler;\n ref: React.RefCallback<HTMLElement>;\n };\n\n /** Currently focused index */\n focusedIndex: number;\n\n /** Programmatically set focused index */\n setFocusedIndex: (index: number) => void;\n}\n\nfunction findNextEnabled(\n current: number,\n total: number,\n isItemDisabled: (index: number) => boolean,\n loop: boolean\n): number {\n for (let i = current + 1; i < total; i += 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n\n if (loop) {\n for (let i = 0; i < current; i += 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n }\n\n return current;\n}\n\nfunction findPreviousEnabled(\n current: number,\n total: number,\n isItemDisabled: (index: number) => boolean,\n loop: boolean\n): number {\n for (let i = current - 1; i >= 0; i -= 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n\n if (loop) {\n for (let i = total - 1; i > current; i -= 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n }\n\n return current;\n}\n\nfunction findFirstEnabled(total: number, isItemDisabled: (index: number) => boolean): number {\n for (let i = 0; i < total; i += 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n\n return 0;\n}\n\nfunction findLastEnabled(total: number, isItemDisabled: (index: number) => boolean): number {\n for (let i = total - 1; i >= 0; i -= 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n\n return 0;\n}\n\nconst defaultIsItemDisabled = () => false;\n\nexport function useRovingIndex(input: UseRovingIndexInput): UseRovingIndexReturnValue {\n const {\n total,\n orientation = 'horizontal',\n loop = true,\n dir = 'ltr',\n activateOnFocus = false,\n columns,\n focusedIndex,\n initialIndex,\n onFocusChange,\n isItemDisabled = defaultIsItemDisabled,\n } = input;\n\n const itemRefs = useRef<Map<number, HTMLElement>>(new Map());\n const isGrid = typeof columns === 'number' && columns > 0;\n\n const resolvedInitialIndex =\n initialIndex !== undefined ? initialIndex : findFirstEnabled(total, isItemDisabled);\n\n const [activeIndex, setActiveIndex] = useUncontrolled({\n value: focusedIndex,\n defaultValue: resolvedInitialIndex,\n finalValue: 0,\n onChange: onFocusChange,\n });\n\n useEffect(() => {\n if (total === 0) {\n return;\n }\n\n if (activeIndex >= total) {\n setActiveIndex(findLastEnabled(total, isItemDisabled));\n } else if (isItemDisabled(activeIndex)) {\n setActiveIndex(findFirstEnabled(total, isItemDisabled));\n }\n }, [total, activeIndex, isItemDisabled]);\n\n const focusItem = useCallback(\n (index: number) => {\n setActiveIndex(index);\n const element = itemRefs.current.get(index);\n if (element) {\n element.focus();\n if (activateOnFocus) {\n element.click();\n }\n }\n },\n [activateOnFocus, setActiveIndex]\n );\n\n const handleGridKeyDown = useCallback(\n (event: React.KeyboardEvent, currentIndex: number) => {\n const row = Math.floor(currentIndex / columns!);\n const col = currentIndex % columns!;\n const totalRows = Math.ceil(total / columns!);\n let nextIndex: number | null = null;\n\n const isRtl = dir === 'rtl';\n\n switch (event.key) {\n case 'ArrowRight': {\n const targetCol = isRtl ? col - 1 : col + 1;\n if (targetCol >= 0 && targetCol < columns! && row * columns! + targetCol < total) {\n const candidate = row * columns! + targetCol;\n if (!isItemDisabled(candidate)) {\n nextIndex = candidate;\n }\n }\n break;\n }\n\n case 'ArrowLeft': {\n const targetCol = isRtl ? col + 1 : col - 1;\n if (targetCol >= 0 && targetCol < columns! && row * columns! + targetCol < total) {\n const candidate = row * columns! + targetCol;\n if (!isItemDisabled(candidate)) {\n nextIndex = candidate;\n }\n }\n break;\n }\n\n case 'ArrowDown': {\n for (let r = row + 1; r < totalRows; r += 1) {\n const candidate = r * columns! + col;\n if (candidate < total && !isItemDisabled(candidate)) {\n nextIndex = candidate;\n break;\n }\n }\n break;\n }\n\n case 'ArrowUp': {\n for (let r = row - 1; r >= 0; r -= 1) {\n const candidate = r * columns! + col;\n if (candidate < total && !isItemDisabled(candidate)) {\n nextIndex = candidate;\n break;\n }\n }\n break;\n }\n\n case 'Home': {\n if (event.ctrlKey) {\n nextIndex = findFirstEnabled(total, isItemDisabled);\n } else {\n const rowStart = row * columns!;\n for (let i = rowStart; i < rowStart + columns! && i < total; i += 1) {\n if (!isItemDisabled(i)) {\n nextIndex = i;\n break;\n }\n }\n }\n break;\n }\n\n case 'End': {\n if (event.ctrlKey) {\n nextIndex = findLastEnabled(total, isItemDisabled);\n } else {\n const rowStart = row * columns!;\n const rowEnd = Math.min(rowStart + columns!, total) - 1;\n for (let i = rowEnd; i >= rowStart; i -= 1) {\n if (!isItemDisabled(i)) {\n nextIndex = i;\n break;\n }\n }\n }\n break;\n }\n }\n\n if (nextIndex !== null && nextIndex !== currentIndex) {\n event.preventDefault();\n event.stopPropagation();\n focusItem(nextIndex);\n }\n },\n [total, columns, dir, isItemDisabled, focusItem]\n );\n\n const handleListKeyDown = useCallback(\n (event: React.KeyboardEvent, currentIndex: number) => {\n const isRtl = dir === 'rtl';\n let nextIndex: number | null = null;\n\n switch (event.key) {\n case 'ArrowRight': {\n if (orientation === 'horizontal' || orientation === 'both') {\n nextIndex = isRtl\n ? findPreviousEnabled(currentIndex, total, isItemDisabled, loop)\n : findNextEnabled(currentIndex, total, isItemDisabled, loop);\n }\n break;\n }\n\n case 'ArrowLeft': {\n if (orientation === 'horizontal' || orientation === 'both') {\n nextIndex = isRtl\n ? findNextEnabled(currentIndex, total, isItemDisabled, loop)\n : findPreviousEnabled(currentIndex, total, isItemDisabled, loop);\n }\n break;\n }\n\n case 'ArrowDown': {\n if (orientation === 'vertical' || orientation === 'both') {\n nextIndex = findNextEnabled(currentIndex, total, isItemDisabled, loop);\n }\n break;\n }\n\n case 'ArrowUp': {\n if (orientation === 'vertical' || orientation === 'both') {\n nextIndex = findPreviousEnabled(currentIndex, total, isItemDisabled, loop);\n }\n break;\n }\n\n case 'Home': {\n nextIndex = findFirstEnabled(total, isItemDisabled);\n break;\n }\n\n case 'End': {\n nextIndex = findLastEnabled(total, isItemDisabled);\n break;\n }\n }\n\n if (nextIndex !== null && nextIndex !== currentIndex) {\n event.preventDefault();\n event.stopPropagation();\n focusItem(nextIndex);\n }\n },\n [total, orientation, loop, dir, isItemDisabled, focusItem]\n );\n\n const getItemProps = useCallback(\n (options: UseRovingIndexGetItemPropsInput) => {\n const { index, onClick, onKeyDown } = options;\n\n return {\n tabIndex: (index === activeIndex ? 0 : -1) as 0 | -1,\n\n ref: (node: HTMLElement | null) => {\n if (node) {\n itemRefs.current.set(index, node);\n } else {\n itemRefs.current.delete(index);\n }\n },\n\n onKeyDown: (event: React.KeyboardEvent) => {\n onKeyDown?.(event);\n\n if (event.defaultPrevented) {\n return;\n }\n\n if (isGrid) {\n handleGridKeyDown(event, index);\n } else {\n handleListKeyDown(event, index);\n }\n },\n\n onClick: (event: React.MouseEvent) => {\n onClick?.(event);\n setActiveIndex(index);\n },\n };\n },\n [activeIndex, isGrid, handleGridKeyDown, handleListKeyDown, setActiveIndex]\n );\n\n return {\n getItemProps,\n focusedIndex: activeIndex,\n setFocusedIndex: setActiveIndex,\n };\n}\n\nexport namespace useRovingIndex {\n export type Input = UseRovingIndexInput;\n export type GetItemPropsInput = UseRovingIndexGetItemPropsInput;\n export type ReturnValue = UseRovingIndexReturnValue;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-roving-index.mjs","names":[],"sources":["../../src/use-roving-index/use-roving-index.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useUncontrolled } from '../use-uncontrolled/use-uncontrolled';\n\nexport interface UseRovingIndexInput {\n /** Total number of items in the group */\n total: number;\n\n /** Which arrow keys navigate, `'horizontal'` by default */\n orientation?: 'horizontal' | 'vertical' | 'both';\n\n /** Whether navigation wraps at boundaries, `true` by default */\n loop?: boolean;\n\n /** Text direction, `'ltr'` by default */\n dir?: 'rtl' | 'ltr';\n\n /** Whether to click element when it receives focus via keyboard, `false` by default */\n activateOnFocus?: boolean;\n\n /** Number of columns for grid (2D) navigation. When set, enables grid mode */\n columns?: number;\n\n /** Controlled focused index */\n focusedIndex?: number;\n\n /** Initial focused index for uncontrolled mode, first non-disabled item by default */\n initialIndex?: number;\n\n /** Called when focused index changes */\n onFocusChange?: (index: number) => void;\n\n /** Function to check if item at given index is disabled, `() => false` by default */\n isItemDisabled?: (index: number) => boolean;\n}\n\nexport interface UseRovingIndexGetItemPropsInput {\n /** Index of the item in the group */\n index: number;\n\n /** Called when item is clicked */\n onClick?: React.MouseEventHandler;\n\n /** Called when keydown event fires on item */\n onKeyDown?: React.KeyboardEventHandler;\n}\n\nexport interface UseRovingIndexReturnValue {\n /** Get props to spread on each navigable item */\n getItemProps: (options: UseRovingIndexGetItemPropsInput) => {\n tabIndex: 0 | -1;\n onKeyDown: React.KeyboardEventHandler;\n onClick: React.MouseEventHandler;\n ref: React.RefCallback<HTMLElement>;\n };\n\n /** Currently focused index */\n focusedIndex: number;\n\n /** Programmatically set focused index */\n setFocusedIndex: (index: number) => void;\n}\n\nfunction findNextEnabled(\n current: number,\n total: number,\n isItemDisabled: (index: number) => boolean,\n loop: boolean\n): number {\n for (let i = current + 1; i < total; i += 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n\n if (loop) {\n for (let i = 0; i < current; i += 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n }\n\n return current;\n}\n\nfunction findPreviousEnabled(\n current: number,\n total: number,\n isItemDisabled: (index: number) => boolean,\n loop: boolean\n): number {\n for (let i = current - 1; i >= 0; i -= 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n\n if (loop) {\n for (let i = total - 1; i > current; i -= 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n }\n\n return current;\n}\n\nfunction findFirstEnabled(total: number, isItemDisabled: (index: number) => boolean): number {\n for (let i = 0; i < total; i += 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n\n return 0;\n}\n\nfunction findLastEnabled(total: number, isItemDisabled: (index: number) => boolean): number {\n for (let i = total - 1; i >= 0; i -= 1) {\n if (!isItemDisabled(i)) {\n return i;\n }\n }\n\n return 0;\n}\n\nfunction findEnabledInRow(\n row: number,\n startCol: number,\n step: number,\n columns: number,\n total: number,\n isItemDisabled: (index: number) => boolean\n): number | null {\n for (let col = startCol; col >= 0 && col < columns; col += step) {\n const candidate = row * columns + col;\n if (candidate < total && !isItemDisabled(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\nconst defaultIsItemDisabled = () => false;\n\nexport function useRovingIndex(input: UseRovingIndexInput): UseRovingIndexReturnValue {\n const {\n total,\n orientation = 'horizontal',\n loop = true,\n dir = 'ltr',\n activateOnFocus = false,\n columns,\n focusedIndex,\n initialIndex,\n onFocusChange,\n isItemDisabled = defaultIsItemDisabled,\n } = input;\n\n const itemRefs = useRef<Map<number, HTMLElement>>(new Map());\n const isGrid = typeof columns === 'number' && columns > 0;\n\n const resolvedInitialIndex =\n initialIndex !== undefined ? initialIndex : findFirstEnabled(total, isItemDisabled);\n\n const [activeIndex, setActiveIndex] = useUncontrolled({\n value: focusedIndex,\n defaultValue: resolvedInitialIndex,\n finalValue: 0,\n onChange: onFocusChange,\n });\n\n useEffect(() => {\n if (total === 0) {\n return;\n }\n\n if (activeIndex >= total) {\n setActiveIndex(findLastEnabled(total, isItemDisabled));\n } else if (isItemDisabled(activeIndex)) {\n setActiveIndex(findFirstEnabled(total, isItemDisabled));\n }\n }, [total, activeIndex, isItemDisabled]);\n\n const focusItem = useCallback(\n (index: number) => {\n setActiveIndex(index);\n const element = itemRefs.current.get(index);\n if (element) {\n element.focus();\n if (activateOnFocus) {\n element.click();\n }\n }\n },\n [activateOnFocus, setActiveIndex]\n );\n\n const handleGridKeyDown = useCallback(\n (event: React.KeyboardEvent, currentIndex: number) => {\n const row = Math.floor(currentIndex / columns!);\n const col = currentIndex % columns!;\n const totalRows = Math.ceil(total / columns!);\n let nextIndex: number | null = null;\n\n const isRtl = dir === 'rtl';\n\n switch (event.key) {\n case 'ArrowRight': {\n const step = isRtl ? -1 : 1;\n nextIndex = findEnabledInRow(row, col + step, step, columns!, total, isItemDisabled);\n break;\n }\n\n case 'ArrowLeft': {\n const step = isRtl ? 1 : -1;\n nextIndex = findEnabledInRow(row, col + step, step, columns!, total, isItemDisabled);\n break;\n }\n\n case 'ArrowDown': {\n for (let r = row + 1; r < totalRows; r += 1) {\n const candidate = r * columns! + col;\n if (candidate < total && !isItemDisabled(candidate)) {\n nextIndex = candidate;\n break;\n }\n }\n break;\n }\n\n case 'ArrowUp': {\n for (let r = row - 1; r >= 0; r -= 1) {\n const candidate = r * columns! + col;\n if (candidate < total && !isItemDisabled(candidate)) {\n nextIndex = candidate;\n break;\n }\n }\n break;\n }\n\n case 'Home': {\n if (event.ctrlKey) {\n nextIndex = findFirstEnabled(total, isItemDisabled);\n } else {\n const rowStart = row * columns!;\n for (let i = rowStart; i < rowStart + columns! && i < total; i += 1) {\n if (!isItemDisabled(i)) {\n nextIndex = i;\n break;\n }\n }\n }\n break;\n }\n\n case 'End': {\n if (event.ctrlKey) {\n nextIndex = findLastEnabled(total, isItemDisabled);\n } else {\n const rowStart = row * columns!;\n const rowEnd = Math.min(rowStart + columns!, total) - 1;\n for (let i = rowEnd; i >= rowStart; i -= 1) {\n if (!isItemDisabled(i)) {\n nextIndex = i;\n break;\n }\n }\n }\n break;\n }\n }\n\n if (nextIndex !== null && nextIndex !== currentIndex) {\n event.preventDefault();\n event.stopPropagation();\n focusItem(nextIndex);\n }\n },\n [total, columns, dir, isItemDisabled, focusItem]\n );\n\n const handleListKeyDown = useCallback(\n (event: React.KeyboardEvent, currentIndex: number) => {\n const isRtl = dir === 'rtl';\n let nextIndex: number | null = null;\n\n switch (event.key) {\n case 'ArrowRight': {\n if (orientation === 'horizontal' || orientation === 'both') {\n nextIndex = isRtl\n ? findPreviousEnabled(currentIndex, total, isItemDisabled, loop)\n : findNextEnabled(currentIndex, total, isItemDisabled, loop);\n }\n break;\n }\n\n case 'ArrowLeft': {\n if (orientation === 'horizontal' || orientation === 'both') {\n nextIndex = isRtl\n ? findNextEnabled(currentIndex, total, isItemDisabled, loop)\n : findPreviousEnabled(currentIndex, total, isItemDisabled, loop);\n }\n break;\n }\n\n case 'ArrowDown': {\n if (orientation === 'vertical' || orientation === 'both') {\n nextIndex = findNextEnabled(currentIndex, total, isItemDisabled, loop);\n }\n break;\n }\n\n case 'ArrowUp': {\n if (orientation === 'vertical' || orientation === 'both') {\n nextIndex = findPreviousEnabled(currentIndex, total, isItemDisabled, loop);\n }\n break;\n }\n\n case 'Home': {\n nextIndex = findFirstEnabled(total, isItemDisabled);\n break;\n }\n\n case 'End': {\n nextIndex = findLastEnabled(total, isItemDisabled);\n break;\n }\n }\n\n if (nextIndex !== null && nextIndex !== currentIndex) {\n event.preventDefault();\n event.stopPropagation();\n focusItem(nextIndex);\n }\n },\n [total, orientation, loop, dir, isItemDisabled, focusItem]\n );\n\n const getItemProps = useCallback(\n (options: UseRovingIndexGetItemPropsInput) => {\n const { index, onClick, onKeyDown } = options;\n\n return {\n tabIndex: (index === activeIndex ? 0 : -1) as 0 | -1,\n\n ref: (node: HTMLElement | null) => {\n if (node) {\n itemRefs.current.set(index, node);\n } else {\n itemRefs.current.delete(index);\n }\n },\n\n onKeyDown: (event: React.KeyboardEvent) => {\n onKeyDown?.(event);\n\n if (event.defaultPrevented) {\n return;\n }\n\n if (isGrid) {\n handleGridKeyDown(event, index);\n } else {\n handleListKeyDown(event, index);\n }\n },\n\n onClick: (event: React.MouseEvent) => {\n onClick?.(event);\n setActiveIndex(index);\n },\n };\n },\n [activeIndex, isGrid, handleGridKeyDown, handleListKeyDown, setActiveIndex]\n );\n\n return {\n getItemProps,\n focusedIndex: activeIndex,\n setFocusedIndex: setActiveIndex,\n };\n}\n\nexport namespace useRovingIndex {\n export type Input = UseRovingIndexInput;\n export type GetItemPropsInput = UseRovingIndexGetItemPropsInput;\n export type ReturnValue = UseRovingIndexReturnValue;\n}\n"],"mappings":";;;;AA8DA,SAAS,gBACP,SACA,OACA,gBACA,MACQ;CACR,KAAK,IAAI,IAAI,UAAU,GAAG,IAAI,OAAO,KAAK,GACxC,IAAI,CAAC,eAAe,CAAC,GACnB,OAAO;CAIX,IAAI,MACG;OAAA,IAAI,IAAI,GAAG,IAAI,SAAS,KAAK,GAChC,IAAI,CAAC,eAAe,CAAC,GACnB,OAAO;CAAA;CAKb,OAAO;AACT;AAEA,SAAS,oBACP,SACA,OACA,gBACA,MACQ;CACR,KAAK,IAAI,IAAI,UAAU,GAAG,KAAK,GAAG,KAAK,GACrC,IAAI,CAAC,eAAe,CAAC,GACnB,OAAO;CAIX,IAAI,MACG;OAAA,IAAI,IAAI,QAAQ,GAAG,IAAI,SAAS,KAAK,GACxC,IAAI,CAAC,eAAe,CAAC,GACnB,OAAO;CAAA;CAKb,OAAO;AACT;AAEA,SAAS,iBAAiB,OAAe,gBAAoD;CAC3F,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK,GAC9B,IAAI,CAAC,eAAe,CAAC,GACnB,OAAO;CAIX,OAAO;AACT;AAEA,SAAS,gBAAgB,OAAe,gBAAoD;CAC1F,KAAK,IAAI,IAAI,QAAQ,GAAG,KAAK,GAAG,KAAK,GACnC,IAAI,CAAC,eAAe,CAAC,GACnB,OAAO;CAIX,OAAO;AACT;AAEA,SAAS,iBACP,KACA,UACA,MACA,SACA,OACA,gBACe;CACf,KAAK,IAAI,MAAM,UAAU,OAAO,KAAK,MAAM,SAAS,OAAO,MAAM;EAC/D,MAAM,YAAY,MAAM,UAAU;EAClC,IAAI,YAAY,SAAS,CAAC,eAAe,SAAS,GAChD,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,8BAA8B;AAEpC,SAAgB,eAAe,OAAuD;CACpF,MAAM,EACJ,OACA,cAAc,cACd,OAAO,MACP,MAAM,OACN,kBAAkB,OAClB,SACA,cACA,cACA,eACA,iBAAiB,0BACf;CAEJ,MAAM,WAAW,uBAAiC,IAAI,IAAI,CAAC;CAC3D,MAAM,SAAS,OAAO,YAAY,YAAY,UAAU;CAExD,MAAM,uBACJ,iBAAiB,KAAA,IAAY,eAAe,iBAAiB,OAAO,cAAc;CAEpF,MAAM,CAAC,aAAa,kBAAkB,gBAAgB;EACpD,OAAO;EACP,cAAc;EACd,YAAY;EACZ,UAAU;CACZ,CAAC;CAED,gBAAgB;EACd,IAAI,UAAU,GACZ;EAGF,IAAI,eAAe,OACjB,eAAe,gBAAgB,OAAO,cAAc,CAAC;OAChD,IAAI,eAAe,WAAW,GACnC,eAAe,iBAAiB,OAAO,cAAc,CAAC;CAE1D,GAAG;EAAC;EAAO;EAAa;CAAc,CAAC;CAEvC,MAAM,YAAY,aACf,UAAkB;EACjB,eAAe,KAAK;EACpB,MAAM,UAAU,SAAS,QAAQ,IAAI,KAAK;EAC1C,IAAI,SAAS;GACX,QAAQ,MAAM;GACd,IAAI,iBACF,QAAQ,MAAM;EAElB;CACF,GACA,CAAC,iBAAiB,cAAc,CAClC;CAEA,MAAM,oBAAoB,aACvB,OAA4B,iBAAyB;EACpD,MAAM,MAAM,KAAK,MAAM,eAAe,OAAQ;EAC9C,MAAM,MAAM,eAAe;EAC3B,MAAM,YAAY,KAAK,KAAK,QAAQ,OAAQ;EAC5C,IAAI,YAA2B;EAE/B,MAAM,QAAQ,QAAQ;EAEtB,QAAQ,MAAM,KAAd;GACE,KAAK,cAAc;IACjB,MAAM,OAAO,QAAQ,KAAK;IAC1B,YAAY,iBAAiB,KAAK,MAAM,MAAM,MAAM,SAAU,OAAO,cAAc;IACnF;GACF;GAEA,KAAK,aAAa;IAChB,MAAM,OAAO,QAAQ,IAAI;IACzB,YAAY,iBAAiB,KAAK,MAAM,MAAM,MAAM,SAAU,OAAO,cAAc;IACnF;GACF;GAEA,KAAK;IACH,KAAK,IAAI,IAAI,MAAM,GAAG,IAAI,WAAW,KAAK,GAAG;KAC3C,MAAM,YAAY,IAAI,UAAW;KACjC,IAAI,YAAY,SAAS,CAAC,eAAe,SAAS,GAAG;MACnD,YAAY;MACZ;KACF;IACF;IACA;GAGF,KAAK;IACH,KAAK,IAAI,IAAI,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG;KACpC,MAAM,YAAY,IAAI,UAAW;KACjC,IAAI,YAAY,SAAS,CAAC,eAAe,SAAS,GAAG;MACnD,YAAY;MACZ;KACF;IACF;IACA;GAGF,KAAK;IACH,IAAI,MAAM,SACR,YAAY,iBAAiB,OAAO,cAAc;SAC7C;KACL,MAAM,WAAW,MAAM;KACvB,KAAK,IAAI,IAAI,UAAU,IAAI,WAAW,WAAY,IAAI,OAAO,KAAK,GAChE,IAAI,CAAC,eAAe,CAAC,GAAG;MACtB,YAAY;MACZ;KACF;IAEJ;IACA;GAGF,KAAK,OACH,IAAI,MAAM,SACR,YAAY,gBAAgB,OAAO,cAAc;QAC5C;IACL,MAAM,WAAW,MAAM;IACvB,MAAM,SAAS,KAAK,IAAI,WAAW,SAAU,KAAK,IAAI;IACtD,KAAK,IAAI,IAAI,QAAQ,KAAK,UAAU,KAAK,GACvC,IAAI,CAAC,eAAe,CAAC,GAAG;KACtB,YAAY;KACZ;IACF;GAEJ;EAGJ;EAEA,IAAI,cAAc,QAAQ,cAAc,cAAc;GACpD,MAAM,eAAe;GACrB,MAAM,gBAAgB;GACtB,UAAU,SAAS;EACrB;CACF,GACA;EAAC;EAAO;EAAS;EAAK;EAAgB;CAAS,CACjD;CAEA,MAAM,oBAAoB,aACvB,OAA4B,iBAAyB;EACpD,MAAM,QAAQ,QAAQ;EACtB,IAAI,YAA2B;EAE/B,QAAQ,MAAM,KAAd;GACE,KAAK;IACH,IAAI,gBAAgB,gBAAgB,gBAAgB,QAClD,YAAY,QACR,oBAAoB,cAAc,OAAO,gBAAgB,IAAI,IAC7D,gBAAgB,cAAc,OAAO,gBAAgB,IAAI;IAE/D;GAGF,KAAK;IACH,IAAI,gBAAgB,gBAAgB,gBAAgB,QAClD,YAAY,QACR,gBAAgB,cAAc,OAAO,gBAAgB,IAAI,IACzD,oBAAoB,cAAc,OAAO,gBAAgB,IAAI;IAEnE;GAGF,KAAK;IACH,IAAI,gBAAgB,cAAc,gBAAgB,QAChD,YAAY,gBAAgB,cAAc,OAAO,gBAAgB,IAAI;IAEvE;GAGF,KAAK;IACH,IAAI,gBAAgB,cAAc,gBAAgB,QAChD,YAAY,oBAAoB,cAAc,OAAO,gBAAgB,IAAI;IAE3E;GAGF,KAAK;IACH,YAAY,iBAAiB,OAAO,cAAc;IAClD;GAGF,KAAK,OACH,YAAY,gBAAgB,OAAO,cAAc;EAGrD;EAEA,IAAI,cAAc,QAAQ,cAAc,cAAc;GACpD,MAAM,eAAe;GACrB,MAAM,gBAAgB;GACtB,UAAU,SAAS;EACrB;CACF,GACA;EAAC;EAAO;EAAa;EAAM;EAAK;EAAgB;CAAS,CAC3D;CAwCA,OAAO;EACL,cAvCmB,aAClB,YAA6C;GAC5C,MAAM,EAAE,OAAO,SAAS,cAAc;GAEtC,OAAO;IACL,UAAW,UAAU,cAAc,IAAI;IAEvC,MAAM,SAA6B;KACjC,IAAI,MACF,SAAS,QAAQ,IAAI,OAAO,IAAI;UAEhC,SAAS,QAAQ,OAAO,KAAK;IAEjC;IAEA,YAAY,UAA+B;KACzC,YAAY,KAAK;KAEjB,IAAI,MAAM,kBACR;KAGF,IAAI,QACF,kBAAkB,OAAO,KAAK;UAE9B,kBAAkB,OAAO,KAAK;IAElC;IAEA,UAAU,UAA4B;KACpC,UAAU,KAAK;KACf,eAAe,KAAK;IACtB;GACF;EACF,GACA;GAAC;GAAa;GAAQ;GAAmB;GAAmB;EAAc,CAI/D;EACX,cAAc;EACd,iBAAiB;CACnB;AACF"}
@@ -2,7 +2,13 @@
2
2
  import { randomId } from "../utils/random-id/random-id.mjs";
3
3
  import { useEffect, useEffectEvent, useRef, useState } from "react";
4
4
  //#region packages/@mantine/hooks/src/use-scroll-spy/use-scroll-spy.ts
5
- function getHeadingsData(headings, getDepth, getValue) {
5
+ function resolveHeadingNode(heading, selector, index) {
6
+ if (heading.isConnected) return heading;
7
+ const nodeById = heading.id ? document.getElementById(heading.id) : null;
8
+ if (nodeById) return nodeById;
9
+ return document.querySelectorAll(selector)[index] || heading;
10
+ }
11
+ function getHeadingsData(headings, getDepth, getValue, selector) {
6
12
  const result = [];
7
13
  for (let i = 0; i < headings.length; i += 1) {
8
14
  const heading = headings[i];
@@ -10,7 +16,7 @@ function getHeadingsData(headings, getDepth, getValue) {
10
16
  depth: getDepth(heading),
11
17
  value: getValue(heading),
12
18
  id: heading.id || randomId(),
13
- getNode: () => heading.id ? document.getElementById(heading.id) : heading
19
+ getNode: () => resolveHeadingNode(heading, selector, i)
14
20
  });
15
21
  }
16
22
  return result;
@@ -48,7 +54,7 @@ function useScrollSpy({ selector = "h1, h2, h3, h4, h5, h6", getDepth = getDefau
48
54
  setActive(getActiveElement(headingsRef.current.map((d) => d.getNode().getBoundingClientRect()), offset));
49
55
  });
50
56
  const initialize = () => {
51
- const headings = getHeadingsData(Array.from(document.querySelectorAll(selector)), getDepth, getValue);
57
+ const headings = getHeadingsData(Array.from(document.querySelectorAll(selector)), getDepth, getValue, selector);
52
58
  headingsRef.current = headings;
53
59
  setInitialized(true);
54
60
  setData(headings);
@@ -1 +1 @@
1
- {"version":3,"file":"use-scroll-spy.mjs","names":[],"sources":["../../src/use-scroll-spy/use-scroll-spy.ts"],"sourcesContent":["import React, { useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { randomId } from '../utils';\n\nfunction getHeadingsData(\n headings: HTMLElement[],\n getDepth: (element: HTMLElement) => number,\n getValue: (element: HTMLElement) => string\n): UseScrollSpyHeadingData[] {\n const result: UseScrollSpyHeadingData[] = [];\n\n for (let i = 0; i < headings.length; i += 1) {\n const heading = headings[i];\n result.push({\n depth: getDepth(heading),\n value: getValue(heading),\n id: heading.id || randomId(),\n getNode: () => (heading.id ? document.getElementById(heading.id)! : heading),\n });\n }\n\n return result;\n}\n\nfunction getActiveElement(rects: DOMRect[], offset: number = 0) {\n if (rects.length === 0) {\n return -1;\n }\n\n const closest = rects.reduce(\n (acc, item, index) => {\n if (Math.abs(acc.position - offset) < Math.abs(item.y - offset)) {\n return acc;\n }\n\n return {\n index,\n position: item.y,\n };\n },\n { index: 0, position: rects[0].y }\n );\n\n return closest.index;\n}\n\nfunction getDefaultDepth(element: HTMLElement) {\n return Number(element.tagName[1]);\n}\n\nfunction getDefaultValue(element: HTMLElement) {\n return element.textContent || '';\n}\n\nfunction resolveScrollHost(\n scrollHost: HTMLElement | React.RefObject<HTMLElement | null> | undefined\n): HTMLElement | Window {\n if (!scrollHost) {\n return window;\n }\n\n if ('current' in scrollHost) {\n return scrollHost.current || window;\n }\n\n return scrollHost;\n}\n\nexport interface UseScrollSpyHeadingData {\n /** Heading depth, 1-6 */\n depth: number;\n\n /** Heading text content value */\n value: string;\n\n /** Heading id */\n id: string;\n\n /** Function to get heading node */\n getNode: () => HTMLElement;\n}\n\nexport interface UseScrollSpyOptions {\n /** Selector to get headings, `'h1, h2, h3, h4, h5, h6'` by default */\n selector?: string;\n\n /** A function to retrieve depth of heading, by default depth is calculated based on tag name */\n getDepth?: (element: HTMLElement) => number;\n\n /** A function to retrieve heading value, by default `element.textContent` is used */\n getValue?: (element: HTMLElement) => string;\n\n /** Host element or a ref to it to attach scroll event listener, if not provided, `window` is used */\n scrollHost?: HTMLElement | React.RefObject<HTMLElement | null>;\n\n /** Offset from the top of the viewport to use when determining the active heading, `0` by default */\n offset?: number;\n}\n\nexport interface UseScrollSpyReturnValue {\n /** Index of the active heading in the `data` array */\n active: number;\n\n /** Headings data. If not initialize, data is represented by an empty array. */\n data: UseScrollSpyHeadingData[];\n\n /** True if headings value have been retrieved from the DOM. */\n initialized: boolean;\n\n /** Function to update headings values after the parent component has mounted. */\n reinitialize: () => void;\n}\n\nexport function useScrollSpy({\n selector = 'h1, h2, h3, h4, h5, h6',\n getDepth = getDefaultDepth,\n getValue = getDefaultValue,\n offset = 0,\n scrollHost,\n}: UseScrollSpyOptions = {}): UseScrollSpyReturnValue {\n const [active, setActive] = useState(-1);\n const [initialized, setInitialized] = useState(false);\n const [data, setData] = useState<UseScrollSpyHeadingData[]>([]);\n const headingsRef = useRef<UseScrollSpyHeadingData[]>([]);\n\n const handleScroll = useEffectEvent(() => {\n setActive(\n getActiveElement(\n headingsRef.current.map((d) => d.getNode().getBoundingClientRect()),\n offset\n )\n );\n });\n\n const initialize = () => {\n const headings = getHeadingsData(\n Array.from(document.querySelectorAll(selector)),\n getDepth,\n getValue\n );\n headingsRef.current = headings;\n setInitialized(true);\n setData(headings);\n setActive(\n getActiveElement(\n headings.map((d) => d.getNode().getBoundingClientRect()),\n offset\n )\n );\n };\n\n useEffect(() => {\n initialize();\n const _scrollHost = resolveScrollHost(scrollHost);\n _scrollHost.addEventListener('scroll', handleScroll);\n return () => _scrollHost.removeEventListener('scroll', handleScroll);\n }, [scrollHost, selector, offset]);\n\n return {\n reinitialize: initialize,\n active,\n initialized,\n data,\n };\n}\n\nexport namespace useScrollSpy {\n export type Options = UseScrollSpyOptions;\n export type ReturnValue = UseScrollSpyReturnValue;\n}\n"],"mappings":";;;;AAGA,SAAS,gBACP,UACA,UACA,UAC2B;CAC3B,MAAM,SAAoC,CAAC;CAE3C,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,GAAG;EAC3C,MAAM,UAAU,SAAS;EACzB,OAAO,KAAK;GACV,OAAO,SAAS,OAAO;GACvB,OAAO,SAAS,OAAO;GACvB,IAAI,QAAQ,MAAM,SAAS;GAC3B,eAAgB,QAAQ,KAAK,SAAS,eAAe,QAAQ,EAAE,IAAK;EACtE,CAAC;CACH;CAEA,OAAO;AACT;AAEA,SAAS,iBAAiB,OAAkB,SAAiB,GAAG;CAC9D,IAAI,MAAM,WAAW,GACnB,OAAO;CAiBT,OAdgB,MAAM,QACnB,KAAK,MAAM,UAAU;EACpB,IAAI,KAAK,IAAI,IAAI,WAAW,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAC5D,OAAO;EAGT,OAAO;GACL;GACA,UAAU,KAAK;EACjB;CACF,GACA;EAAE,OAAO;EAAG,UAAU,MAAM,EAAE,CAAC;CAAE,CAGtB,CAAC,CAAC;AACjB;AAEA,SAAS,gBAAgB,SAAsB;CAC7C,OAAO,OAAO,QAAQ,QAAQ,EAAE;AAClC;AAEA,SAAS,gBAAgB,SAAsB;CAC7C,OAAO,QAAQ,eAAe;AAChC;AAEA,SAAS,kBACP,YACsB;CACtB,IAAI,CAAC,YACH,OAAO;CAGT,IAAI,aAAa,YACf,OAAO,WAAW,WAAW;CAG/B,OAAO;AACT;AA+CA,SAAgB,aAAa,EAC3B,WAAW,0BACX,WAAW,iBACX,WAAW,iBACX,SAAS,GACT,eACuB,CAAC,GAA4B;CACpD,MAAM,CAAC,QAAQ,aAAa,SAAS,EAAE;CACvC,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,MAAM,WAAW,SAAoC,CAAC,CAAC;CAC9D,MAAM,cAAc,OAAkC,CAAC,CAAC;CAExD,MAAM,eAAe,qBAAqB;EACxC,UACE,iBACE,YAAY,QAAQ,KAAK,MAAM,EAAE,QAAQ,CAAC,CAAC,sBAAsB,CAAC,GAClE,MACF,CACF;CACF,CAAC;CAED,MAAM,mBAAmB;EACvB,MAAM,WAAW,gBACf,MAAM,KAAK,SAAS,iBAAiB,QAAQ,CAAC,GAC9C,UACA,QACF;EACA,YAAY,UAAU;EACtB,eAAe,IAAI;EACnB,QAAQ,QAAQ;EAChB,UACE,iBACE,SAAS,KAAK,MAAM,EAAE,QAAQ,CAAC,CAAC,sBAAsB,CAAC,GACvD,MACF,CACF;CACF;CAEA,gBAAgB;EACd,WAAW;EACX,MAAM,cAAc,kBAAkB,UAAU;EAChD,YAAY,iBAAiB,UAAU,YAAY;EACnD,aAAa,YAAY,oBAAoB,UAAU,YAAY;CACrE,GAAG;EAAC;EAAY;EAAU;CAAM,CAAC;CAEjC,OAAO;EACL,cAAc;EACd;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"use-scroll-spy.mjs","names":[],"sources":["../../src/use-scroll-spy/use-scroll-spy.ts"],"sourcesContent":["import React, { useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { randomId } from '../utils';\n\nfunction resolveHeadingNode(heading: HTMLElement, selector: string, index: number): HTMLElement {\n if (heading.isConnected) {\n return heading;\n }\n\n const nodeById = heading.id ? document.getElementById(heading.id) : null;\n\n if (nodeById) {\n return nodeById;\n }\n\n return document.querySelectorAll<HTMLElement>(selector)[index] || heading;\n}\n\nfunction getHeadingsData(\n headings: HTMLElement[],\n getDepth: (element: HTMLElement) => number,\n getValue: (element: HTMLElement) => string,\n selector: string\n): UseScrollSpyHeadingData[] {\n const result: UseScrollSpyHeadingData[] = [];\n\n for (let i = 0; i < headings.length; i += 1) {\n const heading = headings[i];\n result.push({\n depth: getDepth(heading),\n value: getValue(heading),\n id: heading.id || randomId(),\n getNode: () => resolveHeadingNode(heading, selector, i),\n });\n }\n\n return result;\n}\n\nfunction getActiveElement(rects: DOMRect[], offset: number = 0) {\n if (rects.length === 0) {\n return -1;\n }\n\n const closest = rects.reduce(\n (acc, item, index) => {\n if (Math.abs(acc.position - offset) < Math.abs(item.y - offset)) {\n return acc;\n }\n\n return {\n index,\n position: item.y,\n };\n },\n { index: 0, position: rects[0].y }\n );\n\n return closest.index;\n}\n\nfunction getDefaultDepth(element: HTMLElement) {\n return Number(element.tagName[1]);\n}\n\nfunction getDefaultValue(element: HTMLElement) {\n return element.textContent || '';\n}\n\nfunction resolveScrollHost(\n scrollHost: HTMLElement | React.RefObject<HTMLElement | null> | undefined\n): HTMLElement | Window {\n if (!scrollHost) {\n return window;\n }\n\n if ('current' in scrollHost) {\n return scrollHost.current || window;\n }\n\n return scrollHost;\n}\n\nexport interface UseScrollSpyHeadingData {\n /** Heading depth, 1-6 */\n depth: number;\n\n /** Heading text content value */\n value: string;\n\n /** Heading id */\n id: string;\n\n /** Function to get heading node */\n getNode: () => HTMLElement;\n}\n\nexport interface UseScrollSpyOptions {\n /** Selector to get headings, `'h1, h2, h3, h4, h5, h6'` by default */\n selector?: string;\n\n /** A function to retrieve depth of heading, by default depth is calculated based on tag name */\n getDepth?: (element: HTMLElement) => number;\n\n /** A function to retrieve heading value, by default `element.textContent` is used */\n getValue?: (element: HTMLElement) => string;\n\n /** Host element or a ref to it to attach scroll event listener, if not provided, `window` is used */\n scrollHost?: HTMLElement | React.RefObject<HTMLElement | null>;\n\n /** Offset from the top of the viewport to use when determining the active heading, `0` by default */\n offset?: number;\n}\n\nexport interface UseScrollSpyReturnValue {\n /** Index of the active heading in the `data` array */\n active: number;\n\n /** Headings data. If not initialize, data is represented by an empty array. */\n data: UseScrollSpyHeadingData[];\n\n /** True if headings value have been retrieved from the DOM. */\n initialized: boolean;\n\n /** Function to update headings values after the parent component has mounted. */\n reinitialize: () => void;\n}\n\nexport function useScrollSpy({\n selector = 'h1, h2, h3, h4, h5, h6',\n getDepth = getDefaultDepth,\n getValue = getDefaultValue,\n offset = 0,\n scrollHost,\n}: UseScrollSpyOptions = {}): UseScrollSpyReturnValue {\n const [active, setActive] = useState(-1);\n const [initialized, setInitialized] = useState(false);\n const [data, setData] = useState<UseScrollSpyHeadingData[]>([]);\n const headingsRef = useRef<UseScrollSpyHeadingData[]>([]);\n\n const handleScroll = useEffectEvent(() => {\n setActive(\n getActiveElement(\n headingsRef.current.map((d) => d.getNode().getBoundingClientRect()),\n offset\n )\n );\n });\n\n const initialize = () => {\n const headings = getHeadingsData(\n Array.from(document.querySelectorAll(selector)),\n getDepth,\n getValue,\n selector\n );\n headingsRef.current = headings;\n setInitialized(true);\n setData(headings);\n setActive(\n getActiveElement(\n headings.map((d) => d.getNode().getBoundingClientRect()),\n offset\n )\n );\n };\n\n useEffect(() => {\n initialize();\n const _scrollHost = resolveScrollHost(scrollHost);\n _scrollHost.addEventListener('scroll', handleScroll);\n return () => _scrollHost.removeEventListener('scroll', handleScroll);\n }, [scrollHost, selector, offset]);\n\n return {\n reinitialize: initialize,\n active,\n initialized,\n data,\n };\n}\n\nexport namespace useScrollSpy {\n export type Options = UseScrollSpyOptions;\n export type ReturnValue = UseScrollSpyReturnValue;\n}\n"],"mappings":";;;;AAGA,SAAS,mBAAmB,SAAsB,UAAkB,OAA4B;CAC9F,IAAI,QAAQ,aACV,OAAO;CAGT,MAAM,WAAW,QAAQ,KAAK,SAAS,eAAe,QAAQ,EAAE,IAAI;CAEpE,IAAI,UACF,OAAO;CAGT,OAAO,SAAS,iBAA8B,QAAQ,CAAC,CAAC,UAAU;AACpE;AAEA,SAAS,gBACP,UACA,UACA,UACA,UAC2B;CAC3B,MAAM,SAAoC,CAAC;CAE3C,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,GAAG;EAC3C,MAAM,UAAU,SAAS;EACzB,OAAO,KAAK;GACV,OAAO,SAAS,OAAO;GACvB,OAAO,SAAS,OAAO;GACvB,IAAI,QAAQ,MAAM,SAAS;GAC3B,eAAe,mBAAmB,SAAS,UAAU,CAAC;EACxD,CAAC;CACH;CAEA,OAAO;AACT;AAEA,SAAS,iBAAiB,OAAkB,SAAiB,GAAG;CAC9D,IAAI,MAAM,WAAW,GACnB,OAAO;CAiBT,OAdgB,MAAM,QACnB,KAAK,MAAM,UAAU;EACpB,IAAI,KAAK,IAAI,IAAI,WAAW,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAC5D,OAAO;EAGT,OAAO;GACL;GACA,UAAU,KAAK;EACjB;CACF,GACA;EAAE,OAAO;EAAG,UAAU,MAAM,EAAE,CAAC;CAAE,CAGtB,CAAC,CAAC;AACjB;AAEA,SAAS,gBAAgB,SAAsB;CAC7C,OAAO,OAAO,QAAQ,QAAQ,EAAE;AAClC;AAEA,SAAS,gBAAgB,SAAsB;CAC7C,OAAO,QAAQ,eAAe;AAChC;AAEA,SAAS,kBACP,YACsB;CACtB,IAAI,CAAC,YACH,OAAO;CAGT,IAAI,aAAa,YACf,OAAO,WAAW,WAAW;CAG/B,OAAO;AACT;AA+CA,SAAgB,aAAa,EAC3B,WAAW,0BACX,WAAW,iBACX,WAAW,iBACX,SAAS,GACT,eACuB,CAAC,GAA4B;CACpD,MAAM,CAAC,QAAQ,aAAa,SAAS,EAAE;CACvC,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,MAAM,WAAW,SAAoC,CAAC,CAAC;CAC9D,MAAM,cAAc,OAAkC,CAAC,CAAC;CAExD,MAAM,eAAe,qBAAqB;EACxC,UACE,iBACE,YAAY,QAAQ,KAAK,MAAM,EAAE,QAAQ,CAAC,CAAC,sBAAsB,CAAC,GAClE,MACF,CACF;CACF,CAAC;CAED,MAAM,mBAAmB;EACvB,MAAM,WAAW,gBACf,MAAM,KAAK,SAAS,iBAAiB,QAAQ,CAAC,GAC9C,UACA,UACA,QACF;EACA,YAAY,UAAU;EACtB,eAAe,IAAI;EACnB,QAAQ,QAAQ;EAChB,UACE,iBACE,SAAS,KAAK,MAAM,EAAE,QAAQ,CAAC,CAAC,sBAAsB,CAAC,GACvD,MACF,CACF;CACF;CAEA,gBAAgB;EACd,WAAW;EACX,MAAM,cAAc,kBAAkB,UAAU;EAChD,YAAY,iBAAiB,UAAU,YAAY;EACnD,aAAa,YAAY,oBAAoB,UAAU,YAAY;CACrE,GAAG;EAAC;EAAY;EAAU;CAAM,CAAC;CAEjC,OAAO;EACL,cAAc;EACd;EACA;EACA;CACF;AACF"}
@@ -9,7 +9,13 @@ function useThrottledCallbackWithClearTimeout(callback, wait) {
9
9
  const active = useRef(true);
10
10
  const waitRef = useRef(wait);
11
11
  const timeoutRef = useRef(-1);
12
- const clearTimeout = () => window.clearTimeout(timeoutRef.current);
12
+ const clearTimeout = () => {
13
+ window.clearTimeout(timeoutRef.current);
14
+ timeoutRef.current = -1;
15
+ active.current = true;
16
+ latestInArgsRef.current = null;
17
+ latestOutArgsRef.current = null;
18
+ };
13
19
  const callThrottledCallback = useCallback((...args) => {
14
20
  handleCallback(...args);
15
21
  latestInArgsRef.current = args;
@@ -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 = () => window.clearTimeout(timeoutRef.current);\n\n const callThrottledCallback = useCallback(\n (...args: Parameters<T>) => {\n handleCallback(...args);\n latestInArgsRef.current = args;\n latestOutArgsRef.current = args;\n active.current = false;\n },\n [handleCallback]\n );\n\n const timerCallback = useCallback(() => {\n if (latestInArgsRef.current && latestInArgsRef.current !== latestOutArgsRef.current) {\n callThrottledCallback(...latestInArgsRef.current);\n\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n active.current = true;\n }\n }, [callThrottledCallback]);\n\n const throttled = useCallback(\n (...args: Parameters<T>) => {\n if (active.current) {\n callThrottledCallback(...args);\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n latestInArgsRef.current = args;\n }\n },\n [callThrottledCallback, timerCallback]\n );\n\n useEffect(() => {\n waitRef.current = wait;\n }, [wait]);\n\n return [throttled, clearTimeout] as const;\n}\n\nexport function useThrottledCallback<T extends (...args: any[]) => any>(callback: T, wait: number) {\n const [throttled, clearTimeout] = useThrottledCallbackWithClearTimeout(callback, wait);\n useEffect(() => clearTimeout, []);\n return throttled;\n}\n"],"mappings":";;;;AAGA,SAAgB,qCACd,UACA,MACA;CACA,MAAM,iBAAiB,eAAe,QAAQ;CAC9C,MAAM,kBAAkB,OAAsB,IAAI;CAClD,MAAM,mBAAmB,OAAsB,IAAI;CACnD,MAAM,SAAS,OAAO,IAAI;CAC1B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,aAAa,OAAe,EAAE;CAEpC,MAAM,qBAAqB,OAAO,aAAa,WAAW,OAAO;CAEjE,MAAM,wBAAwB,aAC3B,GAAG,SAAwB;EAC1B,eAAe,GAAG,IAAI;EACtB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,OAAO,UAAU;CACnB,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,gBAAgB,WAAW,gBAAgB,YAAY,iBAAiB,SAAS;GACnF,sBAAsB,GAAG,gBAAgB,OAAO;GAEhD,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,OAAO,UAAU;CAErB,GAAG,CAAC,qBAAqB,CAAC;CAE1B,MAAM,YAAY,aACf,GAAG,SAAwB;EAC1B,IAAI,OAAO,SAAS;GAClB,sBAAsB,GAAG,IAAI;GAC7B,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,gBAAgB,UAAU;CAE9B,GACA,CAAC,uBAAuB,aAAa,CACvC;CAEA,gBAAgB;EACd,QAAQ,UAAU;CACpB,GAAG,CAAC,IAAI,CAAC;CAET,OAAO,CAAC,WAAW,YAAY;AACjC;AAEA,SAAgB,qBAAwD,UAAa,MAAc;CACjG,MAAM,CAAC,WAAW,gBAAgB,qCAAqC,UAAU,IAAI;CACrF,gBAAgB,cAAc,CAAC,CAAC;CAChC,OAAO;AACT"}
1
+ {"version":3,"file":"use-throttled-callback.mjs","names":[],"sources":["../../src/use-throttled-callback/use-throttled-callback.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { useCallbackRef } from '../utils';\n\nexport function useThrottledCallbackWithClearTimeout<T extends (...args: any[]) => any>(\n callback: T,\n wait: number\n) {\n const handleCallback = useCallbackRef(callback);\n const latestInArgsRef = useRef<Parameters<T>>(null);\n const latestOutArgsRef = useRef<Parameters<T>>(null);\n const active = useRef(true);\n const waitRef = useRef(wait);\n const timeoutRef = useRef<number>(-1);\n\n const clearTimeout = () => {\n window.clearTimeout(timeoutRef.current);\n timeoutRef.current = -1;\n active.current = true;\n latestInArgsRef.current = null;\n latestOutArgsRef.current = null;\n };\n\n const callThrottledCallback = useCallback(\n (...args: Parameters<T>) => {\n handleCallback(...args);\n latestInArgsRef.current = args;\n latestOutArgsRef.current = args;\n active.current = false;\n },\n [handleCallback]\n );\n\n const timerCallback = useCallback(() => {\n if (latestInArgsRef.current && latestInArgsRef.current !== latestOutArgsRef.current) {\n callThrottledCallback(...latestInArgsRef.current);\n\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n active.current = true;\n }\n }, [callThrottledCallback]);\n\n const throttled = useCallback(\n (...args: Parameters<T>) => {\n if (active.current) {\n callThrottledCallback(...args);\n timeoutRef.current = window.setTimeout(timerCallback, waitRef.current);\n } else {\n latestInArgsRef.current = args;\n }\n },\n [callThrottledCallback, timerCallback]\n );\n\n useEffect(() => {\n waitRef.current = wait;\n }, [wait]);\n\n return [throttled, clearTimeout] as const;\n}\n\nexport function useThrottledCallback<T extends (...args: any[]) => any>(callback: T, wait: number) {\n const [throttled, clearTimeout] = useThrottledCallbackWithClearTimeout(callback, wait);\n useEffect(() => clearTimeout, []);\n return throttled;\n}\n"],"mappings":";;;;AAGA,SAAgB,qCACd,UACA,MACA;CACA,MAAM,iBAAiB,eAAe,QAAQ;CAC9C,MAAM,kBAAkB,OAAsB,IAAI;CAClD,MAAM,mBAAmB,OAAsB,IAAI;CACnD,MAAM,SAAS,OAAO,IAAI;CAC1B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,aAAa,OAAe,EAAE;CAEpC,MAAM,qBAAqB;EACzB,OAAO,aAAa,WAAW,OAAO;EACtC,WAAW,UAAU;EACrB,OAAO,UAAU;EACjB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;CAC7B;CAEA,MAAM,wBAAwB,aAC3B,GAAG,SAAwB;EAC1B,eAAe,GAAG,IAAI;EACtB,gBAAgB,UAAU;EAC1B,iBAAiB,UAAU;EAC3B,OAAO,UAAU;CACnB,GACA,CAAC,cAAc,CACjB;CAEA,MAAM,gBAAgB,kBAAkB;EACtC,IAAI,gBAAgB,WAAW,gBAAgB,YAAY,iBAAiB,SAAS;GACnF,sBAAsB,GAAG,gBAAgB,OAAO;GAEhD,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,OAAO,UAAU;CAErB,GAAG,CAAC,qBAAqB,CAAC;CAE1B,MAAM,YAAY,aACf,GAAG,SAAwB;EAC1B,IAAI,OAAO,SAAS;GAClB,sBAAsB,GAAG,IAAI;GAC7B,WAAW,UAAU,OAAO,WAAW,eAAe,QAAQ,OAAO;EACvE,OACE,gBAAgB,UAAU;CAE9B,GACA,CAAC,uBAAuB,aAAa,CACvC;CAEA,gBAAgB;EACd,QAAQ,UAAU;CACpB,GAAG,CAAC,IAAI,CAAC;CAET,OAAO,CAAC,WAAW,YAAY;AACjC;AAEA,SAAgB,qBAAwD,UAAa,MAAc;CACjG,MAAM,CAAC,WAAW,gBAAgB,qCAAqC,UAAU,IAAI;CACrF,gBAAgB,cAAc,CAAC,CAAC;CAChC,OAAO;AACT"}
@@ -1,3 +1,3 @@
1
1
  type EventType = MouseEvent | TouchEvent;
2
- export declare function useClickOutside<T extends HTMLElement = any>(callback: (event: EventType) => void, events?: string[] | null, nodes?: (HTMLElement | null)[], enabled?: boolean): import("react").RefObject<T | null>;
2
+ export declare function useClickOutside<T extends HTMLElement = any>(callback: (event: EventType) => void, events?: string[] | null, nodes?: (HTMLElement | null)[], enabled?: boolean, capture?: boolean): import("react").RefObject<T | null>;
3
3
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantine/hooks",
3
- "version": "9.6.1",
3
+ "version": "9.6.3",
4
4
  "description": "A collection of 50+ hooks for state and UI management",
5
5
  "homepage": "https://mantine.dev",
6
6
  "license": "MIT",