@react-aria/utils 3.8.0 → 3.10.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/dist/module.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { clamp, snapValueToStep } from "@react-stately/utils";
2
2
  export { clamp, snapValueToStep };
3
3
  import _clsx from "clsx";
4
+ import _babelRuntimeHelpersEsmExtends from "@babel/runtime/helpers/esm/extends";
4
5
  import { useSSRSafeId } from "@react-aria/ssr";
5
- import _react, { useEffect, useRef, useState, useCallback, useLayoutEffect as _useLayoutEffect } from "react";
6
+ import _react, { useCallback, useEffect, useRef, useState } from "react";
6
7
  // During SSR, React emits a warning when calling useLayoutEffect.
7
8
  // Since neither useLayoutEffect nor useEffect run on the server,
8
9
  // we can suppress this by replace it with a noop on the server.
@@ -17,7 +18,8 @@ export function useId(defaultId) {
17
18
  let isRendering = useRef(true);
18
19
  isRendering.current = true;
19
20
  let [value, setValue] = useState(defaultId);
20
- let nextId = useRef(null); // don't memo this, we want it new each render so that the Effects always run
21
+ let nextId = useRef(null);
22
+ let res = useSSRSafeId(value); // don't memo this, we want it new each render so that the Effects always run
21
23
 
22
24
  let updateValue = val => {
23
25
  if (!isRendering.current) {
@@ -27,9 +29,16 @@ export function useId(defaultId) {
27
29
  }
28
30
  };
29
31
 
32
+ $f8b5fdd96fb429d7102983f777c41307$var$idsUpdaterMap.set(res, updateValue);
30
33
  useLayoutEffect(() => {
31
34
  isRendering.current = false;
32
35
  }, [updateValue]);
36
+ useLayoutEffect(() => {
37
+ let r = res;
38
+ return () => {
39
+ $f8b5fdd96fb429d7102983f777c41307$var$idsUpdaterMap.delete(r);
40
+ };
41
+ }, [res]);
33
42
  useEffect(() => {
34
43
  let newId = nextId.current;
35
44
 
@@ -38,8 +47,6 @@ export function useId(defaultId) {
38
47
  nextId.current = null;
39
48
  }
40
49
  }, [setValue, updateValue]);
41
- let res = useSSRSafeId(value);
42
- $f8b5fdd96fb429d7102983f777c41307$var$idsUpdaterMap.set(res, updateValue);
43
50
  return res;
44
51
  }
45
52
  /**
@@ -71,18 +78,24 @@ export function mergeIds(idA, idB) {
71
78
  /**
72
79
  * Used to generate an id, and after render, check if that id is rendered so we know
73
80
  * if we can use it in places such as labelledby.
81
+ * @param depArray - When to recalculate if the id is in the DOM.
74
82
  */
75
83
 
76
- export function useSlotId() {
77
- let [id, setId] = useState(useId());
78
- useLayoutEffect(() => {
79
- let setCurr = $f8b5fdd96fb429d7102983f777c41307$var$idsUpdaterMap.get(id);
84
+ export function useSlotId(depArray) {
85
+ if (depArray === void 0) {
86
+ depArray = [];
87
+ }
80
88
 
81
- if (setCurr && !document.getElementById(id)) {
82
- setId(null);
83
- }
84
- }, [id]);
85
- return id;
89
+ let id = useId();
90
+ let [resolvedId, setResolvedId] = useValueEffect(id);
91
+ let updateId = useCallback(() => {
92
+ setResolvedId(function* () {
93
+ yield id;
94
+ yield document.getElementById(id) ? id : null;
95
+ });
96
+ }, [id, setResolvedId]);
97
+ useLayoutEffect(updateId, [id, updateId, ...depArray]);
98
+ return resolvedId;
86
99
  }
87
100
 
88
101
  /*
@@ -122,32 +135,30 @@ export function chain() {
122
135
  * @param args - Multiple sets of props to merge together.
123
136
  */
124
137
  export function mergeProps() {
125
- let result = {};
126
-
127
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
128
- args[_key] = arguments[_key];
129
- }
130
-
131
- for (let props of args) {
132
- for (let key in result) {
133
- // Chain events
134
- if (/^on[A-Z]/.test(key) && typeof result[key] === 'function' && typeof props[key] === 'function') {
135
- result[key] = chain(result[key], props[key]); // Merge classnames, sometimes classNames are empty string which eval to false, so we just need to do a type check
136
- } else if (key === 'className' && typeof result.className === 'string' && typeof props.className === 'string') {
137
- result[key] = _clsx(result.className, props.className);
138
- } else if (key === 'UNSAFE_className' && typeof result.UNSAFE_className === 'string' && typeof props.UNSAFE_className === 'string') {
139
- result[key] = _clsx(result.UNSAFE_className, props.UNSAFE_className);
140
- } else if (key === 'id' && result.id && props.id) {
141
- result.id = mergeIds(result.id, props.id); // Override others
142
- } else {
143
- result[key] = props[key] !== undefined ? props[key] : result[key];
144
- }
145
- } // Add props from b that are not in a
138
+ // Start with a base clone of the first argument. This is a lot faster than starting
139
+ // with an empty object and adding properties as we go.
140
+ let result = _babelRuntimeHelpersEsmExtends({}, arguments.length <= 0 ? undefined : arguments[0]);
146
141
 
142
+ for (let i = 1; i < arguments.length; i++) {
143
+ let props = i < 0 || arguments.length <= i ? undefined : arguments[i];
147
144
 
148
145
  for (let key in props) {
149
- if (result[key] === undefined) {
150
- result[key] = props[key];
146
+ let a = result[key];
147
+ let b = props[key]; // Chain events
148
+
149
+ if (typeof a === 'function' && typeof b === 'function' && // This is a lot faster than a regex.
150
+ key[0] === 'o' && key[1] === 'n' && key.charCodeAt(2) >=
151
+ /* 'A' */
152
+ 65 && key.charCodeAt(2) <=
153
+ /* 'Z' */
154
+ 90) {
155
+ result[key] = chain(a, b); // Merge classnames, sometimes classNames are empty string which eval to false, so we just need to do a type check
156
+ } else if ((key === 'className' || key === 'UNSAFE_className') && typeof a === 'string' && typeof b === 'string') {
157
+ result[key] = _clsx(a, b);
158
+ } else if (key === 'id' && a && b) {
159
+ result.id = mergeIds(a, b); // Override others
160
+ } else {
161
+ result[key] = b !== undefined ? b : a;
151
162
  }
152
163
  }
153
164
  }
@@ -175,7 +186,7 @@ export function filterDOMProps(props, opts) {
175
186
  let filteredProps = {};
176
187
 
177
188
  for (const prop in props) {
178
- if (Object.prototype.hasOwnProperty.call(props, prop) && ($f6a965352cabf1a7c37e8c1337e5eab$var$DOMPropNames.has(prop) || labelable && $f6a965352cabf1a7c37e8c1337e5eab$var$labelablePropNames.has(prop) || (propNames == null ? void 0 : propNames.has(prop)) || $f6a965352cabf1a7c37e8c1337e5eab$var$propRe.test(prop))) {
189
+ if (Object.prototype.hasOwnProperty.call(props, prop) && ($f6a965352cabf1a7c37e8c1337e5eab$var$DOMPropNames.has(prop) || labelable && $f6a965352cabf1a7c37e8c1337e5eab$var$labelablePropNames.has(prop) || propNames != null && propNames.has(prop) || $f6a965352cabf1a7c37e8c1337e5eab$var$propRe.test(prop))) {
179
190
  filteredProps[prop] = props[prop];
180
191
  }
181
192
  }
@@ -563,15 +574,24 @@ export function useDrag1D(props) {
563
574
  export function useGlobalListeners() {
564
575
  let globalListeners = useRef(new Map());
565
576
  let addGlobalListener = useCallback((eventTarget, type, listener, options) => {
577
+ // Make sure we remove the listener after it is called with the `once` option.
578
+ let fn = options != null && options.once ? function () {
579
+ globalListeners.current.delete(listener);
580
+ listener(...arguments);
581
+ } : listener;
566
582
  globalListeners.current.set(listener, {
567
583
  type,
568
584
  eventTarget,
585
+ fn,
569
586
  options
570
587
  });
571
588
  eventTarget.addEventListener(type, listener, options);
572
589
  }, []);
573
590
  let removeGlobalListener = useCallback((eventTarget, type, listener, options) => {
574
- eventTarget.removeEventListener(type, listener, options);
591
+ var _globalListeners$curr;
592
+
593
+ let fn = ((_globalListeners$curr = globalListeners.current.get(listener)) == null ? void 0 : _globalListeners$curr.fn) || listener;
594
+ eventTarget.removeEventListener(type, fn, options);
575
595
  globalListeners.current.delete(listener);
576
596
  }, []);
577
597
  let removeAllGlobalListeners = useCallback(() => {
@@ -623,6 +643,37 @@ export function useLabels(props, defaultLabel) {
623
643
  'aria-labelledby': labelledBy
624
644
  };
625
645
  }
646
+
647
+ /**
648
+ * Offers an object ref for a given callback ref or an object ref. Especially
649
+ * helfpul when passing forwarded refs (created using `React.forwardRef`) to
650
+ * React Aria Hooks.
651
+ *
652
+ * @param forwardedRef The original ref intended to be used.
653
+ * @returns An object ref that updates the given ref.
654
+ * @see https://reactjs.org/docs/forwarding-refs.html
655
+ */
656
+ export function useObjectRef(forwardedRef) {
657
+ const objRef = useRef();
658
+ /**
659
+ * We're using `useLayoutEffect` here instead of `useEffect` because we want
660
+ * to make sure that the `ref` value is up to date before other places in the
661
+ * the execution cycle try to read it.
662
+ */
663
+
664
+ useLayoutEffect(() => {
665
+ if (!forwardedRef) {
666
+ return;
667
+ }
668
+
669
+ if (typeof forwardedRef === 'function') {
670
+ forwardedRef(objRef.current);
671
+ } else {
672
+ forwardedRef.current = objRef.current;
673
+ }
674
+ }, [forwardedRef]);
675
+ return objRef;
676
+ }
626
677
  // Like useEffect, but only called for updates after the initial render.
627
678
  export function useUpdateEffect(effect, dependencies) {
628
679
  const isInitialMount = useRef(true);
@@ -677,7 +728,7 @@ export function useResizeObserver(options) {
677
728
  // Syncs ref from context with ref passed to hook
678
729
  export function useSyncRef(context, ref) {
679
730
  useLayoutEffect(() => {
680
- if (context && context.ref) {
731
+ if (context && context.ref && ref) {
681
732
  context.ref.current = ref.current;
682
733
  return () => {
683
734
  context.ref.current = null;
@@ -705,7 +756,15 @@ export function useViewportSize() {
705
756
  useEffect(() => {
706
757
  // Use visualViewport api to track available height even on iOS virtual keyboard opening
707
758
  let onResize = () => {
708
- setSize($d662329747d896105af008c761523$var$getViewportSize());
759
+ setSize(size => {
760
+ let newSize = $d662329747d896105af008c761523$var$getViewportSize();
761
+
762
+ if (newSize.width === size.width && newSize.height === size.height) {
763
+ return size;
764
+ }
765
+
766
+ return newSize;
767
+ });
709
768
  };
710
769
 
711
770
  if (!$d662329747d896105af008c761523$var$visualViewport) {
@@ -736,8 +795,7 @@ let $c8aa524f123a75a64d51e06d16b9568$var$descriptionId = 0;
736
795
  const $c8aa524f123a75a64d51e06d16b9568$var$descriptionNodes = new Map();
737
796
  export function useDescription(description) {
738
797
  let [id, setId] = useState(null);
739
-
740
- _useLayoutEffect(() => {
798
+ useLayoutEffect(() => {
741
799
  if (!description) {
742
800
  return;
743
801
  }
@@ -769,7 +827,6 @@ export function useDescription(description) {
769
827
  }
770
828
  };
771
829
  }, [description]);
772
-
773
830
  return {
774
831
  'aria-describedby': description ? id : undefined
775
832
  };
@@ -808,4 +865,67 @@ export function isChrome() {
808
865
  export function isAndroid() {
809
866
  return $b0986c1243f71db8e992f67117a1ed9$var$testUserAgent(/Android/);
810
867
  }
868
+ export function useEvent(ref, event, handler, options) {
869
+ let handlerRef = useRef(handler);
870
+ handlerRef.current = handler;
871
+ let isDisabled = handler == null;
872
+ useEffect(() => {
873
+ if (isDisabled) {
874
+ return;
875
+ }
876
+
877
+ let element = ref.current;
878
+
879
+ let handler = e => handlerRef.current.call(this, e);
880
+
881
+ element.addEventListener(event, handler, options);
882
+ return () => {
883
+ element.removeEventListener(event, handler, options);
884
+ };
885
+ }, [ref, event, options, isDisabled]);
886
+ }
887
+ // This hook works like `useState`, but when setting the value, you pass a generator function
888
+ // that can yield multiple values. Each yielded value updates the state and waits for the next
889
+ // layout effect, then continues the generator. This allows sequential updates to state to be
890
+ // written linearly.
891
+ export function useValueEffect(defaultValue) {
892
+ let [value, setValue] = useState(defaultValue);
893
+ let valueRef = useRef(value);
894
+ let effect = useRef(null);
895
+ valueRef.current = value; // Store the function in a ref so we can always access the current version
896
+ // which has the proper `value` in scope.
897
+
898
+ let nextRef = useRef(null);
899
+
900
+ nextRef.current = () => {
901
+ // Run the generator to the next yield.
902
+ let newValue = effect.current.next(); // If the generator is done, reset the effect.
903
+
904
+ if (newValue.done) {
905
+ effect.current = null;
906
+ return;
907
+ } // If the value is the same as the current value,
908
+ // then continue to the next yield. Otherwise,
909
+ // set the value in state and wait for the next layout effect.
910
+
911
+
912
+ if (value === newValue.value) {
913
+ nextRef.current();
914
+ } else {
915
+ setValue(newValue.value);
916
+ }
917
+ };
918
+
919
+ useLayoutEffect(() => {
920
+ // If there is an effect currently running, continue to the next yield.
921
+ if (effect.current) {
922
+ nextRef.current();
923
+ }
924
+ });
925
+ let queue = useCallback(fn => {
926
+ effect.current = fn(valueRef.current);
927
+ nextRef.current();
928
+ }, [effect, nextRef]);
929
+ return [value, queue];
930
+ }
811
931
  //# sourceMappingURL=module.js.map