@texuli/deck 0.0.0-stage → 0.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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -2
  3. package/dist/authoring/builder.d.ts +351 -0
  4. package/dist/authoring/builder.js +64 -0
  5. package/dist/authoring/index.d.ts +6 -0
  6. package/dist/authoring/index.js +6 -0
  7. package/dist/authoring/lint.d.ts +36 -0
  8. package/dist/authoring/lint.js +325 -0
  9. package/dist/authoring/outlines.d.ts +34 -0
  10. package/dist/authoring/outlines.js +255 -0
  11. package/dist/authoring/patch.d.ts +23 -0
  12. package/dist/authoring/patch.js +166 -0
  13. package/dist/authoring/select-layouts.d.ts +19 -0
  14. package/dist/authoring/select-layouts.js +90 -0
  15. package/dist/engine/auto-slide.d.ts +32 -0
  16. package/dist/engine/auto-slide.js +67 -0
  17. package/dist/engine/canvas.d.ts +56 -0
  18. package/dist/engine/canvas.js +48 -0
  19. package/dist/engine/controller.d.ts +126 -0
  20. package/dist/engine/controller.js +248 -0
  21. package/dist/engine/controls.d.ts +128 -0
  22. package/dist/engine/controls.js +460 -0
  23. package/dist/engine/hash.d.ts +42 -0
  24. package/dist/engine/hash.js +86 -0
  25. package/dist/engine/index.d.ts +88 -0
  26. package/dist/engine/index.js +88 -0
  27. package/dist/engine/messages.d.ts +138 -0
  28. package/dist/engine/messages.js +140 -0
  29. package/dist/engine/motion.d.ts +64 -0
  30. package/dist/engine/motion.js +112 -0
  31. package/dist/engine/navigation.d.ts +87 -0
  32. package/dist/engine/navigation.js +156 -0
  33. package/dist/engine/presenter.d.ts +58 -0
  34. package/dist/engine/presenter.js +157 -0
  35. package/dist/index.d.ts +9 -0
  36. package/dist/index.js +9 -0
  37. package/dist/markdown/attributes.d.ts +24 -0
  38. package/dist/markdown/attributes.js +94 -0
  39. package/dist/markdown/attrs-closed.d.ts +52 -0
  40. package/dist/markdown/attrs-closed.js +520 -0
  41. package/dist/markdown/blocks.d.ts +112 -0
  42. package/dist/markdown/blocks.js +766 -0
  43. package/dist/markdown/frontmatter.d.ts +30 -0
  44. package/dist/markdown/frontmatter.js +165 -0
  45. package/dist/markdown/index.d.ts +29 -0
  46. package/dist/markdown/index.js +29 -0
  47. package/dist/markdown/issues.d.ts +68 -0
  48. package/dist/markdown/issues.js +83 -0
  49. package/dist/markdown/json.d.ts +21 -0
  50. package/dist/markdown/json.js +236 -0
  51. package/dist/markdown/limits.d.ts +32 -0
  52. package/dist/markdown/limits.js +32 -0
  53. package/dist/markdown/parse.d.ts +31 -0
  54. package/dist/markdown/parse.js +283 -0
  55. package/dist/markdown/scan.d.ts +26 -0
  56. package/dist/markdown/scan.js +138 -0
  57. package/dist/markdown/slides.d.ts +32 -0
  58. package/dist/markdown/slides.js +854 -0
  59. package/dist/markdown/text.d.ts +76 -0
  60. package/dist/markdown/text.js +447 -0
  61. package/dist/markdown/twin.d.ts +41 -0
  62. package/dist/markdown/twin.js +374 -0
  63. package/dist/markdown/write.d.ts +17 -0
  64. package/dist/markdown/write.js +688 -0
  65. package/dist/math/index.d.ts +14 -0
  66. package/dist/math/index.js +14 -0
  67. package/dist/math/parse.d.ts +9 -0
  68. package/dist/math/parse.js +648 -0
  69. package/dist/math/symbols.d.ts +44 -0
  70. package/dist/math/symbols.js +333 -0
  71. package/dist/math/types.d.ts +59 -0
  72. package/dist/math/types.js +69 -0
  73. package/dist/model/backgrounds/contrast.d.ts +77 -0
  74. package/dist/model/backgrounds/contrast.js +186 -0
  75. package/dist/model/backgrounds/index.d.ts +7 -0
  76. package/dist/model/backgrounds/index.js +7 -0
  77. package/dist/model/backgrounds/resolve.d.ts +86 -0
  78. package/dist/model/backgrounds/resolve.js +215 -0
  79. package/dist/model/backgrounds/schema.d.ts +178 -0
  80. package/dist/model/backgrounds/schema.js +203 -0
  81. package/dist/model/codes.d.ts +73 -0
  82. package/dist/model/codes.js +67 -0
  83. package/dist/model/deck.d.ts +392 -0
  84. package/dist/model/deck.js +338 -0
  85. package/dist/model/index.d.ts +19 -0
  86. package/dist/model/index.js +19 -0
  87. package/dist/model/layouts/catalog.d.ts +433 -0
  88. package/dist/model/layouts/catalog.js +495 -0
  89. package/dist/model/layouts/index.d.ts +5 -0
  90. package/dist/model/layouts/index.js +5 -0
  91. package/dist/model/layouts/props.d.ts +302 -0
  92. package/dist/model/layouts/props.js +272 -0
  93. package/dist/model/loader.d.ts +37 -0
  94. package/dist/model/loader.js +317 -0
  95. package/dist/model/migrate.d.ts +34 -0
  96. package/dist/model/migrate.js +453 -0
  97. package/dist/model/motion/index.d.ts +6 -0
  98. package/dist/model/motion/index.js +6 -0
  99. package/dist/model/motion/transition.d.ts +96 -0
  100. package/dist/model/motion/transition.js +127 -0
  101. package/dist/model/parse.d.ts +57 -0
  102. package/dist/model/parse.js +307 -0
  103. package/dist/model/plain.d.ts +40 -0
  104. package/dist/model/plain.js +275 -0
  105. package/dist/model/shared.d.ts +63 -0
  106. package/dist/model/shared.js +86 -0
  107. package/dist/model/steps/index.d.ts +7 -0
  108. package/dist/model/steps/index.js +7 -0
  109. package/dist/model/steps/phase.d.ts +34 -0
  110. package/dist/model/steps/phase.js +50 -0
  111. package/dist/model/steps/slide-steps.d.ts +129 -0
  112. package/dist/model/steps/slide-steps.js +256 -0
  113. package/dist/model/style/index.d.ts +8 -0
  114. package/dist/model/style/index.js +8 -0
  115. package/dist/model/style/resolve.d.ts +87 -0
  116. package/dist/model/style/resolve.js +174 -0
  117. package/dist/model/style/schema.d.ts +85 -0
  118. package/dist/model/style/schema.js +53 -0
  119. package/dist/model/style/theme.d.ts +69 -0
  120. package/dist/model/style/theme.js +84 -0
  121. package/dist/schema/builders.d.ts +146 -0
  122. package/dist/schema/builders.js +285 -0
  123. package/dist/schema/diagnostics.d.ts +78 -0
  124. package/dist/schema/diagnostics.js +81 -0
  125. package/dist/schema/index.d.ts +15 -0
  126. package/dist/schema/index.js +14 -0
  127. package/dist/schema/internal.d.ts +40 -0
  128. package/dist/schema/internal.js +209 -0
  129. package/dist/schema/json-schema.d.ts +12 -0
  130. package/dist/schema/json-schema.js +213 -0
  131. package/dist/schema/types.d.ts +166 -0
  132. package/dist/schema/types.js +1 -0
  133. package/dist/schema/validate.d.ts +51 -0
  134. package/dist/schema/validate.js +765 -0
  135. package/dist/templates/index.d.ts +2 -0
  136. package/dist/templates/index.js +2 -0
  137. package/dist/templates/templates.d.ts +11 -0
  138. package/dist/templates/templates.js +47 -0
  139. package/package.json +60 -4
@@ -0,0 +1,338 @@
1
+ /**
2
+ * The deck model v2 schemas (ADR-009, model-v2 §3.1, §3.2): `meta`, `style`,
3
+ * `navigation`, `outline` and 1 to 200 slides discriminated by `layout` (the
4
+ * 20 catalog layouts, `free`, `component` and `custom`), with
5
+ * `formatVersion: 2`. Built on the owned schema kit, so one definition gives
6
+ * all-issues validation, the `Infer` types and the JSON Schema export.
7
+ *
8
+ * The engine is L0: blocks and component props are checked here only as
9
+ * bounded JSON with a block head (`type`, `id`, step fields) and known
10
+ * nested containers. `parseDeck` hands each block and component to the
11
+ * host's validators (the slides stage injects `@texuli/blocks` and the
12
+ * component registry).
13
+ */
14
+ import { t, toJsonSchema, validate, } from "../schema/index.js";
15
+ import { deckBackground } from "./backgrounds/schema.js";
16
+ import { DECK_CATALOG_LAYOUT_IDS, DECK_LAYOUTS, layoutPropsSchema, } from "./layouts/catalog.js";
17
+ import { componentProps, deckTransition, FRAGMENT_TONES, line, MAX_SLIDE_STEPS, multiline, SLIDE_ID_PATTERN, slideId, stepFields, } from "./shared.js";
18
+ import { deckStyle } from "./style/schema.js";
19
+ import { deckThemeReference, styleSlug } from "./style/theme.js";
20
+ /** The model's format version. */
21
+ export const DECK_FORMAT_VERSION = 2;
22
+ /** Deck-level limits (model-v2 §3.2). */
23
+ export const DECK_MODEL_LIMITS = Object.freeze({
24
+ slides: 200,
25
+ sections: 200,
26
+ sectionSlides: 200,
27
+ titleLength: 200,
28
+ descriptionLength: 500,
29
+ authorLength: 120,
30
+ outlineTitleLength: 120,
31
+ notesLength: 6_000,
32
+ stepLabels: MAX_SLIDE_STEPS,
33
+ stepLabelLength: 80,
34
+ freeBlocks: 2_000,
35
+ autoSlideMinMs: 1_000,
36
+ autoSlideMaxMs: 600_000,
37
+ });
38
+ /** v1's eight free-slide arrangements (`master` on a free slide; default `content`). */
39
+ export const SLIDE_MASTERS = [
40
+ 'title',
41
+ 'section',
42
+ 'content',
43
+ 'two-column',
44
+ 'media-left',
45
+ 'media-right',
46
+ 'quote',
47
+ 'closing',
48
+ ];
49
+ /** The slide kinds that are not catalog layouts. */
50
+ export const DECK_SLIDE_KINDS = ['free', 'component', 'custom'];
51
+ /** Every `layout` value a slide may carry: 20 catalog layouts and the 3 slide kinds. */
52
+ export const DECK_SLIDE_LAYOUTS = Object.freeze([
53
+ ...DECK_CATALOG_LAYOUT_IDS,
54
+ ...DECK_SLIDE_KINDS,
55
+ ]);
56
+ export const DECK_DIRECTIONS = ['ltr', 'rtl', 'auto'];
57
+ /** BCP 47 language tag syntax (`en`, `en-GB`, `pt-BR`). */
58
+ export const LOCALE_PATTERN = '^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$';
59
+ /** An ISO 8601 calendar date. */
60
+ export const ISO_DATE_PATTERN = '^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])$';
61
+ /** Block `type` and catalog component ids: a lowercase slug starting with a letter. */
62
+ export const BLOCK_TYPE_PATTERN = '^[a-z][a-z0-9-]{0,63}$';
63
+ export const COMPONENT_ID_PATTERN = BLOCK_TYPE_PATTERN;
64
+ /** Block tones (`@texuli/blocks` `BLOCK_TONES`): the fragment tones plus `neutral`. */
65
+ export const BLOCK_TONES = ['neutral', ...FRAGMENT_TONES];
66
+ /** Component item heights. */
67
+ export const COMPONENT_HEIGHTS = ['sm', 'md', 'lg', 'fill'];
68
+ /** Bounds for one free-slide item (the outer guard; the host block parser is tighter). */
69
+ export const BLOCK_JSON_LIMITS = Object.freeze({
70
+ depth: 12,
71
+ maxKeys: 100,
72
+ maxItems: 2_000,
73
+ /** Room for a 192 KB base64 `data:` image; code and markdown are far smaller. */
74
+ maxString: 300_000,
75
+ maxKeyLength: 64,
76
+ });
77
+ /**
78
+ * Where nested blocks live inside a block, as the steps pass walks them:
79
+ * `blocks` and `children` (lists of blocks), `columns` (a list of lists) and
80
+ * `panels` (objects with `blocks`).
81
+ */
82
+ export const BLOCK_CONTAINERS = Object.freeze({
83
+ lists: ['blocks', 'children'],
84
+ columns: 'columns',
85
+ panels: 'panels',
86
+ });
87
+ /** The step fields every slide item may carry (removed before the host block parser). */
88
+ export const STEP_FIELD_KEYS = ['step', 'fragment', 'reveal'];
89
+ // ---------------------------------------------------------------------------
90
+ // Meta, navigation, outline
91
+ export const deckMeta = t.object({
92
+ title: line(DECK_MODEL_LIMITS.titleLength, 1),
93
+ description: t.optional(line(DECK_MODEL_LIMITS.descriptionLength)),
94
+ locale: t.optional(t.string({ pattern: LOCALE_PATTERN, max: 35 })),
95
+ dir: t.optional(t.enum(DECK_DIRECTIONS), { description: "Default 'auto'." }),
96
+ author: t.optional(line(DECK_MODEL_LIMITS.authorLength)),
97
+ date: t.optional(t.string({ pattern: ISO_DATE_PATTERN, max: 10 })),
98
+ }, { description: 'Deck metadata: title, description, language, direction, author and date.' });
99
+ const autoSlideMs = t.refine(t.int({ min: 0, max: DECK_MODEL_LIMITS.autoSlideMaxMs }), {
100
+ code: 'out-of-range',
101
+ description: '0 turns auto-slide off; otherwise 1000 to 600000 ms.',
102
+ check(value, context) {
103
+ if (value !== 0 && value < DECK_MODEL_LIMITS.autoSlideMinMs) {
104
+ context.issue({
105
+ message: `Use 0 (off) or at least ${DECK_MODEL_LIMITS.autoSlideMinMs} ms.`,
106
+ fix: `Set 0, or a value from ${DECK_MODEL_LIMITS.autoSlideMinMs} to ${DECK_MODEL_LIMITS.autoSlideMaxMs}.`,
107
+ });
108
+ }
109
+ },
110
+ });
111
+ export const deckNavigation = t.object({
112
+ start: t.optional(slideId, {
113
+ description: 'The first slide shown (a visible slide id).',
114
+ }),
115
+ loop: t.optional(t.boolean(), { description: 'Default false.' }),
116
+ autoSlideMs: t.optional(autoSlideMs, { description: 'Default 0 (off).' }),
117
+ }, { description: 'Navigation: start slide, looping and auto-slide.' });
118
+ export const deckSection = t.object({
119
+ id: slideId,
120
+ title: t.optional(line(DECK_MODEL_LIMITS.outlineTitleLength)),
121
+ slideIds: t.array(slideId, { min: 1, max: DECK_MODEL_LIMITS.sectionSlides }),
122
+ stack: t.optional(t.boolean(), {
123
+ description: 'A vertical stack: the slides are adjacent in `slides` and navigate in 2D.',
124
+ }),
125
+ });
126
+ export const deckOutline = t.object({
127
+ title: t.optional(line(DECK_MODEL_LIMITS.outlineTitleLength)),
128
+ sections: t.array(deckSection, { min: 1, max: DECK_MODEL_LIMITS.sections }),
129
+ }, {
130
+ description: 'Sections of slide ids: every id is a known slide, and a slide is in at most one section.',
131
+ });
132
+ const blockHead = t.object({
133
+ type: t.string({ pattern: BLOCK_TYPE_PATTERN, max: 64 }),
134
+ id: t.optional(slideId),
135
+ step: stepFields.step,
136
+ fragment: stepFields.fragment,
137
+ reveal: stepFields.reveal,
138
+ tone: t.optional(t.enum(BLOCK_TONES)),
139
+ }, { strict: false });
140
+ const objectProps = (description) => t.refine(componentProps, {
141
+ code: 'invalid-type',
142
+ description,
143
+ check(value, context) {
144
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
145
+ context.issue({ message: 'Expected an object.', fix: 'Pass the props as a JSON object.' });
146
+ }
147
+ },
148
+ });
149
+ /** A catalog component placed inline on a free slide. */
150
+ export const componentItem = t.object({
151
+ type: t.literal('component'),
152
+ id: t.optional(slideId),
153
+ component: t.string({ pattern: COMPONENT_ID_PATTERN, max: 64 }),
154
+ props: objectProps('Component props (bounded JSON), validated by the host registry.'),
155
+ height: t.optional(t.enum(COMPONENT_HEIGHTS)),
156
+ ...stepFields,
157
+ });
158
+ function isBag(value) {
159
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
160
+ }
161
+ /** Check one item's head, then its nested blocks, reporting through the refine context. */
162
+ function checkItem(value, path, context, depth) {
163
+ if (!isBag(value)) {
164
+ context.issue({
165
+ code: 'invalid-block',
166
+ path,
167
+ message: 'A slide item is a block object with a `type`.',
168
+ fix: 'Use {"type": "text", "markdown": "…"} or {"type": "component", "component": "…", "props": {}}.',
169
+ });
170
+ return;
171
+ }
172
+ const isComponent = value.type === 'component';
173
+ const head = {};
174
+ if (isComponent)
175
+ Object.assign(head, value);
176
+ else {
177
+ for (const key of ['type', 'id', 'step', 'fragment', 'reveal', 'tone']) {
178
+ if (Object.hasOwn(value, key))
179
+ head[key] = value[key];
180
+ }
181
+ }
182
+ const result = validate(isComponent ? componentItem : blockHead, head, { maxIssues: 50 });
183
+ for (const issue of result.issues) {
184
+ context.issue({
185
+ code: issue.code,
186
+ path: [...path, ...issue.path],
187
+ message: issue.message,
188
+ ...(issue.fix === undefined ? {} : { fix: issue.fix }),
189
+ severity: issue.severity,
190
+ });
191
+ }
192
+ if (isComponent || depth >= BLOCK_JSON_LIMITS.depth)
193
+ return;
194
+ forEachNested(value, path, (child, childPath) => {
195
+ checkItem(child, childPath, context, depth + 1);
196
+ });
197
+ }
198
+ /** Visit the nested blocks of a block (see `BLOCK_CONTAINERS`). */
199
+ export function forEachNested(block, path, visit) {
200
+ for (const key of BLOCK_CONTAINERS.lists) {
201
+ const list = block[key];
202
+ if (Array.isArray(list)) {
203
+ list.forEach((child, i) => {
204
+ visit(child, [...path, key, i]);
205
+ });
206
+ }
207
+ }
208
+ const columns = block[BLOCK_CONTAINERS.columns];
209
+ if (Array.isArray(columns)) {
210
+ columns.forEach((column, c) => {
211
+ if (Array.isArray(column)) {
212
+ column.forEach((child, i) => {
213
+ visit(child, [...path, BLOCK_CONTAINERS.columns, c, i]);
214
+ });
215
+ }
216
+ });
217
+ }
218
+ const panels = block[BLOCK_CONTAINERS.panels];
219
+ if (Array.isArray(panels)) {
220
+ panels.forEach((panel, p) => {
221
+ if (isBag(panel) && Array.isArray(panel.blocks)) {
222
+ panel.blocks.forEach((child, i) => {
223
+ visit(child, [...path, BLOCK_CONTAINERS.panels, p, 'blocks', i]);
224
+ });
225
+ }
226
+ });
227
+ }
228
+ }
229
+ /**
230
+ * One free-slide item: a block (any `@texuli/blocks` block, checked here as
231
+ * bounded JSON with a block head) or a component item. Nested blocks in the
232
+ * known containers get the same head check.
233
+ */
234
+ export const slideItem = t.refine(t.json({ ...BLOCK_JSON_LIMITS, text: 'multiline' }), {
235
+ code: 'invalid-block',
236
+ description: 'A block object: `type` (lowercase slug), optional `id` (slide id rules), step fields (`step` 0..1000, `fragment` or `reveal`, `tone`), nested blocks in `blocks`, `children`, `columns` and `panels[].blocks`; or a component item {type: "component", component, props, height?}. The host validates block bodies and component props.',
237
+ check(value, context) {
238
+ checkItem(value, [], context, 0);
239
+ },
240
+ });
241
+ // ---------------------------------------------------------------------------
242
+ // Slides
243
+ const slideBaseShape = {
244
+ id: slideId,
245
+ title: t.optional(line(DECK_MODEL_LIMITS.titleLength)),
246
+ notes: t.optional(multiline(DECK_MODEL_LIMITS.notesLength), {
247
+ description: 'Speaker notes (multi-line, at most 6000 code points).',
248
+ }),
249
+ hidden: t.optional(t.boolean(), {
250
+ description: 'Hidden slides stay out of navigation and print unless the host asks.',
251
+ }),
252
+ background: t.optional(deckBackground),
253
+ transition: t.optional(deckTransition),
254
+ theme: t.optional(deckThemeReference),
255
+ steps: t.optional(t.array(line(DECK_MODEL_LIMITS.stepLabelLength), { max: DECK_MODEL_LIMITS.stepLabels }), { description: 'Step labels: `steps[i]` names step i + 1.' }),
256
+ };
257
+ const slideBase = t.object(slideBaseShape);
258
+ function layoutSlide(id) {
259
+ return t.object({
260
+ ...slideBaseShape,
261
+ layout: t.literal(id),
262
+ props: t.lazy(() => layoutPropsSchema(id), {
263
+ name: id,
264
+ description: `Props of the ${DECK_LAYOUTS[id].label.toLowerCase()} layout.`,
265
+ }),
266
+ }, { description: DECK_LAYOUTS[id].description });
267
+ }
268
+ export const freeSlide = t.object({
269
+ ...slideBaseShape,
270
+ layout: t.literal('free'),
271
+ master: t.optional(t.enum(SLIDE_MASTERS), { description: "Default 'content'." }),
272
+ blocks: t.array(slideItem, { max: DECK_MODEL_LIMITS.freeBlocks }),
273
+ }, { description: 'A free slide: blocks and component items, arranged by a master.' });
274
+ export const componentSlide = t.object({
275
+ ...slideBaseShape,
276
+ layout: t.literal('component'),
277
+ component: t.object({
278
+ component: t.string({ pattern: COMPONENT_ID_PATTERN, max: 64 }),
279
+ props: objectProps('Component props (bounded JSON), validated by the host registry.'),
280
+ }),
281
+ }, { description: 'A whole-slide catalog component.' });
282
+ export const customSlide = t.object({
283
+ ...slideBaseShape,
284
+ layout: t.literal('custom'),
285
+ props: t.object({
286
+ component: styleSlug,
287
+ data: t.optional(objectProps('Data for the host component (bounded JSON).')),
288
+ }),
289
+ }, { description: 'A slide painted by a host-registered component (a placeholder without one).' });
290
+ const SLIDE_OPTIONS = [
291
+ ...DECK_CATALOG_LAYOUT_IDS.map(layoutSlide),
292
+ freeSlide,
293
+ componentSlide,
294
+ customSlide,
295
+ ];
296
+ /** One slide, told apart by `layout` (an unknown one is a single `unknown-layout` issue). */
297
+ export const deckSlide = t.union(SLIDE_OPTIONS, {
298
+ discriminator: 'layout',
299
+ code: 'unknown-layout',
300
+ description: 'A slide: a catalog layout with props, a free slide, a component or a custom slide.',
301
+ });
302
+ // ---------------------------------------------------------------------------
303
+ // The deck
304
+ const deckShape = {
305
+ formatVersion: t.literal(DECK_FORMAT_VERSION, {
306
+ code: 'unsupported-version',
307
+ description: 'Always 2. Older decks convert with migrateDeck.',
308
+ }),
309
+ meta: deckMeta,
310
+ style: t.optional(deckStyle),
311
+ navigation: t.optional(deckNavigation),
312
+ outline: t.optional(deckOutline),
313
+ slides: t.array(deckSlide, { min: 1, max: DECK_MODEL_LIMITS.slides }),
314
+ };
315
+ /** The deck document (model v2). Cross-field rules run in `parseDeck`. */
316
+ export const deckSchema = t.object(deckShape, {
317
+ description: 'A Texuli deck (model v2). Cross-field rules checked by parseDeck: unique slide, section and block ids; sections name known slides, each slide in at most one section, stack sections contiguous; navigation.start is a visible slide; not every slide hidden; at most 32 steps per slide; fragment and reveal exclusive.',
318
+ });
319
+ /** True when `layout` is a slide kind or a catalog layout id. */
320
+ export function isSlideLayout(value) {
321
+ return typeof value === 'string' && DECK_SLIDE_LAYOUTS.includes(value);
322
+ }
323
+ /** True when `value` matches the slide id syntax. */
324
+ export function isSlideId(value) {
325
+ return typeof value === 'string' && new RegExp(SLIDE_ID_PATTERN, 'u').test(value);
326
+ }
327
+ /**
328
+ * The JSON Schema 2020-12 document of a v2 deck, generated from the same
329
+ * schemas `parseDeck` validates with. Each catalog layout's props is a
330
+ * `$defs` entry named by its id (`#/$defs/title-hero`); cross-field rules are
331
+ * described in `description`.
332
+ */
333
+ export function deckJsonSchema(options = {}) {
334
+ return toJsonSchema(deckSchema, {
335
+ ...(options.$id === undefined ? {} : { $id: options.$id }),
336
+ title: 'Texuli deck (model v2)',
337
+ });
338
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @texuli/deck/model: deck model v2 (ADR-009). The schemas and types,
3
+ * `parseDeck` with all-issues diagnostics, the untrusted loader, migration
4
+ * from Texuli v1, grammar trees and Protoyard, the JSON Schema export, the
5
+ * diagnostic code table, and the leaf modules: layouts, style and themes,
6
+ * backgrounds, steps and motion.
7
+ */
8
+ export * from './backgrounds/index.js';
9
+ export { DECK_CODES, type DeckCode, type DeckCodeInfo, type DeckIssue, isDeckCode, } from './codes.js';
10
+ export { BLOCK_CONTAINERS, BLOCK_JSON_LIMITS, BLOCK_TONES, BLOCK_TYPE_PATTERN, COMPONENT_HEIGHTS, COMPONENT_ID_PATTERN, type ComponentSlide, type CustomSlide, componentItem, componentSlide, customSlide, DECK_DIRECTIONS, DECK_FORMAT_VERSION, DECK_MODEL_LIMITS, DECK_SLIDE_KINDS, DECK_SLIDE_LAYOUTS, type Deck, type DeckDirection, type DeckJsonSchemaOptions, type DeckMeta, type DeckNavigation, type DeckOutline, type DeckSection, type DeckSlide, type DeckSlideKind, type DeckSlideLayout, deckJsonSchema, deckMeta, deckNavigation, deckOutline, deckSchema, deckSection, deckSlide, type FreeSlide, forEachNested, freeSlide, ISO_DATE_PATTERN, isSlideId, isSlideLayout, type JsonObject, type LayoutSlide, LOCALE_PATTERN, SLIDE_MASTERS, type SlideBase, type SlideBlock, type SlideComponentItem, type SlideItem, type SlideMasterId, STEP_FIELD_KEYS, slideItem, } from './deck.js';
11
+ export * from './layouts/index.js';
12
+ export { DEFAULT_UNTRUSTED_DECK_LIMITS, jsonErrorOffset, jsonTextDepth, lineColumn, loadUntrustedDeck, type UntrustedDeckLimits, type UntrustedDeckOptions, type UntrustedDeckResult, } from './loader.js';
13
+ export { DECK_SOURCES, type DeckMigration, type DeckSource, detectDeckShape, migrateDeck, } from './migrate.js';
14
+ export * from './motion/index.js';
15
+ export { type DeckComponentInput, type DeckHookResult, type DeckParseResult, type ParseDeckOptions, parseDeck, stripStepFields, } from './parse.js';
16
+ export { copyPlainData, type PlainCopyOptions, type PlainCopyResult, stripHiddenCharacters, utf8Length, } from './plain.js';
17
+ export * from './shared.js';
18
+ export * from './steps/index.js';
19
+ export * from './style/index.js';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @texuli/deck/model: deck model v2 (ADR-009). The schemas and types,
3
+ * `parseDeck` with all-issues diagnostics, the untrusted loader, migration
4
+ * from Texuli v1, grammar trees and Protoyard, the JSON Schema export, the
5
+ * diagnostic code table, and the leaf modules: layouts, style and themes,
6
+ * backgrounds, steps and motion.
7
+ */
8
+ export * from "./backgrounds/index.js";
9
+ export { DECK_CODES, isDeckCode, } from "./codes.js";
10
+ export { BLOCK_CONTAINERS, BLOCK_JSON_LIMITS, BLOCK_TONES, BLOCK_TYPE_PATTERN, COMPONENT_HEIGHTS, COMPONENT_ID_PATTERN, componentItem, componentSlide, customSlide, DECK_DIRECTIONS, DECK_FORMAT_VERSION, DECK_MODEL_LIMITS, DECK_SLIDE_KINDS, DECK_SLIDE_LAYOUTS, deckJsonSchema, deckMeta, deckNavigation, deckOutline, deckSchema, deckSection, deckSlide, forEachNested, freeSlide, ISO_DATE_PATTERN, isSlideId, isSlideLayout, LOCALE_PATTERN, SLIDE_MASTERS, STEP_FIELD_KEYS, slideItem, } from "./deck.js";
11
+ export * from "./layouts/index.js";
12
+ export { DEFAULT_UNTRUSTED_DECK_LIMITS, jsonErrorOffset, jsonTextDepth, lineColumn, loadUntrustedDeck, } from "./loader.js";
13
+ export { DECK_SOURCES, detectDeckShape, migrateDeck, } from "./migrate.js";
14
+ export * from "./motion/index.js";
15
+ export { parseDeck, stripStepFields, } from "./parse.js";
16
+ export { copyPlainData, stripHiddenCharacters, utf8Length, } from "./plain.js";
17
+ export * from "./shared.js";
18
+ export * from "./steps/index.js";
19
+ export * from "./style/index.js";