@alessiofrittoli/react-hooks 2.0.1 → 3.1.0

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/README.md CHANGED
@@ -267,9 +267,13 @@ Get Document Media matches and listen for changes.
267
267
 
268
268
  <summary style="cursor:pointer">Parameters</summary>
269
269
 
270
- | Parameter | Type | Description |
271
- |-----------|----------|-------------|
272
- | `query` | `string` | A string specifying the media query to parse into a `MediaQueryList`. |
270
+ | Parameter | Type | Default | Description |
271
+ |-----------|----------|---------|-------------|
272
+ | `query` | `string` | - | A string specifying the media query to parse into a `MediaQueryList`. |
273
+ | `options` | `UseMediaQueryOptions\|UseMediaQueryStateOptions` | - | An object defining custom options. |
274
+ | `options.updateState` | `boolean` | `true` | Indicates whether the hook will dispatch a React state update when the given `query` change event get dispatched. |
275
+ | `options.onChange` | `OnChangeHandler` | - | A custom callback that will be invoked on initial page load and when the given `query` change event get dispatched. |
276
+ | | | | This callback is required if `updateState` is set to `false`. |
273
277
 
274
278
  </details>
275
279
 
@@ -279,10 +283,10 @@ Get Document Media matches and listen for changes.
279
283
 
280
284
  <summary style="cursor:pointer">Returns</summary>
281
285
 
282
- Type: `boolean`
286
+ Type: `boolean|void`
283
287
 
284
- - `true` if the document currently matches the media query list.
285
- - `false` otherwise.
288
+ - `true` or `false` if the document currently matches the media query list or not.
289
+ - `void` if `updateState` is set to `false`.
286
290
 
287
291
  </details>
288
292
 
@@ -300,6 +304,21 @@ import { useMediaQuery } from '@alessiofrittoli/react-hooks'
300
304
  const isDarkOS = useMediaQuery( '(prefers-color-scheme: dark)' )
301
305
  ```
302
306
 
307
+ ---
308
+
309
+ ###### Listen changes with no state updates
310
+
311
+ ```tsx
312
+ import { useMediaQuery } from '@alessiofrittoli/react-hooks'
313
+
314
+ useMediaQuery( '(prefers-color-scheme: dark)', {
315
+ updateState: false,
316
+ onChange( matches ) {
317
+ console.log( 'is dark OS?', matches )
318
+ }
319
+ } )
320
+ ```
321
+
303
322
  </details>
304
323
 
305
324
  ---
@@ -682,9 +701,17 @@ Check if the given target Element is intersecting with an ancestor Element or wi
682
701
  | | | - `number` |
683
702
  | | | - `number[]` |
684
703
  | `options.once` | `boolean` | (Optional) By setting this to `true` the observer will be disconnected after the target Element enters the viewport. |
685
- | `options.initial` | `boolean` | (Optional) Initial value. Default: `false`. |
704
+ | `options.initial` | `boolean` | (Optional) Initial value. This value is used while server rendering then will be updated in the client based on target visibility. Default: `false`. |
686
705
  | `options.enable` | `boolean` | (Optional) Defines the initial observation activity. Use the returned `setEnabled` to update this state. Default: `true`. |
687
- | `options.onStart` | `OnStartHandler` | (Optional) A custom callback executed when target element's visibility has crossed one or more thresholds. |
706
+ | `options.onIntersect` | `OnIntersectStateHandler` | (Optional) A custom callback executed when target element's visibility has crossed one or more thresholds. |
707
+ | | | This callback is awaited before any state update. |
708
+ | | | If an error is thrown the React State update won't be fired. |
709
+ | | | ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation. |
710
+ | `options.onEnter` | `OnIntersectHandler` | (Optional) A custom callback executed when target element's visibility has crossed one or more thresholds. |
711
+ | | | This callback is awaited before any state update. |
712
+ | | | If an error is thrown the React State update won't be fired. |
713
+ | | | ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation. |
714
+ | `options.onExit` | `OnIntersectHandler` | (Optional) A custom callback executed when target element's visibility has crossed one or more thresholds. |
688
715
  | | | This callback is awaited before any state update. |
689
716
  | | | If an error is thrown the React State update won't be fired. |
690
717
  | | | ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation. |
@@ -839,21 +866,63 @@ const OnDemandObservation: React.FC = () => {
839
866
  'use client'
840
867
 
841
868
  import { useRef } from 'react'
842
- import { useInView, type OnStartHandler } from '@alessiofrittoli/react-hooks'
869
+ import { useInView, type OnIntersectStateHandler } from '@alessiofrittoli/react-hooks'
843
870
 
844
871
 
845
872
  const AsyncStartExample: React.FC = () => {
846
873
 
847
874
  const targetRef = useRef<HTMLDivElement>( null )
848
- const onStart = useCallback<OnStartHandler>( async entry => {
875
+ const onIntersect = useCallback<OnIntersectStateHandler>( async ( { entry, isEntering } ) => {
849
876
 
877
+ if ( isEntering ) {
878
+ console.log( 'Delaying state update...' )
879
+ await new Promise( resolve => setTimeout( resolve, 1000 ) ) // Simulate delay
880
+ console.log( 'Async task completed. `inView` will now be updated.' )
881
+ return
882
+ }
883
+
850
884
  console.log( 'Delaying state update...' )
851
885
  await new Promise( resolve => setTimeout( resolve, 1000 ) ) // Simulate delay
852
886
  console.log( 'Async task completed. `inView` will now be updated.' )
853
887
 
854
888
  }, [] )
855
889
 
856
- const { inView } = useInView( targetRef, { onStart } )
890
+ const { inView } = useInView( targetRef, { onIntersect } )
891
+
892
+ return (
893
+ <div
894
+ ref={ targetRef }
895
+ style={ {
896
+ height : 200,
897
+ background : inView ? 'lime' : 'gray',
898
+ } }
899
+ />
900
+ )
901
+ }
902
+ ```
903
+
904
+ ---
905
+
906
+ ###### Execute custom callback when `onEnter` and `onExit`
907
+
908
+ ```tsx
909
+ 'use client'
910
+
911
+ import { useRef } from 'react'
912
+ import { useInView, type OnIntersectHandler } from '@alessiofrittoli/react-hooks'
913
+
914
+
915
+ const AsyncStartExample: React.FC = () => {
916
+
917
+ const targetRef = useRef<HTMLDivElement>( null )
918
+ const onEnter = useCallback<OnIntersectHandler>( async ( { entry } ) => {
919
+ console.log( 'In viewport - ', entry )
920
+ }, [] )
921
+ const onExit = useCallback<OnIntersectHandler>( async ( { entry } ) => {
922
+ console.log( 'Exited viewport - ', entry )
923
+ }, [] )
924
+
925
+ const { inView } = useInView( targetRef, { onEnter, onExit } )
857
926
 
858
927
  return (
859
928
  <div
package/dist/index.d.mts CHANGED
@@ -481,15 +481,50 @@ declare function useEventListener<T extends Record<string, Event>, K extends key
481
481
  */
482
482
  declare const useIsPortrait: () => boolean;
483
483
 
484
+ type OnChangeHandler = (matches: boolean) => void;
485
+ interface CommonOptions {
486
+ /**
487
+ * A custom callback that will be invoked on initial page load and when the given `query` change event get dispatched.
488
+ *
489
+ * @param matches Whether the document currently matches the media query list.
490
+ */
491
+ onChange?: OnChangeHandler;
492
+ }
493
+ interface UseMediaQueryOptions extends CommonOptions {
494
+ /**
495
+ * Indicates whether the hook will dispatch a React state update when the given `query` change event get dispatched.
496
+ *
497
+ */
498
+ updateState: false;
499
+ onChange: OnChangeHandler;
500
+ }
501
+ interface UseMediaQueryStateOptions extends CommonOptions {
502
+ /**
503
+ * Indicates whether the hook will dispatch a React state update when the given `query` change event get dispatched.
504
+ *
505
+ */
506
+ updateState?: true;
507
+ }
484
508
  /**
485
- * Get Document Media matches and listen for changes.
509
+ * Get Document Media matches and dispatch a React state update on MediaQuery changes.
486
510
  *
487
511
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
488
512
  *
489
- * @param query A string specifying the media query to parse into a `MediaQueryList`.
513
+ * @param query A string specifying the media query to parse into a `MediaQueryList`.
514
+ * @param options An object defining custom options. See {@linkcode UseMediaQueryStateOptions} for more info.
515
+ *
490
516
  * @returns A boolean value that returns `true` if the document currently matches the media query list, or `false` if not.
491
517
  */
492
- declare const useMediaQuery: (query: string) => boolean;
518
+ declare function useMediaQuery(query: string, options?: UseMediaQueryStateOptions): boolean;
519
+ /**
520
+ * Get Document Media matches and listen for changes.
521
+ *
522
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
523
+ *
524
+ * @param query A string specifying the media query to parse into a `MediaQueryList`.
525
+ * @param options An object defining custom options. See {@linkcode UseMediaQueryOptions} for more info.
526
+ */
527
+ declare function useMediaQuery(query: string, options?: UseMediaQueryOptions): void;
493
528
 
494
529
  type SetFocusTrap = (target?: HTMLElement) => void;
495
530
  type RestoreFocusTrap = () => void;
@@ -507,6 +542,42 @@ declare const useFocusTrap: (target?: React.RefObject<HTMLElement | null>) => re
507
542
 
508
543
  type MarginValue = `${number}${'px' | '%'}`;
509
544
  type MarginType = (MarginValue | `${MarginValue} ${MarginValue}` | `${MarginValue} ${MarginValue} ${MarginValue}` | `${MarginValue} ${MarginValue} ${MarginValue} ${MarginValue}`);
545
+ type IntersectionState = ({
546
+ /**
547
+ * Indicates whether the {@link IntersectionObserverEntry} is entering the viewport.
548
+ *
549
+ */
550
+ isEntering: true;
551
+ /**
552
+ * Indicates whether the {@link IntersectionObserverEntry} is exiting the viewport.
553
+ *
554
+ */
555
+ isExiting: false;
556
+ } | {
557
+ /**
558
+ * Indicates whether the {@link IntersectionObserverEntry} is entering the viewport.
559
+ *
560
+ */
561
+ isEntering: false;
562
+ /**
563
+ * Indicates whether the {@link IntersectionObserverEntry} is exiting the viewport.
564
+ *
565
+ */
566
+ isExiting: true;
567
+ });
568
+ interface OnIntersectHandlerOptions {
569
+ /**
570
+ * The intersecting {@link IntersectionObserverEntry}.
571
+ *
572
+ */
573
+ entry: IntersectionObserverEntry;
574
+ /**
575
+ * The current {@link IntersectionObserver} instance.
576
+ *
577
+ */
578
+ observer: IntersectionObserver;
579
+ }
580
+ type OnIntersectStateHandlerOptions = OnIntersectHandlerOptions & IntersectionState;
510
581
  /**
511
582
  * A custom callback executed when target element's visibility has crossed one or more thresholds.
512
583
  *
@@ -516,10 +587,21 @@ type MarginType = (MarginValue | `${MarginValue} ${MarginValue}` | `${MarginValu
516
587
  *
517
588
  * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
518
589
  *
519
- * @param entry The intersecting {@link IntersectionObserverEntry}.
520
- * @param observer The current {@link IntersectionObserver} instance.
590
+ * @param options An object defining the current intersection entry and observer.
521
591
  */
522
- type OnStartHandler = (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void | Promise<void>;
592
+ type OnIntersectHandler = (options: OnIntersectHandlerOptions) => void | Promise<void>;
593
+ /**
594
+ * A custom callback executed when target element's visibility has crossed one or more thresholds.
595
+ *
596
+ * This callback is awaited before any state update.
597
+ *
598
+ * If an error is thrown the React State update won't be fired.
599
+ *
600
+ * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
601
+ *
602
+ * @param options An object defining the current intersection entry, observer and entry state.
603
+ */
604
+ type OnIntersectStateHandler = (options: OnIntersectStateHandlerOptions) => void | Promise<void>;
523
605
  interface UseInViewOptions {
524
606
  /**
525
607
  * Identifies the {@link Element} or {@link Document} whose bounds are treated as the bounding box of the viewport for the Element which is the observer's target.
@@ -554,9 +636,9 @@ interface UseInViewOptions {
554
636
  */
555
637
  once?: boolean;
556
638
  /**
557
- * Initial value.
639
+ * Initial value. This value is used while server rendering then will be updated in the client based on target visibility.
558
640
  *
559
- * @default true
641
+ * @default false
560
642
  */
561
643
  initial?: boolean;
562
644
  /**
@@ -574,10 +656,33 @@ interface UseInViewOptions {
574
656
  *
575
657
  * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
576
658
  *
577
- * @param entry The intersecting {@link IntersectionObserverEntry}.
578
- * @param observer The current {@link IntersectionObserver} instance.
659
+ * @param options An object defining the current intersection entry, observer and entry state.
660
+ */
661
+ onIntersect?: OnIntersectStateHandler;
662
+ /**
663
+ * A custom callback executed when target element is entering the viewport.
664
+ *
665
+ * This callback is awaited before any state update.
666
+ *
667
+ * If an error is thrown the React State update won't be fired.
668
+ *
669
+ * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
670
+ *
671
+ * @param options An object defining the current intersection entry and observer.
579
672
  */
580
- onStart?: OnStartHandler;
673
+ onEnter?: OnIntersectHandler;
674
+ /**
675
+ * A custom callback executed when target element is exiting the viewport.
676
+ *
677
+ * This callback is awaited before any state update.
678
+ *
679
+ * If an error is thrown the React State update won't be fired.
680
+ *
681
+ * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
682
+ *
683
+ * @param options An object defining the current intersection entry and observer.
684
+ */
685
+ onExit?: OnIntersectHandler;
581
686
  }
582
687
  interface UseInViewReturnType {
583
688
  /**
@@ -585,6 +690,11 @@ interface UseInViewReturnType {
585
690
  *
586
691
  */
587
692
  inView: boolean;
693
+ /**
694
+ * Indicates whether the target Element is exiting the viewport.
695
+ *
696
+ */
697
+ isExiting: boolean;
588
698
  /**
589
699
  * Indicates whether the target Element is being observed or not.
590
700
  *
@@ -626,6 +736,13 @@ declare const useInView: (target: React.RefObject<Element | null>, options?: Use
626
736
  */
627
737
  declare const useScrollBlock: (target?: React.RefObject<HTMLElement | null>) => readonly [() => void, () => void];
628
738
 
739
+ /**
740
+ * Modified version of `useEffect` that only run once on intial load.
741
+ *
742
+ * @param effect Imperative function that can return a cleanup function.
743
+ */
744
+ declare const useEffectOnce: (effect: React.EffectCallback) => void;
745
+
629
746
  /**
630
747
  * Check if the React Hook or Component where this hook is executed is running in a browser environment.
631
748
  *
@@ -937,4 +1054,4 @@ declare function useTimeout<T extends readonly unknown[]>(callback: TimerHandler
937
1054
  */
938
1055
  declare const useLightTimeout: <T extends readonly unknown[]>(callback: TimerHandler<T>, options?: BasicTimerOptions<T>) => void;
939
1056
 
940
- export { type AddEventListenerOptions, type BasicTimerOptions, type CommonListenerOptions, type CustomEventListenerOptions, type DocumentEventListener, type DocumentListenerOptions, type ElementEventListener, type ElementListenerOptions, type ListenerOptions, type MarginType, type MarginValue, type MediaQueryChangeListener, type MediaQueryEventListener, type MediaQueryListenerOptions, type OnStartHandler, type StartTimer, type StateTimerOptions, type StateTimerReturnType, type StopTimer, type TimerHandler, type TimerId, type TimerOptions, type TimerReturnType, type UseDarkModeOptions, type UseDarkModeOutput, type UseInViewOptions, type UseInViewReturnType, type UseIntervalWhenVisibleReturnType, type UseIntervalWhenVisibleStateReturnType, type WindowEventListener, type WindowListenerOptions, useDarkMode, useDebounce, useEventListener, useFocusTrap, useInView, useInterval, useIntervalWhenVisible, useIsClient, useIsFirstRender, useIsPortrait, useLightInterval, useLightTimeout, useLocalStorage, useMediaQuery, useScrollBlock, useSessionStorage, useStorage, useTimeout, useUpdateEffect };
1057
+ export { type AddEventListenerOptions, type BasicTimerOptions, type CommonListenerOptions, type CustomEventListenerOptions, type DocumentEventListener, type DocumentListenerOptions, type ElementEventListener, type ElementListenerOptions, type IntersectionState, type ListenerOptions, type MarginType, type MarginValue, type MediaQueryChangeListener, type MediaQueryEventListener, type MediaQueryListenerOptions, type OnChangeHandler, type OnIntersectHandler, type OnIntersectStateHandler, type StartTimer, type StateTimerOptions, type StateTimerReturnType, type StopTimer, type TimerHandler, type TimerId, type TimerOptions, type TimerReturnType, type UseDarkModeOptions, type UseDarkModeOutput, type UseInViewOptions, type UseInViewReturnType, type UseIntervalWhenVisibleReturnType, type UseIntervalWhenVisibleStateReturnType, type UseMediaQueryOptions, type UseMediaQueryStateOptions, type WindowEventListener, type WindowListenerOptions, useDarkMode, useDebounce, useEffectOnce, useEventListener, useFocusTrap, useInView, useInterval, useIntervalWhenVisible, useIsClient, useIsFirstRender, useIsPortrait, useLightInterval, useLightTimeout, useLocalStorage, useMediaQuery, useScrollBlock, useSessionStorage, useStorage, useTimeout, useUpdateEffect };
package/dist/index.d.ts CHANGED
@@ -481,15 +481,50 @@ declare function useEventListener<T extends Record<string, Event>, K extends key
481
481
  */
482
482
  declare const useIsPortrait: () => boolean;
483
483
 
484
+ type OnChangeHandler = (matches: boolean) => void;
485
+ interface CommonOptions {
486
+ /**
487
+ * A custom callback that will be invoked on initial page load and when the given `query` change event get dispatched.
488
+ *
489
+ * @param matches Whether the document currently matches the media query list.
490
+ */
491
+ onChange?: OnChangeHandler;
492
+ }
493
+ interface UseMediaQueryOptions extends CommonOptions {
494
+ /**
495
+ * Indicates whether the hook will dispatch a React state update when the given `query` change event get dispatched.
496
+ *
497
+ */
498
+ updateState: false;
499
+ onChange: OnChangeHandler;
500
+ }
501
+ interface UseMediaQueryStateOptions extends CommonOptions {
502
+ /**
503
+ * Indicates whether the hook will dispatch a React state update when the given `query` change event get dispatched.
504
+ *
505
+ */
506
+ updateState?: true;
507
+ }
484
508
  /**
485
- * Get Document Media matches and listen for changes.
509
+ * Get Document Media matches and dispatch a React state update on MediaQuery changes.
486
510
  *
487
511
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
488
512
  *
489
- * @param query A string specifying the media query to parse into a `MediaQueryList`.
513
+ * @param query A string specifying the media query to parse into a `MediaQueryList`.
514
+ * @param options An object defining custom options. See {@linkcode UseMediaQueryStateOptions} for more info.
515
+ *
490
516
  * @returns A boolean value that returns `true` if the document currently matches the media query list, or `false` if not.
491
517
  */
492
- declare const useMediaQuery: (query: string) => boolean;
518
+ declare function useMediaQuery(query: string, options?: UseMediaQueryStateOptions): boolean;
519
+ /**
520
+ * Get Document Media matches and listen for changes.
521
+ *
522
+ * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
523
+ *
524
+ * @param query A string specifying the media query to parse into a `MediaQueryList`.
525
+ * @param options An object defining custom options. See {@linkcode UseMediaQueryOptions} for more info.
526
+ */
527
+ declare function useMediaQuery(query: string, options?: UseMediaQueryOptions): void;
493
528
 
494
529
  type SetFocusTrap = (target?: HTMLElement) => void;
495
530
  type RestoreFocusTrap = () => void;
@@ -507,6 +542,42 @@ declare const useFocusTrap: (target?: React.RefObject<HTMLElement | null>) => re
507
542
 
508
543
  type MarginValue = `${number}${'px' | '%'}`;
509
544
  type MarginType = (MarginValue | `${MarginValue} ${MarginValue}` | `${MarginValue} ${MarginValue} ${MarginValue}` | `${MarginValue} ${MarginValue} ${MarginValue} ${MarginValue}`);
545
+ type IntersectionState = ({
546
+ /**
547
+ * Indicates whether the {@link IntersectionObserverEntry} is entering the viewport.
548
+ *
549
+ */
550
+ isEntering: true;
551
+ /**
552
+ * Indicates whether the {@link IntersectionObserverEntry} is exiting the viewport.
553
+ *
554
+ */
555
+ isExiting: false;
556
+ } | {
557
+ /**
558
+ * Indicates whether the {@link IntersectionObserverEntry} is entering the viewport.
559
+ *
560
+ */
561
+ isEntering: false;
562
+ /**
563
+ * Indicates whether the {@link IntersectionObserverEntry} is exiting the viewport.
564
+ *
565
+ */
566
+ isExiting: true;
567
+ });
568
+ interface OnIntersectHandlerOptions {
569
+ /**
570
+ * The intersecting {@link IntersectionObserverEntry}.
571
+ *
572
+ */
573
+ entry: IntersectionObserverEntry;
574
+ /**
575
+ * The current {@link IntersectionObserver} instance.
576
+ *
577
+ */
578
+ observer: IntersectionObserver;
579
+ }
580
+ type OnIntersectStateHandlerOptions = OnIntersectHandlerOptions & IntersectionState;
510
581
  /**
511
582
  * A custom callback executed when target element's visibility has crossed one or more thresholds.
512
583
  *
@@ -516,10 +587,21 @@ type MarginType = (MarginValue | `${MarginValue} ${MarginValue}` | `${MarginValu
516
587
  *
517
588
  * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
518
589
  *
519
- * @param entry The intersecting {@link IntersectionObserverEntry}.
520
- * @param observer The current {@link IntersectionObserver} instance.
590
+ * @param options An object defining the current intersection entry and observer.
521
591
  */
522
- type OnStartHandler = (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void | Promise<void>;
592
+ type OnIntersectHandler = (options: OnIntersectHandlerOptions) => void | Promise<void>;
593
+ /**
594
+ * A custom callback executed when target element's visibility has crossed one or more thresholds.
595
+ *
596
+ * This callback is awaited before any state update.
597
+ *
598
+ * If an error is thrown the React State update won't be fired.
599
+ *
600
+ * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
601
+ *
602
+ * @param options An object defining the current intersection entry, observer and entry state.
603
+ */
604
+ type OnIntersectStateHandler = (options: OnIntersectStateHandlerOptions) => void | Promise<void>;
523
605
  interface UseInViewOptions {
524
606
  /**
525
607
  * Identifies the {@link Element} or {@link Document} whose bounds are treated as the bounding box of the viewport for the Element which is the observer's target.
@@ -554,9 +636,9 @@ interface UseInViewOptions {
554
636
  */
555
637
  once?: boolean;
556
638
  /**
557
- * Initial value.
639
+ * Initial value. This value is used while server rendering then will be updated in the client based on target visibility.
558
640
  *
559
- * @default true
641
+ * @default false
560
642
  */
561
643
  initial?: boolean;
562
644
  /**
@@ -574,10 +656,33 @@ interface UseInViewOptions {
574
656
  *
575
657
  * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
576
658
  *
577
- * @param entry The intersecting {@link IntersectionObserverEntry}.
578
- * @param observer The current {@link IntersectionObserver} instance.
659
+ * @param options An object defining the current intersection entry, observer and entry state.
660
+ */
661
+ onIntersect?: OnIntersectStateHandler;
662
+ /**
663
+ * A custom callback executed when target element is entering the viewport.
664
+ *
665
+ * This callback is awaited before any state update.
666
+ *
667
+ * If an error is thrown the React State update won't be fired.
668
+ *
669
+ * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
670
+ *
671
+ * @param options An object defining the current intersection entry and observer.
579
672
  */
580
- onStart?: OnStartHandler;
673
+ onEnter?: OnIntersectHandler;
674
+ /**
675
+ * A custom callback executed when target element is exiting the viewport.
676
+ *
677
+ * This callback is awaited before any state update.
678
+ *
679
+ * If an error is thrown the React State update won't be fired.
680
+ *
681
+ * ⚠️ Wrap your callback with `useCallback` to avoid unnecessary `IntersectionObserver` recreation.
682
+ *
683
+ * @param options An object defining the current intersection entry and observer.
684
+ */
685
+ onExit?: OnIntersectHandler;
581
686
  }
582
687
  interface UseInViewReturnType {
583
688
  /**
@@ -585,6 +690,11 @@ interface UseInViewReturnType {
585
690
  *
586
691
  */
587
692
  inView: boolean;
693
+ /**
694
+ * Indicates whether the target Element is exiting the viewport.
695
+ *
696
+ */
697
+ isExiting: boolean;
588
698
  /**
589
699
  * Indicates whether the target Element is being observed or not.
590
700
  *
@@ -626,6 +736,13 @@ declare const useInView: (target: React.RefObject<Element | null>, options?: Use
626
736
  */
627
737
  declare const useScrollBlock: (target?: React.RefObject<HTMLElement | null>) => readonly [() => void, () => void];
628
738
 
739
+ /**
740
+ * Modified version of `useEffect` that only run once on intial load.
741
+ *
742
+ * @param effect Imperative function that can return a cleanup function.
743
+ */
744
+ declare const useEffectOnce: (effect: React.EffectCallback) => void;
745
+
629
746
  /**
630
747
  * Check if the React Hook or Component where this hook is executed is running in a browser environment.
631
748
  *
@@ -937,4 +1054,4 @@ declare function useTimeout<T extends readonly unknown[]>(callback: TimerHandler
937
1054
  */
938
1055
  declare const useLightTimeout: <T extends readonly unknown[]>(callback: TimerHandler<T>, options?: BasicTimerOptions<T>) => void;
939
1056
 
940
- export { type AddEventListenerOptions, type BasicTimerOptions, type CommonListenerOptions, type CustomEventListenerOptions, type DocumentEventListener, type DocumentListenerOptions, type ElementEventListener, type ElementListenerOptions, type ListenerOptions, type MarginType, type MarginValue, type MediaQueryChangeListener, type MediaQueryEventListener, type MediaQueryListenerOptions, type OnStartHandler, type StartTimer, type StateTimerOptions, type StateTimerReturnType, type StopTimer, type TimerHandler, type TimerId, type TimerOptions, type TimerReturnType, type UseDarkModeOptions, type UseDarkModeOutput, type UseInViewOptions, type UseInViewReturnType, type UseIntervalWhenVisibleReturnType, type UseIntervalWhenVisibleStateReturnType, type WindowEventListener, type WindowListenerOptions, useDarkMode, useDebounce, useEventListener, useFocusTrap, useInView, useInterval, useIntervalWhenVisible, useIsClient, useIsFirstRender, useIsPortrait, useLightInterval, useLightTimeout, useLocalStorage, useMediaQuery, useScrollBlock, useSessionStorage, useStorage, useTimeout, useUpdateEffect };
1057
+ export { type AddEventListenerOptions, type BasicTimerOptions, type CommonListenerOptions, type CustomEventListenerOptions, type DocumentEventListener, type DocumentListenerOptions, type ElementEventListener, type ElementListenerOptions, type IntersectionState, type ListenerOptions, type MarginType, type MarginValue, type MediaQueryChangeListener, type MediaQueryEventListener, type MediaQueryListenerOptions, type OnChangeHandler, type OnIntersectHandler, type OnIntersectStateHandler, type StartTimer, type StateTimerOptions, type StateTimerReturnType, type StopTimer, type TimerHandler, type TimerId, type TimerOptions, type TimerReturnType, type UseDarkModeOptions, type UseDarkModeOutput, type UseInViewOptions, type UseInViewReturnType, type UseIntervalWhenVisibleReturnType, type UseIntervalWhenVisibleStateReturnType, type UseMediaQueryOptions, type UseMediaQueryStateOptions, type WindowEventListener, type WindowListenerOptions, useDarkMode, useDebounce, useEffectOnce, useEventListener, useFocusTrap, useInView, useInterval, useIntervalWhenVisible, useIsClient, useIsFirstRender, useIsPortrait, useLightInterval, useLightTimeout, useLocalStorage, useMediaQuery, useScrollBlock, useSessionStorage, useStorage, useTimeout, useUpdateEffect };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _react = require('react');var _LocalStorage = require('@alessiofrittoli/web-utils/storage/LocalStorage');var _SessionStorage = require('@alessiofrittoli/web-utils/storage/SessionStorage');var v=(e,r,t="local")=>{let n=_react.useCallback.call(void 0, ()=>_nullishCoalesce((t==="local"?_LocalStorage.LocalStorage:_SessionStorage.SessionStorage).get(e), () => (r)),[t,e,r]),[i,a]=_react.useState.call(void 0, r),o=_react.useCallback.call(void 0, s=>{a(l=>{let u=s instanceof Function?s(l):s;return(typeof window<"u"&&t==="local"?_LocalStorage.LocalStorage:_SessionStorage.SessionStorage).set(e,u),u})},[t,e]);return _react.useEffect.call(void 0, ()=>{a(n())},[n]),[i,o]};var D=(e,r)=>v(e,r,"local");var he=(e,r)=>v(e,r,"session");var w=()=>{let[e,r]=_react.useState.call(void 0, !1);return _react.useEffect.call(void 0, ()=>r(!0),[]),e};var I=()=>{let e=_react.useRef.call(void 0, !0);return e.current?(e.current=!1,!0):e.current};var M=(e,r)=>{let t=I();_react.useEffect.call(void 0, ()=>{if(!t)return e()},r)};var _browserapi = require('@alessiofrittoli/web-utils/browser-api');var E=e=>{let[r,t]=_react.useState.call(void 0, _browserapi.getMediaMatches.call(void 0, e)),n=_react.useCallback.call(void 0, ()=>t(_browserapi.getMediaMatches.call(void 0, e)),[e]);return _react.useEffect.call(void 0, ()=>{let i=window.matchMedia(e);return n(),i.addEventListener("change",n),()=>{i.removeEventListener("change",n)}},[e,n]),r};var Je=(e={})=>{let r=w(),t=E("(prefers-color-scheme: dark)"),{initial:n=t,docClassNames:i=[]}=e,[a,o]=D("dark-mode",n),s=_nullishCoalesce(a, () => (t)),[l,u]=i,c=_react.useRef.call(void 0, {light:"",dark:""});return M(()=>{o(t)},[t,o]),_react.useEffect.call(void 0, ()=>{l&&document.documentElement.classList.toggle(l,s),u&&document.documentElement.classList.toggle(u,!s)},[s,l,u]),_react.useEffect.call(void 0, ()=>{document.head.querySelectorAll('meta[name="theme-color"]').forEach(p=>{let m=p.getAttribute("media"),f=p.getAttribute("content");if(f){if(!m||m==="(prefers-color-scheme: light)"){c.current.light=f;return}c.current.dark=f}})},[]),M(()=>{let p=c.current.dark,m=c.current.light;a&&!p||!a&&!m||document.head.querySelectorAll('meta[name="theme-color"]').forEach(f=>{f.setAttribute("content",a?p:m)})},[a]),{isDarkMode:r?s:!1,isDarkOS:r?t:!1,toggleDarkMode:_react.useCallback.call(void 0, ()=>o(p=>!p),[o]),enableDarkMode:_react.useCallback.call(void 0, ()=>o(!0),[o]),disableDarkMode:_react.useCallback.call(void 0, ()=>o(!1),[o])}};function _e(e,r){let{target:t,query:n,options:i,listener:a,onLoad:o,onCleanUp:s}=r;_react.useEffect.call(void 0, ()=>{let l=Array.isArray(e)?e:[e],u=_nullishCoalesce((n?window.matchMedia(n):t&&"current"in t?t.current:t), () => (window));if(u.addEventListener)return _optionalChain([o, 'optionalCall', _2 => _2()]),l.map(c=>{u.addEventListener(c,a,i)}),()=>{l.map(c=>{u.removeEventListener(c,a,i)}),_optionalChain([s, 'optionalCall', _3 => _3()])}},[e,t,n,i,a,o,s])}var _device = require('@alessiofrittoli/web-utils/device');var ot=()=>E(_device.portraitMediaQuery);var ne=["input","select","textarea","button","[href]",'[tabindex]:not([tabindex="-1"])'].join(", "),ut= exports.useFocusTrap =e=>{let[r,t]=_react.useState.call(void 0, !1),n=_react.useRef.call(void 0, null),i=_react.useCallback.call(void 0, o=>{n.current=document.activeElement;let s=o||_optionalChain([e, 'optionalAccess', _4 => _4.current])||!1;if(s)return t(s)},[e]),a=_react.useCallback.call(void 0, ()=>{_optionalChain([n, 'access', _5 => _5.current, 'optionalAccess', _6 => _6.focus, 'call', _7 => _7()]),t(!1)},[]);return _react.useEffect.call(void 0, ()=>{if(!r)return;let o=s=>{if(s.key!=="Tab")return;let l=Array.from(r.querySelectorAll(ne)),u=l.at(0),c=l.at(-1);if(!s.shiftKey){document.activeElement===c&&(s.preventDefault(),_optionalChain([u, 'optionalAccess', _8 => _8.focus, 'call', _9 => _9()]));return}document.activeElement===u&&(s.preventDefault(),_optionalChain([c, 'optionalAccess', _10 => _10.focus, 'call', _11 => _11()]))};return document.addEventListener("keydown",o),()=>{document.removeEventListener("keydown",o)}},[r]),[i,a]};var pt=(e,r={})=>{let{initial:t=!1,once:n,amount:i,margin:a,root:o,enable:s=!0,onStart:l}=r,u=_react.useRef.call(void 0, !0),[c,p]=_react.useState.call(void 0, t),[m,f]=_react.useState.call(void 0, s),T=_react.useMemo.call(void 0, ()=>{if(!m||typeof IntersectionObserver>"u")return;let F=i==="all"?1:i==="some"?.5:i;try{return new IntersectionObserver(async([y],g)=>{if(!y)return;let k=y.isIntersecting;try{if(await _optionalChain([l, 'optionalCall', _12 => _12(y,g)]),!u.current)return;p(k)}catch(Q){console.error(Q)}k&&n&&g.disconnect()},{root:o||void 0,rootMargin:a,threshold:F})}catch(y){console.error(y)}},[o,a,i,n,m,l]);return _react.useEffect.call(void 0, ()=>{if(u.current=!0,!(!m||!e.current||!T))return T.observe(e.current),()=>{u.current=!1,T.disconnect()}},[e,T,m]),{inView:c,enabled:m,observer:T,setInView:p,setEnabled:f}};var _dom = require('@alessiofrittoli/web-utils/dom');var vt=e=>{let r=_react.useCallback.call(void 0, ()=>_dom.blockScroll.call(void 0, _optionalChain([e, 'optionalAccess', _13 => _13.current])||void 0),[e]),t=_react.useCallback.call(void 0, ()=>_dom.restoreScroll.call(void 0, _optionalChain([e, 'optionalAccess', _14 => _14.current])||void 0),[e]);return[r,t]};var A=(e,r={})=>{let{delay:t=1,args:n}=r;_react.useEffect.call(void 0, ()=>{let i=setTimeout(e,t,...n||[]);return()=>clearTimeout(i)},[t,n,e])};var kt=(e,r=500)=>{let[t,n]=_react.useState.call(void 0, e),i=_react.useMemo.call(void 0, ()=>[e],[e]);return A(n,{delay:r,args:i}),t};function U(e,r={}){let{delay:t=1,args:n,autoplay:i=!0,runOnStart:a=!1,updateState:o=!1}=r,s=_react.useRef.call(void 0, void 0),[l,u]=_react.useState.call(void 0, i),c=_react.useCallback.call(void 0, ()=>s.current?(clearInterval(s.current),s.current=void 0,!0):!1,[]),p=_react.useCallback.call(void 0, ()=>{let f=c();return a&&(n?e(...n):e()),s.current=setInterval(e,t,...n||[]),!f&&o&&u(!0),s.current},[t,n,o,a,e,c]),m=_react.useCallback.call(void 0, ()=>{c()&&o&&u(!1)},[o,c]);return _react.useEffect.call(void 0, ()=>{if(i)return p(),m},[i,p,m]),o?{isActive:l,start:p,stop:m}:{start:p,stop:m}}var Ht=(e,r={})=>{let{delay:t=1,args:n}=r;_react.useEffect.call(void 0, ()=>{let i=setInterval(e,t,...n||[]);return()=>clearInterval(i)},[t,n,e])};function At(e,r={}){let{autoplay:t=!0}=r,n=U(e,{autoplay:!1,...r}),{start:i,stop:a}=n,o=_react.useCallback.call(void 0, ()=>document.hidden?a():i(),[i,a]),s=_react.useCallback.call(void 0, ()=>{if(document.addEventListener("visibilitychange",o),!document.hidden)return i()},[i,o]),l=_react.useCallback.call(void 0, ()=>{a(),document.removeEventListener("visibilitychange",o)},[a,o]);return _react.useEffect.call(void 0, ()=>{if(t)return s(),l},[t,s,l]),{...n,start:s,stop:l}}function $t(e,r={}){let{delay:t=1,args:n,autoplay:i=!0,runOnStart:a=!1,updateState:o=!1}=r,s=_react.useRef.call(void 0, void 0),[l,u]=_react.useState.call(void 0, i),c=_react.useCallback.call(void 0, ()=>s.current?(clearTimeout(s.current),s.current=void 0,!0):!1,[]),p=_react.useCallback.call(void 0, ()=>{let f=c();return a&&(n?e(...n):e()),s.current=setTimeout(()=>{if(s.current=void 0,o&&u(!1),n)return e(...n);e()},t),!f&&o&&u(!0),s.current},[t,n,o,a,e,c]),m=_react.useCallback.call(void 0, ()=>{c()&&o&&u(!1)},[o,c]);return _react.useEffect.call(void 0, ()=>{if(i)return p(),m},[i,p,m]),o?{isActive:l,start:p,stop:m}:{start:p,stop:m}}exports.useDarkMode = Je; exports.useDebounce = kt; exports.useEventListener = _e; exports.useFocusTrap = ut; exports.useInView = pt; exports.useInterval = U; exports.useIntervalWhenVisible = At; exports.useIsClient = w; exports.useIsFirstRender = I; exports.useIsPortrait = ot; exports.useLightInterval = Ht; exports.useLightTimeout = A; exports.useLocalStorage = D; exports.useMediaQuery = E; exports.useScrollBlock = vt; exports.useSessionStorage = he; exports.useStorage = v; exports.useTimeout = $t; exports.useUpdateEffect = M;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _react = require('react');var _LocalStorage = require('@alessiofrittoli/web-utils/storage/LocalStorage');var _SessionStorage = require('@alessiofrittoli/web-utils/storage/SessionStorage');var g=(e,o,t="local")=>{let s=_react.useCallback.call(void 0, ()=>_nullishCoalesce((t==="local"?_LocalStorage.LocalStorage:_SessionStorage.SessionStorage).get(e), () => (o)),[t,e,o]),[i,a]=_react.useState.call(void 0, o),r=_react.useCallback.call(void 0, n=>{a(u=>{let l=n instanceof Function?n(u):n;return(typeof window<"u"&&t==="local"?_LocalStorage.LocalStorage:_SessionStorage.SessionStorage).set(e,l),l})},[t,e]);return _react.useEffect.call(void 0, ()=>{a(s())},[s]),[i,r]};var K=(e,o)=>g(e,o,"local");var we=(e,o)=>g(e,o,"session");var O=()=>{let e=_react.useRef.call(void 0, !0);return e.current?(e.current=!1,!0):e.current};var $e=e=>{let o=O();_react.useEffect.call(void 0, ()=>{if(o)return e()},[])};var W=()=>{let[e,o]=_react.useState.call(void 0, !1);return _react.useEffect.call(void 0, ()=>o(!0),[]),e};var k=(e,o)=>{let t=O();_react.useEffect.call(void 0, ()=>{if(!t)return e()},o)};var _browserapi = require('@alessiofrittoli/web-utils/browser-api');function S(e,o={}){let{updateState:t=!0,onChange:s}=o,[i,a]=_react.useState.call(void 0, _browserapi.getMediaMatches.call(void 0, e)),r=_react.useCallback.call(void 0, ()=>{let n=_browserapi.getMediaMatches.call(void 0, e);t&&a(n),_optionalChain([s, 'optionalCall', _2 => _2(n)])},[e,t,s]);if(_react.useEffect.call(void 0, ()=>{let n=window.matchMedia(e),{matches:u}=n;return t&&a(u),_optionalChain([s, 'optionalCall', _3 => _3(u)]),n.addEventListener("change",r),()=>{n.removeEventListener("change",r)}},[e,t,s,r]),!!t)return i}var st=(e={})=>{let o=W(),t=S("(prefers-color-scheme: dark)"),{initial:s=t,docClassNames:i=[]}=e,[a,r]=K("dark-mode",s),n=_nullishCoalesce(a, () => (t)),[u,l]=i,c=_react.useRef.call(void 0, {light:"",dark:""});return k(()=>{r(t)},[t,r]),_react.useEffect.call(void 0, ()=>{u&&document.documentElement.classList.toggle(u,n),l&&document.documentElement.classList.toggle(l,!n)},[n,u,l]),_react.useEffect.call(void 0, ()=>{document.head.querySelectorAll('meta[name="theme-color"]').forEach(p=>{let d=p.getAttribute("media"),f=p.getAttribute("content");if(f){if(!d||d==="(prefers-color-scheme: light)"){c.current.light=f;return}c.current.dark=f}})},[]),k(()=>{let p=c.current.dark,d=c.current.light;a&&!p||!a&&!d||document.head.querySelectorAll('meta[name="theme-color"]').forEach(f=>{f.setAttribute("content",a?p:d)})},[a]),{isDarkMode:o?n:!1,isDarkOS:o?t:!1,toggleDarkMode:_react.useCallback.call(void 0, ()=>r(p=>!p),[r]),enableDarkMode:_react.useCallback.call(void 0, ()=>r(!0),[r]),disableDarkMode:_react.useCallback.call(void 0, ()=>r(!1),[r])}};function ct(e,o){let{target:t,query:s,options:i,listener:a,onLoad:r,onCleanUp:n}=o;_react.useEffect.call(void 0, ()=>{let u=Array.isArray(e)?e:[e],l=_nullishCoalesce((s?window.matchMedia(s):t&&"current"in t?t.current:t), () => (window));if(l.addEventListener)return _optionalChain([r, 'optionalCall', _4 => _4()]),u.map(c=>{l.addEventListener(c,a,i)}),()=>{u.map(c=>{l.removeEventListener(c,a,i)}),_optionalChain([n, 'optionalCall', _5 => _5()])}},[e,t,s,i,a,r,n])}var _device = require('@alessiofrittoli/web-utils/device');var ft=()=>S(_device.portraitMediaQuery);var ue=["input","select","textarea","button","[href]",'[tabindex]:not([tabindex="-1"])'].join(", "),vt= exports.useFocusTrap =e=>{let[o,t]=_react.useState.call(void 0, !1),s=_react.useRef.call(void 0, null),i=_react.useCallback.call(void 0, r=>{s.current=document.activeElement;let n=r||_optionalChain([e, 'optionalAccess', _6 => _6.current])||!1;if(n)return t(n)},[e]),a=_react.useCallback.call(void 0, ()=>{_optionalChain([s, 'access', _7 => _7.current, 'optionalAccess', _8 => _8.focus, 'call', _9 => _9()]),t(!1)},[]);return _react.useEffect.call(void 0, ()=>{if(!o)return;let r=n=>{if(n.key!=="Tab")return;let u=Array.from(o.querySelectorAll(ue)),l=u.at(0),c=u.at(-1);if(!n.shiftKey){document.activeElement===c&&(n.preventDefault(),_optionalChain([l, 'optionalAccess', _10 => _10.focus, 'call', _11 => _11()]));return}document.activeElement===l&&(n.preventDefault(),_optionalChain([c, 'optionalAccess', _12 => _12.focus, 'call', _13 => _13()]))};return document.addEventListener("keydown",r),()=>{document.removeEventListener("keydown",r)}},[o]),[i,a]};var Ot=(e,o={})=>{let{initial:t=!1,once:s,amount:i,margin:a,root:r,enable:n=!0}=o,{onEnter:u,onExit:l,onIntersect:c}=o,p=_react.useRef.call(void 0, !0),[d,f]=_react.useState.call(void 0, t),[E,q]=_react.useState.call(void 0, n),b=_react.useRef.call(void 0, null),x=_react.useRef.call(void 0, !1),v=_react.useMemo.call(void 0, ()=>{if(!E||typeof IntersectionObserver>"u")return;let N=i==="all"?1:i==="some"?.5:i;try{return new IntersectionObserver(async([y],M)=>{if(!y)return;let T=y.isIntersecting;try{if(x.current=!T&&!!b.current,T&&u&&await u({entry:y,observer:M}),x.current&&l&&await l({entry:y,observer:M}),c&&(T||!T&&b.current!=null)){let L={isEntering:T,isExiting:x.current};await c({entry:y,observer:M,...L})}if(b.current=T,!p.current)return;f(T)}catch(L){console.error(L)}T&&s&&M.disconnect()},{root:r||void 0,rootMargin:a,threshold:N})}catch(y){console.error(y)}},[r,a,i,s,E,u,l,c]);return _react.useEffect.call(void 0, ()=>{if(p.current=!0,!(!E||!e.current||!v))return v.observe(e.current),()=>{p.current=!1,v.disconnect()}},[e,v,E]),{inView:d,enabled:E,observer:v,isExiting:x.current,setInView:f,setEnabled:q}};var _dom = require('@alessiofrittoli/web-utils/dom');var ht=e=>{let o=_react.useCallback.call(void 0, ()=>_dom.blockScroll.call(void 0, _optionalChain([e, 'optionalAccess', _14 => _14.current])||void 0),[e]),t=_react.useCallback.call(void 0, ()=>_dom.restoreScroll.call(void 0, _optionalChain([e, 'optionalAccess', _15 => _15.current])||void 0),[e]);return[o,t]};var j=(e,o={})=>{let{delay:t=1,args:s}=o;_react.useEffect.call(void 0, ()=>{let i=setTimeout(e,t,...s||[]);return()=>clearTimeout(i)},[t,s,e])};var Kt=(e,o=500)=>{let[t,s]=_react.useState.call(void 0, e),i=_react.useMemo.call(void 0, ()=>[e],[e]);return j(s,{delay:o,args:i}),t};function B(e,o={}){let{delay:t=1,args:s,autoplay:i=!0,runOnStart:a=!1,updateState:r=!1}=o,n=_react.useRef.call(void 0, void 0),[u,l]=_react.useState.call(void 0, i),c=_react.useCallback.call(void 0, ()=>n.current?(clearInterval(n.current),n.current=void 0,!0):!1,[]),p=_react.useCallback.call(void 0, ()=>{let f=c();return a&&(s?e(...s):e()),n.current=setInterval(e,t,...s||[]),!f&&r&&l(!0),n.current},[t,s,r,a,e,c]),d=_react.useCallback.call(void 0, ()=>{c()&&r&&l(!1)},[r,c]);return _react.useEffect.call(void 0, ()=>{if(i)return p(),d},[i,p,d]),r?{isActive:u,start:p,stop:d}:{start:p,stop:d}}var jt=(e,o={})=>{let{delay:t=1,args:s}=o;_react.useEffect.call(void 0, ()=>{let i=setInterval(e,t,...s||[]);return()=>clearInterval(i)},[t,s,e])};function zt(e,o={}){let{autoplay:t=!0}=o,s=B(e,{autoplay:!1,...o}),{start:i,stop:a}=s,r=_react.useCallback.call(void 0, ()=>document.hidden?a():i(),[i,a]),n=_react.useCallback.call(void 0, ()=>{if(document.addEventListener("visibilitychange",r),!document.hidden)return i()},[i,r]),u=_react.useCallback.call(void 0, ()=>{a(),document.removeEventListener("visibilitychange",r)},[a,r]);return _react.useEffect.call(void 0, ()=>{if(t)return n(),u},[t,n,u]),{...s,start:n,stop:u}}function Yt(e,o={}){let{delay:t=1,args:s,autoplay:i=!0,runOnStart:a=!1,updateState:r=!1}=o,n=_react.useRef.call(void 0, void 0),[u,l]=_react.useState.call(void 0, i),c=_react.useCallback.call(void 0, ()=>n.current?(clearTimeout(n.current),n.current=void 0,!0):!1,[]),p=_react.useCallback.call(void 0, ()=>{let f=c();return a&&(s?e(...s):e()),n.current=setTimeout(()=>{if(n.current=void 0,r&&l(!1),s)return e(...s);e()},t),!f&&r&&l(!0),n.current},[t,s,r,a,e,c]),d=_react.useCallback.call(void 0, ()=>{c()&&r&&l(!1)},[r,c]);return _react.useEffect.call(void 0, ()=>{if(i)return p(),d},[i,p,d]),r?{isActive:u,start:p,stop:d}:{start:p,stop:d}}exports.useDarkMode = st; exports.useDebounce = Kt; exports.useEffectOnce = $e; exports.useEventListener = ct; exports.useFocusTrap = vt; exports.useInView = Ot; exports.useInterval = B; exports.useIntervalWhenVisible = zt; exports.useIsClient = W; exports.useIsFirstRender = O; exports.useIsPortrait = ft; exports.useLightInterval = jt; exports.useLightTimeout = j; exports.useLocalStorage = K; exports.useMediaQuery = S; exports.useScrollBlock = ht; exports.useSessionStorage = we; exports.useStorage = g; exports.useTimeout = Yt; exports.useUpdateEffect = k;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{useCallback as R,useEffect as j,useState as B}from"react";import{LocalStorage as h}from"@alessiofrittoli/web-utils/storage/LocalStorage";import{SessionStorage as D}from"@alessiofrittoli/web-utils/storage/SessionStorage";var E=(e,r,t="local")=>{let n=R(()=>(t==="local"?h:D).get(e)??r,[t,e,r]),[i,a]=B(r),o=R(s=>{a(l=>{let u=s instanceof Function?s(l):s;return(typeof window<"u"&&t==="local"?h:D).set(e,u),u})},[t,e]);return j(()=>{a(n())},[n]),[i,o]};var w=(e,r)=>E(e,r,"local");var De=(e,r)=>E(e,r,"session");import{useCallback as S,useEffect as V,useRef as Y}from"react";import{useEffect as N,useState as q}from"react";var I=()=>{let[e,r]=q(!1);return N(()=>r(!0),[]),e};import{useRef as P}from"react";var H=()=>{let e=P(!0);return e.current?(e.current=!1,!0):e.current};import{useEffect as z}from"react";var x=(e,r)=>{let t=H();z(()=>{if(!t)return e()},r)};import{useCallback as G,useEffect as J,useState as X}from"react";import{getMediaMatches as C}from"@alessiofrittoli/web-utils/browser-api";var M=e=>{let[r,t]=X(C(e)),n=G(()=>t(C(e)),[e]);return J(()=>{let i=window.matchMedia(e);return n(),i.addEventListener("change",n),()=>{i.removeEventListener("change",n)}},[e,n]),r};var Xe=(e={})=>{let r=I(),t=M("(prefers-color-scheme: dark)"),{initial:n=t,docClassNames:i=[]}=e,[a,o]=w("dark-mode",n),s=a??t,[l,u]=i,c=Y({light:"",dark:""});return x(()=>{o(t)},[t,o]),V(()=>{l&&document.documentElement.classList.toggle(l,s),u&&document.documentElement.classList.toggle(u,!s)},[s,l,u]),V(()=>{document.head.querySelectorAll('meta[name="theme-color"]').forEach(p=>{let m=p.getAttribute("media"),T=p.getAttribute("content");if(T){if(!m||m==="(prefers-color-scheme: light)"){c.current.light=T;return}c.current.dark=T}})},[]),x(()=>{let p=c.current.dark,m=c.current.light;a&&!p||!a&&!m||document.head.querySelectorAll('meta[name="theme-color"]').forEach(T=>{T.setAttribute("content",a?p:m)})},[a]),{isDarkMode:r?s:!1,isDarkOS:r?t:!1,toggleDarkMode:S(()=>o(p=>!p),[o]),enableDarkMode:S(()=>o(!0),[o]),disableDarkMode:S(()=>o(!1),[o])}};import{useEffect as Z}from"react";function et(e,r){let{target:t,query:n,options:i,listener:a,onLoad:o,onCleanUp:s}=r;Z(()=>{let l=Array.isArray(e)?e:[e],u=(n?window.matchMedia(n):t&&"current"in t?t.current:t)??window;if(u.addEventListener)return o?.(),l.map(c=>{u.addEventListener(c,a,i)}),()=>{l.map(c=>{u.removeEventListener(c,a,i)}),s?.()}},[e,t,n,i,a,o,s])}import{portraitMediaQuery as _}from"@alessiofrittoli/web-utils/device";var st=()=>M(_);import{useCallback as K,useEffect as ee,useRef as te,useState as ne}from"react";var re=["input","select","textarea","button","[href]",'[tabindex]:not([tabindex="-1"])'].join(", "),ct=e=>{let[r,t]=ne(!1),n=te(null),i=K(o=>{n.current=document.activeElement;let s=o||e?.current||!1;if(s)return t(s)},[e]),a=K(()=>{n.current?.focus(),t(!1)},[]);return ee(()=>{if(!r)return;let o=s=>{if(s.key!=="Tab")return;let l=Array.from(r.querySelectorAll(re)),u=l.at(0),c=l.at(-1);if(!s.shiftKey){document.activeElement===c&&(s.preventDefault(),u?.focus());return}document.activeElement===u&&(s.preventDefault(),c?.focus())};return document.addEventListener("keydown",o),()=>{document.removeEventListener("keydown",o)}},[r]),[i,a]};import{useEffect as oe,useMemo as se,useRef as ie,useState as W}from"react";var dt=(e,r={})=>{let{initial:t=!1,once:n,amount:i,margin:a,root:o,enable:s=!0,onStart:l}=r,u=ie(!0),[c,p]=W(t),[m,T]=W(s),y=se(()=>{if(!m||typeof IntersectionObserver>"u")return;let Q=i==="all"?1:i==="some"?.5:i;try{return new IntersectionObserver(async([v],k)=>{if(!v)return;let O=v.isIntersecting;try{if(await l?.(v,k),!u.current)return;p(O)}catch($){console.error($)}O&&n&&k.disconnect()},{root:o||void 0,rootMargin:a,threshold:Q})}catch(v){console.error(v)}},[o,a,i,n,m,l]);return oe(()=>{if(u.current=!0,!(!m||!e.current||!y))return y.observe(e.current),()=>{u.current=!1,y.disconnect()}},[e,y,m]),{inView:c,enabled:m,observer:y,setInView:p,setEnabled:T}};import{useCallback as A}from"react";import{blockScroll as ae,restoreScroll as ue}from"@alessiofrittoli/web-utils/dom";var Et=e=>{let r=A(()=>ae(e?.current||void 0),[e]),t=A(()=>ue(e?.current||void 0),[e]);return[r,t]};import{useMemo as le,useState as me}from"react";import{useEffect as ce}from"react";var U=(e,r={})=>{let{delay:t=1,args:n}=r;ce(()=>{let i=setTimeout(e,t,...n||[]);return()=>clearTimeout(i)},[t,n,e])};var Ot=(e,r=500)=>{let[t,n]=me(e),i=le(()=>[e],[e]);return U(n,{delay:r,args:i}),t};import{useCallback as b,useEffect as pe,useRef as de,useState as fe}from"react";function F(e,r={}){let{delay:t=1,args:n,autoplay:i=!0,runOnStart:a=!1,updateState:o=!1}=r,s=de(void 0),[l,u]=fe(i),c=b(()=>s.current?(clearInterval(s.current),s.current=void 0,!0):!1,[]),p=b(()=>{let T=c();return a&&(n?e(...n):e()),s.current=setInterval(e,t,...n||[]),!T&&o&&u(!0),s.current},[t,n,o,a,e,c]),m=b(()=>{c()&&o&&u(!1)},[o,c]);return pe(()=>{if(i)return p(),m},[i,p,m]),o?{isActive:l,start:p,stop:m}:{start:p,stop:m}}import{useEffect as Te}from"react";var Ct=(e,r={})=>{let{delay:t=1,args:n}=r;Te(()=>{let i=setInterval(e,t,...n||[]);return()=>clearInterval(i)},[t,n,e])};import{useCallback as L,useEffect as ye}from"react";function Ut(e,r={}){let{autoplay:t=!0}=r,n=F(e,{autoplay:!1,...r}),{start:i,stop:a}=n,o=L(()=>document.hidden?a():i(),[i,a]),s=L(()=>{if(document.addEventListener("visibilitychange",o),!document.hidden)return i()},[i,o]),l=L(()=>{a(),document.removeEventListener("visibilitychange",o)},[a,o]);return ye(()=>{if(t)return s(),l},[t,s,l]),{...n,start:s,stop:l}}import{useCallback as g,useEffect as ve,useRef as Ee,useState as Me}from"react";function jt(e,r={}){let{delay:t=1,args:n,autoplay:i=!0,runOnStart:a=!1,updateState:o=!1}=r,s=Ee(void 0),[l,u]=Me(i),c=g(()=>s.current?(clearTimeout(s.current),s.current=void 0,!0):!1,[]),p=g(()=>{let T=c();return a&&(n?e(...n):e()),s.current=setTimeout(()=>{if(s.current=void 0,o&&u(!1),n)return e(...n);e()},t),!T&&o&&u(!0),s.current},[t,n,o,a,e,c]),m=g(()=>{c()&&o&&u(!1)},[o,c]);return ve(()=>{if(i)return p(),m},[i,p,m]),o?{isActive:l,start:p,stop:m}:{start:p,stop:m}}export{Xe as useDarkMode,Ot as useDebounce,et as useEventListener,ct as useFocusTrap,dt as useInView,F as useInterval,Ut as useIntervalWhenVisible,I as useIsClient,H as useIsFirstRender,st as useIsPortrait,Ct as useLightInterval,U as useLightTimeout,w as useLocalStorage,M as useMediaQuery,Et as useScrollBlock,De as useSessionStorage,E as useStorage,jt as useTimeout,x as useUpdateEffect};
1
+ import{useCallback as w,useEffect as z,useState as G}from"react";import{LocalStorage as V}from"@alessiofrittoli/web-utils/storage/LocalStorage";import{SessionStorage as K}from"@alessiofrittoli/web-utils/storage/SessionStorage";var O=(e,o,t="local")=>{let s=w(()=>(t==="local"?V:K).get(e)??o,[t,e,o]),[i,a]=G(o),r=w(n=>{a(u=>{let l=n instanceof Function?n(u):n;return(typeof window<"u"&&t==="local"?V:K).set(e,l),l})},[t,e]);return z(()=>{a(s())},[s]),[i,r]};var W=(e,o)=>O(e,o,"local");var Ve=(e,o)=>O(e,o,"session");import{useCallback as R,useEffect as Q,useRef as re}from"react";import{useEffect as X}from"react";import{useRef as J}from"react";var S=()=>{let e=J(!0);return e.current?(e.current=!1,!0):e.current};var je=e=>{let o=S();X(()=>{if(o)return e()},[])};import{useEffect as Y,useState as Z}from"react";var U=()=>{let[e,o]=Z(!1);return Y(()=>o(!0),[]),e};import{useEffect as _}from"react";var h=(e,o)=>{let t=S();_(()=>{if(!t)return e()},o)};import{useCallback as ee,useEffect as te,useState as ne}from"react";import{getMediaMatches as A}from"@alessiofrittoli/web-utils/browser-api";function b(e,o={}){let{updateState:t=!0,onChange:s}=o,[i,a]=ne(A(e)),r=ee(()=>{let n=A(e);t&&a(n),s?.(n)},[e,t,s]);if(te(()=>{let n=window.matchMedia(e),{matches:u}=n;return t&&a(u),s?.(u),n.addEventListener("change",r),()=>{n.removeEventListener("change",r)}},[e,t,s,r]),!!t)return i}var it=(e={})=>{let o=U(),t=b("(prefers-color-scheme: dark)"),{initial:s=t,docClassNames:i=[]}=e,[a,r]=W("dark-mode",s),n=a??t,[u,l]=i,c=re({light:"",dark:""});return h(()=>{r(t)},[t,r]),Q(()=>{u&&document.documentElement.classList.toggle(u,n),l&&document.documentElement.classList.toggle(l,!n)},[n,u,l]),Q(()=>{document.head.querySelectorAll('meta[name="theme-color"]').forEach(p=>{let f=p.getAttribute("media"),T=p.getAttribute("content");if(T){if(!f||f==="(prefers-color-scheme: light)"){c.current.light=T;return}c.current.dark=T}})},[]),h(()=>{let p=c.current.dark,f=c.current.light;a&&!p||!a&&!f||document.head.querySelectorAll('meta[name="theme-color"]').forEach(T=>{T.setAttribute("content",a?p:f)})},[a]),{isDarkMode:o?n:!1,isDarkOS:o?t:!1,toggleDarkMode:R(()=>r(p=>!p),[r]),enableDarkMode:R(()=>r(!0),[r]),disableDarkMode:R(()=>r(!1),[r])}};import{useEffect as oe}from"react";function lt(e,o){let{target:t,query:s,options:i,listener:a,onLoad:r,onCleanUp:n}=o;oe(()=>{let u=Array.isArray(e)?e:[e],l=(s?window.matchMedia(s):t&&"current"in t?t.current:t)??window;if(l.addEventListener)return r?.(),u.map(c=>{l.addEventListener(c,a,i)}),()=>{u.map(c=>{l.removeEventListener(c,a,i)}),n?.()}},[e,t,s,i,a,r,n])}import{portraitMediaQuery as se}from"@alessiofrittoli/web-utils/device";var Tt=()=>b(se);import{useCallback as F,useEffect as ie,useRef as ae,useState as ue}from"react";var ce=["input","select","textarea","button","[href]",'[tabindex]:not([tabindex="-1"])'].join(", "),xt=e=>{let[o,t]=ue(!1),s=ae(null),i=F(r=>{s.current=document.activeElement;let n=r||e?.current||!1;if(n)return t(n)},[e]),a=F(()=>{s.current?.focus(),t(!1)},[]);return ie(()=>{if(!o)return;let r=n=>{if(n.key!=="Tab")return;let u=Array.from(o.querySelectorAll(ce)),l=u.at(0),c=u.at(-1);if(!n.shiftKey){document.activeElement===c&&(n.preventDefault(),l?.focus());return}document.activeElement===l&&(n.preventDefault(),c?.focus())};return document.addEventListener("keydown",r),()=>{document.removeEventListener("keydown",r)}},[o]),[i,a]};import{useEffect as le,useMemo as pe,useRef as I,useState as $}from"react";var St=(e,o={})=>{let{initial:t=!1,once:s,amount:i,margin:a,root:r,enable:n=!0}=o,{onEnter:u,onExit:l,onIntersect:c}=o,p=I(!0),[f,T]=$(t),[v,N]=$(n),L=I(null),M=I(!1),x=pe(()=>{if(!v||typeof IntersectionObserver>"u")return;let P=i==="all"?1:i==="some"?.5:i;try{return new IntersectionObserver(async([E],g)=>{if(!E)return;let y=E.isIntersecting;try{if(M.current=!y&&!!L.current,y&&u&&await u({entry:E,observer:g}),M.current&&l&&await l({entry:E,observer:g}),c&&(y||!y&&L.current!=null)){let k={isEntering:y,isExiting:M.current};await c({entry:E,observer:g,...k})}if(L.current=y,!p.current)return;T(y)}catch(k){console.error(k)}y&&s&&g.disconnect()},{root:r||void 0,rootMargin:a,threshold:P})}catch(E){console.error(E)}},[r,a,i,s,v,u,l,c]);return le(()=>{if(p.current=!0,!(!v||!e.current||!x))return x.observe(e.current),()=>{p.current=!1,x.disconnect()}},[e,x,v]),{inView:f,enabled:v,observer:x,isExiting:M.current,setInView:T,setEnabled:N}};import{useCallback as j}from"react";import{blockScroll as me,restoreScroll as de}from"@alessiofrittoli/web-utils/dom";var Rt=e=>{let o=j(()=>me(e?.current||void 0),[e]),t=j(()=>de(e?.current||void 0),[e]);return[o,t]};import{useMemo as Te,useState as ye}from"react";import{useEffect as fe}from"react";var B=(e,o={})=>{let{delay:t=1,args:s}=o;fe(()=>{let i=setTimeout(e,t,...s||[]);return()=>clearTimeout(i)},[t,s,e])};var Wt=(e,o=500)=>{let[t,s]=ye(e),i=Te(()=>[e],[e]);return B(s,{delay:o,args:i}),t};import{useCallback as H,useEffect as Ee,useRef as ve,useState as xe}from"react";function q(e,o={}){let{delay:t=1,args:s,autoplay:i=!0,runOnStart:a=!1,updateState:r=!1}=o,n=ve(void 0),[u,l]=xe(i),c=H(()=>n.current?(clearInterval(n.current),n.current=void 0,!0):!1,[]),p=H(()=>{let T=c();return a&&(s?e(...s):e()),n.current=setInterval(e,t,...s||[]),!T&&r&&l(!0),n.current},[t,s,r,a,e,c]),f=H(()=>{c()&&r&&l(!1)},[r,c]);return Ee(()=>{if(i)return p(),f},[i,p,f]),r?{isActive:u,start:p,stop:f}:{start:p,stop:f}}import{useEffect as Me}from"react";var Bt=(e,o={})=>{let{delay:t=1,args:s}=o;Me(()=>{let i=setInterval(e,t,...s||[]);return()=>clearInterval(i)},[t,s,e])};import{useCallback as C,useEffect as ge}from"react";function Gt(e,o={}){let{autoplay:t=!0}=o,s=q(e,{autoplay:!1,...o}),{start:i,stop:a}=s,r=C(()=>document.hidden?a():i(),[i,a]),n=C(()=>{if(document.addEventListener("visibilitychange",r),!document.hidden)return i()},[i,r]),u=C(()=>{a(),document.removeEventListener("visibilitychange",r)},[a,r]);return ge(()=>{if(t)return n(),u},[t,n,u]),{...s,start:n,stop:u}}import{useCallback as D,useEffect as Oe,useRef as Se,useState as be}from"react";function Zt(e,o={}){let{delay:t=1,args:s,autoplay:i=!0,runOnStart:a=!1,updateState:r=!1}=o,n=Se(void 0),[u,l]=be(i),c=D(()=>n.current?(clearTimeout(n.current),n.current=void 0,!0):!1,[]),p=D(()=>{let T=c();return a&&(s?e(...s):e()),n.current=setTimeout(()=>{if(n.current=void 0,r&&l(!1),s)return e(...s);e()},t),!T&&r&&l(!0),n.current},[t,s,r,a,e,c]),f=D(()=>{c()&&r&&l(!1)},[r,c]);return Oe(()=>{if(i)return p(),f},[i,p,f]),r?{isActive:u,start:p,stop:f}:{start:p,stop:f}}export{it as useDarkMode,Wt as useDebounce,je as useEffectOnce,lt as useEventListener,xt as useFocusTrap,St as useInView,q as useInterval,Gt as useIntervalWhenVisible,U as useIsClient,S as useIsFirstRender,Tt as useIsPortrait,Bt as useLightInterval,B as useLightTimeout,W as useLocalStorage,b as useMediaQuery,Rt as useScrollBlock,Ve as useSessionStorage,O as useStorage,Zt as useTimeout,h as useUpdateEffect};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alessiofrittoli/react-hooks",
3
- "version": "2.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "TypeScript React utility Hooks",
5
5
  "author": {
6
6
  "name": "Alessio Frittoli",