@typecaast/react 0.1.0 → 0.2.1
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 +103 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -24
- package/dist/index.d.ts +85 -24
- package/dist/index.js +98 -87
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
var react = require('react');
|
|
4
|
-
var
|
|
5
|
+
var schema = require('@typecaast/schema');
|
|
5
6
|
var skinKit = require('@typecaast/skin-kit');
|
|
7
|
+
var core = require('@typecaast/core');
|
|
6
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
7
9
|
|
|
8
10
|
// src/typecaast.tsx
|
|
@@ -157,88 +159,6 @@ function buildTranscript(config) {
|
|
|
157
159
|
}
|
|
158
160
|
return lines;
|
|
159
161
|
}
|
|
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
162
|
function FitBox({
|
|
243
163
|
fit,
|
|
244
164
|
canvas,
|
|
@@ -313,6 +233,36 @@ function FitBox({
|
|
|
313
233
|
}
|
|
314
234
|
);
|
|
315
235
|
}
|
|
236
|
+
|
|
237
|
+
// src/builtin-skins.ts
|
|
238
|
+
var BUILTIN_SKIN_LOADERS = {
|
|
239
|
+
slack: () => import('@typecaast/skins/slack'),
|
|
240
|
+
telegram: () => import('@typecaast/skins/telegram'),
|
|
241
|
+
"claude-code": () => import('@typecaast/skins/claude-code'),
|
|
242
|
+
imessage: () => import('@typecaast/skins/imessage'),
|
|
243
|
+
whatsapp: () => import('@typecaast/skins/whatsapp'),
|
|
244
|
+
cursor: () => import('@typecaast/skins/cursor'),
|
|
245
|
+
"messages-macos": () => import('@typecaast/skins/messages-macos'),
|
|
246
|
+
discord: () => import('@typecaast/skins/discord')
|
|
247
|
+
};
|
|
248
|
+
var builtinSkinIds = Object.keys(BUILTIN_SKIN_LOADERS);
|
|
249
|
+
var cache = /* @__PURE__ */ new Map();
|
|
250
|
+
function loadBuiltinSkin(id) {
|
|
251
|
+
let promise = cache.get(id);
|
|
252
|
+
if (!promise) {
|
|
253
|
+
const loader = BUILTIN_SKIN_LOADERS[id];
|
|
254
|
+
if (!loader) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Typecaast: unknown skin "${id}". Built-in skins: ${builtinSkinIds.join(
|
|
257
|
+
", "
|
|
258
|
+
)}. For a custom skin, pass the \`skin\` prop.`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
promise = loader().then((m) => m.default);
|
|
262
|
+
cache.set(id, promise);
|
|
263
|
+
}
|
|
264
|
+
return promise;
|
|
265
|
+
}
|
|
316
266
|
var SR_ONLY = {
|
|
317
267
|
position: "absolute",
|
|
318
268
|
width: 1,
|
|
@@ -324,7 +274,35 @@ var SR_ONLY = {
|
|
|
324
274
|
whiteSpace: "nowrap",
|
|
325
275
|
border: 0
|
|
326
276
|
};
|
|
327
|
-
function Typecaast({
|
|
277
|
+
function Typecaast(props) {
|
|
278
|
+
const config = react.useMemo(
|
|
279
|
+
() => schema.configSchema.parse(props.config),
|
|
280
|
+
[props.config]
|
|
281
|
+
);
|
|
282
|
+
if (props.skin)
|
|
283
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props, config, skin: props.skin });
|
|
284
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
285
|
+
react.Suspense,
|
|
286
|
+
{
|
|
287
|
+
fallback: /* @__PURE__ */ jsxRuntime.jsx(
|
|
288
|
+
SkinFallback,
|
|
289
|
+
{
|
|
290
|
+
config,
|
|
291
|
+
fit: props.fit,
|
|
292
|
+
label: props.label,
|
|
293
|
+
className: props.className,
|
|
294
|
+
style: props.style
|
|
295
|
+
}
|
|
296
|
+
),
|
|
297
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ResolvedPlayer, { ...props, config })
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
function ResolvedPlayer(props) {
|
|
302
|
+
const skin = react.use(loadBuiltinSkin(props.config.meta.skin.id));
|
|
303
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Player, { ...props, skin });
|
|
304
|
+
}
|
|
305
|
+
function Player({
|
|
328
306
|
config,
|
|
329
307
|
skin,
|
|
330
308
|
theme,
|
|
@@ -332,6 +310,7 @@ function Typecaast({
|
|
|
332
310
|
loop,
|
|
333
311
|
rate,
|
|
334
312
|
fit,
|
|
313
|
+
composer,
|
|
335
314
|
label,
|
|
336
315
|
className,
|
|
337
316
|
style
|
|
@@ -365,28 +344,65 @@ function Typecaast({
|
|
|
365
344
|
line.text
|
|
366
345
|
] }, i)) }),
|
|
367
346
|
/* @__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,
|
|
347
|
+
skinKit.TypecaastStage,
|
|
369
348
|
{
|
|
370
349
|
state: tc.state,
|
|
371
350
|
skin,
|
|
372
351
|
participants: config.participants,
|
|
373
|
-
options: config.meta.skin.options
|
|
352
|
+
options: config.meta.skin.options,
|
|
353
|
+
composer: composer ?? config.meta.composer
|
|
374
354
|
}
|
|
375
355
|
) }) })
|
|
376
356
|
]
|
|
377
357
|
}
|
|
378
358
|
);
|
|
379
359
|
}
|
|
360
|
+
function SkinFallback({
|
|
361
|
+
config,
|
|
362
|
+
fit,
|
|
363
|
+
label,
|
|
364
|
+
className,
|
|
365
|
+
style
|
|
366
|
+
}) {
|
|
367
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
368
|
+
"div",
|
|
369
|
+
{
|
|
370
|
+
className,
|
|
371
|
+
style: { position: "relative", ...style },
|
|
372
|
+
"data-typecaast": "",
|
|
373
|
+
"data-typecaast-loading": "",
|
|
374
|
+
role: "figure",
|
|
375
|
+
"aria-label": label ?? "Chat simulation",
|
|
376
|
+
"aria-busy": "true",
|
|
377
|
+
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(
|
|
378
|
+
"div",
|
|
379
|
+
{
|
|
380
|
+
style: {
|
|
381
|
+
width: "100%",
|
|
382
|
+
height: "100%",
|
|
383
|
+
background: "var(--tc-skin-loading-bg, transparent)"
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
) }) })
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
}
|
|
380
390
|
|
|
381
391
|
// src/resolve-theme.ts
|
|
382
392
|
function resolveTheme(mode) {
|
|
383
393
|
return mode === "dark" ? "dark" : mode === "light" ? "light" : "light";
|
|
384
394
|
}
|
|
385
395
|
|
|
396
|
+
Object.defineProperty(exports, "TypecaastStage", {
|
|
397
|
+
enumerable: true,
|
|
398
|
+
get: function () { return skinKit.TypecaastStage; }
|
|
399
|
+
});
|
|
400
|
+
exports.BUILTIN_SKIN_LOADERS = BUILTIN_SKIN_LOADERS;
|
|
386
401
|
exports.FitBox = FitBox;
|
|
387
402
|
exports.Typecaast = Typecaast;
|
|
388
|
-
exports.TypecaastStage = TypecaastStage;
|
|
389
403
|
exports.buildTranscript = buildTranscript;
|
|
404
|
+
exports.builtinSkinIds = builtinSkinIds;
|
|
405
|
+
exports.loadBuiltinSkin = loadBuiltinSkin;
|
|
390
406
|
exports.resolveTheme = resolveTheme;
|
|
391
407
|
exports.usePrefersDark = usePrefersDark;
|
|
392
408
|
exports.useReducedMotion = useReducedMotion;
|
package/dist/index.cjs.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":["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","configSchema","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 {\n configSchema,\n type Config,\n type ConfigInput,\n type FitMode,\n type ThemeMode,\n} from \"@typecaast/schema\";\nimport {\n TypecaastStage,\n type ComposerMode,\n type Skin,\n} from \"@typecaast/skin-kit\";\nimport { useTypecaast } from \"./use-typecaast.js\";\nimport { useSkinFonts } from \"./use-skin-fonts.js\";\nimport { useReducedMotion } from \"./use-reduced-motion.js\";\nimport { buildTranscript } from \"./transcript.js\";\nimport { FitBox } from \"./fit-box.js\";\nimport { loadBuiltinSkin } from \"./builtin-skins.js\";\n\n/**\n * A loosely-typed config shape that a raw `import`ed `typecaast.json` satisfies —\n * TypeScript widens JSON literals (e.g. `version: number`, `type: string`), so it\n * matches neither `Config` nor `ConfigInput`. It's validated and normalized at\n * runtime, so this stays a convenience surface, not a bypass.\n */\nexport interface RawConfig {\n version: number;\n meta: {\n canvas: { width: number; height: number };\n skin: { id: string; options?: Record<string, unknown> };\n [key: string]: unknown;\n };\n participants: Array<{ id: string; name: string; [key: string]: unknown }>;\n timeline: Array<{ type: string; [key: string]: unknown }>;\n pacing?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * What `<Typecaast config>` accepts: a precise `ConfigInput`/`Config` (full\n * intellisense when hand-authoring) or a raw config object such as an imported\n * `typecaast.json`. All forms are normalized through the schema at runtime.\n */\nexport type TypecaastConfig = ConfigInput | Config | RawConfig;\n\nexport interface TypecaastProps {\n /**\n * The conversation config. Accepts your exported `typecaast.json` directly (or\n * a hand-authored `ConfigInput`); it's validated and defaulted at runtime, so\n * you never need to pre-parse it.\n */\n config: TypecaastConfig;\n /**\n * The skin to render with. **Optional** — by default the built-in skin named\n * by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk\n * is fetched), so the config is the single source of truth and the embed stays\n * fully serializable (works in a React Server Component, no `\"use client\"`).\n * Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.\n */\n skin?: Skin;\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** Container fit mode; defaults to `config.meta.fit`. */\n fit?: FitMode;\n /** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */\n composer?: ComposerMode;\n /** Accessible label for the simulation. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nconst SR_ONLY: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\n/**\n * 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 // Normalize once: validate and apply schema defaults (pacing, fit, theme, …)\n // so a raw exported `typecaast.json` works without the caller pre-parsing it.\n const config = useMemo<Config>(\n () => configSchema.parse(props.config),\n [props.config],\n );\n\n // Explicit skin object → render synchronously, no lazy load.\n if (props.skin)\n return <Player {...props} config={config} skin={props.skin} />;\n // Otherwise resolve (and lazy-load) the built-in named in the config.\n return (\n <Suspense\n fallback={\n <SkinFallback\n config={config}\n fit={props.fit}\n label={props.label}\n className={props.className}\n style={props.style}\n />\n }\n >\n <ResolvedPlayer {...props} config={config} />\n </Suspense>\n );\n}\n\nfunction ResolvedPlayer(\n props: Omit<TypecaastProps, \"config\"> & { config: Config },\n): 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}: Omit<TypecaastProps, \"config\"> & {\n config: Config;\n skin: Skin;\n}): 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, \"fit\" | \"label\" | \"className\" | \"style\"> & {\n config: Config;\n}) {\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,61 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
-
import { Config, ThemeMode, FitMode,
|
|
3
|
-
import { Skin } from '@typecaast/skin-kit';
|
|
2
|
+
import { ConfigInput, 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
|
|
|
7
|
+
/**
|
|
8
|
+
* A loosely-typed config shape that a raw `import`ed `typecaast.json` satisfies —
|
|
9
|
+
* TypeScript widens JSON literals (e.g. `version: number`, `type: string`), so it
|
|
10
|
+
* matches neither `Config` nor `ConfigInput`. It's validated and normalized at
|
|
11
|
+
* runtime, so this stays a convenience surface, not a bypass.
|
|
12
|
+
*/
|
|
13
|
+
interface RawConfig {
|
|
14
|
+
version: number;
|
|
15
|
+
meta: {
|
|
16
|
+
canvas: {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
skin: {
|
|
21
|
+
id: string;
|
|
22
|
+
options?: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
participants: Array<{
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}>;
|
|
31
|
+
timeline: Array<{
|
|
32
|
+
type: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}>;
|
|
35
|
+
pacing?: Record<string, unknown>;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* What `<Typecaast config>` accepts: a precise `ConfigInput`/`Config` (full
|
|
40
|
+
* intellisense when hand-authoring) or a raw config object such as an imported
|
|
41
|
+
* `typecaast.json`. All forms are normalized through the schema at runtime.
|
|
42
|
+
*/
|
|
43
|
+
type TypecaastConfig = ConfigInput | Config | RawConfig;
|
|
6
44
|
interface TypecaastProps {
|
|
7
|
-
|
|
8
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The conversation config. Accepts your exported `typecaast.json` directly (or
|
|
47
|
+
* a hand-authored `ConfigInput`); it's validated and defaulted at runtime, so
|
|
48
|
+
* you never need to pre-parse it.
|
|
49
|
+
*/
|
|
50
|
+
config: TypecaastConfig;
|
|
51
|
+
/**
|
|
52
|
+
* The skin to render with. **Optional** — by default the built-in skin named
|
|
53
|
+
* by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk
|
|
54
|
+
* is fetched), so the config is the single source of truth and the embed stays
|
|
55
|
+
* fully serializable (works in a React Server Component, no `"use client"`).
|
|
56
|
+
* Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.
|
|
57
|
+
*/
|
|
58
|
+
skin?: Skin;
|
|
9
59
|
/** Force a theme; otherwise resolved from `config.meta.theme`. */
|
|
10
60
|
theme?: ThemeMode;
|
|
11
61
|
autoplay?: boolean;
|
|
@@ -13,18 +63,21 @@ interface TypecaastProps {
|
|
|
13
63
|
rate?: number;
|
|
14
64
|
/** Container fit mode; defaults to `config.meta.fit`. */
|
|
15
65
|
fit?: FitMode;
|
|
66
|
+
/** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */
|
|
67
|
+
composer?: ComposerMode;
|
|
16
68
|
/** Accessible label for the simulation. */
|
|
17
69
|
label?: string;
|
|
18
70
|
className?: string;
|
|
19
71
|
style?: CSSProperties;
|
|
20
72
|
}
|
|
21
73
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
74
|
+
* Renders a `<Typecaast>` from a config. The skin defaults to the built-in named
|
|
75
|
+
* by `config.meta.skin.id` (lazy-loaded by id — see `builtin-skins.ts`); pass an
|
|
76
|
+
* explicit `skin` to use a custom one. `<Typecaast>` is a client component, but
|
|
77
|
+
* since the default path takes only the serializable `config`, the embed drops
|
|
78
|
+
* straight into a React Server Component.
|
|
26
79
|
*/
|
|
27
|
-
declare function Typecaast(
|
|
80
|
+
declare function Typecaast(props: TypecaastProps): ReactNode;
|
|
28
81
|
|
|
29
82
|
interface UseTypecaastOptions {
|
|
30
83
|
/** Force a theme; otherwise resolved from `config.meta.theme`. */
|
|
@@ -64,20 +117,6 @@ interface TypecaastControls {
|
|
|
64
117
|
*/
|
|
65
118
|
declare function useTypecaast(config: Config, options?: UseTypecaastOptions): TypecaastControls;
|
|
66
119
|
|
|
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
120
|
/**
|
|
82
121
|
* Resolve a theme mode to a concrete theme. `auto` falls back to `light`
|
|
83
122
|
* here; M1U.4 makes `auto` reactive against the host `prefers-color-scheme`
|
|
@@ -135,4 +174,26 @@ interface TranscriptLine {
|
|
|
135
174
|
*/
|
|
136
175
|
declare function buildTranscript(config: Config): TranscriptLine[];
|
|
137
176
|
|
|
138
|
-
|
|
177
|
+
type SkinModule = {
|
|
178
|
+
default: Skin;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Lazy loaders for the built-in skins, keyed by `meta.skin.id`. Each value is a
|
|
182
|
+
* **static** `import()` of a per-skin subpath, so bundlers emit one chunk per
|
|
183
|
+
* skin and only the skin a config actually references is fetched. Custom skins
|
|
184
|
+
* bypass this entirely (pass the `skin` prop to `<Typecaast>`).
|
|
185
|
+
*
|
|
186
|
+
* Adding a built-in skin = add one line here + the subpath export in
|
|
187
|
+
* `@typecaast/skins`.
|
|
188
|
+
*/
|
|
189
|
+
declare const BUILTIN_SKIN_LOADERS: Record<string, () => Promise<SkinModule>>;
|
|
190
|
+
/** Ids of the built-in skins resolvable by `<Typecaast>` without a `skin` prop. */
|
|
191
|
+
declare const builtinSkinIds: string[];
|
|
192
|
+
/**
|
|
193
|
+
* Resolve a built-in skin id to a cached promise of its `Skin`. Throws
|
|
194
|
+
* synchronously for an unknown id (a render error with a clear message), rather
|
|
195
|
+
* than suspending forever.
|
|
196
|
+
*/
|
|
197
|
+
declare function loadBuiltinSkin(id: string): Promise<Skin>;
|
|
198
|
+
|
|
199
|
+
export { BUILTIN_SKIN_LOADERS, FitBox, type FitBoxProps, type FontLoadState, type RawConfig, type TranscriptLine, Typecaast, type TypecaastConfig, type TypecaastControls, type TypecaastProps, type UseTypecaastOptions, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,61 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
-
import { Config, ThemeMode, FitMode,
|
|
3
|
-
import { Skin } from '@typecaast/skin-kit';
|
|
2
|
+
import { ConfigInput, 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
|
|
|
7
|
+
/**
|
|
8
|
+
* A loosely-typed config shape that a raw `import`ed `typecaast.json` satisfies —
|
|
9
|
+
* TypeScript widens JSON literals (e.g. `version: number`, `type: string`), so it
|
|
10
|
+
* matches neither `Config` nor `ConfigInput`. It's validated and normalized at
|
|
11
|
+
* runtime, so this stays a convenience surface, not a bypass.
|
|
12
|
+
*/
|
|
13
|
+
interface RawConfig {
|
|
14
|
+
version: number;
|
|
15
|
+
meta: {
|
|
16
|
+
canvas: {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
skin: {
|
|
21
|
+
id: string;
|
|
22
|
+
options?: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
participants: Array<{
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}>;
|
|
31
|
+
timeline: Array<{
|
|
32
|
+
type: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}>;
|
|
35
|
+
pacing?: Record<string, unknown>;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* What `<Typecaast config>` accepts: a precise `ConfigInput`/`Config` (full
|
|
40
|
+
* intellisense when hand-authoring) or a raw config object such as an imported
|
|
41
|
+
* `typecaast.json`. All forms are normalized through the schema at runtime.
|
|
42
|
+
*/
|
|
43
|
+
type TypecaastConfig = ConfigInput | Config | RawConfig;
|
|
6
44
|
interface TypecaastProps {
|
|
7
|
-
|
|
8
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The conversation config. Accepts your exported `typecaast.json` directly (or
|
|
47
|
+
* a hand-authored `ConfigInput`); it's validated and defaulted at runtime, so
|
|
48
|
+
* you never need to pre-parse it.
|
|
49
|
+
*/
|
|
50
|
+
config: TypecaastConfig;
|
|
51
|
+
/**
|
|
52
|
+
* The skin to render with. **Optional** — by default the built-in skin named
|
|
53
|
+
* by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk
|
|
54
|
+
* is fetched), so the config is the single source of truth and the embed stays
|
|
55
|
+
* fully serializable (works in a React Server Component, no `"use client"`).
|
|
56
|
+
* Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.
|
|
57
|
+
*/
|
|
58
|
+
skin?: Skin;
|
|
9
59
|
/** Force a theme; otherwise resolved from `config.meta.theme`. */
|
|
10
60
|
theme?: ThemeMode;
|
|
11
61
|
autoplay?: boolean;
|
|
@@ -13,18 +63,21 @@ interface TypecaastProps {
|
|
|
13
63
|
rate?: number;
|
|
14
64
|
/** Container fit mode; defaults to `config.meta.fit`. */
|
|
15
65
|
fit?: FitMode;
|
|
66
|
+
/** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */
|
|
67
|
+
composer?: ComposerMode;
|
|
16
68
|
/** Accessible label for the simulation. */
|
|
17
69
|
label?: string;
|
|
18
70
|
className?: string;
|
|
19
71
|
style?: CSSProperties;
|
|
20
72
|
}
|
|
21
73
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
74
|
+
* Renders a `<Typecaast>` from a config. The skin defaults to the built-in named
|
|
75
|
+
* by `config.meta.skin.id` (lazy-loaded by id — see `builtin-skins.ts`); pass an
|
|
76
|
+
* explicit `skin` to use a custom one. `<Typecaast>` is a client component, but
|
|
77
|
+
* since the default path takes only the serializable `config`, the embed drops
|
|
78
|
+
* straight into a React Server Component.
|
|
26
79
|
*/
|
|
27
|
-
declare function Typecaast(
|
|
80
|
+
declare function Typecaast(props: TypecaastProps): ReactNode;
|
|
28
81
|
|
|
29
82
|
interface UseTypecaastOptions {
|
|
30
83
|
/** Force a theme; otherwise resolved from `config.meta.theme`. */
|
|
@@ -64,20 +117,6 @@ interface TypecaastControls {
|
|
|
64
117
|
*/
|
|
65
118
|
declare function useTypecaast(config: Config, options?: UseTypecaastOptions): TypecaastControls;
|
|
66
119
|
|
|
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
120
|
/**
|
|
82
121
|
* Resolve a theme mode to a concrete theme. `auto` falls back to `light`
|
|
83
122
|
* here; M1U.4 makes `auto` reactive against the host `prefers-color-scheme`
|
|
@@ -135,4 +174,26 @@ interface TranscriptLine {
|
|
|
135
174
|
*/
|
|
136
175
|
declare function buildTranscript(config: Config): TranscriptLine[];
|
|
137
176
|
|
|
138
|
-
|
|
177
|
+
type SkinModule = {
|
|
178
|
+
default: Skin;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Lazy loaders for the built-in skins, keyed by `meta.skin.id`. Each value is a
|
|
182
|
+
* **static** `import()` of a per-skin subpath, so bundlers emit one chunk per
|
|
183
|
+
* skin and only the skin a config actually references is fetched. Custom skins
|
|
184
|
+
* bypass this entirely (pass the `skin` prop to `<Typecaast>`).
|
|
185
|
+
*
|
|
186
|
+
* Adding a built-in skin = add one line here + the subpath export in
|
|
187
|
+
* `@typecaast/skins`.
|
|
188
|
+
*/
|
|
189
|
+
declare const BUILTIN_SKIN_LOADERS: Record<string, () => Promise<SkinModule>>;
|
|
190
|
+
/** Ids of the built-in skins resolvable by `<Typecaast>` without a `skin` prop. */
|
|
191
|
+
declare const builtinSkinIds: string[];
|
|
192
|
+
/**
|
|
193
|
+
* Resolve a built-in skin id to a cached promise of its `Skin`. Throws
|
|
194
|
+
* synchronously for an unknown id (a render error with a clear message), rather
|
|
195
|
+
* than suspending forever.
|
|
196
|
+
*/
|
|
197
|
+
declare function loadBuiltinSkin(id: string): Promise<Skin>;
|
|
198
|
+
|
|
199
|
+
export { BUILTIN_SKIN_LOADERS, FitBox, type FitBoxProps, type FontLoadState, type RawConfig, type TranscriptLine, Typecaast, type TypecaastConfig, type TypecaastControls, type TypecaastProps, type UseTypecaastOptions, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import { useSyncExternalStore, useMemo, useState, useEffect, useRef, useLayoutEffect, Suspense, use } from 'react';
|
|
3
|
+
import { configSchema } from '@typecaast/schema';
|
|
4
|
+
import { loadSkinFonts, TypecaastStage } from '@typecaast/skin-kit';
|
|
5
|
+
export { TypecaastStage } from '@typecaast/skin-kit';
|
|
2
6
|
import { createPlayer, createEngine } from '@typecaast/core';
|
|
3
|
-
import { loadSkinFonts, ThemeProvider } from '@typecaast/skin-kit';
|
|
4
7
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
8
|
|
|
6
9
|
// src/typecaast.tsx
|
|
@@ -155,88 +158,6 @@ function buildTranscript(config) {
|
|
|
155
158
|
}
|
|
156
159
|
return lines;
|
|
157
160
|
}
|
|
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
161
|
function FitBox({
|
|
241
162
|
fit,
|
|
242
163
|
canvas,
|
|
@@ -311,6 +232,36 @@ function FitBox({
|
|
|
311
232
|
}
|
|
312
233
|
);
|
|
313
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
|
+
}
|
|
314
265
|
var SR_ONLY = {
|
|
315
266
|
position: "absolute",
|
|
316
267
|
width: 1,
|
|
@@ -322,7 +273,35 @@ var SR_ONLY = {
|
|
|
322
273
|
whiteSpace: "nowrap",
|
|
323
274
|
border: 0
|
|
324
275
|
};
|
|
325
|
-
function Typecaast({
|
|
276
|
+
function Typecaast(props) {
|
|
277
|
+
const config = useMemo(
|
|
278
|
+
() => configSchema.parse(props.config),
|
|
279
|
+
[props.config]
|
|
280
|
+
);
|
|
281
|
+
if (props.skin)
|
|
282
|
+
return /* @__PURE__ */ jsx(Player, { ...props, config, skin: props.skin });
|
|
283
|
+
return /* @__PURE__ */ jsx(
|
|
284
|
+
Suspense,
|
|
285
|
+
{
|
|
286
|
+
fallback: /* @__PURE__ */ jsx(
|
|
287
|
+
SkinFallback,
|
|
288
|
+
{
|
|
289
|
+
config,
|
|
290
|
+
fit: props.fit,
|
|
291
|
+
label: props.label,
|
|
292
|
+
className: props.className,
|
|
293
|
+
style: props.style
|
|
294
|
+
}
|
|
295
|
+
),
|
|
296
|
+
children: /* @__PURE__ */ jsx(ResolvedPlayer, { ...props, config })
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
function ResolvedPlayer(props) {
|
|
301
|
+
const skin = use(loadBuiltinSkin(props.config.meta.skin.id));
|
|
302
|
+
return /* @__PURE__ */ jsx(Player, { ...props, skin });
|
|
303
|
+
}
|
|
304
|
+
function Player({
|
|
326
305
|
config,
|
|
327
306
|
skin,
|
|
328
307
|
theme,
|
|
@@ -330,6 +309,7 @@ function Typecaast({
|
|
|
330
309
|
loop,
|
|
331
310
|
rate,
|
|
332
311
|
fit,
|
|
312
|
+
composer,
|
|
333
313
|
label,
|
|
334
314
|
className,
|
|
335
315
|
style
|
|
@@ -368,19 +348,50 @@ function Typecaast({
|
|
|
368
348
|
state: tc.state,
|
|
369
349
|
skin,
|
|
370
350
|
participants: config.participants,
|
|
371
|
-
options: config.meta.skin.options
|
|
351
|
+
options: config.meta.skin.options,
|
|
352
|
+
composer: composer ?? config.meta.composer
|
|
372
353
|
}
|
|
373
354
|
) }) })
|
|
374
355
|
]
|
|
375
356
|
}
|
|
376
357
|
);
|
|
377
358
|
}
|
|
359
|
+
function SkinFallback({
|
|
360
|
+
config,
|
|
361
|
+
fit,
|
|
362
|
+
label,
|
|
363
|
+
className,
|
|
364
|
+
style
|
|
365
|
+
}) {
|
|
366
|
+
return /* @__PURE__ */ jsx(
|
|
367
|
+
"div",
|
|
368
|
+
{
|
|
369
|
+
className,
|
|
370
|
+
style: { position: "relative", ...style },
|
|
371
|
+
"data-typecaast": "",
|
|
372
|
+
"data-typecaast-loading": "",
|
|
373
|
+
role: "figure",
|
|
374
|
+
"aria-label": label ?? "Chat simulation",
|
|
375
|
+
"aria-busy": "true",
|
|
376
|
+
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(
|
|
377
|
+
"div",
|
|
378
|
+
{
|
|
379
|
+
style: {
|
|
380
|
+
width: "100%",
|
|
381
|
+
height: "100%",
|
|
382
|
+
background: "var(--tc-skin-loading-bg, transparent)"
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
) }) })
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
}
|
|
378
389
|
|
|
379
390
|
// src/resolve-theme.ts
|
|
380
391
|
function resolveTheme(mode) {
|
|
381
392
|
return mode === "dark" ? "dark" : mode === "light" ? "light" : "light";
|
|
382
393
|
}
|
|
383
394
|
|
|
384
|
-
export { FitBox, Typecaast,
|
|
395
|
+
export { BUILTIN_SKIN_LOADERS, FitBox, Typecaast, buildTranscript, builtinSkinIds, loadBuiltinSkin, resolveTheme, usePrefersDark, useReducedMotion, useResolvedTheme, useSkinFonts, useTypecaast };
|
|
385
396
|
//# sourceMappingURL=index.js.map
|
|
386
397
|
//# 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","useMemo","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { Config } from \"@typecaast/schema\";\nimport {\n createEngine,\n type Capabilities,\n type EngineHandle,\n type ResolvedTheme,\n} from \"@typecaast/core\";\n\nexport type Engine = EngineHandle;\n\n/**\n * The single seam between a config and a playable engine. M1-UI ran this over a\n * hand-mocked timeline; M1-engine swaps in the real `compile` + `getStateAt`\n * here — and nothing else in the renderer changed (same `Engine` shape).\n *\n * Optional `capabilities` (from the active skin) drop unsupported events/content\n * from the sampled state while leaving the config intact.\n */\nexport function configToEngine(\n config: Config,\n theme: ResolvedTheme,\n capabilities?: Capabilities,\n): Engine {\n return createEngine(config, theme, capabilities);\n}\n","import { useSyncExternalStore } from \"react\";\nimport type { ThemeMode } from \"@typecaast/schema\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\n\nconst QUERY = \"(prefers-color-scheme: dark)\";\n\nfunction getMql(): MediaQueryList | null {\n if (\n typeof window === \"undefined\" ||\n typeof window.matchMedia !== \"function\"\n ) {\n return null;\n }\n return window.matchMedia(QUERY);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n const mql = getMql();\n if (!mql) return () => {};\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n}\n\nfunction getSnapshot(): boolean {\n return getMql()?.matches ?? false;\n}\n\n/** No `matchMedia` on the server → default to light (consistent with export). */\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\n/** Reactively tracks the host's `prefers-color-scheme: dark`. */\nexport function usePrefersDark(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\n/**\n * Resolve a theme mode to a concrete theme. `light`/`dark` are forced; `auto`\n * tracks the host `prefers-color-scheme` reactively and falls back to `light`\n * when no preference signal is available.\n */\nexport function useResolvedTheme(mode: ThemeMode): ResolvedTheme {\n const prefersDark = usePrefersDark();\n if (mode === \"light\") return \"light\";\n if (mode === \"dark\") return \"dark\";\n return prefersDark ? \"dark\" : \"light\";\n}\n","import { useEffect, useMemo, useState } from \"react\";\nimport type { Config, ThemeMode } from \"@typecaast/schema\";\nimport {\n createPlayer,\n type Capabilities,\n type Player,\n type SimState,\n} from \"@typecaast/core\";\nimport { configToEngine } from \"./engine-adapter.js\";\nimport { useResolvedTheme } from \"./use-resolved-theme.js\";\n\nexport interface UseTypecaastOptions {\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** The active skin's capabilities, to drop what it can't render. */\n capabilities?: Capabilities;\n}\n\n/** Imperative controls + live state returned by {@link useTypecaast}. */\nexport interface TypecaastControls {\n state: SimState;\n /** Current playback time in ms (reactive). */\n currentMs: number;\n play(): void;\n pause(): void;\n seek(timeMs: number): void;\n scrubTo(timeMs: number): void;\n setRate(rate: number): void;\n stepNext(): void;\n stepPrev(): void;\n duration: number;\n rate: number;\n playing: boolean;\n /** Escape hatch to the underlying player. */\n player: Player;\n}\n\n/**\n * Mount a player for a config and expose live state + controls. The player\n * owns the clock (rAF in the browser); this hook bridges its ticks into React\n * state. The builder uses these controls for preview-as-you-go editing.\n *\n * In M1-UI the player runs over the mocked engine (see engine-adapter); the\n * hook's surface is the final one and does not change when the real engine\n * lands.\n */\nexport function useTypecaast(\n config: Config,\n options: UseTypecaastOptions = {},\n): TypecaastControls {\n const {\n theme,\n autoplay = false,\n loop = 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 {\n configSchema,\n type Config,\n type ConfigInput,\n type FitMode,\n type ThemeMode,\n} from \"@typecaast/schema\";\nimport {\n TypecaastStage,\n type ComposerMode,\n type Skin,\n} from \"@typecaast/skin-kit\";\nimport { useTypecaast } from \"./use-typecaast.js\";\nimport { useSkinFonts } from \"./use-skin-fonts.js\";\nimport { useReducedMotion } from \"./use-reduced-motion.js\";\nimport { buildTranscript } from \"./transcript.js\";\nimport { FitBox } from \"./fit-box.js\";\nimport { loadBuiltinSkin } from \"./builtin-skins.js\";\n\n/**\n * A loosely-typed config shape that a raw `import`ed `typecaast.json` satisfies —\n * TypeScript widens JSON literals (e.g. `version: number`, `type: string`), so it\n * matches neither `Config` nor `ConfigInput`. It's validated and normalized at\n * runtime, so this stays a convenience surface, not a bypass.\n */\nexport interface RawConfig {\n version: number;\n meta: {\n canvas: { width: number; height: number };\n skin: { id: string; options?: Record<string, unknown> };\n [key: string]: unknown;\n };\n participants: Array<{ id: string; name: string; [key: string]: unknown }>;\n timeline: Array<{ type: string; [key: string]: unknown }>;\n pacing?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * What `<Typecaast config>` accepts: a precise `ConfigInput`/`Config` (full\n * intellisense when hand-authoring) or a raw config object such as an imported\n * `typecaast.json`. All forms are normalized through the schema at runtime.\n */\nexport type TypecaastConfig = ConfigInput | Config | RawConfig;\n\nexport interface TypecaastProps {\n /**\n * The conversation config. Accepts your exported `typecaast.json` directly (or\n * a hand-authored `ConfigInput`); it's validated and defaulted at runtime, so\n * you never need to pre-parse it.\n */\n config: TypecaastConfig;\n /**\n * The skin to render with. **Optional** — by default the built-in skin named\n * by `config.meta.skin.id` is resolved and lazy-loaded (only that skin's chunk\n * is fetched), so the config is the single source of truth and the embed stays\n * fully serializable (works in a React Server Component, no `\"use client\"`).\n * Pass a `Skin` object only to use a custom skin not in `@typecaast/skins`.\n */\n skin?: Skin;\n /** Force a theme; otherwise resolved from `config.meta.theme`. */\n theme?: ThemeMode;\n autoplay?: boolean;\n loop?: boolean;\n rate?: number;\n /** Container fit mode; defaults to `config.meta.fit`. */\n fit?: FitMode;\n /** Composer (reply box) visibility: `auto` (default) / `always` / `never`. */\n composer?: ComposerMode;\n /** Accessible label for the simulation. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nconst SR_ONLY: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\n/**\n * 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 // Normalize once: validate and apply schema defaults (pacing, fit, theme, …)\n // so a raw exported `typecaast.json` works without the caller pre-parsing it.\n const config = useMemo<Config>(\n () => configSchema.parse(props.config),\n [props.config],\n );\n\n // Explicit skin object → render synchronously, no lazy load.\n if (props.skin)\n return <Player {...props} config={config} skin={props.skin} />;\n // Otherwise resolve (and lazy-load) the built-in named in the config.\n return (\n <Suspense\n fallback={\n <SkinFallback\n config={config}\n fit={props.fit}\n label={props.label}\n className={props.className}\n style={props.style}\n />\n }\n >\n <ResolvedPlayer {...props} config={config} />\n </Suspense>\n );\n}\n\nfunction ResolvedPlayer(\n props: Omit<TypecaastProps, \"config\"> & { config: Config },\n): 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}: Omit<TypecaastProps, \"config\"> & {\n config: Config;\n skin: Skin;\n}): 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, \"fit\" | \"label\" | \"className\" | \"style\"> & {\n config: Config;\n}) {\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
|
|
3
|
+
"version": "0.2.1",
|
|
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.
|
|
27
|
+
"@typecaast/core": "0.1.1",
|
|
23
28
|
"@typecaast/schema": "0.1.0",
|
|
24
|
-
"@typecaast/skin-kit": "0.
|
|
29
|
+
"@typecaast/skin-kit": "0.2.0",
|
|
30
|
+
"@typecaast/skins": "0.2.0"
|
|
25
31
|
},
|
|
26
32
|
"peerDependencies": {
|
|
27
33
|
"react": ">=18"
|