Skip to content

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.

The story
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
What the page reports
// 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')
})