@pyreon/document-primitives 0.24.5 → 0.25.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 +7 -7
- package/package.json +18 -20
- package/src/DocumentPreview.ts +0 -48
- package/src/__tests__/document-primitives.browser.test.ts +0 -253
- package/src/__tests__/manifest-snapshot.test.ts +0 -45
- package/src/__tests__/primitives-attrs.test.ts +0 -517
- package/src/__tests__/primitives.test.ts +0 -104
- package/src/__tests__/reactivity.test.ts +0 -415
- package/src/__tests__/useDocumentExport.test.ts +0 -366
- package/src/index.ts +0 -37
- package/src/manifest.ts +0 -388
- package/src/primitives/DocButton.ts +0 -37
- package/src/primitives/DocCode.ts +0 -18
- package/src/primitives/DocColumn.ts +0 -11
- package/src/primitives/DocDivider.ts +0 -21
- package/src/primitives/DocDocument.ts +0 -69
- package/src/primitives/DocHeading.ts +0 -33
- package/src/primitives/DocImage.ts +0 -23
- package/src/primitives/DocLink.ts +0 -15
- package/src/primitives/DocList.ts +0 -15
- package/src/primitives/DocListItem.ts +0 -15
- package/src/primitives/DocPage.ts +0 -21
- package/src/primitives/DocPageBreak.ts +0 -11
- package/src/primitives/DocQuote.ts +0 -17
- package/src/primitives/DocRow.ts +0 -19
- package/src/primitives/DocSection.ts +0 -23
- package/src/primitives/DocSpacer.ts +0 -11
- package/src/primitives/DocTable.ts +0 -57
- package/src/primitives/DocText.ts +0 -31
- package/src/theme.ts +0 -37
- package/src/useDocumentExport.ts +0 -84
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
import type { DocumentMarker } from '@pyreon/connector-document'
|
|
2
|
-
import { h } from '@pyreon/core'
|
|
3
|
-
import { describe, expect, it } from 'vitest'
|
|
4
|
-
import { createDocumentExport, extractDocNode } from '../useDocumentExport'
|
|
5
|
-
|
|
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 = (
|
|
12
|
-
type: string | ((...args: any[]) => any),
|
|
13
|
-
props: Record<string, any> = {},
|
|
14
|
-
children: unknown[] = [],
|
|
15
|
-
) => h(type as any, props, ...(children as any[])) as any
|
|
16
|
-
|
|
17
|
-
// Document-marked component
|
|
18
|
-
const docComponent = (docType: string) => {
|
|
19
|
-
const fn = (props: any) => node('div', props, props.children ? [props.children] : [])
|
|
20
|
-
;(fn as any)._documentType = docType
|
|
21
|
-
return fn as ((...args: any[]) => any) & DocumentMarker
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const DocDocument = docComponent('document')
|
|
25
|
-
const DocHeading = docComponent('heading')
|
|
26
|
-
const DocText = docComponent('text')
|
|
27
|
-
|
|
28
|
-
describe('createDocumentExport', () => {
|
|
29
|
-
it('extracts a document tree from template function', () => {
|
|
30
|
-
const doc = createDocumentExport(() =>
|
|
31
|
-
node(DocDocument, { _documentProps: { title: 'Test' } }, [
|
|
32
|
-
node(
|
|
33
|
-
DocHeading,
|
|
34
|
-
{
|
|
35
|
-
$rocketstyle: { fontSize: 24, fontWeight: 'bold' },
|
|
36
|
-
_documentProps: { level: 1 },
|
|
37
|
-
},
|
|
38
|
-
['Hello'],
|
|
39
|
-
),
|
|
40
|
-
node(
|
|
41
|
-
DocText,
|
|
42
|
-
{
|
|
43
|
-
$rocketstyle: { fontSize: 14, color: '#333' },
|
|
44
|
-
},
|
|
45
|
-
['World'],
|
|
46
|
-
),
|
|
47
|
-
]),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
const tree = doc.getDocNode()
|
|
51
|
-
|
|
52
|
-
expect(tree.type).toBe('document')
|
|
53
|
-
expect(tree.props.title).toBe('Test')
|
|
54
|
-
expect(tree.children).toHaveLength(2)
|
|
55
|
-
|
|
56
|
-
const heading = tree.children[0] as any
|
|
57
|
-
expect(heading.type).toBe('heading')
|
|
58
|
-
expect(heading.props.level).toBe(1)
|
|
59
|
-
expect(heading.styles?.fontSize).toBe(24)
|
|
60
|
-
expect(heading.children).toEqual(['Hello'])
|
|
61
|
-
|
|
62
|
-
const text = tree.children[1] as any
|
|
63
|
-
expect(text.type).toBe('text')
|
|
64
|
-
expect(text.styles?.fontSize).toBe(14)
|
|
65
|
-
expect(text.styles?.color).toBe('#333')
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('can be called multiple times', () => {
|
|
69
|
-
const doc = createDocumentExport(() =>
|
|
70
|
-
node(DocText, { $rocketstyle: { fontSize: 14 } }, ['Static']),
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
const tree1 = doc.getDocNode()
|
|
74
|
-
const tree2 = doc.getDocNode()
|
|
75
|
-
|
|
76
|
-
expect(tree1.type).toBe('text')
|
|
77
|
-
expect(tree2.type).toBe('text')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('respects includeStyles option', () => {
|
|
81
|
-
const doc = createDocumentExport(
|
|
82
|
-
() => node(DocHeading, { $rocketstyle: { fontSize: 24 } }, ['Hello']),
|
|
83
|
-
{ includeStyles: false },
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
const tree = doc.getDocNode()
|
|
87
|
-
expect(tree.styles).toBeUndefined()
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('handles empty template', () => {
|
|
91
|
-
const doc = createDocumentExport(() => null)
|
|
92
|
-
|
|
93
|
-
const tree = doc.getDocNode()
|
|
94
|
-
expect(tree.type).toBe('document')
|
|
95
|
-
expect(tree.children).toEqual([])
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
describe('extractDocNode (one-step alias)', () => {
|
|
100
|
-
// The one-step form. Equivalent to
|
|
101
|
-
// `createDocumentExport(templateFn).getDocNode()` but without
|
|
102
|
-
// the wrapper-object indirection. This is the form most
|
|
103
|
-
// consumers should use.
|
|
104
|
-
|
|
105
|
-
it('extracts a tree from a template function in one call', () => {
|
|
106
|
-
const tree = extractDocNode(() =>
|
|
107
|
-
node(DocDocument, { _documentProps: { title: 'Test' } }, [
|
|
108
|
-
node(DocHeading, { _documentProps: { level: 1 } }, ['Hello']),
|
|
109
|
-
]),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
expect(tree.type).toBe('document')
|
|
113
|
-
expect(tree.props.title).toBe('Test')
|
|
114
|
-
expect(tree.children).toHaveLength(1)
|
|
115
|
-
const heading = tree.children[0] as { type: string; props: Record<string, unknown> }
|
|
116
|
-
expect(heading.type).toBe('heading')
|
|
117
|
-
expect(heading.props.level).toBe(1)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('respects extraction options (includeStyles: false)', () => {
|
|
121
|
-
const tree = extractDocNode(
|
|
122
|
-
() => node(DocHeading, { $rocketstyle: { fontSize: 24 } }, ['Hello']),
|
|
123
|
-
{ includeStyles: false },
|
|
124
|
-
)
|
|
125
|
-
expect(tree.styles).toBeUndefined()
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('returns the same tree shape as createDocumentExport().getDocNode()', () => {
|
|
129
|
-
// Equivalence guarantee — the two-step form delegates to the
|
|
130
|
-
// one-step form internally, so they MUST produce identical
|
|
131
|
-
// output for identical input.
|
|
132
|
-
const template = () =>
|
|
133
|
-
node(DocText, { $rocketstyle: { fontSize: 14 } }, ['Hello'])
|
|
134
|
-
|
|
135
|
-
const oneStep = extractDocNode(template)
|
|
136
|
-
const twoStep = createDocumentExport(template).getDocNode()
|
|
137
|
-
|
|
138
|
-
expect(oneStep).toEqual(twoStep)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('is idempotent — calling extractDocNode twice on the same template produces equivalent results', () => {
|
|
142
|
-
// The framework fix in PR #197 changed extractDocumentTree to
|
|
143
|
-
// CALL the component function for documentType vnodes when
|
|
144
|
-
// _documentProps is not directly on the JSX vnode. The fix is
|
|
145
|
-
// correct because rocketstyle's attrs HOC is meant to be pure
|
|
146
|
-
// setup with no observable side effects on the second call.
|
|
147
|
-
// This test locks in that purity assumption: extracting the
|
|
148
|
-
// same template twice produces structurally equivalent doc
|
|
149
|
-
// nodes.
|
|
150
|
-
//
|
|
151
|
-
// If a future change to a primitive accidentally introduces a
|
|
152
|
-
// side effect in its attrs callback (e.g. a counter, a log
|
|
153
|
-
// line, a stateful import), the second extraction would still
|
|
154
|
-
// SUCCEED but produce different output — and this test would
|
|
155
|
-
// catch the regression. The three extractions should be deeply
|
|
156
|
-
// equal under any change to the primitive's setup path.
|
|
157
|
-
const template = () =>
|
|
158
|
-
node(
|
|
159
|
-
DocDocument,
|
|
160
|
-
{ _documentProps: { title: 'Idempotent', author: 'Test' } },
|
|
161
|
-
[
|
|
162
|
-
node(DocHeading, { _documentProps: { level: 1 } }, ['Hello']),
|
|
163
|
-
node(DocText, { $rocketstyle: { fontSize: 14 } }, ['World']),
|
|
164
|
-
],
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
const first = extractDocNode(template)
|
|
168
|
-
const second = extractDocNode(template)
|
|
169
|
-
const third = extractDocNode(template)
|
|
170
|
-
|
|
171
|
-
expect(first).toEqual(second)
|
|
172
|
-
expect(second).toEqual(third)
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('resolves function values in _documentProps each call (D1+D2 integration)', () => {
|
|
176
|
-
// The combined contract: extractDocNode is the one-step form,
|
|
177
|
-
// and it benefits from the same function-value resolution
|
|
178
|
-
// that extractDocumentTree does. Each call sees the live
|
|
179
|
-
// value of any accessor in _documentProps.
|
|
180
|
-
let counter = 0
|
|
181
|
-
const template = () =>
|
|
182
|
-
node(
|
|
183
|
-
DocDocument,
|
|
184
|
-
{
|
|
185
|
-
_documentProps: {
|
|
186
|
-
title: () => `Export ${++counter}`,
|
|
187
|
-
author: 'Plain string still works',
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
[],
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
const first = extractDocNode(template)
|
|
194
|
-
const second = extractDocNode(template)
|
|
195
|
-
|
|
196
|
-
expect(first.props.title).toBe('Export 1')
|
|
197
|
-
expect(first.props.author).toBe('Plain string still works')
|
|
198
|
-
expect(second.props.title).toBe('Export 2')
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
describe('DocDocument reactive metadata (D1 integration)', () => {
|
|
203
|
-
// The end-to-end contract: a real DocDocument primitive (NOT a
|
|
204
|
-
// mock vnode) accepts accessor functions for title/author/subject,
|
|
205
|
-
// stores them in _documentProps, and the export pipeline reads
|
|
206
|
-
// the LIVE values at extraction time.
|
|
207
|
-
//
|
|
208
|
-
// This test mounts DocDocument as if from JSX (via h()) and
|
|
209
|
-
// extracts the resulting tree, then mutates the closure-captured
|
|
210
|
-
// state and re-extracts to prove the second extraction sees the
|
|
211
|
-
// updated value. The previous tests use mock vnodes — this one
|
|
212
|
-
// uses the real primitive.
|
|
213
|
-
|
|
214
|
-
// Long timeout: this test dynamic-imports @pyreon/test-utils,
|
|
215
|
-
// @pyreon/core, and DocDocument inside the test body. Each
|
|
216
|
-
// dynamic import triggers Vite's transform pipeline (JSX
|
|
217
|
-
// compilation, rocketstyle wrapping, etc.) which takes 5+
|
|
218
|
-
// seconds on slow CI runners on first hit. The default 5000ms
|
|
219
|
-
// timeout fails reliably on CI.
|
|
220
|
-
it('DocDocument with accessor title produces live values across multiple extractions', { timeout: 60_000 }, async () => {
|
|
221
|
-
// Use happy-dom + initTestConfig like the rest of the test suite
|
|
222
|
-
const { initTestConfig } = await import('@pyreon/test-utils')
|
|
223
|
-
const { h } = await import('@pyreon/core')
|
|
224
|
-
const cleanup = initTestConfig()
|
|
225
|
-
try {
|
|
226
|
-
// Real DocDocument from the package (not the mock above)
|
|
227
|
-
const RealDocDocument = (await import('../primitives/DocDocument')).default
|
|
228
|
-
|
|
229
|
-
// Closure state that the accessor reads from. Mutating it
|
|
230
|
-
// between extractions simulates a signal change.
|
|
231
|
-
let currentName = 'Aisha'
|
|
232
|
-
const titleAccessor = () => `${currentName} — Resume`
|
|
233
|
-
const authorAccessor = () => currentName
|
|
234
|
-
|
|
235
|
-
// Build the template via h() so DocDocument's attrs callback
|
|
236
|
-
// runs (storing the accessor functions in _documentProps).
|
|
237
|
-
const template = () =>
|
|
238
|
-
h(
|
|
239
|
-
RealDocDocument as never,
|
|
240
|
-
{ title: titleAccessor, author: authorAccessor } as never,
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
const first = extractDocNode(template)
|
|
244
|
-
expect(first.type).toBe('document')
|
|
245
|
-
expect(first.props.title).toBe('Aisha — Resume')
|
|
246
|
-
expect(first.props.author).toBe('Aisha')
|
|
247
|
-
|
|
248
|
-
// Mutate the closure-captured name. The next extraction
|
|
249
|
-
// should see the new value because extractDocumentTree calls
|
|
250
|
-
// the function fresh.
|
|
251
|
-
currentName = 'Marcus'
|
|
252
|
-
const second = extractDocNode(template)
|
|
253
|
-
expect(second.props.title).toBe('Marcus — Resume')
|
|
254
|
-
expect(second.props.author).toBe('Marcus')
|
|
255
|
-
|
|
256
|
-
// And once more for good measure
|
|
257
|
-
currentName = 'Priya'
|
|
258
|
-
const third = extractDocNode(template)
|
|
259
|
-
expect(third.props.title).toBe('Priya — Resume')
|
|
260
|
-
expect(third.props.author).toBe('Priya')
|
|
261
|
-
} finally {
|
|
262
|
-
cleanup()
|
|
263
|
-
}
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
it('DocDocument with plain string title still works (backward compat)', { timeout: 30_000 }, async () => {
|
|
267
|
-
const { initTestConfig } = await import('@pyreon/test-utils')
|
|
268
|
-
const { h } = await import('@pyreon/core')
|
|
269
|
-
const cleanup = initTestConfig()
|
|
270
|
-
try {
|
|
271
|
-
const RealDocDocument = (await import('../primitives/DocDocument')).default
|
|
272
|
-
|
|
273
|
-
const tree = extractDocNode(() =>
|
|
274
|
-
h(
|
|
275
|
-
RealDocDocument as never,
|
|
276
|
-
{ title: 'Static title', author: 'Static author' } as never,
|
|
277
|
-
),
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
expect(tree.props.title).toBe('Static title')
|
|
281
|
-
expect(tree.props.author).toBe('Static author')
|
|
282
|
-
} finally {
|
|
283
|
-
cleanup()
|
|
284
|
-
}
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
it('DocDocument subject prop also accepts both string and accessor (full prop coverage)', { timeout: 30_000 }, async () => {
|
|
288
|
-
// The widening covered all three metadata props (title, author,
|
|
289
|
-
// subject). The previous tests only exercise title and author —
|
|
290
|
-
// this test fills the coverage gap so a typo in the subject
|
|
291
|
-
// type widening would be caught.
|
|
292
|
-
const { initTestConfig } = await import('@pyreon/test-utils')
|
|
293
|
-
const { h } = await import('@pyreon/core')
|
|
294
|
-
const cleanup = initTestConfig()
|
|
295
|
-
try {
|
|
296
|
-
const RealDocDocument = (await import('../primitives/DocDocument')).default
|
|
297
|
-
|
|
298
|
-
// Plain string subject
|
|
299
|
-
const plainTree = extractDocNode(() =>
|
|
300
|
-
h(RealDocDocument as never, { subject: 'Q4 Report' } as never),
|
|
301
|
-
)
|
|
302
|
-
expect(plainTree.props.subject).toBe('Q4 Report')
|
|
303
|
-
|
|
304
|
-
// Accessor subject — value resolved at extraction time
|
|
305
|
-
let topic = 'Initial topic'
|
|
306
|
-
const accessorTree1 = extractDocNode(() =>
|
|
307
|
-
h(RealDocDocument as never, { subject: () => topic } as never),
|
|
308
|
-
)
|
|
309
|
-
expect(accessorTree1.props.subject).toBe('Initial topic')
|
|
310
|
-
|
|
311
|
-
topic = 'Updated topic'
|
|
312
|
-
const accessorTree2 = extractDocNode(() =>
|
|
313
|
-
h(RealDocDocument as never, { subject: () => topic } as never),
|
|
314
|
-
)
|
|
315
|
-
expect(accessorTree2.props.subject).toBe('Updated topic')
|
|
316
|
-
} finally {
|
|
317
|
-
cleanup()
|
|
318
|
-
}
|
|
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
|
-
})
|
|
366
|
-
})
|
package/src/index.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// Re-export connector utilities
|
|
2
|
-
|
|
3
|
-
export type {
|
|
4
|
-
DocChild,
|
|
5
|
-
DocNode,
|
|
6
|
-
ExtractOptions,
|
|
7
|
-
NodeType,
|
|
8
|
-
ResolvedStyles,
|
|
9
|
-
} from '@pyreon/connector-document'
|
|
10
|
-
export { extractDocumentTree, resolveStyles } from '@pyreon/connector-document'
|
|
11
|
-
// Preview
|
|
12
|
-
export { default as DocumentPreview } from './DocumentPreview'
|
|
13
|
-
// Primitives
|
|
14
|
-
export { default as DocButton } from './primitives/DocButton'
|
|
15
|
-
export { default as DocCode } from './primitives/DocCode'
|
|
16
|
-
export { default as DocColumn } from './primitives/DocColumn'
|
|
17
|
-
export { default as DocDivider } from './primitives/DocDivider'
|
|
18
|
-
export { default as DocDocument } from './primitives/DocDocument'
|
|
19
|
-
export { default as DocHeading } from './primitives/DocHeading'
|
|
20
|
-
export { default as DocImage } from './primitives/DocImage'
|
|
21
|
-
export { default as DocLink } from './primitives/DocLink'
|
|
22
|
-
export { default as DocList } from './primitives/DocList'
|
|
23
|
-
export { default as DocListItem } from './primitives/DocListItem'
|
|
24
|
-
export { default as DocPage } from './primitives/DocPage'
|
|
25
|
-
export { default as DocPageBreak } from './primitives/DocPageBreak'
|
|
26
|
-
export { default as DocQuote } from './primitives/DocQuote'
|
|
27
|
-
export { default as DocRow } from './primitives/DocRow'
|
|
28
|
-
export { default as DocSection } from './primitives/DocSection'
|
|
29
|
-
export { default as DocSpacer } from './primitives/DocSpacer'
|
|
30
|
-
export { default as DocTable } from './primitives/DocTable'
|
|
31
|
-
export { default as DocText } from './primitives/DocText'
|
|
32
|
-
// Theme
|
|
33
|
-
export type { DocumentTheme } from './theme'
|
|
34
|
-
export { documentTheme } from './theme'
|
|
35
|
-
// Export helper
|
|
36
|
-
export type { DocumentExport, DocumentExportOptions } from './useDocumentExport'
|
|
37
|
-
export { createDocumentExport, extractDocNode } from './useDocumentExport'
|