diff --git a/change/@fluentui-react-jsx-runtime-911c855a-9f72-4f62-8d13-d9f768887ada.json b/change/@fluentui-react-jsx-runtime-911c855a-9f72-4f62-8d13-d9f768887ada.json
new file mode 100644
index 00000000000000..0fbb5abd271f58
--- /dev/null
+++ b/change/@fluentui-react-jsx-runtime-911c855a-9f72-4f62-8d13-d9f768887ada.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "feat: supports new automatic JSX runtime",
+ "packageName": "@fluentui/react-jsx-runtime",
+ "email": "bernardo.sunderhus@gmail.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/react-components/react-jsx-runtime/etc/react-jsx-runtime.api.md b/packages/react-components/react-jsx-runtime/etc/react-jsx-runtime.api.md
index 9ec8f93f3f9b8c..df39490861ddf4 100644
--- a/packages/react-components/react-jsx-runtime/etc/react-jsx-runtime.api.md
+++ b/packages/react-components/react-jsx-runtime/etc/react-jsx-runtime.api.md
@@ -8,7 +8,7 @@ import { Fragment } from 'react';
import * as React_2 from 'react';
// @public (undocumented)
-export function createElement
(type: React_2.ElementType
, props?: P | null, ...children: React_2.ReactNode[]): React_2.ReactElement
| null;
+export function createElement
(type: React_2.ElementType
, props?: P | null, ...children: React_2.ReactNode[]): React_2.ReactElement
;
export { Fragment }
diff --git a/packages/react-components/react-jsx-runtime/package.json b/packages/react-components/react-jsx-runtime/package.json
index 8662a4aef49312..339a3fd0f9ddef 100644
--- a/packages/react-components/react-jsx-runtime/package.json
+++ b/packages/react-components/react-jsx-runtime/package.json
@@ -51,6 +51,18 @@
"import": "./lib/index.js",
"require": "./lib-commonjs/index.js"
},
+ "./jsx-dev-runtime": {
+ "types": "./dist/jsx-dev-runtime.d.ts",
+ "node": "./lib-commonjs/jsx-dev-runtime.js",
+ "import": "./lib/jsx-dev-runtime.js",
+ "require": "./lib-commonjs/jsx-dev-runtime.js"
+ },
+ "./jsx-runtime": {
+ "types": "./dist/jsx-runtime.d.ts",
+ "node": "./lib-commonjs/jsx-runtime.js",
+ "import": "./lib/jsx-runtime.js",
+ "require": "./lib-commonjs/jsx-runtime.js"
+ },
"./package.json": "./package.json"
}
}
diff --git a/packages/react-components/react-jsx-runtime/src/createElement.test.tsx b/packages/react-components/react-jsx-runtime/src/createElement.test.tsx
index 75c605fd981310..d86020619a34a5 100644
--- a/packages/react-components/react-jsx-runtime/src/createElement.test.tsx
+++ b/packages/react-components/react-jsx-runtime/src/createElement.test.tsx
@@ -1,17 +1,9 @@
/** @jsxRuntime classic */
-/** @jsxFrag Fragment */
/** @jsx createElement */
import { render } from '@testing-library/react';
-import {
- ComponentProps,
- ComponentState,
- Slot,
- assertSlots,
- getSlotsNext,
- resolveShorthand,
- slot,
-} from '@fluentui/react-utilities';
+import { assertSlots, getSlotsNext, resolveShorthand, slot } from '@fluentui/react-utilities';
+import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { createElement } from './createElement';
describe('createElement with getSlotsNext', () => {
@@ -55,6 +47,7 @@ describe('createElement with getSlotsNext', () => {
,
);
@@ -66,6 +59,9 @@ describe('createElement with getSlotsNext', () => {
2
+
+ 3
+
`);
});
@@ -347,7 +343,5 @@ describe('createElement with assertSlots', () => {
expect(result.container.firstChild).toMatchInlineSnapshot(``);
});
-
- it.todo("should pass 'as' property to base element that aren't html element");
});
});
diff --git a/packages/react-components/react-jsx-runtime/src/createElement.ts b/packages/react-components/react-jsx-runtime/src/createElement.ts
index a234a7eec2c10a..ff492abb1265d8 100644
--- a/packages/react-components/react-jsx-runtime/src/createElement.ts
+++ b/packages/react-components/react-jsx-runtime/src/createElement.ts
@@ -1,61 +1,21 @@
import * as React from 'react';
-import {
- UnknownSlotProps,
- isSlot,
- SlotComponentType,
- SLOT_ELEMENT_TYPE_SYMBOL,
- SLOT_RENDER_FUNCTION_SYMBOL,
-} from '@fluentui/react-utilities';
+import { isSlot } from '@fluentui/react-utilities';
+import { createElementFromSlotComponent } from './jsx/createElementFromSlotComponent';
+import { createCompatSlotComponent } from './utils/createCompatSlotComponent';
export function createElement(
type: React.ElementType
,
props?: P | null,
...children: React.ReactNode[]
-): React.ReactElement
| null {
+): React.ReactElement
{
// TODO:
// this is for backwards compatibility with getSlotsNext
// it should be removed once getSlotsNext is obsolete
if (isSlot
(props)) {
- return createElementFromSlotComponent(
- { ...props, [SLOT_ELEMENT_TYPE_SYMBOL]: type } as SlotComponentType
,
- children,
- );
+ return createElementFromSlotComponent(createCompatSlotComponent(type, props), children);
}
if (isSlot
(type)) {
return createElementFromSlotComponent(type, children);
}
return React.createElement(type, props, ...children);
}
-
-function createElementFromSlotComponent(
- type: SlotComponentType,
- overrideChildren: React.ReactNode[],
-): React.ReactElement | null {
- const {
- as,
- [SLOT_ELEMENT_TYPE_SYMBOL]: baseElementType,
- [SLOT_RENDER_FUNCTION_SYMBOL]: renderFunction,
- ...propsWithoutMetadata
- } = type;
- const props = propsWithoutMetadata as UnknownSlotProps as Props;
-
- const elementType = typeof baseElementType === 'string' ? as ?? baseElementType : baseElementType;
-
- if (typeof elementType !== 'string' && as) {
- props.as = as;
- }
-
- if (renderFunction) {
- if (overrideChildren.length > 0) {
- props.children = React.createElement(React.Fragment, {}, ...overrideChildren);
- }
-
- return React.createElement(
- React.Fragment,
- {},
- renderFunction(elementType as React.ElementType, props),
- ) as React.ReactElement;
- }
-
- return React.createElement(elementType, props, ...overrideChildren);
-}
diff --git a/packages/react-components/react-jsx-runtime/src/jsx-dev-runtime.ts b/packages/react-components/react-jsx-runtime/src/jsx-dev-runtime.ts
new file mode 100644
index 00000000000000..06a103f49ba479
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/jsx-dev-runtime.ts
@@ -0,0 +1,26 @@
+import type * as React from 'react';
+import { isSlot } from '@fluentui/react-utilities';
+import { jsxDEVFromSlotComponent } from './jsx/jsxDEVFromSlotComponent';
+import { createCompatSlotComponent } from './utils/createCompatSlotComponent';
+import { DevRuntime } from './utils/DevRuntime';
+
+export { Fragment } from 'react';
+
+export function jsxDEV(
+ type: React.ElementType
,
+ props: P,
+ key?: React.Key,
+ source?: boolean,
+ self?: unknown,
+): React.ReactElement
{
+ // TODO:
+ // this is for backwards compatibility with getSlotsNext
+ // it should be removed once getSlotsNext is obsolete
+ if (isSlot
(props)) {
+ return jsxDEVFromSlotComponent(createCompatSlotComponent(type, props), null, key, source, self);
+ }
+ if (isSlot
(type)) {
+ return jsxDEVFromSlotComponent(type, props, key, source, self);
+ }
+ return DevRuntime.jsxDEV(type, props, key, source, self);
+}
diff --git a/packages/react-components/react-jsx-runtime/src/jsx-runtime.test.tsx b/packages/react-components/react-jsx-runtime/src/jsx-runtime.test.tsx
new file mode 100644
index 00000000000000..5f72f4d437929e
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/jsx-runtime.test.tsx
@@ -0,0 +1,341 @@
+/** @jsxImportSource @fluentui/react-jsx-runtime */
+
+import { render } from '@testing-library/react';
+import { assertSlots, getSlotsNext, resolveShorthand, slot } from '@fluentui/react-utilities';
+import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
+
+describe('createElement with getSlotsNext', () => {
+ describe('general behavior tests', () => {
+ it('handles a string', () => {
+ const result = render(
Hello world
);
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+ Hello world
+
+ `);
+ });
+
+ it('handles an array', () => {
+ const result = render(
+
+ {Array.from({ length: 3 }, (_, i) => (
+
{i}
+ ))}
+
,
+ );
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+
+ 0
+
+
+ 1
+
+
+ 2
+
+
+ `);
+ });
+
+ it('handles an array of children', () => {
+ const result = render(
+ ,
+ );
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+ `);
+ });
+ });
+
+ describe('custom behavior tests', () => {
+ it('keeps children from "defaultProps" in a render callback', () => {
+ type TestComponentSlots = { slot: Slot<'div'> };
+ type TestComponentState = ComponentState;
+ type TestComponentProps = ComponentProps>;
+
+ const TestComponent = (props: TestComponentProps) => {
+ const state: TestComponentState = {
+ components: { slot: 'div' },
+
+ slot: resolveShorthand(props.slot, {
+ defaultProps: { children: 'Default Children', id: 'slot' },
+ }),
+ };
+ const { slots, slotProps } = getSlotsNext(state);
+
+ return ;
+ };
+
+ const children = jest.fn().mockImplementation((Component, props) => (
+
+
+
+ ));
+ const result = render();
+
+ expect(children).toHaveBeenCalledTimes(1);
+ expect(children).toHaveBeenCalledWith('div', { children: 'Default Children', id: 'slot' });
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+ `);
+ });
+
+ it('keeps children from a render template in a render callback', () => {
+ type TestComponentSlots = { outer: Slot<'div'>; inner: Slot<'div'> };
+ type TestComponentState = ComponentState;
+ type TestComponentProps = ComponentProps>;
+
+ const TestComponent = (props: TestComponentProps) => {
+ const state: TestComponentState = {
+ components: { inner: 'div', outer: 'div' },
+
+ inner: resolveShorthand(props.inner, { defaultProps: { id: 'inner' } }),
+ outer: resolveShorthand(props.outer, { defaultProps: { id: 'outer' } }),
+ };
+ const { slots, slotProps } = getSlotsNext(state);
+
+ return (
+
+
+
+ );
+ };
+
+ const children = jest.fn().mockImplementation((Component, props) => (
+
+
+
+ ));
+ const result = render();
+
+ expect(children).toHaveBeenCalledTimes(1);
+ expect(children.mock.calls[0][0]).toBe('div');
+ expect(children.mock.calls[0][1].id).toBe('outer');
+ expect(children.mock.calls[0][1].children).toMatchInlineSnapshot(`
+
+ Inner children
+
+ `);
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+ `);
+ });
+ });
+});
+
+describe('createElement with assertSlots', () => {
+ describe('general behavior tests', () => {
+ it('handles a string', () => {
+ const result = render(Hello world
);
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+ Hello world
+
+ `);
+ });
+
+ it('handles an array', () => {
+ const result = render(
+
+ {Array.from({ length: 3 }, (_, i) => (
+
{i}
+ ))}
+
,
+ );
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+
+ 0
+
+
+ 1
+
+
+ 2
+
+
+ `);
+ });
+
+ it('handles an array of children', () => {
+ const result = render(
+ ,
+ );
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+
+ 0
+
+
+ 1
+
+
+ 2
+
+
+ `);
+ });
+ });
+
+ describe('custom behavior tests', () => {
+ it('keeps children from "defaultProps" in a render callback', () => {
+ type TestComponentSlots = {
+ someSlot: NonNullable>;
+ };
+ type TestComponentProps = ComponentProps>;
+ type TestComponentState = ComponentState;
+
+ const TestComponent = (props: TestComponentProps) => {
+ const state: TestComponentState = {
+ components: { someSlot: 'div' },
+ someSlot: slot.always(props.someSlot, {
+ elementType: 'div',
+ defaultProps: { children: 'Default Children', id: 'slot' },
+ }),
+ };
+ assertSlots(state);
+ return ;
+ };
+
+ const children = jest.fn().mockImplementation((Component, props) => (
+
+
+
+ ));
+ const result = render();
+
+ expect(children).toHaveBeenCalledTimes(1);
+ expect(children).toHaveBeenCalledWith('div', { children: 'Default Children', id: 'slot' });
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+ `);
+ });
+
+ it('keeps children from a render template in a render callback', () => {
+ type TestComponentSlots = { outer: NonNullable>; inner: NonNullable> };
+ type TestComponentState = ComponentState;
+ type TestComponentProps = ComponentProps>;
+
+ const TestComponent = (props: TestComponentProps) => {
+ const state: TestComponentState = {
+ components: { outer: 'div', inner: 'div' },
+ inner: slot.always(props.inner, { defaultProps: { id: 'inner' }, elementType: 'div' }),
+ outer: slot.always(props.outer, { defaultProps: { id: 'outer' }, elementType: 'div' }),
+ };
+ assertSlots(state);
+ return (
+
+
+
+ );
+ };
+
+ const children = jest.fn().mockImplementation((Component, props) => (
+
+
+
+ ));
+ const result = render();
+
+ expect(children).toHaveBeenCalledTimes(1);
+ expect(children.mock.calls[0][0]).toBe('div');
+ expect(children.mock.calls[0][1].id).toBe('outer');
+ expect(children.mock.calls[0][1].children).toMatchInlineSnapshot(`
+
+ Inner children
+
+ `);
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(`
+
+ `);
+ });
+
+ it("should support 'as' property to opt-out of base element type", () => {
+ type TestComponentSlots = { slot: NonNullable> };
+ type TestComponentState = ComponentState;
+ type TestComponentProps = ComponentProps>;
+
+ const TestComponent = (props: TestComponentProps) => {
+ const state: TestComponentState = {
+ components: { slot: 'div' },
+ slot: slot.always(props.slot, { elementType: 'div' }),
+ };
+ assertSlots(state);
+ return ;
+ };
+
+ const result = render();
+
+ expect(result.container.firstChild).toMatchInlineSnapshot(``);
+ });
+ });
+});
diff --git a/packages/react-components/react-jsx-runtime/src/jsx-runtime.ts b/packages/react-components/react-jsx-runtime/src/jsx-runtime.ts
new file mode 100644
index 00000000000000..6c5bb260dc56a3
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/jsx-runtime.ts
@@ -0,0 +1,34 @@
+import type * as React from 'react';
+import { isSlot } from '@fluentui/react-utilities';
+import { jsxDynamicFromSlotComponent } from './jsx/jsxDynamicFromSlotComponent';
+import { jsxStaticFromSlotComponent } from './jsx/jsxStaticFromSlotComponent';
+import { createCompatSlotComponent } from './utils/createCompatSlotComponent';
+import { Runtime } from './utils/Runtime';
+
+export { Fragment } from 'react';
+
+export function jsx(type: React.ElementType
, props: P, key?: React.Key): React.ReactElement
{
+ // TODO:
+ // this is for backwards compatibility with getSlotsNext
+ // it should be removed once getSlotsNext is obsolete
+ if (isSlot
(props)) {
+ return jsxDynamicFromSlotComponent(createCompatSlotComponent(type, props), null, key);
+ }
+ if (isSlot
(type)) {
+ return jsxDynamicFromSlotComponent(type, props, key);
+ }
+ return Runtime.jsx(type, props, key);
+}
+
+export function jsxs
(type: React.ElementType
, props: P, key?: React.Key): React.ReactElement
{
+ // TODO:
+ // this is for backwards compatibility with getSlotsNext
+ // it should be removed once getSlotsNext is obsolete
+ if (isSlot
(props)) {
+ return jsxStaticFromSlotComponent(createCompatSlotComponent(type, props), null, key);
+ }
+ if (isSlot
(type)) {
+ return jsxStaticFromSlotComponent(type, props, key);
+ }
+ return Runtime.jsxs(type, props, key);
+}
diff --git a/packages/react-components/react-jsx-runtime/src/jsx/createElementFromSlotComponent.ts b/packages/react-components/react-jsx-runtime/src/jsx/createElementFromSlotComponent.ts
new file mode 100644
index 00000000000000..3c9b4e73fbafc4
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/jsx/createElementFromSlotComponent.ts
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import type { SlotComponentType, UnknownSlotProps } from '@fluentui/react-utilities';
+import { getMetadataFromSlotComponent } from '../utils/getMetadataFromSlotComponent';
+
+/**
+ * @internal
+ * creates a ReactElement from a slot declaration
+ */
+export function createElementFromSlotComponent(
+ type: SlotComponentType,
+ overrideChildren: React.ReactNode[],
+): React.ReactElement {
+ const { elementType, renderFunction, props } = getMetadataFromSlotComponent(type);
+
+ if (renderFunction) {
+ if (overrideChildren.length > 0) {
+ props.children = React.createElement(React.Fragment, {}, ...overrideChildren);
+ }
+
+ return React.createElement(
+ React.Fragment,
+ {},
+ renderFunction(elementType as React.ElementType, props),
+ ) as React.ReactElement;
+ }
+
+ return React.createElement(elementType, props, ...overrideChildren);
+}
diff --git a/packages/react-components/react-jsx-runtime/src/jsx/jsxDEVFromSlotComponent.ts b/packages/react-components/react-jsx-runtime/src/jsx/jsxDEVFromSlotComponent.ts
new file mode 100644
index 00000000000000..dd9f5a47b69f79
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/jsx/jsxDEVFromSlotComponent.ts
@@ -0,0 +1,30 @@
+import * as React from 'react';
+import type { SlotComponentType, UnknownSlotProps } from '@fluentui/react-utilities';
+import { getMetadataFromSlotComponent } from '../utils/getMetadataFromSlotComponent';
+import { DevRuntime } from '../utils/DevRuntime';
+
+export function jsxDEVFromSlotComponent(
+ type: SlotComponentType,
+ overrideProps: Props | null,
+ key?: React.Key,
+ source?: unknown,
+ self?: unknown,
+): React.ReactElement {
+ const { elementType, renderFunction, props: slotProps } = getMetadataFromSlotComponent(type);
+
+ const props: Props = { ...slotProps, ...overrideProps };
+
+ if (renderFunction) {
+ return DevRuntime.jsxDEV(
+ React.Fragment,
+ {
+ children: renderFunction(elementType, props),
+ },
+ key,
+ source,
+ self,
+ ) as React.ReactElement;
+ }
+
+ return DevRuntime.jsxDEV(elementType, props, key, source, self);
+}
diff --git a/packages/react-components/react-jsx-runtime/src/jsx/jsxDynamicFromSlotComponent.ts b/packages/react-components/react-jsx-runtime/src/jsx/jsxDynamicFromSlotComponent.ts
new file mode 100644
index 00000000000000..1898acb4cf0dff
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/jsx/jsxDynamicFromSlotComponent.ts
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import type { SlotComponentType, UnknownSlotProps } from '@fluentui/react-utilities';
+import { getMetadataFromSlotComponent } from '../utils/getMetadataFromSlotComponent';
+import { Runtime } from '../utils/Runtime';
+
+/**
+ * @internal
+ */
+export function jsxDynamicFromSlotComponent(
+ type: SlotComponentType,
+ overrideProps: Props | null,
+ key?: React.Key,
+): React.ReactElement {
+ const { elementType, renderFunction, props: slotProps } = getMetadataFromSlotComponent(type);
+
+ const props: Props = { ...slotProps, ...overrideProps };
+
+ if (renderFunction) {
+ return Runtime.jsx(
+ React.Fragment,
+ {
+ children: renderFunction(elementType, props),
+ },
+ key,
+ ) as React.ReactElement;
+ }
+
+ return Runtime.jsx(elementType, props, key);
+}
diff --git a/packages/react-components/react-jsx-runtime/src/jsx/jsxStaticFromSlotComponent.ts b/packages/react-components/react-jsx-runtime/src/jsx/jsxStaticFromSlotComponent.ts
new file mode 100644
index 00000000000000..4409294991baee
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/jsx/jsxStaticFromSlotComponent.ts
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import type { SlotComponentType, UnknownSlotProps } from '@fluentui/react-utilities';
+import { getMetadataFromSlotComponent } from '../utils/getMetadataFromSlotComponent';
+import { Runtime } from '../utils/Runtime';
+
+/**
+ * @internal
+ */
+export function jsxStaticFromSlotComponent(
+ type: SlotComponentType,
+ overrideProps: Props | null,
+ key?: React.Key,
+): React.ReactElement {
+ const { elementType, renderFunction, props: slotProps } = getMetadataFromSlotComponent(type);
+
+ const props: Props = { ...slotProps, ...overrideProps };
+
+ if (renderFunction) {
+ return Runtime.jsxs(
+ React.Fragment,
+ {
+ children: renderFunction(elementType, props),
+ },
+ key,
+ ) as React.ReactElement;
+ }
+
+ return Runtime.jsxs(elementType, props, key);
+}
diff --git a/packages/react-components/react-jsx-runtime/src/utils/DevRuntime.ts b/packages/react-components/react-jsx-runtime/src/utils/DevRuntime.ts
new file mode 100644
index 00000000000000..b62362bbdd10b6
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/utils/DevRuntime.ts
@@ -0,0 +1,6 @@
+import * as ReactDevRuntime from 'react/jsx-dev-runtime';
+import type { JSXRuntime } from './types';
+
+export const DevRuntime = ReactDevRuntime as {
+ jsxDEV: JSXRuntime;
+};
diff --git a/packages/react-components/react-jsx-runtime/src/utils/Runtime.ts b/packages/react-components/react-jsx-runtime/src/utils/Runtime.ts
new file mode 100644
index 00000000000000..981ed7ee2a4963
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/utils/Runtime.ts
@@ -0,0 +1,7 @@
+import * as ReactRuntime from 'react/jsx-runtime';
+import type { JSXRuntime } from './types';
+
+export const Runtime = ReactRuntime as {
+ jsx: JSXRuntime;
+ jsxs: JSXRuntime;
+};
diff --git a/packages/react-components/react-jsx-runtime/src/utils/createCompatSlotComponent.ts b/packages/react-components/react-jsx-runtime/src/utils/createCompatSlotComponent.ts
new file mode 100644
index 00000000000000..467c2813c728a9
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/utils/createCompatSlotComponent.ts
@@ -0,0 +1,13 @@
+import * as React from 'react';
+import { SLOT_ELEMENT_TYPE_SYMBOL } from '@fluentui/react-utilities';
+import type { SlotComponentType } from '@fluentui/react-utilities';
+
+// TODO:
+// this is for backwards compatibility with getSlotsNext
+// it should be removed once getSlotsNext is obsolete
+export function createCompatSlotComponent(type: React.ElementType
, props: P): SlotComponentType
{
+ return {
+ ...props,
+ [SLOT_ELEMENT_TYPE_SYMBOL]: type,
+ } as SlotComponentType
;
+}
diff --git a/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.test.ts b/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.test.ts
new file mode 100644
index 00000000000000..ff190f3ae41c06
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.test.ts
@@ -0,0 +1,62 @@
+import * as React from 'react';
+import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from '@fluentui/react-utilities';
+import type { SlotComponentType, SlotRenderFunction } from '@fluentui/react-utilities';
+import { getMetadataFromSlotComponent } from './getMetadataFromSlotComponent';
+
+type TestProps = React.HTMLAttributes & { as?: 'div' | 'span' };
+
+describe('getMetadataFromSlotComponent', () => {
+ it('gets metadata from slot component', () => {
+ expect(
+ getMetadataFromSlotComponent({
+ [SLOT_ELEMENT_TYPE_SYMBOL]: 'div',
+ tabIndex: 0,
+ } as SlotComponentType),
+ ).toEqual({
+ elementType: 'div',
+ props: { tabIndex: 0 },
+ renderFunction: undefined,
+ });
+ });
+
+ it('handles render props', () => {
+ expect(
+ getMetadataFromSlotComponent({
+ [SLOT_ELEMENT_TYPE_SYMBOL]: 'div',
+ [SLOT_RENDER_FUNCTION_SYMBOL]: jest.fn() as SlotRenderFunction,
+ tabIndex: 0,
+ } as SlotComponentType),
+ ).toEqual({
+ elementType: 'div',
+ props: { tabIndex: 0 },
+ renderFunction: expect.any(Function),
+ });
+ });
+ it("should override elementType with 'as' when base element is HTML element", () => {
+ expect(
+ getMetadataFromSlotComponent({
+ [SLOT_ELEMENT_TYPE_SYMBOL]: 'div',
+ as: 'span',
+ tabIndex: 0,
+ } as SlotComponentType),
+ ).toEqual({
+ elementType: 'span',
+ props: { tabIndex: 0 },
+ renderFunction: undefined,
+ });
+ });
+ it("should pass 'as' property to base element that aren't HTML element", () => {
+ const fn = (props: TestProps) => null;
+ expect(
+ getMetadataFromSlotComponent({
+ [SLOT_ELEMENT_TYPE_SYMBOL]: fn,
+ as: 'div',
+ tabIndex: 0,
+ } as SlotComponentType),
+ ).toEqual({
+ elementType: fn,
+ props: { tabIndex: 0, as: 'div' },
+ renderFunction: undefined,
+ });
+ });
+});
diff --git a/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.ts b/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.ts
new file mode 100644
index 00000000000000..8c95fd3ba80049
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.ts
@@ -0,0 +1,25 @@
+import type * as React from 'react';
+import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from '@fluentui/react-utilities';
+import type { SlotComponentType, UnknownSlotProps } from '@fluentui/react-utilities';
+
+/**
+ * @internal
+ */
+export function getMetadataFromSlotComponent(type: SlotComponentType) {
+ const {
+ as,
+ [SLOT_ELEMENT_TYPE_SYMBOL]: baseElementType,
+ [SLOT_RENDER_FUNCTION_SYMBOL]: renderFunction,
+ ...propsWithoutMetadata
+ } = type;
+ const props = propsWithoutMetadata as UnknownSlotProps as Props;
+
+ const elementType = (
+ typeof baseElementType === 'string' ? as ?? baseElementType : baseElementType
+ ) as React.ElementType;
+
+ if (typeof elementType !== 'string' && as) {
+ props.as = as;
+ }
+ return { elementType, props, renderFunction };
+}
diff --git a/packages/react-components/react-jsx-runtime/src/utils/types.ts b/packages/react-components/react-jsx-runtime/src/utils/types.ts
new file mode 100644
index 00000000000000..4099a658aeb45c
--- /dev/null
+++ b/packages/react-components/react-jsx-runtime/src/utils/types.ts
@@ -0,0 +1,9 @@
+import type * as React from 'react';
+
+export type JSXRuntime = (
+ type: React.ElementType
,
+ props: P | null,
+ key?: React.Key,
+ source?: unknown,
+ self?: unknown,
+) => React.ReactElement
;
diff --git a/tsconfig.base.all.json b/tsconfig.base.all.json
index b70a0a4b540677..93dfd6b78c44ac 100644
--- a/tsconfig.base.all.json
+++ b/tsconfig.base.all.json
@@ -113,6 +113,10 @@
"@fluentui/react-infobutton": ["packages/react-components/react-infobutton/src/index.ts"],
"@fluentui/react-input": ["packages/react-components/react-input/src/index.ts"],
"@fluentui/react-jsx-runtime": ["packages/react-components/react-jsx-runtime/src/index.ts"],
+ "@fluentui/react-jsx-runtime/jsx-runtime": ["packages/react-components/react-jsx-runtime/src/jsx-runtime.ts"],
+ "@fluentui/react-jsx-runtime/jsx-dev-runtime": [
+ "packages/react-components/react-jsx-runtime/src/jsx-dev-runtime.ts"
+ ],
"@fluentui/react-label": ["packages/react-components/react-label/src/index.ts"],
"@fluentui/react-link": ["packages/react-components/react-link/src/index.ts"],
"@fluentui/react-menu": ["packages/react-components/react-menu/src/index.ts"],
diff --git a/tsconfig.base.json b/tsconfig.base.json
index a0656b78d30c9e..717a1beb47ba1b 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -47,6 +47,10 @@
"@fluentui/react-infobutton": ["packages/react-components/react-infobutton/src/index.ts"],
"@fluentui/react-input": ["packages/react-components/react-input/src/index.ts"],
"@fluentui/react-jsx-runtime": ["packages/react-components/react-jsx-runtime/src/index.ts"],
+ "@fluentui/react-jsx-runtime/jsx-runtime": ["packages/react-components/react-jsx-runtime/src/jsx-runtime.ts"],
+ "@fluentui/react-jsx-runtime/jsx-dev-runtime": [
+ "packages/react-components/react-jsx-runtime/src/jsx-dev-runtime.ts"
+ ],
"@fluentui/react-label": ["packages/react-components/react-label/src/index.ts"],
"@fluentui/react-link": ["packages/react-components/react-link/src/index.ts"],
"@fluentui/react-menu": ["packages/react-components/react-menu/src/index.ts"],