@typecaast/react 0.3.1 → 0.4.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/dist/index.cjs CHANGED
@@ -78,12 +78,14 @@ function useTypecaast(config, options = {}) {
78
78
  player.on("play", () => setPlaying(true)),
79
79
  player.on("pause", () => setPlaying(false))
80
80
  ];
81
- if (autoplay) player.play();
82
81
  return () => {
83
82
  offs.forEach((off) => off());
84
83
  player.destroy();
85
84
  };
86
- }, [player, autoplay]);
85
+ }, [player]);
86
+ react.useEffect(() => {
87
+ if (autoplay) player.play();
88
+ }, [player]);
87
89
  return {
88
90
  state,
89
91
  currentMs,
@@ -305,35 +307,37 @@ function rootStyle(canvas) {
305
307
  aspectRatio: `${canvas.width} / ${canvas.height}`
306
308
  };
307
309
  }
308
- function Typecaast(props) {
309
- const config = react.useMemo(
310
- () => schema.configSchema.parse(props.config),
311
- [props.config]
312
- );
313
- if (props.skin)
314
- return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props, config, skin: props.skin });
315
- return /* @__PURE__ */ jsxRuntime.jsx(
316
- react.Suspense,
317
- {
318
- fallback: /* @__PURE__ */ jsxRuntime.jsx(
319
- SkinFallback,
320
- {
321
- config,
322
- fit: props.fit,
323
- label: props.label,
324
- className: props.className,
325
- style: props.style
326
- }
327
- ),
328
- children: /* @__PURE__ */ jsxRuntime.jsx(ResolvedPlayer, { ...props, config })
329
- }
330
- );
331
- }
332
- function ResolvedPlayer(props) {
310
+ var Typecaast = react.forwardRef(
311
+ function Typecaast2(props, ref) {
312
+ const config = react.useMemo(
313
+ () => schema.configSchema.parse(props.config),
314
+ [props.config]
315
+ );
316
+ if (props.skin)
317
+ return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props, config, skin: props.skin, ref });
318
+ return /* @__PURE__ */ jsxRuntime.jsx(
319
+ react.Suspense,
320
+ {
321
+ fallback: /* @__PURE__ */ jsxRuntime.jsx(
322
+ SkinFallback,
323
+ {
324
+ config,
325
+ fit: props.fit,
326
+ label: props.label,
327
+ className: props.className,
328
+ style: props.style
329
+ }
330
+ ),
331
+ children: /* @__PURE__ */ jsxRuntime.jsx(ResolvedPlayer, { ...props, config, ref })
332
+ }
333
+ );
334
+ }
335
+ );
336
+ var ResolvedPlayer = react.forwardRef(function ResolvedPlayer2(props, ref) {
333
337
  const skin = readBuiltinSkin(props.config.meta.skin.id);
334
- return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props, skin });
335
- }
336
- function Player({
338
+ return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props, skin, ref });
339
+ });
340
+ var Player = react.forwardRef(function Player2({
337
341
  config,
338
342
  skin,
339
343
  theme,
@@ -344,20 +348,61 @@ function Player({
344
348
  composer,
345
349
  label,
346
350
  className,
347
- style
348
- }) {
351
+ style,
352
+ paused,
353
+ onPlay,
354
+ onPause,
355
+ onEnded
356
+ }, ref) {
349
357
  const reduced = useReducedMotion();
350
358
  const tc = useTypecaast(config, {
351
359
  theme,
352
- autoplay: autoplay && !reduced,
360
+ // Gate mount-autoplay with `!paused` so a `paused`-at-mount instance never
361
+ // flashes a frame of playback. `!paused` is `true` when uncontrolled.
362
+ autoplay: autoplay && !reduced && !paused,
353
363
  loop: loop && !reduced,
354
364
  rate,
355
365
  capabilities: skin.meta.capabilities
356
366
  });
367
+ const player = tc.player;
357
368
  const fonts = useSkinFonts(skin);
358
369
  react.useEffect(() => {
359
370
  if (reduced) tc.seek(tc.duration);
360
371
  }, [reduced, tc]);
372
+ react.useEffect(() => {
373
+ if (paused === void 0 || reduced) return;
374
+ if (paused) tc.pause();
375
+ else tc.play();
376
+ }, [paused, reduced, tc]);
377
+ react.useEffect(() => {
378
+ const offs = [];
379
+ if (onPlay) offs.push(player.on("play", onPlay));
380
+ if (onPause) offs.push(player.on("pause", onPause));
381
+ if (onEnded) offs.push(player.on("end", onEnded));
382
+ return () => offs.forEach((off) => off());
383
+ }, [player, onPlay, onPause, onEnded]);
384
+ react.useImperativeHandle(
385
+ ref,
386
+ () => ({
387
+ play: () => player.play(),
388
+ pause: () => player.pause(),
389
+ seek: (t) => player.seek(t),
390
+ scrubTo: (t) => player.scrubTo(t),
391
+ setRate: (r) => player.setRate(r),
392
+ stepNext: () => player.stepNext(),
393
+ stepPrev: () => player.stepPrev(),
394
+ get currentMs() {
395
+ return player.currentMs;
396
+ },
397
+ get duration() {
398
+ return player.durationMs;
399
+ },
400
+ get playing() {
401
+ return player.playing;
402
+ }
403
+ }),
404
+ [player]
405
+ );
361
406
  const transcript = react.useMemo(() => buildTranscript(config), [config]);
362
407
  return /* @__PURE__ */ jsxRuntime.jsxs(
363
408
  "div",
@@ -387,7 +432,7 @@ function Player({
387
432
  ]
388
433
  }
389
434
  );
390
- }
435
+ });
391
436
  function SkinFallback({
392
437
  config,
393
438
  fit,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engine-adapter.ts","../src/use-resolved-theme.ts","../src/use-typecaast.ts","../src/use-skin-fonts.ts","../src/use-reduced-motion.ts","../src/transcript.ts","../src/fit-box.tsx","../src/builtin-skins.ts","../src/typecaast.tsx","../src/resolve-theme.ts"],"names":["createEngine","useSyncExternalStore","useMemo","createPlayer","useState","useEffect","loadSkinFonts","QUERY","getMql","subscribe","getSnapshot","getServerSnapshot","useRef","useLayoutEffect","jsx","configSchema","Suspense","jsxs","TypecaastStage"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Config } from \"@typecaast/schema\";\nimport {\n createEngine,\n type Capabilities,\n type EngineHandle,\n type ResolvedTheme,\n} from \"@typecaast/core\";\n\nexport type Engine = EngineHandle;\n\n/**\n * The single seam between a config and a playable engine. M1-UI ran this over a\n * hand-mocked timeline; M1-engine swaps in the real `compile` + `getStateAt`\n * here — and nothing else in the renderer changed (same `Engine` shape).\n *\n * Optional `capabilities` (from the active skin) drop unsupported events/content\n * from the sampled state while leaving the config intact.\n */\nexport function configToEngine(\n config: Config,\n theme: ResolvedTheme,\n capabilities?: Capabilities,\n): Engine {\n return createEngine(config, theme, capabilities);\n}\n","import { useSyncExternalStore } from \"react\";\nimport type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\nconst QUERY = \"(prefers-color-scheme: dark)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nfunction getSnapshot(): boolean {\n return getMql()?.matches ?? false;\n}\n\n/** No `matchMedia` on the server → default to light (consistent with export). */\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\n/** Reactively tracks the host's `prefers-color-scheme: dark`. */\nexport function usePrefersDark(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\n/**\n * Resolve a theme mode to a concrete theme. `light`/`dark` are forced; `auto`\n * tracks the host `prefers-color-scheme` reactively and falls back to `light`\n * when no preference signal is available.\n */\nexport function useResolvedTheme(mode: ThemeMode): ResolvedTheme {\n const prefersDark = usePrefersDark();\n if (mode === \"light\") return \"light\";\n if (mode === \"dark\") return \"dark\";\n return prefersDark ? \"dark\" : \"light\";\n}\n","import { useEffect, useMemo, useState } from \"react\";\nimport type { Config, ThemeMode } from \"@typecaast/schema\";\nimport {\n createPlayer,\n type Capabilities,\n type Player,\n type SimState,\n} from \"@typecaast/core\";\nimport { configToEngine } from \"./engine-adapter.js\";\nimport { useResolvedTheme } from \"./use-resolved-theme.js\";\n\nexport interface UseTypecaastOptions {\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** The active skin's capabilities, to drop what it can't render. */\n capabilities?: Capabilities;\n}\n\n/** Imperative controls + live state returned by {@link useTypecaast}. */\nexport interface TypecaastControls {\n state: SimState;\n /** Current playback time in ms (reactive). */\n currentMs: number;\n play(): void;\n pause(): void;\n seek(timeMs: number): void;\n scrubTo(timeMs: number): void;\n setRate(rate: number): void;\n stepNext(): void;\n stepPrev(): void;\n duration: number;\n rate: number;\n playing: boolean;\n /** Escape hatch to the underlying player. */\n player: Player;\n}\n\n/**\n * Mount a player for a config and expose live state + controls. The player\n * owns the clock (rAF in the browser); this hook bridges its ticks into React\n * state. The builder uses these controls for preview-as-you-go editing.\n *\n * In M1-UI the player runs over the mocked engine (see engine-adapter); the\n * hook's surface is the final one and does not change when the real engine\n * lands.\n */\nexport function useTypecaast(\n config: Config,\n options: UseTypecaastOptions = {},\n): TypecaastControls {\n const {\n theme,\n autoplay = false,\n // `loop` falls back to `config.meta.loop` so a config authored with looping\n // behaves the same in the builder preview and in zero-prop embeds.\n loop = config.meta.loop ?? false,\n rate = 1,\n capabilities,\n } = options;\n const resolved = useResolvedTheme(theme ?? config.meta.theme);\n\n const player = useMemo<Player>(() => {\n const engine = configToEngine(config, resolved, capabilities);\n return createPlayer(engine.getStateAt, {\n durationMs: engine.durationMs,\n steps: engine.steps,\n loop,\n rate,\n });\n }, [config, resolved, capabilities, loop, rate]);\n\n const [state, setState] = useState<SimState>(() => player.state);\n const [currentMs, setCurrentMs] = useState<number>(() => player.currentMs);\n const [playing, setPlaying] = useState<boolean>(() => player.playing);\n\n useEffect(() => {\n const sync = () => {\n setState(player.state);\n setCurrentMs(player.currentMs);\n };\n sync();\n setPlaying(player.playing);\n const offs = [\n player.on(\"tick\", sync),\n player.on(\"seek\", sync),\n player.on(\"play\", () => setPlaying(true)),\n player.on(\"pause\", () => setPlaying(false)),\n ];\n if (autoplay) player.play();\n return () => {\n offs.forEach((off) => off());\n player.destroy();\n };\n }, [player, autoplay]);\n\n return {\n state,\n currentMs,\n play: () => player.play(),\n pause: () => player.pause(),\n seek: (t) => player.seek(t),\n scrubTo: (t) => player.scrubTo(t),\n setRate: (r) => player.setRate(r),\n stepNext: () => player.stepNext(),\n stepPrev: () => player.stepPrev(),\n duration: player.durationMs,\n rate: player.rate,\n playing,\n player,\n };\n}\n","import { useEffect, useState } from \"react\";\nimport { loadSkinFonts, type Skin } from \"@typecaast/skin-kit\";\n\nexport type FontLoadState = \"loading\" | \"loaded\";\n\n/**\n * Load a skin's declared web fonts on mount so the live preview renders in the\n * correct typeface (PLAN §19) — never relying on a host OS font. SSR-safe and\n * a no-op off the DOM (resolves \"loaded\"). Re-runs if the skin's fonts change.\n */\nexport function useSkinFonts(skin: Skin): FontLoadState {\n const fonts = skin.meta.fonts;\n const [state, setState] = useState<FontLoadState>(() =>\n fonts && fonts.length > 0 ? \"loading\" : \"loaded\",\n );\n\n useEffect(() => {\n if (!fonts || fonts.length === 0) {\n setState(\"loaded\");\n return;\n }\n let cancelled = false;\n setState(\"loading\");\n loadSkinFonts(fonts).finally(() => {\n if (!cancelled) setState(\"loaded\");\n });\n return () => {\n cancelled = true;\n };\n }, [fonts]);\n\n return state;\n}\n","import { useSyncExternalStore } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nconst getSnapshot = (): boolean => getMql()?.matches ?? false;\nconst getServerSnapshot = (): boolean => false;\n\n/**\n * Tracks `prefers-reduced-motion: reduce`. When true, the player snaps to the\n * final state instead of animating (PLAN §20).\n */\nexport function useReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import type { Config } from \"@typecaast/schema\";\n\nexport interface TranscriptLine {\n name: string;\n text: string;\n}\n\n/**\n * Build a plain-text transcript from the authored config — the accessible\n * representation of the conversation for screen readers (PLAN §20). The config\n * is already structured text, so this is a faithful, non-animated version.\n */\nexport function buildTranscript(config: Config): TranscriptLine[] {\n const name = new Map(config.participants.map((p) => [p.id, p.name]));\n const lines: TranscriptLine[] = [];\n for (const step of config.timeline) {\n let from: string | undefined;\n let text: string | undefined;\n if (step.type === \"message\" || step.type === \"system\") {\n from = step.from;\n text = step.text;\n } else if (step.type === \"composerType\") {\n from = step.from;\n text = step.text;\n }\n if (text) {\n lines.push({ name: from ? (name.get(from) ?? from) : \"System\", text });\n }\n }\n return lines;\n}\n","import {\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { FitMode, Size } from \"@typecaast/schema\";\n\nexport interface FitBoxProps {\n fit: FitMode;\n canvas: Size;\n children: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * Applies the `fit` strategy between the authoring canvas and the host\n * container (PLAN §7):\n * - `reflow`: **fills both axes** (width + height); content re-wraps to the\n * container width and the bottom-anchored thread clips older messages\n * when they overflow vertically. The widget is container-driven — it\n * never grows past its host as more steps play.\n * - `scale`: renders at exact canvas size, CSS-scaled to fit (layout\n * preserved). Use when you want the skin to look like its native canvas\n * regardless of container size.\n * - `fixed`: exact canvas size, clipped — the only mode where the widget\n * is *not* container-driven.\n */\nexport function FitBox({\n fit,\n canvas,\n children,\n className,\n style,\n}: FitBoxProps): ReactElement {\n const ref = useRef<HTMLDivElement>(null);\n const [container, setContainer] = useState<{ w: number; h: number } | null>(\n null,\n );\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const ro = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect;\n if (rect) setContainer({ w: rect.width, h: rect.height });\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n if (fit === \"reflow\") {\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"reflow\"\n // `height: 100%` closes the height chain: combined with the parent\n // `<Typecaast>` outer's `aspect-ratio` fallback, the widget fills\n // its host container instead of growing taller as more messages\n // play (the skin's bottom-anchored thread + `overflow: hidden`\n // clip older steps off the top).\n style={{ width: \"100%\", height: \"100%\", ...style }}\n >\n {children}\n </div>\n );\n }\n\n if (fit === \"fixed\") {\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"fixed\"\n style={{\n width: canvas.width,\n height: canvas.height,\n overflow: \"hidden\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n }\n\n // scale: fit the exact-size canvas into the measured container.\n const scale = container\n ? Math.min(container.w / canvas.width, container.h / canvas.height)\n : 1;\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"scale\"\n style={{ width: \"100%\", height: \"100%\", overflow: \"hidden\", ...style }}\n >\n <div\n data-fit-canvas=\"\"\n style={{\n width: canvas.width,\n height: canvas.height,\n transform: `scale(${scale})`,\n transformOrigin: \"top left\",\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n","import type { Skin } from \"@typecaast/skin-kit\";\n\ntype SkinModule = { default: Skin };\n\n/**\n * Lazy loaders for the built-in skins, keyed by `meta.skin.id`. Each value is a\n * **static** `import()` of a per-skin subpath, so bundlers emit one chunk per\n * skin and only the skin a config actually references is fetched. Custom skins\n * bypass this entirely (pass the `skin` prop to `<Typecaast>`).\n *\n * Adding a built-in skin = add one line here + the subpath export in\n * `@typecaast/skins`.\n */\nexport const BUILTIN_SKIN_LOADERS: Record<string, () => Promise<SkinModule>> = {\n slack: () => import(\"@typecaast/skins/slack\"),\n telegram: () => import(\"@typecaast/skins/telegram\"),\n \"claude-code\": () => import(\"@typecaast/skins/claude-code\"),\n imessage: () => import(\"@typecaast/skins/imessage\"),\n whatsapp: () => import(\"@typecaast/skins/whatsapp\"),\n cursor: () => import(\"@typecaast/skins/cursor\"),\n \"messages-macos\": () => import(\"@typecaast/skins/messages-macos\"),\n discord: () => import(\"@typecaast/skins/discord\"),\n};\n\n/** Ids of the built-in skins resolvable by `<Typecaast>` without a `skin` prop. */\nexport const builtinSkinIds = Object.keys(BUILTIN_SKIN_LOADERS);\n\n/**\n * A status-tracked load, cached per id. Tracking the settled state lets us\n * Suspense-read it on **React 18 and 19** alike (via the throw-the-promise\n * primitive) instead of React 19's `use()`, which doesn't exist on 18.\n */\ninterface SkinResource {\n promise: Promise<Skin>;\n status: \"pending\" | \"fulfilled\" | \"rejected\";\n value?: Skin;\n error?: unknown;\n}\n\n// Stable resource per id so the same promise is seen across renders.\nconst cache = new Map<string, SkinResource>();\n\n/**\n * Get (or create) the cached resource for a skin id. Throws synchronously for an\n * unknown id (a render error with a clear message), rather than suspending forever.\n */\nfunction getResource(id: string): SkinResource {\n let resource = cache.get(id);\n if (resource) return resource;\n\n const loader = BUILTIN_SKIN_LOADERS[id];\n if (!loader) {\n throw new Error(\n `Typecaast: unknown skin \"${id}\". Built-in skins: ${builtinSkinIds.join(\n \", \",\n )}. For a custom skin, pass the \\`skin\\` prop.`,\n );\n }\n\n resource = {\n status: \"pending\",\n promise: loader().then((m) => m.default),\n };\n // Record the settled state for the synchronous Suspense read. This handler\n // also keeps `promise`'s rejection from going unhandled.\n void resource.promise.then(\n (skin) => {\n resource!.status = \"fulfilled\";\n resource!.value = skin;\n },\n (error: unknown) => {\n resource!.status = \"rejected\";\n resource!.error = error;\n },\n );\n cache.set(id, resource);\n return resource;\n}\n\n/**\n * Resolve a built-in skin id to a cached promise of its `Skin`. Stable per id.\n */\nexport function loadBuiltinSkin(id: string): Promise<Skin> {\n return getResource(id).promise;\n}\n\n/**\n * Suspense-read a built-in skin by id: returns the `Skin` once loaded, throws the\n * pending promise to suspend, or re-throws a load error. This is the universal\n * Suspense primitive — it works on React 18 and 19, unlike `use()` (19-only).\n */\nexport function readBuiltinSkin(id: string): Skin {\n const resource = getResource(id);\n if (resource.status === \"fulfilled\") return resource.value as Skin;\n if (resource.status === \"rejected\") throw resource.error;\n throw resource.promise;\n}\n","import {\n Suspense,\n useEffect,\n useMemo,\n type CSSProperties,\n type ReactElement,\n} from \"react\";\nimport {\n configSchema,\n type Config,\n type ConfigInput,\n type FitMode,\n type ThemeMode,\n} from \"@typecaast/schema\";\nimport {\n TypecaastStage,\n type ComposerMode,\n type Skin,\n} from \"@typecaast/skin-kit\";\nimport { useTypecaast } from \"./use-typecaast.js\";\nimport { useSkinFonts } from \"./use-skin-fonts.js\";\nimport { useReducedMotion } from \"./use-reduced-motion.js\";\nimport { buildTranscript } from \"./transcript.js\";\nimport { FitBox } from \"./fit-box.js\";\nimport { readBuiltinSkin } from \"./builtin-skins.js\";\n\n/**\n * A loosely-typed config shape that a raw `import`ed `typecaast.json` satisfies —\n * TypeScript widens JSON literals (e.g. `version: number`, `type: string`), so it\n * matches neither `Config` nor `ConfigInput`. It's validated and normalized at\n * runtime, so this stays a convenience surface, not a bypass.\n */\nexport interface RawConfig {\n version: number;\n meta: {\n canvas: { width: number; height: number };\n skin: { id: string; options?: Record<string, unknown> };\n [key: string]: unknown;\n };\n participants: Array<{ id: string; name: string; [key: string]: unknown }>;\n timeline: Array<{ type: string; [key: string]: unknown }>;\n pacing?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * What `<Typecaast config>` accepts: a precise `ConfigInput`/`Config` (full\n * intellisense when hand-authoring) or a raw config object such as an imported\n * `typecaast.json`. All forms are normalized through the schema at runtime.\n */\nexport type TypecaastConfig = ConfigInput | Config | RawConfig;\n\nexport interface TypecaastProps {\n /**\n * The conversation config. Accepts your exported `typecaast.json` directly (or\n * a hand-authored `ConfigInput`); it's validated and defaulted at runtime, so\n * you never need to pre-parse it.\n */\n config: TypecaastConfig;\n /**\n * The skin to render with. **Optional** — by default the built-in skin named\n * by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk\n * is fetched), so the config is the single source of truth and the embed stays\n * fully serializable (works in a React Server Component, no `\"use client\"`).\n * Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.\n */\n skin?: Skin;\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** Container fit mode; defaults to `config.meta.fit`. */\n fit?: FitMode;\n /** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */\n composer?: ComposerMode;\n /** Accessible label for the simulation. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nconst SR_ONLY: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\n/**\n * Default sizing for the outer `<Typecaast>` wrapper. The widget is\n * **container-driven**: it fills its parent in both axes. When the host\n * gives the wrapper a definite height (responsive grid + fixed-height\n * card, hero box with `aspectRatio`, …) `height: 100%` resolves to that\n * height and the skin reflows / scales to fit. When the host gives only a\n * width, `height: 100%` resolves to `auto` and the canvas's own\n * `aspect-ratio` takes over, deriving a sensible height from the\n * authored canvas dimensions instead of letting message content drive\n * the widget taller as more steps play.\n *\n * The user's `style` prop is spread *after* these defaults so any\n * explicit width/height/aspectRatio overrides win — same opt-out story\n * as the `style.position` pass-through.\n */\nfunction rootStyle(canvas: { width: number; height: number }): CSSProperties {\n return {\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n aspectRatio: `${canvas.width} / ${canvas.height}`,\n };\n}\n\n/**\n * Renders a `<Typecaast>` from a config. The skin defaults to the built-in named\n * by `config.meta.skin.id` (lazy-loaded by id — see `builtin-skins.ts`); pass an\n * explicit `skin` to use a custom one. `<Typecaast>` is a client component, but\n * since the default path takes only the serializable `config`, the embed drops\n * straight into a React Server Component.\n */\nexport function Typecaast(props: TypecaastProps): ReactElement {\n // Normalize once: validate and apply schema defaults (pacing, fit, theme, …)\n // so a raw exported `typecaast.json` works without the caller pre-parsing it.\n const config = useMemo<Config>(\n () => configSchema.parse(props.config),\n [props.config],\n );\n\n // Explicit skin object → render synchronously, no lazy load.\n if (props.skin)\n return <Player {...props} config={config} skin={props.skin} />;\n // Otherwise resolve (and lazy-load) the built-in named in the config.\n return (\n <Suspense\n fallback={\n <SkinFallback\n config={config}\n fit={props.fit}\n label={props.label}\n className={props.className}\n style={props.style}\n />\n }\n >\n <ResolvedPlayer {...props} config={config} />\n </Suspense>\n );\n}\n\nfunction ResolvedPlayer(\n props: Omit<TypecaastProps, \"config\"> & { config: Config },\n): ReactElement {\n const skin = readBuiltinSkin(props.config.meta.skin.id);\n return <Player {...props} skin={skin} />;\n}\n\n/**\n * The actual player. The animated visuals are `aria-hidden`; an accessible\n * transcript carries the conversation for screen readers, and\n * `prefers-reduced-motion` snaps to the final state instead of animating\n * (PLAN §20).\n */\nfunction Player({\n config,\n skin,\n theme,\n autoplay,\n loop,\n rate,\n fit,\n composer,\n label,\n className,\n style,\n}: Omit<TypecaastProps, \"config\"> & {\n config: Config;\n skin: Skin;\n}): ReactElement {\n const reduced = useReducedMotion();\n const tc = useTypecaast(config, {\n theme,\n autoplay: autoplay && !reduced,\n loop: loop && !reduced,\n rate,\n capabilities: skin.meta.capabilities,\n });\n const fonts = useSkinFonts(skin);\n\n // Reduced motion: hold the completed conversation, no animation.\n useEffect(() => {\n if (reduced) tc.seek(tc.duration);\n }, [reduced, tc]);\n\n const transcript = useMemo(() => buildTranscript(config), [config]);\n\n return (\n <div\n className={className}\n style={{ ...rootStyle(config.meta.canvas), ...style }}\n data-typecaast=\"\"\n data-fonts={fonts}\n role=\"figure\"\n aria-label={label ?? `Chat simulation (${skin.meta.name})`}\n >\n <ol style={SR_ONLY}>\n {transcript.map((line, i) => (\n <li key={i}>\n {line.name}: {line.text}\n </li>\n ))}\n </ol>\n <div aria-hidden=\"true\" style={{ width: \"100%\", height: \"100%\" }}>\n <FitBox fit={fit ?? config.meta.fit} canvas={config.meta.canvas}>\n <TypecaastStage\n state={tc.state}\n skin={skin}\n participants={config.participants}\n options={config.meta.skin.options}\n composer={composer ?? config.meta.composer}\n />\n </FitBox>\n </div>\n </div>\n );\n}\n\n/**\n * A same-size placeholder shown while a built-in skin's chunk loads, so there's\n * no layout shift between fallback and the rendered skin. (On static/prerendered\n * pages the skin resolves before HTML is emitted, so this never paints.)\n */\nfunction SkinFallback({\n config,\n fit,\n label,\n className,\n style,\n}: Pick<TypecaastProps, \"fit\" | \"label\" | \"className\" | \"style\"> & {\n config: Config;\n}) {\n return (\n <div\n className={className}\n style={{ ...rootStyle(config.meta.canvas), ...style }}\n data-typecaast=\"\"\n data-typecaast-loading=\"\"\n role=\"figure\"\n aria-label={label ?? \"Chat simulation\"}\n aria-busy=\"true\"\n >\n <div aria-hidden=\"true\" style={{ width: \"100%\", height: \"100%\" }}>\n <FitBox fit={fit ?? config.meta.fit} canvas={config.meta.canvas}>\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n background: \"var(--tc-skin-loading-bg, transparent)\",\n }}\n />\n </FitBox>\n </div>\n </div>\n );\n}\n","import type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\n/**\n * Resolve a theme mode to a concrete theme. `auto` falls back to `light`\n * here; M1U.4 makes `auto` reactive against the host `prefers-color-scheme`\n * via a hook layered on top of this.\n */\nexport function resolveTheme(mode: ThemeMode): ResolvedTheme {\n return mode === \"dark\" ? \"dark\" : mode === \"light\" ? \"light\" : \"light\";\n}\n"]}
1
+ {"version":3,"sources":["../src/engine-adapter.ts","../src/use-resolved-theme.ts","../src/use-typecaast.ts","../src/use-skin-fonts.ts","../src/use-reduced-motion.ts","../src/transcript.ts","../src/fit-box.tsx","../src/builtin-skins.ts","../src/typecaast.tsx","../src/resolve-theme.ts"],"names":["createEngine","useSyncExternalStore","useMemo","createPlayer","useState","useEffect","loadSkinFonts","QUERY","getMql","subscribe","getSnapshot","getServerSnapshot","useRef","useLayoutEffect","jsx","forwardRef","Typecaast","configSchema","Suspense","ResolvedPlayer","Player","useImperativeHandle","jsxs","TypecaastStage"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Config } from \"@typecaast/schema\";\nimport {\n createEngine,\n type Capabilities,\n type EngineHandle,\n type ResolvedTheme,\n} from \"@typecaast/core\";\n\nexport type Engine = EngineHandle;\n\n/**\n * The single seam between a config and a playable engine. M1-UI ran this over a\n * hand-mocked timeline; M1-engine swaps in the real `compile` + `getStateAt`\n * here — and nothing else in the renderer changed (same `Engine` shape).\n *\n * Optional `capabilities` (from the active skin) drop unsupported events/content\n * from the sampled state while leaving the config intact.\n */\nexport function configToEngine(\n config: Config,\n theme: ResolvedTheme,\n capabilities?: Capabilities,\n): Engine {\n return createEngine(config, theme, capabilities);\n}\n","import { useSyncExternalStore } from \"react\";\nimport type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\nconst QUERY = \"(prefers-color-scheme: dark)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nfunction getSnapshot(): boolean {\n return getMql()?.matches ?? false;\n}\n\n/** No `matchMedia` on the server → default to light (consistent with export). */\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\n/** Reactively tracks the host's `prefers-color-scheme: dark`. */\nexport function usePrefersDark(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\n/**\n * Resolve a theme mode to a concrete theme. `light`/`dark` are forced; `auto`\n * tracks the host `prefers-color-scheme` reactively and falls back to `light`\n * when no preference signal is available.\n */\nexport function useResolvedTheme(mode: ThemeMode): ResolvedTheme {\n const prefersDark = usePrefersDark();\n if (mode === \"light\") return \"light\";\n if (mode === \"dark\") return \"dark\";\n return prefersDark ? \"dark\" : \"light\";\n}\n","import { useEffect, useMemo, useState } from \"react\";\nimport type { Config, ThemeMode } from \"@typecaast/schema\";\nimport {\n createPlayer,\n type Capabilities,\n type Player,\n type SimState,\n} from \"@typecaast/core\";\nimport { configToEngine } from \"./engine-adapter.js\";\nimport { useResolvedTheme } from \"./use-resolved-theme.js\";\n\nexport interface UseTypecaastOptions {\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** The active skin's capabilities, to drop what it can't render. */\n capabilities?: Capabilities;\n}\n\n/** Imperative controls + live state returned by {@link useTypecaast}. */\nexport interface TypecaastControls {\n state: SimState;\n /** Current playback time in ms (reactive). */\n currentMs: number;\n play(): void;\n pause(): void;\n seek(timeMs: number): void;\n scrubTo(timeMs: number): void;\n setRate(rate: number): void;\n stepNext(): void;\n stepPrev(): void;\n duration: number;\n rate: number;\n playing: boolean;\n /** Escape hatch to the underlying player. */\n player: Player;\n}\n\n/**\n * Mount a player for a config and expose live state + controls. The player\n * owns the clock (rAF in the browser); this hook bridges its ticks into React\n * state. The builder uses these controls for preview-as-you-go editing.\n *\n * In M1-UI the player runs over the mocked engine (see engine-adapter); the\n * hook's surface is the final one and does not change when the real engine\n * lands.\n */\nexport function useTypecaast(\n config: Config,\n options: UseTypecaastOptions = {},\n): TypecaastControls {\n const {\n theme,\n autoplay = false,\n // `loop` falls back to `config.meta.loop` so a config authored with looping\n // behaves the same in the builder preview and in zero-prop embeds.\n loop = config.meta.loop ?? false,\n rate = 1,\n capabilities,\n } = options;\n const resolved = useResolvedTheme(theme ?? config.meta.theme);\n\n const player = useMemo<Player>(() => {\n const engine = configToEngine(config, resolved, capabilities);\n return createPlayer(engine.getStateAt, {\n durationMs: engine.durationMs,\n steps: engine.steps,\n loop,\n rate,\n });\n }, [config, resolved, capabilities, loop, rate]);\n\n const [state, setState] = useState<SimState>(() => player.state);\n const [currentMs, setCurrentMs] = useState<number>(() => player.currentMs);\n const [playing, setPlaying] = useState<boolean>(() => player.playing);\n\n useEffect(() => {\n const sync = () => {\n setState(player.state);\n setCurrentMs(player.currentMs);\n };\n sync();\n setPlaying(player.playing);\n const offs = [\n player.on(\"tick\", sync),\n player.on(\"seek\", sync),\n player.on(\"play\", () => setPlaying(true)),\n player.on(\"pause\", () => setPlaying(false)),\n ];\n return () => {\n offs.forEach((off) => off());\n player.destroy();\n };\n }, [player]);\n\n // Apply `autoplay` once per player (at creation), not reactively. If this read\n // were a dependency of the subscribe/destroy effect above, a consumer toggling\n // a controlled `paused` — which flips the gated `autoplay` value upstream —\n // would tear down and recreate the live player. Post-mount play/pause is the\n // consumer's job (e.g. `<Typecaast>`'s `paused` reconcile).\n // (deps intentionally exclude `autoplay` — see the comment above.)\n useEffect(() => {\n if (autoplay) player.play();\n }, [player]);\n\n return {\n state,\n currentMs,\n play: () => player.play(),\n pause: () => player.pause(),\n seek: (t) => player.seek(t),\n scrubTo: (t) => player.scrubTo(t),\n setRate: (r) => player.setRate(r),\n stepNext: () => player.stepNext(),\n stepPrev: () => player.stepPrev(),\n duration: player.durationMs,\n rate: player.rate,\n playing,\n player,\n };\n}\n","import { useEffect, useState } from \"react\";\nimport { loadSkinFonts, type Skin } from \"@typecaast/skin-kit\";\n\nexport type FontLoadState = \"loading\" | \"loaded\";\n\n/**\n * Load a skin's declared web fonts on mount so the live preview renders in the\n * correct typeface (PLAN §19) — never relying on a host OS font. SSR-safe and\n * a no-op off the DOM (resolves \"loaded\"). Re-runs if the skin's fonts change.\n */\nexport function useSkinFonts(skin: Skin): FontLoadState {\n const fonts = skin.meta.fonts;\n const [state, setState] = useState<FontLoadState>(() =>\n fonts && fonts.length > 0 ? \"loading\" : \"loaded\",\n );\n\n useEffect(() => {\n if (!fonts || fonts.length === 0) {\n setState(\"loaded\");\n return;\n }\n let cancelled = false;\n setState(\"loading\");\n loadSkinFonts(fonts).finally(() => {\n if (!cancelled) setState(\"loaded\");\n });\n return () => {\n cancelled = true;\n };\n }, [fonts]);\n\n return state;\n}\n","import { useSyncExternalStore } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nconst getSnapshot = (): boolean => getMql()?.matches ?? false;\nconst getServerSnapshot = (): boolean => false;\n\n/**\n * Tracks `prefers-reduced-motion: reduce`. When true, the player snaps to the\n * final state instead of animating (PLAN §20).\n */\nexport function useReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import type { Config } from \"@typecaast/schema\";\n\nexport interface TranscriptLine {\n name: string;\n text: string;\n}\n\n/**\n * Build a plain-text transcript from the authored config — the accessible\n * representation of the conversation for screen readers (PLAN §20). The config\n * is already structured text, so this is a faithful, non-animated version.\n */\nexport function buildTranscript(config: Config): TranscriptLine[] {\n const name = new Map(config.participants.map((p) => [p.id, p.name]));\n const lines: TranscriptLine[] = [];\n for (const step of config.timeline) {\n let from: string | undefined;\n let text: string | undefined;\n if (step.type === \"message\" || step.type === \"system\") {\n from = step.from;\n text = step.text;\n } else if (step.type === \"composerType\") {\n from = step.from;\n text = step.text;\n }\n if (text) {\n lines.push({ name: from ? (name.get(from) ?? from) : \"System\", text });\n }\n }\n return lines;\n}\n","import {\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { FitMode, Size } from \"@typecaast/schema\";\n\nexport interface FitBoxProps {\n fit: FitMode;\n canvas: Size;\n children: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * Applies the `fit` strategy between the authoring canvas and the host\n * container (PLAN §7):\n * - `reflow`: **fills both axes** (width + height); content re-wraps to the\n * container width and the bottom-anchored thread clips older messages\n * when they overflow vertically. The widget is container-driven — it\n * never grows past its host as more steps play.\n * - `scale`: renders at exact canvas size, CSS-scaled to fit (layout\n * preserved). Use when you want the skin to look like its native canvas\n * regardless of container size.\n * - `fixed`: exact canvas size, clipped — the only mode where the widget\n * is *not* container-driven.\n */\nexport function FitBox({\n fit,\n canvas,\n children,\n className,\n style,\n}: FitBoxProps): ReactElement {\n const ref = useRef<HTMLDivElement>(null);\n const [container, setContainer] = useState<{ w: number; h: number } | null>(\n null,\n );\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const ro = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect;\n if (rect) setContainer({ w: rect.width, h: rect.height });\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n if (fit === \"reflow\") {\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"reflow\"\n // `height: 100%` closes the height chain: combined with the parent\n // `<Typecaast>` outer's `aspect-ratio` fallback, the widget fills\n // its host container instead of growing taller as more messages\n // play (the skin's bottom-anchored thread + `overflow: hidden`\n // clip older steps off the top).\n style={{ width: \"100%\", height: \"100%\", ...style }}\n >\n {children}\n </div>\n );\n }\n\n if (fit === \"fixed\") {\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"fixed\"\n style={{\n width: canvas.width,\n height: canvas.height,\n overflow: \"hidden\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n }\n\n // scale: fit the exact-size canvas into the measured container.\n const scale = container\n ? Math.min(container.w / canvas.width, container.h / canvas.height)\n : 1;\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"scale\"\n style={{ width: \"100%\", height: \"100%\", overflow: \"hidden\", ...style }}\n >\n <div\n data-fit-canvas=\"\"\n style={{\n width: canvas.width,\n height: canvas.height,\n transform: `scale(${scale})`,\n transformOrigin: \"top left\",\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n","import type { Skin } from \"@typecaast/skin-kit\";\n\ntype SkinModule = { default: Skin };\n\n/**\n * Lazy loaders for the built-in skins, keyed by `meta.skin.id`. Each value is a\n * **static** `import()` of a per-skin subpath, so bundlers emit one chunk per\n * skin and only the skin a config actually references is fetched. Custom skins\n * bypass this entirely (pass the `skin` prop to `<Typecaast>`).\n *\n * Adding a built-in skin = add one line here + the subpath export in\n * `@typecaast/skins`.\n */\nexport const BUILTIN_SKIN_LOADERS: Record<string, () => Promise<SkinModule>> = {\n slack: () => import(\"@typecaast/skins/slack\"),\n telegram: () => import(\"@typecaast/skins/telegram\"),\n \"claude-code\": () => import(\"@typecaast/skins/claude-code\"),\n imessage: () => import(\"@typecaast/skins/imessage\"),\n whatsapp: () => import(\"@typecaast/skins/whatsapp\"),\n cursor: () => import(\"@typecaast/skins/cursor\"),\n \"messages-macos\": () => import(\"@typecaast/skins/messages-macos\"),\n discord: () => import(\"@typecaast/skins/discord\"),\n};\n\n/** Ids of the built-in skins resolvable by `<Typecaast>` without a `skin` prop. */\nexport const builtinSkinIds = Object.keys(BUILTIN_SKIN_LOADERS);\n\n/**\n * A status-tracked load, cached per id. Tracking the settled state lets us\n * Suspense-read it on **React 18 and 19** alike (via the throw-the-promise\n * primitive) instead of React 19's `use()`, which doesn't exist on 18.\n */\ninterface SkinResource {\n promise: Promise<Skin>;\n status: \"pending\" | \"fulfilled\" | \"rejected\";\n value?: Skin;\n error?: unknown;\n}\n\n// Stable resource per id so the same promise is seen across renders.\nconst cache = new Map<string, SkinResource>();\n\n/**\n * Get (or create) the cached resource for a skin id. Throws synchronously for an\n * unknown id (a render error with a clear message), rather than suspending forever.\n */\nfunction getResource(id: string): SkinResource {\n let resource = cache.get(id);\n if (resource) return resource;\n\n const loader = BUILTIN_SKIN_LOADERS[id];\n if (!loader) {\n throw new Error(\n `Typecaast: unknown skin \"${id}\". Built-in skins: ${builtinSkinIds.join(\n \", \",\n )}. For a custom skin, pass the \\`skin\\` prop.`,\n );\n }\n\n resource = {\n status: \"pending\",\n promise: loader().then((m) => m.default),\n };\n // Record the settled state for the synchronous Suspense read. This handler\n // also keeps `promise`'s rejection from going unhandled.\n void resource.promise.then(\n (skin) => {\n resource!.status = \"fulfilled\";\n resource!.value = skin;\n },\n (error: unknown) => {\n resource!.status = \"rejected\";\n resource!.error = error;\n },\n );\n cache.set(id, resource);\n return resource;\n}\n\n/**\n * Resolve a built-in skin id to a cached promise of its `Skin`. Stable per id.\n */\nexport function loadBuiltinSkin(id: string): Promise<Skin> {\n return getResource(id).promise;\n}\n\n/**\n * Suspense-read a built-in skin by id: returns the `Skin` once loaded, throws the\n * pending promise to suspend, or re-throws a load error. This is the universal\n * Suspense primitive — it works on React 18 and 19, unlike `use()` (19-only).\n */\nexport function readBuiltinSkin(id: string): Skin {\n const resource = getResource(id);\n if (resource.status === \"fulfilled\") return resource.value as Skin;\n if (resource.status === \"rejected\") throw resource.error;\n throw resource.promise;\n}\n","import {\n forwardRef,\n Suspense,\n useEffect,\n useImperativeHandle,\n useMemo,\n type CSSProperties,\n type ForwardedRef,\n type ReactElement,\n} from \"react\";\nimport {\n configSchema,\n type Config,\n type ConfigInput,\n type FitMode,\n type ThemeMode,\n} from \"@typecaast/schema\";\nimport {\n TypecaastStage,\n type ComposerMode,\n type Skin,\n} from \"@typecaast/skin-kit\";\nimport { useTypecaast } from \"./use-typecaast.js\";\nimport { useSkinFonts } from \"./use-skin-fonts.js\";\nimport { useReducedMotion } from \"./use-reduced-motion.js\";\nimport { buildTranscript } from \"./transcript.js\";\nimport { FitBox } from \"./fit-box.js\";\nimport { readBuiltinSkin } from \"./builtin-skins.js\";\n\n/**\n * A loosely-typed config shape that a raw `import`ed `typecaast.json` satisfies —\n * TypeScript widens JSON literals (e.g. `version: number`, `type: string`), so it\n * matches neither `Config` nor `ConfigInput`. It's validated and normalized at\n * runtime, so this stays a convenience surface, not a bypass.\n */\nexport interface RawConfig {\n version: number;\n meta: {\n canvas: { width: number; height: number };\n skin: { id: string; options?: Record<string, unknown> };\n [key: string]: unknown;\n };\n participants: Array<{ id: string; name: string; [key: string]: unknown }>;\n timeline: Array<{ type: string; [key: string]: unknown }>;\n pacing?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * What `<Typecaast config>` accepts: a precise `ConfigInput`/`Config` (full\n * intellisense when hand-authoring) or a raw config object such as an imported\n * `typecaast.json`. All forms are normalized through the schema at runtime.\n */\nexport type TypecaastConfig = ConfigInput | Config | RawConfig;\n\nexport interface TypecaastProps {\n /**\n * The conversation config. Accepts your exported `typecaast.json` directly (or\n * a hand-authored `ConfigInput`); it's validated and defaulted at runtime, so\n * you never need to pre-parse it.\n */\n config: TypecaastConfig;\n /**\n * The skin to render with. **Optional** — by default the built-in skin named\n * by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk\n * is fetched), so the config is the single source of truth and the embed stays\n * fully serializable (works in a React Server Component, no `\"use client\"`).\n * Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.\n */\n skin?: Skin;\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** Container fit mode; defaults to `config.meta.fit`. */\n fit?: FitMode;\n /** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */\n composer?: ComposerMode;\n /** Accessible label for the simulation. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n /**\n * Controlled pause. When provided it takes over playback: `true` pauses in\n * place, `false` plays/resumes **from the current position** (never restarts).\n * Omit it to keep the default `autoplay` behavior (uncontrolled). Toggling it\n * (e.g. `paused={activeTab !== \"slack\"}`) pauses/resumes without unmounting.\n * No-op under `prefers-reduced-motion` (which holds the completed thread).\n */\n paused?: boolean;\n /** Called when playback starts or resumes. */\n onPlay?: () => void;\n /** Called when playback pauses. */\n onPause?: () => void;\n /** Called once when playback reaches the end (non-looping). */\n onEnded?: () => void;\n}\n\n/**\n * Imperative playback controls, exposed via a `ref` on `<Typecaast>` for cases\n * the declarative props don't cover (jump to a time, change rate, step):\n *\n * ```tsx\n * const ref = useRef<TypecaastHandle>(null);\n * <Typecaast ref={ref} config={config} autoplay />;\n * // …later: ref.current?.seek(5000);\n * ```\n *\n * `ref.current` is `null` until the skin has loaded and the player mounts.\n */\nexport interface TypecaastHandle {\n play(): void;\n pause(): void;\n /** Jump to an absolute time in ms (clamped to `[0, duration]`). */\n seek(timeMs: number): void;\n /** Like `seek`, used for scrubbing. */\n scrubTo(timeMs: number): void;\n /** Playback rate multiplier (1 = realtime). */\n setRate(rate: number): void;\n /** Jump to the next / previous step boundary. */\n stepNext(): void;\n stepPrev(): void;\n /** Live playback time in ms. */\n readonly currentMs: number;\n /** Total duration in ms. */\n readonly duration: number;\n /** Whether the clock is currently running. */\n readonly playing: boolean;\n}\n\nconst SR_ONLY: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\n/**\n * Default sizing for the outer `<Typecaast>` wrapper. The widget is\n * **container-driven**: it fills its parent in both axes. When the host\n * gives the wrapper a definite height (responsive grid + fixed-height\n * card, hero box with `aspectRatio`, …) `height: 100%` resolves to that\n * height and the skin reflows / scales to fit. When the host gives only a\n * width, `height: 100%` resolves to `auto` and the canvas's own\n * `aspect-ratio` takes over, deriving a sensible height from the\n * authored canvas dimensions instead of letting message content drive\n * the widget taller as more steps play.\n *\n * The user's `style` prop is spread *after* these defaults so any\n * explicit width/height/aspectRatio overrides win — same opt-out story\n * as the `style.position` pass-through.\n */\nfunction rootStyle(canvas: { width: number; height: number }): CSSProperties {\n return {\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n aspectRatio: `${canvas.width} / ${canvas.height}`,\n };\n}\n\n/**\n * Renders a `<Typecaast>` from a config. The skin defaults to the built-in named\n * by `config.meta.skin.id` (lazy-loaded by id — see `builtin-skins.ts`); pass an\n * explicit `skin` to use a custom one. `<Typecaast>` is a client component, but\n * since the default path takes only the serializable `config`, the embed drops\n * straight into a React Server Component.\n */\nexport const Typecaast = forwardRef<TypecaastHandle, TypecaastProps>(\n function Typecaast(props, ref): ReactElement {\n // Normalize once: validate and apply schema defaults (pacing, fit, theme, …)\n // so a raw exported `typecaast.json` works without the caller pre-parsing it.\n const config = useMemo<Config>(\n () => configSchema.parse(props.config),\n [props.config],\n );\n\n // Explicit skin object → render synchronously, no lazy load.\n if (props.skin)\n return <Player {...props} config={config} skin={props.skin} ref={ref} />;\n // Otherwise resolve (and lazy-load) the built-in named in the config.\n return (\n <Suspense\n fallback={\n <SkinFallback\n config={config}\n fit={props.fit}\n label={props.label}\n className={props.className}\n style={props.style}\n />\n }\n >\n <ResolvedPlayer {...props} config={config} ref={ref} />\n </Suspense>\n );\n },\n);\n\nconst ResolvedPlayer = forwardRef<\n TypecaastHandle,\n Omit<TypecaastProps, \"config\"> & { config: Config }\n>(function ResolvedPlayer(props, ref): ReactElement {\n const skin = readBuiltinSkin(props.config.meta.skin.id);\n return <Player {...props} skin={skin} ref={ref} />;\n});\n\n/**\n * The actual player. The animated visuals are `aria-hidden`; an accessible\n * transcript carries the conversation for screen readers, and\n * `prefers-reduced-motion` snaps to the final state instead of animating\n * (PLAN §20).\n */\nconst Player = forwardRef<\n TypecaastHandle,\n Omit<TypecaastProps, \"config\"> & { config: Config; skin: Skin }\n>(function Player(\n {\n config,\n skin,\n theme,\n autoplay,\n loop,\n rate,\n fit,\n composer,\n label,\n className,\n style,\n paused,\n onPlay,\n onPause,\n onEnded,\n },\n ref: ForwardedRef<TypecaastHandle>,\n): ReactElement {\n const reduced = useReducedMotion();\n const tc = useTypecaast(config, {\n theme,\n // Gate mount-autoplay with `!paused` so a `paused`-at-mount instance never\n // flashes a frame of playback. `!paused` is `true` when uncontrolled.\n autoplay: autoplay && !reduced && !paused,\n loop: loop && !reduced,\n rate,\n capabilities: skin.meta.capabilities,\n });\n const player = tc.player;\n const fonts = useSkinFonts(skin);\n\n // Reduced motion: hold the completed conversation, no animation.\n useEffect(() => {\n if (reduced) tc.seek(tc.duration);\n }, [reduced, tc]);\n\n // Controlled `paused`: reconcile only when the consumer drives it (so an\n // uncontrolled instance keeps its autoplay behavior). Runs after the hook's\n // mount-autoplay effect, and re-applies on player recreation (theme change)\n // via the `tc` dep. No-op under reduced motion.\n useEffect(() => {\n if (paused === undefined || reduced) return;\n if (paused) tc.pause();\n else tc.play();\n }, [paused, reduced, tc]);\n\n // Lifecycle callbacks → player events.\n useEffect(() => {\n const offs: Array<() => void> = [];\n if (onPlay) offs.push(player.on(\"play\", onPlay));\n if (onPause) offs.push(player.on(\"pause\", onPause));\n if (onEnded) offs.push(player.on(\"end\", onEnded));\n return () => offs.forEach((off) => off());\n }, [player, onPlay, onPause, onEnded]);\n\n // Imperative controls (jump to a time, change rate, step). Getters read the\n // live player so values are exact; the handle is stable per player.\n useImperativeHandle(\n ref,\n () => ({\n play: () => player.play(),\n pause: () => player.pause(),\n seek: (t: number) => player.seek(t),\n scrubTo: (t: number) => player.scrubTo(t),\n setRate: (r: number) => player.setRate(r),\n stepNext: () => player.stepNext(),\n stepPrev: () => player.stepPrev(),\n get currentMs() {\n return player.currentMs;\n },\n get duration() {\n return player.durationMs;\n },\n get playing() {\n return player.playing;\n },\n }),\n [player],\n );\n\n const transcript = useMemo(() => buildTranscript(config), [config]);\n\n return (\n <div\n className={className}\n style={{ ...rootStyle(config.meta.canvas), ...style }}\n data-typecaast=\"\"\n data-fonts={fonts}\n role=\"figure\"\n aria-label={label ?? `Chat simulation (${skin.meta.name})`}\n >\n <ol style={SR_ONLY}>\n {transcript.map((line, i) => (\n <li key={i}>\n {line.name}: {line.text}\n </li>\n ))}\n </ol>\n <div aria-hidden=\"true\" style={{ width: \"100%\", height: \"100%\" }}>\n <FitBox fit={fit ?? config.meta.fit} canvas={config.meta.canvas}>\n <TypecaastStage\n state={tc.state}\n skin={skin}\n participants={config.participants}\n options={config.meta.skin.options}\n composer={composer ?? config.meta.composer}\n />\n </FitBox>\n </div>\n </div>\n );\n});\n\n/**\n * A same-size placeholder shown while a built-in skin's chunk loads, so there's\n * no layout shift between fallback and the rendered skin. (On static/prerendered\n * pages the skin resolves before HTML is emitted, so this never paints.)\n */\nfunction SkinFallback({\n config,\n fit,\n label,\n className,\n style,\n}: Pick<TypecaastProps, \"fit\" | \"label\" | \"className\" | \"style\"> & {\n config: Config;\n}) {\n return (\n <div\n className={className}\n style={{ ...rootStyle(config.meta.canvas), ...style }}\n data-typecaast=\"\"\n data-typecaast-loading=\"\"\n role=\"figure\"\n aria-label={label ?? \"Chat simulation\"}\n aria-busy=\"true\"\n >\n <div aria-hidden=\"true\" style={{ width: \"100%\", height: \"100%\" }}>\n <FitBox fit={fit ?? config.meta.fit} canvas={config.meta.canvas}>\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n background: \"var(--tc-skin-loading-bg, transparent)\",\n }}\n />\n </FitBox>\n </div>\n </div>\n );\n}\n","import type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\n/**\n * Resolve a theme mode to a concrete theme. `auto` falls back to `light`\n * here; M1U.4 makes `auto` reactive against the host `prefers-color-scheme`\n * via a hook layered on top of this.\n */\nexport function resolveTheme(mode: ThemeMode): ResolvedTheme {\n return mode === \"dark\" ? \"dark\" : mode === \"light\" ? \"light\" : \"light\";\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import { CSSProperties, ReactElement, ReactNode } from 'react';
1
+ import * as react from 'react';
2
+ import { CSSProperties, ReactNode, ReactElement } from 'react';
2
3
  import { ConfigInput, Config, ThemeMode, FitMode, Size } from '@typecaast/schema';
3
4
  import { Skin, ComposerMode } from '@typecaast/skin-kit';
4
5
  export { ComposerMode, TypecaastStage, TypecaastStageProps } from '@typecaast/skin-kit';
@@ -69,6 +70,51 @@ interface TypecaastProps {
69
70
  label?: string;
70
71
  className?: string;
71
72
  style?: CSSProperties;
73
+ /**
74
+ * Controlled pause. When provided it takes over playback: `true` pauses in
75
+ * place, `false` plays/resumes **from the current position** (never restarts).
76
+ * Omit it to keep the default `autoplay` behavior (uncontrolled). Toggling it
77
+ * (e.g. `paused={activeTab !== "slack"}`) pauses/resumes without unmounting.
78
+ * No-op under `prefers-reduced-motion` (which holds the completed thread).
79
+ */
80
+ paused?: boolean;
81
+ /** Called when playback starts or resumes. */
82
+ onPlay?: () => void;
83
+ /** Called when playback pauses. */
84
+ onPause?: () => void;
85
+ /** Called once when playback reaches the end (non-looping). */
86
+ onEnded?: () => void;
87
+ }
88
+ /**
89
+ * Imperative playback controls, exposed via a `ref` on `<Typecaast>` for cases
90
+ * the declarative props don't cover (jump to a time, change rate, step):
91
+ *
92
+ * ```tsx
93
+ * const ref = useRef<TypecaastHandle>(null);
94
+ * <Typecaast ref={ref} config={config} autoplay />;
95
+ * // …later: ref.current?.seek(5000);
96
+ * ```
97
+ *
98
+ * `ref.current` is `null` until the skin has loaded and the player mounts.
99
+ */
100
+ interface TypecaastHandle {
101
+ play(): void;
102
+ pause(): void;
103
+ /** Jump to an absolute time in ms (clamped to `[0, duration]`). */
104
+ seek(timeMs: number): void;
105
+ /** Like `seek`, used for scrubbing. */
106
+ scrubTo(timeMs: number): void;
107
+ /** Playback rate multiplier (1 = realtime). */
108
+ setRate(rate: number): void;
109
+ /** Jump to the next / previous step boundary. */
110
+ stepNext(): void;
111
+ stepPrev(): void;
112
+ /** Live playback time in ms. */
113
+ readonly currentMs: number;
114
+ /** Total duration in ms. */
115
+ readonly duration: number;
116
+ /** Whether the clock is currently running. */
117
+ readonly playing: boolean;
72
118
  }
73
119
  /**
74
120
  * Renders a `<Typecaast>` from a config. The skin defaults to the built-in named
@@ -77,7 +123,7 @@ interface TypecaastProps {
77
123
  * since the default path takes only the serializable `config`, the embed drops
78
124
  * straight into a React Server Component.
79
125
  */
80
- declare function Typecaast(props: TypecaastProps): ReactElement;
126
+ declare const Typecaast: react.ForwardRefExoticComponent<TypecaastProps & react.RefAttributes<TypecaastHandle>>;
81
127
 
82
128
  interface UseTypecaastOptions {
83
129
  /** Force a theme; otherwise resolved from `config.meta.theme`. */
@@ -200,4 +246,4 @@ declare const builtinSkinIds: string[];
200
246
  */
201
247
  declare function loadBuiltinSkin(id: string): Promise<Skin>;
202
248
 
203
- export { BUILTIN_SKIN_LOADERS, FitBox, type FitBoxProps, type FontLoadState, type RawConfig, type TranscriptLine, Typecaast, type TypecaastConfig, type TypecaastControls, type TypecaastProps, type UseTypecaastOptions, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
249
+ export { BUILTIN_SKIN_LOADERS, FitBox, type FitBoxProps, type FontLoadState, type RawConfig, type TranscriptLine, Typecaast, type TypecaastConfig, type TypecaastControls, type TypecaastHandle, type TypecaastProps, type UseTypecaastOptions, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { CSSProperties, ReactElement, ReactNode } from 'react';
1
+ import * as react from 'react';
2
+ import { CSSProperties, ReactNode, ReactElement } from 'react';
2
3
  import { ConfigInput, Config, ThemeMode, FitMode, Size } from '@typecaast/schema';
3
4
  import { Skin, ComposerMode } from '@typecaast/skin-kit';
4
5
  export { ComposerMode, TypecaastStage, TypecaastStageProps } from '@typecaast/skin-kit';
@@ -69,6 +70,51 @@ interface TypecaastProps {
69
70
  label?: string;
70
71
  className?: string;
71
72
  style?: CSSProperties;
73
+ /**
74
+ * Controlled pause. When provided it takes over playback: `true` pauses in
75
+ * place, `false` plays/resumes **from the current position** (never restarts).
76
+ * Omit it to keep the default `autoplay` behavior (uncontrolled). Toggling it
77
+ * (e.g. `paused={activeTab !== "slack"}`) pauses/resumes without unmounting.
78
+ * No-op under `prefers-reduced-motion` (which holds the completed thread).
79
+ */
80
+ paused?: boolean;
81
+ /** Called when playback starts or resumes. */
82
+ onPlay?: () => void;
83
+ /** Called when playback pauses. */
84
+ onPause?: () => void;
85
+ /** Called once when playback reaches the end (non-looping). */
86
+ onEnded?: () => void;
87
+ }
88
+ /**
89
+ * Imperative playback controls, exposed via a `ref` on `<Typecaast>` for cases
90
+ * the declarative props don't cover (jump to a time, change rate, step):
91
+ *
92
+ * ```tsx
93
+ * const ref = useRef<TypecaastHandle>(null);
94
+ * <Typecaast ref={ref} config={config} autoplay />;
95
+ * // …later: ref.current?.seek(5000);
96
+ * ```
97
+ *
98
+ * `ref.current` is `null` until the skin has loaded and the player mounts.
99
+ */
100
+ interface TypecaastHandle {
101
+ play(): void;
102
+ pause(): void;
103
+ /** Jump to an absolute time in ms (clamped to `[0, duration]`). */
104
+ seek(timeMs: number): void;
105
+ /** Like `seek`, used for scrubbing. */
106
+ scrubTo(timeMs: number): void;
107
+ /** Playback rate multiplier (1 = realtime). */
108
+ setRate(rate: number): void;
109
+ /** Jump to the next / previous step boundary. */
110
+ stepNext(): void;
111
+ stepPrev(): void;
112
+ /** Live playback time in ms. */
113
+ readonly currentMs: number;
114
+ /** Total duration in ms. */
115
+ readonly duration: number;
116
+ /** Whether the clock is currently running. */
117
+ readonly playing: boolean;
72
118
  }
73
119
  /**
74
120
  * Renders a `<Typecaast>` from a config. The skin defaults to the built-in named
@@ -77,7 +123,7 @@ interface TypecaastProps {
77
123
  * since the default path takes only the serializable `config`, the embed drops
78
124
  * straight into a React Server Component.
79
125
  */
80
- declare function Typecaast(props: TypecaastProps): ReactElement;
126
+ declare const Typecaast: react.ForwardRefExoticComponent<TypecaastProps & react.RefAttributes<TypecaastHandle>>;
81
127
 
82
128
  interface UseTypecaastOptions {
83
129
  /** Force a theme; otherwise resolved from `config.meta.theme`. */
@@ -200,4 +246,4 @@ declare const builtinSkinIds: string[];
200
246
  */
201
247
  declare function loadBuiltinSkin(id: string): Promise<Skin>;
202
248
 
203
- export { BUILTIN_SKIN_LOADERS, FitBox, type FitBoxProps, type FontLoadState, type RawConfig, type TranscriptLine, Typecaast, type TypecaastConfig, type TypecaastControls, type TypecaastProps, type UseTypecaastOptions, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
249
+ export { BUILTIN_SKIN_LOADERS, FitBox, type FitBoxProps, type FontLoadState, type RawConfig, type TranscriptLine, Typecaast, type TypecaastConfig, type TypecaastControls, type TypecaastHandle, type TypecaastProps, type UseTypecaastOptions, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
- import { useSyncExternalStore, useMemo, useState, useEffect, useRef, useLayoutEffect, Suspense } from 'react';
2
+ import { forwardRef, useMemo, Suspense, useEffect, useImperativeHandle, useSyncExternalStore, useState, useRef, useLayoutEffect } from 'react';
3
3
  import { configSchema } from '@typecaast/schema';
4
- import { loadSkinFonts, TypecaastStage } from '@typecaast/skin-kit';
4
+ import { TypecaastStage, loadSkinFonts } from '@typecaast/skin-kit';
5
5
  export { TypecaastStage } from '@typecaast/skin-kit';
6
6
  import { createPlayer, createEngine } from '@typecaast/core';
7
7
  import { jsx, jsxs } from 'react/jsx-runtime';
@@ -77,12 +77,14 @@ function useTypecaast(config, options = {}) {
77
77
  player.on("play", () => setPlaying(true)),
78
78
  player.on("pause", () => setPlaying(false))
79
79
  ];
80
- if (autoplay) player.play();
81
80
  return () => {
82
81
  offs.forEach((off) => off());
83
82
  player.destroy();
84
83
  };
85
- }, [player, autoplay]);
84
+ }, [player]);
85
+ useEffect(() => {
86
+ if (autoplay) player.play();
87
+ }, [player]);
86
88
  return {
87
89
  state,
88
90
  currentMs,
@@ -304,35 +306,37 @@ function rootStyle(canvas) {
304
306
  aspectRatio: `${canvas.width} / ${canvas.height}`
305
307
  };
306
308
  }
307
- function Typecaast(props) {
308
- const config = useMemo(
309
- () => configSchema.parse(props.config),
310
- [props.config]
311
- );
312
- if (props.skin)
313
- return /* @__PURE__ */ jsx(Player, { ...props, config, skin: props.skin });
314
- return /* @__PURE__ */ jsx(
315
- Suspense,
316
- {
317
- fallback: /* @__PURE__ */ jsx(
318
- SkinFallback,
319
- {
320
- config,
321
- fit: props.fit,
322
- label: props.label,
323
- className: props.className,
324
- style: props.style
325
- }
326
- ),
327
- children: /* @__PURE__ */ jsx(ResolvedPlayer, { ...props, config })
328
- }
329
- );
330
- }
331
- function ResolvedPlayer(props) {
309
+ var Typecaast = forwardRef(
310
+ function Typecaast2(props, ref) {
311
+ const config = useMemo(
312
+ () => configSchema.parse(props.config),
313
+ [props.config]
314
+ );
315
+ if (props.skin)
316
+ return /* @__PURE__ */ jsx(Player, { ...props, config, skin: props.skin, ref });
317
+ return /* @__PURE__ */ jsx(
318
+ Suspense,
319
+ {
320
+ fallback: /* @__PURE__ */ jsx(
321
+ SkinFallback,
322
+ {
323
+ config,
324
+ fit: props.fit,
325
+ label: props.label,
326
+ className: props.className,
327
+ style: props.style
328
+ }
329
+ ),
330
+ children: /* @__PURE__ */ jsx(ResolvedPlayer, { ...props, config, ref })
331
+ }
332
+ );
333
+ }
334
+ );
335
+ var ResolvedPlayer = forwardRef(function ResolvedPlayer2(props, ref) {
332
336
  const skin = readBuiltinSkin(props.config.meta.skin.id);
333
- return /* @__PURE__ */ jsx(Player, { ...props, skin });
334
- }
335
- function Player({
337
+ return /* @__PURE__ */ jsx(Player, { ...props, skin, ref });
338
+ });
339
+ var Player = forwardRef(function Player2({
336
340
  config,
337
341
  skin,
338
342
  theme,
@@ -343,20 +347,61 @@ function Player({
343
347
  composer,
344
348
  label,
345
349
  className,
346
- style
347
- }) {
350
+ style,
351
+ paused,
352
+ onPlay,
353
+ onPause,
354
+ onEnded
355
+ }, ref) {
348
356
  const reduced = useReducedMotion();
349
357
  const tc = useTypecaast(config, {
350
358
  theme,
351
- autoplay: autoplay && !reduced,
359
+ // Gate mount-autoplay with `!paused` so a `paused`-at-mount instance never
360
+ // flashes a frame of playback. `!paused` is `true` when uncontrolled.
361
+ autoplay: autoplay && !reduced && !paused,
352
362
  loop: loop && !reduced,
353
363
  rate,
354
364
  capabilities: skin.meta.capabilities
355
365
  });
366
+ const player = tc.player;
356
367
  const fonts = useSkinFonts(skin);
357
368
  useEffect(() => {
358
369
  if (reduced) tc.seek(tc.duration);
359
370
  }, [reduced, tc]);
371
+ useEffect(() => {
372
+ if (paused === void 0 || reduced) return;
373
+ if (paused) tc.pause();
374
+ else tc.play();
375
+ }, [paused, reduced, tc]);
376
+ useEffect(() => {
377
+ const offs = [];
378
+ if (onPlay) offs.push(player.on("play", onPlay));
379
+ if (onPause) offs.push(player.on("pause", onPause));
380
+ if (onEnded) offs.push(player.on("end", onEnded));
381
+ return () => offs.forEach((off) => off());
382
+ }, [player, onPlay, onPause, onEnded]);
383
+ useImperativeHandle(
384
+ ref,
385
+ () => ({
386
+ play: () => player.play(),
387
+ pause: () => player.pause(),
388
+ seek: (t) => player.seek(t),
389
+ scrubTo: (t) => player.scrubTo(t),
390
+ setRate: (r) => player.setRate(r),
391
+ stepNext: () => player.stepNext(),
392
+ stepPrev: () => player.stepPrev(),
393
+ get currentMs() {
394
+ return player.currentMs;
395
+ },
396
+ get duration() {
397
+ return player.durationMs;
398
+ },
399
+ get playing() {
400
+ return player.playing;
401
+ }
402
+ }),
403
+ [player]
404
+ );
360
405
  const transcript = useMemo(() => buildTranscript(config), [config]);
361
406
  return /* @__PURE__ */ jsxs(
362
407
  "div",
@@ -386,7 +431,7 @@ function Player({
386
431
  ]
387
432
  }
388
433
  );
389
- }
434
+ });
390
435
  function SkinFallback({
391
436
  config,
392
437
  fit,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engine-adapter.ts","../src/use-resolved-theme.ts","../src/use-typecaast.ts","../src/use-skin-fonts.ts","../src/use-reduced-motion.ts","../src/transcript.ts","../src/fit-box.tsx","../src/builtin-skins.ts","../src/typecaast.tsx","../src/resolve-theme.ts"],"names":["useState","useEffect","QUERY","getMql","subscribe","getSnapshot","getServerSnapshot","useSyncExternalStore","useMemo","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { Config } from \"@typecaast/schema\";\nimport {\n createEngine,\n type Capabilities,\n type EngineHandle,\n type ResolvedTheme,\n} from \"@typecaast/core\";\n\nexport type Engine = EngineHandle;\n\n/**\n * The single seam between a config and a playable engine. M1-UI ran this over a\n * hand-mocked timeline; M1-engine swaps in the real `compile` + `getStateAt`\n * here — and nothing else in the renderer changed (same `Engine` shape).\n *\n * Optional `capabilities` (from the active skin) drop unsupported events/content\n * from the sampled state while leaving the config intact.\n */\nexport function configToEngine(\n config: Config,\n theme: ResolvedTheme,\n capabilities?: Capabilities,\n): Engine {\n return createEngine(config, theme, capabilities);\n}\n","import { useSyncExternalStore } from \"react\";\nimport type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\nconst QUERY = \"(prefers-color-scheme: dark)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nfunction getSnapshot(): boolean {\n return getMql()?.matches ?? false;\n}\n\n/** No `matchMedia` on the server → default to light (consistent with export). */\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\n/** Reactively tracks the host's `prefers-color-scheme: dark`. */\nexport function usePrefersDark(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\n/**\n * Resolve a theme mode to a concrete theme. `light`/`dark` are forced; `auto`\n * tracks the host `prefers-color-scheme` reactively and falls back to `light`\n * when no preference signal is available.\n */\nexport function useResolvedTheme(mode: ThemeMode): ResolvedTheme {\n const prefersDark = usePrefersDark();\n if (mode === \"light\") return \"light\";\n if (mode === \"dark\") return \"dark\";\n return prefersDark ? \"dark\" : \"light\";\n}\n","import { useEffect, useMemo, useState } from \"react\";\nimport type { Config, ThemeMode } from \"@typecaast/schema\";\nimport {\n createPlayer,\n type Capabilities,\n type Player,\n type SimState,\n} from \"@typecaast/core\";\nimport { configToEngine } from \"./engine-adapter.js\";\nimport { useResolvedTheme } from \"./use-resolved-theme.js\";\n\nexport interface UseTypecaastOptions {\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** The active skin's capabilities, to drop what it can't render. */\n capabilities?: Capabilities;\n}\n\n/** Imperative controls + live state returned by {@link useTypecaast}. */\nexport interface TypecaastControls {\n state: SimState;\n /** Current playback time in ms (reactive). */\n currentMs: number;\n play(): void;\n pause(): void;\n seek(timeMs: number): void;\n scrubTo(timeMs: number): void;\n setRate(rate: number): void;\n stepNext(): void;\n stepPrev(): void;\n duration: number;\n rate: number;\n playing: boolean;\n /** Escape hatch to the underlying player. */\n player: Player;\n}\n\n/**\n * Mount a player for a config and expose live state + controls. The player\n * owns the clock (rAF in the browser); this hook bridges its ticks into React\n * state. The builder uses these controls for preview-as-you-go editing.\n *\n * In M1-UI the player runs over the mocked engine (see engine-adapter); the\n * hook's surface is the final one and does not change when the real engine\n * lands.\n */\nexport function useTypecaast(\n config: Config,\n options: UseTypecaastOptions = {},\n): TypecaastControls {\n const {\n theme,\n autoplay = false,\n // `loop` falls back to `config.meta.loop` so a config authored with looping\n // behaves the same in the builder preview and in zero-prop embeds.\n loop = config.meta.loop ?? false,\n rate = 1,\n capabilities,\n } = options;\n const resolved = useResolvedTheme(theme ?? config.meta.theme);\n\n const player = useMemo<Player>(() => {\n const engine = configToEngine(config, resolved, capabilities);\n return createPlayer(engine.getStateAt, {\n durationMs: engine.durationMs,\n steps: engine.steps,\n loop,\n rate,\n });\n }, [config, resolved, capabilities, loop, rate]);\n\n const [state, setState] = useState<SimState>(() => player.state);\n const [currentMs, setCurrentMs] = useState<number>(() => player.currentMs);\n const [playing, setPlaying] = useState<boolean>(() => player.playing);\n\n useEffect(() => {\n const sync = () => {\n setState(player.state);\n setCurrentMs(player.currentMs);\n };\n sync();\n setPlaying(player.playing);\n const offs = [\n player.on(\"tick\", sync),\n player.on(\"seek\", sync),\n player.on(\"play\", () => setPlaying(true)),\n player.on(\"pause\", () => setPlaying(false)),\n ];\n if (autoplay) player.play();\n return () => {\n offs.forEach((off) => off());\n player.destroy();\n };\n }, [player, autoplay]);\n\n return {\n state,\n currentMs,\n play: () => player.play(),\n pause: () => player.pause(),\n seek: (t) => player.seek(t),\n scrubTo: (t) => player.scrubTo(t),\n setRate: (r) => player.setRate(r),\n stepNext: () => player.stepNext(),\n stepPrev: () => player.stepPrev(),\n duration: player.durationMs,\n rate: player.rate,\n playing,\n player,\n };\n}\n","import { useEffect, useState } from \"react\";\nimport { loadSkinFonts, type Skin } from \"@typecaast/skin-kit\";\n\nexport type FontLoadState = \"loading\" | \"loaded\";\n\n/**\n * Load a skin's declared web fonts on mount so the live preview renders in the\n * correct typeface (PLAN §19) — never relying on a host OS font. SSR-safe and\n * a no-op off the DOM (resolves \"loaded\"). Re-runs if the skin's fonts change.\n */\nexport function useSkinFonts(skin: Skin): FontLoadState {\n const fonts = skin.meta.fonts;\n const [state, setState] = useState<FontLoadState>(() =>\n fonts && fonts.length > 0 ? \"loading\" : \"loaded\",\n );\n\n useEffect(() => {\n if (!fonts || fonts.length === 0) {\n setState(\"loaded\");\n return;\n }\n let cancelled = false;\n setState(\"loading\");\n loadSkinFonts(fonts).finally(() => {\n if (!cancelled) setState(\"loaded\");\n });\n return () => {\n cancelled = true;\n };\n }, [fonts]);\n\n return state;\n}\n","import { useSyncExternalStore } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nconst getSnapshot = (): boolean => getMql()?.matches ?? false;\nconst getServerSnapshot = (): boolean => false;\n\n/**\n * Tracks `prefers-reduced-motion: reduce`. When true, the player snaps to the\n * final state instead of animating (PLAN §20).\n */\nexport function useReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import type { Config } from \"@typecaast/schema\";\n\nexport interface TranscriptLine {\n name: string;\n text: string;\n}\n\n/**\n * Build a plain-text transcript from the authored config — the accessible\n * representation of the conversation for screen readers (PLAN §20). The config\n * is already structured text, so this is a faithful, non-animated version.\n */\nexport function buildTranscript(config: Config): TranscriptLine[] {\n const name = new Map(config.participants.map((p) => [p.id, p.name]));\n const lines: TranscriptLine[] = [];\n for (const step of config.timeline) {\n let from: string | undefined;\n let text: string | undefined;\n if (step.type === \"message\" || step.type === \"system\") {\n from = step.from;\n text = step.text;\n } else if (step.type === \"composerType\") {\n from = step.from;\n text = step.text;\n }\n if (text) {\n lines.push({ name: from ? (name.get(from) ?? from) : \"System\", text });\n }\n }\n return lines;\n}\n","import {\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { FitMode, Size } from \"@typecaast/schema\";\n\nexport interface FitBoxProps {\n fit: FitMode;\n canvas: Size;\n children: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * Applies the `fit` strategy between the authoring canvas and the host\n * container (PLAN §7):\n * - `reflow`: **fills both axes** (width + height); content re-wraps to the\n * container width and the bottom-anchored thread clips older messages\n * when they overflow vertically. The widget is container-driven — it\n * never grows past its host as more steps play.\n * - `scale`: renders at exact canvas size, CSS-scaled to fit (layout\n * preserved). Use when you want the skin to look like its native canvas\n * regardless of container size.\n * - `fixed`: exact canvas size, clipped — the only mode where the widget\n * is *not* container-driven.\n */\nexport function FitBox({\n fit,\n canvas,\n children,\n className,\n style,\n}: FitBoxProps): ReactElement {\n const ref = useRef<HTMLDivElement>(null);\n const [container, setContainer] = useState<{ w: number; h: number } | null>(\n null,\n );\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const ro = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect;\n if (rect) setContainer({ w: rect.width, h: rect.height });\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n if (fit === \"reflow\") {\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"reflow\"\n // `height: 100%` closes the height chain: combined with the parent\n // `<Typecaast>` outer's `aspect-ratio` fallback, the widget fills\n // its host container instead of growing taller as more messages\n // play (the skin's bottom-anchored thread + `overflow: hidden`\n // clip older steps off the top).\n style={{ width: \"100%\", height: \"100%\", ...style }}\n >\n {children}\n </div>\n );\n }\n\n if (fit === \"fixed\") {\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"fixed\"\n style={{\n width: canvas.width,\n height: canvas.height,\n overflow: \"hidden\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n }\n\n // scale: fit the exact-size canvas into the measured container.\n const scale = container\n ? Math.min(container.w / canvas.width, container.h / canvas.height)\n : 1;\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"scale\"\n style={{ width: \"100%\", height: \"100%\", overflow: \"hidden\", ...style }}\n >\n <div\n data-fit-canvas=\"\"\n style={{\n width: canvas.width,\n height: canvas.height,\n transform: `scale(${scale})`,\n transformOrigin: \"top left\",\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n","import type { Skin } from \"@typecaast/skin-kit\";\n\ntype SkinModule = { default: Skin };\n\n/**\n * Lazy loaders for the built-in skins, keyed by `meta.skin.id`. Each value is a\n * **static** `import()` of a per-skin subpath, so bundlers emit one chunk per\n * skin and only the skin a config actually references is fetched. Custom skins\n * bypass this entirely (pass the `skin` prop to `<Typecaast>`).\n *\n * Adding a built-in skin = add one line here + the subpath export in\n * `@typecaast/skins`.\n */\nexport const BUILTIN_SKIN_LOADERS: Record<string, () => Promise<SkinModule>> = {\n slack: () => import(\"@typecaast/skins/slack\"),\n telegram: () => import(\"@typecaast/skins/telegram\"),\n \"claude-code\": () => import(\"@typecaast/skins/claude-code\"),\n imessage: () => import(\"@typecaast/skins/imessage\"),\n whatsapp: () => import(\"@typecaast/skins/whatsapp\"),\n cursor: () => import(\"@typecaast/skins/cursor\"),\n \"messages-macos\": () => import(\"@typecaast/skins/messages-macos\"),\n discord: () => import(\"@typecaast/skins/discord\"),\n};\n\n/** Ids of the built-in skins resolvable by `<Typecaast>` without a `skin` prop. */\nexport const builtinSkinIds = Object.keys(BUILTIN_SKIN_LOADERS);\n\n/**\n * A status-tracked load, cached per id. Tracking the settled state lets us\n * Suspense-read it on **React 18 and 19** alike (via the throw-the-promise\n * primitive) instead of React 19's `use()`, which doesn't exist on 18.\n */\ninterface SkinResource {\n promise: Promise<Skin>;\n status: \"pending\" | \"fulfilled\" | \"rejected\";\n value?: Skin;\n error?: unknown;\n}\n\n// Stable resource per id so the same promise is seen across renders.\nconst cache = new Map<string, SkinResource>();\n\n/**\n * Get (or create) the cached resource for a skin id. Throws synchronously for an\n * unknown id (a render error with a clear message), rather than suspending forever.\n */\nfunction getResource(id: string): SkinResource {\n let resource = cache.get(id);\n if (resource) return resource;\n\n const loader = BUILTIN_SKIN_LOADERS[id];\n if (!loader) {\n throw new Error(\n `Typecaast: unknown skin \"${id}\". Built-in skins: ${builtinSkinIds.join(\n \", \",\n )}. For a custom skin, pass the \\`skin\\` prop.`,\n );\n }\n\n resource = {\n status: \"pending\",\n promise: loader().then((m) => m.default),\n };\n // Record the settled state for the synchronous Suspense read. This handler\n // also keeps `promise`'s rejection from going unhandled.\n void resource.promise.then(\n (skin) => {\n resource!.status = \"fulfilled\";\n resource!.value = skin;\n },\n (error: unknown) => {\n resource!.status = \"rejected\";\n resource!.error = error;\n },\n );\n cache.set(id, resource);\n return resource;\n}\n\n/**\n * Resolve a built-in skin id to a cached promise of its `Skin`. Stable per id.\n */\nexport function loadBuiltinSkin(id: string): Promise<Skin> {\n return getResource(id).promise;\n}\n\n/**\n * Suspense-read a built-in skin by id: returns the `Skin` once loaded, throws the\n * pending promise to suspend, or re-throws a load error. This is the universal\n * Suspense primitive — it works on React 18 and 19, unlike `use()` (19-only).\n */\nexport function readBuiltinSkin(id: string): Skin {\n const resource = getResource(id);\n if (resource.status === \"fulfilled\") return resource.value as Skin;\n if (resource.status === \"rejected\") throw resource.error;\n throw resource.promise;\n}\n","import {\n Suspense,\n useEffect,\n useMemo,\n type CSSProperties,\n type ReactElement,\n} from \"react\";\nimport {\n configSchema,\n type Config,\n type ConfigInput,\n type FitMode,\n type ThemeMode,\n} from \"@typecaast/schema\";\nimport {\n TypecaastStage,\n type ComposerMode,\n type Skin,\n} from \"@typecaast/skin-kit\";\nimport { useTypecaast } from \"./use-typecaast.js\";\nimport { useSkinFonts } from \"./use-skin-fonts.js\";\nimport { useReducedMotion } from \"./use-reduced-motion.js\";\nimport { buildTranscript } from \"./transcript.js\";\nimport { FitBox } from \"./fit-box.js\";\nimport { readBuiltinSkin } from \"./builtin-skins.js\";\n\n/**\n * A loosely-typed config shape that a raw `import`ed `typecaast.json` satisfies —\n * TypeScript widens JSON literals (e.g. `version: number`, `type: string`), so it\n * matches neither `Config` nor `ConfigInput`. It's validated and normalized at\n * runtime, so this stays a convenience surface, not a bypass.\n */\nexport interface RawConfig {\n version: number;\n meta: {\n canvas: { width: number; height: number };\n skin: { id: string; options?: Record<string, unknown> };\n [key: string]: unknown;\n };\n participants: Array<{ id: string; name: string; [key: string]: unknown }>;\n timeline: Array<{ type: string; [key: string]: unknown }>;\n pacing?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * What `<Typecaast config>` accepts: a precise `ConfigInput`/`Config` (full\n * intellisense when hand-authoring) or a raw config object such as an imported\n * `typecaast.json`. All forms are normalized through the schema at runtime.\n */\nexport type TypecaastConfig = ConfigInput | Config | RawConfig;\n\nexport interface TypecaastProps {\n /**\n * The conversation config. Accepts your exported `typecaast.json` directly (or\n * a hand-authored `ConfigInput`); it's validated and defaulted at runtime, so\n * you never need to pre-parse it.\n */\n config: TypecaastConfig;\n /**\n * The skin to render with. **Optional** — by default the built-in skin named\n * by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk\n * is fetched), so the config is the single source of truth and the embed stays\n * fully serializable (works in a React Server Component, no `\"use client\"`).\n * Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.\n */\n skin?: Skin;\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** Container fit mode; defaults to `config.meta.fit`. */\n fit?: FitMode;\n /** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */\n composer?: ComposerMode;\n /** Accessible label for the simulation. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nconst SR_ONLY: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\n/**\n * Default sizing for the outer `<Typecaast>` wrapper. The widget is\n * **container-driven**: it fills its parent in both axes. When the host\n * gives the wrapper a definite height (responsive grid + fixed-height\n * card, hero box with `aspectRatio`, …) `height: 100%` resolves to that\n * height and the skin reflows / scales to fit. When the host gives only a\n * width, `height: 100%` resolves to `auto` and the canvas's own\n * `aspect-ratio` takes over, deriving a sensible height from the\n * authored canvas dimensions instead of letting message content drive\n * the widget taller as more steps play.\n *\n * The user's `style` prop is spread *after* these defaults so any\n * explicit width/height/aspectRatio overrides win — same opt-out story\n * as the `style.position` pass-through.\n */\nfunction rootStyle(canvas: { width: number; height: number }): CSSProperties {\n return {\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n aspectRatio: `${canvas.width} / ${canvas.height}`,\n };\n}\n\n/**\n * Renders a `<Typecaast>` from a config. The skin defaults to the built-in named\n * by `config.meta.skin.id` (lazy-loaded by id — see `builtin-skins.ts`); pass an\n * explicit `skin` to use a custom one. `<Typecaast>` is a client component, but\n * since the default path takes only the serializable `config`, the embed drops\n * straight into a React Server Component.\n */\nexport function Typecaast(props: TypecaastProps): ReactElement {\n // Normalize once: validate and apply schema defaults (pacing, fit, theme, …)\n // so a raw exported `typecaast.json` works without the caller pre-parsing it.\n const config = useMemo<Config>(\n () => configSchema.parse(props.config),\n [props.config],\n );\n\n // Explicit skin object → render synchronously, no lazy load.\n if (props.skin)\n return <Player {...props} config={config} skin={props.skin} />;\n // Otherwise resolve (and lazy-load) the built-in named in the config.\n return (\n <Suspense\n fallback={\n <SkinFallback\n config={config}\n fit={props.fit}\n label={props.label}\n className={props.className}\n style={props.style}\n />\n }\n >\n <ResolvedPlayer {...props} config={config} />\n </Suspense>\n );\n}\n\nfunction ResolvedPlayer(\n props: Omit<TypecaastProps, \"config\"> & { config: Config },\n): ReactElement {\n const skin = readBuiltinSkin(props.config.meta.skin.id);\n return <Player {...props} skin={skin} />;\n}\n\n/**\n * The actual player. The animated visuals are `aria-hidden`; an accessible\n * transcript carries the conversation for screen readers, and\n * `prefers-reduced-motion` snaps to the final state instead of animating\n * (PLAN §20).\n */\nfunction Player({\n config,\n skin,\n theme,\n autoplay,\n loop,\n rate,\n fit,\n composer,\n label,\n className,\n style,\n}: Omit<TypecaastProps, \"config\"> & {\n config: Config;\n skin: Skin;\n}): ReactElement {\n const reduced = useReducedMotion();\n const tc = useTypecaast(config, {\n theme,\n autoplay: autoplay && !reduced,\n loop: loop && !reduced,\n rate,\n capabilities: skin.meta.capabilities,\n });\n const fonts = useSkinFonts(skin);\n\n // Reduced motion: hold the completed conversation, no animation.\n useEffect(() => {\n if (reduced) tc.seek(tc.duration);\n }, [reduced, tc]);\n\n const transcript = useMemo(() => buildTranscript(config), [config]);\n\n return (\n <div\n className={className}\n style={{ ...rootStyle(config.meta.canvas), ...style }}\n data-typecaast=\"\"\n data-fonts={fonts}\n role=\"figure\"\n aria-label={label ?? `Chat simulation (${skin.meta.name})`}\n >\n <ol style={SR_ONLY}>\n {transcript.map((line, i) => (\n <li key={i}>\n {line.name}: {line.text}\n </li>\n ))}\n </ol>\n <div aria-hidden=\"true\" style={{ width: \"100%\", height: \"100%\" }}>\n <FitBox fit={fit ?? config.meta.fit} canvas={config.meta.canvas}>\n <TypecaastStage\n state={tc.state}\n skin={skin}\n participants={config.participants}\n options={config.meta.skin.options}\n composer={composer ?? config.meta.composer}\n />\n </FitBox>\n </div>\n </div>\n );\n}\n\n/**\n * A same-size placeholder shown while a built-in skin's chunk loads, so there's\n * no layout shift between fallback and the rendered skin. (On static/prerendered\n * pages the skin resolves before HTML is emitted, so this never paints.)\n */\nfunction SkinFallback({\n config,\n fit,\n label,\n className,\n style,\n}: Pick<TypecaastProps, \"fit\" | \"label\" | \"className\" | \"style\"> & {\n config: Config;\n}) {\n return (\n <div\n className={className}\n style={{ ...rootStyle(config.meta.canvas), ...style }}\n data-typecaast=\"\"\n data-typecaast-loading=\"\"\n role=\"figure\"\n aria-label={label ?? \"Chat simulation\"}\n aria-busy=\"true\"\n >\n <div aria-hidden=\"true\" style={{ width: \"100%\", height: \"100%\" }}>\n <FitBox fit={fit ?? config.meta.fit} canvas={config.meta.canvas}>\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n background: \"var(--tc-skin-loading-bg, transparent)\",\n }}\n />\n </FitBox>\n </div>\n </div>\n );\n}\n","import type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\n/**\n * Resolve a theme mode to a concrete theme. `auto` falls back to `light`\n * here; M1U.4 makes `auto` reactive against the host `prefers-color-scheme`\n * via a hook layered on top of this.\n */\nexport function resolveTheme(mode: ThemeMode): ResolvedTheme {\n return mode === \"dark\" ? \"dark\" : mode === \"light\" ? \"light\" : \"light\";\n}\n"]}
1
+ {"version":3,"sources":["../src/engine-adapter.ts","../src/use-resolved-theme.ts","../src/use-typecaast.ts","../src/use-skin-fonts.ts","../src/use-reduced-motion.ts","../src/transcript.ts","../src/fit-box.tsx","../src/builtin-skins.ts","../src/typecaast.tsx","../src/resolve-theme.ts"],"names":["useState","useEffect","QUERY","getMql","subscribe","getSnapshot","getServerSnapshot","useSyncExternalStore","Typecaast","useMemo","jsx","ResolvedPlayer","Player"],"mappings":"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","file":"index.js","sourcesContent":["import type { Config } from \"@typecaast/schema\";\nimport {\n createEngine,\n type Capabilities,\n type EngineHandle,\n type ResolvedTheme,\n} from \"@typecaast/core\";\n\nexport type Engine = EngineHandle;\n\n/**\n * The single seam between a config and a playable engine. M1-UI ran this over a\n * hand-mocked timeline; M1-engine swaps in the real `compile` + `getStateAt`\n * here — and nothing else in the renderer changed (same `Engine` shape).\n *\n * Optional `capabilities` (from the active skin) drop unsupported events/content\n * from the sampled state while leaving the config intact.\n */\nexport function configToEngine(\n config: Config,\n theme: ResolvedTheme,\n capabilities?: Capabilities,\n): Engine {\n return createEngine(config, theme, capabilities);\n}\n","import { useSyncExternalStore } from \"react\";\nimport type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\nconst QUERY = \"(prefers-color-scheme: dark)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nfunction getSnapshot(): boolean {\n return getMql()?.matches ?? false;\n}\n\n/** No `matchMedia` on the server → default to light (consistent with export). */\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\n/** Reactively tracks the host's `prefers-color-scheme: dark`. */\nexport function usePrefersDark(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\n/**\n * Resolve a theme mode to a concrete theme. `light`/`dark` are forced; `auto`\n * tracks the host `prefers-color-scheme` reactively and falls back to `light`\n * when no preference signal is available.\n */\nexport function useResolvedTheme(mode: ThemeMode): ResolvedTheme {\n const prefersDark = usePrefersDark();\n if (mode === \"light\") return \"light\";\n if (mode === \"dark\") return \"dark\";\n return prefersDark ? \"dark\" : \"light\";\n}\n","import { useEffect, useMemo, useState } from \"react\";\nimport type { Config, ThemeMode } from \"@typecaast/schema\";\nimport {\n createPlayer,\n type Capabilities,\n type Player,\n type SimState,\n} from \"@typecaast/core\";\nimport { configToEngine } from \"./engine-adapter.js\";\nimport { useResolvedTheme } from \"./use-resolved-theme.js\";\n\nexport interface UseTypecaastOptions {\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** The active skin's capabilities, to drop what it can't render. */\n capabilities?: Capabilities;\n}\n\n/** Imperative controls + live state returned by {@link useTypecaast}. */\nexport interface TypecaastControls {\n state: SimState;\n /** Current playback time in ms (reactive). */\n currentMs: number;\n play(): void;\n pause(): void;\n seek(timeMs: number): void;\n scrubTo(timeMs: number): void;\n setRate(rate: number): void;\n stepNext(): void;\n stepPrev(): void;\n duration: number;\n rate: number;\n playing: boolean;\n /** Escape hatch to the underlying player. */\n player: Player;\n}\n\n/**\n * Mount a player for a config and expose live state + controls. The player\n * owns the clock (rAF in the browser); this hook bridges its ticks into React\n * state. The builder uses these controls for preview-as-you-go editing.\n *\n * In M1-UI the player runs over the mocked engine (see engine-adapter); the\n * hook's surface is the final one and does not change when the real engine\n * lands.\n */\nexport function useTypecaast(\n config: Config,\n options: UseTypecaastOptions = {},\n): TypecaastControls {\n const {\n theme,\n autoplay = false,\n // `loop` falls back to `config.meta.loop` so a config authored with looping\n // behaves the same in the builder preview and in zero-prop embeds.\n loop = config.meta.loop ?? false,\n rate = 1,\n capabilities,\n } = options;\n const resolved = useResolvedTheme(theme ?? config.meta.theme);\n\n const player = useMemo<Player>(() => {\n const engine = configToEngine(config, resolved, capabilities);\n return createPlayer(engine.getStateAt, {\n durationMs: engine.durationMs,\n steps: engine.steps,\n loop,\n rate,\n });\n }, [config, resolved, capabilities, loop, rate]);\n\n const [state, setState] = useState<SimState>(() => player.state);\n const [currentMs, setCurrentMs] = useState<number>(() => player.currentMs);\n const [playing, setPlaying] = useState<boolean>(() => player.playing);\n\n useEffect(() => {\n const sync = () => {\n setState(player.state);\n setCurrentMs(player.currentMs);\n };\n sync();\n setPlaying(player.playing);\n const offs = [\n player.on(\"tick\", sync),\n player.on(\"seek\", sync),\n player.on(\"play\", () => setPlaying(true)),\n player.on(\"pause\", () => setPlaying(false)),\n ];\n return () => {\n offs.forEach((off) => off());\n player.destroy();\n };\n }, [player]);\n\n // Apply `autoplay` once per player (at creation), not reactively. If this read\n // were a dependency of the subscribe/destroy effect above, a consumer toggling\n // a controlled `paused` — which flips the gated `autoplay` value upstream —\n // would tear down and recreate the live player. Post-mount play/pause is the\n // consumer's job (e.g. `<Typecaast>`'s `paused` reconcile).\n // (deps intentionally exclude `autoplay` — see the comment above.)\n useEffect(() => {\n if (autoplay) player.play();\n }, [player]);\n\n return {\n state,\n currentMs,\n play: () => player.play(),\n pause: () => player.pause(),\n seek: (t) => player.seek(t),\n scrubTo: (t) => player.scrubTo(t),\n setRate: (r) => player.setRate(r),\n stepNext: () => player.stepNext(),\n stepPrev: () => player.stepPrev(),\n duration: player.durationMs,\n rate: player.rate,\n playing,\n player,\n };\n}\n","import { useEffect, useState } from \"react\";\nimport { loadSkinFonts, type Skin } from \"@typecaast/skin-kit\";\n\nexport type FontLoadState = \"loading\" | \"loaded\";\n\n/**\n * Load a skin's declared web fonts on mount so the live preview renders in the\n * correct typeface (PLAN §19) — never relying on a host OS font. SSR-safe and\n * a no-op off the DOM (resolves \"loaded\"). Re-runs if the skin's fonts change.\n */\nexport function useSkinFonts(skin: Skin): FontLoadState {\n const fonts = skin.meta.fonts;\n const [state, setState] = useState<FontLoadState>(() =>\n fonts && fonts.length > 0 ? \"loading\" : \"loaded\",\n );\n\n useEffect(() => {\n if (!fonts || fonts.length === 0) {\n setState(\"loaded\");\n return;\n }\n let cancelled = false;\n setState(\"loading\");\n loadSkinFonts(fonts).finally(() => {\n if (!cancelled) setState(\"loaded\");\n });\n return () => {\n cancelled = true;\n };\n }, [fonts]);\n\n return state;\n}\n","import { useSyncExternalStore } from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nconst getSnapshot = (): boolean => getMql()?.matches ?? false;\nconst getServerSnapshot = (): boolean => false;\n\n/**\n * Tracks `prefers-reduced-motion: reduce`. When true, the player snaps to the\n * final state instead of animating (PLAN §20).\n */\nexport function useReducedMotion(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import type { Config } from \"@typecaast/schema\";\n\nexport interface TranscriptLine {\n name: string;\n text: string;\n}\n\n/**\n * Build a plain-text transcript from the authored config — the accessible\n * representation of the conversation for screen readers (PLAN §20). The config\n * is already structured text, so this is a faithful, non-animated version.\n */\nexport function buildTranscript(config: Config): TranscriptLine[] {\n const name = new Map(config.participants.map((p) => [p.id, p.name]));\n const lines: TranscriptLine[] = [];\n for (const step of config.timeline) {\n let from: string | undefined;\n let text: string | undefined;\n if (step.type === \"message\" || step.type === \"system\") {\n from = step.from;\n text = step.text;\n } else if (step.type === \"composerType\") {\n from = step.from;\n text = step.text;\n }\n if (text) {\n lines.push({ name: from ? (name.get(from) ?? from) : \"System\", text });\n }\n }\n return lines;\n}\n","import {\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { FitMode, Size } from \"@typecaast/schema\";\n\nexport interface FitBoxProps {\n fit: FitMode;\n canvas: Size;\n children: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * Applies the `fit` strategy between the authoring canvas and the host\n * container (PLAN §7):\n * - `reflow`: **fills both axes** (width + height); content re-wraps to the\n * container width and the bottom-anchored thread clips older messages\n * when they overflow vertically. The widget is container-driven — it\n * never grows past its host as more steps play.\n * - `scale`: renders at exact canvas size, CSS-scaled to fit (layout\n * preserved). Use when you want the skin to look like its native canvas\n * regardless of container size.\n * - `fixed`: exact canvas size, clipped — the only mode where the widget\n * is *not* container-driven.\n */\nexport function FitBox({\n fit,\n canvas,\n children,\n className,\n style,\n}: FitBoxProps): ReactElement {\n const ref = useRef<HTMLDivElement>(null);\n const [container, setContainer] = useState<{ w: number; h: number } | null>(\n null,\n );\n\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const ro = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect;\n if (rect) setContainer({ w: rect.width, h: rect.height });\n });\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n if (fit === \"reflow\") {\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"reflow\"\n // `height: 100%` closes the height chain: combined with the parent\n // `<Typecaast>` outer's `aspect-ratio` fallback, the widget fills\n // its host container instead of growing taller as more messages\n // play (the skin's bottom-anchored thread + `overflow: hidden`\n // clip older steps off the top).\n style={{ width: \"100%\", height: \"100%\", ...style }}\n >\n {children}\n </div>\n );\n }\n\n if (fit === \"fixed\") {\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"fixed\"\n style={{\n width: canvas.width,\n height: canvas.height,\n overflow: \"hidden\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n }\n\n // scale: fit the exact-size canvas into the measured container.\n const scale = container\n ? Math.min(container.w / canvas.width, container.h / canvas.height)\n : 1;\n return (\n <div\n ref={ref}\n className={className}\n data-fit=\"scale\"\n style={{ width: \"100%\", height: \"100%\", overflow: \"hidden\", ...style }}\n >\n <div\n data-fit-canvas=\"\"\n style={{\n width: canvas.width,\n height: canvas.height,\n transform: `scale(${scale})`,\n transformOrigin: \"top left\",\n }}\n >\n {children}\n </div>\n </div>\n );\n}\n","import type { Skin } from \"@typecaast/skin-kit\";\n\ntype SkinModule = { default: Skin };\n\n/**\n * Lazy loaders for the built-in skins, keyed by `meta.skin.id`. Each value is a\n * **static** `import()` of a per-skin subpath, so bundlers emit one chunk per\n * skin and only the skin a config actually references is fetched. Custom skins\n * bypass this entirely (pass the `skin` prop to `<Typecaast>`).\n *\n * Adding a built-in skin = add one line here + the subpath export in\n * `@typecaast/skins`.\n */\nexport const BUILTIN_SKIN_LOADERS: Record<string, () => Promise<SkinModule>> = {\n slack: () => import(\"@typecaast/skins/slack\"),\n telegram: () => import(\"@typecaast/skins/telegram\"),\n \"claude-code\": () => import(\"@typecaast/skins/claude-code\"),\n imessage: () => import(\"@typecaast/skins/imessage\"),\n whatsapp: () => import(\"@typecaast/skins/whatsapp\"),\n cursor: () => import(\"@typecaast/skins/cursor\"),\n \"messages-macos\": () => import(\"@typecaast/skins/messages-macos\"),\n discord: () => import(\"@typecaast/skins/discord\"),\n};\n\n/** Ids of the built-in skins resolvable by `<Typecaast>` without a `skin` prop. */\nexport const builtinSkinIds = Object.keys(BUILTIN_SKIN_LOADERS);\n\n/**\n * A status-tracked load, cached per id. Tracking the settled state lets us\n * Suspense-read it on **React 18 and 19** alike (via the throw-the-promise\n * primitive) instead of React 19's `use()`, which doesn't exist on 18.\n */\ninterface SkinResource {\n promise: Promise<Skin>;\n status: \"pending\" | \"fulfilled\" | \"rejected\";\n value?: Skin;\n error?: unknown;\n}\n\n// Stable resource per id so the same promise is seen across renders.\nconst cache = new Map<string, SkinResource>();\n\n/**\n * Get (or create) the cached resource for a skin id. Throws synchronously for an\n * unknown id (a render error with a clear message), rather than suspending forever.\n */\nfunction getResource(id: string): SkinResource {\n let resource = cache.get(id);\n if (resource) return resource;\n\n const loader = BUILTIN_SKIN_LOADERS[id];\n if (!loader) {\n throw new Error(\n `Typecaast: unknown skin \"${id}\". Built-in skins: ${builtinSkinIds.join(\n \", \",\n )}. For a custom skin, pass the \\`skin\\` prop.`,\n );\n }\n\n resource = {\n status: \"pending\",\n promise: loader().then((m) => m.default),\n };\n // Record the settled state for the synchronous Suspense read. This handler\n // also keeps `promise`'s rejection from going unhandled.\n void resource.promise.then(\n (skin) => {\n resource!.status = \"fulfilled\";\n resource!.value = skin;\n },\n (error: unknown) => {\n resource!.status = \"rejected\";\n resource!.error = error;\n },\n );\n cache.set(id, resource);\n return resource;\n}\n\n/**\n * Resolve a built-in skin id to a cached promise of its `Skin`. Stable per id.\n */\nexport function loadBuiltinSkin(id: string): Promise<Skin> {\n return getResource(id).promise;\n}\n\n/**\n * Suspense-read a built-in skin by id: returns the `Skin` once loaded, throws the\n * pending promise to suspend, or re-throws a load error. This is the universal\n * Suspense primitive — it works on React 18 and 19, unlike `use()` (19-only).\n */\nexport function readBuiltinSkin(id: string): Skin {\n const resource = getResource(id);\n if (resource.status === \"fulfilled\") return resource.value as Skin;\n if (resource.status === \"rejected\") throw resource.error;\n throw resource.promise;\n}\n","import {\n forwardRef,\n Suspense,\n useEffect,\n useImperativeHandle,\n useMemo,\n type CSSProperties,\n type ForwardedRef,\n type ReactElement,\n} from \"react\";\nimport {\n configSchema,\n type Config,\n type ConfigInput,\n type FitMode,\n type ThemeMode,\n} from \"@typecaast/schema\";\nimport {\n TypecaastStage,\n type ComposerMode,\n type Skin,\n} from \"@typecaast/skin-kit\";\nimport { useTypecaast } from \"./use-typecaast.js\";\nimport { useSkinFonts } from \"./use-skin-fonts.js\";\nimport { useReducedMotion } from \"./use-reduced-motion.js\";\nimport { buildTranscript } from \"./transcript.js\";\nimport { FitBox } from \"./fit-box.js\";\nimport { readBuiltinSkin } from \"./builtin-skins.js\";\n\n/**\n * A loosely-typed config shape that a raw `import`ed `typecaast.json` satisfies —\n * TypeScript widens JSON literals (e.g. `version: number`, `type: string`), so it\n * matches neither `Config` nor `ConfigInput`. It's validated and normalized at\n * runtime, so this stays a convenience surface, not a bypass.\n */\nexport interface RawConfig {\n version: number;\n meta: {\n canvas: { width: number; height: number };\n skin: { id: string; options?: Record<string, unknown> };\n [key: string]: unknown;\n };\n participants: Array<{ id: string; name: string; [key: string]: unknown }>;\n timeline: Array<{ type: string; [key: string]: unknown }>;\n pacing?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * What `<Typecaast config>` accepts: a precise `ConfigInput`/`Config` (full\n * intellisense when hand-authoring) or a raw config object such as an imported\n * `typecaast.json`. All forms are normalized through the schema at runtime.\n */\nexport type TypecaastConfig = ConfigInput | Config | RawConfig;\n\nexport interface TypecaastProps {\n /**\n * The conversation config. Accepts your exported `typecaast.json` directly (or\n * a hand-authored `ConfigInput`); it's validated and defaulted at runtime, so\n * you never need to pre-parse it.\n */\n config: TypecaastConfig;\n /**\n * The skin to render with. **Optional** — by default the built-in skin named\n * by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk\n * is fetched), so the config is the single source of truth and the embed stays\n * fully serializable (works in a React Server Component, no `\"use client\"`).\n * Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.\n */\n skin?: Skin;\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** Container fit mode; defaults to `config.meta.fit`. */\n fit?: FitMode;\n /** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */\n composer?: ComposerMode;\n /** Accessible label for the simulation. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n /**\n * Controlled pause. When provided it takes over playback: `true` pauses in\n * place, `false` plays/resumes **from the current position** (never restarts).\n * Omit it to keep the default `autoplay` behavior (uncontrolled). Toggling it\n * (e.g. `paused={activeTab !== \"slack\"}`) pauses/resumes without unmounting.\n * No-op under `prefers-reduced-motion` (which holds the completed thread).\n */\n paused?: boolean;\n /** Called when playback starts or resumes. */\n onPlay?: () => void;\n /** Called when playback pauses. */\n onPause?: () => void;\n /** Called once when playback reaches the end (non-looping). */\n onEnded?: () => void;\n}\n\n/**\n * Imperative playback controls, exposed via a `ref` on `<Typecaast>` for cases\n * the declarative props don't cover (jump to a time, change rate, step):\n *\n * ```tsx\n * const ref = useRef<TypecaastHandle>(null);\n * <Typecaast ref={ref} config={config} autoplay />;\n * // …later: ref.current?.seek(5000);\n * ```\n *\n * `ref.current` is `null` until the skin has loaded and the player mounts.\n */\nexport interface TypecaastHandle {\n play(): void;\n pause(): void;\n /** Jump to an absolute time in ms (clamped to `[0, duration]`). */\n seek(timeMs: number): void;\n /** Like `seek`, used for scrubbing. */\n scrubTo(timeMs: number): void;\n /** Playback rate multiplier (1 = realtime). */\n setRate(rate: number): void;\n /** Jump to the next / previous step boundary. */\n stepNext(): void;\n stepPrev(): void;\n /** Live playback time in ms. */\n readonly currentMs: number;\n /** Total duration in ms. */\n readonly duration: number;\n /** Whether the clock is currently running. */\n readonly playing: boolean;\n}\n\nconst SR_ONLY: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\n/**\n * Default sizing for the outer `<Typecaast>` wrapper. The widget is\n * **container-driven**: it fills its parent in both axes. When the host\n * gives the wrapper a definite height (responsive grid + fixed-height\n * card, hero box with `aspectRatio`, …) `height: 100%` resolves to that\n * height and the skin reflows / scales to fit. When the host gives only a\n * width, `height: 100%` resolves to `auto` and the canvas's own\n * `aspect-ratio` takes over, deriving a sensible height from the\n * authored canvas dimensions instead of letting message content drive\n * the widget taller as more steps play.\n *\n * The user's `style` prop is spread *after* these defaults so any\n * explicit width/height/aspectRatio overrides win — same opt-out story\n * as the `style.position` pass-through.\n */\nfunction rootStyle(canvas: { width: number; height: number }): CSSProperties {\n return {\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n aspectRatio: `${canvas.width} / ${canvas.height}`,\n };\n}\n\n/**\n * Renders a `<Typecaast>` from a config. The skin defaults to the built-in named\n * by `config.meta.skin.id` (lazy-loaded by id — see `builtin-skins.ts`); pass an\n * explicit `skin` to use a custom one. `<Typecaast>` is a client component, but\n * since the default path takes only the serializable `config`, the embed drops\n * straight into a React Server Component.\n */\nexport const Typecaast = forwardRef<TypecaastHandle, TypecaastProps>(\n function Typecaast(props, ref): ReactElement {\n // Normalize once: validate and apply schema defaults (pacing, fit, theme, …)\n // so a raw exported `typecaast.json` works without the caller pre-parsing it.\n const config = useMemo<Config>(\n () => configSchema.parse(props.config),\n [props.config],\n );\n\n // Explicit skin object → render synchronously, no lazy load.\n if (props.skin)\n return <Player {...props} config={config} skin={props.skin} ref={ref} />;\n // Otherwise resolve (and lazy-load) the built-in named in the config.\n return (\n <Suspense\n fallback={\n <SkinFallback\n config={config}\n fit={props.fit}\n label={props.label}\n className={props.className}\n style={props.style}\n />\n }\n >\n <ResolvedPlayer {...props} config={config} ref={ref} />\n </Suspense>\n );\n },\n);\n\nconst ResolvedPlayer = forwardRef<\n TypecaastHandle,\n Omit<TypecaastProps, \"config\"> & { config: Config }\n>(function ResolvedPlayer(props, ref): ReactElement {\n const skin = readBuiltinSkin(props.config.meta.skin.id);\n return <Player {...props} skin={skin} ref={ref} />;\n});\n\n/**\n * The actual player. The animated visuals are `aria-hidden`; an accessible\n * transcript carries the conversation for screen readers, and\n * `prefers-reduced-motion` snaps to the final state instead of animating\n * (PLAN §20).\n */\nconst Player = forwardRef<\n TypecaastHandle,\n Omit<TypecaastProps, \"config\"> & { config: Config; skin: Skin }\n>(function Player(\n {\n config,\n skin,\n theme,\n autoplay,\n loop,\n rate,\n fit,\n composer,\n label,\n className,\n style,\n paused,\n onPlay,\n onPause,\n onEnded,\n },\n ref: ForwardedRef<TypecaastHandle>,\n): ReactElement {\n const reduced = useReducedMotion();\n const tc = useTypecaast(config, {\n theme,\n // Gate mount-autoplay with `!paused` so a `paused`-at-mount instance never\n // flashes a frame of playback. `!paused` is `true` when uncontrolled.\n autoplay: autoplay && !reduced && !paused,\n loop: loop && !reduced,\n rate,\n capabilities: skin.meta.capabilities,\n });\n const player = tc.player;\n const fonts = useSkinFonts(skin);\n\n // Reduced motion: hold the completed conversation, no animation.\n useEffect(() => {\n if (reduced) tc.seek(tc.duration);\n }, [reduced, tc]);\n\n // Controlled `paused`: reconcile only when the consumer drives it (so an\n // uncontrolled instance keeps its autoplay behavior). Runs after the hook's\n // mount-autoplay effect, and re-applies on player recreation (theme change)\n // via the `tc` dep. No-op under reduced motion.\n useEffect(() => {\n if (paused === undefined || reduced) return;\n if (paused) tc.pause();\n else tc.play();\n }, [paused, reduced, tc]);\n\n // Lifecycle callbacks → player events.\n useEffect(() => {\n const offs: Array<() => void> = [];\n if (onPlay) offs.push(player.on(\"play\", onPlay));\n if (onPause) offs.push(player.on(\"pause\", onPause));\n if (onEnded) offs.push(player.on(\"end\", onEnded));\n return () => offs.forEach((off) => off());\n }, [player, onPlay, onPause, onEnded]);\n\n // Imperative controls (jump to a time, change rate, step). Getters read the\n // live player so values are exact; the handle is stable per player.\n useImperativeHandle(\n ref,\n () => ({\n play: () => player.play(),\n pause: () => player.pause(),\n seek: (t: number) => player.seek(t),\n scrubTo: (t: number) => player.scrubTo(t),\n setRate: (r: number) => player.setRate(r),\n stepNext: () => player.stepNext(),\n stepPrev: () => player.stepPrev(),\n get currentMs() {\n return player.currentMs;\n },\n get duration() {\n return player.durationMs;\n },\n get playing() {\n return player.playing;\n },\n }),\n [player],\n );\n\n const transcript = useMemo(() => buildTranscript(config), [config]);\n\n return (\n <div\n className={className}\n style={{ ...rootStyle(config.meta.canvas), ...style }}\n data-typecaast=\"\"\n data-fonts={fonts}\n role=\"figure\"\n aria-label={label ?? `Chat simulation (${skin.meta.name})`}\n >\n <ol style={SR_ONLY}>\n {transcript.map((line, i) => (\n <li key={i}>\n {line.name}: {line.text}\n </li>\n ))}\n </ol>\n <div aria-hidden=\"true\" style={{ width: \"100%\", height: \"100%\" }}>\n <FitBox fit={fit ?? config.meta.fit} canvas={config.meta.canvas}>\n <TypecaastStage\n state={tc.state}\n skin={skin}\n participants={config.participants}\n options={config.meta.skin.options}\n composer={composer ?? config.meta.composer}\n />\n </FitBox>\n </div>\n </div>\n );\n});\n\n/**\n * A same-size placeholder shown while a built-in skin's chunk loads, so there's\n * no layout shift between fallback and the rendered skin. (On static/prerendered\n * pages the skin resolves before HTML is emitted, so this never paints.)\n */\nfunction SkinFallback({\n config,\n fit,\n label,\n className,\n style,\n}: Pick<TypecaastProps, \"fit\" | \"label\" | \"className\" | \"style\"> & {\n config: Config;\n}) {\n return (\n <div\n className={className}\n style={{ ...rootStyle(config.meta.canvas), ...style }}\n data-typecaast=\"\"\n data-typecaast-loading=\"\"\n role=\"figure\"\n aria-label={label ?? \"Chat simulation\"}\n aria-busy=\"true\"\n >\n <div aria-hidden=\"true\" style={{ width: \"100%\", height: \"100%\" }}>\n <FitBox fit={fit ?? config.meta.fit} canvas={config.meta.canvas}>\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n background: \"var(--tc-skin-loading-bg, transparent)\",\n }}\n />\n </FitBox>\n </div>\n </div>\n );\n}\n","import type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\n/**\n * Resolve a theme mode to a concrete theme. `auto` falls back to `light`\n * here; M1U.4 makes `auto` reactive against the host `prefers-color-scheme`\n * via a hook layered on top of this.\n */\nexport function resolveTheme(mode: ThemeMode): ResolvedTheme {\n return mode === \"dark\" ? \"dark\" : mode === \"light\" ? \"light\" : \"light\";\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typecaast/react",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "React renderer: <Typecaast> real-time player + useTypecaast hook.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -24,10 +24,10 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@typecaast/core": "0.3.0",
27
+ "@typecaast/core": "0.4.0",
28
28
  "@typecaast/schema": "0.2.1",
29
- "@typecaast/skin-kit": "0.3.1",
30
- "@typecaast/skins": "0.3.0"
29
+ "@typecaast/skin-kit": "0.3.2",
30
+ "@typecaast/skins": "0.3.1"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "react": ">=18"