import { LitElement, html, css } from '@lion/core'; /** * `LionSteps` is a controller for a multi step system. * * @customElement lion-steps * @extends {LitElement} */ export class LionSteps extends LitElement { static get properties() { /** * Fired when a transition between steps happens. * * @event transition */ return { /** * Storage for data gathered across different steps. * Data is passed into each step condition function as a first argument. */ data: { type: Object, }, /** * Number of the current entered step. */ current: { type: Number, }, }; } updated(changedProps) { super.updated(changedProps); if (changedProps.has('current')) { this._onCurrentChanged({ current: this.current }, { current: changedProps.get('current') }); } } constructor() { super(); this.data = {}; this._internalCurrentSync = true; // necessary for preventing side effects on initialization this.current = 0; } static get styles() { return [ css` :host { display: block; } `, ]; } render() { return html` `; } firstUpdated() { super.firstUpdated(); this._max = this.steps.length - 1; let hasInitial = false; this.steps.forEach((step, i) => { if (step.initialStep && i !== 0) { this.current = i; hasInitial = true; } }); if (!hasInitial && this.steps[0]) { this.steps[0].enter(); } } next() { this._goTo(this.current + 1, this.current); } previous() { this._goTo(this.current - 1, this.current); } get steps() { const defaultSlot = this.shadowRoot.querySelector('slot:not([name])'); return defaultSlot.assignedNodes().filter(node => node.nodeType === Node.ELEMENT_NODE); } _goTo(newCurrent, oldCurrent) { if (newCurrent < 0 || newCurrent > this._max) { throw new Error(`There is no step at index ${newCurrent}.`); } const nextStep = this.steps[newCurrent]; const back = newCurrent < oldCurrent; if (nextStep.passesCondition(this.data)) { if (back && nextStep.forwardOnly) { this._goTo(newCurrent - 1, oldCurrent); } else { this._changeStep(newCurrent, oldCurrent); } } else { nextStep.skip(); if (back) { this._goTo(newCurrent - 1, oldCurrent); } else { this._goTo(newCurrent + 1, oldCurrent); } } } _changeStep(newCurrent, oldCurrent) { const oldStepElement = this.steps[oldCurrent]; const newStepElement = this.steps[newCurrent]; const fromStep = { number: oldCurrent, element: oldStepElement }; const toStep = { number: newCurrent, element: newStepElement }; oldStepElement.leave(); if (this.current !== newCurrent) { this._internalCurrentSync = true; this.current = newCurrent; } newStepElement.enter(); this._dispatchTransitionEvent(fromStep, toStep); } _dispatchTransitionEvent(fromStep, toStep) { this.dispatchEvent( new CustomEvent('transition', { bubbles: true, composed: true, detail: { fromStep, toStep }, }), ); } _onCurrentChanged(newValues, oldValues) { if (this._internalCurrentSync) { this._internalCurrentSync = false; } else { this._goTo(newValues.current, oldValues.current); } } }