From 2cba802dddeca418b28301b1d5d9b5ae5755618a Mon Sep 17 00:00:00 2001 From: mnajdova Date: Fri, 11 Sep 2026 11:28:23 +0200 Subject: [PATCH 1/7] [docs] Add ViewTransition demos in the animation guide --- docs/package.json | 8 +- .../css-modules/index.css | 12 + .../css-modules/index.module.css | 142 ++ .../css-modules/index.tsx | 59 + .../demos/view-transition-list/index.ts | 6 + .../css-modules/index.css | 59 + .../css-modules/index.module.css | 138 ++ .../css-modules/index.tsx | 53 + .../demos/view-transition-tabs/index.ts | 6 + .../(docs)/react/handbook/animation/page.mdx | 38 + docs/src/app/(docs)/react/handbook/page.mdx | 6 +- docs/src/components/Header.css | 3 + docs/src/components/QuickNav/QuickNav.css | 3 + docs/src/components/SideNav.css | 3 + package.json | 6 +- packages/react/package.json | 8 +- packages/utils/package.json | 8 +- playground/vite-app/package.json | 8 +- pnpm-lock.yaml | 1587 +++++++++-------- test/package.json | 8 +- test/performance/package.json | 6 +- test/public-types/package.json | 2 +- 22 files changed, 1348 insertions(+), 821 deletions(-) create mode 100644 docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.css create mode 100644 docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.module.css create mode 100644 docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.tsx create mode 100644 docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/index.ts create mode 100644 docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.css create mode 100644 docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.module.css create mode 100644 docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.tsx create mode 100644 docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/index.ts diff --git a/docs/package.json b/docs/package.json index 18bf807bc42..04f2959650c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -43,8 +43,8 @@ "postcss-custom-media": "^12.0.1", "postcss-import": "^16.1.1", "radix-ui": "^1.4.3", - "react": "^19.2.8", - "react-dom": "^19.2.8", + "react": "^19.3.0", + "react-dom": "^19.3.0", "react-error-boundary": "6.1.2", "react-hook-form": "^7.75.0", "remark-gfm": "^4.0.1", @@ -61,8 +61,8 @@ "@types/gtag.js": "0.0.20", "@types/hast": "3.0.5", "@types/node": "22.20.1", - "@types/react": "19.2.18", - "@types/react-dom": "19.2.4", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", "@types/unist": "3.0.3", "cross-env": "10.1.0", "mdast-util-mdx-jsx": "3.2.0", diff --git a/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.css b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.css new file mode 100644 index 00000000000..7b5b27f9b6e --- /dev/null +++ b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.css @@ -0,0 +1,12 @@ +::view-transition-group(*) { + animation-duration: 250ms; + animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); +} + +@media (prefers-reduced-motion: reduce) { + ::view-transition-group(*), + ::view-transition-old(*), + ::view-transition-new(*) { + animation: none; + } +} diff --git a/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.module.css b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.module.css new file mode 100644 index 00000000000..c3eb8a6295b --- /dev/null +++ b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.module.css @@ -0,0 +1,142 @@ +.Container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 1rem; + width: 100%; + max-width: 20rem; +} + +.Panel { + display: flex; + gap: 1px; + padding: 1px; + border: 1px solid oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + border: 1px solid white; + } +} + +.Button { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + height: 2rem; + padding: 0 0.75rem; + margin: 0; + border: none; + border-radius: 0; + background-color: transparent; + color: oklch(14.5% 0 0deg); + font-family: inherit; + font-size: 0.875rem; + line-height: 1.25rem; + -webkit-user-select: none; + user-select: none; + + @media (prefers-color-scheme: dark) { + color: white; + } + + @media (hover: hover) { + &:hover:not([data-disabled]) { + background-color: oklch(97% 0 0deg); + + @media (prefers-color-scheme: dark) { + background-color: oklch(26.9% 0 0deg); + } + } + } + + &:active:not([data-disabled], [data-pressed]) { + background-color: oklch(92.2% 0 0deg); + + @media (prefers-color-scheme: dark) { + background-color: oklch(37.1% 0 0deg); + } + } + + &[data-pressed] { + background-color: oklch(14.5% 0 0deg); + color: white; + + @media (prefers-color-scheme: dark) { + background-color: white; + color: oklch(14.5% 0 0deg); + } + } + + @media (hover: hover) { + &[data-pressed]:hover:not([data-disabled]) { + background-color: oklch(14.5% 0 0deg); + color: white; + + @media (prefers-color-scheme: dark) { + background-color: white; + color: oklch(14.5% 0 0deg); + } + } + } + + &:focus-visible { + outline: 2px solid oklch(14.5% 0 0deg); + outline-offset: 1px; + position: relative; + z-index: 1; + + @media (prefers-color-scheme: dark) { + outline-color: white; + } + } +} + +.List { + box-sizing: border-box; + display: flex; + flex-direction: column; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + border: 1px solid oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + border: 1px solid white; + } +} + +.Item { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.5rem 0.75rem; + background-color: white; + color: oklch(14.5% 0 0deg); + font-size: 0.875rem; + line-height: 1.25rem; + + @media (prefers-color-scheme: dark) { + background-color: oklch(14.5% 0 0deg); + color: white; + } + + & + & { + border-top: 1px solid oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + border-top: 1px solid white; + } + } +} + +.Size { + color: oklch(43.9% 0 0deg); + + @media (prefers-color-scheme: dark) { + color: oklch(70.8% 0 0deg); + } +} diff --git a/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.tsx b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.tsx new file mode 100644 index 00000000000..551747d1a04 --- /dev/null +++ b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/css-modules/index.tsx @@ -0,0 +1,59 @@ +'use client'; +import * as React from 'react'; +import { Toggle } from '@base-ui/react/toggle'; +import { ToggleGroup } from '@base-ui/react/toggle-group'; +import styles from './index.module.css'; +import './index.css'; + +const files = [ + { id: 'readme', name: 'README.md', size: 4 }, + { id: 'package', name: 'package.json', size: 2 }, + { id: 'index', name: 'index.tsx', size: 12 }, + { id: 'styles', name: 'styles.css', size: 7 }, + { id: 'logo', name: 'logo.svg', size: 30 }, +]; + +type SortKey = 'name' | 'size'; + +export default function ViewTransitionList() { + const [sortKey, setSortKey] = React.useState('name'); + + const sortedFiles = [...files].sort((a, b) => + sortKey === 'name' ? a.name.localeCompare(b.name) : a.size - b.size, + ); + + return ( +
+ { + const nextSortKey = groupValue[0]; + if (nextSortKey) { + React.startTransition(() => { + setSortKey(nextSortKey); + }); + } + }} + > + + Name + + + Size + + +
    + {sortedFiles.map((file) => ( + +
  • + {file.name} + {file.size} KB +
  • +
    + ))} +
+
+ ); +} diff --git a/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/index.ts b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/index.ts new file mode 100644 index 00000000000..d158c483346 --- /dev/null +++ b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-list/index.ts @@ -0,0 +1,6 @@ +import { createDemoWithVariants } from 'docs/src/utils/createDemo'; +import CssModules from './css-modules'; + +export const DemoViewTransitionList = createDemoWithVariants(import.meta.url, { + CssModules, +}); diff --git a/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.css b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.css new file mode 100644 index 00000000000..0ffb2a58d25 --- /dev/null +++ b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.css @@ -0,0 +1,59 @@ +::view-transition-old(.slide-to-left), +::view-transition-old(.slide-to-right), +::view-transition-new(.slide-from-left), +::view-transition-new(.slide-from-right) { + animation-duration: 250ms; + animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); +} + +::view-transition-new(.slide-from-right) { + animation-name: slideFromRight; +} + +::view-transition-new(.slide-from-left) { + animation-name: slideFromLeft; +} + +::view-transition-old(.slide-to-left) { + animation-name: slideToLeft; +} + +::view-transition-old(.slide-to-right) { + animation-name: slideToRight; +} + +@keyframes slideFromRight { + from { + opacity: 0; + transform: translateX(1.5rem); + } +} + +@keyframes slideFromLeft { + from { + opacity: 0; + transform: translateX(-1.5rem); + } +} + +@keyframes slideToLeft { + to { + opacity: 0; + transform: translateX(-1.5rem); + } +} + +@keyframes slideToRight { + to { + opacity: 0; + transform: translateX(1.5rem); + } +} + +@media (prefers-reduced-motion: reduce) { + ::view-transition-group(*), + ::view-transition-old(*), + ::view-transition-new(*) { + animation: none; + } +} diff --git a/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.module.css b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.module.css new file mode 100644 index 00000000000..3a7db0d8ad7 --- /dev/null +++ b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.module.css @@ -0,0 +1,138 @@ +.Root { + box-sizing: border-box; + width: 100%; + max-width: 20rem; +} + +.List { + display: flex; + position: relative; + z-index: 1; + gap: 0.25rem; + margin-bottom: -1px; +} + +.Tab { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + border: 0; + margin: 0; + outline: 0; + background: none; + color: oklch(43.9% 0 0deg); + font-family: inherit; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 400; + -webkit-user-select: none; + user-select: none; + white-space: nowrap; + word-break: keep-all; + padding-inline: 0.5rem; + padding-block: 0; + height: calc(2rem + 1px); + + @media (prefers-color-scheme: dark) { + color: oklch(87% 0 0deg); + } + + @media (hover: hover) { + &:hover { + color: oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + color: white; + } + } + } + + &[data-active] { + color: oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + color: white; + } + } + + &:focus-visible { + outline: 2px solid oklch(14.5% 0 0deg); + outline-offset: -1px; + + @media (prefers-color-scheme: dark) { + outline-color: white; + } + } +} + +.Indicator { + box-sizing: border-box; + position: absolute; + z-index: -1; + left: 0; + top: 0; + translate: var(--active-tab-left); + width: var(--active-tab-width); + height: 100%; + transition-property: translate, width; + transition-duration: 150ms; + transition-timing-function: ease-in-out; + border-top: 1px solid oklch(14.5% 0 0deg); + border-inline: 1px solid oklch(14.5% 0 0deg); + background-color: white; + + @media (prefers-color-scheme: dark) { + border-top: 1px solid white; + border-inline: 1px solid white; + background-color: oklch(14.5% 0 0deg); + } +} + +.PanelViewport { + box-sizing: border-box; + position: relative; + width: 100%; + min-height: 8rem; + border-inline: 1px solid oklch(14.5% 0 0deg); + border-block: 1px solid oklch(14.5% 0 0deg); + background-color: white; + + @media (prefers-color-scheme: dark) { + border-inline: 1px solid white; + border-block: 1px solid white; + background-color: oklch(14.5% 0 0deg); + } +} + +.Panel { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 8rem; + padding: 1rem; + outline: 0; + font-size: 0.875rem; + line-height: 1.25rem; + text-align: center; + color: oklch(14.5% 0 0deg); + + @media (prefers-color-scheme: dark) { + color: white; + } + + &:focus-visible { + outline: 2px solid oklch(14.5% 0 0deg); + outline-offset: -1px; + + @media (prefers-color-scheme: dark) { + outline-color: white; + } + } +} + +.Paragraph { + margin: 0; +} diff --git a/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.tsx b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.tsx new file mode 100644 index 00000000000..65f1115886d --- /dev/null +++ b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/css-modules/index.tsx @@ -0,0 +1,53 @@ +'use client'; +import * as React from 'react'; +import { Tabs } from '@base-ui/react/tabs'; +import styles from './index.module.css'; +import './index.css'; + +const tabs = [ + { value: 'overview', label: 'Overview', description: 'Workspace stats and activity.' }, + { value: 'projects', label: 'Projects', description: 'Milestones and deadlines.' }, + { value: 'account', label: 'Account', description: 'Profile and preferences.' }, +]; + +export default function ViewTransitionTabs() { + const [value, setValue] = React.useState('overview'); + + return ( + { + React.startTransition(() => { + React.addTransitionType(eventDetails.activationDirection); + setValue(nextValue); + }); + }} + > + + {tabs.map((tab) => ( + + {tab.label} + + ))} + + +
+ {tabs.map( + (tab) => + tab.value === value && ( + + +

{tab.description}

+
+
+ ), + )} +
+
+ ); +} diff --git a/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/index.ts b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/index.ts new file mode 100644 index 00000000000..ceca716efee --- /dev/null +++ b/docs/src/app/(docs)/react/handbook/animation/demos/view-transition-tabs/index.ts @@ -0,0 +1,6 @@ +import { createDemoWithVariants } from 'docs/src/utils/createDemo'; +import CssModules from './css-modules'; + +export const DemoViewTransitionTabs = createDemoWithVariants(import.meta.url, { + CssModules, +}); diff --git a/docs/src/app/(docs)/react/handbook/animation/page.mdx b/docs/src/app/(docs)/react/handbook/animation/page.mdx index 80ded1f58c7..a0ff4450163 100644 --- a/docs/src/app/(docs)/react/handbook/animation/page.mdx +++ b/docs/src/app/(docs)/react/handbook/animation/page.mdx @@ -216,6 +216,42 @@ function App() { } ``` +## View Transitions + +React 19.3 ships the [``](https://react.dev/reference/react/ViewTransition) component, which animates DOM changes using the browser's [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API). +Base UI components work with it as long as two conditions are met: + +- The update that changes the UI runs inside a [Transition](https://react.dev/reference/react/startTransition). Base UI applies its own state changes as regular updates, so control the state yourself and wrap the update in `startTransition`. +- React, not Base UI, removes the elements being animated. Components like `` stay mounted while their CSS exit animation plays and are removed a frame later, outside of the Transition, which makes the browser skip the view transition. Render such elements conditionally so they leave the tree within the Transition. + +The `::view-transition-*` pseudo-elements are rendered on the root element, so their styles must live in a global stylesheet rather than a CSS Module. + +When a Transition only changes the elements inside `` boundaries, React excludes the root element from the view transition so the rest of the page stays interactive. +Chrome doesn't paint `position: fixed` elements during such a transition, so give fixed elements like headers and sidebars their own `view-transition-name`. + +### Animating Tabs panels with View Transitions + +Render only the active panel inside a `` so React can play its enter and exit animations. +Pass the `activationDirection` from the change event details to `addTransitionType` to slide the panels toward the newly active tab. + +import { DemoViewTransitionTabs } from './demos/view-transition-tabs'; + + + +### Animating list changes with View Transitions + +Wrap each list item in a `` so React assigns it a stable `view-transition-name`. +When a control such as `` changes the order of the list inside a Transition, the items animate to their new positions. + +import { DemoViewTransitionList } from './demos/view-transition-list'; + + + +### Popups + +Popup components such as Dialog, Popover, and Menu render their popup hidden during the commit that opens them and reveal it right after, so a `` cannot capture their opening state. +Use the CSS transition and animation attributes described above to animate popups. + export const metadata = { keywords: [ 'Base UI Animation', @@ -229,5 +265,7 @@ export const metadata = { 'Transition Hooks', 'Animated Components', 'Keyframe Animations', + 'View Transitions', + 'React ViewTransition', ], }; diff --git a/docs/src/app/(docs)/react/handbook/page.mdx b/docs/src/app/(docs)/react/handbook/page.mdx index 71cd280f686..92469338245 100644 --- a/docs/src/app/(docs)/react/handbook/page.mdx +++ b/docs/src/app/(docs)/react/handbook/page.mdx @@ -43,7 +43,7 @@ A guide to animating Base UI components. Outline -- Keywords: Base UI Animation, React Component Animations, CSS Transition Guide, Motion Framer Integration, Animation Data Attributes, Handbook Animation, Spring Animations, Enter Exit Animations, Transition Hooks, Animated Components, Keyframe Animations +- Keywords: Base UI Animation, React Component Animations, CSS Transition Guide, Motion Framer Integration, Animation Data Attributes, Handbook Animation, Spring Animations, Enter Exit Animations, Transition Hooks, Animated Components, Keyframe Animations, View Transitions, React ViewTransition - Sections: - CSS transitions - CSS animations @@ -52,6 +52,10 @@ A guide to animating Base UI components. - Animating components kept in DOM when closed with Motion - Animating Select component with Motion - Manual unmounting + - View Transitions + - Animating Tabs panels with View Transitions + - Animating list changes with View Transitions + - Popups diff --git a/docs/src/components/Header.css b/docs/src/components/Header.css index 32c9fc407a6..fedf9f1bf93 100644 --- a/docs/src/components/Header.css +++ b/docs/src/components/Header.css @@ -21,6 +21,9 @@ justify-content: space-between; padding-inline: var(--content-layout-padding-x); position: fixed; + /* Fixed elements aren't painted while a view transition excludes the root + (React does that for transitions that only affect named elements). */ + view-transition-name: header; top: 0; inset-inline: 0; background: var(--color-content); diff --git a/docs/src/components/QuickNav/QuickNav.css b/docs/src/components/QuickNav/QuickNav.css index 74d2c7a1847..d9e648803b7 100644 --- a/docs/src/components/QuickNav/QuickNav.css +++ b/docs/src/components/QuickNav/QuickNav.css @@ -39,6 +39,9 @@ @media (--show-quick-nav) { display: block; position: fixed; + /* Fixed elements aren't painted while a view transition excludes the root + (React does that for transitions that only affect named elements). */ + view-transition-name: quick-nav; top: var(--header-height); right: max( var(--root-layout-padding-x), diff --git a/docs/src/components/SideNav.css b/docs/src/components/SideNav.css index e304d55016c..a9b8d607cdc 100644 --- a/docs/src/components/SideNav.css +++ b/docs/src/components/SideNav.css @@ -30,6 +30,9 @@ @media (--show-side-nav) { display: block; position: fixed; + /* Fixed elements aren't painted while a view transition excludes the root + (React does that for transitions that only affect named elements). */ + view-transition-name: side-nav; top: var(--header-height); /* Extend left by the link padding so the hover/active pill can bleed past the text (logo) alignment line without being clipped by the viewport. */ diff --git a/package.json b/package.json index 41fee4d3f8c..5eba0fd3b8d 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@tailwindcss/postcss": "4.2.4", "@testing-library/jest-dom": "6.9.1", "@types/node": "22.20.1", - "@types/react": "19.2.18", + "@types/react": "19.3.0", "@typescript/native-preview": "7.0.0-dev.20260707.2", "@vitejs/plugin-react": "6.0.5", "@vitest/browser-playwright": "4.1.10", @@ -93,8 +93,8 @@ "prettier": "3.9.6", "pretty-quick": "4.2.2", "publint": "0.3.20", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "remark-mdx": "3.1.1", "rimraf": "6.1.3", "serve": "14.2.6", diff --git a/packages/react/package.json b/packages/react/package.json index 4bd37b143fb..9f768cc8de1 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -139,14 +139,14 @@ "@testing-library/react": "16.3.2", "@testing-library/user-event": "14.6.3", "@types/luxon": "3.7.4", - "@types/react": "19.2.18", - "@types/react-dom": "19.2.4", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", "@types/use-sync-external-store": "1.5.0", "clsx": "2.1.1", "date-fns": "4.4.0", "luxon": "3.7.2", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "react-router": "7.18.2", "typescript": "6.0.3", "vitest-browser-react": "2.2.0" diff --git a/packages/utils/package.json b/packages/utils/package.json index 003e7f901f1..d49bb974d2f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -36,11 +36,11 @@ "devDependencies": { "@testing-library/react": "16.3.2", "@testing-library/user-event": "14.6.3", - "@types/react": "19.2.18", - "@types/react-dom": "19.2.4", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", "@types/use-sync-external-store": "1.5.0", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "typescript": "6.0.3" }, "peerDependencies": { diff --git a/playground/vite-app/package.json b/playground/vite-app/package.json index 550a95cc99d..b6139dc70a3 100644 --- a/playground/vite-app/package.json +++ b/playground/vite-app/package.json @@ -10,16 +10,16 @@ }, "dependencies": { "radix-ui": "^1.4.3", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "react-router": "7.18.2" }, "devDependencies": { "@eslint/js": "10.0.1", "@tailwindcss/vite": "4.2.4", "@types/node": "22.20.1", - "@types/react": "19.2.18", - "@types/react-dom": "19.2.4", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", "@vitejs/plugin-react": "6.0.5", "cross-env": "10.1.0", "eslint": "10.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87adab8a183..cc55d9aa97c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,7 +45,7 @@ importers: version: 0.0.3-canary.5 '@mui/internal-test-utils': specifier: 2.0.18-canary.25 - version: 2.0.18-canary.25(@playwright/test@1.62.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(@vitest/utils@4.1.10)(chai@6.2.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) + version: 2.0.18-canary.25(@playwright/test@1.62.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(@vitest/utils@4.1.10)(chai@6.2.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) '@next/eslint-plugin-next': specifier: 16.3.0 version: 16.3.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) @@ -62,8 +62,8 @@ importers: specifier: 22.20.1 version: 22.20.1 '@types/react': - specifier: 19.2.18 - version: 19.2.18 + specifier: 19.3.0 + version: 19.3.0 '@typescript/native-preview': specifier: 7.0.0-dev.20260707.2 version: 7.0.0-dev.20260707.2 @@ -116,11 +116,11 @@ importers: specifier: 0.3.20 version: 0.3.20 react: - specifier: 19.2.8 - version: 19.2.8 + specifier: 19.3.0 + version: 19.3.0 react-dom: - specifier: 19.2.8 - version: 19.2.8(react@19.2.8) + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) remark-mdx: specifier: 3.1.1 version: 3.1.1(supports-color@7.2.0) @@ -168,25 +168,25 @@ importers: version: 3.1.1(supports-color@10.2.2)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) '@mdx-js/react': specifier: ^3.1.1 - version: 3.1.1(@types/react@19.2.18)(react@19.2.8) + version: 3.1.1(@types/react@19.3.0)(react@19.3.0) '@mui/internal-docs-infra': specifier: 0.12.1-canary.26 - version: 0.12.1-canary.26(@types/react@19.2.18)(next@16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(prettier@3.9.6)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@10.2.2)(typescript@6.0.3) + version: 0.12.1-canary.26(@types/react@19.3.0)(next@16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(prettier@3.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(supports-color@10.2.2)(typescript@6.0.3) '@next/mdx': specifier: ^16.3.0 - version: 16.3.0(@mdx-js/loader@3.1.1(supports-color@10.2.2)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)))(@mdx-js/react@3.1.1(@types/react@19.2.18)(react@19.2.8)) + version: 16.3.0(@mdx-js/loader@3.1.1(supports-color@10.2.2)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)))(@mdx-js/react@3.1.1(@types/react@19.3.0)(react@19.3.0)) '@react-spring/web': specifier: ^10.1.2 - version: 10.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 10.1.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@stefanprobst/rehype-extract-toc': specifier: ^3.0.0 version: 3.0.0 '@tanstack/react-form': specifier: ^1.33.4 - version: 1.33.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.33.4(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-virtual': specifier: ^3.14.9 - version: 3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 3.14.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@types/mdx': specifier: ^2.0.14 version: 2.0.14 @@ -216,7 +216,7 @@ importers: version: 3.0.1 lucide-react: specifier: ^1.31.0 - version: 1.31.0(react@19.2.8) + version: 1.31.0(react@19.3.0) lz-string: specifier: ^1.5.0 version: 1.5.0 @@ -225,7 +225,7 @@ importers: version: 8.3.0 next: specifier: 16.3.0 - version: 16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) postcss: specifier: ^8.5.26 version: 8.5.26 @@ -237,19 +237,19 @@ importers: version: 16.1.1(postcss@8.5.26) radix-ui: specifier: ^1.4.3 - version: 1.4.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.4.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) react: - specifier: ^19.2.8 - version: 19.2.8 + specifier: ^19.3.0 + version: 19.3.0 react-dom: - specifier: ^19.2.8 - version: 19.2.8(react@19.2.8) + specifier: ^19.3.0 + version: 19.3.0(react@19.3.0) react-error-boundary: specifier: 6.1.2 - version: 6.1.2(react@19.2.8) + version: 6.1.2(react@19.3.0) react-hook-form: specifier: ^7.75.0 - version: 7.75.0(react@19.2.8) + version: 7.75.0(react@19.3.0) remark-gfm: specifier: ^4.0.1 version: 4.0.1(supports-color@10.2.2) @@ -288,11 +288,11 @@ importers: specifier: 22.20.1 version: 22.20.1 '@types/react': - specifier: 19.2.18 - version: 19.2.18 + specifier: 19.3.0 + version: 19.3.0 '@types/react-dom': - specifier: 19.2.4 - version: 19.2.4(@types/react@19.2.18) + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) '@types/unist': specifier: 3.0.3 version: 3.0.3 @@ -304,7 +304,7 @@ importers: version: 3.2.0(supports-color@10.2.2) motion: specifier: 12.43.0 - version: 12.43.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 12.43.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) prettier: specifier: 3.9.6 version: 3.9.6 @@ -340,20 +340,20 @@ importers: version: link:../utils/build '@floating-ui/react-dom': specifier: ^2.1.9 - version: 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@floating-ui/utils': specifier: ^0.2.12 version: 0.2.12 use-sync-external-store: specifier: ^1.6.0 - version: 1.6.0(react@19.2.8) + version: 1.6.0(react@19.3.0) devDependencies: '@date-fns/tz': specifier: 1.5.0 version: 1.5.0 '@testing-library/react': specifier: 16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@testing-library/user-event': specifier: 14.6.3 version: 14.6.3(@testing-library/dom@10.4.1) @@ -361,11 +361,11 @@ importers: specifier: 3.7.4 version: 3.7.4 '@types/react': - specifier: 19.2.18 - version: 19.2.18 + specifier: 19.3.0 + version: 19.3.0 '@types/react-dom': - specifier: 19.2.4 - version: 19.2.4(@types/react@19.2.18) + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) '@types/use-sync-external-store': specifier: 1.5.0 version: 1.5.0 @@ -379,20 +379,20 @@ importers: specifier: 3.7.2 version: 3.7.2 react: - specifier: 19.2.8 - version: 19.2.8 + specifier: 19.3.0 + version: 19.3.0 react-dom: - specifier: 19.2.8 - version: 19.2.8(react@19.2.8) + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) react-router: specifier: 7.18.2 - version: 7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 7.18.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) typescript: specifier: 6.0.3 version: 6.0.3 vitest-browser-react: specifier: 2.2.0 - version: 2.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10) + version: 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.10) publishDirectory: build packages/utils: @@ -408,29 +408,29 @@ importers: version: 5.2.0 use-sync-external-store: specifier: ^1.6.0 - version: 1.6.0(react@19.2.8) + version: 1.6.0(react@19.3.0) devDependencies: '@testing-library/react': specifier: 16.3.2 - version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@testing-library/user-event': specifier: 14.6.3 version: 14.6.3(@testing-library/dom@10.4.1) '@types/react': - specifier: 19.2.18 - version: 19.2.18 + specifier: 19.3.0 + version: 19.3.0 '@types/react-dom': - specifier: 19.2.4 - version: 19.2.4(@types/react@19.2.18) + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) '@types/use-sync-external-store': specifier: 1.5.0 version: 1.5.0 react: - specifier: 19.2.8 - version: 19.2.8 + specifier: 19.3.0 + version: 19.3.0 react-dom: - specifier: 19.2.8 - version: 19.2.8(react@19.2.8) + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) typescript: specifier: 6.0.3 version: 6.0.3 @@ -440,16 +440,16 @@ importers: dependencies: radix-ui: specifier: ^1.4.3 - version: 1.4.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.4.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) react: - specifier: 19.2.8 - version: 19.2.8 + specifier: 19.3.0 + version: 19.3.0 react-dom: - specifier: 19.2.8 - version: 19.2.8(react@19.2.8) + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) react-router: specifier: 7.18.2 - version: 7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 7.18.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) devDependencies: '@eslint/js': specifier: 10.0.1 @@ -461,11 +461,11 @@ importers: specifier: 22.20.1 version: 22.20.1 '@types/react': - specifier: 19.2.18 - version: 19.2.18 + specifier: 19.3.0 + version: 19.3.0 '@types/react-dom': - specifier: 19.2.4 - version: 19.2.4(@types/react@19.2.18) + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) '@vitejs/plugin-react': specifier: 6.0.5 version: 6.0.5(babel-plugin-react-compiler@1.0.0)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0)) @@ -515,11 +515,11 @@ importers: specifier: 6.9.1 version: 6.9.1 '@types/react': - specifier: 19.2.18 - version: 19.2.18 + specifier: 19.3.0 + version: 19.3.0 '@types/react-dom': - specifier: 19.2.4 - version: 19.2.4(@types/react@19.2.18) + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) docs: specifier: workspace:^ version: link:../docs @@ -530,14 +530,14 @@ importers: specifier: 3.1.1 version: 3.1.1 react: - specifier: 19.2.8 - version: 19.2.8 + specifier: 19.3.0 + version: 19.3.0 react-dom: - specifier: 19.2.8 - version: 19.2.8(react@19.2.8) + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) react-router: specifier: 7.18.2 - version: 7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 7.18.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) util: specifier: 0.12.5 version: 0.12.5 @@ -561,7 +561,7 @@ importers: version: 1.0.9-canary.83(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(rolldown@1.2.4)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0) radix-ui: specifier: ^1.4.3 - version: 1.4.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.4.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) devDependencies: cross-env: specifier: 10.1.0 @@ -582,18 +582,18 @@ importers: specifier: workspace:* version: link:../../packages/react/build react: - specifier: 19.2.8 - version: 19.2.8 + specifier: 19.3.0 + version: 19.3.0 react-dom: - specifier: 19.2.8 - version: 19.2.8(react@19.2.8) + specifier: 19.3.0 + version: 19.3.0(react@19.3.0) devDependencies: '@mui/internal-benchmark': specifier: 0.0.3-canary.7 - version: 0.0.3-canary.7(@vitest/browser-playwright@4.1.10)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) + version: 0.0.3-canary.7(@vitest/browser-playwright@4.1.10)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) '@types/react': - specifier: 19.2.18 - version: 19.2.18 + specifier: 19.3.0 + version: 19.3.0 '@vitest/browser-playwright': specifier: 4.1.10 version: 4.1.10(playwright@1.62.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) @@ -610,8 +610,8 @@ importers: specifier: workspace:* version: link:../../packages/react/build react: - specifier: 19.2.8 - version: 19.2.8 + specifier: 19.3.0 + version: 19.3.0 packages: @@ -3963,13 +3963,13 @@ packages: resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed. - '@types/react-dom@19.2.4': - resolution: {integrity: sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==} + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} peerDependencies: - '@types/react': ^19.2.0 + '@types/react': ^19.3.0 - '@types/react@19.2.18': - resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} '@types/supports-color@8.1.3': resolution: {integrity: sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==} @@ -5008,6 +5008,7 @@ packages: conventional-changelog-core@5.0.1: resolution: {integrity: sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==} engines: {node: '>=14'} + deprecated: Deprecated and no longer maintained. Please use conventional-changelog instead. conventional-changelog-preset-loader@3.0.0: resolution: {integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==} @@ -8126,10 +8127,10 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@19.2.8: - resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} + react-dom@19.3.0: + resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} peerDependencies: - react: ^19.2.8 + react: ^19.3.0 react-error-boundary@6.1.2: resolution: {integrity: sha512-3DpCr5HVdZ0caUjYE/kIHBEJN0mNP3ZCgf16c48uJ5TbWjorKVp+YG8W3XqlJ7vJAVNw6wNIImyPXmFydwmyng==} @@ -8191,8 +8192,8 @@ packages: '@types/react': optional: true - react@19.2.8: - resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} + react@19.3.0: + resolution: {integrity: sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -8491,8 +8492,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} schema-utils@4.3.3: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} @@ -10823,11 +10824,11 @@ snapshots: '@floating-ui/core': 1.8.0 '@floating-ui/utils': 0.2.12 - '@floating-ui/react-dom@2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@floating-ui/react-dom@2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@floating-ui/dom': 1.8.0 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) '@floating-ui/utils@0.2.12': {} @@ -11225,11 +11226,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.18)(react@19.2.8)': + '@mdx-js/react@3.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: '@types/mdx': 2.0.14 - '@types/react': 19.2.18 - react: 19.2.8 + '@types/react': 19.3.0 + react: 19.3.0 '@microsoft/api-extractor-model@7.33.10(@types/node@22.20.1)': dependencies: @@ -11288,15 +11289,15 @@ snapshots: '@babel/core': 7.29.7(supports-color@7.2.0) resolve: 1.22.12 - '@mui/internal-benchmark@0.0.3-canary.7(@vitest/browser-playwright@4.1.10)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)': + '@mui/internal-benchmark@0.0.3-canary.7(@vitest/browser-playwright@4.1.10)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)': dependencies: '@babel/runtime': 7.29.7 '@vitejs/plugin-react': 6.0.5(babel-plugin-react-compiler@1.0.0)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0)) '@vitest/browser-playwright': 4.1.10(playwright@1.62.1)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) env-ci: 11.2.0 execa: 9.6.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) vitest: 4.1.10(@types/node@22.20.1)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(jsdom@27.4.0(supports-color@10.2.2))(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0)) zod: 4.4.3 transitivePeerDependencies: @@ -11446,7 +11447,7 @@ snapshots: - supports-color - vitest - '@mui/internal-docs-infra@0.12.1-canary.26(@types/react@19.2.18)(next@16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(prettier@3.9.6)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(supports-color@10.2.2)(typescript@6.0.3)': + '@mui/internal-docs-infra@0.12.1-canary.26(@types/react@19.3.0)(next@16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(prettier@3.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@babel/runtime': 7.29.7 '@csstools/postcss-light-dark-function': 3.0.2(postcss@8.5.26) @@ -11477,8 +11478,8 @@ snapshots: postcss-modules-scope: 3.2.1(postcss@8.5.26) postcss-modules-values: 4.0.0(postcss@8.5.26) proper-lockfile: 4.1.2 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) remark-gfm: 4.0.1(supports-color@10.2.2) remark-mdx: 3.1.1(supports-color@10.2.2) remark-parse: 11.0.0(supports-color@10.2.2) @@ -11494,20 +11495,20 @@ snapshots: yargs: 18.1.0 zx: 8.8.5 optionalDependencies: - '@types/react': 19.2.18 - next: 16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@types/react': 19.3.0 + next: 16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) prettier: 3.9.6 transitivePeerDependencies: - supports-color '@mui/internal-netlify-cache@0.0.3-canary.5': {} - '@mui/internal-test-utils@2.0.18-canary.25(@playwright/test@1.62.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(@vitest/utils@4.1.10)(chai@6.2.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)': + '@mui/internal-test-utils@2.0.18-canary.25(@playwright/test@1.62.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(@vitest/utils@4.1.10)(chai@6.2.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)': dependencies: '@babel/runtime': 7.29.7 '@playwright/test': 1.62.1 '@testing-library/dom': 10.4.1 - '@testing-library/react': 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@testing-library/react': 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@testing-library/user-event': 14.6.3(@testing-library/dom@10.4.1) '@types/chai-dom': 1.11.3 assertion-error: 2.0.1 @@ -11517,8 +11518,8 @@ snapshots: es-toolkit: 1.50.0 format-util: 1.0.5 prop-types: 15.8.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) vitest-fail-on-console: 0.10.1(@vitest/utils@4.1.10)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10) transitivePeerDependencies: - '@types/react' @@ -11672,12 +11673,12 @@ snapshots: transitivePeerDependencies: - eslint - '@next/mdx@16.3.0(@mdx-js/loader@3.1.1(supports-color@10.2.2)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)))(@mdx-js/react@3.1.1(@types/react@19.2.18)(react@19.2.8))': + '@next/mdx@16.3.0(@mdx-js/loader@3.1.1(supports-color@10.2.2)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)))(@mdx-js/react@3.1.1(@types/react@19.3.0)(react@19.3.0))': dependencies: source-map: 0.7.6 optionalDependencies: '@mdx-js/loader': 3.1.1(supports-color@10.2.2)(webpack@5.109.2(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26)(uglify-js@3.19.3)) - '@mdx-js/react': 3.1.1(@types/react@19.2.18)(react@19.2.8) + '@mdx-js/react': 3.1.1(@types/react@19.3.0)(react@19.3.0) '@next/swc-darwin-arm64@16.3.0': optional: true @@ -12169,781 +12170,781 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) - - '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.8 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-context@1.1.2(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-context@1.1.2(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.8 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) aria-hidden: 1.2.6 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-direction@1.1.1(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-direction@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.8 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.8 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-form@0.1.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-id@1.1.1(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-id@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-label@2.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) aria-hidden: 1.2.6 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) aria-hidden: 1.2.6 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) - - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': - dependencies: - '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.2.8) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@floating-ui/react-dom': 2.1.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.3.0)(react@19.3.0) '@radix-ui/rect': 1.1.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-select@2.2.6(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) aria-hidden: 1.2.6 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-slot@1.2.3(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.8 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.8 - use-sync-external-store: 1.6.0(react@19.2.8) + react: 19.3.0 + use-sync-external-store: 1.6.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.8 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: - react: 19.2.8 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: '@radix-ui/rect': 1.1.1 - react: 19.2.8 + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.18)(react@19.2.8)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.3.0)(react@19.3.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - react: 19.2.8 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + react: 19.3.0 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) '@radix-ui/rect@1.1.1': {} - '@react-spring/animated@10.1.2(react@19.2.8)': + '@react-spring/animated@10.1.2(react@19.3.0)': dependencies: - '@react-spring/shared': 10.1.2(react@19.2.8) + '@react-spring/shared': 10.1.2(react@19.3.0) '@react-spring/types': 10.1.2 - react: 19.2.8 + react: 19.3.0 - '@react-spring/core@10.1.2(react@19.2.8)': + '@react-spring/core@10.1.2(react@19.3.0)': dependencies: - '@react-spring/animated': 10.1.2(react@19.2.8) - '@react-spring/shared': 10.1.2(react@19.2.8) + '@react-spring/animated': 10.1.2(react@19.3.0) + '@react-spring/shared': 10.1.2(react@19.3.0) '@react-spring/types': 10.1.2 - react: 19.2.8 + react: 19.3.0 '@react-spring/rafz@10.1.2': {} - '@react-spring/shared@10.1.2(react@19.2.8)': + '@react-spring/shared@10.1.2(react@19.3.0)': dependencies: '@react-spring/rafz': 10.1.2 '@react-spring/types': 10.1.2 - react: 19.2.8 + react: 19.3.0 '@react-spring/types@10.1.2': {} - '@react-spring/web@10.1.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@react-spring/web@10.1.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@react-spring/animated': 10.1.2(react@19.2.8) - '@react-spring/core': 10.1.2(react@19.2.8) - '@react-spring/shared': 10.1.2(react@19.2.8) + '@react-spring/animated': 10.1.2(react@19.3.0) + '@react-spring/core': 10.1.2(react@19.3.0) + '@react-spring/shared': 10.1.2(react@19.3.0) '@react-spring/types': 10.1.2 csstype: 3.2.3 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) '@rolldown/binding-android-arm64@1.2.4': optional: true @@ -13174,26 +13175,26 @@ snapshots: '@tanstack/pacer-lite@0.1.1': {} - '@tanstack/react-form@1.33.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tanstack/react-form@1.33.4(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/form-core': 1.33.4 - '@tanstack/react-store': 0.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 + '@tanstack/react-store': 0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 transitivePeerDependencies: - react-dom - '@tanstack/react-store@0.11.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tanstack/react-store@0.11.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/store': 0.11.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - use-sync-external-store: 1.6.0(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + use-sync-external-store: 1.6.0(react@19.3.0) - '@tanstack/react-virtual@3.14.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@tanstack/react-virtual@3.14.9(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/virtual-core': 3.17.7 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) '@tanstack/store@0.11.1': {} @@ -13219,15 +13220,15 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@babel/runtime': 7.29.7 '@testing-library/dom': 10.4.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) '@testing-library/user-event@14.6.3(@testing-library/dom@10.4.1)': dependencies: @@ -13314,11 +13315,11 @@ snapshots: dependencies: parse-path: 7.1.0 - '@types/react-dom@19.2.4(@types/react@19.2.18)': + '@types/react-dom@19.3.0(@types/react@19.3.0)': dependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - '@types/react@19.2.18': + '@types/react@19.3.0': dependencies: csstype: 3.2.3 @@ -15609,14 +15610,14 @@ snapshots: fraction.js@5.3.4: {} - framer-motion@12.43.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + framer-motion@12.43.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: motion-dom: 12.43.0 motion-utils: 12.39.0 tslib: 2.8.1 optionalDependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) fs-constants@1.0.0: {} @@ -16817,9 +16818,9 @@ snapshots: dependencies: yallist: 4.0.0 - lucide-react@1.31.0(react@19.2.8): + lucide-react@1.31.0(react@19.3.0): dependencies: - react: 19.2.8 + react: 19.3.0 luxon@3.7.2: {} @@ -17662,13 +17663,13 @@ snapshots: motion-utils@12.39.0: {} - motion@12.43.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + motion@12.43.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: - framer-motion: 12.43.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + framer-motion: 12.43.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) tslib: 2.8.1 optionalDependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) mri@1.2.0: {} @@ -17700,16 +17701,16 @@ snapshots: neo-async@2.6.2: {} - next@16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + next@16.3.0(@babel/core@7.29.7(supports-color@10.2.2))(@playwright/test@1.62.1)(@types/node@22.20.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: '@next/env': 16.3.0 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.43 caniuse-lite: 1.0.30001806 postcss: 8.5.26 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@10.2.2))(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@10.2.2))(react@19.3.0) optionalDependencies: '@next/swc-darwin-arm64': 16.3.0 '@next/swc-darwin-x64': 16.3.0 @@ -18539,68 +18540,68 @@ snapshots: quotation@2.0.3: {} - radix-ui@1.4.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + radix-ui@1.4.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.3.0)(react@19.3.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) range-parser@1.2.0: {} @@ -18611,18 +18612,18 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@19.2.8(react@19.2.8): + react-dom@19.3.0(react@19.3.0): dependencies: - react: 19.2.8 - scheduler: 0.27.0 + react: 19.3.0 + scheduler: 0.28.0 - react-error-boundary@6.1.2(react@19.2.8): + react-error-boundary@6.1.2(react@19.3.0): dependencies: - react: 19.2.8 + react: 19.3.0 - react-hook-form@7.75.0(react@19.2.8): + react-hook-form@7.75.0(react@19.3.0): dependencies: - react: 19.2.8 + react: 19.3.0 react-is@16.13.1: {} @@ -18630,42 +18631,42 @@ snapshots: react-is@18.3.1: {} - react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.8): + react-remove-scroll-bar@2.3.8(@types/react@19.3.0)(react@19.3.0): dependencies: - react: 19.2.8 - react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) + react: 19.3.0 + react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.0) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - react-remove-scroll@2.7.2(@types/react@19.2.18)(react@19.2.8): + react-remove-scroll@2.7.2(@types/react@19.3.0)(react@19.3.0): dependencies: - react: 19.2.8 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.2.8) - react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.8) + react: 19.3.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.3.0)(react@19.3.0) + react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.0) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.2.8) - use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.2.8) + use-callback-ref: 1.3.3(@types/react@19.3.0)(react@19.3.0) + use-sidecar: 1.1.3(@types/react@19.3.0)(react@19.3.0) optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - react-router@7.18.2(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + react-router@7.18.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: cookie: 1.1.1 - react: 19.2.8 + react: 19.3.0 set-cookie-parser: 2.7.2 optionalDependencies: - react-dom: 19.2.8(react@19.2.8) + react-dom: 19.3.0(react@19.3.0) - react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.8): + react-style-singleton@2.2.3(@types/react@19.3.0)(react@19.3.0): dependencies: get-nonce: 1.0.1 - react: 19.2.8 + react: 19.3.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - react@19.2.8: {} + react@19.3.0: {} read-cache@1.0.0: dependencies: @@ -19176,7 +19177,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.27.0: {} + scheduler@0.28.0: {} schema-utils@4.3.3: dependencies: @@ -19576,10 +19577,10 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@10.2.2))(react@19.2.8): + styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@10.2.2))(react@19.3.0): dependencies: client-only: 0.0.1 - react: 19.2.8 + react: 19.3.0 optionalDependencies: '@babel/core': 7.29.7(supports-color@10.2.2) @@ -20085,24 +20086,24 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.2.8): + use-callback-ref@1.3.3(@types/react@19.3.0)(react@19.3.0): dependencies: - react: 19.2.8 + react: 19.3.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - use-sidecar@1.1.3(@types/react@19.2.18)(react@19.2.8): + use-sidecar@1.1.3(@types/react@19.3.0)(react@19.3.0): dependencies: detect-node-es: 1.1.0 - react: 19.2.8 + react: 19.3.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.18 + '@types/react': 19.3.0 - use-sync-external-store@1.6.0(react@19.2.8): + use-sync-external-store@1.6.0(react@19.3.0): dependencies: - react: 19.2.8 + react: 19.3.0 util-deprecate@1.0.2: {} @@ -20181,14 +20182,14 @@ snapshots: tsx: 4.23.12 yaml: 2.9.0 - vitest-browser-react@2.2.0(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(vitest@4.1.10): + vitest-browser-react@2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.10): dependencies: - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) vitest: 4.1.10(@types/node@22.20.1)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-istanbul@4.1.10)(@vitest/ui@4.1.10)(jsdom@27.4.0(supports-color@10.2.2))(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0)) optionalDependencies: - '@types/react': 19.2.18 - '@types/react-dom': 19.2.4(@types/react@19.2.18) + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) vitest-fail-on-console@0.10.1(@vitest/utils@4.1.10)(vite@8.2.1(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.49.2)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10): dependencies: diff --git a/test/package.json b/test/package.json index 73867627fa7..17859f24787 100644 --- a/test/package.json +++ b/test/package.json @@ -10,13 +10,13 @@ "@playwright/test": "1.62.1", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "6.9.1", - "@types/react": "19.2.18", - "@types/react-dom": "19.2.4", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", "docs": "workspace:^", "es-toolkit": "1.50.0", "is-wsl": "3.1.1", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "react-router": "7.18.2", "util": "0.12.5", "webpack": "5.109.2", diff --git a/test/performance/package.json b/test/performance/package.json index 702b1d92981..07406877890 100644 --- a/test/performance/package.json +++ b/test/performance/package.json @@ -6,12 +6,12 @@ }, "dependencies": { "@base-ui/react": "workspace:*", - "react": "19.2.8", - "react-dom": "19.2.8" + "react": "19.3.0", + "react-dom": "19.3.0" }, "devDependencies": { "@mui/internal-benchmark": "0.0.3-canary.7", - "@types/react": "19.2.18", + "@types/react": "19.3.0", "@vitest/browser-playwright": "4.1.10", "vite": "8.2.1", "vitest": "4.1.10" diff --git a/test/public-types/package.json b/test/public-types/package.json index 996885ae3de..c661ee785bb 100644 --- a/test/public-types/package.json +++ b/test/public-types/package.json @@ -2,7 +2,7 @@ "private": true, "dependencies": { "@base-ui/react": "workspace:*", - "react": "19.2.8" + "react": "19.3.0" }, "scripts": { "release:test": "tsc --noEmit" From 4952f5be2aa6932e422e782e8793b5aa6297d167 Mon Sep 17 00:00:00 2001 From: mnajdova Date: Fri, 11 Sep 2026 13:43:05 +0200 Subject: [PATCH 2/7] experiment with popups --- .../experiments/popover/view-transition.css | 33 +++++ .../popover/view-transition.module.css | 78 ++++++++++++ .../experiments/popover/view-transition.tsx | 116 ++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 docs/src/app/(private)/experiments/popover/view-transition.css create mode 100644 docs/src/app/(private)/experiments/popover/view-transition.module.css create mode 100644 docs/src/app/(private)/experiments/popover/view-transition.tsx diff --git a/docs/src/app/(private)/experiments/popover/view-transition.css b/docs/src/app/(private)/experiments/popover/view-transition.css new file mode 100644 index 00000000000..620525a4dba --- /dev/null +++ b/docs/src/app/(private)/experiments/popover/view-transition.css @@ -0,0 +1,33 @@ +/* View transition pseudo-elements live on the root, so these rules must be global. */ +::view-transition-old(.pop-out), +::view-transition-new(.pop-in) { + animation-duration: 400ms; + animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); +} + +::view-transition-group(vt-cover) { + animation-duration: 400ms; + animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); +} + +::view-transition-new(.pop-in) { + animation-name: popoverPopIn; +} + +::view-transition-old(.pop-out) { + animation-name: popoverPopOut; +} + +@keyframes popoverPopIn { + from { + opacity: 0; + transform: scale(0.9); + } +} + +@keyframes popoverPopOut { + to { + opacity: 0; + transform: scale(0.9); + } +} diff --git a/docs/src/app/(private)/experiments/popover/view-transition.module.css b/docs/src/app/(private)/experiments/popover/view-transition.module.css new file mode 100644 index 00000000000..df407f3e069 --- /dev/null +++ b/docs/src/app/(private)/experiments/popover/view-transition.module.css @@ -0,0 +1,78 @@ +.Page { + display: grid; + gap: 3rem; + max-width: 32rem; +} + +.Section { + display: grid; + gap: 1rem; + justify-items: start; +} + +.Trigger { + display: inline-flex; + align-items: center; + gap: 0.75rem; + height: auto; + padding: 0.5rem 0.75rem; + border: 1px solid oklch(14.5% 0 0deg); + background: white; + color: oklch(14.5% 0 0deg); + font: inherit; + cursor: pointer; +} + +.ThumbSlot { + display: block; + width: 3rem; + height: 3rem; +} + +.Thumb { + display: block; + width: 3rem; + height: 3rem; + border-radius: 0.5rem; + background: linear-gradient(135deg, oklch(70% 0.2 250deg), oklch(60% 0.2 320deg)); +} + +.Positioner { + width: var(--positioner-width); + height: var(--positioner-height); + max-width: var(--available-width); +} + +.Popup { + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + width: 20rem; + padding: 1rem; + border: 1px solid oklch(14.5% 0 0deg); + background: white; + color: oklch(14.5% 0 0deg); + transform-origin: var(--transform-origin); + outline: none; +} + +.Cover { + width: 100%; + height: 8rem; + border-radius: 0.5rem; + background: linear-gradient(135deg, oklch(70% 0.2 250deg), oklch(60% 0.2 320deg)); +} + +.Title { + margin: 0; + font-size: 1rem; + font-weight: 700; +} + +.Description { + margin: 0; + font-size: 0.875rem; + line-height: 1.25rem; +} diff --git a/docs/src/app/(private)/experiments/popover/view-transition.tsx b/docs/src/app/(private)/experiments/popover/view-transition.tsx new file mode 100644 index 00000000000..79e767fdfe2 --- /dev/null +++ b/docs/src/app/(private)/experiments/popover/view-transition.tsx @@ -0,0 +1,116 @@ +'use client'; +import * as React from 'react'; +import { Popover } from '@base-ui/react/popover'; +import styles from './view-transition.module.css'; +import './view-transition.css'; + +/** + * Popovers animated with React 19.3's ``. + * + * Setup: + * - `open` is controlled and every change runs inside `React.startTransition`, because only + * Transitions activate ``. + * - `` is always rendered so the portal node exists before the popup + * opens (the portal node is created in a layout effect, one commit after the Portal mounts). + * - The positioner/popup are rendered conditionally so React removes them inside the Transition. + * Base UI's own delayed unmount (after CSS animations finish) runs outside the Transition. + * + * What to observe (Chrome, DevTools > Animations helps): + * - Closing animates: the popup plays `pop-out`, and in the shared-element example the cover + * morphs back into the thumbnail in the trigger. + * - Opening does not animate: on the opening commit the positioner is rendered with `hidden` + * (`hidden: !mounted` in PopoverPositioner) because `mounted` reaches the popup store through a + * layout effect, one commit late. The browser captures its "new" snapshot before the follow-up + * synchronous commit reveals it, so neither the positioner nor the shared cover inside it exists + * in the snapshot. Dialog behaves the same way (there it's the popup that is hidden). + */ +export default function PopoverViewTransitionExperiment() { + return ( +
+
+

Enter / exit

+ +
+
+

Shared element

+ +
+
+ ); +} + +function EnterExitPopover() { + const [open, setOpen] = React.useState(false); + + return ( + { + React.startTransition(() => { + setOpen(nextOpen); + }); + }} + > + Notifications + + {open && ( + + + + Notifications + + You are all caught up. Good job! + + + + + )} + + + ); +} + +function SharedElementPopover() { + const [open, setOpen] = React.useState(false); + + return ( + { + React.startTransition(() => { + setOpen(nextOpen); + }); + }} + > + + + {/* Only one `vt-cover` may be mounted at a time, so the thumbnail leaves while open. */} + {!open && ( + + + + )} + + View cover + + + {open && ( + + + + +
+ + Cover + + The thumbnail should morph into this cover when opening, and back when closing. + + Close + + + + )} + + + ); +} From 02e0552e87b060cbd9554c233b070c822299d111 Mon Sep 17 00:00:00 2001 From: atomiks Date: Tue, 15 Sep 2026 12:26:34 +1000 Subject: [PATCH 3/7] [all components] Keep popup lifecycle rendering in React --- .../(docs)/react/handbook/animation/page.mdx | 7 +- .../experiments/dialog/view-transition.css | 58 +++++++ .../experiments/dialog/view-transition.tsx | 111 ++++++++++++++ .../experiments/popover/view-transition.css | 8 +- .../experiments/popover/view-transition.tsx | 14 +- .../src/combobox/arrow/ComboboxArrow.tsx | 8 +- .../combobox/backdrop/ComboboxBackdrop.tsx | 17 ++- .../src/combobox/popup/ComboboxPopup.tsx | 14 +- .../src/combobox/portal/ComboboxPortal.tsx | 4 +- .../positioner/ComboboxPositioner.tsx | 14 +- .../react/src/combobox/root/AriaCombobox.tsx | 39 ++--- .../src/combobox/root/ComboboxRoot.test.tsx | 37 ++++- .../src/combobox/root/ComboboxRootContext.tsx | 6 + .../src/dialog/backdrop/DialogBackdrop.tsx | 13 +- .../react/src/dialog/close/DialogClose.tsx | 4 +- .../react/src/dialog/popup/DialogPopup.tsx | 13 +- .../react/src/dialog/portal/DialogPortal.tsx | 10 +- .../react/src/dialog/root/DialogRoot.test.tsx | 29 ++++ .../src/dialog/root/DialogRootContext.ts | 7 + .../src/dialog/root/useRenderDialogRoot.tsx | 40 +++-- .../src/dialog/viewport/DialogViewport.tsx | 13 +- .../src/drawer/backdrop/DrawerBackdrop.tsx | 13 +- .../react/src/drawer/popup/DrawerPopup.tsx | 13 +- packages/react/src/drawer/root/DrawerRoot.tsx | 4 +- .../src/drawer/swipe-area/DrawerSwipeArea.tsx | 4 +- .../src/drawer/viewport/DrawerViewport.tsx | 10 +- .../DrawerVirtualKeyboardProvider.tsx | 10 +- packages/react/src/menu/arrow/MenuArrow.tsx | 6 +- .../react/src/menu/backdrop/MenuBackdrop.tsx | 13 +- packages/react/src/menu/popup/MenuPopup.tsx | 13 +- packages/react/src/menu/portal/MenuPortal.tsx | 6 +- .../src/menu/positioner/MenuPositioner.tsx | 13 +- packages/react/src/menu/root/MenuRoot.tsx | 23 ++- .../react/src/menu/root/MenuRootContext.ts | 6 + .../react/src/popover/arrow/PopoverArrow.tsx | 6 +- .../src/popover/backdrop/PopoverBackdrop.tsx | 13 +- .../react/src/popover/popup/PopoverPopup.tsx | 13 +- .../src/popover/portal/PopoverPortal.tsx | 6 +- .../popover/positioner/PopoverPositioner.tsx | 13 +- .../src/popover/root/PopoverRoot.test.tsx | 72 +++++++++ .../react/src/popover/root/PopoverRoot.tsx | 28 +++- .../src/popover/root/PopoverRootContext.ts | 7 + .../preview-card/arrow/PreviewCardArrow.tsx | 6 +- .../backdrop/PreviewCardBackdrop.tsx | 15 +- .../preview-card/popup/PreviewCardPopup.tsx | 10 +- .../preview-card/portal/PreviewCardPortal.tsx | 6 +- .../positioner/PreviewCardPositioner.tsx | 13 +- .../preview-card/root/PreviewCardContext.ts | 6 + .../src/preview-card/root/PreviewCardRoot.tsx | 26 +++- .../react/src/select/arrow/SelectArrow.tsx | 8 +- .../src/select/backdrop/SelectBackdrop.tsx | 17 ++- .../react/src/select/popup/SelectPopup.tsx | 13 +- .../react/src/select/portal/SelectPortal.tsx | 4 +- .../select/positioner/SelectPositioner.tsx | 13 +- packages/react/src/select/root/SelectRoot.tsx | 27 ++-- .../src/select/root/SelectRootContext.ts | 6 + .../react/src/tooltip/arrow/TooltipArrow.tsx | 4 +- .../react/src/tooltip/popup/TooltipPopup.tsx | 10 +- .../src/tooltip/portal/TooltipPortal.tsx | 6 +- .../tooltip/positioner/TooltipPositioner.tsx | 13 +- .../react/src/tooltip/root/TooltipRoot.tsx | 32 ++-- .../src/tooltip/root/TooltipRootContext.ts | 6 + .../src/utils/popups/popupLifecycle.test.tsx | 144 ++++++++++++++++++ .../react/src/utils/popups/popupStoreUtils.ts | 16 +- .../src/utils/popups/usePopupCloseComplete.ts | 54 +++++++ 65 files changed, 975 insertions(+), 238 deletions(-) create mode 100644 docs/src/app/(private)/experiments/dialog/view-transition.css create mode 100644 docs/src/app/(private)/experiments/dialog/view-transition.tsx create mode 100644 packages/react/src/utils/popups/popupLifecycle.test.tsx create mode 100644 packages/react/src/utils/popups/usePopupCloseComplete.ts diff --git a/docs/src/app/(docs)/react/handbook/animation/page.mdx b/docs/src/app/(docs)/react/handbook/animation/page.mdx index a0ff4450163..c00063c6c11 100644 --- a/docs/src/app/(docs)/react/handbook/animation/page.mdx +++ b/docs/src/app/(docs)/react/handbook/animation/page.mdx @@ -249,8 +249,11 @@ import { DemoViewTransitionList } from './demos/view-transition-list'; ### Popups -Popup components such as Dialog, Popover, and Menu render their popup hidden during the commit that opens them and reveal it right after, so a `` cannot capture their opening state. -Use the CSS transition and animation attributes described above to animate popups. +Dialog can animate with `` when `open` is controlled inside `startTransition`, `` stays rendered, and the popup and its View Transition boundary are rendered conditionally from `open`. +Keeping the portal mounted lets its DOM container exist before the opening Transition. + +Anchored popups such as Popover, Menu, and Select still calculate their position after the opening commit, so their opening snapshot can be invisible or incorrectly positioned. +Use the CSS transition and animation attributes described above to animate these popups. export const metadata = { keywords: [ diff --git a/docs/src/app/(private)/experiments/dialog/view-transition.css b/docs/src/app/(private)/experiments/dialog/view-transition.css new file mode 100644 index 00000000000..eda5b7fcbb3 --- /dev/null +++ b/docs/src/app/(private)/experiments/dialog/view-transition.css @@ -0,0 +1,58 @@ +.view-transition-experiment { + padding: 2rem; +} + +.view-transition-trigger { + display: flex; + align-items: center; + gap: 1rem; + min-height: 5rem; +} + +.view-transition-cover { + width: 12rem; + height: 8rem; + background: linear-gradient(135deg, #65b8ff, #5536a3); + border-radius: 0.5rem; +} + +.view-transition-trigger .view-transition-cover { + width: 3rem; + height: 3rem; +} + +.view-transition-dialog, +.view-transition-popover { + padding: 2rem; + color: #111; + background: white; + border: 1px solid #aaa; + border-radius: 1rem; +} + +.view-transition-dialog { + position: fixed; + top: 25%; + left: 40%; +} + +::view-transition-group(*) { + animation-duration: 400ms; +} + +/* Keep the shared cover above the popup's separate snapshot. */ +::view-transition-group(.view-transition-cover) { + z-index: 1; +} + +/* Fit both snapshots to the same animated box as the aspect ratio changes. */ +::view-transition-old(.view-transition-cover), +::view-transition-new(.view-transition-cover) { + height: 100%; +} + +@media (prefers-reduced-motion: reduce) { + ::view-transition-group(*) { + animation-duration: 0s; + } +} diff --git a/docs/src/app/(private)/experiments/dialog/view-transition.tsx b/docs/src/app/(private)/experiments/dialog/view-transition.tsx new file mode 100644 index 00000000000..d52be4cd229 --- /dev/null +++ b/docs/src/app/(private)/experiments/dialog/view-transition.tsx @@ -0,0 +1,111 @@ +'use client'; +import * as React from 'react'; +import type {} from 'react/canary'; +import { Dialog } from '@base-ui/react/dialog'; +import { Popover } from '@base-ui/react/popover'; +import './view-transition.css'; + +/** + * Run with React 19.3 to compare dialog and anchored popup snapshots. + * Keep the portal node mounted, and insert/remove the ViewTransition boundary + * in the same React Transition as `open` instead of waiting for CSS exit animations. + * Dialog captures the shared cover at both sizes. Popover still captures an invisible + * positioner until its asynchronous positioning completes. + */ +export default function ViewTransitionExperiment() { + if (!React.ViewTransition) { + return

This experiment requires React 19.3.

; + } + + return ( +
+

Popup view transitions

+ + +
+ ); +} + +function Cover() { + return
; +} + +function DialogExample() { + const [open, setOpen] = React.useState(false); + const coverName = React.useId(); + + function changeOpen(nextOpen: boolean) { + React.startTransition(() => setOpen(nextOpen)); + } + + return ( +
+

Dialog

+ + + {!open && ( + + + + )} + Open dialog + + + {open && ( + + + + + + Dialog + The cover expands into the popup. + Close dialog + + + )} + + +
+ ); +} + +function PopoverExample() { + const [open, setOpen] = React.useState(false); + const coverName = React.useId(); + + function changeOpen(nextOpen: boolean) { + React.startTransition(() => setOpen(nextOpen)); + } + + return ( +
+

Popover

+ + + {!open && ( + + + + )} + Open popover + + + {open && ( + + + + + + + Popover + The cover expands into the popup. + Close popover + + + + )} + + +
+ ); +} diff --git a/docs/src/app/(private)/experiments/popover/view-transition.css b/docs/src/app/(private)/experiments/popover/view-transition.css index 620525a4dba..a520ab5cbd0 100644 --- a/docs/src/app/(private)/experiments/popover/view-transition.css +++ b/docs/src/app/(private)/experiments/popover/view-transition.css @@ -5,7 +5,8 @@ animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); } -::view-transition-group(vt-cover) { +::view-transition-group(.vt-cover) { + z-index: 1; animation-duration: 400ms; animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1); } @@ -31,3 +32,8 @@ transform: scale(0.9); } } + +::view-transition-old(.vt-cover), +::view-transition-new(.vt-cover) { + height: 100%; +} diff --git a/docs/src/app/(private)/experiments/popover/view-transition.tsx b/docs/src/app/(private)/experiments/popover/view-transition.tsx index 79e767fdfe2..e6f9c231664 100644 --- a/docs/src/app/(private)/experiments/popover/view-transition.tsx +++ b/docs/src/app/(private)/experiments/popover/view-transition.tsx @@ -18,11 +18,11 @@ import './view-transition.css'; * What to observe (Chrome, DevTools > Animations helps): * - Closing animates: the popup plays `pop-out`, and in the shared-element example the cover * morphs back into the thumbnail in the trigger. - * - Opening does not animate: on the opening commit the positioner is rendered with `hidden` - * (`hidden: !mounted` in PopoverPositioner) because `mounted` reaches the popup store through a - * layout effect, one commit late. The browser captures its "new" snapshot before the follow-up - * synchronous commit reveals it, so neither the positioner nor the shared cover inside it exists - * in the snapshot. Dialog behaves the same way (there it's the popup that is hidden). + * - Opening still does not animate correctly: lifecycle state now reaches the positioner in + * the opening commit, but its asynchronous positioning has not finished. The snapshot can + * capture the positioner at (0, 0) with opacity: 0. + * - Compare the Dialog example at /experiments/dialog/view-transition, which does not need + * anchor positioning. */ export default function PopoverViewTransitionExperiment() { return ( @@ -86,7 +86,7 @@ function SharedElementPopover() { {/* Only one `vt-cover` may be mounted at a time, so the thumbnail leaves while open. */} {!open && ( - + )} @@ -98,7 +98,7 @@ function SharedElementPopover() { - +
Cover diff --git a/packages/react/src/combobox/arrow/ComboboxArrow.tsx b/packages/react/src/combobox/arrow/ComboboxArrow.tsx index 6b9b6103a6b..40a7979ae44 100644 --- a/packages/react/src/combobox/arrow/ComboboxArrow.tsx +++ b/packages/react/src/combobox/arrow/ComboboxArrow.tsx @@ -1,9 +1,7 @@ 'use client'; import * as React from 'react'; -import { useStore } from '@base-ui/utils/store'; import { useComboboxPositionerContext } from '../positioner/ComboboxPositionerContext'; -import { useComboboxRootContext } from '../root/ComboboxRootContext'; -import { selectors } from '../store'; +import { useComboboxRootContext, ComboboxOpenContext } from '../root/ComboboxRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; @@ -21,10 +19,10 @@ export const ComboboxArrow = React.forwardRef(function ComboboxArrow( ) { const { render, className, style, ...elementProps } = componentProps; - const store = useComboboxRootContext(); + useComboboxRootContext(); const { arrowRef, side, align, arrowUncentered, arrowStyles } = useComboboxPositionerContext(); - const open = useStore(store, selectors.open); + const open = React.useContext(ComboboxOpenContext); const state: ComboboxArrowState = { open, diff --git a/packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx b/packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx index a6a70a09184..a99418135b6 100644 --- a/packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx +++ b/packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx @@ -1,14 +1,17 @@ 'use client'; import * as React from 'react'; -import { useStore } from '@base-ui/utils/store'; import type { BaseUIComponentProps } from '../../internals/types'; -import { useComboboxRootContext } from '../root/ComboboxRootContext'; +import { + useComboboxRootContext, + ComboboxOpenContext, + ComboboxMountedContext, + ComboboxTransitionStatusContext, +} from '../root/ComboboxRootContext'; import { popupStateMapping } from '../../utils/popupStateMapping'; import type { StateAttributesMapping } from '../../internals/getStateAttributesProps'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; import { transitionStatusMapping } from '../../internals/stateAttributesMapping'; import { useRenderElement } from '../../internals/useRenderElement'; -import { selectors } from '../store'; const stateAttributesMapping: StateAttributesMapping = { ...popupStateMapping, @@ -27,11 +30,11 @@ export const ComboboxBackdrop = React.forwardRef(function ComboboxBackdrop( ) { const { render, className, style, ...elementProps } = componentProps; - const store = useComboboxRootContext(); + useComboboxRootContext(); - const open = useStore(store, selectors.open); - const mounted = useStore(store, selectors.mounted); - const transitionStatus = useStore(store, selectors.transitionStatus); + const open = React.useContext(ComboboxOpenContext); + const mounted = React.useContext(ComboboxMountedContext); + const transitionStatus = React.useContext(ComboboxTransitionStatusContext); const state: ComboboxBackdropState = { open, diff --git a/packages/react/src/combobox/popup/ComboboxPopup.tsx b/packages/react/src/combobox/popup/ComboboxPopup.tsx index 39d5718becf..28a469816e7 100644 --- a/packages/react/src/combobox/popup/ComboboxPopup.tsx +++ b/packages/react/src/combobox/popup/ComboboxPopup.tsx @@ -6,7 +6,13 @@ import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { FloatingFocusManager } from '../../floating-ui-react'; import { BaseUIComponentProps } from '../../internals/types'; import { useRenderElement } from '../../internals/useRenderElement'; -import { useComboboxFloatingContext, useComboboxRootContext } from '../root/ComboboxRootContext'; +import { + useComboboxFloatingContext, + useComboboxRootContext, + ComboboxOpenContext, + ComboboxMountedContext, + ComboboxTransitionStatusContext, +} from '../root/ComboboxRootContext'; import { selectors } from '../store'; import { popupStateMapping } from '../../utils/popupStateMapping'; import { useComboboxPositionerContext } from '../positioner/ComboboxPositionerContext'; @@ -42,11 +48,11 @@ export const ComboboxPopup = React.forwardRef(function ComboboxPopup( const positioning = useComboboxPositionerContext(); const floatingRootContext = useComboboxFloatingContext(); - const mounted = useStore(store, selectors.mounted); - const open = useStore(store, selectors.open); + const mounted = React.useContext(ComboboxMountedContext); + const open = React.useContext(ComboboxOpenContext); const openMethod = useStore(store, selectors.openMethod); const popupProps = useStore(store, selectors.popupProps); - const transitionStatus = useStore(store, selectors.transitionStatus); + const transitionStatus = React.useContext(ComboboxTransitionStatusContext); const inputInsidePopup = useStore(store, selectors.inputInsidePopup); const inputElement = useStore(store, selectors.inputElement); const modal = useStore(store, selectors.modal); diff --git a/packages/react/src/combobox/portal/ComboboxPortal.tsx b/packages/react/src/combobox/portal/ComboboxPortal.tsx index 320769bef0f..6207e290261 100644 --- a/packages/react/src/combobox/portal/ComboboxPortal.tsx +++ b/packages/react/src/combobox/portal/ComboboxPortal.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useStore } from '@base-ui/utils/store'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { useComboboxRootContext } from '../root/ComboboxRootContext'; +import { useComboboxRootContext, ComboboxMountedContext } from '../root/ComboboxRootContext'; import { ComboboxPortalContext } from './ComboboxPortalContext'; import { selectors } from '../store'; @@ -22,7 +22,7 @@ export const ComboboxPortal = React.forwardRef(function ComboboxPortal( const store = useComboboxRootContext(); - const mounted = useStore(store, selectors.mounted); + const mounted = React.useContext(ComboboxMountedContext); const forceMounted = useStore(store, selectors.forceMounted); const shouldRender = mounted || keepMounted || forceMounted; diff --git a/packages/react/src/combobox/positioner/ComboboxPositioner.tsx b/packages/react/src/combobox/positioner/ComboboxPositioner.tsx index 6d01171d8dd..1d4af7ab45c 100644 --- a/packages/react/src/combobox/positioner/ComboboxPositioner.tsx +++ b/packages/react/src/combobox/positioner/ComboboxPositioner.tsx @@ -4,7 +4,13 @@ import { useStore } from '@base-ui/utils/store'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { inertValue } from '@base-ui/utils/inertValue'; -import { useComboboxFloatingContext, useComboboxRootContext } from '../root/ComboboxRootContext'; +import { + useComboboxFloatingContext, + useComboboxRootContext, + ComboboxOpenContext, + ComboboxMountedContext, + ComboboxTransitionStatusContext, +} from '../root/ComboboxRootContext'; import { ComboboxPositionerContext } from './ComboboxPositionerContext'; import { useListEmpty } from '../utils/parts'; import { @@ -57,15 +63,15 @@ export const ComboboxPositioner = React.forwardRef(function ComboboxPositioner( const keepMounted = useComboboxPortalContext(); const modal = useStore(store, selectors.modal); - const open = useStore(store, selectors.open); - const mounted = useStore(store, selectors.mounted); + const open = React.useContext(ComboboxOpenContext); + const mounted = React.useContext(ComboboxMountedContext); const openMethod = useStore(store, selectors.openMethod); const positionerElement = useStore(store, selectors.positionerElement); const triggerElement = useStore(store, selectors.triggerElement); const inputElement = useStore(store, selectors.inputElement); const inputGroupElement = useStore(store, selectors.inputGroupElement); const inputInsidePopup = useStore(store, selectors.inputInsidePopup); - const transitionStatus = useStore(store, selectors.transitionStatus); + const transitionStatus = React.useContext(ComboboxTransitionStatusContext); const empty = useListEmpty(); const resolvedAnchor = diff --git a/packages/react/src/combobox/root/AriaCombobox.tsx b/packages/react/src/combobox/root/AriaCombobox.tsx index 2a4ad57efca..d2d6a7d52f2 100644 --- a/packages/react/src/combobox/root/AriaCombobox.tsx +++ b/packages/react/src/combobox/root/AriaCombobox.tsx @@ -34,9 +34,12 @@ import { ComboboxHasItemsContext, ComboboxRootContext, ComboboxInputValueContext, + ComboboxOpenContext, + ComboboxMountedContext, + ComboboxTransitionStatusContext, } from './ComboboxRootContext'; import { selectors, type State as StoreState } from '../store'; -import { useOpenChangeComplete } from '../../internals/useOpenChangeComplete'; +import { usePopupCloseComplete } from '../../utils/popups/usePopupCloseComplete'; import { useFieldRootContext } from '../../internals/field-root-context/FieldRootContext'; import { useRegisterFieldControl } from '../../internals/field-register-control/useRegisterFieldControl'; import { useFormContext } from '../../internals/form-context/FormContext'; @@ -965,15 +968,11 @@ export function AriaCombobox ({ unmount: handleUnmount }), [handleUnmount]); @@ -1631,15 +1630,21 @@ export function AriaCombobox - - - - - {children} - - - - + + + + + + + + {children} + + + + + + + ); } diff --git a/packages/react/src/combobox/root/ComboboxRoot.test.tsx b/packages/react/src/combobox/root/ComboboxRoot.test.tsx index 5d9da238836..ed2f69fe23f 100644 --- a/packages/react/src/combobox/root/ComboboxRoot.test.tsx +++ b/packages/react/src/combobox/root/ComboboxRoot.test.tsx @@ -297,7 +297,14 @@ describe('', () => { await waitFor(() => expect(input).toHaveFocus()); await user.type(input, 'ban'); - await user.keyboard('{ArrowDown}{Enter}'); + // Closing detaches store-backed refs synchronously; await their cascading updates. + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(async () => { + fireEvent.keyDown(input, { key: 'ArrowDown' }); + fireEvent.keyUp(input, { key: 'ArrowDown' }); + fireEvent.keyDown(input, { key: 'Enter' }); + fireEvent.keyUp(input, { key: 'Enter' }); + }); await waitFor(() => expect(screen.queryByRole('dialog')).toBe(null)); expect(trigger).toHaveFocus(); @@ -344,7 +351,13 @@ describe('', () => { const trigger = screen.getByTestId('trigger'); await user.click(trigger); await user.type(await screen.findByTestId('input'), 'ban'); - await user.keyboard('{Escape}'); + // Closing detaches store-backed refs synchronously; await their cascading updates. + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(async () => { + const target = screen.getByTestId('input'); + fireEvent.keyDown(target, { key: 'Escape' }); + fireEvent.keyUp(target, { key: 'Escape' }); + }); await waitFor(() => expect(screen.queryByRole('dialog')).toBe(null)); expect(trigger).toHaveFocus(); @@ -2695,7 +2708,13 @@ describe('', () => { expect(screen.queryByRole('option', { name: 'banana' })).toBe(null); }); - await user.keyboard('{Escape}'); + // Closing detaches store-backed refs synchronously; await their cascading updates. + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(async () => { + const target = screen.getByTestId('input'); + fireEvent.keyDown(target, { key: 'Escape' }); + fireEvent.keyUp(target, { key: 'Escape' }); + }); await waitFor(() => { expect(screen.queryByRole('listbox')).toBe(null); }); @@ -10051,7 +10070,11 @@ describe('', () => { const input = await screen.findByTestId('dialog-input'); await user.type(input, 'ap'); - await user.click(screen.getByRole('option', { name: 'Apple' })); + // Closing detaches store-backed refs synchronously; await their cascading updates. + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(async () => { + fireEvent.click(screen.getByRole('option', { name: 'Apple' })); + }); await waitFor(() => { expect(screen.queryByRole('dialog', { name: 'Fruit chooser' })).toBe(null); @@ -10076,7 +10099,11 @@ describe('', () => { const input = await screen.findByTestId('dialog-input'); await user.type(input, 'ap'); - await user.click(screen.getByRole('option', { name: 'Apple' })); + // Closing detaches store-backed refs synchronously; await their cascading updates. + // eslint-disable-next-line testing-library/no-unnecessary-act + await act(async () => { + fireEvent.click(screen.getByRole('option', { name: 'Apple' })); + }); await waitFor(() => { expect(screen.queryByRole('dialog', { name: 'Fruit chooser' })).toBe(null); diff --git a/packages/react/src/combobox/root/ComboboxRootContext.tsx b/packages/react/src/combobox/root/ComboboxRootContext.tsx index 515f950fd5e..82f39b39b2a 100644 --- a/packages/react/src/combobox/root/ComboboxRootContext.tsx +++ b/packages/react/src/combobox/root/ComboboxRootContext.tsx @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import type { TransitionStatus } from '../../internals/useTransitionStatus'; import { ComboboxStore } from '../store'; import type { FloatingRootContext } from '../../floating-ui-react'; @@ -64,3 +65,8 @@ export function useComboboxInputValueContext() { export function useComboboxHasItemsContext() { return React.useContext(ComboboxHasItemsContext); } + +// Separate contexts preserve subscriptions to individual lifecycle values. +export const ComboboxOpenContext = React.createContext(false); +export const ComboboxMountedContext = React.createContext(false); +export const ComboboxTransitionStatusContext = React.createContext(undefined); diff --git a/packages/react/src/dialog/backdrop/DialogBackdrop.tsx b/packages/react/src/dialog/backdrop/DialogBackdrop.tsx index 0c25c663979..db8bda49581 100644 --- a/packages/react/src/dialog/backdrop/DialogBackdrop.tsx +++ b/packages/react/src/dialog/backdrop/DialogBackdrop.tsx @@ -1,6 +1,11 @@ 'use client'; import * as React from 'react'; -import { useDialogRootContext } from '../root/DialogRootContext'; +import { + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, + DialogTransitionStatusContext, +} from '../root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import { type TransitionStatus } from '../../internals/useTransitionStatus'; import { type BaseUIComponentProps } from '../../internals/types'; @@ -20,10 +25,10 @@ export const DialogBackdrop = React.forwardRef(function DialogBackdrop( const store = useDialogRootContext(); - const open = store.useState('open'); + const open = React.useContext(DialogOpenContext); const nested = store.useState('nested'); - const mounted = store.useState('mounted'); - const transitionStatus = store.useState('transitionStatus'); + const mounted = React.useContext(DialogMountedContext); + const transitionStatus = React.useContext(DialogTransitionStatusContext); const state: DialogBackdropState = { open, diff --git a/packages/react/src/dialog/close/DialogClose.tsx b/packages/react/src/dialog/close/DialogClose.tsx index 737e2996d2c..8d0df362804 100644 --- a/packages/react/src/dialog/close/DialogClose.tsx +++ b/packages/react/src/dialog/close/DialogClose.tsx @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { useDialogRootContext } from '../root/DialogRootContext'; +import { useDialogRootContext, DialogOpenContext } from '../root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { BaseUIComponentProps, NativeButtonProps } from '../../internals/types'; import { useButton } from '../../internals/use-button'; @@ -27,7 +27,7 @@ export const DialogClose = React.forwardRef(function DialogClose( } = componentProps; const store = useDialogRootContext(); - const open = store.useState('open'); + const open = React.useContext(DialogOpenContext); const { getButtonProps, buttonRef } = useButton({ disabled, diff --git a/packages/react/src/dialog/popup/DialogPopup.tsx b/packages/react/src/dialog/popup/DialogPopup.tsx index 5ea6ff73a26..20482ae39c4 100644 --- a/packages/react/src/dialog/popup/DialogPopup.tsx +++ b/packages/react/src/dialog/popup/DialogPopup.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import { InteractionType } from '@base-ui/utils/useEnhancedClickHandler'; import { FloatingFocusManager } from '../../floating-ui-react'; -import { useDialogRootContext } from '../root/DialogRootContext'; +import { + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, + DialogTransitionStatusContext, +} from '../root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import { type BaseUIComponentProps } from '../../internals/types'; import { type TransitionStatus } from '../../internals/useTransitionStatus'; @@ -31,13 +36,13 @@ export const DialogPopup = React.forwardRef(function DialogPopup( const floatingRootContext = store.useState('floatingRootContext'); const rootPopupProps = store.useState('popupProps'); const modal = store.useState('modal'); - const mounted = store.useState('mounted'); + const mounted = React.useContext(DialogMountedContext); const nested = store.useState('nested'); const nestedOpenDialogCount = store.useState('nestedOpenDialogCount'); - const open = store.useState('open'); + const open = React.useContext(DialogOpenContext); const openMethod = store.useState('openMethod'); const titleElementId = store.useState('titleElementId'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(DialogTransitionStatusContext); const role = store.useState('role'); const floatingId = floatingRootContext.useState('floatingId'); diff --git a/packages/react/src/dialog/portal/DialogPortal.tsx b/packages/react/src/dialog/portal/DialogPortal.tsx index 8e64f90c2cd..9fb5ab03b56 100644 --- a/packages/react/src/dialog/portal/DialogPortal.tsx +++ b/packages/react/src/dialog/portal/DialogPortal.tsx @@ -3,7 +3,11 @@ import * as React from 'react'; import { inertValue } from '@base-ui/utils/inertValue'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { useDialogRootContext } from '../root/DialogRootContext'; +import { + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, +} from '../root/DialogRootContext'; import { DialogPortalContext } from './DialogPortalContext'; import { InternalBackdrop } from '../../utils/InternalBackdrop'; @@ -21,9 +25,9 @@ export const DialogPortal = React.forwardRef(function DialogPortal( const { keepMounted = false, ...portalProps } = props; const store = useDialogRootContext(); - const mounted = store.useState('mounted'); + const mounted = React.useContext(DialogMountedContext); const modal = store.useState('modal'); - const open = store.useState('open'); + const open = React.useContext(DialogOpenContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/dialog/root/DialogRoot.test.tsx b/packages/react/src/dialog/root/DialogRoot.test.tsx index da63717ec68..84d2d92fdc1 100644 --- a/packages/react/src/dialog/root/DialogRoot.test.tsx +++ b/packages/react/src/dialog/root/DialogRoot.test.tsx @@ -25,6 +25,35 @@ describe('', () => { globalThis.BASE_UI_ANIMATIONS_DISABLED = true; }); + it('renders the open state without hidden in the opening commit', async () => { + const openingCommits: Array<{ hidden: boolean; open: boolean }> = []; + + function recordOpeningCommit(element: HTMLDivElement | null) { + if (element) { + openingCommits.push({ + hidden: element.hasAttribute('hidden'), + open: element.hasAttribute('data-open'), + }); + } + } + + function App({ open }: { open: boolean }) { + return ( + + Open + + {open && Content} + + + ); + } + + const { rerender } = await render(); + await rerender(); + + expect(openingCommits[0]).toEqual({ hidden: false, open: true }); + }); + popupConformanceTests({ createComponent: (props) => ( diff --git a/packages/react/src/dialog/root/DialogRootContext.ts b/packages/react/src/dialog/root/DialogRootContext.ts index 8178bb9a014..39e2748b4b8 100644 --- a/packages/react/src/dialog/root/DialogRootContext.ts +++ b/packages/react/src/dialog/root/DialogRootContext.ts @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import type { TransitionStatus } from '../../internals/useTransitionStatus'; import { DialogStore } from '../store/DialogStore'; export const DialogRootContext = React.createContext | undefined>(undefined); @@ -17,3 +18,9 @@ export function useDialogRootContext(optional?: boolean) { return store; } + +// Separate contexts keep consumers of `mounted` from rerendering when only +// `transitionStatus` changes. +export const DialogOpenContext = React.createContext(false); +export const DialogMountedContext = React.createContext(false); +export const DialogTransitionStatusContext = React.createContext(undefined); diff --git a/packages/react/src/dialog/root/useRenderDialogRoot.tsx b/packages/react/src/dialog/root/useRenderDialogRoot.tsx index 7deae6a9970..3f90649d6c0 100644 --- a/packages/react/src/dialog/root/useRenderDialogRoot.tsx +++ b/packages/react/src/dialog/root/useRenderDialogRoot.tsx @@ -1,7 +1,13 @@ 'use client'; import * as React from 'react'; import { DialogInteractions } from './useDialogRoot'; -import { DialogRootContext, useDialogRootContext } from './DialogRootContext'; +import { + DialogRootContext, + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, + DialogTransitionStatusContext, +} from './DialogRootContext'; import { DialogStore } from '../store/DialogStore'; import type { DialogRootProps } from './DialogRoot'; import { createChangeEventDetails } from '../../internals/createBaseUIEventDetails'; @@ -69,13 +75,15 @@ export function useRenderDialogRoot( store.useContextCallback('onOpenChange', onOpenChange); store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete); - const open = store.useState('open'); - const mounted = store.useState('mounted'); + const storeOpen = store.useState('open'); + // Read the controlled prop in this render so the popup joins the same React Transition, + // before the store synchronizes it in a layout effect. + const open = openProp ?? storeOpen; const payload = store.useState('payload') as Payload | undefined; usePopupRootSync(store, open); useImplicitActiveTrigger(store); - const { forceUnmount } = useOpenStateTransitions(open, store); + const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions(open, store); React.useImperativeHandle( actionsRef, @@ -90,15 +98,21 @@ export function useRenderDialogRoot( return ( }> - {handle && } - {shouldRenderInteractions && ( - - )} - {typeof children === 'function' ? children({ payload }) : children} + + + + {handle && } + {shouldRenderInteractions && ( + + )} + {typeof children === 'function' ? children({ payload }) : children} + + + ); } diff --git a/packages/react/src/dialog/viewport/DialogViewport.tsx b/packages/react/src/dialog/viewport/DialogViewport.tsx index cc706a3f6df..b6f7134e6a3 100644 --- a/packages/react/src/dialog/viewport/DialogViewport.tsx +++ b/packages/react/src/dialog/viewport/DialogViewport.tsx @@ -3,7 +3,12 @@ import * as React from 'react'; import { useRenderElement } from '../../internals/useRenderElement'; import { type BaseUIComponentProps } from '../../internals/types'; import { type TransitionStatus } from '../../internals/useTransitionStatus'; -import { useDialogRootContext } from '../root/DialogRootContext'; +import { + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, + DialogTransitionStatusContext, +} from '../root/DialogRootContext'; import { useDialogPortalContext } from '../portal/DialogPortalContext'; import { dialogStateAttributesMapping } from '../utils/stateAttributesMapping'; @@ -22,11 +27,11 @@ export const DialogViewport = React.forwardRef(function DialogViewport( const keepMounted = useDialogPortalContext(); const store = useDialogRootContext(); - const open = store.useState('open'); + const open = React.useContext(DialogOpenContext); const nested = store.useState('nested'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(DialogTransitionStatusContext); const nestedOpenDialogCount = store.useState('nestedOpenDialogCount'); - const mounted = store.useState('mounted'); + const mounted = React.useContext(DialogMountedContext); const setViewportElement = store.useStateSetter('viewportElement'); diff --git a/packages/react/src/drawer/backdrop/DrawerBackdrop.tsx b/packages/react/src/drawer/backdrop/DrawerBackdrop.tsx index f3fb016aa0f..38d227935ad 100644 --- a/packages/react/src/drawer/backdrop/DrawerBackdrop.tsx +++ b/packages/react/src/drawer/backdrop/DrawerBackdrop.tsx @@ -1,6 +1,11 @@ 'use client'; import * as React from 'react'; -import { useDialogRootContext } from '../../dialog/root/DialogRootContext'; +import { + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, + DialogTransitionStatusContext, +} from '../../dialog/root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import { type TransitionStatus } from '../../internals/useTransitionStatus'; import { type BaseUIComponentProps } from '../../internals/types'; @@ -22,10 +27,10 @@ export const DrawerBackdrop = React.forwardRef(function DrawerBackdrop( const store = useDialogRootContext(); - const open = store.useState('open'); + const open = React.useContext(DialogOpenContext); const nested = store.useState('nested'); - const mounted = store.useState('mounted'); - const transitionStatus = store.useState('transitionStatus'); + const mounted = React.useContext(DialogMountedContext); + const transitionStatus = React.useContext(DialogTransitionStatusContext); const state: DrawerBackdropState = { open, diff --git a/packages/react/src/drawer/popup/DrawerPopup.tsx b/packages/react/src/drawer/popup/DrawerPopup.tsx index 9f706f94a68..cb2a3327bb4 100644 --- a/packages/react/src/drawer/popup/DrawerPopup.tsx +++ b/packages/react/src/drawer/popup/DrawerPopup.tsx @@ -7,7 +7,12 @@ import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { EMPTY_OBJECT } from '@base-ui/utils/empty'; import { FloatingFocusManager } from '../../floating-ui-react'; -import { useDialogRootContext } from '../../dialog/root/DialogRootContext'; +import { + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, + DialogTransitionStatusContext, +} from '../../dialog/root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { BaseUIComponentProps } from '../../internals/types'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; @@ -137,11 +142,11 @@ export const DrawerPopup = React.forwardRef(function DrawerPopup( const floatingRootContext = store.useState('floatingRootContext'); const rootPopupProps = store.useState('popupProps'); const modal = store.useState('modal'); - const mounted = store.useState('mounted'); + const mounted = React.useContext(DialogMountedContext); const nested = store.useState('nested'); const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount'); - const transitionStatus = store.useState('transitionStatus'); - const open = store.useState('open'); + const transitionStatus = React.useContext(DialogTransitionStatusContext); + const open = React.useContext(DialogOpenContext); const openMethod = store.useState('openMethod'); const titleElementId = store.useState('titleElementId'); const role = store.useState('role'); diff --git a/packages/react/src/drawer/root/DrawerRoot.tsx b/packages/react/src/drawer/root/DrawerRoot.tsx index f2f7afced1b..7660f7187c2 100644 --- a/packages/react/src/drawer/root/DrawerRoot.tsx +++ b/packages/react/src/drawer/root/DrawerRoot.tsx @@ -18,7 +18,7 @@ import { type BaseUIChangeEventDetails, } from '../../internals/createBaseUIEventDetails'; import { REASONS } from '../../internals/reasons'; -import { useDialogRootContext } from '../../dialog/root/DialogRootContext'; +import { useDialogRootContext, DialogOpenContext } from '../../dialog/root/DialogRootContext'; import { useRenderDialogRoot } from '../../dialog/root/useRenderDialogRoot'; import { useDrawerProviderContext } from '../provider/DrawerProviderContext'; import type { DrawerHandle } from '../handle'; @@ -422,7 +422,7 @@ function DrawerProviderReporter() { const setDrawerOpen = providerContext?.setDrawerOpen; const removeDrawer = providerContext?.removeDrawer; - const open = store.useState('open'); + const open = React.useContext(DialogOpenContext); const nestedOpenDialogCount = store.useState('nestedOpenDialogCount'); const popupElement = store.useState('popupElement'); diff --git a/packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx b/packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx index 758e0ad720b..67b74634628 100644 --- a/packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx +++ b/packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { ownerDocument } from '@base-ui/utils/owner'; -import { useDialogRootContext } from '../../dialog/root/DialogRootContext'; +import { useDialogRootContext, DialogOpenContext } from '../../dialog/root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { BaseUIComponentProps } from '../../internals/types'; import type { StateAttributesMapping } from '../../internals/getStateAttributesProps'; @@ -116,7 +116,7 @@ export const DrawerSwipeArea = React.forwardRef(function DrawerSwipeArea( return () => registerTrigger(null); }, [registerTrigger, swipeAreaId, store]); - const open = store.useState('open'); + const open = React.useContext(DialogOpenContext); const resetDragDelta = useStableCallback(() => { dragDeltaRef.current.x = 0; diff --git a/packages/react/src/drawer/viewport/DrawerViewport.tsx b/packages/react/src/drawer/viewport/DrawerViewport.tsx index 49b263830f0..bedb255cf51 100644 --- a/packages/react/src/drawer/viewport/DrawerViewport.tsx +++ b/packages/react/src/drawer/viewport/DrawerViewport.tsx @@ -8,7 +8,11 @@ import { useAnimationFrame } from '@base-ui/utils/useAnimationFrame'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { clamp } from '@base-ui/utils/clamp'; -import { useDialogRootContext } from '../../dialog/root/DialogRootContext'; +import { + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, +} from '../../dialog/root/DialogRootContext'; import { DialogViewport } from '../../dialog/viewport/DialogViewport'; import { mergeProps } from '../../merge-props'; import { useDrawerRootContext } from '../root/DrawerRootContext'; @@ -102,8 +106,8 @@ export const DrawerViewport = React.forwardRef(function DrawerViewport( popupHeight, } = useDrawerSnapPoints(); - const open = store.useState('open'); - const mounted = store.useState('mounted'); + const open = React.useContext(DialogOpenContext); + const mounted = React.useContext(DialogMountedContext); const nested = store.useState('nested'); const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount'); const viewportElement = store.useState('viewportElement'); diff --git a/packages/react/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsx b/packages/react/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsx index a7a712c3738..e90b650d960 100644 --- a/packages/react/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsx +++ b/packages/react/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsx @@ -7,7 +7,11 @@ import { useAnimationFrame } from '@base-ui/utils/useAnimationFrame'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { clamp } from '@base-ui/utils/clamp'; import { useTimeout } from '@base-ui/utils/useTimeout'; -import { useDialogRootContext } from '../../dialog/root/DialogRootContext'; +import { + useDialogRootContext, + DialogOpenContext, + DialogMountedContext, +} from '../../dialog/root/DialogRootContext'; import { activeElement, contains, @@ -87,8 +91,8 @@ export function DrawerVirtualKeyboardProvider(props: DrawerVirtualKeyboardProvid const store = useDialogRootContext(); - const open = store.useState('open'); - const mounted = store.useState('mounted'); + const open = React.useContext(DialogOpenContext); + const mounted = React.useContext(DialogMountedContext); const modal = store.useState('modal'); const nestedOpenDialogCount = store.useState('nestedOpenDialogCount'); const viewportElement = store.useState('viewportElement'); diff --git a/packages/react/src/menu/arrow/MenuArrow.tsx b/packages/react/src/menu/arrow/MenuArrow.tsx index 66d102a3ce6..ac2c13f0e48 100644 --- a/packages/react/src/menu/arrow/MenuArrow.tsx +++ b/packages/react/src/menu/arrow/MenuArrow.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { useMenuPositionerContext } from '../positioner/MenuPositionerContext'; -import { useMenuRootContext } from '../root/MenuRootContext'; +import { useMenuRootContext, MenuOpenContext } from '../root/MenuRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; @@ -19,9 +19,9 @@ export const MenuArrow = React.forwardRef(function MenuArrow( ) { const { render, className, style, ...elementProps } = componentProps; - const { store } = useMenuRootContext(); + useMenuRootContext(); const { arrowRef, side, align, arrowUncentered, arrowStyles } = useMenuPositionerContext(); - const open = store.useState('open'); + const open = React.useContext(MenuOpenContext); const state: MenuArrowState = { open, diff --git a/packages/react/src/menu/backdrop/MenuBackdrop.tsx b/packages/react/src/menu/backdrop/MenuBackdrop.tsx index a9285ad538c..8968f434bd8 100644 --- a/packages/react/src/menu/backdrop/MenuBackdrop.tsx +++ b/packages/react/src/menu/backdrop/MenuBackdrop.tsx @@ -1,6 +1,11 @@ 'use client'; import * as React from 'react'; -import { useMenuRootContext } from '../root/MenuRootContext'; +import { + useMenuRootContext, + MenuOpenContext, + MenuMountedContext, + MenuTransitionStatusContext, +} from '../root/MenuRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { BaseUIComponentProps } from '../../internals/types'; import { popupTransitionStateMapping } from '../../utils/popupStateMapping'; @@ -21,9 +26,9 @@ export const MenuBackdrop = React.forwardRef(function MenuBackdrop( const { render, className, style, ...elementProps } = componentProps; const { store } = useMenuRootContext(); - const open = store.useState('open'); - const mounted = store.useState('mounted'); - const transitionStatus = store.useState('transitionStatus'); + const open = React.useContext(MenuOpenContext); + const mounted = React.useContext(MenuMountedContext); + const transitionStatus = React.useContext(MenuTransitionStatusContext); const lastOpenChangeReason = store.useState('lastOpenChangeReason'); const contextMenuContext = useContextMenuRootContext(); diff --git a/packages/react/src/menu/popup/MenuPopup.tsx b/packages/react/src/menu/popup/MenuPopup.tsx index d506799080b..f6e70c972f6 100644 --- a/packages/react/src/menu/popup/MenuPopup.tsx +++ b/packages/react/src/menu/popup/MenuPopup.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import type { InteractionType } from '@base-ui/utils/useEnhancedClickHandler'; import { FloatingFocusManager, useHoverFloatingInteraction } from '../../floating-ui-react'; -import { useMenuRootContext } from '../root/MenuRootContext'; +import { + useMenuRootContext, + MenuOpenContext, + MenuMountedContext, + MenuTransitionStatusContext, +} from '../root/MenuRootContext'; import type { MenuRoot } from '../root/MenuRoot'; import { useMenuPositionerContext } from '../positioner/MenuPositionerContext'; import { useRenderElement } from '../../internals/useRenderElement'; @@ -33,10 +38,10 @@ export const MenuPopup = React.forwardRef(function MenuPopup( const { side, align } = useMenuPositionerContext(); const insideToolbar = useToolbarRootContext(true) != null; - const open = store.useState('open'); - const transitionStatus = store.useState('transitionStatus'); + const open = React.useContext(MenuOpenContext); + const transitionStatus = React.useContext(MenuTransitionStatusContext); const popupProps = store.useState('popupProps'); - const mounted = store.useState('mounted'); + const mounted = React.useContext(MenuMountedContext); const instantType = store.useState('instantType'); const activeTriggerElement = store.useState('activeTriggerElement'); const parent = store.useState('parent'); diff --git a/packages/react/src/menu/portal/MenuPortal.tsx b/packages/react/src/menu/portal/MenuPortal.tsx index ed3e1a01a62..c88c53d7614 100644 --- a/packages/react/src/menu/portal/MenuPortal.tsx +++ b/packages/react/src/menu/portal/MenuPortal.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { useMenuRootContext } from '../root/MenuRootContext'; +import { useMenuRootContext, MenuMountedContext } from '../root/MenuRootContext'; import { MenuPortalContext } from './MenuPortalContext'; /** @@ -18,8 +18,8 @@ export const MenuPortal = React.forwardRef(function MenuPortal( ) { const { keepMounted = false, ...portalProps } = props; - const { store, parent } = useMenuRootContext(); - const mounted = store.useState('mounted'); + const { parent } = useMenuRootContext(); + const mounted = React.useContext(MenuMountedContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/menu/positioner/MenuPositioner.tsx b/packages/react/src/menu/positioner/MenuPositioner.tsx index 5944f2de1e1..4cdbe780625 100644 --- a/packages/react/src/menu/positioner/MenuPositioner.tsx +++ b/packages/react/src/menu/positioner/MenuPositioner.tsx @@ -5,7 +5,12 @@ import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useTimeout } from '@base-ui/utils/useTimeout'; import { FloatingNode } from '../../floating-ui-react'; import { MenuPositionerContext } from './MenuPositionerContext'; -import { useMenuRootContext } from '../root/MenuRootContext'; +import { + useMenuRootContext, + MenuOpenContext, + MenuMountedContext, + MenuTransitionStatusContext, +} from '../root/MenuRootContext'; import type { MenuRoot } from '../root/MenuRoot'; import { useAnchorPositioning, @@ -63,12 +68,12 @@ export const MenuPositioner = React.forwardRef(function MenuPositioner( const parent = store.useState('parent'); const floatingRootContext = store.useState('floatingRootContext'); const floatingTreeRoot = store.useState('floatingTreeRoot'); - const mounted = store.useState('mounted'); - const open = store.useState('open'); + const mounted = React.useContext(MenuMountedContext); + const open = React.useContext(MenuOpenContext); const modal = store.useState('modal'); const openMethod = store.useState('openMethod'); const triggerElement = store.useState('activeTriggerElement'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(MenuTransitionStatusContext); const positionerElement = store.useState('positionerElement'); const instantType = store.useState('instantType'); const adaptiveOrigin = store.useState('adaptiveOrigin'); diff --git a/packages/react/src/menu/root/MenuRoot.tsx b/packages/react/src/menu/root/MenuRoot.tsx index 1289391b161..8c76639e272 100644 --- a/packages/react/src/menu/root/MenuRoot.tsx +++ b/packages/react/src/menu/root/MenuRoot.tsx @@ -16,7 +16,13 @@ import { useTypeahead, useSyncedFloatingRootContext, } from '../../floating-ui-react'; -import { MenuRootContext, useMenuRootContext } from './MenuRootContext'; +import { + MenuRootContext, + useMenuRootContext, + MenuOpenContext, + MenuMountedContext, + MenuTransitionStatusContext, +} from './MenuRootContext'; import { MenubarContext, useMenubarContext } from '../../menubar/MenubarContext'; import { TYPEAHEAD_RESET_MS } from '../../internals/constants'; import { useDirection } from '../../internals/direction-context/DirectionContext'; @@ -154,7 +160,8 @@ export const MenuRoot = fastComponent(function MenuRoot(props: MenuRoot const floatingTreeRoot = store.useState('floatingTreeRoot'); const floatingNodeIdFromContext = useFloatingNodeId(floatingTreeRoot); - const open = store.useState('open'); + const storeOpen = store.useState('open'); + const open = openProp ?? storeOpen; const activeTriggerElement = store.useState('activeTriggerElement'); const positionerElement = store.useState('positionerElement'); const hoverEnabled = store.useState('hoverEnabled'); @@ -193,7 +200,7 @@ export const MenuRoot = fastComponent(function MenuRoot(props: MenuRoot }); useImplicitActiveTrigger(store); - const { forceUnmount, transitionStatus } = useOpenStateTransitions( + const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions( open, store, () => { @@ -632,8 +639,14 @@ export const MenuRoot = fastComponent(function MenuRoot(props: MenuRoot const content = ( - {handle && } - {typeof children === 'function' ? children({ payload }) : children} + + + + {handle && } + {typeof children === 'function' ? children({ payload }) : children} + + + ); diff --git a/packages/react/src/menu/root/MenuRootContext.ts b/packages/react/src/menu/root/MenuRootContext.ts index e3430e78cbe..b66c2f49f15 100644 --- a/packages/react/src/menu/root/MenuRootContext.ts +++ b/packages/react/src/menu/root/MenuRootContext.ts @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import type { TransitionStatus } from '../../internals/useTransitionStatus'; import { type MenuStore } from '../store/MenuStore'; import { MenuParent } from './MenuRoot'; @@ -22,3 +23,8 @@ export function useMenuRootContext(optional?: boolean) { return context; } + +// Separate contexts preserve subscriptions to individual lifecycle values. +export const MenuOpenContext = React.createContext(false); +export const MenuMountedContext = React.createContext(false); +export const MenuTransitionStatusContext = React.createContext(undefined); diff --git a/packages/react/src/popover/arrow/PopoverArrow.tsx b/packages/react/src/popover/arrow/PopoverArrow.tsx index 32a3e3adda7..29f701825c7 100644 --- a/packages/react/src/popover/arrow/PopoverArrow.tsx +++ b/packages/react/src/popover/arrow/PopoverArrow.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { usePopoverPositionerContext } from '../positioner/PopoverPositionerContext'; -import { usePopoverRootContext } from '../root/PopoverRootContext'; +import { usePopoverRootContext, PopoverOpenContext } from '../root/PopoverRootContext'; import type { Align, Side } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; import { popupStateMapping } from '../../utils/popupStateMapping'; @@ -19,8 +19,8 @@ export const PopoverArrow = React.forwardRef(function PopoverArrow( ) { const { render, className, style, ...elementProps } = componentProps; - const store = usePopoverRootContext(); - const open = store.useState('open'); + usePopoverRootContext(); + const open = React.useContext(PopoverOpenContext); const { arrowRef, side, align, arrowUncentered, arrowStyles } = usePopoverPositionerContext(); const state: PopoverArrowState = { diff --git a/packages/react/src/popover/backdrop/PopoverBackdrop.tsx b/packages/react/src/popover/backdrop/PopoverBackdrop.tsx index 5fe2b718485..637a35db953 100644 --- a/packages/react/src/popover/backdrop/PopoverBackdrop.tsx +++ b/packages/react/src/popover/backdrop/PopoverBackdrop.tsx @@ -1,6 +1,11 @@ 'use client'; import * as React from 'react'; -import { usePopoverRootContext } from '../root/PopoverRootContext'; +import { + usePopoverRootContext, + PopoverOpenContext, + PopoverMountedContext, + PopoverTransitionStatusContext, +} from '../root/PopoverRootContext'; import type { BaseUIComponentProps } from '../../internals/types'; import { popupTransitionStateMapping } from '../../utils/popupStateMapping'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; @@ -21,9 +26,9 @@ export const PopoverBackdrop = React.forwardRef(function PopoverBackdrop( const store = usePopoverRootContext(); - const open = store.useState('open'); - const mounted = store.useState('mounted'); - const transitionStatus = store.useState('transitionStatus'); + const open = React.useContext(PopoverOpenContext); + const mounted = React.useContext(PopoverMountedContext); + const transitionStatus = React.useContext(PopoverTransitionStatusContext); const openReason = store.useState('openChangeReason'); const state: PopoverBackdropState = { diff --git a/packages/react/src/popover/popup/PopoverPopup.tsx b/packages/react/src/popover/popup/PopoverPopup.tsx index c89c3dc64a5..15071a65740 100644 --- a/packages/react/src/popover/popup/PopoverPopup.tsx +++ b/packages/react/src/popover/popup/PopoverPopup.tsx @@ -3,7 +3,12 @@ import * as React from 'react'; import { InteractionType } from '@base-ui/utils/useEnhancedClickHandler'; import { isHTMLElement } from '@floating-ui/utils/dom'; import { FloatingFocusManager, useHoverFloatingInteraction } from '../../floating-ui-react'; -import { usePopoverRootContext } from '../root/PopoverRootContext'; +import { + usePopoverRootContext, + PopoverOpenContext, + PopoverMountedContext, + PopoverTransitionStatusContext, +} from '../root/PopoverRootContext'; import { usePopoverPositionerContext } from '../positioner/PopoverPositionerContext'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; @@ -36,15 +41,15 @@ export const PopoverPopup = React.forwardRef(function PopoverPopup( const insideToolbar = useToolbarRootContext(true) != null; const { context: closePartContext, hasClosePart } = useClosePartCount(); - const open = store.useState('open'); + const open = React.useContext(PopoverOpenContext); const openMethod = store.useState('openMethod'); const instantType = store.useState('instantType'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(PopoverTransitionStatusContext); const popupProps = store.useState('popupProps'); const titleId = store.useState('titleElementId'); const descriptionId = store.useState('descriptionElementId'); const modal = store.useState('modal'); - const mounted = store.useState('mounted'); + const mounted = React.useContext(PopoverMountedContext); const openReason = store.useState('openChangeReason'); const activeTriggerElement = store.useState('activeTriggerElement'); const floatingContext = store.useState('floatingRootContext'); diff --git a/packages/react/src/popover/portal/PopoverPortal.tsx b/packages/react/src/popover/portal/PopoverPortal.tsx index 91d1b193ced..05c069bdf4e 100644 --- a/packages/react/src/popover/portal/PopoverPortal.tsx +++ b/packages/react/src/popover/portal/PopoverPortal.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { usePopoverRootContext } from '../root/PopoverRootContext'; +import { usePopoverRootContext, PopoverMountedContext } from '../root/PopoverRootContext'; import { PopoverPortalContext } from './PopoverPortalContext'; /** @@ -18,8 +18,8 @@ export const PopoverPortal = React.forwardRef(function PopoverPortal( ) { const { keepMounted = false, ...portalProps } = props; - const store = usePopoverRootContext(); - const mounted = store.useState('mounted'); + usePopoverRootContext(); + const mounted = React.useContext(PopoverMountedContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/popover/positioner/PopoverPositioner.tsx b/packages/react/src/popover/positioner/PopoverPositioner.tsx index e2fa272effc..5f4b1b3a4ec 100644 --- a/packages/react/src/popover/positioner/PopoverPositioner.tsx +++ b/packages/react/src/popover/positioner/PopoverPositioner.tsx @@ -3,7 +3,12 @@ import * as React from 'react'; import { inertValue } from '@base-ui/utils/inertValue'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { FloatingNode, useFloatingNodeId } from '../../floating-ui-react'; -import { usePopoverRootContext } from '../root/PopoverRootContext'; +import { + usePopoverRootContext, + PopoverOpenContext, + PopoverMountedContext, + PopoverTransitionStatusContext, +} from '../root/PopoverRootContext'; import { PopoverPositionerContext } from './PopoverPositionerContext'; import { useAnchorPositioning, @@ -56,15 +61,15 @@ export const PopoverPositioner = React.forwardRef(function PopoverPositioner( const nodeId = useFloatingNodeId(); const floatingRootContext = store.useState('floatingRootContext'); - const mounted = store.useState('mounted'); - const open = store.useState('open'); + const mounted = React.useContext(PopoverMountedContext); + const open = React.useContext(PopoverOpenContext); const openReason = store.useState('openChangeReason'); const triggerElement = store.useState('activeTriggerElement'); const modal = store.useState('modal'); const openMethod = store.useState('openMethod'); const positionerElement = store.useState('positionerElement'); const instantType = store.useState('instantType'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(PopoverTransitionStatusContext); const adaptiveOrigin = store.useState('adaptiveOrigin'); const prevTriggerElementRef = React.useRef(null); diff --git a/packages/react/src/popover/root/PopoverRoot.test.tsx b/packages/react/src/popover/root/PopoverRoot.test.tsx index e6b1f9285c3..14045c5e067 100644 --- a/packages/react/src/popover/root/PopoverRoot.test.tsx +++ b/packages/react/src/popover/root/PopoverRoot.test.tsx @@ -17,6 +17,78 @@ describe('', () => { const { render, clock } = createRenderer(); + it('renders the open state without hidden in the opening commit', async () => { + const openingCommits: Array<{ hidden: boolean; open: boolean }> = []; + + function recordOpeningCommit(element: HTMLDivElement | null) { + if (element) { + openingCommits.push({ + hidden: element.hasAttribute('hidden'), + open: element.hasAttribute('data-open'), + }); + } + } + + function App({ open }: { open: boolean }) { + return ( + + Open + + {open && ( + + Content + + )} + + + ); + } + + const { rerender } = await render(); + await rerender(); + + expect(openingCommits[0]).toEqual({ hidden: false, open: true }); + }); + + it('keeps the trigger tabbable when the popup is conditionally removed on close', async () => { + const onOpenChangeComplete = vi.fn(); + + function App() { + const [open, setOpen] = React.useState(false); + return ( + + + + Open + + {open && ( + + + Close + + + )} + + + + ); + } + + const { user } = await render(); + const trigger = screen.getByRole('button', { name: 'Open' }); + await user.click(trigger); + await user.click(screen.getByRole('button', { name: 'Close' })); + await user.click(screen.getByRole('button', { name: 'Before' })); + await user.tab(); + + expect(trigger).toHaveFocus(); + expect(onOpenChangeComplete).toHaveBeenCalledWith(false); + }); + popupConformanceTests({ createComponent: (props) => ( diff --git a/packages/react/src/popover/root/PopoverRoot.tsx b/packages/react/src/popover/root/PopoverRoot.tsx index 9e258bf8d8a..2cb90850887 100644 --- a/packages/react/src/popover/root/PopoverRoot.tsx +++ b/packages/react/src/popover/root/PopoverRoot.tsx @@ -2,7 +2,13 @@ import * as React from 'react'; import { fastComponent } from '@base-ui/utils/fastHooks'; import { useDismiss, FloatingTree } from '../../floating-ui-react'; -import { PopoverRootContext, usePopoverRootContext } from './PopoverRootContext'; +import { + PopoverRootContext, + usePopoverRootContext, + PopoverOpenContext, + PopoverMountedContext, + PopoverTransitionStatusContext, +} from './PopoverRootContext'; import { PopoverStore, type State as PopoverStoreState } from '../store/PopoverStore'; import { PopoverHandle } from '../store/PopoverHandle'; import { @@ -48,8 +54,10 @@ const PopoverRootComponent = fastComponent(function PopoverRootComponent { + const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions(open, store, () => { store.update({ stickIfOpen: true, openChangeReason: null }); }); @@ -84,9 +92,15 @@ const PopoverRootComponent = fastComponent(function PopoverRootComponent}> - {handle && } - {shouldRenderInteractions && } - {typeof children === 'function' ? children({ payload }) : children} + + + + {handle && } + {shouldRenderInteractions && } + {typeof children === 'function' ? children({ payload }) : children} + + + ); }); diff --git a/packages/react/src/popover/root/PopoverRootContext.ts b/packages/react/src/popover/root/PopoverRootContext.ts index 4af44de829b..bd1dabb7350 100644 --- a/packages/react/src/popover/root/PopoverRootContext.ts +++ b/packages/react/src/popover/root/PopoverRootContext.ts @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import type { TransitionStatus } from '../../internals/useTransitionStatus'; import type { PopoverStore } from '../store/PopoverStore'; export type PopoverRootContext = PopoverStore; @@ -17,3 +18,9 @@ export function usePopoverRootContext(optional?: boolean) { } return context; } + +// Separate contexts keep consumers of `mounted` from rerendering when only +// `transitionStatus` changes. +export const PopoverOpenContext = React.createContext(false); +export const PopoverMountedContext = React.createContext(false); +export const PopoverTransitionStatusContext = React.createContext(undefined); diff --git a/packages/react/src/preview-card/arrow/PreviewCardArrow.tsx b/packages/react/src/preview-card/arrow/PreviewCardArrow.tsx index 1597dc5a549..869dfc9edaa 100644 --- a/packages/react/src/preview-card/arrow/PreviewCardArrow.tsx +++ b/packages/react/src/preview-card/arrow/PreviewCardArrow.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { usePreviewCardPositionerContext } from '../positioner/PreviewCardPositionerContext'; -import { usePreviewCardRootContext } from '../root/PreviewCardContext'; +import { usePreviewCardRootContext, PreviewCardOpenContext } from '../root/PreviewCardContext'; import type { BaseUIComponentProps } from '../../internals/types'; import type { Align, Side } from '../../internals/useAnchorPositioning'; import { popupStateMapping } from '../../utils/popupStateMapping'; @@ -19,10 +19,10 @@ export const PreviewCardArrow = React.forwardRef(function PreviewCardArrow( ) { const { render, className, style, ...elementProps } = componentProps; - const store = usePreviewCardRootContext(); + usePreviewCardRootContext(); const { arrowRef, side, align, arrowUncentered, arrowStyles } = usePreviewCardPositionerContext(); - const open = store.useState('open'); + const open = React.useContext(PreviewCardOpenContext); const state: PreviewCardArrowState = { open, diff --git a/packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx b/packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx index 371d2477c71..0ef1880fd32 100644 --- a/packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx +++ b/packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx @@ -1,6 +1,11 @@ 'use client'; import * as React from 'react'; -import { usePreviewCardRootContext } from '../root/PreviewCardContext'; +import { + usePreviewCardRootContext, + PreviewCardOpenContext, + PreviewCardMountedContext, + PreviewCardTransitionStatusContext, +} from '../root/PreviewCardContext'; import type { BaseUIComponentProps } from '../../internals/types'; import { popupTransitionStateMapping } from '../../utils/popupStateMapping'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; @@ -18,10 +23,10 @@ export const PreviewCardBackdrop = React.forwardRef(function PreviewCardBackdrop ) { const { render, className, style, ...elementProps } = componentProps; - const store = usePreviewCardRootContext(); - const open = store.useState('open'); - const mounted = store.useState('mounted'); - const transitionStatus = store.useState('transitionStatus'); + usePreviewCardRootContext(); + const open = React.useContext(PreviewCardOpenContext); + const mounted = React.useContext(PreviewCardMountedContext); + const transitionStatus = React.useContext(PreviewCardTransitionStatusContext); const state: PreviewCardBackdropState = { open, diff --git a/packages/react/src/preview-card/popup/PreviewCardPopup.tsx b/packages/react/src/preview-card/popup/PreviewCardPopup.tsx index 5a44b522986..e42316cbb97 100644 --- a/packages/react/src/preview-card/popup/PreviewCardPopup.tsx +++ b/packages/react/src/preview-card/popup/PreviewCardPopup.tsx @@ -1,6 +1,10 @@ 'use client'; import * as React from 'react'; -import { usePreviewCardRootContext } from '../root/PreviewCardContext'; +import { + usePreviewCardRootContext, + PreviewCardOpenContext, + PreviewCardTransitionStatusContext, +} from '../root/PreviewCardContext'; import { usePreviewCardPositionerContext } from '../positioner/PreviewCardPositionerContext'; import type { Align, Side } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; @@ -27,9 +31,9 @@ export const PreviewCardPopup = React.forwardRef(function PreviewCardPopup( const store = usePreviewCardRootContext(); const { side, align } = usePreviewCardPositionerContext(); - const open = store.useState('open'); + const open = React.useContext(PreviewCardOpenContext); const instantType = store.useState('instantType'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(PreviewCardTransitionStatusContext); const popupProps = store.useState('popupProps'); const floatingContext = store.useState('floatingRootContext'); const closeDelay = store.useState('closeDelay'); diff --git a/packages/react/src/preview-card/portal/PreviewCardPortal.tsx b/packages/react/src/preview-card/portal/PreviewCardPortal.tsx index 7d26458faa4..c92de4d4199 100644 --- a/packages/react/src/preview-card/portal/PreviewCardPortal.tsx +++ b/packages/react/src/preview-card/portal/PreviewCardPortal.tsx @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { usePreviewCardRootContext } from '../root/PreviewCardContext'; +import { usePreviewCardRootContext, PreviewCardMountedContext } from '../root/PreviewCardContext'; import { PreviewCardPortalContext } from './PreviewCardPortalContext'; import { FloatingPortalLite } from '../../utils/FloatingPortalLite'; import { type BaseUIComponentProps } from '../../internals/types'; @@ -18,8 +18,8 @@ export const PreviewCardPortal = React.forwardRef(function PreviewCardPortal( ) { const { keepMounted = false, ...portalProps } = props; - const store = usePreviewCardRootContext(); - const mounted = store.useState('mounted'); + usePreviewCardRootContext(); + const mounted = React.useContext(PreviewCardMountedContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx b/packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx index 812e0a45ab4..700fd048dda 100644 --- a/packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx +++ b/packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx @@ -1,7 +1,12 @@ 'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; -import { usePreviewCardRootContext } from '../root/PreviewCardContext'; +import { + usePreviewCardRootContext, + PreviewCardOpenContext, + PreviewCardMountedContext, + PreviewCardTransitionStatusContext, +} from '../root/PreviewCardContext'; import { PreviewCardPositionerContext } from './PreviewCardPositionerContext'; import { FloatingNode, useFloatingNodeId } from '../../floating-ui-react'; import { @@ -49,11 +54,11 @@ export const PreviewCardPositioner = React.forwardRef(function PreviewCardPositi const keepMounted = usePreviewCardPortalContext(); const nodeId = useFloatingNodeId(); - const open = store.useState('open'); - const mounted = store.useState('mounted'); + const open = React.useContext(PreviewCardOpenContext); + const mounted = React.useContext(PreviewCardMountedContext); const floatingRootContext = store.useState('floatingRootContext'); const instantType = store.useState('instantType'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(PreviewCardTransitionStatusContext); const adaptiveOrigin = store.useState('adaptiveOrigin'); const inlineRectCoordsRef = store.context.inlineRectCoordsRef; diff --git a/packages/react/src/preview-card/root/PreviewCardContext.ts b/packages/react/src/preview-card/root/PreviewCardContext.ts index 370901fd81a..806af5b22c9 100644 --- a/packages/react/src/preview-card/root/PreviewCardContext.ts +++ b/packages/react/src/preview-card/root/PreviewCardContext.ts @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import type { TransitionStatus } from '../../internals/useTransitionStatus'; import { PreviewCardStore } from '../store/PreviewCardStore'; export type PreviewCardRootContext = PreviewCardStore; @@ -20,3 +21,8 @@ export function usePreviewCardRootContext(optional?: boolean) { return context; } + +// Separate contexts preserve subscriptions to individual lifecycle values. +export const PreviewCardOpenContext = React.createContext(false); +export const PreviewCardMountedContext = React.createContext(false); +export const PreviewCardTransitionStatusContext = React.createContext(undefined); diff --git a/packages/react/src/preview-card/root/PreviewCardRoot.tsx b/packages/react/src/preview-card/root/PreviewCardRoot.tsx index 7a9b79428bc..46b68e2f7d9 100644 --- a/packages/react/src/preview-card/root/PreviewCardRoot.tsx +++ b/packages/react/src/preview-card/root/PreviewCardRoot.tsx @@ -3,7 +3,13 @@ import * as React from 'react'; import { fastComponent } from '@base-ui/utils/fastHooks'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useDismiss, FloatingTree } from '../../floating-ui-react'; -import { PreviewCardRootContext, usePreviewCardRootContext } from './PreviewCardContext'; +import { + PreviewCardRootContext, + usePreviewCardRootContext, + PreviewCardOpenContext, + PreviewCardMountedContext, + PreviewCardTransitionStatusContext, +} from './PreviewCardContext'; import { createChangeEventDetails, type BaseUIChangeEventDetails, @@ -53,13 +59,13 @@ function PreviewCardRootComponent(props: PreviewCardRoot.Props store.useContextCallback('onOpenChange', onOpenChange); store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete); - const open = store.useState('open'); + const storeOpen = store.useState('open'); + const open = openProp ?? storeOpen; const activeTriggerId = store.useState('activeTriggerId'); - const mounted = store.useState('mounted'); const payload = store.useState('payload') as Payload | undefined; useImplicitActiveTrigger(store, { closeOnActiveTriggerUnmount: true }); - const { forceUnmount } = useOpenStateTransitions(open, store, () => { + const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions(open, store, () => { store.context.inlineRectCoordsRef.current = undefined; }); @@ -84,9 +90,15 @@ function PreviewCardRootComponent(props: PreviewCardRoot.Props return ( - {handle && } - {shouldRenderInteractions && } - {typeof children === 'function' ? children({ payload }) : children} + + + + {handle && } + {shouldRenderInteractions && } + {typeof children === 'function' ? children({ payload }) : children} + + + ); } diff --git a/packages/react/src/select/arrow/SelectArrow.tsx b/packages/react/src/select/arrow/SelectArrow.tsx index e9a0f8572b3..8025919c94c 100644 --- a/packages/react/src/select/arrow/SelectArrow.tsx +++ b/packages/react/src/select/arrow/SelectArrow.tsx @@ -1,13 +1,11 @@ 'use client'; import * as React from 'react'; -import { useStore } from '@base-ui/utils/store'; import { useSelectPositionerContext } from '../positioner/SelectPositionerContext'; -import { useSelectRootContext } from '../root/SelectRootContext'; +import { useSelectRootContext, SelectOpenContext } from '../root/SelectRootContext'; import type { BaseUIComponentProps } from '../../internals/types'; import type { Align, Side } from '../../internals/useAnchorPositioning'; import { popupTransitionStateMapping } from '../../utils/popupStateMapping'; import { useRenderElement } from '../../internals/useRenderElement'; -import { selectors } from '../store'; /** * Displays an element positioned against the select popup anchor. @@ -21,11 +19,11 @@ export const SelectArrow = React.forwardRef(function SelectArrow( ) { const { render, className, style, ...elementProps } = componentProps; - const { store } = useSelectRootContext(); + useSelectRootContext(); const { side, align, arrowRef, arrowStyles, arrowUncentered, alignItemWithTriggerActive } = useSelectPositionerContext(); - const open = useStore(store, selectors.open); + const open = React.useContext(SelectOpenContext); const state: SelectArrowState = { open, diff --git a/packages/react/src/select/backdrop/SelectBackdrop.tsx b/packages/react/src/select/backdrop/SelectBackdrop.tsx index cf5e594799f..3314203898d 100644 --- a/packages/react/src/select/backdrop/SelectBackdrop.tsx +++ b/packages/react/src/select/backdrop/SelectBackdrop.tsx @@ -1,14 +1,17 @@ 'use client'; import * as React from 'react'; -import { useStore } from '@base-ui/utils/store'; import type { BaseUIComponentProps } from '../../internals/types'; -import { useSelectRootContext } from '../root/SelectRootContext'; +import { + useSelectRootContext, + SelectOpenContext, + SelectMountedContext, + SelectTransitionStatusContext, +} from '../root/SelectRootContext'; import { popupStateMapping } from '../../utils/popupStateMapping'; import type { StateAttributesMapping } from '../../internals/getStateAttributesProps'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; import { transitionStatusMapping } from '../../internals/stateAttributesMapping'; import { useRenderElement } from '../../internals/useRenderElement'; -import { selectors } from '../store'; const stateAttributesMapping: StateAttributesMapping = { ...popupStateMapping, @@ -27,11 +30,11 @@ export const SelectBackdrop = React.forwardRef(function SelectBackdrop( ) { const { render, className, style, ...elementProps } = componentProps; - const { store } = useSelectRootContext(); + useSelectRootContext(); - const open = useStore(store, selectors.open); - const mounted = useStore(store, selectors.mounted); - const transitionStatus = useStore(store, selectors.transitionStatus); + const open = React.useContext(SelectOpenContext); + const mounted = React.useContext(SelectMountedContext); + const transitionStatus = React.useContext(SelectTransitionStatusContext); const state: SelectBackdropState = { open, diff --git a/packages/react/src/select/popup/SelectPopup.tsx b/packages/react/src/select/popup/SelectPopup.tsx index ad8048aa214..0f681f29613 100644 --- a/packages/react/src/select/popup/SelectPopup.tsx +++ b/packages/react/src/select/popup/SelectPopup.tsx @@ -13,7 +13,12 @@ import { clamp } from '@base-ui/utils/clamp'; import { FloatingFocusManager, platform as floatingPlatform } from '../../floating-ui-react'; import type { ClientRectObject } from '../../floating-ui-react'; import type { BaseUIComponentProps, HTMLProps } from '../../internals/types'; -import { useSelectRootContext } from '../root/SelectRootContext'; +import { + useSelectRootContext, + SelectOpenContext, + SelectMountedContext, + SelectTransitionStatusContext, +} from '../root/SelectRootContext'; import { popupStateMapping } from '../../utils/popupStateMapping'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import type { StateAttributesMapping } from '../../internals/getStateAttributesProps'; @@ -79,11 +84,11 @@ export const SelectPopup = React.forwardRef(function SelectPopup( const { nonce, disableStyleElements } = useCSPContext(); const id = useStore(store, selectors.id); - const open = useStore(store, selectors.open); + const open = React.useContext(SelectOpenContext); const openMethod = useStore(store, selectors.openMethod); - const mounted = useStore(store, selectors.mounted); + const mounted = React.useContext(SelectMountedContext); const popupProps = useStore(store, selectors.popupProps); - const transitionStatus = useStore(store, selectors.transitionStatus); + const transitionStatus = React.useContext(SelectTransitionStatusContext); const triggerElement = useStore(store, selectors.triggerElement); const positionerElement = useStore(store, selectors.positionerElement); const listElement = useStore(store, selectors.listElement); diff --git a/packages/react/src/select/portal/SelectPortal.tsx b/packages/react/src/select/portal/SelectPortal.tsx index 92071f8bdc1..ec7d84ef994 100644 --- a/packages/react/src/select/portal/SelectPortal.tsx +++ b/packages/react/src/select/portal/SelectPortal.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useStore } from '@base-ui/utils/store'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { useSelectRootContext } from '../root/SelectRootContext'; +import { useSelectRootContext, SelectMountedContext } from '../root/SelectRootContext'; import { selectors } from '../store'; /** @@ -18,7 +18,7 @@ export const SelectPortal = React.forwardRef(function SelectPortal( forwardedRef: React.ForwardedRef, ) { const { store } = useSelectRootContext(); - const mounted = useStore(store, selectors.mounted); + const mounted = React.useContext(SelectMountedContext); const forceMount = useStore(store, selectors.forceMount); const shouldRender = mounted || forceMount; diff --git a/packages/react/src/select/positioner/SelectPositioner.tsx b/packages/react/src/select/positioner/SelectPositioner.tsx index de8dcc8b3f2..afc0a592498 100644 --- a/packages/react/src/select/positioner/SelectPositioner.tsx +++ b/packages/react/src/select/positioner/SelectPositioner.tsx @@ -4,7 +4,12 @@ import { inertValue } from '@base-ui/utils/inertValue'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { useStore } from '@base-ui/utils/store'; -import { useSelectRootContext } from '../root/SelectRootContext'; +import { + useSelectRootContext, + SelectOpenContext, + SelectMountedContext, + SelectTransitionStatusContext, +} from '../root/SelectRootContext'; import { CompositeList } from '../../internals/composite/list/CompositeList'; import type { BaseUIComponentProps } from '../../internals/types'; import { @@ -71,15 +76,15 @@ export const SelectPositioner = React.forwardRef(function SelectPositioner( floatingContext: floatingRootContext, } = useSelectRootContext(); - const open = useStore(store, selectors.open); - const mounted = useStore(store, selectors.mounted); + const open = React.useContext(SelectOpenContext); + const mounted = React.useContext(SelectMountedContext); const modal = useStore(store, selectors.modal); const value = useStore(store, selectors.value); const openMethod = useStore(store, selectors.openMethod); const positionerElement = useStore(store, selectors.positionerElement); const triggerElement = useStore(store, selectors.triggerElement); const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue); - const transitionStatus = useStore(store, selectors.transitionStatus); + const transitionStatus = React.useContext(SelectTransitionStatusContext); const scrollUpArrowRef = React.useRef(null); const scrollDownArrowRef = React.useRef(null); diff --git a/packages/react/src/select/root/SelectRoot.tsx b/packages/react/src/select/root/SelectRoot.tsx index 83dec017c66..944e1b8cadd 100644 --- a/packages/react/src/select/root/SelectRoot.tsx +++ b/packages/react/src/select/root/SelectRoot.tsx @@ -19,7 +19,12 @@ import { useListNavigation, useTypeahead, } from '../../floating-ui-react'; -import { SelectRootContext } from './SelectRootContext'; +import { + SelectRootContext, + SelectOpenContext, + SelectMountedContext, + SelectTransitionStatusContext, +} from './SelectRootContext'; import { useFieldRootContext } from '../../internals/field-root-context/FieldRootContext'; import { useRegisterFieldControl } from '../../internals/field-register-control/useRegisterFieldControl'; import { useLabelableId } from '../../internals/labelable-provider/useLabelableId'; @@ -30,7 +35,7 @@ import { createChangeEventDetails, } from '../../internals/createBaseUIEventDetails'; import { REASONS } from '../../internals/reasons'; -import { useOpenChangeComplete } from '../../internals/useOpenChangeComplete'; +import { usePopupCloseComplete } from '../../utils/popups/usePopupCloseComplete'; import { useFormContext } from '../../internals/form-context/FormContext'; import { type Group, stringifyAsLabel, stringifyAsValue } from '../../internals/resolveValueLabel'; import { @@ -282,15 +287,11 @@ export function SelectRoot( onOpenChangeComplete?.(false); }); - useOpenChangeComplete({ - enabled: !actionsRef, + usePopupCloseComplete({ + enabled: mounted && !actionsRef, open, ref: popupRef, - onComplete() { - if (!open) { - handleUnmount(); - } - }, + onComplete: handleUnmount, }); React.useImperativeHandle(actionsRef, () => ({ unmount: handleUnmount }), [handleUnmount]); @@ -502,7 +503,13 @@ export function SelectRoot( return ( - {children} + + + + {children} + + + (undefined); diff --git a/packages/react/src/tooltip/arrow/TooltipArrow.tsx b/packages/react/src/tooltip/arrow/TooltipArrow.tsx index 223ca42ad0d..cb728ad396b 100644 --- a/packages/react/src/tooltip/arrow/TooltipArrow.tsx +++ b/packages/react/src/tooltip/arrow/TooltipArrow.tsx @@ -5,7 +5,7 @@ import type { BaseUIComponentProps } from '../../internals/types'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import { popupStateMapping } from '../../utils/popupStateMapping'; import { useRenderElement } from '../../internals/useRenderElement'; -import { useTooltipRootContext } from '../root/TooltipRootContext'; +import { useTooltipRootContext, TooltipOpenContext } from '../root/TooltipRootContext'; /** * Displays an element positioned against the tooltip anchor. @@ -22,7 +22,7 @@ export const TooltipArrow = React.forwardRef(function TooltipArrow( const store = useTooltipRootContext(); const { arrowRef, side, align, arrowUncentered, arrowStyles } = useTooltipPositionerContext(); - const open = store.useState('open'); + const open = React.useContext(TooltipOpenContext); const instantType = store.useState('instantType'); const state: TooltipArrowState = { diff --git a/packages/react/src/tooltip/popup/TooltipPopup.tsx b/packages/react/src/tooltip/popup/TooltipPopup.tsx index b2397fb623d..c11df35cebe 100644 --- a/packages/react/src/tooltip/popup/TooltipPopup.tsx +++ b/packages/react/src/tooltip/popup/TooltipPopup.tsx @@ -1,6 +1,10 @@ 'use client'; import * as React from 'react'; -import { useTooltipRootContext } from '../root/TooltipRootContext'; +import { + useTooltipRootContext, + TooltipOpenContext, + TooltipTransitionStatusContext, +} from '../root/TooltipRootContext'; import { useTooltipPositionerContext } from '../positioner/TooltipPositionerContext'; import type { BaseUIComponentProps } from '../../internals/types'; import type { Align, Side } from '../../internals/useAnchorPositioning'; @@ -27,9 +31,9 @@ export const TooltipPopup = React.forwardRef(function TooltipPopup( const store = useTooltipRootContext(); const { side, align } = useTooltipPositionerContext(); - const open = store.useState('open'); + const open = React.useContext(TooltipOpenContext); const instantType = store.useState('instantType'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(TooltipTransitionStatusContext); const popupProps = store.useState('popupProps'); const floatingContext = store.useState('floatingRootContext'); const disabled = store.useState('disabled'); diff --git a/packages/react/src/tooltip/portal/TooltipPortal.tsx b/packages/react/src/tooltip/portal/TooltipPortal.tsx index 6efacfbfb13..e8ce9ac7c9a 100644 --- a/packages/react/src/tooltip/portal/TooltipPortal.tsx +++ b/packages/react/src/tooltip/portal/TooltipPortal.tsx @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { useTooltipRootContext } from '../root/TooltipRootContext'; +import { useTooltipRootContext, TooltipMountedContext } from '../root/TooltipRootContext'; import { TooltipPortalContext } from './TooltipPortalContext'; import { FloatingPortalLite } from '../../utils/FloatingPortalLite'; import { type BaseUIComponentProps } from '../../internals/types'; @@ -18,8 +18,8 @@ export const TooltipPortal = React.forwardRef(function TooltipPortal( ) { const { keepMounted = false, ...portalProps } = props; - const store = useTooltipRootContext(); - const mounted = store.useState('mounted'); + useTooltipRootContext(); + const mounted = React.useContext(TooltipMountedContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/tooltip/positioner/TooltipPositioner.tsx b/packages/react/src/tooltip/positioner/TooltipPositioner.tsx index 0ef6afc40e3..f8f9c7496ba 100644 --- a/packages/react/src/tooltip/positioner/TooltipPositioner.tsx +++ b/packages/react/src/tooltip/positioner/TooltipPositioner.tsx @@ -1,6 +1,11 @@ 'use client'; import * as React from 'react'; -import { useTooltipRootContext } from '../root/TooltipRootContext'; +import { + useTooltipRootContext, + TooltipOpenContext, + TooltipMountedContext, + TooltipTransitionStatusContext, +} from '../root/TooltipRootContext'; import { TooltipPositionerContext } from './TooltipPositionerContext'; import { useAnchorPositioning, @@ -45,13 +50,13 @@ export const TooltipPositioner = React.forwardRef(function TooltipPositioner( const store = useTooltipRootContext(); const keepMounted = useTooltipPortalContext(); - const open = store.useState('open'); - const mounted = store.useState('mounted'); + const open = React.useContext(TooltipOpenContext); + const mounted = React.useContext(TooltipMountedContext); const trackCursorAxis = store.useState('trackCursorAxis'); const disableHoverablePopup = store.useState('disableHoverablePopup'); const floatingRootContext = store.useState('floatingRootContext'); const instantType = store.useState('instantType'); - const transitionStatus = store.useState('transitionStatus'); + const transitionStatus = React.useContext(TooltipTransitionStatusContext); const adaptiveOrigin = store.useState('adaptiveOrigin'); const positioning = useAnchorPositioning({ diff --git a/packages/react/src/tooltip/root/TooltipRoot.tsx b/packages/react/src/tooltip/root/TooltipRoot.tsx index 2511030b701..ba947f34723 100644 --- a/packages/react/src/tooltip/root/TooltipRoot.tsx +++ b/packages/react/src/tooltip/root/TooltipRoot.tsx @@ -3,7 +3,12 @@ import * as React from 'react'; import { fastComponent } from '@base-ui/utils/fastHooks'; import { EMPTY_OBJECT } from '@base-ui/utils/empty'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; -import { TooltipRootContext } from './TooltipRootContext'; +import { + TooltipRootContext, + TooltipOpenContext, + TooltipMountedContext, + TooltipTransitionStatusContext, +} from './TooltipRootContext'; import { useClientPoint, useDismiss } from '../../floating-ui-react'; import { type BaseUIChangeEventDetails, @@ -67,10 +72,9 @@ export const TooltipRoot = fastComponent(function TooltipRoot( store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete); const openState = store.useState('open'); - const open = !disabled && openState; + const open = !disabled && (openProp ?? openState); const activeTriggerId = store.useState('activeTriggerId'); - const mounted = store.useState('mounted'); const payload = store.useState('payload') as Payload | undefined; store.useSyncedValues({ @@ -80,7 +84,7 @@ export const TooltipRoot = fastComponent(function TooltipRoot( }); useImplicitActiveTrigger(store, { closeOnActiveTriggerUnmount: true }); - const { forceUnmount, transitionStatus } = useOpenStateTransitions(open, store); + const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions(open, store); const isInstantPhase = store.useState('isInstantPhase'); const instantType = store.useState('instantType'); const lastOpenChangeReason = store.useState('lastOpenChangeReason'); @@ -139,11 +143,21 @@ export const TooltipRoot = fastComponent(function TooltipRoot( return ( - {handle && } - {shouldRenderInteractions && ( - - )} - {typeof children === 'function' ? children({ payload }) : children} + + + + {handle && } + {shouldRenderInteractions && ( + + )} + {typeof children === 'function' ? children({ payload }) : children} + + + ); }); diff --git a/packages/react/src/tooltip/root/TooltipRootContext.ts b/packages/react/src/tooltip/root/TooltipRootContext.ts index 9e0def06bb9..8f61867ce44 100644 --- a/packages/react/src/tooltip/root/TooltipRootContext.ts +++ b/packages/react/src/tooltip/root/TooltipRootContext.ts @@ -1,5 +1,6 @@ 'use client'; import * as React from 'react'; +import type { TransitionStatus } from '../../internals/useTransitionStatus'; import { TooltipStore } from '../store/TooltipStore'; export type TooltipRootContext = TooltipStore; @@ -18,3 +19,8 @@ export function useTooltipRootContext(optional?: boolean) { return context; } + +// Separate contexts preserve subscriptions to individual lifecycle values. +export const TooltipOpenContext = React.createContext(false); +export const TooltipMountedContext = React.createContext(false); +export const TooltipTransitionStatusContext = React.createContext(undefined); diff --git a/packages/react/src/utils/popups/popupLifecycle.test.tsx b/packages/react/src/utils/popups/popupLifecycle.test.tsx new file mode 100644 index 00000000000..5fb860d920b --- /dev/null +++ b/packages/react/src/utils/popups/popupLifecycle.test.tsx @@ -0,0 +1,144 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import * as React from 'react'; +import { AlertDialog } from '@base-ui/react/alert-dialog'; +import { Drawer } from '@base-ui/react/drawer'; +import { Menu } from '@base-ui/react/menu'; +import { Tooltip } from '@base-ui/react/tooltip'; +import { PreviewCard } from '@base-ui/react/preview-card'; +import { Select } from '@base-ui/react/select'; +import { Combobox } from '@base-ui/react/combobox'; +import { createRenderer } from '#test-utils'; + +const cases: Array<{ + name: string; + renderPopup: (open: boolean, ref: React.Ref) => React.JSX.Element; +}> = [ + { + name: 'AlertDialog', + renderPopup: (open, ref) => ( + + + {open && Content} + + + ), + }, + { + name: 'Drawer', + renderPopup: (open, ref) => ( + + + {open && ( + + Content + + )} + + + ), + }, + { + name: 'Menu', + renderPopup: (open, ref) => ( + + Open + + {open && ( + + + Content + + + )} + + + ), + }, + { + name: 'Tooltip', + renderPopup: (open, ref) => ( + + Open + + {open && ( + + Content + + )} + + + ), + }, + { + name: 'PreviewCard', + renderPopup: (open, ref) => ( + + Open + + {open && ( + + Content + + )} + + + ), + }, + { + name: 'Select', + // Select.Portal mounts lazily. Render inline to inspect the positioner's opening commit. + renderPopup: (open, ref) => ( + + Open + {open && ( + + + Content + + + )} + + ), + }, + { + name: 'Combobox', + renderPopup: (open, ref) => ( + + + + {open && ( + + + + Content + + + + )} + + + ), + }, +]; + +describe('popup lifecycle rendering', () => { + const { render } = createRenderer(); + + beforeEach(() => { + globalThis.BASE_UI_ANIMATIONS_DISABLED = true; + }); + + it.for(cases)('$name removes hidden in the opening commit', async ({ renderPopup }) => { + const commits: boolean[] = []; + function recordCommit(element: HTMLDivElement | null) { + if (element) { + commits.push(element.hasAttribute('hidden')); + } + } + + const { rerender } = await render(renderPopup(false, recordCommit)); + await rerender(renderPopup(true, recordCommit)); + + expect(commits[0]).toBe(false); + }); +}); diff --git a/packages/react/src/utils/popups/popupStoreUtils.ts b/packages/react/src/utils/popups/popupStoreUtils.ts index 6425bc2ef7d..9ab1178839d 100644 --- a/packages/react/src/utils/popups/popupStoreUtils.ts +++ b/packages/react/src/utils/popups/popupStoreUtils.ts @@ -15,7 +15,7 @@ import { type SyncedFloatingRootContextStore, } from '../../floating-ui-react/hooks/useSyncedFloatingRootContext'; import { useTransitionStatus } from '../../internals/useTransitionStatus'; -import { useOpenChangeComplete } from '../../internals/useOpenChangeComplete'; +import { usePopupCloseComplete } from './usePopupCloseComplete'; import type { HTMLProps } from '../../internals/types'; import { createChangeEventDetails, @@ -549,7 +549,7 @@ export function useImplicitActiveTrigger> * popup on page load, SSR'd markup) appears without animating. Opt in for popups whose subtree * only mounts in response to something the user did, such as a submenu inside a menu popup. * - * @returns A function to forcibly unmount the popup. + * @returns The current lifecycle state and a function to forcibly unmount the popup. */ export function useOpenStateTransitions>( open: boolean, @@ -586,18 +586,14 @@ export function useOpenStateTransitions>( store.context.onOpenChangeComplete?.(false); }); - useOpenChangeComplete({ - enabled: mounted && !open && !syncedPreventUnmountingOnClose, + usePopupCloseComplete({ + enabled: mounted && !syncedPreventUnmountingOnClose, open, ref: store.context.popupRef, - onComplete() { - if (!open) { - forceUnmount(); - } - }, + onComplete: forceUnmount, }); - return { forceUnmount, transitionStatus }; + return { forceUnmount, mounted, transitionStatus }; } type PopupInteractionPropKey = 'activeTriggerProps' | 'inactiveTriggerProps' | 'popupProps'; diff --git a/packages/react/src/utils/popups/usePopupCloseComplete.ts b/packages/react/src/utils/popups/usePopupCloseComplete.ts new file mode 100644 index 00000000000..723c7faf71a --- /dev/null +++ b/packages/react/src/utils/popups/usePopupCloseComplete.ts @@ -0,0 +1,54 @@ +'use client'; +import * as React from 'react'; +import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; +import { useStableCallback } from '@base-ui/utils/useStableCallback'; +import { useAnimationsFinished } from '../../internals/useAnimationsFinished'; + +/** + * Finishes a popup's close after CSS animations, including when the consumer already + * removed the popup in a React Transition. Inline compositions that never rendered + * a popup have no popup lifecycle to finish. + */ +export function usePopupCloseComplete({ + enabled, + open, + ref, + onComplete, +}: { + enabled: boolean; + open: boolean; + ref: React.RefObject; + onComplete: () => void; +}) { + const hadElementRef = React.useRef(false); + useIsoLayoutEffect(() => { + if (open && ref.current) { + hadElementRef.current = true; + } + }); + + const complete = useStableCallback(() => { + if (!open) { + hadElementRef.current = false; + onComplete(); + } + }); + const runOnceAnimationsFinish = useAnimationsFinished(ref); + + React.useEffect(() => { + if (!enabled || open) { + return undefined; + } + + if (!ref.current) { + if (hadElementRef.current) { + complete(); + } + return undefined; + } + + const abortController = new AbortController(); + runOnceAnimationsFinish(complete, abortController.signal); + return () => abortController.abort(); + }, [enabled, open, ref, complete, runOnceAnimationsFinish]); +} From aa3403e4ce7ed93dac154fb95562c7f0368448b5 Mon Sep 17 00:00:00 2001 From: atomiks Date: Tue, 15 Sep 2026 12:57:40 +1000 Subject: [PATCH 4/7] [docs] Keep view transition trigger labels stationary --- .../experiments/dialog/view-transition.css | 7 +++++ .../experiments/dialog/view-transition.tsx | 26 +++++++++++-------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/src/app/(private)/experiments/dialog/view-transition.css b/docs/src/app/(private)/experiments/dialog/view-transition.css index eda5b7fcbb3..c946410d0ab 100644 --- a/docs/src/app/(private)/experiments/dialog/view-transition.css +++ b/docs/src/app/(private)/experiments/dialog/view-transition.css @@ -9,7 +9,14 @@ min-height: 5rem; } +.view-transition-thumb-slot { + flex: none; + width: 3rem; + height: 3rem; +} + .view-transition-cover { + display: block; width: 12rem; height: 8rem; background: linear-gradient(135deg, #65b8ff, #5536a3); diff --git a/docs/src/app/(private)/experiments/dialog/view-transition.tsx b/docs/src/app/(private)/experiments/dialog/view-transition.tsx index d52be4cd229..ba0c3d98ddf 100644 --- a/docs/src/app/(private)/experiments/dialog/view-transition.tsx +++ b/docs/src/app/(private)/experiments/dialog/view-transition.tsx @@ -27,7 +27,7 @@ export default function ViewTransitionExperiment() { } function Cover() { - return
; + return ; } function DialogExample() { @@ -43,11 +43,13 @@ function DialogExample() {

Dialog

- {!open && ( - - - - )} + + {!open && ( + + + + )} + Open dialog @@ -82,11 +84,13 @@ function PopoverExample() {

Popover

- {!open && ( - - - - )} + + {!open && ( + + + + )} + Open popover From 07f54ff9841860f3dfdbccc0c5a9f1bd855b594b Mon Sep 17 00:00:00 2001 From: atomiks Date: Tue, 15 Sep 2026 18:58:21 +1000 Subject: [PATCH 5/7] [all components] Simplify popup lifecycle contexts --- .../(docs)/react/handbook/animation/page.mdx | 6 +- .../src/combobox/arrow/ComboboxArrow.tsx | 4 +- .../combobox/backdrop/ComboboxBackdrop.tsx | 11 +-- .../src/combobox/popup/ComboboxPopup.tsx | 8 +-- .../src/combobox/portal/ComboboxPortal.tsx | 4 +- .../positioner/ComboboxPositioner.tsx | 8 +-- .../react/src/combobox/root/AriaCombobox.tsx | 35 +++++---- .../src/combobox/root/ComboboxRoot.test.tsx | 49 ++++--------- .../src/combobox/root/ComboboxRootContext.tsx | 11 +-- .../src/dialog/backdrop/DialogBackdrop.tsx | 11 +-- .../react/src/dialog/close/DialogClose.tsx | 4 +- .../react/src/dialog/popup/DialogPopup.tsx | 11 +-- .../react/src/dialog/portal/DialogPortal.tsx | 9 +-- .../DialogRoot.detached-triggers.test.tsx | 71 +++++++++++++++++++ .../src/dialog/root/DialogRootContext.ts | 12 ++-- .../src/dialog/root/useRenderDialogRoot.tsx | 35 +++++---- .../src/dialog/viewport/DialogViewport.tsx | 11 +-- .../src/drawer/backdrop/DrawerBackdrop.tsx | 11 +-- .../react/src/drawer/popup/DrawerPopup.tsx | 11 +-- packages/react/src/drawer/root/DrawerRoot.tsx | 4 +- .../src/drawer/swipe-area/DrawerSwipeArea.tsx | 4 +- .../drawer/viewport/DrawerViewport.test.tsx | 29 ++++++-- .../src/drawer/viewport/DrawerViewport.tsx | 9 +-- .../DrawerVirtualKeyboardProvider.tsx | 9 +-- packages/react/src/menu/arrow/MenuArrow.tsx | 4 +- .../react/src/menu/backdrop/MenuBackdrop.tsx | 11 +-- packages/react/src/menu/popup/MenuPopup.tsx | 11 +-- packages/react/src/menu/portal/MenuPortal.tsx | 4 +- .../src/menu/positioner/MenuPositioner.tsx | 11 +-- packages/react/src/menu/root/MenuRoot.tsx | 25 +++---- .../react/src/menu/root/MenuRootContext.ts | 11 +-- .../react/src/popover/arrow/PopoverArrow.tsx | 4 +- .../src/popover/backdrop/PopoverBackdrop.tsx | 11 +-- .../react/src/popover/popup/PopoverPopup.tsx | 11 +-- .../src/popover/portal/PopoverPortal.tsx | 4 +- .../popover/positioner/PopoverPositioner.tsx | 11 +-- .../react/src/popover/root/PopoverRoot.tsx | 23 +++--- .../src/popover/root/PopoverRootContext.ts | 12 ++-- .../preview-card/arrow/PreviewCardArrow.tsx | 4 +- .../backdrop/PreviewCardBackdrop.tsx | 11 +-- .../preview-card/popup/PreviewCardPopup.tsx | 9 +-- .../preview-card/portal/PreviewCardPortal.tsx | 4 +- .../positioner/PreviewCardPositioner.tsx | 11 +-- .../preview-card/root/PreviewCardContext.ts | 11 +-- .../src/preview-card/root/PreviewCardRoot.tsx | 23 +++--- .../react/src/select/arrow/SelectArrow.tsx | 4 +- .../src/select/backdrop/SelectBackdrop.tsx | 11 +-- .../react/src/select/popup/SelectPopup.tsx | 11 +-- .../react/src/select/portal/SelectPortal.tsx | 4 +- .../select/positioner/SelectPositioner.tsx | 11 +-- packages/react/src/select/root/SelectRoot.tsx | 22 +++--- .../src/select/root/SelectRootContext.ts | 11 +-- .../react/src/tooltip/arrow/TooltipArrow.tsx | 4 +- .../react/src/tooltip/popup/TooltipPopup.tsx | 9 +-- .../src/tooltip/portal/TooltipPortal.tsx | 4 +- .../tooltip/positioner/TooltipPositioner.tsx | 11 +-- .../react/src/tooltip/root/TooltipRoot.tsx | 38 +++++----- .../src/tooltip/root/TooltipRootContext.ts | 11 +-- packages/react/src/utils/popups/store.ts | 6 ++ 59 files changed, 329 insertions(+), 431 deletions(-) diff --git a/docs/src/app/(docs)/react/handbook/animation/page.mdx b/docs/src/app/(docs)/react/handbook/animation/page.mdx index c00063c6c11..11231972d3f 100644 --- a/docs/src/app/(docs)/react/handbook/animation/page.mdx +++ b/docs/src/app/(docs)/react/handbook/animation/page.mdx @@ -219,10 +219,10 @@ function App() { ## View Transitions React 19.3 ships the [``](https://react.dev/reference/react/ViewTransition) component, which animates DOM changes using the browser's [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API). -Base UI components work with it as long as two conditions are met: +Base UI components work with it as long as two conditions are met: -- The update that changes the UI runs inside a [Transition](https://react.dev/reference/react/startTransition). Base UI applies its own state changes as regular updates, so control the state yourself and wrap the update in `startTransition`. -- React, not Base UI, removes the elements being animated. Components like `` stay mounted while their CSS exit animation plays and are removed a frame later, outside of the Transition, which makes the browser skip the view transition. Render such elements conditionally so they leave the tree within the Transition. +- The update that changes the UI runs inside a [Transition](https://react.dev/reference/react/startTransition). Base UI applies its own state changes as regular updates, so control the state yourself and wrap the update in `startTransition`. +- React, not Base UI, removes the elements being animated. Components like `` stay mounted while their CSS exit animation plays and are removed a frame later, outside of the Transition, which makes the browser skip the view transition. Render such elements conditionally so they leave the tree within the Transition. The `::view-transition-*` pseudo-elements are rendered on the root element, so their styles must live in a global stylesheet rather than a CSS Module. diff --git a/packages/react/src/combobox/arrow/ComboboxArrow.tsx b/packages/react/src/combobox/arrow/ComboboxArrow.tsx index 40a7979ae44..9a5e01efd61 100644 --- a/packages/react/src/combobox/arrow/ComboboxArrow.tsx +++ b/packages/react/src/combobox/arrow/ComboboxArrow.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { useComboboxPositionerContext } from '../positioner/ComboboxPositionerContext'; -import { useComboboxRootContext, ComboboxOpenContext } from '../root/ComboboxRootContext'; +import { useComboboxRootContext, ComboboxLifecycleContext } from '../root/ComboboxRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; @@ -22,7 +22,7 @@ export const ComboboxArrow = React.forwardRef(function ComboboxArrow( useComboboxRootContext(); const { arrowRef, side, align, arrowUncentered, arrowStyles } = useComboboxPositionerContext(); - const open = React.useContext(ComboboxOpenContext); + const { open } = React.useContext(ComboboxLifecycleContext); const state: ComboboxArrowState = { open, diff --git a/packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx b/packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx index a99418135b6..6ee2c7d095b 100644 --- a/packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx +++ b/packages/react/src/combobox/backdrop/ComboboxBackdrop.tsx @@ -1,12 +1,7 @@ 'use client'; import * as React from 'react'; import type { BaseUIComponentProps } from '../../internals/types'; -import { - useComboboxRootContext, - ComboboxOpenContext, - ComboboxMountedContext, - ComboboxTransitionStatusContext, -} from '../root/ComboboxRootContext'; +import { useComboboxRootContext, ComboboxLifecycleContext } from '../root/ComboboxRootContext'; import { popupStateMapping } from '../../utils/popupStateMapping'; import type { StateAttributesMapping } from '../../internals/getStateAttributesProps'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; @@ -32,9 +27,7 @@ export const ComboboxBackdrop = React.forwardRef(function ComboboxBackdrop( useComboboxRootContext(); - const open = React.useContext(ComboboxOpenContext); - const mounted = React.useContext(ComboboxMountedContext); - const transitionStatus = React.useContext(ComboboxTransitionStatusContext); + const { open, mounted, transitionStatus } = React.useContext(ComboboxLifecycleContext); const state: ComboboxBackdropState = { open, diff --git a/packages/react/src/combobox/popup/ComboboxPopup.tsx b/packages/react/src/combobox/popup/ComboboxPopup.tsx index 28a469816e7..1c7525ce5c5 100644 --- a/packages/react/src/combobox/popup/ComboboxPopup.tsx +++ b/packages/react/src/combobox/popup/ComboboxPopup.tsx @@ -9,9 +9,7 @@ import { useRenderElement } from '../../internals/useRenderElement'; import { useComboboxFloatingContext, useComboboxRootContext, - ComboboxOpenContext, - ComboboxMountedContext, - ComboboxTransitionStatusContext, + ComboboxLifecycleContext, } from '../root/ComboboxRootContext'; import { selectors } from '../store'; import { popupStateMapping } from '../../utils/popupStateMapping'; @@ -48,11 +46,9 @@ export const ComboboxPopup = React.forwardRef(function ComboboxPopup( const positioning = useComboboxPositionerContext(); const floatingRootContext = useComboboxFloatingContext(); - const mounted = React.useContext(ComboboxMountedContext); - const open = React.useContext(ComboboxOpenContext); + const { mounted, open, transitionStatus } = React.useContext(ComboboxLifecycleContext); const openMethod = useStore(store, selectors.openMethod); const popupProps = useStore(store, selectors.popupProps); - const transitionStatus = React.useContext(ComboboxTransitionStatusContext); const inputInsidePopup = useStore(store, selectors.inputInsidePopup); const inputElement = useStore(store, selectors.inputElement); const modal = useStore(store, selectors.modal); diff --git a/packages/react/src/combobox/portal/ComboboxPortal.tsx b/packages/react/src/combobox/portal/ComboboxPortal.tsx index 6207e290261..cd2ffa44365 100644 --- a/packages/react/src/combobox/portal/ComboboxPortal.tsx +++ b/packages/react/src/combobox/portal/ComboboxPortal.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useStore } from '@base-ui/utils/store'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { useComboboxRootContext, ComboboxMountedContext } from '../root/ComboboxRootContext'; +import { useComboboxRootContext, ComboboxLifecycleContext } from '../root/ComboboxRootContext'; import { ComboboxPortalContext } from './ComboboxPortalContext'; import { selectors } from '../store'; @@ -22,7 +22,7 @@ export const ComboboxPortal = React.forwardRef(function ComboboxPortal( const store = useComboboxRootContext(); - const mounted = React.useContext(ComboboxMountedContext); + const { mounted } = React.useContext(ComboboxLifecycleContext); const forceMounted = useStore(store, selectors.forceMounted); const shouldRender = mounted || keepMounted || forceMounted; diff --git a/packages/react/src/combobox/positioner/ComboboxPositioner.tsx b/packages/react/src/combobox/positioner/ComboboxPositioner.tsx index 1d4af7ab45c..151ae8a1e88 100644 --- a/packages/react/src/combobox/positioner/ComboboxPositioner.tsx +++ b/packages/react/src/combobox/positioner/ComboboxPositioner.tsx @@ -7,9 +7,7 @@ import { inertValue } from '@base-ui/utils/inertValue'; import { useComboboxFloatingContext, useComboboxRootContext, - ComboboxOpenContext, - ComboboxMountedContext, - ComboboxTransitionStatusContext, + ComboboxLifecycleContext, } from '../root/ComboboxRootContext'; import { ComboboxPositionerContext } from './ComboboxPositionerContext'; import { useListEmpty } from '../utils/parts'; @@ -63,15 +61,13 @@ export const ComboboxPositioner = React.forwardRef(function ComboboxPositioner( const keepMounted = useComboboxPortalContext(); const modal = useStore(store, selectors.modal); - const open = React.useContext(ComboboxOpenContext); - const mounted = React.useContext(ComboboxMountedContext); + const { open, mounted, transitionStatus } = React.useContext(ComboboxLifecycleContext); const openMethod = useStore(store, selectors.openMethod); const positionerElement = useStore(store, selectors.positionerElement); const triggerElement = useStore(store, selectors.triggerElement); const inputElement = useStore(store, selectors.inputElement); const inputGroupElement = useStore(store, selectors.inputGroupElement); const inputInsidePopup = useStore(store, selectors.inputInsidePopup); - const transitionStatus = React.useContext(ComboboxTransitionStatusContext); const empty = useListEmpty(); const resolvedAnchor = diff --git a/packages/react/src/combobox/root/AriaCombobox.tsx b/packages/react/src/combobox/root/AriaCombobox.tsx index d2d6a7d52f2..4c241812464 100644 --- a/packages/react/src/combobox/root/AriaCombobox.tsx +++ b/packages/react/src/combobox/root/AriaCombobox.tsx @@ -34,9 +34,7 @@ import { ComboboxHasItemsContext, ComboboxRootContext, ComboboxInputValueContext, - ComboboxOpenContext, - ComboboxMountedContext, - ComboboxTransitionStatusContext, + ComboboxLifecycleContext, } from './ComboboxRootContext'; import { selectors, type State as StoreState } from '../store'; import { usePopupCloseComplete } from '../../utils/popups/usePopupCloseComplete'; @@ -1628,23 +1626,24 @@ export function AriaCombobox ); + const lifecycle = React.useMemo( + () => ({ open, mounted, transitionStatus }), + [open, mounted, transitionStatus], + ); + return ( - - - - - - - - {children} - - - - - - - + + + + + + {children} + + + + + ); } diff --git a/packages/react/src/combobox/root/ComboboxRoot.test.tsx b/packages/react/src/combobox/root/ComboboxRoot.test.tsx index ed2f69fe23f..9cbb6eda24c 100644 --- a/packages/react/src/combobox/root/ComboboxRoot.test.tsx +++ b/packages/react/src/combobox/root/ComboboxRoot.test.tsx @@ -296,15 +296,10 @@ describe('', () => { const input = await screen.findByTestId('input'); await waitFor(() => expect(input).toHaveFocus()); - await user.type(input, 'ban'); - // Closing detaches store-backed refs synchronously; await their cascading updates. - // eslint-disable-next-line testing-library/no-unnecessary-act - await act(async () => { - fireEvent.keyDown(input, { key: 'ArrowDown' }); - fireEvent.keyUp(input, { key: 'ArrowDown' }); - fireEvent.keyDown(input, { key: 'Enter' }); - fireEvent.keyUp(input, { key: 'Enter' }); - }); + // user-event synthesizes change on blur, re-entering act during the focused input's + // unmount in Chromium. Set the query directly in these close/reopen tests. + fireEvent.change(input, { target: { value: 'ban' } }); + await user.keyboard('{ArrowDown}{Enter}'); await waitFor(() => expect(screen.queryByRole('dialog')).toBe(null)); expect(trigger).toHaveFocus(); @@ -350,14 +345,8 @@ describe('', () => { const trigger = screen.getByTestId('trigger'); await user.click(trigger); - await user.type(await screen.findByTestId('input'), 'ban'); - // Closing detaches store-backed refs synchronously; await their cascading updates. - // eslint-disable-next-line testing-library/no-unnecessary-act - await act(async () => { - const target = screen.getByTestId('input'); - fireEvent.keyDown(target, { key: 'Escape' }); - fireEvent.keyUp(target, { key: 'Escape' }); - }); + fireEvent.change(await screen.findByTestId('input'), { target: { value: 'ban' } }); + await user.keyboard('{Escape}'); await waitFor(() => expect(screen.queryByRole('dialog')).toBe(null)); expect(trigger).toHaveFocus(); @@ -2703,18 +2692,12 @@ describe('', () => { await user.click(screen.getByTestId('trigger')); const input = await screen.findByTestId('input'); - await user.type(input, 'cherry'); + fireEvent.change(input, { target: { value: 'cherry' } }); await waitFor(() => { expect(screen.queryByRole('option', { name: 'banana' })).toBe(null); }); - // Closing detaches store-backed refs synchronously; await their cascading updates. - // eslint-disable-next-line testing-library/no-unnecessary-act - await act(async () => { - const target = screen.getByTestId('input'); - fireEvent.keyDown(target, { key: 'Escape' }); - fireEvent.keyUp(target, { key: 'Escape' }); - }); + await user.keyboard('{Escape}'); await waitFor(() => { expect(screen.queryByRole('listbox')).toBe(null); }); @@ -10069,12 +10052,8 @@ describe('', () => { await screen.findByRole('dialog', { name: 'Fruit chooser' }); const input = await screen.findByTestId('dialog-input'); - await user.type(input, 'ap'); - // Closing detaches store-backed refs synchronously; await their cascading updates. - // eslint-disable-next-line testing-library/no-unnecessary-act - await act(async () => { - fireEvent.click(screen.getByRole('option', { name: 'Apple' })); - }); + fireEvent.change(input, { target: { value: 'ap' } }); + await user.click(screen.getByRole('option', { name: 'Apple' })); await waitFor(() => { expect(screen.queryByRole('dialog', { name: 'Fruit chooser' })).toBe(null); @@ -10098,12 +10077,8 @@ describe('', () => { await screen.findByRole('dialog', { name: 'Fruit chooser' }); const input = await screen.findByTestId('dialog-input'); - await user.type(input, 'ap'); - // Closing detaches store-backed refs synchronously; await their cascading updates. - // eslint-disable-next-line testing-library/no-unnecessary-act - await act(async () => { - fireEvent.click(screen.getByRole('option', { name: 'Apple' })); - }); + fireEvent.change(input, { target: { value: 'ap' } }); + await user.click(screen.getByRole('option', { name: 'Apple' })); await waitFor(() => { expect(screen.queryByRole('dialog', { name: 'Fruit chooser' })).toBe(null); diff --git a/packages/react/src/combobox/root/ComboboxRootContext.tsx b/packages/react/src/combobox/root/ComboboxRootContext.tsx index 82f39b39b2a..155e13695c6 100644 --- a/packages/react/src/combobox/root/ComboboxRootContext.tsx +++ b/packages/react/src/combobox/root/ComboboxRootContext.tsx @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { TransitionStatus } from '../../internals/useTransitionStatus'; +import type { PopupLifecycleState } from '../../utils/popups/store'; import { ComboboxStore } from '../store'; import type { FloatingRootContext } from '../../floating-ui-react'; @@ -66,7 +66,8 @@ export function useComboboxHasItemsContext() { return React.useContext(ComboboxHasItemsContext); } -// Separate contexts preserve subscriptions to individual lifecycle values. -export const ComboboxOpenContext = React.createContext(false); -export const ComboboxMountedContext = React.createContext(false); -export const ComboboxTransitionStatusContext = React.createContext(undefined); +export const ComboboxLifecycleContext = React.createContext({ + open: false, + mounted: false, + transitionStatus: undefined, +}); diff --git a/packages/react/src/dialog/backdrop/DialogBackdrop.tsx b/packages/react/src/dialog/backdrop/DialogBackdrop.tsx index db8bda49581..3177969a904 100644 --- a/packages/react/src/dialog/backdrop/DialogBackdrop.tsx +++ b/packages/react/src/dialog/backdrop/DialogBackdrop.tsx @@ -1,11 +1,6 @@ 'use client'; import * as React from 'react'; -import { - useDialogRootContext, - DialogOpenContext, - DialogMountedContext, - DialogTransitionStatusContext, -} from '../root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import { type TransitionStatus } from '../../internals/useTransitionStatus'; import { type BaseUIComponentProps } from '../../internals/types'; @@ -25,10 +20,8 @@ export const DialogBackdrop = React.forwardRef(function DialogBackdrop( const store = useDialogRootContext(); - const open = React.useContext(DialogOpenContext); + const { open, mounted, transitionStatus } = React.useContext(DialogLifecycleContext); const nested = store.useState('nested'); - const mounted = React.useContext(DialogMountedContext); - const transitionStatus = React.useContext(DialogTransitionStatusContext); const state: DialogBackdropState = { open, diff --git a/packages/react/src/dialog/close/DialogClose.tsx b/packages/react/src/dialog/close/DialogClose.tsx index 8d0df362804..fc5a0ef630c 100644 --- a/packages/react/src/dialog/close/DialogClose.tsx +++ b/packages/react/src/dialog/close/DialogClose.tsx @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { useDialogRootContext, DialogOpenContext } from '../root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { BaseUIComponentProps, NativeButtonProps } from '../../internals/types'; import { useButton } from '../../internals/use-button'; @@ -27,7 +27,7 @@ export const DialogClose = React.forwardRef(function DialogClose( } = componentProps; const store = useDialogRootContext(); - const open = React.useContext(DialogOpenContext); + const { open } = React.useContext(DialogLifecycleContext); const { getButtonProps, buttonRef } = useButton({ disabled, diff --git a/packages/react/src/dialog/popup/DialogPopup.tsx b/packages/react/src/dialog/popup/DialogPopup.tsx index 20482ae39c4..7b3a0de7575 100644 --- a/packages/react/src/dialog/popup/DialogPopup.tsx +++ b/packages/react/src/dialog/popup/DialogPopup.tsx @@ -2,12 +2,7 @@ import * as React from 'react'; import { InteractionType } from '@base-ui/utils/useEnhancedClickHandler'; import { FloatingFocusManager } from '../../floating-ui-react'; -import { - useDialogRootContext, - DialogOpenContext, - DialogMountedContext, - DialogTransitionStatusContext, -} from '../root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import { type BaseUIComponentProps } from '../../internals/types'; import { type TransitionStatus } from '../../internals/useTransitionStatus'; @@ -36,13 +31,11 @@ export const DialogPopup = React.forwardRef(function DialogPopup( const floatingRootContext = store.useState('floatingRootContext'); const rootPopupProps = store.useState('popupProps'); const modal = store.useState('modal'); - const mounted = React.useContext(DialogMountedContext); + const { mounted, open, transitionStatus } = React.useContext(DialogLifecycleContext); const nested = store.useState('nested'); const nestedOpenDialogCount = store.useState('nestedOpenDialogCount'); - const open = React.useContext(DialogOpenContext); const openMethod = store.useState('openMethod'); const titleElementId = store.useState('titleElementId'); - const transitionStatus = React.useContext(DialogTransitionStatusContext); const role = store.useState('role'); const floatingId = floatingRootContext.useState('floatingId'); diff --git a/packages/react/src/dialog/portal/DialogPortal.tsx b/packages/react/src/dialog/portal/DialogPortal.tsx index 9fb5ab03b56..7f560fb40bd 100644 --- a/packages/react/src/dialog/portal/DialogPortal.tsx +++ b/packages/react/src/dialog/portal/DialogPortal.tsx @@ -3,11 +3,7 @@ import * as React from 'react'; import { inertValue } from '@base-ui/utils/inertValue'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { - useDialogRootContext, - DialogOpenContext, - DialogMountedContext, -} from '../root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../root/DialogRootContext'; import { DialogPortalContext } from './DialogPortalContext'; import { InternalBackdrop } from '../../utils/InternalBackdrop'; @@ -25,9 +21,8 @@ export const DialogPortal = React.forwardRef(function DialogPortal( const { keepMounted = false, ...portalProps } = props; const store = useDialogRootContext(); - const mounted = React.useContext(DialogMountedContext); + const { mounted, open } = React.useContext(DialogLifecycleContext); const modal = store.useState('modal'); - const open = React.useContext(DialogOpenContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/dialog/root/DialogRoot.detached-triggers.test.tsx b/packages/react/src/dialog/root/DialogRoot.detached-triggers.test.tsx index 9ed4e301142..0520b8d5dcd 100644 --- a/packages/react/src/dialog/root/DialogRoot.detached-triggers.test.tsx +++ b/packages/react/src/dialog/root/DialogRoot.detached-triggers.test.tsx @@ -16,6 +16,77 @@ describe('', () => { describe('handle-backed root ownership', () => { type NumberPayload = { payload: number | undefined }; + it('keeps controlled popup lifecycle and focus in sync with detached triggers', async () => { + const handle = Dialog.createHandle(); + const onOpenChangeComplete = vi.fn(); + const openingCommits: Array<{ hidden: boolean; open: boolean }> = []; + + function recordOpeningCommit(element: HTMLDivElement | null) { + if (element) { + openingCommits.push({ + hidden: element.hasAttribute('hidden'), + open: element.hasAttribute('data-open'), + }); + } + } + + function App() { + const [open, setOpen] = React.useState(false); + + return ( + + {[1, 2].map((payload) => ( + + Trigger {payload} + + ))} + React.startTransition(() => setOpen(nextOpen))} + onOpenChangeComplete={onOpenChangeComplete} + > + {({ payload }) => ( + + {open && ( + + Popup {payload} + Close + + )} + + )} + + + ); + } + + const { user } = await render(); + const firstTrigger = screen.getByRole('button', { name: 'Trigger 1' }); + const secondTrigger = screen.getByRole('button', { name: 'Trigger 2' }); + + await user.click(firstTrigger); + expect(openingCommits[0]).toEqual({ hidden: false, open: true }); + expect(screen.getByRole('dialog', { name: 'Popup 1' })).not.toBe(null); + expect(firstTrigger).toHaveAttribute('aria-expanded', 'true'); + expect(secondTrigger).toHaveAttribute('aria-expanded', 'false'); + + await user.click(screen.getByRole('button', { name: 'Close' })); + expect(firstTrigger).toHaveFocus(); + expect(firstTrigger).toHaveAttribute('aria-expanded', 'false'); + expect(handle.isOpen).toBe(false); + + await user.tab(); + expect(secondTrigger).toHaveFocus(); + await user.keyboard('{Enter}'); + expect(screen.getByRole('dialog', { name: 'Popup 2' })).not.toBe(null); + expect(secondTrigger).toHaveAttribute('aria-expanded', 'true'); + + await user.click(screen.getByRole('button', { name: 'Close' })); + expect(secondTrigger).toHaveFocus(); + expect(onOpenChangeComplete.mock.calls.filter(([value]) => value === false)).toHaveLength(2); + }); + it('hydrates a detached trigger from the stable fallback store', async () => { const handle = Dialog.createHandle(); diff --git a/packages/react/src/dialog/root/DialogRootContext.ts b/packages/react/src/dialog/root/DialogRootContext.ts index 39e2748b4b8..041724c6d3d 100644 --- a/packages/react/src/dialog/root/DialogRootContext.ts +++ b/packages/react/src/dialog/root/DialogRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { TransitionStatus } from '../../internals/useTransitionStatus'; +import type { PopupLifecycleState } from '../../utils/popups/store'; import { DialogStore } from '../store/DialogStore'; export const DialogRootContext = React.createContext | undefined>(undefined); @@ -19,8 +19,8 @@ export function useDialogRootContext(optional?: boolean) { return store; } -// Separate contexts keep consumers of `mounted` from rerendering when only -// `transitionStatus` changes. -export const DialogOpenContext = React.createContext(false); -export const DialogMountedContext = React.createContext(false); -export const DialogTransitionStatusContext = React.createContext(undefined); +export const DialogLifecycleContext = React.createContext({ + open: false, + mounted: false, + transitionStatus: undefined, +}); diff --git a/packages/react/src/dialog/root/useRenderDialogRoot.tsx b/packages/react/src/dialog/root/useRenderDialogRoot.tsx index 3f90649d6c0..9218e1ed66b 100644 --- a/packages/react/src/dialog/root/useRenderDialogRoot.tsx +++ b/packages/react/src/dialog/root/useRenderDialogRoot.tsx @@ -4,9 +4,7 @@ import { DialogInteractions } from './useDialogRoot'; import { DialogRootContext, useDialogRootContext, - DialogOpenContext, - DialogMountedContext, - DialogTransitionStatusContext, + DialogLifecycleContext, } from './DialogRootContext'; import { DialogStore } from '../store/DialogStore'; import type { DialogRootProps } from './DialogRoot'; @@ -96,23 +94,24 @@ export function useRenderDialogRoot( const shouldRenderInteractions = open || mounted; + const lifecycle = React.useMemo( + () => ({ open, mounted, transitionStatus }), + [open, mounted, transitionStatus], + ); + return ( }> - - - - {handle && } - {shouldRenderInteractions && ( - - )} - {typeof children === 'function' ? children({ payload }) : children} - - - + + {handle && } + {shouldRenderInteractions && ( + + )} + {typeof children === 'function' ? children({ payload }) : children} + ); } diff --git a/packages/react/src/dialog/viewport/DialogViewport.tsx b/packages/react/src/dialog/viewport/DialogViewport.tsx index b6f7134e6a3..a1278c33787 100644 --- a/packages/react/src/dialog/viewport/DialogViewport.tsx +++ b/packages/react/src/dialog/viewport/DialogViewport.tsx @@ -3,12 +3,7 @@ import * as React from 'react'; import { useRenderElement } from '../../internals/useRenderElement'; import { type BaseUIComponentProps } from '../../internals/types'; import { type TransitionStatus } from '../../internals/useTransitionStatus'; -import { - useDialogRootContext, - DialogOpenContext, - DialogMountedContext, - DialogTransitionStatusContext, -} from '../root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../root/DialogRootContext'; import { useDialogPortalContext } from '../portal/DialogPortalContext'; import { dialogStateAttributesMapping } from '../utils/stateAttributesMapping'; @@ -27,11 +22,9 @@ export const DialogViewport = React.forwardRef(function DialogViewport( const keepMounted = useDialogPortalContext(); const store = useDialogRootContext(); - const open = React.useContext(DialogOpenContext); + const { open, transitionStatus, mounted } = React.useContext(DialogLifecycleContext); const nested = store.useState('nested'); - const transitionStatus = React.useContext(DialogTransitionStatusContext); const nestedOpenDialogCount = store.useState('nestedOpenDialogCount'); - const mounted = React.useContext(DialogMountedContext); const setViewportElement = store.useStateSetter('viewportElement'); diff --git a/packages/react/src/drawer/backdrop/DrawerBackdrop.tsx b/packages/react/src/drawer/backdrop/DrawerBackdrop.tsx index 38d227935ad..2e9c3bbec28 100644 --- a/packages/react/src/drawer/backdrop/DrawerBackdrop.tsx +++ b/packages/react/src/drawer/backdrop/DrawerBackdrop.tsx @@ -1,11 +1,6 @@ 'use client'; import * as React from 'react'; -import { - useDialogRootContext, - DialogOpenContext, - DialogMountedContext, - DialogTransitionStatusContext, -} from '../../dialog/root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../../dialog/root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import { type TransitionStatus } from '../../internals/useTransitionStatus'; import { type BaseUIComponentProps } from '../../internals/types'; @@ -27,10 +22,8 @@ export const DrawerBackdrop = React.forwardRef(function DrawerBackdrop( const store = useDialogRootContext(); - const open = React.useContext(DialogOpenContext); + const { open, mounted, transitionStatus } = React.useContext(DialogLifecycleContext); const nested = store.useState('nested'); - const mounted = React.useContext(DialogMountedContext); - const transitionStatus = React.useContext(DialogTransitionStatusContext); const state: DrawerBackdropState = { open, diff --git a/packages/react/src/drawer/popup/DrawerPopup.tsx b/packages/react/src/drawer/popup/DrawerPopup.tsx index cb2a3327bb4..96fdad88980 100644 --- a/packages/react/src/drawer/popup/DrawerPopup.tsx +++ b/packages/react/src/drawer/popup/DrawerPopup.tsx @@ -7,12 +7,7 @@ import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { EMPTY_OBJECT } from '@base-ui/utils/empty'; import { FloatingFocusManager } from '../../floating-ui-react'; -import { - useDialogRootContext, - DialogOpenContext, - DialogMountedContext, - DialogTransitionStatusContext, -} from '../../dialog/root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../../dialog/root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { BaseUIComponentProps } from '../../internals/types'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; @@ -142,11 +137,9 @@ export const DrawerPopup = React.forwardRef(function DrawerPopup( const floatingRootContext = store.useState('floatingRootContext'); const rootPopupProps = store.useState('popupProps'); const modal = store.useState('modal'); - const mounted = React.useContext(DialogMountedContext); + const { mounted, transitionStatus, open } = React.useContext(DialogLifecycleContext); const nested = store.useState('nested'); const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount'); - const transitionStatus = React.useContext(DialogTransitionStatusContext); - const open = React.useContext(DialogOpenContext); const openMethod = store.useState('openMethod'); const titleElementId = store.useState('titleElementId'); const role = store.useState('role'); diff --git a/packages/react/src/drawer/root/DrawerRoot.tsx b/packages/react/src/drawer/root/DrawerRoot.tsx index 7660f7187c2..6e527ebe31f 100644 --- a/packages/react/src/drawer/root/DrawerRoot.tsx +++ b/packages/react/src/drawer/root/DrawerRoot.tsx @@ -18,7 +18,7 @@ import { type BaseUIChangeEventDetails, } from '../../internals/createBaseUIEventDetails'; import { REASONS } from '../../internals/reasons'; -import { useDialogRootContext, DialogOpenContext } from '../../dialog/root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../../dialog/root/DialogRootContext'; import { useRenderDialogRoot } from '../../dialog/root/useRenderDialogRoot'; import { useDrawerProviderContext } from '../provider/DrawerProviderContext'; import type { DrawerHandle } from '../handle'; @@ -422,7 +422,7 @@ function DrawerProviderReporter() { const setDrawerOpen = providerContext?.setDrawerOpen; const removeDrawer = providerContext?.removeDrawer; - const open = React.useContext(DialogOpenContext); + const { open } = React.useContext(DialogLifecycleContext); const nestedOpenDialogCount = store.useState('nestedOpenDialogCount'); const popupElement = store.useState('popupElement'); diff --git a/packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx b/packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx index 67b74634628..0034b6d0a7c 100644 --- a/packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx +++ b/packages/react/src/drawer/swipe-area/DrawerSwipeArea.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { ownerDocument } from '@base-ui/utils/owner'; -import { useDialogRootContext, DialogOpenContext } from '../../dialog/root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../../dialog/root/DialogRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { BaseUIComponentProps } from '../../internals/types'; import type { StateAttributesMapping } from '../../internals/getStateAttributesProps'; @@ -116,7 +116,7 @@ export const DrawerSwipeArea = React.forwardRef(function DrawerSwipeArea( return () => registerTrigger(null); }, [registerTrigger, swipeAreaId, store]); - const open = React.useContext(DialogOpenContext); + const { open } = React.useContext(DialogLifecycleContext); const resetDragDelta = useStableCallback(() => { dragDeltaRef.current.x = 0; diff --git a/packages/react/src/drawer/viewport/DrawerViewport.test.tsx b/packages/react/src/drawer/viewport/DrawerViewport.test.tsx index 100086c3916..92b2d055230 100644 --- a/packages/react/src/drawer/viewport/DrawerViewport.test.tsx +++ b/packages/react/src/drawer/viewport/DrawerViewport.test.tsx @@ -5,7 +5,14 @@ import { Combobox } from '@base-ui/react/combobox'; import { Drawer } from '@base-ui/react/drawer'; import { Slider } from '@base-ui/react/slider'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; -import { act, fireEvent, flushMicrotasks, screen, waitFor } from '@mui/internal-test-utils'; +import { + act, + createEvent, + fireEvent, + flushMicrotasks, + screen, + waitFor, +} from '@mui/internal-test-utils'; import { createRenderer, describeConformance, isJSDOM } from '#test-utils'; import { useDialogRootContext } from '../../dialog/root/DialogRootContext'; import { useDrawerProviderContext } from '../provider/DrawerProviderContext'; @@ -3173,11 +3180,21 @@ describe('', () => { const originalElementFromPoint = document.elementFromPoint; document.elementFromPoint = () => popup; + function firePointer( + type: 'pointerDown' | 'pointerMove' | 'pointerUp', + options: PointerEventInit & { timeStamp: number }, + ) { + const event = createEvent[type](viewport, options); + // Event constructors ignore timeStamp, so assign it explicitly to pin the swipe velocity. + Object.defineProperty(event, 'timeStamp', { value: options.timeStamp }); + fireEvent(viewport, event); + } + try { // A mostly horizontal flick: cumulative |deltaX| stays above |deltaY| on every // move so no swipe direction is ever attributed, while the final samples carry // fast downward velocity from the finger arcing down at lift. - fireEvent.pointerDown(viewport, { + firePointer('pointerDown', { button: 0, buttons: 1, pointerId: 1, @@ -3187,7 +3204,7 @@ describe('', () => { timeStamp: 1000, }); vi.setSystemTime(new Date(1050)); - fireEvent.pointerMove(viewport, { + firePointer('pointerMove', { buttons: 1, pointerId: 1, clientX: 120, @@ -3196,7 +3213,7 @@ describe('', () => { timeStamp: 1050, }); vi.setSystemTime(new Date(1100)); - fireEvent.pointerMove(viewport, { + firePointer('pointerMove', { buttons: 1, pointerId: 1, clientX: 200, @@ -3205,7 +3222,7 @@ describe('', () => { timeStamp: 1100, }); vi.setSystemTime(new Date(1120)); - fireEvent.pointerMove(viewport, { + firePointer('pointerMove', { buttons: 1, pointerId: 1, clientX: 240, @@ -3214,7 +3231,7 @@ describe('', () => { timeStamp: 1120, }); vi.setSystemTime(new Date(1130)); - fireEvent.pointerUp(viewport, { + firePointer('pointerUp', { pointerId: 1, clientX: 240, clientY: 55, diff --git a/packages/react/src/drawer/viewport/DrawerViewport.tsx b/packages/react/src/drawer/viewport/DrawerViewport.tsx index bedb255cf51..d8ef2636de9 100644 --- a/packages/react/src/drawer/viewport/DrawerViewport.tsx +++ b/packages/react/src/drawer/viewport/DrawerViewport.tsx @@ -8,11 +8,7 @@ import { useAnimationFrame } from '@base-ui/utils/useAnimationFrame'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { clamp } from '@base-ui/utils/clamp'; -import { - useDialogRootContext, - DialogOpenContext, - DialogMountedContext, -} from '../../dialog/root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../../dialog/root/DialogRootContext'; import { DialogViewport } from '../../dialog/viewport/DialogViewport'; import { mergeProps } from '../../merge-props'; import { useDrawerRootContext } from '../root/DrawerRootContext'; @@ -106,8 +102,7 @@ export const DrawerViewport = React.forwardRef(function DrawerViewport( popupHeight, } = useDrawerSnapPoints(); - const open = React.useContext(DialogOpenContext); - const mounted = React.useContext(DialogMountedContext); + const { open, mounted } = React.useContext(DialogLifecycleContext); const nested = store.useState('nested'); const nestedOpenDrawerCount = store.useState('nestedOpenDrawerCount'); const viewportElement = store.useState('viewportElement'); diff --git a/packages/react/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsx b/packages/react/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsx index e90b650d960..cc8bc64cec6 100644 --- a/packages/react/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsx +++ b/packages/react/src/drawer/virtual-keyboard-provider/DrawerVirtualKeyboardProvider.tsx @@ -7,11 +7,7 @@ import { useAnimationFrame } from '@base-ui/utils/useAnimationFrame'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { clamp } from '@base-ui/utils/clamp'; import { useTimeout } from '@base-ui/utils/useTimeout'; -import { - useDialogRootContext, - DialogOpenContext, - DialogMountedContext, -} from '../../dialog/root/DialogRootContext'; +import { useDialogRootContext, DialogLifecycleContext } from '../../dialog/root/DialogRootContext'; import { activeElement, contains, @@ -91,8 +87,7 @@ export function DrawerVirtualKeyboardProvider(props: DrawerVirtualKeyboardProvid const store = useDialogRootContext(); - const open = React.useContext(DialogOpenContext); - const mounted = React.useContext(DialogMountedContext); + const { open, mounted } = React.useContext(DialogLifecycleContext); const modal = store.useState('modal'); const nestedOpenDialogCount = store.useState('nestedOpenDialogCount'); const viewportElement = store.useState('viewportElement'); diff --git a/packages/react/src/menu/arrow/MenuArrow.tsx b/packages/react/src/menu/arrow/MenuArrow.tsx index ac2c13f0e48..01aff8a4a7b 100644 --- a/packages/react/src/menu/arrow/MenuArrow.tsx +++ b/packages/react/src/menu/arrow/MenuArrow.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { useMenuPositionerContext } from '../positioner/MenuPositionerContext'; -import { useMenuRootContext, MenuOpenContext } from '../root/MenuRootContext'; +import { useMenuRootContext, MenuLifecycleContext } from '../root/MenuRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; @@ -21,7 +21,7 @@ export const MenuArrow = React.forwardRef(function MenuArrow( useMenuRootContext(); const { arrowRef, side, align, arrowUncentered, arrowStyles } = useMenuPositionerContext(); - const open = React.useContext(MenuOpenContext); + const { open } = React.useContext(MenuLifecycleContext); const state: MenuArrowState = { open, diff --git a/packages/react/src/menu/backdrop/MenuBackdrop.tsx b/packages/react/src/menu/backdrop/MenuBackdrop.tsx index 8968f434bd8..3532e09321c 100644 --- a/packages/react/src/menu/backdrop/MenuBackdrop.tsx +++ b/packages/react/src/menu/backdrop/MenuBackdrop.tsx @@ -1,11 +1,6 @@ 'use client'; import * as React from 'react'; -import { - useMenuRootContext, - MenuOpenContext, - MenuMountedContext, - MenuTransitionStatusContext, -} from '../root/MenuRootContext'; +import { useMenuRootContext, MenuLifecycleContext } from '../root/MenuRootContext'; import { useRenderElement } from '../../internals/useRenderElement'; import type { BaseUIComponentProps } from '../../internals/types'; import { popupTransitionStateMapping } from '../../utils/popupStateMapping'; @@ -26,9 +21,7 @@ export const MenuBackdrop = React.forwardRef(function MenuBackdrop( const { render, className, style, ...elementProps } = componentProps; const { store } = useMenuRootContext(); - const open = React.useContext(MenuOpenContext); - const mounted = React.useContext(MenuMountedContext); - const transitionStatus = React.useContext(MenuTransitionStatusContext); + const { open, mounted, transitionStatus } = React.useContext(MenuLifecycleContext); const lastOpenChangeReason = store.useState('lastOpenChangeReason'); const contextMenuContext = useContextMenuRootContext(); diff --git a/packages/react/src/menu/popup/MenuPopup.tsx b/packages/react/src/menu/popup/MenuPopup.tsx index f6e70c972f6..5cc00375357 100644 --- a/packages/react/src/menu/popup/MenuPopup.tsx +++ b/packages/react/src/menu/popup/MenuPopup.tsx @@ -2,12 +2,7 @@ import * as React from 'react'; import type { InteractionType } from '@base-ui/utils/useEnhancedClickHandler'; import { FloatingFocusManager, useHoverFloatingInteraction } from '../../floating-ui-react'; -import { - useMenuRootContext, - MenuOpenContext, - MenuMountedContext, - MenuTransitionStatusContext, -} from '../root/MenuRootContext'; +import { useMenuRootContext, MenuLifecycleContext } from '../root/MenuRootContext'; import type { MenuRoot } from '../root/MenuRoot'; import { useMenuPositionerContext } from '../positioner/MenuPositionerContext'; import { useRenderElement } from '../../internals/useRenderElement'; @@ -38,10 +33,8 @@ export const MenuPopup = React.forwardRef(function MenuPopup( const { side, align } = useMenuPositionerContext(); const insideToolbar = useToolbarRootContext(true) != null; - const open = React.useContext(MenuOpenContext); - const transitionStatus = React.useContext(MenuTransitionStatusContext); + const { open, transitionStatus, mounted } = React.useContext(MenuLifecycleContext); const popupProps = store.useState('popupProps'); - const mounted = React.useContext(MenuMountedContext); const instantType = store.useState('instantType'); const activeTriggerElement = store.useState('activeTriggerElement'); const parent = store.useState('parent'); diff --git a/packages/react/src/menu/portal/MenuPortal.tsx b/packages/react/src/menu/portal/MenuPortal.tsx index c88c53d7614..f0f318c1336 100644 --- a/packages/react/src/menu/portal/MenuPortal.tsx +++ b/packages/react/src/menu/portal/MenuPortal.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { useMenuRootContext, MenuMountedContext } from '../root/MenuRootContext'; +import { useMenuRootContext, MenuLifecycleContext } from '../root/MenuRootContext'; import { MenuPortalContext } from './MenuPortalContext'; /** @@ -19,7 +19,7 @@ export const MenuPortal = React.forwardRef(function MenuPortal( const { keepMounted = false, ...portalProps } = props; const { parent } = useMenuRootContext(); - const mounted = React.useContext(MenuMountedContext); + const { mounted } = React.useContext(MenuLifecycleContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/menu/positioner/MenuPositioner.tsx b/packages/react/src/menu/positioner/MenuPositioner.tsx index 4cdbe780625..2560f1dc681 100644 --- a/packages/react/src/menu/positioner/MenuPositioner.tsx +++ b/packages/react/src/menu/positioner/MenuPositioner.tsx @@ -5,12 +5,7 @@ import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useTimeout } from '@base-ui/utils/useTimeout'; import { FloatingNode } from '../../floating-ui-react'; import { MenuPositionerContext } from './MenuPositionerContext'; -import { - useMenuRootContext, - MenuOpenContext, - MenuMountedContext, - MenuTransitionStatusContext, -} from '../root/MenuRootContext'; +import { useMenuRootContext, MenuLifecycleContext } from '../root/MenuRootContext'; import type { MenuRoot } from '../root/MenuRoot'; import { useAnchorPositioning, @@ -68,12 +63,10 @@ export const MenuPositioner = React.forwardRef(function MenuPositioner( const parent = store.useState('parent'); const floatingRootContext = store.useState('floatingRootContext'); const floatingTreeRoot = store.useState('floatingTreeRoot'); - const mounted = React.useContext(MenuMountedContext); - const open = React.useContext(MenuOpenContext); + const { mounted, open, transitionStatus } = React.useContext(MenuLifecycleContext); const modal = store.useState('modal'); const openMethod = store.useState('openMethod'); const triggerElement = store.useState('activeTriggerElement'); - const transitionStatus = React.useContext(MenuTransitionStatusContext); const positionerElement = store.useState('positionerElement'); const instantType = store.useState('instantType'); const adaptiveOrigin = store.useState('adaptiveOrigin'); diff --git a/packages/react/src/menu/root/MenuRoot.tsx b/packages/react/src/menu/root/MenuRoot.tsx index 8c76639e272..39581c71505 100644 --- a/packages/react/src/menu/root/MenuRoot.tsx +++ b/packages/react/src/menu/root/MenuRoot.tsx @@ -16,13 +16,7 @@ import { useTypeahead, useSyncedFloatingRootContext, } from '../../floating-ui-react'; -import { - MenuRootContext, - useMenuRootContext, - MenuOpenContext, - MenuMountedContext, - MenuTransitionStatusContext, -} from './MenuRootContext'; +import { MenuRootContext, useMenuRootContext, MenuLifecycleContext } from './MenuRootContext'; import { MenubarContext, useMenubarContext } from '../../menubar/MenubarContext'; import { TYPEAHEAD_RESET_MS } from '../../internals/constants'; import { useDirection } from '../../internals/direction-context/DirectionContext'; @@ -637,16 +631,17 @@ export const MenuRoot = fastComponent(function MenuRoot(props: MenuRoot [store, parentFromContext], ); + const lifecycle = React.useMemo( + () => ({ open, mounted, transitionStatus }), + [open, mounted, transitionStatus], + ); + const content = ( - - - - {handle && } - {typeof children === 'function' ? children({ payload }) : children} - - - + + {handle && } + {typeof children === 'function' ? children({ payload }) : children} + ); diff --git a/packages/react/src/menu/root/MenuRootContext.ts b/packages/react/src/menu/root/MenuRootContext.ts index b66c2f49f15..e354466f38f 100644 --- a/packages/react/src/menu/root/MenuRootContext.ts +++ b/packages/react/src/menu/root/MenuRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { TransitionStatus } from '../../internals/useTransitionStatus'; +import type { PopupLifecycleState } from '../../utils/popups/store'; import { type MenuStore } from '../store/MenuStore'; import { MenuParent } from './MenuRoot'; @@ -24,7 +24,8 @@ export function useMenuRootContext(optional?: boolean) { return context; } -// Separate contexts preserve subscriptions to individual lifecycle values. -export const MenuOpenContext = React.createContext(false); -export const MenuMountedContext = React.createContext(false); -export const MenuTransitionStatusContext = React.createContext(undefined); +export const MenuLifecycleContext = React.createContext({ + open: false, + mounted: false, + transitionStatus: undefined, +}); diff --git a/packages/react/src/popover/arrow/PopoverArrow.tsx b/packages/react/src/popover/arrow/PopoverArrow.tsx index 29f701825c7..7bc9b534671 100644 --- a/packages/react/src/popover/arrow/PopoverArrow.tsx +++ b/packages/react/src/popover/arrow/PopoverArrow.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { usePopoverPositionerContext } from '../positioner/PopoverPositionerContext'; -import { usePopoverRootContext, PopoverOpenContext } from '../root/PopoverRootContext'; +import { usePopoverRootContext, PopoverLifecycleContext } from '../root/PopoverRootContext'; import type { Align, Side } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; import { popupStateMapping } from '../../utils/popupStateMapping'; @@ -20,7 +20,7 @@ export const PopoverArrow = React.forwardRef(function PopoverArrow( const { render, className, style, ...elementProps } = componentProps; usePopoverRootContext(); - const open = React.useContext(PopoverOpenContext); + const { open } = React.useContext(PopoverLifecycleContext); const { arrowRef, side, align, arrowUncentered, arrowStyles } = usePopoverPositionerContext(); const state: PopoverArrowState = { diff --git a/packages/react/src/popover/backdrop/PopoverBackdrop.tsx b/packages/react/src/popover/backdrop/PopoverBackdrop.tsx index 637a35db953..969c132af03 100644 --- a/packages/react/src/popover/backdrop/PopoverBackdrop.tsx +++ b/packages/react/src/popover/backdrop/PopoverBackdrop.tsx @@ -1,11 +1,6 @@ 'use client'; import * as React from 'react'; -import { - usePopoverRootContext, - PopoverOpenContext, - PopoverMountedContext, - PopoverTransitionStatusContext, -} from '../root/PopoverRootContext'; +import { usePopoverRootContext, PopoverLifecycleContext } from '../root/PopoverRootContext'; import type { BaseUIComponentProps } from '../../internals/types'; import { popupTransitionStateMapping } from '../../utils/popupStateMapping'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; @@ -26,9 +21,7 @@ export const PopoverBackdrop = React.forwardRef(function PopoverBackdrop( const store = usePopoverRootContext(); - const open = React.useContext(PopoverOpenContext); - const mounted = React.useContext(PopoverMountedContext); - const transitionStatus = React.useContext(PopoverTransitionStatusContext); + const { open, mounted, transitionStatus } = React.useContext(PopoverLifecycleContext); const openReason = store.useState('openChangeReason'); const state: PopoverBackdropState = { diff --git a/packages/react/src/popover/popup/PopoverPopup.tsx b/packages/react/src/popover/popup/PopoverPopup.tsx index 15071a65740..fc1cd6334f3 100644 --- a/packages/react/src/popover/popup/PopoverPopup.tsx +++ b/packages/react/src/popover/popup/PopoverPopup.tsx @@ -3,12 +3,7 @@ import * as React from 'react'; import { InteractionType } from '@base-ui/utils/useEnhancedClickHandler'; import { isHTMLElement } from '@floating-ui/utils/dom'; import { FloatingFocusManager, useHoverFloatingInteraction } from '../../floating-ui-react'; -import { - usePopoverRootContext, - PopoverOpenContext, - PopoverMountedContext, - PopoverTransitionStatusContext, -} from '../root/PopoverRootContext'; +import { usePopoverRootContext, PopoverLifecycleContext } from '../root/PopoverRootContext'; import { usePopoverPositionerContext } from '../positioner/PopoverPositionerContext'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; @@ -41,15 +36,13 @@ export const PopoverPopup = React.forwardRef(function PopoverPopup( const insideToolbar = useToolbarRootContext(true) != null; const { context: closePartContext, hasClosePart } = useClosePartCount(); - const open = React.useContext(PopoverOpenContext); + const { open, transitionStatus, mounted } = React.useContext(PopoverLifecycleContext); const openMethod = store.useState('openMethod'); const instantType = store.useState('instantType'); - const transitionStatus = React.useContext(PopoverTransitionStatusContext); const popupProps = store.useState('popupProps'); const titleId = store.useState('titleElementId'); const descriptionId = store.useState('descriptionElementId'); const modal = store.useState('modal'); - const mounted = React.useContext(PopoverMountedContext); const openReason = store.useState('openChangeReason'); const activeTriggerElement = store.useState('activeTriggerElement'); const floatingContext = store.useState('floatingRootContext'); diff --git a/packages/react/src/popover/portal/PopoverPortal.tsx b/packages/react/src/popover/portal/PopoverPortal.tsx index 05c069bdf4e..b25b20e576e 100644 --- a/packages/react/src/popover/portal/PopoverPortal.tsx +++ b/packages/react/src/popover/portal/PopoverPortal.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { usePopoverRootContext, PopoverMountedContext } from '../root/PopoverRootContext'; +import { usePopoverRootContext, PopoverLifecycleContext } from '../root/PopoverRootContext'; import { PopoverPortalContext } from './PopoverPortalContext'; /** @@ -19,7 +19,7 @@ export const PopoverPortal = React.forwardRef(function PopoverPortal( const { keepMounted = false, ...portalProps } = props; usePopoverRootContext(); - const mounted = React.useContext(PopoverMountedContext); + const { mounted } = React.useContext(PopoverLifecycleContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/popover/positioner/PopoverPositioner.tsx b/packages/react/src/popover/positioner/PopoverPositioner.tsx index 5f4b1b3a4ec..f58b887fe54 100644 --- a/packages/react/src/popover/positioner/PopoverPositioner.tsx +++ b/packages/react/src/popover/positioner/PopoverPositioner.tsx @@ -3,12 +3,7 @@ import * as React from 'react'; import { inertValue } from '@base-ui/utils/inertValue'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { FloatingNode, useFloatingNodeId } from '../../floating-ui-react'; -import { - usePopoverRootContext, - PopoverOpenContext, - PopoverMountedContext, - PopoverTransitionStatusContext, -} from '../root/PopoverRootContext'; +import { usePopoverRootContext, PopoverLifecycleContext } from '../root/PopoverRootContext'; import { PopoverPositionerContext } from './PopoverPositionerContext'; import { useAnchorPositioning, @@ -61,15 +56,13 @@ export const PopoverPositioner = React.forwardRef(function PopoverPositioner( const nodeId = useFloatingNodeId(); const floatingRootContext = store.useState('floatingRootContext'); - const mounted = React.useContext(PopoverMountedContext); - const open = React.useContext(PopoverOpenContext); + const { mounted, open, transitionStatus } = React.useContext(PopoverLifecycleContext); const openReason = store.useState('openChangeReason'); const triggerElement = store.useState('activeTriggerElement'); const modal = store.useState('modal'); const openMethod = store.useState('openMethod'); const positionerElement = store.useState('positionerElement'); const instantType = store.useState('instantType'); - const transitionStatus = React.useContext(PopoverTransitionStatusContext); const adaptiveOrigin = store.useState('adaptiveOrigin'); const prevTriggerElementRef = React.useRef(null); diff --git a/packages/react/src/popover/root/PopoverRoot.tsx b/packages/react/src/popover/root/PopoverRoot.tsx index 2cb90850887..7b4b114dadf 100644 --- a/packages/react/src/popover/root/PopoverRoot.tsx +++ b/packages/react/src/popover/root/PopoverRoot.tsx @@ -5,9 +5,7 @@ import { useDismiss, FloatingTree } from '../../floating-ui-react'; import { PopoverRootContext, usePopoverRootContext, - PopoverOpenContext, - PopoverMountedContext, - PopoverTransitionStatusContext, + PopoverLifecycleContext, } from './PopoverRootContext'; import { PopoverStore, type State as PopoverStoreState } from '../store/PopoverStore'; import { PopoverHandle } from '../store/PopoverHandle'; @@ -90,17 +88,18 @@ const PopoverRootComponent = fastComponent(function PopoverRootComponent ({ open, mounted, transitionStatus }), + [open, mounted, transitionStatus], + ); + return ( }> - - - - {handle && } - {shouldRenderInteractions && } - {typeof children === 'function' ? children({ payload }) : children} - - - + + {handle && } + {shouldRenderInteractions && } + {typeof children === 'function' ? children({ payload }) : children} + ); }); diff --git a/packages/react/src/popover/root/PopoverRootContext.ts b/packages/react/src/popover/root/PopoverRootContext.ts index bd1dabb7350..93bb4781909 100644 --- a/packages/react/src/popover/root/PopoverRootContext.ts +++ b/packages/react/src/popover/root/PopoverRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { TransitionStatus } from '../../internals/useTransitionStatus'; +import type { PopupLifecycleState } from '../../utils/popups/store'; import type { PopoverStore } from '../store/PopoverStore'; export type PopoverRootContext = PopoverStore; @@ -19,8 +19,8 @@ export function usePopoverRootContext(optional?: boolean) { return context; } -// Separate contexts keep consumers of `mounted` from rerendering when only -// `transitionStatus` changes. -export const PopoverOpenContext = React.createContext(false); -export const PopoverMountedContext = React.createContext(false); -export const PopoverTransitionStatusContext = React.createContext(undefined); +export const PopoverLifecycleContext = React.createContext({ + open: false, + mounted: false, + transitionStatus: undefined, +}); diff --git a/packages/react/src/preview-card/arrow/PreviewCardArrow.tsx b/packages/react/src/preview-card/arrow/PreviewCardArrow.tsx index 869dfc9edaa..c0e8ee1ee36 100644 --- a/packages/react/src/preview-card/arrow/PreviewCardArrow.tsx +++ b/packages/react/src/preview-card/arrow/PreviewCardArrow.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { usePreviewCardPositionerContext } from '../positioner/PreviewCardPositionerContext'; -import { usePreviewCardRootContext, PreviewCardOpenContext } from '../root/PreviewCardContext'; +import { usePreviewCardRootContext, PreviewCardLifecycleContext } from '../root/PreviewCardContext'; import type { BaseUIComponentProps } from '../../internals/types'; import type { Align, Side } from '../../internals/useAnchorPositioning'; import { popupStateMapping } from '../../utils/popupStateMapping'; @@ -22,7 +22,7 @@ export const PreviewCardArrow = React.forwardRef(function PreviewCardArrow( usePreviewCardRootContext(); const { arrowRef, side, align, arrowUncentered, arrowStyles } = usePreviewCardPositionerContext(); - const open = React.useContext(PreviewCardOpenContext); + const { open } = React.useContext(PreviewCardLifecycleContext); const state: PreviewCardArrowState = { open, diff --git a/packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx b/packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx index 0ef1880fd32..3da33b49fc4 100644 --- a/packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx +++ b/packages/react/src/preview-card/backdrop/PreviewCardBackdrop.tsx @@ -1,11 +1,6 @@ 'use client'; import * as React from 'react'; -import { - usePreviewCardRootContext, - PreviewCardOpenContext, - PreviewCardMountedContext, - PreviewCardTransitionStatusContext, -} from '../root/PreviewCardContext'; +import { usePreviewCardRootContext, PreviewCardLifecycleContext } from '../root/PreviewCardContext'; import type { BaseUIComponentProps } from '../../internals/types'; import { popupTransitionStateMapping } from '../../utils/popupStateMapping'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; @@ -24,9 +19,7 @@ export const PreviewCardBackdrop = React.forwardRef(function PreviewCardBackdrop const { render, className, style, ...elementProps } = componentProps; usePreviewCardRootContext(); - const open = React.useContext(PreviewCardOpenContext); - const mounted = React.useContext(PreviewCardMountedContext); - const transitionStatus = React.useContext(PreviewCardTransitionStatusContext); + const { open, mounted, transitionStatus } = React.useContext(PreviewCardLifecycleContext); const state: PreviewCardBackdropState = { open, diff --git a/packages/react/src/preview-card/popup/PreviewCardPopup.tsx b/packages/react/src/preview-card/popup/PreviewCardPopup.tsx index e42316cbb97..3a182a56cd5 100644 --- a/packages/react/src/preview-card/popup/PreviewCardPopup.tsx +++ b/packages/react/src/preview-card/popup/PreviewCardPopup.tsx @@ -1,10 +1,6 @@ 'use client'; import * as React from 'react'; -import { - usePreviewCardRootContext, - PreviewCardOpenContext, - PreviewCardTransitionStatusContext, -} from '../root/PreviewCardContext'; +import { usePreviewCardRootContext, PreviewCardLifecycleContext } from '../root/PreviewCardContext'; import { usePreviewCardPositionerContext } from '../positioner/PreviewCardPositionerContext'; import type { Align, Side } from '../../internals/useAnchorPositioning'; import type { BaseUIComponentProps } from '../../internals/types'; @@ -31,9 +27,8 @@ export const PreviewCardPopup = React.forwardRef(function PreviewCardPopup( const store = usePreviewCardRootContext(); const { side, align } = usePreviewCardPositionerContext(); - const open = React.useContext(PreviewCardOpenContext); + const { open, transitionStatus } = React.useContext(PreviewCardLifecycleContext); const instantType = store.useState('instantType'); - const transitionStatus = React.useContext(PreviewCardTransitionStatusContext); const popupProps = store.useState('popupProps'); const floatingContext = store.useState('floatingRootContext'); const closeDelay = store.useState('closeDelay'); diff --git a/packages/react/src/preview-card/portal/PreviewCardPortal.tsx b/packages/react/src/preview-card/portal/PreviewCardPortal.tsx index c92de4d4199..27516d246a5 100644 --- a/packages/react/src/preview-card/portal/PreviewCardPortal.tsx +++ b/packages/react/src/preview-card/portal/PreviewCardPortal.tsx @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { usePreviewCardRootContext, PreviewCardMountedContext } from '../root/PreviewCardContext'; +import { usePreviewCardRootContext, PreviewCardLifecycleContext } from '../root/PreviewCardContext'; import { PreviewCardPortalContext } from './PreviewCardPortalContext'; import { FloatingPortalLite } from '../../utils/FloatingPortalLite'; import { type BaseUIComponentProps } from '../../internals/types'; @@ -19,7 +19,7 @@ export const PreviewCardPortal = React.forwardRef(function PreviewCardPortal( const { keepMounted = false, ...portalProps } = props; usePreviewCardRootContext(); - const mounted = React.useContext(PreviewCardMountedContext); + const { mounted } = React.useContext(PreviewCardLifecycleContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx b/packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx index 700fd048dda..4a0a1eb0e26 100644 --- a/packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx +++ b/packages/react/src/preview-card/positioner/PreviewCardPositioner.tsx @@ -1,12 +1,7 @@ 'use client'; import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; -import { - usePreviewCardRootContext, - PreviewCardOpenContext, - PreviewCardMountedContext, - PreviewCardTransitionStatusContext, -} from '../root/PreviewCardContext'; +import { usePreviewCardRootContext, PreviewCardLifecycleContext } from '../root/PreviewCardContext'; import { PreviewCardPositionerContext } from './PreviewCardPositionerContext'; import { FloatingNode, useFloatingNodeId } from '../../floating-ui-react'; import { @@ -54,11 +49,9 @@ export const PreviewCardPositioner = React.forwardRef(function PreviewCardPositi const keepMounted = usePreviewCardPortalContext(); const nodeId = useFloatingNodeId(); - const open = React.useContext(PreviewCardOpenContext); - const mounted = React.useContext(PreviewCardMountedContext); + const { open, mounted, transitionStatus } = React.useContext(PreviewCardLifecycleContext); const floatingRootContext = store.useState('floatingRootContext'); const instantType = store.useState('instantType'); - const transitionStatus = React.useContext(PreviewCardTransitionStatusContext); const adaptiveOrigin = store.useState('adaptiveOrigin'); const inlineRectCoordsRef = store.context.inlineRectCoordsRef; diff --git a/packages/react/src/preview-card/root/PreviewCardContext.ts b/packages/react/src/preview-card/root/PreviewCardContext.ts index 806af5b22c9..e3a20d8412e 100644 --- a/packages/react/src/preview-card/root/PreviewCardContext.ts +++ b/packages/react/src/preview-card/root/PreviewCardContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { TransitionStatus } from '../../internals/useTransitionStatus'; +import type { PopupLifecycleState } from '../../utils/popups/store'; import { PreviewCardStore } from '../store/PreviewCardStore'; export type PreviewCardRootContext = PreviewCardStore; @@ -22,7 +22,8 @@ export function usePreviewCardRootContext(optional?: boolean) { return context; } -// Separate contexts preserve subscriptions to individual lifecycle values. -export const PreviewCardOpenContext = React.createContext(false); -export const PreviewCardMountedContext = React.createContext(false); -export const PreviewCardTransitionStatusContext = React.createContext(undefined); +export const PreviewCardLifecycleContext = React.createContext({ + open: false, + mounted: false, + transitionStatus: undefined, +}); diff --git a/packages/react/src/preview-card/root/PreviewCardRoot.tsx b/packages/react/src/preview-card/root/PreviewCardRoot.tsx index 46b68e2f7d9..a3cc6163bcd 100644 --- a/packages/react/src/preview-card/root/PreviewCardRoot.tsx +++ b/packages/react/src/preview-card/root/PreviewCardRoot.tsx @@ -6,9 +6,7 @@ import { useDismiss, FloatingTree } from '../../floating-ui-react'; import { PreviewCardRootContext, usePreviewCardRootContext, - PreviewCardOpenContext, - PreviewCardMountedContext, - PreviewCardTransitionStatusContext, + PreviewCardLifecycleContext, } from './PreviewCardContext'; import { createChangeEventDetails, @@ -88,17 +86,18 @@ function PreviewCardRootComponent(props: PreviewCardRoot.Props const shouldRenderInteractions = open || mounted; + const lifecycle = React.useMemo( + () => ({ open, mounted, transitionStatus }), + [open, mounted, transitionStatus], + ); + return ( - - - - {handle && } - {shouldRenderInteractions && } - {typeof children === 'function' ? children({ payload }) : children} - - - + + {handle && } + {shouldRenderInteractions && } + {typeof children === 'function' ? children({ payload }) : children} + ); } diff --git a/packages/react/src/select/arrow/SelectArrow.tsx b/packages/react/src/select/arrow/SelectArrow.tsx index 8025919c94c..d82c27af713 100644 --- a/packages/react/src/select/arrow/SelectArrow.tsx +++ b/packages/react/src/select/arrow/SelectArrow.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import { useSelectPositionerContext } from '../positioner/SelectPositionerContext'; -import { useSelectRootContext, SelectOpenContext } from '../root/SelectRootContext'; +import { useSelectRootContext, SelectLifecycleContext } from '../root/SelectRootContext'; import type { BaseUIComponentProps } from '../../internals/types'; import type { Align, Side } from '../../internals/useAnchorPositioning'; import { popupTransitionStateMapping } from '../../utils/popupStateMapping'; @@ -23,7 +23,7 @@ export const SelectArrow = React.forwardRef(function SelectArrow( const { side, align, arrowRef, arrowStyles, arrowUncentered, alignItemWithTriggerActive } = useSelectPositionerContext(); - const open = React.useContext(SelectOpenContext); + const { open } = React.useContext(SelectLifecycleContext); const state: SelectArrowState = { open, diff --git a/packages/react/src/select/backdrop/SelectBackdrop.tsx b/packages/react/src/select/backdrop/SelectBackdrop.tsx index 3314203898d..28b2960fca4 100644 --- a/packages/react/src/select/backdrop/SelectBackdrop.tsx +++ b/packages/react/src/select/backdrop/SelectBackdrop.tsx @@ -1,12 +1,7 @@ 'use client'; import * as React from 'react'; import type { BaseUIComponentProps } from '../../internals/types'; -import { - useSelectRootContext, - SelectOpenContext, - SelectMountedContext, - SelectTransitionStatusContext, -} from '../root/SelectRootContext'; +import { useSelectRootContext, SelectLifecycleContext } from '../root/SelectRootContext'; import { popupStateMapping } from '../../utils/popupStateMapping'; import type { StateAttributesMapping } from '../../internals/getStateAttributesProps'; import type { TransitionStatus } from '../../internals/useTransitionStatus'; @@ -32,9 +27,7 @@ export const SelectBackdrop = React.forwardRef(function SelectBackdrop( useSelectRootContext(); - const open = React.useContext(SelectOpenContext); - const mounted = React.useContext(SelectMountedContext); - const transitionStatus = React.useContext(SelectTransitionStatusContext); + const { open, mounted, transitionStatus } = React.useContext(SelectLifecycleContext); const state: SelectBackdropState = { open, diff --git a/packages/react/src/select/popup/SelectPopup.tsx b/packages/react/src/select/popup/SelectPopup.tsx index 0f681f29613..0e50841e6b2 100644 --- a/packages/react/src/select/popup/SelectPopup.tsx +++ b/packages/react/src/select/popup/SelectPopup.tsx @@ -13,12 +13,7 @@ import { clamp } from '@base-ui/utils/clamp'; import { FloatingFocusManager, platform as floatingPlatform } from '../../floating-ui-react'; import type { ClientRectObject } from '../../floating-ui-react'; import type { BaseUIComponentProps, HTMLProps } from '../../internals/types'; -import { - useSelectRootContext, - SelectOpenContext, - SelectMountedContext, - SelectTransitionStatusContext, -} from '../root/SelectRootContext'; +import { useSelectRootContext, SelectLifecycleContext } from '../root/SelectRootContext'; import { popupStateMapping } from '../../utils/popupStateMapping'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import type { StateAttributesMapping } from '../../internals/getStateAttributesProps'; @@ -84,11 +79,9 @@ export const SelectPopup = React.forwardRef(function SelectPopup( const { nonce, disableStyleElements } = useCSPContext(); const id = useStore(store, selectors.id); - const open = React.useContext(SelectOpenContext); + const { open, mounted, transitionStatus } = React.useContext(SelectLifecycleContext); const openMethod = useStore(store, selectors.openMethod); - const mounted = React.useContext(SelectMountedContext); const popupProps = useStore(store, selectors.popupProps); - const transitionStatus = React.useContext(SelectTransitionStatusContext); const triggerElement = useStore(store, selectors.triggerElement); const positionerElement = useStore(store, selectors.positionerElement); const listElement = useStore(store, selectors.listElement); diff --git a/packages/react/src/select/portal/SelectPortal.tsx b/packages/react/src/select/portal/SelectPortal.tsx index ec7d84ef994..9be75048f76 100644 --- a/packages/react/src/select/portal/SelectPortal.tsx +++ b/packages/react/src/select/portal/SelectPortal.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useStore } from '@base-ui/utils/store'; import { FloatingPortal } from '../../floating-ui-react'; import { type BaseUIComponentProps } from '../../internals/types'; -import { useSelectRootContext, SelectMountedContext } from '../root/SelectRootContext'; +import { useSelectRootContext, SelectLifecycleContext } from '../root/SelectRootContext'; import { selectors } from '../store'; /** @@ -18,7 +18,7 @@ export const SelectPortal = React.forwardRef(function SelectPortal( forwardedRef: React.ForwardedRef, ) { const { store } = useSelectRootContext(); - const mounted = React.useContext(SelectMountedContext); + const { mounted } = React.useContext(SelectLifecycleContext); const forceMount = useStore(store, selectors.forceMount); const shouldRender = mounted || forceMount; diff --git a/packages/react/src/select/positioner/SelectPositioner.tsx b/packages/react/src/select/positioner/SelectPositioner.tsx index afc0a592498..b6c70c85c3c 100644 --- a/packages/react/src/select/positioner/SelectPositioner.tsx +++ b/packages/react/src/select/positioner/SelectPositioner.tsx @@ -4,12 +4,7 @@ import { inertValue } from '@base-ui/utils/inertValue'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; import { useStore } from '@base-ui/utils/store'; -import { - useSelectRootContext, - SelectOpenContext, - SelectMountedContext, - SelectTransitionStatusContext, -} from '../root/SelectRootContext'; +import { useSelectRootContext, SelectLifecycleContext } from '../root/SelectRootContext'; import { CompositeList } from '../../internals/composite/list/CompositeList'; import type { BaseUIComponentProps } from '../../internals/types'; import { @@ -76,15 +71,13 @@ export const SelectPositioner = React.forwardRef(function SelectPositioner( floatingContext: floatingRootContext, } = useSelectRootContext(); - const open = React.useContext(SelectOpenContext); - const mounted = React.useContext(SelectMountedContext); + const { open, mounted, transitionStatus } = React.useContext(SelectLifecycleContext); const modal = useStore(store, selectors.modal); const value = useStore(store, selectors.value); const openMethod = useStore(store, selectors.openMethod); const positionerElement = useStore(store, selectors.positionerElement); const triggerElement = useStore(store, selectors.triggerElement); const isItemEqualToValue = useStore(store, selectors.isItemEqualToValue); - const transitionStatus = React.useContext(SelectTransitionStatusContext); const scrollUpArrowRef = React.useRef(null); const scrollDownArrowRef = React.useRef(null); diff --git a/packages/react/src/select/root/SelectRoot.tsx b/packages/react/src/select/root/SelectRoot.tsx index 944e1b8cadd..a14e59868b3 100644 --- a/packages/react/src/select/root/SelectRoot.tsx +++ b/packages/react/src/select/root/SelectRoot.tsx @@ -19,12 +19,7 @@ import { useListNavigation, useTypeahead, } from '../../floating-ui-react'; -import { - SelectRootContext, - SelectOpenContext, - SelectMountedContext, - SelectTransitionStatusContext, -} from './SelectRootContext'; +import { SelectRootContext, SelectLifecycleContext } from './SelectRootContext'; import { useFieldRootContext } from '../../internals/field-root-context/FieldRootContext'; import { useRegisterFieldControl } from '../../internals/field-register-control/useRegisterFieldControl'; import { useLabelableId } from '../../internals/labelable-provider/useLabelableId'; @@ -501,15 +496,16 @@ export function SelectRoot( }); }, [multiple, value, form, name, itemToStringValue, disabled]); + const lifecycle = React.useMemo( + () => ({ open, mounted, transitionStatus }), + [open, mounted, transitionStatus], + ); + return ( - - - - {children} - - - + + {children} + (undefined); +export const SelectLifecycleContext = React.createContext({ + open: false, + mounted: false, + transitionStatus: undefined, +}); diff --git a/packages/react/src/tooltip/arrow/TooltipArrow.tsx b/packages/react/src/tooltip/arrow/TooltipArrow.tsx index cb728ad396b..d33a51ceaae 100644 --- a/packages/react/src/tooltip/arrow/TooltipArrow.tsx +++ b/packages/react/src/tooltip/arrow/TooltipArrow.tsx @@ -5,7 +5,7 @@ import type { BaseUIComponentProps } from '../../internals/types'; import type { Side, Align } from '../../internals/useAnchorPositioning'; import { popupStateMapping } from '../../utils/popupStateMapping'; import { useRenderElement } from '../../internals/useRenderElement'; -import { useTooltipRootContext, TooltipOpenContext } from '../root/TooltipRootContext'; +import { useTooltipRootContext, TooltipLifecycleContext } from '../root/TooltipRootContext'; /** * Displays an element positioned against the tooltip anchor. @@ -22,7 +22,7 @@ export const TooltipArrow = React.forwardRef(function TooltipArrow( const store = useTooltipRootContext(); const { arrowRef, side, align, arrowUncentered, arrowStyles } = useTooltipPositionerContext(); - const open = React.useContext(TooltipOpenContext); + const { open } = React.useContext(TooltipLifecycleContext); const instantType = store.useState('instantType'); const state: TooltipArrowState = { diff --git a/packages/react/src/tooltip/popup/TooltipPopup.tsx b/packages/react/src/tooltip/popup/TooltipPopup.tsx index c11df35cebe..4405119c69e 100644 --- a/packages/react/src/tooltip/popup/TooltipPopup.tsx +++ b/packages/react/src/tooltip/popup/TooltipPopup.tsx @@ -1,10 +1,6 @@ 'use client'; import * as React from 'react'; -import { - useTooltipRootContext, - TooltipOpenContext, - TooltipTransitionStatusContext, -} from '../root/TooltipRootContext'; +import { useTooltipRootContext, TooltipLifecycleContext } from '../root/TooltipRootContext'; import { useTooltipPositionerContext } from '../positioner/TooltipPositionerContext'; import type { BaseUIComponentProps } from '../../internals/types'; import type { Align, Side } from '../../internals/useAnchorPositioning'; @@ -31,9 +27,8 @@ export const TooltipPopup = React.forwardRef(function TooltipPopup( const store = useTooltipRootContext(); const { side, align } = useTooltipPositionerContext(); - const open = React.useContext(TooltipOpenContext); + const { open, transitionStatus } = React.useContext(TooltipLifecycleContext); const instantType = store.useState('instantType'); - const transitionStatus = React.useContext(TooltipTransitionStatusContext); const popupProps = store.useState('popupProps'); const floatingContext = store.useState('floatingRootContext'); const disabled = store.useState('disabled'); diff --git a/packages/react/src/tooltip/portal/TooltipPortal.tsx b/packages/react/src/tooltip/portal/TooltipPortal.tsx index e8ce9ac7c9a..7da518b4d1f 100644 --- a/packages/react/src/tooltip/portal/TooltipPortal.tsx +++ b/packages/react/src/tooltip/portal/TooltipPortal.tsx @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import { useTooltipRootContext, TooltipMountedContext } from '../root/TooltipRootContext'; +import { useTooltipRootContext, TooltipLifecycleContext } from '../root/TooltipRootContext'; import { TooltipPortalContext } from './TooltipPortalContext'; import { FloatingPortalLite } from '../../utils/FloatingPortalLite'; import { type BaseUIComponentProps } from '../../internals/types'; @@ -19,7 +19,7 @@ export const TooltipPortal = React.forwardRef(function TooltipPortal( const { keepMounted = false, ...portalProps } = props; useTooltipRootContext(); - const mounted = React.useContext(TooltipMountedContext); + const { mounted } = React.useContext(TooltipLifecycleContext); const shouldRender = mounted || keepMounted; if (!shouldRender) { diff --git a/packages/react/src/tooltip/positioner/TooltipPositioner.tsx b/packages/react/src/tooltip/positioner/TooltipPositioner.tsx index f8f9c7496ba..f65778140f7 100644 --- a/packages/react/src/tooltip/positioner/TooltipPositioner.tsx +++ b/packages/react/src/tooltip/positioner/TooltipPositioner.tsx @@ -1,11 +1,6 @@ 'use client'; import * as React from 'react'; -import { - useTooltipRootContext, - TooltipOpenContext, - TooltipMountedContext, - TooltipTransitionStatusContext, -} from '../root/TooltipRootContext'; +import { useTooltipRootContext, TooltipLifecycleContext } from '../root/TooltipRootContext'; import { TooltipPositionerContext } from './TooltipPositionerContext'; import { useAnchorPositioning, @@ -50,13 +45,11 @@ export const TooltipPositioner = React.forwardRef(function TooltipPositioner( const store = useTooltipRootContext(); const keepMounted = useTooltipPortalContext(); - const open = React.useContext(TooltipOpenContext); - const mounted = React.useContext(TooltipMountedContext); + const { open, mounted, transitionStatus } = React.useContext(TooltipLifecycleContext); const trackCursorAxis = store.useState('trackCursorAxis'); const disableHoverablePopup = store.useState('disableHoverablePopup'); const floatingRootContext = store.useState('floatingRootContext'); const instantType = store.useState('instantType'); - const transitionStatus = React.useContext(TooltipTransitionStatusContext); const adaptiveOrigin = store.useState('adaptiveOrigin'); const positioning = useAnchorPositioning({ diff --git a/packages/react/src/tooltip/root/TooltipRoot.tsx b/packages/react/src/tooltip/root/TooltipRoot.tsx index ba947f34723..bb6605ea396 100644 --- a/packages/react/src/tooltip/root/TooltipRoot.tsx +++ b/packages/react/src/tooltip/root/TooltipRoot.tsx @@ -3,12 +3,7 @@ import * as React from 'react'; import { fastComponent } from '@base-ui/utils/fastHooks'; import { EMPTY_OBJECT } from '@base-ui/utils/empty'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; -import { - TooltipRootContext, - TooltipOpenContext, - TooltipMountedContext, - TooltipTransitionStatusContext, -} from './TooltipRootContext'; +import { TooltipRootContext, TooltipLifecycleContext } from './TooltipRootContext'; import { useClientPoint, useDismiss } from '../../floating-ui-react'; import { type BaseUIChangeEventDetails, @@ -141,23 +136,24 @@ export const TooltipRoot = fastComponent(function TooltipRoot( const shouldRenderInteractions = open || mounted || (!disabled && trackCursorAxis !== 'none'); + const lifecycle = React.useMemo( + () => ({ open, mounted, transitionStatus }), + [open, mounted, transitionStatus], + ); + return ( - - - - {handle && } - {shouldRenderInteractions && ( - - )} - {typeof children === 'function' ? children({ payload }) : children} - - - + + {handle && } + {shouldRenderInteractions && ( + + )} + {typeof children === 'function' ? children({ payload }) : children} + ); }); diff --git a/packages/react/src/tooltip/root/TooltipRootContext.ts b/packages/react/src/tooltip/root/TooltipRootContext.ts index 8f61867ce44..07e2f942b26 100644 --- a/packages/react/src/tooltip/root/TooltipRootContext.ts +++ b/packages/react/src/tooltip/root/TooltipRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { TransitionStatus } from '../../internals/useTransitionStatus'; +import type { PopupLifecycleState } from '../../utils/popups/store'; import { TooltipStore } from '../store/TooltipStore'; export type TooltipRootContext = TooltipStore; @@ -20,7 +20,8 @@ export function useTooltipRootContext(optional?: boolean) { return context; } -// Separate contexts preserve subscriptions to individual lifecycle values. -export const TooltipOpenContext = React.createContext(false); -export const TooltipMountedContext = React.createContext(false); -export const TooltipTransitionStatusContext = React.createContext(undefined); +export const TooltipLifecycleContext = React.createContext({ + open: false, + mounted: false, + transitionStatus: undefined, +}); diff --git a/packages/react/src/utils/popups/store.ts b/packages/react/src/utils/popups/store.ts index 547d7644f8c..7a883806d04 100644 --- a/packages/react/src/utils/popups/store.ts +++ b/packages/react/src/utils/popups/store.ts @@ -80,6 +80,12 @@ export type PopupStoreState = { popupProps: HTMLProps; }; +// Render this state through React context; keep the store copy for detached triggers and interactions. +export type PopupLifecycleState = Pick< + PopupStoreState, + 'open' | 'mounted' | 'transitionStatus' +>; + export function createInitialPopupStoreState( triggerElements: PopupTriggerMap, floatingId?: string | undefined, From e10ad10f426a3987b68ae939b968f78ab01985bb Mon Sep 17 00:00:00 2001 From: atomiks Date: Tue, 15 Sep 2026 19:35:59 +1000 Subject: [PATCH 6/7] [docs] Correct popover view transition guidance --- docs/src/app/(docs)/react/handbook/animation/page.mdx | 5 ++--- .../src/app/(private)/experiments/dialog/view-transition.tsx | 3 +-- .../app/(private)/experiments/popover/view-transition.tsx | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/src/app/(docs)/react/handbook/animation/page.mdx b/docs/src/app/(docs)/react/handbook/animation/page.mdx index 11231972d3f..ed1efc6b56c 100644 --- a/docs/src/app/(docs)/react/handbook/animation/page.mdx +++ b/docs/src/app/(docs)/react/handbook/animation/page.mdx @@ -249,11 +249,10 @@ import { DemoViewTransitionList } from './demos/view-transition-list'; ### Popups -Dialog can animate with `` when `open` is controlled inside `startTransition`, `` stays rendered, and the popup and its View Transition boundary are rendered conditionally from `open`. +Dialog and Popover can animate with `` when `open` is controlled inside `startTransition`, the portal uses `keepMounted`, and the popup and its View Transition boundary are rendered conditionally from `open`. Keeping the portal mounted lets its DOM container exist before the opening Transition. -Anchored popups such as Popover, Menu, and Select still calculate their position after the opening commit, so their opening snapshot can be invisible or incorrectly positioned. -Use the CSS transition and animation attributes described above to animate these popups. +For Popover, place `` inside the `` boundary so the positioning element participates in the transition. export const metadata = { keywords: [ diff --git a/docs/src/app/(private)/experiments/dialog/view-transition.tsx b/docs/src/app/(private)/experiments/dialog/view-transition.tsx index ba0c3d98ddf..866ed27f53d 100644 --- a/docs/src/app/(private)/experiments/dialog/view-transition.tsx +++ b/docs/src/app/(private)/experiments/dialog/view-transition.tsx @@ -9,8 +9,7 @@ import './view-transition.css'; * Run with React 19.3 to compare dialog and anchored popup snapshots. * Keep the portal node mounted, and insert/remove the ViewTransition boundary * in the same React Transition as `open` instead of waiting for CSS exit animations. - * Dialog captures the shared cover at both sizes. Popover still captures an invisible - * positioner until its asynchronous positioning completes. + * Both examples animate the shared cover between the trigger and the popup. */ export default function ViewTransitionExperiment() { if (!React.ViewTransition) { diff --git a/docs/src/app/(private)/experiments/popover/view-transition.tsx b/docs/src/app/(private)/experiments/popover/view-transition.tsx index e6f9c231664..34129a2c419 100644 --- a/docs/src/app/(private)/experiments/popover/view-transition.tsx +++ b/docs/src/app/(private)/experiments/popover/view-transition.tsx @@ -18,9 +18,8 @@ import './view-transition.css'; * What to observe (Chrome, DevTools > Animations helps): * - Closing animates: the popup plays `pop-out`, and in the shared-element example the cover * morphs back into the thumbnail in the trigger. - * - Opening still does not animate correctly: lifecycle state now reaches the positioner in - * the opening commit, but its asynchronous positioning has not finished. The snapshot can - * capture the positioner at (0, 0) with opacity: 0. + * - Opening animates: the popup plays `pop-in`, and in the shared-element example the + * thumbnail morphs into the cover at the popup's anchored position. * - Compare the Dialog example at /experiments/dialog/view-transition, which does not need * anchor positioning. */ From 2a086f390a6674f52b8e71b42f5180f1b5940ec5 Mon Sep 17 00:00:00 2001 From: atomiks Date: Wed, 16 Sep 2026 09:42:36 +1000 Subject: [PATCH 7/7] [all components] Simplify popup lifecycle wiring --- .../src/combobox/root/ComboboxRootContext.tsx | 8 ++---- .../src/dialog/root/DialogRootContext.ts | 8 ++---- .../src/dialog/root/useRenderDialogRoot.tsx | 8 ++---- packages/react/src/menu/root/MenuRoot.tsx | 8 ++---- .../react/src/menu/root/MenuRootContext.ts | 8 ++---- .../react/src/popover/root/PopoverRoot.tsx | 8 ++---- .../src/popover/root/PopoverRootContext.ts | 8 ++---- .../preview-card/root/PreviewCardContext.ts | 8 ++---- .../src/preview-card/root/PreviewCardRoot.tsx | 8 ++---- .../src/select/root/SelectRootContext.ts | 8 ++---- .../react/src/tooltip/root/TooltipRoot.tsx | 8 ++---- .../src/tooltip/root/TooltipRootContext.ts | 8 ++---- .../src/utils/popups/popupLifecycleState.ts | 14 ++++++++++ .../react/src/utils/popups/popupStoreUtils.ts | 5 +++- packages/react/src/utils/popups/store.ts | 6 ----- .../src/utils/popups/usePopupCloseComplete.ts | 26 +++++++------------ 16 files changed, 51 insertions(+), 96 deletions(-) create mode 100644 packages/react/src/utils/popups/popupLifecycleState.ts diff --git a/packages/react/src/combobox/root/ComboboxRootContext.tsx b/packages/react/src/combobox/root/ComboboxRootContext.tsx index 155e13695c6..057c6b32e89 100644 --- a/packages/react/src/combobox/root/ComboboxRootContext.tsx +++ b/packages/react/src/combobox/root/ComboboxRootContext.tsx @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { PopupLifecycleState } from '../../utils/popups/store'; +import { DEFAULT_POPUP_LIFECYCLE_STATE } from '../../utils/popups/popupLifecycleState'; import { ComboboxStore } from '../store'; import type { FloatingRootContext } from '../../floating-ui-react'; @@ -66,8 +66,4 @@ export function useComboboxHasItemsContext() { return React.useContext(ComboboxHasItemsContext); } -export const ComboboxLifecycleContext = React.createContext({ - open: false, - mounted: false, - transitionStatus: undefined, -}); +export const ComboboxLifecycleContext = React.createContext(DEFAULT_POPUP_LIFECYCLE_STATE); diff --git a/packages/react/src/dialog/root/DialogRootContext.ts b/packages/react/src/dialog/root/DialogRootContext.ts index 041724c6d3d..b4a2a39542e 100644 --- a/packages/react/src/dialog/root/DialogRootContext.ts +++ b/packages/react/src/dialog/root/DialogRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { PopupLifecycleState } from '../../utils/popups/store'; +import { DEFAULT_POPUP_LIFECYCLE_STATE } from '../../utils/popups/popupLifecycleState'; import { DialogStore } from '../store/DialogStore'; export const DialogRootContext = React.createContext | undefined>(undefined); @@ -19,8 +19,4 @@ export function useDialogRootContext(optional?: boolean) { return store; } -export const DialogLifecycleContext = React.createContext({ - open: false, - mounted: false, - transitionStatus: undefined, -}); +export const DialogLifecycleContext = React.createContext(DEFAULT_POPUP_LIFECYCLE_STATE); diff --git a/packages/react/src/dialog/root/useRenderDialogRoot.tsx b/packages/react/src/dialog/root/useRenderDialogRoot.tsx index 9218e1ed66b..17c866cb3c3 100644 --- a/packages/react/src/dialog/root/useRenderDialogRoot.tsx +++ b/packages/react/src/dialog/root/useRenderDialogRoot.tsx @@ -81,7 +81,8 @@ export function useRenderDialogRoot( usePopupRootSync(store, open); useImplicitActiveTrigger(store); - const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions(open, store); + const lifecycle = useOpenStateTransitions(open, store); + const { forceUnmount, mounted } = lifecycle; React.useImperativeHandle( actionsRef, @@ -94,11 +95,6 @@ export function useRenderDialogRoot( const shouldRenderInteractions = open || mounted; - const lifecycle = React.useMemo( - () => ({ open, mounted, transitionStatus }), - [open, mounted, transitionStatus], - ); - return ( }> diff --git a/packages/react/src/menu/root/MenuRoot.tsx b/packages/react/src/menu/root/MenuRoot.tsx index d2b98ebd2a2..3ff1a5cc95c 100644 --- a/packages/react/src/menu/root/MenuRoot.tsx +++ b/packages/react/src/menu/root/MenuRoot.tsx @@ -194,7 +194,7 @@ export const MenuRoot = fastComponent(function MenuRoot(props: MenuRoot }); useImplicitActiveTrigger(store); - const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions( + const lifecycle = useOpenStateTransitions( open, store, () => { @@ -202,6 +202,7 @@ export const MenuRoot = fastComponent(function MenuRoot(props: MenuRoot }, animateInitialOpen, ); + const { forceUnmount, transitionStatus } = lifecycle; const runOnceAnimationsFinish = useAnimationsFinished(store.context.popupRef); @@ -637,11 +638,6 @@ export const MenuRoot = fastComponent(function MenuRoot(props: MenuRoot [store, parentFromContext], ); - const lifecycle = React.useMemo( - () => ({ open, mounted, transitionStatus }), - [open, mounted, transitionStatus], - ); - const content = ( diff --git a/packages/react/src/menu/root/MenuRootContext.ts b/packages/react/src/menu/root/MenuRootContext.ts index e354466f38f..49480e37808 100644 --- a/packages/react/src/menu/root/MenuRootContext.ts +++ b/packages/react/src/menu/root/MenuRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { PopupLifecycleState } from '../../utils/popups/store'; +import { DEFAULT_POPUP_LIFECYCLE_STATE } from '../../utils/popups/popupLifecycleState'; import { type MenuStore } from '../store/MenuStore'; import { MenuParent } from './MenuRoot'; @@ -24,8 +24,4 @@ export function useMenuRootContext(optional?: boolean) { return context; } -export const MenuLifecycleContext = React.createContext({ - open: false, - mounted: false, - transitionStatus: undefined, -}); +export const MenuLifecycleContext = React.createContext(DEFAULT_POPUP_LIFECYCLE_STATE); diff --git a/packages/react/src/popover/root/PopoverRoot.tsx b/packages/react/src/popover/root/PopoverRoot.tsx index 7b4b114dadf..8402787a0fd 100644 --- a/packages/react/src/popover/root/PopoverRoot.tsx +++ b/packages/react/src/popover/root/PopoverRoot.tsx @@ -63,9 +63,10 @@ const PopoverRootComponent = fastComponent(function PopoverRootComponent { + const lifecycle = useOpenStateTransitions(open, store, () => { store.update({ stickIfOpen: true, openChangeReason: null }); }); + const { forceUnmount, mounted } = lifecycle; store.useSyncedValues({ modal, @@ -88,11 +89,6 @@ const PopoverRootComponent = fastComponent(function PopoverRootComponent ({ open, mounted, transitionStatus }), - [open, mounted, transitionStatus], - ); - return ( }> diff --git a/packages/react/src/popover/root/PopoverRootContext.ts b/packages/react/src/popover/root/PopoverRootContext.ts index 93bb4781909..3d4a86fc28a 100644 --- a/packages/react/src/popover/root/PopoverRootContext.ts +++ b/packages/react/src/popover/root/PopoverRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { PopupLifecycleState } from '../../utils/popups/store'; +import { DEFAULT_POPUP_LIFECYCLE_STATE } from '../../utils/popups/popupLifecycleState'; import type { PopoverStore } from '../store/PopoverStore'; export type PopoverRootContext = PopoverStore; @@ -19,8 +19,4 @@ export function usePopoverRootContext(optional?: boolean) { return context; } -export const PopoverLifecycleContext = React.createContext({ - open: false, - mounted: false, - transitionStatus: undefined, -}); +export const PopoverLifecycleContext = React.createContext(DEFAULT_POPUP_LIFECYCLE_STATE); diff --git a/packages/react/src/preview-card/root/PreviewCardContext.ts b/packages/react/src/preview-card/root/PreviewCardContext.ts index e3a20d8412e..68e352329a2 100644 --- a/packages/react/src/preview-card/root/PreviewCardContext.ts +++ b/packages/react/src/preview-card/root/PreviewCardContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { PopupLifecycleState } from '../../utils/popups/store'; +import { DEFAULT_POPUP_LIFECYCLE_STATE } from '../../utils/popups/popupLifecycleState'; import { PreviewCardStore } from '../store/PreviewCardStore'; export type PreviewCardRootContext = PreviewCardStore; @@ -22,8 +22,4 @@ export function usePreviewCardRootContext(optional?: boolean) { return context; } -export const PreviewCardLifecycleContext = React.createContext({ - open: false, - mounted: false, - transitionStatus: undefined, -}); +export const PreviewCardLifecycleContext = React.createContext(DEFAULT_POPUP_LIFECYCLE_STATE); diff --git a/packages/react/src/preview-card/root/PreviewCardRoot.tsx b/packages/react/src/preview-card/root/PreviewCardRoot.tsx index a3cc6163bcd..57e8e247431 100644 --- a/packages/react/src/preview-card/root/PreviewCardRoot.tsx +++ b/packages/react/src/preview-card/root/PreviewCardRoot.tsx @@ -63,9 +63,10 @@ function PreviewCardRootComponent(props: PreviewCardRoot.Props const payload = store.useState('payload') as Payload | undefined; useImplicitActiveTrigger(store, { closeOnActiveTriggerUnmount: true }); - const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions(open, store, () => { + const lifecycle = useOpenStateTransitions(open, store, () => { store.context.inlineRectCoordsRef.current = undefined; }); + const { forceUnmount, mounted } = lifecycle; useIsoLayoutEffect(() => { if (open) { @@ -86,11 +87,6 @@ function PreviewCardRootComponent(props: PreviewCardRoot.Props const shouldRenderInteractions = open || mounted; - const lifecycle = React.useMemo( - () => ({ open, mounted, transitionStatus }), - [open, mounted, transitionStatus], - ); - return ( diff --git a/packages/react/src/select/root/SelectRootContext.ts b/packages/react/src/select/root/SelectRootContext.ts index 5358129ee40..f93116c57a7 100644 --- a/packages/react/src/select/root/SelectRootContext.ts +++ b/packages/react/src/select/root/SelectRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { PopupLifecycleState } from '../../utils/popups/store'; +import { DEFAULT_POPUP_LIFECYCLE_STATE } from '../../utils/popups/popupLifecycleState'; import { type FloatingRootContext } from '../../floating-ui-react'; import type { SelectStore } from '../store'; import type { HTMLProps } from '../../internals/types'; @@ -56,8 +56,4 @@ export function useSelectFloatingContext() { return context; } -export const SelectLifecycleContext = React.createContext({ - open: false, - mounted: false, - transitionStatus: undefined, -}); +export const SelectLifecycleContext = React.createContext(DEFAULT_POPUP_LIFECYCLE_STATE); diff --git a/packages/react/src/tooltip/root/TooltipRoot.tsx b/packages/react/src/tooltip/root/TooltipRoot.tsx index bb6605ea396..ec45e458355 100644 --- a/packages/react/src/tooltip/root/TooltipRoot.tsx +++ b/packages/react/src/tooltip/root/TooltipRoot.tsx @@ -79,7 +79,8 @@ export const TooltipRoot = fastComponent(function TooltipRoot( }); useImplicitActiveTrigger(store, { closeOnActiveTriggerUnmount: true }); - const { forceUnmount, mounted, transitionStatus } = useOpenStateTransitions(open, store); + const lifecycle = useOpenStateTransitions(open, store); + const { forceUnmount, mounted, transitionStatus } = lifecycle; const isInstantPhase = store.useState('isInstantPhase'); const instantType = store.useState('instantType'); const lastOpenChangeReason = store.useState('lastOpenChangeReason'); @@ -136,11 +137,6 @@ export const TooltipRoot = fastComponent(function TooltipRoot( const shouldRenderInteractions = open || mounted || (!disabled && trackCursorAxis !== 'none'); - const lifecycle = React.useMemo( - () => ({ open, mounted, transitionStatus }), - [open, mounted, transitionStatus], - ); - return ( diff --git a/packages/react/src/tooltip/root/TooltipRootContext.ts b/packages/react/src/tooltip/root/TooltipRootContext.ts index 07e2f942b26..b63cfb6f7cd 100644 --- a/packages/react/src/tooltip/root/TooltipRootContext.ts +++ b/packages/react/src/tooltip/root/TooltipRootContext.ts @@ -1,6 +1,6 @@ 'use client'; import * as React from 'react'; -import type { PopupLifecycleState } from '../../utils/popups/store'; +import { DEFAULT_POPUP_LIFECYCLE_STATE } from '../../utils/popups/popupLifecycleState'; import { TooltipStore } from '../store/TooltipStore'; export type TooltipRootContext = TooltipStore; @@ -20,8 +20,4 @@ export function useTooltipRootContext(optional?: boolean) { return context; } -export const TooltipLifecycleContext = React.createContext({ - open: false, - mounted: false, - transitionStatus: undefined, -}); +export const TooltipLifecycleContext = React.createContext(DEFAULT_POPUP_LIFECYCLE_STATE); diff --git a/packages/react/src/utils/popups/popupLifecycleState.ts b/packages/react/src/utils/popups/popupLifecycleState.ts new file mode 100644 index 00000000000..d48369b1b53 --- /dev/null +++ b/packages/react/src/utils/popups/popupLifecycleState.ts @@ -0,0 +1,14 @@ +import type { TransitionStatus } from '../../internals/useTransitionStatus'; + +// Render through React context; the store copy remains for detached triggers and interactions. +export interface PopupLifecycleState { + open: boolean; + mounted: boolean; + transitionStatus: TransitionStatus; +} + +export const DEFAULT_POPUP_LIFECYCLE_STATE: PopupLifecycleState = { + open: false, + mounted: false, + transitionStatus: undefined, +}; diff --git a/packages/react/src/utils/popups/popupStoreUtils.ts b/packages/react/src/utils/popups/popupStoreUtils.ts index a5187b78ab9..d1006d82db7 100644 --- a/packages/react/src/utils/popups/popupStoreUtils.ts +++ b/packages/react/src/utils/popups/popupStoreUtils.ts @@ -632,7 +632,10 @@ export function useOpenStateTransitions>( onComplete: forceUnmount, }); - return { forceUnmount, mounted, transitionStatus }; + return React.useMemo( + () => ({ forceUnmount, open, mounted, transitionStatus }), + [forceUnmount, open, mounted, transitionStatus], + ); } type PopupInteractionPropKey = 'activeTriggerProps' | 'inactiveTriggerProps' | 'popupProps'; diff --git a/packages/react/src/utils/popups/store.ts b/packages/react/src/utils/popups/store.ts index fcfc6adfee4..6daac373e56 100644 --- a/packages/react/src/utils/popups/store.ts +++ b/packages/react/src/utils/popups/store.ts @@ -87,12 +87,6 @@ export type PopupStoreState = { popupProps: HTMLProps; }; -// Render this state through React context; keep the store copy for detached triggers and interactions. -export type PopupLifecycleState = Pick< - PopupStoreState, - 'open' | 'mounted' | 'transitionStatus' ->; - export function createInitialPopupStoreState( triggerElements: PopupTriggerMap, floatingId?: string | undefined, diff --git a/packages/react/src/utils/popups/usePopupCloseComplete.ts b/packages/react/src/utils/popups/usePopupCloseComplete.ts index 723c7faf71a..5b885aedd21 100644 --- a/packages/react/src/utils/popups/usePopupCloseComplete.ts +++ b/packages/react/src/utils/popups/usePopupCloseComplete.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui/utils/useStableCallback'; -import { useAnimationsFinished } from '../../internals/useAnimationsFinished'; +import { useOpenChangeComplete } from '../../internals/useOpenChangeComplete'; /** * Finishes a popup's close after CSS animations, including when the consumer already @@ -10,7 +10,7 @@ import { useAnimationsFinished } from '../../internals/useAnimationsFinished'; * a popup have no popup lifecycle to finish. */ export function usePopupCloseComplete({ - enabled, + enabled: enabledProp, open, ref, onComplete, @@ -20,7 +20,9 @@ export function usePopupCloseComplete({ ref: React.RefObject; onComplete: () => void; }) { + const enabled = enabledProp && !open; const hadElementRef = React.useRef(false); + useIsoLayoutEffect(() => { if (open && ref.current) { hadElementRef.current = true; @@ -33,22 +35,12 @@ export function usePopupCloseComplete({ onComplete(); } }); - const runOnceAnimationsFinish = useAnimationsFinished(ref); - React.useEffect(() => { - if (!enabled || open) { - return undefined; - } + useOpenChangeComplete({ enabled, ref, onComplete: complete }); - if (!ref.current) { - if (hadElementRef.current) { - complete(); - } - return undefined; + React.useEffect(() => { + if (enabled && !ref.current && hadElementRef.current) { + complete(); } - - const abortController = new AbortController(); - runOnceAnimationsFinish(complete, abortController.signal); - return () => abortController.abort(); - }, [enabled, open, ref, complete, runOnceAnimationsFinish]); + }, [enabled, ref, complete]); }