diff --git a/packages/vkui/docs/common/hooks/useInfiniteList.tsx b/packages/vkui/docs/common/hooks/useInfiniteList.tsx index 550c9f244fb..f4ecff62f37 100644 --- a/packages/vkui/docs/common/hooks/useInfiniteList.tsx +++ b/packages/vkui/docs/common/hooks/useInfiniteList.tsx @@ -1,7 +1,7 @@ import { type ReactNode, type RefObject, useEffect, useMemo, useRef, useState } from 'react'; import { Spinner } from '../../../src'; -import { useResizeObserver } from '../../../src/hooks/useResizeObserver'; import { useDOM } from '../../../src/lib/dom'; +import { useResizeObserver } from '../../../src/hooks/useResizeObserver/useResizeObserver'; const SPINNER_HEIGHT = 24; const WINDOW_PADDING_BOTTOM = 64; @@ -138,7 +138,10 @@ export const useInfiniteList =
( useEffect(recalculateSectionsBounds, [sectionsRefs]); - useResizeObserver(containerRef, () => requestAnimationFrame(showMoreVisible)); + useResizeObserver({ + ref: containerRef, + onResize: showMoreVisible, + }); useEffect(() => { window!.addEventListener('scroll', recalculateVisibleSections); diff --git a/packages/vkui/docs/components-overview/components/ComponentOverviewCard.tsx b/packages/vkui/docs/components-overview/components/ComponentOverviewCard.tsx index dbc9fee268e..2ec3dc666e2 100644 --- a/packages/vkui/docs/components-overview/components/ComponentOverviewCard.tsx +++ b/packages/vkui/docs/components-overview/components/ComponentOverviewCard.tsx @@ -3,12 +3,12 @@ import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; import { Card, Mark, Title, useDirection } from '../../../src'; -import { useResizeObserver } from '../../../src/hooks/useResizeObserver'; import { useDOM } from '../../../src/lib/dom'; import type { CSSCustomProperties } from '../../../src/types'; import { useOverviewLayoutContext } from '../../common/components/OverviewLayoutContext'; import type { ComponentConfigData } from '../config'; import styles from './ComponentOverviewCard.module.css'; +import { useResizeObserver } from '../../../src/hooks/useResizeObserver/useResizeObserver'; const CONTENT_PADDING = 10; @@ -74,7 +74,10 @@ export const ComponentOverviewCard: React.FC = ({ React.useEffect(() => calculateScale(), [calculateScale]); - useResizeObserver(containerRef, calculateScale); + useResizeObserver({ + ref: containerRef, + onResize: calculateScale, + }); const componentUrl = React.useMemo(() => { if (!window) { diff --git a/packages/vkui/src/components/CarouselBase/CarouselBase.tsx b/packages/vkui/src/components/CarouselBase/CarouselBase.tsx index 94300e7445e..6bcd5c84d77 100644 --- a/packages/vkui/src/components/CarouselBase/CarouselBase.tsx +++ b/packages/vkui/src/components/CarouselBase/CarouselBase.tsx @@ -5,8 +5,8 @@ import { classNames } from '@vkontakte/vkjs'; import { useConfigDirection } from '../../hooks/useConfigDirection'; import { useExternRef } from '../../hooks/useExternRef'; import { useMutationObserver } from '../../hooks/useMutationObserver'; -import { useResizeObserver } from '../../hooks/useResizeObserver'; -import { useDOM } from '../../lib/dom'; +import { useResizeObserver } from '../../hooks/useResizeObserver/useResizeObserver'; +import { useWindowResizeObserver } from '../../hooks/useResizeObserver/useWindowResizeObserver'; import { mergeCalls } from '../../lib/mergeCalls'; import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect'; import { warnOnce } from '../../lib/warnOnce'; @@ -347,8 +347,15 @@ export const CarouselBase = ({ initializeSlides(); } }; - const { window } = useDOM(); - useResizeObserver(resizeSource === 'element' ? rootRef : window, onResize); + useWindowResizeObserver({ + enabled: resizeSource === 'window', + onResize, + }); + useResizeObserver({ + ref: rootRef, + enabled: resizeSource === 'element', + onResize, + }); const loopedSlideChangePerform = () => { const { snaps, slides } = slidesManager.current; diff --git a/packages/vkui/src/components/FixedLayout/FixedLayout.test.tsx b/packages/vkui/src/components/FixedLayout/FixedLayout.test.tsx index 27d58a341f5..ff6c4d38f19 100644 --- a/packages/vkui/src/components/FixedLayout/FixedLayout.test.tsx +++ b/packages/vkui/src/components/FixedLayout/FixedLayout.test.tsx @@ -1,98 +1,94 @@ import { act, type RefObject } from 'react'; import { render } from '@testing-library/react'; -import { baselineComponent } from '../../testing/utils'; +import { baselineComponent, withFakeTimers } from '../../testing/utils'; import { SplitCol } from '../SplitCol/SplitCol'; import { FixedLayout, type FixedLayoutProps } from './FixedLayout'; import styles from './FixedLayout.module.css'; -let updateFunction: () => void; - -const mockResizeObserver = vi.fn( - class MockResizeObserver { - constructor(updateFunctionFn: () => void) { - updateFunction = updateFunctionFn; - } +describe('FixedLayout', () => { + baselineComponent(FixedLayout); - observe = vi.fn(); - unobserve = vi.fn(); - disconnect = vi.fn(); - }, -); + it( + 'check update width by parent width', + withFakeTimers(async () => { + const parentRef: RefObject = { + current: null, + }; + const layoutRef: RefObject = { + current: null, + }; + let parentWidth = 500; -vi.stubGlobal('ResizeObserver', mockResizeObserver); + const mockParentRef = (element: HTMLDivElement) => { + if (!element) { + return; + } + vi.spyOn(element, 'getBoundingClientRect').mockImplementation( + () => new DOMRect(0, 0, parentWidth, 800), + ); -describe('FixedLayout', () => { - baselineComponent(FixedLayout); + parentRef.current = element; + }; - it('check update width by parent width', async () => { - const parentRef: RefObject = { - current: null, - }; - const layoutRef: RefObject = { - current: null, - }; - let parentWidth = 500; - - const mockParentRef = (element: HTMLDivElement) => { - if (!element) { - return; - } - vi.spyOn(element, 'getBoundingClientRect').mockImplementation( - () => new DOMRect(0, 0, parentWidth, 800), + render( +
+ +
+ +
, ); - parentRef.current = element; - }; + expect(layoutRef.current!).toHaveStyle('width: 500px'); + + parentWidth = 600; + act(() => { + globalThis.__resizeObserverMock.triggerAll(); + vi.runAllTimers(); + }); - render( -
- -
- -
, - ); + expect(layoutRef.current!).toHaveStyle('width: 600px'); + }), + ); - expect(layoutRef.current!).toHaveStyle('width: 500px'); + it( + 'check update width by column width', + withFakeTimers(async () => { + const colRef: RefObject = { + current: null, + }; + const layoutRef: RefObject = { + current: null, + }; + let colWidth = 280; - parentWidth = 600; - await act(async () => updateFunction()); + const mockColRef = (element: HTMLDivElement) => { + if (!element) { + return; + } + vi.spyOn(element, 'clientWidth', 'get').mockImplementation(() => colWidth); - expect(layoutRef.current!).toHaveStyle('width: 600px'); - }); + colRef.current = element; + }; - it('check update width by column width', async () => { - const colRef: RefObject = { - current: null, - }; - const layoutRef: RefObject = { - current: null, - }; - let colWidth = 280; - - const mockColRef = (element: HTMLDivElement) => { - if (!element) { - return; - } - vi.spyOn(element, 'clientWidth', 'get').mockImplementation(() => colWidth); - - colRef.current = element; - }; - - render( - - -
- - , - ); - - expect(layoutRef.current!).toHaveStyle('width: 280px'); - - colWidth = 360; - await act(async () => updateFunction()); - - expect(layoutRef.current!).toHaveStyle('width: 360px'); - }); + render( + + +
+ + , + ); + + expect(layoutRef.current!).toHaveStyle('width: 280px'); + + colWidth = 360; + act(() => { + globalThis.__resizeObserverMock.triggerAll(); + vi.runAllTimers(); + }); + + expect(layoutRef.current!).toHaveStyle('width: 360px'); + }), + ); describe('check correct classNames', () => { it.each<{ props: Partial; className: string }>([ diff --git a/packages/vkui/src/components/FixedLayout/FixedLayout.tsx b/packages/vkui/src/components/FixedLayout/FixedLayout.tsx index 2af227e0229..f8a3b038d84 100644 --- a/packages/vkui/src/components/FixedLayout/FixedLayout.tsx +++ b/packages/vkui/src/components/FixedLayout/FixedLayout.tsx @@ -4,8 +4,8 @@ import { useCallback } from 'react'; import * as React from 'react'; import { classNames } from '@vkontakte/vkjs'; import { usePlatform } from '../../hooks/usePlatform'; -import { useResizeObserver } from '../../hooks/useResizeObserver'; -import { useDOM } from '../../lib/dom'; +import { useResizeObserver } from '../../hooks/useResizeObserver/useResizeObserver'; +import { useWindowResizeObserver } from '../../hooks/useResizeObserver/useWindowResizeObserver'; import { setRef } from '../../lib/utils'; import { warnOnce } from '../../lib/warnOnce'; import type { HasComponent, HTMLAttributesWithRootRef } from '../../types'; @@ -62,7 +62,6 @@ export const FixedLayout = ({ const platform = usePlatform(); const ref = React.useRef(null); const [width, setWidth] = React.useState(undefined); - const { window } = useDOM(); const { colRef } = React.useContext(SplitColContext); const parentRef = React.useRef(null); @@ -99,8 +98,13 @@ export const FixedLayout = ({ }; React.useEffect(doResize, [colRef, platform, ref, useParentWidth]); - useResizeObserver(window, doResize); - useResizeObserver(useParentWidth ? parentRef : colRef, doResize); + useWindowResizeObserver({ + onResize: doResize, + }); + useResizeObserver({ + ref: useParentWidth ? parentRef : (colRef ?? undefined), + onResize: doResize, + }); return ( ; }, diff --git a/packages/vkui/src/components/Gallery/Gallery.test.tsx b/packages/vkui/src/components/Gallery/Gallery.test.tsx index f36c7dcaa8b..81dcb3c7557 100644 --- a/packages/vkui/src/components/Gallery/Gallery.test.tsx +++ b/packages/vkui/src/components/Gallery/Gallery.test.tsx @@ -491,8 +491,10 @@ describe('Gallery', () => { mockedData.containerWidth = 250; - fireEvent.resize(window); - vi.runAllTimers(); + act(() => { + fireEvent.resize(window); + vi.runAllTimers(); + }); if (looped) { expect(mockedData.layerTransform).toBe('translate3d(35px, 0, 0)'); @@ -763,41 +765,9 @@ describe('Gallery', () => { }); }); - const mockResizeObserver = () => { - const callbacks = new Set(); - - class MockResizeObserver implements ResizeObserver { - constructor(callback: ResizeObserverCallback) { - callbacks.add(callback); - } - - // eslint-disable-next-line @typescript-eslint/no-empty-function - observe() {} - // eslint-disable-next-line @typescript-eslint/no-empty-function - unobserve() {} - // eslint-disable-next-line @typescript-eslint/no-empty-function - disconnect() {} - } - - const originalResizeObserver = window.ResizeObserver; - window.ResizeObserver = MockResizeObserver; - - return { - triggerResize: () => { - callbacks.forEach((callback) => { - callback([], {} as unknown as ResizeObserver); - }); - }, - restore: () => { - window.ResizeObserver = originalResizeObserver; - }, - }; - }; - it( 'check recalculate slides positions when resize element with resizeSource="element"', withFakeTimers(() => { - const { triggerResize, restore } = mockResizeObserver(); const onChange = vi.fn(); const mockedData = setup({ @@ -817,12 +787,13 @@ describe('Gallery', () => { mockedData.containerWidth = 250; - act(triggerResize); - vi.runAllTimers(); + act(() => { + globalThis.__resizeObserverMock.triggerAll(); + vi.runAllTimers(); + }); expect(mockedData.layerTransform).toBe('translate3d(35px, 0, 0)'); expect(mockedData.getSlideMockData(0).transform).toBe('translate3d(0px, 0, 0)'); - restore(); }), ); @@ -884,10 +855,11 @@ describe('Gallery', () => { mockedData.viewPortWidth = 540; onDragStart.mockClear(); onDragEnd.mockClear(); - fireEvent.resize(window); - - rerender({ slideIndex: 1 }); - vi.runAllTimers(); + act(() => { + fireEvent.resize(window); + rerender({ slideIndex: 1 }); + vi.runAllTimers(); + }); expect(getArrows()).toHaveLength(0); diff --git a/packages/vkui/src/components/Skeleton/Skeleton.tsx b/packages/vkui/src/components/Skeleton/Skeleton.tsx index 3426f020ffd..1730ce5012f 100644 --- a/packages/vkui/src/components/Skeleton/Skeleton.tsx +++ b/packages/vkui/src/components/Skeleton/Skeleton.tsx @@ -5,10 +5,9 @@ import { classNames } from '@vkontakte/vkjs'; import { mergeStyle } from '../../helpers/mergeStyle'; import { useBooleanState } from '../../hooks/useBooleanState'; import { useExternRef } from '../../hooks/useExternRef'; -import { useResizeObserver } from '../../hooks/useResizeObserver'; +import { useWindowResizeObserver } from '../../hooks/useResizeObserver/useWindowResizeObserver'; import { useStateWithPrev } from '../../hooks/useStateWithPrev'; import { millisecondsInSecond } from '../../lib/date'; -import { useDOM } from '../../lib/dom'; import { animationVisibilityDelayStyles } from '../../styles/animationVisibilityDelay'; import type { CSSCustomProperties, HTMLAttributesWithRootRef } from '../../types'; import { RootComponent } from '../RootComponent/RootComponent'; @@ -68,7 +67,6 @@ function useSkeletonSyncAnimation(disableAnimation: boolean, duration = 1.5) { * Вычисляет позицию скелетона. */ function useSkeletonPosition(rootRef: React.RefObject) { - const { window } = useDOM(); const [[skeletonGradientLeft, prevSkeletonGradientLeft], setSkeletonGradientLeft] = useStateWithPrev('0'); @@ -88,7 +86,9 @@ function useSkeletonPosition(rootRef: React.RefObject) { }, [prevSkeletonGradientLeft, rootRef, setSkeletonGradientLeft]); React.useEffect(updatePosition, [updatePosition]); - useResizeObserver(window, updatePosition); + useWindowResizeObserver({ + onResize: updatePosition, + }); return skeletonGradientLeft; } diff --git a/packages/vkui/src/components/Textarea/Textarea.test.tsx b/packages/vkui/src/components/Textarea/Textarea.test.tsx index c507e09ba9a..9c6a596b740 100644 --- a/packages/vkui/src/components/Textarea/Textarea.test.tsx +++ b/packages/vkui/src/components/Textarea/Textarea.test.tsx @@ -1,4 +1,4 @@ -import { createRef } from 'react'; +import { act, createRef } from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import { noop } from '@vkontakte/vkjs'; import { Platform } from '../../lib/platform'; @@ -235,7 +235,10 @@ describe(Textarea, () => { const onResize = vi.fn(); render(