@office-kit/pptx-dsl 0.6.0 → 0.8.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 +72 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/{jsx-runtime-D7IpzvWG.d.ts → jsx-runtime-BatlHsjp.d.ts} +19 -7
- package/dist/jsx-runtime-CdMn7JwR.js +109 -0
- package/dist/jsx-runtime-CdMn7JwR.js.map +1 -0
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/source-edit.d.ts +65 -0
- package/dist/source-edit.js +97 -0
- package/dist/source-edit.js.map +1 -0
- package/package.json +13 -3
- package/dist/jsx-runtime-DoUxctgW.js +0 -75
- package/dist/jsx-runtime-DoUxctgW.js.map +0 -1
package/README.md
CHANGED
|
@@ -257,6 +257,78 @@ const { default: root } = await import(builtModuleUrl);
|
|
|
257
257
|
const bytes = await savePresentation(await compile(root));
|
|
258
258
|
```
|
|
259
259
|
|
|
260
|
+
## From a shape back to its source
|
|
261
|
+
|
|
262
|
+
`getShapeJsxSources(shape)` returns the source locations of the elements that
|
|
263
|
+
were being evaluated when `compile()` created the shape, outermost first. It needs
|
|
264
|
+
the dev JSX transform described above. A component call contributes its call site,
|
|
265
|
+
so a shape a prebuilt component made (whose own elements carry no location) still
|
|
266
|
+
leads back to `<Headline … />` in the slide file, and every shape a component returns
|
|
267
|
+
through a fragment gets that location. A `Group` has its own location; its members
|
|
268
|
+
keep theirs.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { getShapeId, getSlideShapes, getSlides } from '@office-kit/pptx';
|
|
272
|
+
import { compile, getShapeJsxSources } from '@office-kit/pptx-dsl';
|
|
273
|
+
|
|
274
|
+
const presentation = await compile(root);
|
|
275
|
+
const sources = getSlides(presentation).map((slide) =>
|
|
276
|
+
getSlideShapes(slide).map((shape) => ({
|
|
277
|
+
id: getShapeId(shape),
|
|
278
|
+
sources: getShapeJsxSources(shape),
|
|
279
|
+
})),
|
|
280
|
+
);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- `lineNumber` and `columnNumber` are 1-based and point at the element's `<`, as
|
|
284
|
+
TypeScript's `react-jsxdev` and esbuild's `jsxDev` emit them. `fileName` is the
|
|
285
|
+
name the transform was given.
|
|
286
|
+
- It is `undefined` for shapes built with the production runtime, shapes a source
|
|
287
|
+
deck or `Slide from` brought along, targets of `Fill`, and shapes `Raw` made.
|
|
288
|
+
- It answers for the presentation `compile()` returned, until that presentation's
|
|
289
|
+
slides or shapes are added, removed or regrouped. Nothing is written into the PPTX.
|
|
290
|
+
Shape ids survive `savePresentation` and `loadPresentation`, so a map from
|
|
291
|
+
slide index and `getShapeId` to sources, built before saving, stays valid for the
|
|
292
|
+
saved file.
|
|
293
|
+
|
|
294
|
+
### Editing a literal in place
|
|
295
|
+
|
|
296
|
+
`@office-kit/pptx-dsl/source-edit` holds the text-edit rules the dev preview's
|
|
297
|
+
double-click edit uses, as pure functions over source strings. It needs
|
|
298
|
+
`typescript`, an optional peer dependency.
|
|
299
|
+
|
|
300
|
+
`planTextEdit` finds the one source literal that renders `before` and returns the
|
|
301
|
+
file with it replaced by `after`: JSX text (whitespace collapsed the way JSX renders
|
|
302
|
+
it), an attribute string, a string literal or a template literal without
|
|
303
|
+
substitutions. JSX gets `{"…"}`, so any text is safe. It refuses with a reason code:
|
|
304
|
+
|
|
305
|
+
| Reason | Meaning |
|
|
306
|
+
| --------------------- | ------------------------------------------------------------------------------------ |
|
|
307
|
+
| `invalid` | `before` is blank, `before` equals `after`, or either exceeds `TEXT_EDIT_MAX_LENGTH` |
|
|
308
|
+
| `not-unique-on-slide` | `before` does not occur exactly once in `slideText` |
|
|
309
|
+
| `not-found` | No literal matches: the text is computed, formatted, or in another file |
|
|
310
|
+
| `ambiguous` | More than one literal matches |
|
|
311
|
+
|
|
312
|
+
Pass an `anchor` (a `getShapeJsxSources` entry, with `fileName` as `path`) to search
|
|
313
|
+
only the element that starts there; two literals inside that element are still
|
|
314
|
+
`ambiguous`. Only `files` are searched, so leave shared components out to keep their
|
|
315
|
+
literals untouched.
|
|
316
|
+
|
|
317
|
+
After rebuilding with the change, `verifyTextEdit` confirms that the only difference
|
|
318
|
+
is `before` → `after` on the edited slide, or returns `slide-count-changed`,
|
|
319
|
+
`other-slides-changed` or `text-mismatch`. Writing, rebuilding and rolling back are
|
|
320
|
+
the caller's.
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { planTextEdit, verifyTextEdit } from '@office-kit/pptx-dsl/source-edit';
|
|
324
|
+
|
|
325
|
+
const plan = planTextEdit({ files, slideText, before, after, anchor });
|
|
326
|
+
if (plan.ok) {
|
|
327
|
+
const next = await rebuild(plan.change); // { slides: svg[], slideTexts: string[] }
|
|
328
|
+
const verdict = verifyTextEdit({ previous, next, slide, before, after });
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
260
332
|
## Current coverage
|
|
261
333
|
|
|
262
334
|
This is an initial DSL, not complete OOXML coverage. Typed master/layout creation,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as
|
|
1
|
+
import { c as JsxSource, d as RawContext, f as getShapeJsxSources, l as Node, o as Child, s as Children, t as Fragment, u as PresentationNode } from "./jsx-runtime-BatlHsjp.js";
|
|
2
2
|
import * as api from "@office-kit/pptx";
|
|
3
3
|
|
|
4
4
|
//#region src/index.d.ts
|
|
@@ -204,5 +204,5 @@ declare function Remove(props: {
|
|
|
204
204
|
target: Target;
|
|
205
205
|
}): Node;
|
|
206
206
|
//#endregion
|
|
207
|
-
export { Bounds, CellBorderSide, CellStyle, Chart, ChartProps, type Child, type Children, Fill, FillProps, Fragment, Group, GroupProps, Image, ImageProps, LayoutRef, Line, LineProps, Media, MediaProps, type Node, Presentation, type PresentationNode, PresentationProps, Raw, type RawContext, RawProps, Remove, Shape, ShapeProps, ShapeStyle, Slide, SlideProps, SlideRef, Table, TableCell, TableCellInfo, TableCellSpec, TableProps, Target, Text, TextLevel, TextParagraph, TextProps, compile };
|
|
207
|
+
export { Bounds, CellBorderSide, CellStyle, Chart, ChartProps, type Child, type Children, Fill, FillProps, Fragment, Group, GroupProps, Image, ImageProps, type JsxSource, LayoutRef, Line, LineProps, Media, MediaProps, type Node, Presentation, type PresentationNode, PresentationProps, Raw, type RawContext, RawProps, Remove, Shape, ShapeProps, ShapeStyle, Slide, SlideProps, SlideRef, Table, TableCell, TableCellInfo, TableCellSpec, TableProps, Target, Text, TextLevel, TextParagraph, TextProps, compile, getShapeJsxSources };
|
|
208
208
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as bindJsxSources, c as literal, d as textContent, f as visit, l as node, o as created, s as getShapeJsxSources, t as Fragment, u as requireSlide } from "./jsx-runtime-CdMn7JwR.js";
|
|
2
2
|
import * as api from "@office-kit/pptx";
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
function Presentation(props) {
|
|
@@ -17,11 +17,14 @@ function Presentation(props) {
|
|
|
17
17
|
originals,
|
|
18
18
|
mode: props.mode ?? (props.source === void 0 ? "compose" : "edit"),
|
|
19
19
|
scope: "presentation",
|
|
20
|
+
sources: [],
|
|
21
|
+
shapeSources: /* @__PURE__ */ new Map(),
|
|
20
22
|
deferred: []
|
|
21
23
|
};
|
|
22
24
|
await visit(props.children, context);
|
|
23
25
|
for (const apply of context.deferred) await apply();
|
|
24
26
|
if (context.mode === "compose") for (const slide of originals) api.removeSlide(presentation, slide);
|
|
27
|
+
bindJsxSources(presentation, context.shapeSources);
|
|
25
28
|
return presentation;
|
|
26
29
|
}
|
|
27
30
|
};
|
|
@@ -388,6 +391,6 @@ function Remove(props) {
|
|
|
388
391
|
});
|
|
389
392
|
}
|
|
390
393
|
//#endregion
|
|
391
|
-
export { Chart, Fill, Fragment, Group, Image, Line, Media, Presentation, Raw, Remove, Shape, Slide, Table, Text, compile };
|
|
394
|
+
export { Chart, Fill, Fragment, Group, Image, Line, Media, Presentation, Raw, Remove, Shape, Slide, Table, Text, compile, getShapeJsxSources };
|
|
392
395
|
|
|
393
396
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import * as api from '@office-kit/pptx';\nimport {\n created,\n textContent,\n literal,\n node,\n requireSlide,\n visit,\n type Children,\n type Context,\n type Node,\n type PresentationNode,\n type RawContext,\n} from './model.ts';\nexport type { Child, Children, Node, PresentationNode, RawContext } from './model.ts';\nexport { Fragment } from './jsx-runtime.ts';\n\nexport type PresentationProps = Children &\n (\n | { source: api.PresentationInput; size?: never }\n | { source?: never; size?: api.PresentationSize }\n ) & {\n theme?: Parameters<typeof api.setPresentationTheme>[1];\n /** Compose replaces the slide sequence; edit retains the original sequence. */\n mode?: 'compose' | 'edit';\n };\nexport function Presentation(props: PresentationProps): PresentationNode {\n return {\n kind: 'Presentation',\n evaluate: () => {\n throw new Error('Presentation must be the root.');\n },\n async compile() {\n if (props.source !== undefined && props.size !== undefined)\n throw new Error('Presentation source retains its original size; do not specify size.');\n const presentation =\n props.source === undefined\n ? api.createPresentation(props.size === undefined ? {} : { size: props.size })\n : await api.loadPresentation(props.source);\n if (props.theme) api.setPresentationTheme(presentation, props.theme);\n const originals = [...api.getSlides(presentation)];\n const context: Context = {\n presentation,\n originals,\n mode: props.mode ?? (props.source === undefined ? 'compose' : 'edit'),\n scope: 'presentation',\n deferred: [],\n };\n await visit(props.children, context);\n for (const apply of context.deferred) await apply();\n if (context.mode === 'compose') {\n for (const slide of originals) api.removeSlide(presentation, slide);\n }\n return presentation;\n },\n };\n}\nexport async function compile(root: Node): Promise<api.PresentationData> {\n if (!('compile' in root) || typeof root.compile !== 'function')\n throw new Error('Export a Presentation as the root.');\n return root.compile();\n}\n\nexport type LayoutRef = { name: string } | { part: string } | { type: string };\nexport type SlideRef = { index: number } | { part: string } | { title: string };\nexport type Target =\n | { name: string }\n | { id: number }\n | { placeholder: { type?: string; idx?: number } };\nexport type SlideProps = Children &\n ({ from?: SlideRef; target?: never } | { from?: never; target: SlideRef }) & {\n layout?: LayoutRef;\n background?: api.Color;\n notes?: string;\n };\nfunction selectSlide(context: Context, ref: SlideRef): api.SlideData {\n const matches = context.originals.filter((slide, index) =>\n 'index' in ref\n ? index === ref.index\n : 'part' in ref\n ? api.getSlidePartName(slide) === ref.part\n : api.getSlideTitle(slide) === ref.title,\n );\n if (matches.length !== 1)\n throw new Error(`Slide reference matched ${matches.length} slides: ${JSON.stringify(ref)}`);\n return matches[0]!;\n}\nfunction selectLayout(presentation: api.PresentationData, ref: LayoutRef): api.SlideLayoutData {\n const matches = api\n .getSlideLayouts(presentation)\n .filter((layout) =>\n 'name' in ref\n ? api.getSlideLayoutName(layout) === ref.name\n : 'part' in ref\n ? api.getSlideLayoutPartName(layout) === ref.part\n : api.getSlideLayoutType(layout) === ref.type,\n );\n if (matches.length !== 1)\n throw new Error(`Layout reference matched ${matches.length} layouts: ${JSON.stringify(ref)}`);\n return matches[0]!;\n}\nexport function Slide(props: SlideProps): Node {\n return node('Slide', async (context) => {\n if (context.scope !== 'presentation') throw new Error('Slide must be inside Presentation.');\n if (props.target && context.mode !== 'edit')\n throw new Error('Slide target requires Presentation mode=\"edit\".');\n if (props.from && props.target) throw new Error('Slide cannot specify both from and target.');\n const layout = props.layout ? selectLayout(context.presentation, props.layout) : undefined;\n const slide = props.target\n ? selectSlide(context, props.target)\n : props.from\n ? api.duplicateSlide(context.presentation, selectSlide(context, props.from))\n : layout\n ? api.addSlide(context.presentation, { layout })\n : api.addBlankSlide(context.presentation);\n if (layout && (props.from || props.target)) api.setSlideLayout(slide, layout);\n if (props.background !== undefined) api.setSlideBackground(slide, props.background);\n if (props.notes !== undefined) api.setSlideNotes(slide, props.notes);\n await visit(props.children, { ...context, slide, scope: 'slide' });\n });\n}\n// A format read back from the cell carries colors as plain strings, since a\n// deck can hold a scheme token outside the theme. Re-applying it has to go\n// through the same check the writer would run.\nconst writableFormat = (format: api.ReadTextFormat): api.TextFormat => {\n const { color, highlight, ...rest } = format;\n return {\n ...rest,\n ...(color == null ? {} : { color: api.asColor(color) }),\n ...(highlight == null ? {} : { highlight: api.asColor(highlight) }),\n };\n};\n\nexport type RawProps =\n | { scope?: undefined; apply: (context: RawContext) => void | Promise<void> }\n | {\n scope: 'presentation';\n apply: (context: Pick<RawContext, 'presentation'>) => void | Promise<void>;\n }\n | {\n scope: 'slide';\n apply: (context: RawContext & { slide: api.SlideData }) => void | Promise<void>;\n }\n | { scope: 'shape'; apply: (context: Required<RawContext>) => void | Promise<void> };\n\nexport function Raw(props: RawProps): Node {\n return node('Raw', (context) => {\n if (props.scope !== undefined && props.scope !== context.scope) {\n throw new Error(`Raw scope=\"${props.scope}\" cannot run inside ${context.scope}.`);\n }\n context.deferred.push(() => {\n if (props.scope === 'shape') {\n if (!context.shape || !context.slide) throw new Error('Raw requires a shape context.');\n return props.apply({\n presentation: context.presentation,\n slide: context.slide,\n shape: context.shape,\n });\n }\n if (props.scope === 'slide') {\n if (!context.slide) throw new Error('Raw requires a slide context.');\n return props.apply({ ...context, slide: context.slide });\n }\n return props.apply(context);\n });\n });\n}\nexport interface Bounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\nexport interface ShapeStyle {\n name?: string;\n fill?: api.Color | api.GradientFillOptions;\n stroke?: { color: api.Color; width?: number } | false;\n rotation?: number;\n shadow?: api.ShadowOptions;\n glow?: api.GlowOptions;\n click?: api.ShapeClickAction;\n}\nfunction bounds(props: Bounds) {\n return {\n x: api.inches(props.x),\n y: api.inches(props.y),\n w: api.inches(props.width),\n h: api.inches(props.height),\n };\n}\nfunction style(shape: api.SlideShapeData, props: ShapeStyle) {\n if (props.fill !== undefined) {\n if (typeof props.fill === 'string') api.setShapeFill(shape, props.fill);\n else api.setShapeGradientFill(shape, props.fill);\n }\n if (props.stroke === false) api.setShapeNoStroke(shape);\n else if (props.stroke)\n api.setShapeStroke(shape, {\n color: props.stroke.color,\n widthEmu: api.pt(props.stroke.width ?? 1),\n });\n if (props.rotation !== undefined) api.setShapeRotation(shape, props.rotation);\n if (props.shadow) api.setShapeShadow(shape, props.shadow);\n if (props.glow) api.setShapeGlow(shape, props.glow);\n if (props.click) api.setShapeClickAction(shape, props.click);\n}\n/** A paragraph's indent level: 0 is the top, 8 the deepest PowerPoint has. */\nexport type TextLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;\nexport interface TextParagraph extends api.ParagraphSpec {\n /** This paragraph's own bullet; wins over `bullets`. */\n bullet?: api.BulletStyle;\n level?: TextLevel;\n}\nexport interface TextProps extends Bounds, ShapeStyle, api.TextFormat, Children {\n paragraphs?: readonly TextParagraph[];\n align?: api.ParagraphAlignment;\n anchor?: api.TextAnchor;\n autoFit?: api.TextAutoFit;\n /** Bullet or numbering for every paragraph; a newline in the text starts a paragraph. */\n bullets?: api.BulletStyle;\n /** Space before / after every paragraph, in points. */\n paragraphSpacing?: { before?: number; after?: number };\n}\nexport function Text(props: TextProps): Node {\n return node('Text', async (context) => {\n const slide = requireSlide(context);\n const content = textContent(props.children);\n if (props.paragraphs && content.text.trim())\n throw new Error('Text accepts either text children or paragraphs.');\n const shape = created(\n context,\n api.addSlideTextBox(slide, {\n ...bounds(props),\n ...(props.name ? { name: props.name } : {}),\n text: content.text,\n }),\n );\n if (props.paragraphs) api.setShapeParagraphs(shape, props.paragraphs);\n api.setShapeTextFormat(shape, props);\n if (props.align) api.setShapeAlignment(shape, props.align);\n if (props.anchor) api.setShapeTextAnchor(shape, props.anchor);\n if (props.autoFit) api.setShapeTextAutoFit(shape, props.autoFit);\n // Before any bullet: core 0.17.0, still in the peer range, sizes a bullet's hanging\n // indent from the level at that moment and never moves it afterwards.\n props.paragraphs?.forEach((paragraph, index) => {\n if (paragraph.level !== undefined) api.setParagraphLevel(shape, index, paragraph.level);\n });\n if (props.bullets !== undefined) api.setShapeBulletStyle(shape, props.bullets);\n props.paragraphs?.forEach((paragraph, index) => {\n if (paragraph.bullet !== undefined) api.setParagraphBullet(shape, index, paragraph.bullet);\n });\n if (props.paragraphSpacing) {\n const { before, after } = props.paragraphSpacing;\n const spacing = {\n ...(before !== undefined ? { beforePts: before } : {}),\n ...(after !== undefined ? { afterPts: after } : {}),\n };\n for (let i = 0; i < api.getShapeParagraphCount(shape); i++) {\n api.setParagraphSpacing(shape, i, spacing);\n }\n }\n style(shape, props);\n await visit(content.elements, { ...context, slide, shape, scope: 'shape' });\n });\n}\nexport interface ShapeProps extends Bounds, ShapeStyle, Children {\n preset: Parameters<typeof api.addSlideShape>[1]['preset'];\n text?: string;\n format?: api.TextFormat;\n /** Alignment and anchor of `text` inside the shape. */\n align?: api.ParagraphAlignment;\n anchor?: api.TextAnchor;\n}\nexport function Shape(props: ShapeProps): Node {\n return node('Shape', async (context) => {\n if ((props.align || props.anchor) && props.text === undefined)\n throw new Error('Shape align and anchor position its text; set text as well.');\n const shape = created(\n context,\n api.addSlideShape(requireSlide(context), {\n ...bounds(props),\n preset: props.preset,\n ...(props.name ? { name: props.name } : {}),\n ...(props.text !== undefined ? { text: props.text } : {}),\n }),\n );\n style(shape, props);\n if (props.format) api.setShapeTextFormat(shape, props.format);\n if (props.align) api.setShapeAlignment(shape, props.align);\n if (props.anchor) api.setShapeTextAnchor(shape, props.anchor);\n await visit(props.children, { ...context, shape, scope: 'shape' });\n });\n}\nexport interface ImageProps extends Bounds, ShapeStyle, Children {\n data: Uint8Array;\n format?: api.ImageFormat;\n fit?: api.ImageFit;\n}\nexport function Image(props: ImageProps): Node {\n return node('Image', async (context) => {\n const shape = created(\n context,\n api.addSlideImage(requireSlide(context), props.data, {\n ...bounds(props),\n ...(props.format ? { format: props.format } : {}),\n ...(props.fit ? { fit: props.fit } : {}),\n ...(props.name ? { name: props.name } : {}),\n }),\n );\n style(shape, props);\n await visit(props.children, { ...context, shape, scope: 'shape' });\n });\n}\n// No ShapeStyle: a clip's picture already carries the `ppaction://media` click\n// action that makes it playable, which `click` would replace.\nexport type MediaProps = Bounds &\n Children &\n api.SlideMediaSource & {\n name?: string;\n poster?: Uint8Array;\n posterFormat?: api.ImageFormat;\n };\nexport function Media(props: MediaProps): Node {\n return node('Media', async (context) => {\n const { x: _x, y: _y, width: _width, height: _height, children, ...media } = props;\n const shape = created(\n context,\n api.addSlideMedia(requireSlide(context), { ...media, ...bounds(props) }),\n );\n await visit(children, { ...context, shape, scope: 'shape' });\n });\n}\nexport interface ChartProps extends Bounds, ShapeStyle, Children {\n spec: api.ChartSpec;\n}\nexport function Chart(props: ChartProps): Node {\n return node('Chart', async (context) => {\n const shape = created(\n context,\n api.addSlideChart(requireSlide(context), {\n ...bounds(props),\n spec: props.spec,\n ...(props.name ? { name: props.name } : {}),\n }),\n );\n style(shape, props);\n await visit(props.children, { ...context, shape, scope: 'shape' });\n });\n}\nexport type CellBorderSide = 'left' | 'right' | 'top' | 'bottom';\nexport interface CellStyle {\n fill?: api.Color;\n format?: api.TextFormat;\n anchor?: api.TextAnchor;\n align?: api.ParagraphAlignment;\n /** Border per side. `width` is in points; without it the line has no explicit width. */\n borders?: Partial<Record<CellBorderSide, { color: api.Color; width?: number }>>;\n}\nexport interface TableCellInfo {\n /** Zero-based; row 0 is the header. */\n row: number;\n column: number;\n /** A cell's text; for `paragraphs`, the run texts joined, one line per paragraph. */\n value: string;\n}\n/** Exactly one of `text` and `paragraphs`. An empty cell is the string `''`. */\ntype TableCellContent =\n | { text: string; paragraphs?: never }\n | { text?: never; paragraphs: readonly api.ParagraphSpec[] };\nexport type TableCellSpec = TableCellContent & {\n /** This cell is the top-left of a merge. The positions it covers stay in `rows` as `''`. */\n colSpan?: number;\n rowSpan?: number;\n};\nexport type TableCell = string | TableCellSpec;\nexport interface TableProps extends Bounds, Children {\n rows: readonly (readonly TableCell[])[];\n columnWidths?: readonly number[];\n rowHeights?: readonly number[];\n cellStyle?: CellStyle;\n headerStyle?: CellStyle;\n stripeFill?: api.Color;\n /**\n * Style for one cell, merged over `cellStyle`, `headerStyle` and\n * `stripeFill` — for a status column, a total row, a highlighted figure.\n */\n styleCell?: (cell: TableCellInfo) => CellStyle | undefined;\n}\nfunction cellBorders(borders: NonNullable<CellStyle['borders']>) {\n const sides: Partial<Record<CellBorderSide, { color: api.Color; widthEmu?: number }>> = {};\n for (const side of ['left', 'right', 'top', 'bottom'] as const) {\n const border = borders[side];\n if (!border) continue;\n sides[side] = {\n color: border.color,\n ...(border.width !== undefined ? { widthEmu: api.pt(border.width) } : {}),\n };\n }\n return sides;\n}\ntype CellPosition = { row: number; col: number };\nconst positionKey = (row: number, col: number) => `${row}:${col}`;\nconst cellSpec = (cell: TableCell): TableCellSpec =>\n typeof cell === 'string' ? { text: cell } : cell;\nconst cellValue = (spec: TableCellSpec): string =>\n spec.paragraphs\n ? spec.paragraphs.map((paragraph) => paragraph.runs.map((run) => run.text).join('')).join('\\n')\n : spec.text;\n\n/** Maps every position a merge covers to that merge's top-left cell. */\nfunction coveredPositions(rows: TableProps['rows']): Map<string, CellPosition> {\n const covered = new Map<string, CellPosition>();\n rows.forEach((cells, row) =>\n cells.forEach((cell, col) => {\n const { rowSpan = 1, colSpan = 1 } = cellSpec(cell);\n // Clamped to the grid: the core rejects an oversized span later, and an\n // unbounded loop must not run first.\n const lastRow = Math.min(row + rowSpan, rows.length);\n const lastCol = Math.min(col + colSpan, cells.length);\n for (let r = row; r < lastRow; r++)\n for (let c = col; c < lastCol; c++)\n if (r !== row || c !== col) covered.set(positionKey(r, c), { row, col });\n }),\n );\n return covered;\n}\nfunction checkCells(rows: TableProps['rows'], covered: Map<string, CellPosition>) {\n rows.forEach((cells, row) =>\n cells.forEach((cell, col) => {\n const spec = cellSpec(cell);\n if ((spec.text === undefined) === (spec.paragraphs === undefined))\n throw new Error(`Table cell (${row}, ${col}) accepts either text or paragraphs.`);\n const anchor = covered.get(positionKey(row, col));\n // PowerPoint paints only the merge's top-left cell, so anything written\n // here would vanish from the slide without a trace.\n if (anchor && cell !== '')\n throw new Error(\n `Table cell (${row}, ${col}) is covered by the merge at (${anchor.row}, ${anchor.col}); write it as ''.`,\n );\n }),\n );\n}\nexport function Table(props: TableProps): Node {\n return node('Table', async (context) => {\n const covered = coveredPositions(props.rows);\n checkCells(props.rows, covered);\n const shape = created(\n context,\n api.addSlideTable(requireSlide(context), {\n ...bounds(props),\n rows: props.rows.map((cells) => cells.map((cell) => cellSpec(cell).text ?? '')),\n ...(props.columnWidths ? { colWidths: props.columnWidths.map(api.inches) } : {}),\n ...(props.rowHeights ? { rowHeights: props.rowHeights.map(api.inches) } : {}),\n }),\n );\n props.rows.forEach((row, r) =>\n row.forEach((entry, c) => {\n // A merged block is painted from its top-left cell alone, fill and all\n // four borders, so a covered position gets no style and no styleCell call.\n if (covered.has(positionKey(r, c))) return;\n const spec = cellSpec(entry);\n const cell = api.getTableCell(shape, r, c);\n const header = r === 0 ? props.headerStyle : undefined;\n const own = props.styleCell?.({ row: r, column: c, value: cellValue(spec) });\n const striped = r > 0 && r % 2 === 0 ? props.stripeFill : undefined;\n const appearance: CellStyle = {\n ...props.cellStyle,\n ...header,\n ...(striped !== undefined ? { fill: striped } : {}),\n ...own,\n format: { ...props.cellStyle?.format, ...header?.format, ...own?.format },\n };\n if (appearance.fill) api.setTableCellFill(cell, appearance.fill);\n if (spec.paragraphs) {\n // addSlideTable bakes the deck's body-text color into each run so text\n // stays readable on an inverted color map; new runs must keep it.\n const built = api.getTableCellParagraphs(cell)[0]?.elements[0];\n const base = {\n ...(built?.kind === 'r' && built.format ? writableFormat(built.format) : {}),\n ...appearance.format,\n };\n const { align } = appearance;\n api.setTableCellParagraphs(\n cell,\n spec.paragraphs.map((paragraph) => ({\n ...paragraph,\n ...(paragraph.align === undefined && align ? { align } : {}),\n runs: paragraph.runs.map((run) => ({ ...run, format: { ...base, ...run.format } })),\n })),\n );\n } else {\n if (appearance.format) api.setTableCellTextFormat(cell, appearance.format);\n if (appearance.align) api.setTableCellAlignment(cell, appearance.align);\n }\n if (appearance.anchor) api.setTableCellAnchor(cell, appearance.anchor);\n // Only when a side is set: an empty call would still give every cell a <a:tcPr>.\n const borders = cellBorders({\n ...props.cellStyle?.borders,\n ...header?.borders,\n ...own?.borders,\n });\n if (Object.keys(borders).length > 0) api.setTableCellBorders(cell, borders);\n }),\n );\n props.rows.forEach((row, r) =>\n row.forEach((entry, c) => {\n const { rowSpan = 1, colSpan = 1 } = cellSpec(entry);\n if (rowSpan !== 1 || colSpan !== 1)\n api.mergeTableCells(shape, { row: r, col: c, rowSpan, colSpan });\n }),\n );\n await visit(props.children, { ...context, shape, scope: 'shape' });\n });\n}\nexport interface LineProps {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n color?: api.Color;\n /** Stroke width in points. */\n width?: number;\n name?: string;\n}\nexport function Line(props: LineProps): Node {\n return node('Line', (context) => {\n created(\n context,\n api.addSlideLine(requireSlide(context), {\n from: { x: api.inches(props.x1), y: api.inches(props.y1) },\n to: { x: api.inches(props.x2), y: api.inches(props.y2) },\n ...(props.color !== undefined ? { color: props.color } : {}),\n ...(props.width !== undefined ? { widthEmu: api.pt(props.width) } : {}),\n ...(props.name ? { name: props.name } : {}),\n }),\n );\n });\n}\nexport interface GroupProps extends Children {\n name?: string;\n}\n/** Groups the shapes its children create, so they move and resize as one object. */\nexport function Group(props: GroupProps): Node {\n return node('Group', async (context) => {\n requireSlide(context);\n const members: api.SlideShapeData[] = [];\n await visit(props.children, { ...context, members });\n created(context, api.groupShapes(members, props.name ? { name: props.name } : {}));\n });\n}\nfunction selectShape(slide: api.SlideData, target: Target): api.SlideShapeData {\n const matches = api.getSlideShapes(slide).filter((shape) => {\n if ('name' in target) return api.getShapeName(shape) === target.name;\n if ('id' in target) return api.getShapeId(shape) === target.id;\n const ph = target.placeholder;\n return (\n (ph.type !== undefined || ph.idx !== undefined) &&\n (ph.type === undefined || api.getShapePlaceholderType(shape) === ph.type) &&\n (ph.idx === undefined || api.getShapePlaceholderIdx(shape) === ph.idx)\n );\n });\n if (matches.length !== 1)\n throw new Error(`Shape reference matched ${matches.length} shapes: ${JSON.stringify(target)}`);\n return matches[0]!;\n}\nexport interface FillProps extends Children {\n target: Target;\n text?: string;\n format?: api.TextFormat;\n /** Omitted keeps whatever the template's `<a:bodyPr>` says. */\n autoFit?: api.TextAutoFit;\n}\nexport function Fill(props: FillProps): Node {\n return node('Fill', (context) => {\n const shape = selectShape(requireSlide(context), props.target);\n api.setShapeText(shape, props.text ?? literal(props.children));\n if (props.format) api.setShapeTextFormat(shape, props.format);\n if (props.autoFit) api.setShapeTextAutoFit(shape, props.autoFit);\n });\n}\nexport function Remove(props: { target: Target }): Node {\n return node('Remove', (context) => {\n const slide = requireSlide(context);\n api.removeShape(selectShape(slide, props.target));\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import * as api from '@office-kit/pptx';\nimport {\n bindJsxSources,\n created,\n textContent,\n literal,\n node,\n requireSlide,\n visit,\n type Children,\n type Context,\n type Node,\n type PresentationNode,\n type RawContext,\n} from './model.ts';\nexport { getShapeJsxSources } from './model.ts';\nexport type { Child, Children, JsxSource, Node, PresentationNode, RawContext } from './model.ts';\nexport { Fragment } from './jsx-runtime.ts';\n\nexport type PresentationProps = Children &\n (\n | { source: api.PresentationInput; size?: never }\n | { source?: never; size?: api.PresentationSize }\n ) & {\n theme?: Parameters<typeof api.setPresentationTheme>[1];\n /** Compose replaces the slide sequence; edit retains the original sequence. */\n mode?: 'compose' | 'edit';\n };\nexport function Presentation(props: PresentationProps): PresentationNode {\n return {\n kind: 'Presentation',\n evaluate: () => {\n throw new Error('Presentation must be the root.');\n },\n async compile() {\n if (props.source !== undefined && props.size !== undefined)\n throw new Error('Presentation source retains its original size; do not specify size.');\n const presentation =\n props.source === undefined\n ? api.createPresentation(props.size === undefined ? {} : { size: props.size })\n : await api.loadPresentation(props.source);\n if (props.theme) api.setPresentationTheme(presentation, props.theme);\n const originals = [...api.getSlides(presentation)];\n const context: Context = {\n presentation,\n originals,\n mode: props.mode ?? (props.source === undefined ? 'compose' : 'edit'),\n scope: 'presentation',\n sources: [],\n shapeSources: new Map(),\n deferred: [],\n };\n await visit(props.children, context);\n for (const apply of context.deferred) await apply();\n if (context.mode === 'compose') {\n for (const slide of originals) api.removeSlide(presentation, slide);\n }\n bindJsxSources(presentation, context.shapeSources);\n return presentation;\n },\n };\n}\nexport async function compile(root: Node): Promise<api.PresentationData> {\n if (!('compile' in root) || typeof root.compile !== 'function')\n throw new Error('Export a Presentation as the root.');\n return root.compile();\n}\n\nexport type LayoutRef = { name: string } | { part: string } | { type: string };\nexport type SlideRef = { index: number } | { part: string } | { title: string };\nexport type Target =\n | { name: string }\n | { id: number }\n | { placeholder: { type?: string; idx?: number } };\nexport type SlideProps = Children &\n ({ from?: SlideRef; target?: never } | { from?: never; target: SlideRef }) & {\n layout?: LayoutRef;\n background?: api.Color;\n notes?: string;\n };\nfunction selectSlide(context: Context, ref: SlideRef): api.SlideData {\n const matches = context.originals.filter((slide, index) =>\n 'index' in ref\n ? index === ref.index\n : 'part' in ref\n ? api.getSlidePartName(slide) === ref.part\n : api.getSlideTitle(slide) === ref.title,\n );\n if (matches.length !== 1)\n throw new Error(`Slide reference matched ${matches.length} slides: ${JSON.stringify(ref)}`);\n return matches[0]!;\n}\nfunction selectLayout(presentation: api.PresentationData, ref: LayoutRef): api.SlideLayoutData {\n const matches = api\n .getSlideLayouts(presentation)\n .filter((layout) =>\n 'name' in ref\n ? api.getSlideLayoutName(layout) === ref.name\n : 'part' in ref\n ? api.getSlideLayoutPartName(layout) === ref.part\n : api.getSlideLayoutType(layout) === ref.type,\n );\n if (matches.length !== 1)\n throw new Error(`Layout reference matched ${matches.length} layouts: ${JSON.stringify(ref)}`);\n return matches[0]!;\n}\nexport function Slide(props: SlideProps): Node {\n return node('Slide', async (context) => {\n if (context.scope !== 'presentation') throw new Error('Slide must be inside Presentation.');\n if (props.target && context.mode !== 'edit')\n throw new Error('Slide target requires Presentation mode=\"edit\".');\n if (props.from && props.target) throw new Error('Slide cannot specify both from and target.');\n const layout = props.layout ? selectLayout(context.presentation, props.layout) : undefined;\n const slide = props.target\n ? selectSlide(context, props.target)\n : props.from\n ? api.duplicateSlide(context.presentation, selectSlide(context, props.from))\n : layout\n ? api.addSlide(context.presentation, { layout })\n : api.addBlankSlide(context.presentation);\n if (layout && (props.from || props.target)) api.setSlideLayout(slide, layout);\n if (props.background !== undefined) api.setSlideBackground(slide, props.background);\n if (props.notes !== undefined) api.setSlideNotes(slide, props.notes);\n await visit(props.children, { ...context, slide, scope: 'slide' });\n });\n}\n// A format read back from the cell carries colors as plain strings, since a\n// deck can hold a scheme token outside the theme. Re-applying it has to go\n// through the same check the writer would run.\nconst writableFormat = (format: api.ReadTextFormat): api.TextFormat => {\n const { color, highlight, ...rest } = format;\n return {\n ...rest,\n ...(color == null ? {} : { color: api.asColor(color) }),\n ...(highlight == null ? {} : { highlight: api.asColor(highlight) }),\n };\n};\n\nexport type RawProps =\n | { scope?: undefined; apply: (context: RawContext) => void | Promise<void> }\n | {\n scope: 'presentation';\n apply: (context: Pick<RawContext, 'presentation'>) => void | Promise<void>;\n }\n | {\n scope: 'slide';\n apply: (context: RawContext & { slide: api.SlideData }) => void | Promise<void>;\n }\n | { scope: 'shape'; apply: (context: Required<RawContext>) => void | Promise<void> };\n\nexport function Raw(props: RawProps): Node {\n return node('Raw', (context) => {\n if (props.scope !== undefined && props.scope !== context.scope) {\n throw new Error(`Raw scope=\"${props.scope}\" cannot run inside ${context.scope}.`);\n }\n context.deferred.push(() => {\n if (props.scope === 'shape') {\n if (!context.shape || !context.slide) throw new Error('Raw requires a shape context.');\n return props.apply({\n presentation: context.presentation,\n slide: context.slide,\n shape: context.shape,\n });\n }\n if (props.scope === 'slide') {\n if (!context.slide) throw new Error('Raw requires a slide context.');\n return props.apply({ ...context, slide: context.slide });\n }\n return props.apply(context);\n });\n });\n}\nexport interface Bounds {\n x: number;\n y: number;\n width: number;\n height: number;\n}\nexport interface ShapeStyle {\n name?: string;\n fill?: api.Color | api.GradientFillOptions;\n stroke?: { color: api.Color; width?: number } | false;\n rotation?: number;\n shadow?: api.ShadowOptions;\n glow?: api.GlowOptions;\n click?: api.ShapeClickAction;\n}\nfunction bounds(props: Bounds) {\n return {\n x: api.inches(props.x),\n y: api.inches(props.y),\n w: api.inches(props.width),\n h: api.inches(props.height),\n };\n}\nfunction style(shape: api.SlideShapeData, props: ShapeStyle) {\n if (props.fill !== undefined) {\n if (typeof props.fill === 'string') api.setShapeFill(shape, props.fill);\n else api.setShapeGradientFill(shape, props.fill);\n }\n if (props.stroke === false) api.setShapeNoStroke(shape);\n else if (props.stroke)\n api.setShapeStroke(shape, {\n color: props.stroke.color,\n widthEmu: api.pt(props.stroke.width ?? 1),\n });\n if (props.rotation !== undefined) api.setShapeRotation(shape, props.rotation);\n if (props.shadow) api.setShapeShadow(shape, props.shadow);\n if (props.glow) api.setShapeGlow(shape, props.glow);\n if (props.click) api.setShapeClickAction(shape, props.click);\n}\n/** A paragraph's indent level: 0 is the top, 8 the deepest PowerPoint has. */\nexport type TextLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;\nexport interface TextParagraph extends api.ParagraphSpec {\n /** This paragraph's own bullet; wins over `bullets`. */\n bullet?: api.BulletStyle;\n level?: TextLevel;\n}\nexport interface TextProps extends Bounds, ShapeStyle, api.TextFormat, Children {\n paragraphs?: readonly TextParagraph[];\n align?: api.ParagraphAlignment;\n anchor?: api.TextAnchor;\n autoFit?: api.TextAutoFit;\n /** Bullet or numbering for every paragraph; a newline in the text starts a paragraph. */\n bullets?: api.BulletStyle;\n /** Space before / after every paragraph, in points. */\n paragraphSpacing?: { before?: number; after?: number };\n}\nexport function Text(props: TextProps): Node {\n return node('Text', async (context) => {\n const slide = requireSlide(context);\n const content = textContent(props.children);\n if (props.paragraphs && content.text.trim())\n throw new Error('Text accepts either text children or paragraphs.');\n const shape = created(\n context,\n api.addSlideTextBox(slide, {\n ...bounds(props),\n ...(props.name ? { name: props.name } : {}),\n text: content.text,\n }),\n );\n if (props.paragraphs) api.setShapeParagraphs(shape, props.paragraphs);\n api.setShapeTextFormat(shape, props);\n if (props.align) api.setShapeAlignment(shape, props.align);\n if (props.anchor) api.setShapeTextAnchor(shape, props.anchor);\n if (props.autoFit) api.setShapeTextAutoFit(shape, props.autoFit);\n // Before any bullet: core 0.17.0, still in the peer range, sizes a bullet's hanging\n // indent from the level at that moment and never moves it afterwards.\n props.paragraphs?.forEach((paragraph, index) => {\n if (paragraph.level !== undefined) api.setParagraphLevel(shape, index, paragraph.level);\n });\n if (props.bullets !== undefined) api.setShapeBulletStyle(shape, props.bullets);\n props.paragraphs?.forEach((paragraph, index) => {\n if (paragraph.bullet !== undefined) api.setParagraphBullet(shape, index, paragraph.bullet);\n });\n if (props.paragraphSpacing) {\n const { before, after } = props.paragraphSpacing;\n const spacing = {\n ...(before !== undefined ? { beforePts: before } : {}),\n ...(after !== undefined ? { afterPts: after } : {}),\n };\n for (let i = 0; i < api.getShapeParagraphCount(shape); i++) {\n api.setParagraphSpacing(shape, i, spacing);\n }\n }\n style(shape, props);\n await visit(content.elements, { ...context, slide, shape, scope: 'shape' });\n });\n}\nexport interface ShapeProps extends Bounds, ShapeStyle, Children {\n preset: Parameters<typeof api.addSlideShape>[1]['preset'];\n text?: string;\n format?: api.TextFormat;\n /** Alignment and anchor of `text` inside the shape. */\n align?: api.ParagraphAlignment;\n anchor?: api.TextAnchor;\n}\nexport function Shape(props: ShapeProps): Node {\n return node('Shape', async (context) => {\n if ((props.align || props.anchor) && props.text === undefined)\n throw new Error('Shape align and anchor position its text; set text as well.');\n const shape = created(\n context,\n api.addSlideShape(requireSlide(context), {\n ...bounds(props),\n preset: props.preset,\n ...(props.name ? { name: props.name } : {}),\n ...(props.text !== undefined ? { text: props.text } : {}),\n }),\n );\n style(shape, props);\n if (props.format) api.setShapeTextFormat(shape, props.format);\n if (props.align) api.setShapeAlignment(shape, props.align);\n if (props.anchor) api.setShapeTextAnchor(shape, props.anchor);\n await visit(props.children, { ...context, shape, scope: 'shape' });\n });\n}\nexport interface ImageProps extends Bounds, ShapeStyle, Children {\n data: Uint8Array;\n format?: api.ImageFormat;\n fit?: api.ImageFit;\n}\nexport function Image(props: ImageProps): Node {\n return node('Image', async (context) => {\n const shape = created(\n context,\n api.addSlideImage(requireSlide(context), props.data, {\n ...bounds(props),\n ...(props.format ? { format: props.format } : {}),\n ...(props.fit ? { fit: props.fit } : {}),\n ...(props.name ? { name: props.name } : {}),\n }),\n );\n style(shape, props);\n await visit(props.children, { ...context, shape, scope: 'shape' });\n });\n}\n// No ShapeStyle: a clip's picture already carries the `ppaction://media` click\n// action that makes it playable, which `click` would replace.\nexport type MediaProps = Bounds &\n Children &\n api.SlideMediaSource & {\n name?: string;\n poster?: Uint8Array;\n posterFormat?: api.ImageFormat;\n };\nexport function Media(props: MediaProps): Node {\n return node('Media', async (context) => {\n const { x: _x, y: _y, width: _width, height: _height, children, ...media } = props;\n const shape = created(\n context,\n api.addSlideMedia(requireSlide(context), { ...media, ...bounds(props) }),\n );\n await visit(children, { ...context, shape, scope: 'shape' });\n });\n}\nexport interface ChartProps extends Bounds, ShapeStyle, Children {\n spec: api.ChartSpec;\n}\nexport function Chart(props: ChartProps): Node {\n return node('Chart', async (context) => {\n const shape = created(\n context,\n api.addSlideChart(requireSlide(context), {\n ...bounds(props),\n spec: props.spec,\n ...(props.name ? { name: props.name } : {}),\n }),\n );\n style(shape, props);\n await visit(props.children, { ...context, shape, scope: 'shape' });\n });\n}\nexport type CellBorderSide = 'left' | 'right' | 'top' | 'bottom';\nexport interface CellStyle {\n fill?: api.Color;\n format?: api.TextFormat;\n anchor?: api.TextAnchor;\n align?: api.ParagraphAlignment;\n /** Border per side. `width` is in points; without it the line has no explicit width. */\n borders?: Partial<Record<CellBorderSide, { color: api.Color; width?: number }>>;\n}\nexport interface TableCellInfo {\n /** Zero-based; row 0 is the header. */\n row: number;\n column: number;\n /** A cell's text; for `paragraphs`, the run texts joined, one line per paragraph. */\n value: string;\n}\n/** Exactly one of `text` and `paragraphs`. An empty cell is the string `''`. */\ntype TableCellContent =\n | { text: string; paragraphs?: never }\n | { text?: never; paragraphs: readonly api.ParagraphSpec[] };\nexport type TableCellSpec = TableCellContent & {\n /** This cell is the top-left of a merge. The positions it covers stay in `rows` as `''`. */\n colSpan?: number;\n rowSpan?: number;\n};\nexport type TableCell = string | TableCellSpec;\nexport interface TableProps extends Bounds, Children {\n rows: readonly (readonly TableCell[])[];\n columnWidths?: readonly number[];\n rowHeights?: readonly number[];\n cellStyle?: CellStyle;\n headerStyle?: CellStyle;\n stripeFill?: api.Color;\n /**\n * Style for one cell, merged over `cellStyle`, `headerStyle` and\n * `stripeFill` — for a status column, a total row, a highlighted figure.\n */\n styleCell?: (cell: TableCellInfo) => CellStyle | undefined;\n}\nfunction cellBorders(borders: NonNullable<CellStyle['borders']>) {\n const sides: Partial<Record<CellBorderSide, { color: api.Color; widthEmu?: number }>> = {};\n for (const side of ['left', 'right', 'top', 'bottom'] as const) {\n const border = borders[side];\n if (!border) continue;\n sides[side] = {\n color: border.color,\n ...(border.width !== undefined ? { widthEmu: api.pt(border.width) } : {}),\n };\n }\n return sides;\n}\ntype CellPosition = { row: number; col: number };\nconst positionKey = (row: number, col: number) => `${row}:${col}`;\nconst cellSpec = (cell: TableCell): TableCellSpec =>\n typeof cell === 'string' ? { text: cell } : cell;\nconst cellValue = (spec: TableCellSpec): string =>\n spec.paragraphs\n ? spec.paragraphs.map((paragraph) => paragraph.runs.map((run) => run.text).join('')).join('\\n')\n : spec.text;\n\n/** Maps every position a merge covers to that merge's top-left cell. */\nfunction coveredPositions(rows: TableProps['rows']): Map<string, CellPosition> {\n const covered = new Map<string, CellPosition>();\n rows.forEach((cells, row) =>\n cells.forEach((cell, col) => {\n const { rowSpan = 1, colSpan = 1 } = cellSpec(cell);\n // Clamped to the grid: the core rejects an oversized span later, and an\n // unbounded loop must not run first.\n const lastRow = Math.min(row + rowSpan, rows.length);\n const lastCol = Math.min(col + colSpan, cells.length);\n for (let r = row; r < lastRow; r++)\n for (let c = col; c < lastCol; c++)\n if (r !== row || c !== col) covered.set(positionKey(r, c), { row, col });\n }),\n );\n return covered;\n}\nfunction checkCells(rows: TableProps['rows'], covered: Map<string, CellPosition>) {\n rows.forEach((cells, row) =>\n cells.forEach((cell, col) => {\n const spec = cellSpec(cell);\n if ((spec.text === undefined) === (spec.paragraphs === undefined))\n throw new Error(`Table cell (${row}, ${col}) accepts either text or paragraphs.`);\n const anchor = covered.get(positionKey(row, col));\n // PowerPoint paints only the merge's top-left cell, so anything written\n // here would vanish from the slide without a trace.\n if (anchor && cell !== '')\n throw new Error(\n `Table cell (${row}, ${col}) is covered by the merge at (${anchor.row}, ${anchor.col}); write it as ''.`,\n );\n }),\n );\n}\nexport function Table(props: TableProps): Node {\n return node('Table', async (context) => {\n const covered = coveredPositions(props.rows);\n checkCells(props.rows, covered);\n const shape = created(\n context,\n api.addSlideTable(requireSlide(context), {\n ...bounds(props),\n rows: props.rows.map((cells) => cells.map((cell) => cellSpec(cell).text ?? '')),\n ...(props.columnWidths ? { colWidths: props.columnWidths.map(api.inches) } : {}),\n ...(props.rowHeights ? { rowHeights: props.rowHeights.map(api.inches) } : {}),\n }),\n );\n props.rows.forEach((row, r) =>\n row.forEach((entry, c) => {\n // A merged block is painted from its top-left cell alone, fill and all\n // four borders, so a covered position gets no style and no styleCell call.\n if (covered.has(positionKey(r, c))) return;\n const spec = cellSpec(entry);\n const cell = api.getTableCell(shape, r, c);\n const header = r === 0 ? props.headerStyle : undefined;\n const own = props.styleCell?.({ row: r, column: c, value: cellValue(spec) });\n const striped = r > 0 && r % 2 === 0 ? props.stripeFill : undefined;\n const appearance: CellStyle = {\n ...props.cellStyle,\n ...header,\n ...(striped !== undefined ? { fill: striped } : {}),\n ...own,\n format: { ...props.cellStyle?.format, ...header?.format, ...own?.format },\n };\n if (appearance.fill) api.setTableCellFill(cell, appearance.fill);\n if (spec.paragraphs) {\n // addSlideTable bakes the deck's body-text color into each run so text\n // stays readable on an inverted color map; new runs must keep it.\n const built = api.getTableCellParagraphs(cell)[0]?.elements[0];\n const base = {\n ...(built?.kind === 'r' && built.format ? writableFormat(built.format) : {}),\n ...appearance.format,\n };\n const { align } = appearance;\n api.setTableCellParagraphs(\n cell,\n spec.paragraphs.map((paragraph) => ({\n ...paragraph,\n ...(paragraph.align === undefined && align ? { align } : {}),\n runs: paragraph.runs.map((run) => ({ ...run, format: { ...base, ...run.format } })),\n })),\n );\n } else {\n if (appearance.format) api.setTableCellTextFormat(cell, appearance.format);\n if (appearance.align) api.setTableCellAlignment(cell, appearance.align);\n }\n if (appearance.anchor) api.setTableCellAnchor(cell, appearance.anchor);\n // Only when a side is set: an empty call would still give every cell a <a:tcPr>.\n const borders = cellBorders({\n ...props.cellStyle?.borders,\n ...header?.borders,\n ...own?.borders,\n });\n if (Object.keys(borders).length > 0) api.setTableCellBorders(cell, borders);\n }),\n );\n props.rows.forEach((row, r) =>\n row.forEach((entry, c) => {\n const { rowSpan = 1, colSpan = 1 } = cellSpec(entry);\n if (rowSpan !== 1 || colSpan !== 1)\n api.mergeTableCells(shape, { row: r, col: c, rowSpan, colSpan });\n }),\n );\n await visit(props.children, { ...context, shape, scope: 'shape' });\n });\n}\nexport interface LineProps {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n color?: api.Color;\n /** Stroke width in points. */\n width?: number;\n name?: string;\n}\nexport function Line(props: LineProps): Node {\n return node('Line', (context) => {\n created(\n context,\n api.addSlideLine(requireSlide(context), {\n from: { x: api.inches(props.x1), y: api.inches(props.y1) },\n to: { x: api.inches(props.x2), y: api.inches(props.y2) },\n ...(props.color !== undefined ? { color: props.color } : {}),\n ...(props.width !== undefined ? { widthEmu: api.pt(props.width) } : {}),\n ...(props.name ? { name: props.name } : {}),\n }),\n );\n });\n}\nexport interface GroupProps extends Children {\n name?: string;\n}\n/** Groups the shapes its children create, so they move and resize as one object. */\nexport function Group(props: GroupProps): Node {\n return node('Group', async (context) => {\n requireSlide(context);\n const members: api.SlideShapeData[] = [];\n await visit(props.children, { ...context, members });\n created(context, api.groupShapes(members, props.name ? { name: props.name } : {}));\n });\n}\nfunction selectShape(slide: api.SlideData, target: Target): api.SlideShapeData {\n const matches = api.getSlideShapes(slide).filter((shape) => {\n if ('name' in target) return api.getShapeName(shape) === target.name;\n if ('id' in target) return api.getShapeId(shape) === target.id;\n const ph = target.placeholder;\n return (\n (ph.type !== undefined || ph.idx !== undefined) &&\n (ph.type === undefined || api.getShapePlaceholderType(shape) === ph.type) &&\n (ph.idx === undefined || api.getShapePlaceholderIdx(shape) === ph.idx)\n );\n });\n if (matches.length !== 1)\n throw new Error(`Shape reference matched ${matches.length} shapes: ${JSON.stringify(target)}`);\n return matches[0]!;\n}\nexport interface FillProps extends Children {\n target: Target;\n text?: string;\n format?: api.TextFormat;\n /** Omitted keeps whatever the template's `<a:bodyPr>` says. */\n autoFit?: api.TextAutoFit;\n}\nexport function Fill(props: FillProps): Node {\n return node('Fill', (context) => {\n const shape = selectShape(requireSlide(context), props.target);\n api.setShapeText(shape, props.text ?? literal(props.children));\n if (props.format) api.setShapeTextFormat(shape, props.format);\n if (props.autoFit) api.setShapeTextAutoFit(shape, props.autoFit);\n });\n}\nexport function Remove(props: { target: Target }): Node {\n return node('Remove', (context) => {\n const slide = requireSlide(context);\n api.removeShape(selectShape(slide, props.target));\n });\n}\n"],"mappings":"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"}
|
|
@@ -10,8 +10,18 @@ interface RawContext {
|
|
|
10
10
|
slide?: api.SlideData;
|
|
11
11
|
shape?: api.SlideShapeData;
|
|
12
12
|
}
|
|
13
|
+
/** Where `jsxDEV` saw an element. Line and column are 1-based, as TypeScript and esbuild emit them. */
|
|
14
|
+
interface JsxSource {
|
|
15
|
+
fileName: string;
|
|
16
|
+
lineNumber: number;
|
|
17
|
+
columnNumber: number;
|
|
18
|
+
}
|
|
13
19
|
interface Context extends RawContext {
|
|
14
20
|
mode: 'compose' | 'edit';
|
|
21
|
+
/** Sources of the elements being evaluated, outermost first. */
|
|
22
|
+
sources: readonly JsxSource[];
|
|
23
|
+
/** Every created shape's `sources`, by slide part name and shape id. */
|
|
24
|
+
shapeSources: Map<string, readonly JsxSource[]>;
|
|
15
25
|
scope: 'presentation' | 'slide' | 'shape';
|
|
16
26
|
originals: readonly api.SlideData[];
|
|
17
27
|
deferred: Array<() => void | Promise<void>>;
|
|
@@ -20,16 +30,18 @@ interface Context extends RawContext {
|
|
|
20
30
|
}
|
|
21
31
|
interface Node {
|
|
22
32
|
readonly kind: string;
|
|
23
|
-
readonly source?:
|
|
24
|
-
fileName: string;
|
|
25
|
-
lineNumber: number;
|
|
26
|
-
columnNumber: number;
|
|
27
|
-
};
|
|
33
|
+
readonly source?: JsxSource;
|
|
28
34
|
readonly evaluate: (context: Context) => void | Promise<void>;
|
|
29
35
|
}
|
|
30
36
|
interface PresentationNode extends Node {
|
|
31
37
|
readonly compile: () => Promise<api.PresentationData>;
|
|
32
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The JSX elements, outermost first, that were being evaluated when `compile`
|
|
41
|
+
* created this shape. `undefined` without the development JSX runtime, and for
|
|
42
|
+
* shapes a template, `Slide from` or `Raw` supplied.
|
|
43
|
+
*/
|
|
44
|
+
declare function getShapeJsxSources(shape: api.SlideShapeData): readonly JsxSource[] | undefined;
|
|
33
45
|
//#endregion
|
|
34
46
|
//#region src/jsx-runtime.d.ts
|
|
35
47
|
declare function jsx<P>(component: (props: P) => Node, props: P, _key?: string | number): Node;
|
|
@@ -48,5 +60,5 @@ declare namespace JSX {
|
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
//#endregion
|
|
51
|
-
export { jsxs as a,
|
|
52
|
-
//# sourceMappingURL=jsx-runtime-
|
|
63
|
+
export { jsxs as a, JsxSource as c, RawContext as d, getShapeJsxSources as f, jsxDEV as i, Node as l, JSX as n, Child as o, jsx as r, Children as s, Fragment as t, PresentationNode as u };
|
|
64
|
+
//# sourceMappingURL=jsx-runtime-BatlHsjp.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as api from "@office-kit/pptx";
|
|
2
|
+
//#region src/model.ts
|
|
3
|
+
function node(kind, evaluate) {
|
|
4
|
+
return {
|
|
5
|
+
kind,
|
|
6
|
+
evaluate
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
async function visit(children, context) {
|
|
10
|
+
if (children == null || typeof children === "boolean") return;
|
|
11
|
+
if (typeof children === "string" || typeof children === "number") {
|
|
12
|
+
if (String(children).trim()) throw new Error("Text content must be inside Text or Fill.");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if ("evaluate" in children) {
|
|
16
|
+
const { source } = children;
|
|
17
|
+
try {
|
|
18
|
+
await children.evaluate(source ? {
|
|
19
|
+
...context,
|
|
20
|
+
sources: [...context.sources, source]
|
|
21
|
+
} : context);
|
|
22
|
+
} catch (cause) {
|
|
23
|
+
if (!source) throw cause;
|
|
24
|
+
const { fileName, lineNumber, columnNumber } = source;
|
|
25
|
+
throw new Error(`${fileName}:${lineNumber}:${columnNumber} <${children.kind}>: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
26
|
+
}
|
|
27
|
+
} else for (const child of children) await visit(child, context);
|
|
28
|
+
}
|
|
29
|
+
function requireSlide(context) {
|
|
30
|
+
if (!context.slide || context.scope !== "slide") throw new Error("This element must be a child of Slide.");
|
|
31
|
+
return context.slide;
|
|
32
|
+
}
|
|
33
|
+
/** Reports a new shape to the enclosing `Group`, if any, and records the JSX that made it. */
|
|
34
|
+
function created(context, shape) {
|
|
35
|
+
if (context.sources.length > 0) context.shapeSources.set(sourceKey(api.getShapeSlide(shape), shape), context.sources);
|
|
36
|
+
context.members?.push(shape);
|
|
37
|
+
return shape;
|
|
38
|
+
}
|
|
39
|
+
const sourceKey = (slide, shape) => `${api.getSlidePartName(slide)}#${api.getShapeId(shape)}`;
|
|
40
|
+
const jsxSources = /* @__PURE__ */ new WeakMap();
|
|
41
|
+
function bindJsxSources(presentation, recorded) {
|
|
42
|
+
if (recorded.size === 0) return;
|
|
43
|
+
for (const slide of api.getSlides(presentation)) {
|
|
44
|
+
const byId = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const shape of api.getSlideShapes(slide)) {
|
|
46
|
+
const sources = recorded.get(sourceKey(slide, shape));
|
|
47
|
+
if (sources) byId.set(api.getShapeId(shape), sources);
|
|
48
|
+
}
|
|
49
|
+
jsxSources.set(slide, byId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The JSX elements, outermost first, that were being evaluated when `compile`
|
|
54
|
+
* created this shape. `undefined` without the development JSX runtime, and for
|
|
55
|
+
* shapes a template, `Slide from` or `Raw` supplied.
|
|
56
|
+
*/
|
|
57
|
+
function getShapeJsxSources(shape) {
|
|
58
|
+
return jsxSources.get(api.getShapeSlide(shape))?.get(api.getShapeId(shape));
|
|
59
|
+
}
|
|
60
|
+
function literal(children) {
|
|
61
|
+
if (children == null || typeof children === "boolean") return "";
|
|
62
|
+
if (typeof children === "string" || typeof children === "number") return String(children);
|
|
63
|
+
if ("evaluate" in children) throw new Error("Use paragraphs for rich text; element children are not text.");
|
|
64
|
+
return children.map(literal).join("");
|
|
65
|
+
}
|
|
66
|
+
/** Separate inline text from shape-scoped escape hatches without evaluating them. */
|
|
67
|
+
function textContent(children) {
|
|
68
|
+
const elements = [];
|
|
69
|
+
function collect(child) {
|
|
70
|
+
if (child == null || typeof child === "boolean") return "";
|
|
71
|
+
if (typeof child === "string" || typeof child === "number") return String(child);
|
|
72
|
+
if ("evaluate" in child) {
|
|
73
|
+
elements.push(child);
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
return child.map(collect).join("");
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
text: collect(children),
|
|
80
|
+
elements
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/jsx-runtime.ts
|
|
85
|
+
function jsx(component, props, _key) {
|
|
86
|
+
if (typeof component !== "function") throw new Error("Use imported PPTX components, not HTML tags.");
|
|
87
|
+
return component(props);
|
|
88
|
+
}
|
|
89
|
+
const jsxs = jsx;
|
|
90
|
+
function jsxDEV(component, props, key, _isStaticChildren, source) {
|
|
91
|
+
const result = jsx(component, props, key);
|
|
92
|
+
if (!source) return result;
|
|
93
|
+
if (result.source && !("compile" in result)) return {
|
|
94
|
+
kind: result.kind,
|
|
95
|
+
source,
|
|
96
|
+
evaluate: (context) => visit(result, context)
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
...result,
|
|
100
|
+
source
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function Fragment({ children }) {
|
|
104
|
+
return node("Fragment", (context) => visit(children, context));
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { bindJsxSources as a, literal as c, textContent as d, visit as f, jsxs as i, node as l, jsx as n, created as o, jsxDEV as r, getShapeJsxSources as s, Fragment as t, requireSlide as u };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=jsx-runtime-CdMn7JwR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsx-runtime-CdMn7JwR.js","names":[],"sources":["../src/model.ts","../src/jsx-runtime.ts"],"sourcesContent":["import * as api from '@office-kit/pptx';\n\nexport type Child = Node | string | number | boolean | null | undefined | readonly Child[];\nexport interface Children {\n children?: Child;\n}\nexport interface RawContext {\n presentation: api.PresentationData;\n slide?: api.SlideData;\n shape?: api.SlideShapeData;\n}\n/** Where `jsxDEV` saw an element. Line and column are 1-based, as TypeScript and esbuild emit them. */\nexport interface JsxSource {\n fileName: string;\n lineNumber: number;\n columnNumber: number;\n}\nexport interface Context extends RawContext {\n mode: 'compose' | 'edit';\n /** Sources of the elements being evaluated, outermost first. */\n sources: readonly JsxSource[];\n /** Every created shape's `sources`, by slide part name and shape id. */\n shapeSources: Map<string, readonly JsxSource[]>;\n scope: 'presentation' | 'slide' | 'shape';\n originals: readonly api.SlideData[];\n deferred: Array<() => void | Promise<void>>;\n /** Set inside a `Group`: every shape created there, in drawing order. */\n members?: api.SlideShapeData[];\n}\nexport interface Node {\n readonly kind: string;\n readonly source?: JsxSource;\n readonly evaluate: (context: Context) => void | Promise<void>;\n}\nexport interface PresentationNode extends Node {\n readonly compile: () => Promise<api.PresentationData>;\n}\nexport function node(kind: string, evaluate: Node['evaluate']): Node {\n return { kind, evaluate };\n}\nexport async function visit(children: Child, context: Context): Promise<void> {\n if (children == null || typeof children === 'boolean') return;\n if (typeof children === 'string' || typeof children === 'number') {\n if (String(children).trim()) throw new Error('Text content must be inside Text or Fill.');\n return;\n }\n if ('evaluate' in children) {\n const { source } = children;\n try {\n await children.evaluate(\n source ? { ...context, sources: [...context.sources, source] } : context,\n );\n } catch (cause) {\n if (!source) throw cause;\n const { fileName, lineNumber, columnNumber } = source;\n throw new Error(\n `${fileName}:${lineNumber}:${columnNumber} <${children.kind}>: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n );\n }\n } else {\n for (const child of children) await visit(child, context);\n }\n}\nexport function requireSlide(context: Context): api.SlideData {\n if (!context.slide || context.scope !== 'slide')\n throw new Error('This element must be a child of Slide.');\n return context.slide;\n}\n/** Reports a new shape to the enclosing `Group`, if any, and records the JSX that made it. */\nexport function created(context: Context, shape: api.SlideShapeData): api.SlideShapeData {\n if (context.sources.length > 0)\n context.shapeSources.set(sourceKey(api.getShapeSlide(shape), shape), context.sources);\n context.members?.push(shape);\n return shape;\n}\n\n// The core replaces slide and shape handles whenever a slide or shape list\n// changes (removing the source slides, grouping), and `getGroupChildren`\n// makes new ones on every call. Part names and shape ids survive all of that,\n// so compile records by them and binds the result to the final slides.\nconst sourceKey = (slide: api.SlideData, shape: api.SlideShapeData) =>\n `${api.getSlidePartName(slide)}#${api.getShapeId(shape)}`;\nconst jsxSources = new WeakMap<api.SlideData, Map<number, readonly JsxSource[]>>();\n\nexport function bindJsxSources(\n presentation: api.PresentationData,\n recorded: Context['shapeSources'],\n): void {\n if (recorded.size === 0) return;\n for (const slide of api.getSlides(presentation)) {\n const byId = new Map<number, readonly JsxSource[]>();\n for (const shape of api.getSlideShapes(slide)) {\n const sources = recorded.get(sourceKey(slide, shape));\n if (sources) byId.set(api.getShapeId(shape), sources);\n }\n jsxSources.set(slide, byId);\n }\n}\n\n/**\n * The JSX elements, outermost first, that were being evaluated when `compile`\n * created this shape. `undefined` without the development JSX runtime, and for\n * shapes a template, `Slide from` or `Raw` supplied.\n */\nexport function getShapeJsxSources(shape: api.SlideShapeData): readonly JsxSource[] | undefined {\n return jsxSources.get(api.getShapeSlide(shape))?.get(api.getShapeId(shape));\n}\nexport function literal(children: Child): string {\n if (children == null || typeof children === 'boolean') return '';\n if (typeof children === 'string' || typeof children === 'number') return String(children);\n if ('evaluate' in children)\n throw new Error('Use paragraphs for rich text; element children are not text.');\n return children.map(literal).join('');\n}\n\n/** Separate inline text from shape-scoped escape hatches without evaluating them. */\nexport function textContent(children: Child): { text: string; elements: Node[] } {\n const elements: Node[] = [];\n function collect(child: Child): string {\n if (child == null || typeof child === 'boolean') return '';\n if (typeof child === 'string' || typeof child === 'number') return String(child);\n if ('evaluate' in child) {\n elements.push(child);\n return '';\n }\n return child.map(collect).join('');\n }\n return { text: collect(children), elements };\n}\n","import { node, visit, type Child, type Children, type Node } from './model.ts';\n\nexport function jsx<P>(component: (props: P) => Node, props: P, _key?: string | number): Node {\n if (typeof component !== 'function')\n throw new Error('Use imported PPTX components, not HTML tags.');\n return component(props);\n}\nexport const jsxs = jsx;\nexport function jsxDEV<P>(\n component: (props: P) => Node,\n props: P,\n key?: string | number,\n _isStaticChildren?: boolean,\n source?: Node['source'],\n): Node {\n const result = jsx(component, props, key);\n if (!source) return result;\n // A component that returns a located element keeps that location nested\n // under the call site, so errors and shape sources name both.\n if (result.source && !('compile' in result))\n return { kind: result.kind, source, evaluate: (context) => visit(result, context) };\n return { ...result, source };\n}\nexport function Fragment({ children }: Children): Node {\n return node('Fragment', (context) => visit(children, context));\n}\nexport namespace JSX {\n export type Element = Node;\n export interface ElementChildrenAttribute {\n children: Child;\n }\n export interface IntrinsicAttributes {\n key?: string | number;\n }\n}\n"],"mappings":"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"}
|
package/dist/jsx-runtime.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as jsxs, i as jsxDEV, n as JSX, r as jsx, t as Fragment } from "./jsx-runtime-
|
|
1
|
+
import { a as jsxs, i as jsxDEV, n as JSX, r as jsx, t as Fragment } from "./jsx-runtime-BatlHsjp.js";
|
|
2
2
|
export { Fragment, JSX, jsx, jsxDEV, jsxs };
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as jsxs, n as jsx, r as jsxDEV, t as Fragment } from "./jsx-runtime-
|
|
1
|
+
import { i as jsxs, n as jsx, r as jsxDEV, t as Fragment } from "./jsx-runtime-CdMn7JwR.js";
|
|
2
2
|
export { Fragment, jsx, jsxDEV, jsxs };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//#region src/source-edit.d.ts
|
|
2
|
+
/** Longest `before` / `after` a text edit accepts, in UTF-16 code units. */
|
|
3
|
+
declare const TEXT_EDIT_MAX_LENGTH = 12000;
|
|
4
|
+
interface SourceFile {
|
|
5
|
+
path: string;
|
|
6
|
+
source: string;
|
|
7
|
+
}
|
|
8
|
+
type TextEditPlan = {
|
|
9
|
+
ok: true;
|
|
10
|
+
change: {
|
|
11
|
+
path: string;
|
|
12
|
+
before: string;
|
|
13
|
+
after: string;
|
|
14
|
+
};
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
reason: 'invalid' | 'not-unique-on-slide' | 'not-found' | 'ambiguous';
|
|
18
|
+
};
|
|
19
|
+
/** A `getShapeJsxSources` entry, with `fileName` given as the matching `SourceFile.path`. */
|
|
20
|
+
interface TextEditAnchor {
|
|
21
|
+
path: string;
|
|
22
|
+
/** 1-based, as in `JsxSource`. */
|
|
23
|
+
lineNumber: number;
|
|
24
|
+
/** 1-based, as in `JsxSource`. */
|
|
25
|
+
columnNumber: number;
|
|
26
|
+
}
|
|
27
|
+
interface TextEditRequest {
|
|
28
|
+
/** Only these files are searched; pass the slide sources, not shared components. */
|
|
29
|
+
files: readonly SourceFile[];
|
|
30
|
+
/** The rendered text of the slide being edited. `before` must occur in it exactly once. */
|
|
31
|
+
slideText: string;
|
|
32
|
+
before: string;
|
|
33
|
+
after: string;
|
|
34
|
+
/** Searches only the JSX element that starts here. */
|
|
35
|
+
anchor?: TextEditAnchor;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Plans replacing the source literal that renders `before` with `after`.
|
|
39
|
+
* Only a JSX text or a string literal written out whole qualifies; computed
|
|
40
|
+
* text and text written in more than one place are refused, so the caller
|
|
41
|
+
* can fall back to a broader edit.
|
|
42
|
+
*/
|
|
43
|
+
declare function planTextEdit(request: TextEditRequest): TextEditPlan;
|
|
44
|
+
type TextEditVerification = {
|
|
45
|
+
ok: true;
|
|
46
|
+
} | {
|
|
47
|
+
ok: false;
|
|
48
|
+
reason: 'slide-count-changed' | 'other-slides-changed' | 'text-mismatch';
|
|
49
|
+
};
|
|
50
|
+
interface PreviewSnapshot {
|
|
51
|
+
/** One rendering per slide (SVG); compared for equality only. */
|
|
52
|
+
slides: readonly string[];
|
|
53
|
+
slideTexts: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
/** Checks that a rebuilt deck differs from the previous one only by `before` → `after` on `slide`. */
|
|
56
|
+
declare function verifyTextEdit(request: {
|
|
57
|
+
previous: PreviewSnapshot;
|
|
58
|
+
next: PreviewSnapshot;
|
|
59
|
+
slide: number;
|
|
60
|
+
before: string;
|
|
61
|
+
after: string;
|
|
62
|
+
}): TextEditVerification;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { PreviewSnapshot, SourceFile, TEXT_EDIT_MAX_LENGTH, TextEditAnchor, TextEditPlan, TextEditRequest, TextEditVerification, planTextEdit, verifyTextEdit };
|
|
65
|
+
//# sourceMappingURL=source-edit.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
//#region src/source-edit.ts
|
|
3
|
+
/** Longest `before` / `after` a text edit accepts, in UTF-16 code units. */
|
|
4
|
+
const TEXT_EDIT_MAX_LENGTH = 12e3;
|
|
5
|
+
function jsxText(node) {
|
|
6
|
+
return node.text.split(/\r\n|\n|\r/).map((line, index, lines) => {
|
|
7
|
+
let value = line.replaceAll(" ", " ");
|
|
8
|
+
if (index) value = value.trimStart();
|
|
9
|
+
if (index < lines.length - 1) value = value.trimEnd();
|
|
10
|
+
return value;
|
|
11
|
+
}).filter(Boolean).join(" ");
|
|
12
|
+
}
|
|
13
|
+
function anchoredElement(source, anchor) {
|
|
14
|
+
const lines = source.getLineStarts();
|
|
15
|
+
const lineStart = lines[anchor.lineNumber - 1];
|
|
16
|
+
if (lineStart === void 0 || anchor.columnNumber < 1) return void 0;
|
|
17
|
+
const position = lineStart + anchor.columnNumber - 1;
|
|
18
|
+
if (position >= (lines[anchor.lineNumber] ?? source.end)) return void 0;
|
|
19
|
+
let found;
|
|
20
|
+
const visit = (node) => {
|
|
21
|
+
if (found || node.getStart(source) > position || node.end <= position) return;
|
|
22
|
+
if (node.getStart(source) === position && (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node))) found = node;
|
|
23
|
+
else ts.forEachChild(node, visit);
|
|
24
|
+
};
|
|
25
|
+
visit(source);
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Plans replacing the source literal that renders `before` with `after`.
|
|
30
|
+
* Only a JSX text or a string literal written out whole qualifies; computed
|
|
31
|
+
* text and text written in more than one place are refused, so the caller
|
|
32
|
+
* can fall back to a broader edit.
|
|
33
|
+
*/
|
|
34
|
+
function planTextEdit(request) {
|
|
35
|
+
const { files, slideText, before, after, anchor } = request;
|
|
36
|
+
if (!before.trim() || before.length > 12e3 || after.length > 12e3 || after === before) return {
|
|
37
|
+
ok: false,
|
|
38
|
+
reason: "invalid"
|
|
39
|
+
};
|
|
40
|
+
if (slideText.split(before).length !== 2) return {
|
|
41
|
+
ok: false,
|
|
42
|
+
reason: "not-unique-on-slide"
|
|
43
|
+
};
|
|
44
|
+
const changes = [];
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
if (anchor && file.path !== anchor.path) continue;
|
|
47
|
+
const source = ts.createSourceFile(file.path, file.source, ts.ScriptTarget.Latest, true, /[jt]sx$/.test(file.path) ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
48
|
+
const root = anchor ? anchoredElement(source, anchor) : source;
|
|
49
|
+
if (!root) continue;
|
|
50
|
+
const visit = (node) => {
|
|
51
|
+
if ((ts.isJsxText(node) ? jsxText(node) : ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) ? node.text : void 0) === before) {
|
|
52
|
+
const replacement = ts.isJsxText(node) || ts.isJsxAttribute(node.parent) ? "{" + JSON.stringify(after) + "}" : JSON.stringify(after);
|
|
53
|
+
const start = ts.isJsxText(node) ? node.pos : node.getStart(source);
|
|
54
|
+
changes.push({
|
|
55
|
+
path: file.path,
|
|
56
|
+
before: file.source,
|
|
57
|
+
after: file.source.slice(0, start) + replacement + file.source.slice(node.end)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
ts.forEachChild(node, visit);
|
|
61
|
+
};
|
|
62
|
+
visit(root);
|
|
63
|
+
}
|
|
64
|
+
if (changes.length === 0) return {
|
|
65
|
+
ok: false,
|
|
66
|
+
reason: "not-found"
|
|
67
|
+
};
|
|
68
|
+
if (changes.length > 1) return {
|
|
69
|
+
ok: false,
|
|
70
|
+
reason: "ambiguous"
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
ok: true,
|
|
74
|
+
change: changes[0]
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Checks that a rebuilt deck differs from the previous one only by `before` → `after` on `slide`. */
|
|
78
|
+
function verifyTextEdit(request) {
|
|
79
|
+
const { previous, next, slide, before, after } = request;
|
|
80
|
+
if (next.slides.length !== previous.slides.length) return {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: "slide-count-changed"
|
|
83
|
+
};
|
|
84
|
+
if (next.slides.some((svg, index) => index !== slide && svg !== previous.slides[index])) return {
|
|
85
|
+
ok: false,
|
|
86
|
+
reason: "other-slides-changed"
|
|
87
|
+
};
|
|
88
|
+
if (next.slideTexts[slide] !== previous.slideTexts[slide]?.replace(before, () => after)) return {
|
|
89
|
+
ok: false,
|
|
90
|
+
reason: "text-mismatch"
|
|
91
|
+
};
|
|
92
|
+
return { ok: true };
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { TEXT_EDIT_MAX_LENGTH, planTextEdit, verifyTextEdit };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=source-edit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-edit.js","names":[],"sources":["../src/source-edit.ts"],"sourcesContent":["import ts from 'typescript';\n\n/** Longest `before` / `after` a text edit accepts, in UTF-16 code units. */\nexport const TEXT_EDIT_MAX_LENGTH = 12000;\n\nexport interface SourceFile {\n path: string;\n source: string;\n}\n\nexport type TextEditPlan =\n | { ok: true; change: { path: string; before: string; after: string } }\n | { ok: false; reason: 'invalid' | 'not-unique-on-slide' | 'not-found' | 'ambiguous' };\n\n/** A `getShapeJsxSources` entry, with `fileName` given as the matching `SourceFile.path`. */\nexport interface TextEditAnchor {\n path: string;\n /** 1-based, as in `JsxSource`. */\n lineNumber: number;\n /** 1-based, as in `JsxSource`. */\n columnNumber: number;\n}\n\nexport interface TextEditRequest {\n /** Only these files are searched; pass the slide sources, not shared components. */\n files: readonly SourceFile[];\n /** The rendered text of the slide being edited. `before` must occur in it exactly once. */\n slideText: string;\n before: string;\n after: string;\n /** Searches only the JSX element that starts here. */\n anchor?: TextEditAnchor;\n}\n\n// A JSX text node renders the way JSX collapses it: each line trimmed where\n// it meets a line break, blank lines dropped, the rest joined by one space.\nfunction jsxText(node: ts.JsxText): string {\n return node.text\n .split(/\\r\\n|\\n|\\r/)\n .map((line, index, lines) => {\n let value = line.replaceAll('\\t', ' ');\n if (index) value = value.trimStart();\n if (index < lines.length - 1) value = value.trimEnd();\n return value;\n })\n .filter(Boolean)\n .join(' ');\n}\n\nfunction anchoredElement(source: ts.SourceFile, anchor: TextEditAnchor): ts.Node | undefined {\n const lines = source.getLineStarts();\n const lineStart = lines[anchor.lineNumber - 1];\n // A stale anchor may point past the file or past the end of its line.\n if (lineStart === undefined || anchor.columnNumber < 1) return undefined;\n const position = lineStart + anchor.columnNumber - 1;\n if (position >= (lines[anchor.lineNumber] ?? source.end)) return undefined;\n let found: ts.Node | undefined;\n const visit = (node: ts.Node) => {\n if (found || node.getStart(source) > position || node.end <= position) return;\n if (\n node.getStart(source) === position &&\n (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node))\n )\n found = node;\n else ts.forEachChild(node, visit);\n };\n visit(source);\n return found;\n}\n\n/**\n * Plans replacing the source literal that renders `before` with `after`.\n * Only a JSX text or a string literal written out whole qualifies; computed\n * text and text written in more than one place are refused, so the caller\n * can fall back to a broader edit.\n */\nexport function planTextEdit(request: TextEditRequest): TextEditPlan {\n const { files, slideText, before, after, anchor } = request;\n if (\n !before.trim() ||\n before.length > TEXT_EDIT_MAX_LENGTH ||\n after.length > TEXT_EDIT_MAX_LENGTH ||\n after === before\n )\n return { ok: false, reason: 'invalid' };\n if (slideText.split(before).length !== 2) return { ok: false, reason: 'not-unique-on-slide' };\n const changes: { path: string; before: string; after: string }[] = [];\n for (const file of files) {\n if (anchor && file.path !== anchor.path) continue;\n const source = ts.createSourceFile(\n file.path,\n file.source,\n ts.ScriptTarget.Latest,\n true,\n /[jt]sx$/.test(file.path) ? ts.ScriptKind.TSX : ts.ScriptKind.TS,\n );\n const root = anchor ? anchoredElement(source, anchor) : source;\n if (!root) continue;\n const visit = (node: ts.Node) => {\n const text = ts.isJsxText(node)\n ? jsxText(node)\n : ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)\n ? node.text\n : undefined;\n if (text === before) {\n // Attribute strings use JSX entity rules, not JavaScript string escapes.\n const jsx = ts.isJsxText(node) || ts.isJsxAttribute(node.parent);\n const replacement = jsx ? '{' + JSON.stringify(after) + '}' : JSON.stringify(after);\n const start = ts.isJsxText(node) ? node.pos : node.getStart(source);\n changes.push({\n path: file.path,\n before: file.source,\n after: file.source.slice(0, start) + replacement + file.source.slice(node.end),\n });\n }\n ts.forEachChild(node, visit);\n };\n visit(root);\n }\n if (changes.length === 0) return { ok: false, reason: 'not-found' };\n if (changes.length > 1) return { ok: false, reason: 'ambiguous' };\n return { ok: true, change: changes[0]! };\n}\n\nexport type TextEditVerification =\n | { ok: true }\n | { ok: false; reason: 'slide-count-changed' | 'other-slides-changed' | 'text-mismatch' };\n\nexport interface PreviewSnapshot {\n /** One rendering per slide (SVG); compared for equality only. */\n slides: readonly string[];\n slideTexts: readonly string[];\n}\n\n/** Checks that a rebuilt deck differs from the previous one only by `before` → `after` on `slide`. */\nexport function verifyTextEdit(request: {\n previous: PreviewSnapshot;\n next: PreviewSnapshot;\n slide: number;\n before: string;\n after: string;\n}): TextEditVerification {\n const { previous, next, slide, before, after } = request;\n if (next.slides.length !== previous.slides.length)\n return { ok: false, reason: 'slide-count-changed' };\n if (next.slides.some((svg, index) => index !== slide && svg !== previous.slides[index]))\n return { ok: false, reason: 'other-slides-changed' };\n // A function replacement: `after` may contain `$&` and friends.\n if (next.slideTexts[slide] !== previous.slideTexts[slide]?.replace(before, () => after))\n return { ok: false, reason: 'text-mismatch' };\n return { ok: true };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@office-kit/pptx-dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Typed TSX authoring for editable PowerPoint presentations",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"types": "./dist/jsx-runtime.d.ts",
|
|
28
28
|
"import": "./dist/jsx-runtime.js"
|
|
29
29
|
},
|
|
30
|
+
"./source-edit": {
|
|
31
|
+
"types": "./dist/source-edit.d.ts",
|
|
32
|
+
"import": "./dist/source-edit.js"
|
|
33
|
+
},
|
|
30
34
|
"./package.json": "./package.json"
|
|
31
35
|
},
|
|
32
36
|
"publishConfig": {
|
|
@@ -34,12 +38,18 @@
|
|
|
34
38
|
"provenance": true
|
|
35
39
|
},
|
|
36
40
|
"devDependencies": {
|
|
37
|
-
"@office-kit/pptx": "0.
|
|
41
|
+
"@office-kit/pptx": "0.21.0",
|
|
38
42
|
"tsdown": "^0.22.2",
|
|
39
43
|
"typescript": "^6.0.3"
|
|
40
44
|
},
|
|
41
45
|
"peerDependencies": {
|
|
42
|
-
"@office-kit/pptx": "^0.
|
|
46
|
+
"@office-kit/pptx": "^0.21.0",
|
|
47
|
+
"typescript": ">=5.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"typescript": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
43
53
|
},
|
|
44
54
|
"engines": {
|
|
45
55
|
"node": ">=22.18.0"
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
//#region src/model.ts
|
|
2
|
-
function node(kind, evaluate) {
|
|
3
|
-
return {
|
|
4
|
-
kind,
|
|
5
|
-
evaluate
|
|
6
|
-
};
|
|
7
|
-
}
|
|
8
|
-
async function visit(children, context) {
|
|
9
|
-
if (children == null || typeof children === "boolean") return;
|
|
10
|
-
if (typeof children === "string" || typeof children === "number") {
|
|
11
|
-
if (String(children).trim()) throw new Error("Text content must be inside Text or Fill.");
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if ("evaluate" in children) try {
|
|
15
|
-
await children.evaluate(context);
|
|
16
|
-
} catch (cause) {
|
|
17
|
-
if (!children.source) throw cause;
|
|
18
|
-
const { fileName, lineNumber, columnNumber } = children.source;
|
|
19
|
-
throw new Error(`${fileName}:${lineNumber}:${columnNumber} <${children.kind}>: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
20
|
-
}
|
|
21
|
-
else for (const child of children) await visit(child, context);
|
|
22
|
-
}
|
|
23
|
-
function requireSlide(context) {
|
|
24
|
-
if (!context.slide || context.scope !== "slide") throw new Error("This element must be a child of Slide.");
|
|
25
|
-
return context.slide;
|
|
26
|
-
}
|
|
27
|
-
/** Reports a new shape to the enclosing `Group`, if there is one. */
|
|
28
|
-
function created(context, shape) {
|
|
29
|
-
context.members?.push(shape);
|
|
30
|
-
return shape;
|
|
31
|
-
}
|
|
32
|
-
function literal(children) {
|
|
33
|
-
if (children == null || typeof children === "boolean") return "";
|
|
34
|
-
if (typeof children === "string" || typeof children === "number") return String(children);
|
|
35
|
-
if ("evaluate" in children) throw new Error("Use paragraphs for rich text; element children are not text.");
|
|
36
|
-
return children.map(literal).join("");
|
|
37
|
-
}
|
|
38
|
-
/** Separate inline text from shape-scoped escape hatches without evaluating them. */
|
|
39
|
-
function textContent(children) {
|
|
40
|
-
const elements = [];
|
|
41
|
-
function collect(child) {
|
|
42
|
-
if (child == null || typeof child === "boolean") return "";
|
|
43
|
-
if (typeof child === "string" || typeof child === "number") return String(child);
|
|
44
|
-
if ("evaluate" in child) {
|
|
45
|
-
elements.push(child);
|
|
46
|
-
return "";
|
|
47
|
-
}
|
|
48
|
-
return child.map(collect).join("");
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
text: collect(children),
|
|
52
|
-
elements
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
//#endregion
|
|
56
|
-
//#region src/jsx-runtime.ts
|
|
57
|
-
function jsx(component, props, _key) {
|
|
58
|
-
if (typeof component !== "function") throw new Error("Use imported PPTX components, not HTML tags.");
|
|
59
|
-
return component(props);
|
|
60
|
-
}
|
|
61
|
-
const jsxs = jsx;
|
|
62
|
-
function jsxDEV(component, props, key, _isStaticChildren, source) {
|
|
63
|
-
const result = jsx(component, props, key);
|
|
64
|
-
return source ? {
|
|
65
|
-
...result,
|
|
66
|
-
source
|
|
67
|
-
} : result;
|
|
68
|
-
}
|
|
69
|
-
function Fragment({ children }) {
|
|
70
|
-
return node("Fragment", (context) => visit(children, context));
|
|
71
|
-
}
|
|
72
|
-
//#endregion
|
|
73
|
-
export { created as a, requireSlide as c, jsxs as i, textContent as l, jsx as n, literal as o, jsxDEV as r, node as s, Fragment as t, visit as u };
|
|
74
|
-
|
|
75
|
-
//# sourceMappingURL=jsx-runtime-DoUxctgW.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-runtime-DoUxctgW.js","names":[],"sources":["../src/model.ts","../src/jsx-runtime.ts"],"sourcesContent":["import type * as api from '@office-kit/pptx';\n\nexport type Child = Node | string | number | boolean | null | undefined | readonly Child[];\nexport interface Children {\n children?: Child;\n}\nexport interface RawContext {\n presentation: api.PresentationData;\n slide?: api.SlideData;\n shape?: api.SlideShapeData;\n}\nexport interface Context extends RawContext {\n mode: 'compose' | 'edit';\n scope: 'presentation' | 'slide' | 'shape';\n originals: readonly api.SlideData[];\n deferred: Array<() => void | Promise<void>>;\n /** Set inside a `Group`: every shape created there, in drawing order. */\n members?: api.SlideShapeData[];\n}\nexport interface Node {\n readonly kind: string;\n readonly source?: { fileName: string; lineNumber: number; columnNumber: number };\n readonly evaluate: (context: Context) => void | Promise<void>;\n}\nexport interface PresentationNode extends Node {\n readonly compile: () => Promise<api.PresentationData>;\n}\nexport function node(kind: string, evaluate: Node['evaluate']): Node {\n return { kind, evaluate };\n}\nexport async function visit(children: Child, context: Context): Promise<void> {\n if (children == null || typeof children === 'boolean') return;\n if (typeof children === 'string' || typeof children === 'number') {\n if (String(children).trim()) throw new Error('Text content must be inside Text or Fill.');\n return;\n }\n if ('evaluate' in children) {\n try {\n await children.evaluate(context);\n } catch (cause) {\n if (!children.source) throw cause;\n const { fileName, lineNumber, columnNumber } = children.source;\n throw new Error(\n `${fileName}:${lineNumber}:${columnNumber} <${children.kind}>: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n );\n }\n } else {\n for (const child of children) await visit(child, context);\n }\n}\nexport function requireSlide(context: Context): api.SlideData {\n if (!context.slide || context.scope !== 'slide')\n throw new Error('This element must be a child of Slide.');\n return context.slide;\n}\n/** Reports a new shape to the enclosing `Group`, if there is one. */\nexport function created(context: Context, shape: api.SlideShapeData): api.SlideShapeData {\n context.members?.push(shape);\n return shape;\n}\nexport function literal(children: Child): string {\n if (children == null || typeof children === 'boolean') return '';\n if (typeof children === 'string' || typeof children === 'number') return String(children);\n if ('evaluate' in children)\n throw new Error('Use paragraphs for rich text; element children are not text.');\n return children.map(literal).join('');\n}\n\n/** Separate inline text from shape-scoped escape hatches without evaluating them. */\nexport function textContent(children: Child): { text: string; elements: Node[] } {\n const elements: Node[] = [];\n function collect(child: Child): string {\n if (child == null || typeof child === 'boolean') return '';\n if (typeof child === 'string' || typeof child === 'number') return String(child);\n if ('evaluate' in child) {\n elements.push(child);\n return '';\n }\n return child.map(collect).join('');\n }\n return { text: collect(children), elements };\n}\n","import { node, visit, type Child, type Children, type Node } from './model.ts';\n\nexport function jsx<P>(component: (props: P) => Node, props: P, _key?: string | number): Node {\n if (typeof component !== 'function')\n throw new Error('Use imported PPTX components, not HTML tags.');\n return component(props);\n}\nexport const jsxs = jsx;\nexport function jsxDEV<P>(\n component: (props: P) => Node,\n props: P,\n key?: string | number,\n _isStaticChildren?: boolean,\n source?: Node['source'],\n): Node {\n const result = jsx(component, props, key);\n return source ? { ...result, source } : result;\n}\nexport function Fragment({ children }: Children): Node {\n return node('Fragment', (context) => visit(children, context));\n}\nexport namespace JSX {\n export type Element = Node;\n export interface ElementChildrenAttribute {\n children: Child;\n }\n export interface IntrinsicAttributes {\n key?: string | number;\n }\n}\n"],"mappings":";AA2BA,SAAgB,KAAK,MAAc,UAAkC;CACnE,OAAO;EAAE;EAAM;CAAS;AAC1B;AACA,eAAsB,MAAM,UAAiB,SAAiC;CAC5E,IAAI,YAAY,QAAQ,OAAO,aAAa,WAAW;CACvD,IAAI,OAAO,aAAa,YAAY,OAAO,aAAa,UAAU;EAChE,IAAI,OAAO,QAAQ,CAAC,CAAC,KAAK,GAAG,MAAM,IAAI,MAAM,2CAA2C;EACxF;CACF;CACA,IAAI,cAAc,UAChB,IAAI;EACF,MAAM,SAAS,SAAS,OAAO;CACjC,SAAS,OAAO;EACd,IAAI,CAAC,SAAS,QAAQ,MAAM;EAC5B,MAAM,EAAE,UAAU,YAAY,iBAAiB,SAAS;EACxD,MAAM,IAAI,MACR,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,IAAI,SAAS,KAAK,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KACtH,EAAE,MAAM,CACV;CACF;MAEA,KAAK,MAAM,SAAS,UAAU,MAAM,MAAM,OAAO,OAAO;AAE5D;AACA,SAAgB,aAAa,SAAiC;CAC5D,IAAI,CAAC,QAAQ,SAAS,QAAQ,UAAU,SACtC,MAAM,IAAI,MAAM,wCAAwC;CAC1D,OAAO,QAAQ;AACjB;;AAEA,SAAgB,QAAQ,SAAkB,OAA+C;CACvF,QAAQ,SAAS,KAAK,KAAK;CAC3B,OAAO;AACT;AACA,SAAgB,QAAQ,UAAyB;CAC/C,IAAI,YAAY,QAAQ,OAAO,aAAa,WAAW,OAAO;CAC9D,IAAI,OAAO,aAAa,YAAY,OAAO,aAAa,UAAU,OAAO,OAAO,QAAQ;CACxF,IAAI,cAAc,UAChB,MAAM,IAAI,MAAM,8DAA8D;CAChF,OAAO,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,EAAE;AACtC;;AAGA,SAAgB,YAAY,UAAqD;CAC/E,MAAM,WAAmB,CAAC;CAC1B,SAAS,QAAQ,OAAsB;EACrC,IAAI,SAAS,QAAQ,OAAO,UAAU,WAAW,OAAO;EACxD,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU,OAAO,OAAO,KAAK;EAC/E,IAAI,cAAc,OAAO;GACvB,SAAS,KAAK,KAAK;GACnB,OAAO;EACT;EACA,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC,KAAK,EAAE;CACnC;CACA,OAAO;EAAE,MAAM,QAAQ,QAAQ;EAAG;CAAS;AAC7C;;;AChFA,SAAgB,IAAO,WAA+B,OAAU,MAA8B;CAC5F,IAAI,OAAO,cAAc,YACvB,MAAM,IAAI,MAAM,8CAA8C;CAChE,OAAO,UAAU,KAAK;AACxB;AACA,MAAa,OAAO;AACpB,SAAgB,OACd,WACA,OACA,KACA,mBACA,QACM;CACN,MAAM,SAAS,IAAI,WAAW,OAAO,GAAG;CACxC,OAAO,SAAS;EAAE,GAAG;EAAQ;CAAO,IAAI;AAC1C;AACA,SAAgB,SAAS,EAAE,YAA4B;CACrD,OAAO,KAAK,aAAa,YAAY,MAAM,UAAU,OAAO,CAAC;AAC/D"}
|