fix(ui): update viewport height without visualViewport (#1314)
* fix(ui): update viewport height without visualViewport * fix(ui): initialize visual viewport state from helper --------- Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
This commit is contained in:
committed by
GitHub
co-authored by
Isaac Sanchez
parent
46bef9b0c7
commit
9ed44326f8
@@ -0,0 +1,35 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
|
||||||
|
import { getVisualViewportState } from './useVisualViewport';
|
||||||
|
|
||||||
|
const withWindow = (value: { innerHeight: number; visualViewport?: { height: number } | null }, run: () => void) => {
|
||||||
|
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, 'window');
|
||||||
|
Object.defineProperty(globalThis, 'window', {
|
||||||
|
configurable: true,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
run();
|
||||||
|
} finally {
|
||||||
|
if (originalWindow) {
|
||||||
|
Object.defineProperty(globalThis, 'window', originalWindow);
|
||||||
|
} else {
|
||||||
|
Reflect.deleteProperty(globalThis, 'window');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('getVisualViewportState', () => {
|
||||||
|
test('falls back to innerHeight when visualViewport is unavailable', () => {
|
||||||
|
withWindow({ innerHeight: 812 }, () => {
|
||||||
|
expect(getVisualViewportState()).toEqual({ height: 812, keyboardHeight: 0 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('derives keyboard height from visualViewport height', () => {
|
||||||
|
withWindow({ innerHeight: 812, visualViewport: { height: 512 } }, () => {
|
||||||
|
expect(getVisualViewportState()).toEqual({ height: 512, keyboardHeight: 300 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,46 +5,59 @@ export interface VisualViewportState {
|
|||||||
keyboardHeight: number;
|
keyboardHeight: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInitialHeight = (): number => {
|
export const getVisualViewportState = (): VisualViewportState => {
|
||||||
if (typeof window === 'undefined') return 0;
|
if (typeof window === 'undefined') {
|
||||||
return window.visualViewport?.height ?? window.innerHeight;
|
return { height: 0, keyboardHeight: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const height = window.visualViewport?.height ?? window.innerHeight;
|
||||||
|
const keyboardHeight = window.visualViewport
|
||||||
|
? Math.max(0, window.innerHeight - height)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return { height, keyboardHeight };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useVisualViewport = (): VisualViewportState => {
|
export const useVisualViewport = (): VisualViewportState => {
|
||||||
const [state, setState] = React.useState<VisualViewportState>(() => ({
|
const [state, setState] = React.useState<VisualViewportState>(getVisualViewportState);
|
||||||
height: getInitialHeight(),
|
|
||||||
keyboardHeight: 0,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const rafIdRef = React.useRef<number | null>(null);
|
const rafIdRef = React.useRef<number | null>(null);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (typeof window === 'undefined' || !window.visualViewport) return;
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
const visualViewport = window.visualViewport;
|
||||||
|
|
||||||
const handleChange = () => {
|
const handleChange = () => {
|
||||||
if (rafIdRef.current !== null) return;
|
if (rafIdRef.current !== null) return;
|
||||||
rafIdRef.current = requestAnimationFrame(() => {
|
rafIdRef.current = requestAnimationFrame(() => {
|
||||||
rafIdRef.current = null;
|
rafIdRef.current = null;
|
||||||
const vv = window.visualViewport!;
|
const nextState = getVisualViewportState();
|
||||||
const height = vv.height;
|
|
||||||
const keyboardHeight = Math.max(0, window.innerHeight - height);
|
|
||||||
setState((prev) => {
|
setState((prev) => {
|
||||||
if (prev.height === height && prev.keyboardHeight === keyboardHeight) return prev;
|
if (prev.height === nextState.height && prev.keyboardHeight === nextState.keyboardHeight) return prev;
|
||||||
return { height, keyboardHeight };
|
return nextState;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
window.visualViewport.addEventListener('resize', handleChange);
|
if (visualViewport) {
|
||||||
window.visualViewport.addEventListener('scroll', handleChange, { passive: true });
|
visualViewport.addEventListener('resize', handleChange);
|
||||||
|
visualViewport.addEventListener('scroll', handleChange, { passive: true });
|
||||||
|
} else {
|
||||||
|
window.addEventListener('resize', handleChange);
|
||||||
|
}
|
||||||
handleChange();
|
handleChange();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (rafIdRef.current !== null) {
|
if (rafIdRef.current !== null) {
|
||||||
cancelAnimationFrame(rafIdRef.current);
|
cancelAnimationFrame(rafIdRef.current);
|
||||||
}
|
}
|
||||||
window.visualViewport?.removeEventListener('resize', handleChange);
|
if (visualViewport) {
|
||||||
window.visualViewport?.removeEventListener('scroll', handleChange);
|
visualViewport.removeEventListener('resize', handleChange);
|
||||||
|
visualViewport.removeEventListener('scroll', handleChange);
|
||||||
|
} else {
|
||||||
|
window.removeEventListener('resize', handleChange);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user