Skip to content

Commit 6c399e5

Browse files
committed
Fixes
1 parent 2cd14bd commit 6c399e5

15 files changed

Lines changed: 273 additions & 167 deletions

File tree

docs/pages/material-ui/api/chip.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"avatar": {
55
"type": { "name": "element" },
66
"deprecated": true,
7-
"deprecationInfo": "Use <code>startAdornment</code> instead. Ignored when <code>startAdornment</code> is present."
7+
"deprecationInfo": "Use <code>startAdornment</code> instead. Ignored when <code>startAdornment</code> or <code>action</code> are used."
88
},
99
"children": { "type": { "name": "custom", "description": "unsupportedProp" } },
1010
"classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } },
@@ -24,21 +24,21 @@
2424
"deleteIcon": {
2525
"type": { "name": "element" },
2626
"deprecated": true,
27-
"deprecationInfo": "Use <code>endAdornment={&lt;ChipDelete /&gt;}</code> instead. Ignored when <code>endAdornment</code> is present."
27+
"deprecationInfo": "Use <code>endAdornment={&lt;ChipDelete /&gt;}</code> instead. Ignored when <code>endAdornment</code> or <code>action</code> are used."
2828
},
2929
"disabled": { "type": { "name": "bool" }, "default": "false" },
3030
"endAdornment": { "type": { "name": "node" } },
3131
"icon": {
3232
"type": { "name": "element" },
3333
"deprecated": true,
34-
"deprecationInfo": "Use <code>startAdornment</code> instead. Ignored when <code>startAdornment</code> is present."
34+
"deprecationInfo": "Use <code>startAdornment</code> instead. Ignored when <code>startAdornment</code> or <code>action</code> are used."
3535
},
3636
"label": { "type": { "name": "node" } },
3737
"nativeButton": { "type": { "name": "bool" } },
3838
"onDelete": {
3939
"type": { "name": "func" },
4040
"deprecated": true,
41-
"deprecationInfo": "Use <code>endAdornment={&lt;ChipDelete onClick={...} /&gt;}</code> instead. Ignored when <code>endAdornment</code> is present."
41+
"deprecationInfo": "Use <code>endAdornment={&lt;ChipDelete onClick={...} /&gt;}</code> instead. Ignored when <code>endAdornment</code> or <code>action</code> are used."
4242
},
4343
"size": {
4444
"type": {

docs/translations/api-docs/chip-button/chip-button.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"description": "If <code>true</code>, the component is disabled. When nested inside a <code>Chip</code>, inherits from the parent&#39;s <code>disabled</code> prop."
1111
},
1212
"focusableWhenDisabled": {
13-
"description": "If <code>true</code>, the disabled chip can receive focus."
13+
"description": "If <code>true</code>, the disabled button can receive focus."
1414
},
1515
"nativeButton": {
1616
"description": "If <code>true</code>, the component is expected to resolve to a native <code>&lt;button&gt;</code> element. When omitted, native button semantics are inferred when <code>component</code> is <code>&#39;button&#39;</code> or absent. Set explicitly when using a custom <code>component</code> that resolves to a native <code>&lt;button&gt;</code>."

docs/translations/api-docs/chip-delete/chip-delete.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"componentDescription": "A chip-aware delete button, designed to be used as an adornment of `Chip`.\n\nWhen rendered inside a `Chip`, it inherits `disabled`, `color`, `size`, and `variant`\nfrom the chip context. Local props override context values.",
2+
"componentDescription": "A chip-aware delete button, designed to be used as an adornment of `Chip`.\n\nWhen rendered inside a `Chip`, it inherits `disabled`, `color`, `size`, and `variant`\nfrom the chip context.",
33
"propDescriptions": {
44
"classes": { "description": "Override or extend the styles applied to the component." },
55
"component": {

packages/mui-material/src/Chip/Chip.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export interface ChipOwnProps {
7373
action?: React.ReactElement<unknown> | undefined;
7474
/**
7575
* The Avatar element to display.
76-
* @deprecated Use `startAdornment` instead. Ignored when `startAdornment` is present.
76+
* @deprecated Use `startAdornment` instead. Ignored when `startAdornment` or `action` are used.
7777
*/
7878
avatar?: React.ReactElement<unknown> | undefined;
7979
/**
@@ -109,7 +109,7 @@ export interface ChipOwnProps {
109109
| undefined;
110110
/**
111111
* Override the default delete icon element. Shown only if `onDelete` is set.
112-
* @deprecated Use `endAdornment={<ChipDelete />}` instead. Ignored when `endAdornment` is present.
112+
* @deprecated Use `endAdornment={<ChipDelete />}` instead. Ignored when `endAdornment` or `action` are used.
113113
*/
114114
deleteIcon?: React.ReactElement<unknown> | undefined;
115115
/**
@@ -125,7 +125,7 @@ export interface ChipOwnProps {
125125
endAdornment?: React.ReactNode | undefined;
126126
/**
127127
* Icon element.
128-
* @deprecated Use `startAdornment` instead. Ignored when `startAdornment` is present.
128+
* @deprecated Use `startAdornment` instead. Ignored when `startAdornment` or `action` are used.
129129
*/
130130
icon?: React.ReactElement<unknown> | undefined;
131131
/**
@@ -135,7 +135,7 @@ export interface ChipOwnProps {
135135
/**
136136
* Callback fired when the delete icon is clicked.
137137
* If set, the delete icon will be shown.
138-
* @deprecated Use `endAdornment={<ChipDelete onClick={...} />}` instead. Ignored when `endAdornment` is present.
138+
* @deprecated Use `endAdornment={<ChipDelete onClick={...} />}` instead. Ignored when `endAdornment` or `action` are used.
139139
*/
140140
onDelete?: React.EventHandler<any> | undefined;
141141
/**

packages/mui-material/src/Chip/Chip.js

Lines changed: 85 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,6 @@ const ChipNewApiRoot = styled('div', {
386386
});
387387
return {
388388
...baseStyles,
389-
[`&.${chipClasses.disabled}`]: {
390-
opacity: (theme.vars || theme).palette.action.disabledOpacity,
391-
pointerEvents: 'none',
392-
},
393389
variants: [
394390
...baseStyles.variants,
395391
{
@@ -485,46 +481,67 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
485481
} = props;
486482
const { nativeButton, ...buttonBaseProps } = other;
487483

484+
const hasAction = action != null;
485+
const hasAdornment = startAdornment != null || endAdornment != null;
486+
const hasLegacyInteraction = onClick || clickableProp !== undefined;
487+
const hasLegacyVisual = avatarProp || iconProp;
488+
const hasLegacyDelete = deleteIconProp || onDelete;
489+
const actionMuiName = action?.type?.muiName;
490+
const isChipLinkAction = actionMuiName === 'ChipLink';
491+
const isValidChipAction = actionMuiName === 'ChipButton' || isChipLinkAction;
492+
488493
// Detect new slot-based API (loose null check so `null` from ternaries is treated as absent)
489-
const isNewApi = action != null || startAdornment != null || endAdornment != null;
494+
const usesNewApiProps = hasAction || hasAdornment;
490495

491496
// Dev warnings for new API
492497
if (process.env.NODE_ENV !== 'production') {
493-
if (isNewApi) {
494-
if (
495-
action &&
496-
action.type &&
497-
action.type.muiName !== 'ChipButton' &&
498-
action.type.muiName !== 'ChipLink'
499-
) {
498+
if (usesNewApiProps) {
499+
if (hasAction && !isValidChipAction) {
500+
console.error('MUI: The `action` prop expects a `<ChipButton>` or `<ChipLink>` component.');
501+
}
502+
if (hasAction && hasLegacyInteraction) {
500503
console.error(
501-
'MUI: The Chip `action` prop expects a `<ChipButton>` or `<ChipLink>` element.',
504+
'MUI: The `onClick` and `clickable` props are incompatible with the `action` prop. ' +
505+
'Pass event handlers directly to the `action` component instead.',
502506
);
503507
}
504-
if (action && (onClick || clickableProp !== undefined)) {
508+
if (hasAdornment && onDelete) {
505509
console.error(
506-
'MUI: When `action` is provided, `onClick` and `clickable` are ignored. ' +
507-
'Pass event handlers directly to the action element.',
510+
'MUI: The `onDelete` prop is incompatible with the `startAdornment` and `endAdornment` props. ' +
511+
'Use `<ChipDelete>` as an adornment instead.',
508512
);
509513
}
510-
if ((startAdornment || endAdornment) && onDelete) {
514+
if (startAdornment && hasLegacyVisual) {
511515
console.error(
512-
'MUI: When `startAdornment` or `endAdornment` is provided, `onDelete` is ignored. ' +
513-
'Use `<ChipDelete>` as an adornment instead.',
516+
'MUI: The `avatar` and `icon` props are incompatible with the `startAdornment` prop. ' +
517+
'Pass avatars or icons to `startAdornment` instead.',
514518
);
515519
}
516-
if (startAdornment && (avatarProp || iconProp)) {
520+
if (hasAction && hasLegacyVisual) {
517521
console.error(
518-
'MUI: When `startAdornment` is provided, `avatar` and `icon` are ignored. ' +
519-
'Place content directly inside `startAdornment`.',
522+
'MUI: The `avatar` and `icon` props are incompatible with the `action` prop. ' +
523+
'Use `startAdornment` and `endAdornment` instead.',
524+
);
525+
}
526+
if (hasAction && hasLegacyDelete) {
527+
console.error(
528+
'MUI: The `deleteIcon` and `onDelete` props are incompatible with the `action` prop. ' +
529+
'Use the `<ChipDelete>` component instead.',
530+
);
531+
}
532+
if (!hasAction && hasAdornment && hasLegacyInteraction) {
533+
console.error(
534+
'MUI: The `onClick` and `clickable` props have no effect when `startAdornment` or ' +
535+
'`endAdornment` is provided without `action`. ' +
536+
'Use `action={<ChipButton onClick={...} />}` to make the chip interactive.',
520537
);
521538
}
522539
}
523540
}
524541

525542
const handleKeyDown = (event) => {
526543
// Legacy-only handler: new API delegates keyboard handling to the action element.
527-
if (isNewApi) {
544+
if (usesNewApiProps) {
528545
return;
529546
}
530547

@@ -542,7 +559,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
542559

543560
const handleKeyUp = (event) => {
544561
// Legacy-only handler: new API delegates keyboard handling to the action element.
545-
if (isNewApi) {
562+
if (usesNewApiProps) {
546563
return;
547564
}
548565

@@ -562,11 +579,10 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
562579
const component = clickable || onDelete ? ButtonBase : ComponentProp || 'div';
563580

564581
// ChipLink ignores disabled — suppress disabled styles on the root when action is ChipLink
565-
const isChipLink = action && action.type && action.type.muiName === 'ChipLink';
566-
const rootDisabled = isChipLink ? false : disabled;
582+
const rootDisabled = isChipLinkAction ? false : disabled;
567583

568584
// ownerState differs by API path
569-
const ownerState = isNewApi
585+
const ownerState = usesNewApiProps
570586
? {
571587
...props,
572588
color,
@@ -588,7 +604,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
588604
variant,
589605
};
590606

591-
const classes = useUtilityClasses(ownerState, isNewApi);
607+
const classes = useUtilityClasses(ownerState, usesNewApiProps);
592608

593609
const moreProps =
594610
component === ButtonBase
@@ -608,16 +624,16 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
608624

609625
// useSlot calls with conditional params (always called, per rules of hooks)
610626
const [RootSlot, rootProps] = useSlot('root', {
611-
elementType: isNewApi ? ChipNewApiRoot : ChipRoot,
627+
elementType: usesNewApiProps ? ChipNewApiRoot : ChipRoot,
612628
externalForwardedProps: {
613629
...externalForwardedProps,
614630
...buttonBaseProps,
615631
},
616632
ownerState,
617-
shouldForwardComponentProp: !isNewApi,
633+
shouldForwardComponentProp: !usesNewApiProps,
618634
ref,
619635
className: clsx(classes.root, className),
620-
...(isNewApi
636+
...(usesNewApiProps
621637
? {}
622638
: {
623639
additionalProps: {
@@ -649,10 +665,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
649665
ownerState,
650666
className: classes.label,
651667
});
652-
const labelElement = React.useMemo(
653-
() => <LabelSlot {...labelProps}>{label}</LabelSlot>,
654-
[LabelSlot, labelProps, label],
655-
);
668+
const labelElement = <LabelSlot {...labelProps}>{label}</LabelSlot>;
656669

657670
const [StartAdornmentSlot, startAdornmentSlotProps] = useSlot('startAdornment', {
658671
elementType: ChipStartAdornment,
@@ -672,28 +685,45 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
672685
() => ({
673686
color,
674687
disabled,
675-
labelElement,
676688
size,
677689
variant,
678690
}),
679-
[color, disabled, labelElement, size, variant],
691+
[color, disabled, size, variant],
680692
);
681693

682694
// ---- New API render path ----
683-
if (isNewApi) {
684-
return (
685-
<ChipContext.Provider value={chipContextValue}>
686-
<RootSlot as={ComponentProp} {...rootProps}>
687-
{startAdornment ? (
688-
<StartAdornmentSlot {...startAdornmentSlotProps}>{startAdornment}</StartAdornmentSlot>
689-
) : null}
690-
{action || labelElement}
691-
{endAdornment ? (
692-
<EndAdornmentSlot {...endAdornmentSlotProps}>{endAdornment}</EndAdornmentSlot>
693-
) : null}
694-
</RootSlot>
695-
</ChipContext.Provider>
695+
if (usesNewApiProps) {
696+
const actionElement =
697+
hasAction && React.isValidElement(action) && isValidChipAction
698+
? React.cloneElement(
699+
action,
700+
{
701+
insideChip: true,
702+
...(actionMuiName === 'ChipButton' && {
703+
disabled: action.props.disabled ?? disabled,
704+
}),
705+
},
706+
labelElement,
707+
)
708+
: action;
709+
710+
const content = (
711+
<RootSlot as={ComponentProp} {...rootProps}>
712+
{startAdornment ? (
713+
<StartAdornmentSlot {...startAdornmentSlotProps}>{startAdornment}</StartAdornmentSlot>
714+
) : null}
715+
{actionElement || labelElement}
716+
{endAdornment ? (
717+
<EndAdornmentSlot {...endAdornmentSlotProps}>{endAdornment}</EndAdornmentSlot>
718+
) : null}
719+
</RootSlot>
696720
);
721+
722+
if (!hasAdornment) {
723+
return content;
724+
}
725+
726+
return <ChipContext.Provider value={chipContextValue}>{content}</ChipContext.Provider>;
697727
}
698728

699729
// ---- Legacy render path ----
@@ -742,7 +772,7 @@ const Chip = React.forwardRef(function Chip(inProps, ref) {
742772
return (
743773
<RootSlot as={component} {...rootProps}>
744774
{avatar || icon}
745-
<LabelSlot {...labelProps}>{label}</LabelSlot>
775+
{labelElement}
746776
{deleteIcon}
747777
</RootSlot>
748778
);
@@ -762,7 +792,7 @@ Chip.propTypes /* remove-proptypes */ = {
762792
action: PropTypes.element,
763793
/**
764794
* The Avatar element to display.
765-
* @deprecated Use `startAdornment` instead. Ignored when `startAdornment` is present.
795+
* @deprecated Use `startAdornment` instead. Ignored when `startAdornment` or `action` are used.
766796
*/
767797
avatar: PropTypes.element,
768798
/**
@@ -805,7 +835,7 @@ Chip.propTypes /* remove-proptypes */ = {
805835
component: PropTypes.elementType,
806836
/**
807837
* Override the default delete icon element. Shown only if `onDelete` is set.
808-
* @deprecated Use `endAdornment={<ChipDelete />}` instead. Ignored when `endAdornment` is present.
838+
* @deprecated Use `endAdornment={<ChipDelete />}` instead. Ignored when `endAdornment` or `action` are used.
809839
*/
810840
deleteIcon: PropTypes.element,
811841
/**
@@ -821,7 +851,7 @@ Chip.propTypes /* remove-proptypes */ = {
821851
endAdornment: PropTypes.node,
822852
/**
823853
* Icon element.
824-
* @deprecated Use `startAdornment` instead. Ignored when `startAdornment` is present.
854+
* @deprecated Use `startAdornment` instead. Ignored when `startAdornment` or `action` are used.
825855
*/
826856
icon: PropTypes.element,
827857
/**
@@ -842,7 +872,7 @@ Chip.propTypes /* remove-proptypes */ = {
842872
/**
843873
* Callback fired when the delete icon is clicked.
844874
* If set, the delete icon will be shown.
845-
* @deprecated Use `endAdornment={<ChipDelete onClick={...} />}` instead. Ignored when `endAdornment` is present.
875+
* @deprecated Use `endAdornment={<ChipDelete onClick={...} />}` instead. Ignored when `endAdornment` or `action` are used.
846876
*/
847877
onDelete: PropTypes.func,
848878
/**

0 commit comments

Comments
 (0)