From c0c53290db2018b7cbb54205a517e16a34c334c9 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Mon, 6 Oct 2025 22:27:25 -0700 Subject: [PATCH 1/2] [react] Enable `` and Fragment refs in Canary (#73813) --- types/react-dom/canary.d.ts | 36 ++++++ types/react-dom/experimental.d.ts | 33 ------ types/react-dom/test/canary-tests.tsx | 95 ++++++++++++++++ types/react-dom/test/experimental-tests.tsx | 94 ---------------- types/react/canary.d.ts | 85 +++++++++++++++ types/react/experimental.d.ts | 81 -------------- types/react/test/canary.tsx | 115 ++++++++++++++++++++ types/react/test/experimental.tsx | 115 -------------------- types/react/ts5.0/canary.d.ts | 85 +++++++++++++++ types/react/ts5.0/experimental.d.ts | 81 -------------- types/react/ts5.0/test/canary.tsx | 115 ++++++++++++++++++++ types/react/ts5.0/test/experimental.tsx | 115 -------------------- 12 files changed, 531 insertions(+), 519 deletions(-) diff --git a/types/react-dom/canary.d.ts b/types/react-dom/canary.d.ts index e0929224b20c79..403466347a8071 100644 --- a/types/react-dom/canary.d.ts +++ b/types/react-dom/canary.d.ts @@ -32,3 +32,39 @@ import React = require("react"); import ReactDOM = require("."); export {}; + +declare module "react" { + // @enableViewTransition + interface ViewTransitionPseudoElement extends Animatable { + getComputedStyle: () => CSSStyleDeclaration; + } + + interface ViewTransitionInstance { + group: ViewTransitionPseudoElement; + imagePair: ViewTransitionPseudoElement; + old: ViewTransitionPseudoElement; + new: ViewTransitionPseudoElement; + } + + // @enableFragmentRefs + interface FragmentInstance { + blur: () => void; + focus: (focusOptions?: FocusOptions | undefined) => void; + focusLast: (focusOptions?: FocusOptions | undefined) => void; + observeUsing(observer: IntersectionObserver | ResizeObserver): void; + unobserveUsing(observer: IntersectionObserver | ResizeObserver): void; + getClientRects(): Array; + getRootNode(getRootNodeOptions?: GetRootNodeOptions | undefined): Document | ShadowRoot | FragmentInstance; + addEventListener( + type: string, + listener: EventListener, + optionsOrUseCapture?: Parameters[2], + ): void; + removeEventListener( + type: string, + listener: EventListener, + optionsOrUseCapture?: Parameters[2], + ): void; + scrollIntoView(alignToTop?: boolean): void; + } +} diff --git a/types/react-dom/experimental.d.ts b/types/react-dom/experimental.d.ts index b0c7853d3ef71a..01999d3591e144 100644 --- a/types/react-dom/experimental.d.ts +++ b/types/react-dom/experimental.d.ts @@ -39,41 +39,8 @@ declare module "." { } declare module "react" { - interface ViewTransitionPseudoElement extends Animatable { - getComputedStyle: () => CSSStyleDeclaration; - } - - interface ViewTransitionInstance { - group: ViewTransitionPseudoElement; - imagePair: ViewTransitionPseudoElement; - old: ViewTransitionPseudoElement; - new: ViewTransitionPseudoElement; - } - // eslint-disable-next-line @typescript-eslint/no-empty-interface interface GestureProvider extends AnimationTimeline {} - - // @enableFragmentRefs - interface FragmentInstance { - blur: () => void; - focus: (focusOptions?: FocusOptions | undefined) => void; - focusLast: (focusOptions?: FocusOptions | undefined) => void; - observeUsing(observer: IntersectionObserver | ResizeObserver): void; - unobserveUsing(observer: IntersectionObserver | ResizeObserver): void; - getClientRects(): Array; - getRootNode(getRootNodeOptions?: GetRootNodeOptions | undefined): Document | ShadowRoot | FragmentInstance; - addEventListener( - type: string, - listener: EventListener, - optionsOrUseCapture?: Parameters[2], - ): void; - removeEventListener( - type: string, - listener: EventListener, - optionsOrUseCapture?: Parameters[2], - ): void; - experimental_scrollIntoView(alignToTop?: boolean): void; - } } declare module "./client" { diff --git a/types/react-dom/test/canary-tests.tsx b/types/react-dom/test/canary-tests.tsx index d7857c7449e1c2..d44db301a7160b 100644 --- a/types/react-dom/test/canary-tests.tsx +++ b/types/react-dom/test/canary-tests.tsx @@ -4,3 +4,98 @@ import ReactDOM = require("react-dom"); import ReactDOMClient = require("react-dom/client"); import ReactDOMServer = require("react-dom/server"); import ReactDOMStatic = require("react-dom/static"); + +// @enableViewTransition +function viewTransitionTests() { + const ViewTransition = React.ViewTransition; + + { + if (current !== null) { + // $ExpectType string + current.name; + + // $ExpectType ViewTransitionPseudoElement + current.group; + // $ExpectType ViewTransitionPseudoElement + current.imagePair; + // $ExpectType ViewTransitionPseudoElement + current.old; + // $ExpectType ViewTransitionPseudoElement + current.new; + + // $ExpectType CSSStyleDeclaration + current.old.getComputedStyle(); + // @ts-expect-error -- Implemented on the pseudo elements. + current.getComputedStyle(); + } + }} + > +
+ ; +} + +// @enableFragmentRefs +function fragmentRefTest() { + { + // $ExpectType FragmentInstance | null + maybeInstance; + + // See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69022/commits/57825689c7abb50a79395d1266226cfa1b31a4e1 + const instance = maybeInstance!; + + instance.focus(); + instance.blur(); + instance.focusLast(); + instance.observeUsing(new IntersectionObserver(() => {})); + instance.unobserveUsing(new IntersectionObserver(() => {})); + instance.observeUsing(new ResizeObserver(() => {})); + instance.unobserveUsing(new ResizeObserver(() => {})); + instance.getClientRects(); + instance.getRootNode(); + instance.getRootNode({ composed: true }); + instance.addEventListener("click", () => {}); + instance.addEventListener("click", () => {}, true); + instance.addEventListener("click", () => {}, { capture: true }); + instance.addEventListener("click", () => {}, true); + instance.removeEventListener("click", () => {}); + instance.removeEventListener("click", () => {}, { capture: true }); + instance.removeEventListener("click", () => {}, true); + instance.addEventListener("click", () => {}, { passive: true }); + instance.addEventListener("click", () => {}, { once: true }); + instance.addEventListener("click", () => {}, { signal: new AbortController().signal }); + instance.addEventListener("click", () => {}, { signal: new AbortSignal() }); + instance.addEventListener("click", () => {}, { signal: new AbortSignal(), once: true }); + instance.addEventListener("click", () => {}, { signal: new AbortSignal(), passive: true }); + instance.addEventListener("click", () => {}, { signal: new AbortSignal(), capture: true }); + instance.addEventListener("click", () => {}, { signal: new AbortSignal(), capture: true, once: true }); + instance.addEventListener("click", () => {}, { signal: new AbortSignal(), capture: true, passive: true }); + instance.addEventListener("click", () => {}, { signal: new AbortSignal(), once: true, passive: true }); + instance.addEventListener("click", () => {}, { + signal: new AbortSignal(), + capture: true, + once: true, + passive: true, + }); + instance.removeEventListener("click", () => {}, { capture: true }); + instance.removeEventListener("click", () => {}, true); + instance.removeEventListener("click", () => {}, { + // @ts-expect-error -- Not the same options as addEventListener + passive: true, + }); + instance.scrollIntoView(false); + instance.scrollIntoView(true); + instance.scrollIntoView(undefined); + + instance.scrollIntoView( + // @ts-expect-error -- options are not supported yet + {}, + ); + return () => {}; + }} + > +
+
+ ; +} diff --git a/types/react-dom/test/experimental-tests.tsx b/types/react-dom/test/experimental-tests.tsx index 8d44c1640e3c82..9f250c62e5b290 100644 --- a/types/react-dom/test/experimental-tests.tsx +++ b/types/react-dom/test/experimental-tests.tsx @@ -4,35 +4,6 @@ import ReactDOMClient = require("react-dom/client"); import "react/experimental"; import "react-dom/experimental"; -function viewTransitionTests() { - const ViewTransition = React.unstable_ViewTransition; - - { - if (current !== null) { - // $ExpectType string - current.name; - - // $ExpectType ViewTransitionPseudoElement - current.group; - // $ExpectType ViewTransitionPseudoElement - current.imagePair; - // $ExpectType ViewTransitionPseudoElement - current.old; - // $ExpectType ViewTransitionPseudoElement - current.new; - - // $ExpectType CSSStyleDeclaration - current.old.getComputedStyle(); - // @ts-expect-error -- Implemented on the pseudo elements. - current.getComputedStyle(); - } - }} - > -
- ; -} - // @enableGestureTransition function swipeTransitionTest() { const startGestureTransition = React.unstable_startGestureTransition; @@ -56,71 +27,6 @@ function swipeTransitionTest() { } } -// @enableFragmentRefs -function fragmentRefTest() { - { - // $ExpectType FragmentInstance | null - maybeInstance; - - // See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69022/commits/57825689c7abb50a79395d1266226cfa1b31a4e1 - const instance = maybeInstance!; - - instance.focus(); - instance.blur(); - instance.focusLast(); - instance.observeUsing(new IntersectionObserver(() => {})); - instance.unobserveUsing(new IntersectionObserver(() => {})); - instance.observeUsing(new ResizeObserver(() => {})); - instance.unobserveUsing(new ResizeObserver(() => {})); - instance.getClientRects(); - instance.getRootNode(); - instance.getRootNode({ composed: true }); - instance.addEventListener("click", () => {}); - instance.addEventListener("click", () => {}, true); - instance.addEventListener("click", () => {}, { capture: true }); - instance.addEventListener("click", () => {}, true); - instance.removeEventListener("click", () => {}); - instance.removeEventListener("click", () => {}, { capture: true }); - instance.removeEventListener("click", () => {}, true); - instance.addEventListener("click", () => {}, { passive: true }); - instance.addEventListener("click", () => {}, { once: true }); - instance.addEventListener("click", () => {}, { signal: new AbortController().signal }); - instance.addEventListener("click", () => {}, { signal: new AbortSignal() }); - instance.addEventListener("click", () => {}, { signal: new AbortSignal(), once: true }); - instance.addEventListener("click", () => {}, { signal: new AbortSignal(), passive: true }); - instance.addEventListener("click", () => {}, { signal: new AbortSignal(), capture: true }); - instance.addEventListener("click", () => {}, { signal: new AbortSignal(), capture: true, once: true }); - instance.addEventListener("click", () => {}, { signal: new AbortSignal(), capture: true, passive: true }); - instance.addEventListener("click", () => {}, { signal: new AbortSignal(), once: true, passive: true }); - instance.addEventListener("click", () => {}, { - signal: new AbortSignal(), - capture: true, - once: true, - passive: true, - }); - instance.removeEventListener("click", () => {}, { capture: true }); - instance.removeEventListener("click", () => {}, true); - instance.removeEventListener("click", () => {}, { - // @ts-expect-error -- Not the same options as addEventListener - passive: true, - }); - instance.experimental_scrollIntoView(false); - instance.experimental_scrollIntoView(true); - instance.experimental_scrollIntoView(undefined); - - instance.experimental_scrollIntoView( - // @ts-expect-error -- options are not supported yet - {}, - ); - return () => {}; - }} - > -
-
- ; -} - // @enableSrcObject function srcObjectTest() { ; diff --git a/types/react/canary.d.ts b/types/react/canary.d.ts index 9215a16bb87bab..22b1807e699a62 100644 --- a/types/react/canary.d.ts +++ b/types/react/canary.d.ts @@ -32,4 +32,89 @@ type VoidOrUndefinedOnly = void | { [UNDEFINED_VOID_ONLY]: never }; declare module "." { export function unstable_useCacheRefresh(): () => void; + + // @enableViewTransition + export interface ViewTransitionInstance { + /** + * The {@link ViewTransitionProps name} that was used in the corresponding {@link ViewTransition} component or `"auto"` if the `name` prop was omitted. + */ + name: string; + } + + export type ViewTransitionClassPerType = Record<"default" | (string & {}), "none" | "auto" | (string & {})>; + export type ViewTransitionClass = ViewTransitionClassPerType | ViewTransitionClassPerType[string]; + + export interface ViewTransitionProps { + children?: ReactNode | undefined; + /** + * Assigns the {@link https://developer.chrome.com/blog/view-transitions-update-io24#view-transition-class `view-transition-class`} class to the underlying DOM node. + */ + default?: ViewTransitionClass | undefined; + /** + * Combined with {@link className} if this `` or its parent Component is mounted and there's no other with the same name being deleted. + * `"none"` is a special value that deactivates the view transition name under that condition. + */ + enter?: ViewTransitionClass | undefined; + /** + * Combined with {@link className} if this `` or its parent Component is unmounted and there's no other with the same name being deleted. + * `"none"` is a special value that deactivates the view transition name under that condition. + */ + exit?: ViewTransitionClass | undefined; + /** + * "auto" will automatically assign a view-transition-name to the inner DOM node. + * That way you can add a View Transition to a Component without controlling its DOM nodes styling otherwise. + * + * A difference between this and the browser's built-in view-transition-name: auto is that switching the DOM nodes within the `` component preserves the same name so this example cross-fades between the DOM nodes instead of causing an exit and enter. + * @default "auto" + */ + name?: "auto" | (string & {}) | undefined; + /** + * The `` or its parent Component is mounted and there's no other `` with the same name being deleted. + */ + onEnter?: (instance: ViewTransitionInstance, types: Array) => void; + /** + * The `` or its parent Component is unmounted and there's no other `` with the same name being deleted. + */ + onExit?: (instance: ViewTransitionInstance, types: Array) => void; + /** + * This `` is being mounted and another `` instance with the same name is being unmounted elsewhere. + */ + onShare?: (instance: ViewTransitionInstance, types: Array) => void; + /** + * The content of `` has changed either due to DOM mutations or because an inner child `` has resized. + */ + onUpdate?: (instance: ViewTransitionInstance, types: Array) => void; + ref?: Ref | undefined; + /** + * Combined with {@link className} if this `` is being mounted and another instance with the same name is being unmounted elsewhere. + * `"none"` is a special value that deactivates the view transition name under that condition. + */ + share?: ViewTransitionClass | undefined; + /** + * Combined with {@link className} if the content of this `` has changed either due to DOM mutations or because an inner child has resized. + * `"none"` is a special value that deactivates the view transition name under that condition. + */ + update?: ViewTransitionClass | undefined; + } + + /** + * Opt-in for using {@link https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API View Transitions} in React. + * View Transitions only trigger for async updates like {@link startTransition}, {@link useDeferredValue}, Actions or <{@link Suspense}> revealing from fallback to content. + * Synchronous updates provide an opt-out but also guarantee that they commit immediately which View Transitions can't. + * + * @see {@link https://react.dev/reference/react/ViewTransition `` reference documentation} + */ + export const ViewTransition: ExoticComponent; + + /** + * @see {@link https://react.dev/reference/react/addTransitionType `addTransitionType` reference documentation} + */ + export function addTransitionType(type: string): void; + + // @enableFragmentRefs + export interface FragmentInstance {} + + export interface FragmentProps { + ref?: Ref | undefined; + } } diff --git a/types/react/experimental.d.ts b/types/react/experimental.d.ts index 4ab57e3398c274..7e681ca73229f6 100644 --- a/types/react/experimental.d.ts +++ b/types/react/experimental.d.ts @@ -117,80 +117,6 @@ declare module "." { ): void; function experimental_taintObjectReference(message: string | undefined, object: Reference): void; - export interface ViewTransitionInstance { - /** - * The {@link ViewTransitionProps name} that was used in the corresponding {@link ViewTransition} component or `"auto"` if the `name` prop was omitted. - */ - name: string; - } - - export type ViewTransitionClassPerType = Record<"default" | (string & {}), "none" | "auto" | (string & {})>; - export type ViewTransitionClass = ViewTransitionClassPerType | ViewTransitionClassPerType[string]; - - export interface ViewTransitionProps { - children?: ReactNode | undefined; - /** - * Assigns the {@link https://developer.chrome.com/blog/view-transitions-update-io24#view-transition-class `view-transition-class`} class to the underlying DOM node. - */ - default?: ViewTransitionClass | undefined; - /** - * Combined with {@link className} if this `` or its parent Component is mounted and there's no other with the same name being deleted. - * `"none"` is a special value that deactivates the view transition name under that condition. - */ - enter?: ViewTransitionClass | undefined; - /** - * Combined with {@link className} if this `` or its parent Component is unmounted and there's no other with the same name being deleted. - * `"none"` is a special value that deactivates the view transition name under that condition. - */ - exit?: ViewTransitionClass | undefined; - /** - * "auto" will automatically assign a view-transition-name to the inner DOM node. - * That way you can add a View Transition to a Component without controlling its DOM nodes styling otherwise. - * - * A difference between this and the browser's built-in view-transition-name: auto is that switching the DOM nodes within the `` component preserves the same name so this example cross-fades between the DOM nodes instead of causing an exit and enter. - * @default "auto" - */ - name?: "auto" | (string & {}) | undefined; - /** - * The `` or its parent Component is mounted and there's no other `` with the same name being deleted. - */ - onEnter?: (instance: ViewTransitionInstance, types: Array) => void; - /** - * The `` or its parent Component is unmounted and there's no other `` with the same name being deleted. - */ - onExit?: (instance: ViewTransitionInstance, types: Array) => void; - /** - * This `` is being mounted and another `` instance with the same name is being unmounted elsewhere. - */ - onShare?: (instance: ViewTransitionInstance, types: Array) => void; - /** - * The content of `` has changed either due to DOM mutations or because an inner child `` has resized. - */ - onUpdate?: (instance: ViewTransitionInstance, types: Array) => void; - ref?: Ref | undefined; - /** - * Combined with {@link className} if this `` is being mounted and another instance with the same name is being unmounted elsewhere. - * `"none"` is a special value that deactivates the view transition name under that condition. - */ - share?: ViewTransitionClass | undefined; - /** - * Combined with {@link className} if the content of this `` has changed either due to DOM mutations or because an inner child has resized. - * `"none"` is a special value that deactivates the view transition name under that condition. - */ - update?: ViewTransitionClass | undefined; - } - - /** - * Opt-in for using {@link https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API View Transitions} in React. - * View Transitions only trigger for async updates like {@link startTransition}, {@link useDeferredValue}, Actions or <{@link Suspense}> revealing from fallback to content. - * Synchronous updates provide an opt-out but also guarantee that they commit immediately which View Transitions can't. - * - * @see {@link https://github.com/facebook/react/pull/31975} - */ - export const unstable_ViewTransition: ExoticComponent; - - export function unstable_addTransitionType(type: string): void; - // @enableGestureTransition // Implemented by the specific renderer e.g. `react-dom`. // Keep in mind that augmented interfaces merge their JSDoc so if you put @@ -207,13 +133,6 @@ declare module "." { options?: GestureOptions, ): () => void; - // @enableFragmentRefs - export interface FragmentInstance {} - - export interface FragmentProps { - ref?: Ref | undefined; - } - // @enableSrcObject interface DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES { srcObject: Blob; diff --git a/types/react/test/canary.tsx b/types/react/test/canary.tsx index 117711db251c7e..cc72b615ddd282 100644 --- a/types/react/test/canary.tsx +++ b/types/react/test/canary.tsx @@ -25,3 +25,118 @@ function useCacheTest() { refresh(() => "refresh"); } } + +function viewTransitionTests() { + const ViewTransition = React.ViewTransition; + const addTransitionType = React.addTransitionType; + + ; + ; + ; + ; + ; + ; + ; + + { + // $ExpectType ViewTransitionInstance + instance; + // $ExpectType string[] + types; + }} + onExit={(instance, types) => { + // $ExpectType ViewTransitionInstance + instance; + // $ExpectType string[] + types; + }} + onShare={(instance, types) => { + // $ExpectType ViewTransitionInstance + instance; + // $ExpectType string[] + types; + }} + onUpdate={(instance, types) => { + // $ExpectType ViewTransitionInstance + instance; + // $ExpectType string[] + types; + }} + />; + + { + if (current !== null) { + // $ExpectType string + current.name; + } + }} + > +
+ ; + + +
+ ; + + const Null = () => null; + + + ; + + const Div = ({ children }: { children?: React.ReactNode }) =>
{children}
; + +
+ ; + + function Component() { + function handleNavigation() { + React.startTransition(() => { + // @ts-expect-error + addTransitionType(); + // @ts-expect-error + addTransitionType(undefined); + addTransitionType("navigation"); + }); + } + } +} + +// @enableFragmentRefs +function fragmentRefTest() { + { + // $ExpectType FragmentInstance | null + maybeInstance; + + // See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69022/commits/57825689c7abb50a79395d1266226cfa1b31a4e1 + const instance = maybeInstance!; + + // @ts-expect-error -- Not implemented by isomorphic renderer but react-dom. + instance.focus; + + return () => {}; + }} + > +
+
+ ; +} diff --git a/types/react/test/experimental.tsx b/types/react/test/experimental.tsx index e2609d11aca2ae..b7fc2b40f9e7ab 100644 --- a/types/react/test/experimental.tsx +++ b/types/react/test/experimental.tsx @@ -139,100 +139,6 @@ function taintTests() { ); } -function viewTransitionTests() { - const ViewTransition = React.unstable_ViewTransition; - const addTransitionType = React.unstable_addTransitionType; - - ; - ; - ; - ; - ; - ; - ; - - { - // $ExpectType ViewTransitionInstance - instance; - // $ExpectType string[] - types; - }} - onExit={(instance, types) => { - // $ExpectType ViewTransitionInstance - instance; - // $ExpectType string[] - types; - }} - onShare={(instance, types) => { - // $ExpectType ViewTransitionInstance - instance; - // $ExpectType string[] - types; - }} - onUpdate={(instance, types) => { - // $ExpectType ViewTransitionInstance - instance; - // $ExpectType string[] - types; - }} - />; - - { - if (current !== null) { - // $ExpectType string - current.name; - } - }} - > -
- ; - - -
- ; - - const Null = () => null; - - - ; - - const Div = ({ children }: { children?: React.ReactNode }) =>
{children}
; - -
- ; - - function Component() { - function handleNavigation() { - React.startTransition(() => { - // @ts-expect-error - addTransitionType(); - // @ts-expect-error - addTransitionType(undefined); - addTransitionType("navigation"); - }); - } - } -} - // @enableGestureTransition function swipeTransitionTest() { const startGestureTransition = React.unstable_startGestureTransition; @@ -268,24 +174,3 @@ function swipeTransitionTest() { startGestureTransition(gestureProvider, () => {}, {}); } } - -// @enableFragmentRefs -function fragmentRefTest() { - { - // $ExpectType FragmentInstance | null - maybeInstance; - - // See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69022/commits/57825689c7abb50a79395d1266226cfa1b31a4e1 - const instance = maybeInstance!; - - // @ts-expect-error -- Not implemented by isomorphic renderer but react-dom. - instance.focus; - - return () => {}; - }} - > -
-
- ; -} diff --git a/types/react/ts5.0/canary.d.ts b/types/react/ts5.0/canary.d.ts index 9215a16bb87bab..22b1807e699a62 100644 --- a/types/react/ts5.0/canary.d.ts +++ b/types/react/ts5.0/canary.d.ts @@ -32,4 +32,89 @@ type VoidOrUndefinedOnly = void | { [UNDEFINED_VOID_ONLY]: never }; declare module "." { export function unstable_useCacheRefresh(): () => void; + + // @enableViewTransition + export interface ViewTransitionInstance { + /** + * The {@link ViewTransitionProps name} that was used in the corresponding {@link ViewTransition} component or `"auto"` if the `name` prop was omitted. + */ + name: string; + } + + export type ViewTransitionClassPerType = Record<"default" | (string & {}), "none" | "auto" | (string & {})>; + export type ViewTransitionClass = ViewTransitionClassPerType | ViewTransitionClassPerType[string]; + + export interface ViewTransitionProps { + children?: ReactNode | undefined; + /** + * Assigns the {@link https://developer.chrome.com/blog/view-transitions-update-io24#view-transition-class `view-transition-class`} class to the underlying DOM node. + */ + default?: ViewTransitionClass | undefined; + /** + * Combined with {@link className} if this `` or its parent Component is mounted and there's no other with the same name being deleted. + * `"none"` is a special value that deactivates the view transition name under that condition. + */ + enter?: ViewTransitionClass | undefined; + /** + * Combined with {@link className} if this `` or its parent Component is unmounted and there's no other with the same name being deleted. + * `"none"` is a special value that deactivates the view transition name under that condition. + */ + exit?: ViewTransitionClass | undefined; + /** + * "auto" will automatically assign a view-transition-name to the inner DOM node. + * That way you can add a View Transition to a Component without controlling its DOM nodes styling otherwise. + * + * A difference between this and the browser's built-in view-transition-name: auto is that switching the DOM nodes within the `` component preserves the same name so this example cross-fades between the DOM nodes instead of causing an exit and enter. + * @default "auto" + */ + name?: "auto" | (string & {}) | undefined; + /** + * The `` or its parent Component is mounted and there's no other `` with the same name being deleted. + */ + onEnter?: (instance: ViewTransitionInstance, types: Array) => void; + /** + * The `` or its parent Component is unmounted and there's no other `` with the same name being deleted. + */ + onExit?: (instance: ViewTransitionInstance, types: Array) => void; + /** + * This `` is being mounted and another `` instance with the same name is being unmounted elsewhere. + */ + onShare?: (instance: ViewTransitionInstance, types: Array) => void; + /** + * The content of `` has changed either due to DOM mutations or because an inner child `` has resized. + */ + onUpdate?: (instance: ViewTransitionInstance, types: Array) => void; + ref?: Ref | undefined; + /** + * Combined with {@link className} if this `` is being mounted and another instance with the same name is being unmounted elsewhere. + * `"none"` is a special value that deactivates the view transition name under that condition. + */ + share?: ViewTransitionClass | undefined; + /** + * Combined with {@link className} if the content of this `` has changed either due to DOM mutations or because an inner child has resized. + * `"none"` is a special value that deactivates the view transition name under that condition. + */ + update?: ViewTransitionClass | undefined; + } + + /** + * Opt-in for using {@link https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API View Transitions} in React. + * View Transitions only trigger for async updates like {@link startTransition}, {@link useDeferredValue}, Actions or <{@link Suspense}> revealing from fallback to content. + * Synchronous updates provide an opt-out but also guarantee that they commit immediately which View Transitions can't. + * + * @see {@link https://react.dev/reference/react/ViewTransition `` reference documentation} + */ + export const ViewTransition: ExoticComponent; + + /** + * @see {@link https://react.dev/reference/react/addTransitionType `addTransitionType` reference documentation} + */ + export function addTransitionType(type: string): void; + + // @enableFragmentRefs + export interface FragmentInstance {} + + export interface FragmentProps { + ref?: Ref | undefined; + } } diff --git a/types/react/ts5.0/experimental.d.ts b/types/react/ts5.0/experimental.d.ts index 4ab57e3398c274..7e681ca73229f6 100644 --- a/types/react/ts5.0/experimental.d.ts +++ b/types/react/ts5.0/experimental.d.ts @@ -117,80 +117,6 @@ declare module "." { ): void; function experimental_taintObjectReference(message: string | undefined, object: Reference): void; - export interface ViewTransitionInstance { - /** - * The {@link ViewTransitionProps name} that was used in the corresponding {@link ViewTransition} component or `"auto"` if the `name` prop was omitted. - */ - name: string; - } - - export type ViewTransitionClassPerType = Record<"default" | (string & {}), "none" | "auto" | (string & {})>; - export type ViewTransitionClass = ViewTransitionClassPerType | ViewTransitionClassPerType[string]; - - export interface ViewTransitionProps { - children?: ReactNode | undefined; - /** - * Assigns the {@link https://developer.chrome.com/blog/view-transitions-update-io24#view-transition-class `view-transition-class`} class to the underlying DOM node. - */ - default?: ViewTransitionClass | undefined; - /** - * Combined with {@link className} if this `` or its parent Component is mounted and there's no other with the same name being deleted. - * `"none"` is a special value that deactivates the view transition name under that condition. - */ - enter?: ViewTransitionClass | undefined; - /** - * Combined with {@link className} if this `` or its parent Component is unmounted and there's no other with the same name being deleted. - * `"none"` is a special value that deactivates the view transition name under that condition. - */ - exit?: ViewTransitionClass | undefined; - /** - * "auto" will automatically assign a view-transition-name to the inner DOM node. - * That way you can add a View Transition to a Component without controlling its DOM nodes styling otherwise. - * - * A difference between this and the browser's built-in view-transition-name: auto is that switching the DOM nodes within the `` component preserves the same name so this example cross-fades between the DOM nodes instead of causing an exit and enter. - * @default "auto" - */ - name?: "auto" | (string & {}) | undefined; - /** - * The `` or its parent Component is mounted and there's no other `` with the same name being deleted. - */ - onEnter?: (instance: ViewTransitionInstance, types: Array) => void; - /** - * The `` or its parent Component is unmounted and there's no other `` with the same name being deleted. - */ - onExit?: (instance: ViewTransitionInstance, types: Array) => void; - /** - * This `` is being mounted and another `` instance with the same name is being unmounted elsewhere. - */ - onShare?: (instance: ViewTransitionInstance, types: Array) => void; - /** - * The content of `` has changed either due to DOM mutations or because an inner child `` has resized. - */ - onUpdate?: (instance: ViewTransitionInstance, types: Array) => void; - ref?: Ref | undefined; - /** - * Combined with {@link className} if this `` is being mounted and another instance with the same name is being unmounted elsewhere. - * `"none"` is a special value that deactivates the view transition name under that condition. - */ - share?: ViewTransitionClass | undefined; - /** - * Combined with {@link className} if the content of this `` has changed either due to DOM mutations or because an inner child has resized. - * `"none"` is a special value that deactivates the view transition name under that condition. - */ - update?: ViewTransitionClass | undefined; - } - - /** - * Opt-in for using {@link https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API View Transitions} in React. - * View Transitions only trigger for async updates like {@link startTransition}, {@link useDeferredValue}, Actions or <{@link Suspense}> revealing from fallback to content. - * Synchronous updates provide an opt-out but also guarantee that they commit immediately which View Transitions can't. - * - * @see {@link https://github.com/facebook/react/pull/31975} - */ - export const unstable_ViewTransition: ExoticComponent; - - export function unstable_addTransitionType(type: string): void; - // @enableGestureTransition // Implemented by the specific renderer e.g. `react-dom`. // Keep in mind that augmented interfaces merge their JSDoc so if you put @@ -207,13 +133,6 @@ declare module "." { options?: GestureOptions, ): () => void; - // @enableFragmentRefs - export interface FragmentInstance {} - - export interface FragmentProps { - ref?: Ref | undefined; - } - // @enableSrcObject interface DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_IMG_SRC_TYPES { srcObject: Blob; diff --git a/types/react/ts5.0/test/canary.tsx b/types/react/ts5.0/test/canary.tsx index 117711db251c7e..cc72b615ddd282 100644 --- a/types/react/ts5.0/test/canary.tsx +++ b/types/react/ts5.0/test/canary.tsx @@ -25,3 +25,118 @@ function useCacheTest() { refresh(() => "refresh"); } } + +function viewTransitionTests() { + const ViewTransition = React.ViewTransition; + const addTransitionType = React.addTransitionType; + + ; + ; + ; + ; + ; + ; + ; + + { + // $ExpectType ViewTransitionInstance + instance; + // $ExpectType string[] + types; + }} + onExit={(instance, types) => { + // $ExpectType ViewTransitionInstance + instance; + // $ExpectType string[] + types; + }} + onShare={(instance, types) => { + // $ExpectType ViewTransitionInstance + instance; + // $ExpectType string[] + types; + }} + onUpdate={(instance, types) => { + // $ExpectType ViewTransitionInstance + instance; + // $ExpectType string[] + types; + }} + />; + + { + if (current !== null) { + // $ExpectType string + current.name; + } + }} + > +
+ ; + + +
+ ; + + const Null = () => null; + + + ; + + const Div = ({ children }: { children?: React.ReactNode }) =>
{children}
; + +
+ ; + + function Component() { + function handleNavigation() { + React.startTransition(() => { + // @ts-expect-error + addTransitionType(); + // @ts-expect-error + addTransitionType(undefined); + addTransitionType("navigation"); + }); + } + } +} + +// @enableFragmentRefs +function fragmentRefTest() { + { + // $ExpectType FragmentInstance | null + maybeInstance; + + // See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69022/commits/57825689c7abb50a79395d1266226cfa1b31a4e1 + const instance = maybeInstance!; + + // @ts-expect-error -- Not implemented by isomorphic renderer but react-dom. + instance.focus; + + return () => {}; + }} + > +
+
+ ; +} diff --git a/types/react/ts5.0/test/experimental.tsx b/types/react/ts5.0/test/experimental.tsx index e2609d11aca2ae..b7fc2b40f9e7ab 100644 --- a/types/react/ts5.0/test/experimental.tsx +++ b/types/react/ts5.0/test/experimental.tsx @@ -139,100 +139,6 @@ function taintTests() { ); } -function viewTransitionTests() { - const ViewTransition = React.unstable_ViewTransition; - const addTransitionType = React.unstable_addTransitionType; - - ; - ; - ; - ; - ; - ; - ; - - { - // $ExpectType ViewTransitionInstance - instance; - // $ExpectType string[] - types; - }} - onExit={(instance, types) => { - // $ExpectType ViewTransitionInstance - instance; - // $ExpectType string[] - types; - }} - onShare={(instance, types) => { - // $ExpectType ViewTransitionInstance - instance; - // $ExpectType string[] - types; - }} - onUpdate={(instance, types) => { - // $ExpectType ViewTransitionInstance - instance; - // $ExpectType string[] - types; - }} - />; - - { - if (current !== null) { - // $ExpectType string - current.name; - } - }} - > -
- ; - - -
- ; - - const Null = () => null; - - - ; - - const Div = ({ children }: { children?: React.ReactNode }) =>
{children}
; - -
- ; - - function Component() { - function handleNavigation() { - React.startTransition(() => { - // @ts-expect-error - addTransitionType(); - // @ts-expect-error - addTransitionType(undefined); - addTransitionType("navigation"); - }); - } - } -} - // @enableGestureTransition function swipeTransitionTest() { const startGestureTransition = React.unstable_startGestureTransition; @@ -268,24 +174,3 @@ function swipeTransitionTest() { startGestureTransition(gestureProvider, () => {}, {}); } } - -// @enableFragmentRefs -function fragmentRefTest() { - { - // $ExpectType FragmentInstance | null - maybeInstance; - - // See https://github.com/DefinitelyTyped/DefinitelyTyped/pull/69022/commits/57825689c7abb50a79395d1266226cfa1b31a4e1 - const instance = maybeInstance!; - - // @ts-expect-error -- Not implemented by isomorphic renderer but react-dom. - instance.focus; - - return () => {}; - }} - > -
-
- ; -} From 2992f14bec16c1496900390114cdbf2b48f7f6b7 Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Tue, 7 Oct 2025 16:01:44 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=A4=96=20Merge=20PR=20#73791=20k6:=20?= =?UTF-8?q?create=20Device=20type=20definition=20by=20@Juneezee?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eng Zer Jun --- types/k6/browser/index.d.ts | 133 ++++++++++++++++++++++++++++++++++++ types/k6/test/browser.ts | 7 +- 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/types/k6/browser/index.d.ts b/types/k6/browser/index.d.ts index 2050420f525eff..577a878ced4916 100644 --- a/types/k6/browser/index.d.ts +++ b/types/k6/browser/index.d.ts @@ -414,6 +414,139 @@ export interface ElementStateFilter { state?: ElementState; } +/** + * The `devices` named export defines emulation settings for many end-user + * devices that can be used to simulate browser behavior on a mobile device. + * + * @example + * ```js + * import { browser, devices } from 'k6/browser'; + * ... // redacted + * const iphoneX = devices['iPhone X']; + * const context = await browser.newContext(iphoneX); + * ... // redacted + * ``` + */ +export const devices: Record< + | "Blackberry PlayBook" + | "Blackberry PlayBook landscape" + | "BlackBerry Z30" + | "BlackBerry Z30 landscape" + | "Galaxy Note 3" + | "Galaxy Note 3 landscape" + | "Galaxy Note II" + | "Galaxy Note II landscape" + | "Galaxy S III" + | "Galaxy S III landscape" + | "Galaxy S5" + | "Galaxy S5 landscape" + | "iPad" + | "iPad landscape" + | "iPad Mini" + | "iPad Mini landscape" + | "iPad Pro" + | "iPad Pro landscape" + | "iPhone 4" + | "iPhone 4 landscape" + | "iPhone 5" + | "iPhone 5 landscape" + | "iPhone 6" + | "iPhone 6 landscape" + | "iPhone 6 Plus" + | "iPhone 6 Plus landscape" + | "iPhone 7" + | "iPhone 7 landscape" + | "iPhone 7 Plus" + | "iPhone 7 Plus landscape" + | "iPhone 8" + | "iPhone 8 landscape" + | "iPhone 8 Plus" + | "iPhone 8 Plus landscape" + | "iPhone SE" + | "iPhone SE landscape" + | "iPhone X" + | "iPhone X landscape" + | "iPhone XR" + | "iPhone XR landscape" + | "JioPhone 2" + | "JioPhone 2 landscape" + | "Kindle Fire HDX" + | "Kindle Fire HDX landscape" + | "LG Optimus L70" + | "LG Optimus L70 landscape" + | "Microsoft Lumia 550" + | "Microsoft Lumia 950" + | "Microsoft Lumia 950 landscape" + | "Nexus 10" + | "Nexus 10 landscape" + | "Nexus 4" + | "Nexus 4 landscape" + | "Nexus 5" + | "Nexus 5 landscape" + | "Nexus 5X" + | "Nexus 5X landscape" + | "Nexus 6" + | "Nexus 6 landscape" + | "Nexus 6P" + | "Nexus 6P landscape" + | "Nexus 7" + | "Nexus 7 landscape" + | "Nokia Lumia 520" + | "Nokia Lumia 520 landscape" + | "Nokia N9" + | "Nokia N9 landscape" + | "Pixel 2" + | "Pixel 2 landscape" + | "Pixel 2 XL" + | "Pixel 2 XL landscape", + Device +>; + +/** + * Device represents an end-user device (computer, tablet, phone etc.). + */ +export interface Device { + /** + * Name of the device. + */ + name: string; + + /** + * User agent of the device. + */ + userAgent: string; + + /** + * Viewport size of the device. + */ + viewport: { + /** + * page width in pixels. + */ + width: number; + + /** + * page height in pixels. + */ + height: number; + }; + + /** + * Device viewport scale factor. + */ + deviceScaleFactor: number; + + /** + * Indicates whether the device is a mobile device. + */ + isMobile: boolean; + + /** + * Indicates whether the device support touch events. + */ + hasTouch: boolean; +} + /** * BrowserPermissions defines all the possible permissions that can be granted * to the browser application. diff --git a/types/k6/test/browser.ts b/types/k6/test/browser.ts index 091ee35c7014f3..896c6319f0530e 100644 --- a/types/k6/test/browser.ts +++ b/types/k6/test/browser.ts @@ -1,4 +1,4 @@ -import { browser } from "k6/browser"; +import { browser, devices } from "k6/browser"; const url = "http://example.com"; const selector = "a[href=\"http://example.com\"]"; @@ -63,6 +63,11 @@ async function test() { // $ExpectType Promise browser.newContext({ viewport: { width: 1280, height: 720 } }); + // $ExpectType Device + const iphoneX = devices["iPhone X"]; + // $ExpectType Promise + browser.newContext(iphoneX); + // $ExpectType Promise browser.newPage(); // $ExpectType Promise