@steppy/player

中文文档
0.9.0发布于 2026/11/02Apache-2.0TypeScriptES Module构建来源已验证

Steppy players — the interactive guide (Lit web component + manifest validator) and the in-app tour overlay

原始使用文档
文档目录8 节
  1. Development
  2. Presentation templates
  3. The template attribute + resolution order
  4. Per-guide default ( defaultTemplate )
  5. Engine → template API contract
  6. Adding a chrome string
  7. Design tokens
  8. In-app tour overlay ( ./tour )

@steppy/player

The Lit <steppy-guide> web component that renders interactive guide manifests (guide.json), plus the shared cursor animator — and the in-app tour overlay <steppy-tour-host> (the ./tour entry). Framework-agnostic, fully offline, no CDN.

npm install @steppy/player

The guide player is the package's main entry; the tour overlay is @steppy/player/tour.

Development

  • npm run build — four bundles: the guide player ESM bundle (dist/steppy-player.js), the legacy cursor IIFE (dist/steppy-cursor-legacy.js), the video renderer IIFE (dist/steppy-video.js), and the tour overlay (dist/steppy-tour.js with declarations in dist/types/), plus the JSON Schema. Re-vendor the first three into src/steppy/_player_bundle/ after changing src/ (CI checks them byte for byte and does it on release); the tour overlay is never vendored.
  • npm run size:tour — the tour overlay's size budget (min + gzip).
  • npm run check:pack — publish-readiness: every file package.json points at (main, types, exports, typesVersions) is in what npm pack would ship, and nothing from src/ or test/ is. prepack builds the package. Declarations for the guide entry and for ./tour come from tsc -p tsconfig.types.json (npm run build:types).
  • npm test — Vitest (schema, animator, positioning, component, templates, Python↔TS bridge, the shared src/ui/ modules, the tour overlay).
  • npm run typecheck — tsc --noEmit.
  • Dev harness: dist/ built, then open dev/index.html.

Presentation templates

The engine (step state, cursor animation, target highlighting, i18n, navigation, keyboard, locale/layout switching) is one thing; how a step is presented is another. Presentation is a swappable template — one of overlay (default: full-bleed preview + floating callout pinned to the target), sidebar (left rail with a vertical step timeline + the current step's description), or inline (slim bottom caption bar). All three render the same engine state; only the chrome differs.

The template attribute + resolution order

template is a runtime-switchable, persisted viewer setting:

<steppy-guide src="./guide.json" template="sidebar"></steppy-guide>

The effective template is resolved (highest precedence first):

  1. the explicit template attribute/property,
  2. the persisted choice in localStorage['steppy:active-template'],
  3. the manifest's top-level defaultTemplate (the per-guide default),
  4. 'overlay'.

Switching templates (via the built-in segmented control or by setting the attribute) preserves the current step, locale, layout, and highlight — it is a pure re-render of engine state, never a reset. The choice is persisted; an explicit attribute or the switcher persists, the initial resolution does not.

Per-guide default (defaultTemplate)

Authored in the build config, not hardcoded in the component:

// steppy.guide.config.ts
defineGuideConfig({
  defaultTemplate: 'sidebar',          // project-wide default
  guides: [
    { slug: 'onboarding', /* … */ template: 'inline' }, // per-guide override
  ],
});

steppy build resolves entry.template || config.defaultTemplate || 'overlay' and emits it as the manifest's defaultTemplate; it is also set as the initial template attribute on the generated host's <steppy-guide>.

Engine → template API contract

Templates are pure functions in src/templates/{overlay,sidebar,inline}.ts, each exporting render(ctx: TemplateContext): TemplateResult and a styles CSSResult. The component (steppy-guide.ts) owns all state and builds an immutable TemplateContext (src/templates/types.ts) per render:

  • steps: StepView[] — the full ordered list, each with index, localized title / description / subLabel, status (completed|current|upcoming), and reachable (the goTo gate);
  • currentIndex, total, durationLabel, guideTitle, locale, ui;
  • waitingForInput, completed;
  • nav callbacks (onNext/onPrev/onRestart/onGoTo) + precomputed prevDisabled/nextDisabled so templates never re-implement gates;
  • switcher data + handlers (locales/layouts/templates, onLocaleChange/ onLayoutChange/onTemplateChange);
  • renderStage() — a thunk returning the live stage subtree (screenshot + cursor/ripple/kbd + hotspot + miss-toast), shared verbatim so the refs and handlers stay bound to the component.

To add a template: add a module, register it in src/templates/index.ts (TEMPLATES + ALL_TEMPLATES), and extend TEMPLATE_MODES in manifest/schema.ts (the .strict() schema rejects unknown ids).

Stage re-mount invariant: switching templates swaps the whole top-level template, so Lit recreates the .steppy-stage element. updated() detects the new element identity and calls _reattachStage — re-pointing the CursorAnimator (via animator.setStage) and the ResizeObserver, then recomputing the hotspot — without touching step state, so an in-flight animation survives the switch.

Adding a chrome string

UI chrome strings live in src/i18n/ui-strings.ts: add the key to the UiStrings interface and a value to every locale in DEFAULT_UI_STRINGS (en, fr, nl, de). Use format(ui.key, vars) for {placeholder} interpolation. Hosts can override per-locale via the uiStrings property. A regional locale falls back to its base language (fr-BE reads fr), then to English. next and progress come from CHROME_STRINGS in src/ui/strings.ts, shared with the tour overlay.

Design tokens

The warm palette (cream surfaces, near-black text, rust-orange accent, green completed) and the serif/sans/mono type scale are CSS custom properties on :host in src/templates/base-styles.ts (system font stacks only — no web fonts). Override any --steppy-* variable from the host to re-theme. Animations respect prefers-reduced-motion.

The sidebar template adds four layout tokens (declared inline in src/templates/sidebar.ts, not on :host, since they mean nothing to the other templates). Everything the rail cap touches is a token, because the shadow DOM has no part= attributes — a host that cannot live with a default has no other way in, and finding that out costs a release:

TokenDefaultWhat it does
--steppy-rail-width340pxLeft rail width; the rest of the row is the preview
--steppy-rail-max-heightcalc(100vh - 32px)Rail cap; the timeline scrolls inside it
--steppy-sidebar-offset16pxtop for the sticky rail
--steppy-rail-max-height-narrow60vhRail cap once collapsed to one column (≤640px)

An embedded host should override --steppy-rail-max-height. The 100vh default assumes the player owns the viewport, which is true for the static target's interactive.html and false for the Angular target, where the host's own header and nav sit above the component — there calc(100vh - <chrome>) is the correct value and only the host knows it.

In-app tour overlay (./tour)

import '@steppy/player/tour' registers <steppy-tour-host> (once, even when two copies of the bundle load) and exports its types, the contract values (TOUR_EVENTS, TOUR_PARTS, ADVANCE_KINDS, …) and the validators. The user documentation — host API, events, theming, CSP — is docs/tours.md at the repository root.

Layout of the code:

  • src/ui/ is shared with the guide player: design tokens (tokens.ts, which templates/base-styles.ts interpolates — the guide's stylesheet hash is pinned by test/ui/guide-styles-stable.test.ts), chrome strings and the locale lookup (strings.ts), the inline Markdown renderer with its link policy (markdown.ts), placeTooltip (placement.ts) and the product names (names.ts). It never registers an element and never imports zod.
  • src/tour/engine.ts is a pure reducer: reduce(state, event) returns the next state and the effects to run. host.ts interprets the effects through DOM adapters — loader, resolver, triggers, tracker, layer, storage — and dom.ts is the seam tests swap (SteppyTourHost.dom): happy-dom has no layout, no popover API and a requestAnimationFrame outside fake timers, so test/tour/helpers.ts provides a stub DOM with settable rects and a manual scheduler.
  • src/tour/validate.ts is the security boundary: every manifest and tour file is validated whole, by hand (no zod, for size). It runs the same golden cases as the Python producer (contracts/fixtures/tours/cases.json).
  • Geometry is written through CSSOM custom properties only — no style= binding and no styleMap — so the overlay runs under a strict CSP; test/tour/boundaries.test.ts enforces that and the import boundary.
  • Real browser behaviour (top layer, hit-testing, focus, keyboard, remote loading with CORS and SRI) is covered by the opt-in Python suites in tests/integration/test_tour_*_it.py against fixtures/tour-demo/.

Every chrome string the overlay shows exists in en, fr, nl and de (src/tour/strings.ts); add a key to all four. Adding a language means adding it to CHROME_STRING_LOCALES in src/tour/contract.ts and src/steppy/tour_spec.py (the compiler warns chrome-strings-missing for every other tour language) and checking npm run size:tour. The layer carries the tour file's lang and dir (textDir in strings.ts), so the bubble's inner layout uses logical properties only — test/tour/small-modules.test.ts rejects a physical margin-left/right. A running tour reloads when the host's locale (else <html lang>) changes; see _swapLocale in host.ts. Unlike the guide player, the overlay exposes part= attributes for ::part() styling. Its --steppy-tour-* tokens are one table, TOUR_THEME_KEYS in theme.ts: the stylesheet, the host's theme property and the docs all follow it, and appearance="light"/"auto" swap the defaults (TOUR_LIGHT in ui/tokens.ts, AA-checked like the dark ones). Add a token there, never as a literal in styles.ts.

版本与发布记录

@steppy/player 共发布 9 个版本,首次发布于 2026/10/22;过去 12 个月发布了 9 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.9.0latest2026/11/02今天
0.8.12026/10/304 天前
0.8.02026/10/277 天前
0.7.02026/10/277 天前
0.6.02026/10/259 天前
0.5.02026/10/2410 天前

关于 @steppy/player 的常见问题

@steppy/player 是什么?

Steppy players -交互式指南(Lit web组件+ manifest验证器)和应用内导览覆盖

如何安装 @steppy/player?

在项目目录运行 npm install @steppy/player,或使用 pnpm add / yarn add / bun add。

@steppy/player 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@steppy/player 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@steppy/player 可以商用吗?

@steppy/player 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@steppy/player 还在维护吗?

最新版本 0.9.0 发布于 2026/11/02(今天);至今共发布 9 个版本,其中过去 12 个月发布了 9 个;首次发布于 2026/10/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。