diff --git a/src/components/stepper/step.ts b/src/components/stepper/step.ts index 67811ddab..113c14b95 100644 --- a/src/components/stepper/step.ts +++ b/src/components/stepper/step.ts @@ -1,4 +1,3 @@ -import { consume } from '@lit/context'; import { html, LitElement, nothing, type PropertyValues } from 'lit'; import { property } from 'lit/decorators.js'; import { cache } from 'lit/directives/cache.js'; @@ -6,6 +5,7 @@ import { createRef, ref } from 'lit/directives/ref.js'; import { EaseInOut } from '../../animations/easings.js'; import { addAnimationController } from '../../animations/player.js'; import { addThemingController } from '../../theming/theming-controller.js'; +import { createAsyncContext } from '../common/controllers/async-consumer.js'; import { addSlotController, setSlots } from '../common/controllers/slot.js'; import { registerComponent } from '../common/definitions/register.js'; import { partMap } from '../common/part-map.js'; @@ -113,8 +113,7 @@ export default class IgcStepComponent extends LitElement { slots: setSlots('indicator', 'title', 'subtitle'), }); - @consume({ context: STEPPER_CONTEXT, subscribe: true }) - private readonly _stepperContext?: StepperContext; + private _stepperContext?: StepperContext; private get _stepper(): IgcStepperComponent | undefined { return this._stepperContext?.stepper; @@ -245,7 +244,11 @@ export default class IgcStepComponent extends LitElement { constructor() { super(); + addThemingController(this, all); + createAsyncContext(this, STEPPER_CONTEXT, (context) => { + this._stepperContext = context; + }); } protected override willUpdate(changed: PropertyValues): void { diff --git a/src/components/stepper/stepper.spec.ts b/src/components/stepper/stepper.spec.ts index 8316af6b2..6cbfd7483 100644 --- a/src/components/stepper/stepper.spec.ts +++ b/src/components/stepper/stepper.spec.ts @@ -958,6 +958,50 @@ describe('Stepper', () => { expect(step1Header).to.equal(stepper.steps[1].shadowRoot!.activeElement); }); }); + + describe('Context binding', () => { + it('should correctly bind context when a step is connected before its stepper parent', async () => { + // Connect the step in isolation — the AsyncContextConsumer defers ContextConsumer + // creation until after updateComplete, but no provider exists at that point so + // the context remains unresolved. + const step = document.createElement( + IgcStepComponent.tagName + ) as IgcStepComponent; + + document.body.appendChild(step); + await elementUpdated(step); + + // No stepper context yet — step is not active and not part of any stepper. + expect(step.active).to.be.false; + + // Create the stepper, adopt the step, and connect it to the document. + // The step first disconnects from body, then reconnects as a child of the + // stepper. On reconnect, the ContextConsumer (subscribe: true) re-dispatches + // a context-request event that the stepper's ContextProvider answers, + // completing the binding via createAsyncContext. + const stepperEl = document.createElement( + IgcStepperComponent.tagName + ) as IgcStepperComponent; + + stepperEl.appendChild(step); + document.body.appendChild(stepperEl); + await elementUpdated(stepperEl); + + // Context is now bound — the stepper recognizes the step. + expect(stepperEl.steps).to.include(step); + expect(stepperEl.steps).to.have.lengthOf(1); + + // The sole step is activated by default. + expect(stepperEl.steps[0].active).to.be.true; + + // Indicator reflects the correct step index (1-based). + expect( + getStepDOM(step).parts.indicator.querySelector('span')!.textContent + ).to.equal('1'); + + stepperEl.remove(); + }); + }); }); function isStepAccessible(step: IgcStepComponent): boolean {