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,35 @@
import * as React from 'react';
import Backdrop from '@mui/material/Backdrop';

// Loose opt-in: accept any `data-*` key on every MUI slot prop, mirroring the
// primitive DOM elements. A single augmentation of the shared
// `DataAttributesOverrides` interface flows through `SlotComponentProps` /
// `SlotComponentPropsWithSlotState` in `@mui/utils/types` (and `SlotProps` in
// `@mui/material`) to every slot of every component wired through these helpers.
declare module '@mui/utils/types' {
interface DataAttributesOverrides {
[k: `data-${string}`]: string | number | boolean | undefined;
}
}

// Object form: arbitrary `data-*` keys are assignable.
<Backdrop
open
slotProps={{
root: {
'data-testid': 'backdrop-root',
'data-custom': 'forwarded',
},
}}
/>;

// Callback form: the same widening applies to the function branch.
<Backdrop
open
slotProps={{
root: () => ({
'data-testid': 'backdrop-root',
'data-custom': 'forwarded',
}),
}}
/>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../../../../tsconfig.json",
"files": ["dataAttributesOverrides.spec.tsx"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react';
import Backdrop from '@mui/material/Backdrop';

// Strict opt-in: only the explicitly declared keys become assignable, giving
// teams a closed allow-list. An undeclared `data-*` key still errors.
declare module '@mui/utils/types' {
interface DataAttributesOverrides {
'data-testid'?: string;
}
}

<Backdrop
open
slotProps={{
root: {
'data-testid': 'backdrop-root',
// @ts-expect-error -- only the declared keys are allowed in the strict form
'data-custom': 'forwarded',
},
}}
/>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../../../../tsconfig.json",
"files": ["dataAttributesOverridesAllowList.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.
*
* @example
* // Loose: accept any `data-*` key on slots (mirrors the primitive elements).
* declare module '@mui/utils/types' {
* interface DataAttributesOverrides {
* [k: `data-${string}`]: string | number | boolean | undefined;
* }
* }
*
* @example
* // Strongly-typed: only the declared keys become assignable on slots.
* declare module '@mui/utils/types' {
* interface DataAttributesOverrides {
* 'data-testid'?: string;
* }
* }
*/
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