@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,18 @@
1
+ /**
2
+ * `@texuli/blocks/media/engine` — Svelte-free media family guards, labels and URL resolve.
3
+ */
4
+ import type { Block, FieldIssue, FigureBlock, GalleryBlock, ImageBlock, VideoBlock } from '../core/index.js';
5
+ import type { MediaBlockLabelOverrides } from './labels.js';
6
+ export { checkMediaBlock, type MediaBlockCheck } from './check.js';
7
+ export { DEFAULT_MEDIA_BLOCK_LABELS, type MediaBlockLabelOverrides, type MediaBlockLabels, resolveMediaBlockLabels, } from './labels.js';
8
+ export { resolveBlockMediaSrc } from './resolve.js';
9
+ export declare const MEDIA_BLOCK_TYPES: readonly ["image", "figure", "video", "gallery"];
10
+ export type MediaFamilyBlockType = (typeof MEDIA_BLOCK_TYPES)[number];
11
+ export type MediaFamilyBlock = ImageBlock | FigureBlock | VideoBlock | GalleryBlock;
12
+ export declare function isMediaFamilyBlock(block: Block): block is MediaFamilyBlock;
13
+ export interface MediaBlockViewProps {
14
+ block: unknown;
15
+ labels?: MediaBlockLabelOverrides;
16
+ resolveAvatar?: (id: string) => string | undefined;
17
+ oninvalid?: (issues: FieldIssue[]) => void;
18
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@texuli/blocks/media/engine` — Svelte-free media family guards, labels and URL resolve.
3
+ */
4
+ export { checkMediaBlock } from "./check.js";
5
+ export { DEFAULT_MEDIA_BLOCK_LABELS, resolveMediaBlockLabels, } from "./labels.js";
6
+ export { resolveBlockMediaSrc } from "./resolve.js";
7
+ export const MEDIA_BLOCK_TYPES = ['image', 'figure', 'video', 'gallery'];
8
+ export function isMediaFamilyBlock(block) {
9
+ return MEDIA_BLOCK_TYPES.includes(block.type);
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { Component } from 'svelte';
2
+ import type { MediaBlockViewProps } from './engine.js';
3
+ export { isMediaFamilyBlock, MEDIA_BLOCK_TYPES, type MediaBlockViewProps, type MediaFamilyBlock, type MediaFamilyBlockType, } from './engine.js';
4
+ /** Lazily loads the media family renderer (its own chunk). */
5
+ export declare function loadMediaFamily(): Promise<{
6
+ default: Component<MediaBlockViewProps>;
7
+ }>;
@@ -0,0 +1,5 @@
1
+ export { isMediaFamilyBlock, MEDIA_BLOCK_TYPES, } from "./engine.js";
2
+ /** Lazily loads the media family renderer (its own chunk). */
3
+ export function loadMediaFamily() {
4
+ return import('./MediaBlockView.svelte');
5
+ }
@@ -0,0 +1,11 @@
1
+ /// <reference path="../css-modules.d.ts" />
2
+ /**
3
+ * `@texuli/blocks` media family (T-blocks-media): image, figure, video and
4
+ * gallery. No Svelte is imported eagerly: renderers load through
5
+ * `loadMediaFamily()` (its own chunk) or directly from
6
+ * `@texuli/blocks/media/renderers`.
7
+ */
8
+ export { checkMediaBlock, type MediaBlockCheck } from './check.js';
9
+ export { isMediaFamilyBlock, loadMediaFamily, MEDIA_BLOCK_TYPES, type MediaBlockViewProps, type MediaFamilyBlock, type MediaFamilyBlockType, } from './family.js';
10
+ export { DEFAULT_MEDIA_BLOCK_LABELS, type MediaBlockLabelOverrides, type MediaBlockLabels, resolveMediaBlockLabels, } from './labels.js';
11
+ export { resolveBlockMediaSrc } from './resolve.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@texuli/blocks` media family (T-blocks-media): image, figure, video and
3
+ * gallery. No Svelte is imported eagerly: renderers load through
4
+ * `loadMediaFamily()` (its own chunk) or directly from
5
+ * `@texuli/blocks/media/renderers`.
6
+ */
7
+ export { checkMediaBlock } from "./check.js";
8
+ export { isMediaFamilyBlock, loadMediaFamily, MEDIA_BLOCK_TYPES, } from "./family.js";
9
+ export { DEFAULT_MEDIA_BLOCK_LABELS, resolveMediaBlockLabels, } from "./labels.js";
10
+ export { resolveBlockMediaSrc } from "./resolve.js";
@@ -0,0 +1,7 @@
1
+ export type MediaBlockLabels = {
2
+ invalid: string;
3
+ galleryFallback: string;
4
+ };
5
+ export type MediaBlockLabelOverrides = Partial<MediaBlockLabels>;
6
+ export declare const DEFAULT_MEDIA_BLOCK_LABELS: MediaBlockLabels;
7
+ export declare function resolveMediaBlockLabels(overrides?: MediaBlockLabelOverrides): MediaBlockLabels;
@@ -0,0 +1,12 @@
1
+ export const DEFAULT_MEDIA_BLOCK_LABELS = Object.freeze({
2
+ invalid: 'This media block could not be shown.',
3
+ galleryFallback: 'Image unavailable',
4
+ });
5
+ export function resolveMediaBlockLabels(overrides) {
6
+ if (!overrides)
7
+ return DEFAULT_MEDIA_BLOCK_LABELS;
8
+ return {
9
+ invalid: overrides.invalid ?? DEFAULT_MEDIA_BLOCK_LABELS.invalid,
10
+ galleryFallback: overrides.galleryFallback ?? DEFAULT_MEDIA_BLOCK_LABELS.galleryFallback,
11
+ };
12
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Eager entry for hosts that render media blocks on first paint. Most hosts
3
+ * should prefer `loadMediaFamily()` from `@texuli/blocks` (lazy chunk).
4
+ */
5
+ export { default as MediaBlockView } from './MediaBlockView.svelte';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Eager entry for hosts that render media blocks on first paint. Most hosts
3
+ * should prefer `loadMediaFamily()` from `@texuli/blocks` (lazy chunk).
4
+ */
5
+ export { default as MediaBlockView } from './MediaBlockView.svelte';
@@ -0,0 +1,6 @@
1
+ import { type MediaKind } from '../core/safety.js';
2
+ /**
3
+ * Resolve a block media `src` after schema validation. Avatars need a host
4
+ * adapter (not shipped here); data and relative sources pass through.
5
+ */
6
+ export declare function resolveBlockMediaSrc(src: string, kind: MediaKind, resolveAvatar?: (id: string) => string | undefined): string | undefined;
@@ -0,0 +1,15 @@
1
+ import { classifyMediaSource } from "../core/safety.js";
2
+ /**
3
+ * Resolve a block media `src` after schema validation. Avatars need a host
4
+ * adapter (not shipped here); data and relative sources pass through.
5
+ */
6
+ export function resolveBlockMediaSrc(src, kind, resolveAvatar) {
7
+ const result = classifyMediaSource(src, kind);
8
+ if (!result.ok)
9
+ return undefined;
10
+ if (result.kind === 'avatar') {
11
+ const id = src.slice('avatar:'.length);
12
+ return resolveAvatar?.(id);
13
+ }
14
+ return src;
15
+ }
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ import type { Component } from 'svelte';
3
+ import type { Block } from '../core/index.js';
4
+ import type { StructureBlockLabels } from './labels.js';
5
+
6
+ /**
7
+ * Nested child blocks inside columns or tab panels. Dynamically imports the
8
+ * matching family renderer so the structure chunk can finish loading without a
9
+ * hard cycle through every other family.
10
+ */
11
+ let {
12
+ blocks,
13
+ labels,
14
+ }: {
15
+ blocks: readonly Block[];
16
+ labels: StructureBlockLabels;
17
+ } = $props();
18
+
19
+ const TEXT_TYPES = new Set(['heading', 'text', 'list', 'quote', 'callout']);
20
+ const STRUCTURE_TYPES = new Set([
21
+ 'columns',
22
+ 'grid',
23
+ 'tabs',
24
+ 'accordion',
25
+ 'cards',
26
+ 'divider',
27
+ 'cta',
28
+ ]);
29
+
30
+ type View = Component<{ block: unknown; labels?: unknown; oninvalid?: unknown }>;
31
+
32
+ async function loadView(type: string): Promise<View | null> {
33
+ if (TEXT_TYPES.has(type)) {
34
+ const mod = await import('../text/TextBlockView.svelte');
35
+ return mod.default as View;
36
+ }
37
+ if (STRUCTURE_TYPES.has(type)) {
38
+ const mod = await import('./StructureFamily.svelte');
39
+ return mod.default as View;
40
+ }
41
+ return null;
42
+ }
43
+ </script>
44
+
45
+ {#if blocks.length === 0}
46
+ <p class="tx-structure-empty" role="note">{labels.emptyPanel}</p>
47
+ {:else}
48
+ <div class="tx-structure-nested">
49
+ {#each blocks as child, i (i)}
50
+ {#await loadView(child.type)}
51
+ <p class="tx-structure-loading" role="status">…</p>
52
+ {:then View}
53
+ {#if View}
54
+ <View block={child} />
55
+ {:else}
56
+ <p class="tx-structure-empty" role="note" data-tx-nested-unsupported={child.type}>
57
+ {labels.invalid}
58
+ </p>
59
+ {/if}
60
+ {:catch}
61
+ <p class="tx-structure-empty" role="note">{labels.invalid}</p>
62
+ {/await}
63
+ {/each}
64
+ </div>
65
+ {/if}
@@ -0,0 +1,11 @@
1
+ /// <reference path="../css-modules.d.ts" />
2
+ import type { Component } from 'svelte';
3
+ import type { Block } from '../core/index.js';
4
+ import type { StructureBlockLabels } from './labels.js';
5
+ type $$ComponentProps = {
6
+ blocks: readonly Block[];
7
+ labels: StructureBlockLabels;
8
+ };
9
+ declare const NestedBlocks: Component<$$ComponentProps, {}, "">;
10
+ type NestedBlocks = ReturnType<typeof NestedBlocks>;
11
+ export default NestedBlocks;
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import { DEFAULT_TEXT_BLOCK_LABELS } from '../text/labels.js';
3
+ import RichText from '../text/RichText.svelte';
4
+
5
+ /**
6
+ * Renders a short markdown body through the owned closed subset. No HTML sink.
7
+ */
8
+ let { text }: { text: string } = $props();
9
+ </script>
10
+
11
+ <div class="tx-structure-body">
12
+ <RichText source={text} labels={DEFAULT_TEXT_BLOCK_LABELS} />
13
+ </div>
@@ -0,0 +1,7 @@
1
+ /// <reference path="../css-modules.d.ts" />
2
+ type $$ComponentProps = {
3
+ text: string;
4
+ };
5
+ declare const SafeBody: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SafeBody = ReturnType<typeof SafeBody>;
7
+ export default SafeBody;
@@ -0,0 +1,157 @@
1
+ <script lang="ts">
2
+ import { Card, CardContent, CardHeader, CardTitle } from 'texuli/card';
3
+ import { Accordion, Tabs } from 'texuli/tabs-accordion';
4
+ import { checkStructureBlock } from './check.js';
5
+ import type { StructureBlockViewProps } from './family.js';
6
+ import { resolveStructureBlockLabels } from './labels.js';
7
+ import NestedBlocks from './NestedBlocks.svelte';
8
+ import SafeBody from './SafeBody.svelte';
9
+ import './structure.css';
10
+
11
+ /**
12
+ * Structure family renderer: columns, grid, tabs, accordion, cards, divider
13
+ * and cta. Untrusted data is validated before anything paints.
14
+ */
15
+ let { block, labels, oninvalid }: StructureBlockViewProps = $props();
16
+
17
+ const check = $derived(checkStructureBlock(block));
18
+ const text = $derived(resolveStructureBlockLabels(labels));
19
+
20
+ $effect(() => {
21
+ if (!check.ok) oninvalid?.(check.issues);
22
+ });
23
+
24
+ function colsAttr(n: number | undefined, fallback: number): string {
25
+ return String(n ?? fallback);
26
+ }
27
+ </script>
28
+
29
+ {#if !check.ok}
30
+ <p class="tx-block-invalid" role="note" data-tx-block="invalid">{text.invalid}</p>
31
+ {:else}
32
+ {@const b = check.block}
33
+ {#if b.type === 'columns'}
34
+ <div
35
+ class="tx-structure-columns"
36
+ data-tx-block="columns"
37
+ data-cols={colsAttr(b.columns.length, 2)}
38
+ data-tone={b.tone}
39
+ role="group"
40
+ aria-label={text.columnsLabel}
41
+ >
42
+ {#each b.columns as column, c (c)}
43
+ <div class="tx-structure-column" data-column={c}>
44
+ <NestedBlocks blocks={column} labels={text} />
45
+ </div>
46
+ {/each}
47
+ </div>
48
+ {:else if b.type === 'grid'}
49
+ <section
50
+ class="tx-block"
51
+ data-tx-block="grid"
52
+ data-tone={b.tone}
53
+ aria-label={b.title ?? text.gridLabel}
54
+ >
55
+ {#if b.title}
56
+ <h3 class="tx-structure-section-title">{b.title}</h3>
57
+ {/if}
58
+ <div class="tx-structure-grid" data-cols={colsAttr(b.columns, 3)}>
59
+ {#each b.cells as cell, i (i)}
60
+ <article class="tx-structure-cell" data-tone={cell.tone}>
61
+ {#if cell.title}
62
+ <h4 class="tx-structure-cell-title">{cell.title}</h4>
63
+ {/if}
64
+ {#if cell.body}
65
+ <div class="tx-structure-cell-body"><SafeBody text={cell.body} /></div>
66
+ {/if}
67
+ </article>
68
+ {/each}
69
+ </div>
70
+ </section>
71
+ {:else if b.type === 'tabs'}
72
+ {@const items = b.panels.map((panel) => ({ value: panel.id, label: panel.label }))}
73
+ {@const initial = b.defaultPanel ?? b.panels[0]?.id ?? ''}
74
+ <div class="tx-structure-tabs" data-tx-block="tabs" data-tone={b.tone} data-block-id={b.id}>
75
+ <Tabs items={items} value={initial} label={b.label} variant="line">
76
+ {#snippet content(item)}
77
+ {@const panel = b.panels.find((p) => p.id === item.value)}
78
+ {#if panel}
79
+ <div class="tx-structure-panel">
80
+ <NestedBlocks blocks={panel.blocks} labels={text} />
81
+ </div>
82
+ {/if}
83
+ {/snippet}
84
+ </Tabs>
85
+ </div>
86
+ {:else if b.type === 'accordion'}
87
+ {@const items = b.items.map((item) => ({
88
+ value: item.id,
89
+ title: item.title,
90
+ content: item.body,
91
+ }))}
92
+ <div
93
+ class="tx-structure-accordion"
94
+ data-tx-block="accordion"
95
+ data-tone={b.tone}
96
+ data-block-id={b.id}
97
+ role="group"
98
+ aria-label={b.label}
99
+ >
100
+ <Accordion items={items} type={b.mode === 'multiple' ? 'multiple' : 'single'} variant="default">
101
+ {#snippet content(item)}
102
+ {@const source = b.items.find((i) => i.id === item.value)}
103
+ {#if source?.body}
104
+ <SafeBody text={source.body} />
105
+ {/if}
106
+ {/snippet}
107
+ </Accordion>
108
+ </div>
109
+ {:else if b.type === 'cards'}
110
+ <section
111
+ class="tx-block"
112
+ data-tx-block="cards"
113
+ data-tone={b.tone}
114
+ aria-label={b.title ?? text.cardsLabel}
115
+ >
116
+ {#if b.title}
117
+ <h3 class="tx-structure-section-title">{b.title}</h3>
118
+ {/if}
119
+ <ul class="tx-structure-cards" data-cols={colsAttr(b.columns, 3)}>
120
+ {#each b.items as card, i (i)}
121
+ <li>
122
+ <Card
123
+ variant="flat"
124
+ href={card.href}
125
+ hrefTrust="untrusted"
126
+ >
127
+ <CardHeader>
128
+ {#if card.eyebrow}
129
+ <p class="tx-structure-card-eyebrow">{card.eyebrow}</p>
130
+ {/if}
131
+ <CardTitle level={4}>{card.title}</CardTitle>
132
+ </CardHeader>
133
+ {#if card.body}
134
+ <CardContent>
135
+ <SafeBody text={card.body} />
136
+ </CardContent>
137
+ {/if}
138
+ </Card>
139
+ </li>
140
+ {/each}
141
+ </ul>
142
+ </section>
143
+ {:else if b.type === 'divider'}
144
+ <hr class="tx-structure-divider" data-tx-block="divider" data-tone={b.tone} />
145
+ {:else if b.type === 'cta'}
146
+ <nav
147
+ class="tx-structure-cta"
148
+ data-tx-block="cta"
149
+ data-tone={b.tone}
150
+ aria-label={text.ctaLabel}
151
+ >
152
+ {#each b.actions as action, i (i)}
153
+ <a href={action.href}>{action.label}</a>
154
+ {/each}
155
+ </nav>
156
+ {/if}
157
+ {/if}
@@ -0,0 +1,5 @@
1
+ /// <reference path="../css-modules.d.ts" />
2
+ import type { StructureBlockViewProps } from './family.js';
3
+ declare const StructureFamily: import("svelte").Component<StructureBlockViewProps, {}, "">;
4
+ type StructureFamily = ReturnType<typeof StructureFamily>;
5
+ export default StructureFamily;
@@ -0,0 +1,14 @@
1
+ import type { FieldIssue } from '../core/schema/primitives.js';
2
+ import type { StructureFamilyBlock } from './engine.js';
3
+ export type StructureBlockCheck = {
4
+ ok: true;
5
+ block: StructureFamilyBlock;
6
+ } | {
7
+ ok: false;
8
+ issues: FieldIssue[];
9
+ };
10
+ /**
11
+ * The only way data reaches a structure renderer: untrusted input runs through
12
+ * the block schemas, then must be one of the structure family types.
13
+ */
14
+ export declare function checkStructureBlock(value: unknown): StructureBlockCheck;
@@ -0,0 +1,27 @@
1
+ // Structure blocks nest any block, so they need the full multi-family parser.
2
+ import { safeParseBlock } from "../core/schema/blocks.js";
3
+ const STRUCTURE_TYPES = new Set([
4
+ 'columns',
5
+ 'grid',
6
+ 'tabs',
7
+ 'accordion',
8
+ 'cards',
9
+ 'divider',
10
+ 'cta',
11
+ ]);
12
+ /**
13
+ * The only way data reaches a structure renderer: untrusted input runs through
14
+ * the block schemas, then must be one of the structure family types.
15
+ */
16
+ export function checkStructureBlock(value) {
17
+ const parsed = safeParseBlock(value);
18
+ if (!parsed.success)
19
+ return { ok: false, issues: parsed.issues };
20
+ if (!STRUCTURE_TYPES.has(parsed.data.type)) {
21
+ return {
22
+ ok: false,
23
+ issues: [{ path: ['type'], message: `"${parsed.data.type}" is not a structure block` }],
24
+ };
25
+ }
26
+ return { ok: true, block: parsed.data };
27
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@texuli/blocks/structure/engine` — Svelte-free structure parsers, guards and labels.
3
+ */
4
+ import type { Block, ColumnsBlock, CtaBlock, DividerBlock, FieldIssue } from '../core/index.js';
5
+ import type { StructureBlockLabelOverrides } from './labels.js';
6
+ import type { AccordionBlock, CardsBlock, GridBlock, TabsBlock } from './schema.js';
7
+ export { checkStructureBlock, type StructureBlockCheck } from './check.js';
8
+ export { DEFAULT_STRUCTURE_BLOCK_LABELS, resolveStructureBlockLabels, type StructureBlockLabelOverrides, type StructureBlockLabels, } from './labels.js';
9
+ export { type AccordionBlock, type AccordionItem, type CardItem, type CardsBlock, type GridBlock, type GridCell, parseAccordionBlock, parseCardsBlock, parseGridBlock, parseStructureBlock, parseTabsBlock, STRUCTURE_OWNED_TYPES, type StructureChildParser, type StructureOwnedBlock, type StructureOwnedBlockType, type TabsBlock, type TabsPanel, } from './schema.js';
10
+ export declare const STRUCTURE_BLOCK_TYPES: readonly ["columns", "grid", "tabs", "accordion", "cards", "divider", "cta"];
11
+ export type StructureFamilyBlockType = (typeof STRUCTURE_BLOCK_TYPES)[number];
12
+ export type StructureFamilyBlock = ColumnsBlock | GridBlock | TabsBlock | AccordionBlock | CardsBlock | DividerBlock | CtaBlock;
13
+ export declare function isStructureFamilyBlock(block: Block): block is StructureFamilyBlock;
14
+ export interface StructureBlockViewProps {
15
+ block: unknown;
16
+ labels?: StructureBlockLabelOverrides;
17
+ oninvalid?: (issues: FieldIssue[]) => void;
18
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@texuli/blocks/structure/engine` — Svelte-free structure parsers, guards and labels.
3
+ */
4
+ export { checkStructureBlock } from "./check.js";
5
+ export { DEFAULT_STRUCTURE_BLOCK_LABELS, resolveStructureBlockLabels, } from "./labels.js";
6
+ export { parseAccordionBlock, parseCardsBlock, parseGridBlock, parseStructureBlock, parseTabsBlock, STRUCTURE_OWNED_TYPES, } from "./schema.js";
7
+ export const STRUCTURE_BLOCK_TYPES = [
8
+ 'columns',
9
+ 'grid',
10
+ 'tabs',
11
+ 'accordion',
12
+ 'cards',
13
+ 'divider',
14
+ 'cta',
15
+ ];
16
+ export function isStructureFamilyBlock(block) {
17
+ return STRUCTURE_BLOCK_TYPES.includes(block.type);
18
+ }
@@ -0,0 +1,7 @@
1
+ import type { Component } from 'svelte';
2
+ import type { StructureBlockViewProps } from './engine.js';
3
+ export { isStructureFamilyBlock, STRUCTURE_BLOCK_TYPES, type StructureBlockViewProps, type StructureFamilyBlock, type StructureFamilyBlockType, } from './engine.js';
4
+ /** Lazily loads the structure family renderer (its own chunk). */
5
+ export declare function loadStructureFamily(): Promise<{
6
+ default: Component<StructureBlockViewProps>;
7
+ }>;
@@ -0,0 +1,5 @@
1
+ export { isStructureFamilyBlock, STRUCTURE_BLOCK_TYPES, } from "./engine.js";
2
+ /** Lazily loads the structure family renderer (its own chunk). */
3
+ export function loadStructureFamily() {
4
+ return import('./StructureFamily.svelte');
5
+ }
@@ -0,0 +1,11 @@
1
+ /// <reference path="../css-modules.d.ts" />
2
+ /**
3
+ * `@texuli/blocks` structure family (T-blocks-structure): columns, grid, tabs,
4
+ * accordion, cards, divider and cta. No Svelte is imported eagerly: renderers
5
+ * load through `loadStructureFamily()` (its own chunk) or directly from
6
+ * `@texuli/blocks/structure/renderers`.
7
+ */
8
+ export { checkStructureBlock, type StructureBlockCheck } from './check.js';
9
+ export { isStructureFamilyBlock, loadStructureFamily, STRUCTURE_BLOCK_TYPES, type StructureBlockViewProps, type StructureFamilyBlock, type StructureFamilyBlockType, } from './family.js';
10
+ export { DEFAULT_STRUCTURE_BLOCK_LABELS, resolveStructureBlockLabels, type StructureBlockLabelOverrides, type StructureBlockLabels, } from './labels.js';
11
+ export { type AccordionBlock, type AccordionItem, type CardItem, type CardsBlock, type GridBlock, type GridCell, parseAccordionBlock, parseCardsBlock, parseGridBlock, parseStructureBlock, parseTabsBlock, STRUCTURE_OWNED_TYPES, type StructureChildParser, type StructureOwnedBlock, type StructureOwnedBlockType, type TabsBlock, type TabsPanel, } from './schema.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@texuli/blocks` structure family (T-blocks-structure): columns, grid, tabs,
3
+ * accordion, cards, divider and cta. No Svelte is imported eagerly: renderers
4
+ * load through `loadStructureFamily()` (its own chunk) or directly from
5
+ * `@texuli/blocks/structure/renderers`.
6
+ */
7
+ export { checkStructureBlock } from "./check.js";
8
+ export { isStructureFamilyBlock, loadStructureFamily, STRUCTURE_BLOCK_TYPES, } from "./family.js";
9
+ export { DEFAULT_STRUCTURE_BLOCK_LABELS, resolveStructureBlockLabels, } from "./labels.js";
10
+ export { parseAccordionBlock, parseCardsBlock, parseGridBlock, parseStructureBlock, parseTabsBlock, STRUCTURE_OWNED_TYPES, } from "./schema.js";
@@ -0,0 +1,11 @@
1
+ export interface StructureBlockLabels {
2
+ invalid: string;
3
+ emptyPanel: string;
4
+ cardsLabel: string;
5
+ gridLabel: string;
6
+ columnsLabel: string;
7
+ ctaLabel: string;
8
+ }
9
+ export type StructureBlockLabelOverrides = Partial<StructureBlockLabels>;
10
+ export declare const DEFAULT_STRUCTURE_BLOCK_LABELS: StructureBlockLabels;
11
+ export declare function resolveStructureBlockLabels(overrides?: StructureBlockLabelOverrides): StructureBlockLabels;
@@ -0,0 +1,11 @@
1
+ export const DEFAULT_STRUCTURE_BLOCK_LABELS = {
2
+ invalid: 'This block could not be shown.',
3
+ emptyPanel: 'This panel is empty.',
4
+ cardsLabel: 'Cards',
5
+ gridLabel: 'Grid',
6
+ columnsLabel: 'Columns',
7
+ ctaLabel: 'Actions',
8
+ };
9
+ export function resolveStructureBlockLabels(overrides) {
10
+ return { ...DEFAULT_STRUCTURE_BLOCK_LABELS, ...overrides };
11
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Eager structure family renderers. Prefer `loadStructureFamily()` from
3
+ * `@texuli/blocks/structure` (lazy chunk) in app code.
4
+ */
5
+ export { default as StructureFamily } from './StructureFamily.svelte';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Eager structure family renderers. Prefer `loadStructureFamily()` from
3
+ * `@texuli/blocks/structure` (lazy chunk) in app code.
4
+ */
5
+ export { default as StructureFamily } from './StructureFamily.svelte';
@@ -0,0 +1,71 @@
1
+ import type { Block } from '../core/schema/blocks.js';
2
+ import { type BlockBase, type ParseResult } from '../core/schema/primitives.js';
3
+ import { type BlockTone } from '../core/schema/vocabulary.js';
4
+ export type StructureChildParser = (value: unknown, path: (string | number)[], containerDepth: number) => ParseResult<Block[]>;
5
+ export type TabsPanel = {
6
+ id: string;
7
+ label: string;
8
+ blocks: Block[];
9
+ };
10
+ export type TabsBlock = BlockBase & {
11
+ type: 'tabs';
12
+ /** Stable id for the tab list (deep links and state). */
13
+ id: string;
14
+ /** Accessible name of the tab list. */
15
+ label: string;
16
+ panels: TabsPanel[];
17
+ defaultPanel?: string;
18
+ };
19
+ export type AccordionItem = {
20
+ id: string;
21
+ title: string;
22
+ /** Markdown body (closed subset via RichText). */
23
+ body?: string;
24
+ };
25
+ export type AccordionBlock = BlockBase & {
26
+ type: 'accordion';
27
+ id?: string;
28
+ /** Accessible name for the accordion group. */
29
+ label?: string;
30
+ /** single (default) or multiple open items. */
31
+ mode?: 'single' | 'multiple';
32
+ items: AccordionItem[];
33
+ };
34
+ export type CardItem = {
35
+ title: string;
36
+ eyebrow?: string;
37
+ body?: string;
38
+ href?: string;
39
+ tone?: BlockTone;
40
+ };
41
+ export type CardsBlock = BlockBase & {
42
+ type: 'cards';
43
+ title?: string;
44
+ /** Preferred column count on wide viewports (2–4). */
45
+ columns?: 2 | 3 | 4;
46
+ items: CardItem[];
47
+ };
48
+ export type GridCell = {
49
+ title?: string;
50
+ body?: string;
51
+ tone?: BlockTone;
52
+ };
53
+ export type GridBlock = BlockBase & {
54
+ type: 'grid';
55
+ title?: string;
56
+ /** Preferred column count on wide viewports (2–4). */
57
+ columns?: 2 | 3 | 4;
58
+ cells: GridCell[];
59
+ };
60
+ export type StructureOwnedBlock = TabsBlock | AccordionBlock | CardsBlock | GridBlock;
61
+ export type StructureOwnedBlockType = StructureOwnedBlock['type'];
62
+ export declare const STRUCTURE_OWNED_TYPES: readonly ["tabs", "accordion", "cards", "grid"];
63
+ export declare function parseTabsBlock(record: Record<string, unknown>, path: (string | number)[], base: BlockBase, containerDepth: number, parseChildren: StructureChildParser): ParseResult<TabsBlock>;
64
+ export declare function parseAccordionBlock(record: Record<string, unknown>, path: (string | number)[], base: BlockBase): ParseResult<AccordionBlock>;
65
+ export declare function parseCardsBlock(record: Record<string, unknown>, path: (string | number)[], base: BlockBase): ParseResult<CardsBlock>;
66
+ export declare function parseGridBlock(record: Record<string, unknown>, path: (string | number)[], base: BlockBase): ParseResult<GridBlock>;
67
+ /**
68
+ * Dispatch for structure-owned types. Returns null when `type` is not owned
69
+ * here (core handles columns/divider/cta).
70
+ */
71
+ export declare function parseStructureBlock(type: string, record: Record<string, unknown>, path: (string | number)[], base: BlockBase, containerDepth: number, parseChildren: StructureChildParser): ParseResult<StructureOwnedBlock> | null;