@folienbaukasten/extension-toolkit 1.0.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/README.md +109 -0
- package/abi/README.md +20 -0
- package/abi/abi.json +151 -0
- package/abi/pack.d.ts +196 -0
- package/abi/pack.js +126 -0
- package/abi/provenance.json +5 -0
- package/abi/shims/jsx-runtime.js +13 -0
- package/abi/shims/platform.js +48 -0
- package/abi/shims/react.js +18 -0
- package/abi/types/charts/geometry.d.ts +43 -0
- package/abi/types/charts/option.d.ts +15 -0
- package/abi/types/charts/scale.d.ts +14 -0
- package/abi/types/charts/theme.d.ts +16 -0
- package/abi/types/charts/waterfall.d.ts +35 -0
- package/abi/types/components/Block.d.ts +31 -0
- package/abi/types/components/BlockArrow.d.ts +16 -0
- package/abi/types/components/Card.d.ts +25 -0
- package/abi/types/components/ChartView.d.ts +4 -0
- package/abi/types/components/DriverTree.d.ts +9 -0
- package/abi/types/components/FindingsBanner.d.ts +21 -0
- package/abi/types/components/HarveyBall.d.ts +12 -0
- package/abi/types/components/HeaderTitle.d.ts +19 -0
- package/abi/types/components/InsightRail.d.ts +13 -0
- package/abi/types/components/KpiTile.d.ts +5 -0
- package/abi/types/components/Matrix.d.ts +5 -0
- package/abi/types/components/QuoteColumn.d.ts +5 -0
- package/abi/types/components/SourceLine.d.ts +6 -0
- package/abi/types/components/Table.d.ts +5 -0
- package/abi/types/components/Timeline.d.ts +5 -0
- package/abi/types/components/slide/ContentSlide.d.ts +29 -0
- package/abi/types/components/slide/MissingComponent.d.ts +26 -0
- package/abi/types/components/slide/SlideFrame.d.ts +20 -0
- package/abi/types/components/slide/SlotView.d.ts +30 -0
- package/abi/types/components/slide/registry.d.ts +27 -0
- package/abi/types/components/slide/views/AgendaSlideView.d.ts +8 -0
- package/abi/types/components/slide/views/CardsSlideView.d.ts +12 -0
- package/abi/types/components/slide/views/ChaptersSlideView.d.ts +11 -0
- package/abi/types/components/slide/views/ChartSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/ComponentSlideView.d.ts +19 -0
- package/abi/types/components/slide/views/DriverTreeSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/FreeformSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/KpiSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/MatrixSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/QuoteSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/QuotesSlideView.d.ts +5 -0
- package/abi/types/components/slide/views/SplitSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/TableSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/TimelineSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/TitleSlideView.d.ts +7 -0
- package/abi/types/components/slide/views/TwoBlockSlideView.d.ts +3 -0
- package/abi/types/components/slide/views/WebSlideView.d.ts +23 -0
- package/abi/types/components/slide/views/WhileArrowSlideView.d.ts +14 -0
- package/abi/types/deck/data.d.ts +127 -0
- package/abi/types/deck/host.d.ts +9 -0
- package/abi/types/deck/layouts.d.ts +685 -0
- package/abi/types/deck/schema.d.ts +437 -0
- package/abi/types/deck/web-body.d.ts +47 -0
- package/abi/types/lib/format.d.ts +12 -0
- package/abi/types/sets/platform.d.ts +45 -0
- package/abi/types/sets/types.d.ts +128 -0
- package/dist/fbk-pack.js +756 -0
- package/package.json +49 -0
- package/tsconfig.extension.json +17 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import type { AgendaItemSpec, BlockSpec, CardSpec, ChapterSpec, ChartSpec, DriverSpec, FindingSpec, HeaderSpec, InsightSpec, KpiSpec, MatrixSpec, QuoteColumnSpec, QuoteSpec, SlideComponentSpec, TableSpec, TimelineEventSpec } from './schema';
|
|
2
|
+
/** The complete layout vocabulary — every set renders every one of these. */
|
|
3
|
+
export type SharedSlide = TitleSlideSpec | SplitSlideSpec | FreeformSlideSpec | TwoBlockSlideSpec | WhileArrowSlideSpec | ChartSlideSpec | TableSlideSpec | AgendaSlideSpec | TimelineSlideSpec | KpiSlideSpec | MatrixSlideSpec | DriverTreeSlideSpec | QuoteSlideSpec | QuotesSlideSpec | ChaptersSlideSpec | CardsSlideSpec | ComponentSlideSpec | WebSlideSpec;
|
|
4
|
+
/** Opening slide: heading, byline and an optional figure. The optional
|
|
5
|
+
* fields are the union of what sets render — a set's title view ignores the
|
|
6
|
+
* ones it has no place for. */
|
|
7
|
+
export interface TitleSlideSpec {
|
|
8
|
+
layout: 'title';
|
|
9
|
+
props: {
|
|
10
|
+
/** Small uppercase line above the heading, e.g. "Strategic review". */
|
|
11
|
+
eyebrow?: string;
|
|
12
|
+
heading: string;
|
|
13
|
+
subheading?: string;
|
|
14
|
+
/** Short framing labels under the subtitle, e.g. scope or workstreams. */
|
|
15
|
+
chips?: string[];
|
|
16
|
+
author?: string;
|
|
17
|
+
/** Display string; formatting is the author's choice. */
|
|
18
|
+
date?: string;
|
|
19
|
+
/** Title figure on the right side. Whether it is bled to the edge
|
|
20
|
+
* (cover) or shown whole (contain) is the set's choice. */
|
|
21
|
+
imageUrl?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Two content blocks side by side — the workhorse comparison/detail slide. */
|
|
25
|
+
export interface TwoBlockSlideSpec {
|
|
26
|
+
layout: 'two-block';
|
|
27
|
+
props: {
|
|
28
|
+
header: HeaderSpec;
|
|
29
|
+
left: BlockSpec;
|
|
30
|
+
right: BlockSpec;
|
|
31
|
+
finding?: FindingSpec;
|
|
32
|
+
source?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** "While a … (left) we need to consider b (right)", joined by a blade. */
|
|
36
|
+
export interface WhileArrowSlideSpec {
|
|
37
|
+
layout: 'while-arrow';
|
|
38
|
+
props: {
|
|
39
|
+
header: HeaderSpec;
|
|
40
|
+
left: BlockSpec;
|
|
41
|
+
/** Rendered with `left`'s variant: the two halves are one sentence
|
|
42
|
+
* across an arrow, so they always share a treatment. */
|
|
43
|
+
right: BlockSpec;
|
|
44
|
+
finding?: FindingSpec;
|
|
45
|
+
source?: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** One chart given the whole body — bar, stacked-bar, line or waterfall. */
|
|
49
|
+
export interface ChartSlideSpec {
|
|
50
|
+
layout: 'chart';
|
|
51
|
+
props: {
|
|
52
|
+
header: HeaderSpec;
|
|
53
|
+
chart: ChartSpec;
|
|
54
|
+
finding?: FindingSpec;
|
|
55
|
+
source?: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** A data table filling the body; cells hold text, numbers or harvey balls. */
|
|
59
|
+
export interface TableSlideSpec {
|
|
60
|
+
layout: 'table';
|
|
61
|
+
props: {
|
|
62
|
+
header: HeaderSpec;
|
|
63
|
+
table: TableSpec;
|
|
64
|
+
finding?: FindingSpec;
|
|
65
|
+
source?: string;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Agenda / table of contents: numbered topics on hairline-separated rows,
|
|
69
|
+
* with an optional picture column. Four to six items read best. */
|
|
70
|
+
export interface AgendaSlideSpec {
|
|
71
|
+
layout: 'agenda';
|
|
72
|
+
props: {
|
|
73
|
+
header: HeaderSpec;
|
|
74
|
+
items: AgendaItemSpec[];
|
|
75
|
+
/** Picture on the right side (object-fit: cover), as on the title slide. */
|
|
76
|
+
imageUrl?: string;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Chronology on a horizontal axis; events alternate above and below it.
|
|
80
|
+
* Three to six events fit; more crowd the columns. */
|
|
81
|
+
export interface TimelineSlideSpec {
|
|
82
|
+
layout: 'timeline';
|
|
83
|
+
props: {
|
|
84
|
+
header: HeaderSpec;
|
|
85
|
+
events: TimelineEventSpec[];
|
|
86
|
+
finding?: FindingSpec;
|
|
87
|
+
source?: string;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A row of big-number KPI tiles with labels and optional deltas, over
|
|
92
|
+
* supporting blocks that say what the numbers mean.
|
|
93
|
+
*
|
|
94
|
+
* The tiles alone do not make a slide — four figures floating in the body
|
|
95
|
+
* region read as a fragment. `blocks` is what turns the headline numbers
|
|
96
|
+
* into an argument, and it is where the "so what" belongs; two or three is
|
|
97
|
+
* the natural width. It stays optional so a bare KPI row still renders (the
|
|
98
|
+
* tiles then sit at the top of the body, never centred in it).
|
|
99
|
+
*/
|
|
100
|
+
export interface KpiSlideSpec {
|
|
101
|
+
layout: 'kpis';
|
|
102
|
+
props: {
|
|
103
|
+
header: HeaderSpec;
|
|
104
|
+
kpis: KpiSpec[];
|
|
105
|
+
/** Supporting "so what" blocks under the tiles. Two or three. */
|
|
106
|
+
blocks?: BlockSpec[];
|
|
107
|
+
finding?: FindingSpec;
|
|
108
|
+
source?: string;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Options placed on two named axes — the "where do we play" slide. */
|
|
112
|
+
export interface MatrixSlideSpec {
|
|
113
|
+
layout: 'matrix';
|
|
114
|
+
props: {
|
|
115
|
+
header: HeaderSpec;
|
|
116
|
+
matrix: MatrixSpec;
|
|
117
|
+
finding?: FindingSpec;
|
|
118
|
+
source?: string;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** One root metric decomposed into the levers that move it. Three branches
|
|
122
|
+
* is the natural width; two and four also fit. */
|
|
123
|
+
export interface DriverTreeSlideSpec {
|
|
124
|
+
layout: 'driver-tree';
|
|
125
|
+
props: {
|
|
126
|
+
header: HeaderSpec;
|
|
127
|
+
/** The metric being decomposed, shown in the accent root box. */
|
|
128
|
+
root: string;
|
|
129
|
+
branches: DriverSpec[];
|
|
130
|
+
finding?: FindingSpec;
|
|
131
|
+
source?: string;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** A single quote given the whole slide — the one voice worth pausing on. */
|
|
135
|
+
export interface QuoteSlideSpec {
|
|
136
|
+
layout: 'quote';
|
|
137
|
+
props: {
|
|
138
|
+
/** Optional: a bare quote with no action title is a legitimate beat. */
|
|
139
|
+
header?: HeaderSpec;
|
|
140
|
+
quote: QuoteSpec;
|
|
141
|
+
source?: string;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** Verbatim voice-of-customer evidence in two themed columns. */
|
|
145
|
+
export interface QuotesSlideSpec {
|
|
146
|
+
layout: 'quotes';
|
|
147
|
+
props: {
|
|
148
|
+
header: HeaderSpec;
|
|
149
|
+
columns: QuoteColumnSpec[];
|
|
150
|
+
finding?: FindingSpec;
|
|
151
|
+
source?: string;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** Section divider: the numbered chapters the deck is about to walk through.
|
|
155
|
+
* Two to four chapters; the columns get cramped beyond that. */
|
|
156
|
+
export interface ChaptersSlideSpec {
|
|
157
|
+
layout: 'chapters';
|
|
158
|
+
props: {
|
|
159
|
+
header: HeaderSpec;
|
|
160
|
+
chapters: ChapterSpec[];
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** A grid of cards. Two to six cards; `columns` overrides the automatic
|
|
164
|
+
* choice (3 for four or more cards, otherwise one column per card). */
|
|
165
|
+
export interface CardsSlideSpec {
|
|
166
|
+
layout: 'cards';
|
|
167
|
+
props: {
|
|
168
|
+
header: HeaderSpec;
|
|
169
|
+
cards: CardSpec[];
|
|
170
|
+
columns?: 2 | 3;
|
|
171
|
+
/** Optional 3-up takeaway strip under the grid. */
|
|
172
|
+
insights?: InsightSpec[];
|
|
173
|
+
finding?: FindingSpec;
|
|
174
|
+
source?: string;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** A slide whose whole body is one extension-supplied component (a bespoke
|
|
178
|
+
* pricing sheet, framework diagram, …). Only useful when the extension that
|
|
179
|
+
* ships the named kind is installed; without it the body shows an install
|
|
180
|
+
* placeholder and the deck still renders. */
|
|
181
|
+
export interface ComponentSlideSpec {
|
|
182
|
+
layout: 'component';
|
|
183
|
+
props: {
|
|
184
|
+
header: HeaderSpec;
|
|
185
|
+
component: SlideComponentSpec;
|
|
186
|
+
source?: string;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** A slide body the model authors directly, as a web page fragment.
|
|
190
|
+
*
|
|
191
|
+
* The body rectangle only: header, footer, slide number and canvas belong to
|
|
192
|
+
* the theme, and the palette reaches the markup as CSS custom properties
|
|
193
|
+
* already resolved for the active set — so a web body still follows a theme
|
|
194
|
+
* swap. Raw colours and font faces are rejected for exactly that reason;
|
|
195
|
+
* everything else CSS can do is open, which is the point of the layout.
|
|
196
|
+
*
|
|
197
|
+
* Web Mode only — a hand-authored layout cannot be measured into PowerPoint
|
|
198
|
+
* shapes, so this is the one layout with no PPTX story at all.
|
|
199
|
+
*
|
|
200
|
+
* Reach for it when the vocabulary genuinely cannot draw the slide: a hero,
|
|
201
|
+
* an opener, a diagram. A deck of hand-written bodies has no theme and no
|
|
202
|
+
* consistency, which is what the layouts are for. */
|
|
203
|
+
export interface WebSlideSpec {
|
|
204
|
+
layout: 'web';
|
|
205
|
+
props: {
|
|
206
|
+
header: HeaderSpec;
|
|
207
|
+
/** The body's markup. A fragment, not a document: no <html>, <head>,
|
|
208
|
+
* <script>, <style> or <iframe>, and no network URLs. Tag the meaningful
|
|
209
|
+
* elements `data-pptx` ("text", "shape", "image", …) so the picker lands
|
|
210
|
+
* on them. */
|
|
211
|
+
html: string;
|
|
212
|
+
/** Author styles, injected as one <style> block scoped to this slide's
|
|
213
|
+
* body — selectors cannot reach another slide or the chrome. Theme
|
|
214
|
+
* tokens are already declared: use them (var(--color-accent),
|
|
215
|
+
* var(--font-family)); raw colours and font families are rejected. */
|
|
216
|
+
css?: string;
|
|
217
|
+
/** The body's editable copy, keyed by name. An element written as
|
|
218
|
+
* `<h2 data-edit="text.headline"></h2>` shows `text.headline` and stays
|
|
219
|
+
* double-click editable in the editor — which also means a wording
|
|
220
|
+
* change is a one-key write, not a rewrite of the markup. */
|
|
221
|
+
text?: Record<string, string>;
|
|
222
|
+
/** Own the whole 1280×720 canvas instead of the body band — for a
|
|
223
|
+
* full-bleed section opener. The theme's footer still paints over it, so
|
|
224
|
+
* keep the bottom strip clear. */
|
|
225
|
+
bleed?: boolean;
|
|
226
|
+
/** Draw the theme's footer. Default true, and only meaningful under
|
|
227
|
+
* `bleed` — a banded body always reserves the footer's strip, so there
|
|
228
|
+
* is nothing to decide there.
|
|
229
|
+
*
|
|
230
|
+
* It exists for the converted cover. A `title` slide is the one layout
|
|
231
|
+
* that signs nothing: no page number, no deck note. Converting one has
|
|
232
|
+
* to be able to say that, or every cover comes back from the conversion
|
|
233
|
+
* wearing a footer it never had. */
|
|
234
|
+
footer?: boolean;
|
|
235
|
+
finding?: FindingSpec;
|
|
236
|
+
source?: string;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** One chart. */
|
|
240
|
+
export interface ChartSlot {
|
|
241
|
+
kind: 'chart';
|
|
242
|
+
chart: ChartSpec;
|
|
243
|
+
}
|
|
244
|
+
/** One table. */
|
|
245
|
+
export interface TableSlot {
|
|
246
|
+
kind: 'table';
|
|
247
|
+
table: TableSpec;
|
|
248
|
+
}
|
|
249
|
+
/** One content block — the card/callout box. */
|
|
250
|
+
export interface BlockSlot {
|
|
251
|
+
kind: 'block';
|
|
252
|
+
block: BlockSpec;
|
|
253
|
+
}
|
|
254
|
+
/** A stack of KPI tiles. In a split slot two or three fit and four get
|
|
255
|
+
* cramped; in a freeform item they fill the item's width, so let the area
|
|
256
|
+
* say how many. */
|
|
257
|
+
export interface KpisSlot {
|
|
258
|
+
kind: 'kpis';
|
|
259
|
+
kpis: KpiSpec[];
|
|
260
|
+
}
|
|
261
|
+
/** A horizontal timeline — the events alternate above and below the axis,
|
|
262
|
+
* so give it a wide, short area. Freeform only: a timeline in half a slide
|
|
263
|
+
* reads as a list. */
|
|
264
|
+
export interface TimelineSlot {
|
|
265
|
+
kind: 'timeline';
|
|
266
|
+
events: TimelineEventSpec[];
|
|
267
|
+
}
|
|
268
|
+
/** A 2×2 positioning matrix. Wants a near-square area — it has two axes to
|
|
269
|
+
* label and a plot that only reads square. Freeform only. */
|
|
270
|
+
export interface MatrixSlot {
|
|
271
|
+
kind: 'matrix';
|
|
272
|
+
matrix: MatrixSpec;
|
|
273
|
+
}
|
|
274
|
+
/** One or more quote columns under their accent bars. Freeform only; reach
|
|
275
|
+
* for it when verbatim evidence sits beside something else on the slide. */
|
|
276
|
+
export interface QuotesSlot {
|
|
277
|
+
kind: 'quotes';
|
|
278
|
+
columns: QuoteColumnSpec[];
|
|
279
|
+
}
|
|
280
|
+
/** The takeaway strip: equal columns under a hairline rule. A full-width
|
|
281
|
+
* band, so give it the whole body width and little height. Freeform only. */
|
|
282
|
+
export interface InsightsSlot {
|
|
283
|
+
kind: 'insights';
|
|
284
|
+
insights: InsightSpec[];
|
|
285
|
+
}
|
|
286
|
+
/** A driver tree: the root metric on the left, its branches on the right.
|
|
287
|
+
* Freeform only — the root column is a fixed width, so a narrow area leaves
|
|
288
|
+
* the branches nothing. */
|
|
289
|
+
export interface DriverTreeSlot {
|
|
290
|
+
kind: 'driver-tree';
|
|
291
|
+
/** The metric being decomposed. */
|
|
292
|
+
root: string;
|
|
293
|
+
branches: DriverSpec[];
|
|
294
|
+
}
|
|
295
|
+
/** A component an installed extension provides, by namespaced kind — the
|
|
296
|
+
* same reference the `component` layout takes, so a pack component can be
|
|
297
|
+
* one item among many instead of owning the whole body. Without the
|
|
298
|
+
* extension the item shows the install placeholder and the .pptx export is
|
|
299
|
+
* refused rather than written blank. Freeform only. */
|
|
300
|
+
export interface ExtensionSlot {
|
|
301
|
+
kind: 'extension';
|
|
302
|
+
component: SlideComponentSpec;
|
|
303
|
+
}
|
|
304
|
+
/** One side of a split slide — exactly one kind of content. The four that
|
|
305
|
+
* survive half a slide; the rest of the vocabulary is in `SlotSpec`. */
|
|
306
|
+
export type SplitSlotSpec = ChartSlot | TableSlot | BlockSlot | KpisSlot;
|
|
307
|
+
/** Everything a slot can be. A freeform `component` item takes one of
|
|
308
|
+
* these; `split` takes the narrower `SplitSlotSpec`. */
|
|
309
|
+
export type SlotSpec = SplitSlotSpec | TimelineSlot | MatrixSlot | QuotesSlot | InsightsSlot | DriverTreeSlot | ExtensionSlot;
|
|
310
|
+
/** Two independent content slots side by side — a chart next to the table
|
|
311
|
+
* behind it, KPIs beside the trend they summarize, a block arguing what the
|
|
312
|
+
* evidence next to it shows. The composition slide for pairings the fixed
|
|
313
|
+
* layouts don't cover; prefer a fixed layout when one fits. */
|
|
314
|
+
export interface SplitSlideSpec {
|
|
315
|
+
layout: 'split';
|
|
316
|
+
props: {
|
|
317
|
+
header: HeaderSpec;
|
|
318
|
+
left: SplitSlotSpec;
|
|
319
|
+
right: SplitSlotSpec;
|
|
320
|
+
/** Width ratio left:right. Default '1:1'; give a chart or table the
|
|
321
|
+
* wider side. */
|
|
322
|
+
ratio?: '1:1' | '1:2' | '2:1';
|
|
323
|
+
finding?: FindingSpec;
|
|
324
|
+
source?: string;
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/** Where a freeform item sits: fractional placement inside the slide's
|
|
328
|
+
* body region (between the header and footer bands), each value a percent
|
|
329
|
+
* of the body's width or height. Items may overlap; later items draw on
|
|
330
|
+
* top. Spacing between items is the author's choice — nothing is inserted
|
|
331
|
+
* automatically, so leave gutters where the design wants them. */
|
|
332
|
+
export interface FreeformAreaSpec {
|
|
333
|
+
/** Left edge, % of body width; x + w must stay ≤ 100.
|
|
334
|
+
* @minimum 0
|
|
335
|
+
* @maximum 100 */
|
|
336
|
+
x: number;
|
|
337
|
+
/** Top edge, % of body height; y + h must stay ≤ 100.
|
|
338
|
+
* @minimum 0
|
|
339
|
+
* @maximum 100 */
|
|
340
|
+
y: number;
|
|
341
|
+
/** Width, % of body width.
|
|
342
|
+
* @exclusiveMinimum 0
|
|
343
|
+
* @maximum 100 */
|
|
344
|
+
w: number;
|
|
345
|
+
/** Height, % of body height.
|
|
346
|
+
* @exclusiveMinimum 0
|
|
347
|
+
* @maximum 100 */
|
|
348
|
+
h: number;
|
|
349
|
+
}
|
|
350
|
+
/** Type-scale steps a freeform item's text may name — theme steps, never px.
|
|
351
|
+
* One list for all three kinds of label: a `text` run, a `box`'s centred
|
|
352
|
+
* label and a `shape`'s. A second copy of the union is how a callout ends up
|
|
353
|
+
* offering a size the renderer has never heard of.
|
|
354
|
+
*
|
|
355
|
+
* `xs` is the caption step the theme has always defined and this list used
|
|
356
|
+
* to stop short of. It was added because the flow→freeform conversion needs
|
|
357
|
+
* it: a source line is set at `--text-xs`, and with the smallest name
|
|
358
|
+
* available being `sm` the conversion had to round it UP — so converting a
|
|
359
|
+
* slide grew its source line by 2px, which is exactly the kind of silent
|
|
360
|
+
* shift that makes a converted slide read as subtly wrong. Reach for it for
|
|
361
|
+
* captions and footnotes, not for body copy. */
|
|
362
|
+
export type FreeformTextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
363
|
+
/**
|
|
364
|
+
* Every colour the theme defines, as one palette.
|
|
365
|
+
*
|
|
366
|
+
* Text and fills used to draw from two lists of different widths — a caption
|
|
367
|
+
* could be `positive` green while a box could not be filled with it, for no
|
|
368
|
+
* reason anyone chose. One palette, and what a token is *for* is the
|
|
369
|
+
* author's business: the inspector paints each swatch in the colour this set
|
|
370
|
+
* resolves it to, so the choice is made by looking rather than by guessing
|
|
371
|
+
* what a name means.
|
|
372
|
+
*
|
|
373
|
+
* Readability is deliberately not enforced. A `faint` label on a `faint` box
|
|
374
|
+
* is unreadable and is also nobody's business but the author's — the swatch
|
|
375
|
+
* showed them both. What the platform still guarantees is that these are
|
|
376
|
+
* TOKENS: a deck survives a set swap, and the exporter resolves every one to
|
|
377
|
+
* a real PowerPoint fill.
|
|
378
|
+
*/
|
|
379
|
+
export type FreeformColor = 'text' | 'muted' | 'faint' | 'inverse' | 'accent' | 'accent-deep' | 'accent-soft' | 'positive' | 'negative' | 'bg' | 'box' | 'box-border' | 'edge-muted' | 'highlight' | 'highlight-soft' | 'highlight-deep';
|
|
380
|
+
/** Text colors a freeform item may name — the whole palette. */
|
|
381
|
+
export type FreeformTextColor = FreeformColor;
|
|
382
|
+
/** Fills a freeform box may name — theme tokens, never raw colors. */
|
|
383
|
+
/** Fills a freeform box may name: the palette, plus `none` for no fill at
|
|
384
|
+
* all — which is a box's own state rather than a colour, and the one thing
|
|
385
|
+
* text has no use for. */
|
|
386
|
+
export type FreeformBoxFill = FreeformColor | 'none';
|
|
387
|
+
/** Gradient fills — box only (the SVG preset shapes stay solid). Each is a
|
|
388
|
+
* theme-defined two-stop gradient: `accent-gradient` the saturated hero/tile
|
|
389
|
+
* wash (labels render inverse), `soft-gradient` a barely-there panel wash. */
|
|
390
|
+
export type FreeformGradientFill = 'accent-gradient' | 'soft-gradient';
|
|
391
|
+
/** Corner rounding steps a freeform box may name — theme radii, never px.
|
|
392
|
+
* `pill` fully rounds the shorter edge (stadium/capsule). */
|
|
393
|
+
export type FreeformRadius = 'none' | 'sm' | 'md' | 'lg' | 'pill';
|
|
394
|
+
/** Drop-shadow steps a freeform box may name — theme shadows, never raw
|
|
395
|
+
* values. `soft` is a subtle lift, `lifted` a pronounced card float. */
|
|
396
|
+
export type FreeformShadow = 'none' | 'soft' | 'lifted';
|
|
397
|
+
/** Fields every freeform item carries.
|
|
398
|
+
*
|
|
399
|
+
* `id` is the item's identity across slides: items sharing an id on
|
|
400
|
+
* *adjacent* freeform slides morph into each other when presenting
|
|
401
|
+
* (position, size — reveal.js auto-animate) and the exported PPTX carries
|
|
402
|
+
* a PowerPoint Morph transition matching them the same way. Duplicate a
|
|
403
|
+
* slide, move the items, and the motion designs itself. Omit it and the
|
|
404
|
+
* item simply cuts like everything else. */
|
|
405
|
+
interface FreeformItemBase {
|
|
406
|
+
/** Stable identity for cross-slide morphing. Unique within a slide;
|
|
407
|
+
* reuse the same id on the neighbouring slide to animate between them. */
|
|
408
|
+
id?: string;
|
|
409
|
+
area: FreeformAreaSpec;
|
|
410
|
+
}
|
|
411
|
+
/** Everything an item that the platform *draws* can carry on top of its
|
|
412
|
+
* placement. `chart` and `table` deliberately do not extend this: both
|
|
413
|
+
* export as a PPTX graphicFrame, which carries neither a rotation nor an
|
|
414
|
+
* alpha, so offering either there would be a control that works on screen
|
|
415
|
+
* and is silently dropped in the .pptx. A field that only sometimes
|
|
416
|
+
* survives is worse than one that is not offered. */
|
|
417
|
+
interface FreeformDrawnBase extends FreeformItemBase {
|
|
418
|
+
/** 0–1. Fades the whole item — everything it draws, together: a box's
|
|
419
|
+
* fill, border and label, a picture, a caption, a component's subtree.
|
|
420
|
+
* For washes and de-emphasis. Default 1.
|
|
421
|
+
* @minimum 0
|
|
422
|
+
* @maximum 1 */
|
|
423
|
+
opacity?: number;
|
|
424
|
+
/** Rotation in degrees, clockwise about the item's centre. Small tilts
|
|
425
|
+
* (±4–8°) read as a deliberate accent — a snapshot pinned to the slide, a
|
|
426
|
+
* stamp over a panel; keep anything meant to be read level.
|
|
427
|
+
* @minimum -180
|
|
428
|
+
* @maximum 180 */
|
|
429
|
+
rotate?: number;
|
|
430
|
+
}
|
|
431
|
+
/** A run of text in one grid area. Font, exact sizes and colors come from
|
|
432
|
+
* the theme; the size/weight/color fields pick among its tokens. */
|
|
433
|
+
export interface FreeformTextSpec extends FreeformDrawnBase {
|
|
434
|
+
type: 'text';
|
|
435
|
+
/** Separate strings are separate paragraphs. */
|
|
436
|
+
text: string | string[];
|
|
437
|
+
/** Type-scale step. Default 'md'. */
|
|
438
|
+
size?: FreeformTextSize;
|
|
439
|
+
weight?: 'regular' | 'medium' | 'bold';
|
|
440
|
+
color?: FreeformTextColor;
|
|
441
|
+
align?: 'left' | 'center' | 'right';
|
|
442
|
+
/** Vertical placement inside the area. Default 'top'. */
|
|
443
|
+
valign?: 'top' | 'middle' | 'bottom';
|
|
444
|
+
/**
|
|
445
|
+
* Line spacing, as a ratio of the font size. Default absent, which is the
|
|
446
|
+
* theme's body leading (`--line-height`) — so leaving it off is what every
|
|
447
|
+
* existing slide already does and nothing moves.
|
|
448
|
+
*
|
|
449
|
+
* It exists because display type wants tighter leading than body copy, and
|
|
450
|
+
* a freeform text item had no way to ask: a 44px numeral set at the body's
|
|
451
|
+
* 1.4 sits 9px lower in its line box than the same numeral in a fixed
|
|
452
|
+
* layout, which is what made a converted slide's headings visibly jump
|
|
453
|
+
* against an unconverted one. `tight` for big numerals and display lines,
|
|
454
|
+
* `snug` for headings, `relaxed` for large body copy.
|
|
455
|
+
*/
|
|
456
|
+
leading?: 'tight' | 'snug' | 'relaxed' | 'normal';
|
|
457
|
+
}
|
|
458
|
+
/** A rectangle in one grid area: background panel, accent bar (span one
|
|
459
|
+
* row/column for a line), or a labeled shape. With `radius` and `shadow` it
|
|
460
|
+
* becomes a card — the rounded, lifted tile a styled slide is built from. */
|
|
461
|
+
export interface FreeformBoxSpec extends FreeformDrawnBase {
|
|
462
|
+
type: 'box';
|
|
463
|
+
/** Optional label, centered in the box. */
|
|
464
|
+
text?: string;
|
|
465
|
+
/** Type-scale step for that label — a number in a big callout tile and a
|
|
466
|
+
* word on a small badge are not the same size. Ignored when there is no
|
|
467
|
+
* `text`. Default 'md'. */
|
|
468
|
+
size?: FreeformTextSize;
|
|
469
|
+
/** Default 'box' (the neutral panel fill). 'none' with a border is an
|
|
470
|
+
* outline-only frame; the gradient fills are theme-defined washes. */
|
|
471
|
+
fill?: FreeformBoxFill | FreeformGradientFill;
|
|
472
|
+
/** The label's colour. Default 'text'. Worth setting whenever `fill` is
|
|
473
|
+
* dark: the label does not pick a readable ink for you, because "readable"
|
|
474
|
+
* depends on a theme's actual colours and guessing it would quietly
|
|
475
|
+
* override an author who meant it. */
|
|
476
|
+
color?: FreeformColor;
|
|
477
|
+
/** Outline: `true` for the theme's panel border, or a token to pick its
|
|
478
|
+
* colour. Default false. */
|
|
479
|
+
border?: boolean | FreeformColor;
|
|
480
|
+
/** How thick that outline is. Default 'hairline' — 1px, which is what
|
|
481
|
+
* every bordered box drew before this existed, so leaving it off changes
|
|
482
|
+
* nothing.
|
|
483
|
+
*
|
|
484
|
+
* The ladder is the LINE's (`hairline` 1, `regular` 2, `heavy` 4), so the
|
|
485
|
+
* two cannot disagree about what "heavy" means. Note the different
|
|
486
|
+
* defaults: a line defaults to `regular` because a 1px connector is
|
|
487
|
+
* usually too faint to follow, while a border defaults to `hairline`
|
|
488
|
+
* because an outline is a boundary, not a stroke. */
|
|
489
|
+
borderWidth?: 'hairline' | 'regular' | 'heavy';
|
|
490
|
+
/** Corner rounding. Default 'sm' (the theme's base radius). */
|
|
491
|
+
radius?: FreeformRadius;
|
|
492
|
+
/** Drop shadow. Default 'none'. Use on cards over a plain background —
|
|
493
|
+
* never on thin bars or full-bleed panels. */
|
|
494
|
+
shadow?: FreeformShadow;
|
|
495
|
+
}
|
|
496
|
+
/** Shapes beyond the rectangle. Each is a PowerPoint preset, so an exported
|
|
497
|
+
* deck carries a real editable shape rather than a picture of one:
|
|
498
|
+
*
|
|
499
|
+
* - `ellipse` — the oval, and the only circle there is: give it a square
|
|
500
|
+
* area and it *is* a circle (there is no separate `circle` kind)
|
|
501
|
+
* - `ring` — a donut; the hole is a quarter of the width, PowerPoint's default
|
|
502
|
+
* - `triangle` — apex at the top centre
|
|
503
|
+
* - `diamond`
|
|
504
|
+
* - `chevron` — the arrow block a process row is built from
|
|
505
|
+
* - `callout` — a rounded speech bubble with a tail; see `tail` for where
|
|
506
|
+
* the tail points
|
|
507
|
+
* - `arrow` — a right-pointing block arrow; rotate it for the other three
|
|
508
|
+
* - `star` — five-pointed
|
|
509
|
+
* - `hexagon` — flat top and bottom, points left and right
|
|
510
|
+
* - `pentagon` — regular, apex at the top centre
|
|
511
|
+
* - `cylinder` — a can seen from slightly above; the database/volume glyph
|
|
512
|
+
* - `parallelogram` — the leaning rectangle (a flow-chart input step)
|
|
513
|
+
*
|
|
514
|
+
* All stretch to fill their area, exactly as their PowerPoint counterparts
|
|
515
|
+
* do, so a circle needs a square area.
|
|
516
|
+
*/
|
|
517
|
+
export type FreeformShapeKind = 'ellipse' | 'ring' | 'triangle' | 'diamond' | 'chevron' | 'callout' | 'arrow' | 'star' | 'hexagon' | 'pentagon' | 'cylinder' | 'parallelogram';
|
|
518
|
+
/** The tip of a `callout`'s tail, in percent of the item's own area. Values
|
|
519
|
+
* outside 0–100 are the point, not a mistake: the bubble fills the area and
|
|
520
|
+
* the tail reaches *out* of it towards whatever is being spoken about — a
|
|
521
|
+
* tip inside the box leaves only a stub. */
|
|
522
|
+
export interface FreeformTailSpec {
|
|
523
|
+
/** % of the item's width; < 0 is left of the bubble, > 100 right of it.
|
|
524
|
+
* @minimum -200
|
|
525
|
+
* @maximum 300 */
|
|
526
|
+
x: number;
|
|
527
|
+
/** % of the item's height; < 0 is above the bubble, > 100 below it.
|
|
528
|
+
* @minimum -200
|
|
529
|
+
* @maximum 300 */
|
|
530
|
+
y: number;
|
|
531
|
+
}
|
|
532
|
+
/** A non-rectangular shape in one area. Fills, borders and the optional
|
|
533
|
+
* centered label work exactly as they do on `box`. */
|
|
534
|
+
export interface FreeformShapeSpec extends FreeformDrawnBase {
|
|
535
|
+
type: 'shape';
|
|
536
|
+
shape: FreeformShapeKind;
|
|
537
|
+
/** Where a `callout`'s tail points — ignored by every other shape.
|
|
538
|
+
* Default `{ x: 25, y: 130 }`, a tail hanging below the bubble's left. */
|
|
539
|
+
tail?: FreeformTailSpec;
|
|
540
|
+
/** Optional label, centered in the shape. Keep it short — a triangle or a
|
|
541
|
+
* ring has far less usable middle than its area suggests. */
|
|
542
|
+
text?: string;
|
|
543
|
+
/** Type-scale step for that label, exactly as on `box`. Ignored when there
|
|
544
|
+
* is no `text`. Default 'md'. */
|
|
545
|
+
size?: FreeformTextSize;
|
|
546
|
+
/** Default 'box' (the neutral panel fill). 'none' with a border is an
|
|
547
|
+
* outline-only shape. */
|
|
548
|
+
fill?: FreeformBoxFill;
|
|
549
|
+
/** The label's colour. Default 'text'; see `FreeformBoxSpec.color`. */
|
|
550
|
+
color?: FreeformColor;
|
|
551
|
+
/** Outline: `true` for the theme's panel border, or a token to pick its
|
|
552
|
+
* colour. Default false. */
|
|
553
|
+
border?: boolean | FreeformColor;
|
|
554
|
+
/** How thick that outline is. Default 'hairline' — 1px, which is what
|
|
555
|
+
* every bordered box drew before this existed, so leaving it off changes
|
|
556
|
+
* nothing.
|
|
557
|
+
*
|
|
558
|
+
* The ladder is the LINE's (`hairline` 1, `regular` 2, `heavy` 4), so the
|
|
559
|
+
* two cannot disagree about what "heavy" means. Note the different
|
|
560
|
+
* defaults: a line defaults to `regular` because a 1px connector is
|
|
561
|
+
* usually too faint to follow, while a border defaults to `hairline`
|
|
562
|
+
* because an outline is a boundary, not a stroke. */
|
|
563
|
+
borderWidth?: 'hairline' | 'regular' | 'heavy';
|
|
564
|
+
}
|
|
565
|
+
/** A straight connector between two points in the body — annotation arrows,
|
|
566
|
+
* flow links, leader lines. Unlike the other items it is placed by its two
|
|
567
|
+
* endpoints, not an area; the exported deck carries a real PowerPoint
|
|
568
|
+
* connector with the same arrowheads.
|
|
569
|
+
*
|
|
570
|
+
* **It deliberately has no `rotate`** — and so deliberately extends neither
|
|
571
|
+
* base: `FreeformItemBase` is `id` + `area`, which a line has no use for,
|
|
572
|
+
* and `FreeformDrawnBase` adds the `rotate` it must not have. A line's
|
|
573
|
+
* rotation IS its endpoints: turning it is moving `to`, and a separate angle
|
|
574
|
+
* would be a second way to say the one thing, with no answer for which wins.
|
|
575
|
+
* Tilt a line by dragging an end, not by typing a number. (`opacity` is
|
|
576
|
+
* absent for the plainer reason that nothing has asked for a faded
|
|
577
|
+
* connector; add it here if that changes.) */
|
|
578
|
+
export interface FreeformLineSpec {
|
|
579
|
+
/** Stable identity for cross-slide morphing (see box/shape `id`). */
|
|
580
|
+
id?: string;
|
|
581
|
+
type: 'line';
|
|
582
|
+
/** Start point, % of body width/height.
|
|
583
|
+
* @minimum 0
|
|
584
|
+
* @maximum 100 */
|
|
585
|
+
from: {
|
|
586
|
+
x: number;
|
|
587
|
+
y: number;
|
|
588
|
+
};
|
|
589
|
+
/** End point, % of body width/height. Arrowheads point here (`arrow:
|
|
590
|
+
* 'end'`). */
|
|
591
|
+
to: {
|
|
592
|
+
x: number;
|
|
593
|
+
y: number;
|
|
594
|
+
};
|
|
595
|
+
/** Default 'text'. */
|
|
596
|
+
color?: FreeformTextColor;
|
|
597
|
+
/** Stroke weight step: hairline 1px, regular 2px, heavy 4px.
|
|
598
|
+
* Default 'regular'. */
|
|
599
|
+
weight?: 'hairline' | 'regular' | 'heavy';
|
|
600
|
+
/** Default 'solid'. */
|
|
601
|
+
style?: 'solid' | 'dashed';
|
|
602
|
+
/** Arrowheads: none, at `to`, or both ends. Default 'none'. */
|
|
603
|
+
arrow?: 'none' | 'end' | 'both';
|
|
604
|
+
}
|
|
605
|
+
/** An image filling one grid area. */
|
|
606
|
+
export interface FreeformImageSpec extends FreeformDrawnBase {
|
|
607
|
+
type: 'image';
|
|
608
|
+
url: string;
|
|
609
|
+
/** cover crops to fill, contain shows the whole image. Default 'cover'. */
|
|
610
|
+
fit?: 'cover' | 'contain';
|
|
611
|
+
}
|
|
612
|
+
/** A chart in one grid area. */
|
|
613
|
+
export interface FreeformChartSpec extends FreeformItemBase {
|
|
614
|
+
type: 'chart';
|
|
615
|
+
chart: ChartSpec;
|
|
616
|
+
}
|
|
617
|
+
/** A table in one grid area. */
|
|
618
|
+
export interface FreeformTableSpec extends FreeformItemBase {
|
|
619
|
+
type: 'table';
|
|
620
|
+
table: TableSpec;
|
|
621
|
+
}
|
|
622
|
+
/** A platform component or an installed pack component, placed as one item
|
|
623
|
+
* — the KPI stack, the timeline, the driver tree, rendered by the same
|
|
624
|
+
* component the dedicated layout uses, with the theme owning every token
|
|
625
|
+
* and CSS owning the internal alignment.
|
|
626
|
+
*
|
|
627
|
+
* This is what to reach for instead of assembling a card out of a box and
|
|
628
|
+
* three text runs: the item carries the component's own spec, so it still
|
|
629
|
+
* follows a theme swap, still answers inline editing, and still exports as
|
|
630
|
+
* real shapes rather than as a drawing of itself.
|
|
631
|
+
*
|
|
632
|
+
* **`area.h` means something different here.** For every other item the
|
|
633
|
+
* four area values are exact. For a component the height is a MINIMUM: the
|
|
634
|
+
* item renders at its natural height and never below `h`, because its
|
|
635
|
+
* content can change under it and a reworded block in a fixed-height box
|
|
636
|
+
* either clips or leaves a hole. Growth runs downward out of the area; it
|
|
637
|
+
* does not push the items below it, because nothing on a freeform slide
|
|
638
|
+
* reflows. `x`, `y` and `w` are exact as everywhere else. Give `h` the
|
|
639
|
+
* height you want the item to occupy when it is empty — a matrix or a
|
|
640
|
+
* driver tree has no natural height of its own and will take exactly `h`. */
|
|
641
|
+
export interface FreeformComponentSpec extends FreeformDrawnBase {
|
|
642
|
+
type: 'component';
|
|
643
|
+
/** Which component, and its spec — see `SlotSpec`. */
|
|
644
|
+
content: SlotSpec;
|
|
645
|
+
}
|
|
646
|
+
export type FreeformItemSpec = FreeformTextSpec | FreeformBoxSpec | FreeformShapeSpec | FreeformLineSpec | FreeformImageSpec | FreeformChartSpec | FreeformTableSpec | FreeformComponentSpec;
|
|
647
|
+
/** Free composition on a 12×8 grid inside the bounded body region — the
|
|
648
|
+
* escape hatch for genuinely bespoke slides. Reach for a fixed layout (or
|
|
649
|
+
* `split`) first: freeform trades their guardrails for placement freedom,
|
|
650
|
+
* and overflowing items are reported, not resolved. Chrome (header, footer,
|
|
651
|
+
* finding, source) still comes from the platform. */
|
|
652
|
+
export interface FreeformSlideSpec {
|
|
653
|
+
layout: 'freeform';
|
|
654
|
+
props: {
|
|
655
|
+
/** Optional: a freeform slide may run header-less, which gives the items
|
|
656
|
+
* the body band the header would have taken. */
|
|
657
|
+
header?: HeaderSpec;
|
|
658
|
+
items: FreeformItemSpec[];
|
|
659
|
+
/** Own the whole 1280x720 canvas instead of the body band, the way the
|
|
660
|
+
* `web` layout's opener does. Every item's `area` is then a percent of
|
|
661
|
+
* the canvas, so items can sit in the safe-area margins and over the
|
|
662
|
+
* chrome bands.
|
|
663
|
+
*
|
|
664
|
+
* This is what a slide converted from a flowed layout runs in: a header
|
|
665
|
+
* and a findings banner sit OUTSIDE the body band, so converting them
|
|
666
|
+
* into items that land where they were needs a coordinate space that
|
|
667
|
+
* reaches them. The header and finding props are dropped by that
|
|
668
|
+
* conversion for the same reason — they are items now, and keeping both
|
|
669
|
+
* would draw them twice. The footer still signs the slide, so keep the
|
|
670
|
+
* bottom strip clear. */
|
|
671
|
+
bleed?: boolean;
|
|
672
|
+
/** Draw the theme's footer. Default true, and only meaningful under
|
|
673
|
+
* `bleed` — a banded body always reserves the footer's strip, so there
|
|
674
|
+
* is nothing to decide there.
|
|
675
|
+
*
|
|
676
|
+
* It exists for the converted cover. A `title` slide is the one layout
|
|
677
|
+
* that signs nothing: no page number, no deck note. Converting one has
|
|
678
|
+
* to be able to say that, or every cover comes back from the conversion
|
|
679
|
+
* wearing a footer it never had. */
|
|
680
|
+
footer?: boolean;
|
|
681
|
+
finding?: FindingSpec;
|
|
682
|
+
source?: string;
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
export {};
|