deckjsx 0.8.0 → 0.8.2

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,10 +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()`, synchronous `.compile()`, async
18
- `.project()`, and async `.render()`. Authoring uses typed JSX elements with CSS-like style and class
19
- 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.
20
19
 
21
20
  The implementation preserves the compiler model with explicit module boundaries for authoring,
22
21
  semantic graph construction, style resolution, output projection, writer adapters, and runtime
@@ -28,66 +27,88 @@ output.
28
27
  npm install deckjsx
29
28
  ```
30
29
 
31
- The package currently targets PPTX output through deckjsx's direct PPTX writer.
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`.
32
33
 
33
34
  ## Usage
34
35
 
35
36
  ```tsx
36
- import { Deck } from "deckjsx";
37
+ import { Deck, StyleSheet, Theme } from "deckjsx";
37
38
 
38
39
  const deck = new Deck({
39
40
  layout: { width: 13.333, height: 7.5, unit: "in" },
40
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
+ }),
41
57
  });
42
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
+
43
79
  deck.slide(
44
- { name: "Quarterly Review", style: { backgroundColor: "#F8FAFC" } },
45
- ({ composition }) => (
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 style={{ width: "100%", height: 0.6, fontSize: 28, fontWeight: 700, color: "#0F172A" }}>
59
- Quarterly Review
60
- </h1>
61
- </header>
62
-
63
- <section style={{ display: "grid", gridTemplateColumns: "1fr 1fr", columnGap: 0.35 }}>
64
- <p style={{ fontSize: 18, color: "#334155", fit: "shrink" }}>
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" }}>
65
89
  Author slides with typed JSX, inspect the projected document model, and render PPTX files.
66
90
  </p>
67
- <figure style={{ backgroundColor: "#E0F2FE", borderRadius: 0.15, padding: 0.25 }}>
68
- <img src="chart.png" style={{ width: "100%", height: "100%", fit: "contain" }} />
91
+ <figure className="chartFrame">
92
+ <img src="chart.png" className="chart" />
69
93
  </figure>
70
94
  </section>
71
95
 
72
- <footer>
73
- <p
74
- style={{
75
- width: "100%",
76
- height: 0.3,
77
- fontSize: 11,
78
- color: "#64748B",
79
- textAlign: "right",
80
- }}
81
- >
82
- {composition.slideIndex + 1} / {composition.totalSlides}
83
- </p>
84
- </footer>
96
+ <p area={template.footer} className="footerText">
97
+ {composition.slideIndex + 1} / {composition.totalSlides}
98
+ </p>
85
99
  </main>
86
100
  ),
87
101
  );
88
102
 
89
- const project = await deck.project();
90
- 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
+ }
91
112
  ```
92
113
 
93
114
  Use `deck.compile()` for authoring semantics, `await deck.project()` for output-facing inspection,
@@ -98,8 +119,8 @@ skipped with `await deck.project({ inspection: "none" })` or
98
119
  `await deck.render({ inspection: "none" })`.
99
120
 
100
121
  The default render path uses deckjsx's built-in direct PPTX writer. If an explicit writer adapter is
101
- needed, import `pptx()` from `deckjsx/adapter`; writer internals such as XML emitters, ZIP settings,
102
- and sinks are intentionally not part of the public API.
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.
103
124
 
104
125
  ```tsx
105
126
  import { pptx } from "deckjsx/adapter";
@@ -136,15 +157,94 @@ Text-like lowercase elements compile to text boxes:
136
157
  Image lowercase elements compile to images and require either `src` or `data`:
137
158
 
138
159
  ```tsx
139
- <img src="diagram.png" style={{ width: 4, height: 2.5, fit: "contain" }} />
160
+ <img src="diagram.png" className="diagram" />
140
161
  ```
141
162
 
142
163
  The lowercase `shape` element compiles to PPTX shapes:
143
164
 
144
165
  ```tsx
145
- <shape shape="rect" style={{ width: 2, height: 1, fill: "#2563EB" }} />
166
+ <shape shape="rect" className="accentBlock" />
146
167
  ```
147
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
+ Inline `span` text runs inherit text-related parent values such as `color`, `fontFamily`,
241
+ `fontSize`, `fontWeight`, `lineHeight`, `letterSpacing`, `direction`, and wrapping controls. The
242
+ inherited values are visible in resolved-style inspection; Project avoids duplicating inherited-only
243
+ run styles when the parent text box already carries the concrete PPTX text body style.
244
+
245
+ Style cascade is source-local. A mounted child deck resolves its own theme and stylesheets against
246
+ its own slides, which keeps sandboxed and HMR-style composition predictable.
247
+
148
248
  ## Slide Templates
149
249
 
150
250
  Deck templates describe reusable slide structure without asking authors to write PowerPoint
@@ -167,7 +267,9 @@ const deck = new Deck({
167
267
  deck.slide({ template: "report" }, ({ template }) => (
168
268
  <main>
169
269
  <h1 area={template.title}>Quarterly Review</h1>
170
- <section area={template.body}>Performance highlights</section>
270
+ <section area={template.body}>
271
+ <p style={{ width: "100%", height: 0.5 }}>Performance highlights</p>
272
+ </section>
171
273
  </main>
172
274
  ));
173
275
  ```
@@ -212,12 +314,31 @@ If a loader claims an image source but cannot provide dimensions, treat that as
212
314
  retrieval failure and report it through Project diagnostics rather than waiting for the writer to
213
315
  guess.
214
316
 
317
+ When an `img` has probed intrinsic `width` and `height`, layout uses that ratio if the author did
318
+ not provide `aspectRatio`. This means an image with only `width` can derive its projected height,
319
+ and an image with only `height` can derive its projected width. Author `aspectRatio` still wins for
320
+ intentional crops, logos, or placeholder boxes. `aspectRatio: "auto"` is accepted as the CSS-like
321
+ spelling for no authored ratio.
322
+
323
+ For foreground images, use `objectFit` / `fit`, `objectPosition`, and `crop`. `objectFit: "fill"`
324
+ uses the same projection as deckjsx's `stretch` fit; unsupported CSS values such as `"none"` and
325
+ `"scale-down"` are preserved as diagnostics and fall back to `contain`:
326
+
327
+ ```tsx
328
+ <img src="hero.png" style={{ x: 1, y: 1, width: 4 }} />
329
+ <img src="portrait.png" style={{ x: 5.3, y: 1, width: 2, height: 2, objectFit: "cover" }} />
330
+ <img src="map.png" style={{ x: 1, y: 3.4, width: 4, height: 1.6, objectPosition: "right 25% bottom 10%" }} />
331
+ ```
332
+
333
+ For decorative or underlay images inside a box, use background layers with `backgroundSize`,
334
+ `backgroundPosition`, `backgroundRepeat`, `backgroundClip`, and `backgroundOrigin`.
335
+
215
336
  Primitive string and number children inside view-like elements are normalized to implicit text
216
337
  nodes. Inline rich text uses `span` inside text-like elements:
217
338
 
218
339
  ```tsx
219
340
  <p>
220
- Revenue grew <span style={{ color: "#16A34A", fontWeight: 700 }}>12%</span>.
341
+ Revenue grew <span className="positiveDelta">12%</span>.
221
342
  </p>
222
343
  ```
223
344
 
@@ -229,9 +350,9 @@ the slide, so authors can build panels with local coordinates. Percentage length
229
350
  the parent frame as their reference.
230
351
 
231
352
  ```tsx
232
- <div style={{ x: 1, y: 1, width: 6, height: 3 }}>
233
- <p style={{ x: "10%", y: "20%", width: "50%", height: "25%" }}>local percent frame</p>
234
- <p style={{ left: "55%", top: "10%", right: "10%", bottom: "60%" }}>inset frame</p>
353
+ <div className="panel">
354
+ <p className="localPercentFrame">local percent frame</p>
355
+ <p className="insetFrame">inset frame</p>
235
356
  </div>
236
357
  ```
237
358
 
@@ -241,6 +362,10 @@ and simple `gridColumn` / `gridRow` spans resolve to concrete slide coordinates
241
362
  rendering. Absolutely positioned children inside flex or grid containers also use the
242
363
  container content frame, including padding, as their containing block.
243
364
 
365
+ `display: "flex"` follows CSS-like defaults for the supported subset: row direction when
366
+ `flexDirection` is omitted, and cross-axis stretch when `alignItems` is omitted. The older
367
+ deck-specific `layout: "stack"` default remains vertical.
368
+
244
369
  Use direct slide children when you want slide-global absolute placement. Use children
245
370
  inside a view-like element when you want a local, web-like layout region.
246
371
 
@@ -255,20 +380,39 @@ include the unsupported feature, the projected value, and a fallback strategy de
255
380
  were preserved and which behavior is still missing. Malformed projected unsupported-semantic payloads
256
381
  from custom projections fail before Render emits bytes.
257
382
 
383
+ ## CSS-like Defaults And Gotchas
384
+
385
+ deckjsx intentionally stays close to HTML/CSS naming, but the current v0.8 layout engine is a slide
386
+ layout solver, not a browser. These are the defaults most likely to surprise CSS authors:
387
+
388
+ | Area | deckjsx v0.8 behavior | Browser expectation | Guidance |
389
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
390
+ | Block views | `display: "block"` creates a local containing block and vertically flows unpositioned children; explicit frame props still opt into local absolute placement. | Block formatting creates vertical flow. | Use ordinary block flow for simple stacks; use flex/grid or explicit frames for decks. |
391
+ | Flex | `display: "flex"` defaults to row and cross-axis stretch. | Same for the supported subset. | Prefer flex for simple rows/columns. |
392
+ | Sizing | Block-flow text gets available width and a line-height based height; explicit/local absolute boxes still need size, insets, layout stretch, or image ratio. | Many elements have intrinsic or content-based size. | Use declared sizes for precise PPTX geometry; rely on block defaults for simple text. |
393
+ | Text measurement | Stack/grid do not measure wrapped text to push later siblings. | Browser layout uses measured content. | Use declared heights or `fit: "shrink"` for text boxes. |
394
+ | Units | Layout numbers are inches; font-size-like numbers are points. Strings support common CSS units including `cm`, `mm`, `Q`, `pc`, `vmin`, and `vmax`. | CSS unitless numbers are property-specific. | Use strings for CSS-like units when supported, or keep numeric domains explicit. |
395
+ | CSS-wide keywords | `initial`, `inherit`, `unset`, `revert`, and `revert-layer` fall back to supported-subset defaults with diagnostics where full cascade/reset semantics are missing. | CSS has full cascade defaulting semantics. | Prefer ordinary omission for defaults; inspect diagnostics when using reset keywords. |
396
+ | Text spacing | `letterSpacing` accepts `normal` or point lengths; paragraph before/after spacing accepts point lengths. | CSS text spacing uses property-specific length rules. | Prefer `px`, `pt`, `em`, or `rem` when porting CSS-like text spacing. |
397
+ | Style keys | Unsupported CSS-like property names produce nonblocking compile warnings and remain visible in graph inspection. | Browsers ignore invalid declarations after parsing rules. | Use supported style keys; expect warnings for `flex`, `flexFlow`, or logical aliases. |
398
+ | Box sizing | Containers, text, and shapes default to `border-box`. | CSS initial is `content-box`. | This is deliberate for slide geometry. |
399
+ | Border radius | Single-value `borderRadius` supports percentages against the projected short side. | CSS supports richer per-corner radii. | `borderRadius: "50%"` works for capsule-like PPTX geometry. |
400
+ | Shadows | One shadow layer projects offset/blur/color; spread radius is preserved as unsupported fallback metadata. | CSS box-shadow supports spread and multiple layers. | Avoid relying on spread for exact PPTX output; Project diagnostics preserve it. |
401
+ | Grid | Missing tracks fill the available grid content frame. | CSS implicit tracks default to `auto`. | Declare tracks for precise dashboards. |
402
+ | Images | Foreground images default to contain/center and can use probed natural ratio. | `<img>` has intrinsic layout behavior in normal document flow. | Use one axis plus probed dimensions, or set both axes for a fixed box. |
403
+ | Shapes | Shapes default to visible white fill with no stroke. | CSS boxes are transparent unless styled. | Use `fill: "transparent"` or a `div` when you need a layout/debug box. |
404
+ | zIndex | Simple projected paint-order number. | CSS stacking contexts and `auto`. | Use it for slide paint order, not browser compositing semantics. |
405
+
258
406
  ## Development
259
407
 
260
408
  ```bash
261
409
  vp install
262
410
  vp check
263
- bun run build
264
- npm ci --prefix sample
265
- npm run --prefix sample smoke
411
+ vp build
266
412
  vp test
267
413
  bun run benchmark:pptx -- --iterations 1 --strict
268
414
  bun run verify:render -- --skip-raster
269
- npm run --prefix .github/compat/pptxgenjs compare
270
415
  ```
271
416
 
272
417
  For output or public-surface changes, keep the direct PPTX writer as the documented built-in path.
273
- `pptxgenjs` should appear only in isolated regression tooling, not in runtime dependencies or public
274
- adapter examples.
418
+ Use render verification and XML/package inspection to catch regressions in emitted PPTX structure.
@@ -1,13 +1,9 @@
1
- import { $ as Diagnostics } from "./index-dx2ZSBgF.mjs";
2
- import { bn as RenderedArtifact, hn as RenderInspectionSummary, in as OutputFormat, rn as InspectionDetailLevel, sn as ProjectionFormat, w as PptxPackageModel } from "./model-BVkO8qGK.mjs";
1
+ import { $ as Diagnostics } from "./index-DOxigSfK.mjs";
2
+ import { bn as RenderedArtifact, hn as RenderInspectionSummary, in as OutputFormat, rn as InspectionDetailLevel, sn as ProjectionFormat, w as PptxPackageModel } from "./model-C82_FI4k.mjs";
3
3
 
4
- //#region src/pptx-options.d.ts
5
- type PptxCompressionMode = "balanced" | "fast" | "small" | "store";
6
- //#endregion
7
4
  //#region src/adapter.d.ts
8
5
  type PptxRenderOptions = {
9
6
  readonly output?: string;
10
- readonly compression?: PptxCompressionMode;
11
7
  readonly inspection?: InspectionDetailLevel;
12
8
  };
13
9
  type RenderOptions = PptxRenderOptions;
@@ -35,4 +31,4 @@ type WriterAdapter<TProjection = PptxPackageModel, TFormat extends OutputFormat
35
31
  };
36
32
  declare function pptx(options?: PptxRenderOptions): WriterAdapter<PptxPackageModel, "pptx">;
37
33
  //#endregion
38
- export { WriterRenderContext as a, WriterAdapterResult as i, RenderOptions as n, pptx as o, WriterAdapter as r, PptxCompressionMode as s, PptxRenderOptions as t };
34
+ export { WriterRenderContext as a, WriterAdapterResult as i, RenderOptions as n, pptx as o, WriterAdapter as r, PptxRenderOptions as t };