@velumo/core 0.1.0-beta.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/dist/background-validation.d.ts +3 -0
- package/dist/background-validation.d.ts.map +1 -0
- package/dist/background-validation.js +177 -0
- package/dist/background-validation.js.map +1 -0
- package/dist/builders.d.ts +134 -0
- package/dist/builders.d.ts.map +1 -0
- package/dist/builders.js +332 -0
- package/dist/builders.js.map +1 -0
- package/dist/capabilities.d.ts +7 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +59 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/layout-validation.d.ts +4 -0
- package/dist/layout-validation.d.ts.map +1 -0
- package/dist/layout-validation.js +745 -0
- package/dist/layout-validation.js.map +1 -0
- package/dist/manifest-schema.d.ts +1328 -0
- package/dist/manifest-schema.d.ts.map +1 -0
- package/dist/manifest-schema.js +774 -0
- package/dist/manifest-schema.js.map +1 -0
- package/dist/theme-validation.d.ts +3 -0
- package/dist/theme-validation.d.ts.map +1 -0
- package/dist/theme-validation.js +239 -0
- package/dist/theme-validation.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +398 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation-issue.d.ts +18 -0
- package/dist/validation-issue.d.ts.map +1 -0
- package/dist/validation-issue.js +30 -0
- package/dist/validation-issue.js.map +1 -0
- package/dist/validation.d.ts +4 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +274 -0
- package/dist/validation.js.map +1 -0
- package/package.json +16 -0
- package/src/background-validation.ts +315 -0
- package/src/builders.ts +626 -0
- package/src/capabilities.ts +99 -0
- package/src/index.ts +147 -0
- package/src/layout-validation.ts +1385 -0
- package/src/manifest-schema.ts +823 -0
- package/src/theme-validation.ts +410 -0
- package/src/types.ts +560 -0
- package/src/validation-issue.ts +43 -0
- package/src/validation.ts +468 -0
- package/tsconfig.json +9 -0
package/src/builders.ts
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnimateLayout,
|
|
3
|
+
AnimationName,
|
|
4
|
+
CalloutElement,
|
|
5
|
+
CameraStop,
|
|
6
|
+
CameraView,
|
|
7
|
+
CanvasItem,
|
|
8
|
+
CanvasLayout,
|
|
9
|
+
CodeElement,
|
|
10
|
+
CodeToken,
|
|
11
|
+
CodeTokenKind,
|
|
12
|
+
ConnectorElement,
|
|
13
|
+
ConnectorPoint,
|
|
14
|
+
Cue,
|
|
15
|
+
CueAction,
|
|
16
|
+
ExportOptions,
|
|
17
|
+
FrameLayout,
|
|
18
|
+
Fragment,
|
|
19
|
+
GridLayout,
|
|
20
|
+
HeadingElement,
|
|
21
|
+
HeroLayout,
|
|
22
|
+
BadgeElement,
|
|
23
|
+
IconElement,
|
|
24
|
+
RuleElement,
|
|
25
|
+
IconName,
|
|
26
|
+
JsonValue,
|
|
27
|
+
Layer,
|
|
28
|
+
LayoutMeta,
|
|
29
|
+
LayoutNode,
|
|
30
|
+
Manifest,
|
|
31
|
+
MediaElement,
|
|
32
|
+
Plugin,
|
|
33
|
+
Scene,
|
|
34
|
+
Slide,
|
|
35
|
+
SlideEntry,
|
|
36
|
+
SplitLayout,
|
|
37
|
+
StackLayout,
|
|
38
|
+
TableColumn,
|
|
39
|
+
TableLayout,
|
|
40
|
+
TextElement,
|
|
41
|
+
Theme,
|
|
42
|
+
} from "./types.js";
|
|
43
|
+
|
|
44
|
+
export interface DeckInput {
|
|
45
|
+
readonly title: string;
|
|
46
|
+
readonly description?: string;
|
|
47
|
+
readonly author?: string;
|
|
48
|
+
readonly watermark?: string;
|
|
49
|
+
readonly slides: readonly SlideEntry[];
|
|
50
|
+
readonly assets?: Manifest["assets"];
|
|
51
|
+
readonly plugins?: readonly Plugin[];
|
|
52
|
+
readonly theme?: Theme;
|
|
53
|
+
readonly export?: ExportOptions;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type SlideOptions = Omit<Slide, "id" | "kind">;
|
|
57
|
+
export type SceneOptions = Omit<Scene, "id" | "kind">;
|
|
58
|
+
export type LayerOptions = Omit<Layer, "type">;
|
|
59
|
+
export type FragmentOptions = Omit<Fragment, "id">;
|
|
60
|
+
export type StackOptions = Omit<StackLayout, "kind" | "children">;
|
|
61
|
+
export type GridOptions = Omit<GridLayout, "kind" | "children">;
|
|
62
|
+
export type SplitOptions = Omit<SplitLayout, "kind" | "children">;
|
|
63
|
+
export type HeroOptions = Omit<HeroLayout, "kind" | "children">;
|
|
64
|
+
export type FrameOptions = Omit<FrameLayout, "kind" | "children">;
|
|
65
|
+
export type CanvasOptions = Omit<CanvasLayout, "kind" | "items">;
|
|
66
|
+
export type CameraStopOptions = Omit<CameraStop, "camera">;
|
|
67
|
+
|
|
68
|
+
export interface AtOptions {
|
|
69
|
+
readonly width?: number;
|
|
70
|
+
readonly height?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AnimateOptions {
|
|
74
|
+
readonly delay?: number;
|
|
75
|
+
readonly duration?: number;
|
|
76
|
+
readonly easing?: string;
|
|
77
|
+
}
|
|
78
|
+
export type HeadingOptions = Omit<HeadingElement, "kind" | "text">;
|
|
79
|
+
export type TextOptions = Omit<TextElement, "kind" | "text">;
|
|
80
|
+
export type MediaOptions = Omit<MediaElement, "kind" | "src" | "alt"> & {
|
|
81
|
+
readonly alt?: string;
|
|
82
|
+
};
|
|
83
|
+
export type CalloutOptions = Omit<CalloutElement, "kind" | "children">;
|
|
84
|
+
export type LayoutChildInput = LayoutNode | readonly LayoutNode[];
|
|
85
|
+
export type LayoutSlideOptions = Omit<SlideOptions, "layout">;
|
|
86
|
+
export type LayoutSceneOptions = Omit<SceneOptions, "layout">;
|
|
87
|
+
|
|
88
|
+
export interface TitleLayoutOptions {
|
|
89
|
+
readonly subtitle?: string;
|
|
90
|
+
readonly kicker?: string;
|
|
91
|
+
readonly level?: HeadingElement["level"];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface SectionLayoutOptions {
|
|
95
|
+
readonly level?: HeadingElement["level"];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface TwoColumnOptions {
|
|
99
|
+
readonly weights?: readonly [number, number];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface MediaHeroOptions {
|
|
103
|
+
readonly alt?: string;
|
|
104
|
+
readonly title?: string;
|
|
105
|
+
readonly body?: string;
|
|
106
|
+
readonly fit?: MediaElement["fit"];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface QuoteOptions {
|
|
110
|
+
readonly attribution?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface VelumoConfig {
|
|
114
|
+
readonly entry?: string;
|
|
115
|
+
readonly root?: string;
|
|
116
|
+
readonly outDir?: string;
|
|
117
|
+
readonly theme?: string;
|
|
118
|
+
readonly [key: string]: JsonValue | undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function assertUniqueSlideEntryIds(slides: readonly SlideEntry[]): void {
|
|
122
|
+
const seen = new Set<string>();
|
|
123
|
+
|
|
124
|
+
for (const entry of slides) {
|
|
125
|
+
if (seen.has(entry.id)) {
|
|
126
|
+
throw new Error(`duplicate slide/scene id: ${entry.id}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
seen.add(entry.id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function deck(input: DeckInput): Manifest {
|
|
134
|
+
assertUniqueSlideEntryIds(input.slides);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
deck: {
|
|
138
|
+
title: input.title,
|
|
139
|
+
...(input.description === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: { description: input.description }),
|
|
142
|
+
...(input.author === undefined ? {} : { author: input.author }),
|
|
143
|
+
...(input.watermark === undefined ? {} : { watermark: input.watermark }),
|
|
144
|
+
},
|
|
145
|
+
slides: input.slides,
|
|
146
|
+
...(input.assets === undefined ? {} : { assets: input.assets }),
|
|
147
|
+
...(input.plugins === undefined ? {} : { plugins: input.plugins }),
|
|
148
|
+
...(input.theme === undefined ? {} : { theme: input.theme }),
|
|
149
|
+
...(input.export === undefined ? {} : { export: input.export }),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function slide(id: string, options: SlideOptions = {}): Slide {
|
|
154
|
+
return {
|
|
155
|
+
id,
|
|
156
|
+
kind: "slide",
|
|
157
|
+
...options,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function scene(id: string, options: SceneOptions = {}): Scene {
|
|
162
|
+
return {
|
|
163
|
+
id,
|
|
164
|
+
kind: "scene",
|
|
165
|
+
...options,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function layer(type: string, options: LayerOptions = {}): Layer {
|
|
170
|
+
return {
|
|
171
|
+
...options,
|
|
172
|
+
type,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function markdown(content: string, options: LayerOptions = {}): Layer {
|
|
177
|
+
return layer("markdown", {
|
|
178
|
+
...options,
|
|
179
|
+
content,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function normalizeLayoutChildren(
|
|
184
|
+
input: LayoutChildInput | undefined,
|
|
185
|
+
): LayoutNode[] {
|
|
186
|
+
if (input === undefined) {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (Array.isArray(input)) {
|
|
191
|
+
return [...input];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return [input as LayoutNode];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeColumnLayout(input: LayoutChildInput): LayoutNode {
|
|
198
|
+
const children = normalizeLayoutChildren(input);
|
|
199
|
+
|
|
200
|
+
if (Array.isArray(input)) {
|
|
201
|
+
return stack(children);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return children[0];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function headingOptions(
|
|
208
|
+
level: HeadingElement["level"] | undefined,
|
|
209
|
+
): HeadingOptions {
|
|
210
|
+
return level === undefined ? {} : { level };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function stack(
|
|
214
|
+
children: readonly LayoutNode[] = [],
|
|
215
|
+
options: StackOptions = {},
|
|
216
|
+
): StackLayout {
|
|
217
|
+
return {
|
|
218
|
+
kind: "stack",
|
|
219
|
+
children,
|
|
220
|
+
...options,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function grid(
|
|
225
|
+
children: readonly LayoutNode[] = [],
|
|
226
|
+
options: GridOptions = {},
|
|
227
|
+
): GridLayout {
|
|
228
|
+
return {
|
|
229
|
+
kind: "grid",
|
|
230
|
+
children,
|
|
231
|
+
...options,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function split(
|
|
236
|
+
children: readonly LayoutNode[],
|
|
237
|
+
options: SplitOptions = {},
|
|
238
|
+
): SplitLayout {
|
|
239
|
+
return {
|
|
240
|
+
kind: "split",
|
|
241
|
+
children,
|
|
242
|
+
...options,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function hero(
|
|
247
|
+
children: readonly LayoutNode[],
|
|
248
|
+
options: HeroOptions = {},
|
|
249
|
+
): HeroLayout {
|
|
250
|
+
return {
|
|
251
|
+
kind: "hero",
|
|
252
|
+
children,
|
|
253
|
+
...options,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function frame(
|
|
258
|
+
children: readonly LayoutNode[] = [],
|
|
259
|
+
options: FrameOptions = {},
|
|
260
|
+
): FrameLayout {
|
|
261
|
+
return {
|
|
262
|
+
kind: "frame",
|
|
263
|
+
children,
|
|
264
|
+
...options,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function canvas(
|
|
269
|
+
items: readonly CanvasItem[] = [],
|
|
270
|
+
options: CanvasOptions = {},
|
|
271
|
+
): CanvasLayout {
|
|
272
|
+
return {
|
|
273
|
+
kind: "canvas",
|
|
274
|
+
items,
|
|
275
|
+
...options,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function col(
|
|
280
|
+
header?: LayoutNode,
|
|
281
|
+
options: {
|
|
282
|
+
readonly weight?: number;
|
|
283
|
+
readonly align?: TableColumn["align"];
|
|
284
|
+
readonly emphasis?: boolean;
|
|
285
|
+
} = {},
|
|
286
|
+
): TableColumn {
|
|
287
|
+
return {
|
|
288
|
+
...(header === undefined ? {} : { header }),
|
|
289
|
+
...(options.weight === undefined ? {} : { weight: options.weight }),
|
|
290
|
+
...(options.align === undefined ? {} : { align: options.align }),
|
|
291
|
+
...(options.emphasis === undefined ? {} : { emphasis: options.emphasis }),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function table(
|
|
296
|
+
columns: readonly TableColumn[],
|
|
297
|
+
rows: readonly (readonly LayoutNode[])[],
|
|
298
|
+
options: { readonly meta?: LayoutMeta } = {},
|
|
299
|
+
): TableLayout {
|
|
300
|
+
return {
|
|
301
|
+
kind: "table",
|
|
302
|
+
columns,
|
|
303
|
+
rows,
|
|
304
|
+
...(options.meta === undefined ? {} : { meta: options.meta }),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function at(
|
|
309
|
+
x: number,
|
|
310
|
+
y: number,
|
|
311
|
+
child: LayoutNode,
|
|
312
|
+
options: AtOptions = {},
|
|
313
|
+
): CanvasItem {
|
|
314
|
+
return {
|
|
315
|
+
x,
|
|
316
|
+
y,
|
|
317
|
+
...(options.width === undefined ? {} : { width: options.width }),
|
|
318
|
+
...(options.height === undefined ? {} : { height: options.height }),
|
|
319
|
+
child,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function cameraView(x: number, y: number, zoom?: number): CameraView {
|
|
324
|
+
return zoom === undefined ? { x, y } : { x, y, zoom };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function cameraStop(
|
|
328
|
+
camera: CameraView,
|
|
329
|
+
options: CameraStopOptions = {},
|
|
330
|
+
): CameraStop {
|
|
331
|
+
return {
|
|
332
|
+
camera,
|
|
333
|
+
...(options.transition === undefined
|
|
334
|
+
? {}
|
|
335
|
+
: { transition: options.transition }),
|
|
336
|
+
...(options.cues === undefined ? {} : { cues: options.cues }),
|
|
337
|
+
...(options.hold === undefined ? {} : { hold: options.hold }),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function animate(
|
|
342
|
+
name: AnimationName,
|
|
343
|
+
child: LayoutNode,
|
|
344
|
+
options: AnimateOptions = {},
|
|
345
|
+
): AnimateLayout {
|
|
346
|
+
return {
|
|
347
|
+
kind: "animate",
|
|
348
|
+
name,
|
|
349
|
+
...(options.delay === undefined ? {} : { delay: options.delay }),
|
|
350
|
+
...(options.duration === undefined ? {} : { duration: options.duration }),
|
|
351
|
+
...(options.easing === undefined ? {} : { easing: options.easing }),
|
|
352
|
+
child,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function heading(
|
|
357
|
+
text: string,
|
|
358
|
+
options: HeadingOptions = {},
|
|
359
|
+
): HeadingElement {
|
|
360
|
+
return {
|
|
361
|
+
kind: "heading",
|
|
362
|
+
text,
|
|
363
|
+
...options,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function text(text: string, options: TextOptions = {}): TextElement {
|
|
368
|
+
return {
|
|
369
|
+
kind: "text",
|
|
370
|
+
text,
|
|
371
|
+
...options,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Derive a sensible, non-empty `alt` from a media `src` so a minimal
|
|
377
|
+
* `media(src)` call still produces a valid (non-empty alt) manifest. This is a
|
|
378
|
+
* fallback, not a substitute for authored alt text: explicit `alt` should
|
|
379
|
+
* always be preferred and overrides this default.
|
|
380
|
+
*/
|
|
381
|
+
function deriveMediaAlt(src: string): string {
|
|
382
|
+
if (typeof src !== "string") {
|
|
383
|
+
return "image";
|
|
384
|
+
}
|
|
385
|
+
const base = src.split(/[/\\]/).pop() ?? "";
|
|
386
|
+
const withoutExtension = base.replace(/\.[^.]+$/, "");
|
|
387
|
+
const words = withoutExtension.replace(/[-_]+/g, " ").trim();
|
|
388
|
+
return words.length > 0 ? words : "image";
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function media(src: string, options: MediaOptions = {}): MediaElement {
|
|
392
|
+
const { alt, ...rest } = options;
|
|
393
|
+
return {
|
|
394
|
+
kind: "media",
|
|
395
|
+
src,
|
|
396
|
+
alt: alt ?? deriveMediaAlt(src),
|
|
397
|
+
...rest,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function callout(
|
|
402
|
+
children: readonly LayoutNode[] = [],
|
|
403
|
+
options: CalloutOptions = {},
|
|
404
|
+
): CalloutElement {
|
|
405
|
+
return {
|
|
406
|
+
kind: "callout",
|
|
407
|
+
children,
|
|
408
|
+
...options,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function token(text: string, kind?: CodeTokenKind): CodeToken {
|
|
413
|
+
return kind === undefined ? { text } : { text, kind };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export function code(lines: readonly (readonly CodeToken[])[]): CodeElement {
|
|
417
|
+
return { kind: "code", lines };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function icon(
|
|
421
|
+
name: IconName,
|
|
422
|
+
options: {
|
|
423
|
+
readonly tone?: IconElement["tone"];
|
|
424
|
+
readonly label?: string;
|
|
425
|
+
} = {},
|
|
426
|
+
): IconElement {
|
|
427
|
+
return {
|
|
428
|
+
kind: "icon",
|
|
429
|
+
name,
|
|
430
|
+
...(options.tone === undefined ? {} : { tone: options.tone }),
|
|
431
|
+
...(options.label === undefined ? {} : { label: options.label }),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function badge(
|
|
436
|
+
text: string,
|
|
437
|
+
options: {
|
|
438
|
+
readonly tone?: BadgeElement["tone"];
|
|
439
|
+
readonly variant?: BadgeElement["variant"];
|
|
440
|
+
readonly dot?: BadgeElement["tone"];
|
|
441
|
+
readonly mono?: boolean;
|
|
442
|
+
} = {},
|
|
443
|
+
): BadgeElement {
|
|
444
|
+
return {
|
|
445
|
+
kind: "badge",
|
|
446
|
+
text,
|
|
447
|
+
...(options.tone === undefined ? {} : { tone: options.tone }),
|
|
448
|
+
...(options.variant === undefined ? {} : { variant: options.variant }),
|
|
449
|
+
...(options.dot === undefined ? {} : { dot: options.dot }),
|
|
450
|
+
...(options.mono === undefined ? {} : { mono: options.mono }),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function rule(
|
|
455
|
+
options: {
|
|
456
|
+
readonly length?: string | number;
|
|
457
|
+
readonly thickness?: number;
|
|
458
|
+
readonly tone?: RuleElement["tone"];
|
|
459
|
+
readonly gradient?: RuleElement["gradient"];
|
|
460
|
+
readonly radius?: number;
|
|
461
|
+
} = {},
|
|
462
|
+
): RuleElement {
|
|
463
|
+
return {
|
|
464
|
+
kind: "rule",
|
|
465
|
+
...(options.length === undefined ? {} : { length: options.length }),
|
|
466
|
+
...(options.thickness === undefined
|
|
467
|
+
? {}
|
|
468
|
+
: { thickness: options.thickness }),
|
|
469
|
+
...(options.tone === undefined ? {} : { tone: options.tone }),
|
|
470
|
+
...(options.gradient === undefined ? {} : { gradient: options.gradient }),
|
|
471
|
+
...(options.radius === undefined ? {} : { radius: options.radius }),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function point(x: number, y: number): ConnectorPoint {
|
|
476
|
+
return { x, y };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export function connector(
|
|
480
|
+
from: ConnectorPoint,
|
|
481
|
+
to: ConnectorPoint,
|
|
482
|
+
options: {
|
|
483
|
+
readonly control?: ConnectorPoint;
|
|
484
|
+
readonly arrow?: ConnectorElement["arrow"];
|
|
485
|
+
readonly dashed?: boolean;
|
|
486
|
+
readonly tone?: ConnectorElement["tone"];
|
|
487
|
+
} = {},
|
|
488
|
+
): ConnectorElement {
|
|
489
|
+
return {
|
|
490
|
+
kind: "connector",
|
|
491
|
+
from,
|
|
492
|
+
to,
|
|
493
|
+
...(options.control === undefined ? {} : { control: options.control }),
|
|
494
|
+
...(options.arrow === undefined ? {} : { arrow: options.arrow }),
|
|
495
|
+
...(options.dashed === undefined ? {} : { dashed: options.dashed }),
|
|
496
|
+
...(options.tone === undefined ? {} : { tone: options.tone }),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function layoutSlide(
|
|
501
|
+
id: string,
|
|
502
|
+
layout: LayoutNode,
|
|
503
|
+
options: LayoutSlideOptions = {},
|
|
504
|
+
): Slide {
|
|
505
|
+
return slide(id, {
|
|
506
|
+
...options,
|
|
507
|
+
layout,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function layoutScene(
|
|
512
|
+
id: string,
|
|
513
|
+
layout: LayoutNode,
|
|
514
|
+
options: LayoutSceneOptions = {},
|
|
515
|
+
): Scene {
|
|
516
|
+
return scene(id, {
|
|
517
|
+
...options,
|
|
518
|
+
layout,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export function titleLayout(
|
|
523
|
+
title: string,
|
|
524
|
+
options: TitleLayoutOptions = {},
|
|
525
|
+
): HeroLayout {
|
|
526
|
+
const children: LayoutNode[] = [];
|
|
527
|
+
|
|
528
|
+
if (options.kicker !== undefined) {
|
|
529
|
+
children.push(text(options.kicker, { variant: "caption" }));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
children.push(heading(title, headingOptions(options.level)));
|
|
533
|
+
|
|
534
|
+
if (options.subtitle !== undefined) {
|
|
535
|
+
children.push(text(options.subtitle, { variant: "lead" }));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return hero(children);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function sectionLayout(
|
|
542
|
+
title: string,
|
|
543
|
+
children?: LayoutChildInput,
|
|
544
|
+
options: SectionLayoutOptions = {},
|
|
545
|
+
): StackLayout {
|
|
546
|
+
return stack([
|
|
547
|
+
heading(title, headingOptions(options.level)),
|
|
548
|
+
...normalizeLayoutChildren(children),
|
|
549
|
+
]);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function twoColumn(
|
|
553
|
+
left: LayoutChildInput,
|
|
554
|
+
right: LayoutChildInput,
|
|
555
|
+
options: TwoColumnOptions = {},
|
|
556
|
+
): SplitLayout {
|
|
557
|
+
return split(
|
|
558
|
+
[normalizeColumnLayout(left), normalizeColumnLayout(right)],
|
|
559
|
+
options,
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function mediaHero(
|
|
564
|
+
src: string,
|
|
565
|
+
options: MediaHeroOptions = {},
|
|
566
|
+
): HeroLayout {
|
|
567
|
+
const children: LayoutNode[] = [];
|
|
568
|
+
|
|
569
|
+
if (options.title !== undefined) {
|
|
570
|
+
children.push(heading(options.title));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (options.body !== undefined) {
|
|
574
|
+
children.push(text(options.body, { variant: "lead" }));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
children.push(
|
|
578
|
+
media(src, {
|
|
579
|
+
...(options.alt === undefined ? {} : { alt: options.alt }),
|
|
580
|
+
...(options.fit === undefined ? {} : { fit: options.fit }),
|
|
581
|
+
}),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
return hero(children);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export function quote(
|
|
588
|
+
value: string,
|
|
589
|
+
options: QuoteOptions = {},
|
|
590
|
+
): CalloutElement {
|
|
591
|
+
const children: LayoutNode[] = [text(value)];
|
|
592
|
+
|
|
593
|
+
if (options.attribution !== undefined) {
|
|
594
|
+
children.push(text(options.attribution, { variant: "caption" }));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return callout(children, { tone: "quote" });
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function fragment(id: string, options: FragmentOptions = {}): Fragment {
|
|
601
|
+
return {
|
|
602
|
+
id,
|
|
603
|
+
...options,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export function cue(time: number, action: CueAction): Cue {
|
|
608
|
+
return {
|
|
609
|
+
time,
|
|
610
|
+
action,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function defineTheme<TTheme extends Theme>(theme: TTheme): TTheme {
|
|
615
|
+
return theme;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export function definePlugin<TPlugin extends Plugin>(plugin: TPlugin): TPlugin {
|
|
619
|
+
return plugin;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export function defineConfig<TConfig extends VelumoConfig>(
|
|
623
|
+
config: TConfig,
|
|
624
|
+
): TConfig {
|
|
625
|
+
return config;
|
|
626
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CapabilityField,
|
|
3
|
+
VelumoCapabilities,
|
|
4
|
+
NodeCapability,
|
|
5
|
+
} from "./types.js";
|
|
6
|
+
import { manifestJsonSchema } from "./manifest-schema.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal structural view of the parts of `manifestJsonSchema` this projection
|
|
10
|
+
* reads. The schema is authored as a literal `as const`; this loose shape lets
|
|
11
|
+
* us walk it without fighting the inferred literal types.
|
|
12
|
+
*/
|
|
13
|
+
interface PropSchema {
|
|
14
|
+
readonly const?: string;
|
|
15
|
+
readonly type?: string | readonly string[];
|
|
16
|
+
readonly enum?: readonly string[];
|
|
17
|
+
readonly items?: { readonly $ref?: string };
|
|
18
|
+
readonly $ref?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface DefSchema {
|
|
22
|
+
readonly oneOf?: readonly { readonly $ref: string }[];
|
|
23
|
+
readonly required?: readonly string[];
|
|
24
|
+
readonly properties?: Record<string, PropSchema>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const schema = manifestJsonSchema as unknown as {
|
|
28
|
+
readonly $id: string;
|
|
29
|
+
readonly $defs: Record<string, DefSchema>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const LAYOUT_NODE_REF = "#/$defs/layoutNode";
|
|
33
|
+
|
|
34
|
+
function resolveRef(ref: string): DefSchema {
|
|
35
|
+
return schema.$defs[ref.replace("#/$defs/", "")];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function deriveType(prop: PropSchema): string {
|
|
39
|
+
if (Array.isArray(prop.enum)) {
|
|
40
|
+
return "enum";
|
|
41
|
+
}
|
|
42
|
+
if (prop.type === "array") {
|
|
43
|
+
return prop.items?.$ref === LAYOUT_NODE_REF ? "node[]" : "array";
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(prop.type)) {
|
|
46
|
+
return prop.type.join("|");
|
|
47
|
+
}
|
|
48
|
+
if (typeof prop.type === "string") {
|
|
49
|
+
return prop.type;
|
|
50
|
+
}
|
|
51
|
+
return "object";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function describeNode(def: DefSchema): NodeCapability {
|
|
55
|
+
const required = new Set(def.required ?? []);
|
|
56
|
+
const properties = def.properties ?? {};
|
|
57
|
+
|
|
58
|
+
const fields: CapabilityField[] = Object.entries(properties)
|
|
59
|
+
.filter(([name]) => name !== "kind")
|
|
60
|
+
.map(([name, prop]) => {
|
|
61
|
+
const field: CapabilityField = {
|
|
62
|
+
name,
|
|
63
|
+
required: required.has(name),
|
|
64
|
+
type: deriveType(prop),
|
|
65
|
+
};
|
|
66
|
+
return Array.isArray(prop.enum)
|
|
67
|
+
? { ...field, enum: [...prop.enum] }
|
|
68
|
+
: field;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
kind: def.properties?.kind?.const ?? "unknown",
|
|
73
|
+
fields,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function discriminatorKinds(def: DefSchema): readonly string[] {
|
|
78
|
+
return (def.oneOf ?? []).map(
|
|
79
|
+
(ref) => resolveRef(ref.$ref).properties?.kind?.const ?? "unknown",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Project the manifest JSON Schema into an agent-friendly capability catalog.
|
|
85
|
+
* Pure and deterministic: returns a fresh, JSON-serializable object every call.
|
|
86
|
+
*/
|
|
87
|
+
export function describeCapabilities(): VelumoCapabilities {
|
|
88
|
+
const layoutNode = schema.$defs.layoutNode;
|
|
89
|
+
const nodes = (layoutNode.oneOf ?? []).map((ref) =>
|
|
90
|
+
describeNode(resolveRef(ref.$ref)),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
schemaId: schema.$id,
|
|
95
|
+
slideKinds: discriminatorKinds(schema.$defs.slideEntry),
|
|
96
|
+
nodes,
|
|
97
|
+
themeTokenFamilies: Object.keys(schema.$defs.themeTokens.properties ?? {}),
|
|
98
|
+
};
|
|
99
|
+
}
|