@texuli/blocks 0.0.0-stage → 0.1.0-next.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 (257) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -2
  3. package/dist/code/CodeBlockView.svelte +135 -0
  4. package/dist/code/CodeBlockView.svelte.d.ts +5 -0
  5. package/dist/code/check.d.ts +15 -0
  6. package/dist/code/check.js +14 -0
  7. package/dist/code/code.css +201 -0
  8. package/dist/code/code.css.d.ts +1 -0
  9. package/dist/code/engine-entry.d.ts +10 -0
  10. package/dist/code/engine-entry.js +10 -0
  11. package/dist/code/engine.d.ts +18 -0
  12. package/dist/code/engine.js +18 -0
  13. package/dist/code/family-engine.d.ts +23 -0
  14. package/dist/code/family-engine.js +4 -0
  15. package/dist/code/family.d.ts +7 -0
  16. package/dist/code/family.js +5 -0
  17. package/dist/code/grammars.d.ts +12 -0
  18. package/dist/code/grammars.js +83 -0
  19. package/dist/code/highlight.d.ts +15 -0
  20. package/dist/code/highlight.js +53 -0
  21. package/dist/code/index.d.ts +13 -0
  22. package/dist/code/index.js +12 -0
  23. package/dist/code/labels.d.ts +27 -0
  24. package/dist/code/labels.js +24 -0
  25. package/dist/code/languages/bash.d.ts +3 -0
  26. package/dist/code/languages/bash.js +48 -0
  27. package/dist/code/languages/css.d.ts +10 -0
  28. package/dist/code/languages/css.js +67 -0
  29. package/dist/code/languages/html.d.ts +6 -0
  30. package/dist/code/languages/html.js +35 -0
  31. package/dist/code/languages/javascript.d.ts +3 -0
  32. package/dist/code/languages/javascript.js +113 -0
  33. package/dist/code/languages/json.d.ts +3 -0
  34. package/dist/code/languages/json.js +24 -0
  35. package/dist/code/languages/python.d.ts +3 -0
  36. package/dist/code/languages/python.js +35 -0
  37. package/dist/code/languages/shared.d.ts +22 -0
  38. package/dist/code/languages/shared.js +37 -0
  39. package/dist/code/languages/sql.d.ts +3 -0
  40. package/dist/code/languages/sql.js +32 -0
  41. package/dist/code/languages/yaml.d.ts +7 -0
  42. package/dist/code/languages/yaml.js +36 -0
  43. package/dist/code/renderers.d.ts +5 -0
  44. package/dist/code/renderers.js +5 -0
  45. package/dist/code/token-styles.d.ts +25 -0
  46. package/dist/code/token-styles.js +51 -0
  47. package/dist/code/tokenize-code.d.ts +17 -0
  48. package/dist/code/tokenize-code.js +34 -0
  49. package/dist/code/tokenizer.d.ts +76 -0
  50. package/dist/code/tokenizer.js +171 -0
  51. package/dist/core/catalog/entries.d.ts +10 -0
  52. package/dist/core/catalog/entries.js +589 -0
  53. package/dist/core/catalog/index.d.ts +4 -0
  54. package/dist/core/catalog/index.js +2 -0
  55. package/dist/core/catalog/types.d.ts +36 -0
  56. package/dist/core/catalog/types.js +1 -0
  57. package/dist/core/catalog/validate.d.ts +15 -0
  58. package/dist/core/catalog/validate.js +46 -0
  59. package/dist/core/formula/evaluate.d.ts +14 -0
  60. package/dist/core/formula/evaluate.js +181 -0
  61. package/dist/core/formula/index.d.ts +8 -0
  62. package/dist/core/formula/index.js +6 -0
  63. package/dist/core/formula/parse.d.ts +19 -0
  64. package/dist/core/formula/parse.js +452 -0
  65. package/dist/core/formula/types.d.ts +76 -0
  66. package/dist/core/formula/types.js +7 -0
  67. package/dist/core/index.d.ts +14 -0
  68. package/dist/core/index.js +13 -0
  69. package/dist/core/limits.d.ts +27 -0
  70. package/dist/core/limits.js +26 -0
  71. package/dist/core/safety.d.ts +30 -0
  72. package/dist/core/safety.js +110 -0
  73. package/dist/core/schema/blocks.d.ts +138 -0
  74. package/dist/core/schema/blocks.js +181 -0
  75. package/dist/core/schema/code-family.d.ts +4 -0
  76. package/dist/core/schema/code-family.js +75 -0
  77. package/dist/core/schema/line-spec.d.ts +7 -0
  78. package/dist/core/schema/line-spec.js +19 -0
  79. package/dist/core/schema/media-family.d.ts +4 -0
  80. package/dist/core/schema/media-family.js +260 -0
  81. package/dist/core/schema/parse-family.d.ts +14 -0
  82. package/dist/core/schema/parse-family.js +37 -0
  83. package/dist/core/schema/primitives.d.ts +73 -0
  84. package/dist/core/schema/primitives.js +140 -0
  85. package/dist/core/schema/text-family.d.ts +4 -0
  86. package/dist/core/schema/text-family.js +121 -0
  87. package/dist/core/schema/vocabulary.d.ts +20 -0
  88. package/dist/core/schema/vocabulary.js +33 -0
  89. package/dist/core/text.d.ts +31 -0
  90. package/dist/core/text.js +139 -0
  91. package/dist/core/untrusted.d.ts +20 -0
  92. package/dist/core/untrusted.js +377 -0
  93. package/dist/css-modules.d.ts +1 -0
  94. package/dist/css.d.ts +1 -0
  95. package/dist/data/ChartBlockView.svelte +77 -0
  96. package/dist/data/ChartBlockView.svelte.d.ts +10 -0
  97. package/dist/data/DataBlockView.svelte +109 -0
  98. package/dist/data/DataBlockView.svelte.d.ts +5 -0
  99. package/dist/data/check.d.ts +10 -0
  100. package/dist/data/check.js +9 -0
  101. package/dist/data/engine.d.ts +18 -0
  102. package/dist/data/engine.js +10 -0
  103. package/dist/data/family.d.ts +6 -0
  104. package/dist/data/family.js +4 -0
  105. package/dist/data/index.d.ts +9 -0
  106. package/dist/data/index.js +8 -0
  107. package/dist/data/labels.d.ts +8 -0
  108. package/dist/data/labels.js +8 -0
  109. package/dist/data/renderers.d.ts +2 -0
  110. package/dist/data/renderers.js +2 -0
  111. package/dist/data/schema.d.ts +84 -0
  112. package/dist/data/schema.js +385 -0
  113. package/dist/diagram/BoardDiagram.svelte +48 -0
  114. package/dist/diagram/BoardDiagram.svelte.d.ts +8 -0
  115. package/dist/diagram/DiagramFamily.svelte +46 -0
  116. package/dist/diagram/DiagramFamily.svelte.d.ts +10 -0
  117. package/dist/diagram/FlowDiagram.svelte +108 -0
  118. package/dist/diagram/FlowDiagram.svelte.d.ts +8 -0
  119. package/dist/diagram/TerminalDiagram.svelte +38 -0
  120. package/dist/diagram/TerminalDiagram.svelte.d.ts +8 -0
  121. package/dist/diagram/TreeDiagram.svelte +149 -0
  122. package/dist/diagram/TreeDiagram.svelte.d.ts +8 -0
  123. package/dist/diagram/diagram.css +287 -0
  124. package/dist/diagram/diagram.css.d.ts +1 -0
  125. package/dist/diagram/engine.d.ts +16 -0
  126. package/dist/diagram/engine.js +14 -0
  127. package/dist/diagram/family.d.ts +7 -0
  128. package/dist/diagram/family.js +5 -0
  129. package/dist/diagram/index.d.ts +10 -0
  130. package/dist/diagram/index.js +9 -0
  131. package/dist/diagram/layout.d.ts +26 -0
  132. package/dist/diagram/layout.js +165 -0
  133. package/dist/diagram/schema.d.ts +74 -0
  134. package/dist/diagram/schema.js +358 -0
  135. package/dist/diagram/tones.d.ts +6 -0
  136. package/dist/diagram/tones.js +16 -0
  137. package/dist/embed/EmbedApp.svelte +138 -0
  138. package/dist/embed/EmbedApp.svelte.d.ts +17 -0
  139. package/dist/embed/EmbedBlockView.svelte +32 -0
  140. package/dist/embed/EmbedBlockView.svelte.d.ts +5 -0
  141. package/dist/embed/LinkPreview.svelte +48 -0
  142. package/dist/embed/LinkPreview.svelte.d.ts +10 -0
  143. package/dist/embed/engine.d.ts +30 -0
  144. package/dist/embed/engine.js +17 -0
  145. package/dist/embed/family.d.ts +7 -0
  146. package/dist/embed/family.js +5 -0
  147. package/dist/embed/index.d.ts +5 -0
  148. package/dist/embed/index.js +3 -0
  149. package/dist/embed/renderers.d.ts +3 -0
  150. package/dist/embed/renderers.js +3 -0
  151. package/dist/embed/sandbox.d.ts +86 -0
  152. package/dist/embed/sandbox.js +114 -0
  153. package/dist/embed/schema.d.ts +30 -0
  154. package/dist/embed/schema.js +217 -0
  155. package/dist/index.d.ts +16 -0
  156. package/dist/index.js +15 -0
  157. package/dist/interactive/CalculatorBlock.svelte +61 -0
  158. package/dist/interactive/CalculatorBlock.svelte.d.ts +10 -0
  159. package/dist/interactive/ChecklistBlock.svelte +44 -0
  160. package/dist/interactive/ChecklistBlock.svelte.d.ts +10 -0
  161. package/dist/interactive/InteractiveBlockView.svelte +31 -0
  162. package/dist/interactive/InteractiveBlockView.svelte.d.ts +5 -0
  163. package/dist/interactive/PollBlock.svelte +111 -0
  164. package/dist/interactive/PollBlock.svelte.d.ts +10 -0
  165. package/dist/interactive/QuizBlock.svelte +75 -0
  166. package/dist/interactive/QuizBlock.svelte.d.ts +10 -0
  167. package/dist/interactive/check.d.ts +10 -0
  168. package/dist/interactive/check.js +9 -0
  169. package/dist/interactive/engine.d.ts +19 -0
  170. package/dist/interactive/engine.js +12 -0
  171. package/dist/interactive/family.d.ts +6 -0
  172. package/dist/interactive/family.js +4 -0
  173. package/dist/interactive/index.d.ts +13 -0
  174. package/dist/interactive/index.js +13 -0
  175. package/dist/interactive/interactive.css +97 -0
  176. package/dist/interactive/interactive.css.d.ts +1 -0
  177. package/dist/interactive/labels.d.ts +16 -0
  178. package/dist/interactive/labels.js +16 -0
  179. package/dist/interactive/renderers.d.ts +9 -0
  180. package/dist/interactive/renderers.js +9 -0
  181. package/dist/interactive/schema.d.ts +76 -0
  182. package/dist/interactive/schema.js +324 -0
  183. package/dist/map/MapBlockView.svelte +465 -0
  184. package/dist/map/MapBlockView.svelte.d.ts +5 -0
  185. package/dist/map/check.d.ts +11 -0
  186. package/dist/map/check.js +6 -0
  187. package/dist/map/engine.d.ts +19 -0
  188. package/dist/map/engine.js +8 -0
  189. package/dist/map/family.d.ts +7 -0
  190. package/dist/map/family.js +5 -0
  191. package/dist/map/index.d.ts +11 -0
  192. package/dist/map/index.js +10 -0
  193. package/dist/map/labels.d.ts +27 -0
  194. package/dist/map/labels.js +21 -0
  195. package/dist/map/renderers.d.ts +2 -0
  196. package/dist/map/renderers.js +2 -0
  197. package/dist/map/schema.d.ts +41 -0
  198. package/dist/map/schema.js +176 -0
  199. package/dist/map/view-model.d.ts +55 -0
  200. package/dist/map/view-model.js +168 -0
  201. package/dist/media/MediaBlockView.svelte +258 -0
  202. package/dist/media/MediaBlockView.svelte.d.ts +5 -0
  203. package/dist/media/check.d.ts +14 -0
  204. package/dist/media/check.js +13 -0
  205. package/dist/media/engine.d.ts +18 -0
  206. package/dist/media/engine.js +10 -0
  207. package/dist/media/family.d.ts +7 -0
  208. package/dist/media/family.js +5 -0
  209. package/dist/media/index.d.ts +11 -0
  210. package/dist/media/index.js +10 -0
  211. package/dist/media/labels.d.ts +7 -0
  212. package/dist/media/labels.js +12 -0
  213. package/dist/media/renderers.d.ts +5 -0
  214. package/dist/media/renderers.js +5 -0
  215. package/dist/media/resolve.d.ts +6 -0
  216. package/dist/media/resolve.js +15 -0
  217. package/dist/structure/NestedBlocks.svelte +65 -0
  218. package/dist/structure/NestedBlocks.svelte.d.ts +11 -0
  219. package/dist/structure/SafeBody.svelte +13 -0
  220. package/dist/structure/SafeBody.svelte.d.ts +7 -0
  221. package/dist/structure/StructureFamily.svelte +157 -0
  222. package/dist/structure/StructureFamily.svelte.d.ts +5 -0
  223. package/dist/structure/check.d.ts +14 -0
  224. package/dist/structure/check.js +27 -0
  225. package/dist/structure/engine.d.ts +18 -0
  226. package/dist/structure/engine.js +18 -0
  227. package/dist/structure/family.d.ts +7 -0
  228. package/dist/structure/family.js +5 -0
  229. package/dist/structure/index.d.ts +11 -0
  230. package/dist/structure/index.js +10 -0
  231. package/dist/structure/labels.d.ts +11 -0
  232. package/dist/structure/labels.js +11 -0
  233. package/dist/structure/renderers.d.ts +5 -0
  234. package/dist/structure/renderers.js +5 -0
  235. package/dist/structure/schema.d.ts +71 -0
  236. package/dist/structure/schema.js +327 -0
  237. package/dist/structure/structure.css +182 -0
  238. package/dist/structure/structure.css.d.ts +1 -0
  239. package/dist/text/RichText.svelte +169 -0
  240. package/dist/text/RichText.svelte.d.ts +13 -0
  241. package/dist/text/TextBlockView.svelte +177 -0
  242. package/dist/text/TextBlockView.svelte.d.ts +5 -0
  243. package/dist/text/check.d.ts +14 -0
  244. package/dist/text/check.js +13 -0
  245. package/dist/text/engine.d.ts +17 -0
  246. package/dist/text/engine.js +10 -0
  247. package/dist/text/family.d.ts +7 -0
  248. package/dist/text/family.js +5 -0
  249. package/dist/text/index.d.ts +12 -0
  250. package/dist/text/index.js +11 -0
  251. package/dist/text/labels.d.ts +22 -0
  252. package/dist/text/labels.js +29 -0
  253. package/dist/text/markdown.d.ts +80 -0
  254. package/dist/text/markdown.js +361 -0
  255. package/dist/text/renderers.d.ts +6 -0
  256. package/dist/text/renderers.js +6 -0
  257. package/package.json +167 -4
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@texuli/blocks/interactive/engine` — Svelte-free interactive parsers + formula.
3
+ * Prefer `@texuli/blocks/formula` when only the calculator AST is needed.
4
+ */
5
+ import { INTERACTIVE_BLOCK_TYPES, } from "./schema.js";
6
+ export { evaluateCondition, evaluateFormula, FORMULA_LIMITS, formatFormulaValue, formulaJsonPath, formulaVariables, parseFormula, roundTo, } from "../core/formula/index.js";
7
+ export { checkInteractiveBlock } from "./check.js";
8
+ export { DEFAULT_INTERACTIVE_BLOCK_LABELS, resolveInteractiveBlockLabels, } from "./labels.js";
9
+ export { evaluateCalculator, INTERACTIVE_BLOCK_TYPES, parseInteractiveBlock, } from "./schema.js";
10
+ export function isInteractiveFamilyBlock(block) {
11
+ return INTERACTIVE_BLOCK_TYPES.includes(block.type);
12
+ }
@@ -0,0 +1,6 @@
1
+ import type { Component } from 'svelte';
2
+ import type { InteractiveBlockViewProps } from './engine.js';
3
+ export { type CalculatorBlock, type ChecklistBlock, INTERACTIVE_BLOCK_TYPES, type InteractiveBlock, type InteractiveBlockType, type InteractiveBlockViewProps, type InteractiveFamilyBlock, type InteractiveFamilyBlockType, isInteractiveFamilyBlock, type PollBlock, type QuizBlock, } from './engine.js';
4
+ export declare function loadInteractiveFamily(): Promise<{
5
+ default: Component<InteractiveBlockViewProps>;
6
+ }>;
@@ -0,0 +1,4 @@
1
+ export { INTERACTIVE_BLOCK_TYPES, isInteractiveFamilyBlock, } from "./engine.js";
2
+ export function loadInteractiveFamily() {
3
+ return import('./InteractiveBlockView.svelte');
4
+ }
@@ -0,0 +1,13 @@
1
+ /// <reference path="../css-modules.d.ts" />
2
+ /**
3
+ * `@texuli/blocks` interactive family: poll, quiz, calculator, checklist.
4
+ *
5
+ * The closed formula engine is also re-exported here for compatibility.
6
+ * Prefer `@texuli/blocks/formula` for a cold start that must not pull
7
+ * interactive renderers (Protoyard adoption).
8
+ */
9
+ export { type ArithmeticOp, type ComparisonOp, type Condition, evaluateCondition, evaluateFormula, FORMULA_LIMITS, type FormatFormulaOptions, type FormulaDiagnostic, type FormulaInputs, type FormulaParseResult, type FormulaUnit, formatFormulaValue, formulaJsonPath, formulaVariables, type NumberFormula, type ParseFormulaOptions, parseFormula, roundTo, } from '../core/formula/index.js';
10
+ export { checkInteractiveBlock, type InteractiveBlockCheck } from './check.js';
11
+ export { INTERACTIVE_BLOCK_TYPES, type InteractiveBlockViewProps, type InteractiveFamilyBlock, type InteractiveFamilyBlockType, isInteractiveFamilyBlock, loadInteractiveFamily, } from './family.js';
12
+ export { DEFAULT_INTERACTIVE_BLOCK_LABELS, type InteractiveBlockLabelOverrides, type InteractiveBlockLabels, resolveInteractiveBlockLabels, } from './labels.js';
13
+ export { type CalculatorBlock, type CalculatorInput, type ChecklistBlock, type ChecklistItem, evaluateCalculator, type InteractiveBlock, type InteractiveBlockType, type PollBlock, type PollOption, parseInteractiveBlock, type QuizBlock, type QuizOption, type QuizQuestion, } from './schema.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@texuli/blocks` interactive family: poll, quiz, calculator, checklist.
3
+ *
4
+ * The closed formula engine is also re-exported here for compatibility.
5
+ * Prefer `@texuli/blocks/formula` for a cold start that must not pull
6
+ * interactive renderers (Protoyard adoption).
7
+ */
8
+ // Light engine — same surface as `@texuli/blocks/formula`.
9
+ export { evaluateCondition, evaluateFormula, FORMULA_LIMITS, formatFormulaValue, formulaJsonPath, formulaVariables, parseFormula, roundTo, } from "../core/formula/index.js";
10
+ export { checkInteractiveBlock } from "./check.js";
11
+ export { INTERACTIVE_BLOCK_TYPES, isInteractiveFamilyBlock, loadInteractiveFamily, } from "./family.js";
12
+ export { DEFAULT_INTERACTIVE_BLOCK_LABELS, resolveInteractiveBlockLabels, } from "./labels.js";
13
+ export { evaluateCalculator, parseInteractiveBlock, } from "./schema.js";
@@ -0,0 +1,97 @@
1
+ @layer tx.components {
2
+ .tx-block-interactive {
3
+ display: grid;
4
+ gap: 0.75rem;
5
+ padding: 1rem;
6
+ border: 1px solid var(--tx-border, ButtonBorder);
7
+ border-radius: var(--tx-radius, 0.625rem);
8
+ background: var(--tx-surface, Canvas);
9
+ color: var(--tx-foreground, CanvasText);
10
+ max-inline-size: 40rem;
11
+ }
12
+
13
+ .tx-block-interactive__title {
14
+ margin: 0;
15
+ font-size: 1.05rem;
16
+ font-weight: 600;
17
+ }
18
+
19
+ .tx-block-interactive__prompt {
20
+ margin: 0;
21
+ font-size: 0.95rem;
22
+ }
23
+
24
+ .tx-block-interactive__options {
25
+ display: grid;
26
+ gap: 0.4rem;
27
+ }
28
+
29
+ .tx-block-interactive__option {
30
+ display: flex;
31
+ gap: 0.5rem;
32
+ align-items: flex-start;
33
+ }
34
+
35
+ .tx-block-interactive__actions {
36
+ display: flex;
37
+ flex-wrap: wrap;
38
+ gap: 0.5rem;
39
+ }
40
+
41
+ .tx-block-interactive__result {
42
+ margin: 0;
43
+ font-weight: 600;
44
+ }
45
+
46
+ .tx-block-interactive__bar {
47
+ display: grid;
48
+ gap: 0.15rem;
49
+ }
50
+
51
+ .tx-block-interactive__bar-track {
52
+ block-size: 0.5rem;
53
+ border-radius: 999px;
54
+ background: var(--tx-muted, color-mix(in srgb, CanvasText 10%, Canvas));
55
+ overflow: hidden;
56
+ }
57
+
58
+ .tx-block-interactive__bar-fill {
59
+ block-size: 100%;
60
+ background: var(--tx-primary, Highlight);
61
+ inline-size: 0%;
62
+ }
63
+
64
+ .tx-block-interactive__inputs {
65
+ display: grid;
66
+ gap: 0.65rem;
67
+ }
68
+
69
+ .tx-block-interactive__field {
70
+ display: grid;
71
+ gap: 0.25rem;
72
+ }
73
+
74
+ .tx-block-interactive__field input {
75
+ min-block-size: 2.25rem;
76
+ padding-inline: 0.5rem;
77
+ border: 1px solid var(--tx-input, var(--tx-border, ButtonBorder));
78
+ border-radius: calc(var(--tx-radius, 0.625rem) * 0.5);
79
+ background: var(--tx-background, Canvas);
80
+ color: inherit;
81
+ font: inherit;
82
+ }
83
+
84
+ .tx-block-invalid {
85
+ margin: 0;
86
+ padding: 0.75rem 1rem;
87
+ border: 1px solid var(--tx-destructive, Mark);
88
+ border-radius: var(--tx-radius, 0.5rem);
89
+ }
90
+
91
+ @media (forced-colors: active) {
92
+ .tx-block-interactive,
93
+ .tx-block-interactive__field input {
94
+ border: 1px solid ButtonText;
95
+ }
96
+ }
97
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ export type InteractiveBlockLabels = {
2
+ invalid: string;
3
+ pollSubmit: string;
4
+ pollReset: string;
5
+ pollResults: string;
6
+ quizSubmit: string;
7
+ quizNext: string;
8
+ quizScore: string;
9
+ quizCorrect: string;
10
+ quizIncorrect: string;
11
+ calculatorResult: string;
12
+ checklistProgress: string;
13
+ };
14
+ export type InteractiveBlockLabelOverrides = Partial<InteractiveBlockLabels>;
15
+ export declare const DEFAULT_INTERACTIVE_BLOCK_LABELS: InteractiveBlockLabels;
16
+ export declare function resolveInteractiveBlockLabels(overrides?: InteractiveBlockLabelOverrides): InteractiveBlockLabels;
@@ -0,0 +1,16 @@
1
+ export const DEFAULT_INTERACTIVE_BLOCK_LABELS = {
2
+ invalid: 'This interactive block is not valid.',
3
+ pollSubmit: 'Vote',
4
+ pollReset: 'Reset',
5
+ pollResults: 'Results',
6
+ quizSubmit: 'Check answer',
7
+ quizNext: 'Next question',
8
+ quizScore: 'Score',
9
+ quizCorrect: 'Correct',
10
+ quizIncorrect: 'Not quite',
11
+ calculatorResult: 'Result',
12
+ checklistProgress: 'Completed',
13
+ };
14
+ export function resolveInteractiveBlockLabels(overrides) {
15
+ return { ...DEFAULT_INTERACTIVE_BLOCK_LABELS, ...overrides };
16
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Eager entry for hosts that render interactive blocks on first paint.
3
+ * Prefer `loadInteractiveFamily()` for a lazy chunk.
4
+ */
5
+ export { default as CalculatorBlock } from './CalculatorBlock.svelte';
6
+ export { default as ChecklistBlock } from './ChecklistBlock.svelte';
7
+ export { default as InteractiveBlockView } from './InteractiveBlockView.svelte';
8
+ export { default as PollBlock } from './PollBlock.svelte';
9
+ export { default as QuizBlock } from './QuizBlock.svelte';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Eager entry for hosts that render interactive blocks on first paint.
3
+ * Prefer `loadInteractiveFamily()` for a lazy chunk.
4
+ */
5
+ export { default as CalculatorBlock } from './CalculatorBlock.svelte';
6
+ export { default as ChecklistBlock } from './ChecklistBlock.svelte';
7
+ export { default as InteractiveBlockView } from './InteractiveBlockView.svelte';
8
+ export { default as PollBlock } from './PollBlock.svelte';
9
+ export { default as QuizBlock } from './QuizBlock.svelte';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Interactive-family block shapes: poll, quiz, calculator, checklist.
3
+ * Wired into core `safeParseBlock` via `parseInteractiveBlock`.
4
+ */
5
+ import { type FormulaUnit, type NumberFormula } from '../core/formula/index.js';
6
+ import { type BlockBase, type ParseResult } from '../core/schema/primitives.js';
7
+ export type PollOption = {
8
+ id: string;
9
+ label: string;
10
+ };
11
+ export type PollBlock = BlockBase & {
12
+ type: 'poll';
13
+ question: string;
14
+ options: PollOption[];
15
+ allowMultiple?: boolean;
16
+ };
17
+ export type QuizOption = {
18
+ id: string;
19
+ label: string;
20
+ };
21
+ export type QuizQuestion = {
22
+ id: string;
23
+ prompt: string;
24
+ options: QuizOption[];
25
+ /** Single correct option id. */
26
+ correctId: string;
27
+ explanation?: string;
28
+ };
29
+ export type QuizBlock = BlockBase & {
30
+ type: 'quiz';
31
+ title?: string;
32
+ questions: QuizQuestion[];
33
+ };
34
+ export type CalculatorInput = {
35
+ name: string;
36
+ label: string;
37
+ min?: number;
38
+ max?: number;
39
+ step?: number;
40
+ default?: number;
41
+ };
42
+ export type CalculatorBlock = BlockBase & {
43
+ type: 'calculator';
44
+ title?: string;
45
+ description?: string;
46
+ inputs: CalculatorInput[];
47
+ formula: NumberFormula;
48
+ unit?: FormulaUnit;
49
+ outputLabel?: string;
50
+ };
51
+ export type ChecklistItem = {
52
+ id: string;
53
+ label: string;
54
+ done?: boolean;
55
+ };
56
+ export type ChecklistBlock = BlockBase & {
57
+ type: 'checklist';
58
+ title?: string;
59
+ items: ChecklistItem[];
60
+ };
61
+ export type InteractiveBlock = PollBlock | QuizBlock | CalculatorBlock | ChecklistBlock;
62
+ export type InteractiveBlockType = 'poll' | 'quiz' | 'calculator' | 'checklist';
63
+ export declare const INTERACTIVE_BLOCK_TYPES: readonly ["poll", "quiz", "calculator", "checklist"];
64
+ /**
65
+ * Parse an interactive-family block, or `null` when `type` is not interactive.
66
+ */
67
+ export declare function parseInteractiveBlock(type: string, record: Record<string, unknown>, path: (string | number)[], base: BlockBase): ParseResult<InteractiveBlock> | null;
68
+ /** Evaluate a calculator block against current input values (finite numbers only). */
69
+ export declare function evaluateCalculator(block: CalculatorBlock, values: Readonly<Record<string, number | null | undefined>>): {
70
+ ok: true;
71
+ value: number | null;
72
+ formatted: string;
73
+ } | {
74
+ ok: false;
75
+ message: string;
76
+ };
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Interactive-family block shapes: poll, quiz, calculator, checklist.
3
+ * Wired into core `safeParseBlock` via `parseInteractiveBlock`.
4
+ */
5
+ import { evaluateFormula, formatFormulaValue, parseFormula, } from "../core/formula/index.js";
6
+ import { BLOCK_CONTENT_LIMITS, fail, failMany, ok, parseMultilineText, parsePlainText, } from "../core/schema/primitives.js";
7
+ import { isBlockTone } from "../core/schema/vocabulary.js";
8
+ const OPTION_ID = /^[a-z][a-z0-9-]{0,31}$/;
9
+ const VAR_NAME = /^[a-z][a-z0-9_]{0,31}$/;
10
+ const FORMULA_UNITS = ['number', 'compact', 'cents', 'percent'];
11
+ export const INTERACTIVE_BLOCK_TYPES = [
12
+ 'poll',
13
+ 'quiz',
14
+ 'calculator',
15
+ 'checklist',
16
+ ];
17
+ function plainObject(value, path) {
18
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
19
+ return fail(path, 'Expected an object');
20
+ }
21
+ return ok(value);
22
+ }
23
+ function parseOptionId(value, path) {
24
+ if (typeof value !== 'string' || !OPTION_ID.test(value)) {
25
+ return fail(path, 'Expected a stable option id (lowercase, max 32)');
26
+ }
27
+ return ok(value);
28
+ }
29
+ function parseOptions(value, path, max) {
30
+ if (!Array.isArray(value) || value.length < 2) {
31
+ return fail(path, 'Expected at least two options');
32
+ }
33
+ if (value.length > max)
34
+ return fail(path, `At most ${max} options`);
35
+ const options = [];
36
+ const seen = new Set();
37
+ const issues = [];
38
+ for (let i = 0; i < value.length; i += 1) {
39
+ const itemPath = [...path, i];
40
+ const obj = plainObject(value[i], itemPath);
41
+ if (!obj.success) {
42
+ issues.push(...obj.issues);
43
+ continue;
44
+ }
45
+ const id = parseOptionId(obj.data.id, [...itemPath, 'id']);
46
+ const label = parsePlainText(obj.data.label, [...itemPath, 'label'], BLOCK_CONTENT_LIMITS.plain);
47
+ if (!id.success) {
48
+ issues.push(...id.issues);
49
+ continue;
50
+ }
51
+ if (!label.success) {
52
+ issues.push(...label.issues);
53
+ continue;
54
+ }
55
+ if (seen.has(id.data)) {
56
+ issues.push({ path: [...itemPath, 'id'], message: 'Duplicate option id' });
57
+ continue;
58
+ }
59
+ seen.add(id.data);
60
+ options.push({ id: id.data, label: label.data });
61
+ }
62
+ if (issues.length > 0)
63
+ return failMany(issues);
64
+ return ok(options);
65
+ }
66
+ function parsePollBlock(record, path, base) {
67
+ const question = parseMultilineText(record.question, [...path, 'question'], BLOCK_CONTENT_LIMITS.shortMarkdown);
68
+ if (!question.success)
69
+ return question;
70
+ const options = parseOptions(record.options, [...path, 'options'], BLOCK_CONTENT_LIMITS.pollOptions);
71
+ if (!options.success)
72
+ return options;
73
+ const block = {
74
+ ...base,
75
+ type: 'poll',
76
+ question: question.data,
77
+ options: options.data,
78
+ };
79
+ if (record.allowMultiple === true)
80
+ block.allowMultiple = true;
81
+ if (record.tone !== undefined) {
82
+ if (!isBlockTone(record.tone))
83
+ return fail([...path, 'tone'], 'Invalid tone');
84
+ block.tone = record.tone;
85
+ }
86
+ return ok(block);
87
+ }
88
+ function parseQuizBlock(record, path, base) {
89
+ if (!Array.isArray(record.questions) || record.questions.length < 1) {
90
+ return fail([...path, 'questions'], 'Expected at least one question');
91
+ }
92
+ if (record.questions.length > BLOCK_CONTENT_LIMITS.steps) {
93
+ return fail([...path, 'questions'], `At most ${BLOCK_CONTENT_LIMITS.steps} questions`);
94
+ }
95
+ const questions = [];
96
+ const seenQ = new Set();
97
+ const issues = [];
98
+ for (let i = 0; i < record.questions.length; i += 1) {
99
+ const qPath = [...path, 'questions', i];
100
+ const obj = plainObject(record.questions[i], qPath);
101
+ if (!obj.success) {
102
+ issues.push(...obj.issues);
103
+ continue;
104
+ }
105
+ const id = parseOptionId(obj.data.id, [...qPath, 'id']);
106
+ const prompt = parseMultilineText(obj.data.prompt, [...qPath, 'prompt'], BLOCK_CONTENT_LIMITS.shortMarkdown);
107
+ const options = parseOptions(obj.data.options, [...qPath, 'options'], BLOCK_CONTENT_LIMITS.pollOptions);
108
+ if (!id.success || !prompt.success || !options.success) {
109
+ if (!id.success)
110
+ issues.push(...id.issues);
111
+ if (!prompt.success)
112
+ issues.push(...prompt.issues);
113
+ if (!options.success)
114
+ issues.push(...options.issues);
115
+ continue;
116
+ }
117
+ if (seenQ.has(id.data)) {
118
+ issues.push({ path: [...qPath, 'id'], message: 'Duplicate question id' });
119
+ continue;
120
+ }
121
+ seenQ.add(id.data);
122
+ const correctId = parseOptionId(obj.data.correctId, [...qPath, 'correctId']);
123
+ if (!correctId.success) {
124
+ issues.push(...correctId.issues);
125
+ continue;
126
+ }
127
+ if (!options.data.some((o) => o.id === correctId.data)) {
128
+ issues.push({
129
+ path: [...qPath, 'correctId'],
130
+ message: 'correctId must match an option id',
131
+ });
132
+ continue;
133
+ }
134
+ const q = {
135
+ id: id.data,
136
+ prompt: prompt.data,
137
+ options: options.data,
138
+ correctId: correctId.data,
139
+ };
140
+ if (obj.data.explanation !== undefined) {
141
+ const explanation = parseMultilineText(obj.data.explanation, [...qPath, 'explanation'], BLOCK_CONTENT_LIMITS.shortMarkdown);
142
+ if (!explanation.success) {
143
+ issues.push(...explanation.issues);
144
+ continue;
145
+ }
146
+ q.explanation = explanation.data;
147
+ }
148
+ questions.push(q);
149
+ }
150
+ if (issues.length > 0)
151
+ return failMany(issues);
152
+ const block = { ...base, type: 'quiz', questions };
153
+ if (record.title !== undefined) {
154
+ const title = parsePlainText(record.title, [...path, 'title'], BLOCK_CONTENT_LIMITS.heading);
155
+ if (!title.success)
156
+ return title;
157
+ block.title = title.data;
158
+ }
159
+ return ok(block);
160
+ }
161
+ function parseFiniteNumber(value, path) {
162
+ if (value === undefined)
163
+ return ok(undefined);
164
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
165
+ return fail(path, 'Expected a finite number');
166
+ }
167
+ return ok(value);
168
+ }
169
+ function parseCalculatorBlock(record, path, base) {
170
+ if (!Array.isArray(record.inputs) || record.inputs.length < 1) {
171
+ return fail([...path, 'inputs'], 'Expected at least one input');
172
+ }
173
+ if (record.inputs.length > BLOCK_CONTENT_LIMITS.formFields) {
174
+ return fail([...path, 'inputs'], `At most ${BLOCK_CONTENT_LIMITS.formFields} inputs`);
175
+ }
176
+ const inputs = [];
177
+ const names = new Set();
178
+ const issues = [];
179
+ for (let i = 0; i < record.inputs.length; i += 1) {
180
+ const iPath = [...path, 'inputs', i];
181
+ const obj = plainObject(record.inputs[i], iPath);
182
+ if (!obj.success) {
183
+ issues.push(...obj.issues);
184
+ continue;
185
+ }
186
+ if (typeof obj.data.name !== 'string' || !VAR_NAME.test(obj.data.name)) {
187
+ issues.push({ path: [...iPath, 'name'], message: 'Invalid input name' });
188
+ continue;
189
+ }
190
+ if (names.has(obj.data.name)) {
191
+ issues.push({ path: [...iPath, 'name'], message: 'Duplicate input name' });
192
+ continue;
193
+ }
194
+ names.add(obj.data.name);
195
+ const label = parsePlainText(obj.data.label, [...iPath, 'label'], BLOCK_CONTENT_LIMITS.plain);
196
+ if (!label.success) {
197
+ issues.push(...label.issues);
198
+ continue;
199
+ }
200
+ const input = { name: obj.data.name, label: label.data };
201
+ for (const key of ['min', 'max', 'step', 'default']) {
202
+ if (obj.data[key] === undefined)
203
+ continue;
204
+ const n = parseFiniteNumber(obj.data[key], [...iPath, key]);
205
+ if (!n.success) {
206
+ issues.push(...n.issues);
207
+ continue;
208
+ }
209
+ if (n.data !== undefined)
210
+ input[key] = n.data;
211
+ }
212
+ inputs.push(input);
213
+ }
214
+ if (issues.length > 0)
215
+ return failMany(issues);
216
+ const formulaResult = parseFormula(record.formula, {
217
+ variables: [...names],
218
+ });
219
+ if (!formulaResult.success) {
220
+ return fail([...path, 'formula'], formulaResult.diagnostics[0]?.message ?? 'Invalid formula');
221
+ }
222
+ const block = {
223
+ ...base,
224
+ type: 'calculator',
225
+ inputs,
226
+ formula: formulaResult.formula,
227
+ };
228
+ if (record.title !== undefined) {
229
+ const title = parsePlainText(record.title, [...path, 'title'], BLOCK_CONTENT_LIMITS.heading);
230
+ if (!title.success)
231
+ return title;
232
+ block.title = title.data;
233
+ }
234
+ if (record.description !== undefined) {
235
+ const description = parseMultilineText(record.description, [...path, 'description'], BLOCK_CONTENT_LIMITS.shortMarkdown);
236
+ if (!description.success)
237
+ return description;
238
+ block.description = description.data;
239
+ }
240
+ if (record.outputLabel !== undefined) {
241
+ const outputLabel = parsePlainText(record.outputLabel, [...path, 'outputLabel'], BLOCK_CONTENT_LIMITS.plain);
242
+ if (!outputLabel.success)
243
+ return outputLabel;
244
+ block.outputLabel = outputLabel.data;
245
+ }
246
+ if (record.unit !== undefined) {
247
+ if (typeof record.unit !== 'string' ||
248
+ !FORMULA_UNITS.includes(record.unit)) {
249
+ return fail([...path, 'unit'], 'Invalid formula unit');
250
+ }
251
+ block.unit = record.unit;
252
+ }
253
+ return ok(block);
254
+ }
255
+ function parseChecklistBlock(record, path, base) {
256
+ if (!Array.isArray(record.items) || record.items.length < 1) {
257
+ return fail([...path, 'items'], 'Expected at least one item');
258
+ }
259
+ if (record.items.length > BLOCK_CONTENT_LIMITS.listItems) {
260
+ return fail([...path, 'items'], `At most ${BLOCK_CONTENT_LIMITS.listItems} items`);
261
+ }
262
+ const items = [];
263
+ const seen = new Set();
264
+ const issues = [];
265
+ for (let i = 0; i < record.items.length; i += 1) {
266
+ const iPath = [...path, 'items', i];
267
+ const obj = plainObject(record.items[i], iPath);
268
+ if (!obj.success) {
269
+ issues.push(...obj.issues);
270
+ continue;
271
+ }
272
+ const id = parseOptionId(obj.data.id, [...iPath, 'id']);
273
+ const label = parsePlainText(obj.data.label, [...iPath, 'label'], BLOCK_CONTENT_LIMITS.plain);
274
+ if (!id.success || !label.success) {
275
+ if (!id.success)
276
+ issues.push(...id.issues);
277
+ if (!label.success)
278
+ issues.push(...label.issues);
279
+ continue;
280
+ }
281
+ if (seen.has(id.data)) {
282
+ issues.push({ path: [...iPath, 'id'], message: 'Duplicate item id' });
283
+ continue;
284
+ }
285
+ seen.add(id.data);
286
+ const item = { id: id.data, label: label.data };
287
+ if (obj.data.done === true)
288
+ item.done = true;
289
+ items.push(item);
290
+ }
291
+ if (issues.length > 0)
292
+ return failMany(issues);
293
+ const block = { ...base, type: 'checklist', items };
294
+ if (record.title !== undefined) {
295
+ const title = parsePlainText(record.title, [...path, 'title'], BLOCK_CONTENT_LIMITS.heading);
296
+ if (!title.success)
297
+ return title;
298
+ block.title = title.data;
299
+ }
300
+ return ok(block);
301
+ }
302
+ /**
303
+ * Parse an interactive-family block, or `null` when `type` is not interactive.
304
+ */
305
+ export function parseInteractiveBlock(type, record, path, base) {
306
+ switch (type) {
307
+ case 'poll':
308
+ return parsePollBlock(record, path, base);
309
+ case 'quiz':
310
+ return parseQuizBlock(record, path, base);
311
+ case 'calculator':
312
+ return parseCalculatorBlock(record, path, base);
313
+ case 'checklist':
314
+ return parseChecklistBlock(record, path, base);
315
+ default:
316
+ return null;
317
+ }
318
+ }
319
+ /** Evaluate a calculator block against current input values (finite numbers only). */
320
+ export function evaluateCalculator(block, values) {
321
+ const result = evaluateFormula(block.formula, values);
322
+ const formatted = formatFormulaValue(result, block.unit ?? 'number');
323
+ return { ok: true, value: result, formatted };
324
+ }