/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import type {ReactNodeList} from 'shared/ReactTypes'; import type {FiberRoot} from './ReactInternalTypes'; import type {ViewTransitionInstance} from './ReactFiberConfig'; import { getWorkInProgressRoot, getPendingTransitionTypes, } from './ReactFiberWorkLoop'; import {getIsHydrating} from './ReactFiberHydrationContext'; import {getTreeId} from './ReactFiberTreeContext'; export type ViewTransitionClassPerType = { [transitionType: 'default' | string]: 'none' | string, }; export type ViewTransitionClass = 'none' | string | ViewTransitionClassPerType; export type ViewTransitionProps = { name?: string, children?: ReactNodeList, className?: ViewTransitionClass, enter?: ViewTransitionClass, exit?: ViewTransitionClass, layout?: ViewTransitionClass, share?: ViewTransitionClass, update?: ViewTransitionClass, onEnter?: (instance: ViewTransitionInstance, types: Array) => void, onExit?: (instance: ViewTransitionInstance, types: Array) => void, onLayout?: (instance: ViewTransitionInstance, types: Array) => void, onShare?: (instance: ViewTransitionInstance, types: Array) => void, onUpdate?: (instance: ViewTransitionInstance, types: Array) => void, }; export type ViewTransitionState = { autoName: null | string, // the view-transition-name to use when an explicit one is not specified paired: null | ViewTransitionState, // a temporary state during the commit phase if we have paired this with another instance ref: null | ViewTransitionInstance, // the current ref instance. This can change through the lifetime of the instance. }; let globalClientIdCounter: number = 0; export function assignViewTransitionAutoName( props: ViewTransitionProps, instance: ViewTransitionState, ): string { if (instance.autoName !== null) { return instance.autoName; } const root = ((getWorkInProgressRoot(): any): FiberRoot); const identifierPrefix = root.identifierPrefix; let name; if (getIsHydrating()) { const treeId = getTreeId(); // Use a captial R prefix for server-generated ids. name = '\u00AB' + identifierPrefix + 'T' + treeId + '\u00BB'; } else { // Use a lowercase r prefix for client-generated ids. const globalClientId = globalClientIdCounter++; name = '\u00AB' + identifierPrefix + 't' + globalClientId.toString(32) + '\u00BB'; } instance.autoName = name; return name; } export function getViewTransitionName( props: ViewTransitionProps, instance: ViewTransitionState, ): string { if (props.name != null && props.name !== 'auto') { return props.name; } // We should have assigned a name by now. return (instance.autoName: any); } function getClassNameByType(classByType: ?ViewTransitionClass): ?string { if (classByType == null || typeof classByType === 'string') { return classByType; } let className: ?string = null; const activeTypes = getPendingTransitionTypes(); if (activeTypes !== null) { for (let i = 0; i < activeTypes.length; i++) { const match = classByType[activeTypes[i]]; if (match != null) { if (match === 'none') { // If anything matches "none" that takes precedence over any other // type that also matches. return 'none'; } if (className == null) { className = match; } else { className += ' ' + match; } } } } if (className == null) { // We had no other matches. Match the default for this configuration. return classByType.default; } return className; } export function getViewTransitionClassName( defaultClass: ?ViewTransitionClass, eventClass: ?ViewTransitionClass, ): ?string { const className: ?string = getClassNameByType(defaultClass); const eventClassName: ?string = getClassNameByType(eventClass); if (eventClassName == null) { return className; } if (eventClassName === 'none') { return eventClassName; } if (className != null && className !== 'none') { return className + ' ' + eventClassName; } return eventClassName; }