;
+};
+
+export function createElement(
+ type: React.ElementType
,
+ props?: P | null,
+ ...children: React.ReactNode[]
+): React.ReactElement
| null {
+ if (!isSlotComponent(props)) {
+ return React.createElement(type, props, ...children);
+ }
+
+ const result = normalizeRenderFunction(props, children);
+ return React.createElement(
+ React.Fragment,
+ {},
+ result.renderFunction(type, { ...result.props, children: result.children }),
+ ) as React.ReactElement
;
+}
+
+function normalizeRenderFunction(
+ propsWithMetadata: WithMetadata,
+ overrideChildren?: React.ReactNode[],
+): {
+ props: Props;
+ children: React.ReactNode;
+ renderFunction: SlotRenderFunction;
+} {
+ const { [SLOT_RENDER_FUNCTION_SYMBOL]: renderFunction, children: externalChildren, ...props } = propsWithMetadata;
+
+ const children: React.ReactNode =
+ Array.isArray(overrideChildren) && overrideChildren.length > 0
+ ? React.createElement(React.Fragment, {}, ...overrideChildren)
+ : externalChildren;
+
+ return {
+ children,
+ renderFunction,
+ props: props as UnknownSlotProps as Props,
+ };
+}
+
+export function isSlotComponent(props?: Props | null): props is WithMetadata {
+ return Boolean(props?.hasOwnProperty(SLOT_RENDER_FUNCTION_SYMBOL));
+}
diff --git a/packages/react-components/react-jsx-runtime/src/index.ts b/packages/react-components/react-jsx-runtime/src/index.ts
index 388451abd3502..0276e539054a3 100644
--- a/packages/react-components/react-jsx-runtime/src/index.ts
+++ b/packages/react-components/react-jsx-runtime/src/index.ts
@@ -1,2 +1,2 @@
-export { jsx } from './jsx';
+export { createElement } from './createElement';
export { Fragment } from 'react';
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
deleted file mode 100644
index 85b668ae011b3..0000000000000
--- a/packages/react-components/react-jsx-runtime/src/jsx-dev-runtime.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-///
-
-import * as React from 'react';
-import { jsx as createElement, extractChildrenFromProps } from './jsx';
-
-export function jsxDEV(
- type: React.ElementType
,
- props?: P | null,
- key?: string,
- isStaticChildren?: boolean,
- source?: unknown,
- self?: unknown,
-): React.ReactElement
| null {
- // extractChildrenFromProps is required since jsxDev signature differs from React.createElement signature
- return createElement(type, props, ...extractChildrenFromProps(props));
-}
-
-export { Fragment } from 'react/jsx-dev-runtime';
diff --git a/packages/react-components/react-jsx-runtime/src/jsx-runtime.ts b/packages/react-components/react-jsx-runtime/src/jsx-runtime.ts
deleted file mode 100644
index bae0a1772ecdd..0000000000000
--- a/packages/react-components/react-jsx-runtime/src/jsx-runtime.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-///
-
-import * as React from 'react';
-import { jsx as createElement, extractChildrenFromProps } from './jsx';
-
-export function jsx
(
- type: React.ElementType
,
- props?: P | null,
- key?: string,
-): React.ReactElement
| null {
- // extractChildrenFromProps is required since jsx signature differs from React.createElement signature
- return createElement(type, props, ...extractChildrenFromProps(props));
-}
-
-export function jsxs
(
- type: React.ElementType
,
- props?: P | null,
- key?: string,
-): React.ReactElement
| null {
- // extractChildrenFromProps is required since jsxs signature differs from React.createElement signature
- return createElement(type, props, ...extractChildrenFromProps(props));
-}
-
-export { Fragment } from 'react/jsx-runtime';
diff --git a/packages/react-components/react-jsx-runtime/src/jsx.test.tsx b/packages/react-components/react-jsx-runtime/src/jsx.test.tsx
deleted file mode 100644
index f99decf9e4863..0000000000000
--- a/packages/react-components/react-jsx-runtime/src/jsx.test.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-/* eslint-disable jsdoc/check-tag-names */
-/** @jsxRuntime classic */
-/** @jsxFrag Fragment */
-/** @jsx jsx */
-/* eslint-enable jsdoc/check-tag-names */
-
-import {
- ComponentProps,
- ComponentState,
- Slot,
- getSlots,
- getSlotsNext,
- resolveShorthand,
-} from '@fluentui/react-utilities';
-import { jsx, Fragment } from './index';
-import { render } from '@testing-library/react';
-
-type TestComponentSlots = { slot: Slot<'div'> };
-type TestComponentState = ComponentState;
-type TestComponentProps = ComponentProps> & {
- getSlots: typeof getSlots | typeof getSlotsNext;
-};
-
-const TestComponent = (props: TestComponentProps) => {
- const state: TestComponentState = {
- components: {
- slot: 'div',
- },
- slot: resolveShorthand(
- props.slot ?? {
- children: (C, p) => (
- <>
- before
-
- after
- >
- ),
- },
- {
- required: true,
- defaultProps: {
- children: this is internal children
,
- },
- },
- ),
- };
- const { slots, slotProps } = props.getSlots(state);
- return ;
-};
-
-describe('jsx', () => {
- it('should lose internal children while using getSlots', () => {
- const result = render();
- expect(result.container).toMatchInlineSnapshot(`
-
-
- before
-
-
-
- after
-
-
- `);
- });
- it('should keep internal children while using getSlotsNext', () => {
- const result = render();
- expect(result.container).toMatchInlineSnapshot(`
-
-
- before
-
-
-
- this is internal children
-
-
-
- after
-
-
- `);
- });
-});
diff --git a/packages/react-components/react-jsx-runtime/src/jsx.ts b/packages/react-components/react-jsx-runtime/src/jsx.ts
deleted file mode 100644
index 47811383c2e7b..0000000000000
--- a/packages/react-components/react-jsx-runtime/src/jsx.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import * as React from 'react';
-import {
- SlotRenderFunction,
- UnknownSlotProps,
- SLOT_EXTERNAL_CHILDREN_SYMBOL,
- SLOT_INTERNAL_CHILDREN_SYMBOL,
-} from '@fluentui/react-utilities';
-
-type WithMetadata = Props & {
- [SLOT_EXTERNAL_CHILDREN_SYMBOL]: React.ReactNode | SlotRenderFunction;
- [SLOT_INTERNAL_CHILDREN_SYMBOL]: React.ReactNode | SlotRenderFunction;
-};
-
-/**
- * Equivalent to React.createElement but supporting v9 slot API
- */
-export function jsx(
- type: React.ElementType
,
- props?: P | null,
- ...children: React.ReactNode[]
-): React.ReactElement
| null {
- return isSlotComponent(props)
- ? jsxFromSlotComponent(type, props, ...children)
- : React.createElement(type, props, ...children);
-}
-
-function jsxFromSlotComponent(
- type: React.ElementType,
- propsWithMetadata: WithMetadata,
- ...overrideChildren: React.ReactNode[]
-): React.ReactElement | null {
- const { children, renderFn, props } = normalizeChildren(propsWithMetadata, overrideChildren);
- if (renderFn) {
- return React.createElement(React.Fragment, {}, renderFn(type, { ...props, children })) as React.ReactElement;
- }
-
- return React.createElement(type, { ...props, children } as Props);
-}
-
-export function extractChildrenFromProps>(
- props?: Props | null,
-): React.ReactNode[] {
- if (!props) {
- return [];
- }
- return Array.isArray(props.children) ? props.children : [props.children];
-}
-
-function normalizeChildren(
- propsWithMetadata: WithMetadata,
- overrideChildren: React.ReactNode[] = [],
-): {
- props: Props;
- children: React.ReactNode;
- renderFn?: SlotRenderFunction;
-} {
- const {
- [SLOT_EXTERNAL_CHILDREN_SYMBOL]: slotExternalChildren,
- [SLOT_INTERNAL_CHILDREN_SYMBOL]: slotInternalChildren,
- ...props
- } = propsWithMetadata;
- const isRenderFn = typeof slotExternalChildren === 'function';
- const notNormalizedChildren: React.ReactNode | React.ReactNode[] =
- (overrideChildren.length > 0 ? overrideChildren : undefined) ??
- (isRenderFn ? slotInternalChildren : slotExternalChildren) ??
- slotInternalChildren ??
- propsWithMetadata.children;
- const normalizedChildren = Array.isArray(notNormalizedChildren)
- ? React.createElement(React.Fragment, {}, ...notNormalizedChildren)
- : notNormalizedChildren;
- const renderFn = isRenderFn ? (slotExternalChildren as SlotRenderFunction) : undefined;
- if (isRenderFn) {
- const { children: _, ...propsWithoutChildren } = props as UnknownSlotProps;
- return {
- children: normalizedChildren,
- renderFn,
- props: propsWithoutChildren as Props,
- };
- }
- return {
- children: normalizedChildren,
- renderFn,
- props: props as UnknownSlotProps as Props,
- };
-}
-
-export function isSlotComponent(props?: Props | null): props is WithMetadata {
- return Boolean(props?.hasOwnProperty(SLOT_INTERNAL_CHILDREN_SYMBOL));
-}
diff --git a/packages/react-components/react-jsx-runtime/src/types.d.ts b/packages/react-components/react-jsx-runtime/src/types.d.ts
deleted file mode 100644
index 922fbea70b0a0..0000000000000
--- a/packages/react-components/react-jsx-runtime/src/types.d.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-declare module 'react/jsx-runtime' {
- import type * as React from 'react';
-
- export const Fragment: symbol;
-
- export function jsx(
- type: React.ElementType
,
- props?: P | null,
- key?: string,
- ): React.ReactElement
| null;
-
- export function jsxs
(
- type: React.ElementType
,
- props?: P | null,
- key?: string,
- ): React.ReactElement
| null;
-}
-declare module 'react/jsx-dev-runtime' {
- import type * as React from 'react';
-
- export const Fragment: symbol;
-
- export function jsxDEV
(
- type: React.ElementType
,
- props?: P | null,
- key?: string,
- isStaticChildren?: boolean,
- source?: unknown,
- self?: unknown,
- ): React.ReactElement
| null;
-}
diff --git a/packages/react-components/react-utilities/etc/react-utilities.api.md b/packages/react-components/react-utilities/etc/react-utilities.api.md
index 985a481231f31..a03ffd720af38 100644
--- a/packages/react-components/react-utilities/etc/react-utilities.api.md
+++ b/packages/react-components/react-utilities/etc/react-utilities.api.md
@@ -150,10 +150,7 @@ export type Slot = {
diff --git a/packages/react-components/react-utilities/src/compose/constants.ts b/packages/react-components/react-utilities/src/compose/constants.ts
index 55e6c0a70eae3..f5c1593f9203a 100644
--- a/packages/react-components/react-utilities/src/compose/constants.ts
+++ b/packages/react-components/react-utilities/src/compose/constants.ts
@@ -1,10 +1,5 @@
/**
* @internal
- * internal symbol used to keep defaultProps.children available
+ * Internal reference for the render function
*/
-export const SLOT_INTERNAL_CHILDREN_SYMBOL = Symbol('fui.slotInternalChildren');
-/**
- * @internal
- * internal symbol used to keep slot.children available
- */
-export const SLOT_EXTERNAL_CHILDREN_SYMBOL = Symbol('fui.slotExternalChildren');
+export const SLOT_RENDER_FUNCTION_SYMBOL = Symbol('fui.slotRenderFunction');
diff --git a/packages/react-components/react-utilities/src/compose/getSlots.ts b/packages/react-components/react-utilities/src/compose/getSlots.ts
index 561b7af095746..2799542d26308 100644
--- a/packages/react-components/react-utilities/src/compose/getSlots.ts
+++ b/packages/react-components/react-utilities/src/compose/getSlots.ts
@@ -10,7 +10,6 @@ import type {
UnionToIntersection,
UnknownSlotProps,
} from './types';
-import { SLOT_EXTERNAL_CHILDREN_SYMBOL, SLOT_INTERNAL_CHILDREN_SYMBOL } from './constants';
export type Slots = {
[K in keyof S]: ExtractSlotProps extends AsIntrinsicElement
@@ -76,10 +75,6 @@ function getSlot(
return [null, undefined as R[K]];
}
- // Symbols must be deleted to ensure new custom pragma won't recognize this as the new version element
- delete (props as { [SLOT_EXTERNAL_CHILDREN_SYMBOL]: unknown })[SLOT_EXTERNAL_CHILDREN_SYMBOL];
- delete (props as { [SLOT_INTERNAL_CHILDREN_SYMBOL]: unknown })[SLOT_INTERNAL_CHILDREN_SYMBOL];
-
const { children, as: asProp, ...rest } = props;
const slot = (
diff --git a/packages/react-components/react-utilities/src/compose/resolveShorthand.test.tsx b/packages/react-components/react-utilities/src/compose/resolveShorthand.test.tsx
index aa41fb44b50a5..4d564af249623 100644
--- a/packages/react-components/react-utilities/src/compose/resolveShorthand.test.tsx
+++ b/packages/react-components/react-utilities/src/compose/resolveShorthand.test.tsx
@@ -1,7 +1,6 @@
import * as React from 'react';
import { resolveShorthand } from './resolveShorthand';
import type { Slot } from './types';
-import { SLOT_EXTERNAL_CHILDREN_SYMBOL, SLOT_INTERNAL_CHILDREN_SYMBOL } from './constants';
type TestProps = {
slotA?: Slot<'div'>;
@@ -18,8 +17,6 @@ describe('resolveShorthand', () => {
expect(resolvedProps).toEqual({
children: 'hello',
- [SLOT_EXTERNAL_CHILDREN_SYMBOL]: 'hello',
- [SLOT_INTERNAL_CHILDREN_SYMBOL]: undefined,
});
});
@@ -29,8 +26,6 @@ describe('resolveShorthand', () => {
expect(resolvedProps).toEqual({
children: hello
,
- [SLOT_EXTERNAL_CHILDREN_SYMBOL]: hello
,
- [SLOT_INTERNAL_CHILDREN_SYMBOL]: undefined,
});
});
@@ -40,8 +35,6 @@ describe('resolveShorthand', () => {
expect(resolvedProps).toEqual({
children: 42,
- [SLOT_EXTERNAL_CHILDREN_SYMBOL]: 42,
- [SLOT_INTERNAL_CHILDREN_SYMBOL]: undefined,
});
});
diff --git a/packages/react-components/react-utilities/src/compose/resolveShorthand.ts b/packages/react-components/react-utilities/src/compose/resolveShorthand.ts
index 64ada767e217f..76b109240de9f 100644
--- a/packages/react-components/react-utilities/src/compose/resolveShorthand.ts
+++ b/packages/react-components/react-utilities/src/compose/resolveShorthand.ts
@@ -1,6 +1,6 @@
import { isValidElement } from 'react';
-import type { SlotShorthandValue, UnknownSlotProps } from './types';
-import { SLOT_EXTERNAL_CHILDREN_SYMBOL, SLOT_INTERNAL_CHILDREN_SYMBOL } from './constants';
+import type { SlotRenderFunction, SlotShorthandValue, UnknownSlotProps } from './types';
+import { SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
export type ResolveShorthandOptions = Required extends true
? { required: true; defaultProps?: Props }
@@ -25,7 +25,9 @@ export const resolveShorthand: ResolveShorthandFunction = (value, options) => {
return undefined;
}
- let resolvedShorthand = {} as UnknownSlotProps;
+ let resolvedShorthand: UnknownSlotProps & {
+ [SLOT_RENDER_FUNCTION_SYMBOL]?: SlotRenderFunction;
+ } = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (typeof value === 'string' || typeof value === 'number' || Array.isArray(value) || isValidElement(value)) {
@@ -34,10 +36,15 @@ export const resolveShorthand: ResolveShorthandFunction = (value, options) => {
resolvedShorthand = value;
}
- return {
+ resolvedShorthand = {
...defaultProps,
...resolvedShorthand,
- [SLOT_EXTERNAL_CHILDREN_SYMBOL]: resolvedShorthand.children,
- [SLOT_INTERNAL_CHILDREN_SYMBOL]: defaultProps?.children,
};
+
+ if (typeof resolvedShorthand.children === 'function') {
+ resolvedShorthand[SLOT_RENDER_FUNCTION_SYMBOL] = resolvedShorthand.children as SlotRenderFunction;
+ resolvedShorthand.children = defaultProps?.children;
+ }
+
+ return resolvedShorthand;
};
diff --git a/packages/react-components/react-utilities/src/index.ts b/packages/react-components/react-utilities/src/index.ts
index a95032278b593..c2d0023253e0b 100644
--- a/packages/react-components/react-utilities/src/index.ts
+++ b/packages/react-components/react-utilities/src/index.ts
@@ -3,8 +3,7 @@ export {
getSlotsNext,
resolveShorthand,
isResolvedShorthand,
- SLOT_EXTERNAL_CHILDREN_SYMBOL,
- SLOT_INTERNAL_CHILDREN_SYMBOL,
+ SLOT_RENDER_FUNCTION_SYMBOL,
} from './compose/index';
export type {
ExtractSlotProps,
diff --git a/tsconfig.base.json b/tsconfig.base.json
index ba88b1270f996..b994a894ae86f 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -48,7 +48,6 @@
"@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-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"],