Typing, and the control that follows it
A step the page cannot report gets a Next control with a guard on it, and the step after it waits for the save and has neither.
const story = { id: 'next-control', steps: [ { id: 'name', target: { elements: 'input[name="name"]', interactive: true }, message: 'Give the project a name of your own, then press Next.', // Pressing the control claims the moment has come and claims // nothing about the state behind it, which is why a step with a // control is the kind of step that can want a guard. validate: (el) => { const value = (el as HTMLInputElement).value.trim() return value !== '' && value !== 'Untitled' }, // Beside the instruction, not instead of it: a second failed // attempt must still say what the step is asking for. error: 'Still “Untitled”. Type something else first.', }, { id: 'save', target: { elements: '[data-save]', interactive: true }, message: 'Now save it. No control on this one, and no guard. The save ends it.', awaits: 'project-renamed', }, { id: 'saved', target: '[data-status]', message: 'The request came back, and that is what moved the story on.', }, ],} satisfies LekoStory// Nothing is reported while the name is typed, deliberately. Wiring an// `input` listener up to the tour is the guessing the library exists to// avoid, and it is what leaves the typing step with no way to end.button.addEventListener('click', async () => { button.disabled = true status.textContent = 'Saving…' await new Promise((resolve) => setTimeout(resolve, 800)) status.textContent = `Saved as “${input.value}”.` button.disabled = false
leko.reached('project-renamed')})