diff --git a/.changeset/great-suits-poke.md b/.changeset/great-suits-poke.md
new file mode 100644
index 000000000..b7c11604b
--- /dev/null
+++ b/.changeset/great-suits-poke.md
@@ -0,0 +1,5 @@
+---
+'@lion/steps': minor
+---
+
+Add types for steps package.
diff --git a/.changeset/seven-lizards-explain.md b/.changeset/seven-lizards-explain.md
new file mode 100644
index 000000000..b7c11604b
--- /dev/null
+++ b/.changeset/seven-lizards-explain.md
@@ -0,0 +1,5 @@
+---
+'@lion/steps': minor
+---
+
+Add types for steps package.
diff --git a/packages/steps/src/LionStep.js b/packages/steps/src/LionStep.js
index c961cdd2d..85fa96c15 100644
--- a/packages/steps/src/LionStep.js
+++ b/packages/steps/src/LionStep.js
@@ -1,5 +1,9 @@
import { css, html, LitElement } from '@lion/core';
+/**
+ * @typedef {import('./LionSteps').LionSteps} LionSteps
+ */
+
/**
* `LionStep` is one of many in a LionSteps Controller
*
@@ -8,24 +12,6 @@ import { css, html, LitElement } from '@lion/core';
*/
export class LionStep extends LitElement {
static get properties() {
- /**
- * Fired when the step is entered.
- *
- * @event enter
- */
-
- /**
- * Fired when the step is left.
- *
- * @event left
- */
-
- /**
- * Fired when the step is skipped.
- *
- * @event skipped
- */
-
return {
/**
* Step status, one of: "untouched", "entered", "left", "skipped".
@@ -39,13 +25,14 @@ export class LionStep extends LitElement {
* Takes lion-steps data as a first argument `myConditionFunc(data)`.
*/
condition: {
- type: Function,
+ attribute: false,
},
/**
* Allows to invert condition function result.
*/
invertCondition: {
type: Boolean,
+ reflect: true,
attribute: 'invert-condition',
},
/**
@@ -55,6 +42,7 @@ export class LionStep extends LitElement {
*/
forwardOnly: {
type: Boolean,
+ reflect: true,
attribute: 'forward-only',
},
/**
@@ -63,6 +51,7 @@ export class LionStep extends LitElement {
*/
initialStep: {
type: Boolean,
+ reflect: true,
attribute: 'initial-step',
},
};
@@ -71,7 +60,8 @@ export class LionStep extends LitElement {
constructor() {
super();
this.status = 'untouched';
- this.condition = () => true;
+ // eslint-disable-next-line no-unused-vars
+ this.condition = /** @param {Object} [data] */ data => true;
this.invertCondition = false;
this.forwardOnly = false;
this.initialStep = false;
@@ -99,9 +89,10 @@ export class LionStep extends LitElement {
return html``;
}
- firstUpdated() {
- super.firstUpdated();
- this.controller = this.parentNode;
+ /** @param {import('lit-element').PropertyValues } changedProperties */
+ firstUpdated(changedProperties) {
+ super.firstUpdated(changedProperties);
+ this.controller = /** @type {LionSteps} */ (this.parentNode);
}
enter() {
@@ -119,6 +110,7 @@ export class LionStep extends LitElement {
this.dispatchEvent(new CustomEvent('skip', { bubbles: true, composed: true }));
}
+ /** @param {Object} [data] */
passesCondition(data) {
const result = this.condition(data);
return this.invertCondition ? !result : result;
diff --git a/packages/steps/src/LionSteps.js b/packages/steps/src/LionSteps.js
index 0ba25ff69..a56988e4e 100644
--- a/packages/steps/src/LionSteps.js
+++ b/packages/steps/src/LionSteps.js
@@ -1,5 +1,9 @@
import { css, html, LitElement } from '@lion/core';
+/**
+ * @typedef {import('./LionStep.js').LionStep} LionStep
+ */
+
/**
* `LionSteps` is a controller for a multi step system.
*
@@ -7,6 +11,20 @@ import { css, html, LitElement } from '@lion/core';
* @extends {LitElement}
*/
export class LionSteps extends LitElement {
+ static get styles() {
+ return [
+ css`
+ :host {
+ display: block;
+ }
+
+ :host([hidden]) {
+ display: none;
+ }
+ `,
+ ];
+ }
+
static get properties() {
/**
* Fired when a transition between steps happens.
@@ -31,43 +49,19 @@ export class LionSteps extends LitElement {
};
}
- updated(changedProperties) {
- super.updated(changedProperties);
- if (changedProperties.has('current')) {
- this._onCurrentChanged(
- { current: this.current },
- { current: changedProperties.get('current') },
- );
- }
- }
-
constructor() {
super();
+ /** @type {{[key: string]: ?}} */
this.data = {};
this._internalCurrentSync = true; // necessary for preventing side effects on initialization
+ /** @type {number} */
this.current = 0;
+ this._max = 0;
}
- static get styles() {
- return [
- css`
- :host {
- display: block;
- }
-
- :host([hidden]) {
- display: none;
- }
- `,
- ];
- }
-
- render() {
- return html``;
- }
-
- firstUpdated() {
- super.firstUpdated();
+ /** @param {import('lit-element').PropertyValues } changedProperties */
+ firstUpdated(changedProperties) {
+ super.firstUpdated(changedProperties);
this._max = this.steps.length - 1;
let hasInitial = false;
@@ -82,6 +76,21 @@ export class LionSteps extends LitElement {
}
}
+ /** @param {import('lit-element').PropertyValues } changedProperties */
+ updated(changedProperties) {
+ super.updated(changedProperties);
+ if (changedProperties.has('current')) {
+ this._onCurrentChanged(
+ { current: this.current },
+ { current: /** @type {number} */ (changedProperties.get('current')) },
+ );
+ }
+ }
+
+ render() {
+ return html``;
+ }
+
next() {
this._goTo(this.current + 1, this.current);
}
@@ -91,10 +100,18 @@ export class LionSteps extends LitElement {
}
get steps() {
- const defaultSlot = this.shadowRoot.querySelector('slot:not([name])');
- return defaultSlot.assignedNodes().filter(node => node.nodeType === Node.ELEMENT_NODE);
+ const defaultSlot = /** @type {HTMLSlotElement} */ (this.shadowRoot?.querySelector(
+ 'slot:not([name])',
+ ));
+ return /** @type {LionStep[]} */ (defaultSlot.assignedNodes()).filter(
+ node => node.nodeType === Node.ELEMENT_NODE,
+ );
}
+ /**
+ * @param {number} newCurrent
+ * @param {number} oldCurrent
+ */
_goTo(newCurrent, oldCurrent) {
if (newCurrent < 0 || newCurrent > this._max) {
throw new Error(`There is no step at index ${newCurrent}.`);
@@ -119,6 +136,10 @@ export class LionSteps extends LitElement {
}
}
+ /**
+ * @param {number} newCurrent
+ * @param {number} oldCurrent
+ */
_changeStep(newCurrent, oldCurrent) {
const oldStepElement = this.steps[oldCurrent];
const newStepElement = this.steps[newCurrent];
@@ -137,6 +158,10 @@ export class LionSteps extends LitElement {
this._dispatchTransitionEvent(fromStep, toStep);
}
+ /**
+ * @param {{number: number, element: LionStep}} fromStep
+ * @param {{number: number, element: LionStep}} toStep
+ */
_dispatchTransitionEvent(fromStep, toStep) {
this.dispatchEvent(
new CustomEvent('transition', {
@@ -147,6 +172,10 @@ export class LionSteps extends LitElement {
);
}
+ /**
+ * @param {{current: number}} newValues
+ * @param {{current: number}} oldValues
+ */
_onCurrentChanged(newValues, oldValues) {
if (this._internalCurrentSync) {
this._internalCurrentSync = false;
diff --git a/packages/steps/test/lion-step.test.js b/packages/steps/test/lion-step.test.js
index ab3dfc82c..876cb18b2 100644
--- a/packages/steps/test/lion-step.test.js
+++ b/packages/steps/test/lion-step.test.js
@@ -2,6 +2,10 @@ import { expect, fixture, html, oneEvent } from '@open-wc/testing';
import '../lion-step.js';
+/**
+ * @typedef {import('../src/LionStep').LionStep} LionStep
+ */
+
describe('lion-step', () => {
it('has a condition which allows it to become active (condition is true by default)', async () => {
const el = await fixture(html`
@@ -9,8 +13,8 @@ describe('lion-step', () => {
Step 1
`);
- expect(el.children[0].condition()).to.equal(true);
- expect(el.children[0].passesCondition()).to.equal(true);
+ expect(/** @type {LionStep} */ (el.children[0]).condition()).to.equal(true);
+ expect(/** @type {LionStep} */ (el.children[0]).passesCondition()).to.equal(true);
});
it('does not invert condition by default', async () => {
@@ -19,8 +23,8 @@ describe('lion-step', () => {
Step 1
`);
- expect(el.children[0].invertCondition).to.equal(false);
- expect(el.children[0].passesCondition()).to.equal(true);
+ expect(/** @type {LionStep} */ (el.children[0]).invertCondition).to.equal(false);
+ expect(/** @type {LionStep} */ (el.children[0]).passesCondition()).to.equal(true);
});
it('can invert its condition', async () => {
@@ -29,10 +33,10 @@ describe('lion-step', () => {
Step 1
`);
- el.children[0].condition = () => true;
- el.children[0].invertCondition = true;
- expect(el.children[0].condition()).to.equal(true);
- expect(el.children[0].passesCondition()).to.equal(false);
+ /** @type {LionStep} */ (el.children[0]).condition = () => true;
+ /** @type {LionStep} */ (el.children[0]).invertCondition = true;
+ expect(/** @type {LionStep} */ (el.children[0]).condition()).to.equal(true);
+ expect(/** @type {LionStep} */ (el.children[0]).passesCondition()).to.equal(false);
});
it('has "untouched" status by default', async () => {
@@ -41,7 +45,7 @@ describe('lion-step', () => {
Step 1
`);
- expect(el.children[0].status).to.equal('untouched');
+ expect(/** @type {LionStep} */ (el.children[0]).status).to.equal('untouched');
});
it('is hidden when attribute hidden is true', async () => {
@@ -61,7 +65,7 @@ describe('lion-step', () => {
Step 1
`);
- expect(el.children[0].controller).to.equal(el);
+ expect(/** @type {LionStep} */ (el.children[0]).controller).to.equal(el);
});
describe('navigation', () => {
@@ -71,9 +75,9 @@ describe('lion-step', () => {
Step 1
`);
- setTimeout(() => el.children[0].enter(), 0);
+ setTimeout(() => /** @type {LionStep} */ (el.children[0]).enter(), 0);
await oneEvent(el.children[0], 'enter');
- expect(el.children[0].status).to.equal('entered');
+ expect(/** @type {LionStep} */ (el.children[0]).status).to.equal('entered');
});
it('can be left', async () => {
@@ -82,9 +86,9 @@ describe('lion-step', () => {
Step 1
`);
- setTimeout(() => el.children[0].leave(), 0);
+ setTimeout(() => /** @type {LionStep} */ (el.children[0]).leave(), 0);
await oneEvent(el.children[0], 'leave');
- expect(el.children[0].status).to.equal('left');
+ expect(/** @type {LionStep} */ (el.children[0]).status).to.equal('left');
});
it('can be skipped', async () => {
@@ -93,9 +97,9 @@ describe('lion-step', () => {
Step 1
`);
- setTimeout(() => el.children[0].skip(), 0);
+ setTimeout(() => /** @type {LionStep} */ (el.children[0]).skip(), 0);
await oneEvent(el.children[0], 'skip');
- expect(el.children[0].status).to.equal('skipped');
+ expect(/** @type {LionStep} */ (el.children[0]).status).to.equal('skipped');
});
});
});
diff --git a/packages/steps/test/lion-steps.test.js b/packages/steps/test/lion-steps.test.js
index 2f2a4d2e6..4d36064c3 100644
--- a/packages/steps/test/lion-steps.test.js
+++ b/packages/steps/test/lion-steps.test.js
@@ -1,22 +1,40 @@
-import { expect, fixture, html, oneEvent } from '@open-wc/testing';
+import { expect, fixture as _fixture, html, oneEvent } from '@open-wc/testing';
import sinon from 'sinon';
import '../lion-step.js';
import '../lion-steps.js';
+/**
+ * @typedef {import('../src/LionSteps').LionSteps} LionSteps
+ * @typedef {import('../src/LionStep').LionStep} LionStep
+ * @typedef {import('lit-html').TemplateResult} TemplateResult
+ * @typedef {{[key: string]: ?}} UnknownData
+ */
+
+const fixture = /** @type {(arg: TemplateResult) => Promise} */ (_fixture);
+
+/**
+ *
+ * @param {LionSteps} steps
+ * @param {Object} expected
+ * @param {string} expected.transitions
+ * @param {string} expected.statuses
+ */
async function checkWorkflow(steps, expected) {
return new Promise(resolve => {
+ /** @type {number[]} */
const transitions = [];
steps.addEventListener('transition', event => {
+ const _event = /** @type {CustomEvent} */ (event);
if (!transitions.length) {
- transitions.push(event.detail.fromStep.number);
+ transitions.push(_event.detail.fromStep.number);
}
- transitions.push(event.detail.toStep.number);
+ transitions.push(_event.detail.toStep.number);
});
setTimeout(() => {
// allow time for other transitions to happen if any
const transitionsString = transitions.join(' => ');
expect(transitionsString).to.equal(expected.transitions, 'transition flow is different');
- const statusesString = Array.from(steps.children)
+ const statusesString = /** @type {LionStep[]} */ (Array.from(steps.children))
.map(step => step.status)
.join(' ');
expect(statusesString).to.equal(expected.statuses, 'steps statuses are different');
@@ -60,7 +78,7 @@ describe('lion-steps', () => {
`);
expect(el.current).to.equal(0);
- expect(el.children[0].status).to.equal('entered');
+ expect(/** @type {LionStep} */ (el.children[0]).status).to.equal('entered');
});
});
@@ -133,9 +151,13 @@ describe('lion-steps', () => {
const el = await fixture(html`
Step 0
- data.age < 18}>Step 1
+ data.age < 18}
+ >Step 1
Step 2
- data.age < 22}>Step 3
+ data.age < 22}
+ >Step 3
Step 4
`);
@@ -170,8 +192,8 @@ describe('lion-steps', () => {
describe('events', () => {
it('will fire lion-step @leave event before changing .current', async () => {
let currentInLeaveEvent;
- const onLeave = ev => {
- currentInLeaveEvent = ev.target.controller.current;
+ const onLeave = /** @param {Event} ev */ ev => {
+ currentInLeaveEvent = /** @type {LionStep} */ (ev.target).controller?.current;
};
const el = await fixture(html`
@@ -186,8 +208,8 @@ describe('lion-steps', () => {
it('will fire lion-step @enter event after changing .current', async () => {
let currentInEnterEvent;
- const onEnter = ev => {
- currentInEnterEvent = ev.target.controller.current;
+ const onEnter = /** @param {Event} ev */ ev => {
+ currentInEnterEvent = /** @type {LionStep} */ (ev.target).controller?.current;
};
const el = await fixture(html`
@@ -245,9 +267,16 @@ describe('lion-steps', () => {
const el = await fixture(html`
Step 0
- data.age < 18}>Step 1
- data.age >= 18 && data.age < 21}>Step 2
- data.age >= 21}>Step 3
+ data.age < 18}
+ >Step 1
+ data.age >= 18 && data.age < 21}
+ >Step 2
+ data.age >= 21}
+ >Step 3
Step 4
`);
@@ -264,9 +293,16 @@ describe('lion-steps', () => {
const el = await fixture(html`
Step 0
- data.age < 18}>Step 1
- data.age >= 18 && data.age < 21}>Step 2
- data.age >= 21}>Step 3
+ data.age < 18}
+ >Step 1
+ data.age >= 18 && data.age < 21}
+ >Step 2
+ data.age >= 21}
+ >Step 3
Step 4
`);
@@ -285,9 +321,16 @@ describe('lion-steps', () => {
const el = await fixture(html`
Step 0
- data.age < 18}>Step 1
- data.age >= 18 && data.age < 21}>Step 2
- data.age >= 21}>Step 3
+ data.age < 18}
+ >Step 1
+ data.age >= 18 && data.age < 21}
+ >Step 2
+ data.age >= 21}
+ >Step 3
Step 4
`);
@@ -309,7 +352,11 @@ describe('lion-steps', () => {
const el = await fixture(html`
Step 0
- data.age < 18} invert-condition>Step 1
+ data.age < 18}
+ invert-condition
+ >Step 1
Step 2
`);
@@ -332,7 +379,7 @@ describe('lion-steps', () => {
});
it('behaves like "if/else" in case both condition and inverted condition are present', async () => {
- const condition = data => data.age < 18;
+ const condition = /** @param {UnknownData} data */ data => data.age < 18;
const el = await fixture(html`
Step 0
@@ -411,7 +458,7 @@ describe('lion-steps', () => {
el.next();
el.previous();
- expect(el.children[1].status).to.equal('left');
+ expect(/** @type {LionStep} */ (el.children[1]).status).to.equal('left');
});
});
});