@typecaast/react 0.1.0 → 0.2.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
@@ -1,8 +1,9 @@
1
+ "use client";
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
4
- var core = require('@typecaast/core');
5
5
  var skinKit = require('@typecaast/skin-kit');
6
+ var core = require('@typecaast/core');
6
7
  var jsxRuntime = require('react/jsx-runtime');
7
8
 
8
9
  // src/typecaast.tsx
@@ -157,88 +158,6 @@ function buildTranscript(config) {
157
158
  }
158
159
  return lines;
159
160
  }
160
- function TypecaastStage({
161
- state,
162
- skin,
163
- participants,
164
- options
165
- }) {
166
- const theme = state.theme;
167
- const byId = react.useMemo(() => {
168
- const map = /* @__PURE__ */ new Map();
169
- for (const p of participants) map.set(p.id, p);
170
- return map;
171
- }, [participants]);
172
- const { Frame, Message, SystemMessage, TypingIndicator, Composer } = skin.components;
173
- const tokens = skin.tokens?.[theme];
174
- const composerAuthor = state.composer.from ? byId.get(state.composer.from) : void 0;
175
- return /* @__PURE__ */ jsxRuntime.jsx(skinKit.ThemeProvider, { theme, tokens, children: /* @__PURE__ */ jsxRuntime.jsxs(Frame, { theme, options, children: [
176
- /* @__PURE__ */ jsxRuntime.jsxs(
177
- "div",
178
- {
179
- "data-typecaast-thread": "",
180
- style: {
181
- display: "flex",
182
- flexDirection: "column",
183
- justifyContent: "flex-end",
184
- flex: "1 1 auto",
185
- minHeight: 0,
186
- overflow: "hidden"
187
- },
188
- children: [
189
- state.messages.map((message, i) => {
190
- const author = byId.get(message.from);
191
- if (!author) return null;
192
- if (message.variant === "system") {
193
- return /* @__PURE__ */ jsxRuntime.jsx(
194
- SystemMessage,
195
- {
196
- theme,
197
- message,
198
- author
199
- },
200
- message.id
201
- );
202
- }
203
- const prev = state.messages[i - 1];
204
- const previousAuthor = prev ? byId.get(prev.from) : void 0;
205
- return /* @__PURE__ */ jsxRuntime.jsx(
206
- Message,
207
- {
208
- theme,
209
- message,
210
- author,
211
- previousAuthor
212
- },
213
- message.id
214
- );
215
- }),
216
- state.typingIndicators.map((typing, i) => {
217
- const author = byId.get(typing.from);
218
- if (!author) return null;
219
- return /* @__PURE__ */ jsxRuntime.jsx(
220
- TypingIndicator,
221
- {
222
- theme,
223
- typing,
224
- author
225
- },
226
- `typing-${typing.from}-${i}`
227
- );
228
- })
229
- ]
230
- }
231
- ),
232
- composerAuthor ? /* @__PURE__ */ jsxRuntime.jsx(
233
- Composer,
234
- {
235
- theme,
236
- composer: state.composer,
237
- author: composerAuthor
238
- }
239
- ) : null
240
- ] }) });
241
- }
242
161
  function FitBox({
243
162
  fit,
244
163
  canvas,
@@ -313,6 +232,36 @@ function FitBox({
313
232
  }
314
233
  );
315
234
  }
235
+
236
+ // src/builtin-skins.ts
237
+ var BUILTIN_SKIN_LOADERS = {
238
+ slack: () => import('@typecaast/skins/slack'),
239
+ telegram: () => import('@typecaast/skins/telegram'),
240
+ "claude-code": () => import('@typecaast/skins/claude-code'),
241
+ imessage: () => import('@typecaast/skins/imessage'),
242
+ whatsapp: () => import('@typecaast/skins/whatsapp'),
243
+ cursor: () => import('@typecaast/skins/cursor'),
244
+ "messages-macos": () => import('@typecaast/skins/messages-macos'),
245
+ discord: () => import('@typecaast/skins/discord')
246
+ };
247
+ var builtinSkinIds = Object.keys(BUILTIN_SKIN_LOADERS);
248
+ var cache = /* @__PURE__ */ new Map();
249
+ function loadBuiltinSkin(id) {
250
+ let promise = cache.get(id);
251
+ if (!promise) {
252
+ const loader = BUILTIN_SKIN_LOADERS[id];
253
+ if (!loader) {
254
+ throw new Error(
255
+ `Typecaast: unknown skin "${id}". Built-in skins: ${builtinSkinIds.join(
256
+ ", "
257
+ )}. For a custom skin, pass the \`skin\` prop.`
258
+ );
259
+ }
260
+ promise = loader().then((m) => m.default);
261
+ cache.set(id, promise);
262
+ }
263
+ return promise;
264
+ }
316
265
  var SR_ONLY = {
317
266
  position: "absolute",
318
267
  width: 1,
@@ -324,7 +273,30 @@ var SR_ONLY = {
324
273
  whiteSpace: "nowrap",
325
274
  border: 0
326
275
  };
327
- function Typecaast({
276
+ function Typecaast(props) {
277
+ if (props.skin) return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props, skin: props.skin });
278
+ return /* @__PURE__ */ jsxRuntime.jsx(
279
+ react.Suspense,
280
+ {
281
+ fallback: /* @__PURE__ */ jsxRuntime.jsx(
282
+ SkinFallback,
283
+ {
284
+ config: props.config,
285
+ fit: props.fit,
286
+ label: props.label,
287
+ className: props.className,
288
+ style: props.style
289
+ }
290
+ ),
291
+ children: /* @__PURE__ */ jsxRuntime.jsx(ResolvedPlayer, { ...props })
292
+ }
293
+ );
294
+ }
295
+ function ResolvedPlayer(props) {
296
+ const skin = react.use(loadBuiltinSkin(props.config.meta.skin.id));
297
+ return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props, skin });
298
+ }
299
+ function Player({
328
300
  config,
329
301
  skin,
330
302
  theme,
@@ -332,6 +304,7 @@ function Typecaast({
332
304
  loop,
333
305
  rate,
334
306
  fit,
307
+ composer,
335
308
  label,
336
309
  className,
337
310
  style
@@ -365,28 +338,65 @@ function Typecaast({
365
338
  line.text
366
339
  ] }, i)) }),
367
340
  /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(FitBox, { fit: fit ?? config.meta.fit, canvas: config.meta.canvas, children: /* @__PURE__ */ jsxRuntime.jsx(
368
- TypecaastStage,
341
+ skinKit.TypecaastStage,
369
342
  {
370
343
  state: tc.state,
371
344
  skin,
372
345
  participants: config.participants,
373
- options: config.meta.skin.options
346
+ options: config.meta.skin.options,
347
+ composer: composer ?? config.meta.composer
374
348
  }
375
349
  ) }) })
376
350
  ]
377
351
  }
378
352
  );
379
353
  }
354
+ function SkinFallback({
355
+ config,
356
+ fit,
357
+ label,
358
+ className,
359
+ style
360
+ }) {
361
+ return /* @__PURE__ */ jsxRuntime.jsx(
362
+ "div",
363
+ {
364
+ className,
365
+ style: { position: "relative", ...style },
366
+ "data-typecaast": "",
367
+ "data-typecaast-loading": "",
368
+ role: "figure",
369
+ "aria-label": label ?? "Chat simulation",
370
+ "aria-busy": "true",
371
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(FitBox, { fit: fit ?? config.meta.fit, canvas: config.meta.canvas, children: /* @__PURE__ */ jsxRuntime.jsx(
372
+ "div",
373
+ {
374
+ style: {
375
+ width: "100%",
376
+ height: "100%",
377
+ background: "var(--tc-skin-loading-bg, transparent)"
378
+ }
379
+ }
380
+ ) }) })
381
+ }
382
+ );
383
+ }
380
384
 
381
385
  // src/resolve-theme.ts
382
386
  function resolveTheme(mode) {
383
387
  return mode === "dark" ? "dark" : mode === "light" ? "light" : "light";
384
388
  }
385
389
 
390
+ Object.defineProperty(exports, "TypecaastStage", {
391
+ enumerable: true,
392
+ get: function () { return skinKit.TypecaastStage; }
393
+ });
394
+ exports.BUILTIN_SKIN_LOADERS = BUILTIN_SKIN_LOADERS;
386
395
  exports.FitBox = FitBox;
387
396
  exports.Typecaast = Typecaast;
388
- exports.TypecaastStage = TypecaastStage;
389
397
  exports.buildTranscript = buildTranscript;
398
+ exports.builtinSkinIds = builtinSkinIds;
399
+ exports.loadBuiltinSkin = loadBuiltinSkin;
390
400
  exports.resolveTheme = resolveTheme;
391
401
  exports.usePrefersDark = usePrefersDark;
392
402
  exports.useReducedMotion = useReducedMotion;
@@ -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/stage.tsx","../src/fit-box.tsx","../src/typecaast.tsx","../src/resolve-theme.ts"],"names":["createEngine","useSyncExternalStore","useMemo","createPlayer","useState","useEffect","loadSkinFonts","QUERY","getMql","subscribe","getSnapshot","getServerSnapshot","ThemeProvider","jsxs","jsx","useRef","useLayoutEffect"],"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 = 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 { useMemo, type ReactNode } from \"react\";\nimport type { Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider, type Skin } from \"@typecaast/skin-kit\";\n\nexport interface TypecaastStageProps {\n state: SimState;\n skin: Skin;\n participants: Participant[];\n /** Skin-specific options from `meta.skin.options`. */\n options?: Record<string, unknown>;\n}\n\n/**\n * Maps a `SimState` onto a skin's components: a `Frame` wrapping the thread\n * items (Message / SystemMessage), the typing indicators, and the composer.\n * Reactions render inside the skin's `Message` (it reads `message.reactions`).\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n}: TypecaastStageProps): ReactNode {\n const theme = state.theme;\n const byId = useMemo(() => {\n const map = new Map<string, Participant>();\n for (const p of participants) map.set(p.id, p);\n return map;\n }, [participants]);\n\n const { Frame, Message, SystemMessage, TypingIndicator, Composer } =\n skin.components;\n const tokens = skin.tokens?.[theme];\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : undefined;\n\n return (\n <ThemeProvider theme={theme} tokens={tokens}>\n <Frame theme={theme} options={options}>\n {/* Thread viewport: bottom-anchored + clipped, so as the thread grows\n older items shift up out of view (\"content shifts up\", PLAN §7).\n The engine's scroll.targetOffset is honored here once real layout\n measurement lands; for now the flex anchor gives the same feel. */}\n <div\n data-typecaast-thread=\"\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-end\",\n flex: \"1 1 auto\",\n minHeight: 0,\n overflow: \"hidden\",\n }}\n >\n {state.messages.map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={message.id}\n theme={theme}\n message={message}\n author={author}\n />\n );\n }\n const prev = state.messages[i - 1];\n const previousAuthor = prev ? byId.get(prev.from) : undefined;\n return (\n <Message\n key={message.id}\n theme={theme}\n message={message}\n author={author}\n previousAuthor={previousAuthor}\n />\n );\n })}\n {state.typingIndicators.map((typing, i) => {\n const author = byId.get(typing.from);\n if (!author) return null;\n return (\n <TypingIndicator\n key={`typing-${typing.from}-${i}`}\n theme={theme}\n typing={typing}\n author={author}\n />\n );\n })}\n </div>\n {composerAuthor ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n />\n ) : null}\n </Frame>\n </ThemeProvider>\n );\n}\n","import {\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\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 width; content re-wraps (container query / ResizeObserver).\n * - `scale`: renders at exact canvas size, CSS-scaled to fit (layout preserved).\n * - `fixed`: exact canvas size, clipped.\n */\nexport function FitBox({\n fit,\n canvas,\n children,\n className,\n style,\n}: FitBoxProps): ReactNode {\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 style={{ width: \"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 { useEffect, useMemo, type CSSProperties, type ReactNode } from \"react\";\nimport type { Config, FitMode, ThemeMode } from \"@typecaast/schema\";\nimport type { Skin } 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 { TypecaastStage } from \"./stage.js\";\nimport { FitBox } from \"./fit-box.js\";\n\nexport interface TypecaastProps {\n config: Config;\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 /** 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 * Mounts the real-time player and renders the resolved skin from live state.\n * The animated visuals are `aria-hidden`; an accessible transcript carries the\n * conversation for screen readers, and `prefers-reduced-motion` snaps to the\n * final state instead of animating (PLAN §20).\n */\nexport function Typecaast({\n config,\n skin,\n theme,\n autoplay,\n loop,\n rate,\n fit,\n label,\n className,\n style,\n}: TypecaastProps): ReactNode {\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={{ position: \"relative\", ...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={{ 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 />\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","Suspense","use","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 = 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 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 width; content re-wraps (container query / ResizeObserver).\n * - `scale`: renders at exact canvas size, CSS-scaled to fit (layout preserved).\n * - `fixed`: exact canvas size, clipped.\n */\nexport function FitBox({\n fit,\n canvas,\n children,\n className,\n style,\n}: FitBoxProps): ReactNode {\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 style={{ width: \"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// Stable promise per id so React's `use()` sees the same promise across renders.\nconst cache = new Map<string, Promise<Skin>>();\n\n/**\n * Resolve a built-in skin id to a cached promise of its `Skin`. Throws\n * synchronously for an unknown id (a render error with a clear message), rather\n * than suspending forever.\n */\nexport function loadBuiltinSkin(id: string): Promise<Skin> {\n let promise = cache.get(id);\n if (!promise) {\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 promise = loader().then((m) => m.default);\n cache.set(id, promise);\n }\n return promise;\n}\n","import {\n Suspense,\n use,\n useEffect,\n useMemo,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport type { Config, FitMode, ThemeMode } 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 { loadBuiltinSkin } from \"./builtin-skins.js\";\n\nexport interface TypecaastProps {\n config: Config;\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 * 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): ReactNode {\n // Explicit skin object → render synchronously, no lazy load.\n if (props.skin) return <Player {...props} 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={props.config}\n fit={props.fit}\n label={props.label}\n className={props.className}\n style={props.style}\n />\n }\n >\n <ResolvedPlayer {...props} />\n </Suspense>\n );\n}\n\nfunction ResolvedPlayer(props: TypecaastProps): ReactNode {\n const skin = use(loadBuiltinSkin(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}: TypecaastProps & { skin: Skin }): ReactNode {\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={{ position: \"relative\", ...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={{ 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, \"config\" | \"fit\" | \"label\" | \"className\" | \"style\">) {\n return (\n <div\n className={className}\n style={{ position: \"relative\", ...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={{ 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,11 +1,19 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
- import { Config, ThemeMode, FitMode, Participant, Size } from '@typecaast/schema';
3
- import { Skin } from '@typecaast/skin-kit';
2
+ import { Config, ThemeMode, FitMode, Size } from '@typecaast/schema';
3
+ import { Skin, ComposerMode } from '@typecaast/skin-kit';
4
+ export { ComposerMode, TypecaastStage, TypecaastStageProps } from '@typecaast/skin-kit';
4
5
  import { SimState, Player, Capabilities, ResolvedTheme } from '@typecaast/core';
5
6
 
6
7
  interface TypecaastProps {
7
8
  config: Config;
8
- skin: Skin;
9
+ /**
10
+ * The skin to render with. **Optional** — by default the built-in skin named
11
+ * by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk
12
+ * is fetched), so the config is the single source of truth and the embed stays
13
+ * fully serializable (works in a React Server Component, no `"use client"`).
14
+ * Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.
15
+ */
16
+ skin?: Skin;
9
17
  /** Force a theme; otherwise resolved from `config.meta.theme`. */
10
18
  theme?: ThemeMode;
11
19
  autoplay?: boolean;
@@ -13,18 +21,21 @@ interface TypecaastProps {
13
21
  rate?: number;
14
22
  /** Container fit mode; defaults to `config.meta.fit`. */
15
23
  fit?: FitMode;
24
+ /** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */
25
+ composer?: ComposerMode;
16
26
  /** Accessible label for the simulation. */
17
27
  label?: string;
18
28
  className?: string;
19
29
  style?: CSSProperties;
20
30
  }
21
31
  /**
22
- * Mounts the real-time player and renders the resolved skin from live state.
23
- * The animated visuals are `aria-hidden`; an accessible transcript carries the
24
- * conversation for screen readers, and `prefers-reduced-motion` snaps to the
25
- * final state instead of animating (PLAN §20).
32
+ * Renders a `<Typecaast>` from a config. The skin defaults to the built-in named
33
+ * by `config.meta.skin.id` (lazy-loaded by id see `builtin-skins.ts`); pass an
34
+ * explicit `skin` to use a custom one. `<Typecaast>` is a client component, but
35
+ * since the default path takes only the serializable `config`, the embed drops
36
+ * straight into a React Server Component.
26
37
  */
27
- declare function Typecaast({ config, skin, theme, autoplay, loop, rate, fit, label, className, style, }: TypecaastProps): ReactNode;
38
+ declare function Typecaast(props: TypecaastProps): ReactNode;
28
39
 
29
40
  interface UseTypecaastOptions {
30
41
  /** Force a theme; otherwise resolved from `config.meta.theme`. */
@@ -64,20 +75,6 @@ interface TypecaastControls {
64
75
  */
65
76
  declare function useTypecaast(config: Config, options?: UseTypecaastOptions): TypecaastControls;
66
77
 
67
- interface TypecaastStageProps {
68
- state: SimState;
69
- skin: Skin;
70
- participants: Participant[];
71
- /** Skin-specific options from `meta.skin.options`. */
72
- options?: Record<string, unknown>;
73
- }
74
- /**
75
- * Maps a `SimState` onto a skin's components: a `Frame` wrapping the thread
76
- * items (Message / SystemMessage), the typing indicators, and the composer.
77
- * Reactions render inside the skin's `Message` (it reads `message.reactions`).
78
- */
79
- declare function TypecaastStage({ state, skin, participants, options, }: TypecaastStageProps): ReactNode;
80
-
81
78
  /**
82
79
  * Resolve a theme mode to a concrete theme. `auto` falls back to `light`
83
80
  * here; M1U.4 makes `auto` reactive against the host `prefers-color-scheme`
@@ -135,4 +132,26 @@ interface TranscriptLine {
135
132
  */
136
133
  declare function buildTranscript(config: Config): TranscriptLine[];
137
134
 
138
- export { FitBox, type FitBoxProps, type FontLoadState, type TranscriptLine, Typecaast, type TypecaastControls, type TypecaastProps, TypecaastStage, type TypecaastStageProps, type UseTypecaastOptions, buildTranscript, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
135
+ type SkinModule = {
136
+ default: Skin;
137
+ };
138
+ /**
139
+ * Lazy loaders for the built-in skins, keyed by `meta.skin.id`. Each value is a
140
+ * **static** `import()` of a per-skin subpath, so bundlers emit one chunk per
141
+ * skin and only the skin a config actually references is fetched. Custom skins
142
+ * bypass this entirely (pass the `skin` prop to `<Typecaast>`).
143
+ *
144
+ * Adding a built-in skin = add one line here + the subpath export in
145
+ * `@typecaast/skins`.
146
+ */
147
+ declare const BUILTIN_SKIN_LOADERS: Record<string, () => Promise<SkinModule>>;
148
+ /** Ids of the built-in skins resolvable by `<Typecaast>` without a `skin` prop. */
149
+ declare const builtinSkinIds: string[];
150
+ /**
151
+ * Resolve a built-in skin id to a cached promise of its `Skin`. Throws
152
+ * synchronously for an unknown id (a render error with a clear message), rather
153
+ * than suspending forever.
154
+ */
155
+ declare function loadBuiltinSkin(id: string): Promise<Skin>;
156
+
157
+ export { BUILTIN_SKIN_LOADERS, FitBox, type FitBoxProps, type FontLoadState, type TranscriptLine, Typecaast, type TypecaastControls, type TypecaastProps, type UseTypecaastOptions, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,19 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
- import { Config, ThemeMode, FitMode, Participant, Size } from '@typecaast/schema';
3
- import { Skin } from '@typecaast/skin-kit';
2
+ import { Config, ThemeMode, FitMode, Size } from '@typecaast/schema';
3
+ import { Skin, ComposerMode } from '@typecaast/skin-kit';
4
+ export { ComposerMode, TypecaastStage, TypecaastStageProps } from '@typecaast/skin-kit';
4
5
  import { SimState, Player, Capabilities, ResolvedTheme } from '@typecaast/core';
5
6
 
6
7
  interface TypecaastProps {
7
8
  config: Config;
8
- skin: Skin;
9
+ /**
10
+ * The skin to render with. **Optional** — by default the built-in skin named
11
+ * by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk
12
+ * is fetched), so the config is the single source of truth and the embed stays
13
+ * fully serializable (works in a React Server Component, no `"use client"`).
14
+ * Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.
15
+ */
16
+ skin?: Skin;
9
17
  /** Force a theme; otherwise resolved from `config.meta.theme`. */
10
18
  theme?: ThemeMode;
11
19
  autoplay?: boolean;
@@ -13,18 +21,21 @@ interface TypecaastProps {
13
21
  rate?: number;
14
22
  /** Container fit mode; defaults to `config.meta.fit`. */
15
23
  fit?: FitMode;
24
+ /** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */
25
+ composer?: ComposerMode;
16
26
  /** Accessible label for the simulation. */
17
27
  label?: string;
18
28
  className?: string;
19
29
  style?: CSSProperties;
20
30
  }
21
31
  /**
22
- * Mounts the real-time player and renders the resolved skin from live state.
23
- * The animated visuals are `aria-hidden`; an accessible transcript carries the
24
- * conversation for screen readers, and `prefers-reduced-motion` snaps to the
25
- * final state instead of animating (PLAN §20).
32
+ * Renders a `<Typecaast>` from a config. The skin defaults to the built-in named
33
+ * by `config.meta.skin.id` (lazy-loaded by id see `builtin-skins.ts`); pass an
34
+ * explicit `skin` to use a custom one. `<Typecaast>` is a client component, but
35
+ * since the default path takes only the serializable `config`, the embed drops
36
+ * straight into a React Server Component.
26
37
  */
27
- declare function Typecaast({ config, skin, theme, autoplay, loop, rate, fit, label, className, style, }: TypecaastProps): ReactNode;
38
+ declare function Typecaast(props: TypecaastProps): ReactNode;
28
39
 
29
40
  interface UseTypecaastOptions {
30
41
  /** Force a theme; otherwise resolved from `config.meta.theme`. */
@@ -64,20 +75,6 @@ interface TypecaastControls {
64
75
  */
65
76
  declare function useTypecaast(config: Config, options?: UseTypecaastOptions): TypecaastControls;
66
77
 
67
- interface TypecaastStageProps {
68
- state: SimState;
69
- skin: Skin;
70
- participants: Participant[];
71
- /** Skin-specific options from `meta.skin.options`. */
72
- options?: Record<string, unknown>;
73
- }
74
- /**
75
- * Maps a `SimState` onto a skin's components: a `Frame` wrapping the thread
76
- * items (Message / SystemMessage), the typing indicators, and the composer.
77
- * Reactions render inside the skin's `Message` (it reads `message.reactions`).
78
- */
79
- declare function TypecaastStage({ state, skin, participants, options, }: TypecaastStageProps): ReactNode;
80
-
81
78
  /**
82
79
  * Resolve a theme mode to a concrete theme. `auto` falls back to `light`
83
80
  * here; M1U.4 makes `auto` reactive against the host `prefers-color-scheme`
@@ -135,4 +132,26 @@ interface TranscriptLine {
135
132
  */
136
133
  declare function buildTranscript(config: Config): TranscriptLine[];
137
134
 
138
- export { FitBox, type FitBoxProps, type FontLoadState, type TranscriptLine, Typecaast, type TypecaastControls, type TypecaastProps, TypecaastStage, type TypecaastStageProps, type UseTypecaastOptions, buildTranscript, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
135
+ type SkinModule = {
136
+ default: Skin;
137
+ };
138
+ /**
139
+ * Lazy loaders for the built-in skins, keyed by `meta.skin.id`. Each value is a
140
+ * **static** `import()` of a per-skin subpath, so bundlers emit one chunk per
141
+ * skin and only the skin a config actually references is fetched. Custom skins
142
+ * bypass this entirely (pass the `skin` prop to `<Typecaast>`).
143
+ *
144
+ * Adding a built-in skin = add one line here + the subpath export in
145
+ * `@typecaast/skins`.
146
+ */
147
+ declare const BUILTIN_SKIN_LOADERS: Record<string, () => Promise<SkinModule>>;
148
+ /** Ids of the built-in skins resolvable by `<Typecaast>` without a `skin` prop. */
149
+ declare const builtinSkinIds: string[];
150
+ /**
151
+ * Resolve a built-in skin id to a cached promise of its `Skin`. Throws
152
+ * synchronously for an unknown id (a render error with a clear message), rather
153
+ * than suspending forever.
154
+ */
155
+ declare function loadBuiltinSkin(id: string): Promise<Skin>;
156
+
157
+ export { BUILTIN_SKIN_LOADERS, FitBox, type FitBoxProps, type FontLoadState, type TranscriptLine, Typecaast, type TypecaastControls, type TypecaastProps, type UseTypecaastOptions, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
- import { useSyncExternalStore, useMemo, useState, useEffect, useRef, useLayoutEffect } from 'react';
1
+ "use client";
2
+ import { useSyncExternalStore, useMemo, useState, useEffect, useRef, useLayoutEffect, Suspense, use } from 'react';
3
+ import { loadSkinFonts, TypecaastStage } from '@typecaast/skin-kit';
4
+ export { TypecaastStage } from '@typecaast/skin-kit';
2
5
  import { createPlayer, createEngine } from '@typecaast/core';
3
- import { loadSkinFonts, ThemeProvider } from '@typecaast/skin-kit';
4
6
  import { jsx, jsxs } from 'react/jsx-runtime';
5
7
 
6
8
  // src/typecaast.tsx
@@ -155,88 +157,6 @@ function buildTranscript(config) {
155
157
  }
156
158
  return lines;
157
159
  }
158
- function TypecaastStage({
159
- state,
160
- skin,
161
- participants,
162
- options
163
- }) {
164
- const theme = state.theme;
165
- const byId = useMemo(() => {
166
- const map = /* @__PURE__ */ new Map();
167
- for (const p of participants) map.set(p.id, p);
168
- return map;
169
- }, [participants]);
170
- const { Frame, Message, SystemMessage, TypingIndicator, Composer } = skin.components;
171
- const tokens = skin.tokens?.[theme];
172
- const composerAuthor = state.composer.from ? byId.get(state.composer.from) : void 0;
173
- return /* @__PURE__ */ jsx(ThemeProvider, { theme, tokens, children: /* @__PURE__ */ jsxs(Frame, { theme, options, children: [
174
- /* @__PURE__ */ jsxs(
175
- "div",
176
- {
177
- "data-typecaast-thread": "",
178
- style: {
179
- display: "flex",
180
- flexDirection: "column",
181
- justifyContent: "flex-end",
182
- flex: "1 1 auto",
183
- minHeight: 0,
184
- overflow: "hidden"
185
- },
186
- children: [
187
- state.messages.map((message, i) => {
188
- const author = byId.get(message.from);
189
- if (!author) return null;
190
- if (message.variant === "system") {
191
- return /* @__PURE__ */ jsx(
192
- SystemMessage,
193
- {
194
- theme,
195
- message,
196
- author
197
- },
198
- message.id
199
- );
200
- }
201
- const prev = state.messages[i - 1];
202
- const previousAuthor = prev ? byId.get(prev.from) : void 0;
203
- return /* @__PURE__ */ jsx(
204
- Message,
205
- {
206
- theme,
207
- message,
208
- author,
209
- previousAuthor
210
- },
211
- message.id
212
- );
213
- }),
214
- state.typingIndicators.map((typing, i) => {
215
- const author = byId.get(typing.from);
216
- if (!author) return null;
217
- return /* @__PURE__ */ jsx(
218
- TypingIndicator,
219
- {
220
- theme,
221
- typing,
222
- author
223
- },
224
- `typing-${typing.from}-${i}`
225
- );
226
- })
227
- ]
228
- }
229
- ),
230
- composerAuthor ? /* @__PURE__ */ jsx(
231
- Composer,
232
- {
233
- theme,
234
- composer: state.composer,
235
- author: composerAuthor
236
- }
237
- ) : null
238
- ] }) });
239
- }
240
160
  function FitBox({
241
161
  fit,
242
162
  canvas,
@@ -311,6 +231,36 @@ function FitBox({
311
231
  }
312
232
  );
313
233
  }
234
+
235
+ // src/builtin-skins.ts
236
+ var BUILTIN_SKIN_LOADERS = {
237
+ slack: () => import('@typecaast/skins/slack'),
238
+ telegram: () => import('@typecaast/skins/telegram'),
239
+ "claude-code": () => import('@typecaast/skins/claude-code'),
240
+ imessage: () => import('@typecaast/skins/imessage'),
241
+ whatsapp: () => import('@typecaast/skins/whatsapp'),
242
+ cursor: () => import('@typecaast/skins/cursor'),
243
+ "messages-macos": () => import('@typecaast/skins/messages-macos'),
244
+ discord: () => import('@typecaast/skins/discord')
245
+ };
246
+ var builtinSkinIds = Object.keys(BUILTIN_SKIN_LOADERS);
247
+ var cache = /* @__PURE__ */ new Map();
248
+ function loadBuiltinSkin(id) {
249
+ let promise = cache.get(id);
250
+ if (!promise) {
251
+ const loader = BUILTIN_SKIN_LOADERS[id];
252
+ if (!loader) {
253
+ throw new Error(
254
+ `Typecaast: unknown skin "${id}". Built-in skins: ${builtinSkinIds.join(
255
+ ", "
256
+ )}. For a custom skin, pass the \`skin\` prop.`
257
+ );
258
+ }
259
+ promise = loader().then((m) => m.default);
260
+ cache.set(id, promise);
261
+ }
262
+ return promise;
263
+ }
314
264
  var SR_ONLY = {
315
265
  position: "absolute",
316
266
  width: 1,
@@ -322,7 +272,30 @@ var SR_ONLY = {
322
272
  whiteSpace: "nowrap",
323
273
  border: 0
324
274
  };
325
- function Typecaast({
275
+ function Typecaast(props) {
276
+ if (props.skin) return /* @__PURE__ */ jsx(Player, { ...props, skin: props.skin });
277
+ return /* @__PURE__ */ jsx(
278
+ Suspense,
279
+ {
280
+ fallback: /* @__PURE__ */ jsx(
281
+ SkinFallback,
282
+ {
283
+ config: props.config,
284
+ fit: props.fit,
285
+ label: props.label,
286
+ className: props.className,
287
+ style: props.style
288
+ }
289
+ ),
290
+ children: /* @__PURE__ */ jsx(ResolvedPlayer, { ...props })
291
+ }
292
+ );
293
+ }
294
+ function ResolvedPlayer(props) {
295
+ const skin = use(loadBuiltinSkin(props.config.meta.skin.id));
296
+ return /* @__PURE__ */ jsx(Player, { ...props, skin });
297
+ }
298
+ function Player({
326
299
  config,
327
300
  skin,
328
301
  theme,
@@ -330,6 +303,7 @@ function Typecaast({
330
303
  loop,
331
304
  rate,
332
305
  fit,
306
+ composer,
333
307
  label,
334
308
  className,
335
309
  style
@@ -368,19 +342,50 @@ function Typecaast({
368
342
  state: tc.state,
369
343
  skin,
370
344
  participants: config.participants,
371
- options: config.meta.skin.options
345
+ options: config.meta.skin.options,
346
+ composer: composer ?? config.meta.composer
372
347
  }
373
348
  ) }) })
374
349
  ]
375
350
  }
376
351
  );
377
352
  }
353
+ function SkinFallback({
354
+ config,
355
+ fit,
356
+ label,
357
+ className,
358
+ style
359
+ }) {
360
+ return /* @__PURE__ */ jsx(
361
+ "div",
362
+ {
363
+ className,
364
+ style: { position: "relative", ...style },
365
+ "data-typecaast": "",
366
+ "data-typecaast-loading": "",
367
+ role: "figure",
368
+ "aria-label": label ?? "Chat simulation",
369
+ "aria-busy": "true",
370
+ children: /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { height: "100%" }, children: /* @__PURE__ */ jsx(FitBox, { fit: fit ?? config.meta.fit, canvas: config.meta.canvas, children: /* @__PURE__ */ jsx(
371
+ "div",
372
+ {
373
+ style: {
374
+ width: "100%",
375
+ height: "100%",
376
+ background: "var(--tc-skin-loading-bg, transparent)"
377
+ }
378
+ }
379
+ ) }) })
380
+ }
381
+ );
382
+ }
378
383
 
379
384
  // src/resolve-theme.ts
380
385
  function resolveTheme(mode) {
381
386
  return mode === "dark" ? "dark" : mode === "light" ? "light" : "light";
382
387
  }
383
388
 
384
- export { FitBox, Typecaast, TypecaastStage, buildTranscript, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
389
+ export { BUILTIN_SKIN_LOADERS, FitBox, Typecaast, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
385
390
  //# sourceMappingURL=index.js.map
386
391
  //# sourceMappingURL=index.js.map
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/stage.tsx","../src/fit-box.tsx","../src/typecaast.tsx","../src/resolve-theme.ts"],"names":["useState","useEffect","QUERY","getMql","subscribe","getSnapshot","getServerSnapshot","useSyncExternalStore","useMemo","jsx","jsxs"],"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 = 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 { useMemo, type ReactNode } from \"react\";\nimport type { Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider, type Skin } from \"@typecaast/skin-kit\";\n\nexport interface TypecaastStageProps {\n state: SimState;\n skin: Skin;\n participants: Participant[];\n /** Skin-specific options from `meta.skin.options`. */\n options?: Record<string, unknown>;\n}\n\n/**\n * Maps a `SimState` onto a skin's components: a `Frame` wrapping the thread\n * items (Message / SystemMessage), the typing indicators, and the composer.\n * Reactions render inside the skin's `Message` (it reads `message.reactions`).\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n}: TypecaastStageProps): ReactNode {\n const theme = state.theme;\n const byId = useMemo(() => {\n const map = new Map<string, Participant>();\n for (const p of participants) map.set(p.id, p);\n return map;\n }, [participants]);\n\n const { Frame, Message, SystemMessage, TypingIndicator, Composer } =\n skin.components;\n const tokens = skin.tokens?.[theme];\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : undefined;\n\n return (\n <ThemeProvider theme={theme} tokens={tokens}>\n <Frame theme={theme} options={options}>\n {/* Thread viewport: bottom-anchored + clipped, so as the thread grows\n older items shift up out of view (\"content shifts up\", PLAN §7).\n The engine's scroll.targetOffset is honored here once real layout\n measurement lands; for now the flex anchor gives the same feel. */}\n <div\n data-typecaast-thread=\"\"\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"flex-end\",\n flex: \"1 1 auto\",\n minHeight: 0,\n overflow: \"hidden\",\n }}\n >\n {state.messages.map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={message.id}\n theme={theme}\n message={message}\n author={author}\n />\n );\n }\n const prev = state.messages[i - 1];\n const previousAuthor = prev ? byId.get(prev.from) : undefined;\n return (\n <Message\n key={message.id}\n theme={theme}\n message={message}\n author={author}\n previousAuthor={previousAuthor}\n />\n );\n })}\n {state.typingIndicators.map((typing, i) => {\n const author = byId.get(typing.from);\n if (!author) return null;\n return (\n <TypingIndicator\n key={`typing-${typing.from}-${i}`}\n theme={theme}\n typing={typing}\n author={author}\n />\n );\n })}\n </div>\n {composerAuthor ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n />\n ) : null}\n </Frame>\n </ThemeProvider>\n );\n}\n","import {\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\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 width; content re-wraps (container query / ResizeObserver).\n * - `scale`: renders at exact canvas size, CSS-scaled to fit (layout preserved).\n * - `fixed`: exact canvas size, clipped.\n */\nexport function FitBox({\n fit,\n canvas,\n children,\n className,\n style,\n}: FitBoxProps): ReactNode {\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 style={{ width: \"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 { useEffect, useMemo, type CSSProperties, type ReactNode } from \"react\";\nimport type { Config, FitMode, ThemeMode } from \"@typecaast/schema\";\nimport type { Skin } 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 { TypecaastStage } from \"./stage.js\";\nimport { FitBox } from \"./fit-box.js\";\n\nexport interface TypecaastProps {\n config: Config;\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 /** 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 * Mounts the real-time player and renders the resolved skin from live state.\n * The animated visuals are `aria-hidden`; an accessible transcript carries the\n * conversation for screen readers, and `prefers-reduced-motion` snaps to the\n * final state instead of animating (PLAN §20).\n */\nexport function Typecaast({\n config,\n skin,\n theme,\n autoplay,\n loop,\n rate,\n fit,\n label,\n className,\n style,\n}: TypecaastProps): ReactNode {\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={{ position: \"relative\", ...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={{ 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 />\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","jsx","useMemo"],"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 = 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 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 width; content re-wraps (container query / ResizeObserver).\n * - `scale`: renders at exact canvas size, CSS-scaled to fit (layout preserved).\n * - `fixed`: exact canvas size, clipped.\n */\nexport function FitBox({\n fit,\n canvas,\n children,\n className,\n style,\n}: FitBoxProps): ReactNode {\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 style={{ width: \"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// Stable promise per id so React's `use()` sees the same promise across renders.\nconst cache = new Map<string, Promise<Skin>>();\n\n/**\n * Resolve a built-in skin id to a cached promise of its `Skin`. Throws\n * synchronously for an unknown id (a render error with a clear message), rather\n * than suspending forever.\n */\nexport function loadBuiltinSkin(id: string): Promise<Skin> {\n let promise = cache.get(id);\n if (!promise) {\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 promise = loader().then((m) => m.default);\n cache.set(id, promise);\n }\n return promise;\n}\n","import {\n Suspense,\n use,\n useEffect,\n useMemo,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport type { Config, FitMode, ThemeMode } 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 { loadBuiltinSkin } from \"./builtin-skins.js\";\n\nexport interface TypecaastProps {\n config: Config;\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 * 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): ReactNode {\n // Explicit skin object → render synchronously, no lazy load.\n if (props.skin) return <Player {...props} 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={props.config}\n fit={props.fit}\n label={props.label}\n className={props.className}\n style={props.style}\n />\n }\n >\n <ResolvedPlayer {...props} />\n </Suspense>\n );\n}\n\nfunction ResolvedPlayer(props: TypecaastProps): ReactNode {\n const skin = use(loadBuiltinSkin(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}: TypecaastProps & { skin: Skin }): ReactNode {\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={{ position: \"relative\", ...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={{ 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, \"config\" | \"fit\" | \"label\" | \"className\" | \"style\">) {\n return (\n <div\n className={className}\n style={{ position: \"relative\", ...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={{ 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,8 +1,13 @@
1
1
  {
2
2
  "name": "@typecaast/react",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "React renderer: <Typecaast> real-time player + useTypecaast hook.",
5
5
  "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/corywatilo/typecaast.git",
9
+ "directory": "packages/react"
10
+ },
6
11
  "type": "module",
7
12
  "sideEffects": false,
8
13
  "files": [
@@ -19,9 +24,10 @@
19
24
  }
20
25
  },
21
26
  "dependencies": {
22
- "@typecaast/core": "0.1.0",
27
+ "@typecaast/core": "0.1.1",
23
28
  "@typecaast/schema": "0.1.0",
24
- "@typecaast/skin-kit": "0.1.0"
29
+ "@typecaast/skin-kit": "0.2.0",
30
+ "@typecaast/skins": "0.2.0"
25
31
  },
26
32
  "peerDependencies": {
27
33
  "react": ">=18"