@pyreon/document-primitives 0.22.0 → 0.23.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.
Files changed (2) hide show
  1. package/README.md +148 -4
  2. package/package.json +18 -18
package/README.md CHANGED
@@ -1,16 +1,160 @@
1
1
  # @pyreon/document-primitives
2
2
 
3
- Rocketstyle-based document components for `@pyreon/document` export.
3
+ 18 rocketstyle document components that render in the browser AND export to 14+ formats.
4
+
5
+ `@pyreon/document-primitives` ships a complete set of styled, themeable document primitives — `DocDocument`, `DocPage`, `DocSection`, `DocRow`, `DocColumn`, `DocHeading`, `DocText`, `DocLink`, `DocImage`, `DocTable`, `DocList`, `DocListItem`, `DocCode`, `DocDivider`, `DocSpacer`, `DocButton`, `DocQuote`, `DocPageBreak` — built on `@pyreon/rocketstyle` + `@pyreon/elements`. Each primitive carries a `_documentType` marker via `.statics()` so `@pyreon/connector-document` can walk the same JSX tree and produce a serializable `DocNode` for `@pyreon/document`'s renderers (PDF, DOCX, XLSX, PPTX, HTML email, Markdown, plain text, …). **Write one template, render it in the browser AS PREVIEW, then export the same tree to any format.**
4
6
 
5
7
  ## Install
6
8
 
7
9
  ```bash
8
- bun add @pyreon/document-primitives
10
+ bun add @pyreon/document-primitives @pyreon/document @pyreon/connector-document
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```tsx
16
+ import {
17
+ DocDocument, DocPage, DocHeading, DocText, DocList, DocListItem,
18
+ extractDocNode,
19
+ } from '@pyreon/document-primitives'
20
+ import { render, download } from '@pyreon/document'
21
+
22
+ function ReportTemplate({ data }: { data: () => Report }) {
23
+ return (
24
+ <DocDocument
25
+ title={() => `${data().org} — Q4 report`}
26
+ author={() => data().author}
27
+ >
28
+ <DocPage>
29
+ <DocHeading level={1}>Q4 Highlights</DocHeading>
30
+ <DocText>Revenue grew {() => data().revenueDelta}% year-over-year.</DocText>
31
+ <DocList>
32
+ {() => data().bullets.map((b) => <DocListItem>{b}</DocListItem>)}
33
+ </DocList>
34
+ </DocPage>
35
+ </DocDocument>
36
+ )
37
+ }
38
+
39
+ // Live preview — render the same tree into the DOM
40
+ <ReportTemplate data={() => store.report} />
41
+
42
+ // Export — extract + render to a format
43
+ const tree = extractDocNode(() => <ReportTemplate data={() => store.report} />)
44
+ const pdfBuffer = await render(tree, 'pdf')
45
+ await download(tree, 'q4-report.pdf') // browser shortcut
46
+ const markdown = await render(tree, 'markdown')
47
+ const docxBuffer = await render(tree, 'docx')
48
+ ```
49
+
50
+ ## The 18 primitives
51
+
52
+ | Component | `_documentType` | Base | Notes |
53
+ |---|---|---|---|
54
+ | `DocDocument` | `'document'` | `Element` | Root container; accepts `title` / `author` / `subject` (string or `() => string` accessor) |
55
+ | `DocPage` | `'page'` | `Element` | Logical page; carries page-level metadata for paginated formats |
56
+ | `DocSection` | `'section'` | `Element` | Semantic section grouping |
57
+ | `DocRow` | `'row'` | `Element` | Horizontal layout row |
58
+ | `DocColumn` | `'column'` | `Element` | Vertical layout column with optional `width` |
59
+ | `DocHeading` | `'heading'` | `Text` | `level` 1-6 (or `h1`-`h6` flags) |
60
+ | `DocText` | `'text'` | `Text` | Paragraph text |
61
+ | `DocLink` | `'link'` | `Text` | `href` |
62
+ | `DocImage` | `'image'` | `Element` | `src` / `alt` / `width` / `height` |
63
+ | `DocTable` | `'table'` | `Element` | Tabular data with column definitions |
64
+ | `DocList` | `'list'` | `Element` | `ordered: boolean` |
65
+ | `DocListItem` | `'list-item'` | `Element` | List item |
66
+ | `DocCode` | `'code'` | `Text` | Optional `language` for syntax-highlighted exports |
67
+ | `DocDivider` | `'divider'` | `Element` | Horizontal rule |
68
+ | `DocSpacer` | `'spacer'` | `Element` | Vertical spacing; `height` (default 16) |
69
+ | `DocButton` | `'button'` | `Text` | CTA with `href` |
70
+ | `DocQuote` | `'quote'` | `Element` | Blockquote with optional `borderColor` |
71
+ | `DocPageBreak` | `'page-break'` | `Element` | Forces a page break in paginated formats |
72
+
73
+ Every primitive is themeable via rocketstyle's `.theme()` and `.attrs()` chain — wrap, restyle, restyle again, all while preserving the `_documentType` marker.
74
+
75
+ ## Reactive metadata via accessor props
76
+
77
+ `DocDocument`'s `title` / `author` / `subject` accept either a plain `string` or `() => string`. Function values are stored in `_documentProps` and **resolved by the extractor at extraction time** — so every export click reads the LIVE value from any underlying signal. You don't need a `const initial = signal()` workaround.
78
+
79
+ ```tsx
80
+ // Both work; the accessor form is what you want for templates that drive
81
+ // a live preview AND export the same tree.
82
+ <DocDocument title="Static" />
83
+ <DocDocument title={() => `${name()} — ${date()}`} />
84
+ ```
85
+
86
+ The same accessor pattern works for reactive children — pass a thunk inside the JSX slot and the extractor resolves it on each extraction:
87
+
88
+ ```tsx
89
+ <DocText>{() => store.summary()}</DocText>
90
+ ```
91
+
92
+ ## Export helpers
93
+
94
+ ### `extractDocNode(templateFn, options?)`
95
+
96
+ One-step helper — wrap your template render in a thunk and get a serializable `DocNode`.
97
+
98
+ ```ts
99
+ const tree = extractDocNode(() => <ReportTemplate data={store.report} />)
100
+ await render(tree, 'pdf')
101
+ ```
102
+
103
+ ### `createDocumentExport(templateFn, options?)`
104
+
105
+ Two-step form — returns `{ getDocNode(): DocNode }`. Kept for callers that need to pass the helper object around (e.g. an "Export" component that takes a `DocumentExport` prop). New code should prefer `extractDocNode`.
106
+
107
+ ```ts
108
+ const doc = createDocumentExport(() => <ReportTemplate data={store.report} />)
109
+ const tree = doc.getDocNode()
9
110
  ```
10
111
 
11
- ## Overview
112
+ `DocumentExportOptions` extends `ExtractOptions` from `@pyreon/connector-document` (`rootSize`, `includeStyles`) plus `theme` and `mode: 'light' | 'dark'` for export-time theming.
113
+
114
+ ## Theme
115
+
116
+ `documentTheme` ships sensible defaults (font sizes, spacing, colors) that match the on-screen render. Compose with your own theme via `PyreonUI` or pass per-primitive via the rocketstyle `.theme()` chain.
117
+
118
+ ```ts
119
+ import { documentTheme, DocumentTheme } from '@pyreon/document-primitives'
120
+ ```
121
+
122
+ ## `<DocumentPreview>`
123
+
124
+ Convenience component that renders a tree-shaped template into a styled preview frame. Use it when you want a quick "what will this look like when exported?" pane without building your own preview layout.
125
+
126
+ ```tsx
127
+ import { DocumentPreview } from '@pyreon/document-primitives'
128
+
129
+ <DocumentPreview>
130
+ <ReportTemplate data={store.report} />
131
+ </DocumentPreview>
132
+ ```
133
+
134
+ ## Connector re-exports
135
+
136
+ `extractDocumentTree`, `resolveStyles`, and the related types from `@pyreon/connector-document` are re-exported so `@pyreon/document-primitives` can be your single import.
137
+
138
+ ```ts
139
+ import {
140
+ extractDocumentTree, // walk a vnode → DocNode
141
+ resolveStyles, // $rocketstyle → ResolvedStyles
142
+ type DocNode, type DocChild, type NodeType, type ResolvedStyles,
143
+ type ExtractOptions,
144
+ } from '@pyreon/document-primitives'
145
+ ```
146
+
147
+ ## Gotchas
148
+
149
+ - **JSX slot expressions are resolved at extraction time, not subscribed.** Calling `extractDocNode` again is what produces a fresh tree after signal changes.
150
+ - **Rocketstyle `.attrs<P>()` is your PUBLIC prop type** — never include runtime-filled fields like `_documentProps` in the generic, or they'll leak as required props. They live inside the callback body, not in the type parameter.
151
+ - **Watch for prop names that collide with read-only DOM properties.** `HTMLTableElement.rows` / `.columns` are read-only `HTMLCollection` getters; if those names reach the rendered DOM, the runtime throws `Cannot set property rows of [object Object] which has only a getter`. `DocTable` strips them via rocketstyle's `.attrs(callback, { filter: ['rows', 'columns', ...] })` — apply the same pattern if you author new primitives.
152
+ - **The browser and export trees are the SAME tree.** A debug session that breaks the preview will also break the export. Test both paths.
153
+ - **`DocDocument.title` accessor stores a function in `_documentProps`** — `extractDocumentTree` calls it once per extraction. Plain strings pass through unchanged.
154
+
155
+ ## Documentation
12
156
 
13
- Provides styled document primitives built on `@pyreon/rocketstyle` that can be rendered to PDF, DOCX, email, and other formats via `@pyreon/document`.
157
+ Full docs: [docs.pyreon.dev/docs/document-primitives](https://docs.pyreon.dev/docs/document-primitives) (or `docs/docs/document-primitives.md` in this repo).
14
158
 
15
159
  ## License
16
160
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/document-primitives",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Rocketstyle document components — render in browser, export to 18 formats",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,27 +42,27 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "dependencies": {
45
- "@pyreon/connector-document": "^0.22.0",
46
- "@pyreon/core": "^0.22.0",
47
- "@pyreon/document": "^0.22.0",
48
- "@pyreon/elements": "^0.22.0",
49
- "@pyreon/rocketstyle": "^0.22.0",
50
- "@pyreon/styler": "^0.22.0",
51
- "@pyreon/ui-core": "^0.22.0"
45
+ "@pyreon/connector-document": "^0.23.0",
46
+ "@pyreon/core": "^0.23.0",
47
+ "@pyreon/document": "^0.23.0",
48
+ "@pyreon/elements": "^0.23.0",
49
+ "@pyreon/rocketstyle": "^0.23.0",
50
+ "@pyreon/styler": "^0.23.0",
51
+ "@pyreon/ui-core": "^0.23.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@pyreon/core": "^0.22.0",
55
- "@pyreon/elements": "^0.22.0",
54
+ "@pyreon/core": "^0.23.0",
55
+ "@pyreon/elements": "^0.23.0",
56
56
  "@pyreon/manifest": "0.13.1",
57
- "@pyreon/reactivity": "^0.22.0",
58
- "@pyreon/rocketstyle": "^0.22.0",
59
- "@pyreon/runtime-dom": "^0.22.0",
60
- "@pyreon/styler": "^0.22.0",
61
- "@pyreon/test-utils": "^0.13.9",
62
- "@pyreon/typescript": "^0.22.0",
63
- "@pyreon/ui-core": "^0.22.0",
57
+ "@pyreon/reactivity": "^0.23.0",
58
+ "@pyreon/rocketstyle": "^0.23.0",
59
+ "@pyreon/runtime-dom": "^0.23.0",
60
+ "@pyreon/styler": "^0.23.0",
61
+ "@pyreon/test-utils": "^0.13.10",
62
+ "@pyreon/typescript": "^0.23.0",
63
+ "@pyreon/ui-core": "^0.23.0",
64
64
  "@vitest/browser-playwright": "^4.1.4",
65
- "@vitus-labs/tools-rolldown": "^2.3.0"
65
+ "@vitus-labs/tools-rolldown": "^2.4.0"
66
66
  },
67
67
  "engines": {
68
68
  "node": ">= 22"