Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
<Backdrop
open
slotProps={{
root: {
'data-testid': 'backdrop-root',
},
}}
/>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../../../../tsconfig.json",
"files": ["dataAttributesOverrides.spec.tsx"]
}
47 changes: 47 additions & 0 deletions packages/mui-utils/src/types/DataAttributes.ts
Original file line number Diff line number Diff line change
@@ -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:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could use JSdoc @example

*
* // 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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the spec cover this as well?

* 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 | (T & DataAttributes);
11 changes: 7 additions & 4 deletions packages/mui-utils/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as React from 'react';
import { WithDataAttributes } from './DataAttributes';

export * from './DataAttributes';

export type EventHandlers = Record<string, React.EventHandler<any>>;

Expand All @@ -9,19 +12,19 @@ export type WithOptionalOwnerState<Props extends { ownerState: unknown }> = Omit
Partial<Pick<Props, 'ownerState'>>;

export type SlotComponentProps<TSlotComponent extends React.ElementType, TOverrides, TOwnerState> =
| (Partial<React.ComponentPropsWithRef<TSlotComponent>> & TOverrides)
| WithDataAttributes<Partial<React.ComponentPropsWithRef<TSlotComponent>> & TOverrides>
| ((
ownerState: TOwnerState,
) => Partial<React.ComponentPropsWithRef<TSlotComponent>> & TOverrides);
) => WithDataAttributes<Partial<React.ComponentPropsWithRef<TSlotComponent>> & TOverrides>);

export type SlotComponentPropsWithSlotState<
TSlotComponent extends React.ElementType,
TOverrides,
TOwnerState,
TSlotState,
> =
| (Partial<React.ComponentPropsWithRef<TSlotComponent>> & TOverrides)
| WithDataAttributes<Partial<React.ComponentPropsWithRef<TSlotComponent>> & TOverrides>
| ((
ownerState: TOwnerState,
slotState: TSlotState,
) => Partial<React.ComponentPropsWithRef<TSlotComponent>> & TOverrides);
) => WithDataAttributes<Partial<React.ComponentPropsWithRef<TSlotComponent>> & TOverrides>);
Loading