Migrating to GlowTour.js 1.4
GlowTour.js 1.4 reshapes part of the public API before it is locked in. Old names are removed, not deprecated: TypeScript reports each removed name, and a JSON config using an old key is rejected as an unknown key. This page lists every change, grouped by area.
The fastest path is to upgrade every @glowhop/* package together, run your type checker, validate
your JSON configs, and follow the sections that match the reported errors.
Step hooks and step props
Section titled “Step hooks and step props”What changed
Section titled “What changed”beforeAdvance(),beforePrevious(), andbeforeCancel()are removed from the step builder, along with theBeforeActionStepContextandStepTransitionActiontypes.beforeLeave()runs before any navigation away from the step:advance(),previous(),goTo(), or finishing the tour. It never runs on cancel.beforeEnter()runs each time the step is entered, after its target is resolved and before the step is shown, so the props it sets are the first ones rendered.- Both hooks receive a
StepHookContext:props,initialProps,target,signal,direction, andabort(), without navigation methods. The context no longer spreads the step props. - Step props are no longer reset when the tour enters a step. A value set with
context.props.set()orcontext.props.update()is kept when the tour comes back to that step, until the workflow runs again.resetPropsOnEnteris removed.
What to update
Section titled “What to update”// Before.beforeAdvance((context) => save(context.data)).beforeCancel(() => cleanup())
// After.beforeLeave((context) => { if (context.direction === "advance") save(context.props.get().data);})// ...and in the workflow options{ onCancel: () => cleanup() }-
Steps that relied on props being reset on every visit: reset them in
beforeEnter:.beforeEnter(({ props, initialProps }) => props.set(initialProps)) -
resetPropsOnEnter: false: remove it, not resetting is now the default. -
Cleanup that ran in
beforeCancel: move it to the workflowonCanceloption, which receives the current step incontext.step, or listen tocontext.signalin a step action.
Step behavior
Section titled “Step behavior”What changed
Section titled “What changed”behavioris part of the dynamic step props. Read it withcontext.props.get().behaviorand change it during the step withcontext.props.update({ behavior }).WorkflowStepDefinition.behavioris removed: the step behavior, merged over the workflow defaults, is inprops.behavior.props.set()replacesbehavioralong with the other props.allowScrollmoves from the workflow options tobehavior.allowScroll. It can be set per step and changed during a step, likeallowInteraction. The workflowbehaviorstill sets it for every step.
What to update
Section titled “What to update”-
allowScrollin the workflow options or a JSON config: move it intobehavior:// Beforetour.create("onboarding", { allowScroll: false });// Aftertour.create("onboarding", { behavior: { allowScroll: false } }); -
Code reading
definition.steps[index].behavior: readdefinition.steps[index].props.behavior. -
props.set()calls that build the props from scratch: spread the current props to keep the behavior, or useprops.update():props.set((current) => ({ ...current, content: "Saved." }));props.update({ content: "Saved." });
Step transitions
Section titled “Step transitions”What changed
Section titled “What changed”beforeEnterandbeforeLeavereceiveabort(). It stops the navigation: the tour stays on its current step and emits no event.- A navigation now emits its events once it cannot be stopped anymore, in this order:
step:skipfor each step skipped bymissingTarget: { strategy: "skip" },step:leavefor the step being left, thenstep:enterfor the step shown. tour:startis emitted once the first step’sbeforeEnterhas passed, instead of right afteronStart.- A navigation over several skipped steps emits one
step:leave, instead of one per skipped step. - Going back over skipped steps past the first step keeps the current step. It used to cancel the tour.
What to update
Section titled “What to update”- Analytics: handle the new
step:skipevent, or ignore it in a default branch. If you countedstep:leaveevents to detect skips, countstep:skipinstead. - Code relying on
tour:startright afteronStart: move that code toonStartitself. - Code implementing
StepHookContextby hand (tests, mocks): add anabort()method.
Starting a tour
Section titled “Starting a tour”What changed
Section titled “What changed”tour.run(workflow, options?)is renamedtour.start(workflow, options?). Its arguments and its promise are unchanged: it resolves once the first step is on screen.- The value returned by
useGlowTour()(Angular:injectGlowTour()) exposesstartinstead ofrun.
| 1.3 | 1.4 |
|---|---|
tour.run(workflow) |
tour.start(workflow) |
tour.run(workflow, { startAt }) |
tour.start(workflow, { startAt }) |
What to update
Section titled “What to update”// Beforeawait tour.run(workflow);
// Afterawait tour.start(workflow);- Mocks or wrappers that implement the tour controller (tests, facades): rename their
runmethod tostart.
Jumping to a step
Section titled “Jumping to a step”What changed
Section titled “What changed”tour.goToStep(index)is removed.tour.goTo(id)replaces it and takes the step’sid, likestartAtinstart(). An index breaks as soon as steps are reordered or inserted.tour.goTo(id)throws when no step has thisid. It does nothing while a transition is in progress or when that step is already shown.- Step actions and target event handlers can call
context.goTo(id). Likecontext.advance(), it stops the remaining actions of the step.
What to update
Section titled “What to update”// Beforeawait tour.goToStep(2);
// Afterawait tour.goTo("billing");Step options
Section titled “Step options”What changed
Section titled “What changed”Negative booleans become positive ones, and related behavior settings are grouped:
| 1.3 | 1.4 |
|---|---|
behavior.disableAutoFocus: true |
behavior.autoFocus: false |
behavior.disableAutoScroll: true |
behavior.autoScroll: false |
indicator.disabled |
indicator.hidden |
popover.arrow.disabled |
popover.arrow.hidden |
popover.arrow.disableAutoStyles: true |
popover.arrow.autoStyles: false |
popover.keyboardShortcuts |
controls: { advance: { keys }, previous: { keys }, cancel: { keys } } (see Controls) |
behavior.missingTargetStrategy, behavior.targetTimeout |
behavior.missingTarget: { strategy, timeout } |
The same shapes apply to the workflow options, each step, and the JSON config.
autoFocus: false also does more than disableAutoFocus: true did. It used to keep focus only when it was already in the popover, and moved it onto the Advance button otherwise. It now never moves focus during the step: focus stays on the page when the step allows interaction, and stays on the body when a modal step makes the page inert. See Keeping focus where it is.
What to update
Section titled “What to update”// Before.step({ id: "report", target: "#report", title: "Report", content: "Your latest report.", popover: { arrow: { disabled: true }, keyboardShortcuts: { previous: [] } }, behavior: { disableAutoScroll: true, missingTargetStrategy: "wait", targetTimeout: 5000 },})
// After.step({ id: "report", target: "#report", title: "Report", content: "Your latest report.", popover: { arrow: { hidden: true } }, behavior: { autoScroll: false, missingTarget: { strategy: "wait", timeout: 5000 } }, controls: { previous: { keys: [] } },})Controls
Section titled “Controls”What changed
Section titled “What changed”- The five popover button flags and
popover.keyboardShortcutsare replaced by acontrolsoption, next topopoverandbehavior. It holds one entry per command,advance,previousandcancel, each with astate,"enabled"(default) or"disabled", and thekeysthat run it. - A step’s controls override the workflow ones field by field: a step that sets only
advance.statekeeps the workflow’sadvance.keys. - A disabled control blocks everything the tour UI offers for it: the button, its keys, and
overlayClick. - Hiding a button is a styling choice: give it a class through
classNamesand hide that class in your CSS, see Hiding a control’s button. LikehideAdvanceButton, a hidden button keeps its keys; addstate: "disabled"to block them too. tour.advance(),tour.previous(),tour.cancel(),tour.goTo()and the step context are not affected bycontrols.- The
cancellablestart option is removed. To keep the user from ending a tour, disable its cancel control on the workflow: the Skip button is disabled, andEscapeandoverlayClick: "cancel"do nothing. Unlikecancellable: false,tour.cancel()andcontext.cancel()still cancel, and the Skip button stays rendered unless you hide it. - A step whose target disappears, with no step left to fall back to, now always cancels the tour.
With
cancellable: falseit used to fail with a missing-target error. - The footer is always rendered, in the footer component and in the default tour component. To
hide it, give it a class through
classNames.footer.
| 1.3 | 1.4 |
|---|---|
popover.hideAdvanceButton: true |
classNames: { advance: "tour-hidden" } |
popover.disableAdvanceButton: true |
controls: { advance: { state: "disabled" } } |
popover.hidePreviousButton: true |
classNames: { previous: "tour-hidden" } |
popover.disablePreviousButton: true |
controls: { previous: { state: "disabled" } } |
popover.hideFooter: true |
classNames: { footer: "tour-hidden" } |
popover.keyboardShortcuts: { advance: ["n"] } |
controls: { advance: { keys: ["n"] } } |
cancellable: false |
controls: { cancel: { state: "disabled" } }, plus classNames: { cancel: "tour-hidden" } to hide the button |
What to update
Section titled “What to update”// Beforepopover: { hideAdvanceButton: true },// ...props.update({ popover: { hideAdvanceButton: false } });
// After, with `[data-glow-tour-root] .tour-hidden { display: none; }` in your CSSclassNames: { advance: "tour-hidden" },// ...props.update({ classNames: { advance: [] } });- A step that hid the Next button to keep the user on it: also set
controls: { advance: { state: "disabled" } }, soEnter,ArrowRightandoverlayClick: "advance"cannot advance either, and set it back to"enabled"with the class. - A tour that relied on
cancellable: falseto ignoretour.cancel(): check the condition in your own code before calling it. - A footer that relied on
hideFooter:classNames: { footer: "tour-hidden" }hides it with its three buttons. Their keys stay active unless their controls are disabled.
JSON config
Section titled “JSON config”What changed
Section titled “What changed”- A workflow config carries
version: "1.1", the version of the config format. A config without it is rejected. - Step keys follow the builder names:
| 1.3 | 1.4 |
|---|---|
eventHandlers |
targetEvents (entries keep event and action) |
advanceAction, previousAction |
beforeLeave, branching on direction |
cancelAction |
the workflow onCancel |
resetPropsOnEnter |
removed |
| (none) | beforeEnter |
- Types:
EventHandlerConfigbecomesTargetEventConfig,TransitionActionRefbecomesStepHookActionRef, and the coreEventHandlertype becomesTargetEventHandler. - The step option and popover control changes above apply to the config as well, and the config
rejects
cancellable.
What to update
Section titled “What to update”// Beforeconst config: WorkflowConfig = { name: "onboarding", steps: [ { id: "profile", target: "#profile", title: "Profile", content: "Fill in your profile.", eventHandlers: [{ event: "click", action: { type: "focusTarget" } }], advanceAction: () => saveDraft(), }, ],};
// Afterconst config: WorkflowConfig = { version: "1.1", name: "onboarding", steps: [ { id: "profile", target: "#profile", title: "Profile", content: "Fill in your profile.", targetEvents: [{ event: "click", action: { type: "focusTarget" } }], beforeLeave: ({ direction }) => { if (direction === "advance") saveDraft(); }, }, ],};Component names
Section titled “Component names”What changed
Section titled “What changed”- Every adapter uses the same names, with the
GlowTourprefix. React and Solid lose their unprefixed components. TheGlowTourobject keeps its compound syntax (<GlowTour.Root>), now also in Vue, but losesGlowTour.DefaultandGlowTour.BackTrigger. - The back trigger is now the previous trigger, like
tour.previous(). Its default label is “Previous step”. - Vanilla renders the default tour with a
<glow-tour-default>element instead ofcreateDefaultTourElement(). - React and Solid:
GlowTourPopover,GlowTourPointer, andGlowTourFooterno longer acceptas. They always render a<section>, a<div>, and a<footer>, the elements the tour binds its positioning, focus, and ARIA behavior to. On the footer,asnever had an effect.
| 1.3 | 1.4 |
|---|---|
React / Solid Root, Popover, Header, Content, Footer, Overlay, Pointer |
GlowTourRoot, GlowTourPopover, GlowTourHeader, GlowTourContent, GlowTourFooter, GlowTourOverlay, GlowTourPointer |
React / Solid AdvanceTrigger, CancelTrigger |
GlowTourAdvanceTrigger, GlowTourCancelTrigger |
React / Solid GlowTour.BackTrigger |
GlowTour.PreviousTrigger |
React / Solid DefaultTour, GlowTour.Default, DefaultTourProps |
GlowTourDefault, GlowTourDefaultProps |
BackTrigger (React, Solid), GlowTourBackTrigger (Vue, Angular) |
GlowTourPreviousTrigger |
<glow-tour-back-trigger> (Angular, Vanilla) |
<glow-tour-previous-trigger> |
backLabel prop, Vanilla back-label attribute |
previousLabel, previous-label |
Vanilla createDefaultTourElement(tour, { idPrefix }) |
<glow-tour-default> with tour and id-prefix |
Vanilla VanillaGlowTour |
Tour |
What to update
Section titled “What to update”// Beforeimport { GlowTour } from "@glowhop/react-tour";
<GlowTour.Root tour={tour}> <GlowTour.Popover> <GlowTour.BackTrigger backLabel="Back" /> </GlowTour.Popover></GlowTour.Root>;
// After, with named componentsimport { GlowTourPopover, GlowTourPreviousTrigger, GlowTourRoot } from "@glowhop/react-tour";
<GlowTourRoot tour={tour}> <GlowTourPopover> <GlowTourPreviousTrigger previousLabel="Back" /> </GlowTourPopover></GlowTourRoot>;
// After, with the GlowTour objectimport { GlowTour } from "@glowhop/react-tour";
<GlowTour.Root tour={tour}> <GlowTour.Popover> <GlowTour.PreviousTrigger previousLabel="Back" /> </GlowTour.Popover></GlowTour.Root>;// Beforedocument.body.append(createDefaultTourElement(tour, { idPrefix: "help" }));
// Afterconst element = document.createElement("glow-tour-default");element.idPrefix = "help";element.tour = tour;document.body.append(element);ason the popover, pointer, or footer: remove it. Style the component withclassName(Solid:class) andstyle, or put your own element inside it.- Tests or styles matching the “Back step” label: match “Previous step”, or pass your own
previousLabel.
State hooks
Section titled “State hooks”What changed
Section titled “What changed”- The hook that reads the state inside
GlowTourRootis renamed, so thatuseGlowTourcan become the main way to run a tour from a component. - React, Vue, and Solid:
useTour()becomesuseGlowTourContext(). It still works only insideGlowTourRootand returns the same value. - Angular:
injectGlowTour()becomesinjectGlowTourContext(). The nameinjectGlowTour()now belongs to the new API below, which returns an object, not a state signal: code that still calls it insideglow-tour-rootcompiles but reads the wrong shape.
| 1.3 | 1.4 |
|---|---|
React / Vue / Solid useTour() |
useGlowTourContext() |
Angular injectGlowTour() |
injectGlowTourContext() |
What to update
Section titled “What to update”// Beforeimport { useTour } from "@glowhop/react-tour";
const state = useTour();
// Afterimport { useGlowTourContext } from "@glowhop/react-tour";
const state = useGlowTourContext();// Beforeprotected readonly state = injectGlowTour();
// Afterprotected readonly state = injectGlowTourContext();- State read outside
GlowTourRootwithtour.state.subscribe: replace the subscription withuseGlowTour(tour)(Angular:injectGlowTour(tour)), which returns each state field as a ref, accessor, or signal.
Also new in 1.4
Section titled “Also new in 1.4”These additions need no change to existing code:
-
useGlowTour()(Angular:injectGlowTour()) runs a tour from a component. It returns the tour, its methods, and each state field in the framework’s reactive form. Pass an existing tour to share it between components. -
context.props.update(patch)merges a partial change into the step props. -
controls.cancel.statehides or disables the cancel button, with its keys andoverlayClick: "cancel". -
A step
titleis optional: without it, the header is omitted and the content names the dialog. -
StepContextexposesinitialPropsanddirection.
