From 49d10943d75309f13045c47a2f5c7265400973cf Mon Sep 17 00:00:00 2001 From: Dmytro Kirpa Date: Thu, 17 Apr 2025 12:58:59 +0200 Subject: [PATCH 1/2] chore: slot api react v17/18 support --- ...-70e37d4f-804b-494a-9d94-31840668c018.json | 7 + ...-7315db23-bdae-4372-8c87-60449326a492.json | 7 + ...-ca8b08cc-1dd4-48ef-bca4-dbc92125076d.json | 7 + ...-e41fbdeb-e121-4581-b878-3e5b347014d2.json | 7 + ...-0ff32a7c-9022-4bb0-a248-a264c594dd8d.json | 7 + ...-cd6b824f-ed5f-42f2-a7f1-7dc092156272.json | 7 + ...-144e4fd8-32af-4d8f-884f-0fc35f3fc444.json | 7 + ...-85a9961d-3d0a-436a-95a5-ebf0cc3e8078.json | 7 + ...-23e6ee8d-8a67-46de-b73b-f91e33ff9620.json | 7 + ...-82a0e2be-25f8-446a-ad2f-0a7eea97d9b3.json | 7 + ...-8e7b7750-4364-47ef-882a-6a1fe583a08f.json | 7 + ...-b8edc307-0cbe-4c48-a497-983989809ad7.json | 7 + ...-050271d4-8526-4aaa-a0ec-3f009240b858.json | 7 + ...-158b5bca-5551-4171-a402-6023509a0626.json | 7 + ...-66488148-0108-4253-a5e2-c4b61e9dc30b.json | 7 + .../AccordionPanel/useAccordionPanel.ts | 8 +- .../react-aria/library/etc/react-aria.api.md | 3 +- .../react-aria/library/src/button/types.ts | 7 +- .../src/button/useARIAButtonShorthand.ts | 4 +- .../src/components/Dialog/useDialog.ts | 8 +- .../DialogSurface/useDialogSurface.ts | 8 +- .../useDrawerHeaderTitleStyles.styles.ts | 5 + .../react-jsx-runtime/src/interop.test.tsx | 1 + .../src/components/MenuItem/useMenuItem.tsx | 15 +- .../MenuItemLink/useMenuItemLink.ts | 1 + .../MenuItemSwitch/useMenuItemSwitch.tsx | 1 + .../useMenuItemSwitchStyles.styles.ts | 1 + .../library/etc/react-migration-v0-v9.api.md | 9 +- .../library/src/slots/presenceMotionSlot.tsx | 2 - .../src/components/SearchBox/useSearchBox.tsx | 1 + .../components/DataGridBody/DataGridBody.tsx | 14 +- .../components/DataGridRow/DataGridRow.tsx | 14 +- .../components/DataGridRow/useDataGridRow.tsx | 1 + .../TableCellLayout/useTableCellLayout.ts | 5 +- .../useTableSelectionCell.tsx | 1 + .../library/src/components/Tab/useTab.ts | 3 +- .../TagPickerOption/useTagPickerOption.ts | 1 + .../ToastContainer/useToastContainer.ts | 4 +- .../library/src/hooks/useRootTree.ts | 7 +- .../library/src/hooks/useSubtree.ts | 8 +- .../etc/react-utilities.api.md | 50 ++++--- .../src/compose/assertSlots.ts | 8 +- .../src/compose/deprecated/getSlots.ts | 48 +++--- .../src/compose/deprecated/getSlotsNext.ts | 19 ++- .../compose/deprecated/resolveShorthand.ts | 15 +- .../react-utilities/src/compose/index.ts | 4 +- .../react-utilities/src/compose/slot.ts | 9 +- .../react-utilities/src/compose/types.ts | 141 +++++++++--------- .../src/hooks/useMergedRefs.ts | 20 ++- .../react-utilities/src/index.ts | 3 +- .../trigger/applyTriggerPropsToChildren.ts | 2 +- .../react-utilities/src/utils/types.ts | 99 ++++++++++++ .../useVirtualizerScrollView.ts | 1 + .../useVirtualizerScrollViewDynamic.tsx | 1 + 54 files changed, 450 insertions(+), 207 deletions(-) create mode 100644 change/@fluentui-react-accordion-70e37d4f-804b-494a-9d94-31840668c018.json create mode 100644 change/@fluentui-react-aria-7315db23-bdae-4372-8c87-60449326a492.json create mode 100644 change/@fluentui-react-dialog-ca8b08cc-1dd4-48ef-bca4-dbc92125076d.json create mode 100644 change/@fluentui-react-drawer-e41fbdeb-e121-4581-b878-3e5b347014d2.json create mode 100644 change/@fluentui-react-menu-0ff32a7c-9022-4bb0-a248-a264c594dd8d.json create mode 100644 change/@fluentui-react-migration-v0-v9-cd6b824f-ed5f-42f2-a7f1-7dc092156272.json create mode 100644 change/@fluentui-react-motion-144e4fd8-32af-4d8f-884f-0fc35f3fc444.json create mode 100644 change/@fluentui-react-search-85a9961d-3d0a-436a-95a5-ebf0cc3e8078.json create mode 100644 change/@fluentui-react-table-23e6ee8d-8a67-46de-b73b-f91e33ff9620.json create mode 100644 change/@fluentui-react-tabs-82a0e2be-25f8-446a-ad2f-0a7eea97d9b3.json create mode 100644 change/@fluentui-react-tag-picker-8e7b7750-4364-47ef-882a-6a1fe583a08f.json create mode 100644 change/@fluentui-react-toast-b8edc307-0cbe-4c48-a497-983989809ad7.json create mode 100644 change/@fluentui-react-tree-050271d4-8526-4aaa-a0ec-3f009240b858.json create mode 100644 change/@fluentui-react-utilities-158b5bca-5551-4171-a402-6023509a0626.json create mode 100644 change/@fluentui-react-virtualizer-66488148-0108-4253-a5e2-c4b61e9dc30b.json diff --git a/change/@fluentui-react-accordion-70e37d4f-804b-494a-9d94-31840668c018.json b/change/@fluentui-react-accordion-70e37d4f-804b-494a-9d94-31840668c018.json new file mode 100644 index 00000000000000..f50fe1cbf82e3e --- /dev/null +++ b/change/@fluentui-react-accordion-70e37d4f-804b-494a-9d94-31840668c018.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-accordion", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-aria-7315db23-bdae-4372-8c87-60449326a492.json b/change/@fluentui-react-aria-7315db23-bdae-4372-8c87-60449326a492.json new file mode 100644 index 00000000000000..a5d88be7f90c18 --- /dev/null +++ b/change/@fluentui-react-aria-7315db23-bdae-4372-8c87-60449326a492.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-aria", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-dialog-ca8b08cc-1dd4-48ef-bca4-dbc92125076d.json b/change/@fluentui-react-dialog-ca8b08cc-1dd4-48ef-bca4-dbc92125076d.json new file mode 100644 index 00000000000000..298859cc330d4c --- /dev/null +++ b/change/@fluentui-react-dialog-ca8b08cc-1dd4-48ef-bca4-dbc92125076d.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-dialog", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-drawer-e41fbdeb-e121-4581-b878-3e5b347014d2.json b/change/@fluentui-react-drawer-e41fbdeb-e121-4581-b878-3e5b347014d2.json new file mode 100644 index 00000000000000..af80f98a54d3f5 --- /dev/null +++ b/change/@fluentui-react-drawer-e41fbdeb-e121-4581-b878-3e5b347014d2.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-drawer", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-menu-0ff32a7c-9022-4bb0-a248-a264c594dd8d.json b/change/@fluentui-react-menu-0ff32a7c-9022-4bb0-a248-a264c594dd8d.json new file mode 100644 index 00000000000000..391ff02e4a0883 --- /dev/null +++ b/change/@fluentui-react-menu-0ff32a7c-9022-4bb0-a248-a264c594dd8d.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-menu", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-migration-v0-v9-cd6b824f-ed5f-42f2-a7f1-7dc092156272.json b/change/@fluentui-react-migration-v0-v9-cd6b824f-ed5f-42f2-a7f1-7dc092156272.json new file mode 100644 index 00000000000000..4d41e528bd8b7e --- /dev/null +++ b/change/@fluentui-react-migration-v0-v9-cd6b824f-ed5f-42f2-a7f1-7dc092156272.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-migration-v0-v9", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-motion-144e4fd8-32af-4d8f-884f-0fc35f3fc444.json b/change/@fluentui-react-motion-144e4fd8-32af-4d8f-884f-0fc35f3fc444.json new file mode 100644 index 00000000000000..e2a02cf34b72fc --- /dev/null +++ b/change/@fluentui-react-motion-144e4fd8-32af-4d8f-884f-0fc35f3fc444.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-motion", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-search-85a9961d-3d0a-436a-95a5-ebf0cc3e8078.json b/change/@fluentui-react-search-85a9961d-3d0a-436a-95a5-ebf0cc3e8078.json new file mode 100644 index 00000000000000..519b75a68db97e --- /dev/null +++ b/change/@fluentui-react-search-85a9961d-3d0a-436a-95a5-ebf0cc3e8078.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-search", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-table-23e6ee8d-8a67-46de-b73b-f91e33ff9620.json b/change/@fluentui-react-table-23e6ee8d-8a67-46de-b73b-f91e33ff9620.json new file mode 100644 index 00000000000000..3d5e5b16af0c36 --- /dev/null +++ b/change/@fluentui-react-table-23e6ee8d-8a67-46de-b73b-f91e33ff9620.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-table", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-tabs-82a0e2be-25f8-446a-ad2f-0a7eea97d9b3.json b/change/@fluentui-react-tabs-82a0e2be-25f8-446a-ad2f-0a7eea97d9b3.json new file mode 100644 index 00000000000000..367cba53509833 --- /dev/null +++ b/change/@fluentui-react-tabs-82a0e2be-25f8-446a-ad2f-0a7eea97d9b3.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-tabs", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-tag-picker-8e7b7750-4364-47ef-882a-6a1fe583a08f.json b/change/@fluentui-react-tag-picker-8e7b7750-4364-47ef-882a-6a1fe583a08f.json new file mode 100644 index 00000000000000..308a9673afd478 --- /dev/null +++ b/change/@fluentui-react-tag-picker-8e7b7750-4364-47ef-882a-6a1fe583a08f.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-tag-picker", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-toast-b8edc307-0cbe-4c48-a497-983989809ad7.json b/change/@fluentui-react-toast-b8edc307-0cbe-4c48-a497-983989809ad7.json new file mode 100644 index 00000000000000..57393fa0a39e2b --- /dev/null +++ b/change/@fluentui-react-toast-b8edc307-0cbe-4c48-a497-983989809ad7.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-toast", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-tree-050271d4-8526-4aaa-a0ec-3f009240b858.json b/change/@fluentui-react-tree-050271d4-8526-4aaa-a0ec-3f009240b858.json new file mode 100644 index 00000000000000..e4718e1d893a11 --- /dev/null +++ b/change/@fluentui-react-tree-050271d4-8526-4aaa-a0ec-3f009240b858.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-tree", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-utilities-158b5bca-5551-4171-a402-6023509a0626.json b/change/@fluentui-react-utilities-158b5bca-5551-4171-a402-6023509a0626.json new file mode 100644 index 00000000000000..aed7d12d9dfa6d --- /dev/null +++ b/change/@fluentui-react-utilities-158b5bca-5551-4171-a402-6023509a0626.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-utilities", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-virtualizer-66488148-0108-4253-a5e2-c4b61e9dc30b.json b/change/@fluentui-react-virtualizer-66488148-0108-4253-a5e2-c4b61e9dc30b.json new file mode 100644 index 00000000000000..a6a7124734dc8b --- /dev/null +++ b/change/@fluentui-react-virtualizer-66488148-0108-4253-a5e2-c4b61e9dc30b.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: slot api react v17/18 support", + "packageName": "@fluentui/react-virtualizer", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "none" +} diff --git a/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts b/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts index 808a7f62ec14f1..2e00c494ac3662 100644 --- a/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts +++ b/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { getIntrinsicElementProps, slot } from '@fluentui/react-utilities'; import { useTabsterAttributes } from '@fluentui/react-tabster'; -import { presenceMotionSlot, type PresenceMotionSlotProps } from '@fluentui/react-motion'; +import { presenceMotionSlot } from '@fluentui/react-motion'; import { Collapse } from '@fluentui/react-motion-components-preview'; import { useAccordionContext_unstable } from '../../contexts/accordion'; import type { AccordionPanelProps, AccordionPanelState } from './AccordionPanel.types'; @@ -24,11 +24,7 @@ export const useAccordionPanel_unstable = ( open, components: { root: 'div', - // TODO: remove once React v18 slot API is modified - // This is a problem at the moment due to UnknownSlotProps assumption - // that `children` property is `ReactNode`, which in this case is not valid - // as PresenceComponentProps['children'] is `ReactElement` - collapseMotion: Collapse as React.FC, + collapseMotion: Collapse, }, root: slot.always( getIntrinsicElementProps('div', { diff --git a/packages/react-components/react-aria/library/etc/react-aria.api.md b/packages/react-components/react-aria/library/etc/react-aria.api.md index 46aadb79afe483..9922330545c9c3 100644 --- a/packages/react-components/react-aria/library/etc/react-aria.api.md +++ b/packages/react-components/react-aria/library/etc/react-aria.api.md @@ -5,6 +5,7 @@ ```ts import type { AnnounceContextValue } from '@fluentui/react-shared-contexts'; +import type { DistributiveOmit } from '@fluentui/react-utilities'; import type { ExtractSlotProps } from '@fluentui/react-utilities'; import * as React_2 from 'react'; import type { ResolveShorthandFunction } from '@fluentui/react-utilities'; @@ -73,7 +74,7 @@ export type ARIAButtonElement = H export type ARIAButtonElementIntersection = UnionToIntersection>; // @public -export type ARIAButtonProps = React_2.PropsWithRef & { +export type ARIAButtonProps = DistributiveOmit, 'children'> & { disabled?: boolean; disabledFocusable?: boolean; }; diff --git a/packages/react-components/react-aria/library/src/button/types.ts b/packages/react-components/react-aria/library/src/button/types.ts index 478b894dadb0c1..8e0ae86460c03b 100644 --- a/packages/react-components/react-aria/library/src/button/types.ts +++ b/packages/react-components/react-aria/library/src/button/types.ts @@ -1,4 +1,4 @@ -import type { ExtractSlotProps, Slot, UnionToIntersection } from '@fluentui/react-utilities'; +import type { DistributiveOmit, ExtractSlotProps, Slot, UnionToIntersection } from '@fluentui/react-utilities'; import * as React from 'react'; export type ARIAButtonType = 'button' | 'a' | 'div'; @@ -18,8 +18,9 @@ export type ARIAButtonElementIntersection = React.PropsWithRef< - JSX.IntrinsicElements[Type] +export type ARIAButtonProps = DistributiveOmit< + React.PropsWithRef, + 'children' > & { disabled?: boolean; /** diff --git a/packages/react-components/react-aria/library/src/button/useARIAButtonShorthand.ts b/packages/react-components/react-aria/library/src/button/useARIAButtonShorthand.ts index 9d8b64067a863e..c0a28537c5ad00 100644 --- a/packages/react-components/react-aria/library/src/button/useARIAButtonShorthand.ts +++ b/packages/react-components/react-aria/library/src/button/useARIAButtonShorthand.ts @@ -15,9 +15,9 @@ import type { ARIAButtonProps, ARIAButtonSlotProps, ARIAButtonType } from './typ * where no attribute addition is required. */ // eslint-disable-next-line @typescript-eslint/no-deprecated -export const useARIAButtonShorthand: ResolveShorthandFunction = (value, options) => { +export const useARIAButtonShorthand = ((value, options) => { // eslint-disable-next-line @typescript-eslint/no-deprecated const shorthand = resolveShorthand(value, options); const shorthandARIAButton = useARIAButtonProps(shorthand?.as ?? 'button', shorthand); return shorthand && shorthandARIAButton; -}; +}) as ResolveShorthandFunction; diff --git a/packages/react-components/react-dialog/library/src/components/Dialog/useDialog.ts b/packages/react-components/react-dialog/library/src/components/Dialog/useDialog.ts index 84d3930686a8a3..84d4d0e2622d7a 100644 --- a/packages/react-components/react-dialog/library/src/components/Dialog/useDialog.ts +++ b/packages/react-components/react-dialog/library/src/components/Dialog/useDialog.ts @@ -1,6 +1,6 @@ import { useHasParentContext } from '@fluentui/react-context-selector'; import { useModalAttributes } from '@fluentui/react-tabster'; -import { presenceMotionSlot, type PresenceMotionSlotProps } from '@fluentui/react-motion'; +import { presenceMotionSlot } from '@fluentui/react-motion'; import { useControllableState, useEventCallback, useId } from '@fluentui/react-utilities'; import * as React from 'react'; @@ -48,11 +48,7 @@ export const useDialog_unstable = (props: DialogProps): DialogState => { return { components: { - // TODO: remove once React v18 slot API is modified - // This is a problem at the moment due to UnknownSlotProps assumption - // that `children` property is `ReactNode`, which in this case is not valid - // as PresenceComponentProps['children'] is `ReactElement` - surfaceMotion: DialogSurfaceMotion as React.FC, + surfaceMotion: DialogSurfaceMotion, }, inertTrapFocus, open, diff --git a/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts b/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts index 51a6f5c59c7bc7..71fb7defa6347e 100644 --- a/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts +++ b/packages/react-components/react-dialog/library/src/components/DialogSurface/useDialogSurface.ts @@ -1,5 +1,5 @@ import { Escape } from '@fluentui/keyboard-keys'; -import { presenceMotionSlot, type PresenceMotionSlotProps } from '@fluentui/react-motion'; +import { presenceMotionSlot } from '@fluentui/react-motion'; import { useEventCallback, useMergedRefs, @@ -97,11 +97,7 @@ export const useDialogSurface_unstable = ( components: { backdrop: 'div', root: 'div', - // TODO: remove once React v18 slot API is modified - // This is a problem at the moment due to UnknownSlotProps assumption - // that `children` property is `ReactNode`, which in this case is not valid - // as PresenceComponentProps['children'] is `ReactElement` - backdropMotion: DialogBackdropMotion as React.FC, + backdropMotion: DialogBackdropMotion, }, open, backdrop, diff --git a/packages/react-components/react-drawer/library/src/components/DrawerHeaderTitle/useDrawerHeaderTitleStyles.styles.ts b/packages/react-components/react-drawer/library/src/components/DrawerHeaderTitle/useDrawerHeaderTitleStyles.styles.ts index 139d604726c523..e0b13d16247e96 100644 --- a/packages/react-components/react-drawer/library/src/components/DrawerHeaderTitle/useDrawerHeaderTitleStyles.styles.ts +++ b/packages/react-components/react-drawer/library/src/components/DrawerHeaderTitle/useDrawerHeaderTitleStyles.styles.ts @@ -35,6 +35,11 @@ export const useDrawerHeaderTitleStyles_unstable = (state: DrawerHeaderTitleStat const styles = useStyles(); + // We should not use components to pass along the base element type of a slot + // but there's no way to retrieve the element type of a slot from the slot definition + // right now without using SLOT_ELEMENT_TYPE_SYMBOL + // TODO: create a method to retrieve the element type of a slot + // eslint-disable-next-line @typescript-eslint/no-deprecated const { heading: root = {}, action, components } = state; useDialogTitleStyles_unstable({ diff --git a/packages/react-components/react-jsx-runtime/src/interop.test.tsx b/packages/react-components/react-jsx-runtime/src/interop.test.tsx index 24d2f754c91f99..76614a14bb15cb 100644 --- a/packages/react-components/react-jsx-runtime/src/interop.test.tsx +++ b/packages/react-components/react-jsx-runtime/src/interop.test.tsx @@ -171,6 +171,7 @@ describe('resolveShorthand with assertSlots', () => { const TestComponent = (props: TestComponentProps) => { const higherOrderState = useHigherOrderStateHook(props); const state: TestComponentState = { + // eslint-disable-next-line @typescript-eslint/no-deprecated components: { ...higherOrderState.components, slot: 'span' }, slot: { ...higherOrderState.slot, diff --git a/packages/react-components/react-menu/library/src/components/MenuItem/useMenuItem.tsx b/packages/react-components/react-menu/library/src/components/MenuItem/useMenuItem.tsx index 750a3deeda900c..042d27d2a454b1 100644 --- a/packages/react-components/react-menu/library/src/components/MenuItem/useMenuItem.tsx +++ b/packages/react-components/react-menu/library/src/components/MenuItem/useMenuItem.tsx @@ -5,6 +5,7 @@ import { getIntrinsicElementProps, slot, useIsomorphicLayoutEffect, + omit, } from '@fluentui/react-utilities'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { useCharacterSearch } from './useCharacterSearch'; @@ -37,7 +38,13 @@ const ChevronLeftIcon = bundleIcon(ChevronLeftFilled, ChevronLeftRegular); export const useMenuItem_unstable = (props: MenuItemProps, ref: React.Ref>): MenuItemState => { const isSubmenuTrigger = useMenuTriggerContext_unstable(); const persistOnClickContext = useMenuContext_unstable(context => context.persistOnItemClick); - const { as = 'div', disabled = false, hasSubmenu = isSubmenuTrigger, persistOnClick = persistOnClickContext } = props; + const { + as = 'div', + disabled = false, + hasSubmenu = isSubmenuTrigger, + persistOnClick = persistOnClickContext, + ...rest + } = props; const { hasIcons, hasCheckmarks } = useIconAndCheckmarkAlignment({ hasSubmenu }); const setOpen = useMenuContext_unstable(context => context.setOpen); useNotifySplitItemMultiline({ multiline: !!props.subText, hasSubmenu }); @@ -60,11 +67,12 @@ export const useMenuItem_unstable = (props: MenuItemProps, ref: React.Ref( as, useARIAButtonProps<'div', ARIAButtonProps<'div'>>(as, { role: 'menuitem', - ...props, + // `content` is a slot and it's type clashes with the HTMLElement `content` attribute + ...omit(rest, ['content']), disabled: false, disabledFocusable: disabled, ref: useMergedRefs(ref, innerRef) as React.Ref>, @@ -96,6 +104,7 @@ export const useMenuItem_unstable = (props: MenuItemProps, ref: React.Ref>; @@ -157,12 +156,10 @@ export const input: { // @public (undocumented) export const ItemLayout: React_2.ForwardRefExoticComponent & Omit<{ as?: "div" | undefined; -} & Pick, HTMLDivElement>, "key" | keyof React_2.HTMLAttributes> & { +} & Omit, HTMLDivElement>, "key" | keyof React_2.HTMLAttributes> & { ref?: ((instance: HTMLDivElement | null) => void) | React_2.RefObject | null | undefined; -} & { - children?: React_2.ReactNode | SlotRenderFunction, HTMLDivElement>, "key" | keyof React_2.HTMLAttributes> & { - ref?: ((instance: HTMLDivElement | null) => void) | React_2.RefObject | null | undefined; - }>; +}, "children"> & { + children?: any; }, "ref"> & React_2.RefAttributes>; // @public (undocumented) diff --git a/packages/react-components/react-motion/library/src/slots/presenceMotionSlot.tsx b/packages/react-components/react-motion/library/src/slots/presenceMotionSlot.tsx index 54f108e3b66c74..f52c1d86585e7a 100644 --- a/packages/react-components/react-motion/library/src/slots/presenceMotionSlot.tsx +++ b/packages/react-components/react-motion/library/src/slots/presenceMotionSlot.tsx @@ -30,8 +30,6 @@ export type PresenceMotionSlotProps; }; diff --git a/packages/react-components/react-search/library/src/components/SearchBox/useSearchBox.tsx b/packages/react-components/react-search/library/src/components/SearchBox/useSearchBox.tsx index 266a2c08a10d2f..6418b53a982851 100644 --- a/packages/react-components/react-search/library/src/components/SearchBox/useSearchBox.tsx +++ b/packages/react-components/react-search/library/src/components/SearchBox/useSearchBox.tsx @@ -111,6 +111,7 @@ export const useSearchBox_unstable = (props: SearchBoxProps, ref: React.Ref & - ((props: DataGridBodyProps) => JSX.Element) = React.forwardRef((props, ref) => { - const state = useDataGridBody_unstable(props, ref); + ((props: DataGridBodyProps) => JSX.Element) = React.forwardRef( + (props, ref) => { + const state = useDataGridBody_unstable(props, ref); - useDataGridBodyStyles_unstable(state); + useDataGridBodyStyles_unstable(state); - useCustomStyleHook_unstable('useDataGridBodyStyles_unstable')(state); + useCustomStyleHook_unstable('useDataGridBodyStyles_unstable')(state); - return renderDataGridBody_unstable(state); -}) as ForwardRefComponent & ((props: DataGridBodyProps) => JSX.Element); + return renderDataGridBody_unstable(state); + }, +) as ForwardRefComponent & ((props: DataGridBodyProps) => JSX.Element); DataGridBody.displayName = 'DataGridBody'; diff --git a/packages/react-components/react-table/library/src/components/DataGridRow/DataGridRow.tsx b/packages/react-components/react-table/library/src/components/DataGridRow/DataGridRow.tsx index 36bfd9647550b5..a5e2d692fd742f 100644 --- a/packages/react-components/react-table/library/src/components/DataGridRow/DataGridRow.tsx +++ b/packages/react-components/react-table/library/src/components/DataGridRow/DataGridRow.tsx @@ -10,14 +10,16 @@ import { useCustomStyleHook_unstable } from '@fluentui/react-shared-contexts'; * DataGridRow component */ export const DataGridRow: ForwardRefComponent & - ((props: DataGridRowProps) => JSX.Element) = React.forwardRef((props, ref) => { - const state = useDataGridRow_unstable(props, ref); + ((props: DataGridRowProps) => JSX.Element) = React.forwardRef( + (props, ref) => { + const state = useDataGridRow_unstable(props, ref); - useDataGridRowStyles_unstable(state); + useDataGridRowStyles_unstable(state); - useCustomStyleHook_unstable('useDataGridRowStyles_unstable')(state); + useCustomStyleHook_unstable('useDataGridRowStyles_unstable')(state); - return renderDataGridRow_unstable(state); -}) as ForwardRefComponent & ((props: DataGridRowProps) => JSX.Element); + return renderDataGridRow_unstable(state); + }, +) as ForwardRefComponent & ((props: DataGridRowProps) => JSX.Element); DataGridRow.displayName = 'DataGridRow'; diff --git a/packages/react-components/react-table/library/src/components/DataGridRow/useDataGridRow.tsx b/packages/react-components/react-table/library/src/components/DataGridRow/useDataGridRow.tsx index 8b71d7d4b6358c..a004745273a003 100644 --- a/packages/react-components/react-table/library/src/components/DataGridRow/useDataGridRow.tsx +++ b/packages/react-components/react-table/library/src/components/DataGridRow/useDataGridRow.tsx @@ -72,6 +72,7 @@ export const useDataGridRow_unstable = (props: DataGridRowProps, ref: React.Ref< return { ...baseState, components: { + // eslint-disable-next-line @typescript-eslint/no-deprecated ...baseState.components, selectionCell: DataGridSelectionCell, }, diff --git a/packages/react-components/react-table/library/src/components/TableCellLayout/useTableCellLayout.ts b/packages/react-components/react-table/library/src/components/TableCellLayout/useTableCellLayout.ts index bbf2e096734a5a..81bc48e43a3342 100644 --- a/packages/react-components/react-table/library/src/components/TableCellLayout/useTableCellLayout.ts +++ b/packages/react-components/react-table/library/src/components/TableCellLayout/useTableCellLayout.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getIntrinsicElementProps, slot } from '@fluentui/react-utilities'; +import { getIntrinsicElementProps, slot, omit } from '@fluentui/react-utilities'; import type { TableCellLayoutProps, TableCellLayoutState } from './TableCellLayout.types'; import { useTableContext } from '../../contexts/tableContext'; @@ -38,7 +38,8 @@ export const useTableCellLayout_unstable = ( // `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement` // but since it would be a breaking change to fix it, we are casting ref to it's proper type ref: ref as React.Ref, - ...props, + // `content` is a slot and it's type clashes with the HTMLElement `content` attribute + ...omit(props, ['content']), }), { elementType: 'div' }, ), diff --git a/packages/react-components/react-table/library/src/components/TableSelectionCell/useTableSelectionCell.tsx b/packages/react-components/react-table/library/src/components/TableSelectionCell/useTableSelectionCell.tsx index 3770b04f5b33f3..068e5a93359bad 100644 --- a/packages/react-components/react-table/library/src/components/TableSelectionCell/useTableSelectionCell.tsx +++ b/packages/react-components/react-table/library/src/components/TableSelectionCell/useTableSelectionCell.tsx @@ -34,6 +34,7 @@ export const useTableSelectionCell_unstable = ( return { ...tableCellState, components: { + // eslint-disable-next-line @typescript-eslint/no-deprecated ...tableCellState.components, checkboxIndicator: Checkbox, radioIndicator: Radio, diff --git a/packages/react-components/react-tabs/library/src/components/Tab/useTab.ts b/packages/react-components/react-tabs/library/src/components/Tab/useTab.ts index ebce1ec3bbc930..0d6829ee065bef 100644 --- a/packages/react-components/react-tabs/library/src/components/Tab/useTab.ts +++ b/packages/react-components/react-tabs/library/src/components/Tab/useTab.ts @@ -78,7 +78,8 @@ export const useTab_unstable = (props: TabProps, ref: React.Ref): T // according to https://www.w3.org/TR/wai-aria-1.1/#aria-selected 'aria-selected': disabled ? undefined : (`${selected}` as 'true' | 'false'), ...focusProps, - ...props, + // `content` is a slot and it's type clashes with the HTMLElement `content` attribute + ...omit(props, ['content']), disabled, onClick: onTabClick, onFocus: selectTabOnFocus ? onTabFocus : onFocus, diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerOption/useTagPickerOption.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerOption/useTagPickerOption.ts index 775bbe458bea9f..0a7478beb45c9d 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerOption/useTagPickerOption.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerOption/useTagPickerOption.ts @@ -19,6 +19,7 @@ export const useTagPickerOption_unstable = ( const optionState = useOption_unstable(props, ref); const state: TagPickerOptionState = { components: { + // eslint-disable-next-line @typescript-eslint/no-deprecated ...optionState.components, media: 'div', secondaryContent: 'span', diff --git a/packages/react-components/react-toast/library/src/components/ToastContainer/useToastContainer.ts b/packages/react-components/react-toast/library/src/components/ToastContainer/useToastContainer.ts index 8923147185e43b..26528e7e062224 100644 --- a/packages/react-components/react-toast/library/src/components/ToastContainer/useToastContainer.ts +++ b/packages/react-components/react-toast/library/src/components/ToastContainer/useToastContainer.ts @@ -7,6 +7,7 @@ import { useEventCallback, useId, slot, + omit, } from '@fluentui/react-utilities'; import { useFluent_unstable } from '@fluentui/react-shared-contexts'; import { Delete, Tab } from '@fluentui/keyboard-keys'; @@ -210,7 +211,8 @@ export const useToastContainer_unstable = ( role: 'listitem', 'aria-labelledby': titleId, 'aria-describedby': bodyId, - ...rest, + // The `content` type doesn't match the HTMLElement `content` attribute + ...omit(rest, ['content']), ...userRootSlot, ...focusableGroupAttribute, onMouseEnter, diff --git a/packages/react-components/react-tree/library/src/hooks/useRootTree.ts b/packages/react-components/react-tree/library/src/hooks/useRootTree.ts index f7714274176ce1..0becfe3a82e1bb 100644 --- a/packages/react-components/react-tree/library/src/hooks/useRootTree.ts +++ b/packages/react-components/react-tree/library/src/hooks/useRootTree.ts @@ -2,7 +2,6 @@ import { getIntrinsicElementProps, useEventCallback, slot } from '@fluentui/reac import type { TreeCheckedChangeData, TreeProps, TreeState } from '../Tree'; import * as React from 'react'; import { Collapse } from '@fluentui/react-motion-components-preview'; -import { PresenceMotionSlotProps } from '@fluentui/react-motion'; import { TreeContextValue, TreeItemRequest } from '../contexts/treeContext'; import { createCheckedItems } from '../utils/createCheckedItems'; import { treeDataTypes } from '../utils/tokens'; @@ -80,11 +79,7 @@ export function useRootTree( return { components: { root: 'div', - // TODO: remove once React v18 slot API is modified - // This is a problem at the moment due to UnknownSlotProps assumption - // that `children` property is `ReactNode`, which in this case is not valid - // as PresenceComponentProps['children'] is `ReactElement` - collapseMotion: Collapse as React.FC, + collapseMotion: Collapse, }, contextType: 'root', selectionMode, diff --git a/packages/react-components/react-tree/library/src/hooks/useSubtree.ts b/packages/react-components/react-tree/library/src/hooks/useSubtree.ts index bb79652f6d094e..93d553600ea66d 100644 --- a/packages/react-components/react-tree/library/src/hooks/useSubtree.ts +++ b/packages/react-components/react-tree/library/src/hooks/useSubtree.ts @@ -3,7 +3,7 @@ import { TreeProps, TreeState } from '../Tree'; import { SubtreeContextValue, useSubtreeContext_unstable, useTreeItemContext_unstable } from '../contexts/index'; import { getIntrinsicElementProps, useMergedRefs, slot } from '@fluentui/react-utilities'; import { Collapse } from '@fluentui/react-motion-components-preview'; -import { presenceMotionSlot, PresenceMotionSlotProps } from '@fluentui/react-motion'; +import { presenceMotionSlot } from '@fluentui/react-motion'; /** * Create the state required to render a sub-level tree. @@ -26,11 +26,7 @@ export function useSubtree( open, components: { root: 'div', - // TODO: remove once React v18 slot API is modified - // This is a problem at the moment due to UnknownSlotProps assumption - // that `children` property is `ReactNode`, which in this case is not valid - // as PresenceComponentProps['children'] is `ReactElement` - collapseMotion: Collapse as React.FC, + collapseMotion: Collapse, }, level: parentLevel + 1, root: slot.always( 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 70e8390f3ef249..15f6cf0d77be6c 100644 --- a/packages/react-components/react-utilities/etc/react-utilities.api.md +++ b/packages/react-components/react-utilities/etc/react-utilities.api.md @@ -27,10 +27,10 @@ export type ComponentProps = { components: { - [Key in keyof Slots]-?: React_2.ComponentType> | (ExtractSlotProps extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); + [Key in keyof Slots]-?: React_2.ElementType; }; } & { - [Key in keyof Slots]: ReplaceNullWithUndefined>; + [Key in keyof Slots]: ReplaceNullWithUndefined>>; }; // @internal (undocumented) @@ -63,7 +63,7 @@ export type FluentTriggerComponent = { }; // @public -export type ForwardRefComponent = React_2.ForwardRefExoticComponent>>; +export type ForwardRefComponent = NamedExoticComponent>>; // @public export function getEventClientCoords(event: TouchOrMouseEvent): { @@ -100,13 +100,13 @@ export const getRTLSafeKey: (key: string, dir: 'ltr' | 'rtl') => string; export const getSlotClassNameProp_unstable: (slot: UnknownSlotProps) => string | undefined; // @public @deprecated -export function getSlots(state: ComponentState): { +export function getSlots(state: unknown): { slots: Slots; slotProps: ObjectSlotProps; }; // @internal @deprecated -export function getSlotsNext(state: ComponentState): { +export function getSlotsNext(state: unknown): { slots: Slots; slotProps: ObjectSlotProps; }; @@ -190,6 +190,12 @@ export interface PriorityQueue { // @public (undocumented) export type ReactTouchOrMouseEvent = React_2.MouseEvent | React_2.TouchEvent; +// @public (undocumented) +export interface RefAttributes extends React_2.Attributes { + // (undocumented) + ref?: React_2.Ref | undefined; +} + // @public export type RefObjectFunction = React_2.RefObject & ((value: T | null) => void); @@ -204,8 +210,8 @@ function resolveShorthand_2(v // @public @deprecated (undocumented) export type ResolveShorthandFunction = { -

(value: P | SlotShorthandValue | undefined, options: ResolveShorthandOptions): P; -

(value: P | SlotShorthandValue | null | undefined, options?: ResolveShorthandOptions): P | undefined; +

(value: P | SlotShorthandValue | undefined, options: ResolveShorthandOptions): WithoutSlotRenderFunction

; +

(value: P | SlotShorthandValue | null | undefined, options?: ResolveShorthandOptions): WithoutSlotRenderFunction

| undefined; }; // @public @deprecated (undocumented) @@ -252,13 +258,11 @@ export { SelectionMode_2 as SelectionMode } export function setVirtualParent(child: Node, parent?: Node): void; // @public -export type Slot = IsSingleton> extends true ? WithSlotShorthandValue | UnknownSlotProps, AlternateAs extends keyof JSX.IntrinsicElements = never> = IsSingleton> extends true ? WithSlotShorthandValue> : Type extends React_2.ComponentType ? WithSlotRenderFunction : Type> | { - [As in AlternateAs]: { - as: As; - } & WithSlotRenderFunction>; -}[AlternateAs] | null : 'Error: First parameter to Slot must not be not a union of types. See documentation of Slot type.'; +} & WithSlotRenderFunction> : Type extends ComponentType ? Props extends UnknownSlotProps ? Props : WithSlotRenderFunction : Type> | (AlternateAs extends unknown ? { + as: AlternateAs; +} & WithSlotRenderFunction> : never) | null : 'Error: First parameter to Slot must not be not a union of types. See documentation of Slot type.'; declare namespace slot { export { @@ -285,32 +289,35 @@ export type SlotClassNames = { }; // @public -export type SlotComponentType = Props & { - (props: React_2.PropsWithChildren<{}>): React_2.ReactElement | null; +export type SlotComponentType = WithoutSlotRenderFunction & FunctionComponent<{ + children?: React_2.ReactNode; +}> & { [SLOT_RENDER_FUNCTION_SYMBOL]?: SlotRenderFunction; - [SLOT_ELEMENT_TYPE_SYMBOL]: React_2.ComponentType | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); + [SLOT_ELEMENT_TYPE_SYMBOL]: ComponentType | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); [SLOT_CLASS_NAME_PROP_SYMBOL]?: string; }; // @public (undocumented) export type SlotOptions = { elementType: React_2.ComponentType | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); - defaultProps?: Partial; + defaultProps?: Partial>; + }>; }; // @public export type SlotPropsRecord = Record; // @public (undocumented) -export type SlotRenderFunction = (Component: React_2.ElementType, props: Omit) => React_2.ReactNode; +export type SlotRenderFunction = (Component: React_2.ElementType, props: Omit) => ReactNode; // @public @deprecated (undocumented) export type Slots = { - [K in keyof S]: ExtractSlotProps extends AsIntrinsicElement ? As : ExtractSlotProps extends React_2.ComponentType ? React_2.ElementType> : React_2.ElementType>; + [K in keyof S]: React_2.ElementType; }; // @public -export type SlotShorthandValue = React_2.ReactChild | React_2.ReactNode[] | React_2.ReactPortal; +export type SlotShorthandValue = React_2.ReactElement | string | number | Iterable | React_2.ReactPortal; // @public export const SSRProvider: React_2.FC<{ @@ -329,8 +336,9 @@ export type TriggerProps = { export type UnionToIntersection = (U extends unknown ? (x: U) => U : never) extends (x: infer I) => U ? I : never; // @public -export type UnknownSlotProps = Pick, 'children' | 'className' | 'style'> & { +export type UnknownSlotProps = Pick, 'className' | 'style'> & { as?: keyof JSX.IntrinsicElements; + children?: ReactNode; }; // @internal diff --git a/packages/react-components/react-utilities/src/compose/assertSlots.ts b/packages/react-components/react-utilities/src/compose/assertSlots.ts index 5b2d20d1ad38f4..bf93df9657d74c 100644 --- a/packages/react-components/react-utilities/src/compose/assertSlots.ts +++ b/packages/react-components/react-utilities/src/compose/assertSlots.ts @@ -34,6 +34,7 @@ export function assertSlots(state: unknown): asse */ if (process.env.NODE_ENV !== 'production') { const typedState = state as ComponentState; + // eslint-disable-next-line @typescript-eslint/no-deprecated for (const slotName of Object.keys(typedState.components)) { const slotElement = typedState[slotName]; if (slotElement === undefined) { @@ -44,6 +45,7 @@ export function assertSlots(state: unknown): asse // FIXME: this slot will still fail to support child render function scenario if (!isSlot(slotElement)) { typedState[slotName as keyof ComponentState] = slot.always(slotElement, { + // eslint-disable-next-line @typescript-eslint/no-deprecated elementType: typedState.components[slotName] as React.ComponentType<{}>, }) as ComponentState[keyof ComponentState]; // eslint-disable-next-line no-console @@ -56,13 +58,17 @@ export function assertSlots(state: unknown): asse // This means a slot is being declared by using resolveShorthand on the state hook, // but the render method is using the new `assertSlots` method. That scenario can be solved by simply updating the slot element with the proper element type const { [SLOT_ELEMENT_TYPE_SYMBOL]: elementType } = slotElement; + // eslint-disable-next-line @typescript-eslint/no-deprecated if (elementType !== typedState.components[slotName]) { + // eslint-disable-next-line @typescript-eslint/no-deprecated slotElement[SLOT_ELEMENT_TYPE_SYMBOL] = typedState.components[slotName] as React.ComponentType<{}>; // eslint-disable-next-line no-console console.warn(/** #__DE-INDENT__ */ ` @fluentui/react-utilities [${assertSlots.name}]: "state.${slotName}" element type differs from "state.components.${slotName}", - ${elementType} !== ${typedState.components[slotName]}. + ${elementType} !== ${ + typedState.components[slotName] /* eslint-disable-line @typescript-eslint/no-deprecated */ + }. Be sure to create slots properly by using "slot.always" or "slot.optional" with the correct elementType. `); } diff --git a/packages/react-components/react-utilities/src/compose/deprecated/getSlots.ts b/packages/react-components/react-utilities/src/compose/deprecated/getSlots.ts index c75947a2bc85b6..9fd9c00672f3a1 100644 --- a/packages/react-components/react-utilities/src/compose/deprecated/getSlots.ts +++ b/packages/react-components/react-utilities/src/compose/deprecated/getSlots.ts @@ -1,40 +1,23 @@ import * as React from 'react'; import { omit } from '../../utils/omit'; -import type { - AsIntrinsicElement, - ComponentState, - ExtractSlotProps, - SlotPropsRecord, - SlotRenderFunction, - UnknownSlotProps, -} from '../types'; +import type { ComponentState, SlotPropsRecord, SlotRenderFunction, UnknownSlotProps } from '../types'; import { isSlot } from '../isSlot'; import { SLOT_RENDER_FUNCTION_SYMBOL } from '../constants'; -import { UnionToIntersection } from '../../utils/types'; /** * @deprecated - use slot.always or slot.optional combined with assertSlots instead */ export type Slots = { - [K in keyof S]: ExtractSlotProps extends AsIntrinsicElement - ? // for slots with an `as` prop, the slot will be any one of the possible values of `as` - As - : ExtractSlotProps extends React.ComponentType - ? React.ElementType> - : React.ElementType>; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [K in keyof S]: React.ElementType; }; /** * @deprecated - use slot.always or slot.optional combined with assertSlots instead */ export type ObjectSlotProps = { - [K in keyof S]-?: ExtractSlotProps extends AsIntrinsicElement - ? // For intrinsic element types, return the intersection of all possible - // element's props, to be compatible with the As type returned by Slots<> - UnionToIntersection // Slot<'div', 'span'> - : ExtractSlotProps extends React.ComponentType - ? P // Slot - : ExtractSlotProps; // Slot + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [K in keyof S]-?: any; // Slot }; /** @@ -57,20 +40,22 @@ export type ObjectSlotProps = { * @returns An object containing the `slots` map and `slotProps` map. */ export function getSlots( - state: ComponentState, + state: unknown, ): { // eslint-disable-next-line @typescript-eslint/no-deprecated slots: Slots; // eslint-disable-next-line @typescript-eslint/no-deprecated slotProps: ObjectSlotProps; } { + const typeState = state as ComponentState; // eslint-disable-next-line @typescript-eslint/no-deprecated const slots = {} as Slots; const slotProps = {} as R; - const slotNames: (keyof R)[] = Object.keys(state.components); + // eslint-disable-next-line @typescript-eslint/no-deprecated + const slotNames: (keyof R)[] = Object.keys(typeState.components); for (const slotName of slotNames) { - const [slot, props] = getSlot(state, slotName); + const [slot, props] = getSlot(typeState, slotName); // eslint-disable-next-line @typescript-eslint/no-deprecated slots[slotName] = slot as Slots[typeof slotName]; slotProps[slotName] = props; @@ -96,15 +81,19 @@ function getSlot( const renderFunction = isSlot(props) ? props[SLOT_RENDER_FUNCTION_SYMBOL] : undefined; const slot = ( - state.components?.[slotName] === undefined || typeof state.components[slotName] === 'string' - ? asProp || state.components?.[slotName] || 'div' - : state.components[slotName] + state.components?.[slotName] === undefined || // eslint-disable-line @typescript-eslint/no-deprecated + // eslint-disable-next-line @typescript-eslint/no-deprecated + typeof state.components[slotName] === 'string' + ? // eslint-disable-next-line @typescript-eslint/no-deprecated + asProp || state.components?.[slotName] || 'div' + : // eslint-disable-next-line @typescript-eslint/no-deprecated + state.components[slotName] ) as React.ElementType; if (renderFunction || typeof children === 'function') { const render = (renderFunction || children) as SlotRenderFunction; return [ - React.Fragment, + React.Fragment as React.ElementType, { children: render(slot, rest as Omit), } as unknown as R[K], @@ -112,6 +101,7 @@ function getSlot( } const shouldOmitAsProp = typeof slot === 'string' && asProp; + // eslint-disable-next-line @typescript-eslint/no-deprecated const slotProps = (shouldOmitAsProp ? omit(props, ['as']) : (props as UnknownSlotProps)) as R[K]; return [slot, slotProps]; } diff --git a/packages/react-components/react-utilities/src/compose/deprecated/getSlotsNext.ts b/packages/react-components/react-utilities/src/compose/deprecated/getSlotsNext.ts index 9889ea0654fdf7..f8dc0a1f58be6f 100644 --- a/packages/react-components/react-utilities/src/compose/deprecated/getSlotsNext.ts +++ b/packages/react-components/react-utilities/src/compose/deprecated/getSlotsNext.ts @@ -13,21 +13,23 @@ import { ObjectSlotProps, Slots } from './getSlots'; * @deprecated use slot.always or slot.optional combined with assertSlots instead */ export function getSlotsNext( - state: ComponentState, + state: unknown, ): { // eslint-disable-next-line @typescript-eslint/no-deprecated slots: Slots; // eslint-disable-next-line @typescript-eslint/no-deprecated slotProps: ObjectSlotProps; } { + const typedState = state as ComponentState; // eslint-disable-next-line @typescript-eslint/no-deprecated const slots = {} as Slots; const slotProps = {} as R; - const slotNames: (keyof R)[] = Object.keys(state.components); + // eslint-disable-next-line @typescript-eslint/no-deprecated + const slotNames: (keyof R)[] = Object.keys(typedState.components); for (const slotName of slotNames) { // eslint-disable-next-line @typescript-eslint/no-deprecated - const [slot, props] = getSlotNext(state, slotName); + const [slot, props] = getSlotNext(typedState, slotName); // eslint-disable-next-line @typescript-eslint/no-deprecated slots[slotName] = slot as Slots[typeof slotName]; slotProps[slotName] = props; @@ -54,12 +56,17 @@ function getSlotNext( const { as: asProp, ...propsWithoutAs } = props as NonUndefined; const slot = ( - state.components?.[slotName] === undefined || typeof state.components[slotName] === 'string' - ? asProp || state.components?.[slotName] || 'div' - : state.components[slotName] + state.components?.[slotName] === undefined || // eslint-disable-line @typescript-eslint/no-deprecated + // eslint-disable-next-line @typescript-eslint/no-deprecated + typeof state.components[slotName] === 'string' + ? // eslint-disable-next-line @typescript-eslint/no-deprecated + asProp || state.components?.[slotName] || 'div' + : // eslint-disable-next-line @typescript-eslint/no-deprecated + state.components[slotName] ) as React.ElementType; const shouldOmitAsProp = typeof slot === 'string' && asProp; + // eslint-disable-next-line @typescript-eslint/no-deprecated const slotProps: UnknownSlotProps = shouldOmitAsProp ? propsWithoutAs : props; return [slot, slotProps as R[K]]; diff --git a/packages/react-components/react-utilities/src/compose/deprecated/resolveShorthand.ts b/packages/react-components/react-utilities/src/compose/deprecated/resolveShorthand.ts index 5ba28a48fe71c7..498f618ca739ea 100644 --- a/packages/react-components/react-utilities/src/compose/deprecated/resolveShorthand.ts +++ b/packages/react-components/react-utilities/src/compose/deprecated/resolveShorthand.ts @@ -1,5 +1,5 @@ import * as slot from '../slot'; -import type { SlotShorthandValue, UnknownSlotProps } from '../types'; +import type { UnknownSlotProps, SlotShorthandValue, WithoutSlotRenderFunction } from '../types'; /** * @deprecated - use slot.always or slot.optional combined with assertSlots instead @@ -12,11 +12,14 @@ export type ResolveShorthandOptions = R * @deprecated use slot.always or slot.optional combined with assertSlots instead */ export type ResolveShorthandFunction = { - // eslint-disable-next-line @typescript-eslint/no-deprecated -

(value: P | SlotShorthandValue | undefined, options: ResolveShorthandOptions): P; +

( + value: P | SlotShorthandValue | undefined, + // eslint-disable-next-line @typescript-eslint/no-deprecated + options: ResolveShorthandOptions, + ): WithoutSlotRenderFunction

; // eslint-disable-next-line @typescript-eslint/no-deprecated

(value: P | SlotShorthandValue | null | undefined, options?: ResolveShorthandOptions): - | P + | WithoutSlotRenderFunction

| undefined; }; @@ -27,7 +30,7 @@ export type ResolveShorthandFunction = (value, options) => @@ -37,4 +40,4 @@ export const resolveShorthand: ResolveShorthandFunction = (val // elementType as undefined is the way to identify between a slot and a resolveShorthand call // in the case elementType is undefined assertSlots will fail, ensuring it'll only work with slot method. elementType: undefined!, - }); + }) as WithoutSlotRenderFunction; diff --git a/packages/react-components/react-utilities/src/compose/index.ts b/packages/react-components/react-utilities/src/compose/index.ts index 9dd0db0086066d..a19990bfa45331 100644 --- a/packages/react-components/react-utilities/src/compose/index.ts +++ b/packages/react-components/react-utilities/src/compose/index.ts @@ -10,8 +10,6 @@ export type { ForwardRefComponent, InferredElementRefType, IsSingleton, - PropsWithoutChildren, - PropsWithoutRef, Slot, SlotClassNames, SlotComponentType, @@ -20,6 +18,7 @@ export type { SlotShorthandValue, UnknownSlotProps, } from './types'; + export { isResolvedShorthand } from './isResolvedShorthand'; export { SLOT_CLASS_NAME_PROP_SYMBOL, SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants'; export { isSlot } from './isSlot'; @@ -40,3 +39,4 @@ export { getSlotsNext } from './deprecated/getSlotsNext'; export { slot }; export type { SlotOptions } from './slot'; +export type { PropsWithoutChildren, PropsWithoutRef } from '../utils/types'; diff --git a/packages/react-components/react-utilities/src/compose/slot.ts b/packages/react-components/react-utilities/src/compose/slot.ts index f8e506ca11cc7f..1335402ae56a67 100644 --- a/packages/react-components/react-utilities/src/compose/slot.ts +++ b/packages/react-components/react-utilities/src/compose/slot.ts @@ -4,6 +4,7 @@ import type { SlotRenderFunction, SlotShorthandValue, UnknownSlotProps, + InferredElementRefType, } from './types'; import * as React from 'react'; import { SLOT_CLASS_NAME_PROP_SYMBOL, SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants'; @@ -12,7 +13,7 @@ export type SlotOptions = { elementType: | React.ComponentType | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); - defaultProps?: Partial; + defaultProps?: Partial> }>; }; /** @@ -86,7 +87,7 @@ export function resolveShorthand(value) ) { @@ -105,3 +106,7 @@ export function resolveShorthand => + typeof value === 'object' && value !== null && Symbol.iterator in value; diff --git a/packages/react-components/react-utilities/src/compose/types.ts b/packages/react-components/react-utilities/src/compose/types.ts index 277b29455a4f9d..e64c48cc8e4b59 100644 --- a/packages/react-components/react-utilities/src/compose/types.ts +++ b/packages/react-components/react-utilities/src/compose/types.ts @@ -1,11 +1,17 @@ import * as React from 'react'; import { SLOT_CLASS_NAME_PROP_SYMBOL, SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants'; -import { DistributiveOmit, ReplaceNullWithUndefined } from '../utils/types'; +import { + ComponentType, + FunctionComponent, + NamedExoticComponent, + PropsWithoutChildren, + PropsWithoutRef, + ReactNode, + RefAttributes, + ReplaceNullWithUndefined, +} from '../utils/types'; -export type SlotRenderFunction = ( - Component: React.ElementType, - props: Omit, -) => React.ReactNode; +export type SlotRenderFunction = (Component: React.ElementType, props: Omit) => ReactNode; /** * Matches any component's Slots type (such as ButtonSlots). @@ -18,7 +24,7 @@ export type SlotPropsRecord = Record | React.ReactPortal; /** * Matches any slot props type. @@ -26,8 +32,9 @@ export type SlotShorthandValue = React.ReactChild | React.ReactNode[] | React.Re * This should ONLY be used in type templates as in `extends UnknownSlotProps`; * it shouldn't be used as the type of a slot. */ -export type UnknownSlotProps = Pick, 'children' | 'className' | 'style'> & { +export type UnknownSlotProps = Pick, 'className' | 'style'> & { as?: keyof JSX.IntrinsicElements; + children?: ReactNode; }; /** @@ -38,13 +45,23 @@ type WithSlotShorthandValue = | ('children' extends keyof Props ? Extract : never); /** + * @internal * Helper type for {@link Slot}. Takes the props we want to support for a slot and adds the ability for `children` * to be a render function that takes those props. */ -type WithSlotRenderFunction = Props & { - children?: ('children' extends keyof Props ? Props['children'] : never) | SlotRenderFunction; +export type WithSlotRenderFunction = Omit & { + children?: ('children' extends keyof Props ? ReactNode : never) | SlotRenderFunction; }; +/** + * @internal + */ +export type WithoutSlotRenderFunction = Props extends unknown + ? 'children' extends keyof Props + ? Omit & { children?: Exclude } + : Props + : never; + /** * HTML element types that are not allowed to have children. * @@ -98,20 +115,23 @@ type IntrinsicElementProps = Type exte * ``` */ export type Slot< - Type extends keyof JSX.IntrinsicElements | React.ComponentType | React.VoidFunctionComponent | UnknownSlotProps, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Type extends keyof JSX.IntrinsicElements | ComponentType | UnknownSlotProps, AlternateAs extends keyof JSX.IntrinsicElements = never, > = IsSingleton> extends true ? | WithSlotShorthandValue< Type extends keyof JSX.IntrinsicElements // Intrinsic elements like `div` ? { as?: Type } & WithSlotRenderFunction> - : Type extends React.ComponentType // Component types like `typeof Button` - ? WithSlotRenderFunction + : Type extends ComponentType // Component types like `typeof Button` + ? Props extends UnknownSlotProps + ? Props + : WithSlotRenderFunction : Type // Props types like `ButtonProps` > - | { - [As in AlternateAs]: { as: As } & WithSlotRenderFunction>; - }[AlternateAs] + | (AlternateAs extends unknown + ? { as: AlternateAs } & WithSlotRenderFunction> + : never) | null : 'Error: First parameter to Slot must not be not a union of types. See documentation of Slot type.'; @@ -135,24 +155,6 @@ export type IsSingleton = { [K in T]: Exclude extends ne */ export type AsIntrinsicElement = { as?: As }; -/** - * Removes the 'ref' prop from the given Props type, leaving unions intact (such as the discriminated union created by - * IntrinsicSlotProps). This allows IntrinsicSlotProps to be used with React.forwardRef. - * - * The conditional "extends unknown" (always true) exploits a quirk in the way TypeScript handles conditional - * types, to prevent unions from being expanded. - */ -export type PropsWithoutRef

= 'ref' extends keyof P ? DistributiveOmit : P; - -/** - * Removes the 'ref' prop from the given Props type, leaving unions intact (such as the discriminated union created by - * IntrinsicSlotProps). This allows IntrinsicSlotProps to be used with React.forwardRef. - * - * The conditional "extends unknown" (always true) exploits a quirk in the way TypeScript handles conditional - * types, to prevent unions from being expanded. - */ -export type PropsWithoutChildren

= 'children' extends keyof P ? DistributiveOmit : P; - /** * Removes SlotShorthandValue and null from the slot type, extracting just the slot's Props object. */ @@ -164,30 +166,33 @@ export type ExtractSlotProps = Exclude = // Include a prop for each slot (see note below about the Omit) + // Note: the `Omit` here is a little tricky. Here's what it's doing: + // * If the Primary slot is 'root', then omit the `root` slot prop. + // * Otherwise, don't omit any props: include *both* the Primary and `root` props. + // We need both props to allow the user to specify native props for either slot because the `root` slot is + // special and always gets className and style props, per RFC https://github.com/microsoft/fluentui/pull/18983 Omit & // Include all of the props of the primary slot inline in the component's props PropsWithoutRef>; -// Note: the `Omit` above is a little tricky. Here's what it's doing: -// * If the Primary slot is 'root', then omit the `root` slot prop. -// * Otherwise, don't omit any props: include *both* the Primary and `root` props. -// We need both props to allow the user to specify native props for either slot because the `root` slot is -// special and always gets className and style props, per RFC https://github.com/microsoft/fluentui/pull/18983 - /** * Defines the State object of a component given its slots. */ export type ComponentState = { + /** + * @deprecated + * The base element type for each slot. + * This property is deprecated and will be removed in a future version. + * The slot base element type is declared through `slot.*(slotShorthand, {elementType: ElementType})` instead. + */ components: { - [Key in keyof Slots]-?: - | React.ComponentType> - | (ExtractSlotProps extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); + [Key in keyof Slots]-?: React.ElementType; }; } & { // Include a prop for each slot, with the shorthand resolved to a props object // The root slot can never be null, so also exclude null from it [Key in keyof Slots]: ReplaceNullWithUndefined< - Exclude + WithoutSlotRenderFunction> >; }; @@ -216,15 +221,10 @@ export type InferredElementRefType = ObscureEventName extends keyof Props /** * Return type for `React.forwardRef`, including inference of the proper typing for the ref. + * + * Note: {@link React.RefAttributes} is {@link https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/69756 | leaking string references} into forwardRef components, forwardRef component do not support string refs. */ -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 -// ? React.ForwardRefExoticComponent & React.RefAttributes -// : never; +export type ForwardRefComponent = NamedExoticComponent>>; /** * Helper type to correctly define the slot class names object. @@ -237,27 +237,24 @@ export type SlotClassNames = { * A definition of a slot, as a component, very similar to how a React component is declared, * but with some additional metadata that is used to determine how to render the slot. */ -export type SlotComponentType = Props & { - /** - * **NOTE**: Slot components are not callable. - */ - (props: React.PropsWithChildren<{}>): React.ReactElement | null; - /** - * @internal - */ - [SLOT_RENDER_FUNCTION_SYMBOL]?: SlotRenderFunction; - /** - * @internal - */ - [SLOT_ELEMENT_TYPE_SYMBOL]: - | React.ComponentType - | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); - /** - * @internal - * The original className prop for the slot, before being modified by the useStyles hook. - */ - [SLOT_CLASS_NAME_PROP_SYMBOL]?: string; -}; +export type SlotComponentType = WithoutSlotRenderFunction & + FunctionComponent<{ children?: React.ReactNode }> & { + /** + * @internal + */ + [SLOT_RENDER_FUNCTION_SYMBOL]?: SlotRenderFunction; + /** + * @internal + */ + [SLOT_ELEMENT_TYPE_SYMBOL]: + | ComponentType + | (Props extends AsIntrinsicElement ? As : keyof JSX.IntrinsicElements); + /** + * @internal + * The original className prop for the slot, before being modified by the useStyles hook. + */ + [SLOT_CLASS_NAME_PROP_SYMBOL]?: string; + }; /** * Data type for event handlers. It makes data a discriminated union, where each object requires `event` and `type` property. diff --git a/packages/react-components/react-utilities/src/hooks/useMergedRefs.ts b/packages/react-components/react-utilities/src/hooks/useMergedRefs.ts index f7c5e60d8757b7..91a6358d8fb678 100644 --- a/packages/react-components/react-utilities/src/hooks/useMergedRefs.ts +++ b/packages/react-components/react-utilities/src/hooks/useMergedRefs.ts @@ -6,21 +6,35 @@ import * as React from 'react'; */ export type RefObjectFunction = React.RefObject & ((value: T | null) => void); +/** @internal */ +type MutableRefObjectFunction = React.MutableRefObject & ((value: T | null) => void); + /** * React hook to merge multiple React refs (either MutableRefObjects or ref callbacks) into a single ref callback that * updates all provided refs * @param refs - Refs to collectively update with one ref value. * @returns A function with an attached "current" prop, so that it can be treated like a RefObject. */ +// LegacyRef is actually not supported, but in React v18 types this is leaking directly from forwardRef component declaration export function useMergedRefs(...refs: (React.Ref | undefined)[]): RefObjectFunction { 'use no memo'; - const mergedCallback: RefObjectFunction = React.useCallback( + const mergedCallback = React.useCallback( (value: T | null) => { // Update the "current" prop hanging on the function. - (mergedCallback as React.MutableRefObject).current = value; + mergedCallback.current = value; for (const ref of refs) { + if (typeof ref === 'string' && process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.error(/** #__DE-INDENT__ */ ` + @fluentui/react-utilities [useMergedRefs]: + This hook does not support the usage of string refs. Please use React.useRef instead. + + For more info on 'React.useRef', see https://react.dev/reference/react/useRef. + For more info on string refs, see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-string-refs. + `); + } if (typeof ref === 'function') { ref(value); } else if (ref) { @@ -31,7 +45,7 @@ export function useMergedRefs(...refs: (React.Ref | undefined)[]): RefObje }, // eslint-disable-next-line react-hooks/exhaustive-deps -- already exhaustive [...refs], - ) as RefObjectFunction; + ) as MutableRefObjectFunction; return mergedCallback; } diff --git a/packages/react-components/react-utilities/src/index.ts b/packages/react-components/react-utilities/src/index.ts index 76b147ac229fbe..4c05bdb63d0c9b 100644 --- a/packages/react-components/react-utilities/src/index.ts +++ b/packages/react-components/react-utilities/src/index.ts @@ -31,6 +31,7 @@ export type { SlotPropsRecord, SlotRenderFunction, SlotShorthandValue, + // eslint-disable-next-line @typescript-eslint/no-deprecated UnknownSlotProps, SlotComponentType, SlotOptions, @@ -73,7 +74,7 @@ export { createPriorityQueue, } from './utils/index'; -export type { DistributiveOmit, UnionToIntersection } from './utils/types'; +export type { DistributiveOmit, UnionToIntersection, RefAttributes } from './utils/types'; export type { PriorityQueue } from './utils/priorityQueue'; diff --git a/packages/react-components/react-utilities/src/trigger/applyTriggerPropsToChildren.ts b/packages/react-components/react-utilities/src/trigger/applyTriggerPropsToChildren.ts index d13a4d3b8225db..12f3b5dbaf0780 100644 --- a/packages/react-components/react-utilities/src/trigger/applyTriggerPropsToChildren.ts +++ b/packages/react-components/react-utilities/src/trigger/applyTriggerPropsToChildren.ts @@ -27,7 +27,7 @@ export function applyTriggerPropsToChildren( * a FluentTriggerComponent or React Fragment (the same element returned by {@link getTriggerChild}). */ function cloneTriggerTree( - child: React.ReactNode, + child: TriggerProps['children'], triggerProps: TriggerChildProps, ): React.ReactElement { if (!React.isValidElement(child) || child.type === React.Fragment) { diff --git a/packages/react-components/react-utilities/src/utils/types.ts b/packages/react-components/react-utilities/src/utils/types.ts index 3d0f96236b358e..5c23022f571772 100644 --- a/packages/react-components/react-utilities/src/utils/types.ts +++ b/packages/react-components/react-utilities/src/utils/types.ts @@ -1,3 +1,5 @@ +import * as React from 'react'; + /** * Helper type that works similar to Omit, * but when modifying an union type it will distribute the omission to all the union members. @@ -21,6 +23,16 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type DistributiveOmit = T extends unknown ? Omit : T; +/** + * @public + * + * Helper type that works similar to Pick, + * but when modifying an union type it will distribute the picking to all the union members. + * + * See {@link https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types} for more information + */ +export type DistributivePick = T extends unknown ? Pick : never; + /** * Converts a union type (`A | B | C`) to an intersection type (`A & B & C`) */ @@ -31,3 +43,90 @@ export type UnionToIntersection = (U extends unknown ? (x: U) => U : never) e * If type T includes `null`, remove it and add `undefined` instead. */ export type ReplaceNullWithUndefined = T extends null ? Exclude | undefined : T; + +/** + * @internal + * With react 18, our `children` type starts leaking everywhere and that causes conflicts on component declaration, specially in the `propTypes` property of + * both `ComponentClass` and `FunctionComponent`. + * + * This type substitutes `React.ComponentType` only keeping the function signature, it omits `propTypes`, `displayName` and other properties that are not + * required for the inference. + */ +export type ComponentType

= ComponentClass

| FunctionComponent

; + +/** + * @internal + * + * On types/react 18 there are two types being delivered, + * they rely on the typescript version to decide which will be consumed {@link https://github.com/DefinitelyTyped/DefinitelyTyped/blob/b59dc3ac1e2770fbd6cdbb90ba52abe04c168196/types/react/package.json#L10} + * + * If TS is higher than 5.0 then the `FunctionComponent` will be returning ReactNode (which we don't support) + * If TS is below or equal to 5.0 then the `FunctionComponent` will be returning ReactElement | null (which we support) + * + * Since it's not possible to have a single type that works for both cases + * (as ReactNode is more specific, and this will break while evaluating functions), + * we need to create our own `FunctionComponent` type + * that will work for both cases. + * + * **THIS TYPE IS INTERNAL AND SHOULD NEVER BE EXPOSED** + */ +export interface FunctionComponent

{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (props: P): any; + displayName?: string; +} + +export type FC

= FunctionComponent

; + +export interface ExoticComponent

{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (props: P): any; + $$typeof: symbol; +} + +export interface NamedExoticComponent

extends ExoticComponent

{ + displayName?: string; +} + +export interface RefAttributes extends React.Attributes { + ref?: React.Ref | undefined; +} + +/** + * @internal + * **THIS TYPE IS INTERNAL AND SHOULD NEVER BE EXPOSED** + */ +export interface ComponentClass

extends React.StaticLifecycle { + new (props: P): React.Component; +} + +/** + * @internal + * + * on types/react 18 ReactNode becomes a more strict type, which is not compatible with our current implementation. to avoid any issues we are creating our own ReactNode type which allows anything. + * + * This type should only be used for inference purposes, and should never be exposed. + * + * **THIS TYPE IS INTERNAL AND SHOULD NEVER BE EXPOSED** + * + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ReactNode = any; + +/** + * Removes the 'ref' prop from the given Props type, leaving unions intact (such as the discriminated union created by + * IntrinsicSlotProps). This allows IntrinsicSlotProps to be used with React.forwardRef. + * + * The conditional "extends unknown" (always true) exploits a quirk in the way TypeScript handles conditional + * types, to prevent unions from being expanded. + */ +export type PropsWithoutRef

= 'ref' extends keyof P ? DistributiveOmit : P; + +/** + * Removes the 'ref' prop from the given Props type, leaving unions intact (such as the discriminated union created by + * IntrinsicSlotProps). This allows IntrinsicSlotProps to be used with React.forwardRef. + * + * The conditional "extends unknown" (always true) exploits a quirk in the way TypeScript handles conditional + * types, to prevent unions from being expanded. + */ +export type PropsWithoutChildren

= 'children' extends keyof P ? DistributiveOmit : P; diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index 27fa73b27d53e8..21b8ee0acd4996 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -68,6 +68,7 @@ export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewPr return { ...virtualizerState, components: { + // eslint-disable-next-line @typescript-eslint/no-deprecated ...virtualizerState.components, container: 'div', }, diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx index 0e2de6647db6d5..891978344695c2 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx @@ -183,6 +183,7 @@ export function useVirtualizerScrollViewDynamic_unstable( return { ...virtualizerState, components: { + // eslint-disable-next-line @typescript-eslint/no-deprecated ...virtualizerState.components, container: 'div', }, From c136a109690e5ccf6f94a4685456c8f5c1cc7e39 Mon Sep 17 00:00:00 2001 From: Dmytro Kirpa Date: Thu, 17 Apr 2025 21:16:04 +0200 Subject: [PATCH 2/2] fix lint & type-check issues --- .../react-aria/library/src/button/useARIAButtonShorthand.ts | 2 +- .../stories/src/Provider/FluentProviderFrame.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-aria/library/src/button/useARIAButtonShorthand.ts b/packages/react-components/react-aria/library/src/button/useARIAButtonShorthand.ts index c0a28537c5ad00..cf903b8093961b 100644 --- a/packages/react-components/react-aria/library/src/button/useARIAButtonShorthand.ts +++ b/packages/react-components/react-aria/library/src/button/useARIAButtonShorthand.ts @@ -14,10 +14,10 @@ import type { ARIAButtonProps, ARIAButtonSlotProps, ARIAButtonType } from './typ * for multiple scenarios of shorthand properties. Ensuring 1st rule of ARIA for cases * where no attribute addition is required. */ -// eslint-disable-next-line @typescript-eslint/no-deprecated export const useARIAButtonShorthand = ((value, options) => { // eslint-disable-next-line @typescript-eslint/no-deprecated const shorthand = resolveShorthand(value, options); const shorthandARIAButton = useARIAButtonProps(shorthand?.as ?? 'button', shorthand); return shorthand && shorthandARIAButton; + // eslint-disable-next-line @typescript-eslint/no-deprecated }) as ResolveShorthandFunction; diff --git a/packages/react-components/react-provider/stories/src/Provider/FluentProviderFrame.stories.tsx b/packages/react-components/react-provider/stories/src/Provider/FluentProviderFrame.stories.tsx index adb2d131d4685a..f844c0c23ea856 100644 --- a/packages/react-components/react-provider/stories/src/Provider/FluentProviderFrame.stories.tsx +++ b/packages/react-components/react-provider/stories/src/Provider/FluentProviderFrame.stories.tsx @@ -53,7 +53,7 @@ const FrameRenderer: React.FunctionComponent = ({ children } ); }; -const Example: React.FC = props => { +const Example: React.FC> = props => { const styles = useExampleStyles(); return (