diff --git a/packages/mui-material/test/typescript/moduleAugmentation/dataAttributesOverrides.spec.tsx b/packages/mui-material/test/typescript/moduleAugmentation/dataAttributesOverrides.spec.tsx new file mode 100644 index 00000000000000..20e53ab6dd4354 --- /dev/null +++ b/packages/mui-material/test/typescript/moduleAugmentation/dataAttributesOverrides.spec.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import Backdrop from '@mui/material/Backdrop'; + +// Augment the shared `DataAttributesOverrides` interface to opt in to typed +// support for `data-testid` on every MUI slot prop. The augmentation flows +// through `SlotComponentProps` / `SlotComponentPropsWithSlotState` in +// `@mui/utils/types`, and via `SlotProps` in `@mui/material`, to every slot +// of every Material component that wires slot props through these helpers. +declare module '@mui/utils/types' { + interface DataAttributesOverrides { + 'data-testid'?: string; + } +} + +// After augmentation: `data-testid` is assignable on any Material slot prop. +; diff --git a/packages/mui-material/test/typescript/moduleAugmentation/dataAttributesOverrides.tsconfig.json b/packages/mui-material/test/typescript/moduleAugmentation/dataAttributesOverrides.tsconfig.json new file mode 100644 index 00000000000000..03626e49c20291 --- /dev/null +++ b/packages/mui-material/test/typescript/moduleAugmentation/dataAttributesOverrides.tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../../../tsconfig.json", + "files": ["dataAttributesOverrides.spec.tsx"] +} diff --git a/packages/mui-utils/src/types/DataAttributes.ts b/packages/mui-utils/src/types/DataAttributes.ts new file mode 100644 index 00000000000000..16d247f3b6b2da --- /dev/null +++ b/packages/mui-utils/src/types/DataAttributes.ts @@ -0,0 +1,47 @@ +/** + * Module-augmentable interface that lets consumers opt in to typed support for + * `data-*` (and any other) attributes on MUI slot props. Empty by default — + * by design, MUI slot prop types do not include arbitrary `data-*` keys; the + * augmentation is the single switch consumers can flip to choose their level + * of strictness. + * + * Examples: + * + * // Strongly-typed: only `data-testid` becomes assignable on slots. + * declare module '@mui/utils/types' { + * interface DataAttributesOverrides { + * 'data-testid'?: string; + * } + * } + * + * // Loose: accept any `data-*` key on slots. + * declare module '@mui/utils/types' { + * interface DataAttributesOverrides { + * [k: `data-${string}`]: string | number | boolean | undefined; + * } + * } + */ +export interface DataAttributesOverrides {} + +/** + * Surface contributed to slot prop types by the `DataAttributesOverrides` + * augmentation. Empty by default; populated only when a consumer declares + * `data-*` keys via module augmentation. This is what `WithDataAttributes` + * intersects into the widened branch of every slot prop union exposed by + * `@mui/utils/types`. + */ +export type DataAttributes = DataAttributesOverrides; + +/** + * Widens a slot-props type so that, when a consumer augments + * `DataAttributesOverrides`, the augmented keys become assignable to the + * widened branch. The default `DataAttributes` is empty, so this widening is + * a no-op until a consumer opts in. + * + * Implemented as a union between the original type and the intersected widened + * form — `T | (T & DataAttributes)` — so that pre-typed values remain + * assignable to the original branch without having to declare a `data-*` + * index signature themselves, while object literals can pick up the widened + * branch and include the augmented keys. + */ +export type WithDataAttributes = T | (T & DataAttributes); diff --git a/packages/mui-utils/src/types/index.ts b/packages/mui-utils/src/types/index.ts index 4d2a96ce8f99e1..d5c9327b1e80ab 100644 --- a/packages/mui-utils/src/types/index.ts +++ b/packages/mui-utils/src/types/index.ts @@ -1,4 +1,7 @@ import * as React from 'react'; +import { WithDataAttributes } from './DataAttributes'; + +export * from './DataAttributes'; export type EventHandlers = Record>; @@ -9,10 +12,10 @@ export type WithOptionalOwnerState = Omit Partial>; export type SlotComponentProps = - | (Partial> & TOverrides) + | WithDataAttributes> & TOverrides> | (( ownerState: TOwnerState, - ) => Partial> & TOverrides); + ) => WithDataAttributes> & TOverrides>); export type SlotComponentPropsWithSlotState< TSlotComponent extends React.ElementType, @@ -20,8 +23,8 @@ export type SlotComponentPropsWithSlotState< TOwnerState, TSlotState, > = - | (Partial> & TOverrides) + | WithDataAttributes> & TOverrides> | (( ownerState: TOwnerState, slotState: TSlotState, - ) => Partial> & TOverrides); + ) => WithDataAttributes> & TOverrides>);