mirror of
https://github.com/facebook/react.git
synced 2026-02-26 16:24:59 +00:00
202 lines
5.5 KiB
JavaScript
202 lines
5.5 KiB
JavaScript
// @flow
|
|
|
|
import throttle from 'lodash.throttle';
|
|
import { useCallback, useEffect, useLayoutEffect, useState } from 'react';
|
|
import { localStorageGetItem, localStorageSetItem } from 'react-devtools-shared/src/storage';
|
|
|
|
export function useIsOverflowing(
|
|
containerRef: { current: HTMLDivElement | null },
|
|
totalChildWidth: number
|
|
): boolean {
|
|
const [isOverflowing, setIsOverflowing] = useState<boolean>(false);
|
|
|
|
// It's important to use a layout effect, so that we avoid showing a flash of overflowed content.
|
|
useLayoutEffect(() => {
|
|
if (containerRef.current === null) {
|
|
return () => {};
|
|
}
|
|
|
|
const container = ((containerRef.current: any): HTMLDivElement);
|
|
|
|
const handleResize = throttle(
|
|
() => setIsOverflowing(container.clientWidth <= totalChildWidth),
|
|
100
|
|
);
|
|
|
|
handleResize();
|
|
|
|
// It's important to listen to the ownerDocument.defaultView to support the browser extension.
|
|
// Here we use portals to render individual tabs (e.g. Profiler),
|
|
// and the root document might belong to a different window.
|
|
const ownerWindow = container.ownerDocument.defaultView;
|
|
ownerWindow.addEventListener('resize', handleResize);
|
|
return () => ownerWindow.removeEventListener('resize', handleResize);
|
|
}, [containerRef, totalChildWidth]);
|
|
|
|
return isOverflowing;
|
|
}
|
|
|
|
// Forked from https://usehooks.com/useLocalStorage/
|
|
export function useLocalStorage<T>(
|
|
key: string,
|
|
initialValue: T | (() => T)
|
|
): [T, (value: T | (() => T)) => void] {
|
|
const getValueFromLocalStorage = useCallback(() => {
|
|
try {
|
|
const item = localStorageGetItem(key);
|
|
if (item != null) {
|
|
return JSON.parse(item);
|
|
}
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
if (typeof initialValue === 'function') {
|
|
return ((initialValue: any): () => T)();
|
|
} else {
|
|
return initialValue;
|
|
}
|
|
}, [initialValue, key]);
|
|
|
|
const [storedValue, setStoredValue] = useState(getValueFromLocalStorage);
|
|
|
|
const setValue = useCallback(
|
|
value => {
|
|
try {
|
|
const valueToStore =
|
|
value instanceof Function ? (value: any)(storedValue) : value;
|
|
setStoredValue(valueToStore);
|
|
localStorageSetItem(key, JSON.stringify(valueToStore));
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
},
|
|
[key, storedValue]
|
|
);
|
|
|
|
// Listen for changes to this local storage value made from other windows.
|
|
// This enables the e.g. "⚛️ Elements" tab to update in response to changes from "⚛️ Settings".
|
|
useLayoutEffect(() => {
|
|
const onStorage = event => {
|
|
const newValue = getValueFromLocalStorage();
|
|
if (key === event.key && storedValue !== newValue) {
|
|
setValue(newValue);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('storage', onStorage);
|
|
|
|
return () => {
|
|
window.removeEventListener('storage', onStorage);
|
|
};
|
|
}, [getValueFromLocalStorage, key, storedValue, setValue]);
|
|
|
|
return [storedValue, setValue];
|
|
}
|
|
|
|
export function useModalDismissSignal(
|
|
modalRef: { current: HTMLDivElement | null },
|
|
dismissCallback: () => void,
|
|
dismissOnClickOutside?: boolean = true
|
|
): void {
|
|
useEffect(() => {
|
|
if (modalRef.current === null) {
|
|
return () => {};
|
|
}
|
|
|
|
const handleDocumentKeyDown = ({ key }: any) => {
|
|
if (key === 'Escape') {
|
|
dismissCallback();
|
|
}
|
|
};
|
|
|
|
const handleDocumentClick = (event: any) => {
|
|
// $FlowFixMe
|
|
if (
|
|
modalRef.current !== null &&
|
|
!modalRef.current.contains(event.target)
|
|
) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
|
|
dismissCallback();
|
|
}
|
|
};
|
|
|
|
// It's important to listen to the ownerDocument to support the browser extension.
|
|
// Here we use portals to render individual tabs (e.g. Profiler),
|
|
// and the root document might belong to a different window.
|
|
const ownerDocument = modalRef.current.ownerDocument;
|
|
ownerDocument.addEventListener('keydown', handleDocumentKeyDown);
|
|
if (dismissOnClickOutside) {
|
|
ownerDocument.addEventListener('click', handleDocumentClick);
|
|
}
|
|
|
|
return () => {
|
|
ownerDocument.removeEventListener('keydown', handleDocumentKeyDown);
|
|
ownerDocument.removeEventListener('click', handleDocumentClick);
|
|
};
|
|
}, [modalRef, dismissCallback, dismissOnClickOutside]);
|
|
}
|
|
|
|
// Copied from https://github.com/facebook/react/pull/15022
|
|
export function useSubscription<Value>({
|
|
getCurrentValue,
|
|
subscribe,
|
|
}: {|
|
|
getCurrentValue: () => Value,
|
|
subscribe: (callback: Function) => () => void,
|
|
|}): Value {
|
|
const [state, setState] = useState({
|
|
getCurrentValue,
|
|
subscribe,
|
|
value: getCurrentValue(),
|
|
});
|
|
|
|
if (
|
|
state.getCurrentValue !== getCurrentValue ||
|
|
state.subscribe !== subscribe
|
|
) {
|
|
setState({
|
|
getCurrentValue,
|
|
subscribe,
|
|
value: getCurrentValue(),
|
|
});
|
|
}
|
|
|
|
useEffect(() => {
|
|
let didUnsubscribe = false;
|
|
|
|
const checkForUpdates = () => {
|
|
if (didUnsubscribe) {
|
|
return;
|
|
}
|
|
|
|
setState(prevState => {
|
|
if (
|
|
prevState.getCurrentValue !== getCurrentValue ||
|
|
prevState.subscribe !== subscribe
|
|
) {
|
|
return prevState;
|
|
}
|
|
|
|
const value = getCurrentValue();
|
|
if (prevState.value === value) {
|
|
return prevState;
|
|
}
|
|
|
|
return { ...prevState, value };
|
|
});
|
|
};
|
|
const unsubscribe = subscribe(checkForUpdates);
|
|
|
|
checkForUpdates();
|
|
|
|
return () => {
|
|
didUnsubscribe = true;
|
|
unsubscribe();
|
|
};
|
|
}, [getCurrentValue, subscribe]);
|
|
|
|
return state.value;
|
|
}
|