@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.
- package/LICENSE +21 -0
- package/README.md +183 -2
- package/dist/authoring/builder.d.ts +351 -0
- package/dist/authoring/builder.js +64 -0
- package/dist/authoring/index.d.ts +6 -0
- package/dist/authoring/index.js +6 -0
- package/dist/authoring/lint.d.ts +36 -0
- package/dist/authoring/lint.js +325 -0
- package/dist/authoring/outlines.d.ts +34 -0
- package/dist/authoring/outlines.js +255 -0
- package/dist/authoring/patch.d.ts +23 -0
- package/dist/authoring/patch.js +166 -0
- package/dist/authoring/select-layouts.d.ts +19 -0
- package/dist/authoring/select-layouts.js +90 -0
- package/dist/engine/auto-slide.d.ts +32 -0
- package/dist/engine/auto-slide.js +67 -0
- package/dist/engine/canvas.d.ts +56 -0
- package/dist/engine/canvas.js +48 -0
- package/dist/engine/controller.d.ts +126 -0
- package/dist/engine/controller.js +248 -0
- package/dist/engine/controls.d.ts +128 -0
- package/dist/engine/controls.js +460 -0
- package/dist/engine/hash.d.ts +42 -0
- package/dist/engine/hash.js +86 -0
- package/dist/engine/index.d.ts +88 -0
- package/dist/engine/index.js +88 -0
- package/dist/engine/messages.d.ts +138 -0
- package/dist/engine/messages.js +140 -0
- package/dist/engine/motion.d.ts +64 -0
- package/dist/engine/motion.js +112 -0
- package/dist/engine/navigation.d.ts +87 -0
- package/dist/engine/navigation.js +156 -0
- package/dist/engine/presenter.d.ts +58 -0
- package/dist/engine/presenter.js +157 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/markdown/attributes.d.ts +24 -0
- package/dist/markdown/attributes.js +94 -0
- package/dist/markdown/attrs-closed.d.ts +52 -0
- package/dist/markdown/attrs-closed.js +520 -0
- package/dist/markdown/blocks.d.ts +112 -0
- package/dist/markdown/blocks.js +766 -0
- package/dist/markdown/frontmatter.d.ts +30 -0
- package/dist/markdown/frontmatter.js +165 -0
- package/dist/markdown/index.d.ts +29 -0
- package/dist/markdown/index.js +29 -0
- package/dist/markdown/issues.d.ts +68 -0
- package/dist/markdown/issues.js +83 -0
- package/dist/markdown/json.d.ts +21 -0
- package/dist/markdown/json.js +236 -0
- package/dist/markdown/limits.d.ts +32 -0
- package/dist/markdown/limits.js +32 -0
- package/dist/markdown/parse.d.ts +31 -0
- package/dist/markdown/parse.js +283 -0
- package/dist/markdown/scan.d.ts +26 -0
- package/dist/markdown/scan.js +138 -0
- package/dist/markdown/slides.d.ts +32 -0
- package/dist/markdown/slides.js +854 -0
- package/dist/markdown/text.d.ts +76 -0
- package/dist/markdown/text.js +447 -0
- package/dist/markdown/twin.d.ts +41 -0
- package/dist/markdown/twin.js +374 -0
- package/dist/markdown/write.d.ts +17 -0
- package/dist/markdown/write.js +688 -0
- package/dist/math/index.d.ts +14 -0
- package/dist/math/index.js +14 -0
- package/dist/math/parse.d.ts +9 -0
- package/dist/math/parse.js +648 -0
- package/dist/math/symbols.d.ts +44 -0
- package/dist/math/symbols.js +333 -0
- package/dist/math/types.d.ts +59 -0
- package/dist/math/types.js +69 -0
- package/dist/model/backgrounds/contrast.d.ts +77 -0
- package/dist/model/backgrounds/contrast.js +186 -0
- package/dist/model/backgrounds/index.d.ts +7 -0
- package/dist/model/backgrounds/index.js +7 -0
- package/dist/model/backgrounds/resolve.d.ts +86 -0
- package/dist/model/backgrounds/resolve.js +215 -0
- package/dist/model/backgrounds/schema.d.ts +178 -0
- package/dist/model/backgrounds/schema.js +203 -0
- package/dist/model/codes.d.ts +73 -0
- package/dist/model/codes.js +67 -0
- package/dist/model/deck.d.ts +392 -0
- package/dist/model/deck.js +338 -0
- package/dist/model/index.d.ts +19 -0
- package/dist/model/index.js +19 -0
- package/dist/model/layouts/catalog.d.ts +433 -0
- package/dist/model/layouts/catalog.js +495 -0
- package/dist/model/layouts/index.d.ts +5 -0
- package/dist/model/layouts/index.js +5 -0
- package/dist/model/layouts/props.d.ts +302 -0
- package/dist/model/layouts/props.js +272 -0
- package/dist/model/loader.d.ts +37 -0
- package/dist/model/loader.js +317 -0
- package/dist/model/migrate.d.ts +34 -0
- package/dist/model/migrate.js +453 -0
- package/dist/model/motion/index.d.ts +6 -0
- package/dist/model/motion/index.js +6 -0
- package/dist/model/motion/transition.d.ts +96 -0
- package/dist/model/motion/transition.js +127 -0
- package/dist/model/parse.d.ts +57 -0
- package/dist/model/parse.js +307 -0
- package/dist/model/plain.d.ts +40 -0
- package/dist/model/plain.js +275 -0
- package/dist/model/shared.d.ts +63 -0
- package/dist/model/shared.js +86 -0
- package/dist/model/steps/index.d.ts +7 -0
- package/dist/model/steps/index.js +7 -0
- package/dist/model/steps/phase.d.ts +34 -0
- package/dist/model/steps/phase.js +50 -0
- package/dist/model/steps/slide-steps.d.ts +129 -0
- package/dist/model/steps/slide-steps.js +256 -0
- package/dist/model/style/index.d.ts +8 -0
- package/dist/model/style/index.js +8 -0
- package/dist/model/style/resolve.d.ts +87 -0
- package/dist/model/style/resolve.js +174 -0
- package/dist/model/style/schema.d.ts +85 -0
- package/dist/model/style/schema.js +53 -0
- package/dist/model/style/theme.d.ts +69 -0
- package/dist/model/style/theme.js +84 -0
- package/dist/schema/builders.d.ts +146 -0
- package/dist/schema/builders.js +285 -0
- package/dist/schema/diagnostics.d.ts +78 -0
- package/dist/schema/diagnostics.js +81 -0
- package/dist/schema/index.d.ts +15 -0
- package/dist/schema/index.js +14 -0
- package/dist/schema/internal.d.ts +40 -0
- package/dist/schema/internal.js +209 -0
- package/dist/schema/json-schema.d.ts +12 -0
- package/dist/schema/json-schema.js +213 -0
- package/dist/schema/types.d.ts +166 -0
- package/dist/schema/types.js +1 -0
- package/dist/schema/validate.d.ts +51 -0
- package/dist/schema/validate.js +765 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.js +2 -0
- package/dist/templates/templates.d.ts +11 -0
- package/dist/templates/templates.js +47 -0
- package/package.json +60 -4
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bounded plain-data copy of untrusted input (ported from Protoyard's
|
|
3
|
+
* `untrusted.ts` sanitizer). Only JSON data survives: own enumerable data
|
|
4
|
+
* properties of plain objects and dense arrays, strings, finite numbers,
|
|
5
|
+
* booleans and `null`. Accessors, class instances, functions, symbols,
|
|
6
|
+
* cycles, prototype keys and anything past the depth or byte budget are
|
|
7
|
+
* dropped and reported, never followed. Optionally strips characters that
|
|
8
|
+
* hide or reorder text. Walked with an explicit stack, so hostile nesting
|
|
9
|
+
* never costs native recursion. Never throws.
|
|
10
|
+
*/
|
|
11
|
+
import { createDiagnostic, UNSAFE_KEYS, } from "../schema/index.js";
|
|
12
|
+
const UNSAFE = new Set(UNSAFE_KEYS);
|
|
13
|
+
/** How many strings get their own `hidden-characters` warning before one summary. */
|
|
14
|
+
const HIDDEN_REPORTS = 20;
|
|
15
|
+
/**
|
|
16
|
+
* True for a character that can hide or reorder text: C0 and C1 controls
|
|
17
|
+
* (except tab, LF and CR), U+061C, U+200B, U+200E, U+200F, the bidi
|
|
18
|
+
* embeddings, overrides and isolates, U+2060 and the byte-order mark. ZWJ
|
|
19
|
+
* and ZWNJ (U+200C, U+200D) stay: emoji sequences and several scripts need them.
|
|
20
|
+
*/
|
|
21
|
+
function isHidden(code) {
|
|
22
|
+
return ((code <= 0x1f && code !== 0x09 && code !== 0x0a && code !== 0x0d) ||
|
|
23
|
+
(code >= 0x7f && code <= 0x9f) ||
|
|
24
|
+
code === 0x061c ||
|
|
25
|
+
code === 0x200b ||
|
|
26
|
+
code === 0x200e ||
|
|
27
|
+
code === 0x200f ||
|
|
28
|
+
(code >= 0x202a && code <= 0x202e) ||
|
|
29
|
+
code === 0x2060 ||
|
|
30
|
+
(code >= 0x2066 && code <= 0x2069) ||
|
|
31
|
+
code === 0xfeff);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Remove hidden characters (see `isHidden`); U+2028 and U+2029 become a
|
|
35
|
+
* space. Returns the string unchanged (same reference) when nothing is hidden.
|
|
36
|
+
*/
|
|
37
|
+
export function stripHiddenCharacters(value) {
|
|
38
|
+
let clean = true;
|
|
39
|
+
for (let i = 0; i < value.length; i++) {
|
|
40
|
+
const c = value.charCodeAt(i);
|
|
41
|
+
if (isHidden(c) || c === 0x2028 || c === 0x2029) {
|
|
42
|
+
clean = false;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (clean)
|
|
47
|
+
return value;
|
|
48
|
+
let out = '';
|
|
49
|
+
for (let i = 0; i < value.length; i++) {
|
|
50
|
+
const c = value.charCodeAt(i);
|
|
51
|
+
if (c === 0x2028 || c === 0x2029)
|
|
52
|
+
out += ' ';
|
|
53
|
+
else if (!isHidden(c))
|
|
54
|
+
out += value[i];
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** UTF-8 byte length without allocating. */
|
|
59
|
+
export function utf8Length(value) {
|
|
60
|
+
let bytes = 0;
|
|
61
|
+
for (let i = 0; i < value.length; i++) {
|
|
62
|
+
const c = value.charCodeAt(i);
|
|
63
|
+
if (c < 0x80)
|
|
64
|
+
bytes += 1;
|
|
65
|
+
else if (c < 0x800)
|
|
66
|
+
bytes += 2;
|
|
67
|
+
else if (c >= 0xd800 && c <= 0xdbff && i + 1 < value.length) {
|
|
68
|
+
const next = value.charCodeAt(i + 1);
|
|
69
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
70
|
+
bytes += 4;
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
else
|
|
74
|
+
bytes += 3;
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
bytes += 3;
|
|
78
|
+
}
|
|
79
|
+
return bytes;
|
|
80
|
+
}
|
|
81
|
+
function describe(value) {
|
|
82
|
+
if (typeof value === 'function')
|
|
83
|
+
return 'a function';
|
|
84
|
+
if (typeof value === 'symbol')
|
|
85
|
+
return 'a symbol';
|
|
86
|
+
if (typeof value === 'bigint')
|
|
87
|
+
return 'a bigint';
|
|
88
|
+
if (value === undefined)
|
|
89
|
+
return 'undefined';
|
|
90
|
+
return 'a non-plain object';
|
|
91
|
+
}
|
|
92
|
+
/** Copy `input` as plain JSON data; see the module comment for what is dropped. */
|
|
93
|
+
export function copyPlainData(input, options = {}) {
|
|
94
|
+
const maxDepth = options.maxDepth ?? 64;
|
|
95
|
+
const maxBytes = options.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
96
|
+
const maxIssues = options.maxIssues ?? 200;
|
|
97
|
+
const strip = options.strip === true;
|
|
98
|
+
const issues = [];
|
|
99
|
+
let failed = false;
|
|
100
|
+
let truncated = false;
|
|
101
|
+
let stopped = false;
|
|
102
|
+
let bytes = 0;
|
|
103
|
+
let hiddenReports = 0;
|
|
104
|
+
let hiddenStrings = 0;
|
|
105
|
+
const report = (code, message, path, fix, severity = 'error') => {
|
|
106
|
+
if (severity === 'error')
|
|
107
|
+
failed = true;
|
|
108
|
+
if (issues.length >= maxIssues) {
|
|
109
|
+
truncated = true;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
issues.push(createDiagnostic(code, message, path, { severity, fix }));
|
|
113
|
+
};
|
|
114
|
+
const text = (value, path) => {
|
|
115
|
+
if (!strip)
|
|
116
|
+
return value;
|
|
117
|
+
const clean = stripHiddenCharacters(value);
|
|
118
|
+
if (clean !== value) {
|
|
119
|
+
hiddenStrings++;
|
|
120
|
+
if (hiddenReports < HIDDEN_REPORTS) {
|
|
121
|
+
hiddenReports++;
|
|
122
|
+
report('hidden-characters', `Removed ${value.length - clean.length} hidden or reordering character(s).`, path, 'Remove control, zero-width and bidi characters from the source.', 'warning');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return clean;
|
|
126
|
+
};
|
|
127
|
+
const holder = {};
|
|
128
|
+
const stack = [
|
|
129
|
+
{ value: input, path: [], depth: 0, target: holder, slot: 'root', ancestors: [] },
|
|
130
|
+
];
|
|
131
|
+
while (stack.length > 0) {
|
|
132
|
+
const frame = stack.pop();
|
|
133
|
+
if (bytes > maxBytes) {
|
|
134
|
+
if (!stopped) {
|
|
135
|
+
stopped = true;
|
|
136
|
+
report('too-large', `The deck is larger than ${maxBytes} bytes of JSON.`, [], 'Shorten notes, media or blocks, or split the deck.');
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
const value = frame.value;
|
|
141
|
+
switch (typeof value) {
|
|
142
|
+
case 'string': {
|
|
143
|
+
const clean = text(value, frame.path);
|
|
144
|
+
bytes += utf8Length(clean) + 2;
|
|
145
|
+
frame.target[frame.slot] = clean;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
case 'number':
|
|
149
|
+
if (!Number.isFinite(value)) {
|
|
150
|
+
report('not-finite', 'NaN and Infinity are not JSON data.', frame.path, 'Use a finite number.');
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
bytes += String(value).length;
|
|
154
|
+
frame.target[frame.slot] = value;
|
|
155
|
+
continue;
|
|
156
|
+
case 'boolean':
|
|
157
|
+
bytes += value ? 4 : 5;
|
|
158
|
+
frame.target[frame.slot] = value;
|
|
159
|
+
continue;
|
|
160
|
+
case 'object':
|
|
161
|
+
break;
|
|
162
|
+
default:
|
|
163
|
+
report('invalid-input', `Received ${describe(value)}, which is not JSON data.`, frame.path, 'Pass JSON text or plain JSON data.');
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (value === null) {
|
|
167
|
+
bytes += 4;
|
|
168
|
+
frame.target[frame.slot] = null;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (frame.ancestors.includes(value)) {
|
|
172
|
+
report('cycle', 'The value contains itself (a cyclic reference).', frame.path, 'Pass acyclic data.');
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (frame.depth >= maxDepth) {
|
|
176
|
+
report('too-deep', `The deck nests deeper than ${maxDepth} levels.`, frame.path, 'Flatten nested blocks or props.');
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
let isArray;
|
|
180
|
+
let keys;
|
|
181
|
+
try {
|
|
182
|
+
isArray = Array.isArray(value);
|
|
183
|
+
if (!isArray) {
|
|
184
|
+
const proto = Object.getPrototypeOf(value);
|
|
185
|
+
if (proto !== null && Object.getPrototypeOf(proto) !== null) {
|
|
186
|
+
report('invalid-input', 'Objects must be plain JSON objects (no class instances).', frame.path, 'Pass JSON text or plain objects.');
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
190
|
+
report('invalid-input', 'Symbol keys are not JSON data.', frame.path, 'Pass JSON data.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
keys = isArray ? [] : Object.keys(value);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
report('invalid-input', 'The value could not be read.', frame.path, 'Pass JSON data.');
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const ancestors = [...frame.ancestors, value];
|
|
200
|
+
const depth = frame.depth + 1;
|
|
201
|
+
const children = [];
|
|
202
|
+
const read = (key, path) => {
|
|
203
|
+
try {
|
|
204
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
205
|
+
if (descriptor && 'value' in descriptor)
|
|
206
|
+
return { ok: true, value: descriptor.value };
|
|
207
|
+
report('invalid-input', descriptor
|
|
208
|
+
? 'Accessor properties are not data; use a plain value.'
|
|
209
|
+
: 'Arrays must be dense (no holes).', path, 'Pass JSON data.');
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
report('invalid-input', 'The property could not be read.', path, 'Pass JSON data.');
|
|
213
|
+
}
|
|
214
|
+
return { ok: false };
|
|
215
|
+
};
|
|
216
|
+
if (isArray) {
|
|
217
|
+
let length = 0;
|
|
218
|
+
try {
|
|
219
|
+
length = value.length;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
report('invalid-input', 'The array could not be read.', frame.path, 'Pass JSON data.');
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const out = [];
|
|
226
|
+
frame.target[frame.slot] = out;
|
|
227
|
+
bytes += 2 + Math.max(0, length - 1);
|
|
228
|
+
for (let i = 0; i < length && bytes <= maxBytes; i++) {
|
|
229
|
+
const path = [...frame.path, i];
|
|
230
|
+
const child = read(i, path);
|
|
231
|
+
if (!child.ok)
|
|
232
|
+
continue;
|
|
233
|
+
children.push({
|
|
234
|
+
value: child.value,
|
|
235
|
+
path,
|
|
236
|
+
depth,
|
|
237
|
+
target: out,
|
|
238
|
+
slot: children.length,
|
|
239
|
+
ancestors,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
const out = {};
|
|
245
|
+
frame.target[frame.slot] = out;
|
|
246
|
+
bytes += 2;
|
|
247
|
+
for (const rawKey of keys) {
|
|
248
|
+
const path = [...frame.path, rawKey];
|
|
249
|
+
if (UNSAFE.has(rawKey)) {
|
|
250
|
+
report('unsafe-key', `The key ${JSON.stringify(rawKey)} is not allowed.`, path, 'Remove the key.');
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const child = read(rawKey, path);
|
|
254
|
+
if (!child.ok)
|
|
255
|
+
continue;
|
|
256
|
+
if (child.value === undefined)
|
|
257
|
+
continue; // JSON drops undefined values
|
|
258
|
+
const key = text(rawKey, path);
|
|
259
|
+
if (UNSAFE.has(key)) {
|
|
260
|
+
report('unsafe-key', `The key ${JSON.stringify(key)} is not allowed.`, path, 'Remove the key.');
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
bytes += utf8Length(key) + 4;
|
|
264
|
+
children.push({ value: child.value, path, depth, target: out, slot: key, ancestors });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// Reverse, so children are visited (and reported) in document order.
|
|
268
|
+
for (let i = children.length - 1; i >= 0; i--)
|
|
269
|
+
stack.push(children[i]);
|
|
270
|
+
}
|
|
271
|
+
if (hiddenStrings > hiddenReports) {
|
|
272
|
+
report('hidden-characters', `Hidden characters were removed from ${hiddenStrings - hiddenReports} more string(s).`, [], 'Remove control, zero-width and bidi characters from the source.', 'warning');
|
|
273
|
+
}
|
|
274
|
+
return { value: holder.root, issues, failed, truncated, bytes };
|
|
275
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Slide, section and block ids: lowercase slug, 1 to 64 characters. */
|
|
2
|
+
export declare const SLIDE_ID_PATTERN = "^[a-z0-9][a-z0-9-]{0,63}$";
|
|
3
|
+
export declare const slideId: import("../index.js").StringSchema;
|
|
4
|
+
/** A trimmed single-line string of at most `max` code points. */
|
|
5
|
+
export declare function line(max: number, min?: number): import("../index.js").StringSchema;
|
|
6
|
+
/** A multi-line string (notes, captions) of at most `max` code points. */
|
|
7
|
+
export declare function multiline(max: number): import("../index.js").StringSchema;
|
|
8
|
+
/** The v2 fragment effects (Protoyard's vocabulary). */
|
|
9
|
+
export declare const FRAGMENT_EFFECTS: readonly ["in", "fade-up", "out", "in-then-out", "grow", "shrink", "highlight", "strike"];
|
|
10
|
+
export type FragmentEffect = (typeof FRAGMENT_EFFECTS)[number];
|
|
11
|
+
/** The legacy block reveal vocabulary (Texuli v1), mapped onto fragments. */
|
|
12
|
+
export declare const BLOCK_REVEALS: readonly ["fade", "fade-up", "grow", "highlight", "strike"];
|
|
13
|
+
export type BlockReveal = (typeof BLOCK_REVEALS)[number];
|
|
14
|
+
/** Highlight tones (reveal.js `highlight-red`/`-blue` map onto these). */
|
|
15
|
+
export declare const FRAGMENT_TONES: readonly ["accent", "info", "success", "warning", "danger"];
|
|
16
|
+
export type FragmentTone = (typeof FRAGMENT_TONES)[number];
|
|
17
|
+
/** The most distinct steps one slide may have (more is an error). */
|
|
18
|
+
export declare const MAX_SLIDE_STEPS = 32;
|
|
19
|
+
/** The largest authored step number before dense renumbering. */
|
|
20
|
+
export declare const MAX_AUTHORED_STEP = 1000;
|
|
21
|
+
/**
|
|
22
|
+
* Step fields any block, component item or steppable props object may carry.
|
|
23
|
+
* `fragment` and `reveal` are mutually exclusive (checked by the steps pass).
|
|
24
|
+
*/
|
|
25
|
+
export declare const stepFields: {
|
|
26
|
+
readonly step: import("../index.js").OptionalSchema<import("../index.js").NumberSchema, false>;
|
|
27
|
+
readonly fragment: import("../index.js").OptionalSchema<import("../index.js").EnumSchema<"in" | "fade-up" | "out" | "in-then-out" | "grow" | "shrink" | "highlight" | "strike">, false>;
|
|
28
|
+
readonly reveal: import("../index.js").OptionalSchema<import("../index.js").EnumSchema<"fade-up" | "grow" | "highlight" | "strike" | "fade">, false>;
|
|
29
|
+
readonly tone: import("../index.js").OptionalSchema<import("../index.js").EnumSchema<"accent" | "info" | "success" | "warning" | "danger">, false>;
|
|
30
|
+
};
|
|
31
|
+
export declare const DECK_TRANSITIONS: readonly ["none", "fade", "fade-up", "slide", "zoom", "morph"];
|
|
32
|
+
export type DeckTransition = (typeof DECK_TRANSITIONS)[number];
|
|
33
|
+
export declare const deckTransition: import("../index.js").EnumSchema<"fade-up" | "fade" | "none" | "slide" | "zoom" | "morph">;
|
|
34
|
+
export declare const DECK_ASPECTS: readonly ["16:9", "4:3", "9:16", "1:1"];
|
|
35
|
+
export type DeckAspect = (typeof DECK_ASPECTS)[number];
|
|
36
|
+
/** The fixed virtual canvas for each aspect, in CSS pixels. */
|
|
37
|
+
export declare const DECK_CANVAS: Readonly<Record<DeckAspect, {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}>>;
|
|
41
|
+
/**
|
|
42
|
+
* Media source syntax: an `https:` URL, a relative path without `..`,
|
|
43
|
+
* backslashes or SVG (any case, also percent-encoded), a base64 `data:`
|
|
44
|
+
* raster or video, or an `avatar:` id. Size caps for `data:` and the trust
|
|
45
|
+
* tier are the host's, applied later.
|
|
46
|
+
*/
|
|
47
|
+
export declare const MEDIA_SRC_PATTERN = "^(?:https://(?![^?#]*(?:\\.|%2[eE])[sS][vV][gG](?:[?#]|%(?:3[fF]|23)|$))[^\\s\"<>\\\\]+|(?!/)(?!.*\\.\\.)(?!.*(?:\\.|%2[eE])[sS][vV][gG](?:[?#]|%(?:3[fF]|23)|$))[A-Za-z0-9._~/-]+(?:[?#][^\\s\"<>\\\\]*)?|data:(?:image/(?:png|jpeg|gif|webp|avif)|video/(?:mp4|webm));base64,[A-Za-z0-9+/=]+|avatar:av-[0-9]{3})$";
|
|
48
|
+
export declare const mediaSrc: import("../index.js").StringSchema;
|
|
49
|
+
/**
|
|
50
|
+
* Link syntax: `https:`, `mailto:`, a fragment, the deck's own route
|
|
51
|
+
* (`#/<slide-id>` or `#/<slide-id>/<step>`, step 0 to 32, as `parseDeckHash`
|
|
52
|
+
* reads it) or a relative path.
|
|
53
|
+
*/
|
|
54
|
+
export declare const LINK_HREF_PATTERN = "^(?:https://[^\\s\"<>\\\\]+|mailto:[^\\s\"<>\\\\]+|#[A-Za-z0-9._~-]*|#/[a-z0-9][a-z0-9-]{0,63}(?:/(?:[0-9]|[12][0-9]|3[0-2]))?|(?!//)(?!.*\\.\\.)[A-Za-z0-9._~/-]+(?:[?#][^\\s\"<>\\\\]*)?)$";
|
|
55
|
+
export declare const linkHref: import("../index.js").StringSchema;
|
|
56
|
+
/** Bounded JSON props (Protoyard's limits): depth 4, 100 keys, 200 items. */
|
|
57
|
+
export declare const boundedProps: import("../index.js").JsonValueSchema;
|
|
58
|
+
/**
|
|
59
|
+
* Catalog component props on a slide: deeper and longer than layout props, so
|
|
60
|
+
* data views and workflow graphs (500 nodes, 2,000 edges) fit. The host's
|
|
61
|
+
* registry validates the content at its tier.
|
|
62
|
+
*/
|
|
63
|
+
export declare const componentProps: import("../index.js").JsonValueSchema;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schemas every part of deck model v2 shares: ids, text, step markers,
|
|
3
|
+
* transitions and media syntax (ADR-009). The engine checks syntax only; the
|
|
4
|
+
* stage applies the URL policy at the host's tier when it paints.
|
|
5
|
+
*/
|
|
6
|
+
import { t } from "../schema/index.js";
|
|
7
|
+
/** Slide, section and block ids: lowercase slug, 1 to 64 characters. */
|
|
8
|
+
export const SLIDE_ID_PATTERN = '^[a-z0-9][a-z0-9-]{0,63}$';
|
|
9
|
+
export const slideId = t.string({ pattern: SLIDE_ID_PATTERN, max: 64 });
|
|
10
|
+
/** A trimmed single-line string of at most `max` code points. */
|
|
11
|
+
export function line(max, min = 0) {
|
|
12
|
+
return t.string({ min, max, trim: true, text: 'line' });
|
|
13
|
+
}
|
|
14
|
+
/** A multi-line string (notes, captions) of at most `max` code points. */
|
|
15
|
+
export function multiline(max) {
|
|
16
|
+
return t.string({ max, text: 'multiline' });
|
|
17
|
+
}
|
|
18
|
+
/** The v2 fragment effects (Protoyard's vocabulary). */
|
|
19
|
+
export const FRAGMENT_EFFECTS = [
|
|
20
|
+
'in',
|
|
21
|
+
'fade-up',
|
|
22
|
+
'out',
|
|
23
|
+
'in-then-out',
|
|
24
|
+
'grow',
|
|
25
|
+
'shrink',
|
|
26
|
+
'highlight',
|
|
27
|
+
'strike',
|
|
28
|
+
];
|
|
29
|
+
/** The legacy block reveal vocabulary (Texuli v1), mapped onto fragments. */
|
|
30
|
+
export const BLOCK_REVEALS = ['fade', 'fade-up', 'grow', 'highlight', 'strike'];
|
|
31
|
+
/** Highlight tones (reveal.js `highlight-red`/`-blue` map onto these). */
|
|
32
|
+
export const FRAGMENT_TONES = ['accent', 'info', 'success', 'warning', 'danger'];
|
|
33
|
+
/** The most distinct steps one slide may have (more is an error). */
|
|
34
|
+
export const MAX_SLIDE_STEPS = 32;
|
|
35
|
+
/** The largest authored step number before dense renumbering. */
|
|
36
|
+
export const MAX_AUTHORED_STEP = 1000;
|
|
37
|
+
/**
|
|
38
|
+
* Step fields any block, component item or steppable props object may carry.
|
|
39
|
+
* `fragment` and `reveal` are mutually exclusive (checked by the steps pass).
|
|
40
|
+
*/
|
|
41
|
+
export const stepFields = {
|
|
42
|
+
step: t.optional(t.int({ min: 0, max: MAX_AUTHORED_STEP })),
|
|
43
|
+
fragment: t.optional(t.enum(FRAGMENT_EFFECTS)),
|
|
44
|
+
reveal: t.optional(t.enum(BLOCK_REVEALS)),
|
|
45
|
+
tone: t.optional(t.enum(FRAGMENT_TONES)),
|
|
46
|
+
};
|
|
47
|
+
export const DECK_TRANSITIONS = ['none', 'fade', 'fade-up', 'slide', 'zoom', 'morph'];
|
|
48
|
+
export const deckTransition = t.enum(DECK_TRANSITIONS);
|
|
49
|
+
export const DECK_ASPECTS = ['16:9', '4:3', '9:16', '1:1'];
|
|
50
|
+
/** The fixed virtual canvas for each aspect, in CSS pixels. */
|
|
51
|
+
export const DECK_CANVAS = Object.freeze({
|
|
52
|
+
'16:9': { width: 1280, height: 720 },
|
|
53
|
+
'4:3': { width: 1024, height: 768 },
|
|
54
|
+
'9:16': { width: 720, height: 1280 },
|
|
55
|
+
'1:1': { width: 1080, height: 1080 },
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* An SVG path ending, in any letter case, with the dot and the end marker
|
|
59
|
+
* also percent-encoded: `.svg`, `.SVG`, `%2esvg`, then `?`, `#`, `%3F`,
|
|
60
|
+
* `%23` or the end. A character class per letter, not the `i` flag: the
|
|
61
|
+
* pattern travels as a JSON Schema `pattern` string.
|
|
62
|
+
*/
|
|
63
|
+
const SVG_ENDING = '(?:\\.|%2[eE])[sS][vV][gG](?:[?#]|%(?:3[fF]|23)|$)';
|
|
64
|
+
/**
|
|
65
|
+
* Media source syntax: an `https:` URL, a relative path without `..`,
|
|
66
|
+
* backslashes or SVG (any case, also percent-encoded), a base64 `data:`
|
|
67
|
+
* raster or video, or an `avatar:` id. Size caps for `data:` and the trust
|
|
68
|
+
* tier are the host's, applied later.
|
|
69
|
+
*/
|
|
70
|
+
export const MEDIA_SRC_PATTERN = `^(?:https://(?![^?#]*${SVG_ENDING})[^\\s"<>\\\\]+|(?!/)(?!.*\\.\\.)(?!.*${SVG_ENDING})[A-Za-z0-9._~/-]+(?:[?#][^\\s"<>\\\\]*)?|data:(?:image/(?:png|jpeg|gif|webp|avif)|video/(?:mp4|webm));base64,[A-Za-z0-9+/=]+|avatar:av-[0-9]{3})$`;
|
|
71
|
+
export const mediaSrc = t.string({ min: 1, max: 2_000_000, pattern: MEDIA_SRC_PATTERN });
|
|
72
|
+
/**
|
|
73
|
+
* Link syntax: `https:`, `mailto:`, a fragment, the deck's own route
|
|
74
|
+
* (`#/<slide-id>` or `#/<slide-id>/<step>`, step 0 to 32, as `parseDeckHash`
|
|
75
|
+
* reads it) or a relative path.
|
|
76
|
+
*/
|
|
77
|
+
export const LINK_HREF_PATTERN = '^(?:https://[^\\s"<>\\\\]+|mailto:[^\\s"<>\\\\]+|#[A-Za-z0-9._~-]*|#/[a-z0-9][a-z0-9-]{0,63}(?:/(?:[0-9]|[12][0-9]|3[0-2]))?|(?!//)(?!.*\\.\\.)[A-Za-z0-9._~/-]+(?:[?#][^\\s"<>\\\\]*)?)$';
|
|
78
|
+
export const linkHref = t.string({ min: 1, max: 2048, pattern: LINK_HREF_PATTERN });
|
|
79
|
+
/** Bounded JSON props (Protoyard's limits): depth 4, 100 keys, 200 items. */
|
|
80
|
+
export const boundedProps = t.json({ depth: 4 });
|
|
81
|
+
/**
|
|
82
|
+
* Catalog component props on a slide: deeper and longer than layout props, so
|
|
83
|
+
* data views and workflow graphs (500 nodes, 2,000 edges) fit. The host's
|
|
84
|
+
* registry validates the content at its tier.
|
|
85
|
+
*/
|
|
86
|
+
export const componentProps = t.json({ depth: 8, maxItems: 2000 });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fragments and steps of deck model v2 (model-v2 §3.7): one algorithm for
|
|
3
|
+
* blocks, list items, code highlights and layout props markers, with dense
|
|
4
|
+
* renumbering and Protoyard's fragment phases.
|
|
5
|
+
*/
|
|
6
|
+
export { activeCodeHighlight, FRAGMENT_PHASES, type FragmentPhase, fragmentPhase, isPhaseShown, } from './phase.js';
|
|
7
|
+
export { type FragmentKind, REVEAL_EFFECT, type SlideFragment, type SlideStep, type SlideSteps, type SlideStepsOptions, STEP_CODES, type StepCode, type StepDiagnostic, type StepMarkerFields, type StepSource, type StepSourceBlock, slideFragments, slideStepCount, slideSteps, steppablePattern, } from './slide-steps.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fragments and steps of deck model v2 (model-v2 §3.7): one algorithm for
|
|
3
|
+
* blocks, list items, code highlights and layout props markers, with dense
|
|
4
|
+
* renumbering and Protoyard's fragment phases.
|
|
5
|
+
*/
|
|
6
|
+
export { activeCodeHighlight, FRAGMENT_PHASES, fragmentPhase, isPhaseShown, } from "./phase.js";
|
|
7
|
+
export { REVEAL_EFFECT, STEP_CODES, slideFragments, slideStepCount, slideSteps, steppablePattern, } from "./slide-steps.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a fragment looks like at a step (Protoyard's `fragmentPhase`, as four
|
|
3
|
+
* named phases):
|
|
4
|
+
*
|
|
5
|
+
* - `hidden`: not shown yet (an entering fragment before its step);
|
|
6
|
+
* - `visible`: shown, effect not applied (or an entrance that has played);
|
|
7
|
+
* - `active`: shown with its effect applied (grown, shrunk, highlighted,
|
|
8
|
+
* struck through);
|
|
9
|
+
* - `gone`: shown once, now removed (`out` from its step, `in-then-out`
|
|
10
|
+
* after its step).
|
|
11
|
+
*
|
|
12
|
+
* Hidden and gone fragments keep their layout space and leave the
|
|
13
|
+
* accessibility tree; that is the stage's job.
|
|
14
|
+
*/
|
|
15
|
+
import type { FragmentEffect } from '../shared.js';
|
|
16
|
+
import type { SlideFragment } from './slide-steps.js';
|
|
17
|
+
export declare const FRAGMENT_PHASES: readonly ["hidden", "visible", "active", "gone"];
|
|
18
|
+
export type FragmentPhase = (typeof FRAGMENT_PHASES)[number];
|
|
19
|
+
/**
|
|
20
|
+
* The phase of a fragment at `current` (0 = initial state). Steps past the
|
|
21
|
+
* slide's count behave like its last step.
|
|
22
|
+
*/
|
|
23
|
+
export declare function fragmentPhase(fragment: {
|
|
24
|
+
step: number;
|
|
25
|
+
effect: FragmentEffect;
|
|
26
|
+
}, current: number): FragmentPhase;
|
|
27
|
+
/** Whether a phase is rendered (`visible` or `active`). */
|
|
28
|
+
export declare function isPhaseShown(phase: FragmentPhase): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The code highlight shown at `current` for the code block (or props code
|
|
31
|
+
* object) at `ownerPath`: the latest highlight whose step was reached, or
|
|
32
|
+
* `null` before the first one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function activeCodeHighlight(fragments: readonly SlideFragment[], ownerPath: readonly (string | number)[], current: number): string | null;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const FRAGMENT_PHASES = ['hidden', 'visible', 'active', 'gone'];
|
|
2
|
+
/** The step to evaluate: non-finite or negative is the initial state. */
|
|
3
|
+
function at(current) {
|
|
4
|
+
return Number.isFinite(current) ? Math.max(0, Math.trunc(current)) : 0;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The phase of a fragment at `current` (0 = initial state). Steps past the
|
|
8
|
+
* slide's count behave like its last step.
|
|
9
|
+
*/
|
|
10
|
+
export function fragmentPhase(fragment, current) {
|
|
11
|
+
const now = at(current);
|
|
12
|
+
const reached = now >= fragment.step;
|
|
13
|
+
switch (fragment.effect) {
|
|
14
|
+
case 'in':
|
|
15
|
+
case 'fade-up':
|
|
16
|
+
return reached ? 'visible' : 'hidden';
|
|
17
|
+
case 'out':
|
|
18
|
+
return reached ? 'gone' : 'visible';
|
|
19
|
+
case 'in-then-out':
|
|
20
|
+
return now < fragment.step ? 'hidden' : now === fragment.step ? 'visible' : 'gone';
|
|
21
|
+
case 'grow':
|
|
22
|
+
case 'shrink':
|
|
23
|
+
case 'highlight':
|
|
24
|
+
case 'strike':
|
|
25
|
+
return reached ? 'active' : 'visible';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Whether a phase is rendered (`visible` or `active`). */
|
|
29
|
+
export function isPhaseShown(phase) {
|
|
30
|
+
return phase === 'visible' || phase === 'active';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The code highlight shown at `current` for the code block (or props code
|
|
34
|
+
* object) at `ownerPath`: the latest highlight whose step was reached, or
|
|
35
|
+
* `null` before the first one.
|
|
36
|
+
*/
|
|
37
|
+
export function activeCodeHighlight(fragments, ownerPath, current) {
|
|
38
|
+
const key = JSON.stringify(ownerPath);
|
|
39
|
+
const now = at(current);
|
|
40
|
+
let shown = null;
|
|
41
|
+
for (const fragment of fragments) {
|
|
42
|
+
if (fragment.kind !== 'highlight' || fragment.lines === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
if (JSON.stringify(fragment.path.slice(0, -2)) !== key)
|
|
45
|
+
continue;
|
|
46
|
+
if (fragment.step <= now)
|
|
47
|
+
shown = fragment.lines;
|
|
48
|
+
}
|
|
49
|
+
return shown;
|
|
50
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Steps and fragments of one slide (model-v2 §3.7, ported from Protoyard's
|
|
3
|
+
* `motion/steps.ts` and extended with v1's list `items` kind).
|
|
4
|
+
*
|
|
5
|
+
* A slide starts at step 0 (its initial state) and advances one step per
|
|
6
|
+
* "next". Authors give fragments an authored step number (0 to
|
|
7
|
+
* `MAX_AUTHORED_STEP`); the engine sorts the distinct non-zero numbers and
|
|
8
|
+
* renumbers them 1, 2, 3…, so gaps ("step": 2 then 5) never cost an empty
|
|
9
|
+
* click. A fragment at authored step 0 belongs to the initial state.
|
|
10
|
+
*
|
|
11
|
+
* Sources, in document order:
|
|
12
|
+
* - a block or component item with `step` (nested blocks may step too);
|
|
13
|
+
* - a `list` block with `step`: item *i* enters at `step + i`;
|
|
14
|
+
* - a `code` block's `highlights` (or a props `{ source, highlights }` code
|
|
15
|
+
* object): highlight *i* at `step + i`, `step` defaulting to 0;
|
|
16
|
+
* - props markers `{ step, fragment | reveal, tone }` anywhere in `props`
|
|
17
|
+
* (a marker off the layout's steppable paths still counts, with a
|
|
18
|
+
* `step-unrevealed` warning, as in Protoyard); a props object with
|
|
19
|
+
* `fragment` or `reveal` but no `step` reveals nothing and is reported as
|
|
20
|
+
* the warning `step-missing`;
|
|
21
|
+
* - `slide.steps` labels, which name steps 1… and may extend the count.
|
|
22
|
+
* A `custom` slide's props belong to its host component: labels only.
|
|
23
|
+
*
|
|
24
|
+
* The input is structural (plain objects, validated or not): anything that is
|
|
25
|
+
* not a valid step, effect or tone is ignored rather than thrown on.
|
|
26
|
+
*/
|
|
27
|
+
import { type DeckDiagnostic, type PathSegment } from '../../schema/index.js';
|
|
28
|
+
import { type BlockReveal, type FragmentEffect, type FragmentTone } from '../shared.js';
|
|
29
|
+
/** The legacy block `reveal` vocabulary in fragment terms (`fade` is a plain `in`). */
|
|
30
|
+
export declare const REVEAL_EFFECT: Readonly<Record<BlockReveal, FragmentEffect>>;
|
|
31
|
+
/** The step codes this pass reports (model-v2 §3.9 cross-field and lint groups). */
|
|
32
|
+
export declare const STEP_CODES: Readonly<{
|
|
33
|
+
readonly fragmentConflict: "fragment-conflict";
|
|
34
|
+
readonly tooManySteps: "too-many-steps";
|
|
35
|
+
readonly stepUnrevealed: "step-unrevealed";
|
|
36
|
+
readonly stepMissing: "step-missing";
|
|
37
|
+
}>;
|
|
38
|
+
export type StepCode = (typeof STEP_CODES)[keyof typeof STEP_CODES];
|
|
39
|
+
export type StepDiagnostic = DeckDiagnostic<StepCode>;
|
|
40
|
+
/** The step fields any block, component item or props marker may carry. */
|
|
41
|
+
export interface StepMarkerFields {
|
|
42
|
+
step?: unknown;
|
|
43
|
+
fragment?: unknown;
|
|
44
|
+
reveal?: unknown;
|
|
45
|
+
tone?: unknown;
|
|
46
|
+
}
|
|
47
|
+
/** A block-like item: a block, a component item or anything with a `type`. */
|
|
48
|
+
export interface StepSourceBlock extends StepMarkerFields {
|
|
49
|
+
type?: unknown;
|
|
50
|
+
id?: unknown;
|
|
51
|
+
/** List items (`type: 'list'`): item *i* enters at `step + i`. */
|
|
52
|
+
items?: unknown;
|
|
53
|
+
/** Code highlights (`type: 'code'`): highlight *i* at `step + i`. */
|
|
54
|
+
highlights?: unknown;
|
|
55
|
+
/** Nested blocks. */
|
|
56
|
+
blocks?: unknown;
|
|
57
|
+
children?: unknown;
|
|
58
|
+
/** `@texuli/blocks` containers: `columns` (arrays of blocks) and `tabs` panels. */
|
|
59
|
+
columns?: unknown;
|
|
60
|
+
panels?: unknown;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
/** The minimal slide shape the steps pass reads. */
|
|
64
|
+
export interface StepSource {
|
|
65
|
+
id?: unknown;
|
|
66
|
+
/** `custom` slides step through their labels only. */
|
|
67
|
+
layout?: unknown;
|
|
68
|
+
/** Layout props: any JSON; step markers are found anywhere inside. */
|
|
69
|
+
props?: unknown;
|
|
70
|
+
/** Free-slide items (blocks and component items). */
|
|
71
|
+
blocks?: readonly unknown[] | unknown;
|
|
72
|
+
/** Step labels: `steps[i]` names step `i + 1`. */
|
|
73
|
+
steps?: readonly unknown[] | unknown;
|
|
74
|
+
}
|
|
75
|
+
export interface SlideStepsOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Where the slide's layout reveals a props marker, relative to `props`:
|
|
78
|
+
* `''` is the props object, `cta` a nested object, `cards.*` each list
|
|
79
|
+
* item. Supplied by the layout catalog; omit it to skip the
|
|
80
|
+
* `step-unrevealed` check.
|
|
81
|
+
*/
|
|
82
|
+
steppablePaths?: readonly string[];
|
|
83
|
+
/** Prefix for diagnostic paths (`['slides', 3]`); fragment paths stay slide-relative. */
|
|
84
|
+
path?: readonly PathSegment[];
|
|
85
|
+
}
|
|
86
|
+
export type FragmentKind = 'block' | 'item' | 'highlight' | 'prop';
|
|
87
|
+
export interface SlideFragment {
|
|
88
|
+
kind: FragmentKind;
|
|
89
|
+
/** Path relative to the slide (`['blocks', 0, 'items', 2]`, `['props', 'cards', 1]`). */
|
|
90
|
+
path: PathSegment[];
|
|
91
|
+
/** `toJsonPath(path)`: `$.blocks[0].items[2]`. */
|
|
92
|
+
jsonPath: string;
|
|
93
|
+
effect: FragmentEffect;
|
|
94
|
+
tone?: FragmentTone;
|
|
95
|
+
/** The dense step (0 = initial state, 1…count). */
|
|
96
|
+
step: number;
|
|
97
|
+
/** The authored step before renumbering. */
|
|
98
|
+
authored: number;
|
|
99
|
+
/** The id of the block the fragment belongs to, when it has one. */
|
|
100
|
+
blockId?: string;
|
|
101
|
+
/** The list item index (`item`). */
|
|
102
|
+
item?: number;
|
|
103
|
+
/** The highlighted line range (`highlight`), such as `'1-2'`. */
|
|
104
|
+
lines?: string;
|
|
105
|
+
}
|
|
106
|
+
export interface SlideStep {
|
|
107
|
+
/** `steps[i].index === i`; 0 is the initial state. */
|
|
108
|
+
index: number;
|
|
109
|
+
/** The author's label from `slide.steps[index - 1]`. */
|
|
110
|
+
label?: string;
|
|
111
|
+
fragments: SlideFragment[];
|
|
112
|
+
}
|
|
113
|
+
export interface SlideSteps {
|
|
114
|
+
/** "Next" presses on this slide before the deck moves on (≤ `MAX_SLIDE_STEPS`). */
|
|
115
|
+
count: number;
|
|
116
|
+
/** The initial step: always 0. */
|
|
117
|
+
initial: 0;
|
|
118
|
+
/** `count + 1` entries: the initial state, then one per step. */
|
|
119
|
+
steps: SlideStep[];
|
|
120
|
+
issues: StepDiagnostic[];
|
|
121
|
+
}
|
|
122
|
+
/** `['cards', 2]` → `cards.*`; the props object itself is `''`. */
|
|
123
|
+
export declare function steppablePattern(path: readonly PathSegment[]): string;
|
|
124
|
+
/** Derives the ordered, densely numbered steps of a slide. Never throws. */
|
|
125
|
+
export declare function slideSteps(slide: StepSource, options?: SlideStepsOptions): SlideSteps;
|
|
126
|
+
/** How many "next" presses a slide takes before the deck moves on. */
|
|
127
|
+
export declare function slideStepCount(slide: StepSource, options?: SlideStepsOptions): number;
|
|
128
|
+
/** Every fragment of a slide, initial state first, in step order. */
|
|
129
|
+
export declare function slideFragments(steps: Pick<SlideSteps, 'steps'>): SlideFragment[];
|