diff --git a/.nx/version-plans/version-plan-1775222101294.md b/.nx/version-plans/version-plan-1775222101294.md new file mode 100644 index 00000000000..eb5fc9a63f4 --- /dev/null +++ b/.nx/version-plans/version-plan-1775222101294.md @@ -0,0 +1,11 @@ +--- +gamut-illustrations: major +gamut-patterns: major +gamut-styles: major +gamut-icons: major +gamut-tests: major +variance: major +gamut: major +--- + +Update to React 19 compatibility diff --git a/package.json b/package.json index 4f1b3f1f732..5d206890b10 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "@vidstack/react": "^1.12.12", "core-js": "3.7.0", "lodash": "^4.17.23", - "react": "18.3.1", - "react-dom": "18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-helmet-async": "^2.0.5" }, "devDependencies": { @@ -42,18 +42,18 @@ "@svgr/cli": "5.5.0", "@swc-node/register": "^1.11.1", "@swc/core": "^1.15.18", - "@testing-library/dom": "^8.11.1", + "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^5.16.1", - "@testing-library/react": "15.0.6", + "@testing-library/react": "^16.0.0", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^14.5.2", "@types/classnames": "2.2.10", "@types/invariant": "2.2.29", "@types/konami-code-js": "^0.8.0", "@types/lodash": "4.17.23", - "@types/react": "18.3.27", - "@types/react-dom": "18.3.1", - "@types/react-test-renderer": "18.3.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@types/react-test-renderer": "^19.0.0", "@types/stylis": "^4.2.0", "@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/parser": "^5.15.0", @@ -82,7 +82,7 @@ "onchange": "^7.0.2", "playwright": "^1.49.1", "prettier": "^2.8.7", - "react-test-renderer": "18.3.1", + "react-test-renderer": "^19.0.0", "start-server-and-test": "^2.1.5", "storybook": "^10.3.1", "storybook-addon-deep-controls": "^0.9.5", @@ -113,13 +113,13 @@ "repository": "git@github.com:Codecademy/gamut.git", "resolutions": { "@react-aria/interactions": "3.25.0", - "@types/react": "18.3.27", - "@types/react-dom": "18.3.1", + "@types/react-dom": "^19.0.0", + "@types/react": "^19.0.0", "@typescript-eslint/utils": "^5.15.0", "axios": "1.14.0", "error-ex": "1.3.4", - "react": "18.3.1", - "react-dom": "18.3.1" + "react-dom": "^19.0.0", + "react": "^19.0.0" }, "scripts": { "build": "nx run-many --target=build --all", diff --git a/packages/gamut-icons/package.json b/packages/gamut-icons/package.json index 895d845c552..d6a1b4bf709 100644 --- a/packages/gamut-icons/package.json +++ b/packages/gamut-icons/package.json @@ -17,7 +17,7 @@ "@emotion/react": "^11.4.0", "@emotion/styled": "^11.3.0", "lodash": "^4.17.23", - "react": "^17.0.2 || ^18.3.0" + "react": "^17.0.2 || ^18.3.0 || ^19.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/gamut-illustrations/package.json b/packages/gamut-illustrations/package.json index ea0d17c44d6..d0912616a31 100644 --- a/packages/gamut-illustrations/package.json +++ b/packages/gamut-illustrations/package.json @@ -18,8 +18,8 @@ "peerDependencies": { "@emotion/react": "^11.4.0", "@emotion/styled": "^11.3.0", - "react": "^17.0.2 || ^18.3.0", - "react-dom": "^17.0.2 || ^18.3.0" + "react": "^17.0.2 || ^18.3.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.3.0 || ^19.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/gamut-patterns/package.json b/packages/gamut-patterns/package.json index b71ba1c214a..2c30edd12fd 100644 --- a/packages/gamut-patterns/package.json +++ b/packages/gamut-patterns/package.json @@ -19,8 +19,8 @@ "peerDependencies": { "@emotion/react": "^11.4.0", "@emotion/styled": "^11.3.0", - "react": "^17.0.2 || ^18.3.0", - "react-dom": "^17.0.2 || ^18.3.0" + "react": "^17.0.2 || ^18.3.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.3.0 || ^19.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/gamut-styles/package.json b/packages/gamut-styles/package.json index 145d6035d8b..23c8b65c9eb 100644 --- a/packages/gamut-styles/package.json +++ b/packages/gamut-styles/package.json @@ -6,7 +6,7 @@ "dependencies": { "@codecademy/variance": "0.26.1", "@emotion/is-prop-valid": "^1.1.0", - "framer-motion": "^11.18.0", + "framer-motion": "^12.0.0", "get-nonce": "^1.0.0", "polished": "^4.1.2" }, @@ -26,7 +26,7 @@ "@emotion/react": "^11.4.0", "@emotion/styled": "^11.3.0", "lodash": "^4.17.23", - "react": "^17.0.2 || ^18.3.0", + "react": "^17.0.2 || ^18.3.0 || ^19.0.0", "stylis": "^4.0.7" }, "publishConfig": { diff --git a/packages/gamut-styles/src/__tests__/AssetProvider.test.tsx b/packages/gamut-styles/src/__tests__/AssetProvider.test.tsx index 47a000399c5..f8fe24cc464 100644 --- a/packages/gamut-styles/src/__tests__/AssetProvider.test.tsx +++ b/packages/gamut-styles/src/__tests__/AssetProvider.test.tsx @@ -4,12 +4,13 @@ import { render } from '@testing-library/react'; import { AssetProvider, createFontLinks } from '../AssetProvider'; import { coreTheme, percipioTheme } from '../themes'; +import { getFontsMock } from './fontUtilsMock'; import { setupRtl } from './testUtils'; const renderView = setupRtl(AssetProvider, {}); jest.mock('../utils/fontUtils', () => ({ - getFonts: jest.fn(), + getFonts: require('./fontUtilsMock').getFontsMock, })); jest.mock('../remoteAssets/fonts', () => ({ @@ -43,11 +44,36 @@ jest.mock('../remoteAssets/fonts', () => ({ }, })); -const mockGetFonts = require('../utils/fontUtils').getFonts; +const mockGetFonts = getFontsMock; + +function getPreloadLinks(container: HTMLElement): NodeListOf { + const inContainer = container.querySelectorAll('link[rel="preload"]'); + if (inContainer.length > 0) return inContainer; + return document.querySelectorAll('link[rel="preload"]'); +} + +const defaultFonts = [ + { + filePath: 'https://www.codecademy.com/gamut/apercu-regular-pro', + extensions: ['woff2', 'woff'], + name: 'Apercu', + }, + { + filePath: 'https://www.codecademy.com/gamut/apercu-bold-pro', + extensions: ['woff2', 'woff'], + name: 'Apercu', + weight: 'bold', + }, +]; describe('AssetProvider', () => { beforeEach(() => { jest.clearAllMocks(); + mockGetFonts.mockReturnValue(defaultFonts); + }); + + afterEach(() => { + mockGetFonts.mockReturnValue(defaultFonts); }); describe('createFontLinks', () => { @@ -66,7 +92,7 @@ describe('AssetProvider', () => { ]; const { container } = render(<>{createFontLinks(fonts)}); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(1); expect(links[0]).toHaveAttribute( @@ -80,13 +106,13 @@ describe('AssetProvider', () => { it('should handle empty fonts array', () => { const { container } = render(<>{createFontLinks([])}); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(0); }); it('should handle undefined fonts parameter', () => { const { container } = render(<>{createFontLinks(undefined)}); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(2); }); @@ -110,7 +136,7 @@ describe('AssetProvider', () => { ]; const { container } = render(<>{createFontLinks(fonts)}); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(1); expect(links[0]).toHaveAttribute( 'href', @@ -139,7 +165,7 @@ describe('AssetProvider', () => { ]; const { container } = render(<>{createFontLinks(fonts)}); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(2); }); }); @@ -155,14 +181,14 @@ describe('AssetProvider', () => { ]); const { view } = renderView(); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); + expect(mockGetFonts).toHaveBeenCalledWith('core'); expect(links).toHaveLength(1); expect(links[0]).toHaveAttribute( 'href', 'https://www.codecademy.com/gamut/apercu-regular-pro.woff2' ); - expect(mockGetFonts).toHaveBeenCalledWith('core'); }); it('should render font links for percipio theme', () => { @@ -175,7 +201,7 @@ describe('AssetProvider', () => { ]); const { view } = renderView({ theme: percipioTheme as any }); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); expect(links).toHaveLength(1); expect(links[0]).toHaveAttribute( @@ -207,7 +233,7 @@ describe('AssetProvider', () => { }); const { view } = renderView(); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); expect(links).toHaveLength(0); }); @@ -215,7 +241,7 @@ describe('AssetProvider', () => { mockGetFonts.mockReturnValue(undefined); const { view } = renderView(); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); expect(links).toHaveLength(2); }); @@ -223,7 +249,7 @@ describe('AssetProvider', () => { mockGetFonts.mockReturnValue(null); const { view } = renderView(); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); expect(links).toHaveLength(2); }); @@ -231,7 +257,7 @@ describe('AssetProvider', () => { mockGetFonts.mockReturnValue('not-an-array'); const { view } = renderView(); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); expect(links).toHaveLength(0); }); @@ -255,7 +281,7 @@ describe('AssetProvider', () => { ]); const { view } = renderView(); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); expect(links).toHaveLength(3); expect(links[0]).toHaveAttribute( @@ -292,7 +318,7 @@ describe('AssetProvider', () => { ]); const { view } = renderView(); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); expect(links).toHaveLength(1); expect(links[0]).toHaveAttribute( @@ -321,7 +347,7 @@ describe('AssetProvider', () => { ]); const { view } = renderView(); - const links = view.container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(view.container); expect(links).toHaveLength(1); expect(links[0]).toHaveAttribute( diff --git a/packages/gamut-styles/src/__tests__/fontLoading.test.tsx b/packages/gamut-styles/src/__tests__/fontLoading.test.tsx index 6a46a50c3ab..c5cbdabed2c 100644 --- a/packages/gamut-styles/src/__tests__/fontLoading.test.tsx +++ b/packages/gamut-styles/src/__tests__/fontLoading.test.tsx @@ -2,12 +2,12 @@ import { render } from '@testing-library/react'; import { AssetProvider } from '../AssetProvider'; import { coreTheme, percipioTheme } from '../themes'; +import { getFontsMock } from './fontUtilsMock'; -// Type assertion to satisfy Theme interface in GamutProvider from theme.d.ts - this lib is typed to the CoreTheme interface const typedPercipioTheme = percipioTheme as any; jest.mock('../utils/fontUtils', () => ({ - getFonts: jest.fn(), + getFonts: require('./fontUtilsMock').getFontsMock, })); jest.mock('../remoteAssets/fonts', () => ({ @@ -29,7 +29,13 @@ jest.mock('../remoteAssets/fonts', () => ({ }, })); -const mockGetFonts = require('../utils/fontUtils').getFonts; +const mockGetFonts = getFontsMock; + +function getPreloadLinks(container: HTMLElement): NodeListOf { + const inContainer = container.querySelectorAll('link[rel="preload"]'); + if (inContainer.length > 0) return inContainer; + return document.querySelectorAll('link[rel="preload"]'); +} const mockDocumentFonts = { load: jest.fn(), @@ -47,14 +53,27 @@ Object.defineProperty(document, 'fonts', { const mockFetch = jest.fn(); global.fetch = mockFetch; +const defaultFonts = [ + { + filePath: 'https://www.codecademy.com/gamut/apercu-regular-pro', + extensions: ['woff2', 'woff'], + name: 'Apercu', + }, +]; + describe('Font Loading and Error Handling', () => { beforeEach(() => { jest.clearAllMocks(); + mockGetFonts.mockReturnValue(defaultFonts); mockDocumentFonts.load.mockClear(); mockDocumentFonts.check.mockClear(); mockFetch.mockClear(); }); + afterEach(() => { + mockGetFonts.mockReturnValue(defaultFonts); + }); + describe('Font Preloading', () => { it('should create preload links for fonts', () => { mockGetFonts.mockReturnValue([ @@ -67,7 +86,7 @@ describe('Font Loading and Error Handling', () => { const { container } = render(); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(1); expect(links[0]).toHaveAttribute( 'href', @@ -93,7 +112,7 @@ describe('Font Loading and Error Handling', () => { const { container } = render(); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(2); expect(links[0]).toHaveAttribute( 'href', @@ -114,8 +133,7 @@ describe('Font Loading and Error Handling', () => { const { container } = render(); - // Should not render any links when getFonts fails - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(0); }); @@ -141,7 +159,7 @@ describe('Font Loading and Error Handling', () => { const { container } = render(); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(2); }); }); @@ -163,8 +181,7 @@ describe('Font Loading and Error Handling', () => { const { container } = render(); - // Should render preload links for all fonts - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(2); expect(links[0]).toHaveAttribute( 'href', @@ -195,7 +212,7 @@ describe('Font Loading and Error Handling', () => { const { container } = render(); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(1); Object.defineProperty(document, 'fonts', { @@ -218,7 +235,7 @@ describe('Font Loading and Error Handling', () => { const { container } = render(); - const links = container.querySelectorAll('link[rel="preload"]'); + const links = getPreloadLinks(container); expect(links).toHaveLength(1); global.fetch = originalFetch; diff --git a/packages/gamut-styles/src/__tests__/fontUtilsMock.ts b/packages/gamut-styles/src/__tests__/fontUtilsMock.ts new file mode 100644 index 00000000000..22501405fc9 --- /dev/null +++ b/packages/gamut-styles/src/__tests__/fontUtilsMock.ts @@ -0,0 +1 @@ +export const getFontsMock = jest.fn(); diff --git a/packages/gamut-styles/src/variance/utils.ts b/packages/gamut-styles/src/variance/utils.ts index a88b1bfb116..efccefdd8cd 100644 --- a/packages/gamut-styles/src/variance/utils.ts +++ b/packages/gamut-styles/src/variance/utils.ts @@ -1,5 +1,6 @@ import { ThemeProps } from '@codecademy/variance'; import isPropValid from '@emotion/is-prop-valid'; +import type React from 'react'; import { all as allProps } from './config'; @@ -17,10 +18,10 @@ const validPropnames = allPropnames.filter(isPropValid); export type SystemPropNames = (typeof allPropnames)[number]; -export type ElementOrProps = keyof JSX.IntrinsicElements | ThemeProps; +export type ElementOrProps = keyof React.JSX.IntrinsicElements | ThemeProps; export type ForwardableProps = Exclude< - El extends keyof JSX.IntrinsicElements - ? keyof JSX.IntrinsicElements[El] + El extends keyof React.JSX.IntrinsicElements + ? keyof React.JSX.IntrinsicElements[El] : keyof Element, Additional | SystemPropNames >; diff --git a/packages/gamut-styles/tsconfig.lib.json b/packages/gamut-styles/tsconfig.lib.json index bf001194960..daaeddbd0e7 100644 --- a/packages/gamut-styles/tsconfig.lib.json +++ b/packages/gamut-styles/tsconfig.lib.json @@ -12,6 +12,7 @@ ], "exclude": [ "jest.config.ts", + "**/__tests__/**", "**/*.spec.ts", "**/*.test.ts", "**/*.spec.tsx", diff --git a/packages/gamut-tests/package.json b/packages/gamut-tests/package.json index 6cd7fd94ad1..3ccd636fd00 100644 --- a/packages/gamut-tests/package.json +++ b/packages/gamut-tests/package.json @@ -22,7 +22,7 @@ "main": "dist/index.js", "module": "dist/index.js", "peerDependencies": { - "react": "^17.0.2 || ^18.3.0" + "react": "^17.0.2 || ^18.3.0 || ^19.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/gamut-tests/src/index.tsx b/packages/gamut-tests/src/index.tsx index 700132e1cd4..ff5d61022f6 100644 --- a/packages/gamut-tests/src/index.tsx +++ b/packages/gamut-tests/src/index.tsx @@ -24,7 +24,7 @@ export const MockGamutProvider: React.FC<{ ); }; -function withMockGamutProvider( +function withMockGamutProvider( WrappedComponent: React.ComponentType ) { const WithBoundaryComponent: React.FC = (props) => ( diff --git a/packages/gamut/package.json b/packages/gamut/package.json index 440d6032e9e..28f2622e151 100644 --- a/packages/gamut/package.json +++ b/packages/gamut/package.json @@ -13,7 +13,7 @@ "@types/marked": "^4.0.8", "@vidstack/react": "^1.12.12", "classnames": "^2.2.5", - "framer-motion": "^11.18.0", + "framer-motion": "^12.0.0", "html-to-react": "^1.6.0", "invariant": "^2.2.4", "lodash": "^4.17.23", @@ -40,8 +40,8 @@ "peerDependencies": { "@emotion/react": "^11.4.0", "@emotion/styled": "^11.3.0", - "react": "^17.0.2 || ^18.3.0", - "react-dom": "^17.0.2 || ^18.3.0" + "react": "^17.0.2 || ^18.3.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.3.0 || ^19.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/gamut/src/Anchor/__tests__/Anchor.test.tsx b/packages/gamut/src/Anchor/__tests__/Anchor.test.tsx index 7aedaca8f59..5e36e529d60 100644 --- a/packages/gamut/src/Anchor/__tests__/Anchor.test.tsx +++ b/packages/gamut/src/Anchor/__tests__/Anchor.test.tsx @@ -1,6 +1,7 @@ import { MiniWarningTriangleIcon } from '@codecademy/gamut-icons'; import { setupRtl } from '@codecademy/gamut-tests'; -import { screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; import { Anchor } from '..'; @@ -33,4 +34,21 @@ describe('Anchor', () => { expect(buttonElement).toHaveTextContent(anchorText); }); + + it('forwards ref to the anchor element', () => { + const ref = React.createRef(); + render( + + {anchorText} + + ); + expect(ref.current).toBeInstanceOf(HTMLAnchorElement); + expect(ref.current).toHaveAttribute('href', href); + }); + + it('forwards ref to the button when rendered without href', () => { + const ref = React.createRef(); + render({anchorText}); + expect(ref.current).toBeInstanceOf(HTMLButtonElement); + }); }); diff --git a/packages/gamut/src/Anchor/index.tsx b/packages/gamut/src/Anchor/index.tsx index 287ff0e6d37..677074fac9a 100644 --- a/packages/gamut/src/Anchor/index.tsx +++ b/packages/gamut/src/Anchor/index.tsx @@ -1,9 +1,19 @@ import { styledOptions, system, variant } from '@codecademy/gamut-styles'; import { StyleProps, variance } from '@codecademy/variance'; import styled from '@emotion/styled'; -import { ComponentProps, forwardRef, HTMLProps, RefObject } from 'react'; +import { + ComponentProps, + ComponentType, + forwardRef, + HTMLProps, + Ref, +} from 'react'; -import { ButtonBase, ButtonSelectors } from '../ButtonBase/ButtonBase'; +import { + ButtonBase, + ButtonSelectors, + narrowButtonBaseRef, +} from '../ButtonBase/ButtonBase'; import { AppendedIconProps, appendIconToContent } from '../helpers'; export interface AnchorProps @@ -107,11 +117,18 @@ const anchorProps = variance.compose( system.typography ); -export const AnchorBase = styled('a', styledOptions<'a'>())( +const AnchorBaseStyled = styled('a', styledOptions<'a'>())( anchorVariants, anchorProps ); +/** AnchorBase ref accepts anchor or button because it can render as ButtonBase when there is no href. */ +export const AnchorBase = AnchorBaseStyled as ComponentType< + Omit, 'ref'> & { + ref?: Ref; + } +>; + type AnchorBaseProps = | ComponentProps | (Exclude, 'ref'> & @@ -150,7 +167,7 @@ export const Anchor = forwardRef< return ( } + ref={narrowButtonBaseRef(ref)} variant={variant} {...rest} > @@ -161,7 +178,7 @@ export const Anchor = forwardRef< return ( } + ref={narrowButtonBaseRef(ref)} variant={variant} {...rest} > diff --git a/packages/gamut/src/BarChart/utils/hooks.ts b/packages/gamut/src/BarChart/utils/hooks.ts index cae93671846..a255e9d8d5a 100644 --- a/packages/gamut/src/BarChart/utils/hooks.ts +++ b/packages/gamut/src/BarChart/utils/hooks.ts @@ -211,7 +211,7 @@ const useMeasureWidth = ({ onMeasure, isMeasuring, }: { - ref: React.RefObject; + ref: React.RefObject; onMeasure: (width: number) => void; isMeasuring: boolean; }): void => { @@ -233,7 +233,7 @@ const useMeasureWidth = ({ } const element = ref.current; - const width = element?.getBoundingClientRect()?.width; + const { width } = element.getBoundingClientRect(); if (width > 0) { onMeasure(width); @@ -245,7 +245,7 @@ const useMeasureWidth = ({ export const useMeasureCategoryLabelWidth = ({ ref, }: { - ref: React.RefObject; + ref: React.RefObject; }): void => { const { setWidestCategoryLabelWidth, isMeasuring } = useBarChartContext(); useMeasureWidth({ @@ -258,7 +258,7 @@ export const useMeasureCategoryLabelWidth = ({ export const useMeasureTotalValueLabelWidth = ({ ref, }: { - ref: React.RefObject; + ref: React.RefObject; }): void => { const { setWidestTotalValueLabelWidth, isMeasuring } = useBarChartContext(); useMeasureWidth({ diff --git a/packages/gamut/src/Box/GridBox.tsx b/packages/gamut/src/Box/GridBox.tsx index b005a768020..67d562fb57c 100644 --- a/packages/gamut/src/Box/GridBox.tsx +++ b/packages/gamut/src/Box/GridBox.tsx @@ -12,3 +12,5 @@ export const GridBox = styled( gridStates, boxProps ); + +export type { GridBoxProps } from './props'; diff --git a/packages/gamut/src/Button/shared/types.ts b/packages/gamut/src/Button/shared/types.ts index 02c84d4e278..053ccd9615b 100644 --- a/packages/gamut/src/Button/shared/types.ts +++ b/packages/gamut/src/Button/shared/types.ts @@ -23,7 +23,7 @@ export type ButtonProps = ButtonBaseProps & ComponentProps; export type InlineIconButtonProps< BaseButtonType extends - | keyof JSX.IntrinsicElements + | keyof React.JSX.IntrinsicElements | React.JSXElementConstructor > = ComponentProps & Partial & { diff --git a/packages/gamut/src/ButtonBase/ButtonBase.tsx b/packages/gamut/src/ButtonBase/ButtonBase.tsx index 3441636cf52..5c365befb3e 100644 --- a/packages/gamut/src/ButtonBase/ButtonBase.tsx +++ b/packages/gamut/src/ButtonBase/ButtonBase.tsx @@ -1,12 +1,9 @@ import { css, styledOptions } from '@codecademy/gamut-styles'; import styled from '@emotion/styled'; -import { ComponentProps, forwardRef, HTMLProps, MutableRefObject } from 'react'; +import { ComponentProps, forwardRef, HTMLProps, Ref } from 'react'; export type ButtonBaseElements = HTMLAnchorElement | HTMLButtonElement; -export type ButtonBaseRef = - | ((instance: ButtonBaseElements | null) => void) - | MutableRefObject - | null; +export type ButtonBaseRef = Ref; export type ButtonBaseElementProps = HTMLProps< HTMLAnchorElement | HTMLButtonElement @@ -62,6 +59,16 @@ type ButtonBaseProps = | (Exclude, 'ref'> & ComponentProps); +/** + * Narrows a ref union (anchor | button) to the element type for the current render branch. + * Use when forwarding refs from components that render either an anchor or a button (e.g. ButtonBase, Anchor). + */ +export function narrowButtonBaseRef( + ref: Ref +): Ref { + return ref as Ref; +} + export const ButtonBase = forwardRef< HTMLButtonElement | HTMLAnchorElement, ButtonBaseProps @@ -76,7 +83,7 @@ export const ButtonBase = forwardRef< {...filteredProps} as="button" disabled={!!disabled} - ref={ref as MutableRefObject} + ref={narrowButtonBaseRef(ref)} role={role} type={type} > @@ -90,7 +97,7 @@ export const ButtonBase = forwardRef< {...rest} as="a" href={rest?.href} - ref={ref as MutableRefObject} + ref={narrowButtonBaseRef(ref)} role={role} > {children} diff --git a/packages/gamut/src/ButtonBase/__tests__/ButtonBase.test.tsx b/packages/gamut/src/ButtonBase/__tests__/ButtonBase.test.tsx index 62eeff1b449..60aea38e8a4 100644 --- a/packages/gamut/src/ButtonBase/__tests__/ButtonBase.test.tsx +++ b/packages/gamut/src/ButtonBase/__tests__/ButtonBase.test.tsx @@ -1,4 +1,6 @@ import { setupRtl } from '@codecademy/gamut-tests'; +import { render } from '@testing-library/react'; +import React from 'react'; import { ButtonBase } from '../ButtonBase'; @@ -53,4 +55,20 @@ describe('ButtonBase', () => { expect(el.getAttribute('disabled')).toBe(''); }); }); + + it('forwards ref to the button element', () => { + const ref = React.createRef(); + render({buttonText}); + expect(ref.current).toBeInstanceOf(HTMLButtonElement); + }); + + it('forwards ref to the anchor when href is provided', () => { + const ref = React.createRef(); + render( + + {buttonText} + + ); + expect(ref.current).toBeInstanceOf(HTMLAnchorElement); + }); }); diff --git a/packages/gamut/src/Card/styles.tsx b/packages/gamut/src/Card/styles.tsx index 1a080095ea7..aa64aab6a30 100644 --- a/packages/gamut/src/Card/styles.tsx +++ b/packages/gamut/src/Card/styles.tsx @@ -49,14 +49,14 @@ export const patternFadeInOut = { opacity: 1, transition: { duration: timingValues.medium / 1000, - ease: 'easeOut', + ease: 'easeOut' as const, }, }, animate: { opacity: 0, transition: { duration: timingValues.medium / 1000, - ease: 'easeIn', + ease: 'easeIn' as const, }, }, }; @@ -67,7 +67,7 @@ export const hoverShadowLeft = (borderRadius?: string) => ({ borderRadius, transition: { duration: timingValues.fast / 1000, - ease: 'easeOut', + ease: 'easeOut' as const, }, }, initialOutline: { @@ -75,7 +75,7 @@ export const hoverShadowLeft = (borderRadius?: string) => ({ borderRadius, transition: { duration: timingValues.fast / 1000, - ease: 'easeOut', + ease: 'easeOut' as const, }, }, animate: { @@ -84,7 +84,7 @@ export const hoverShadowLeft = (borderRadius?: string) => ({ borderRadius, transition: { duration: timingValues.fast / 1000, - ease: 'easeIn', + ease: 'easeIn' as const, }, }, animateOutline: { @@ -93,7 +93,7 @@ export const hoverShadowLeft = (borderRadius?: string) => ({ borderRadius, transition: { duration: timingValues.fast / 1000, - ease: 'easeIn', + ease: 'easeIn' as const, }, }, }); @@ -104,7 +104,7 @@ export const hoverShadowRight = (borderRadius?: string) => ({ borderRadius, transition: { duration: timingValues.fast / 1000, - ease: 'easeOut', + ease: 'easeOut' as const, }, }, animate: { @@ -113,7 +113,7 @@ export const hoverShadowRight = (borderRadius?: string) => ({ borderRadius, transition: { duration: timingValues.fast / 1000, - ease: 'easeIn', + ease: 'easeIn' as const, }, }, }); diff --git a/packages/gamut/src/Coachmark/index.tsx b/packages/gamut/src/Coachmark/index.tsx index a4316f6fde3..999c3c0a119 100644 --- a/packages/gamut/src/Coachmark/index.tsx +++ b/packages/gamut/src/Coachmark/index.tsx @@ -30,7 +30,7 @@ export type CoachmarkProps = PopoverFocusProps & { /** * Function that returns the contents of the coachmark. */ - renderPopover: (onDismiss?: () => void) => JSX.Element; + renderPopover: (onDismiss?: () => void) => React.JSX.Element; /** * Props to be passed into the popover component. */ diff --git a/packages/gamut/src/ConnectedForm/ConnectedForm.tsx b/packages/gamut/src/ConnectedForm/ConnectedForm.tsx index 95575145d48..1525284e29b 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedForm.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedForm.tsx @@ -181,7 +181,7 @@ export const ConnectedForm = forwardRef( ); } -) as >( - props: ConnectedFormProps, - ref: React.ForwardedRef -) => React.ReactElement; +) as React.ForwardRefExoticComponent< + ConnectedFormProps>> & + React.RefAttributes +>; diff --git a/packages/gamut/src/ConnectedForm/utils.tsx b/packages/gamut/src/ConnectedForm/utils.tsx index 95560991c7f..caf917c27ff 100644 --- a/packages/gamut/src/ConnectedForm/utils.tsx +++ b/packages/gamut/src/ConnectedForm/utils.tsx @@ -71,7 +71,7 @@ export const useConnectedForm = < () => ({ ConnectedFormGroup: ConnectedFormGroup as ConnectedGroupStrictProps, - ConnectedForm: ConnectedForm as ConnectedFormStrictProps< + ConnectedForm: ConnectedForm as unknown as ConnectedFormStrictProps< Values, ValidationRules >, diff --git a/packages/gamut/src/FeatureShimmer/__tests__/FeatureShimmer.test.tsx b/packages/gamut/src/FeatureShimmer/__tests__/FeatureShimmer.test.tsx index c5bb6d7704a..5f1bb550589 100644 --- a/packages/gamut/src/FeatureShimmer/__tests__/FeatureShimmer.test.tsx +++ b/packages/gamut/src/FeatureShimmer/__tests__/FeatureShimmer.test.tsx @@ -17,6 +17,7 @@ describe('FeatureShimmer', () => { beforeEach(() => { mockIntersectionObserver.mockReturnValue({ observe: jest.fn(), + unobserve: jest.fn(), disconnect: jest.fn(), }); window.IntersectionObserver = mockIntersectionObserver; diff --git a/packages/gamut/src/FeatureShimmer/index.tsx b/packages/gamut/src/FeatureShimmer/index.tsx index 78218b1ceca..21b2ad849c7 100644 --- a/packages/gamut/src/FeatureShimmer/index.tsx +++ b/packages/gamut/src/FeatureShimmer/index.tsx @@ -24,7 +24,7 @@ const boxVariants = { backgroundColor: 'rgba(0, 0, 0, 0)', borderColor: 'rgba(0, 0, 0, 0)', transition: { - ease: 'easeOut', + ease: 'easeOut' as const, duration: 0.3, delay: 4, }, @@ -37,12 +37,12 @@ const shimmerVariants = { backgroundColor: 'rgba(0, 0, 0, 0)', transition: { left: { - ease: 'easeInOut', + ease: 'easeInOut' as const, duration: 2, delay: 2, }, backgroundColor: { - ease: 'easeOut', + ease: 'easeOut' as const, duration: 1, delay: 4, }, diff --git a/packages/gamut/src/Form/SelectDropdown/SelectDropdown.tsx b/packages/gamut/src/Form/SelectDropdown/SelectDropdown.tsx index f31839eaa46..553d243604a 100644 --- a/packages/gamut/src/Form/SelectDropdown/SelectDropdown.tsx +++ b/packages/gamut/src/Form/SelectDropdown/SelectDropdown.tsx @@ -273,7 +273,9 @@ export const SelectDropdown: React.FC = ({ inputWidth={inputWidth} isDisabled={disabled} isMulti={multiple} - isOptionDisabled={(option) => option.disabled} + isOptionDisabled={(option: OptionStrict & { disabled?: boolean }) => + option.disabled + } isSearchable={isSearchable} menuAlignment={menuAlignment} name={name} @@ -285,7 +287,11 @@ export const SelectDropdown: React.FC = ({ styles={memoizedStyles} value={multiple ? multiValues : parsedValue} onChange={changeHandler} - onKeyDown={multiple ? (e) => keyPressHandler(e) : undefined} + onKeyDown={ + multiple + ? (e: React.KeyboardEvent) => keyPressHandler(e) + : undefined + } {...rest} /> diff --git a/packages/gamut/src/Form/SelectDropdown/elements/controls.tsx b/packages/gamut/src/Form/SelectDropdown/elements/controls.tsx index 3152ed97e2c..422584fd0f5 100644 --- a/packages/gamut/src/Form/SelectDropdown/elements/controls.tsx +++ b/packages/gamut/src/Form/SelectDropdown/elements/controls.tsx @@ -90,7 +90,7 @@ export const RemoveAllButton = (props: SizedIndicatorProps) => { selectInputRef?.current && (e.key === 'ArrowRight' || e.key === 'ArrowLeft' || e.key === 'ArrowDown') ) { - selectInputRef?.current.focus(); + selectInputRef.current.focus(); } }; diff --git a/packages/gamut/src/Form/SelectDropdown/elements/multi-value.tsx b/packages/gamut/src/Form/SelectDropdown/elements/multi-value.tsx index bb1d2002565..716941d2a22 100644 --- a/packages/gamut/src/Form/SelectDropdown/elements/multi-value.tsx +++ b/packages/gamut/src/Form/SelectDropdown/elements/multi-value.tsx @@ -109,7 +109,7 @@ export const RemoveAllButton = (props: SizedIndicatorProps) => { selectInputRef?.current && (e.key === 'ArrowRight' || e.key === 'ArrowLeft' || e.key === 'ArrowDown') ) { - selectInputRef?.current.focus(); + selectInputRef.current.focus(); } }; diff --git a/packages/gamut/src/Form/SelectDropdown/types/internal.ts b/packages/gamut/src/Form/SelectDropdown/types/internal.ts index 8a151e2102c..fabd1307227 100644 --- a/packages/gamut/src/Form/SelectDropdown/types/internal.ts +++ b/packages/gamut/src/Form/SelectDropdown/types/internal.ts @@ -14,12 +14,10 @@ export type InternalSelectProps = { }; /** - * Ref type for programmatic focus management. + * Ref type for programmatic focus management (internal refs from useRef). * Used for managing focus on select input and remove all button. */ -export type ProgramaticFocusRef = - | React.MutableRefObject - | React.MutableRefObject; +export type ProgramaticFocusRef = React.RefObject; /** * Context value for SelectDropdown internal state management. diff --git a/packages/gamut/src/Form/__tests__/__snapshots__/utils.test.tsx.snap b/packages/gamut/src/Form/__tests__/__snapshots__/utils.test.tsx.snap index 2690797df50..6db6a89db06 100644 --- a/packages/gamut/src/Form/__tests__/__snapshots__/utils.test.tsx.snap +++ b/packages/gamut/src/Form/__tests__/__snapshots__/utils.test.tsx.snap @@ -2,39 +2,63 @@ exports[`parseSelectOptions creates an option list 1`] = ` [ - , - , + { + "$$typeof": Symbol(react.transitional.element), + "_owner": null, + "_store": {}, + "key": "test-val", + "props": { + "children": "Value", + "data-testid": "test-val", + "label": "Value", + "value": "val", + }, + "type": "option", + }, + { + "$$typeof": Symbol(react.transitional.element), + "_owner": null, + "_store": {}, + "key": "test-val2", + "props": { + "children": "Value 2", + "data-testid": "test-val2", + "label": "Value 2", + "value": "val2", + }, + "type": "option", + }, ] `; exports[`parseSelectOptions creates an option list 2`] = ` [ - , - , + { + "$$typeof": Symbol(react.transitional.element), + "_owner": null, + "_store": {}, + "key": "test-val", + "props": { + "children": "val", + "data-testid": "test-val", + "label": "val", + "value": "val", + }, + "type": "option", + }, + { + "$$typeof": Symbol(react.transitional.element), + "_owner": null, + "_store": {}, + "key": "test-val2", + "props": { + "children": "val2", + "data-testid": "test-val2", + "label": "val2", + "value": "val2", + }, + "type": "option", + }, ] `; diff --git a/packages/gamut/src/GridForm/GridForm.tsx b/packages/gamut/src/GridForm/GridForm.tsx index 001e2ace889..d1d74d6df17 100644 --- a/packages/gamut/src/GridForm/GridForm.tsx +++ b/packages/gamut/src/GridForm/GridForm.tsx @@ -137,7 +137,7 @@ export function GridForm>({ const showRequiredText = hideRequiredText ? false : !hasComputedSoloField; return ( - + } display="flex" flexDirection="column" diff --git a/packages/gamut/src/Menu/MenuItem.tsx b/packages/gamut/src/Menu/MenuItem.tsx index 0429e639192..87010792494 100644 --- a/packages/gamut/src/Menu/MenuItem.tsx +++ b/packages/gamut/src/Menu/MenuItem.tsx @@ -4,7 +4,7 @@ import { ComponentProps, forwardRef, MouseEventHandler, - MutableRefObject, + Ref, useId, } from 'react'; @@ -60,6 +60,18 @@ interface MenuTextItem extends HTMLProps, ForwardListItemProps { type MenuItemTypes = MenuItemIconOnly | MenuTextItem; +type MenuItemRefElement = HTMLLIElement | HTMLAnchorElement | HTMLButtonElement; + +/** + * Narrows the forwarded ref union to a specific element type for the current render branch. + * MenuItem renders exactly one of li, a, or button per call, so the ref is forwarded to that element. + */ +function narrowMenuItemRef( + ref: React.Ref +): Ref { + return ref as Ref; +} + export const MenuItem = forwardRef< HTMLLIElement | HTMLAnchorElement | HTMLButtonElement, MenuItemTypes @@ -133,15 +145,13 @@ export const MenuItem = forwardRef< ); if (listItemType === 'link' && !disabled) { - const linkRef = ref as MutableRefObject; - return ( (ref)} target={target} > {content} @@ -152,7 +162,6 @@ export const MenuItem = forwardRef< } if (listItemType === 'button' || (listItemType === 'link' && disabled)) { - const buttonRef = ref as MutableRefObject; const handleClick: MouseEventHandler = disabled ? () => null : (props.onClick as any as MouseEventHandler); @@ -162,7 +171,7 @@ export const MenuItem = forwardRef< (ref)} onClick={handleClick} > {content} @@ -172,11 +181,12 @@ export const MenuItem = forwardRef< ); } - const liRef = ref as MutableRefObject; - return ( // These are non-interactive and will never have tooltips (nor should they). - + (ref)} + > {content} ); diff --git a/packages/gamut/src/Menu/__tests__/Menu.test.tsx b/packages/gamut/src/Menu/__tests__/Menu.test.tsx index 883c39d1604..bd8e0e50fa8 100644 --- a/packages/gamut/src/Menu/__tests__/Menu.test.tsx +++ b/packages/gamut/src/Menu/__tests__/Menu.test.tsx @@ -1,7 +1,8 @@ import { MultipleUsersIcon } from '@codecademy/gamut-icons'; import { setupRtl } from '@codecademy/gamut-tests'; -import { screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; import { Menu } from '../Menu'; import { MenuItem } from '../MenuItem'; @@ -32,6 +33,30 @@ describe('Menu', () => { screen.getByRole('link'); expect(screen.queryByRole('menuitem')).toBeNull(); }); + + it('forwards ref to the link when MenuItem has href', () => { + const ref = React.createRef(); + render( + + + Cool Town + + + ); + expect(ref.current).toBeInstanceOf(HTMLAnchorElement); + }); + + it('forwards ref to the button when MenuItem has onClick', () => { + const ref = React.createRef(); + render( + + null}> + Cool Town + + + ); + expect(ref.current).toBeInstanceOf(HTMLButtonElement); + }); it('renders MenuItems with onClicks as buttons within a li', () => { renderView({ children: null}>Cool Town, diff --git a/packages/gamut/src/Pagination/utils.tsx b/packages/gamut/src/Pagination/utils.tsx index 930c3e9cdbe..8f16cbf92ae 100644 --- a/packages/gamut/src/Pagination/utils.tsx +++ b/packages/gamut/src/Pagination/utils.tsx @@ -1,3 +1,4 @@ +import { timingValues } from '@codecademy/gamut-styles'; import { AnimatePresence, motion } from 'framer-motion'; import { useEffect, useRef } from 'react'; import * as React from 'react'; @@ -5,6 +6,8 @@ import * as React from 'react'; import { BaseEllipsisButton } from './EllipsisButton'; import { PaginationButton } from './PaginationButton'; +const FADE_DURATION_SECONDS = timingValues.base / 1000; + type PaginationUtils = { chapterSize: number; currentPage: number; @@ -76,7 +79,7 @@ export const wrapWithSlideAnimation = ( ? 'shown' : 'hidden' } - transition={{ duration: 0.3 }} + transition={{ duration: FADE_DURATION_SECONDS }} variants={slideAnimationVariants} > @@ -98,6 +101,13 @@ const fadeAnimationVariants = { }, }; +const fadeTransition = { + duration: FADE_DURATION_SECONDS, + ease: 'easeOut' as const, + transitionStart: { visibility: 'visible' as const }, + transitionEnd: { visibility: 'hidden' as const }, +}; + export const createAnimatedFadeButton = ( WrappedComponent: typeof PaginationButton ) => { @@ -110,12 +120,7 @@ export const createAnimatedFadeButton = ( } disabled={props.showButton === 'hidden'} initial={false} - transition={{ - transitionStart: { visibility: 'visible' }, - duration: 0.3, - ease: [0.04, 0.62, 0.23, 0.98], - transitionEnd: { visibility: 'hidden' }, - }} + transition={fadeTransition} variants={fadeAnimationVariants} {...props} /> diff --git a/packages/gamut/src/Popover/Popover.tsx b/packages/gamut/src/Popover/Popover.tsx index 86bbb05d9ee..c83e4f7a6ac 100755 --- a/packages/gamut/src/Popover/Popover.tsx +++ b/packages/gamut/src/Popover/Popover.tsx @@ -3,6 +3,7 @@ import { useWindowScroll, useWindowSize } from 'react-use'; import { FocusTrap } from '../FocusTrap'; import { + getRefElement, useResizingParentEffect, useScrollingParentsEffect, } from '../PopoverContainer/hooks'; @@ -137,12 +138,12 @@ export const Popover: React.FC = ({ ]); useEffect(() => { - setTargetRect(targetRef?.current?.getBoundingClientRect()); + setTargetRect(getRefElement(targetRef)?.getBoundingClientRect()); }, [targetRef, isOpen, width, height, x, y]); const updateTargetPosition = useCallback( (rect?: DOMRect) => { - const target = targetRef?.current; + const target = getRefElement(targetRef); if (!target) return; const newRect = rect || target.getBoundingClientRect(); @@ -152,7 +153,6 @@ export const Popover: React.FC = ({ ); useScrollingParentsEffect(targetRef, updateTargetPosition); - useResizingParentEffect(targetRef, setTargetRect); useEffect(() => { @@ -172,7 +172,7 @@ export const Popover: React.FC = ({ const handleClickOutside = useCallback( (e: MouseEvent) => { const target = e.target as Node; - const targetElement = targetRef.current; + const targetElement = getRefElement(targetRef); if (!targetElement) return; diff --git a/packages/gamut/src/Popover/__tests__/Popover.test.tsx b/packages/gamut/src/Popover/__tests__/Popover.test.tsx index fe3700e3b9d..86575610a03 100644 --- a/packages/gamut/src/Popover/__tests__/Popover.test.tsx +++ b/packages/gamut/src/Popover/__tests__/Popover.test.tsx @@ -2,7 +2,8 @@ import { CheckerDense } from '@codecademy/gamut-patterns'; import { theme } from '@codecademy/gamut-styles'; import { setupRtl } from '@codecademy/gamut-tests'; import { ThemeProvider } from '@emotion/react'; -import { fireEvent } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; import { Popover, PopoverProps } from '..'; @@ -57,6 +58,23 @@ describe('Popover', () => { expect(popoverIsRendered(view)).toBeTruthy(); }); + it('accepts targetRef from useRef and renders when open', () => { + const PopoverWithUseRefTarget = () => { + const targetRef = React.useRef(null); + return ( + +
+ +
Content
+
+
outside
+ + ); + }; + render(); + expect(screen.getByTestId('popover-content')).toBeInTheDocument(); + }); + it('triggers onRequestClose callback when clicking outside', () => { const onRequestClose = jest.fn(); const { view } = renderView({ diff --git a/packages/gamut/src/Popover/types.tsx b/packages/gamut/src/Popover/types.tsx index b85d4d4595c..e563b60b0b4 100755 --- a/packages/gamut/src/Popover/types.tsx +++ b/packages/gamut/src/Popover/types.tsx @@ -97,17 +97,14 @@ export type PopoverProps = PopoverBaseProps & /** * The target element around which the popover will be positioned. + * Only ref objects (e.g. from useRef) are supported at runtime; RefCallback is not. */ - targetRef: React.RefObject< - Pick - >; + targetRef: React.Ref; /** * The PopoverContainer which contents will be rendered into. */ - popoverContainerRef?: - | React.RefObject - | React.RefCallback; + popoverContainerRef?: React.Ref; /** * Whether to add width restrictions to Popover. diff --git a/packages/gamut/src/PopoverContainer/PopoverContainer.tsx b/packages/gamut/src/PopoverContainer/PopoverContainer.tsx index 09c66536ff1..032364e2ae4 100644 --- a/packages/gamut/src/PopoverContainer/PopoverContainer.tsx +++ b/packages/gamut/src/PopoverContainer/PopoverContainer.tsx @@ -8,11 +8,13 @@ import { useWindowScroll, useWindowSize } from 'react-use'; import { BodyPortal } from '../BodyPortal'; import { FocusTrap } from '../FocusTrap'; import { + getRefElement, + getTargetAsElement, useResizingParentEffect, useScrollingParents, useScrollingParentsEffect, } from './hooks'; -import { ContainerState, PopoverContainerProps } from './types'; +import { ContainerState, PopoverContainerProps, TargetRef } from './types'; import { getContainers, getPosition, isOutOfView } from './utils'; const PopoverContent = styled.div( @@ -49,10 +51,7 @@ export const PopoverContainer: React.FC = ({ const [targetRect, setTargetRect] = useState(); const parent = containers?.parent; - // Memoize scrolling parents to avoid expensive DOM traversals - const scrollingParents = useScrollingParents( - targetRef as React.RefObject - ); + const scrollingParents = useScrollingParents(targetRef); // Keep onRequestClose ref up to date useEffect(() => { @@ -64,7 +63,7 @@ export const PopoverContainer: React.FC = ({ const [isRtl, setIsRtl] = useState(false); useEffect(() => { const checkDirection = () => { - const target = targetRef?.current; + const target = getRefElement(targetRef); const el = target instanceof Element ? target : document.documentElement; setIsRtl(getComputedStyle(el).direction === 'rtl'); }; @@ -96,20 +95,22 @@ export const PopoverContainer: React.FC = ({ }, [parent, x, y, offset, alignment, invertAxis, isRtl]); useEffect(() => { - const target = targetRef?.current; + const target = getRefElement(targetRef); if (!target) return; - setContainers(getContainers(target, inline, { x: winX, y: winY })); + setContainers( + getContainers(target as TargetRef, inline, { x: winX, y: winY }) + ); }, [targetRef, inline, winW, winH, winX, winY, targetRect]); // Update target rectangle when window size/scroll changes useEffect(() => { - setTargetRect(targetRef?.current?.getBoundingClientRect()); + setTargetRect(getRefElement(targetRef)?.getBoundingClientRect()); }, [targetRef, isOpen, winW, winH, winX, winY]); // Update target rectangle when parent size/scroll changes const updateTargetPosition = useCallback( (rect?: DOMRect) => { - const target = targetRef?.current; + const target = getRefElement(targetRef); if (!target) return; const newRect = rect || target.getBoundingClientRect(); @@ -121,14 +122,16 @@ export const PopoverContainer: React.FC = ({ window.pageYOffset || document.documentElement.scrollTop; setContainers( - getContainers(target, inline, { x: currentScrollX, y: currentScrollY }) + getContainers(target as TargetRef, inline, { + x: currentScrollX, + y: currentScrollY, + }) ); }, [targetRef, inline] ); useScrollingParentsEffect(targetRef, updateTargetPosition); - useResizingParentEffect(targetRef, setTargetRect); // Handle closeOnViewportExit with cached scrolling parents for performance @@ -140,7 +143,7 @@ export const PopoverContainer: React.FC = ({ const isOut = isOutOfView( rect, - targetRef?.current as HTMLElement, + getTargetAsElement(getRefElement(targetRef)) ?? undefined, scrollingParents ); @@ -165,7 +168,7 @@ export const PopoverContainer: React.FC = ({ const handleClickOutside = useCallback( (e: MouseEvent | TouchEvent) => { const target = e.target as Node; - const targetElement = targetRef.current; + const targetElement = getRefElement(targetRef); if (!targetElement) return; if (targetElement.contains(target)) return; @@ -184,7 +187,7 @@ export const PopoverContainer: React.FC = ({ const handleGlobalClickOutside = useCallback( (e: MouseEvent) => { const target = e.target as Node; - const targetElement = targetRef.current; + const targetElement = getRefElement(targetRef); if (!targetElement || !isOpen) return; diff --git a/packages/gamut/src/PopoverContainer/__tests__/PopoverContainer.test.tsx b/packages/gamut/src/PopoverContainer/__tests__/PopoverContainer.test.tsx index ebb006d5ea0..ddd0c1505aa 100644 --- a/packages/gamut/src/PopoverContainer/__tests__/PopoverContainer.test.tsx +++ b/packages/gamut/src/PopoverContainer/__tests__/PopoverContainer.test.tsx @@ -1,5 +1,6 @@ import { MockGamutProvider, setupRtl } from '@codecademy/gamut-tests'; import { cleanup, fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; import { PopoverContainer } from '..'; import { PopoverContainerProps, TargetRef } from '../types'; @@ -91,6 +92,23 @@ describe('Popover', () => { expect(popoverIsRendered()).toBeTruthy(); }); + it('accepts targetRef from useRef and renders when open', () => { + const ContainerWithUseRefTarget = () => { + const targetRef = React.useRef(null); + return ( + +
+ +
Content
+
+
outside
+ + ); + }; + render(); + expect(screen.getByTestId('popover-content')).toBeInTheDocument(); + }); + it('triggers onRequestClose callback when clicking outside', () => { const onRequestClose = jest.fn(); renderView({ diff --git a/packages/gamut/src/PopoverContainer/hooks.ts b/packages/gamut/src/PopoverContainer/hooks.ts index 295591da7cd..627640a9b1e 100644 --- a/packages/gamut/src/PopoverContainer/hooks.ts +++ b/packages/gamut/src/PopoverContainer/hooks.ts @@ -2,27 +2,50 @@ import { useEffect, useMemo } from 'react'; import { findAllAdditionalScrollingParents, findResizingParent } from './utils'; +/** + * Minimal element shape required for popover positioning. + * Accepts both HTMLElement and TargetRef so Popover and PopoverContainer can share hooks. + */ +export interface PopoverTargetElement { + getBoundingClientRect(): DOMRect; + contains(other: Node | null): boolean; +} + +/** Resolves Ref to current element; returns null for RefCallback or null ref. */ +export function getRefElement( + ref: React.Ref +): PopoverTargetElement | null { + if (ref == null) return null; + if (typeof ref === 'function') return null; + return ref.current; +} + +/** Casts minimal target to HTMLElement for utils that need full DOM (e.g. parentElement). */ +export function getTargetAsElement( + target: PopoverTargetElement | null +): HTMLElement | null { + return target as HTMLElement | null; +} + export const useScrollingParentsEffect = ( - targetRef: React.RefObject< - Pick - >, + targetRef: React.Ref, setTargetRect: (rect: DOMRect | undefined) => void ) => { useEffect(() => { - if (!targetRef.current) { - return; - } + const target = getRefElement(targetRef); + if (!target) return; - const target = targetRef.current as unknown as HTMLElement; - const scrollingParents = findAllAdditionalScrollingParents(target); + const scrollingParents = findAllAdditionalScrollingParents( + getTargetAsElement(target)! + ); const updatePosition = () => { - setTargetRect(targetRef?.current?.getBoundingClientRect()); + const el = getRefElement(targetRef); + setTargetRect(el?.getBoundingClientRect()); }; const cleanup: (() => void)[] = []; - // Add listeners to all scrolling parents (window scroll handled by useWindowScroll) scrollingParents.forEach((parent) => { if (parent.addEventListener) { parent.addEventListener('scroll', updatePosition, { passive: true }); @@ -39,24 +62,18 @@ export const useScrollingParentsEffect = ( }; export const useResizingParentEffect = ( - targetRef: React.RefObject< - Pick - >, + targetRef: React.Ref, setTargetRect: (rect: DOMRect | undefined) => void ) => { useEffect(() => { - // handles movement of target within a clipped container e.g. Drawer - if (!targetRef.current || typeof ResizeObserver === 'undefined') { - return; - } - const resizingParent = findResizingParent( - targetRef.current as unknown as HTMLElement - ); - if (!resizingParent?.addEventListener) { - return; - } + const target = getRefElement(targetRef); + if (!target || typeof ResizeObserver === 'undefined') return; + + const resizingParent = findResizingParent(getTargetAsElement(target)!); + if (!resizingParent?.addEventListener) return; + const handler = () => { - setTargetRect(targetRef?.current?.getBoundingClientRect()); + setTargetRect(getRefElement(targetRef)?.getBoundingClientRect()); }; const ro = new ResizeObserver(handler); ro.observe(resizingParent); @@ -66,17 +83,14 @@ export const useResizingParentEffect = ( /** * Memoizes the list of scrolling parent elements for a target element. - * This avoids expensive DOM traversals and getComputedStyle calls on every render. * Returns an empty array if the target element is not available. */ export const useScrollingParents = ( - targetRef: React.RefObject + targetRef: React.Ref ): HTMLElement[] => { return useMemo(() => { - if (!targetRef.current) { - return []; - } - return findAllAdditionalScrollingParents(targetRef.current); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [targetRef.current]); + const target = getRefElement(targetRef); + if (!target) return []; + return findAllAdditionalScrollingParents(getTargetAsElement(target)!); + }, [targetRef]); }; diff --git a/packages/gamut/src/PopoverContainer/types.ts b/packages/gamut/src/PopoverContainer/types.ts index 2c14f860579..76935abe9fa 100644 --- a/packages/gamut/src/PopoverContainer/types.ts +++ b/packages/gamut/src/PopoverContainer/types.ts @@ -1,4 +1,4 @@ -import { RefObject } from 'react'; +import { Ref } from 'react'; import { WithChildrenProp } from '../utils'; @@ -81,8 +81,9 @@ export interface PopoverContainerProps onRequestClose?: () => void; /** * The target element around which the popover will be positioned. + * Only ref objects (e.g. from useRef) are supported at runtime; RefCallback is not. */ - targetRef: RefObject; + targetRef: Ref; /** * If true, it will allow outside page interaction. Popover container will still close when clicking outside of the popover or hitting the escape key. */ diff --git a/packages/gamut/src/Tip/__tests__/helpers.tsx b/packages/gamut/src/Tip/__tests__/helpers.tsx index da836cbef31..c8adbfbcaf2 100644 --- a/packages/gamut/src/Tip/__tests__/helpers.tsx +++ b/packages/gamut/src/Tip/__tests__/helpers.tsx @@ -17,7 +17,7 @@ type LinkTextParam = { linkText: string }; type InfoParam = { info: string }; type PlacementParam = { placement: TipPlacements }; -export const createFocusOnClick = (ref: RefObject) => { +export const createFocusOnClick = (ref: RefObject) => { return ({ isTipHidden }: { isTipHidden: boolean }) => { if (!isTipHidden) ref.current?.focus(); }; @@ -419,24 +419,18 @@ export const openInfoTipsWithKeyboard = async ({ }) => { const buttons = view.getAllByLabelText('Show information'); - await act(async () => { - buttons[0].focus(); - await userEvent.keyboard('{Enter}'); - - for (let i = 1; i < count; i += 1) { - // eslint-disable-next-line no-await-in-loop - await userEvent.tab(); - // eslint-disable-next-line no-await-in-loop + const openNext = async (i: number): Promise => { + if (i >= count) return; + await act(async () => { + buttons[i].focus(); await userEvent.keyboard('{Enter}'); - } - }); - - // Wait for all tips to finish opening - await waitFor(() => { - buttons.forEach((button) => { - expect(button).toHaveAttribute('aria-expanded', 'true'); }); - }); + await waitFor(() => { + expect(buttons[i]).toHaveAttribute('aria-expanded', 'true'); + }); + await openNext(i + 1); + }; + await openNext(0); }; export const expectTipsVisible = (tips: { text: string }[]) => { diff --git a/packages/gamut/src/Tip/shared/FloatingTip.tsx b/packages/gamut/src/Tip/shared/FloatingTip.tsx index c3013661549..23ff041957b 100644 --- a/packages/gamut/src/Tip/shared/FloatingTip.tsx +++ b/packages/gamut/src/Tip/shared/FloatingTip.tsx @@ -43,8 +43,8 @@ export const FloatingTip: React.FC = ({ const [isFocused, setIsFocused] = useState(false); // Use refs to store timeouts to prevent race conditions - const hoverDelayRef = useRef(); - const focusDelayRef = useRef(); + const hoverDelayRef = useRef(undefined); + const focusDelayRef = useRef(undefined); const commonPopoverProps = getPopoverAlignmentAndPattern({ alignment, type }); const dims = getAlignmentStyles({ avatar, alignment, type }); diff --git a/packages/gamut/src/Tip/shared/types.tsx b/packages/gamut/src/Tip/shared/types.tsx index 164ed1aaf37..32299dd678e 100644 --- a/packages/gamut/src/Tip/shared/types.tsx +++ b/packages/gamut/src/Tip/shared/types.tsx @@ -78,10 +78,8 @@ export type TipPlacementComponentProps = Omit< escapeKeyPressHandler?: (event: React.KeyboardEvent) => void; id?: string; isTipHidden?: boolean; - contentRef?: - | React.RefObject - | ((node: HTMLDivElement | null) => void); + contentRef?: React.Ref; type: 'info' | 'tool' | 'preview'; - wrapperRef?: React.RefObject; + wrapperRef?: React.Ref; zIndex?: number; } & React.PropsWithChildren; diff --git a/packages/gamut/src/utils/react.ts b/packages/gamut/src/utils/react.ts index c0b673bb24d..a3ebd4391cb 100644 --- a/packages/gamut/src/utils/react.ts +++ b/packages/gamut/src/utils/react.ts @@ -35,7 +35,11 @@ export const extractTextContent = (children: React.ReactNode): string => { return ''; } if (isValidElement(child)) { - const textContent = child.props.children ?? child.props.text ?? ''; + const props = child.props as { + children?: React.ReactNode; + text?: string; + }; + const textContent = props.children ?? props.text ?? ''; return extractTextContent(textContent); } return ''; diff --git a/packages/variance/integration/__tests__/component.test.tsx b/packages/variance/integration/__tests__/component.test.tsx index bdc494b5fca..e3355484625 100644 --- a/packages/variance/integration/__tests__/component.test.tsx +++ b/packages/variance/integration/__tests__/component.test.tsx @@ -1,13 +1,13 @@ import { matchers } from '@emotion/jest'; import { ThemeProvider } from '@emotion/react'; import styled from '@emotion/styled'; +import { render } from '@testing-library/react'; import { ComponentProps } from 'react'; import * as React from 'react'; -import renderer from 'react-test-renderer'; import { variance } from '../../src/core'; import { theme } from '../__fixtures__/theme'; -// Add the custom matchers provided by '@emotion/jest' + expect.extend(matchers); const styles = variance.create({ @@ -27,16 +27,20 @@ const setupRender = >( Component: T, defaultProps?: P ) => { - return (props?: P) => { + return (props?: P): HTMLElement => { const mergedProps = { ...defaultProps, ...props }; - return renderer - .create( - - - - ) - .toJSON(); + const { container } = render( + + + + ); + + const el = container.firstElementChild; + if (!el || !(el instanceof HTMLElement)) { + throw new Error('Expected a styled root element'); + } + return el; }; }; diff --git a/yarn.lock b/yarn.lock index ee3b5280d1c..9d672d952d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -283,7 +283,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.28.6, @babel/helper-plugin-utils@npm:^7.8.0": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": version: 7.28.6 resolution: "@babel/helper-plugin-utils@npm:7.28.6" checksum: 10c0/3f5f8acc152fdbb69a84b8624145ff4f9b9f6e776cb989f9f968f8606eb7185c5c3cfcf3ba08534e37e1e0e1c118ac67080610333f56baa4f7376c99b5f1143d @@ -594,13 +594,13 @@ __metadata: linkType: hard "@babel/plugin-syntax-jsx@npm:^7.25.9, @babel/plugin-syntax-jsx@npm:^7.27.1, @babel/plugin-syntax-jsx@npm:^7.7.2": - version: 7.28.6 - resolution: "@babel/plugin-syntax-jsx@npm:7.28.6" + version: 7.27.1 + resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/b98fc3cd75e4ca3d5ca1162f610c286e14ede1486e0d297c13a5eb0ac85680ac9656d17d348bddd9160a54d797a08cea5eaac02b9330ddebb7b26732b7b99fb5 + checksum: 10c0/bc5afe6a458d5f0492c02a54ad98c5756a0c13bd6d20609aae65acd560a9e141b0876da5f358dce34ea136f271c1016df58b461184d7ae9c4321e0f98588bc84 languageName: node linkType: hard @@ -693,13 +693,13 @@ __metadata: linkType: hard "@babel/plugin-syntax-typescript@npm:^7.25.9, @babel/plugin-syntax-typescript@npm:^7.27.1, @babel/plugin-syntax-typescript@npm:^7.3.3, @babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.28.6 - resolution: "@babel/plugin-syntax-typescript@npm:7.28.6" + version: 7.27.1 + resolution: "@babel/plugin-syntax-typescript@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/b0c392a35624883ac480277401ac7d92d8646b66e33639f5d350de7a6723924265985ae11ab9ebd551740ded261c443eaa9a87ea19def9763ca1e0d78c97dea8 + checksum: 10c0/11589b4c89c66ef02d57bf56c6246267851ec0c361f58929327dc3e070b0dab644be625bbe7fb4c4df30c3634bfdfe31244e1f517be397d2def1487dbbe3c37d languageName: node linkType: hard @@ -1626,7 +1626,7 @@ __metadata: "@emotion/react": ^11.4.0 "@emotion/styled": ^11.3.0 lodash: ^4.17.23 - react: ^17.0.2 || ^18.3.0 + react: ^17.0.2 || ^18.3.0 || ^19.0.0 languageName: unknown linkType: soft @@ -1639,8 +1639,8 @@ __metadata: peerDependencies: "@emotion/react": ^11.4.0 "@emotion/styled": ^11.3.0 - react: ^17.0.2 || ^18.3.0 - react-dom: ^17.0.2 || ^18.3.0 + react: ^17.0.2 || ^18.3.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.3.0 || ^19.0.0 languageName: unknown linkType: soft @@ -1669,8 +1669,8 @@ __metadata: peerDependencies: "@emotion/react": ^11.4.0 "@emotion/styled": ^11.3.0 - react: ^17.0.2 || ^18.3.0 - react-dom: ^17.0.2 || ^18.3.0 + react: ^17.0.2 || ^18.3.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.3.0 || ^19.0.0 languageName: unknown linkType: soft @@ -1680,7 +1680,7 @@ __metadata: dependencies: "@codecademy/variance": "npm:0.26.1" "@emotion/is-prop-valid": "npm:^1.1.0" - framer-motion: "npm:^11.18.0" + framer-motion: "npm:^12.0.0" get-nonce: "npm:^1.0.0" polished: "npm:^4.1.2" peerDependencies: @@ -1688,7 +1688,7 @@ __metadata: "@emotion/react": ^11.4.0 "@emotion/styled": ^11.3.0 lodash: ^4.17.23 - react: ^17.0.2 || ^18.3.0 + react: ^17.0.2 || ^18.3.0 || ^19.0.0 stylis: ^4.0.7 languageName: unknown linkType: soft @@ -1701,7 +1701,7 @@ __metadata: component-test-setup: "npm:*" lodash: "npm:^4.17.23" peerDependencies: - react: ^17.0.2 || ^18.3.0 + react: ^17.0.2 || ^18.3.0 || ^19.0.0 languageName: unknown linkType: soft @@ -1718,7 +1718,7 @@ __metadata: "@types/marked": "npm:^4.0.8" "@vidstack/react": "npm:^1.12.12" classnames: "npm:^2.2.5" - framer-motion: "npm:^11.18.0" + framer-motion: "npm:^12.0.0" html-to-react: "npm:^1.6.0" invariant: "npm:^2.2.4" lodash: "npm:^4.17.23" @@ -1735,8 +1735,8 @@ __metadata: peerDependencies: "@emotion/react": ^11.4.0 "@emotion/styled": ^11.3.0 - react: ^17.0.2 || ^18.3.0 - react-dom: ^17.0.2 || ^18.3.0 + react: ^17.0.2 || ^18.3.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.3.0 || ^19.0.0 languageName: unknown linkType: soft @@ -3094,12 +3094,12 @@ __metadata: linkType: hard "@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": - version: 0.3.31 - resolution: "@jridgewell/trace-mapping@npm:0.3.31" + version: 0.3.30 + resolution: "@jridgewell/trace-mapping@npm:0.3.30" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + checksum: 10c0/3a1516c10f44613b9ba27c37a02ff8f410893776b2b3dad20a391b51b884dd60f97bbb56936d65d2ff8fe978510a0000266654ab8426bdb9ceb5fb4585b19e23 languageName: node linkType: hard @@ -7309,22 +7309,6 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:^8.11.1": - version: 8.20.1 - resolution: "@testing-library/dom@npm:8.20.1" - dependencies: - "@babel/code-frame": "npm:^7.10.4" - "@babel/runtime": "npm:^7.12.5" - "@types/aria-query": "npm:^5.0.1" - aria-query: "npm:5.1.3" - chalk: "npm:^4.1.0" - dom-accessibility-api: "npm:^0.5.9" - lz-string: "npm:^1.5.0" - pretty-format: "npm:^27.0.2" - checksum: 10c0/614013756706467f2a7f3f693c18377048c210ec809884f0f9be866f7d865d075805ad15f5d100e8a699467fdde09085bf79e23a00ea0a6ab001d9583ef15e5d - languageName: node - linkType: hard - "@testing-library/jest-dom@npm:^5.16.1": version: 5.17.0 resolution: "@testing-library/jest-dom@npm:5.17.0" @@ -7378,21 +7362,23 @@ __metadata: languageName: node linkType: hard -"@testing-library/react@npm:15.0.6": - version: 15.0.6 - resolution: "@testing-library/react@npm:15.0.6" +"@testing-library/react@npm:^16.0.0": + version: 16.3.2 + resolution: "@testing-library/react@npm:16.3.2" dependencies: "@babel/runtime": "npm:^7.12.5" - "@testing-library/dom": "npm:^10.0.0" - "@types/react-dom": "npm:^18.0.0" peerDependencies: - "@types/react": ^18.0.0 - react: ^18.0.0 - react-dom: ^18.0.0 + "@testing-library/dom": ^10.0.0 + "@types/react": ^18.0.0 || ^19.0.0 + "@types/react-dom": ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/3705a2272f929f2f848f5d7e6ac9829bf7ecc1725a35733ffae7e7a261d4bdab470b080558e8544edb1f9ba25db9fbc4232527df9b4ec6ab6ae4462a902a7f95 + "@types/react-dom": + optional: true + checksum: 10c0/f9c7f0915e1b5f7b750e6c7d8b51f091b8ae7ea99bacb761d7b8505ba25de9cfcb749a0f779f1650fb268b499dd79165dc7e1ee0b8b4cb63430d3ddc81ffe044 languageName: node linkType: hard @@ -7846,13 +7832,6 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*": - version: 15.7.13 - resolution: "@types/prop-types@npm:15.7.13" - checksum: 10c0/1b20fc67281902c6743379960247bc161f3f0406ffc0df8e7058745a85ea1538612109db0406290512947f9632fe9e10e7337bf0ce6338a91d6c948df16a7c61 - languageName: node - linkType: hard - "@types/q@npm:^1.5.1": version: 1.5.8 resolution: "@types/q@npm:1.5.8" @@ -7874,21 +7853,21 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:18.3.1": - version: 18.3.1 - resolution: "@types/react-dom@npm:18.3.1" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/8b416551c60bb6bd8ec10e198c957910cfb271bc3922463040b0d57cf4739cdcd24b13224f8d68f10318926e1ec3cd69af0af79f0291b599a992f8c80d47f1eb +"@types/react-dom@npm:^19.0.0": + version: 19.2.3 + resolution: "@types/react-dom@npm:19.2.3" + peerDependencies: + "@types/react": ^19.2.0 + checksum: 10c0/b486ebe0f4e2fb35e2e108df1d8fc0927ca5d6002d5771e8a739de11239fe62d0e207c50886185253c99eb9dedfeeb956ea7429e5ba17f6693c7acb4c02f8cd1 languageName: node linkType: hard -"@types/react-test-renderer@npm:18.3.0, @types/react-test-renderer@npm:>=16.9.0": - version: 18.3.0 - resolution: "@types/react-test-renderer@npm:18.3.0" +"@types/react-test-renderer@npm:>=16.9.0, @types/react-test-renderer@npm:^19.0.0": + version: 19.1.0 + resolution: "@types/react-test-renderer@npm:19.1.0" dependencies: "@types/react": "npm:*" - checksum: 10c0/3c9748be52e8e659e7adf91dea6939486463264e6f633bf21c4cb116de18af7bef0595568a1e588160420b2f65289473075dda1cb417c2875df8cf7a09f5d913 + checksum: 10c0/799654e430df10aeaf267d71507fb64ec151359ead7e3774111bfd4abce7e0911dba461811195c06c22a6d17496ea92537d3185320ff4112fe29954cad1b9152 languageName: node linkType: hard @@ -7901,13 +7880,12 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:18.3.27": - version: 18.3.27 - resolution: "@types/react@npm:18.3.27" +"@types/react@npm:^19.0.0": + version: 19.2.14 + resolution: "@types/react@npm:19.2.14" dependencies: - "@types/prop-types": "npm:*" csstype: "npm:^3.2.2" - checksum: 10c0/a761d2f58de03d0714806cc65d32bb3d73fb33a08dd030d255b47a295e5fff2a775cf1c20b786824d8deb6454eaccce9bc6998d9899c14fc04bbd1b0b0b72897 + checksum: 10c0/7d25bf41b57719452d86d2ac0570b659210402707313a36ee612666bf11275a1c69824f8c3ee1fdca077ccfe15452f6da8f1224529b917050eb2d861e52b59b7 languageName: node linkType: hard @@ -8638,11 +8616,11 @@ __metadata: linkType: hard "acorn-walk@npm:^8.0.2, acorn-walk@npm:^8.1.1": - version: 8.3.5 - resolution: "acorn-walk@npm:8.3.5" + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" dependencies: acorn: "npm:^8.11.0" - checksum: 10c0/e31bf5b5423ed1349437029d66d708b9fbd1b77a644b031501e2c753b028d13b56348210ed901d5b1d0d86eb3381c0a0fc0d0998511a9d546d1194936266a332 + checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 languageName: node linkType: hard @@ -8908,15 +8886,6 @@ __metadata: languageName: node linkType: hard -"aria-query@npm:5.1.3": - version: 5.1.3 - resolution: "aria-query@npm:5.1.3" - dependencies: - deep-equal: "npm:^2.0.5" - checksum: 10c0/edcbc8044c4663d6f88f785e983e6784f98cb62b4ba1e9dd8d61b725d0203e4cfca38d676aee984c31f354103461102a3d583aa4fbe4fd0a89b679744f4e5faf - languageName: node - linkType: hard - "aria-query@npm:5.3.0": version: 5.3.0 resolution: "aria-query@npm:5.3.0" @@ -8933,7 +8902,7 @@ __metadata: languageName: node linkType: hard -"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1": +"array-buffer-byte-length@npm:^1.0.1": version: 1.0.1 resolution: "array-buffer-byte-length@npm:1.0.1" dependencies: @@ -10165,9 +10134,9 @@ __metadata: linkType: hard "collect-v8-coverage@npm:^1.0.0, collect-v8-coverage@npm:^1.0.2": - version: 1.0.3 - resolution: "collect-v8-coverage@npm:1.0.3" - checksum: 10c0/bc62ba251bcce5e3354a8f88fa6442bee56e3e612fec08d4dfcf66179b41ea0bf544b0f78c4ebc0f8050871220af95bb5c5578a6aef346feea155640582f09dc + version: 1.0.2 + resolution: "collect-v8-coverage@npm:1.0.2" + checksum: 10c0/ed7008e2e8b6852c5483b444a3ae6e976e088d4335a85aa0a9db2861c5f1d31bd2d7ff97a60469b3388deeba661a619753afbe201279fb159b4b9548ab8269a1 languageName: node linkType: hard @@ -11094,14 +11063,14 @@ __metadata: linkType: hard "dedent@npm:^1.0.0, dedent@npm:^1.6.0": - version: 1.7.2 - resolution: "dedent@npm:1.7.2" + version: 1.7.0 + resolution: "dedent@npm:1.7.0" peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: babel-plugin-macros: optional: true - checksum: 10c0/acaff07cac355b93f17b1b17ebbb84d3cc55af6ab4b7814c3f505e061903e168bc6bf9ddce331552d64dee1525f0b4c549c9ade46aebfac6f69caaed74e90751 + checksum: 10c0/c5e8a8beb5072bd5e520cb64b27a82d7ec3c2a63ee5ce47dbc2a05d5b7700cefd77a992a752cd0a8b1d979c1db06b14fb9486e805f3ad6088eda6e07cd9bf2d5 languageName: node linkType: hard @@ -11112,32 +11081,6 @@ __metadata: languageName: node linkType: hard -"deep-equal@npm:^2.0.5": - version: 2.2.3 - resolution: "deep-equal@npm:2.2.3" - dependencies: - array-buffer-byte-length: "npm:^1.0.0" - call-bind: "npm:^1.0.5" - es-get-iterator: "npm:^1.1.3" - get-intrinsic: "npm:^1.2.2" - is-arguments: "npm:^1.1.1" - is-array-buffer: "npm:^3.0.2" - is-date-object: "npm:^1.0.5" - is-regex: "npm:^1.1.4" - is-shared-array-buffer: "npm:^1.0.2" - isarray: "npm:^2.0.5" - object-is: "npm:^1.1.5" - object-keys: "npm:^1.1.1" - object.assign: "npm:^4.1.4" - regexp.prototype.flags: "npm:^1.5.1" - side-channel: "npm:^1.0.4" - which-boxed-primitive: "npm:^1.0.2" - which-collection: "npm:^1.0.1" - which-typed-array: "npm:^1.1.13" - checksum: 10c0/a48244f90fa989f63ff5ef0cc6de1e4916b48ea0220a9c89a378561960814794a5800c600254482a2c8fd2e49d6c2e196131dc983976adb024c94a42dfe4949f - languageName: node - linkType: hard - "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -11857,23 +11800,6 @@ __metadata: languageName: node linkType: hard -"es-get-iterator@npm:^1.1.3": - version: 1.1.3 - resolution: "es-get-iterator@npm:1.1.3" - dependencies: - call-bind: "npm:^1.0.2" - get-intrinsic: "npm:^1.1.3" - has-symbols: "npm:^1.0.3" - is-arguments: "npm:^1.1.1" - is-map: "npm:^2.0.2" - is-set: "npm:^2.0.2" - is-string: "npm:^1.0.7" - isarray: "npm:^2.0.5" - stop-iteration-iterator: "npm:^1.0.0" - checksum: 10c0/ebd11effa79851ea75d7f079405f9d0dc185559fd65d986c6afea59a0ff2d46c2ed8675f19f03dce7429d7f6c14ff9aede8d121fbab78d75cfda6a263030bac0 - languageName: node - linkType: hard - "es-iterator-helpers@npm:^1.1.0": version: 1.1.0 resolution: "es-iterator-helpers@npm:1.1.0" @@ -13104,12 +13030,12 @@ __metadata: languageName: node linkType: hard -"framer-motion@npm:^11.18.0": - version: 11.18.2 - resolution: "framer-motion@npm:11.18.2" +"framer-motion@npm:^12.0.0": + version: 12.37.0 + resolution: "framer-motion@npm:12.37.0" dependencies: - motion-dom: "npm:^11.18.1" - motion-utils: "npm:^11.18.1" + motion-dom: "npm:^12.37.0" + motion-utils: "npm:^12.36.0" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -13122,7 +13048,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10c0/41b1ef1b4e54ea13adaf01d61812a8783d2352f74641c91b50519775704bc6274db6b6863ff494a1f705fa6c6ed8f4df3497292327c906d53ea0129cef3ec361 + checksum: 10c0/6bdf132876e5a323c60ba3930bf2d894338f6235d942b219cf3068b330ecc7c915d9b8ee6631f89ddcde7a4ba7373bad3abf40d19d2c882b186697cbe11ee311 languageName: node linkType: hard @@ -13363,18 +13289,18 @@ __metadata: "@svgr/cli": "npm:5.5.0" "@swc-node/register": "npm:^1.11.1" "@swc/core": "npm:^1.15.18" - "@testing-library/dom": "npm:^8.11.1" + "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^5.16.1" - "@testing-library/react": "npm:15.0.6" + "@testing-library/react": "npm:^16.0.0" "@testing-library/react-hooks": "npm:^7.0.2" "@testing-library/user-event": "npm:^14.5.2" "@types/classnames": "npm:2.2.10" "@types/invariant": "npm:2.2.29" "@types/konami-code-js": "npm:^0.8.0" "@types/lodash": "npm:4.17.23" - "@types/react": "npm:18.3.27" - "@types/react-dom": "npm:18.3.1" - "@types/react-test-renderer": "npm:18.3.0" + "@types/react": "npm:^19.0.0" + "@types/react-dom": "npm:^19.0.0" + "@types/react-test-renderer": "npm:^19.0.0" "@types/stylis": "npm:^4.2.0" "@typescript-eslint/eslint-plugin": "npm:^5.15.0" "@typescript-eslint/parser": "npm:^5.15.0" @@ -13406,10 +13332,10 @@ __metadata: onchange: "npm:^7.0.2" playwright: "npm:^1.49.1" prettier: "npm:^2.8.7" - react: "npm:18.3.1" - react-dom: "npm:18.3.1" + react: "npm:^19.0.0" + react-dom: "npm:^19.0.0" react-helmet-async: "npm:^2.0.5" - react-test-renderer: "npm:18.3.1" + react-test-renderer: "npm:^19.0.0" start-server-and-test: "npm:^2.1.5" storybook: "npm:^10.3.1" storybook-addon-deep-controls: "npm:^0.9.5" @@ -13453,7 +13379,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.6": +"get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.6": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" dependencies: @@ -14351,7 +14277,7 @@ __metadata: languageName: node linkType: hard -"internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.7": +"internal-slot@npm:^1.0.7": version: 1.0.7 resolution: "internal-slot@npm:1.0.7" dependencies: @@ -14428,17 +14354,7 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.1.1": - version: 1.1.1 - resolution: "is-arguments@npm:1.1.1" - dependencies: - call-bind: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.0" - checksum: 10c0/5ff1f341ee4475350adfc14b2328b38962564b7c2076be2f5bac7bd9b61779efba99b9f844a7b82ba7654adccf8e8eb19d1bb0cc6d1c1a085e498f6793d4328f - languageName: node - linkType: hard - -"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4": +"is-array-buffer@npm:^3.0.4": version: 3.0.4 resolution: "is-array-buffer@npm:3.0.4" dependencies: @@ -14640,7 +14556,7 @@ __metadata: languageName: node linkType: hard -"is-map@npm:^2.0.2, is-map@npm:^2.0.3": +"is-map@npm:^2.0.3": version: 2.0.3 resolution: "is-map@npm:2.0.3" checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc @@ -14745,7 +14661,7 @@ __metadata: languageName: node linkType: hard -"is-set@npm:^2.0.2, is-set@npm:^2.0.3": +"is-set@npm:^2.0.3": version: 2.0.3 resolution: "is-set@npm:2.0.3" checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 @@ -16649,7 +16565,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -17263,19 +17179,19 @@ __metadata: languageName: node linkType: hard -"motion-dom@npm:^11.18.1": - version: 11.18.1 - resolution: "motion-dom@npm:11.18.1" +"motion-dom@npm:^12.37.0": + version: 12.37.0 + resolution: "motion-dom@npm:12.37.0" dependencies: - motion-utils: "npm:^11.18.1" - checksum: 10c0/98378bdf9d77870829cdf3624c5eff02e48cfa820dfc74450364d7421884700048d60e277bfbf477df33270fbae4c1980e5914586f5b6dff28d4921fdca8ac47 + motion-utils: "npm:^12.36.0" + checksum: 10c0/e3b2be1e6796658d021921fed8f5ce690b833c4ce5f63d0ca11c86b5520b35a626d29ef9d97bd8551bd946333c3d4ab159d95aae9951c92ce8733039560b4c0c languageName: node linkType: hard -"motion-utils@npm:^11.18.1": - version: 11.18.1 - resolution: "motion-utils@npm:11.18.1" - checksum: 10c0/dac083bdeb6e433a277ac4362211b0fdce59ff09d6f7897f0f49d1e3561209c6481f676876daf99a33485054bc7e4b1d1b8d1de16f7b1e5c6f117fe76358ca00 +"motion-utils@npm:^12.36.0": + version: 12.36.0 + resolution: "motion-utils@npm:12.36.0" + checksum: 10c0/fe08231759064eef5d351a869379246f1e1b2031bda357a6197d2a99ff6b472bce69f8250212713d8bff2ff46978f354ca8c3b8c8f0c5bd337d26a6793ba42fa languageName: node linkType: hard @@ -17588,9 +17504,9 @@ __metadata: linkType: hard "nwsapi@npm:^2.2.2": - version: 2.2.23 - resolution: "nwsapi@npm:2.2.23" - checksum: 10c0/e44bfc9246baf659581206ed716d291a1905185247795fb8a302cb09315c943a31023b4ac4d026a5eaf32b2def51d77b3d0f9ebf4f3d35f70e105fcb6447c76e + version: 2.2.22 + resolution: "nwsapi@npm:2.2.22" + checksum: 10c0/b6a0e5ea6754aacfdfe551c8c0f1b374eaf94d48b0a4e7eac666f879ecbc1892ef1d7c457e9b02eefad3fa1323ea1faebcba533eeab6582e24c9c503411bf879 languageName: node linkType: hard @@ -17752,16 +17668,6 @@ __metadata: languageName: node linkType: hard -"object-is@npm:^1.1.5": - version: 1.1.6 - resolution: "object-is@npm:1.1.6" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - checksum: 10c0/506af444c4dce7f8e31f34fc549e2fb8152d6b9c4a30c6e62852badd7f520b579c679af433e7a072f9d78eb7808d230dc12e1cf58da9154dfbf8813099ea0fe0 - languageName: node - linkType: hard - "object-keys@npm:^1.1.1": version: 1.1.1 resolution: "object-keys@npm:1.1.1" @@ -19349,15 +19255,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:18.3.1": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" +"react-dom@npm:^19.0.0": + version: 19.2.4 + resolution: "react-dom@npm:19.2.4" dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" + scheduler: "npm:^0.27.0" peerDependencies: - react: ^18.3.1 - checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85 + react: ^19.2.4 + checksum: 10c0/f0c63f1794dedb154136d4d0f59af00b41907f4859571c155940296808f4b94bf9c0c20633db75b5b2112ec13d8d7dd4f9bf57362ed48782f317b11d05a44f35 languageName: node linkType: hard @@ -19441,13 +19346,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.3.1": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - "react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -19462,6 +19360,20 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.0.0, react-is@npm:^18.3.1": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"react-is@npm:^19.2.4": + version: 19.2.4 + resolution: "react-is@npm:19.2.4" + checksum: 10c0/477a7cfc900f24194606e315fa353856a3a13487ea8eca841678817cad4daef64339ea0d1e84e58459fc75dbe0d9ba00bb0cc626db3d07e0cf31edc64cb4fa37 + languageName: node + linkType: hard + "react-player@npm:^2.16.0": version: 2.16.0 resolution: "react-player@npm:2.16.0" @@ -19532,18 +19444,6 @@ __metadata: languageName: node linkType: hard -"react-shallow-renderer@npm:^16.15.0": - version: 16.15.0 - resolution: "react-shallow-renderer@npm:16.15.0" - dependencies: - object-assign: "npm:^4.1.1" - react-is: "npm:^16.12.0 || ^17.0.0 || ^18.0.0" - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/c194d741792e86043a4ae272f7353c1cb9412bc649945c4220c6a101a6ea5410cceb3d65d5a4d750f11a24f7426e8eec7977e8a4e3ad5d3ee235ca2b18166fa8 - languageName: node - linkType: hard - "react-stately@npm:^3.36.1": version: 3.44.0 resolution: "react-stately@npm:3.44.0" @@ -19596,16 +19496,15 @@ __metadata: languageName: node linkType: hard -"react-test-renderer@npm:18.3.1": - version: 18.3.1 - resolution: "react-test-renderer@npm:18.3.1" +"react-test-renderer@npm:^19.0.0": + version: 19.2.4 + resolution: "react-test-renderer@npm:19.2.4" dependencies: - react-is: "npm:^18.3.1" - react-shallow-renderer: "npm:^16.15.0" - scheduler: "npm:^0.23.2" + react-is: "npm:^19.2.4" + scheduler: "npm:^0.27.0" peerDependencies: - react: ^18.3.1 - checksum: 10c0/c633558ef9af33bc68f0c4dbb5163a004c4fb9eade7bd0a7cfc0355fb367f36bd9d96533c90b7e85a146be6c525113a15f58683d269e0177ad77e2b04d4fe51c + react: ^19.2.4 + checksum: 10c0/76f0a419a8ca7776ac6fae7c25056b9326a92a3d79971817e8ffd262d9121d1e87fde058b4fd6d33c365b1001cd6236d9053816dabed20afa62fbfe2a1a59f1a languageName: node linkType: hard @@ -19673,12 +19572,10 @@ __metadata: languageName: node linkType: hard -"react@npm:18.3.1": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/283e8c5efcf37802c9d1ce767f302dd569dd97a70d9bb8c7be79a789b9902451e0d16334b05d73299b20f048cbc3c7d288bbbde10b701fa194e2089c237dbea3 +"react@npm:^19.0.0": + version: 19.2.4 + resolution: "react@npm:19.2.4" + checksum: 10c0/cd2c9ff67a720799cc3b38a516009986f7fc4cb8d3e15716c6211cf098d1357ee3e348ab05ad0600042bbb0fd888530ba92e329198c92eafa0994f5213396596 languageName: node linkType: hard @@ -19839,7 +19736,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2": +"regexp.prototype.flags@npm:^1.5.2": version: 1.5.3 resolution: "regexp.prototype.flags@npm:1.5.3" dependencies: @@ -20666,12 +20563,10 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78 +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 languageName: node linkType: hard @@ -21344,15 +21239,6 @@ __metadata: languageName: node linkType: hard -"stop-iteration-iterator@npm:^1.0.0": - version: 1.0.0 - resolution: "stop-iteration-iterator@npm:1.0.0" - dependencies: - internal-slot: "npm:^1.0.4" - checksum: 10c0/c4158d6188aac510d9e92925b58709207bd94699e9c31186a040c80932a687f84a51356b5895e6dc72710aad83addb9411c22171832c9ae0e6e11b7d61b0dfb9 - languageName: node - linkType: hard - "storybook-addon-deep-controls@npm:^0.9.5": version: 0.9.5 resolution: "storybook-addon-deep-controls@npm:0.9.5" @@ -23336,7 +23222,7 @@ __metadata: languageName: node linkType: hard -"which-collection@npm:^1.0.1, which-collection@npm:^1.0.2": +"which-collection@npm:^1.0.2": version: 1.0.2 resolution: "which-collection@npm:1.0.2" dependencies: @@ -23355,7 +23241,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15": +"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15": version: 1.1.15 resolution: "which-typed-array@npm:1.1.15" dependencies: @@ -23507,8 +23393,8 @@ __metadata: linkType: hard "ws@npm:^8.11.0, ws@npm:^8.18.0": - version: 8.19.0 - resolution: "ws@npm:8.19.0" + version: 8.18.3 + resolution: "ws@npm:8.18.3" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -23517,7 +23403,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10c0/4741d9b9bc3f9c791880882414f96e36b8b254e34d4b503279d6400d9a4b87a033834856dbdd94ee4b637944df17ea8afc4bce0ff4a1560d2166be8855da5b04 + checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 languageName: node linkType: hard