@typecaast/skin-kit 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -216,7 +216,21 @@ function TypecaastStage({
216
216
  );
217
217
  const composerAuthor = state.composer.from ? byId.get(state.composer.from) : composer === "always" ? selfParticipant : void 0;
218
218
  const showComposer = composer !== "never" && composerAuthor !== void 0;
219
- return /* @__PURE__ */ jsxRuntime.jsx(ThemeProvider, { theme, tokens, children: /* @__PURE__ */ jsxRuntime.jsxs(Frame, { theme, options, children: [
219
+ const typingPlacement = skin.meta.typingPlacement ?? "thread";
220
+ const typingNodes = state.typingIndicators.map((typing, i) => {
221
+ const author = byId.get(typing.from);
222
+ if (!author || author.isSelf) return null;
223
+ return /* @__PURE__ */ jsxRuntime.jsx(
224
+ TypingIndicator,
225
+ {
226
+ theme,
227
+ typing,
228
+ author
229
+ },
230
+ `typing-${typing.from}-${i}`
231
+ );
232
+ }).filter(Boolean);
233
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeProvider, { theme, tokens, children: /* @__PURE__ */ jsxRuntime.jsxs(Frame, { theme, options, composer, children: [
220
234
  /* @__PURE__ */ jsxRuntime.jsxs(
221
235
  "div",
222
236
  {
@@ -261,19 +275,7 @@ function TypecaastStage({
261
275
  key
262
276
  );
263
277
  }),
264
- state.typingIndicators.map((typing, i) => {
265
- const author = byId.get(typing.from);
266
- if (!author) return null;
267
- return /* @__PURE__ */ jsxRuntime.jsx(
268
- TypingIndicator,
269
- {
270
- theme,
271
- typing,
272
- author
273
- },
274
- `typing-${typing.from}-${i}`
275
- );
276
- })
278
+ typingPlacement === "thread" ? typingNodes : null
277
279
  ]
278
280
  }
279
281
  ),
@@ -284,7 +286,8 @@ function TypecaastStage({
284
286
  composer: state.composer,
285
287
  author: composerAuthor
286
288
  }
287
- ) : null
289
+ ) : null,
290
+ typingPlacement === "below-composer" ? typingNodes : null
288
291
  ] }) });
289
292
  }
290
293
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/define-skin.ts","../src/theme.tsx","../src/fonts.ts","../src/animation.tsx","../src/content.tsx","../src/stage.tsx"],"names":["createContext","jsx","useContext","Fragment","useMemo","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Skin } from \"./types.js\";\n\n/**\n * Identity helper for type-safety and a single registration point when\n * authoring a skin. Keeping it a function (rather than a bare object) lets us\n * add validation/registration later without changing skin call sites.\n */\nexport function defineSkin(skin: Skin): Skin {\n return skin;\n}\n","import { createContext, useContext, type ReactNode } from \"react\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\nimport type { SkinTokens } from \"./types.js\";\n\ninterface ThemeContextValue {\n theme: ResolvedTheme;\n tokens?: SkinTokens;\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({ theme: \"light\" });\n\nexport interface ThemeProviderProps {\n theme: ResolvedTheme;\n /** Resolved per-theme tokens for the active skin. */\n tokens?: SkinTokens;\n children?: ReactNode;\n}\n\n/** Provides the resolved theme + tokens to every skin component below it. */\nexport function ThemeProvider({\n theme,\n tokens,\n children,\n}: ThemeProviderProps): ReactNode {\n return (\n <ThemeContext.Provider value={{ theme, tokens }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/** The resolved theme (`\"light\" | \"dark\"`) for the current subtree. */\nexport function useTheme(): ResolvedTheme {\n return useContext(ThemeContext).theme;\n}\n\n/** The resolved design tokens for the current subtree, if provided. */\nexport function useTokens(): SkinTokens | undefined {\n return useContext(ThemeContext).tokens;\n}\n","import type { FontDeclaration } from \"./types.js\";\n\n/** Build a CSS `src:` value from a font's sources. */\nfunction srcValue(decl: FontDeclaration): string {\n return decl.sources\n .map((s) => {\n const format = s.format ? ` format(\"${s.format}\")` : \"\";\n return `url(\"${s.url}\")${format}`;\n })\n .join(\", \");\n}\n\n/**\n * Load a skin's declared web fonts so live preview matches the platform (not\n * just video export). SSR-safe: a no-op when `document`/`FontFace` are absent\n * (server render, Remotion Node). Idempotent per family+weight+style.\n *\n * Returns once every face has loaded (or immediately, off the main document).\n */\nexport async function loadSkinFonts(\n fonts: FontDeclaration[] | undefined,\n): Promise<void> {\n if (!fonts || fonts.length === 0) return;\n if (\n typeof document === \"undefined\" ||\n typeof FontFace === \"undefined\" ||\n !document.fonts\n ) {\n return;\n }\n\n const pending: Promise<unknown>[] = [];\n for (const decl of fonts) {\n for (const source of decl.sources) {\n const descriptors: FontFaceDescriptors = {};\n if (source.weight !== undefined)\n descriptors.weight = String(source.weight);\n if (source.style !== undefined) descriptors.style = source.style;\n\n const single: FontDeclaration = { ...decl, sources: [source] };\n const face = new FontFace(decl.family, srcValue(single), descriptors);\n\n // Skip if an identical face is already registered.\n const already = [...document.fonts].some(\n (f) =>\n f.family === decl.family &&\n f.weight === (descriptors.weight ?? \"normal\") &&\n f.style === (descriptors.style ?? \"normal\"),\n );\n if (already) continue;\n\n document.fonts.add(face);\n pending.push(face.load());\n }\n }\n await Promise.all(pending);\n}\n","import type { CSSProperties, ReactNode } from \"react\";\n\n/**\n * Animation primitives are **pure functions of progress** (0..1), not CSS\n * transitions or JS timers — so the React preview and the Remotion render\n * animate identically frame-for-frame (PLAN §7). Skins call these driven by\n * `revealProgress` / typing `progress` from `SimState`.\n */\n\nexport const clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\n\n/** Decelerating ease, good for reveals. */\nexport const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Back-ease-out with overshoot, good for pops. Lands exactly on 1 at t=1. */\nexport function backEaseOut(t: number, tension = 2.2): number {\n const c3 = tension + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + tension * Math.pow(t - 1, 2);\n}\n\nexport interface FadeSlideOptions {\n /** Slide distance in px at progress 0 (default 8). */\n distance?: number;\n /** Axis to slide along (default \"y\"). */\n axis?: \"x\" | \"y\";\n easing?: (t: number) => number;\n}\n\n/** Fade + slide-in reveal. `progress` 0 → hidden+offset, 1 → shown+settled. */\nexport function fadeSlideIn(\n progress: number,\n options: FadeSlideOptions = {},\n): CSSProperties {\n const eased = (options.easing ?? easeOutCubic)(clamp01(progress));\n const distance = options.distance ?? 8;\n const offset = (1 - eased) * distance;\n const translate =\n options.axis === \"x\"\n ? `translateX(${offset}px)`\n : `translateY(${offset}px)`;\n return { opacity: eased, transform: translate };\n}\n\nexport interface PopOptions {\n /** Overshoot tension (default 2.2 ≈ ~10% overshoot). */\n tension?: number;\n}\n\n/** Scale pop-in (with a little overshoot), good for reactions landing. */\nexport function popIn(\n progress: number,\n options: PopOptions = {},\n): CSSProperties {\n const p = clamp01(progress);\n const scale = backEaseOut(p, options.tension ?? 2.2);\n return {\n opacity: clamp01(p * 3),\n transform: `scale(${scale})`,\n transformOrigin: \"center\",\n };\n}\n\nexport interface TypingDotsProps {\n /** 0..1 progress through the indicator's shown duration. */\n progress?: number;\n count?: number;\n /** Bounce cycles across the full progress (default 4). */\n cycles?: number;\n /** Dot color (default `currentColor`). */\n color?: string;\n /** Dot diameter in px (default 6). */\n size?: number;\n /** Gap between dots in px (default 4). */\n gap?: number;\n}\n\n/**\n * The three-dot bouncing typing indicator, animated purely from `progress`\n * (deterministic per frame). Skins style it via props or wrap it.\n */\nexport function TypingDots({\n progress = 0,\n count = 3,\n cycles = 4,\n color = \"currentColor\",\n size = 6,\n gap = 4,\n}: TypingDotsProps): ReactNode {\n const phase = clamp01(progress) * cycles * Math.PI * 2;\n const dots: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n const wave = Math.sin(phase - i * 0.9);\n const lift = Math.max(0, wave) * 4;\n const opacity = 0.4 + Math.max(0, wave) * 0.6;\n dots.push(\n <span\n key={i}\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n background: color,\n transform: `translateY(${-lift}px)`,\n opacity,\n }}\n />,\n );\n }\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"flex-end\", gap }}>\n {dots}\n </span>\n );\n}\n","import type { CSSProperties, ReactNode } from \"react\";\nimport type {\n ContentNode,\n ImageNode,\n InlineNode,\n TextNode,\n} from \"@typecaast/schema\";\n\nexport interface ContentClassNames {\n text?: string;\n link?: string;\n mention?: string;\n code?: string;\n emoji?: string;\n image?: string;\n}\n\n/** Per-mark inline styles, so skins can theme marks without a CSS file. */\nexport interface ContentStyles {\n text?: CSSProperties;\n link?: CSSProperties;\n mention?: CSSProperties;\n code?: CSSProperties;\n emoji?: CSSProperties;\n}\n\nexport interface MessageContentProps {\n nodes: ContentNode[];\n /** Per-mark class names so skins style marks with their own CSS. */\n classNames?: ContentClassNames;\n /** Per-mark inline styles (merged with the defaults). */\n styles?: ContentStyles;\n /** Extra style for in-message images (skins set radius, max size, etc.). */\n imageStyle?: CSSProperties;\n}\n\nfunction renderInline(\n span: InlineNode,\n key: number,\n cn: ContentClassNames,\n st: ContentStyles,\n): ReactNode {\n switch (span.type) {\n case \"text\":\n return span.value;\n case \"code\":\n return (\n <code key={key} data-tc-mark=\"code\" className={cn.code} style={st.code}>\n {span.value}\n </code>\n );\n case \"link\":\n return (\n <a\n key={key}\n data-tc-mark=\"link\"\n className={cn.link}\n style={st.link}\n href={span.href}\n rel=\"noreferrer\"\n >\n {span.label ?? span.href}\n </a>\n );\n case \"mention\":\n return (\n <span\n key={key}\n data-tc-mark=\"mention\"\n className={cn.mention}\n style={st.mention}\n >\n {span.label}\n </span>\n );\n case \"emoji\":\n return (\n <span\n key={key}\n data-tc-mark=\"emoji\"\n className={cn.emoji}\n style={st.emoji}\n >\n {span.value}\n </span>\n );\n }\n}\n\nfunction renderImage(\n node: ImageNode,\n key: number,\n cn: ContentClassNames,\n imageStyle?: CSSProperties,\n): ReactNode {\n return (\n <img\n key={key}\n data-tc-node=\"image\"\n className={cn.image}\n src={node.src}\n alt={node.alt ?? \"\"}\n width={node.width}\n height={node.height}\n style={{ maxWidth: \"100%\", display: \"block\", ...imageStyle }}\n />\n );\n}\n\n/**\n * Render a message body (`ContentNode[]`) to React: text nodes with inline\n * marks (code/link/mention/emoji) and in-message images. Unknown node types are\n * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically\n * in the browser and in Remotion's Node renderer. Skins style via `classNames`.\n */\nexport function MessageContent({\n nodes,\n classNames = {},\n styles = {},\n imageStyle,\n}: MessageContentProps): ReactNode {\n return (\n <>\n {nodes.map((node, i) => {\n if (node.type === \"text\") {\n const text = node as TextNode;\n return (\n <span\n key={i}\n data-tc-node=\"text\"\n className={classNames.text}\n style={styles.text}\n >\n {text.spans.map((span, j) =>\n renderInline(span, j, classNames, styles),\n )}\n </span>\n );\n }\n if (node.type === \"image\") {\n return renderImage(node as ImageNode, i, classNames, imageStyle);\n }\n return null; // unknown future node type — skipped\n })}\n </>\n );\n}\n","import { useMemo, type ReactNode } from \"react\";\nimport type { ComposerMode, Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider } from \"./theme.js\";\nimport type { Skin } from \"./types.js\";\n\nexport type { ComposerMode };\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 * Composer visibility: `auto` (default) shows it only while someone is\n * typing/sending; `always` keeps the reply box visible (idle = placeholder);\n * `never` hides it.\n */\n composer?: ComposerMode;\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 *\n * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the\n * skins' own stories can render a frame without depending on the React player.\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n composer = \"auto\",\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 // `always` keeps the reply box mounted even when idle (falls back to the self\n // participant so a placeholder shows); `auto` only shows it while composing.\n const selfParticipant = useMemo(\n () => participants.find((p) => p.isSelf),\n [participants],\n );\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : composer === \"always\"\n ? selfParticipant\n : undefined;\n const showComposer = composer !== \"never\" && composerAuthor !== 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 // Breathing room beneath the last message — keeps it off the\n // composer (when shown) and the Frame's bottom edge (when hidden).\n paddingBottom: 16,\n }}\n >\n {state.messages.map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n // Index-disambiguated so a config with duplicate message ids can't\n // collide React keys (the builder can produce them transiently).\n const key = `${message.id}-${i}`;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={key}\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={key}\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 {showComposer ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n />\n ) : null}\n </Frame>\n </ThemeProvider>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/define-skin.ts","../src/theme.tsx","../src/fonts.ts","../src/animation.tsx","../src/content.tsx","../src/stage.tsx"],"names":["createContext","jsx","useContext","Fragment","useMemo","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Skin } from \"./types.js\";\n\n/**\n * Identity helper for type-safety and a single registration point when\n * authoring a skin. Keeping it a function (rather than a bare object) lets us\n * add validation/registration later without changing skin call sites.\n */\nexport function defineSkin(skin: Skin): Skin {\n return skin;\n}\n","import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\nimport type { SkinTokens } from \"./types.js\";\n\ninterface ThemeContextValue {\n theme: ResolvedTheme;\n tokens?: SkinTokens;\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({ theme: \"light\" });\n\nexport interface ThemeProviderProps {\n theme: ResolvedTheme;\n /** Resolved per-theme tokens for the active skin. */\n tokens?: SkinTokens;\n children?: ReactNode;\n}\n\n/** Provides the resolved theme + tokens to every skin component below it. */\nexport function ThemeProvider({\n theme,\n tokens,\n children,\n}: ThemeProviderProps): ReactElement {\n return (\n <ThemeContext.Provider value={{ theme, tokens }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/** The resolved theme (`\"light\" | \"dark\"`) for the current subtree. */\nexport function useTheme(): ResolvedTheme {\n return useContext(ThemeContext).theme;\n}\n\n/** The resolved design tokens for the current subtree, if provided. */\nexport function useTokens(): SkinTokens | undefined {\n return useContext(ThemeContext).tokens;\n}\n","import type { FontDeclaration } from \"./types.js\";\n\n/** Build a CSS `src:` value from a font's sources. */\nfunction srcValue(decl: FontDeclaration): string {\n return decl.sources\n .map((s) => {\n const format = s.format ? ` format(\"${s.format}\")` : \"\";\n return `url(\"${s.url}\")${format}`;\n })\n .join(\", \");\n}\n\n/**\n * Load a skin's declared web fonts so live preview matches the platform (not\n * just video export). SSR-safe: a no-op when `document`/`FontFace` are absent\n * (server render, Remotion Node). Idempotent per family+weight+style.\n *\n * Returns once every face has loaded (or immediately, off the main document).\n */\nexport async function loadSkinFonts(\n fonts: FontDeclaration[] | undefined,\n): Promise<void> {\n if (!fonts || fonts.length === 0) return;\n if (\n typeof document === \"undefined\" ||\n typeof FontFace === \"undefined\" ||\n !document.fonts\n ) {\n return;\n }\n\n const pending: Promise<unknown>[] = [];\n for (const decl of fonts) {\n for (const source of decl.sources) {\n const descriptors: FontFaceDescriptors = {};\n if (source.weight !== undefined)\n descriptors.weight = String(source.weight);\n if (source.style !== undefined) descriptors.style = source.style;\n\n const single: FontDeclaration = { ...decl, sources: [source] };\n const face = new FontFace(decl.family, srcValue(single), descriptors);\n\n // Skip if an identical face is already registered.\n const already = [...document.fonts].some(\n (f) =>\n f.family === decl.family &&\n f.weight === (descriptors.weight ?? \"normal\") &&\n f.style === (descriptors.style ?? \"normal\"),\n );\n if (already) continue;\n\n document.fonts.add(face);\n pending.push(face.load());\n }\n }\n await Promise.all(pending);\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\n\n/**\n * Animation primitives are **pure functions of progress** (0..1), not CSS\n * transitions or JS timers — so the React preview and the Remotion render\n * animate identically frame-for-frame (PLAN §7). Skins call these driven by\n * `revealProgress` / typing `progress` from `SimState`.\n */\n\nexport const clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\n\n/** Decelerating ease, good for reveals. */\nexport const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Back-ease-out with overshoot, good for pops. Lands exactly on 1 at t=1. */\nexport function backEaseOut(t: number, tension = 2.2): number {\n const c3 = tension + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + tension * Math.pow(t - 1, 2);\n}\n\nexport interface FadeSlideOptions {\n /** Slide distance in px at progress 0 (default 8). */\n distance?: number;\n /** Axis to slide along (default \"y\"). */\n axis?: \"x\" | \"y\";\n easing?: (t: number) => number;\n}\n\n/** Fade + slide-in reveal. `progress` 0 → hidden+offset, 1 → shown+settled. */\nexport function fadeSlideIn(\n progress: number,\n options: FadeSlideOptions = {},\n): CSSProperties {\n const eased = (options.easing ?? easeOutCubic)(clamp01(progress));\n const distance = options.distance ?? 8;\n const offset = (1 - eased) * distance;\n const translate =\n options.axis === \"x\"\n ? `translateX(${offset}px)`\n : `translateY(${offset}px)`;\n return { opacity: eased, transform: translate };\n}\n\nexport interface PopOptions {\n /** Overshoot tension (default 2.2 ≈ ~10% overshoot). */\n tension?: number;\n}\n\n/** Scale pop-in (with a little overshoot), good for reactions landing. */\nexport function popIn(\n progress: number,\n options: PopOptions = {},\n): CSSProperties {\n const p = clamp01(progress);\n const scale = backEaseOut(p, options.tension ?? 2.2);\n return {\n opacity: clamp01(p * 3),\n transform: `scale(${scale})`,\n transformOrigin: \"center\",\n };\n}\n\nexport interface TypingDotsProps {\n /** 0..1 progress through the indicator's shown duration. */\n progress?: number;\n count?: number;\n /** Bounce cycles across the full progress (default 4). */\n cycles?: number;\n /** Dot color (default `currentColor`). */\n color?: string;\n /** Dot diameter in px (default 6). */\n size?: number;\n /** Gap between dots in px (default 4). */\n gap?: number;\n}\n\n/**\n * The three-dot bouncing typing indicator, animated purely from `progress`\n * (deterministic per frame). Skins style it via props or wrap it.\n */\nexport function TypingDots({\n progress = 0,\n count = 3,\n cycles = 4,\n color = \"currentColor\",\n size = 6,\n gap = 4,\n}: TypingDotsProps): ReactElement {\n const phase = clamp01(progress) * cycles * Math.PI * 2;\n const dots: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n const wave = Math.sin(phase - i * 0.9);\n const lift = Math.max(0, wave) * 4;\n const opacity = 0.4 + Math.max(0, wave) * 0.6;\n dots.push(\n <span\n key={i}\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n background: color,\n transform: `translateY(${-lift}px)`,\n opacity,\n }}\n />,\n );\n }\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"flex-end\", gap }}>\n {dots}\n </span>\n );\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport type {\n ContentNode,\n ImageNode,\n InlineNode,\n TextNode,\n} from \"@typecaast/schema\";\n\nexport interface ContentClassNames {\n text?: string;\n link?: string;\n mention?: string;\n code?: string;\n emoji?: string;\n image?: string;\n}\n\n/** Per-mark inline styles, so skins can theme marks without a CSS file. */\nexport interface ContentStyles {\n text?: CSSProperties;\n link?: CSSProperties;\n mention?: CSSProperties;\n code?: CSSProperties;\n emoji?: CSSProperties;\n}\n\nexport interface MessageContentProps {\n nodes: ContentNode[];\n /** Per-mark class names so skins style marks with their own CSS. */\n classNames?: ContentClassNames;\n /** Per-mark inline styles (merged with the defaults). */\n styles?: ContentStyles;\n /** Extra style for in-message images (skins set radius, max size, etc.). */\n imageStyle?: CSSProperties;\n}\n\nfunction renderInline(\n span: InlineNode,\n key: number,\n cn: ContentClassNames,\n st: ContentStyles,\n): ReactNode {\n switch (span.type) {\n case \"text\":\n return span.value;\n case \"code\":\n return (\n <code key={key} data-tc-mark=\"code\" className={cn.code} style={st.code}>\n {span.value}\n </code>\n );\n case \"link\":\n return (\n <a\n key={key}\n data-tc-mark=\"link\"\n className={cn.link}\n style={st.link}\n href={span.href}\n rel=\"noreferrer\"\n >\n {span.label ?? span.href}\n </a>\n );\n case \"mention\":\n return (\n <span\n key={key}\n data-tc-mark=\"mention\"\n className={cn.mention}\n style={st.mention}\n >\n {span.label}\n </span>\n );\n case \"emoji\":\n return (\n <span\n key={key}\n data-tc-mark=\"emoji\"\n className={cn.emoji}\n style={st.emoji}\n >\n {span.value}\n </span>\n );\n }\n}\n\nfunction renderImage(\n node: ImageNode,\n key: number,\n cn: ContentClassNames,\n imageStyle?: CSSProperties,\n): ReactNode {\n return (\n <img\n key={key}\n data-tc-node=\"image\"\n className={cn.image}\n src={node.src}\n alt={node.alt ?? \"\"}\n width={node.width}\n height={node.height}\n style={{ maxWidth: \"100%\", display: \"block\", ...imageStyle }}\n />\n );\n}\n\n/**\n * Render a message body (`ContentNode[]`) to React: text nodes with inline\n * marks (code/link/mention/emoji) and in-message images. Unknown node types are\n * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically\n * in the browser and in Remotion's Node renderer. Skins style via `classNames`.\n */\nexport function MessageContent({\n nodes,\n classNames = {},\n styles = {},\n imageStyle,\n}: MessageContentProps): ReactElement {\n return (\n <>\n {nodes.map((node, i) => {\n if (node.type === \"text\") {\n const text = node as TextNode;\n return (\n <span\n key={i}\n data-tc-node=\"text\"\n className={classNames.text}\n style={styles.text}\n >\n {text.spans.map((span, j) =>\n renderInline(span, j, classNames, styles),\n )}\n </span>\n );\n }\n if (node.type === \"image\") {\n return renderImage(node as ImageNode, i, classNames, imageStyle);\n }\n return null; // unknown future node type — skipped\n })}\n </>\n );\n}\n","import { useMemo, type ReactElement } from \"react\";\nimport type { ComposerMode, Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider } from \"./theme.js\";\nimport type { Skin } from \"./types.js\";\n\nexport type { ComposerMode };\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 * Composer visibility: `auto` (default) shows it only while someone is\n * typing/sending; `always` keeps the reply box visible (idle = placeholder);\n * `never` hides it.\n */\n composer?: ComposerMode;\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 *\n * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the\n * skins' own stories can render a frame without depending on the React player.\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n composer = \"auto\",\n}: TypecaastStageProps): ReactElement {\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 // `always` keeps the reply box mounted even when idle (falls back to the self\n // participant so a placeholder shows); `auto` only shows it while composing.\n const selfParticipant = useMemo(\n () => participants.find((p) => p.isSelf),\n [participants],\n );\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : composer === \"always\"\n ? selfParticipant\n : undefined;\n const showComposer = composer !== \"never\" && composerAuthor !== undefined;\n\n // \"X is typing…\" indicators, minus the viewer's own (you never see yourself\n // typing — that's what the composer shows). Placement is skin-driven: inline\n // in the thread by default, or below the composer (Slack).\n const typingPlacement = skin.meta.typingPlacement ?? \"thread\";\n const typingNodes = state.typingIndicators\n .map((typing, i) => {\n const author = byId.get(typing.from);\n if (!author || author.isSelf) 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 .filter(Boolean);\n\n return (\n <ThemeProvider theme={theme} tokens={tokens}>\n <Frame theme={theme} options={options} composer={composer}>\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 // Breathing room beneath the last message — keeps it off the\n // composer (when shown) and the Frame's bottom edge (when hidden).\n paddingBottom: 16,\n }}\n >\n {state.messages.map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n // Index-disambiguated so a config with duplicate message ids can't\n // collide React keys (the builder can produce them transiently).\n const key = `${message.id}-${i}`;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={key}\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={key}\n theme={theme}\n message={message}\n author={author}\n previousAuthor={previousAuthor}\n />\n );\n })}\n {typingPlacement === \"thread\" ? typingNodes : null}\n </div>\n {showComposer ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n />\n ) : null}\n {typingPlacement === \"below-composer\" ? typingNodes : null}\n </Frame>\n </ThemeProvider>\n );\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { FC, ReactNode, CSSProperties } from 'react';
1
+ import { FC, ReactNode, ReactElement, CSSProperties } from 'react';
2
2
  import { ZodType } from 'zod';
3
3
  import { Size, ContentNode, Participant, ComposerMode } from '@typecaast/schema';
4
4
  export { ComposerMode } from '@typecaast/schema';
@@ -44,6 +44,12 @@ interface SkinMeta {
44
44
  optionsSchema?: ZodType;
45
45
  /** Fonts the skin declares and loads (live + export). */
46
46
  fonts?: FontDeclaration[];
47
+ /**
48
+ * Where "X is typing…" renders. `"thread"` (default) shows it inline in the
49
+ * message flow (a bubble, like iMessage); `"below-composer"` shows it under
50
+ * the reply box (like Slack). Self-authored typing never renders either way.
51
+ */
52
+ typingPlacement?: "thread" | "below-composer";
47
53
  }
48
54
  /** The presentational React components a skin provides. */
49
55
  interface SkinComponents {
@@ -87,7 +93,7 @@ interface ThemeProviderProps {
87
93
  children?: ReactNode;
88
94
  }
89
95
  /** Provides the resolved theme + tokens to every skin component below it. */
90
- declare function ThemeProvider({ theme, tokens, children, }: ThemeProviderProps): ReactNode;
96
+ declare function ThemeProvider({ theme, tokens, children, }: ThemeProviderProps): ReactElement;
91
97
  /** The resolved theme (`"light" | "dark"`) for the current subtree. */
92
98
  declare function useTheme(): ResolvedTheme;
93
99
  /** The resolved design tokens for the current subtree, if provided. */
@@ -145,7 +151,7 @@ interface TypingDotsProps {
145
151
  * The three-dot bouncing typing indicator, animated purely from `progress`
146
152
  * (deterministic per frame). Skins style it via props or wrap it.
147
153
  */
148
- declare function TypingDots({ progress, count, cycles, color, size, gap, }: TypingDotsProps): ReactNode;
154
+ declare function TypingDots({ progress, count, cycles, color, size, gap, }: TypingDotsProps): ReactElement;
149
155
 
150
156
  interface ContentClassNames {
151
157
  text?: string;
@@ -178,7 +184,7 @@ interface MessageContentProps {
178
184
  * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically
179
185
  * in the browser and in Remotion's Node renderer. Skins style via `classNames`.
180
186
  */
181
- declare function MessageContent({ nodes, classNames, styles, imageStyle, }: MessageContentProps): ReactNode;
187
+ declare function MessageContent({ nodes, classNames, styles, imageStyle, }: MessageContentProps): ReactElement;
182
188
 
183
189
  interface TypecaastStageProps {
184
190
  state: SimState;
@@ -201,6 +207,6 @@ interface TypecaastStageProps {
201
207
  * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the
202
208
  * skins' own stories can render a frame without depending on the React player.
203
209
  */
204
- declare function TypecaastStage({ state, skin, participants, options, composer, }: TypecaastStageProps): ReactNode;
210
+ declare function TypecaastStage({ state, skin, participants, options, composer, }: TypecaastStageProps): ReactElement;
205
211
 
206
212
  export { type ContentClassNames, type ContentStyles, type FadeSlideOptions, type FontDeclaration, type FontSource, MessageContent, type MessageContentProps, type PopOptions, type Skin, type SkinComponents, type SkinMeta, type SkinTokens, ThemeProvider, type ThemeProviderProps, TypecaastStage, type TypecaastStageProps, TypingDots, type TypingDotsProps, backEaseOut, clamp01, defineSkin, easeOutCubic, fadeSlideIn, loadSkinFonts, popIn, useTheme, useTokens };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FC, ReactNode, CSSProperties } from 'react';
1
+ import { FC, ReactNode, ReactElement, CSSProperties } from 'react';
2
2
  import { ZodType } from 'zod';
3
3
  import { Size, ContentNode, Participant, ComposerMode } from '@typecaast/schema';
4
4
  export { ComposerMode } from '@typecaast/schema';
@@ -44,6 +44,12 @@ interface SkinMeta {
44
44
  optionsSchema?: ZodType;
45
45
  /** Fonts the skin declares and loads (live + export). */
46
46
  fonts?: FontDeclaration[];
47
+ /**
48
+ * Where "X is typing…" renders. `"thread"` (default) shows it inline in the
49
+ * message flow (a bubble, like iMessage); `"below-composer"` shows it under
50
+ * the reply box (like Slack). Self-authored typing never renders either way.
51
+ */
52
+ typingPlacement?: "thread" | "below-composer";
47
53
  }
48
54
  /** The presentational React components a skin provides. */
49
55
  interface SkinComponents {
@@ -87,7 +93,7 @@ interface ThemeProviderProps {
87
93
  children?: ReactNode;
88
94
  }
89
95
  /** Provides the resolved theme + tokens to every skin component below it. */
90
- declare function ThemeProvider({ theme, tokens, children, }: ThemeProviderProps): ReactNode;
96
+ declare function ThemeProvider({ theme, tokens, children, }: ThemeProviderProps): ReactElement;
91
97
  /** The resolved theme (`"light" | "dark"`) for the current subtree. */
92
98
  declare function useTheme(): ResolvedTheme;
93
99
  /** The resolved design tokens for the current subtree, if provided. */
@@ -145,7 +151,7 @@ interface TypingDotsProps {
145
151
  * The three-dot bouncing typing indicator, animated purely from `progress`
146
152
  * (deterministic per frame). Skins style it via props or wrap it.
147
153
  */
148
- declare function TypingDots({ progress, count, cycles, color, size, gap, }: TypingDotsProps): ReactNode;
154
+ declare function TypingDots({ progress, count, cycles, color, size, gap, }: TypingDotsProps): ReactElement;
149
155
 
150
156
  interface ContentClassNames {
151
157
  text?: string;
@@ -178,7 +184,7 @@ interface MessageContentProps {
178
184
  * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically
179
185
  * in the browser and in Remotion's Node renderer. Skins style via `classNames`.
180
186
  */
181
- declare function MessageContent({ nodes, classNames, styles, imageStyle, }: MessageContentProps): ReactNode;
187
+ declare function MessageContent({ nodes, classNames, styles, imageStyle, }: MessageContentProps): ReactElement;
182
188
 
183
189
  interface TypecaastStageProps {
184
190
  state: SimState;
@@ -201,6 +207,6 @@ interface TypecaastStageProps {
201
207
  * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the
202
208
  * skins' own stories can render a frame without depending on the React player.
203
209
  */
204
- declare function TypecaastStage({ state, skin, participants, options, composer, }: TypecaastStageProps): ReactNode;
210
+ declare function TypecaastStage({ state, skin, participants, options, composer, }: TypecaastStageProps): ReactElement;
205
211
 
206
212
  export { type ContentClassNames, type ContentStyles, type FadeSlideOptions, type FontDeclaration, type FontSource, MessageContent, type MessageContentProps, type PopOptions, type Skin, type SkinComponents, type SkinMeta, type SkinTokens, ThemeProvider, type ThemeProviderProps, TypecaastStage, type TypecaastStageProps, TypingDots, type TypingDotsProps, backEaseOut, clamp01, defineSkin, easeOutCubic, fadeSlideIn, loadSkinFonts, popIn, useTheme, useTokens };
package/dist/index.js CHANGED
@@ -214,7 +214,21 @@ function TypecaastStage({
214
214
  );
215
215
  const composerAuthor = state.composer.from ? byId.get(state.composer.from) : composer === "always" ? selfParticipant : void 0;
216
216
  const showComposer = composer !== "never" && composerAuthor !== void 0;
217
- return /* @__PURE__ */ jsx(ThemeProvider, { theme, tokens, children: /* @__PURE__ */ jsxs(Frame, { theme, options, children: [
217
+ const typingPlacement = skin.meta.typingPlacement ?? "thread";
218
+ const typingNodes = state.typingIndicators.map((typing, i) => {
219
+ const author = byId.get(typing.from);
220
+ if (!author || author.isSelf) return null;
221
+ return /* @__PURE__ */ jsx(
222
+ TypingIndicator,
223
+ {
224
+ theme,
225
+ typing,
226
+ author
227
+ },
228
+ `typing-${typing.from}-${i}`
229
+ );
230
+ }).filter(Boolean);
231
+ return /* @__PURE__ */ jsx(ThemeProvider, { theme, tokens, children: /* @__PURE__ */ jsxs(Frame, { theme, options, composer, children: [
218
232
  /* @__PURE__ */ jsxs(
219
233
  "div",
220
234
  {
@@ -259,19 +273,7 @@ function TypecaastStage({
259
273
  key
260
274
  );
261
275
  }),
262
- state.typingIndicators.map((typing, i) => {
263
- const author = byId.get(typing.from);
264
- if (!author) return null;
265
- return /* @__PURE__ */ jsx(
266
- TypingIndicator,
267
- {
268
- theme,
269
- typing,
270
- author
271
- },
272
- `typing-${typing.from}-${i}`
273
- );
274
- })
276
+ typingPlacement === "thread" ? typingNodes : null
275
277
  ]
276
278
  }
277
279
  ),
@@ -282,7 +284,8 @@ function TypecaastStage({
282
284
  composer: state.composer,
283
285
  author: composerAuthor
284
286
  }
285
- ) : null
287
+ ) : null,
288
+ typingPlacement === "below-composer" ? typingNodes : null
286
289
  ] }) });
287
290
  }
288
291
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/define-skin.ts","../src/theme.tsx","../src/fonts.ts","../src/animation.tsx","../src/content.tsx","../src/stage.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { Skin } from \"./types.js\";\n\n/**\n * Identity helper for type-safety and a single registration point when\n * authoring a skin. Keeping it a function (rather than a bare object) lets us\n * add validation/registration later without changing skin call sites.\n */\nexport function defineSkin(skin: Skin): Skin {\n return skin;\n}\n","import { createContext, useContext, type ReactNode } from \"react\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\nimport type { SkinTokens } from \"./types.js\";\n\ninterface ThemeContextValue {\n theme: ResolvedTheme;\n tokens?: SkinTokens;\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({ theme: \"light\" });\n\nexport interface ThemeProviderProps {\n theme: ResolvedTheme;\n /** Resolved per-theme tokens for the active skin. */\n tokens?: SkinTokens;\n children?: ReactNode;\n}\n\n/** Provides the resolved theme + tokens to every skin component below it. */\nexport function ThemeProvider({\n theme,\n tokens,\n children,\n}: ThemeProviderProps): ReactNode {\n return (\n <ThemeContext.Provider value={{ theme, tokens }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/** The resolved theme (`\"light\" | \"dark\"`) for the current subtree. */\nexport function useTheme(): ResolvedTheme {\n return useContext(ThemeContext).theme;\n}\n\n/** The resolved design tokens for the current subtree, if provided. */\nexport function useTokens(): SkinTokens | undefined {\n return useContext(ThemeContext).tokens;\n}\n","import type { FontDeclaration } from \"./types.js\";\n\n/** Build a CSS `src:` value from a font's sources. */\nfunction srcValue(decl: FontDeclaration): string {\n return decl.sources\n .map((s) => {\n const format = s.format ? ` format(\"${s.format}\")` : \"\";\n return `url(\"${s.url}\")${format}`;\n })\n .join(\", \");\n}\n\n/**\n * Load a skin's declared web fonts so live preview matches the platform (not\n * just video export). SSR-safe: a no-op when `document`/`FontFace` are absent\n * (server render, Remotion Node). Idempotent per family+weight+style.\n *\n * Returns once every face has loaded (or immediately, off the main document).\n */\nexport async function loadSkinFonts(\n fonts: FontDeclaration[] | undefined,\n): Promise<void> {\n if (!fonts || fonts.length === 0) return;\n if (\n typeof document === \"undefined\" ||\n typeof FontFace === \"undefined\" ||\n !document.fonts\n ) {\n return;\n }\n\n const pending: Promise<unknown>[] = [];\n for (const decl of fonts) {\n for (const source of decl.sources) {\n const descriptors: FontFaceDescriptors = {};\n if (source.weight !== undefined)\n descriptors.weight = String(source.weight);\n if (source.style !== undefined) descriptors.style = source.style;\n\n const single: FontDeclaration = { ...decl, sources: [source] };\n const face = new FontFace(decl.family, srcValue(single), descriptors);\n\n // Skip if an identical face is already registered.\n const already = [...document.fonts].some(\n (f) =>\n f.family === decl.family &&\n f.weight === (descriptors.weight ?? \"normal\") &&\n f.style === (descriptors.style ?? \"normal\"),\n );\n if (already) continue;\n\n document.fonts.add(face);\n pending.push(face.load());\n }\n }\n await Promise.all(pending);\n}\n","import type { CSSProperties, ReactNode } from \"react\";\n\n/**\n * Animation primitives are **pure functions of progress** (0..1), not CSS\n * transitions or JS timers — so the React preview and the Remotion render\n * animate identically frame-for-frame (PLAN §7). Skins call these driven by\n * `revealProgress` / typing `progress` from `SimState`.\n */\n\nexport const clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\n\n/** Decelerating ease, good for reveals. */\nexport const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Back-ease-out with overshoot, good for pops. Lands exactly on 1 at t=1. */\nexport function backEaseOut(t: number, tension = 2.2): number {\n const c3 = tension + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + tension * Math.pow(t - 1, 2);\n}\n\nexport interface FadeSlideOptions {\n /** Slide distance in px at progress 0 (default 8). */\n distance?: number;\n /** Axis to slide along (default \"y\"). */\n axis?: \"x\" | \"y\";\n easing?: (t: number) => number;\n}\n\n/** Fade + slide-in reveal. `progress` 0 → hidden+offset, 1 → shown+settled. */\nexport function fadeSlideIn(\n progress: number,\n options: FadeSlideOptions = {},\n): CSSProperties {\n const eased = (options.easing ?? easeOutCubic)(clamp01(progress));\n const distance = options.distance ?? 8;\n const offset = (1 - eased) * distance;\n const translate =\n options.axis === \"x\"\n ? `translateX(${offset}px)`\n : `translateY(${offset}px)`;\n return { opacity: eased, transform: translate };\n}\n\nexport interface PopOptions {\n /** Overshoot tension (default 2.2 ≈ ~10% overshoot). */\n tension?: number;\n}\n\n/** Scale pop-in (with a little overshoot), good for reactions landing. */\nexport function popIn(\n progress: number,\n options: PopOptions = {},\n): CSSProperties {\n const p = clamp01(progress);\n const scale = backEaseOut(p, options.tension ?? 2.2);\n return {\n opacity: clamp01(p * 3),\n transform: `scale(${scale})`,\n transformOrigin: \"center\",\n };\n}\n\nexport interface TypingDotsProps {\n /** 0..1 progress through the indicator's shown duration. */\n progress?: number;\n count?: number;\n /** Bounce cycles across the full progress (default 4). */\n cycles?: number;\n /** Dot color (default `currentColor`). */\n color?: string;\n /** Dot diameter in px (default 6). */\n size?: number;\n /** Gap between dots in px (default 4). */\n gap?: number;\n}\n\n/**\n * The three-dot bouncing typing indicator, animated purely from `progress`\n * (deterministic per frame). Skins style it via props or wrap it.\n */\nexport function TypingDots({\n progress = 0,\n count = 3,\n cycles = 4,\n color = \"currentColor\",\n size = 6,\n gap = 4,\n}: TypingDotsProps): ReactNode {\n const phase = clamp01(progress) * cycles * Math.PI * 2;\n const dots: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n const wave = Math.sin(phase - i * 0.9);\n const lift = Math.max(0, wave) * 4;\n const opacity = 0.4 + Math.max(0, wave) * 0.6;\n dots.push(\n <span\n key={i}\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n background: color,\n transform: `translateY(${-lift}px)`,\n opacity,\n }}\n />,\n );\n }\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"flex-end\", gap }}>\n {dots}\n </span>\n );\n}\n","import type { CSSProperties, ReactNode } from \"react\";\nimport type {\n ContentNode,\n ImageNode,\n InlineNode,\n TextNode,\n} from \"@typecaast/schema\";\n\nexport interface ContentClassNames {\n text?: string;\n link?: string;\n mention?: string;\n code?: string;\n emoji?: string;\n image?: string;\n}\n\n/** Per-mark inline styles, so skins can theme marks without a CSS file. */\nexport interface ContentStyles {\n text?: CSSProperties;\n link?: CSSProperties;\n mention?: CSSProperties;\n code?: CSSProperties;\n emoji?: CSSProperties;\n}\n\nexport interface MessageContentProps {\n nodes: ContentNode[];\n /** Per-mark class names so skins style marks with their own CSS. */\n classNames?: ContentClassNames;\n /** Per-mark inline styles (merged with the defaults). */\n styles?: ContentStyles;\n /** Extra style for in-message images (skins set radius, max size, etc.). */\n imageStyle?: CSSProperties;\n}\n\nfunction renderInline(\n span: InlineNode,\n key: number,\n cn: ContentClassNames,\n st: ContentStyles,\n): ReactNode {\n switch (span.type) {\n case \"text\":\n return span.value;\n case \"code\":\n return (\n <code key={key} data-tc-mark=\"code\" className={cn.code} style={st.code}>\n {span.value}\n </code>\n );\n case \"link\":\n return (\n <a\n key={key}\n data-tc-mark=\"link\"\n className={cn.link}\n style={st.link}\n href={span.href}\n rel=\"noreferrer\"\n >\n {span.label ?? span.href}\n </a>\n );\n case \"mention\":\n return (\n <span\n key={key}\n data-tc-mark=\"mention\"\n className={cn.mention}\n style={st.mention}\n >\n {span.label}\n </span>\n );\n case \"emoji\":\n return (\n <span\n key={key}\n data-tc-mark=\"emoji\"\n className={cn.emoji}\n style={st.emoji}\n >\n {span.value}\n </span>\n );\n }\n}\n\nfunction renderImage(\n node: ImageNode,\n key: number,\n cn: ContentClassNames,\n imageStyle?: CSSProperties,\n): ReactNode {\n return (\n <img\n key={key}\n data-tc-node=\"image\"\n className={cn.image}\n src={node.src}\n alt={node.alt ?? \"\"}\n width={node.width}\n height={node.height}\n style={{ maxWidth: \"100%\", display: \"block\", ...imageStyle }}\n />\n );\n}\n\n/**\n * Render a message body (`ContentNode[]`) to React: text nodes with inline\n * marks (code/link/mention/emoji) and in-message images. Unknown node types are\n * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically\n * in the browser and in Remotion's Node renderer. Skins style via `classNames`.\n */\nexport function MessageContent({\n nodes,\n classNames = {},\n styles = {},\n imageStyle,\n}: MessageContentProps): ReactNode {\n return (\n <>\n {nodes.map((node, i) => {\n if (node.type === \"text\") {\n const text = node as TextNode;\n return (\n <span\n key={i}\n data-tc-node=\"text\"\n className={classNames.text}\n style={styles.text}\n >\n {text.spans.map((span, j) =>\n renderInline(span, j, classNames, styles),\n )}\n </span>\n );\n }\n if (node.type === \"image\") {\n return renderImage(node as ImageNode, i, classNames, imageStyle);\n }\n return null; // unknown future node type — skipped\n })}\n </>\n );\n}\n","import { useMemo, type ReactNode } from \"react\";\nimport type { ComposerMode, Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider } from \"./theme.js\";\nimport type { Skin } from \"./types.js\";\n\nexport type { ComposerMode };\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 * Composer visibility: `auto` (default) shows it only while someone is\n * typing/sending; `always` keeps the reply box visible (idle = placeholder);\n * `never` hides it.\n */\n composer?: ComposerMode;\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 *\n * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the\n * skins' own stories can render a frame without depending on the React player.\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n composer = \"auto\",\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 // `always` keeps the reply box mounted even when idle (falls back to the self\n // participant so a placeholder shows); `auto` only shows it while composing.\n const selfParticipant = useMemo(\n () => participants.find((p) => p.isSelf),\n [participants],\n );\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : composer === \"always\"\n ? selfParticipant\n : undefined;\n const showComposer = composer !== \"never\" && composerAuthor !== 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 // Breathing room beneath the last message — keeps it off the\n // composer (when shown) and the Frame's bottom edge (when hidden).\n paddingBottom: 16,\n }}\n >\n {state.messages.map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n // Index-disambiguated so a config with duplicate message ids can't\n // collide React keys (the builder can produce them transiently).\n const key = `${message.id}-${i}`;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={key}\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={key}\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 {showComposer ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n />\n ) : null}\n </Frame>\n </ThemeProvider>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/define-skin.ts","../src/theme.tsx","../src/fonts.ts","../src/animation.tsx","../src/content.tsx","../src/stage.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { Skin } from \"./types.js\";\n\n/**\n * Identity helper for type-safety and a single registration point when\n * authoring a skin. Keeping it a function (rather than a bare object) lets us\n * add validation/registration later without changing skin call sites.\n */\nexport function defineSkin(skin: Skin): Skin {\n return skin;\n}\n","import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\nimport type { SkinTokens } from \"./types.js\";\n\ninterface ThemeContextValue {\n theme: ResolvedTheme;\n tokens?: SkinTokens;\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({ theme: \"light\" });\n\nexport interface ThemeProviderProps {\n theme: ResolvedTheme;\n /** Resolved per-theme tokens for the active skin. */\n tokens?: SkinTokens;\n children?: ReactNode;\n}\n\n/** Provides the resolved theme + tokens to every skin component below it. */\nexport function ThemeProvider({\n theme,\n tokens,\n children,\n}: ThemeProviderProps): ReactElement {\n return (\n <ThemeContext.Provider value={{ theme, tokens }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/** The resolved theme (`\"light\" | \"dark\"`) for the current subtree. */\nexport function useTheme(): ResolvedTheme {\n return useContext(ThemeContext).theme;\n}\n\n/** The resolved design tokens for the current subtree, if provided. */\nexport function useTokens(): SkinTokens | undefined {\n return useContext(ThemeContext).tokens;\n}\n","import type { FontDeclaration } from \"./types.js\";\n\n/** Build a CSS `src:` value from a font's sources. */\nfunction srcValue(decl: FontDeclaration): string {\n return decl.sources\n .map((s) => {\n const format = s.format ? ` format(\"${s.format}\")` : \"\";\n return `url(\"${s.url}\")${format}`;\n })\n .join(\", \");\n}\n\n/**\n * Load a skin's declared web fonts so live preview matches the platform (not\n * just video export). SSR-safe: a no-op when `document`/`FontFace` are absent\n * (server render, Remotion Node). Idempotent per family+weight+style.\n *\n * Returns once every face has loaded (or immediately, off the main document).\n */\nexport async function loadSkinFonts(\n fonts: FontDeclaration[] | undefined,\n): Promise<void> {\n if (!fonts || fonts.length === 0) return;\n if (\n typeof document === \"undefined\" ||\n typeof FontFace === \"undefined\" ||\n !document.fonts\n ) {\n return;\n }\n\n const pending: Promise<unknown>[] = [];\n for (const decl of fonts) {\n for (const source of decl.sources) {\n const descriptors: FontFaceDescriptors = {};\n if (source.weight !== undefined)\n descriptors.weight = String(source.weight);\n if (source.style !== undefined) descriptors.style = source.style;\n\n const single: FontDeclaration = { ...decl, sources: [source] };\n const face = new FontFace(decl.family, srcValue(single), descriptors);\n\n // Skip if an identical face is already registered.\n const already = [...document.fonts].some(\n (f) =>\n f.family === decl.family &&\n f.weight === (descriptors.weight ?? \"normal\") &&\n f.style === (descriptors.style ?? \"normal\"),\n );\n if (already) continue;\n\n document.fonts.add(face);\n pending.push(face.load());\n }\n }\n await Promise.all(pending);\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\n\n/**\n * Animation primitives are **pure functions of progress** (0..1), not CSS\n * transitions or JS timers — so the React preview and the Remotion render\n * animate identically frame-for-frame (PLAN §7). Skins call these driven by\n * `revealProgress` / typing `progress` from `SimState`.\n */\n\nexport const clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\n\n/** Decelerating ease, good for reveals. */\nexport const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Back-ease-out with overshoot, good for pops. Lands exactly on 1 at t=1. */\nexport function backEaseOut(t: number, tension = 2.2): number {\n const c3 = tension + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + tension * Math.pow(t - 1, 2);\n}\n\nexport interface FadeSlideOptions {\n /** Slide distance in px at progress 0 (default 8). */\n distance?: number;\n /** Axis to slide along (default \"y\"). */\n axis?: \"x\" | \"y\";\n easing?: (t: number) => number;\n}\n\n/** Fade + slide-in reveal. `progress` 0 → hidden+offset, 1 → shown+settled. */\nexport function fadeSlideIn(\n progress: number,\n options: FadeSlideOptions = {},\n): CSSProperties {\n const eased = (options.easing ?? easeOutCubic)(clamp01(progress));\n const distance = options.distance ?? 8;\n const offset = (1 - eased) * distance;\n const translate =\n options.axis === \"x\"\n ? `translateX(${offset}px)`\n : `translateY(${offset}px)`;\n return { opacity: eased, transform: translate };\n}\n\nexport interface PopOptions {\n /** Overshoot tension (default 2.2 ≈ ~10% overshoot). */\n tension?: number;\n}\n\n/** Scale pop-in (with a little overshoot), good for reactions landing. */\nexport function popIn(\n progress: number,\n options: PopOptions = {},\n): CSSProperties {\n const p = clamp01(progress);\n const scale = backEaseOut(p, options.tension ?? 2.2);\n return {\n opacity: clamp01(p * 3),\n transform: `scale(${scale})`,\n transformOrigin: \"center\",\n };\n}\n\nexport interface TypingDotsProps {\n /** 0..1 progress through the indicator's shown duration. */\n progress?: number;\n count?: number;\n /** Bounce cycles across the full progress (default 4). */\n cycles?: number;\n /** Dot color (default `currentColor`). */\n color?: string;\n /** Dot diameter in px (default 6). */\n size?: number;\n /** Gap between dots in px (default 4). */\n gap?: number;\n}\n\n/**\n * The three-dot bouncing typing indicator, animated purely from `progress`\n * (deterministic per frame). Skins style it via props or wrap it.\n */\nexport function TypingDots({\n progress = 0,\n count = 3,\n cycles = 4,\n color = \"currentColor\",\n size = 6,\n gap = 4,\n}: TypingDotsProps): ReactElement {\n const phase = clamp01(progress) * cycles * Math.PI * 2;\n const dots: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n const wave = Math.sin(phase - i * 0.9);\n const lift = Math.max(0, wave) * 4;\n const opacity = 0.4 + Math.max(0, wave) * 0.6;\n dots.push(\n <span\n key={i}\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n background: color,\n transform: `translateY(${-lift}px)`,\n opacity,\n }}\n />,\n );\n }\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"flex-end\", gap }}>\n {dots}\n </span>\n );\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport type {\n ContentNode,\n ImageNode,\n InlineNode,\n TextNode,\n} from \"@typecaast/schema\";\n\nexport interface ContentClassNames {\n text?: string;\n link?: string;\n mention?: string;\n code?: string;\n emoji?: string;\n image?: string;\n}\n\n/** Per-mark inline styles, so skins can theme marks without a CSS file. */\nexport interface ContentStyles {\n text?: CSSProperties;\n link?: CSSProperties;\n mention?: CSSProperties;\n code?: CSSProperties;\n emoji?: CSSProperties;\n}\n\nexport interface MessageContentProps {\n nodes: ContentNode[];\n /** Per-mark class names so skins style marks with their own CSS. */\n classNames?: ContentClassNames;\n /** Per-mark inline styles (merged with the defaults). */\n styles?: ContentStyles;\n /** Extra style for in-message images (skins set radius, max size, etc.). */\n imageStyle?: CSSProperties;\n}\n\nfunction renderInline(\n span: InlineNode,\n key: number,\n cn: ContentClassNames,\n st: ContentStyles,\n): ReactNode {\n switch (span.type) {\n case \"text\":\n return span.value;\n case \"code\":\n return (\n <code key={key} data-tc-mark=\"code\" className={cn.code} style={st.code}>\n {span.value}\n </code>\n );\n case \"link\":\n return (\n <a\n key={key}\n data-tc-mark=\"link\"\n className={cn.link}\n style={st.link}\n href={span.href}\n rel=\"noreferrer\"\n >\n {span.label ?? span.href}\n </a>\n );\n case \"mention\":\n return (\n <span\n key={key}\n data-tc-mark=\"mention\"\n className={cn.mention}\n style={st.mention}\n >\n {span.label}\n </span>\n );\n case \"emoji\":\n return (\n <span\n key={key}\n data-tc-mark=\"emoji\"\n className={cn.emoji}\n style={st.emoji}\n >\n {span.value}\n </span>\n );\n }\n}\n\nfunction renderImage(\n node: ImageNode,\n key: number,\n cn: ContentClassNames,\n imageStyle?: CSSProperties,\n): ReactNode {\n return (\n <img\n key={key}\n data-tc-node=\"image\"\n className={cn.image}\n src={node.src}\n alt={node.alt ?? \"\"}\n width={node.width}\n height={node.height}\n style={{ maxWidth: \"100%\", display: \"block\", ...imageStyle }}\n />\n );\n}\n\n/**\n * Render a message body (`ContentNode[]`) to React: text nodes with inline\n * marks (code/link/mention/emoji) and in-message images. Unknown node types are\n * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically\n * in the browser and in Remotion's Node renderer. Skins style via `classNames`.\n */\nexport function MessageContent({\n nodes,\n classNames = {},\n styles = {},\n imageStyle,\n}: MessageContentProps): ReactElement {\n return (\n <>\n {nodes.map((node, i) => {\n if (node.type === \"text\") {\n const text = node as TextNode;\n return (\n <span\n key={i}\n data-tc-node=\"text\"\n className={classNames.text}\n style={styles.text}\n >\n {text.spans.map((span, j) =>\n renderInline(span, j, classNames, styles),\n )}\n </span>\n );\n }\n if (node.type === \"image\") {\n return renderImage(node as ImageNode, i, classNames, imageStyle);\n }\n return null; // unknown future node type — skipped\n })}\n </>\n );\n}\n","import { useMemo, type ReactElement } from \"react\";\nimport type { ComposerMode, Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider } from \"./theme.js\";\nimport type { Skin } from \"./types.js\";\n\nexport type { ComposerMode };\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 * Composer visibility: `auto` (default) shows it only while someone is\n * typing/sending; `always` keeps the reply box visible (idle = placeholder);\n * `never` hides it.\n */\n composer?: ComposerMode;\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 *\n * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the\n * skins' own stories can render a frame without depending on the React player.\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n composer = \"auto\",\n}: TypecaastStageProps): ReactElement {\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 // `always` keeps the reply box mounted even when idle (falls back to the self\n // participant so a placeholder shows); `auto` only shows it while composing.\n const selfParticipant = useMemo(\n () => participants.find((p) => p.isSelf),\n [participants],\n );\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : composer === \"always\"\n ? selfParticipant\n : undefined;\n const showComposer = composer !== \"never\" && composerAuthor !== undefined;\n\n // \"X is typing…\" indicators, minus the viewer's own (you never see yourself\n // typing — that's what the composer shows). Placement is skin-driven: inline\n // in the thread by default, or below the composer (Slack).\n const typingPlacement = skin.meta.typingPlacement ?? \"thread\";\n const typingNodes = state.typingIndicators\n .map((typing, i) => {\n const author = byId.get(typing.from);\n if (!author || author.isSelf) 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 .filter(Boolean);\n\n return (\n <ThemeProvider theme={theme} tokens={tokens}>\n <Frame theme={theme} options={options} composer={composer}>\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 // Breathing room beneath the last message — keeps it off the\n // composer (when shown) and the Frame's bottom edge (when hidden).\n paddingBottom: 16,\n }}\n >\n {state.messages.map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n // Index-disambiguated so a config with duplicate message ids can't\n // collide React keys (the builder can produce them transiently).\n const key = `${message.id}-${i}`;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={key}\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={key}\n theme={theme}\n message={message}\n author={author}\n previousAuthor={previousAuthor}\n />\n );\n })}\n {typingPlacement === \"thread\" ? typingNodes : null}\n </div>\n {showComposer ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n />\n ) : null}\n {typingPlacement === \"below-composer\" ? typingNodes : null}\n </Frame>\n </ThemeProvider>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typecaast/skin-kit",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Skin authoring kit: defineSkin, Skin/Capabilities/SkinTokens types, theme context, font loader.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -25,8 +25,8 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "zod": "^4.0.0",
28
- "@typecaast/schema": "0.2.0",
29
- "@typecaast/core": "0.2.0"
28
+ "@typecaast/core": "0.3.0",
29
+ "@typecaast/schema": "0.2.1"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "react": ">=18"