@react-aria/utils 3.8.2 → 3.11.1

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.
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Copyright 2021 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {RefObject, useEffect, useRef} from 'react';
14
+
15
+ export function useEvent<K extends keyof GlobalEventHandlersEventMap>(
16
+ ref: RefObject<EventTarget>,
17
+ event: K,
18
+ handler: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,
19
+ options?: boolean | AddEventListenerOptions
20
+ ) {
21
+ let handlerRef = useRef(handler);
22
+ handlerRef.current = handler;
23
+
24
+ let isDisabled = handler == null;
25
+
26
+ useEffect(() => {
27
+ if (isDisabled) {
28
+ return;
29
+ }
30
+
31
+ let element = ref.current;
32
+ let handler = (e: GlobalEventHandlersEventMap[K]) => handlerRef.current.call(this, e);
33
+
34
+ element.addEventListener(event, handler, options);
35
+ return () => {
36
+ element.removeEventListener(event, handler, options);
37
+ };
38
+ }, [ref, event, options, isDisabled]);
39
+ }
@@ -23,11 +23,17 @@ interface GlobalListeners {
23
23
  export function useGlobalListeners(): GlobalListeners {
24
24
  let globalListeners = useRef(new Map());
25
25
  let addGlobalListener = useCallback((eventTarget, type, listener, options) => {
26
- globalListeners.current.set(listener, {type, eventTarget, options});
26
+ // Make sure we remove the listener after it is called with the `once` option.
27
+ let fn = options?.once ? (...args) => {
28
+ globalListeners.current.delete(listener);
29
+ listener(...args);
30
+ } : listener;
31
+ globalListeners.current.set(listener, {type, eventTarget, fn, options});
27
32
  eventTarget.addEventListener(type, listener, options);
28
33
  }, []);
29
34
  let removeGlobalListener = useCallback((eventTarget, type, listener, options) => {
30
- eventTarget.removeEventListener(type, listener, options);
35
+ let fn = globalListeners.current.get(listener)?.fn || listener;
36
+ eventTarget.removeEventListener(type, fn, options);
31
37
  globalListeners.current.delete(listener);
32
38
  }, []);
33
39
  let removeAllGlobalListeners = useCallback(() => {
package/src/useId.ts CHANGED
@@ -10,9 +10,10 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {useEffect, useRef, useState} from 'react';
13
+ import {useCallback, useEffect, useRef, useState} from 'react';
14
14
  import {useLayoutEffect} from './useLayoutEffect';
15
15
  import {useSSRSafeId} from '@react-aria/ssr';
16
+ import {useValueEffect} from './';
16
17
 
17
18
  let idsUpdaterMap: Map<string, (v: string) => void> = new Map();
18
19
 
@@ -87,18 +88,20 @@ export function mergeIds(idA: string, idB: string): string {
87
88
  /**
88
89
  * Used to generate an id, and after render, check if that id is rendered so we know
89
90
  * if we can use it in places such as labelledby.
91
+ * @param depArray - When to recalculate if the id is in the DOM.
90
92
  */
91
- export function useSlotId(): string {
93
+ export function useSlotId(depArray: ReadonlyArray<any> = []): string {
92
94
  let id = useId();
93
- let [resolvedId, setResolvedId] = useState(id);
94
- useLayoutEffect(() => {
95
- let setCurr = idsUpdaterMap.get(id);
96
- if (setCurr && !document.getElementById(id)) {
97
- setResolvedId(null);
98
- } else {
99
- setResolvedId(id);
100
- }
101
- }, [id]);
95
+ let [resolvedId, setResolvedId] = useValueEffect(id);
96
+ let updateId = useCallback(() => {
97
+ setResolvedId(function *() {
98
+ yield id;
99
+
100
+ yield document.getElementById(id) ? id : null;
101
+ });
102
+ }, [id, setResolvedId]);
103
+
104
+ useLayoutEffect(updateId, [id, updateId, ...depArray]);
102
105
 
103
106
  return resolvedId;
104
107
  }
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Copyright 2021 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {MutableRefObject, useRef} from 'react';
14
+ import {useLayoutEffect} from './';
15
+
16
+ /**
17
+ * Offers an object ref for a given callback ref or an object ref. Especially
18
+ * helfpul when passing forwarded refs (created using `React.forwardRef`) to
19
+ * React Aria Hooks.
20
+ *
21
+ * @param forwardedRef The original ref intended to be used.
22
+ * @returns An object ref that updates the given ref.
23
+ * @see https://reactjs.org/docs/forwarding-refs.html
24
+ */
25
+ export function useObjectRef<T>(forwardedRef?: ((instance: T | null) => void) | MutableRefObject<T | null> | null): MutableRefObject<T> {
26
+ const objRef = useRef<T>();
27
+
28
+ /**
29
+ * We're using `useLayoutEffect` here instead of `useEffect` because we want
30
+ * to make sure that the `ref` value is up to date before other places in the
31
+ * the execution cycle try to read it.
32
+ */
33
+ useLayoutEffect(() => {
34
+ if (!forwardedRef) {
35
+ return;
36
+ }
37
+
38
+ if (typeof forwardedRef === 'function') {
39
+ forwardedRef(objRef.current);
40
+ } else {
41
+ forwardedRef.current = objRef.current;
42
+ }
43
+ }, [forwardedRef]);
44
+
45
+ return objRef;
46
+ }
@@ -0,0 +1,65 @@
1
+ /*
2
+ * Copyright 2020 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {Dispatch, useCallback, useRef, useState} from 'react';
14
+ import {useLayoutEffect} from './';
15
+
16
+ type SetValueAction<S> = (prev: S) => Generator<any, void, unknown>;
17
+
18
+ // This hook works like `useState`, but when setting the value, you pass a generator function
19
+ // that can yield multiple values. Each yielded value updates the state and waits for the next
20
+ // layout effect, then continues the generator. This allows sequential updates to state to be
21
+ // written linearly.
22
+ export function useValueEffect<S>(defaultValue: S | (() => S)): [S, Dispatch<SetValueAction<S>>] {
23
+ let [value, setValue] = useState(defaultValue);
24
+ let valueRef = useRef(value);
25
+ let effect = useRef(null);
26
+
27
+ valueRef.current = value;
28
+
29
+ // Store the function in a ref so we can always access the current version
30
+ // which has the proper `value` in scope.
31
+ let nextRef = useRef(null);
32
+ nextRef.current = () => {
33
+ // Run the generator to the next yield.
34
+ let newValue = effect.current.next();
35
+
36
+ // If the generator is done, reset the effect.
37
+ if (newValue.done) {
38
+ effect.current = null;
39
+ return;
40
+ }
41
+
42
+ // If the value is the same as the current value,
43
+ // then continue to the next yield. Otherwise,
44
+ // set the value in state and wait for the next layout effect.
45
+ if (value === newValue.value) {
46
+ nextRef.current();
47
+ } else {
48
+ setValue(newValue.value);
49
+ }
50
+ };
51
+
52
+ useLayoutEffect(() => {
53
+ // If there is an effect currently running, continue to the next yield.
54
+ if (effect.current) {
55
+ nextRef.current();
56
+ }
57
+ });
58
+
59
+ let queue = useCallback(fn => {
60
+ effect.current = fn(valueRef.current);
61
+ nextRef.current();
62
+ }, [effect, nextRef]);
63
+
64
+ return [value, queue];
65
+ }