Targets
A step names its target with a CSS selector, or with a function where no
selector reaches the element. Where a selector matches several elements, the
step takes the first one the visitor can see. A target that is not on the page
when its step arrives is waited for a moment, and one that is off screen stays
there unless scroll asks for it to be brought in. A fixed or sticky target
needs nothing of its own: its hole stays on it while the page scrolls.
A target is a selector, or a function that finds it
Section titled “A target is a selector, or a function that finds it”Name a target with a selector wherever one can say which element you mean.
Where none can, such as for a button inside a component’s shadow root, hand a
function that finds the element, or answers null where there is none. A
target takes no element, because one handed over when the story is written
may have been replaced by the time its step arrives —
A target is a question.
Here the Send button is inside the open shadow root of a send-button
element, which document.querySelector does not enter.
import type { LekoStep } from '@annetaan/leko'
const steps: LekoStep[] = [ { id: 'total', target: '#total', message: 'This is what the order comes to.' }, { id: 'send', target: { elements: () => document.querySelector('send-button')?.shadowRoot?.querySelector('button') ?? null, interactive: true, }, message: 'Send the order.', },]- A shadow root changes nothing about the step. The hole is cut, blocked or opened, and the message is placed, the same as for any other target.
- A framework ref is a function too. How to write one in React and Vue is under Refs as targets.
Of several matches, the step takes the first the visitor can see
Section titled “Of several matches, the step takes the first the visitor can see”Where a selector matches several elements, the step takes the first one the visitor could see and passes over a copy that is hidden. Where a compact layout for narrow windows puts a second Save button above the form and hides it on wide ones, a step pointed at the Save button gets the one on screen. Taking the first match whatever it looked like would cut a hole over a copy that is invisible but still laid out, draw the step and report nothing — Which of several matches a selector means.
- A step asks for another rule with
resolve, and the instance for every step. What each value takes is under resolve. - A step that opens the compact layout first gets the copy inside it, as A target that is not there yet gets a moment explains.
A target that is not there yet gets a moment
Section titled “A target that is not there yet gets a moment”A step whose target is not on the page when it arrives waits 100 ms for it,
and draws nothing new while it waits. A target that turns up in that time is
drawn as though nothing had happened, and one that does not ends the tour with
target-lost. The wait is for a
framework that has not finished rendering, and for nothing longer —
Target loss & recovery.
- A target in a closed panel or tab is opened in the step’s
onEnter— A step sets up what it alone stands on. - A target that appears only once a request comes back gets a step before it that waits for the request — A wait is a step that declares awaits, not a promise.
- What happens to a target removed or hidden once its step is drawn is under LekoTarget.
A target off screen is brought into view only where scroll asks
Section titled “A target off screen is brought into view only where scroll asks”A step draws its target where the target is, and brings it into view only
where scroll is on, for that step or for the instance. It is off by default,
because where the page is scrolled to is the application’s own state —
Bringing a target into view.
Here every step is brought into view except more, which asks the visitor to
scroll to the end of the list themselves.
import { createLeko, type LekoStep } from '@annetaan/leko'
export const leko = createLeko({ scroll: true })
const steps: LekoStep[] = [ { id: 'total', target: '#total', message: 'This is what the order comes to.' }, { id: 'more', target: '#load-more', message: 'Scroll to the end of the list, and load the next page from here.', scroll: false, },]- A target already on screen in full is left where it is, even with
scrollon. - What is moved,
'staged'included, and when the step is drawn are under scroll. - Where
scrollmeetsresolve: 'in-viewport-first'is under resolve.
A fixed or sticky target keeps its hole as the page scrolls
Section titled “A fixed or sticky target keeps its hole as the page scrolls”Point a step at a position: fixed or position: sticky element the way you
point at any other. Its hole stays on it while the page scrolls.
- A fixed target’s hole stays put while the page scrolls under it, because its layer is fixed to the viewport as the element is — Scrolling.
- A “fixed” element inside an ancestor with a transform scrolls with the page, and its hole with it. Leko asks the browser whether the viewport still holds the element, rather than reading the stylesheet — Scrolling.
- A sticky target’s hole follows it across its pin, both ways, whether it pins to the top of the page or of a panel, and whichever side of the pin the step was drawn on. A pinned element moves against the page it sits in, so Leko puts its hole back on each frame while the visitor scrolls, the one target it does that for, and its message moves to a side with room once the side it had runs out — Scrolling.
See it run
Section titled “See it run”The example A target inside a shadow root names its Send button with a function, because the button is inside a shadow root. The line above the button counts the presses that reach it, and a press through the open hole on the second step adds one.
Which of several matches a step means
has three copies of one Save button, the first of them in a compact layout
hidden with display: none. which-match-visible asks for no rule and points
at the copy on screen, and its second step opens the compact layout in
onEnter and gets the copy inside it. which-match-first asks for first,
takes the hidden copy, and ends with target-lost: the footer says its
target never turned up.
A target that has not turned up yet
takes its row away in arrives-late’s onEnter and puts it back 60 ms later,
and the step is drawn as though nothing had happened. Pressing Dismiss once it
is drawn takes the row away, and the hole stays where it was.
never-arrives points at nothing the page has, and after its moment the
footer says its target never turned up.
A step that goes and gets its target
turns scroll on for the instance. Its first step moves nothing, because its
target is already on screen, and its second glides the page to a card past the
fold.
staged-scroll.ts
reaches one row inside a list, inside a panel, down the page, twice:
one-movement in one movement, and one-at-a-time with 'staged', the page
first, then the panel, then the list.
A bar that pins, and a table head that pins inside a panel points twice at a button in a bar that pins to the top of the screen, once drawn before the bar pins and once after, and scrolling across the pin either way keeps the hole on the button. Its last step does the same for a table head pinned inside a scrolling panel. Chrome that does not scroll keeps the hole on its fixed Share button while the page scrolls, and the hole on a badge that a transform took back into the page scrolls with the badge.