deckjsx 0.7.0 → 0.8.1

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 CHANGED
@@ -13,9 +13,9 @@ JSX
13
13
  -> Output Writer
14
14
  ```
15
15
 
16
- This project is being designed as a compiler, not as a thin `PptxGenJS` wrapper.
17
- The API uses a class-based compiler with callback-based `.slide()`, `.compile()`, `.project()`, and
18
- `.render()`. Authoring uses typed JSX elements with CSS-like style and class semantics.
16
+ This project is designed as a presentation compiler. The API uses a class-based compiler with
17
+ callback-based `.slide()`, synchronous `.compile()`, async `.project()`, and async `.render()`.
18
+ Authoring uses typed JSX elements with CSS-like style and class semantics.
19
19
 
20
20
  The implementation preserves the compiler model with explicit module boundaries for authoring,
21
21
  semantic graph construction, style resolution, output projection, writer adapters, and runtime
@@ -27,81 +27,112 @@ output.
27
27
  npm install deckjsx
28
28
  ```
29
29
 
30
- The package currently targets PPTX output and ships a temporary `pptxgenjs` writer adapter.
30
+ The package currently targets PPTX output through deckjsx's direct PPTX writer. The public authoring
31
+ surface is `deckjsx`; explicit writer selection lives in `deckjsx/adapter`; inspection helpers live
32
+ in `deckjsx/inspect`.
31
33
 
32
34
  ## Usage
33
35
 
34
36
  ```tsx
35
- import { Deck } from "deckjsx";
37
+ import { Deck, StyleSheet, Theme } from "deckjsx";
36
38
 
37
39
  const deck = new Deck({
38
40
  layout: { width: 13.333, height: 7.5, unit: "in" },
39
41
  meta: { title: "Quarterly Review", author: "deckjsx" },
42
+ templates: {
43
+ report: {
44
+ areas: {
45
+ title: { kind: "title", frame: { x: 0.7, y: 0.5, width: 11.9, height: 0.8 } },
46
+ body: { frame: { x: 0.7, y: 1.5, width: 11.9, height: 4.9 } },
47
+ footer: { frame: { x: 0.7, y: 6.9, width: 11.9, height: 0.3 } },
48
+ },
49
+ },
50
+ },
51
+ theme: new Theme({
52
+ defaults: {
53
+ h1: { fontFamily: "Aptos Display", fontSize: 28, fontWeight: 700, color: "#0F172A" },
54
+ p: { fontFamily: "Aptos", fontSize: 18, color: "#334155", fit: "shrink" },
55
+ },
56
+ }),
40
57
  });
41
58
 
59
+ deck.useStyles(
60
+ new StyleSheet({
61
+ classes: {
62
+ review: { backgroundColor: "#F8FAFC" },
63
+ title: { target: "h1.title", style: { width: "100%", height: 0.6 } },
64
+ contentGrid: {
65
+ target: "section.contentGrid",
66
+ style: { display: "grid", gridTemplateColumns: "1fr 1fr", columnGap: 0.35 },
67
+ },
68
+ lead: { target: "p.lead", style: { lineHeight: 1.2 } },
69
+ chartFrame: { backgroundColor: "#E0F2FE", borderRadius: 0.15, padding: 0.25 },
70
+ chart: { width: "100%", height: "100%", fit: "contain" },
71
+ footerText: {
72
+ target: "p.footerText",
73
+ style: { width: "100%", height: 0.3, fontSize: 11, color: "#64748B", textAlign: "right" },
74
+ },
75
+ },
76
+ }),
77
+ );
78
+
42
79
  deck.slide(
43
- { name: "Quarterly Review", style: { backgroundColor: "#F8FAFC" } },
44
- ({ composition }) => (
45
- <>
46
- <main
47
- style={{
48
- x: 0.7,
49
- y: 0.5,
50
- width: 11.9,
51
- height: 6.3,
52
- display: "grid",
53
- gridTemplateRows: ["0.9in", "1fr", "0.4in"],
54
- rowGap: 0.25,
55
- }}
56
- >
57
- <header>
58
- <h1
59
- style={{ width: "100%", height: 0.6, fontSize: 28, fontWeight: 700, color: "#0F172A" }}
60
- >
61
- Quarterly Review
62
- </h1>
63
- </header>
64
-
65
- <section style={{ display: "grid", gridTemplateColumns: "1fr 1fr", columnGap: 0.35 }}>
66
- <p style={{ fontSize: 18, color: "#334155", fit: "shrink" }}>
67
- Author slides with typed JSX, inspect the projected document model, and render PPTX
68
- files.
69
- </p>
70
- <figure style={{ backgroundColor: "#E0F2FE", borderRadius: 0.15, padding: 0.25 }}>
71
- <img src="chart.png" style={{ width: "100%", height: "100%", fit: "contain" }} />
72
- </figure>
73
- </section>
74
-
75
- <footer>
76
- <p
77
- style={{
78
- width: "100%",
79
- height: 0.3,
80
- fontSize: 11,
81
- color: "#64748B",
82
- textAlign: "right",
83
- }}
84
- >
85
- {composition.slideIndex + 1} / {composition.totalSlides}
86
- </p>
87
- </footer>
88
- </main>
89
- </>
80
+ { name: "Quarterly Review", template: "report", className: "review" },
81
+ ({ composition, template }) => (
82
+ <main>
83
+ <h1 area={template.title} className="title">
84
+ Quarterly Review
85
+ </h1>
86
+
87
+ <section area={template.body} className="contentGrid" style={{ columnGap: 0.45 }}>
88
+ <p className="lead" style={{ color: "#1E293B" }}>
89
+ Author slides with typed JSX, inspect the projected document model, and render PPTX files.
90
+ </p>
91
+ <figure className="chartFrame">
92
+ <img src="chart.png" className="chart" />
93
+ </figure>
94
+ </section>
95
+
96
+ <p area={template.footer} className="footerText">
97
+ {composition.slideIndex + 1} / {composition.totalSlides}
98
+ </p>
99
+ </main>
90
100
  ),
91
101
  );
92
102
 
93
- const project = deck.project();
94
- await deck.render({ output: "quarterly-review.pptx" });
103
+ const projected = await deck.project();
104
+ if (!projected.ok) {
105
+ console.warn(projected.diagnostics.items);
106
+ }
107
+
108
+ const rendered = await deck.render({ output: "quarterly-review.pptx" });
109
+ if (!rendered.ok) {
110
+ throw new Error("PPTX render failed");
111
+ }
95
112
  ```
96
113
 
97
- Use `deck.compile()` for authoring semantics, `deck.project()` for output-facing inspection, and
98
- `deck.render({ output })` when writing a PowerPoint file.
114
+ Use `deck.compile()` for authoring semantics, `await deck.project()` for output-facing inspection,
115
+ and `await deck.render({ output })` when writing a PowerPoint file.
116
+
117
+ When a hot path only needs the projected model or rendered artifact, inspection summaries can be
118
+ skipped with `await deck.project({ inspection: "none" })` or
119
+ `await deck.render({ inspection: "none" })`.
120
+
121
+ The default render path uses deckjsx's built-in direct PPTX writer. If an explicit writer adapter is
122
+ needed, import `pptx()` from `deckjsx/adapter`. Writer internals such as XML emitters, ZIP assembly,
123
+ and output sinks are intentionally not part of the public API.
124
+
125
+ ```tsx
126
+ import { pptx } from "deckjsx/adapter";
127
+
128
+ await deck.render(pptx({ output: "quarterly-review.pptx" }));
129
+ ```
99
130
 
100
131
  ## JSX elements
101
132
 
102
- `deckjsx` supports both the original capitalized components and a typed HTML-like JSX surface.
133
+ `deckjsx` exposes a typed HTML-like JSX authoring surface.
103
134
 
104
- View-like elements compile to grouped layout containers:
135
+ View-like lowercase elements compile to grouped layout containers:
105
136
 
106
137
  ```tsx
107
138
  <main>
@@ -115,7 +146,7 @@ View-like elements compile to grouped layout containers:
115
146
  </main>
116
147
  ```
117
148
 
118
- Text-like elements compile to text boxes:
149
+ Text-like lowercase elements compile to text boxes:
119
150
 
120
151
  ```tsx
121
152
  <h1>Title</h1>
@@ -123,33 +154,180 @@ Text-like elements compile to text boxes:
123
154
  <p>Body copy</p>
124
155
  ```
125
156
 
126
- Image elements compile to images and require either `src` or `data`:
157
+ Image lowercase elements compile to images and require either `src` or `data`:
158
+
159
+ ```tsx
160
+ <img src="diagram.png" className="diagram" />
161
+ ```
162
+
163
+ The lowercase `shape` element compiles to PPTX shapes:
164
+
165
+ ```tsx
166
+ <shape shape="rect" className="accentBlock" />
167
+ ```
168
+
169
+ ## Layout, Style, And Templates
170
+
171
+ `deckjsx` keeps layout, style, and templates as separate authoring ideas even when they are written
172
+ through JSX and CSS-like objects.
173
+
174
+ - Layout describes where things are and how children flow: deck slide size, `x`, `y`, `width`,
175
+ `height`, `left`, `top`, `right`, `bottom`, `display`, flex, grid, gaps, padding, and stacking
176
+ order. Project resolves these values into concrete frames and paint order.
177
+ - Style describes how resolved boxes are drawn: fills, borders, shadows, opacity, rotation, text
178
+ color, font, alignment, bullets, links, image fitting, and background layers.
179
+ - Templates describe reusable slide structure: named areas such as `title`, `body`, `media`, or
180
+ `footer` that authored JSX can target without exposing PowerPoint placeholder ids.
181
+
182
+ Reusable layout and appearance should usually live in `StyleSheet` classes and `Theme` defaults.
183
+ Use the JSX `style` prop for slide-local variations, data-dependent overrides, or one-off values
184
+ that should stay close to the authored element. Direct style props exist in the current v0.8 surface,
185
+ but they are not the preferred HTML/CSS-like authoring form and are planned to be removed in v0.8.1.
186
+
187
+ Templates should be used when the same semantic slide regions repeat across slides; layout should be
188
+ used for per-slide geometry and flow; visual style should be used for appearance after the geometry
189
+ is known.
190
+
191
+ ## Style Cascade
192
+
193
+ In deckjsx, cascade means the per-element process that turns defaults, theme defaults, stylesheet
194
+ classes, and inline authoring styles into one resolved style snapshot for Project. It is CSS-like,
195
+ but it is not a full browser CSS engine and does not mean every property automatically inherits from
196
+ parent elements.
197
+
198
+ For each style-capable element, values are resolved in this order:
199
+
200
+ 1. Element defaults, such as default text box behavior.
201
+ 2. `Theme` defaults for the authored tag, such as `p`, `h1`, `div`, `span`, or `img`.
202
+ 3. Matching `StyleSheet` class rules registered with `deck.useStyles()`.
203
+ 4. Authored inline style from the JSX `style` object.
204
+
205
+ Later layers replace earlier layers property by property. The v0.8 authoring surface still accepts
206
+ some direct style props, but new examples should prefer `style={{ ... }}` for inline values because
207
+ direct style props are planned for removal in v0.8.1.
208
+
209
+ ```tsx
210
+ import { Deck, StyleSheet, Theme } from "deckjsx";
211
+
212
+ const deck = new Deck({
213
+ layout: { width: 13.333, height: 7.5, unit: "in" },
214
+ theme: new Theme({
215
+ defaults: {
216
+ p: { color: "#334155", fontSize: 18 },
217
+ },
218
+ }),
219
+ });
220
+
221
+ deck.useStyles(
222
+ new StyleSheet({
223
+ classes: {
224
+ muted: { color: "#64748B" },
225
+ title: { target: "p.title", style: { color: "#0F172A", fontSize: 28, fontWeight: 700 } },
226
+ },
227
+ }),
228
+ );
229
+
230
+ deck.slide(() => <p className="muted title">Revenue</p>);
231
+ ```
232
+
233
+ In this example, `fontSize`, `fontWeight`, and `color` come from the matching `title` class, and the
234
+ theme default supplies any remaining `p` defaults. `className`
235
+ token order is preserved for inspection, but it is not the priority rule for conflicting class
236
+ styles. Class conflicts are resolved by selector specificity first, then stylesheet registration and
237
+ rule order. Supported selectors are intentionally small: class selectors, tag/class compounds, and
238
+ descendant selectors such as `.title`, `p.title`, or `.card .caption`.
239
+
240
+ Style cascade is source-local. A mounted child deck resolves its own theme and stylesheets against
241
+ its own slides, which keeps sandboxed and HMR-style composition predictable.
242
+
243
+ ## Slide Templates
244
+
245
+ Deck templates describe reusable slide structure without asking authors to write PowerPoint
246
+ placeholder ids. Define named Template Areas on the Deck, then place authored content through the
247
+ typed `template` handle passed to templated slide factories:
127
248
 
128
249
  ```tsx
129
- <img src="diagram.png" style={{ width: 4, height: 2.5, fit: "contain" }} />
250
+ const deck = new Deck({
251
+ layout: { width: 13.333, height: 7.5, unit: "in" },
252
+ templates: {
253
+ report: {
254
+ areas: {
255
+ title: { kind: "title", frame: { x: 0.7, y: 0.6, width: 8, height: 0.8 } },
256
+ body: { frame: { x: 0.7, y: 1.6, width: 8, height: 4.8 } },
257
+ },
258
+ },
259
+ },
260
+ });
261
+
262
+ deck.slide({ template: "report" }, ({ template }) => (
263
+ <main>
264
+ <h1 area={template.title}>Quarterly Review</h1>
265
+ <section area={template.body}>Performance highlights</section>
266
+ </main>
267
+ ));
130
268
  ```
131
269
 
270
+ `TemplateArea.kind` is an authoring-level hint such as `"title"`, `"body"`, `"picture"`, or
271
+ `"generic"`. Missing kinds default to `"generic"` and are not inferred from area names. Project keeps
272
+ Template Area anchors visible in the Pptx Package Model inspection surface, while the writer decides
273
+ how to serialize the corresponding PPTX slide layout structure.
274
+
275
+ ## Assets
276
+
277
+ Image sources are resolved through the asset loading boundary. The core package includes
278
+ multi-runtime handling for data/bytes and absolute URL-like sources, while filesystem paths,
279
+ framework-public assets, authenticated URLs, and app media stores should be provided with
280
+ `deck.useAssets(loader)`.
281
+ For built-in data, bytes, and absolute URL-like image sources, Project probes PNG, GIF, JPEG, and
282
+ SVG dimensions into media metadata without putting media bytes into the Pptx Package Model.
283
+
284
+ ```tsx
285
+ import type { AssetLoader } from "deckjsx";
286
+
287
+ const publicAssets = {
288
+ name: "public-assets",
289
+ async probe({ source }) {
290
+ if (source.kind !== "path") return undefined;
291
+ return { mediaType: "image/png", extension: "png", width: 1200, height: 800 };
292
+ },
293
+ async load({ source }) {
294
+ if (source.kind !== "path") return undefined;
295
+ const bytes = await loadFromYourRuntime(source.path);
296
+ return { bytes, mediaType: "image/png", extension: "png", width: 1200, height: 800 };
297
+ },
298
+ } satisfies AssetLoader;
299
+
300
+ deck.useAssets(publicAssets);
301
+ ```
302
+
303
+ Registered loaders run in registration order before the built-in fallback. Project uses `probe()` for
304
+ metadata needed by the Pptx Package Model, and Render uses the same winning resolver scope for
305
+ `load()` so media metadata and bytes come from the same runtime assumptions.
306
+ If a loader claims an image source but cannot provide dimensions, treat that as an asset data
307
+ retrieval failure and report it through Project diagnostics rather than waiting for the writer to
308
+ guess.
309
+
132
310
  Primitive string and number children inside view-like elements are normalized to implicit text
133
311
  nodes. Inline rich text uses `span` inside text-like elements:
134
312
 
135
313
  ```tsx
136
314
  <p>
137
- Revenue grew <span style={{ color: "#16A34A", fontWeight: 700 }}>12%</span>.
315
+ Revenue grew <span className="positiveDelta">12%</span>.
138
316
  </p>
139
317
  ```
140
318
 
141
- ## View Layout Semantics
319
+ ## View-like Layout Semantics
142
320
 
143
- `View` is a containing block for its children. Child `x`, `y`, `left`, `top`, `right`,
144
- `bottom`, `width`, and `height` values are resolved relative to the parent `View`, not
321
+ View-like elements are containing blocks for their children. Child `x`, `y`, `left`, `top`, `right`,
322
+ `bottom`, `width`, and `height` values are resolved relative to the parent view-like element, not
145
323
  the slide, so authors can build panels with local coordinates. Percentage lengths use
146
324
  the parent frame as their reference.
147
325
 
148
326
  ```tsx
149
- <View style={{ x: 1, y: 1, width: 6, height: 3 }}>
150
- <Text style={{ x: "10%", y: "20%", width: "50%", height: "25%" }}>local percent frame</Text>
151
- <Text style={{ left: "55%", top: "10%", right: "10%", bottom: "60%" }}>inset frame</Text>
152
- </View>
327
+ <div className="panel">
328
+ <p className="localPercentFrame">local percent frame</p>
329
+ <p className="insetFrame">inset frame</p>
330
+ </div>
153
331
  ```
154
332
 
155
333
  For `display: "flex"` and `display: "grid"`, normal-flow children are laid out inside
@@ -159,13 +337,29 @@ rendering. Absolutely positioned children inside flex or grid containers also us
159
337
  container content frame, including padding, as their containing block.
160
338
 
161
339
  Use direct slide children when you want slide-global absolute placement. Use children
162
- inside a `View` when you want a local, web-like layout region.
340
+ inside a view-like element when you want a local, web-like layout region.
341
+
342
+ `overflow: "hidden"` is projected as clipping metadata rather than treated as an authoring error.
343
+ When CSS-like clipping, transform, opacity, or compositing behavior cannot be represented exactly in
344
+ PPTX yet, Project reports nonblocking warnings and preserves the observable projected values for
345
+ inspection.
346
+
347
+ Unsupported CSS-like meanings that can still produce a structurally valid PPTX are reported through
348
+ Project diagnostics and the inspection surface rather than treated as authoring errors. These records
349
+ include the unsupported feature, the projected value, and a fallback strategy describing which values
350
+ were preserved and which behavior is still missing. Malformed projected unsupported-semantic payloads
351
+ from custom projections fail before Render emits bytes.
163
352
 
164
353
  ## Development
165
354
 
166
355
  ```bash
167
356
  vp install
168
357
  vp check
358
+ vp build
169
359
  vp test
170
- vp pack
360
+ bun run benchmark:pptx -- --iterations 1 --strict
361
+ bun run verify:render -- --skip-raster
171
362
  ```
363
+
364
+ For output or public-surface changes, keep the direct PPTX writer as the documented built-in path.
365
+ Use render verification and XML/package inspection to catch regressions in emitted PPTX structure.
@@ -0,0 +1,34 @@
1
+ import { $ as Diagnostics } from "./index-BlOsGMTm.mjs";
2
+ import { bn as RenderedArtifact, hn as RenderInspectionSummary, in as OutputFormat, rn as InspectionDetailLevel, sn as ProjectionFormat, w as PptxPackageModel } from "./model-oqG9gKTq.mjs";
3
+
4
+ //#region src/adapter.d.ts
5
+ type PptxRenderOptions = {
6
+ readonly output?: string;
7
+ readonly inspection?: InspectionDetailLevel;
8
+ };
9
+ type RenderOptions = PptxRenderOptions;
10
+ type WriterAdapterResult<TFormat extends OutputFormat = OutputFormat> = {
11
+ readonly diagnostics: Diagnostics;
12
+ readonly artifact?: RenderedArtifact<TFormat>;
13
+ readonly summary?: RenderInspectionSummary;
14
+ readonly outputSideEffect?: {
15
+ readonly path: string;
16
+ readonly failure?: {
17
+ readonly message: string;
18
+ };
19
+ };
20
+ };
21
+ type WriterRenderContext = {
22
+ readonly kind: "deckjsx.writerRenderContext";
23
+ };
24
+ type WriterAdapter<TProjection = PptxPackageModel, TFormat extends OutputFormat = OutputFormat> = {
25
+ readonly kind: "deckjsx.writerAdapter";
26
+ readonly name: string;
27
+ readonly projectionFormat: ProjectionFormat;
28
+ readonly format: TFormat;
29
+ readonly options: RenderOptions;
30
+ render(projection: TProjection, context?: WriterRenderContext): Promise<WriterAdapterResult<TFormat>>;
31
+ };
32
+ declare function pptx(options?: PptxRenderOptions): WriterAdapter<PptxPackageModel, "pptx">;
33
+ //#endregion
34
+ export { WriterRenderContext as a, WriterAdapterResult as i, RenderOptions as n, pptx as o, WriterAdapter as r, PptxRenderOptions as t };