@pyreon/document-primitives 0.13.0 → 0.14.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/lib/index.d.ts
CHANGED
|
@@ -81,10 +81,10 @@ declare const DocButton: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
81
81
|
}> & _pyreon_core0.PyreonHTMLAttributes<HTMLElement>, {
|
|
82
82
|
href?: string;
|
|
83
83
|
}, {}, {
|
|
84
|
-
padding: string;
|
|
85
84
|
borderRadius: number;
|
|
86
85
|
fontSize: number;
|
|
87
86
|
fontWeight: string;
|
|
87
|
+
padding: string;
|
|
88
88
|
textAlign: string;
|
|
89
89
|
textDecoration: string;
|
|
90
90
|
}, {
|
|
@@ -118,10 +118,10 @@ declare const DocCode: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
118
118
|
language?: string;
|
|
119
119
|
}, {}, {
|
|
120
120
|
backgroundColor: string;
|
|
121
|
-
padding: string;
|
|
122
121
|
borderRadius: number;
|
|
123
122
|
fontFamily: string;
|
|
124
123
|
fontSize: number;
|
|
124
|
+
padding: string;
|
|
125
125
|
}, {
|
|
126
126
|
_documentType: "code";
|
|
127
127
|
}, {}, {
|
|
@@ -354,9 +354,9 @@ declare const DocHeading: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
354
354
|
}> & _pyreon_core0.PyreonHTMLAttributes<HTMLElement>, {
|
|
355
355
|
level?: string;
|
|
356
356
|
}, {}, {
|
|
357
|
+
marginBottom: number;
|
|
357
358
|
color: string;
|
|
358
359
|
fontWeight: string;
|
|
359
|
-
marginBottom: number;
|
|
360
360
|
}, {
|
|
361
361
|
_documentType: "heading";
|
|
362
362
|
}, {}, {
|
|
@@ -423,9 +423,9 @@ declare const DocImage: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
423
423
|
beforeContentCss: _pyreon_elements0.ExtendCss;
|
|
424
424
|
afterContentCss: _pyreon_elements0.ExtendCss;
|
|
425
425
|
}> & _pyreon_core0.PyreonHTMLAttributes<HTMLElement>, {
|
|
426
|
-
width?: number | string;
|
|
427
426
|
caption?: string;
|
|
428
427
|
height?: number | string;
|
|
428
|
+
width?: number | string;
|
|
429
429
|
src?: string;
|
|
430
430
|
alt?: string;
|
|
431
431
|
}, {}, {}, {
|
|
@@ -694,10 +694,10 @@ declare const DocQuote: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
694
694
|
}> & _pyreon_core0.PyreonHTMLAttributes<HTMLElement>, {
|
|
695
695
|
borderColor?: string;
|
|
696
696
|
}, {}, {
|
|
697
|
-
padding: string;
|
|
698
697
|
borderColor: string;
|
|
699
698
|
color: string;
|
|
700
699
|
fontStyle: string;
|
|
700
|
+
padding: string;
|
|
701
701
|
}, {
|
|
702
702
|
_documentType: "quote";
|
|
703
703
|
}, {}, {
|
|
@@ -944,9 +944,9 @@ declare const DocText: _pyreon_rocketstyle0.RocketStyleComponent<Partial<{
|
|
|
944
944
|
tag: _pyreon_ui_core0.HTMLTextTags;
|
|
945
945
|
css: _pyreon_elements0.ExtendCss;
|
|
946
946
|
}> & _pyreon_core0.PyreonHTMLAttributes<HTMLElement>, {}, {}, {
|
|
947
|
+
marginBottom: number;
|
|
947
948
|
color: string;
|
|
948
949
|
lineHeight: number;
|
|
949
|
-
marginBottom: number;
|
|
950
950
|
}, {
|
|
951
951
|
_documentType: "text";
|
|
952
952
|
}, {}, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/document-primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Rocketstyle document components — render in browser, export to 18 formats",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,28 +41,29 @@
|
|
|
41
41
|
"typecheck": "tsc --noEmit"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@pyreon/connector-document": "^0.
|
|
44
|
+
"@pyreon/connector-document": "^0.14.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@pyreon/core": "^0.
|
|
48
|
-
"@pyreon/elements": "^0.
|
|
49
|
-
"@pyreon/
|
|
50
|
-
"@pyreon/
|
|
51
|
-
"@pyreon/
|
|
52
|
-
"@pyreon/
|
|
53
|
-
"@pyreon/
|
|
54
|
-
"@pyreon/
|
|
55
|
-
"@pyreon/
|
|
47
|
+
"@pyreon/core": "^0.14.0",
|
|
48
|
+
"@pyreon/elements": "^0.14.0",
|
|
49
|
+
"@pyreon/manifest": "0.13.1",
|
|
50
|
+
"@pyreon/reactivity": "^0.14.0",
|
|
51
|
+
"@pyreon/rocketstyle": "^0.14.0",
|
|
52
|
+
"@pyreon/runtime-dom": "^0.14.0",
|
|
53
|
+
"@pyreon/styler": "^0.14.0",
|
|
54
|
+
"@pyreon/test-utils": "^0.13.2",
|
|
55
|
+
"@pyreon/typescript": "^0.14.0",
|
|
56
|
+
"@pyreon/ui-core": "^0.14.0",
|
|
56
57
|
"@vitest/browser-playwright": "^4.1.4",
|
|
57
58
|
"@vitus-labs/tools-rolldown": "^1.15.4"
|
|
58
59
|
},
|
|
59
60
|
"peerDependencies": {
|
|
60
|
-
"@pyreon/core": "^0.
|
|
61
|
-
"@pyreon/document": "^0.
|
|
62
|
-
"@pyreon/elements": "^0.
|
|
63
|
-
"@pyreon/rocketstyle": "^0.
|
|
64
|
-
"@pyreon/styler": "^0.
|
|
65
|
-
"@pyreon/ui-core": "^0.
|
|
61
|
+
"@pyreon/core": "^0.14.0",
|
|
62
|
+
"@pyreon/document": "^0.14.0",
|
|
63
|
+
"@pyreon/elements": "^0.14.0",
|
|
64
|
+
"@pyreon/rocketstyle": "^0.14.0",
|
|
65
|
+
"@pyreon/styler": "^0.14.0",
|
|
66
|
+
"@pyreon/ui-core": "^0.14.0"
|
|
66
67
|
},
|
|
67
68
|
"engines": {
|
|
68
69
|
"node": ">= 22"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
renderApiReferenceEntries,
|
|
3
|
+
renderLlmsFullSection,
|
|
4
|
+
renderLlmsTxtLine,
|
|
5
|
+
} from '@pyreon/manifest'
|
|
6
|
+
import manifest from '../manifest'
|
|
7
|
+
|
|
8
|
+
describe('gen-docs — document-primitives snapshot', () => {
|
|
9
|
+
it('renders a llms.txt bullet starting with the package prefix', () => {
|
|
10
|
+
const line = renderLlmsTxtLine(manifest)
|
|
11
|
+
expect(line.startsWith('- @pyreon/document-primitives —')).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('renders a llms-full.txt section with the right header', () => {
|
|
15
|
+
const section = renderLlmsFullSection(manifest)
|
|
16
|
+
expect(section.startsWith('## @pyreon/document-primitives —')).toBe(true)
|
|
17
|
+
expect(section).toContain('```typescript')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('renders MCP api-reference entries for every api[] item', () => {
|
|
21
|
+
const record = renderApiReferenceEntries(manifest)
|
|
22
|
+
expect(Object.keys(record).sort()).toEqual([
|
|
23
|
+
'document-primitives/DocButton',
|
|
24
|
+
'document-primitives/DocCode',
|
|
25
|
+
'document-primitives/DocColumn',
|
|
26
|
+
'document-primitives/DocDivider',
|
|
27
|
+
'document-primitives/DocDocument',
|
|
28
|
+
'document-primitives/DocHeading',
|
|
29
|
+
'document-primitives/DocImage',
|
|
30
|
+
'document-primitives/DocLink',
|
|
31
|
+
'document-primitives/DocList',
|
|
32
|
+
'document-primitives/DocListItem',
|
|
33
|
+
'document-primitives/DocPage',
|
|
34
|
+
'document-primitives/DocPageBreak',
|
|
35
|
+
'document-primitives/DocQuote',
|
|
36
|
+
'document-primitives/DocRow',
|
|
37
|
+
'document-primitives/DocSection',
|
|
38
|
+
'document-primitives/DocSpacer',
|
|
39
|
+
'document-primitives/DocTable',
|
|
40
|
+
'document-primitives/DocText',
|
|
41
|
+
'document-primitives/createDocumentExport',
|
|
42
|
+
'document-primitives/extractDocNode',
|
|
43
|
+
])
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -14,7 +14,7 @@ describe('DocDocument attrs', () => {
|
|
|
14
14
|
it('sets tag to div', async () => {
|
|
15
15
|
const DocDocument = (await import('../primitives/DocDocument')).default
|
|
16
16
|
const result = renderProps(DocDocument, { children: 'test' })
|
|
17
|
-
expect(result.
|
|
17
|
+
expect(result.as).toBe('div')
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
it('passes title to _documentProps', async () => {
|
|
@@ -158,7 +158,7 @@ describe('DocTable attrs', () => {
|
|
|
158
158
|
it('sets tag to table', async () => {
|
|
159
159
|
const DocTable = (await import('../primitives/DocTable')).default
|
|
160
160
|
const result = renderProps(DocTable, { children: null })
|
|
161
|
-
expect(result.
|
|
161
|
+
expect(result.as).toBe('table')
|
|
162
162
|
})
|
|
163
163
|
|
|
164
164
|
it('defaults columns and rows to empty arrays', async () => {
|
|
@@ -195,13 +195,13 @@ describe('DocList attrs', () => {
|
|
|
195
195
|
it('sets tag to ul by default', async () => {
|
|
196
196
|
const DocList = (await import('../primitives/DocList')).default
|
|
197
197
|
const result = renderProps(DocList, { children: null })
|
|
198
|
-
expect(result.
|
|
198
|
+
expect(result.as).toBe('ul')
|
|
199
199
|
})
|
|
200
200
|
|
|
201
201
|
it('sets tag to ol when ordered is true', async () => {
|
|
202
202
|
const DocList = (await import('../primitives/DocList')).default
|
|
203
203
|
const result = renderProps(DocList, { ordered: true, children: null })
|
|
204
|
-
expect(result.
|
|
204
|
+
expect(result.as).toBe('ol')
|
|
205
205
|
expect(result._documentProps.ordered).toBe(true)
|
|
206
206
|
})
|
|
207
207
|
|
|
@@ -278,7 +278,7 @@ describe('DocPage attrs', () => {
|
|
|
278
278
|
it('sets tag to div', async () => {
|
|
279
279
|
const DocPage = (await import('../primitives/DocPage')).default
|
|
280
280
|
const result = renderProps(DocPage, { children: 'page' })
|
|
281
|
-
expect(result.
|
|
281
|
+
expect(result.as).toBe('div')
|
|
282
282
|
})
|
|
283
283
|
|
|
284
284
|
it('passes size and orientation when provided', async () => {
|
|
@@ -306,7 +306,7 @@ describe('DocPageBreak attrs', () => {
|
|
|
306
306
|
it('sets tag to div with empty _documentProps', async () => {
|
|
307
307
|
const DocPageBreak = (await import('../primitives/DocPageBreak')).default
|
|
308
308
|
const result = renderProps(DocPageBreak, { children: null })
|
|
309
|
-
expect(result.
|
|
309
|
+
expect(result.as).toBe('div')
|
|
310
310
|
expect(result._documentProps).toEqual({})
|
|
311
311
|
})
|
|
312
312
|
})
|
|
@@ -318,7 +318,7 @@ describe('DocQuote attrs', () => {
|
|
|
318
318
|
it('sets tag to blockquote', async () => {
|
|
319
319
|
const DocQuote = (await import('../primitives/DocQuote')).default
|
|
320
320
|
const result = renderProps(DocQuote, { children: 'quote' })
|
|
321
|
-
expect(result.
|
|
321
|
+
expect(result.as).toBe('blockquote')
|
|
322
322
|
})
|
|
323
323
|
|
|
324
324
|
it('passes borderColor when provided', async () => {
|
|
@@ -341,7 +341,7 @@ describe('DocRow attrs', () => {
|
|
|
341
341
|
it('sets tag to div with empty _documentProps', async () => {
|
|
342
342
|
const DocRow = (await import('../primitives/DocRow')).default
|
|
343
343
|
const result = renderProps(DocRow, { children: null })
|
|
344
|
-
expect(result.
|
|
344
|
+
expect(result.as).toBe('div')
|
|
345
345
|
expect(result._documentProps).toEqual({})
|
|
346
346
|
})
|
|
347
347
|
})
|
|
@@ -353,7 +353,7 @@ describe('DocColumn attrs', () => {
|
|
|
353
353
|
it('sets tag to div', async () => {
|
|
354
354
|
const DocColumn = (await import('../primitives/DocColumn')).default
|
|
355
355
|
const result = renderProps(DocColumn, { children: null })
|
|
356
|
-
expect(result.
|
|
356
|
+
expect(result.as).toBe('div')
|
|
357
357
|
})
|
|
358
358
|
|
|
359
359
|
it('passes width to _documentProps when provided', async () => {
|
|
@@ -376,7 +376,7 @@ describe('DocSpacer attrs', () => {
|
|
|
376
376
|
it('sets tag to div', async () => {
|
|
377
377
|
const DocSpacer = (await import('../primitives/DocSpacer')).default
|
|
378
378
|
const result = renderProps(DocSpacer, { children: null })
|
|
379
|
-
expect(result.
|
|
379
|
+
expect(result.as).toBe('div')
|
|
380
380
|
})
|
|
381
381
|
|
|
382
382
|
it('defaults height to 16', async () => {
|
|
@@ -399,7 +399,7 @@ describe('DocSection attrs', () => {
|
|
|
399
399
|
it('sets tag to div', async () => {
|
|
400
400
|
const DocSection = (await import('../primitives/DocSection')).default
|
|
401
401
|
const result = renderProps(DocSection, { children: null })
|
|
402
|
-
expect(result.
|
|
402
|
+
expect(result.as).toBe('div')
|
|
403
403
|
})
|
|
404
404
|
|
|
405
405
|
it('defaults direction to column', async () => {
|
|
@@ -445,7 +445,7 @@ describe('DocumentPreview attrs', () => {
|
|
|
445
445
|
it('sets tag to div', async () => {
|
|
446
446
|
const DocumentPreview = (await import('../DocumentPreview')).default
|
|
447
447
|
const result = renderProps(DocumentPreview, { children: null })
|
|
448
|
-
expect(result.
|
|
448
|
+
expect(result.as).toBe('div')
|
|
449
449
|
})
|
|
450
450
|
|
|
451
451
|
it('defaults size to A4 when not provided', async () => {
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import type { DocumentMarker } from '@pyreon/connector-document'
|
|
2
|
+
import { h } from '@pyreon/core'
|
|
2
3
|
import { describe, expect, it } from 'vitest'
|
|
3
4
|
import { createDocumentExport, extractDocNode } from '../useDocumentExport'
|
|
4
5
|
|
|
5
|
-
//
|
|
6
|
-
|
|
6
|
+
// Helper: build a real VNode via @pyreon/core's h(). The third arg
|
|
7
|
+
// is an array (kept for parity with the prior mock helper) and
|
|
8
|
+
// spreads into h()'s varargs. Tests run real-h() VNodes through
|
|
9
|
+
// the export pipeline — the mock-only path masked PR #197's silent
|
|
10
|
+
// metadata drop bug.
|
|
11
|
+
const node = (
|
|
7
12
|
type: string | ((...args: any[]) => any),
|
|
8
13
|
props: Record<string, any> = {},
|
|
9
14
|
children: unknown[] = [],
|
|
10
|
-
) => (
|
|
15
|
+
) => h(type as any, props, ...(children as any[])) as any
|
|
11
16
|
|
|
12
|
-
//
|
|
17
|
+
// Document-marked component
|
|
13
18
|
const docComponent = (docType: string) => {
|
|
14
|
-
const fn = (props: any) =>
|
|
19
|
+
const fn = (props: any) => node('div', props, props.children ? [props.children] : [])
|
|
15
20
|
;(fn as any)._documentType = docType
|
|
16
21
|
return fn as ((...args: any[]) => any) & DocumentMarker
|
|
17
22
|
}
|
|
@@ -23,8 +28,8 @@ const DocText = docComponent('text')
|
|
|
23
28
|
describe('createDocumentExport', () => {
|
|
24
29
|
it('extracts a document tree from template function', () => {
|
|
25
30
|
const doc = createDocumentExport(() =>
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
node(DocDocument, { _documentProps: { title: 'Test' } }, [
|
|
32
|
+
node(
|
|
28
33
|
DocHeading,
|
|
29
34
|
{
|
|
30
35
|
$rocketstyle: { fontSize: 24, fontWeight: 'bold' },
|
|
@@ -32,7 +37,7 @@ describe('createDocumentExport', () => {
|
|
|
32
37
|
},
|
|
33
38
|
['Hello'],
|
|
34
39
|
),
|
|
35
|
-
|
|
40
|
+
node(
|
|
36
41
|
DocText,
|
|
37
42
|
{
|
|
38
43
|
$rocketstyle: { fontSize: 14, color: '#333' },
|
|
@@ -62,7 +67,7 @@ describe('createDocumentExport', () => {
|
|
|
62
67
|
|
|
63
68
|
it('can be called multiple times', () => {
|
|
64
69
|
const doc = createDocumentExport(() =>
|
|
65
|
-
|
|
70
|
+
node(DocText, { $rocketstyle: { fontSize: 14 } }, ['Static']),
|
|
66
71
|
)
|
|
67
72
|
|
|
68
73
|
const tree1 = doc.getDocNode()
|
|
@@ -74,7 +79,7 @@ describe('createDocumentExport', () => {
|
|
|
74
79
|
|
|
75
80
|
it('respects includeStyles option', () => {
|
|
76
81
|
const doc = createDocumentExport(
|
|
77
|
-
() =>
|
|
82
|
+
() => node(DocHeading, { $rocketstyle: { fontSize: 24 } }, ['Hello']),
|
|
78
83
|
{ includeStyles: false },
|
|
79
84
|
)
|
|
80
85
|
|
|
@@ -99,8 +104,8 @@ describe('extractDocNode (one-step alias)', () => {
|
|
|
99
104
|
|
|
100
105
|
it('extracts a tree from a template function in one call', () => {
|
|
101
106
|
const tree = extractDocNode(() =>
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
node(DocDocument, { _documentProps: { title: 'Test' } }, [
|
|
108
|
+
node(DocHeading, { _documentProps: { level: 1 } }, ['Hello']),
|
|
104
109
|
]),
|
|
105
110
|
)
|
|
106
111
|
|
|
@@ -114,7 +119,7 @@ describe('extractDocNode (one-step alias)', () => {
|
|
|
114
119
|
|
|
115
120
|
it('respects extraction options (includeStyles: false)', () => {
|
|
116
121
|
const tree = extractDocNode(
|
|
117
|
-
() =>
|
|
122
|
+
() => node(DocHeading, { $rocketstyle: { fontSize: 24 } }, ['Hello']),
|
|
118
123
|
{ includeStyles: false },
|
|
119
124
|
)
|
|
120
125
|
expect(tree.styles).toBeUndefined()
|
|
@@ -125,7 +130,7 @@ describe('extractDocNode (one-step alias)', () => {
|
|
|
125
130
|
// one-step form internally, so they MUST produce identical
|
|
126
131
|
// output for identical input.
|
|
127
132
|
const template = () =>
|
|
128
|
-
|
|
133
|
+
node(DocText, { $rocketstyle: { fontSize: 14 } }, ['Hello'])
|
|
129
134
|
|
|
130
135
|
const oneStep = extractDocNode(template)
|
|
131
136
|
const twoStep = createDocumentExport(template).getDocNode()
|
|
@@ -150,12 +155,12 @@ describe('extractDocNode (one-step alias)', () => {
|
|
|
150
155
|
// catch the regression. The three extractions should be deeply
|
|
151
156
|
// equal under any change to the primitive's setup path.
|
|
152
157
|
const template = () =>
|
|
153
|
-
|
|
158
|
+
node(
|
|
154
159
|
DocDocument,
|
|
155
160
|
{ _documentProps: { title: 'Idempotent', author: 'Test' } },
|
|
156
161
|
[
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
node(DocHeading, { _documentProps: { level: 1 } }, ['Hello']),
|
|
163
|
+
node(DocText, { $rocketstyle: { fontSize: 14 } }, ['World']),
|
|
159
164
|
],
|
|
160
165
|
)
|
|
161
166
|
|
|
@@ -174,7 +179,7 @@ describe('extractDocNode (one-step alias)', () => {
|
|
|
174
179
|
// value of any accessor in _documentProps.
|
|
175
180
|
let counter = 0
|
|
176
181
|
const template = () =>
|
|
177
|
-
|
|
182
|
+
node(
|
|
178
183
|
DocDocument,
|
|
179
184
|
{
|
|
180
185
|
_documentProps: {
|
|
@@ -312,4 +317,50 @@ describe('DocDocument reactive metadata (D1 integration)', () => {
|
|
|
312
317
|
cleanup()
|
|
313
318
|
}
|
|
314
319
|
})
|
|
320
|
+
|
|
321
|
+
it('extractDocNode does NOT invoke a real DocDocument component (T3.1 Path C)', { timeout: 30_000 }, async () => {
|
|
322
|
+
// The architectural invariant locked in by T3.1 (PR #321):
|
|
323
|
+
// `extractDocumentTree` consumes a real rocketstyle primitive's
|
|
324
|
+
// `__rs_attrs` chain directly, never invoking the wrapped component
|
|
325
|
+
// function. The previous Path B workaround had to call the full
|
|
326
|
+
// styled wrapper per export to read post-attrs `_documentProps`.
|
|
327
|
+
//
|
|
328
|
+
// Spy mechanism: wrap the imported component in a Proxy whose
|
|
329
|
+
// `apply` trap counts function-call invocations. Property reads
|
|
330
|
+
// (IS_ROCKETSTYLE, __rs_attrs, _documentType, .meta, .displayName,
|
|
331
|
+
// etc.) flow through to the original via the default `get` handler,
|
|
332
|
+
// so extractDocumentTree's contract still works — but any code
|
|
333
|
+
// path that CALLS the spied component bumps the counter.
|
|
334
|
+
//
|
|
335
|
+
// The connector-document tests already pin this with a
|
|
336
|
+
// `FakeRocketDoc` fixture; this test pins it for a real
|
|
337
|
+
// rocketstyle primitive end-to-end, closing the abstract /
|
|
338
|
+
// concrete coverage gap.
|
|
339
|
+
const { initTestConfig } = await import('@pyreon/test-utils')
|
|
340
|
+
const { h } = await import('@pyreon/core')
|
|
341
|
+
const cleanup = initTestConfig()
|
|
342
|
+
try {
|
|
343
|
+
const RealDocDocument = (await import('../primitives/DocDocument')).default
|
|
344
|
+
|
|
345
|
+
let callCount = 0
|
|
346
|
+
const SpiedDoc = new Proxy(RealDocDocument, {
|
|
347
|
+
apply(target, thisArg, args) {
|
|
348
|
+
callCount++
|
|
349
|
+
return Reflect.apply(target as (...a: unknown[]) => unknown, thisArg, args as unknown[])
|
|
350
|
+
},
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
const tree = extractDocNode(() =>
|
|
354
|
+
h(SpiedDoc as never, { title: 'Hoisted', author: 'Aisha' } as never),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
expect(tree.type).toBe('document')
|
|
358
|
+
expect(tree.props.title).toBe('Hoisted')
|
|
359
|
+
expect(tree.props.author).toBe('Aisha')
|
|
360
|
+
// The architectural assertion — the styled wrapper must NOT run.
|
|
361
|
+
expect(callCount).toBe(0)
|
|
362
|
+
} finally {
|
|
363
|
+
cleanup()
|
|
364
|
+
}
|
|
365
|
+
})
|
|
315
366
|
})
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { defineManifest } from '@pyreon/manifest'
|
|
2
|
+
|
|
3
|
+
export default defineManifest({
|
|
4
|
+
name: '@pyreon/document-primitives',
|
|
5
|
+
title: 'Document Primitives',
|
|
6
|
+
tagline:
|
|
7
|
+
'18 rocketstyle document components — render in browser AND export to 14+ formats via the same tree',
|
|
8
|
+
description:
|
|
9
|
+
'18 rocketstyle-based document primitives — `DocDocument`, `DocPage`, `DocSection`, `DocRow`, `DocColumn`, `DocHeading`, `DocText`, `DocLink`, `DocImage`, `DocTable`, `DocList`, `DocListItem`, `DocCode`, `DocDivider`, `DocSpacer`, `DocButton`, `DocQuote`, `DocPageBreak`. The same JSX tree renders in the browser AND exports to 14+ output formats (PDF, DOCX, XLSX, PPTX, HTML, Markdown, email, Slack, Teams, etc.). Primitives carry `_documentType` static markers; `extractDocumentTree` (from `@pyreon/connector-document`) walks the tree to produce a `DocNode` for `@pyreon/document`\\\'s `render()` to consume. `DocDocument` accepts reactive accessors for `title` / `author` / `subject` — function values are stored in `_documentProps` and resolved at extraction time so each export click reads the LIVE value from the underlying signal.',
|
|
10
|
+
category: 'browser',
|
|
11
|
+
features: [
|
|
12
|
+
'18 primitives covering structure, text, lists, tables, code, layout',
|
|
13
|
+
'Same component tree renders in browser AND exports to 14+ formats',
|
|
14
|
+
'extractDocNode(templateFn) — one-step extraction (recommended)',
|
|
15
|
+
'createDocumentExport(templateFn) — two-step form (backward compat)',
|
|
16
|
+
'DocDocument accepts reactive accessors for title / author / subject',
|
|
17
|
+
'PR #197 fix: extractDocumentTree now calls rocketstyle components to read post-attrs metadata',
|
|
18
|
+
'Layout props in .attrs() (direction / gap), CSS in .theme()',
|
|
19
|
+
],
|
|
20
|
+
longExample: `import {
|
|
21
|
+
DocDocument, DocPage, DocSection, DocRow, DocColumn,
|
|
22
|
+
DocHeading, DocText, DocLink, DocImage, DocTable,
|
|
23
|
+
DocList, DocListItem, DocCode, DocDivider, DocSpacer,
|
|
24
|
+
DocButton, DocQuote, DocPageBreak,
|
|
25
|
+
extractDocNode,
|
|
26
|
+
} from '@pyreon/document-primitives'
|
|
27
|
+
import { download } from '@pyreon/document'
|
|
28
|
+
|
|
29
|
+
interface Resume { name: string; headline: string }
|
|
30
|
+
|
|
31
|
+
function ResumeTemplate(props: { resume: () => Resume }) {
|
|
32
|
+
return (
|
|
33
|
+
// title and author accept reactive accessors — extractDocNode
|
|
34
|
+
// resolves them at extraction time, so each export click reads
|
|
35
|
+
// the LIVE value from the underlying signal
|
|
36
|
+
<DocDocument
|
|
37
|
+
title={() => \`\${props.resume().name} — Resume\`}
|
|
38
|
+
author={() => props.resume().name}
|
|
39
|
+
>
|
|
40
|
+
<DocPage>
|
|
41
|
+
<DocSection>
|
|
42
|
+
<DocHeading level="h1">{() => props.resume().name}</DocHeading>
|
|
43
|
+
<DocText>{() => props.resume().headline}</DocText>
|
|
44
|
+
</DocSection>
|
|
45
|
+
</DocPage>
|
|
46
|
+
</DocDocument>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// One-step extraction → render to any of 14+ formats
|
|
51
|
+
const tree = extractDocNode(() => <ResumeTemplate resume={store.resume} />)
|
|
52
|
+
await download(tree, 'resume.pdf')
|
|
53
|
+
await download(tree, 'resume.docx')
|
|
54
|
+
await download(tree, 'resume.html')
|
|
55
|
+
await download(tree, 'resume.md')`,
|
|
56
|
+
api: [
|
|
57
|
+
{
|
|
58
|
+
name: 'extractDocNode',
|
|
59
|
+
kind: 'function',
|
|
60
|
+
signature: 'extractDocNode(templateFn: () => VNode, options?: ExtractOptions): DocNode',
|
|
61
|
+
summary:
|
|
62
|
+
"18 primitives: `DocDocument`, `DocPage`, `DocSection`, `DocRow`, `DocColumn`, `DocHeading`, `DocText`, `DocLink`, `DocImage`, `DocTable`, `DocList`, `DocListItem`, `DocCode`, `DocDivider`, `DocSpacer`, `DocButton`, `DocQuote`, `DocPageBreak`. Same component tree renders in browser AND exports — primitives carry `_documentType` statics that `extractDocumentTree` (from `@pyreon/connector-document`) walks to produce a `DocNode` for `@pyreon/document`\\\'s `render()` to consume. `DocDocument`\\\'s `title` / `author` / `subject` accept either a string OR a `() => string` accessor; function values are stored in `_documentProps` and resolved at extraction time so reactive metadata works without `const initial = get()` workarounds. PR #197 also fixed a latent bug in `extractDocumentTree`: it now CALLS rocketstyle component functions to read post-attrs `_documentProps`, where before it only looked at the JSX vnode\\\'s props directly — every primitive\\\'s metadata was silently dropped during export until that fix landed.",
|
|
63
|
+
example: `import {
|
|
64
|
+
DocDocument, DocPage, DocHeading, DocText,
|
|
65
|
+
extractDocNode,
|
|
66
|
+
} from '@pyreon/document-primitives'
|
|
67
|
+
import { download } from '@pyreon/document'
|
|
68
|
+
|
|
69
|
+
const tree = extractDocNode(() => (
|
|
70
|
+
<DocDocument title="Quarterly Report" author="Aisha">
|
|
71
|
+
<DocPage>
|
|
72
|
+
<DocHeading level="h1">Q4 Results</DocHeading>
|
|
73
|
+
<DocText>Revenue grew 23% YoY.</DocText>
|
|
74
|
+
</DocPage>
|
|
75
|
+
</DocDocument>
|
|
76
|
+
))
|
|
77
|
+
await download(tree, 'report.pdf')
|
|
78
|
+
await download(tree, 'report.docx')`,
|
|
79
|
+
mistakes: [
|
|
80
|
+
'Calling `props.title()` at the top of a template body to "fix" reactivity — components run ONCE at mount, so this captures the initial value forever. Pass the accessor through to DocDocument as-is: `<DocDocument title={() => get().name}>`',
|
|
81
|
+
"DocRow direction: layout props (direction, gap) go in `.attrs()` not `.theme()`. Element accepts `'inline'` | `'rows'` | `'reverseInline'` | `'reverseRows'` — `'row'` is NOT valid",
|
|
82
|
+
"For text children reactivity, pass a signal accessor and read inside body: `<DocText>{() => store.field()}</DocText>`",
|
|
83
|
+
"Don't declare runtime-filled fields (`tag`, `_documentProps`) in the rocketstyle `.attrs<P>()` generic — they leak as required JSX props",
|
|
84
|
+
'Using `createDocumentExport(...).getDocNode()` in new code — prefer `extractDocNode(fn)` which is one call instead of two. `createDocumentExport` is kept for backward compat',
|
|
85
|
+
],
|
|
86
|
+
seeAlso: ['createDocumentExport'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'createDocumentExport',
|
|
90
|
+
kind: 'function',
|
|
91
|
+
signature:
|
|
92
|
+
'createDocumentExport(templateFn: () => VNode): { getDocNode(): DocNode }',
|
|
93
|
+
summary:
|
|
94
|
+
'Wrapper around `extractDocNode`. The wrapper-object form is kept for callers that want to pass the helper around (e.g. to wrapper components that take a `DocumentExport` instance). New code should use `extractDocNode(templateFn)` which is one call instead of two.',
|
|
95
|
+
example: `// Two-step form (kept for backward compat). New code should
|
|
96
|
+
// prefer the one-step extractDocNode helper.
|
|
97
|
+
import { createDocumentExport } from '@pyreon/document-primitives'
|
|
98
|
+
|
|
99
|
+
const helper = createDocumentExport(() => <Resume name="Aisha" />)
|
|
100
|
+
const tree = helper.getDocNode()`,
|
|
101
|
+
seeAlso: ['extractDocNode'],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'DocDocument',
|
|
105
|
+
kind: 'component',
|
|
106
|
+
signature:
|
|
107
|
+
'(props: { title?: string | (() => string); author?: string | (() => string); subject?: string | (() => string); children: VNodeChild }) => VNodeChild',
|
|
108
|
+
summary:
|
|
109
|
+
'Root container for a document tree — produces a `_documentType: "document"` node. Accepts optional metadata: `title`, `author`, `subject`. Each accepts either a plain string OR a `() => string` accessor; function values are stored in `_documentProps` and resolved at extraction time so each export call reads the LIVE value from any underlying signal.',
|
|
110
|
+
example: `<DocDocument title="Quarterly Report" author="Aisha" subject="Q4 2025">
|
|
111
|
+
<DocPage>...</DocPage>
|
|
112
|
+
</DocDocument>
|
|
113
|
+
|
|
114
|
+
// Reactive metadata via accessor
|
|
115
|
+
<DocDocument title={() => \`\${user().name} — Resume\`}>
|
|
116
|
+
<DocPage>...</DocPage>
|
|
117
|
+
</DocDocument>`,
|
|
118
|
+
seeAlso: ['DocPage', 'extractDocNode'],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'DocPage',
|
|
122
|
+
kind: 'component',
|
|
123
|
+
signature:
|
|
124
|
+
"(props: { size?: string; orientation?: 'portrait' | 'landscape'; children: VNodeChild }) => VNodeChild",
|
|
125
|
+
summary:
|
|
126
|
+
'A page boundary inside a `DocDocument`. Paginated outputs (PDF, DOCX) treat each `DocPage` as a separate page; flow outputs (HTML, Markdown) render the contents inline with no page boundary. `size` and `orientation` configure paginated formats — common values: `"A4"`, `"Letter"`, `"Legal"`.',
|
|
127
|
+
example: `<DocDocument>
|
|
128
|
+
<DocPage size="A4" orientation="portrait">
|
|
129
|
+
<DocHeading level="h1">Page 1</DocHeading>
|
|
130
|
+
</DocPage>
|
|
131
|
+
<DocPage size="A4" orientation="landscape">
|
|
132
|
+
<DocHeading level="h1">Page 2 — landscape</DocHeading>
|
|
133
|
+
</DocPage>
|
|
134
|
+
</DocDocument>`,
|
|
135
|
+
seeAlso: ['DocDocument', 'DocPageBreak'],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'DocSection',
|
|
139
|
+
kind: 'component',
|
|
140
|
+
signature:
|
|
141
|
+
"(props: { direction?: 'column' | 'row'; children: VNodeChild }) => VNodeChild",
|
|
142
|
+
summary:
|
|
143
|
+
'Semantic grouping inside a page. Default `direction` is `"column"` (children stack vertically); `"row"` arranges them horizontally. Use to group related content for visual rhythm and for export targets that emit semantic section markers (HTML `<section>`, DOCX section breaks).',
|
|
144
|
+
example: `<DocPage>
|
|
145
|
+
<DocSection direction="column">
|
|
146
|
+
<DocHeading level="h2">Introduction</DocHeading>
|
|
147
|
+
<DocText>Background paragraph.</DocText>
|
|
148
|
+
</DocSection>
|
|
149
|
+
</DocPage>`,
|
|
150
|
+
seeAlso: ['DocRow', 'DocColumn'],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'DocRow',
|
|
154
|
+
kind: 'component',
|
|
155
|
+
signature: '(props: { children: VNodeChild }) => VNodeChild',
|
|
156
|
+
summary:
|
|
157
|
+
'Horizontal layout container — children flow inline with a fixed 8px gap. Use for side-by-side content (label + value pairs, columns of metadata, button rows). Layout-only — no user-configurable props on this primitive; for columns with custom widths use `DocColumn` inside.',
|
|
158
|
+
example: `<DocRow>
|
|
159
|
+
<DocText>Name:</DocText>
|
|
160
|
+
<DocText>Aisha Patel</DocText>
|
|
161
|
+
</DocRow>`,
|
|
162
|
+
seeAlso: ['DocColumn', 'DocSection'],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'DocColumn',
|
|
166
|
+
kind: 'component',
|
|
167
|
+
signature:
|
|
168
|
+
'(props: { width?: number | string; children: VNodeChild }) => VNodeChild',
|
|
169
|
+
summary:
|
|
170
|
+
'A column inside a row layout. Optional `width` controls the column\\\'s share of the row — accepts a number (interpreted as pixels) or a string (`"50%"`, `"1fr"`). When omitted, columns share available width equally. Most common shape is `<DocRow><DocColumn width="30%" /> <DocColumn width="70%" /></DocRow>`.',
|
|
171
|
+
example: `<DocRow>
|
|
172
|
+
<DocColumn width="30%">
|
|
173
|
+
<DocText>Label</DocText>
|
|
174
|
+
</DocColumn>
|
|
175
|
+
<DocColumn width="70%">
|
|
176
|
+
<DocText>Value</DocText>
|
|
177
|
+
</DocColumn>
|
|
178
|
+
</DocRow>`,
|
|
179
|
+
seeAlso: ['DocRow', 'DocSection'],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'DocHeading',
|
|
183
|
+
kind: 'component',
|
|
184
|
+
signature:
|
|
185
|
+
"(props: { level?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; children: VNodeChild }) => VNodeChild",
|
|
186
|
+
summary:
|
|
187
|
+
'Heading text — `level` (`"h1"` through `"h6"`) controls both visual size and the semantic level emitted to outputs (HTML `<h1>...<h6>`, DOCX heading styles, Markdown `#`...`######`). Default `level` is `"h1"`. Used for document structure that downstream tooling can build a TOC from.',
|
|
188
|
+
example: `<DocHeading level="h1">Quarterly Report</DocHeading>
|
|
189
|
+
<DocHeading level="h2">Q4 Results</DocHeading>
|
|
190
|
+
<DocHeading level="h3">Revenue Breakdown</DocHeading>`,
|
|
191
|
+
seeAlso: ['DocText', 'DocSection'],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'DocText',
|
|
195
|
+
kind: 'component',
|
|
196
|
+
signature: '(props: { children: VNodeChild }) => VNodeChild',
|
|
197
|
+
summary:
|
|
198
|
+
'Paragraph / inline text. The most common primitive — wraps any text content for the document. Children may be string literals OR signal accessors (`{() => store.field()}`) for reactive content. Visual styling (font weight, variant) is controlled via rocketstyle dimension props on the wrapping component definition.',
|
|
199
|
+
example: `<DocText>Static paragraph content.</DocText>
|
|
200
|
+
|
|
201
|
+
// Reactive children
|
|
202
|
+
<DocText>{() => \`Hello, \${user().name}\`}</DocText>`,
|
|
203
|
+
seeAlso: ['DocHeading', 'DocLink'],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'DocLink',
|
|
207
|
+
kind: 'component',
|
|
208
|
+
signature: '(props: { href?: string; children: VNodeChild }) => VNodeChild',
|
|
209
|
+
summary:
|
|
210
|
+
'Hyperlink within text. `href` is the URL — defaults to `"#"`. Outputs that support hyperlinks (HTML, PDF, DOCX, email) render this as a clickable link; flat outputs (plain text, certain Slack variants) render the link target inline as `text (href)`.',
|
|
211
|
+
example: `<DocText>
|
|
212
|
+
Read more on
|
|
213
|
+
<DocLink href="https://pyreon.dev">our blog</DocLink>
|
|
214
|
+
for the latest releases.
|
|
215
|
+
</DocText>`,
|
|
216
|
+
seeAlso: ['DocText'],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: 'DocImage',
|
|
220
|
+
kind: 'component',
|
|
221
|
+
signature:
|
|
222
|
+
'(props: { src: string; alt?: string; width?: number; height?: number; caption?: string }) => VNodeChild',
|
|
223
|
+
summary:
|
|
224
|
+
'An image embedded in the document. `src` is the image URL or data URI. `alt` is the accessible description (also used as fallback text in non-visual outputs). `width` / `height` constrain dimensions in pixels. Optional `caption` renders a caption beneath the image.',
|
|
225
|
+
example: `<DocImage
|
|
226
|
+
src="/charts/q4-revenue.png"
|
|
227
|
+
alt="Revenue grew 23% in Q4"
|
|
228
|
+
width={600}
|
|
229
|
+
height={400}
|
|
230
|
+
caption="Figure 1: Quarterly revenue, 2024-2025"
|
|
231
|
+
/>`,
|
|
232
|
+
seeAlso: ['DocCode'],
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'DocTable',
|
|
236
|
+
kind: 'component',
|
|
237
|
+
signature:
|
|
238
|
+
'(props: { columns: TableColumn[]; rows: TableRow[]; headerStyle?: object; striped?: boolean; bordered?: boolean; caption?: string }) => VNodeChild',
|
|
239
|
+
summary:
|
|
240
|
+
'Tabular data. `columns` defines the header cells (label, key, optional alignment). `rows` is an array of data rows keyed by column key. `striped` adds alternating row backgrounds; `bordered` adds cell borders; `caption` renders an accessible table caption. Both `rows` and `columns` are filtered before reaching the DOM via `.attrs(..., { filter: [...] })` because `HTMLTableElement.rows` / `.cells` are read-only DOM properties — assignment would crash.',
|
|
241
|
+
example: `<DocTable
|
|
242
|
+
caption="Q4 results by region"
|
|
243
|
+
bordered
|
|
244
|
+
striped
|
|
245
|
+
columns={[
|
|
246
|
+
{ key: 'region', label: 'Region', align: 'left' },
|
|
247
|
+
{ key: 'revenue', label: 'Revenue', align: 'right' },
|
|
248
|
+
{ key: 'growth', label: 'YoY Growth', align: 'right' },
|
|
249
|
+
]}
|
|
250
|
+
rows={[
|
|
251
|
+
{ region: 'NA', revenue: '$12.4M', growth: '+23%' },
|
|
252
|
+
{ region: 'EU', revenue: '$8.7M', growth: '+18%' },
|
|
253
|
+
{ region: 'APAC', revenue: '$5.1M', growth: '+41%' },
|
|
254
|
+
]}
|
|
255
|
+
/>`,
|
|
256
|
+
seeAlso: ['DocList', 'DocSection'],
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'DocList',
|
|
260
|
+
kind: 'component',
|
|
261
|
+
signature: '(props: { ordered?: boolean; children: VNodeChild }) => VNodeChild',
|
|
262
|
+
summary:
|
|
263
|
+
'Bulleted (default) or numbered (`ordered`) list. Children are typically `DocListItem` instances. Outputs map this to the right native list type — HTML `<ul>` / `<ol>`, Markdown `-` / `1.`, DOCX list styles.',
|
|
264
|
+
example: `<DocList>
|
|
265
|
+
<DocListItem>First bullet</DocListItem>
|
|
266
|
+
<DocListItem>Second bullet</DocListItem>
|
|
267
|
+
</DocList>
|
|
268
|
+
|
|
269
|
+
<DocList ordered>
|
|
270
|
+
<DocListItem>First step</DocListItem>
|
|
271
|
+
<DocListItem>Second step</DocListItem>
|
|
272
|
+
</DocList>`,
|
|
273
|
+
seeAlso: ['DocListItem'],
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'DocListItem',
|
|
277
|
+
kind: 'component',
|
|
278
|
+
signature: '(props: { children: VNodeChild }) => VNodeChild',
|
|
279
|
+
summary:
|
|
280
|
+
'Single item inside a `DocList`. Children may be plain text, `DocText`, nested `DocList` for sublists, or any other inline primitive. Visual marker (bullet vs number) is decided by the parent list\\\'s `ordered` prop, not by the item.',
|
|
281
|
+
example: `<DocList>
|
|
282
|
+
<DocListItem>Top-level item</DocListItem>
|
|
283
|
+
<DocListItem>
|
|
284
|
+
Item with nested list
|
|
285
|
+
<DocList>
|
|
286
|
+
<DocListItem>Nested A</DocListItem>
|
|
287
|
+
<DocListItem>Nested B</DocListItem>
|
|
288
|
+
</DocList>
|
|
289
|
+
</DocListItem>
|
|
290
|
+
</DocList>`,
|
|
291
|
+
seeAlso: ['DocList'],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'DocCode',
|
|
295
|
+
kind: 'component',
|
|
296
|
+
signature: '(props: { language?: string; children: VNodeChild }) => VNodeChild',
|
|
297
|
+
summary:
|
|
298
|
+
'Monospace code block. Optional `language` hint enables syntax highlighting in outputs that support it (HTML via Prism / Shiki, Markdown fenced code blocks with language tag). Whitespace is preserved verbatim — pass code as a single string child to keep newlines.',
|
|
299
|
+
example: `<DocCode language="typescript">{
|
|
300
|
+
\`const flow = createFlow({
|
|
301
|
+
nodes: [{ id: '1', position: { x: 0, y: 0 } }],
|
|
302
|
+
edges: [],
|
|
303
|
+
})\`
|
|
304
|
+
}</DocCode>`,
|
|
305
|
+
seeAlso: ['DocText'],
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'DocDivider',
|
|
309
|
+
kind: 'component',
|
|
310
|
+
signature: '(props: { color?: string; thickness?: number }) => VNodeChild',
|
|
311
|
+
summary:
|
|
312
|
+
'Horizontal rule — visual section separator. `color` controls the line color (any CSS color string); `thickness` controls the line thickness in pixels. Outputs map this to native dividers — HTML `<hr>`, Markdown `---`, DOCX horizontal rule.',
|
|
313
|
+
example: `<DocText>Above the divider.</DocText>
|
|
314
|
+
<DocDivider color="#e5e7eb" thickness={1} />
|
|
315
|
+
<DocText>Below the divider.</DocText>`,
|
|
316
|
+
seeAlso: ['DocSpacer'],
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: 'DocSpacer',
|
|
320
|
+
kind: 'component',
|
|
321
|
+
signature: '(props: { height?: number }) => VNodeChild',
|
|
322
|
+
summary:
|
|
323
|
+
'Vertical whitespace — adds a blank vertical gap. `height` is in pixels (default 16). Use to space out content beyond what `DocSection` / `DocPage` margins provide. In flow outputs this becomes a styled blank block; in plain-text outputs, a sequence of newlines.',
|
|
324
|
+
example: `<DocSection>
|
|
325
|
+
<DocHeading level="h2">Section A</DocHeading>
|
|
326
|
+
<DocText>Content...</DocText>
|
|
327
|
+
<DocSpacer height={32} />
|
|
328
|
+
<DocHeading level="h2">Section B</DocHeading>
|
|
329
|
+
<DocText>More content...</DocText>
|
|
330
|
+
</DocSection>`,
|
|
331
|
+
seeAlso: ['DocDivider'],
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: 'DocButton',
|
|
335
|
+
kind: 'component',
|
|
336
|
+
signature: '(props: { href?: string; children: VNodeChild }) => VNodeChild',
|
|
337
|
+
summary:
|
|
338
|
+
'Call-to-action button. Renders as a styled clickable element in HTML / email outputs (mail-safe button table layout for email), and as a labeled link in PDF / DOCX. `href` is the action URL — defaults to `"#"`. Visual style (variant) is controlled via rocketstyle dimensions on the component definition.',
|
|
339
|
+
example: `<DocButton href="https://pyreon.dev/signup">
|
|
340
|
+
Get started
|
|
341
|
+
</DocButton>`,
|
|
342
|
+
seeAlso: ['DocLink'],
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: 'DocQuote',
|
|
346
|
+
kind: 'component',
|
|
347
|
+
signature: '(props: { borderColor?: string; children: VNodeChild }) => VNodeChild',
|
|
348
|
+
summary:
|
|
349
|
+
'Block quote — sets off a quoted passage with an indented left border. `borderColor` controls the indicator stripe (any CSS color). Outputs map this to native quote styling — HTML `<blockquote>`, Markdown `> ...`, DOCX quote style.',
|
|
350
|
+
example: `<DocQuote borderColor="#3b82f6">
|
|
351
|
+
<DocText>"The best way to predict the future is to build it."</DocText>
|
|
352
|
+
<DocText>— Aisha Patel, Q4 keynote</DocText>
|
|
353
|
+
</DocQuote>`,
|
|
354
|
+
seeAlso: ['DocText'],
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'DocPageBreak',
|
|
358
|
+
kind: 'component',
|
|
359
|
+
signature: '() => VNodeChild',
|
|
360
|
+
summary:
|
|
361
|
+
'Explicit page boundary inside a `DocPage`. Forces the renderer to start a new page at this point in paginated outputs (PDF, DOCX). In flow outputs (HTML, Markdown), it renders as visible whitespace or is omitted entirely. Use for explicit pagination control beyond what `DocPage` boundaries already provide.',
|
|
362
|
+
example: `<DocPage>
|
|
363
|
+
<DocHeading level="h1">Section 1</DocHeading>
|
|
364
|
+
<DocText>...long content...</DocText>
|
|
365
|
+
<DocPageBreak />
|
|
366
|
+
<DocHeading level="h1">Section 2 — new page</DocHeading>
|
|
367
|
+
</DocPage>`,
|
|
368
|
+
seeAlso: ['DocPage'],
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
gotchas: [
|
|
372
|
+
{
|
|
373
|
+
label: 'Reactive metadata',
|
|
374
|
+
note:
|
|
375
|
+
'`DocDocument` `title` / `author` / `subject` accept either strings or `() => string` accessors. Function values are stored in `_documentProps` and resolved by `extractDocumentTree` at extraction time, so each export click reads the LIVE value from any underlying signal — no `const initial = get()` workaround needed.',
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
label: 'PR #197 framework fix',
|
|
379
|
+
note:
|
|
380
|
+
'Before PR #197, `extractDocumentTree` only looked at the JSX vnode\\\'s direct props for `_documentProps` — but rocketstyle\\\'s attrs HOC stamps that field AFTER the component runs, so every real primitive\\\'s metadata was silently dropped during export. The extractor now CALLS the component function to capture the post-attrs vnode and reads `_documentProps` from there.',
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
label: 'DocTable read-only DOM property collision',
|
|
384
|
+
note:
|
|
385
|
+
'`HTMLTableElement.rows` and `.cells` are read-only DOM properties — assigning to them throws. `DocTable` uses `.attrs(callback, { filter: ["rows", "columns", ...] })` to strip these props before they reach the DOM. Watch for similar collisions when adding new primitives that accept prop names matching native HTML element properties.',
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
})
|