1// Wrap a state update in a View Transition for a smooth cross-fade (#281). 2// 3// document.startViewTransition snapshots the DOM before and after `update` 4// runs and cross-fades between them. With React 18 the update must commit 5// synchronously inside the callback, or the API takes its "after" snapshot 6// before React paints and nothing animates -- so flushSync forces the commit. 7// react-dom is pinned to the same 18.2.0 build as react-dom/client in the 8// import map, so flushSync drives the same root createRoot mounted. 9// 10// Browsers without the API (Firefox < 130, older Safari) run `update` directly: 11// same state change, no animation. startViewTransition can also throw, so that 12// path falls back to a plain update too -- the modal can never get stuck behind 13// a failed or unsupported transition. 14 15import { flushSync } from 'react-dom'; 16 17export function withViewTransition(update) { 18 const reduceMotion = typeof window !== 'undefined' 19 && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; 20 if ( 21 reduceMotion 22 || typeof document === 'undefined' 23 || typeof document.startViewTransition !== 'function' 24 ) { 25 update(); 26 return; 27 } 28 try { 29 document.startViewTransition(() => flushSync(update)); 30 } catch { 31 update(); 32 } 33}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.