Make the debounced node inner rect hook more reliable (#350)

Rather than try to track the transition state, which was proving
unreliable, I am just directly tracking the node state and determining
whether to debounce the inner rect based on whether the user has the
prefers-reduced-motion setting or query, whether the node is resizing,
and whether it's currently magnified. I'm then using the actual
animation time setting to determine how long to debounce.
This commit is contained in:
Evan Simkowitz
2024-09-06 16:20:27 -07:00
committed by GitHub
parent 30c1d56be6
commit afe73c3e85
6 changed files with 44 additions and 35 deletions
+2 -11
View File
@@ -252,20 +252,11 @@ const AppKeyHandlers = () => {
};
const AppInner = () => {
const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(false);
const prefersReducedMotionSetting = jotai.useAtomValue(atoms.reducedMotionPreferenceAtom);
const prefersReducedMotion = jotai.useAtomValue(atoms.prefersReducedMotionAtom);
const client = jotai.useAtomValue(atoms.client);
const windowData = jotai.useAtomValue(atoms.waveWindow);
const isFullScreen = jotai.useAtomValue(atoms.isFullScreen);
React.useEffect(() => {
const reducedMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
setPrefersReducedMotion(!reducedMotionQuery || reducedMotionQuery.matches);
reducedMotionQuery.addEventListener("change", () => {
setPrefersReducedMotion(reducedMotionQuery.matches);
});
}, []);
if (client == null || windowData == null) {
return (
<div className="mainapp">
@@ -279,7 +270,7 @@ const AppInner = () => {
<div
className={clsx("mainapp", PLATFORM, {
fullscreen: isFullScreen,
"prefers-reduced-motion": prefersReducedMotion || prefersReducedMotionSetting,
"prefers-reduced-motion": prefersReducedMotion,
})}
onContextMenu={handleContextMenu}
>
+21 -2
View File
@@ -124,7 +124,26 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
} catch (_) {
// do nothing
}
const reducedMotionPreferenceAtom = jotai.atom((get) => get(settingsAtom)?.["window:reducedmotion"]);
const reducedMotionSettingAtom = jotai.atom((get) => get(settingsAtom)?.["window:reducedmotion"]);
const reducedMotionSystemPreferenceAtom = jotai.atom(false);
// Composite of the prefers-reduced-motion media query and the window:reducedmotion user setting.
const prefersReducedMotionAtom = jotai.atom((get) => {
const reducedMotionSetting = get(reducedMotionSettingAtom);
const reducedMotionSystemPreference = get(reducedMotionSystemPreferenceAtom);
return reducedMotionSetting || reducedMotionSystemPreference;
});
// Set up a handler for changes to the prefers-reduced-motion media query.
if (globalThis.window != null) {
const reducedMotionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
globalStore.set(reducedMotionSystemPreferenceAtom, !reducedMotionQuery || reducedMotionQuery.matches);
reducedMotionQuery?.addEventListener("change", () => {
globalStore.set(reducedMotionSystemPreferenceAtom, reducedMotionQuery.matches);
});
}
const typeAheadModalAtom = jotai.atom({});
const modalOpen = jotai.atom(false);
const allConnStatusAtom = jotai.atom<ConnStatus[]>((get) => {
@@ -146,7 +165,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
isFullScreen: isFullScreenAtom,
controlShiftDelayAtom,
updaterStatusAtom,
reducedMotionPreferenceAtom,
prefersReducedMotionAtom,
typeAheadModalAtom,
modalOpen,
allConnStatus: allConnStatusAtom,
+2
View File
@@ -773,10 +773,12 @@ export class LayoutModel {
return isFocused;
}),
numLeafs: this.numLeafs,
isResizing: this.isResizing,
isMagnified: atom((get) => {
const treeState = get(this.treeStateAtom);
return treeState.magnifiedNodeId === nodeid;
}),
animationTimeS: this.animationTimeS,
ready: this.ready,
disablePointerEvents: this.activeDrag,
onClose: async () => await this.closeNode(nodeid),
+16 -21
View File
@@ -5,7 +5,8 @@ import { atoms, globalStore, WOS } from "@/app/store/global";
import { fireAndForget } from "@/util/util";
import useResizeObserver from "@react-hook/resize-observer";
import { Atom, useAtomValue } from "jotai";
import { CSSProperties, useEffect, useLayoutEffect, useState } from "react";
import { CSSProperties, useCallback, useEffect, useLayoutEffect, useState } from "react";
import { debounce } from "throttle-debounce";
import { withLayoutTreeStateAtomFromTab } from "./layoutAtom";
import { LayoutModel } from "./layoutModel";
import { LayoutNode, NodeModel, TileLayoutContents } from "./types";
@@ -67,32 +68,26 @@ export function useNodeModel(layoutModel: LayoutModel, layoutNode: LayoutNode):
export function useDebouncedNodeInnerRect(nodeModel: NodeModel): CSSProperties {
const nodeInnerRect = useAtomValue(nodeModel.innerRect);
const animationTimeS = useAtomValue(nodeModel.animationTimeS);
const isMagnified = useAtomValue(nodeModel.isMagnified);
const isResizing = useAtomValue(nodeModel.isResizing);
const prefersReducedMotion = useAtomValue(atoms.prefersReducedMotionAtom);
const [innerRect, setInnerRect] = useState<CSSProperties>();
const [isTransitioning, setIsTransitioning] = useState(false);
useEffect(() => {
const onTransitionStart = () => {
setIsTransitioning(true);
};
const onTransitionEnd = () => {
setIsTransitioning(false);
};
if (nodeModel.displayContainerRef.current) {
nodeModel.displayContainerRef.current.addEventListener("transitionstart", onTransitionStart);
nodeModel.displayContainerRef.current.addEventListener("transitionend", onTransitionEnd);
}
return () => {
nodeModel.displayContainerRef.current?.removeEventListener("transitionstart", onTransitionStart);
nodeModel.displayContainerRef.current?.removeEventListener("transitionend", onTransitionEnd);
};
}, [nodeModel]);
const setInnerRectDebounced = useCallback(
debounce(animationTimeS * 1000, (nodeInnerRect) => {
setInnerRect(nodeInnerRect);
}),
[animationTimeS]
);
useLayoutEffect(() => {
if (!isTransitioning) {
if (prefersReducedMotion || isMagnified || isResizing) {
setInnerRect(nodeInnerRect);
} else {
setInnerRectDebounced(nodeInnerRect);
}
}, [nodeInnerRect, isTransitioning]);
}, [nodeInnerRect]);
return innerRect;
}
+2
View File
@@ -335,6 +335,8 @@ export interface NodeModel {
numLeafs: Atom<number>;
nodeId: string;
blockId: string;
animationTimeS: Atom<number>;
isResizing: Atom<boolean>;
isFocused: Atom<boolean>;
isMagnified: Atom<boolean>;
ready: Atom<boolean>;
+1 -1
View File
@@ -18,7 +18,7 @@ declare global {
activeTabId: jotai.Atom<string>; // derrived from windowDataAtom
isFullScreen: jotai.PrimitiveAtom<boolean>;
controlShiftDelayAtom: jotai.PrimitiveAtom<boolean>;
reducedMotionPreferenceAtom: jotai.Atom<boolean>;
prefersReducedMotionAtom: jotai.Atom<boolean>;
updaterStatusAtom: jotai.PrimitiveAtom<UpdaterStatus>;
typeAheadModalAtom: jotai.PrimitiveAtom<TypeAheadModalType>;
modalOpen: jotai.PrimitiveAtom<boolean>;