@bendyline/squisq 1.4.1 → 1.5.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.
Files changed (254) hide show
  1. package/README.md +14 -1
  2. package/dist/{ContentContainer-DNx460y8.d.ts → ContentContainer-BXUlIf18.d.ts} +6 -2
  3. package/dist/{Doc-BeSeH2iX.d.ts → Doc-BBVGq1_9.d.ts} +922 -273
  4. package/dist/{ImageEditDoc-BTIvw0oq.d.ts → ImageEditDoc-FuyGtt6o.d.ts} +11 -4
  5. package/dist/{chunk-ZTX4PFFT.js → chunk-2OIBZYKP.js} +75 -2
  6. package/dist/chunk-2OIBZYKP.js.map +1 -0
  7. package/dist/chunk-2UQOHYE2.js +45 -0
  8. package/dist/chunk-2UQOHYE2.js.map +1 -0
  9. package/dist/chunk-6ATE2PSM.js +344 -0
  10. package/dist/chunk-6ATE2PSM.js.map +1 -0
  11. package/dist/{chunk-57CVQCFX.js → chunk-A4O7GIWE.js} +74 -23
  12. package/dist/chunk-A4O7GIWE.js.map +1 -0
  13. package/dist/{chunk-5WFRKURX.js → chunk-DLXZMS5K.js} +4 -2
  14. package/dist/chunk-DLXZMS5K.js.map +1 -0
  15. package/dist/chunk-ET53IIEP.js +1 -0
  16. package/dist/chunk-ET53IIEP.js.map +1 -0
  17. package/dist/chunk-HWVFJAAH.js +362 -0
  18. package/dist/chunk-HWVFJAAH.js.map +1 -0
  19. package/dist/{chunk-2TMAWB4Y.js → chunk-IMSCRLLV.js} +1 -1
  20. package/dist/chunk-IMSCRLLV.js.map +1 -0
  21. package/dist/{chunk-B4EEON3N.js → chunk-KKNUBQ6Y.js} +536 -103
  22. package/dist/chunk-KKNUBQ6Y.js.map +1 -0
  23. package/dist/chunk-LDQ2HJIX.js +596 -0
  24. package/dist/chunk-LDQ2HJIX.js.map +1 -0
  25. package/dist/chunk-LH7I6SH7.js +7208 -0
  26. package/dist/chunk-LH7I6SH7.js.map +1 -0
  27. package/dist/chunk-PGGGKYWG.js +1 -0
  28. package/dist/chunk-PGGGKYWG.js.map +1 -0
  29. package/dist/{chunk-7UDSRZKG.js → chunk-Q3ROPT5H.js} +35 -14
  30. package/dist/chunk-Q3ROPT5H.js.map +1 -0
  31. package/dist/chunk-QE7TWGVT.js +178 -0
  32. package/dist/chunk-QE7TWGVT.js.map +1 -0
  33. package/dist/chunk-RUDYOTA6.js +345 -0
  34. package/dist/chunk-RUDYOTA6.js.map +1 -0
  35. package/dist/{chunk-K32VJONL.js → chunk-T5UK6YOB.js} +41 -2
  36. package/dist/chunk-T5UK6YOB.js.map +1 -0
  37. package/dist/{chunk-3K5OG6KI.js → chunk-TEEEILMP.js} +412 -186
  38. package/dist/chunk-TEEEILMP.js.map +1 -0
  39. package/dist/{chunk-FR2RBTKO.js → chunk-TQWLI6S2.js} +93 -27
  40. package/dist/chunk-TQWLI6S2.js.map +1 -0
  41. package/dist/{chunk-WMOI7FN3.js → chunk-UY7KGQ5R.js} +12 -2
  42. package/dist/{chunk-WMOI7FN3.js.map → chunk-UY7KGQ5R.js.map} +1 -1
  43. package/dist/{chunk-3E5F2XMR.js → chunk-VWUFZ6ZG.js} +29 -2
  44. package/dist/chunk-VWUFZ6ZG.js.map +1 -0
  45. package/dist/chunk-ZLQKNOZQ.js +1 -0
  46. package/dist/chunk-ZLQKNOZQ.js.map +1 -0
  47. package/dist/doc/index.d.ts +5 -5
  48. package/dist/doc/index.js +135 -8
  49. package/dist/generate/index.d.ts +3 -74
  50. package/dist/generate/index.js +2 -7
  51. package/dist/icons/index.d.ts +3 -26
  52. package/dist/icons/index.js +13 -0
  53. package/dist/icons/inlineIconMarker.d.ts +1 -0
  54. package/dist/icons/inlineIconMarker.js +15 -0
  55. package/dist/icons/inlineIconMarker.js.map +1 -0
  56. package/dist/imageEdit/index.d.ts +12 -6
  57. package/dist/imageEdit/index.js +4 -1
  58. package/dist/index.d.ts +14 -11
  59. package/dist/index.js +265 -37
  60. package/dist/inlineIconMarker-CiE8Hlvz.d.ts +59 -0
  61. package/dist/jsonForm/index.d.ts +42 -1
  62. package/dist/jsonForm/index.js +9 -1
  63. package/dist/markdown/index.d.ts +228 -3
  64. package/dist/markdown/index.js +42 -7
  65. package/dist/recommend/index.d.ts +1 -1
  66. package/dist/recommend/index.js +3 -2
  67. package/dist/schemas/index.d.ts +28 -8
  68. package/dist/schemas/index.js +36 -8
  69. package/dist/spatial/index.d.ts +9 -4
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.d.ts +2 -2
  72. package/dist/storage/index.js +1 -1
  73. package/dist/story/index.d.ts +1021 -41
  74. package/dist/story/index.js +135 -8
  75. package/dist/{themeLibrary-BHjJ0Qx6.d.ts → themeLibrary-CehcJhzz.d.ts} +1 -1
  76. package/dist/timing/index.js +3 -5
  77. package/dist/transform/index.d.ts +48 -10
  78. package/dist/transform/index.js +17 -481
  79. package/dist/transform/index.js.map +1 -1
  80. package/dist/{types-BdZkdJ3z.d.ts → types-DlAZ7MW4.d.ts} +115 -1
  81. package/dist/versions/index.d.ts +1 -1
  82. package/package.json +6 -1
  83. package/src/__tests__/animationUtils.test.ts +40 -0
  84. package/src/__tests__/annotationCoercion.test.ts +243 -0
  85. package/src/__tests__/applyRenderStyle.test.ts +146 -0
  86. package/src/__tests__/atmosphereLayers.test.ts +108 -0
  87. package/src/__tests__/attrTokens.test.ts +162 -0
  88. package/src/__tests__/contentExtractor.test.ts +60 -0
  89. package/src/__tests__/coverBlock.test.ts +46 -1
  90. package/src/__tests__/customTemplateSample.test.ts +144 -0
  91. package/src/__tests__/customTemplatesFrontmatter.test.ts +236 -0
  92. package/src/__tests__/customThemesFrontmatter.test.ts +145 -0
  93. package/src/__tests__/diagramBlock.test.ts +122 -0
  94. package/src/__tests__/diagramLayout.test.ts +134 -0
  95. package/src/__tests__/drawing.test.ts +109 -0
  96. package/src/__tests__/drawingBlock.test.ts +124 -0
  97. package/src/__tests__/drawingLayout.test.ts +171 -0
  98. package/src/__tests__/geohash.test.ts +6 -5
  99. package/src/__tests__/getLayers.test.ts +6 -2
  100. package/src/__tests__/getLayersFallback.test.ts +103 -0
  101. package/src/__tests__/imageEditExportSvg.test.ts +70 -0
  102. package/src/__tests__/imageEditVersions.test.ts +72 -0
  103. package/src/__tests__/inlineIconMarker.test.ts +36 -0
  104. package/src/__tests__/inputDescriptors.test.ts +162 -0
  105. package/src/__tests__/jsonForm.tokens.test.ts +84 -0
  106. package/src/__tests__/layoutBlock.test.ts +83 -0
  107. package/src/__tests__/layoutLayout.test.ts +169 -0
  108. package/src/__tests__/markdown.test.ts +87 -0
  109. package/src/__tests__/markdownSanitize.test.ts +79 -0
  110. package/src/__tests__/markdownToDoc.test.ts +391 -2
  111. package/src/__tests__/mediaAnnotations.test.ts +91 -0
  112. package/src/__tests__/mediaSchedule.test.ts +118 -0
  113. package/src/__tests__/pandocAttrParse.test.ts +187 -0
  114. package/src/__tests__/resolveDocTheme.test.ts +55 -0
  115. package/src/__tests__/rootExports.test.ts +43 -0
  116. package/src/__tests__/shapeGeometry.test.ts +89 -0
  117. package/src/__tests__/shapeVocabulary.test.ts +133 -0
  118. package/src/__tests__/structuredData.test.ts +249 -0
  119. package/src/__tests__/templateAnnotationParse.test.ts +108 -0
  120. package/src/__tests__/templateMetadata.test.ts +31 -0
  121. package/src/__tests__/templates.test.ts +257 -4
  122. package/src/__tests__/themeCompile.test.ts +42 -0
  123. package/src/__tests__/themeValidator.test.ts +6 -0
  124. package/src/__tests__/transformV2.test.ts +137 -0
  125. package/src/__tests__/transitionNormalize.test.ts +61 -0
  126. package/src/__tests__/validateDoc.test.ts +260 -0
  127. package/src/doc/annotationBlocks.ts +117 -0
  128. package/src/doc/customTemplatesFrontmatter.ts +314 -0
  129. package/src/doc/customThemesFrontmatter.ts +95 -0
  130. package/src/doc/docToMarkdown.ts +104 -5
  131. package/src/doc/getLayers.ts +49 -17
  132. package/src/doc/index.ts +47 -1
  133. package/src/doc/markdownToDoc.ts +411 -13
  134. package/src/doc/mediaAnnotations.ts +100 -0
  135. package/src/doc/resolveDocTheme.ts +55 -0
  136. package/src/doc/standaloneAnnotation.ts +58 -0
  137. package/src/doc/structuredData.ts +301 -0
  138. package/src/doc/templateInputs.ts +240 -0
  139. package/src/doc/templates/__tests__/customTemplate.test.ts +170 -0
  140. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +110 -0
  141. package/src/doc/templates/accentImage.ts +30 -6
  142. package/src/doc/templates/captionUtils.ts +23 -0
  143. package/src/doc/templates/comparisonBar.ts +33 -18
  144. package/src/doc/templates/coverBlock.ts +35 -5
  145. package/src/doc/templates/customTemplate.ts +51 -0
  146. package/src/doc/templates/dataTable.ts +37 -19
  147. package/src/doc/templates/dateEvent.ts +41 -20
  148. package/src/doc/templates/definitionCard.ts +26 -18
  149. package/src/doc/templates/diagramBlock.ts +204 -0
  150. package/src/doc/templates/diagramLayout.ts +159 -0
  151. package/src/doc/templates/drawingBlock.ts +325 -0
  152. package/src/doc/templates/drawingLayout.ts +539 -0
  153. package/src/doc/templates/factCard.ts +54 -26
  154. package/src/doc/templates/fallbackBlock.ts +123 -0
  155. package/src/doc/templates/featureBlock.ts +20 -13
  156. package/src/doc/templates/fullBleedQuote.ts +18 -8
  157. package/src/doc/templates/imageWithCaption.ts +30 -15
  158. package/src/doc/templates/index.ts +225 -46
  159. package/src/doc/templates/inputDescriptors.ts +432 -0
  160. package/src/doc/templates/layoutBlock.ts +43 -0
  161. package/src/doc/templates/layoutLayout.ts +243 -0
  162. package/src/doc/templates/listBlock.ts +56 -30
  163. package/src/doc/templates/mapBlock.ts +15 -12
  164. package/src/doc/templates/metadata.ts +120 -0
  165. package/src/doc/templates/persistentLayers.ts +129 -11
  166. package/src/doc/templates/photoGrid.ts +22 -34
  167. package/src/doc/templates/pullQuote.ts +30 -12
  168. package/src/doc/templates/quoteBlock.ts +54 -29
  169. package/src/doc/templates/sectionHeader.ts +25 -11
  170. package/src/doc/templates/statHighlight.ts +39 -22
  171. package/src/doc/templates/titleBlock.ts +59 -21
  172. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +487 -0
  173. package/src/doc/templates/tokens/resolveTokens.ts +453 -0
  174. package/src/doc/templates/twoColumn.ts +48 -30
  175. package/src/doc/templates/videoPullQuote.ts +22 -12
  176. package/src/doc/templates/videoWithCaption.ts +21 -15
  177. package/src/doc/utils/animationUtils.ts +267 -3
  178. package/src/doc/utils/applyRenderStyle.ts +113 -0
  179. package/src/doc/utils/imageTreatment.ts +70 -0
  180. package/src/doc/utils/nearest.ts +59 -0
  181. package/src/doc/utils/shapeGeometry.ts +464 -0
  182. package/src/doc/utils/themeUtils.ts +75 -1
  183. package/src/doc/validate.ts +575 -0
  184. package/src/generate/contentExtractor.ts +148 -35
  185. package/src/generate/index.ts +0 -3
  186. package/src/icons/index.ts +8 -0
  187. package/src/icons/inlineIconMarker.ts +73 -0
  188. package/src/imageEdit/export.ts +85 -2
  189. package/src/imageEdit/index.ts +1 -0
  190. package/src/imageEdit/persistence.ts +51 -0
  191. package/src/index.ts +2 -0
  192. package/src/jsonForm/index.ts +2 -0
  193. package/src/jsonForm/tokens.ts +78 -0
  194. package/src/markdown/annotationCoercion.ts +295 -0
  195. package/src/markdown/attrTokens.ts +241 -0
  196. package/src/markdown/convert.ts +229 -26
  197. package/src/markdown/index.ts +37 -0
  198. package/src/markdown/sanitize.ts +318 -0
  199. package/src/markdown/stringify.ts +96 -6
  200. package/src/markdown/types.ts +66 -0
  201. package/src/markdown/utils.ts +111 -2
  202. package/src/recommend/templates.ts +24 -0
  203. package/src/schemas/BlockTemplates.ts +133 -4
  204. package/src/schemas/CustomTemplates.ts +126 -0
  205. package/src/schemas/Doc.ts +387 -65
  206. package/src/schemas/ImageEditDoc.ts +11 -4
  207. package/src/schemas/Media.ts +169 -0
  208. package/src/schemas/Theme.ts +23 -1
  209. package/src/schemas/Transitions.ts +224 -0
  210. package/src/schemas/colorUtils.ts +33 -0
  211. package/src/schemas/fontStacks.ts +5 -2
  212. package/src/schemas/index.ts +3 -0
  213. package/src/schemas/themeCompile.ts +24 -5
  214. package/src/schemas/themeValidator.ts +49 -11
  215. package/src/schemas/themes/cinematic.json +69 -12
  216. package/src/schemas/themes/documentary.json +58 -11
  217. package/src/schemas/themes/gezellig.json +60 -11
  218. package/src/schemas/themes/magazine.json +54 -9
  219. package/src/schemas/themes/morning-light.json +52 -9
  220. package/src/schemas/themes/standard-dark.json +48 -13
  221. package/src/schemas/themes/standard.json +44 -12
  222. package/src/schemas/themes/tech-dark.json +65 -12
  223. package/src/spatial/Geohash.ts +52 -21
  224. package/src/storage/ContentContainer.ts +6 -2
  225. package/src/transform/applyTransform.ts +43 -4
  226. package/src/transform/index.ts +1 -0
  227. package/src/transform/registry.ts +44 -13
  228. package/src/transform/styles/dataDriven.ts +2 -1
  229. package/src/transform/styles/documentary.ts +2 -1
  230. package/src/transform/styles/magazine.ts +3 -1
  231. package/src/transform/styles/minimal.ts +2 -1
  232. package/src/transform/styles/narrative.ts +3 -1
  233. package/src/transform/templateSelector.ts +87 -11
  234. package/src/transform/types.ts +35 -2
  235. package/dist/chunk-2TMAWB4Y.js.map +0 -1
  236. package/dist/chunk-3E5F2XMR.js.map +0 -1
  237. package/dist/chunk-3K5OG6KI.js.map +0 -1
  238. package/dist/chunk-4X3JQXNM.js +0 -35
  239. package/dist/chunk-4X3JQXNM.js.map +0 -1
  240. package/dist/chunk-57CVQCFX.js.map +0 -1
  241. package/dist/chunk-5WFRKURX.js.map +0 -1
  242. package/dist/chunk-7UDSRZKG.js.map +0 -1
  243. package/dist/chunk-B4EEON3N.js.map +0 -1
  244. package/dist/chunk-FR2RBTKO.js.map +0 -1
  245. package/dist/chunk-H3AXU4MJ.js +0 -179
  246. package/dist/chunk-H3AXU4MJ.js.map +0 -1
  247. package/dist/chunk-K32VJONL.js.map +0 -1
  248. package/dist/chunk-KSWJR66U.js +0 -3706
  249. package/dist/chunk-KSWJR66U.js.map +0 -1
  250. package/dist/chunk-MYIH7FWD.js +0 -145
  251. package/dist/chunk-MYIH7FWD.js.map +0 -1
  252. package/dist/chunk-ZTX4PFFT.js.map +0 -1
  253. package/src/__tests__/slideshowGenerator.test.ts +0 -128
  254. package/src/generate/slideshowGenerator.ts +0 -273
@@ -20,12 +20,23 @@ import type {
20
20
  PersistentLayerConfig,
21
21
  } from '../../schemas/BlockTemplates.js';
22
22
  import type { Theme } from '../../schemas/Theme.js';
23
+ import type { CustomTemplateDefinition } from '../../schemas/CustomTemplates.js';
23
24
  import { isTemplateBlock, createTemplateContext } from '../../schemas/BlockTemplates.js';
24
25
  import { DEFAULT_THEME as defaultTheme } from '../../schemas/themeLibrary.js';
25
- import { expandPersistentLayers } from './persistentLayers.js';
26
+ import { expandPersistentLayers, wrapWithPersistentLayers } from './persistentLayers.js';
27
+ import { applyRenderStyleToLayers } from '../utils/applyRenderStyle.js';
28
+ import { resolveBlockTransition } from '../../schemas/Transitions.js';
29
+ import { makeCustomTemplateFn } from './customTemplate.js';
30
+ // Coerces inline `{[…]}` string params into typed template input. inputDescriptors
31
+ // imports `resolveTemplateName` from this module in return; the cycle is benign
32
+ // because every cross-reference lives inside a function body, not module init.
33
+ import { coerceTemplateParams } from './inputDescriptors.js';
26
34
  import type { ViewportConfig } from '../../schemas/Viewport.js';
27
35
  import { VIEWPORT_PRESETS } from '../../schemas/Viewport.js';
28
36
 
37
+ export { TEMPLATE_METADATA } from './metadata.js';
38
+ export type { TemplateMetadata } from './metadata.js';
39
+
29
40
  // Import all template functions
30
41
  import { titleBlock } from './titleBlock.js';
31
42
  import { sectionHeader } from './sectionHeader.js';
@@ -46,6 +57,9 @@ import { pullQuote } from './pullQuote.js';
46
57
  import { videoWithCaption } from './videoWithCaption.js';
47
58
  import { videoPullQuote } from './videoPullQuote.js';
48
59
  import { dataTable } from './dataTable.js';
60
+ import { diagramBlock } from './diagramBlock.js';
61
+ import { drawingBlock } from './drawingBlock.js';
62
+ import { layoutBlock } from './layoutBlock.js';
49
63
 
50
64
  /**
51
65
  * Registry mapping template ids (the strings that appear in
@@ -82,6 +96,9 @@ export const templateRegistry: TemplateRegistry = {
82
96
  videoWithCaption,
83
97
  videoPullQuote,
84
98
  dataTable,
99
+ diagram: diagramBlock,
100
+ layout: layoutBlock,
101
+ drawing: drawingBlock,
85
102
  };
86
103
 
87
104
  /**
@@ -95,6 +112,8 @@ export const TEMPLATE_ALIASES: Readonly<Record<string, string>> = {
95
112
  quoteBlock: 'quote',
96
113
  mapBlock: 'map',
97
114
  listBlock: 'list',
115
+ diagramBlock: 'diagram',
116
+ diagramNode: 'diagram',
98
117
  };
99
118
 
100
119
  /**
@@ -105,14 +124,67 @@ export function resolveTemplateName(name: string): string {
105
124
  return TEMPLATE_ALIASES[name] ?? name;
106
125
  }
107
126
 
127
+ /**
128
+ * Container templates render their parent block by consuming the block's
129
+ * child headings (via `context.children`) — `diagram` draws them as nodes,
130
+ * `drawing` as shapes, `layout` as absolutely-positioned layers. Those
131
+ * children are therefore NOT independently renderable slides/sections;
132
+ * render paths use {@link isContainerTemplate} to skip descending into
133
+ * them (see `flattenRenderableBlocks`).
134
+ */
135
+ export const CONTAINER_TEMPLATES: ReadonlySet<string> = new Set(['diagram', 'drawing', 'layout']);
136
+
137
+ /** True when `name` (or its alias) is a children-consuming container template. */
138
+ export function isContainerTemplate(name: string | undefined): boolean {
139
+ if (!name) return false;
140
+ return CONTAINER_TEMPLATES.has(resolveTemplateName(name));
141
+ }
142
+
143
+ /**
144
+ * Merge user-defined custom templates onto the built-in registry.
145
+ * Built-in names take precedence on collision — a user can't shadow
146
+ * `title` or `diagram` from a custom definition. Returns a frozen view
147
+ * so callers don't accidentally mutate the global registry.
148
+ */
149
+ export type RuntimeTemplateRegistry = Record<
150
+ string,
151
+ (input: TemplateBlock, ctx: TemplateContext) => Layer[]
152
+ >;
153
+
154
+ export function buildRegistry(
155
+ custom?: readonly CustomTemplateDefinition[],
156
+ ): RuntimeTemplateRegistry {
157
+ // The exported `templateRegistry` is a discriminated-union mapped type;
158
+ // we cast through `unknown` because each entry's `input` type is the
159
+ // template-specific variant, not the broad `TemplateBlock` union.
160
+ // The runtime contract — "given any TemplateBlock that resolves to a
161
+ // registered name, the function returns Layer[]" — holds because the
162
+ // expansion path looks up by `template` string before calling.
163
+ const merged: RuntimeTemplateRegistry = {
164
+ ...(templateRegistry as unknown as RuntimeTemplateRegistry),
165
+ };
166
+ if (custom) {
167
+ for (const def of custom) {
168
+ if (merged[def.name]) continue; // built-in wins
169
+ merged[def.name] = makeCustomTemplateFn(def) as unknown as RuntimeTemplateRegistry[string];
170
+ }
171
+ }
172
+ return merged;
173
+ }
174
+
108
175
  /**
109
176
  * Expand a template block into a full Block with layers.
177
+ *
178
+ * `registry` lets callers swap in a registry that includes user-defined
179
+ * custom templates. Defaults to the built-in registry when omitted.
110
180
  */
111
- export function expandTemplateBlock(templateBlock: TemplateBlock, context: TemplateContext): Block {
181
+ export function expandTemplateBlock(
182
+ templateBlock: TemplateBlock,
183
+ context: TemplateContext,
184
+ registry: RuntimeTemplateRegistry = templateRegistry as unknown as RuntimeTemplateRegistry,
185
+ ): Block {
112
186
  const resolved = resolveTemplateName(templateBlock.template);
113
- const templateFn = (templateRegistry as Record<string, unknown>)[resolved] as
114
- | ((input: TemplateBlock, ctx: TemplateContext) => Layer[])
115
- | undefined;
187
+ const templateFn = registry[resolved];
116
188
 
117
189
  if (!templateFn) {
118
190
  console.warn(`Unknown template: ${templateBlock.template}`);
@@ -127,10 +199,25 @@ export function expandTemplateBlock(templateBlock: TemplateBlock, context: Templ
127
199
  };
128
200
  }
129
201
 
202
+ // Effective template input: the block's own fields, then structured
203
+ // body data (```json data fences / derived auto-template inputs), then
204
+ // `{[…]}` string overrides — the same merge order getLayers uses. Inline
205
+ // overrides are coerced to their typed shape (e.g. `center="47.6,-122.3"` →
206
+ // `{lat,lng}`); `templateOverrides` itself stays raw for round-tripping.
207
+ const { templateData, templateOverrides } = templateBlock as Block;
208
+ const input =
209
+ templateData || templateOverrides
210
+ ? ({
211
+ ...templateBlock,
212
+ ...templateData,
213
+ ...coerceTemplateParams(templateBlock.template, templateOverrides ?? {}).input,
214
+ } as TemplateBlock)
215
+ : templateBlock;
216
+
130
217
  // Generate layers from template with error handling
131
218
  let layers: Layer[];
132
219
  try {
133
- layers = templateFn(templateBlock, context);
220
+ layers = templateFn(input, context);
134
221
  if (!Array.isArray(layers)) {
135
222
  console.error(
136
223
  `Template ${templateBlock.template} did not return an array, got:`,
@@ -180,6 +267,12 @@ export interface ExpandDocBlocksOptions {
180
267
  * ensuring proper synchronization with audio playback.
181
268
  */
182
269
  audioSegments?: AudioSegmentTiming[];
270
+ /**
271
+ * User-defined custom templates to merge onto the built-in registry
272
+ * before expanding blocks. Typically passed straight from
273
+ * `Doc.customTemplates`. Built-in names take precedence on collision.
274
+ */
275
+ customTemplates?: readonly CustomTemplateDefinition[];
183
276
  }
184
277
 
185
278
  /**
@@ -193,38 +286,88 @@ export interface ExpandDocBlocksOptions {
193
286
  * @param blocks - Array of template or raw blocks
194
287
  * @param options - Expansion options including theme, viewport, and persistent layers
195
288
  */
289
+ /**
290
+ * Finalize an expanded block in place: apply the theme's motion post-pass
291
+ * to template-generated layers, wrap with persistent bottom/top layers,
292
+ * and fill the theme's default transition (never on block 0, never over an
293
+ * authored transition). Shared by every expansion path in this module.
294
+ */
295
+ function finalizeExpandedBlock(
296
+ expandedBlock: Block,
297
+ sourceBlock: DocBlock,
298
+ blockIndex: number,
299
+ theme: Theme,
300
+ bottomLayers: Layer[],
301
+ topLayers: Layer[],
302
+ ): void {
303
+ // Theme motion defaults apply only to template-generated layers —
304
+ // raw authored block.layers are never restyled.
305
+ if (isTemplateBlock(sourceBlock) && expandedBlock.layers && expandedBlock.layers.length > 0) {
306
+ expandedBlock.layers = applyRenderStyleToLayers(expandedBlock.layers, expandedBlock, theme);
307
+ }
308
+
309
+ if (bottomLayers.length > 0 || topLayers.length > 0) {
310
+ expandedBlock.layers = wrapWithPersistentLayers(
311
+ expandedBlock.layers ?? [],
312
+ sourceBlock as TemplateBlock,
313
+ bottomLayers,
314
+ topLayers,
315
+ );
316
+ }
317
+
318
+ const transition = resolveBlockTransition(expandedBlock, theme, blockIndex);
319
+ if (transition !== expandedBlock.transition) {
320
+ expandedBlock.transition = transition;
321
+ }
322
+ }
323
+
196
324
  export function expandDocBlocks(blocks: DocBlock[], options: ExpandDocBlocksOptions = {}): Block[] {
197
325
  const opts: ExpandDocBlocksOptions = options;
198
326
 
199
327
  const theme = opts.theme ?? defaultTheme;
200
328
  const viewport = opts.viewport ?? VIEWPORT_PRESETS.landscape;
201
- const { persistentLayers, audioSegments } = opts;
329
+ const { persistentLayers, audioSegments, customTemplates } = opts;
202
330
  const totalBlocks = blocks.length;
203
-
204
- // Pre-expand persistent layers once
205
- const bottomLayers = expandPersistentLayers(persistentLayers?.bottomLayers);
206
- const topLayers = expandPersistentLayers(persistentLayers?.topLayers);
331
+ // Merge user-defined templates onto the built-in registry once per
332
+ // call; passed into every `expandTemplateBlock` invocation below.
333
+ const registry: RuntimeTemplateRegistry =
334
+ customTemplates && customTemplates.length > 0
335
+ ? buildRegistry(customTemplates)
336
+ : (templateRegistry as unknown as RuntimeTemplateRegistry);
337
+
338
+ // Pre-expand persistent layers once. Callers that pass no config inherit
339
+ // the theme's atmosphere (docs with their own layers pass them in and win
340
+ // wholesale — see resolvePersistentLayers).
341
+ const effectivePersistentLayers = persistentLayers ?? theme.persistentLayers;
342
+ const bottomLayers = expandPersistentLayers(effectivePersistentLayers?.bottomLayers, theme);
343
+ const topLayers = expandPersistentLayers(effectivePersistentLayers?.topLayers, theme);
207
344
 
208
345
  // If no audio segments provided, use simple cumulative timing
209
346
  if (!audioSegments || audioSegments.length === 0) {
210
347
  let currentTime = 0;
211
348
  return blocks.map((block, index) => {
212
349
  const context = createTemplateContext(theme, index, totalBlocks, viewport);
350
+ const maybeChildren = (block as Block).children;
351
+ if (maybeChildren && maybeChildren.length > 0) {
352
+ context.children = maybeChildren;
353
+ }
354
+ // Expose the source block so custom templates' token resolver can
355
+ // see the block's title / contents / children. Only set for
356
+ // custom-templated blocks — built-ins ignore context.block but
357
+ // exposing it changes object identity which we want to avoid for
358
+ // the perf-sensitive built-in path.
359
+ if (
360
+ isTemplateBlock(block) &&
361
+ customTemplates &&
362
+ customTemplates.some((c) => c.name === block.template)
363
+ ) {
364
+ context.block = block as Block;
365
+ }
213
366
  const expandedBlock = isTemplateBlock(block)
214
- ? expandTemplateBlock(block, context)
367
+ ? expandTemplateBlock(block, context, registry)
215
368
  : (block as Block);
216
369
 
217
- // Inject persistent layers
218
- const templateBlock = block as TemplateBlock;
219
- const useBottom = templateBlock.useBottomLayer !== false;
220
- const useTop = templateBlock.useTopLayer !== false;
221
- if (bottomLayers.length > 0 || topLayers.length > 0) {
222
- expandedBlock.layers = [
223
- ...(useBottom ? bottomLayers : []),
224
- ...(expandedBlock.layers ?? []),
225
- ...(useTop ? topLayers : []),
226
- ];
227
- }
370
+ finalizeExpandedBlock(expandedBlock, block, index, theme, bottomLayers, topLayers);
228
371
 
229
372
  expandedBlock.startTime = currentTime;
230
373
  currentTime += expandedBlock.duration;
@@ -252,20 +395,22 @@ export function expandDocBlocks(blocks: DocBlock[], options: ExpandDocBlocksOpti
252
395
  let offsetTime = 0;
253
396
  for (const { block, originalIndex } of segmentBlocks) {
254
397
  const context = createTemplateContext(theme, originalIndex, totalBlocks, viewport);
398
+ const maybeChildren = (block as Block).children;
399
+ if (maybeChildren && maybeChildren.length > 0) {
400
+ context.children = maybeChildren;
401
+ }
402
+ if (
403
+ isTemplateBlock(block) &&
404
+ customTemplates &&
405
+ customTemplates.some((c) => c.name === block.template)
406
+ ) {
407
+ context.block = block as Block;
408
+ }
255
409
  const expandedBlock = isTemplateBlock(block)
256
- ? expandTemplateBlock(block, context)
410
+ ? expandTemplateBlock(block, context, registry)
257
411
  : (block as Block);
258
412
 
259
- const templateBlock = block as TemplateBlock;
260
- const useBottom = templateBlock.useBottomLayer !== false;
261
- const useTop = templateBlock.useTopLayer !== false;
262
- if (bottomLayers.length > 0 || topLayers.length > 0) {
263
- expandedBlock.layers = [
264
- ...(useBottom ? bottomLayers : []),
265
- ...(expandedBlock.layers ?? []),
266
- ...(useTop ? topLayers : []),
267
- ];
268
- }
413
+ finalizeExpandedBlock(expandedBlock, block, originalIndex, theme, bottomLayers, topLayers);
269
414
 
270
415
  expandedBlock.startTime = offsetTime;
271
416
  offsetTime += expandedBlock.duration;
@@ -318,21 +463,24 @@ export function expandDocBlocks(blocks: DocBlock[], options: ExpandDocBlocksOpti
318
463
 
319
464
  for (const { block, originalIndex } of segmentBlocks) {
320
465
  const context = createTemplateContext(theme, originalIndex, totalBlocks, viewport);
466
+ const maybeChildren = (block as Block).children;
467
+ if (maybeChildren && maybeChildren.length > 0) {
468
+ context.children = maybeChildren;
469
+ }
470
+ if (
471
+ isTemplateBlock(block) &&
472
+ customTemplates &&
473
+ customTemplates.some((c) => c.name === block.template)
474
+ ) {
475
+ context.block = block as Block;
476
+ }
321
477
  const expandedBlock = isTemplateBlock(block)
322
- ? expandTemplateBlock(block, context)
478
+ ? expandTemplateBlock(block, context, registry)
323
479
  : (block as Block);
324
480
 
325
- const templateBlock = block as TemplateBlock;
326
- const useBottom = templateBlock.useBottomLayer !== false;
327
- const useTop = templateBlock.useTopLayer !== false;
328
- if (bottomLayers.length > 0 || topLayers.length > 0) {
329
- expandedBlock.layers = [
330
- ...(useBottom ? bottomLayers : []),
331
- ...(expandedBlock.layers ?? []),
332
- ...(useTop ? topLayers : []),
333
- ];
334
- }
481
+ finalizeExpandedBlock(expandedBlock, block, originalIndex, theme, bottomLayers, topLayers);
335
482
 
483
+ const templateBlock = block as TemplateBlock;
336
484
  expandedInfos.push({
337
485
  block: expandedBlock,
338
486
  originalIndex,
@@ -578,7 +726,13 @@ export type {
578
726
  } from '../../schemas/Viewport.js';
579
727
  export { getLayoutHints, getTwoColumnPositions } from '../../schemas/LayoutStrategy.js';
580
728
  export type { LayoutHints } from '../../schemas/LayoutStrategy.js';
581
- export { expandPersistentLayers, getDocStyleConfig } from './persistentLayers.js';
729
+ export {
730
+ expandPersistentLayers,
731
+ getDocStyleConfig,
732
+ getPersistentLayersFromTheme,
733
+ resolvePersistentLayers,
734
+ wrapWithPersistentLayers,
735
+ } from './persistentLayers.js';
582
736
 
583
737
  // Re-export individual templates for direct access
584
738
  export { titleBlock } from './titleBlock.js';
@@ -602,6 +756,31 @@ export { pullQuote } from './pullQuote.js';
602
756
  export { videoWithCaption } from './videoWithCaption.js';
603
757
  export { videoPullQuote } from './videoPullQuote.js';
604
758
  export { dataTable } from './dataTable.js';
759
+ export { diagramBlock } from './diagramBlock.js';
760
+ export { computeDiagramLayout } from './diagramLayout.js';
761
+ export type {
762
+ DiagramLayout,
763
+ DiagramNodePosition,
764
+ DiagramEdge,
765
+ DiagramLayoutOptions,
766
+ } from './diagramLayout.js';
767
+ export { drawingBlock } from './drawingBlock.js';
768
+ export {
769
+ computeDrawingLayout,
770
+ normalizeShapeKind,
771
+ isShapeName,
772
+ SHAPE_NAMES,
773
+ } from './drawingLayout.js';
774
+ export type {
775
+ DrawingLayout,
776
+ DrawingShape,
777
+ DrawingShapeKind,
778
+ DrawingConnector,
779
+ DrawingLayoutOptions,
780
+ } from './drawingLayout.js';
781
+ export { layoutBlock } from './layoutBlock.js';
782
+ export { computeLayoutLayers } from './layoutLayout.js';
783
+ export type { LayoutLayersResult, LayoutLayerDefaults } from './layoutLayout.js';
605
784
 
606
785
  // Re-export accent image utilities
607
786
  export { getAccentLayout, createAccentLayers, adjustY, DEFAULT_LAYOUT } from './accentImage.js';