diff --git a/change/@fluentui-react-button-0096137a-3751-4300-8655-523baee276bb.json b/change/@fluentui-react-button-0096137a-3751-4300-8655-523baee276bb.json new file mode 100644 index 00000000000000..c61fe909d1c357 --- /dev/null +++ b/change/@fluentui-react-button-0096137a-3751-4300-8655-523baee276bb.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "fix: explicitly assert components getNativeElemProps calls that are incompatible with Slot api", + "packageName": "@fluentui/react-button", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-link-c03b6435-131b-4d57-afed-794040e1e29b.json b/change/@fluentui-react-link-c03b6435-131b-4d57-afed-794040e1e29b.json new file mode 100644 index 00000000000000..ce9b36b4dc1e4a --- /dev/null +++ b/change/@fluentui-react-link-c03b6435-131b-4d57-afed-794040e1e29b.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "fix: explicitly assert components getNativeElemProps calls that are incompatible with Slot api", + "packageName": "@fluentui/react-link", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-menu-4259442d-2ab3-4223-af79-2d2528545b9b.json b/change/@fluentui-react-menu-4259442d-2ab3-4223-af79-2d2528545b9b.json new file mode 100644 index 00000000000000..651943afa00daf --- /dev/null +++ b/change/@fluentui-react-menu-4259442d-2ab3-4223-af79-2d2528545b9b.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "fix: explicitly assert components getNativeElemProps calls that are incompatible with Slot api", + "packageName": "@fluentui/react-menu", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-text-400681a3-c296-408b-8cf7-4968f358a69a.json b/change/@fluentui-react-text-400681a3-c296-408b-8cf7-4968f358a69a.json new file mode 100644 index 00000000000000..37e1d8c98097fe --- /dev/null +++ b/change/@fluentui-react-text-400681a3-c296-408b-8cf7-4968f358a69a.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "fix: explicitly assert components getNativeElemProps calls that are incompatible with Slot api", + "packageName": "@fluentui/react-text", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-utilities-93110975-e438-4f2f-b983-ff54c25c6dfa.json b/change/@fluentui-react-utilities-93110975-e438-4f2f-b983-ff54c25c6dfa.json new file mode 100644 index 00000000000000..decf9e0dbb82ec --- /dev/null +++ b/change/@fluentui-react-utilities-93110975-e438-4f2f-b983-ff54c25c6dfa.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feat: make dictionaries and getNativeElementProps strict and get rid of unused generics to improve DX and type safety", + "packageName": "@fluentui/react-utilities", + "email": "martinhochel@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/package.json b/package.json index 47093afe61bcab..562d19afa4fc23 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "check:change": "beachball check --scope \"!packages/fluentui/*\"", "check:modified-files": "yarn workspace @fluentui/scripts just check-for-modified-files", "check:affected-package": "node ./scripts/monorepo/checkIfPackagesAffected.js", - "check:installed-dependencies-versions": "satisfied --skip-invalid --ignore \"prettier|angular|lit|sass|@storybook/web-components|@storybook/html|svelte|@testing-library|vue|@cypress/react\"", + "check:installed-dependencies-versions": "satisfied --skip-invalid --ignore \"prettier|angular|lit|sass|@storybook/web-components|@storybook/html|svelte|@testing-library|vue|@cypress/react|eslint-plugin-etc\"", "clean": "lage clean --verbose", "code-style": "lage code-style --verbose", "codepen": "cd packages/react && node ../../scripts/local-codepen.js", @@ -181,6 +181,7 @@ "eslint-import-resolver-typescript": "2.5.0", "eslint-plugin-deprecation": "1.2.1", "eslint-plugin-es": "4.1.0", + "eslint-plugin-etc": "2.0.2", "eslint-plugin-import": "2.25.4", "eslint-plugin-jest": "23.20.0", "eslint-plugin-jsdoc": "^36.0.7", diff --git a/packages/react-components/react-button/src/components/Button/useButton.ts b/packages/react-components/react-button/src/components/Button/useButton.ts index d7fb6b4952f555..d24febda84d793 100644 --- a/packages/react-components/react-button/src/components/Button/useButton.ts +++ b/packages/react-components/react-button/src/components/Button/useButton.ts @@ -45,7 +45,6 @@ export const useButton_unstable = ( root: 'button', icon: 'span', }, - root: getNativeElementProps( as || 'button', useARIAButton(props, { @@ -56,7 +55,7 @@ export const useButton_unstable = ( type: 'button', // This is added because the default for type is 'submit' }, }), - ), + ) as ButtonState['root'], icon: iconShorthand, }; }; diff --git a/packages/react-components/react-link/src/components/Link/useLink.ts b/packages/react-components/react-link/src/components/Link/useLink.ts index b453f5b701f0b2..673c9a956bd035 100644 --- a/packages/react-components/react-link/src/components/Link/useLink.ts +++ b/packages/react-components/react-link/src/components/Link/useLink.ts @@ -31,7 +31,7 @@ export const useLink_unstable = ( ref, ...props, as, - }), + }) as LinkState['root'], }; useLinkState_unstable(state); diff --git a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts index 90aacc84de0e12..81ad8172d9237a 100644 --- a/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts +++ b/packages/react-components/react-menu/src/components/MenuPopover/useMenuPopover.ts @@ -62,7 +62,7 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< const { onMouseEnter: onMouseEnterOriginal, onKeyDown: onKeyDownOriginal } = rootProps; - rootProps.onMouseEnter = useEventCallback((e: React.MouseEvent) => { + rootProps.onMouseEnter = useEventCallback((e: React.MouseEvent) => { if (openOnHover) { setOpen(e, { open: true, keyboard: false }); } @@ -70,7 +70,7 @@ export const useMenuPopover_unstable = (props: MenuPopoverProps, ref: React.Ref< onMouseEnterOriginal?.(e); }); - rootProps.onKeyDown = useEventCallback((e: React.KeyboardEvent) => { + rootProps.onKeyDown = useEventCallback((e: React.KeyboardEvent) => { const key = e.key; if (key === Escape || (isSubmenu && key === CloseArrowKey)) { diff --git a/packages/react-components/react-text/src/components/Text/useText.ts b/packages/react-components/react-text/src/components/Text/useText.ts index 349c306424feaa..62bd51ca4d2abf 100644 --- a/packages/react-components/react-text/src/components/Text/useText.ts +++ b/packages/react-components/react-text/src/components/Text/useText.ts @@ -33,7 +33,7 @@ export const useText_unstable = (props: TextProps, ref: React.Ref): ref, ...props, as, - }), + }) as TextState['root'], }; return state; diff --git a/packages/react-components/react-utilities/.eslintrc.json b/packages/react-components/react-utilities/.eslintrc.json index ceea884c70dccc..67ee0717392aaf 100644 --- a/packages/react-components/react-utilities/.eslintrc.json +++ b/packages/react-components/react-utilities/.eslintrc.json @@ -1,4 +1,8 @@ { "extends": ["plugin:@fluentui/eslint-plugin/react"], - "root": true + "plugins": ["etc"], + "root": true, + "rules": { + "etc/no-misused-generics": "warn" + } } diff --git a/packages/react-components/react-utilities/config/api-extractor.local.json b/packages/react-components/react-utilities/config/api-extractor.local.json index f8d7afe5e2cd1d..69e764bce3a592 100644 --- a/packages/react-components/react-utilities/config/api-extractor.local.json +++ b/packages/react-components/react-utilities/config/api-extractor.local.json @@ -1,5 +1,5 @@ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", "extends": "./api-extractor.json", - "mainEntryPointFilePath": "/dist/packages/react-components//src/index.d.ts" + "mainEntryPointFilePath": "/dist/types/packages/react-components//src/index.d.ts" } 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 b47862ef6bc411..edcf554601e700 100644 --- a/packages/react-components/react-utilities/etc/react-utilities.api.md +++ b/packages/react-components/react-utilities/etc/react-utilities.api.md @@ -8,7 +8,7 @@ import { DispatchWithoutAction } from 'react'; import * as React_2 from 'react'; // @public -export const anchorProperties: Record; +export const anchorProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "media" | "name" | "target" | "type" | "href" | "hrefLang" | "rel" | "download" | "htmlFor", 1>; // @public export const applyTriggerPropsToChildren: (children: React_2.ReactElement> | ((props: TTriggerProps) => React_2.ReactElement | null) | null | undefined, triggerProps: TTriggerProps) => React_2.ReactElement | null; @@ -19,16 +19,16 @@ export type AsIntrinsicElement = { }; // @public -export const audioProperties: Record; +export const audioProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "height" | "name" | "width" | "loop" | "muted" | "preload" | "src" | "htmlFor", 1>; // @public -export const baseElementEvents: Record; +export const baseElementEvents: Record<"onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel", 1>; // @public -export const baseElementProperties: Record; +export const baseElementProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "ref" | "name" | "htmlFor", 1>; // @public -export const buttonProperties: Record; +export const buttonProperties: Record<"form" | "style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "type" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "value" | "htmlFor", 1>; // @public export function canUseDOM(): boolean; @@ -37,10 +37,10 @@ export function canUseDOM(): boolean; export const clamp: (value: number, min: number, max: number) => number; // @public (undocumented) -export const colGroupProperties: Record; +export const colGroupProperties: Record<"span" | "style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor", 1>; // @public (undocumented) -export const colProperties: Record; +export const colProperties: Record<"span" | "style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor", 1>; // @public export type ComponentProps = Omit & PropsWithoutRef>; @@ -60,13 +60,13 @@ export type ComponentState = { export const defaultSSRContextValue: SSRContextValue; // @public -export const divProperties: Record; +export const divProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor", 1>; // @public export type ExtractSlotProps = Exclude; // @public -export const fieldsetProperties: Record; +export const fieldsetProperties: Record<"form" | "style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "disabled" | "htmlFor", 1>; // @public export type FluentTriggerComponent = { @@ -74,20 +74,20 @@ export type FluentTriggerComponent = { }; // @public -export const formProperties: Record; +export const formProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "method" | "name" | "target" | "acceptCharset" | "action" | "encType" | "noValidate" | "htmlFor", 1>; // @public export type ForwardRefComponent = ObscureEventName extends keyof Props ? Required[ObscureEventName] extends React_2.PointerEventHandler ? React_2.ForwardRefExoticComponent> : never : never; // @public -export function getNativeElementProps>(tagName: string, props: {}, excludedPropNames?: string[]): TAttributes; +export function getNativeElementProps, ExcludedPropKeys extends Extract, string> = never>(tagName: Tag, props: Props, excludedPropNames?: ExcludedPropKeys[]): Omit, ExcludedPropKeys>; // @public -export function getNativeProps>(props: Record, allowedPropNames: string[] | Record, excludedPropNames?: string[]): T; +export function getNativeProps, A extends string, E extends Extract = never>(props: Props, allowedPropNames: A[] | Record, excludedPropNames?: E[]): Omit, E>; // @public -export const getPartitionedNativeProps: , "style" | "className">, ExcludedPropKeys extends Extract = never>({ primarySlotTagName, props, excludedPropNames, }: { - primarySlotTagName: keyof JSX.IntrinsicElements; +export const getPartitionedNativeProps: & Pick, "style" | "className">, ExcludedPropKeys extends Extract, string> = never>({ primarySlotTagName, props, excludedPropNames, }: { + primarySlotTagName: Tag; props: Props; excludedPropNames?: ExcludedPropKeys[] | undefined; }) => { @@ -95,7 +95,7 @@ export const getPartitionedNativeProps: ; + primary: Omit, "style" | "className" | ExcludedPropKeys>; }; // @public @@ -113,19 +113,19 @@ export const getTriggerChild: (children: React_2.ReactNode) => React_2.ReactElem }; // @public -export const htmlElementProperties: Record; +export const htmlElementProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor", 1>; // @public -export const iframeProperties: Record; +export const iframeProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "height" | "name" | "width" | "referrerPolicy" | "src" | "allow" | "allowFullScreen" | "allowTransparency" | "sandbox" | "srcDoc" | "htmlFor" | "allowPaymentRequest" | "csp" | "importance", 1>; // @public @deprecated (undocumented) -export const imageProperties: Record; +export const imageProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "height" | "name" | "width" | "crossOrigin" | "useMap" | "alt" | "src" | "srcSet" | "htmlFor", 1>; // @public -export const imgProperties: Record; +export const imgProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "height" | "name" | "width" | "crossOrigin" | "useMap" | "alt" | "src" | "srcSet" | "htmlFor", 1>; // @public -export const inputProperties: Record; +export const inputProperties: Record<"form" | "style" | "title" | "pattern" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "role" | "autoCapitalize" | "inputMode" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "height" | "max" | "min" | "name" | "type" | "width" | "alt" | "src" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "value" | "autoComplete" | "accept" | "checked" | "list" | "maxLength" | "multiple" | "readOnly" | "required" | "size" | "step" | "htmlFor" | "dirname", 1>; // @public export const isFluentTrigger: (element: React_2.ReactElement) => boolean | undefined; @@ -136,19 +136,19 @@ export type IsSingleton = { }[T]; // @public -export const labelProperties: Record; +export const labelProperties: Record<"form" | "style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor", 1>; // @public -export const liProperties: Record; +export const liProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "value" | "htmlFor", 1>; // @public -export const olProperties: Record; +export const olProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor" | "start", 1>; // @public export function omit, Exclusions extends (keyof TObj)[]>(obj: TObj, exclusions: Exclusions): Omit; // @public (undocumented) -export const optionProperties: Record; +export const optionProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "value" | "htmlFor" | "selected", 1>; // @public export type PropsWithoutRef

= 'ref' extends keyof P ? (P extends unknown ? Omit : P) : P; @@ -178,7 +178,7 @@ export type ResolveShorthandOptions = { }; // @public -export const selectProperties: Record; +export const selectProperties: Record<"form" | "style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "type" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "value" | "multiple" | "required" | "htmlFor", 1>; // @public export function shouldPreventDefaultOnKeyDown(e: KeyboardEvent | React_2.KeyboardEvent): boolean; @@ -227,19 +227,19 @@ export type SSRContextValue = { export const SSRProvider: React_2.FC; // @public -export const tableProperties: Record; +export const tableProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor" | "cellPadding" | "cellSpacing", 1>; // @public -export const tdProperties: Record; +export const tdProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor" | "colSpan" | "headers" | "rowSpan" | "scope", 1>; // @public -export const textAreaProperties: Record; +export const textAreaProperties: Record<"form" | "style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "role" | "autoCapitalize" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "type" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "value" | "maxLength" | "readOnly" | "required" | "htmlFor" | "cols" | "rows" | "wrap" | "dirname", 1>; // @public -export const thProperties: Record; +export const thProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor" | "rowSpan" | "scope", 1>; // @public -export const trProperties: Record; +export const trProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "name" | "htmlFor", 1>; // @public export type UnionToIntersection = (U extends unknown ? (x: U) => U : never) extends (x: infer I) => U ? I : never; @@ -329,7 +329,7 @@ export function useTimeout(): readonly [(fn: () => void, delay: number) => void, export const useUnmount: (callback: () => void) => void; // @public -export const videoProperties: Record; +export const videoProperties: Record<"style" | "title" | "children" | "className" | "accessKey" | "contentEditable" | "dir" | "draggable" | "hidden" | "id" | "lang" | "spellCheck" | "tabIndex" | "translate" | "role" | "onCopy" | "onCut" | "onPaste" | "onCompositionEnd" | "onCompositionStart" | "onCompositionUpdate" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChange" | "onInput" | "onSubmit" | "onLoad" | "onError" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyUp" | "onAbort" | "onCanPlay" | "onCanPlayThrough" | "onDurationChange" | "onEmptied" | "onEncrypted" | "onEnded" | "onLoadedData" | "onLoadedMetadata" | "onLoadStart" | "onPause" | "onPlay" | "onPlaying" | "onProgress" | "onRateChange" | "onSeeked" | "onSeeking" | "onStalled" | "onSuspend" | "onTimeUpdate" | "onVolumeChange" | "onWaiting" | "onAuxClick" | "onClick" | "onClickCapture" | "onContextMenu" | "onDoubleClick" | "onDrag" | "onDragEnd" | "onDragEnter" | "onDragExit" | "onDragLeave" | "onDragOver" | "onDragStart" | "onDrop" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onTouchCancel" | "onTouchEnd" | "onTouchMove" | "onTouchStart" | "onPointerDown" | "onPointerMove" | "onPointerUp" | "onPointerCancel" | "onPointerEnter" | "onPointerLeave" | "onPointerOver" | "onPointerOut" | "onGotPointerCapture" | "onLostPointerCapture" | "onScroll" | "onWheel" | "ref" | "height" | "name" | "width" | "loop" | "muted" | "preload" | "src" | "htmlFor" | "poster", 1>; // (No @packageDocumentation comment for this package) diff --git a/packages/react-components/react-utilities/package.json b/packages/react-components/react-utilities/package.json index 67901b761d152d..2390db637c6eee 100644 --- a/packages/react-components/react-utilities/package.json +++ b/packages/react-components/react-utilities/package.json @@ -20,7 +20,7 @@ "lint": "just-scripts lint", "test": "jest --passWithNoTests", "docs": "api-extractor run --config=config/api-extractor.local.json --local", - "build:local": "tsc -p ./tsconfig.lib.json --module esnext --emitDeclarationOnly && node ../../../scripts/typescript/normalize-import --output ./dist/packages/react-components/react-utilities/src && yarn docs", + "build:local": "tsc -p ./tsconfig.lib.json --module esnext --emitDeclarationOnly && node ../../../scripts/typescript/normalize-import --output ./dist/types/packages/react-components/react-utilities/src && yarn docs", "type-check": "tsc -b tsconfig.json" }, "devDependencies": { diff --git a/packages/react-components/react-utilities/src/compose/types.ts b/packages/react-components/react-utilities/src/compose/types.ts index aed567548e3a93..b294e0f2fb7e89 100644 --- a/packages/react-components/react-utilities/src/compose/types.ts +++ b/packages/react-components/react-utilities/src/compose/types.ts @@ -69,7 +69,9 @@ type EmptyIntrisicElements = * * Removes legacy string ref. * * Disallows children for empty tags like 'img'. */ -type IntrisicElementProps = React.PropsWithRef & +export type IntrisicElementProps = React.PropsWithRef< + JSX.IntrinsicElements[Type] +> & (Type extends EmptyIntrisicElements ? { children?: never } : {}); /** diff --git a/packages/react-components/react-utilities/src/utils/getNativeElementProps.test.ts b/packages/react-components/react-utilities/src/utils/getNativeElementProps.test.ts index 301125d9dd6723..c4304bdbacb865 100644 --- a/packages/react-components/react-utilities/src/utils/getNativeElementProps.test.ts +++ b/packages/react-components/react-utilities/src/utils/getNativeElementProps.test.ts @@ -1,10 +1,20 @@ -import { getNativeElementProps } from './getNativeElementProps'; +import { getNativeElementProps, getPartitionedNativeProps } from './getNativeElementProps'; describe('getNativeElementProps', () => { it('can filter native element properties', () => { - expect(getNativeElementProps('div', { id: '123', checked: true })).toEqual({ id: '123' }); + const divWithInvalidProps = getNativeElementProps('div', { id: '123', checked: true }); + expect(divWithInvalidProps).toEqual({ + id: '123', + }); + // @ts-expect-error -- 'id' prop is not allowed `div` prop + expect(divWithInvalidProps.checked).toBeUndefined(); + expect(getNativeElementProps('input', { id: '123', checked: true })).toEqual({ id: '123', checked: true }); - expect(getNativeElementProps('input', { id: '123', checked: true }, ['id'])).toEqual({ checked: true }); + + const inputWithExclude = getNativeElementProps('input', { id: '123', checked: true }, ['id']); + expect(inputWithExclude).toEqual({ checked: true }); + // @ts-expect-error -- 'id' prop was excluded + expect(inputWithExclude.id).toBeUndefined(); }); it('includes `as` as a native prop', () => { @@ -12,6 +22,54 @@ describe('getNativeElementProps', () => { }); it('excludes props regardless of the allowed', () => { - expect(getNativeElementProps('div', { as: 'span' }, ['as'])).toEqual({}); + const actual = getNativeElementProps('div', { as: 'span' }, ['as']); + + // @ts-expect-error -- `as` prop was excluded + expect(actual.as).toBeUndefined(); + expect(actual).toEqual({}); + }); +}); + +describe('getPartitionedNativeProps', () => { + it('creates modified root and primary and always removes className and styles prop from primary', () => { + const actual = getPartitionedNativeProps({ + primarySlotTagName: 'div', + props: { + className: 'hello', + style: { width: '100px' }, + id: '123', + dir: 'ltr', + defaultChecked: true, + }, + }); + + expect(actual.root).toEqual({ className: 'hello', style: { width: '100px' } }); + expect(actual.primary).toEqual({ id: '123', dir: 'ltr' }); + + // This should throw Type error - `defaultChecked` is not valid DIV prop + // Unfortunately react typings provide `React.HTMLAttributes` interface + // which assign lot of HTML invalid props to every intrinsic element, thus making this invalid by default + expect(actual.primary.defaultChecked).toBeUndefined(); + + // @ts-expect-error -- `className` prop was excluded + expect(actual.primary.className).toBeUndefined(); + // @ts-expect-error -- `style` prop was excluded + expect(actual.primary.style).toBeUndefined(); + }); + + it('works with excluded prop names for "primary"', () => { + const actual = getPartitionedNativeProps({ + primarySlotTagName: 'div', + props: { id: '123', dir: 'ltr', defaultChecked: false }, + excludedPropNames: ['id', 'defaultChecked'], + }); + + expect(actual.primary).toEqual({ dir: 'ltr' }); + + expect(actual.primary.dir).toBe('ltr'); + // @ts-expect-error -- `defaultChecked` prop was excluded + expect(actual.primary.defaultChecked).toBeUndefined(); + // @ts-expect-error -- `id `prop was excluded + expect(actual.primary.id).toBeUndefined(); }); }); diff --git a/packages/react-components/react-utilities/src/utils/getNativeElementProps.ts b/packages/react-components/react-utilities/src/utils/getNativeElementProps.ts index 6e5b988143d4ed..86a8284311bb54 100644 --- a/packages/react-components/react-utilities/src/utils/getNativeElementProps.ts +++ b/packages/react-components/react-utilities/src/utils/getNativeElementProps.ts @@ -1,4 +1,6 @@ import * as React from 'react'; +import type { IntrisicElementProps } from '../compose/index'; + import { labelProperties, audioProperties, @@ -26,7 +28,7 @@ import { timeProperties, } from './properties'; -const nativeElementMap: Record> = { +const nativeElementMap = { label: labelProperties, audio: audioProperties, video: videoProperties, @@ -50,6 +52,18 @@ const nativeElementMap: Record> = { img: imgProperties, time: timeProperties, }; +type HtmlElementProperties = typeof htmlElementProperties; +type NativeElementMap = typeof nativeElementMap; +type GetAllowedPropNames> = Tag extends keyof NativeElementMap + ? keyof NativeElementMap[Tag] | 'as' | keyof Props + : keyof HtmlElementProperties | 'as' | keyof Props; + +/** + * AsTagNames - can be a single string or union of strings + */ +type NativeElemProps = { + [As in AsTagNames]: { as?: As } & IntrisicElementProps; +}[AsTagNames]; /** * Given an element tagname and user props, filters the props to only allowed props for the given @@ -58,16 +72,21 @@ const nativeElementMap: Record> = { * @param props - Props object * @param excludedPropNames - List of props to disallow */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function getNativeElementProps>( - tagName: string, - props: {}, - excludedPropNames?: string[], -): TAttributes { - const allowedPropNames = (tagName && nativeElementMap[tagName]) || htmlElementProperties; - allowedPropNames.as = 1; +export function getNativeElementProps< + Tag extends keyof JSX.IntrinsicElements, + Props extends Record, + ExcludedPropKeys extends Extract, string> = never +>(tagName: Tag, props: Props, excludedPropNames?: ExcludedPropKeys[]): Omit, ExcludedPropKeys> { + const allowedPropNames = nativeElementMap[tagName as keyof NativeElementMap] || htmlElementProperties; - return getNativeProps(props, allowedPropNames, excludedPropNames); + /** + * extends object dictionary with `as` to avoid adding it to nativeElementMap and htmlElementProperties + */ + type ExtendedAllowedPropNames = typeof allowedPropNames & { as: 1 }; + + (allowedPropNames as ExtendedAllowedPropNames).as = 1; + + return getNativeProps(props, allowedPropNames, excludedPropNames) as Omit, ExcludedPropKeys>; } /** @@ -79,15 +98,16 @@ export function getNativeElementProps, 'style' | 'className'>, - ExcludedPropKeys extends Extract = never + Tag extends keyof JSX.IntrinsicElements, + Props extends Record & Pick, 'style' | 'className'>, + ExcludedPropKeys extends Extract, string> = never >({ primarySlotTagName, props, excludedPropNames, }: { /** The primary slot's element type (e.g. 'div') */ - primarySlotTagName: keyof JSX.IntrinsicElements; + primarySlotTagName: Tag; /** The component's props object */ props: Props; @@ -95,12 +115,13 @@ export const getPartitionedNativeProps = < /** List of native props to exclude from the returned value */ excludedPropNames?: ExcludedPropKeys[]; }) => { + const normalizedExcludedPropNames = [...(excludedPropNames || []), 'style', 'className'] as ExcludedPropKeys[]; + return { root: { style: props.style, className: props.className }, - primary: getNativeElementProps>(primarySlotTagName, props, [ - ...(excludedPropNames || []), - 'style', - 'className', - ]), + primary: getNativeElementProps(primarySlotTagName, props, normalizedExcludedPropNames) as Omit< + NativeElemProps, + ExcludedPropKeys | 'style' | 'className' + >, }; }; diff --git a/packages/react-components/react-utilities/src/utils/properties.test.ts b/packages/react-components/react-utilities/src/utils/properties.test.ts index 8b7a6a0735dd0c..7da1f5487b5e56 100644 --- a/packages/react-components/react-utilities/src/utils/properties.test.ts +++ b/packages/react-components/react-utilities/src/utils/properties.test.ts @@ -1,31 +1,35 @@ -import * as React from 'react'; import { getNativeProps, divProperties } from './properties'; describe('getNativeProps', () => { it('can pass through data tags', () => { - const result = getNativeProps>( + const result = getNativeProps( { 'data-automation-id': 1, }, - divProperties, + divProperties as typeof divProperties & { + 'data-automation-id': 1; + }, ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect((result as any)['data-automation-id']).toEqual(1); + + expect(result['data-automation-id']).toEqual(1); }); it('can pass through aria tags', () => { - const result = getNativeProps>( + const result = getNativeProps( { - 'aria-label': 1, + 'aria-label': '1', + }, + divProperties as typeof divProperties & { + 'aria-label': 1; }, - divProperties, ); - expect(result['aria-label']).toEqual(1); + expect(result['aria-label']).toEqual('1'); }); it('can pass through basic div properties and events', () => { - const result = getNativeProps>( + // + const result = getNativeProps( { className: 'foo', onClick: () => { @@ -44,7 +48,7 @@ describe('getNativeProps', () => { }); it('can remove unexpected properties', () => { - const result = getNativeProps>( + const result = getNativeProps( { foobar: 1, className: 'hi', @@ -53,14 +57,25 @@ describe('getNativeProps', () => { ); expect(result.className).toEqual('hi'); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect((result as any).foobar).toEqual(undefined); + // @ts-expect-error -- foobar props was removed + expect(result.foobar).toBeUndefined(); }); it('can exclude properties', () => { - const result = getNativeProps<{ a: number; b: number }>({ a: 1, b: 2 }, ['a', 'b'], ['b']); + const result = getNativeProps({ a: 1, b: 2, c: 3 }, ['a', 'b'], ['b']); expect(result.a).toBeDefined(); + // @ts-expect-error -- strict type checking for exclusion, b doesn't exist after removal + expect(result.c).toBeUndefined(); + // @ts-expect-error -- strict type checking for exclusion, b doesn't exist after removal expect(result.b).toBeUndefined(); + + const resultObj = getNativeProps({ a: 1, b: 2, c: 3 }, { a: 1, b: 1 }, ['b']); + + expect(resultObj.a).toBeDefined(); + // @ts-expect-error -- strict type checking for exclusion, b doesn't exist after removal + expect(resultObj.c).toBeUndefined(); + // @ts-expect-error -- strict type checking for exclusion, b doesn't exist after removal + expect(resultObj.b).toBeUndefined(); }); }); diff --git a/packages/react-components/react-utilities/src/utils/properties.ts b/packages/react-components/react-utilities/src/utils/properties.ts index 98f8e939563f18..97fb84001c9a6c 100644 --- a/packages/react-components/react-utilities/src/utils/properties.ts +++ b/packages/react-components/react-utilities/src/utils/properties.ts @@ -1,8 +1,11 @@ -const toObjectMap = (...items: (string[] | Record)[]) => { - const result: Record = {}; +type ObjectItem = Array | Record; +function toObjectMap( + ...items: [ObjectItem, ObjectItem?] +): Record { + const result: Record = {}; for (const item of items) { - const keys = Array.isArray(item) ? item : Object.keys(item); + const keys = Array.isArray(item) ? item : Object.keys(item ?? []); for (const key of keys) { result[key] = 1; @@ -10,7 +13,7 @@ const toObjectMap = (...items: (string[] | Record)[]) => { } return result; -}; +} /** * An array of events that are allowed on every html element type. @@ -410,9 +413,10 @@ export const imageProperties = imgProperties; */ export const divProperties = htmlElementProperties; +// type GetValue = T extends unknown[] ? T[number] : /** * Gets native supported props for an html element provided the allowance set. Use one of the property - * sets defined (divProperties, buttonPropertes, etc) to filter out supported properties from a given + * sets defined (divProperties, buttonProperties, etc) to filter out supported properties from a given * props set. Note that all data- and aria- prefixed attributes will be allowed. * NOTE: getNativeProps should always be applied first when adding props to a react component. The * non-native props should be applied second. This will prevent getNativeProps from overriding your custom props. @@ -424,36 +428,35 @@ export const divProperties = htmlElementProperties; * @param allowedPropsNames - The array or record of allowed prop names. * @returns The filtered props */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function getNativeProps>( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - props: Record, - allowedPropNames: string[] | Record, - excludedPropNames?: string[], -): T { +export function getNativeProps< + Props extends Record, + A extends string, + E extends Extract = never +>(props: Props, allowedPropNames: A[] | Record, excludedPropNames?: E[]): Omit, E> { // It'd be great to properly type this while allowing 'aria-` and 'data-' attributes like TypeScript does for // JSX attributes, but that ability is hardcoded into the TS compiler with no analog in TypeScript typings. // Then we'd be able to enforce props extends native props (including aria- and data- attributes), and then // return native props. // We should be able to do this once this PR is merged: https://github.com/microsoft/TypeScript/pull/26797 - const isArray = Array.isArray(allowedPropNames); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result: Record = {}; + const result: Record = {}; const keys = Object.keys(props); for (const key of keys) { - const isNativeProp = - (!isArray && (allowedPropNames as Record)[key]) || - (isArray && (allowedPropNames as string[]).indexOf(key) >= 0) || - key.indexOf('data-') === 0 || - key.indexOf('aria-') === 0; - - if (isNativeProp && (!excludedPropNames || excludedPropNames?.indexOf(key) === -1)) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - result[key] = props![key] as any; + const isNative = isNativeProp(key, allowedPropNames); + + if (isNative && (!excludedPropNames || excludedPropNames.indexOf(key as E) === -1)) { + result[key] = props[key]; } } - return result as T; + return result as Omit; +} + +function isNativeProp(propName: string, nativeProps: ObjectItem) { + if (propName.indexOf('data-') === 0 || propName.indexOf('aria-') === 0) { + return true; + } + + return Array.isArray(nativeProps) ? nativeProps.indexOf(propName) !== -1 : Boolean(nativeProps[propName]); } diff --git a/yarn.lock b/yarn.lock index 44572827c1bc14..39bef53b8ce294 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3509,6 +3509,13 @@ dependencies: esquery "^1.0.1" +"@phenomnomnominal/tsquery@^4.0.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-4.2.0.tgz#7742ff4af12ce673b0b601ba5515c934f1876b14" + integrity sha512-hR2U3uVcrrdkuG30ItQ+uFDs4ncZAybxWG0OjTE8ptPzVoU7GVeXpy+vMU8zX9EbmjGeITPw/su5HjYQyAH8bA== + dependencies: + esquery "^1.0.1" + "@pmmmwh/react-refresh-webpack-plugin@^0.5.1": version "0.5.4" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.4.tgz#df0d0d855fc527db48aac93c218a0bf4ada41f99" @@ -5867,6 +5874,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.0": + version "17.0.10" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" + integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== + dependencies: + "@types/yargs-parser" "*" + "@types/yauzl@^2.9.1": version "2.9.2" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" @@ -11771,6 +11785,15 @@ eslint-config-prettier@8.3.0, eslint-config-prettier@^8.3.0: resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== +eslint-etc@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-etc/-/eslint-etc-5.1.0.tgz#7e99b80452fe8335065fddf7800e4495dc0922a0" + integrity sha512-Rmjl01h5smi5cbsFne2xpTuch2xNnwXiX2lbS4HttXUN5FwXKAwG1UEFBVGO1nC091YO/QyVahyfNPJSX2ae+g== + dependencies: + "@typescript-eslint/experimental-utils" "^5.0.0" + tsutils "^3.17.1" + tsutils-etc "^1.4.1" + eslint-import-resolver-node@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" @@ -11823,6 +11846,18 @@ eslint-plugin-es@^3.0.0: eslint-utils "^2.0.0" regexpp "^3.0.0" +eslint-plugin-etc@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-etc/-/eslint-plugin-etc-2.0.2.tgz#98013013058209f5a1d364caf17caeb62bd817ce" + integrity sha512-g3b95LCdTCwZA8On9EICYL8m1NMWaiGfmNUd/ftZTeGZDXrwujKXUr+unYzqKjKFo1EbqJ31vt+Dqzrdm/sUcw== + dependencies: + "@phenomnomnominal/tsquery" "^4.0.0" + "@typescript-eslint/experimental-utils" "^5.0.0" + eslint-etc "^5.1.0" + requireindex "~1.2.0" + tslib "^2.0.0" + tsutils "^3.0.0" + eslint-plugin-import@2.25.4, eslint-plugin-import@^2.22.1: version "2.25.4" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" @@ -22692,6 +22727,11 @@ require-package-name@^2.0.1: resolved "https://registry.yarnpkg.com/require-package-name/-/require-package-name-2.0.1.tgz#c11e97276b65b8e2923f75dabf5fb2ef0c3841b9" integrity sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk= +requireindex@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" + integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -25438,6 +25478,14 @@ tslib@^1.10.0, tslib@^1.13.0, tslib@^1.14.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tsutils-etc@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/tsutils-etc/-/tsutils-etc-1.4.1.tgz#bd42a0079d534765ab314d087f8a89c77a68723f" + integrity sha512-6UPYgc7OXcIW5tFxlsZF3OVSBvDInl/BkS3Xsu64YITXk7WrnWTVByKWPCThFDBp5gl5IGHOzGMdQuDCE7OL4g== + dependencies: + "@types/yargs" "^17.0.0" + yargs "^17.0.0" + tsutils@^3.0.0, tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -27273,6 +27321,19 @@ yargs@^16.1.1, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.0.0: + version "17.5.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + yargs@^17.2.1: version "17.3.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9"