@oneplatformdev/hooks 0.1.0-7 → 0.1.0-9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/.babelrc +12 -0
  2. package/CHANGELOG.md +23 -0
  3. package/dist/CHANGELOG.md +8 -0
  4. package/dist/package.json +2 -1
  5. package/node_modules/@types/lodash.debounce/LICENSE +21 -0
  6. package/node_modules/@types/lodash.debounce/README.md +23 -0
  7. package/node_modules/@types/lodash.debounce/index.d.ts +4 -0
  8. package/node_modules/@types/lodash.debounce/package.json +32 -0
  9. package/node_modules/@types/react/LICENSE +21 -0
  10. package/node_modules/@types/react/README.md +15 -0
  11. package/node_modules/@types/react/canary.d.ts +35 -0
  12. package/node_modules/@types/react/compiler-runtime.d.ts +4 -0
  13. package/node_modules/@types/react/experimental.d.ts +125 -0
  14. package/node_modules/@types/react/global.d.ts +160 -0
  15. package/node_modules/@types/react/index.d.ts +4206 -0
  16. package/node_modules/@types/react/jsx-dev-runtime.d.ts +45 -0
  17. package/node_modules/@types/react/jsx-runtime.d.ts +36 -0
  18. package/node_modules/@types/react/package.json +210 -0
  19. package/node_modules/@types/react/ts5.0/canary.d.ts +35 -0
  20. package/node_modules/@types/react/ts5.0/experimental.d.ts +125 -0
  21. package/node_modules/@types/react/ts5.0/global.d.ts +160 -0
  22. package/node_modules/@types/react/ts5.0/index.d.ts +4193 -0
  23. package/node_modules/@types/react/ts5.0/jsx-dev-runtime.d.ts +44 -0
  24. package/node_modules/@types/react/ts5.0/jsx-runtime.d.ts +35 -0
  25. package/node_modules/@types/react/ts5.0/v18/global.d.ts +160 -0
  26. package/node_modules/@types/react/ts5.0/v18/index.d.ts +4543 -0
  27. package/node_modules/@types/react/ts5.0/v18/jsx-dev-runtime.d.ts +45 -0
  28. package/node_modules/@types/react/ts5.0/v18/jsx-runtime.d.ts +36 -0
  29. package/node_modules/@types/react/ts5.0/v18/ts5.0/global.d.ts +160 -0
  30. package/node_modules/@types/react/ts5.0/v18/ts5.0/index.d.ts +4530 -0
  31. package/node_modules/@types/react/ts5.0/v18/ts5.0/jsx-dev-runtime.d.ts +44 -0
  32. package/node_modules/@types/react/ts5.0/v18/ts5.0/jsx-runtime.d.ts +35 -0
  33. package/package.json +2 -1
  34. package/src/index.ts +36 -0
  35. package/src/useBoolean/index.ts +1 -0
  36. package/src/useBoolean/useBoolean.ts +50 -0
  37. package/src/useClickAnyWhere/index.ts +1 -0
  38. package/src/useClickAnyWhere/useClickAnyWhere.ts +22 -0
  39. package/src/useCopyToClipboard/index.ts +1 -0
  40. package/src/useCopyToClipboard/useCopyToClipboard.ts +58 -0
  41. package/src/useCountdown/index.ts +1 -0
  42. package/src/useCountdown/useCountdown.ts +102 -0
  43. package/src/useCounter/index.ts +1 -0
  44. package/src/useCounter/useCounter.ts +52 -0
  45. package/src/useDarkMode/index.ts +1 -0
  46. package/src/useDarkMode/useDarkMode.ts +92 -0
  47. package/src/useDebounceCallback/index.ts +2 -0
  48. package/src/useDebounceCallback/useDebounceCallback.ts +115 -0
  49. package/src/useDebounceValue/index.ts +1 -0
  50. package/src/useDebounceValue/useDebounceValue.ts +67 -0
  51. package/src/useDocumentTitle/index.ts +1 -0
  52. package/src/useDocumentTitle/useDocumentTitle.ts +43 -0
  53. package/src/useEventCallback/index.ts +1 -0
  54. package/src/useEventCallback/useEventCallback.ts +40 -0
  55. package/src/useEventListener/index.ts +1 -0
  56. package/src/useEventListener/useEventListener.ts +121 -0
  57. package/src/useHover/index.ts +1 -0
  58. package/src/useHover/useHover.ts +37 -0
  59. package/src/useIntersectionObserver/index.ts +1 -0
  60. package/src/useIntersectionObserver/useIntersectionObserver.ts +186 -0
  61. package/src/useInterval/index.ts +1 -0
  62. package/src/useInterval/useInterval.ts +43 -0
  63. package/src/useIsClient/index.ts +1 -0
  64. package/src/useIsClient/useIsClient.ts +22 -0
  65. package/src/useIsMobile/index.ts +2 -0
  66. package/src/useIsMobile/types.ts +3 -0
  67. package/src/useIsMobile/useIsMobile.tsx +23 -0
  68. package/src/useIsMounted/index.ts +1 -0
  69. package/src/useIsMounted/useIsMounted.ts +15 -0
  70. package/src/useIsomorphicLayoutEffect/index.ts +1 -0
  71. package/src/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts +17 -0
  72. package/src/useLocalStorage/index.ts +1 -0
  73. package/src/useLocalStorage/useLocalStorage.ts +191 -0
  74. package/src/useLockBody/index.ts +1 -0
  75. package/src/useLockBody/useLockBody.tsx +12 -0
  76. package/src/useMap/index.ts +1 -0
  77. package/src/useMap/useMap.ts +83 -0
  78. package/src/useMediaQuery/index.ts +1 -0
  79. package/src/useMediaQuery/useMediaQuery.ts +83 -0
  80. package/src/useOnClickOutside/index.ts +1 -0
  81. package/src/useOnClickOutside/useOnClickOutside.ts +61 -0
  82. package/src/useQueryString/index.ts +1 -0
  83. package/src/useQueryString/useQueryString.ts +26 -0
  84. package/src/useReadLocalStorage/index.ts +1 -0
  85. package/src/useReadLocalStorage/useReadLocalStorage.ts +122 -0
  86. package/src/useResizeObserver/index.ts +1 -0
  87. package/src/useResizeObserver/useResizeObserver.ts +131 -0
  88. package/src/useScreen/index.ts +1 -0
  89. package/src/useScreen/useScreen.ts +106 -0
  90. package/src/useScript/index.ts +1 -0
  91. package/src/useScript/useScript.ts +142 -0
  92. package/src/useScrollLock/index.ts +1 -0
  93. package/src/useScrollLock/useScrollLock.ts +141 -0
  94. package/src/useSessionStorage/index.ts +1 -0
  95. package/src/useSessionStorage/useSessionStorage.ts +191 -0
  96. package/src/useStep/index.ts +1 -0
  97. package/src/useStep/useStep.ts +83 -0
  98. package/src/useTernaryDarkMode/index.ts +1 -0
  99. package/src/useTernaryDarkMode/useTernaryDarkMode.ts +81 -0
  100. package/src/useTimeout/index.ts +1 -0
  101. package/src/useTimeout/useTimeout.ts +44 -0
  102. package/src/useToggle/index.ts +1 -0
  103. package/src/useToggle/useToggle.ts +30 -0
  104. package/src/useUnmount/index.ts +1 -0
  105. package/src/useUnmount/useUnmount.ts +26 -0
  106. package/src/useWindowSize/index.ts +1 -0
  107. package/src/useWindowSize/useWindowSize.ts +100 -0
  108. package/tsconfig.json +10 -0
  109. package/tsconfig.lib.json +33 -0
  110. package/vite.config.ts +62 -0
@@ -0,0 +1,131 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+
3
+ import type { RefObject } from 'react'
4
+
5
+ import { useIsMounted } from '../useIsMounted'
6
+
7
+ /** The size of the observed element. */
8
+ type Size = {
9
+ /** The width of the observed element. */
10
+ width: number | undefined
11
+ /** The height of the observed element. */
12
+ height: number | undefined
13
+ }
14
+
15
+ /** The options for the ResizeObserver. */
16
+ type UseResizeObserverOptions<T extends HTMLElement = HTMLElement> = {
17
+ /** The ref of the element to observe. */
18
+ ref: RefObject<T>
19
+ /**
20
+ * When using `onResize`, the hook doesn't re-render on element size changes; it delegates handling to the provided callback.
21
+ * @default undefined
22
+ */
23
+ onResize?: (size: Size) => void
24
+ /**
25
+ * The box model to use for the ResizeObserver.
26
+ * @default 'content-box'
27
+ */
28
+ box?: 'border-box' | 'content-box' | 'device-pixel-content-box'
29
+ }
30
+
31
+ const initialSize: Size = {
32
+ width: undefined,
33
+ height: undefined,
34
+ }
35
+
36
+ /**
37
+ * Custom hook that observes the size of an element using the [`ResizeObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
38
+ * @template T - The type of the element to observe.
39
+ * @param {UseResizeObserverOptions<T>} options - The options for the ResizeObserver.
40
+ * @returns {Size} - The size of the observed element.
41
+ * @public
42
+ * @see [Documentation](https://usehooks-ts.com/react-hook/use-resize-observer)
43
+ * @example
44
+ * ```tsx
45
+ * const myRef = useRef(null);
46
+ * const { width = 0, height = 0 } = useResizeObserver({
47
+ * ref: myRef,
48
+ * box: 'content-box',
49
+ * });
50
+ *
51
+ * <div ref={myRef}>Hello, world!</div>
52
+ * ```
53
+ */
54
+ export function useResizeObserver<T extends HTMLElement = HTMLElement>(
55
+ options: UseResizeObserverOptions<T>,
56
+ ): Size {
57
+ const { ref, box = 'content-box' } = options
58
+ const [{ width, height }, setSize] = useState<Size>(initialSize)
59
+ const isMounted = useIsMounted()
60
+ const previousSize = useRef<Size>({ ...initialSize })
61
+ const onResize = useRef<((size: Size) => void) | undefined>(undefined)
62
+ onResize.current = options.onResize
63
+
64
+ useEffect(() => {
65
+ if (!ref.current) return
66
+
67
+ if (typeof window === 'undefined' || !('ResizeObserver' in window)) return
68
+
69
+ const observer = new ResizeObserver(([entry]) => {
70
+ const boxProp =
71
+ box === 'border-box'
72
+ ? 'borderBoxSize'
73
+ : box === 'device-pixel-content-box'
74
+ ? 'devicePixelContentBoxSize'
75
+ : 'contentBoxSize'
76
+
77
+ const newWidth = extractSize(entry, boxProp, 'inlineSize')
78
+ const newHeight = extractSize(entry, boxProp, 'blockSize')
79
+
80
+ const hasChanged =
81
+ previousSize.current.width !== newWidth ||
82
+ previousSize.current.height !== newHeight
83
+
84
+ if (hasChanged) {
85
+ const newSize: Size = { width: newWidth, height: newHeight }
86
+ previousSize.current.width = newWidth
87
+ previousSize.current.height = newHeight
88
+
89
+ if (onResize.current) {
90
+ onResize.current(newSize)
91
+ } else {
92
+ if (isMounted()) {
93
+ setSize(newSize)
94
+ }
95
+ }
96
+ }
97
+ })
98
+
99
+ observer.observe(ref.current, { box })
100
+
101
+ return () => {
102
+ observer.disconnect()
103
+ }
104
+ }, [box, ref, isMounted])
105
+
106
+ return { width, height }
107
+ }
108
+
109
+ /** @private */
110
+ type BoxSizesKey = keyof Pick<
111
+ ResizeObserverEntry,
112
+ 'borderBoxSize' | 'contentBoxSize' | 'devicePixelContentBoxSize'
113
+ >
114
+
115
+ function extractSize(
116
+ entry: ResizeObserverEntry,
117
+ box: BoxSizesKey,
118
+ sizeType: keyof ResizeObserverSize,
119
+ ): number | undefined {
120
+ if (!entry[box]) {
121
+ if (box === 'contentBoxSize') {
122
+ return entry.contentRect[sizeType === 'inlineSize' ? 'width' : 'height']
123
+ }
124
+ return undefined
125
+ }
126
+
127
+ return Array.isArray(entry[box])
128
+ ? entry[box][0][sizeType]
129
+ : // @ts-ignore Support Firefox's non-standard behavior
130
+ (entry[box][sizeType] as number)
131
+ }
@@ -0,0 +1 @@
1
+ export * from './useScreen'
@@ -0,0 +1,106 @@
1
+ import { useState } from 'react'
2
+
3
+ import { useDebounceCallback } from '../useDebounceCallback'
4
+ import { useEventListener } from '../useEventListener'
5
+ import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect'
6
+
7
+ /**
8
+ * The hooks options.
9
+ * @template InitializeWithValue - If `true` (default), the hook will initialize reading the screen dimensions. In SSR, you should set it to `false`, returning `undefined` initially.
10
+ */
11
+ type UseScreenOptions<InitializeWithValue extends boolean | undefined> = {
12
+ /**
13
+ * If `true` (default), the hook will initialize reading the screen dimensions. In SSR, you should set it to `false`, returning `undefined` initially.
14
+ * @default true
15
+ */
16
+ initializeWithValue: InitializeWithValue
17
+ /**
18
+ * The delay in milliseconds before the state is updated (disabled by default for retro-compatibility).
19
+ * @default undefined
20
+ */
21
+ debounceDelay?: number
22
+ }
23
+
24
+ const IS_SERVER = typeof window === 'undefined'
25
+
26
+ // SSR version of useScreen.
27
+ export function useScreen(options: UseScreenOptions<false>): Screen | undefined
28
+ // CSR version of useScreen.
29
+ export function useScreen(options?: Partial<UseScreenOptions<true>>): Screen
30
+ /**
31
+ * Custom hook that tracks the [`screen`](https://developer.mozilla.org/en-US/docs/Web/API/Window/screen) dimensions and properties.
32
+ * @param {?UseScreenOptions} [options] - The options for customizing the behavior of the hook (optional).
33
+ * @returns {Screen | undefined} The current `Screen` object representing the screen dimensions and properties, or `undefined` if not available.
34
+ * @public
35
+ * @see [Documentation](https://usehooks-ts.com/react-hook/use-screen)
36
+ * @example
37
+ * ```tsx
38
+ * const currentScreen = useScreen();
39
+ * // Access properties of the current screen, such as width and height.
40
+ * ```
41
+ */
42
+ export function useScreen(
43
+ options: Partial<UseScreenOptions<boolean>> = {},
44
+ ): Screen | undefined {
45
+ let { initializeWithValue = true } = options
46
+ if (IS_SERVER) {
47
+ initializeWithValue = false
48
+ }
49
+
50
+ const readScreen = () => {
51
+ if (IS_SERVER) {
52
+ return undefined
53
+ }
54
+ return window.screen
55
+ }
56
+
57
+ const [screen, setScreen] = useState<Screen | undefined>(() => {
58
+ if (initializeWithValue) {
59
+ return readScreen()
60
+ }
61
+ return undefined
62
+ })
63
+
64
+ const debouncedSetScreen = useDebounceCallback(
65
+ setScreen,
66
+ options.debounceDelay,
67
+ )
68
+
69
+ // Handles the resize event of the window.
70
+ function handleSize() {
71
+ const newScreen = readScreen()
72
+ const setSize = options.debounceDelay ? debouncedSetScreen : setScreen
73
+
74
+ if (newScreen) {
75
+ // Create a shallow clone to trigger a re-render (#280).
76
+ const {
77
+ width,
78
+ height,
79
+ availHeight,
80
+ availWidth,
81
+ colorDepth,
82
+ orientation,
83
+ pixelDepth,
84
+ } = newScreen
85
+
86
+ setSize({
87
+ width,
88
+ height,
89
+ availHeight,
90
+ availWidth,
91
+ colorDepth,
92
+ orientation,
93
+ pixelDepth,
94
+ })
95
+ }
96
+ }
97
+
98
+ useEventListener('resize', handleSize)
99
+
100
+ // Set size at the first client-side load
101
+ useIsomorphicLayoutEffect(() => {
102
+ handleSize()
103
+ }, [])
104
+
105
+ return screen
106
+ }
@@ -0,0 +1 @@
1
+ export * from './useScript'
@@ -0,0 +1,142 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ /** Script loading status. */
4
+ type UseScriptStatus = 'idle' | 'loading' | 'ready' | 'error'
5
+
6
+ /** Hook options. */
7
+ type UseScriptOptions = {
8
+ /** If `true`, prevents the script from being loaded (optional). */
9
+ shouldPreventLoad?: boolean
10
+ /** If `true`, removes the script from the DOM when the component unmounts (optional). */
11
+ removeOnUnmount?: boolean
12
+ /** Script's `id` (optional). */
13
+ id?: string
14
+ }
15
+
16
+ // Cached script statuses
17
+ const cachedScriptStatuses = new Map<string, UseScriptStatus | undefined>()
18
+
19
+ /**
20
+ * Gets the script element with the specified source URL.
21
+ * @param {string} src - The source URL of the script to get.
22
+ * @returns {{ node: HTMLScriptElement | null, status: UseScriptStatus | undefined }} The script element and its loading status.
23
+ * @public
24
+ * @example
25
+ * ```tsx
26
+ * const script = getScriptNode(src);
27
+ * ```
28
+ */
29
+ function getScriptNode(src: string) {
30
+ const node: HTMLScriptElement | null = document.querySelector(
31
+ `script[src="${src}"]`,
32
+ )
33
+ const status = node?.getAttribute('data-status') as
34
+ | UseScriptStatus
35
+ | undefined
36
+
37
+ return {
38
+ node,
39
+ status,
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Custom hook that dynamically loads scripts and tracking their loading status.
45
+ * @param {string | null} src - The source URL of the script to load. Set to `null` or omit to prevent loading (optional).
46
+ * @param {UseScriptOptions} [options] - Additional options for controlling script loading (optional).
47
+ * @returns {UseScriptStatus} The status of the script loading, which can be one of 'idle', 'loading', 'ready', or 'error'.
48
+ * @see [Documentation](https://usehooks-ts.com/react-hook/use-script)
49
+ * @example
50
+ * const scriptStatus = useScript('https://example.com/script.js', { removeOnUnmount: true });
51
+ * // Access the status of the script loading (e.g., 'loading', 'ready', 'error').
52
+ */
53
+ export function useScript(
54
+ src: string | null,
55
+ options?: UseScriptOptions,
56
+ ): UseScriptStatus {
57
+ const [status, setStatus] = useState<UseScriptStatus>(() => {
58
+ if (!src || options?.shouldPreventLoad) {
59
+ return 'idle'
60
+ }
61
+
62
+ if (typeof window === 'undefined') {
63
+ // SSR Handling - always return 'loading'
64
+ return 'loading'
65
+ }
66
+
67
+ return cachedScriptStatuses.get(src) ?? 'loading'
68
+ })
69
+
70
+ useEffect(() => {
71
+ if (!src || options?.shouldPreventLoad) {
72
+ return
73
+ }
74
+
75
+ const cachedScriptStatus = cachedScriptStatuses.get(src)
76
+ if (cachedScriptStatus === 'ready' || cachedScriptStatus === 'error') {
77
+ // If the script is already cached, set its status immediately
78
+ setStatus(cachedScriptStatus)
79
+ return
80
+ }
81
+
82
+ // Fetch existing script element by src
83
+ // It may have been added by another instance of this hook
84
+ const script = getScriptNode(src)
85
+ let scriptNode = script.node
86
+
87
+ if (!scriptNode) {
88
+ // Create script element and add it to document body
89
+ scriptNode = document.createElement('script')
90
+ scriptNode.src = src
91
+ scriptNode.async = true
92
+ if (options?.id) {
93
+ scriptNode.id = options.id
94
+ }
95
+ scriptNode.setAttribute('data-status', 'loading')
96
+ document.body.appendChild(scriptNode)
97
+
98
+ // Store status in attribute on script
99
+ // This can be read by other instances of this hook
100
+ const setAttributeFromEvent = (event: Event) => {
101
+ const scriptStatus: UseScriptStatus =
102
+ event.type === 'load' ? 'ready' : 'error'
103
+
104
+ scriptNode?.setAttribute('data-status', scriptStatus)
105
+ }
106
+
107
+ scriptNode.addEventListener('load', setAttributeFromEvent)
108
+ scriptNode.addEventListener('error', setAttributeFromEvent)
109
+ } else {
110
+ // Grab existing script status from attribute and set to state.
111
+ setStatus(script.status ?? cachedScriptStatus ?? 'loading')
112
+ }
113
+
114
+ // Script event handler to update status in state
115
+ // Note: Even if the script already exists we still need to add
116
+ // event handlers to update the state for *this* hook instance.
117
+ const setStateFromEvent = (event: Event) => {
118
+ const newStatus = event.type === 'load' ? 'ready' : 'error'
119
+ setStatus(newStatus)
120
+ cachedScriptStatuses.set(src, newStatus)
121
+ }
122
+
123
+ // Add event listeners
124
+ scriptNode.addEventListener('load', setStateFromEvent)
125
+ scriptNode.addEventListener('error', setStateFromEvent)
126
+
127
+ // Remove event listeners on cleanup
128
+ return () => {
129
+ if (scriptNode) {
130
+ scriptNode.removeEventListener('load', setStateFromEvent)
131
+ scriptNode.removeEventListener('error', setStateFromEvent)
132
+ }
133
+
134
+ if (scriptNode && options?.removeOnUnmount) {
135
+ scriptNode.remove()
136
+ cachedScriptStatuses.delete(src)
137
+ }
138
+ }
139
+ }, [src, options?.shouldPreventLoad, options?.removeOnUnmount, options?.id])
140
+
141
+ return status
142
+ }
@@ -0,0 +1 @@
1
+ export * from './useScrollLock'
@@ -0,0 +1,141 @@
1
+ import { useRef, useState } from 'react'
2
+
3
+ import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect'
4
+
5
+ /** Hook options. */
6
+ type UseScrollLockOptions = {
7
+ /**
8
+ * Whether to lock the scroll initially.
9
+ * @default true
10
+ */
11
+ autoLock?: boolean
12
+ /**
13
+ * The target element to lock the scroll (default is the body element).
14
+ * @default document.body
15
+ */
16
+ lockTarget?: HTMLElement | string
17
+ /**
18
+ * Whether to prevent width reflow when locking the scroll.
19
+ * @default true
20
+ */
21
+ widthReflow?: boolean
22
+ }
23
+
24
+ /** Hook return type. */
25
+ type UseScrollLockReturn = {
26
+ /** Whether the scroll is locked. */
27
+ isLocked: boolean
28
+ /** Lock the scroll. */
29
+ lock: () => void
30
+ /** Unlock the scroll. */
31
+ unlock: () => void
32
+ }
33
+
34
+ type OriginalStyle = {
35
+ overflow: CSSStyleDeclaration['overflow']
36
+ paddingRight: CSSStyleDeclaration['paddingRight']
37
+ }
38
+
39
+ const IS_SERVER = typeof window === 'undefined'
40
+
41
+ /**
42
+ * A custom hook that locks and unlocks scroll.
43
+ * @param {UseScrollLockOptions} [options] - Options to configure the hook, by default it will lock the scroll automatically.
44
+ * @returns {UseScrollLockReturn} - An object containing the lock and unlock functions.
45
+ * @public
46
+ * @see [Documentation](https://usehooks-ts.com/react-hook/use-scroll-lock)
47
+ * @example
48
+ * ```tsx
49
+ * // Lock the scroll when the modal is mounted, and unlock it when it's unmounted
50
+ * useScrollLock()
51
+ * ```
52
+ * @example
53
+ * ```tsx
54
+ * // Manually lock and unlock the scroll
55
+ * const { lock, unlock } = useScrollLock({ autoLock: false })
56
+ *
57
+ * return (
58
+ * <div>
59
+ * <button onClick={lock}>Lock</button>
60
+ * <button onClick={unlock}>Unlock</button>
61
+ * </div>
62
+ * )
63
+ * ```
64
+ */
65
+ export function useScrollLock(
66
+ options: UseScrollLockOptions = {},
67
+ ): UseScrollLockReturn {
68
+ const { autoLock = true, lockTarget, widthReflow = true } = options
69
+ const [isLocked, setIsLocked] = useState(false)
70
+ const target = useRef<HTMLElement | null>(null)
71
+ const originalStyle = useRef<OriginalStyle | null>(null)
72
+
73
+ const lock = () => {
74
+ if (target.current) {
75
+ const { overflow, paddingRight } = target.current.style
76
+
77
+ // Save the original styles
78
+ originalStyle.current = { overflow, paddingRight }
79
+
80
+ // Prevent width reflow
81
+ if (widthReflow) {
82
+ // Use window inner width if body is the target as global scrollbar isn't part of the document
83
+ const offsetWidth =
84
+ target.current === document.body
85
+ ? window.innerWidth
86
+ : target.current.offsetWidth
87
+ // Get current computed padding right in pixels
88
+ const currentPaddingRight =
89
+ parseInt(window.getComputedStyle(target.current).paddingRight, 10) ||
90
+ 0
91
+
92
+ const scrollbarWidth = offsetWidth - target.current.scrollWidth
93
+ target.current.style.paddingRight = `${scrollbarWidth + currentPaddingRight}px`
94
+ }
95
+
96
+ // Lock the scroll
97
+ target.current.style.overflow = 'hidden'
98
+
99
+ setIsLocked(true)
100
+ }
101
+ }
102
+
103
+ const unlock = () => {
104
+ if (target.current && originalStyle.current) {
105
+ target.current.style.overflow = originalStyle.current.overflow
106
+
107
+ // Only reset padding right if we changed it
108
+ if (widthReflow) {
109
+ target.current.style.paddingRight = originalStyle.current.paddingRight
110
+ }
111
+ }
112
+
113
+ setIsLocked(false)
114
+ }
115
+
116
+ useIsomorphicLayoutEffect(() => {
117
+ if (IS_SERVER) return
118
+
119
+ if (lockTarget) {
120
+ target.current =
121
+ typeof lockTarget === 'string'
122
+ ? document.querySelector(lockTarget)
123
+ : lockTarget
124
+ }
125
+
126
+ if (!target.current) {
127
+ target.current = document.body
128
+ }
129
+
130
+ if (autoLock) {
131
+ lock()
132
+ }
133
+
134
+ return () => {
135
+ unlock()
136
+ }
137
+ // eslint-disable-next-line react-hooks/exhaustive-deps
138
+ }, [autoLock, lockTarget, widthReflow])
139
+
140
+ return { isLocked, lock, unlock }
141
+ }
@@ -0,0 +1 @@
1
+ export * from './useSessionStorage'