@bendyline/squisq 1.4.1 → 1.5.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.
Files changed (216) hide show
  1. package/dist/{Doc-BeSeH2iX.d.ts → Doc-BsMkmzH8.d.ts} +838 -268
  2. package/dist/{ImageEditDoc-BTIvw0oq.d.ts → ImageEditDoc-DvoeHMP9.d.ts} +11 -4
  3. package/dist/{chunk-ZTX4PFFT.js → chunk-2OIBZYKP.js} +75 -2
  4. package/dist/chunk-2OIBZYKP.js.map +1 -0
  5. package/dist/chunk-2UQOHYE2.js +45 -0
  6. package/dist/chunk-2UQOHYE2.js.map +1 -0
  7. package/dist/{chunk-B4EEON3N.js → chunk-4AKUQPEE.js} +536 -103
  8. package/dist/chunk-4AKUQPEE.js.map +1 -0
  9. package/dist/chunk-4ZFRZBU2.js +6582 -0
  10. package/dist/chunk-4ZFRZBU2.js.map +1 -0
  11. package/dist/{chunk-57CVQCFX.js → chunk-7MQGNR76.js} +79 -3
  12. package/dist/chunk-7MQGNR76.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-MYIH7FWD.js → chunk-FBKP5CAO.js} +1 -1
  16. package/dist/chunk-FBKP5CAO.js.map +1 -0
  17. package/dist/chunk-KZNXZ5SM.js +333 -0
  18. package/dist/chunk-KZNXZ5SM.js.map +1 -0
  19. package/dist/{chunk-WMOI7FN3.js → chunk-LRCV7N7F.js} +12 -2
  20. package/dist/{chunk-WMOI7FN3.js.map → chunk-LRCV7N7F.js.map} +1 -1
  21. package/dist/{chunk-3K5OG6KI.js → chunk-OH43RPTP.js} +403 -187
  22. package/dist/chunk-OH43RPTP.js.map +1 -0
  23. package/dist/{chunk-H3AXU4MJ.js → chunk-OKJOQXK4.js} +149 -2
  24. package/dist/chunk-OKJOQXK4.js.map +1 -0
  25. package/dist/chunk-PGGGKYWG.js +1 -0
  26. package/dist/chunk-PGGGKYWG.js.map +1 -0
  27. package/dist/{chunk-7UDSRZKG.js → chunk-Q3ROPT5H.js} +35 -14
  28. package/dist/chunk-Q3ROPT5H.js.map +1 -0
  29. package/dist/chunk-QE7TWGVT.js +178 -0
  30. package/dist/chunk-QE7TWGVT.js.map +1 -0
  31. package/dist/chunk-RUDYOTA6.js +345 -0
  32. package/dist/chunk-RUDYOTA6.js.map +1 -0
  33. package/dist/chunk-TVSGLUID.js +596 -0
  34. package/dist/chunk-TVSGLUID.js.map +1 -0
  35. package/dist/chunk-ZLQKNOZQ.js +1 -0
  36. package/dist/chunk-ZLQKNOZQ.js.map +1 -0
  37. package/dist/doc/index.d.ts +4 -4
  38. package/dist/doc/index.js +124 -6
  39. package/dist/generate/index.d.ts +9 -2
  40. package/dist/generate/index.js +1 -1
  41. package/dist/icons/index.d.ts +3 -26
  42. package/dist/icons/index.js +13 -0
  43. package/dist/icons/inlineIconMarker.d.ts +1 -0
  44. package/dist/icons/inlineIconMarker.js +15 -0
  45. package/dist/icons/inlineIconMarker.js.map +1 -0
  46. package/dist/imageEdit/index.d.ts +11 -5
  47. package/dist/imageEdit/index.js +4 -1
  48. package/dist/index.d.ts +11 -8
  49. package/dist/index.js +243 -24
  50. package/dist/inlineIconMarker-CiE8Hlvz.d.ts +59 -0
  51. package/dist/markdown/index.d.ts +212 -3
  52. package/dist/markdown/index.js +40 -7
  53. package/dist/recommend/index.d.ts +1 -1
  54. package/dist/recommend/index.js +3 -2
  55. package/dist/schemas/index.d.ts +28 -8
  56. package/dist/schemas/index.js +35 -5
  57. package/dist/spatial/index.d.ts +9 -4
  58. package/dist/spatial/index.js +1 -1
  59. package/dist/story/index.d.ts +906 -40
  60. package/dist/story/index.js +124 -6
  61. package/dist/{themeLibrary-BHjJ0Qx6.d.ts → themeLibrary-DQQKuRMx.d.ts} +1 -1
  62. package/dist/transform/index.d.ts +48 -10
  63. package/dist/transform/index.js +17 -481
  64. package/dist/transform/index.js.map +1 -1
  65. package/dist/{types-BdZkdJ3z.d.ts → types-DlAZ7MW4.d.ts} +115 -1
  66. package/package.json +6 -1
  67. package/src/__tests__/animationUtils.test.ts +40 -0
  68. package/src/__tests__/annotationCoercion.test.ts +243 -0
  69. package/src/__tests__/applyRenderStyle.test.ts +146 -0
  70. package/src/__tests__/atmosphereLayers.test.ts +108 -0
  71. package/src/__tests__/attrTokens.test.ts +162 -0
  72. package/src/__tests__/customTemplateSample.test.ts +83 -0
  73. package/src/__tests__/customTemplatesFrontmatter.test.ts +205 -0
  74. package/src/__tests__/customThemesFrontmatter.test.ts +118 -0
  75. package/src/__tests__/diagramBlock.test.ts +122 -0
  76. package/src/__tests__/diagramLayout.test.ts +134 -0
  77. package/src/__tests__/drawing.test.ts +109 -0
  78. package/src/__tests__/drawingBlock.test.ts +124 -0
  79. package/src/__tests__/drawingLayout.test.ts +171 -0
  80. package/src/__tests__/geohash.test.ts +6 -5
  81. package/src/__tests__/getLayers.test.ts +6 -2
  82. package/src/__tests__/getLayersFallback.test.ts +103 -0
  83. package/src/__tests__/imageEditExportSvg.test.ts +70 -0
  84. package/src/__tests__/imageEditVersions.test.ts +72 -0
  85. package/src/__tests__/inlineIconMarker.test.ts +36 -0
  86. package/src/__tests__/layoutBlock.test.ts +83 -0
  87. package/src/__tests__/layoutLayout.test.ts +169 -0
  88. package/src/__tests__/markdown.test.ts +23 -0
  89. package/src/__tests__/markdownSanitize.test.ts +64 -0
  90. package/src/__tests__/markdownToDoc.test.ts +223 -2
  91. package/src/__tests__/mediaAnnotations.test.ts +64 -0
  92. package/src/__tests__/mediaSchedule.test.ts +118 -0
  93. package/src/__tests__/pandocAttrParse.test.ts +187 -0
  94. package/src/__tests__/resolveDocTheme.test.ts +55 -0
  95. package/src/__tests__/rootExports.test.ts +43 -0
  96. package/src/__tests__/shapeGeometry.test.ts +89 -0
  97. package/src/__tests__/shapeVocabulary.test.ts +133 -0
  98. package/src/__tests__/structuredData.test.ts +179 -0
  99. package/src/__tests__/templateAnnotationParse.test.ts +108 -0
  100. package/src/__tests__/templateMetadata.test.ts +21 -0
  101. package/src/__tests__/templates.test.ts +145 -4
  102. package/src/__tests__/themeCompile.test.ts +42 -0
  103. package/src/__tests__/themeValidator.test.ts +6 -0
  104. package/src/__tests__/transformV2.test.ts +137 -0
  105. package/src/__tests__/transitionNormalize.test.ts +61 -0
  106. package/src/__tests__/validateDoc.test.ts +100 -0
  107. package/src/doc/customTemplatesFrontmatter.ts +308 -0
  108. package/src/doc/customThemesFrontmatter.ts +90 -0
  109. package/src/doc/docToMarkdown.ts +73 -4
  110. package/src/doc/getLayers.ts +40 -17
  111. package/src/doc/index.ts +35 -1
  112. package/src/doc/markdownToDoc.ts +333 -12
  113. package/src/doc/mediaAnnotations.ts +118 -0
  114. package/src/doc/resolveDocTheme.ts +55 -0
  115. package/src/doc/structuredData.ts +230 -0
  116. package/src/doc/templateInputs.ts +240 -0
  117. package/src/doc/templates/__tests__/customTemplate.test.ts +151 -0
  118. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +110 -0
  119. package/src/doc/templates/accentImage.ts +30 -6
  120. package/src/doc/templates/captionUtils.ts +23 -0
  121. package/src/doc/templates/comparisonBar.ts +26 -18
  122. package/src/doc/templates/coverBlock.ts +7 -4
  123. package/src/doc/templates/customTemplate.ts +45 -0
  124. package/src/doc/templates/dataTable.ts +37 -19
  125. package/src/doc/templates/dateEvent.ts +41 -20
  126. package/src/doc/templates/definitionCard.ts +26 -18
  127. package/src/doc/templates/diagramBlock.ts +204 -0
  128. package/src/doc/templates/diagramLayout.ts +159 -0
  129. package/src/doc/templates/drawingBlock.ts +325 -0
  130. package/src/doc/templates/drawingLayout.ts +539 -0
  131. package/src/doc/templates/factCard.ts +51 -25
  132. package/src/doc/templates/fallbackBlock.ts +123 -0
  133. package/src/doc/templates/featureBlock.ts +20 -13
  134. package/src/doc/templates/fullBleedQuote.ts +18 -8
  135. package/src/doc/templates/imageWithCaption.ts +22 -9
  136. package/src/doc/templates/index.ts +215 -46
  137. package/src/doc/templates/layoutBlock.ts +43 -0
  138. package/src/doc/templates/layoutLayout.ts +243 -0
  139. package/src/doc/templates/listBlock.ts +56 -30
  140. package/src/doc/templates/mapBlock.ts +15 -12
  141. package/src/doc/templates/metadata.ts +120 -0
  142. package/src/doc/templates/persistentLayers.ts +129 -11
  143. package/src/doc/templates/photoGrid.ts +23 -12
  144. package/src/doc/templates/pullQuote.ts +30 -12
  145. package/src/doc/templates/quoteBlock.ts +54 -29
  146. package/src/doc/templates/sectionHeader.ts +25 -11
  147. package/src/doc/templates/statHighlight.ts +39 -22
  148. package/src/doc/templates/titleBlock.ts +59 -21
  149. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +221 -0
  150. package/src/doc/templates/tokens/resolveTokens.ts +0 -0
  151. package/src/doc/templates/twoColumn.ts +48 -30
  152. package/src/doc/templates/videoPullQuote.ts +22 -12
  153. package/src/doc/templates/videoWithCaption.ts +13 -8
  154. package/src/doc/utils/animationUtils.ts +267 -3
  155. package/src/doc/utils/applyRenderStyle.ts +113 -0
  156. package/src/doc/utils/imageTreatment.ts +70 -0
  157. package/src/doc/utils/shapeGeometry.ts +464 -0
  158. package/src/doc/utils/themeUtils.ts +75 -1
  159. package/src/doc/validate.ts +476 -0
  160. package/src/generate/slideshowGenerator.ts +7 -0
  161. package/src/icons/index.ts +8 -0
  162. package/src/icons/inlineIconMarker.ts +73 -0
  163. package/src/imageEdit/export.ts +85 -2
  164. package/src/imageEdit/index.ts +1 -0
  165. package/src/imageEdit/persistence.ts +51 -0
  166. package/src/index.ts +2 -0
  167. package/src/markdown/annotationCoercion.ts +295 -0
  168. package/src/markdown/attrTokens.ts +222 -0
  169. package/src/markdown/convert.ts +229 -26
  170. package/src/markdown/index.ts +36 -0
  171. package/src/markdown/sanitize.ts +298 -0
  172. package/src/markdown/stringify.ts +84 -3
  173. package/src/markdown/types.ts +66 -0
  174. package/src/markdown/utils.ts +49 -1
  175. package/src/recommend/templates.ts +24 -0
  176. package/src/schemas/BlockTemplates.ts +133 -4
  177. package/src/schemas/CustomTemplates.ts +74 -0
  178. package/src/schemas/Doc.ts +353 -29
  179. package/src/schemas/ImageEditDoc.ts +11 -4
  180. package/src/schemas/Media.ts +169 -0
  181. package/src/schemas/Theme.ts +23 -1
  182. package/src/schemas/Transitions.ts +224 -0
  183. package/src/schemas/colorUtils.ts +33 -0
  184. package/src/schemas/fontStacks.ts +5 -2
  185. package/src/schemas/index.ts +3 -0
  186. package/src/schemas/themeCompile.ts +23 -4
  187. package/src/schemas/themeValidator.ts +49 -11
  188. package/src/schemas/themes/cinematic.json +69 -12
  189. package/src/schemas/themes/documentary.json +58 -11
  190. package/src/schemas/themes/gezellig.json +60 -11
  191. package/src/schemas/themes/magazine.json +54 -9
  192. package/src/schemas/themes/morning-light.json +52 -9
  193. package/src/schemas/themes/standard-dark.json +48 -13
  194. package/src/schemas/themes/standard.json +44 -12
  195. package/src/schemas/themes/tech-dark.json +65 -12
  196. package/src/spatial/Geohash.ts +52 -21
  197. package/src/transform/applyTransform.ts +43 -4
  198. package/src/transform/index.ts +1 -0
  199. package/src/transform/registry.ts +44 -13
  200. package/src/transform/styles/dataDriven.ts +2 -1
  201. package/src/transform/styles/documentary.ts +2 -1
  202. package/src/transform/styles/magazine.ts +3 -1
  203. package/src/transform/styles/minimal.ts +2 -1
  204. package/src/transform/styles/narrative.ts +3 -1
  205. package/src/transform/templateSelector.ts +87 -11
  206. package/src/transform/types.ts +35 -2
  207. package/dist/chunk-3K5OG6KI.js.map +0 -1
  208. package/dist/chunk-57CVQCFX.js.map +0 -1
  209. package/dist/chunk-5WFRKURX.js.map +0 -1
  210. package/dist/chunk-7UDSRZKG.js.map +0 -1
  211. package/dist/chunk-B4EEON3N.js.map +0 -1
  212. package/dist/chunk-H3AXU4MJ.js.map +0 -1
  213. package/dist/chunk-KSWJR66U.js +0 -3706
  214. package/dist/chunk-KSWJR66U.js.map +0 -1
  215. package/dist/chunk-MYIH7FWD.js.map +0 -1
  216. package/dist/chunk-ZTX4PFFT.js.map +0 -1
@@ -1,3 +1,53 @@
1
+ /**
2
+ * Shared transition vocabulary for block-to-block slide changes.
3
+ *
4
+ * The canonical names are based on PowerPoint / PresentationML transition
5
+ * names where possible, with a few legacy renderer aliases kept so older
6
+ * Squisq documents continue to work.
7
+ */
8
+ declare const DEFAULT_TRANSITION_DURATION_SECONDS = 0.7;
9
+ declare const TRANSITION_TYPES: readonly ["cut", "fade", "dissolve", "push", "wipe", "split", "reveal", "randomBars", "shape", "uncover", "cover", "zoom", "pan", "flash", "fallOver", "drape", "curtains", "wind", "prestige", "fracture", "crush", "peelOff", "pageCurl", "airplane", "origami", "checkerboard", "blinds", "clock", "ripple", "honeycomb", "glitter", "vortex", "shred", "switch", "flip", "gallery", "cube", "doors", "box", "ferrisWheel", "conveyor", "rotate", "window", "orbit", "flyThrough", "morph", "checker", "circle", "comb", "diamond", "newsflash", "plus", "pull", "random", "randomBar", "strips", "wedge", "wheel", "prism", "warp", "ferris", "flythrough", "wheelReverse", "pageCurlDouble", "pageCurlSingle", "slideLeft", "slideRight", "slideUp", "slideDown"];
10
+ type TransitionType = (typeof TRANSITION_TYPES)[number];
11
+ declare const TRANSITION_DIRECTIONS: readonly ["left", "right", "up", "down", "in", "out", "horizontal", "vertical"];
12
+ type TransitionDirection = (typeof TRANSITION_DIRECTIONS)[number];
13
+ /**
14
+ * Transition between blocks.
15
+ *
16
+ * A block's transition is the lead-in from the previous block. The same
17
+ * transition drives the entering block and the previous block's exit.
18
+ */
19
+ interface Transition {
20
+ /** Transition type */
21
+ type: TransitionType;
22
+ /** Duration in seconds. Defaults to DEFAULT_TRANSITION_DURATION_SECONDS. */
23
+ duration?: number;
24
+ /** Optional direction/variant for directional transitions. */
25
+ direction?: TransitionDirection;
26
+ }
27
+ declare function isTransitionType(value: string): value is TransitionType;
28
+ declare function normalizeTransitionType(value: string): TransitionType | null;
29
+ declare function normalizeTransitionDirection(value: string): TransitionDirection | null;
30
+ declare function resolveTransitionDuration(transition: Transition | undefined, fallback?: number): number;
31
+ /**
32
+ * Resolve the effective transition for a block: the authored value wins
33
+ * (markdown frontmatter and transform styles both write `block.transition`
34
+ * upstream), then the theme's `renderStyle.defaultTransition`, then
35
+ * `undefined` (the player's built-in default).
36
+ *
37
+ * Block 0 never gets a theme default — an entrance transition on the first
38
+ * block would dim the opening frames of every exported video.
39
+ *
40
+ * The theme parameter is structural (not `Theme`) to avoid a schema import
41
+ * cycle; pass any resolved theme.
42
+ */
43
+ declare function resolveBlockTransition(block: {
44
+ transition?: Transition;
45
+ }, theme: {
46
+ renderStyle: {
47
+ defaultTransition?: Transition;
48
+ };
49
+ }, blockIndex: number): Transition | undefined;
50
+
1
51
  /**
2
52
  * Markdown DOM Types
3
53
  *
@@ -100,6 +150,63 @@ interface HeadingTemplateAnnotation {
100
150
  /** Optional key-value parameters (remaining `key=value` tokens) */
101
151
  params?: Record<string, string>;
102
152
  }
153
+ /**
154
+ * A directed connection from one block to another, parsed from a heading
155
+ * attribute `connectsTo=target1,target2:type,target3`. Mirrors the schema
156
+ * `BlockConnection` type — duplicated here so this module stays
157
+ * dependency-free from the doc schema.
158
+ */
159
+ interface BlockConnection {
160
+ /** Target block id. */
161
+ target: string;
162
+ /** Optional connection type/label (e.g., "flow", "requires"). */
163
+ type?: string;
164
+ }
165
+ /**
166
+ * Pandoc-style heading attribute block, parsed from `{#id .class key=value}`
167
+ * appearing at the end of a heading line (alongside, or instead of, the
168
+ * squisq-native `{[templateName …]}` block).
169
+ *
170
+ * @example
171
+ * ```markdown
172
+ * ## Architecture {#arch x=600 y=300 connectsTo=foo:flow,bar:requires}
173
+ * ```
174
+ * →
175
+ * ```ts
176
+ * {
177
+ * id: 'arch',
178
+ * params: { x: '600', y: '300', connectsTo: 'foo:flow,bar:requires' },
179
+ * blockMeta: {
180
+ * x: 600,
181
+ * y: 300,
182
+ * connectsTo: [{ target: 'foo', type: 'flow' }, { target: 'bar', type: 'requires' }],
183
+ * },
184
+ * }
185
+ * ```
186
+ *
187
+ * `params` carries the raw pre-coercion strings (lossless for round-tripping),
188
+ * while `blockMeta` carries the typed values for known keys. Unknown keys flow
189
+ * to `metadata`.
190
+ */
191
+ interface HeadingAttributes {
192
+ /** Block id from `#identifier` (overrides slug-from-heading). */
193
+ id?: string;
194
+ /** Class tokens from `.classname` (reserved for downstream use). */
195
+ classes?: string[];
196
+ /** All key=value attributes as raw strings (lossless source for serialization). */
197
+ params?: Record<string, string>;
198
+ /** Typed values for known block-meta keys (coerced from `params`). */
199
+ blockMeta?: {
200
+ x?: number;
201
+ y?: number;
202
+ startTime?: number;
203
+ duration?: number;
204
+ connectsTo?: BlockConnection[];
205
+ transition?: Transition;
206
+ };
207
+ /** Subset of `params` whose keys are NOT in the known-key registry. */
208
+ metadata?: Record<string, string>;
209
+ }
103
210
  /**
104
211
  * ATX or setext heading (# Heading).
105
212
  */
@@ -116,6 +223,13 @@ interface MarkdownHeading extends MarkdownNodeBase {
116
223
  * stringification, so it round-trips transparently.
117
224
  */
118
225
  templateAnnotation?: HeadingTemplateAnnotation;
226
+ /**
227
+ * Pandoc-style heading attributes parsed from trailing `{#id .class key=value}`
228
+ * syntax. Independent from `templateAnnotation` — both may appear on the
229
+ * same heading in either order. Stripped from `children` during parsing
230
+ * and re-injected during stringification.
231
+ */
232
+ attributes?: HeadingAttributes;
119
233
  }
120
234
  /**
121
235
  * A paragraph of inline content.
@@ -528,4 +642,4 @@ interface StringifyOptions {
528
642
  setext?: boolean;
529
643
  }
530
644
 
531
- export type { MarkdownLinkReference as A, MarkdownList as B, MarkdownListItem as C, MarkdownMathBlock as D, MarkdownMention as E, MarkdownNode as F, MarkdownParagraph as G, HeadingTemplateAnnotation as H, MarkdownPoint as I, MarkdownSourcePosition as J, MarkdownStrikethrough as K, MarkdownStrong as L, MarkdownBlockNode as M, MarkdownTable as N, MarkdownTableCell as O, MarkdownTableRow as P, MarkdownText as Q, MarkdownTextDirective as R, MarkdownThematicBreak as S, ParseOptions as T, StringifyOptions as U, HtmlComment as a, HtmlElement as b, HtmlNode as c, HtmlText as d, MarkdownBlockquote as e, MarkdownBreak as f, MarkdownCodeBlock as g, MarkdownContainerDirective as h, MarkdownDefinitionDescription as i, MarkdownDefinitionList as j, MarkdownDefinitionTerm as k, MarkdownDocument as l, MarkdownEmphasis as m, MarkdownFootnoteDefinition as n, MarkdownFootnoteReference as o, MarkdownHeading as p, MarkdownHtmlBlock as q, MarkdownImage as r, MarkdownImageReference as s, MarkdownInlineCode as t, MarkdownInlineHtml as u, MarkdownInlineMath as v, MarkdownInlineNode as w, MarkdownLeafDirective as x, MarkdownLink as y, MarkdownLinkDefinition as z };
645
+ export { type TransitionType as $, type MarkdownLinkDefinition as A, type MarkdownLinkReference as B, type MarkdownList as C, DEFAULT_TRANSITION_DURATION_SECONDS as D, type MarkdownListItem as E, type MarkdownMathBlock as F, type MarkdownMention as G, type HeadingAttributes as H, type MarkdownNode as I, type MarkdownParagraph as J, type MarkdownPoint as K, type MarkdownSourcePosition as L, type MarkdownBlockNode as M, type MarkdownStrikethrough as N, type MarkdownStrong as O, type MarkdownTable as P, type MarkdownTableCell as Q, type MarkdownTableRow as R, type MarkdownText as S, type MarkdownTextDirective as T, type MarkdownThematicBreak as U, type ParseOptions as V, type StringifyOptions as W, TRANSITION_DIRECTIONS as X, TRANSITION_TYPES as Y, type Transition as Z, type TransitionDirection as _, type HeadingTemplateAnnotation as a, isTransitionType as a0, normalizeTransitionDirection as a1, normalizeTransitionType as a2, resolveBlockTransition as a3, resolveTransitionDuration as a4, type HtmlComment as b, type HtmlElement as c, type HtmlNode as d, type HtmlText as e, type MarkdownBlockquote as f, type MarkdownBreak as g, type MarkdownCodeBlock as h, type MarkdownContainerDirective as i, type MarkdownDefinitionDescription as j, type MarkdownDefinitionList as k, type MarkdownDefinitionTerm as l, type MarkdownDocument as m, type MarkdownEmphasis as n, type MarkdownFootnoteDefinition as o, type MarkdownFootnoteReference as p, type MarkdownHeading as q, type MarkdownHtmlBlock as r, type MarkdownImage as s, type MarkdownImageReference as t, type MarkdownInlineCode as u, type MarkdownInlineHtml as v, type MarkdownInlineMath as w, type MarkdownInlineNode as x, type MarkdownLeafDirective as y, type MarkdownLink as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Headless utilities for doc/block rendering, spatial math, Markdown, and storage",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -106,6 +106,11 @@
106
106
  "import": "./dist/icons/index.js",
107
107
  "default": "./dist/icons/index.js"
108
108
  },
109
+ "./icon-marker": {
110
+ "types": "./dist/icons/inlineIconMarker.d.ts",
111
+ "import": "./dist/icons/inlineIconMarker.js",
112
+ "default": "./dist/icons/inlineIconMarker.js"
113
+ },
109
114
  "./recommend": {
110
115
  "types": "./dist/recommend/index.d.ts",
111
116
  "import": "./dist/recommend/index.js",
@@ -5,6 +5,7 @@ import {
5
5
  getTransitionClass,
6
6
  getAnimationProgress,
7
7
  } from '../doc/utils/animationUtils';
8
+ import { TRANSITION_TYPES } from '../schemas/Transitions';
8
9
 
9
10
  describe('getAnimationStyle', () => {
10
11
  it('returns empty for undefined animation', () => {
@@ -57,6 +58,45 @@ describe('getTransitionClass', () => {
57
58
  it('returns exit class', () => {
58
59
  expect(getTransitionClass('dissolve', false)).toBe('transition-dissolve-exit');
59
60
  });
61
+
62
+ it('returns no class for cut', () => {
63
+ expect(getTransitionClass('cut', true)).toBe('');
64
+ });
65
+
66
+ it('maps PowerPoint-style transitions to supported visual classes', () => {
67
+ for (const type of TRANSITION_TYPES) {
68
+ if (type === 'cut') continue;
69
+ expect(getTransitionClass(type, true), type).toMatch(/^transition-.+-enter$/);
70
+ expect(getTransitionClass(type, false), type).toMatch(/^transition-.+-exit$/);
71
+ }
72
+ });
73
+
74
+ it('keeps the PowerPoint transition vocabulary visually expressive', () => {
75
+ const families = new Set(
76
+ TRANSITION_TYPES.flatMap((type) => {
77
+ const className = getTransitionClass(type, true);
78
+ const match = /^transition-(.+)-enter$/.exec(className);
79
+ return match ? [match[1]] : [];
80
+ }),
81
+ );
82
+
83
+ expect(families.size).toBeGreaterThanOrEqual(50);
84
+ });
85
+
86
+ it('maps aliases to stable visual families', () => {
87
+ expect(getTransitionClass('randomBars', true)).toBe('transition-randomBarsVertical-enter');
88
+ expect(getTransitionClass('flyThrough', true)).toBe('transition-flyThrough-enter');
89
+ expect(getTransitionClass('flythrough', true)).toBe('transition-flyThrough-enter');
90
+ expect(getTransitionClass('cube', true)).toBe('transition-cube-enter');
91
+ expect(getTransitionClass('ferris', true)).toBe('transition-ferrisWheel-enter');
92
+ });
93
+
94
+ it('honors transition directions for directional families', () => {
95
+ expect(getTransitionClass('wipe', true, 'right')).toBe('transition-wipeRight-enter');
96
+ expect(getTransitionClass('push', false, 'up')).toBe('transition-pushUp-exit');
97
+ expect(getTransitionClass('split', true, 'vertical')).toBe('transition-splitVertical-enter');
98
+ expect(getTransitionClass('blinds', false, 'vertical')).toBe('transition-blindsVertical-exit');
99
+ });
60
100
  });
61
101
 
62
102
  describe('getAnimationProgress', () => {
@@ -0,0 +1,243 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ coerceAnnotationValues,
4
+ parseTimeSeconds,
5
+ parseConnectionList,
6
+ KNOWN_BLOCK_META_KEYS,
7
+ BLOCK_META_KEY_DESCRIPTORS,
8
+ } from '../markdown/annotationCoercion.js';
9
+ import { TRANSITION_TYPES, TRANSITION_DIRECTIONS } from '../schemas/Transitions.js';
10
+
11
+ describe('parseTimeSeconds', () => {
12
+ it('accepts bare seconds (integer)', () => {
13
+ expect(parseTimeSeconds('5')).toBe(5);
14
+ });
15
+
16
+ it('accepts bare seconds (decimal)', () => {
17
+ expect(parseTimeSeconds('5.5')).toBe(5.5);
18
+ });
19
+
20
+ it('accepts mm:ss', () => {
21
+ expect(parseTimeSeconds('01:30')).toBe(90);
22
+ expect(parseTimeSeconds('0:05')).toBe(5);
23
+ expect(parseTimeSeconds('10:00')).toBe(600);
24
+ });
25
+
26
+ it('accepts mm:ss.ms (fractional seconds)', () => {
27
+ expect(parseTimeSeconds('01:30.500')).toBeCloseTo(90.5, 6);
28
+ expect(parseTimeSeconds('02:30.5')).toBeCloseTo(150.5, 6);
29
+ });
30
+
31
+ it('accepts Nms (milliseconds)', () => {
32
+ expect(parseTimeSeconds('1500ms')).toBe(1.5);
33
+ expect(parseTimeSeconds('0ms')).toBe(0);
34
+ });
35
+
36
+ it('trims leading/trailing whitespace', () => {
37
+ expect(parseTimeSeconds(' 90 ')).toBe(90);
38
+ });
39
+
40
+ it('rejects malformed input', () => {
41
+ expect(parseTimeSeconds('not-a-time')).toBeNull();
42
+ expect(parseTimeSeconds('99:bad')).toBeNull();
43
+ expect(parseTimeSeconds('')).toBeNull();
44
+ expect(parseTimeSeconds(' ')).toBeNull();
45
+ });
46
+
47
+ it('rejects seconds field >= 60', () => {
48
+ expect(parseTimeSeconds('1:60')).toBeNull();
49
+ expect(parseTimeSeconds('1:75')).toBeNull();
50
+ });
51
+ });
52
+
53
+ describe('parseConnectionList', () => {
54
+ it('parses a single bare target', () => {
55
+ expect(parseConnectionList('foo')).toEqual({
56
+ list: [{ target: 'foo' }],
57
+ warning: null,
58
+ });
59
+ });
60
+
61
+ it('parses target:type pairs', () => {
62
+ expect(parseConnectionList('foo:flow')).toEqual({
63
+ list: [{ target: 'foo', type: 'flow' }],
64
+ warning: null,
65
+ });
66
+ });
67
+
68
+ it('parses mixed bare + typed entries', () => {
69
+ expect(parseConnectionList('foo:veryImportant,bar:requires,baz:requires')).toEqual({
70
+ list: [
71
+ { target: 'foo', type: 'veryImportant' },
72
+ { target: 'bar', type: 'requires' },
73
+ { target: 'baz', type: 'requires' },
74
+ ],
75
+ warning: null,
76
+ });
77
+ });
78
+
79
+ it('trims whitespace around items', () => {
80
+ expect(parseConnectionList('foo, bar:flow , baz').list).toEqual([
81
+ { target: 'foo' },
82
+ { target: 'bar', type: 'flow' },
83
+ { target: 'baz' },
84
+ ]);
85
+ });
86
+
87
+ it('returns empty list for empty input', () => {
88
+ expect(parseConnectionList('')).toEqual({ list: [], warning: null });
89
+ expect(parseConnectionList(' ')).toEqual({ list: [], warning: null });
90
+ });
91
+
92
+ it('filters empty segments and warns', () => {
93
+ const result = parseConnectionList('a,,b');
94
+ expect(result.list).toEqual([{ target: 'a' }, { target: 'b' }]);
95
+ expect(result.warning).toContain('empty');
96
+ });
97
+
98
+ it('drops entries with empty target before colon', () => {
99
+ const result = parseConnectionList(':flow,foo');
100
+ expect(result.list).toEqual([{ target: 'foo' }]);
101
+ expect(result.warning).toContain('empty');
102
+ });
103
+
104
+ it('treats target:<empty> as a bare target (no type)', () => {
105
+ expect(parseConnectionList('foo:').list).toEqual([{ target: 'foo' }]);
106
+ });
107
+ });
108
+
109
+ describe('coerceAnnotationValues', () => {
110
+ it('routes known keys to blockMeta, unknown to metadata', () => {
111
+ const result = coerceAnnotationValues({
112
+ x: '400',
113
+ y: '200',
114
+ priority: 'high',
115
+ caption: 'hello',
116
+ });
117
+ expect(result.blockMeta).toEqual({ x: 400, y: 200 });
118
+ expect(result.metadata).toEqual({ priority: 'high', caption: 'hello' });
119
+ expect(result.warnings).toEqual([]);
120
+ });
121
+
122
+ it('coerces startTime and duration to seconds', () => {
123
+ const result = coerceAnnotationValues({
124
+ startTime: '01:30',
125
+ duration: '00:15',
126
+ });
127
+ expect(result.blockMeta).toEqual({ startTime: 90, duration: 15 });
128
+ });
129
+
130
+ it('coerces connectsTo into a BlockConnection list', () => {
131
+ const result = coerceAnnotationValues({
132
+ connectsTo: 'foo:veryImportant,bar:requires,baz:requires',
133
+ });
134
+ expect(result.blockMeta.connectsTo).toEqual([
135
+ { target: 'foo', type: 'veryImportant' },
136
+ { target: 'bar', type: 'requires' },
137
+ { target: 'baz', type: 'requires' },
138
+ ]);
139
+ });
140
+
141
+ it('drops malformed numbers with a warning', () => {
142
+ const result = coerceAnnotationValues({ x: 'not-a-number' });
143
+ expect(result.blockMeta.x).toBeUndefined();
144
+ expect(result.metadata).toEqual({});
145
+ expect(result.warnings.length).toBe(1);
146
+ expect(result.warnings[0]).toContain('x');
147
+ });
148
+
149
+ it('drops malformed time values with a warning', () => {
150
+ const result = coerceAnnotationValues({ startTime: '99:bad' });
151
+ expect(result.blockMeta.startTime).toBeUndefined();
152
+ expect(result.warnings.length).toBe(1);
153
+ });
154
+
155
+ it('preserves empty connection list without warning', () => {
156
+ const result = coerceAnnotationValues({ connectsTo: '' });
157
+ expect(result.blockMeta.connectsTo).toEqual([]);
158
+ expect(result.warnings).toEqual([]);
159
+ });
160
+
161
+ it('coerces transition metadata', () => {
162
+ const result = coerceAnnotationValues({
163
+ transition: 'random-bars',
164
+ transitionDuration: '850ms',
165
+ transitionDirection: 'left',
166
+ });
167
+
168
+ expect(result.blockMeta.transition).toEqual({
169
+ type: 'randomBars',
170
+ duration: 0.85,
171
+ direction: 'left',
172
+ });
173
+ expect(result.metadata).toEqual({});
174
+ expect(result.warnings).toEqual([]);
175
+ });
176
+
177
+ it('drops malformed transition values with warnings', () => {
178
+ const result = coerceAnnotationValues({
179
+ transition: 'sideways-fold',
180
+ transitionDuration: 'fast',
181
+ transitionDirection: 'diagonal',
182
+ });
183
+
184
+ expect(result.blockMeta.transition).toBeUndefined();
185
+ expect(result.metadata).toEqual({});
186
+ expect(result.warnings).toHaveLength(3);
187
+ });
188
+
189
+ it('emits a warning for connectsTo with empty segments', () => {
190
+ const result = coerceAnnotationValues({ connectsTo: 'a,,b' });
191
+ expect(result.blockMeta.connectsTo).toEqual([{ target: 'a' }, { target: 'b' }]);
192
+ expect(result.warnings.length).toBe(1);
193
+ });
194
+
195
+ it('handles an empty input map', () => {
196
+ expect(coerceAnnotationValues({})).toEqual({
197
+ blockMeta: {},
198
+ metadata: {},
199
+ warnings: [],
200
+ });
201
+ });
202
+ });
203
+
204
+ describe('KNOWN_BLOCK_META_KEYS', () => {
205
+ it('lists the expected typed keys', () => {
206
+ expect(Object.keys(KNOWN_BLOCK_META_KEYS).sort()).toEqual([
207
+ 'connectsTo',
208
+ 'duration',
209
+ 'startTime',
210
+ 'transition',
211
+ 'transitionDirection',
212
+ 'transitionDuration',
213
+ 'x',
214
+ 'y',
215
+ ]);
216
+ });
217
+ });
218
+
219
+ describe('BLOCK_META_KEY_DESCRIPTORS', () => {
220
+ it('describes exactly the keys in KNOWN_BLOCK_META_KEYS', () => {
221
+ const described = BLOCK_META_KEY_DESCRIPTORS.map((d) => d.key).sort();
222
+ expect(described).toEqual(Object.keys(KNOWN_BLOCK_META_KEYS).sort());
223
+ });
224
+
225
+ it('has no duplicate keys', () => {
226
+ const keys = BLOCK_META_KEY_DESCRIPTORS.map((d) => d.key);
227
+ expect(new Set(keys).size).toBe(keys.length);
228
+ });
229
+
230
+ it('gives each key either a closed value set or a format hint, never both', () => {
231
+ for (const d of BLOCK_META_KEY_DESCRIPTORS) {
232
+ expect(Boolean(d.values) !== Boolean(d.valueHint)).toBe(true);
233
+ expect(d.description.length).toBeGreaterThan(0);
234
+ }
235
+ });
236
+
237
+ it('sources transition value sets from the canonical vocabulary', () => {
238
+ const transition = BLOCK_META_KEY_DESCRIPTORS.find((d) => d.key === 'transition');
239
+ const direction = BLOCK_META_KEY_DESCRIPTORS.find((d) => d.key === 'transitionDirection');
240
+ expect(transition?.values).toBe(TRANSITION_TYPES);
241
+ expect(direction?.values).toBe(TRANSITION_DIRECTIONS);
242
+ });
243
+ });
@@ -0,0 +1,146 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { applyRenderStyleToLayers } from '../doc/utils/applyRenderStyle.js';
3
+ import { resolveBlockTransition } from '../schemas/Transitions.js';
4
+ import { DEFAULT_THEME, resolveTheme } from '../schemas/themeLibrary.js';
5
+ import type { Theme } from '../schemas/Theme.js';
6
+ import type { Layer } from '../schemas/Doc.js';
7
+
8
+ function themeWith(patch: {
9
+ animationSpeed?: number;
10
+ ambientMotion?: boolean;
11
+ defaultImageAnimation?: Theme['renderStyle']['defaultImageAnimation'];
12
+ defaultTransition?: Theme['renderStyle']['defaultTransition'];
13
+ }): Theme {
14
+ return {
15
+ ...DEFAULT_THEME,
16
+ style: {
17
+ ...DEFAULT_THEME.style,
18
+ ...(patch.animationSpeed != null ? { animationSpeed: patch.animationSpeed } : {}),
19
+ },
20
+ renderStyle: {
21
+ ...DEFAULT_THEME.renderStyle,
22
+ ...(patch.ambientMotion != null ? { ambientMotion: patch.ambientMotion } : {}),
23
+ ...(patch.defaultImageAnimation
24
+ ? { defaultImageAnimation: patch.defaultImageAnimation }
25
+ : {}),
26
+ ...(patch.defaultTransition ? { defaultTransition: patch.defaultTransition } : {}),
27
+ },
28
+ };
29
+ }
30
+
31
+ const fullBleedImage = (id = 'img'): Layer => ({
32
+ type: 'image',
33
+ id,
34
+ content: { src: 'x.jpg', alt: '' },
35
+ position: { x: 0, y: 0, width: '100%', height: '100%' },
36
+ });
37
+
38
+ const BLOCK = { id: 'b1', duration: 6 };
39
+
40
+ describe('applyRenderStyleToLayers', () => {
41
+ it('is a no-op at animationSpeed 1.0 with no ambient motion (returns same objects)', () => {
42
+ const layers: Layer[] = [{ ...fullBleedImage(), animation: { type: 'fadeIn', duration: 2 } }];
43
+ const out = applyRenderStyleToLayers(layers, BLOCK, DEFAULT_THEME);
44
+ expect(out).toBe(layers);
45
+ });
46
+
47
+ it('scales duration and delay by animationSpeed', () => {
48
+ const layers: Layer[] = [
49
+ {
50
+ type: 'text',
51
+ id: 't',
52
+ content: { text: 'hi', style: { fontSize: 20, color: '#ffffff' } },
53
+ position: { x: '50%', y: '50%' },
54
+ animation: { type: 'fadeIn', duration: 2, delay: 1 },
55
+ },
56
+ ];
57
+ const out = applyRenderStyleToLayers(layers, BLOCK, themeWith({ animationSpeed: 0.5 }));
58
+ expect(out[0].animation).toEqual({ type: 'fadeIn', duration: 1, delay: 0.5 });
59
+ // Input untouched (pure)
60
+ expect(layers[0].animation).toEqual({ type: 'fadeIn', duration: 2, delay: 1 });
61
+ });
62
+
63
+ it('gives full-bleed cover imagery a deterministic ambient Ken Burns', () => {
64
+ const theme = themeWith({ ambientMotion: true });
65
+ const a = applyRenderStyleToLayers([fullBleedImage()], BLOCK, theme);
66
+ const b = applyRenderStyleToLayers([fullBleedImage()], BLOCK, theme);
67
+ expect(a[0].animation?.type).toBe('slowZoom');
68
+ expect(a[0].animation?.duration).toBeGreaterThanOrEqual(8);
69
+ // Deterministic: same block+layer id → same variant
70
+ expect(a[0].animation).toEqual(b[0].animation);
71
+ // Different layer id → independent (usually different) but still defined
72
+ const c = applyRenderStyleToLayers([fullBleedImage('other')], BLOCK, theme);
73
+ expect(c[0].animation?.type).toBe('slowZoom');
74
+ });
75
+
76
+ it("treats defaultImageAnimation 'slowZoom' as ambient motion", () => {
77
+ const out = applyRenderStyleToLayers(
78
+ [fullBleedImage()],
79
+ BLOCK,
80
+ themeWith({ defaultImageAnimation: 'slowZoom' }),
81
+ );
82
+ expect(out[0].animation?.type).toBe('slowZoom');
83
+ });
84
+
85
+ it('never overrides an authored animation, including type none', () => {
86
+ const theme = themeWith({ ambientMotion: true });
87
+ const none: Layer = { ...fullBleedImage(), animation: { type: 'none' } };
88
+ const authored: Layer = {
89
+ ...fullBleedImage('a2'),
90
+ animation: { type: 'fadeIn', duration: 3 },
91
+ };
92
+ const out = applyRenderStyleToLayers([none, authored], BLOCK, theme);
93
+ expect(out[0].animation).toEqual({ type: 'none' });
94
+ expect(out[1].animation?.type).toBe('fadeIn');
95
+ });
96
+
97
+ it('does not blanket-animate partial or contain-fit imagery', () => {
98
+ const theme = themeWith({ ambientMotion: true });
99
+ const strip: Layer = {
100
+ type: 'image',
101
+ id: 'strip',
102
+ content: { src: 'x.jpg', alt: '' },
103
+ position: { x: '65%', y: 0, width: '35%', height: '100%' },
104
+ };
105
+ const contained: Layer = {
106
+ type: 'image',
107
+ id: 'contained',
108
+ content: { src: 'x.jpg', alt: '', fit: 'contain' },
109
+ position: { x: 0, y: 0, width: '100%', height: '100%' },
110
+ };
111
+ const out = applyRenderStyleToLayers([strip, contained], BLOCK, theme);
112
+ expect(out[0].animation).toBeUndefined();
113
+ expect(out[1].animation).toBeUndefined();
114
+ });
115
+ });
116
+
117
+ describe('resolveBlockTransition', () => {
118
+ const theme = themeWith({ defaultTransition: { type: 'dissolve', duration: 1.2 } });
119
+
120
+ it('authored transition always wins', () => {
121
+ const t = resolveBlockTransition({ transition: { type: 'cut' } }, theme, 3);
122
+ expect(t).toEqual({ type: 'cut' });
123
+ });
124
+
125
+ it('falls back to the theme default for blocks after the first', () => {
126
+ expect(resolveBlockTransition({}, theme, 1)).toEqual({ type: 'dissolve', duration: 1.2 });
127
+ });
128
+
129
+ it('never applies a theme default to block 0', () => {
130
+ expect(resolveBlockTransition({}, theme, 0)).toBeUndefined();
131
+ });
132
+
133
+ it('returns undefined when neither block nor theme specify one', () => {
134
+ const bare: Theme = {
135
+ ...DEFAULT_THEME,
136
+ renderStyle: { ...DEFAULT_THEME.renderStyle, defaultTransition: undefined },
137
+ };
138
+ expect(resolveBlockTransition({}, bare, 2)).toBeUndefined();
139
+ });
140
+
141
+ it('documentary (built-in) authors a default transition that now resolves', () => {
142
+ const documentary = resolveTheme('documentary');
143
+ const t = resolveBlockTransition({}, documentary, 1);
144
+ expect(t?.type).toBe('fade');
145
+ });
146
+ });