Skip to content

Commit 5f0f2dc

Browse files
crisbetommalerba
authored andcommitted
fix(stepper): not picking up indirect descendant elements (#17529)
Fixes the stepper not picking up indirect descendant steps, icon overrides and step headers.
1 parent ee863b2 commit 5f0f2dc

File tree

3 files changed

+112
-4
lines changed

3 files changed

+112
-4
lines changed

src/cdk/stepper/stepper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
255255
* @deprecated use `steps` instead
256256
* @breaking-change 9.0.0 remove this property
257257
*/
258-
@ContentChildren(CdkStep) _steps: QueryList<CdkStep>;
258+
@ContentChildren(CdkStep, {descendants: true}) _steps: QueryList<CdkStep>;
259259

260260
/**
261261
* We need to store the steps in an Iterable due to strict template type checking with *ngFor and
@@ -273,7 +273,7 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
273273
* @deprecated Type to be changed to `QueryList<CdkStepHeader>`.
274274
* @breaking-change 8.0.0
275275
*/
276-
@ContentChildren(CdkStepHeader) _stepHeader: QueryList<FocusableOption>;
276+
@ContentChildren(CdkStepHeader, {descendants: true}) _stepHeader: QueryList<FocusableOption>;
277277

278278
/** Whether the validity of previous steps should be checked or not. */
279279
@Input()

src/material/stepper/stepper.spec.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,79 @@ describe('MatStepper', () => {
11021102
expect(stepper._getIndicatorType(1)).toBe(STEP_STATE.EDIT);
11031103
});
11041104
});
1105+
1106+
describe('indirect descendants', () => {
1107+
it('should be able to change steps when steps are indirect descendants', () => {
1108+
const fixture = createComponent(StepperWithIndirectDescendantSteps);
1109+
fixture.detectChanges();
1110+
1111+
const stepHeaders = fixture.debugElement.queryAll(By.css('.mat-vertical-stepper-header'));
1112+
const stepperComponent =
1113+
fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
1114+
1115+
expect(stepperComponent.selectedIndex).toBe(0);
1116+
expect(stepperComponent.selected instanceof MatStep).toBe(true);
1117+
1118+
// select the second step
1119+
let stepHeaderEl = stepHeaders[1].nativeElement;
1120+
stepHeaderEl.click();
1121+
fixture.detectChanges();
1122+
1123+
expect(stepperComponent.selectedIndex).toBe(1);
1124+
expect(stepperComponent.selected instanceof MatStep).toBe(true);
1125+
1126+
// select the third step
1127+
stepHeaderEl = stepHeaders[2].nativeElement;
1128+
stepHeaderEl.click();
1129+
fixture.detectChanges();
1130+
1131+
expect(stepperComponent.selectedIndex).toBe(2);
1132+
expect(stepperComponent.selected instanceof MatStep).toBe(true);
1133+
});
1134+
1135+
it('should allow for the `edit` icon to be overridden', () => {
1136+
const fixture = createComponent(IndirectDescendantIconOverridesStepper);
1137+
fixture.detectChanges();
1138+
1139+
const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
1140+
const stepperComponent: MatStepper = stepperDebugElement.componentInstance;
1141+
1142+
stepperComponent.steps.toArray()[0].editable = true;
1143+
stepperComponent.next();
1144+
fixture.detectChanges();
1145+
1146+
const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');
1147+
1148+
expect(header.textContent).toContain('Custom edit');
1149+
});
1150+
1151+
it('should allow for the `done` icon to be overridden', () => {
1152+
const fixture = createComponent(IndirectDescendantIconOverridesStepper);
1153+
fixture.detectChanges();
1154+
1155+
const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
1156+
const stepperComponent: MatStepper = stepperDebugElement.componentInstance;
1157+
1158+
stepperComponent.steps.toArray()[0].editable = false;
1159+
stepperComponent.next();
1160+
fixture.detectChanges();
1161+
1162+
const header = stepperDebugElement.nativeElement.querySelector('mat-step-header');
1163+
1164+
expect(header.textContent).toContain('Custom done');
1165+
});
1166+
1167+
it('should allow for the `number` icon to be overridden with context', () => {
1168+
const fixture = createComponent(IndirectDescendantIconOverridesStepper);
1169+
fixture.detectChanges();
1170+
1171+
const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper))!;
1172+
const headers = stepperDebugElement.nativeElement.querySelectorAll('mat-step-header');
1173+
1174+
expect(headers[2].textContent).toContain('III');
1175+
});
1176+
1177+
});
11051178
});
11061179

11071180
/** Asserts that keyboard interaction works correctly. */
@@ -1510,6 +1583,26 @@ class IconOverridesStepper {
15101583
}
15111584
}
15121585

1586+
@Component({
1587+
template: `
1588+
<mat-horizontal-stepper>
1589+
<ng-container [ngSwitch]="true">
1590+
<ng-template matStepperIcon="edit">Custom edit</ng-template>
1591+
<ng-template matStepperIcon="done">Custom done</ng-template>
1592+
<ng-template matStepperIcon="number" let-index="index">
1593+
{{getRomanNumeral(index + 1)}}
1594+
</ng-template>
1595+
</ng-container>
1596+
1597+
<mat-step>Content 1</mat-step>
1598+
<mat-step>Content 2</mat-step>
1599+
<mat-step>Content 3</mat-step>
1600+
</mat-horizontal-stepper>
1601+
`
1602+
})
1603+
class IndirectDescendantIconOverridesStepper extends IconOverridesStepper {
1604+
}
1605+
15131606
@Component({
15141607
template: `
15151608
<mat-horizontal-stepper linear>
@@ -1536,3 +1629,18 @@ class StepperWithAriaInputs {
15361629
ariaLabel: string;
15371630
ariaLabelledby: string;
15381631
}
1632+
1633+
1634+
@Component({
1635+
template: `
1636+
<mat-vertical-stepper>
1637+
<ng-container [ngSwitch]="true">
1638+
<mat-step label="Step 1">Content 1</mat-step>
1639+
<mat-step label="Step 2">Content 2</mat-step>
1640+
<mat-step label="Step 3">Content 3</mat-step>
1641+
</ng-container>
1642+
</mat-vertical-stepper>
1643+
`
1644+
})
1645+
class StepperWithIndirectDescendantSteps {
1646+
}

src/material/stepper/stepper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
8787
@ViewChildren(MatStepHeader) _stepHeader: QueryList<MatStepHeader>;
8888

8989
/** Steps that the stepper holds. */
90-
@ContentChildren(MatStep) _steps: QueryList<MatStep>;
90+
@ContentChildren(MatStep, {descendants: true}) _steps: QueryList<MatStep>;
9191

9292
/** Custom icon overrides passed in by the consumer. */
93-
@ContentChildren(MatStepperIcon) _icons: QueryList<MatStepperIcon>;
93+
@ContentChildren(MatStepperIcon, {descendants: true}) _icons: QueryList<MatStepperIcon>;
9494

9595
/** Event emitted when the current step is done transitioning in. */
9696
@Output() readonly animationDone: EventEmitter<void> = new EventEmitter<void>();

0 commit comments

Comments
 (0)