@docentjs/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +31 -0
- package/dist/index.cjs +696 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +582 -0
- package/dist/index.d.ts +582 -0
- package/dist/index.js +670 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/schema/tour.ts","../src/define.ts","../src/engine/route.ts","../src/engine/conditions.ts","../src/seams.ts","../src/engine/progress.ts","../src/engine/events.ts","../src/engine/reducer.ts","../src/engine/controller.ts"],"sourcesContent":["/**\n * Tour schema — the JSON contract shared by the core engine, every renderer,\n * the visual builder and the hosted service.\n *\n * Everything in this file must stay JSON-serialisable. Functions (hooks) live\n * in `hooks.ts` and are attached at runtime, keyed by step id.\n */\n\n/** Current schema version. Bump only on breaking changes to this file. */\nexport const SCHEMA_VERSION = 1 as const\n\nexport type SchemaVersion = typeof SCHEMA_VERSION\n\n// ---------------------------------------------------------------------------\n// Targets\n// ---------------------------------------------------------------------------\n\n/**\n * Where a step points.\n *\n * - A bare string is a CSS selector (web only). Convenient for hand-written tours.\n * - A {@link TargetSpec} is the portable, self-healing form the builder produces.\n *\n * Prefer `{ name }` over raw selectors: on the web it resolves to\n * `[data-docent=\"<name>\"]`, on native to a `testID`, so one tour works everywhere.\n */\nexport type Target = string | TargetSpec\n\nexport interface TargetSpec {\n /**\n * Logical name. Web: `[data-docent=\"<name>\"]`. Native: `testID` / `nativeID`.\n * The most robust anchor; survives refactors and works cross-platform.\n */\n name?: string\n /**\n * Ordered CSS selector fallbacks (web only). Tried in order until one matches.\n * The builder fills several so a tour keeps working after markup changes.\n */\n selectors?: string[]\n /** Explicit native identifier when it differs from `name`. */\n native?: string\n /** Restrict the search to a container matched by this selector. */\n within?: string\n /** When a selector matches several elements, pick this index (default 0). */\n nth?: number\n}\n\n// ---------------------------------------------------------------------------\n// Placement & visuals\n// ---------------------------------------------------------------------------\n\nexport type Side = 'top' | 'right' | 'bottom' | 'left'\nexport type Alignment = 'start' | 'center' | 'end'\n\n/**\n * Preferred popover position relative to the target. `auto` lets the renderer\n * pick the side with the most room. Any placement flips or shifts when it\n * would overflow the viewport.\n */\nexport type Placement = 'auto' | Side | `${Side}-${Exclude<Alignment, 'center'>}`\n\nexport interface SpotlightOptions {\n /** Space between the target's edge and the cutout, in px. */\n padding?: number\n /** Corner radius of the cutout, in px. */\n radius?: number\n /** Animate the cutout moving between targets. */\n animate?: boolean\n}\n\nexport interface OverlayOptions {\n /** Backdrop colour, any CSS colour. */\n color?: string\n /** Backdrop opacity, 0–1. */\n opacity?: number\n}\n\nexport interface ScrollOptions {\n /** Scroll the target into view before showing the step. */\n enabled?: boolean\n behavior?: 'auto' | 'smooth'\n block?: 'start' | 'center' | 'end' | 'nearest'\n}\n\nexport interface Media {\n type: 'image' | 'video'\n src: string\n alt?: string\n}\n\n// ---------------------------------------------------------------------------\n// Behaviour\n// ---------------------------------------------------------------------------\n\n/**\n * How a step completes.\n *\n * - `'button'` (default): the user presses Next.\n * - `click` / `input`: the user interacts with the target (or another element).\n * - `event`: a named event is emitted through the runtime.\n * - `element`: an element appears (e.g. a menu the user was asked to open).\n * - `delay`: automatically after `ms`.\n */\nexport type Advance =\n | 'button'\n | { on: 'click'; target?: Target }\n | { on: 'input'; target?: Target; match?: string }\n | { on: 'event'; name: string }\n | { on: 'element'; target: Target }\n | { on: 'delay'; ms: number }\n\n/** Whether the user can interact with the spotlighted target. */\nexport type Interaction = 'block' | 'allow'\n\n/** What to do when a step's target cannot be found. */\nexport type OnMissing = 'skip' | 'wait' | 'abort'\n\nexport interface StepButtons {\n back?: boolean\n next?: boolean\n skip?: boolean\n close?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Conditions & triggers\n// ---------------------------------------------------------------------------\n\nexport type TraitValue = string | number | boolean | null | string[]\n\nexport type TraitOperator =\n | 'eq'\n | 'neq'\n | 'gt'\n | 'gte'\n | 'lt'\n | 'lte'\n | 'in'\n | 'nin'\n | 'contains'\n | 'exists'\n | 'missing'\n\n/**\n * Serialisable predicate evaluated by the core at runtime.\n * `custom` predicates are registered by name on the runtime.\n */\nexport type Condition =\n | { type: 'trait'; key: string; op: TraitOperator; value?: TraitValue }\n | { type: 'route'; pattern: string }\n | { type: 'element'; target: Target; exists?: boolean }\n | { type: 'tour'; id: string; state: TourProgressState }\n | { type: 'all'; conditions: Condition[] }\n | { type: 'any'; conditions: Condition[] }\n | { type: 'not'; condition: Condition }\n | { type: 'custom'; name: string; args?: Record<string, TraitValue> }\n\nexport type TourProgressState = 'not-started' | 'in-progress' | 'completed' | 'skipped'\n\n/** What starts a tour. `manual` means only through the runtime API. */\nexport type Trigger =\n | { type: 'manual' }\n | { type: 'auto'; delay?: number }\n | { type: 'route'; pattern: string; delay?: number }\n | { type: 'element'; target: Target; delay?: number }\n | { type: 'event'; name: string }\n\n/**\n * How often an eligible user sees the tour.\n * - `once`: show once per {@link Tour.version}, however it ended.\n * - `until-completed`: keep offering it until the user finishes it.\n * - `always`: every time the trigger fires.\n */\nexport type Frequency = 'once' | 'until-completed' | 'always'\n\n// ---------------------------------------------------------------------------\n// Theme\n// ---------------------------------------------------------------------------\n\n/**\n * Visual tokens, serialisable so a builder or customizer can produce them.\n * Each maps to a CSS custom property in the renderer (`--docent-*`).\n * Values are CSS strings, e.g. `'#111'`, `'12px'`, `'0 4px 12px rgba(0,0,0,.2)'`.\n */\nexport interface Theme {\n background?: string\n foreground?: string\n muted?: string\n accent?: string\n accentForeground?: string\n radius?: string\n shadow?: string\n font?: string\n width?: string\n overlay?: string\n overlayOpacity?: string\n duration?: string\n zIndex?: string\n}\n\n// ---------------------------------------------------------------------------\n// Labels\n// ---------------------------------------------------------------------------\n\nexport interface Labels {\n next?: string\n back?: string\n skip?: string\n done?: string\n close?: string\n /** Supports `{current}` and `{total}` placeholders. */\n progress?: string\n}\n\n// ---------------------------------------------------------------------------\n// Step\n// ---------------------------------------------------------------------------\n\nexport interface Step {\n /** Unique within the tour. Used for persistence, hooks and analytics. */\n id: string\n /** Omit for a centred modal step (welcome / finish screens). */\n target?: Target\n title?: string\n body?: string\n /** How `body` is interpreted. Renderers never inject raw HTML. */\n format?: 'text' | 'markdown'\n media?: Media\n placement?: Placement\n /** Per-step override of the tour's spotlight options. */\n spotlight?: SpotlightOptions\n advance?: Advance\n interaction?: Interaction\n /** Skip this step when the condition is false. */\n condition?: Condition\n onMissing?: OnMissing\n /** How long to wait for the target when `onMissing` is `wait`, in ms. */\n waitFor?: number\n /** URL pattern this step belongs to. Enables multi-page tours. */\n route?: string\n buttons?: StepButtons\n scroll?: ScrollOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Tour\n// ---------------------------------------------------------------------------\n\nexport interface TourOptions {\n /** Persist progress so the tour survives navigation and reloads. */\n persist?: boolean\n frequency?: Frequency\n showProgress?: boolean\n /** Allow closing with Escape or the close button. */\n allowClose?: boolean\n closeOnOverlayClick?: boolean\n keyboard?: boolean\n spotlight?: SpotlightOptions\n overlay?: OverlayOptions\n scroll?: ScrollOptions\n labels?: Labels\n /** Visual tokens applied on top of the renderer's theme. */\n theme?: Theme\n /** Name of a template registered on the renderer (slots, css, theme). */\n template?: string\n}\n\nexport interface Tour {\n schemaVersion: SchemaVersion\n /** Stable identifier. Used for persistence, targeting and analytics. */\n id: string\n /** Bump to re-show the tour to users who already saw an older version. */\n version?: number\n /** Human-readable name, mainly for the builder and dashboards. */\n name?: string\n description?: string\n steps: Step[]\n trigger?: Trigger\n /** All must hold for the tour to be eligible. */\n conditions?: Condition[]\n options?: TourOptions\n /** Free-form extension bag for the builder or integrations. */\n meta?: Record<string, unknown>\n}\n","import { SCHEMA_VERSION, type SchemaVersion, type Tour } from './schema/tour'\n\n/**\n * Identity helper that gives hand-written tours full type inference and\n * fills in the schema version. Returns the same object.\n */\nexport function defineTour(\n tour: Omit<Tour, 'schemaVersion'> & { schemaVersion?: SchemaVersion },\n): Tour {\n return { ...tour, schemaVersion: SCHEMA_VERSION }\n}\n","/**\n * Route pattern matching for `route` triggers, conditions and step routes.\n *\n * Patterns are path globs:\n * - `/settings` exact\n * - `/users/:id` one segment (named for readability, value ignored)\n * - `/users/*` one segment\n * - `/docs/**` zero or more segments\n *\n * Query strings and hashes are ignored. Trailing slashes are tolerated.\n */\n\nfunction segments(path: string): string[] {\n const clean = path.split(/[?#]/, 1)[0] ?? ''\n return clean.split('/').filter(Boolean)\n}\n\nfunction matchSegments(pattern: string[], path: string[], pi = 0, si = 0): boolean {\n if (pi === pattern.length) return si === path.length\n const p = pattern[pi]\n if (p === '**') {\n for (let k = si; k <= path.length; k++) {\n if (matchSegments(pattern, path, pi + 1, k)) return true\n }\n return false\n }\n if (si === path.length) return false\n if (p === '*' || p?.startsWith(':') || p === path[si]) {\n return matchSegments(pattern, path, pi + 1, si + 1)\n }\n return false\n}\n\nexport function matchRoute(pattern: string, path: string): boolean {\n return matchSegments(segments(pattern), segments(path))\n}\n","/**\n * Evaluates serialisable {@link Condition}s against a runtime environment.\n * Pure: everything it needs is passed in.\n */\n\nimport type { Condition, Target, TourProgressState, TraitValue } from '../schema/tour'\nimport type { Identity } from '../seams'\nimport { matchRoute } from './route'\n\nexport type CustomPredicate = (args?: Record<string, TraitValue>) => boolean\n\nexport interface ConditionEnv {\n identity: Identity\n /** Current path, e.g. `/invoices/new`. */\n route?: string\n elementExists?: (target: Target) => boolean\n tourState?: (tourId: string) => TourProgressState\n custom?: Record<string, CustomPredicate>\n}\n\nfunction sameValue(a: TraitValue | undefined, b: TraitValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => v === b[i])\n }\n return a === b\n}\n\nfunction compare(a: TraitValue | undefined, b: TraitValue | undefined): number | null {\n if (typeof a === 'number' && typeof b === 'number') return a - b\n if (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : a > b ? 1 : 0\n return null\n}\n\nfunction contains(haystack: TraitValue | undefined, needle: TraitValue | undefined): boolean {\n if (Array.isArray(haystack)) return typeof needle === 'string' && haystack.includes(needle)\n if (typeof haystack === 'string') {\n return (\n (typeof needle === 'string' || typeof needle === 'number') &&\n haystack.includes(String(needle))\n )\n }\n return false\n}\n\nfunction inList(value: TraitValue | undefined, list: TraitValue | undefined): boolean {\n if (!Array.isArray(list)) return false\n if (Array.isArray(value)) return value.some((v) => list.includes(v))\n return typeof value === 'string' && list.includes(value)\n}\n\nexport function evaluateTrait(\n actual: TraitValue | undefined,\n op: Extract<Condition, { type: 'trait' }>['op'],\n expected: TraitValue | undefined,\n): boolean {\n switch (op) {\n case 'exists':\n return actual !== undefined && actual !== null\n case 'missing':\n return actual === undefined || actual === null\n case 'eq':\n return sameValue(actual, expected)\n case 'neq':\n return !sameValue(actual, expected)\n case 'in':\n return inList(actual, expected)\n case 'nin':\n return !inList(actual, expected)\n case 'contains':\n return contains(actual, expected)\n case 'gt':\n case 'gte':\n case 'lt':\n case 'lte': {\n const c = compare(actual, expected)\n if (c === null) return false\n if (op === 'gt') return c > 0\n if (op === 'gte') return c >= 0\n if (op === 'lt') return c < 0\n return c <= 0\n }\n }\n}\n\nexport function evaluateCondition(condition: Condition, env: ConditionEnv): boolean {\n switch (condition.type) {\n case 'trait':\n return evaluateTrait(env.identity.traits[condition.key], condition.op, condition.value)\n case 'route':\n return env.route !== undefined && matchRoute(condition.pattern, env.route)\n case 'element': {\n const exists = env.elementExists?.(condition.target) ?? false\n return exists === (condition.exists ?? true)\n }\n case 'tour':\n return (env.tourState?.(condition.id) ?? 'not-started') === condition.state\n case 'all':\n return condition.conditions.every((c) => evaluateCondition(c, env))\n case 'any':\n return condition.conditions.some((c) => evaluateCondition(c, env))\n case 'not':\n return !evaluateCondition(condition.condition, env)\n case 'custom':\n return env.custom?.[condition.name]?.(condition.args) ?? false\n }\n}\n\n/** All conditions must hold. An empty or missing list holds. */\nexport function evaluateAll(conditions: Condition[] | undefined, env: ConditionEnv): boolean {\n return (conditions ?? []).every((c) => evaluateCondition(c, env))\n}\n","/**\n * Extension seams — the interfaces the runtime is built against so that tours,\n * user identity, storage and analytics can come from anywhere: inline JSON,\n * a local file, the user's own backend, or a hosted service.\n */\n\nimport type { Tour, TraitValue } from './schema/tour'\n\nexport type MaybePromise<T> = T | Promise<T>\n\n// ---------------------------------------------------------------------------\n// Tour source\n// ---------------------------------------------------------------------------\n\nexport interface TourSource {\n /** Return every tour this source knows about. */\n load(): MaybePromise<Tour[]>\n /**\n * Optional live updates. Return an unsubscribe function.\n * Lets a hosted source push tour changes without a page reload.\n */\n subscribe?(listener: (tours: Tour[]) => void): () => void\n}\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\nexport interface Identity {\n /** Stable user id. Omit for anonymous visitors. */\n id?: string\n /** Attributes that `trait` conditions evaluate against. */\n traits: Record<string, TraitValue>\n}\n\nexport const ANONYMOUS_IDENTITY: Identity = { traits: {} }\n\n// ---------------------------------------------------------------------------\n// Storage\n// ---------------------------------------------------------------------------\n\n/**\n * Key/value storage for progress and seen-state. Shape matches `localStorage`\n * but every method may be async so React Native's AsyncStorage fits too.\n */\nexport interface StorageAdapter {\n get(key: string): MaybePromise<string | null>\n set(key: string, value: string): MaybePromise<void>\n remove(key: string): MaybePromise<void>\n}\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type DocentEventType =\n | 'tour:started'\n | 'tour:completed'\n | 'tour:skipped'\n | 'tour:aborted'\n | 'step:shown'\n | 'step:completed'\n | 'step:skipped'\n | 'step:missing'\n\nexport interface DocentEvent {\n type: DocentEventType\n tourId: string\n tourVersion: number\n stepId?: string\n stepIndex?: number\n /** Unix epoch milliseconds. */\n timestamp: number\n identity: Identity\n}\n\n/** Receives every lifecycle event. Wire it to console, your analytics, or a hosted endpoint. */\nexport interface EventSink {\n emit(event: DocentEvent): void\n}\n","/**\n * Persisted per-tour progress: what the user has seen, where they stopped,\n * and whether the tour should be offered again.\n */\n\nimport type { Frequency, Tour, TourProgressState } from '../schema/tour'\nimport type { StorageAdapter } from '../seams'\n\nexport interface TourRecord {\n tourId: string\n version: number\n state: TourProgressState\n /** Step to resume from when the tour was interrupted. */\n stepId?: string\n /** Unix epoch milliseconds. */\n updatedAt: number\n}\n\nexport const STORAGE_PREFIX = 'docent:'\n\nexport function storageKey(tourId: string): string {\n return `${STORAGE_PREFIX}${tourId}`\n}\n\nexport function tourVersion(tour: Tour): number {\n return tour.version ?? 1\n}\n\n/** Whether a tour should be offered given what the user has already done. */\nexport function shouldShow(tour: Tour, record: TourRecord | null): boolean {\n if (!record) return true\n if (record.version < tourVersion(tour)) return true\n const frequency: Frequency = tour.options?.frequency ?? 'once'\n switch (frequency) {\n case 'always':\n return true\n case 'until-completed':\n return record.state !== 'completed'\n case 'once':\n return record.state === 'not-started' || record.state === 'in-progress'\n }\n}\n\nexport class ProgressStore {\n constructor(private readonly storage: StorageAdapter) {}\n\n async get(tourId: string): Promise<TourRecord | null> {\n const raw = await this.storage.get(storageKey(tourId))\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as Partial<TourRecord>\n if (parsed.tourId !== tourId || typeof parsed.version !== 'number' || !parsed.state) {\n return null\n }\n return parsed as TourRecord\n } catch {\n return null\n }\n }\n\n async set(record: TourRecord): Promise<void> {\n await this.storage.set(storageKey(record.tourId), JSON.stringify(record))\n }\n\n async clear(tourId: string): Promise<void> {\n await this.storage.remove(storageKey(tourId))\n }\n}\n\n/** In-memory adapter. Default when no storage is configured, and handy in tests. */\nexport function createMemoryStorage(): StorageAdapter {\n const map = new Map<string, string>()\n return {\n get: (key) => map.get(key) ?? null,\n set: (key, value) => {\n map.set(key, value)\n },\n remove: (key) => {\n map.delete(key)\n },\n }\n}\n","import type { Tour } from '../schema/tour'\nimport type { DocentEvent, DocentEventType, EventSink, Identity } from '../seams'\nimport { tourVersion } from './progress'\n\nexport interface EventInput {\n tour: Tour\n identity: Identity\n stepIndex?: number\n now?: () => number\n}\n\nexport function createEvent(type: DocentEventType, input: EventInput): DocentEvent {\n const event: DocentEvent = {\n type,\n tourId: input.tour.id,\n tourVersion: tourVersion(input.tour),\n timestamp: (input.now ?? Date.now)(),\n identity: input.identity,\n }\n if (input.stepIndex !== undefined && input.stepIndex >= 0) {\n const step = input.tour.steps[input.stepIndex]\n event.stepIndex = input.stepIndex\n if (step) event.stepId = step.id\n }\n return event\n}\n\n/** Sink that drops everything. Default when none is configured. */\nexport const NOOP_SINK: EventSink = { emit() {} }\n\n/** Fan out to several sinks. */\nexport function combineSinks(...sinks: EventSink[]): EventSink {\n return {\n emit: (e) => {\n for (const s of sinks) s.emit(e)\n },\n }\n}\n","/**\n * Pure state machine for a single running tour. No side effects, no timers,\n * no DOM. Eligibility of steps (conditions, missing targets) is supplied by\n * the caller through {@link EngineContext} so the reducer stays pure.\n */\n\nimport type { Tour } from '../schema/tour'\n\nexport type TourStatus = 'idle' | 'running' | 'paused' | 'completed' | 'skipped' | 'aborted'\n\nexport interface EngineState {\n status: TourStatus\n /** Index into `tour.steps`, or -1 when not running. */\n index: number\n /** Indices of previously shown steps, oldest first. Drives `back`. */\n history: number[]\n /** Set when status is `aborted` or `paused`. */\n reason?: string\n}\n\nexport type EngineAction =\n | { type: 'start'; at?: number | string }\n | { type: 'next' }\n | { type: 'back' }\n | { type: 'go'; to: number | string }\n | { type: 'skip' }\n | { type: 'complete' }\n | { type: 'abort'; reason: string }\n | { type: 'pause'; reason: string }\n | { type: 'resume' }\n | { type: 'stepMissing' }\n | { type: 'stepSkipped' }\n\nexport interface EngineContext {\n tour: Tour\n /** Whether the step at `index` may be shown right now. */\n isEligible: (index: number) => boolean\n}\n\nexport const IDLE_STATE: EngineState = Object.freeze({ status: 'idle', index: -1, history: [] })\n\nexport function resolveStepIndex(tour: Tour, ref: number | string): number {\n if (typeof ref === 'number') return ref >= 0 && ref < tour.steps.length ? ref : -1\n return tour.steps.findIndex((s) => s.id === ref)\n}\n\n/** First eligible index at or after `from` (or at or before, when `dir` is -1). -1 if none. */\nexport function findEligible(ctx: EngineContext, from: number, dir: 1 | -1 = 1): number {\n const total = ctx.tour.steps.length\n for (let i = from; i >= 0 && i < total; i += dir) {\n if (ctx.isEligible(i)) return i\n }\n return -1\n}\n\nfunction advance(state: EngineState, ctx: EngineContext, record: boolean): EngineState {\n const next = findEligible(ctx, state.index + 1)\n if (next === -1) return { status: 'completed', index: state.index, history: state.history }\n return {\n status: 'running',\n index: next,\n history: record ? [...state.history, state.index] : state.history,\n }\n}\n\nexport function reduce(state: EngineState, action: EngineAction, ctx: EngineContext): EngineState {\n switch (action.type) {\n case 'start': {\n const at = action.at === undefined ? 0 : resolveStepIndex(ctx.tour, action.at)\n if (at === -1) return { status: 'aborted', index: -1, history: [], reason: 'unknown-step' }\n const first = findEligible(ctx, at)\n if (first === -1) {\n return { status: 'aborted', index: -1, history: [], reason: 'no-eligible-steps' }\n }\n return { status: 'running', index: first, history: [] }\n }\n\n case 'next':\n if (state.status !== 'running') return state\n return advance(state, ctx, true)\n\n case 'stepMissing': {\n if (state.status !== 'running') return state\n const step = ctx.tour.steps[state.index]\n if (step?.onMissing === 'abort') {\n return { ...state, status: 'aborted', reason: 'target-missing' }\n }\n // 'skip' and 'wait' (after the wait elapsed) both move on without recording history.\n return advance(state, ctx, false)\n }\n\n case 'stepSkipped':\n if (state.status !== 'running') return state\n return advance(state, ctx, false)\n\n case 'back': {\n if (state.status !== 'running') return state\n const history = [...state.history]\n let prev = history.pop()\n // Skip over history entries that are no longer eligible.\n while (prev !== undefined && !ctx.isEligible(prev)) prev = history.pop()\n if (prev === undefined) return state\n return { status: 'running', index: prev, history }\n }\n\n case 'go': {\n if (state.status !== 'running') return state\n const to = resolveStepIndex(ctx.tour, action.to)\n if (to === -1 || to === state.index || !ctx.isEligible(to)) return state\n return { status: 'running', index: to, history: [...state.history, state.index] }\n }\n\n case 'pause':\n if (state.status !== 'running') return state\n return { ...state, status: 'paused', reason: action.reason }\n\n case 'resume': {\n if (state.status !== 'paused') return state\n const { reason: _reason, ...rest } = state\n return { ...rest, status: 'running' }\n }\n\n case 'skip':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'skipped', index: state.index, history: state.history }\n\n case 'complete':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { status: 'completed', index: state.index, history: state.history }\n\n case 'abort':\n if (state.status !== 'running' && state.status !== 'paused') return state\n return { ...state, status: 'aborted', reason: action.reason }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Selectors\n// ---------------------------------------------------------------------------\n\nexport function isActive(state: EngineState): boolean {\n return state.status === 'running' || state.status === 'paused'\n}\n\nexport function isFinished(state: EngineState): boolean {\n return state.status === 'completed' || state.status === 'skipped' || state.status === 'aborted'\n}\n\nexport function canGoBack(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && state.history.some((i) => ctx.isEligible(i))\n}\n\n/** True when another eligible step follows. False on the last step. */\nexport function hasNext(state: EngineState, ctx: EngineContext): boolean {\n return state.status === 'running' && findEligible(ctx, state.index + 1) !== -1\n}\n\nexport interface Progress {\n /** 1-based position of the current step. */\n current: number\n total: number\n}\n\n/** Position over all steps, ineligible ones included, so numbers stay stable. */\nexport function progress(state: EngineState, ctx: EngineContext): Progress {\n return { current: state.index + 1, total: ctx.tour.steps.length }\n}\n","/**\n * Orchestrates one tour: drives the pure reducer, talks to the renderer,\n * runs hooks, persists progress and emits events. This is the only stateful,\n * side-effectful piece of the core.\n */\n\nimport type { StepContext, TourHooks } from '../hooks'\nimport type { Step, Tour, TourProgressState } from '../schema/tour'\nimport { ANONYMOUS_IDENTITY, type EventSink, type Identity, type StorageAdapter } from '../seams'\nimport { type ConditionEnv, type CustomPredicate, evaluateCondition } from './conditions'\nimport { createEvent, NOOP_SINK } from './events'\nimport { createMemoryStorage, ProgressStore, type TourRecord, tourVersion } from './progress'\nimport {\n canGoBack,\n type EngineAction,\n type EngineContext,\n type EngineState,\n hasNext,\n IDLE_STATE,\n isFinished,\n progress,\n reduce,\n} from './reducer'\nimport type { RenderContext, Renderer } from './renderer'\nimport { matchRoute } from './route'\n\nexport interface ControllerOptions {\n tour: Tour\n renderer: Renderer\n identity?: Identity\n storage?: StorageAdapter\n sink?: EventSink\n hooks?: TourHooks\n /** Predicates for `custom` conditions, by name. */\n custom?: Record<string, CustomPredicate>\n /** Progress of other tours, for `tour` conditions. */\n tourState?: (tourId: string) => TourProgressState\n /** How long `onMissing: 'wait'` waits when the step sets no `waitFor`. */\n defaultWaitMs?: number\n now?: () => number\n}\n\nexport type StateListener = (state: EngineState) => void\n\nconst DEFAULT_WAIT_MS = 3000\n\nexport class TourController {\n readonly tour: Tour\n private state: EngineState = IDLE_STATE\n private readonly renderer: Renderer\n private readonly identity: Identity\n private readonly store: ProgressStore\n private readonly sink: EventSink\n private readonly hooks: TourHooks\n private readonly custom: Record<string, CustomPredicate>\n private readonly tourStateOf: ((tourId: string) => TourProgressState) | undefined\n private readonly defaultWaitMs: number\n private readonly now: () => number\n private readonly listeners = new Set<StateListener>()\n\n /** Bumped whenever an async flow must be abandoned. */\n private generation = 0\n private pendingAbort: AbortController | undefined\n private pendingTimer: ReturnType<typeof setTimeout> | undefined\n\n constructor(options: ControllerOptions) {\n this.tour = options.tour\n this.renderer = options.renderer\n this.identity = options.identity ?? ANONYMOUS_IDENTITY\n this.store = new ProgressStore(options.storage ?? createMemoryStorage())\n this.sink = options.sink ?? NOOP_SINK\n this.hooks = options.hooks ?? {}\n this.custom = options.custom ?? {}\n this.tourStateOf = options.tourState\n this.defaultWaitMs = options.defaultWaitMs ?? DEFAULT_WAIT_MS\n this.now = options.now ?? Date.now\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n\n getState(): EngineState {\n return this.state\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener)\n return () => this.listeners.delete(listener)\n }\n\n /** Start from the first eligible step, or from `at` (step id or index). */\n async start(at?: number | string): Promise<void> {\n if (this.state.status === 'running' || this.state.status === 'paused') return\n this.cancelPending()\n this.dispatch(at === undefined ? { type: 'start' } : { type: 'start', at })\n if (isFinished(this.state)) return this.finish()\n this.emit('tour:started')\n this.hooks.onStart?.(this.tour)\n await this.persist('in-progress')\n await this.showCurrent()\n }\n\n /** Start where the user left off, according to persisted progress. */\n async resume(): Promise<void> {\n const record = await this.store.get(this.tour.id)\n if (record?.state === 'in-progress' && record.version === tourVersion(this.tour)) {\n return this.start(record.stepId)\n }\n return this.start()\n }\n\n async next(): Promise<void> {\n if (this.state.status !== 'running') return\n await this.leaveCurrent()\n this.emit('step:completed', this.state.index)\n this.dispatch({ type: 'next' })\n await this.afterTransition()\n }\n\n async back(): Promise<void> {\n if (!canGoBack(this.state, this.context())) return\n await this.leaveCurrent()\n this.dispatch({ type: 'back' })\n await this.afterTransition()\n }\n\n async goTo(step: number | string): Promise<void> {\n if (this.state.status !== 'running') return\n const before = this.state\n const after = reduce(before, { type: 'go', to: step }, this.context())\n if (after === before) return\n await this.leaveCurrent()\n this.setState(after)\n await this.afterTransition()\n }\n\n /** The user gave up on the tour (Skip button, close, Escape). */\n async skip(): Promise<void> {\n if (!this.isActive()) return\n const ctx = this.stepContext()\n await this.leaveCurrent()\n this.dispatch({ type: 'skip' })\n await this.finish()\n if (ctx) this.hooks.onSkip?.(ctx)\n }\n\n async abort(reason: string): Promise<void> {\n if (!this.isActive()) return\n await this.leaveCurrent()\n this.dispatch({ type: 'abort', reason })\n await this.finish()\n }\n\n /** Report a named application event. Advances a step waiting on it. */\n notify(eventName: string): void {\n const step = this.currentStep()\n const advance = step?.advance\n if (typeof advance === 'object' && advance.on === 'event' && advance.name === eventName) {\n void this.next()\n }\n }\n\n /** Tell the controller the route changed. Pauses or resumes route-bound steps. */\n async routeChanged(): Promise<void> {\n const step = this.currentStep()\n if (!step) return\n const onRoute = this.stepOnRoute(step)\n if (this.state.status === 'paused' && this.state.reason === 'route' && onRoute) {\n this.dispatch({ type: 'resume' })\n await this.showCurrent()\n } else if (this.state.status === 'running' && !onRoute) {\n this.cancelPending()\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n }\n }\n\n /** Stop everything and clear the screen without recording an outcome. */\n async destroy(): Promise<void> {\n this.cancelPending()\n this.listeners.clear()\n await this.renderer.hide()\n this.state = IDLE_STATE\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private isActive(): boolean {\n return this.state.status === 'running' || this.state.status === 'paused'\n }\n\n private currentStep(): Step | undefined {\n return this.isActive() ? this.tour.steps[this.state.index] : undefined\n }\n\n private conditionEnv(): ConditionEnv {\n const env: ConditionEnv = {\n identity: this.identity,\n elementExists: (t) => this.renderer.hasTarget(t),\n custom: this.custom,\n }\n const route = this.renderer.currentRoute?.()\n if (route !== undefined) env.route = route\n if (this.tourStateOf) env.tourState = this.tourStateOf\n return env\n }\n\n private context(): EngineContext {\n const env = this.conditionEnv()\n return {\n tour: this.tour,\n isEligible: (i) => {\n const step = this.tour.steps[i]\n if (!step) return false\n return step.condition ? evaluateCondition(step.condition, env) : true\n },\n }\n }\n\n private stepOnRoute(step: Step): boolean {\n const route = this.renderer.currentRoute?.()\n if (!step.route || route === undefined) return true\n return matchRoute(step.route, route)\n }\n\n private dispatch(action: EngineAction): void {\n this.setState(reduce(this.state, action, this.context()))\n }\n\n private setState(next: EngineState): void {\n if (next === this.state) return\n this.state = next\n for (const l of this.listeners) l(next)\n }\n\n private stepContext(): StepContext | undefined {\n const step = this.currentStep()\n if (!step) return undefined\n return { tour: this.tour, step, index: this.state.index, total: this.tour.steps.length }\n }\n\n private emit(type: Parameters<typeof createEvent>[0], stepIndex?: number): void {\n const input: Parameters<typeof createEvent>[1] = {\n tour: this.tour,\n identity: this.identity,\n now: this.now,\n }\n if (stepIndex !== undefined) input.stepIndex = stepIndex\n this.sink.emit(createEvent(type, input))\n }\n\n private async persist(state: TourProgressState): Promise<void> {\n const record: TourRecord = {\n tourId: this.tour.id,\n version: tourVersion(this.tour),\n state,\n updatedAt: this.now(),\n }\n const step = this.currentStep()\n if (state === 'in-progress' && step) record.stepId = step.id\n await this.store.set(record)\n }\n\n private cancelPending(): void {\n this.generation++\n this.pendingAbort?.abort()\n this.pendingAbort = undefined\n if (this.pendingTimer !== undefined) clearTimeout(this.pendingTimer)\n this.pendingTimer = undefined\n }\n\n /** Run `beforeHide` for the step being left, if any. */\n private async leaveCurrent(): Promise<void> {\n const ctx = this.stepContext()\n this.cancelPending()\n if (ctx && this.state.status === 'running') {\n await this.hooks.steps?.[ctx.step.id]?.beforeHide?.(ctx)\n }\n }\n\n private async afterTransition(): Promise<void> {\n if (this.state.status === 'running') return this.showCurrent()\n if (isFinished(this.state)) return this.finish()\n }\n\n private async finish(): Promise<void> {\n this.cancelPending()\n await this.renderer.hide()\n switch (this.state.status) {\n case 'completed':\n this.emit('tour:completed')\n await this.persist('completed')\n this.hooks.onComplete?.(this.tour)\n break\n case 'skipped':\n this.emit('tour:skipped')\n await this.persist('skipped')\n break\n case 'aborted':\n this.emit('tour:aborted')\n await this.persist('skipped')\n this.hooks.onAbort?.(this.tour, this.state.reason ?? 'unknown')\n break\n }\n }\n\n private async showCurrent(): Promise<void> {\n this.cancelPending()\n const generation = this.generation\n const stale = () => generation !== this.generation\n const ctx = this.stepContext()\n if (!ctx) return\n const { step } = ctx\n\n if (!this.stepOnRoute(step)) {\n this.dispatch({ type: 'pause', reason: 'route' })\n await this.renderer.hide()\n return\n }\n\n const proceed = await this.hooks.steps?.[step.id]?.beforeShow?.(ctx)\n if (stale()) return\n if (proceed === false) {\n this.emit('step:skipped', this.state.index)\n this.dispatch({ type: 'stepSkipped' })\n return this.afterTransition()\n }\n\n if (step.target !== undefined && !(await this.ensureTarget(step, generation))) {\n if (stale()) return\n this.emit('step:missing', this.state.index)\n this.dispatch({ type: 'stepMissing' })\n return this.afterTransition()\n }\n if (stale()) return\n\n await this.renderer.show(this.renderContext(ctx))\n if (stale()) return\n this.emit('step:shown', this.state.index)\n this.hooks.onStepChange?.(ctx)\n await this.persist('in-progress')\n await this.hooks.steps?.[step.id]?.afterShow?.(ctx)\n if (stale()) return\n this.armAdvance(step, generation)\n }\n\n /** Resolve `true` when the target is present, waiting if the step allows it. */\n private async ensureTarget(step: Step, generation: number): Promise<boolean> {\n const target = step.target\n if (target === undefined) return true\n if (this.renderer.hasTarget(target)) return true\n const waitMs = step.waitFor ?? (step.onMissing === 'wait' ? this.defaultWaitMs : 0)\n if (waitMs <= 0) return false\n const abort = new AbortController()\n this.pendingAbort = abort\n const found = await this.renderer.waitForTarget(target, waitMs, abort.signal)\n if (generation !== this.generation) return false\n this.pendingAbort = undefined\n return found\n }\n\n /** Set up automatic advancement for `delay` and `element` steps. */\n private armAdvance(step: Step, generation: number): void {\n const advance = step.advance\n if (typeof advance !== 'object') return\n if (advance.on === 'delay') {\n this.pendingTimer = setTimeout(() => {\n this.pendingTimer = undefined\n if (generation === this.generation) void this.next()\n }, advance.ms)\n } else if (advance.on === 'element') {\n const abort = new AbortController()\n this.pendingAbort = abort\n void this.renderer\n .waitForTarget(advance.target, Number.POSITIVE_INFINITY, abort.signal)\n .then((found) => {\n if (found && generation === this.generation) void this.next()\n })\n }\n }\n\n private renderContext(ctx: StepContext): RenderContext {\n const engineCtx = this.context()\n return {\n tour: this.tour,\n step: ctx.step,\n index: ctx.index,\n progress: progress(this.state, engineCtx),\n isFirst: this.state.history.length === 0,\n isLast: !hasNext(this.state, engineCtx),\n canGoBack: canGoBack(this.state, engineCtx),\n actions: {\n next: () => void this.next(),\n back: () => void this.back(),\n skip: () => void this.skip(),\n goTo: (s) => void this.goTo(s),\n },\n }\n }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@docentjs/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Platform-agnostic tour engine: schema, state machine, triggers, persistence, events. No DOM access.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/FgrReloaded/docentjs.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/FgrReloaded/docentjs/tree/main/packages/core#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/FgrReloaded/docentjs/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"product-tour",
|
|
17
|
+
"guided-tour",
|
|
18
|
+
"onboarding",
|
|
19
|
+
"walkthrough",
|
|
20
|
+
"spotlight",
|
|
21
|
+
"tooltip",
|
|
22
|
+
"docent"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"main": "./dist/index.cjs",
|
|
27
|
+
"module": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/index.d.cts",
|
|
37
|
+
"default": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsdown",
|
|
50
|
+
"dev": "tsdown --watch",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"clean": "rm -rf dist .turbo"
|
|
53
|
+
}
|
|
54
|
+
}
|