diff --git a/change/@fluentui-react-components-62dd54a8-eca3-46ea-a6ba-844aea837127.json b/change/@fluentui-react-components-62dd54a8-eca3-46ea-a6ba-844aea837127.json new file mode 100644 index 0000000000000..b36e695449e27 --- /dev/null +++ b/change/@fluentui-react-components-62dd54a8-eca3-46ea-a6ba-844aea837127.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: exports getIntrinsicElementProps method", + "packageName": "@fluentui/react-components", + "email": "bernardo.sunderhus@gmail.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-utilities-23bf3f01-9fbb-4ca2-84a5-18d7839c6289.json b/change/@fluentui-react-utilities-23bf3f01-9fbb-4ca2-84a5-18d7839c6289.json new file mode 100644 index 0000000000000..1c251b2a0e779 --- /dev/null +++ b/change/@fluentui-react-utilities-23bf3f01-9fbb-4ca2-84a5-18d7839c6289.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: creates getIntrinsicElementProps to replace getNativeElementProps on slots creation", + "packageName": "@fluentui/react-utilities", + "email": "bernardo.sunderhus@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-components/etc/react-components.api.md b/packages/react-components/react-components/etc/react-components.api.md index 7beeb0522e148..385d144b4a4d6 100644 --- a/packages/react-components/react-components/etc/react-components.api.md +++ b/packages/react-components/react-components/etc/react-components.api.md @@ -295,6 +295,7 @@ import { FontFamilyTokens } from '@fluentui/react-theme'; import { FontSizeTokens } from '@fluentui/react-theme'; import { FontWeightTokens } from '@fluentui/react-theme'; import { ForwardRefComponent } from '@fluentui/react-utilities'; +import { getIntrinsicElementProps } from '@fluentui/react-utilities'; import { getNativeElementProps } from '@fluentui/react-utilities'; import { getPartitionedNativeProps } from '@fluentui/react-utilities'; import { getSlots } from '@fluentui/react-utilities'; @@ -1776,6 +1777,8 @@ export { FontWeightTokens } export { ForwardRefComponent } +export { getIntrinsicElementProps } + export { getNativeElementProps } export { getPartitionedNativeProps } diff --git a/packages/react-components/react-components/src/index.ts b/packages/react-components/react-components/src/index.ts index 0e55ba01fba95..93f48fa0368dc 100644 --- a/packages/react-components/react-components/src/index.ts +++ b/packages/react-components/react-components/src/index.ts @@ -97,6 +97,7 @@ export { } from '@fluentui/react-shared-contexts'; export { getNativeElementProps, + getIntrinsicElementProps, getPartitionedNativeProps, getSlots, slot, 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 5ac7b70c5fe23..37848d3587eff 100644 --- a/packages/react-components/react-utilities/etc/react-utilities.api.md +++ b/packages/react-components/react-utilities/etc/react-utilities.api.md @@ -46,7 +46,7 @@ export type FluentTriggerComponent = { }; // @public -export type ForwardRefComponent = ObscureEventName extends keyof Props ? Required[ObscureEventName] extends React_2.PointerEventHandler ? React_2.ForwardRefExoticComponent> : never : never; +export type ForwardRefComponent = React_2.ForwardRefExoticComponent>>; // @public export function getEventClientCoords(event: TouchOrMouseEvent): { @@ -54,6 +54,9 @@ export function getEventClientCoords(event: TouchOrMouseEvent): { clientY: number; }; +// @public +export const getIntrinsicElementProps: = never>(tagName: NonNullable, props: Props & React_2.RefAttributes>, excludedPropNames?: ExcludedPropKeys[] | undefined) => OmitWithoutExpanding>; + // @public export function getNativeElementProps>(tagName: string, props: {}, excludedPropNames?: string[]): TAttributes; @@ -93,6 +96,9 @@ export function getTriggerChild(children: TriggerProps; +// @public +export type InferredElementRefType = ObscureEventName extends keyof Props ? Required[ObscureEventName] extends React_2.PointerEventHandler ? Element : never : never; + // @internal export function isFluentTrigger(element: React_2.ReactElement): element is React_2.ReactElement; diff --git a/packages/react-components/react-utilities/src/compose/getIntrinsicElementProps.ts b/packages/react-components/react-utilities/src/compose/getIntrinsicElementProps.ts new file mode 100644 index 0000000000000..30371ba1ab726 --- /dev/null +++ b/packages/react-components/react-utilities/src/compose/getIntrinsicElementProps.ts @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { getNativeElementProps } from '../utils/getNativeElementProps'; +import type { InferredElementRefType, UnknownSlotProps } from './types'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type HTMLAttributes = React.HTMLAttributes; + +/** + * Given an element tagname and user props, filters the props to only allowed props for the given + * element type. + * + * Equivalent to {@link getNativeElementProps}, but more type-safe. + */ +export const getIntrinsicElementProps = < + Props extends UnknownSlotProps, + ExcludedPropKeys extends Extract = never, +>( + /** The slot's default element type (e.g. 'div') */ + tagName: NonNullable, + /** The component's props object */ + props: Props & React.RefAttributes>, + /** List of native props to exclude from the returned value */ + excludedPropNames?: ExcludedPropKeys[], +) => { + return getNativeElementProps< + OmitWithoutExpanding | ExcludedPropKeys> + >(props.as ?? tagName, props, excludedPropNames); +}; + +/** + * helper type that avoids the expansion of unions while inferring it, + * should work exactly the same as Omit + */ +type OmitWithoutExpanding = P extends unknown ? Omit : P; diff --git a/packages/react-components/react-utilities/src/compose/index.ts b/packages/react-components/react-utilities/src/compose/index.ts index 5d1bedacf2bca..cceec47b9f42c 100644 --- a/packages/react-components/react-utilities/src/compose/index.ts +++ b/packages/react-components/react-utilities/src/compose/index.ts @@ -8,6 +8,7 @@ export * from './constants'; export * from './getSlotsNext'; export * from './isSlot'; export * from './assertSlots'; +export * from './getIntrinsicElementProps'; export { slot }; export type { SlotOptions } from './slot'; diff --git a/packages/react-components/react-utilities/src/compose/types.ts b/packages/react-components/react-utilities/src/compose/types.ts index 4f4ec3f75094b..b134e3420ed26 100644 --- a/packages/react-components/react-utilities/src/compose/types.ts +++ b/packages/react-components/react-utilities/src/compose/types.ts @@ -215,13 +215,20 @@ export type ComponentState = { type ObscureEventName = 'onLostPointerCaptureCapture'; /** - * Return type for `React.forwardRef`, including inference of the proper typing for the ref. + * Infers the element type from props that are declared using ComponentProps. */ -export type ForwardRefComponent = ObscureEventName extends keyof Props +export type InferredElementRefType = ObscureEventName extends keyof Props ? Required[ObscureEventName] extends React.PointerEventHandler - ? React.ForwardRefExoticComponent> + ? Element : never : never; + +/** + * Return type for `React.forwardRef`, including inference of the proper typing for the ref. + */ +export type ForwardRefComponent = React.ForwardRefExoticComponent< + Props & React.RefAttributes> +>; // A definition like this would also work, but typescript is more likely to unnecessarily expand // the props type with this version (and it's likely much more expensive to evaluate) // export type ForwardRefComponent = Props extends React.DOMAttributes diff --git a/packages/react-components/react-utilities/src/index.ts b/packages/react-components/react-utilities/src/index.ts index 97fc981ebeb74..f2ecb9d8598c6 100644 --- a/packages/react-components/react-utilities/src/index.ts +++ b/packages/react-components/react-utilities/src/index.ts @@ -6,6 +6,7 @@ export { assertSlots, resolveShorthand, isResolvedShorthand, + getIntrinsicElementProps, SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL, } from './compose/index'; @@ -25,6 +26,7 @@ export type { UnknownSlotProps, SlotComponentType, SlotOptions, + InferredElementRefType, } from './compose/index'; export {