@pyreon/document-primitives 0.12.10 → 0.12.12
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 +115 -64
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +128 -14
- package/lib/index.js.map +1 -1
- package/package.json +16 -14
- package/src/DocumentPreview.ts +0 -2
- package/src/__tests__/reactivity.test.ts +415 -0
- package/src/__tests__/useDocumentExport.test.ts +224 -1
- package/src/index.ts +1 -1
- package/src/primitives/DocButton.ts +1 -1
- package/src/primitives/DocCode.ts +1 -1
- package/src/primitives/DocColumn.ts +4 -6
- package/src/primitives/DocDivider.ts +0 -2
- package/src/primitives/DocDocument.ts +56 -8
- package/src/primitives/DocHeading.ts +1 -1
- package/src/primitives/DocImage.ts +0 -2
- package/src/primitives/DocLink.ts +1 -1
- package/src/primitives/DocList.ts +1 -1
- package/src/primitives/DocListItem.ts +1 -1
- package/src/primitives/DocPage.ts +0 -2
- package/src/primitives/DocPageBreak.ts +1 -1
- package/src/primitives/DocQuote.ts +4 -6
- package/src/primitives/DocRow.ts +9 -4
- package/src/primitives/DocSection.ts +1 -1
- package/src/primitives/DocSpacer.ts +1 -1
- package/src/primitives/DocTable.ts +33 -12
- package/src/primitives/DocText.ts +1 -5
- package/src/useDocumentExport.ts +44 -7
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["extractDocumentTree"],"sources":["../src/DocumentPreview.ts","../src/primitives/DocButton.ts","../src/primitives/DocCode.ts","../src/primitives/DocColumn.ts","../src/primitives/DocDivider.ts","../src/primitives/DocDocument.ts","../src/primitives/DocHeading.ts","../src/primitives/DocImage.ts","../src/primitives/DocLink.ts","../src/primitives/DocList.ts","../src/primitives/DocListItem.ts","../src/primitives/DocPage.ts","../src/primitives/DocPageBreak.ts","../src/primitives/DocQuote.ts","../src/primitives/DocRow.ts","../src/primitives/DocSection.ts","../src/primitives/DocSpacer.ts","../src/primitives/DocTable.ts","../src/primitives/DocText.ts","../src/theme.ts","../src/useDocumentExport.ts"],"sourcesContent":["import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocumentPreview = rocketstyle({\n dimensions: {\n sizes: 'size',\n },\n useBooleans: true,\n})({ name: 'DocumentPreview', component: Element })\n .theme({\n backgroundColor: '#f5f5f5',\n padding: 40,\n })\n .sizes({\n A4: { width: '210mm', minHeight: '297mm' },\n A3: { width: '297mm', minHeight: '420mm' },\n A5: { width: '148mm', minHeight: '210mm' },\n letter: { width: '8.5in', minHeight: '11in' },\n legal: { width: '8.5in', minHeight: '14in' },\n })\n .styles(\n (css: any) => css`\n display: flex;\n flex-direction: column;\n align-items: center;\n min-height: 100vh;\n\n & > * {\n background: white;\n padding: 25mm;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n margin: 20px 0;\n }\n `,\n )\n .statics({ _documentType: 'document' as const })\n .attrs<{\n size?: string\n showPageBreaks?: boolean\n tag: string\n _documentProps: Record<string, unknown>\n }>((props) => ({\n tag: 'div',\n _documentProps: {\n ...(props.size ? { size: props.size } : { size: 'A4' }),\n ...(props.showPageBreaks ? { showPageBreaks: props.showPageBreaks } : {}),\n },\n }))\n\nexport default DocumentPreview\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocButton = rocketstyle({\n dimensions: {\n variants: 'variant',\n },\n useBooleans: true,\n})({ name: 'DocButton', component: Text })\n .theme({\n fontSize: 14,\n fontWeight: 'bold',\n padding: '10px 24px',\n borderRadius: 4,\n textAlign: 'center',\n textDecoration: 'none',\n })\n .variants({\n primary: {\n backgroundColor: '#4f46e5',\n color: '#ffffff',\n },\n secondary: {\n backgroundColor: '#ffffff',\n color: '#4f46e5',\n borderWidth: 1,\n borderColor: '#4f46e5',\n borderStyle: 'solid',\n },\n })\n .statics({ _documentType: 'button' as const })\n .attrs<{ href?: string; tag: string; _documentProps: { href: string } }>((props) => ({\n tag: 'a',\n _documentProps: { href: props.href ?? '#' },\n }))\n\nexport default DocButton\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocCode = rocketstyle()({ name: 'DocCode', component: Text })\n .theme({\n fontFamily: 'ui-monospace, monospace',\n fontSize: 13,\n backgroundColor: '#f5f5f5',\n padding: '8px 12px',\n borderRadius: 4,\n })\n .statics({ _documentType: 'code' as const })\n .attrs<{ language?: string; tag: string; _documentProps: Record<string, unknown> }>((props) => ({\n tag: 'pre',\n _documentProps: props.language ? { language: props.language } : {},\n }))\n\nexport default DocCode\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocColumn = rocketstyle()({ name: 'DocColumn', component: Element })\n .statics({ _documentType: 'column' as const })\n .attrs<{ width?: number | string; tag: string; _documentProps: Record<string, unknown> }>(\n (props) => ({\n tag: 'div',\n _documentProps: props.width != null ? { width: props.width } : {},\n }),\n )\n\nexport default DocColumn\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocDivider = rocketstyle()({ name: 'DocDivider', component: Element })\n .theme({\n borderColor: '#dddddd',\n borderWidth: 1,\n })\n .statics({ _documentType: 'divider' as const })\n .attrs<{\n color?: string\n thickness?: number\n tag: string\n _documentProps: Record<string, unknown>\n }>((props) => ({\n tag: 'hr',\n _documentProps: {\n ...(props.color ? { color: props.color } : {}),\n ...(props.thickness ? { thickness: props.thickness } : {}),\n },\n }))\n\nexport default DocDivider\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocDocument = rocketstyle()({ name: 'DocDocument', component: Element })\n .statics({ _documentType: 'document' as const })\n .attrs<{\n title?: string\n author?: string\n subject?: string\n tag: string\n _documentProps: Record<string, unknown>\n }>((props) => ({\n tag: 'div',\n _documentProps: {\n ...(props.title ? { title: props.title } : {}),\n ...(props.author ? { author: props.author } : {}),\n ...(props.subject ? { subject: props.subject } : {}),\n },\n }))\n\nexport default DocDocument\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocHeading = rocketstyle({\n dimensions: {\n levels: 'level',\n },\n useBooleans: true,\n})({ name: 'DocHeading', component: Text })\n .theme({\n fontWeight: 'bold',\n color: '#1a1a2e',\n marginBottom: 12,\n })\n .levels({\n h1: { fontSize: 32, lineHeight: 1.2 },\n h2: { fontSize: 24, lineHeight: 1.3 },\n h3: { fontSize: 20, lineHeight: 1.4 },\n h4: { fontSize: 18, lineHeight: 1.4 },\n h5: { fontSize: 16, lineHeight: 1.5 },\n h6: { fontSize: 14, lineHeight: 1.5 },\n })\n .statics({ _documentType: 'heading' as const })\n .attrs<{ level?: string; tag: string; _documentProps: { level: number } }>((props) => {\n const lvl = props.level ?? 'h1'\n const num = Number.parseInt(String(lvl).replace('h', ''), 10) || 1\n return {\n tag: lvl,\n _documentProps: { level: num },\n }\n })\n\nexport default DocHeading\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocImage = rocketstyle()({ name: 'DocImage', component: Element })\n .statics({ _documentType: 'image' as const })\n .attrs<{\n src?: string\n alt?: string\n width?: number | string\n height?: number | string\n caption?: string\n tag: string\n _documentProps: Record<string, unknown>\n }>((props) => ({\n tag: 'img',\n _documentProps: {\n src: props.src ?? '',\n ...(props.alt ? { alt: props.alt } : {}),\n ...(props.width ? { width: props.width } : {}),\n ...(props.height ? { height: props.height } : {}),\n ...(props.caption ? { caption: props.caption } : {}),\n },\n }))\n\nexport default DocImage\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocLink = rocketstyle()({ name: 'DocLink', component: Text })\n .theme({\n color: '#4f46e5',\n textDecoration: 'underline',\n })\n .statics({ _documentType: 'link' as const })\n .attrs<{ href?: string; tag: string; _documentProps: { href: string } }>((props) => ({\n tag: 'a',\n _documentProps: { href: props.href ?? '#' },\n }))\n\nexport default DocLink\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocList = rocketstyle()({ name: 'DocList', component: Element })\n .theme({\n marginBottom: 8,\n paddingLeft: 20,\n })\n .statics({ _documentType: 'list' as const })\n .attrs<{ ordered?: boolean; tag: string; _documentProps: Record<string, unknown> }>((props) => ({\n tag: props.ordered ? 'ol' : 'ul',\n _documentProps: props.ordered ? { ordered: props.ordered } : {},\n }))\n\nexport default DocList\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocListItem = rocketstyle()({ name: 'DocListItem', component: Text })\n .theme({\n fontSize: 14,\n lineHeight: 1.5,\n })\n .statics({ _documentType: 'list-item' as const })\n .attrs<{ tag: string; _documentProps: Record<string, unknown> }>((_props) => ({\n tag: 'li',\n _documentProps: {},\n }))\n\nexport default DocListItem\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocPage = rocketstyle()({ name: 'DocPage', component: Element })\n .theme({\n backgroundColor: '#ffffff',\n padding: '25mm',\n })\n .statics({ _documentType: 'page' as const })\n .attrs<{\n size?: string\n orientation?: string\n tag: string\n _documentProps: Record<string, unknown>\n }>((props) => ({\n tag: 'div',\n _documentProps: {\n ...(props.size ? { size: props.size } : {}),\n ...(props.orientation ? { orientation: props.orientation } : {}),\n },\n }))\n\nexport default DocPage\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocPageBreak = rocketstyle()({ name: 'DocPageBreak', component: Element })\n .statics({ _documentType: 'page-break' as const })\n .attrs<{ tag: string; _documentProps: Record<string, unknown> }>((_props) => ({\n tag: 'div',\n _documentProps: {},\n }))\n\nexport default DocPageBreak\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocQuote = rocketstyle()({ name: 'DocQuote', component: Element })\n .theme({\n borderColor: '#4f46e5',\n padding: '8px 16px',\n fontStyle: 'italic',\n color: '#666666',\n })\n .statics({ _documentType: 'quote' as const })\n .attrs<{ borderColor?: string; tag: string; _documentProps: Record<string, unknown> }>(\n (props) => ({\n tag: 'blockquote',\n _documentProps: props.borderColor ? { borderColor: props.borderColor } : {},\n }),\n )\n\nexport default DocQuote\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocRow = rocketstyle()({ name: 'DocRow', component: Element })\n .theme({\n direction: 'row',\n })\n .statics({ _documentType: 'row' as const })\n .attrs<{ tag: string; _documentProps: Record<string, unknown> }>((_props) => ({\n tag: 'div',\n _documentProps: {},\n }))\n\nexport default DocRow\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocSection = rocketstyle({\n dimensions: {\n directions: 'direction',\n },\n useBooleans: false,\n})({ name: 'DocSection', component: Element })\n .theme({\n padding: 0,\n })\n .directions({\n column: {},\n row: { direction: 'row' },\n })\n .statics({ _documentType: 'section' as const })\n .attrs<{ direction?: string; tag: string; _documentProps: { direction: string } }>((props) => ({\n tag: 'div',\n _documentProps: { direction: props.direction ?? 'column' },\n }))\n\nexport default DocSection\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocSpacer = rocketstyle()({ name: 'DocSpacer', component: Element })\n .statics({ _documentType: 'spacer' as const })\n .attrs<{ height?: number; tag: string; _documentProps: { height: number } }>((props) => ({\n tag: 'div',\n _documentProps: { height: props.height ?? 16 },\n }))\n\nexport default DocSpacer\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocTable = rocketstyle({\n dimensions: {\n variants: 'variant',\n },\n useBooleans: true,\n})({ name: 'DocTable', component: Element })\n .theme({\n fontSize: 14,\n borderColor: '#dddddd',\n })\n .statics({ _documentType: 'table' as const })\n .attrs<{\n columns?: unknown[]\n rows?: unknown[]\n headerStyle?: Record<string, unknown>\n striped?: boolean\n bordered?: boolean\n caption?: string\n tag: string\n _documentProps: Record<string, unknown>\n }>((props) => ({\n tag: 'table',\n _documentProps: {\n columns: props.columns ?? [],\n rows: props.rows ?? [],\n ...(props.headerStyle ? { headerStyle: props.headerStyle } : {}),\n ...(props.striped ? { striped: props.striped } : {}),\n ...(props.bordered ? { bordered: props.bordered } : {}),\n ...(props.caption ? { caption: props.caption } : {}),\n },\n }))\n\nexport default DocTable\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocText = rocketstyle({\n dimensions: {\n variants: 'variant',\n weights: 'weight',\n },\n useBooleans: true,\n})({ name: 'DocText', component: Text })\n .theme({\n color: '#333333',\n lineHeight: 1.5,\n marginBottom: 8,\n })\n .variants({\n body: { fontSize: 14 },\n caption: { fontSize: 12, color: '#666666' },\n label: { fontSize: 11, fontWeight: 'bold' },\n })\n .weights({\n normal: { fontWeight: 'normal' },\n bold: { fontWeight: 'bold' },\n })\n .statics({ _documentType: 'text' as const })\n // .attrs(\n // (props: any) =>\n // ({\n // tag: \"p\",\n .attrs<{ tag: string; _documentProps: Record<string, unknown> }>((_props) => ({\n tag: 'p',\n _documentProps: {},\n }))\n\nexport default DocText\n","export const documentTheme = {\n colors: {\n primary: '#4f46e5',\n text: '#333333',\n textSecondary: '#666666',\n background: '#ffffff',\n border: '#dddddd',\n headerBg: '#1a1a2e',\n headerText: '#ffffff',\n stripedRow: '#f9f9f9',\n },\n fonts: {\n heading: 'system-ui, -apple-system, sans-serif',\n body: 'system-ui, -apple-system, sans-serif',\n mono: 'ui-monospace, monospace',\n },\n sizes: {\n h1: 32,\n h2: 24,\n h3: 20,\n h4: 18,\n h5: 16,\n h6: 14,\n body: 14,\n caption: 12,\n label: 11,\n },\n spacing: {\n xs: 4,\n sm: 8,\n md: 16,\n lg: 24,\n xl: 40,\n },\n}\n\nexport type DocumentTheme = typeof documentTheme\n","import type { DocNode, ExtractOptions } from '@pyreon/connector-document'\nimport { extractDocumentTree } from '@pyreon/connector-document'\n\nexport interface DocumentExportOptions extends ExtractOptions {\n /** Theme object to provide during extraction. */\n theme?: Record<string, unknown>\n /** Mode: 'light' or 'dark'. */\n mode?: 'light' | 'dark'\n}\n\nexport interface DocumentExport {\n /** Extract the DocNode tree from the template. */\n getDocNode: () => DocNode\n}\n\n/**\n * Create a document export helper from a template function.\n *\n * The template function should return a VNode tree built with\n * document primitives (DocHeading, DocText, DocTable, etc.).\n *\n * ```ts\n * const doc = createDocumentExport(() =>\n * DocDocument({ title: 'Report', children: [\n * DocHeading({ h1: true, children: 'Sales Report' }),\n * DocText({ children: 'Q4 summary.' }),\n * ]})\n * )\n *\n * const tree = doc.getDocNode()\n * // Pass to @pyreon/document's render() for any format\n * ```\n *\n * When @pyreon/document is published, this will also expose\n * convenience methods like toPdf(), toDocx(), download(), etc.\n */\nexport function createDocumentExport(\n templateFn: () => unknown,\n options: DocumentExportOptions = {},\n): DocumentExport {\n const getDocNode = (): DocNode => {\n const vnode = templateFn()\n return extractDocumentTree(vnode, options)\n }\n\n return { getDocNode }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["extractDocumentTree"],"sources":["../src/DocumentPreview.ts","../src/primitives/DocButton.ts","../src/primitives/DocCode.ts","../src/primitives/DocColumn.ts","../src/primitives/DocDivider.ts","../src/primitives/DocDocument.ts","../src/primitives/DocHeading.ts","../src/primitives/DocImage.ts","../src/primitives/DocLink.ts","../src/primitives/DocList.ts","../src/primitives/DocListItem.ts","../src/primitives/DocPage.ts","../src/primitives/DocPageBreak.ts","../src/primitives/DocQuote.ts","../src/primitives/DocRow.ts","../src/primitives/DocSection.ts","../src/primitives/DocSpacer.ts","../src/primitives/DocTable.ts","../src/primitives/DocText.ts","../src/theme.ts","../src/useDocumentExport.ts"],"sourcesContent":["import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocumentPreview = rocketstyle({\n dimensions: {\n sizes: 'size',\n },\n useBooleans: true,\n})({ name: 'DocumentPreview', component: Element })\n .theme({\n backgroundColor: '#f5f5f5',\n padding: 40,\n })\n .sizes({\n A4: { width: '210mm', minHeight: '297mm' },\n A3: { width: '297mm', minHeight: '420mm' },\n A5: { width: '148mm', minHeight: '210mm' },\n letter: { width: '8.5in', minHeight: '11in' },\n legal: { width: '8.5in', minHeight: '14in' },\n })\n .styles(\n (css: any) => css`\n display: flex;\n flex-direction: column;\n align-items: center;\n min-height: 100vh;\n\n & > * {\n background: white;\n padding: 25mm;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n margin: 20px 0;\n }\n `,\n )\n .statics({ _documentType: 'document' as const })\n .attrs<{\n size?: string\n showPageBreaks?: boolean\n }>((props) => ({\n tag: 'div',\n _documentProps: {\n ...(props.size ? { size: props.size } : { size: 'A4' }),\n ...(props.showPageBreaks ? { showPageBreaks: props.showPageBreaks } : {}),\n },\n }))\n\nexport default DocumentPreview\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocButton = rocketstyle({\n dimensions: {\n variants: 'variant',\n },\n useBooleans: true,\n})({ name: 'DocButton', component: Text })\n .theme({\n fontSize: 14,\n fontWeight: 'bold',\n padding: '10px 24px',\n borderRadius: 4,\n textAlign: 'center',\n textDecoration: 'none',\n })\n .variants({\n primary: {\n backgroundColor: '#4f46e5',\n color: '#ffffff',\n },\n secondary: {\n backgroundColor: '#ffffff',\n color: '#4f46e5',\n borderWidth: 1,\n borderColor: '#4f46e5',\n borderStyle: 'solid',\n },\n })\n .statics({ _documentType: 'button' as const })\n .attrs<{ href?: string }>((props) => ({\n tag: 'a',\n _documentProps: { href: props.href ?? '#' },\n }))\n\nexport default DocButton\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocCode = rocketstyle()({ name: 'DocCode', component: Text })\n .theme({\n fontFamily: 'ui-monospace, monospace',\n fontSize: 13,\n backgroundColor: '#f5f5f5',\n padding: '8px 12px',\n borderRadius: 4,\n })\n .statics({ _documentType: 'code' as const })\n .attrs<{ language?: string }>((props) => ({\n tag: 'pre',\n _documentProps: props.language ? { language: props.language } : {},\n }))\n\nexport default DocCode\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocColumn = rocketstyle()({ name: 'DocColumn', component: Element })\n .statics({ _documentType: 'column' as const })\n .attrs<{ width?: number | string }>((props) => ({\n tag: 'div',\n _documentProps: props.width != null ? { width: props.width } : {},\n }))\n\nexport default DocColumn\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocDivider = rocketstyle()({ name: 'DocDivider', component: Element })\n .theme({\n borderColor: '#dddddd',\n borderWidth: 1,\n })\n .statics({ _documentType: 'divider' as const })\n .attrs<{\n color?: string\n thickness?: number\n }>((props) => ({\n tag: 'hr',\n _documentProps: {\n ...(props.color ? { color: props.color } : {}),\n ...(props.thickness ? { thickness: props.thickness } : {}),\n },\n }))\n\nexport default DocDivider\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\n/**\n * Root document container with metadata for the export pipeline.\n *\n * The `title`, `author`, and `subject` props each accept either a\n * plain `string` or a `() => string` accessor. Accessors are\n * resolved by `extractDocumentTree` at export time, so consumers\n * can pass live signal accessors without capturing values at\n * component mount.\n *\n * **Why accessors are needed**: rocketstyle's `.attrs()` callback\n * runs ONCE at component mount (see\n * `packages/ui-system/rocketstyle/src/hoc/rocketstyleAttrsHoc.ts`\n * line 38: \".attrs() callbacks run once at mount\"). If `title` were\n * `string`-only and a consumer wanted to bind it to a live signal,\n * they'd have to capture the initial value at template setup time\n * — meaning the export metadata would be permanently stale relative\n * to the live UI state.\n *\n * Storing the accessor in `_documentProps` and resolving it at\n * extraction time means every `extractDocumentTree` call (one per\n * export click) reads the live value. Plain string values still\n * work as before — `extractDocumentTree` only calls the value if\n * it's a function.\n *\n * @example Plain string\n * ```tsx\n * <DocDocument title=\"My Report\" author=\"Alice\">\n * ...\n * </DocDocument>\n * ```\n *\n * @example Reactive accessor (recommended for templates that drive\n * a live preview AND export the same tree)\n * ```tsx\n * function MyTemplate({ resume }: { resume: () => Resume }) {\n * return (\n * <DocDocument\n * title={() => `${resume().name} — Resume`}\n * author={() => resume().name}\n * >\n * ...\n * </DocDocument>\n * )\n * }\n * ```\n */\nconst DocDocument = rocketstyle()({ name: 'DocDocument', component: Element })\n .statics({ _documentType: 'document' as const })\n .attrs<{\n title?: string | (() => string)\n author?: string | (() => string)\n subject?: string | (() => string)\n }>((props) => ({\n tag: 'div',\n _documentProps: {\n // Pass accessor functions through unmodified — extractDocumentTree\n // resolves them at export time. Plain strings pass through too.\n // Empty / nullish values are omitted entirely so they don't\n // appear as `title: undefined` in the export metadata.\n ...(props.title != null ? { title: props.title } : {}),\n ...(props.author != null ? { author: props.author } : {}),\n ...(props.subject != null ? { subject: props.subject } : {}),\n },\n }))\n\nexport default DocDocument\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocHeading = rocketstyle({\n dimensions: {\n levels: 'level',\n },\n useBooleans: true,\n})({ name: 'DocHeading', component: Text })\n .theme({\n fontWeight: 'bold',\n color: '#1a1a2e',\n marginBottom: 12,\n })\n .levels({\n h1: { fontSize: 32, lineHeight: 1.2 },\n h2: { fontSize: 24, lineHeight: 1.3 },\n h3: { fontSize: 20, lineHeight: 1.4 },\n h4: { fontSize: 18, lineHeight: 1.4 },\n h5: { fontSize: 16, lineHeight: 1.5 },\n h6: { fontSize: 14, lineHeight: 1.5 },\n })\n .statics({ _documentType: 'heading' as const })\n .attrs<{ level?: string }>((props) => {\n const lvl = props.level ?? 'h1'\n const num = Number.parseInt(String(lvl).replace('h', ''), 10) || 1\n return {\n tag: lvl,\n _documentProps: { level: num },\n }\n })\n\nexport default DocHeading\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocImage = rocketstyle()({ name: 'DocImage', component: Element })\n .statics({ _documentType: 'image' as const })\n .attrs<{\n src?: string\n alt?: string\n width?: number | string\n height?: number | string\n caption?: string\n }>((props) => ({\n tag: 'img',\n _documentProps: {\n src: props.src ?? '',\n ...(props.alt ? { alt: props.alt } : {}),\n ...(props.width ? { width: props.width } : {}),\n ...(props.height ? { height: props.height } : {}),\n ...(props.caption ? { caption: props.caption } : {}),\n },\n }))\n\nexport default DocImage\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocLink = rocketstyle()({ name: 'DocLink', component: Text })\n .theme({\n color: '#4f46e5',\n textDecoration: 'underline',\n })\n .statics({ _documentType: 'link' as const })\n .attrs<{ href?: string }>((props) => ({\n tag: 'a',\n _documentProps: { href: props.href ?? '#' },\n }))\n\nexport default DocLink\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocList = rocketstyle()({ name: 'DocList', component: Element })\n .theme({\n marginBottom: 8,\n paddingLeft: 20,\n })\n .statics({ _documentType: 'list' as const })\n .attrs<{ ordered?: boolean }>((props) => ({\n tag: props.ordered ? 'ol' : 'ul',\n _documentProps: props.ordered ? { ordered: props.ordered } : {},\n }))\n\nexport default DocList\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocListItem = rocketstyle()({ name: 'DocListItem', component: Text })\n .theme({\n fontSize: 14,\n lineHeight: 1.5,\n })\n .statics({ _documentType: 'list-item' as const })\n .attrs(() => ({\n tag: 'li',\n _documentProps: {},\n }))\n\nexport default DocListItem\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocPage = rocketstyle()({ name: 'DocPage', component: Element })\n .theme({\n backgroundColor: '#ffffff',\n padding: '25mm',\n })\n .statics({ _documentType: 'page' as const })\n .attrs<{\n size?: string\n orientation?: string\n }>((props) => ({\n tag: 'div',\n _documentProps: {\n ...(props.size ? { size: props.size } : {}),\n ...(props.orientation ? { orientation: props.orientation } : {}),\n },\n }))\n\nexport default DocPage\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocPageBreak = rocketstyle()({ name: 'DocPageBreak', component: Element })\n .statics({ _documentType: 'page-break' as const })\n .attrs(() => ({\n tag: 'div',\n _documentProps: {},\n }))\n\nexport default DocPageBreak\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocQuote = rocketstyle()({ name: 'DocQuote', component: Element })\n .theme({\n borderColor: '#4f46e5',\n padding: '8px 16px',\n fontStyle: 'italic',\n color: '#666666',\n })\n .statics({ _documentType: 'quote' as const })\n .attrs<{ borderColor?: string }>((props) => ({\n tag: 'blockquote',\n _documentProps: props.borderColor ? { borderColor: props.borderColor } : {},\n }))\n\nexport default DocQuote\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\n/**\n * Horizontal row of inline children. Per project conventions, layout\n * props (`direction`, `gap`) live in `.attrs()`, not `.theme()`. The\n * Element base accepts `direction: 'inline' | 'rows' | 'reverseInline'\n * | 'reverseRows'` — `'row'` is not a valid value.\n */\nconst DocRow = rocketstyle()({ name: 'DocRow', component: Element })\n .statics({ _documentType: 'row' as const })\n .attrs(() => ({\n tag: 'div',\n direction: 'inline' as const,\n gap: 8,\n _documentProps: {},\n }))\n\nexport default DocRow\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocSection = rocketstyle({\n dimensions: {\n directions: 'direction',\n },\n useBooleans: false,\n})({ name: 'DocSection', component: Element })\n .theme({\n padding: 0,\n })\n .directions({\n column: {},\n row: { direction: 'row' },\n })\n .statics({ _documentType: 'section' as const })\n .attrs<{ direction?: string }>((props) => ({\n tag: 'div',\n _documentProps: { direction: props.direction ?? 'column' },\n }))\n\nexport default DocSection\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocSpacer = rocketstyle()({ name: 'DocSpacer', component: Element })\n .statics({ _documentType: 'spacer' as const })\n .attrs<{ height?: number }>((props) => ({\n tag: 'div',\n _documentProps: { height: props.height ?? 16 },\n }))\n\nexport default DocSpacer\n","import { Element } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\n/**\n * Tabular data primitive.\n *\n * The `columns`, `rows`, `headerStyle`, `striped`, `bordered`, and\n * `caption` props are document-export metadata — they belong in\n * `_documentProps` only and must NOT be forwarded to the rendered\n * `<table>` element. The `filter` option on `.attrs()` strips them\n * from the props that flow into the DOM.\n *\n * Why this matters: HTMLTableElement's `rows` property is a\n * read-only `HTMLCollection` of `<tr>` elements. If `rows` were\n * forwarded as a DOM attr, the runtime would call\n * `el.rows = [...]` and crash with\n * `TypeError: Cannot set property rows of [object Object] which has\n * only a getter`. Same family for `columns` (`HTMLTableColElement`'s\n * column collection on parent table). Filtering them at the\n * rocketstyle layer keeps the DOM render path clean.\n */\nconst DocTable = rocketstyle({\n dimensions: {\n variants: 'variant',\n },\n useBooleans: true,\n})({ name: 'DocTable', component: Element })\n .theme({\n fontSize: 14,\n borderColor: '#dddddd',\n })\n .statics({ _documentType: 'table' as const })\n .attrs<{\n columns?: unknown[]\n rows?: unknown[]\n headerStyle?: Record<string, unknown>\n striped?: boolean\n bordered?: boolean\n caption?: string\n }>(\n (props) => ({\n tag: 'table',\n _documentProps: {\n columns: props.columns ?? [],\n rows: props.rows ?? [],\n ...(props.headerStyle ? { headerStyle: props.headerStyle } : {}),\n ...(props.striped ? { striped: props.striped } : {}),\n ...(props.bordered ? { bordered: props.bordered } : {}),\n ...(props.caption ? { caption: props.caption } : {}),\n },\n }),\n {\n filter: ['columns', 'rows', 'headerStyle', 'striped', 'bordered', 'caption'],\n },\n )\n\nexport default DocTable\n","import { Text } from '@pyreon/elements'\nimport rocketstyle from '@pyreon/rocketstyle'\n\nconst DocText = rocketstyle({\n dimensions: {\n variants: 'variant',\n weights: 'weight',\n },\n useBooleans: true,\n})({ name: 'DocText', component: Text })\n .theme({\n color: '#333333',\n lineHeight: 1.5,\n marginBottom: 8,\n })\n .variants({\n body: { fontSize: 14 },\n caption: { fontSize: 12, color: '#666666' },\n label: { fontSize: 11, fontWeight: 'bold' },\n })\n .weights({\n normal: { fontWeight: 'normal' },\n bold: { fontWeight: 'bold' },\n })\n .statics({ _documentType: 'text' as const })\n .attrs(() => ({\n tag: 'p',\n _documentProps: {},\n }))\n\nexport default DocText\n","export const documentTheme = {\n colors: {\n primary: '#4f46e5',\n text: '#333333',\n textSecondary: '#666666',\n background: '#ffffff',\n border: '#dddddd',\n headerBg: '#1a1a2e',\n headerText: '#ffffff',\n stripedRow: '#f9f9f9',\n },\n fonts: {\n heading: 'system-ui, -apple-system, sans-serif',\n body: 'system-ui, -apple-system, sans-serif',\n mono: 'ui-monospace, monospace',\n },\n sizes: {\n h1: 32,\n h2: 24,\n h3: 20,\n h4: 18,\n h5: 16,\n h6: 14,\n body: 14,\n caption: 12,\n label: 11,\n },\n spacing: {\n xs: 4,\n sm: 8,\n md: 16,\n lg: 24,\n xl: 40,\n },\n}\n\nexport type DocumentTheme = typeof documentTheme\n","import type { DocNode, ExtractOptions } from '@pyreon/connector-document'\nimport { extractDocumentTree } from '@pyreon/connector-document'\n\nexport interface DocumentExportOptions extends ExtractOptions {\n /** Theme object to provide during extraction. */\n theme?: Record<string, unknown>\n /** Mode: 'light' or 'dark'. */\n mode?: 'light' | 'dark'\n}\n\nexport interface DocumentExport {\n /** Extract the DocNode tree from the template. */\n getDocNode: () => DocNode\n}\n\n/**\n * One-step helper: extract a DocNode tree from a template function.\n *\n * Equivalent to `createDocumentExport(templateFn).getDocNode()` but\n * without the wrapper-object indirection. Use this when you just\n * need the tree to feed into `@pyreon/document`'s `render()` or\n * `download()` — which is the only thing the wrapper object was\n * ever used for in practice.\n *\n * ```ts\n * import { extractDocNode } from '@pyreon/document-primitives'\n * import { download } from '@pyreon/document'\n *\n * function ResumeTemplate({ resume }: { resume: () => Resume }) {\n * return (\n * <DocDocument title={() => `${resume().name} — Resume`}>\n * <DocPage>...</DocPage>\n * </DocDocument>\n * )\n * }\n *\n * // Export click handler:\n * const tree = extractDocNode(() => <ResumeTemplate resume={store.resume} />)\n * await download(tree, 'resume.pdf')\n * ```\n *\n * The two-step `createDocumentExport` form is still exported for\n * backward compatibility and for callers that want to pass the\n * helper object around (e.g. to wrapper components that take a\n * `DocumentExport` instance). New code should prefer this one-step\n * form unless you specifically need the helper object.\n */\nexport function extractDocNode(\n templateFn: () => unknown,\n options: DocumentExportOptions = {},\n): DocNode {\n return extractDocumentTree(templateFn(), options)\n}\n\n/**\n * Create a document export helper from a template function.\n *\n * The template function should return a VNode tree built with\n * document primitives (DocHeading, DocText, DocTable, etc.).\n *\n * ```ts\n * const doc = createDocumentExport(() =>\n * DocDocument({ title: 'Report', children: [\n * DocHeading({ h1: true, children: 'Sales Report' }),\n * DocText({ children: 'Q4 summary.' }),\n * ]})\n * )\n *\n * const tree = doc.getDocNode()\n * // Pass to @pyreon/document's render() for any format\n * ```\n *\n * **Most consumers should use `extractDocNode(templateFn)` instead**\n * — it's the same operation in one call without the wrapper\n * object. `createDocumentExport` is kept for callers that want to\n * pass the helper object around.\n */\nexport function createDocumentExport(\n templateFn: () => unknown,\n options: DocumentExportOptions = {},\n): DocumentExport {\n const getDocNode = (): DocNode => extractDocNode(templateFn, options)\n return { getDocNode }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/document-primitives",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.12",
|
|
4
4
|
"description": "Rocketstyle document components — render in browser, export to 18 formats",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,25 +40,27 @@
|
|
|
40
40
|
"typecheck": "tsc --noEmit"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@pyreon/connector-document": "^0.12.
|
|
43
|
+
"@pyreon/connector-document": "^0.12.12"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@pyreon/core": "^0.12.
|
|
47
|
-
"@pyreon/elements": "^0.12.
|
|
48
|
-
"@pyreon/
|
|
49
|
-
"@pyreon/
|
|
46
|
+
"@pyreon/core": "^0.12.12",
|
|
47
|
+
"@pyreon/elements": "^0.12.12",
|
|
48
|
+
"@pyreon/reactivity": "^0.12.12",
|
|
49
|
+
"@pyreon/rocketstyle": "^0.12.12",
|
|
50
|
+
"@pyreon/runtime-dom": "^0.12.12",
|
|
51
|
+
"@pyreon/styler": "^0.12.12",
|
|
50
52
|
"@pyreon/test-utils": "^0.12.10",
|
|
51
|
-
"@pyreon/typescript": "^0.12.
|
|
52
|
-
"@pyreon/ui-core": "^0.12.
|
|
53
|
+
"@pyreon/typescript": "^0.12.12",
|
|
54
|
+
"@pyreon/ui-core": "^0.12.12",
|
|
53
55
|
"@vitus-labs/tools-rolldown": "^1.15.4"
|
|
54
56
|
},
|
|
55
57
|
"peerDependencies": {
|
|
56
|
-
"@pyreon/core": "^0.12.
|
|
57
|
-
"@pyreon/document": "^0.12.
|
|
58
|
-
"@pyreon/elements": "^0.12.
|
|
59
|
-
"@pyreon/rocketstyle": "^0.12.
|
|
60
|
-
"@pyreon/styler": "^0.12.
|
|
61
|
-
"@pyreon/ui-core": "^0.12.
|
|
58
|
+
"@pyreon/core": "^0.12.12",
|
|
59
|
+
"@pyreon/document": "^0.12.12",
|
|
60
|
+
"@pyreon/elements": "^0.12.12",
|
|
61
|
+
"@pyreon/rocketstyle": "^0.12.12",
|
|
62
|
+
"@pyreon/styler": "^0.12.12",
|
|
63
|
+
"@pyreon/ui-core": "^0.12.12"
|
|
62
64
|
},
|
|
63
65
|
"engines": {
|
|
64
66
|
"node": ">= 22"
|
package/src/DocumentPreview.ts
CHANGED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fine-grained reactivity tests for every document-primitive that
|
|
3
|
+
* accepts children or data props.
|
|
4
|
+
*
|
|
5
|
+
* **Why the `as any` casts**: rocketstyle's component prop type
|
|
6
|
+
* collapses children to `VNodeChildAtom` (the singular form) inside
|
|
7
|
+
* its child slot, even though `VNodeChild` already covers
|
|
8
|
+
* `VNodeChild[]`. This is a recurring DX paper-cut tracked as item
|
|
9
|
+
* R1 in the post-flow-PR catalog. The casts here aren't covering a
|
|
10
|
+
* runtime bug — they're a type-level workaround that lets us pass
|
|
11
|
+
* function children through `h(DocPrimitive, ...)` without
|
|
12
|
+
* fighting TS. When R1 lands, the casts can be removed.
|
|
13
|
+
*
|
|
14
|
+
* Proves the load-bearing claim of the resume-builder showcase: when
|
|
15
|
+
* a rocketstyle document primitive is given a function child like
|
|
16
|
+
* `<DocText>{() => signal()}</DocText>`, the function passes through
|
|
17
|
+
* rocketstyle untouched and is treated by Pyreon's runtime as a
|
|
18
|
+
* reactive children accessor. The text node patches in place when
|
|
19
|
+
* the signal changes — the parent component does NOT re-render.
|
|
20
|
+
*
|
|
21
|
+
* Without this guarantee, the resume builder's "single tree, two
|
|
22
|
+
* render targets" design degrades to a top-down re-render on every
|
|
23
|
+
* keystroke, defeating the entire reason to use document-primitives
|
|
24
|
+
* for the live preview.
|
|
25
|
+
*
|
|
26
|
+
* The tests are organised by what each primitive wraps:
|
|
27
|
+
*
|
|
28
|
+
* • TEXT-BASED — wraps `@pyreon/elements/Text`. The function child
|
|
29
|
+
* becomes a reactive text node. Tests assert that mutating the
|
|
30
|
+
* signal updates `container.textContent` AND that the parent
|
|
31
|
+
* component runs exactly once across multiple mutations.
|
|
32
|
+
*
|
|
33
|
+
* • ELEMENT/CONTAINER — wraps `@pyreon/elements/Element`. Function
|
|
34
|
+
* children inside the container are still reactive (same
|
|
35
|
+
* mechanism — rocketstyle passes children through unmodified).
|
|
36
|
+
* Tests assert mutation propagation through nested children.
|
|
37
|
+
*
|
|
38
|
+
* • LEAF — primitives with no children (Divider, Spacer, PageBreak,
|
|
39
|
+
* Image). These take props only. Tests verify that the primitive
|
|
40
|
+
* mounts cleanly and produces the right tag, with no children
|
|
41
|
+
* surface to be reactive against.
|
|
42
|
+
*
|
|
43
|
+
* • DATA-DRIVEN — DocTable receives `columns`/`rows` as attrs, not
|
|
44
|
+
* JSX children. Tests verify the primitive mounts with array
|
|
45
|
+
* props.
|
|
46
|
+
*/
|
|
47
|
+
import { h } from '@pyreon/core'
|
|
48
|
+
import { signal } from '@pyreon/reactivity'
|
|
49
|
+
import { initTestConfig, mountAndExpectOnce, mountReactive } from '@pyreon/test-utils'
|
|
50
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
51
|
+
|
|
52
|
+
import DocButton from '../primitives/DocButton'
|
|
53
|
+
import DocCode from '../primitives/DocCode'
|
|
54
|
+
import DocColumn from '../primitives/DocColumn'
|
|
55
|
+
import DocDivider from '../primitives/DocDivider'
|
|
56
|
+
import DocDocument from '../primitives/DocDocument'
|
|
57
|
+
import DocHeading from '../primitives/DocHeading'
|
|
58
|
+
import DocImage from '../primitives/DocImage'
|
|
59
|
+
import DocLink from '../primitives/DocLink'
|
|
60
|
+
import DocList from '../primitives/DocList'
|
|
61
|
+
import DocListItem from '../primitives/DocListItem'
|
|
62
|
+
import DocPage from '../primitives/DocPage'
|
|
63
|
+
import DocPageBreak from '../primitives/DocPageBreak'
|
|
64
|
+
import DocQuote from '../primitives/DocQuote'
|
|
65
|
+
import DocRow from '../primitives/DocRow'
|
|
66
|
+
import DocSection from '../primitives/DocSection'
|
|
67
|
+
import DocSpacer from '../primitives/DocSpacer'
|
|
68
|
+
import DocTable from '../primitives/DocTable'
|
|
69
|
+
import DocText from '../primitives/DocText'
|
|
70
|
+
|
|
71
|
+
let cleanup: () => void
|
|
72
|
+
beforeAll(() => {
|
|
73
|
+
cleanup = initTestConfig()
|
|
74
|
+
})
|
|
75
|
+
afterAll(() => cleanup())
|
|
76
|
+
|
|
77
|
+
// ─── Text-based primitives ──────────────────────────────────────────
|
|
78
|
+
//
|
|
79
|
+
// All wrap `@pyreon/elements/Text`. Function children render as text
|
|
80
|
+
// nodes that the runtime patches in place when their signal source
|
|
81
|
+
// changes. The "parent runs once" contract is asserted via
|
|
82
|
+
// mountAndExpectOnce.
|
|
83
|
+
|
|
84
|
+
describe('text-based primitives — function children patch text nodes', () => {
|
|
85
|
+
it('DocText patches its text node when the signal changes', () => {
|
|
86
|
+
const name = signal('Aisha')
|
|
87
|
+
const { container, cleanup: c } = mountReactive(h(DocText as any, null, () => name()))
|
|
88
|
+
expect(container.textContent).toBe('Aisha')
|
|
89
|
+
name.set('Marcus')
|
|
90
|
+
expect(container.textContent).toBe('Marcus')
|
|
91
|
+
name.set('Priya')
|
|
92
|
+
expect(container.textContent).toBe('Priya')
|
|
93
|
+
c()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('DocText parent component runs once across multiple mutations', () => {
|
|
97
|
+
const headline = signal('Senior Engineer')
|
|
98
|
+
const { container, parentCalls, cleanup: c } = mountAndExpectOnce(
|
|
99
|
+
() => h(DocText as any, null, () => headline()),
|
|
100
|
+
() => {
|
|
101
|
+
headline.set('Staff Engineer')
|
|
102
|
+
headline.set('Principal Engineer')
|
|
103
|
+
headline.set('Distinguished Engineer')
|
|
104
|
+
headline.set('Fellow')
|
|
105
|
+
headline.set('CTO')
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
expect(parentCalls()).toBe(1)
|
|
109
|
+
expect(container.textContent).toBe('CTO')
|
|
110
|
+
c()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('DocHeading patches its text node when the signal changes', () => {
|
|
114
|
+
const title = signal('Resume')
|
|
115
|
+
const { container, cleanup: c } = mountReactive(
|
|
116
|
+
h(DocHeading as any, { level: 'h1' }, () => title()),
|
|
117
|
+
)
|
|
118
|
+
expect(container.textContent).toBe('Resume')
|
|
119
|
+
title.set('Curriculum Vitae')
|
|
120
|
+
expect(container.textContent).toBe('Curriculum Vitae')
|
|
121
|
+
c()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('DocHeading parent runs once across mutations', () => {
|
|
125
|
+
const title = signal('A')
|
|
126
|
+
const { parentCalls, cleanup: c } = mountAndExpectOnce(
|
|
127
|
+
() => h(DocHeading as any, { level: 'h2' }, () => title()),
|
|
128
|
+
() => {
|
|
129
|
+
title.set('B')
|
|
130
|
+
title.set('C')
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
expect(parentCalls()).toBe(1)
|
|
134
|
+
c()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('DocCode patches its text node when the signal changes', () => {
|
|
138
|
+
const snippet = signal('const x = 1')
|
|
139
|
+
const { container, cleanup: c } = mountReactive(
|
|
140
|
+
h(DocCode as any, { language: 'typescript' }, () => snippet()),
|
|
141
|
+
)
|
|
142
|
+
expect(container.textContent).toBe('const x = 1')
|
|
143
|
+
snippet.set('const y = 2')
|
|
144
|
+
expect(container.textContent).toBe('const y = 2')
|
|
145
|
+
c()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('DocLink patches its text node when the signal changes', () => {
|
|
149
|
+
const label = signal('Click here')
|
|
150
|
+
const { container, cleanup: c } = mountReactive(
|
|
151
|
+
h(DocLink as any, { href: 'https://example.com' }, () => label()),
|
|
152
|
+
)
|
|
153
|
+
expect(container.textContent).toBe('Click here')
|
|
154
|
+
label.set('Visit us')
|
|
155
|
+
expect(container.textContent).toBe('Visit us')
|
|
156
|
+
c()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('DocListItem patches its text node when the signal changes', () => {
|
|
160
|
+
const item = signal('First task')
|
|
161
|
+
const { container, cleanup: c } = mountReactive(h(DocListItem as any, null, () => item()))
|
|
162
|
+
expect(container.textContent).toBe('First task')
|
|
163
|
+
item.set('Second task')
|
|
164
|
+
expect(container.textContent).toBe('Second task')
|
|
165
|
+
c()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('DocButton patches its text node when the signal changes', () => {
|
|
169
|
+
const cta = signal('Sign up')
|
|
170
|
+
const { container, cleanup: c } = mountReactive(
|
|
171
|
+
h(DocButton as any, { variant: 'primary' }, () => cta()),
|
|
172
|
+
)
|
|
173
|
+
expect(container.textContent).toBe('Sign up')
|
|
174
|
+
cta.set('Get started')
|
|
175
|
+
expect(container.textContent).toBe('Get started')
|
|
176
|
+
c()
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// ─── Element/container primitives ───────────────────────────────────
|
|
181
|
+
//
|
|
182
|
+
// All wrap `@pyreon/elements/Element`. Function children INSIDE the
|
|
183
|
+
// container (typically wrapped in a text-bearing primitive like
|
|
184
|
+
// DocText) are still reactive — rocketstyle passes children through
|
|
185
|
+
// unmodified, so the reactive accessor lands at whatever level the
|
|
186
|
+
// consumer puts it.
|
|
187
|
+
|
|
188
|
+
describe('container primitives — function children inside containers stay reactive', () => {
|
|
189
|
+
it('DocDocument propagates reactive children through to text leaves', () => {
|
|
190
|
+
const title = signal('Original')
|
|
191
|
+
const { container, cleanup: c } = mountReactive(
|
|
192
|
+
h(
|
|
193
|
+
DocDocument as any,
|
|
194
|
+
{ title: 'My Doc' },
|
|
195
|
+
h(DocText as any, null, () => title()),
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
expect(container.textContent).toBe('Original')
|
|
199
|
+
title.set('Updated')
|
|
200
|
+
expect(container.textContent).toBe('Updated')
|
|
201
|
+
c()
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('DocDocument parent runs once across nested signal mutations', () => {
|
|
205
|
+
const body = signal('a')
|
|
206
|
+
const { parentCalls, cleanup: c } = mountAndExpectOnce(
|
|
207
|
+
() =>
|
|
208
|
+
h(
|
|
209
|
+
DocDocument as any,
|
|
210
|
+
{ title: 'X' },
|
|
211
|
+
h(DocText as any, null, () => body()),
|
|
212
|
+
),
|
|
213
|
+
() => {
|
|
214
|
+
body.set('b')
|
|
215
|
+
body.set('c')
|
|
216
|
+
body.set('d')
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
expect(parentCalls()).toBe(1)
|
|
220
|
+
c()
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('DocPage propagates reactive children through to text leaves', () => {
|
|
224
|
+
const heading = signal('Page 1')
|
|
225
|
+
const { container, cleanup: c } = mountReactive(
|
|
226
|
+
h(
|
|
227
|
+
DocPage as any,
|
|
228
|
+
{ size: 'A4' },
|
|
229
|
+
h(DocHeading as any, { level: 'h1' }, () => heading()),
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
expect(container.textContent).toBe('Page 1')
|
|
233
|
+
heading.set('Page 2')
|
|
234
|
+
expect(container.textContent).toBe('Page 2')
|
|
235
|
+
c()
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('DocSection propagates reactive children', () => {
|
|
239
|
+
const text = signal('section A')
|
|
240
|
+
const { container, cleanup: c } = mountReactive(
|
|
241
|
+
h(DocSection as any, null, h(DocText as any, null, () => text())),
|
|
242
|
+
)
|
|
243
|
+
expect(container.textContent).toBe('section A')
|
|
244
|
+
text.set('section B')
|
|
245
|
+
expect(container.textContent).toBe('section B')
|
|
246
|
+
c()
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('DocRow propagates reactive children', () => {
|
|
250
|
+
const inline = signal('one')
|
|
251
|
+
const { container, cleanup: c } = mountReactive(
|
|
252
|
+
h(DocRow as any, null, h(DocText as any, null, () => inline())),
|
|
253
|
+
)
|
|
254
|
+
expect(container.textContent).toBe('one')
|
|
255
|
+
inline.set('two')
|
|
256
|
+
expect(container.textContent).toBe('two')
|
|
257
|
+
c()
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('DocColumn propagates reactive children', () => {
|
|
261
|
+
const col = signal('left')
|
|
262
|
+
const { container, cleanup: c } = mountReactive(
|
|
263
|
+
h(DocColumn as any, { width: '50%' }, h(DocText as any, null, () => col())),
|
|
264
|
+
)
|
|
265
|
+
expect(container.textContent).toBe('left')
|
|
266
|
+
col.set('right')
|
|
267
|
+
expect(container.textContent).toBe('right')
|
|
268
|
+
c()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('DocList propagates reactive children to nested DocListItem', () => {
|
|
272
|
+
const first = signal('Apple')
|
|
273
|
+
const { container, cleanup: c } = mountReactive(
|
|
274
|
+
h(
|
|
275
|
+
DocList as any,
|
|
276
|
+
null,
|
|
277
|
+
h(DocListItem as any, null, () => first()),
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
expect(container.textContent).toBe('Apple')
|
|
281
|
+
first.set('Banana')
|
|
282
|
+
expect(container.textContent).toBe('Banana')
|
|
283
|
+
c()
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('DocList parent runs once when ordered list items mutate', () => {
|
|
287
|
+
const item = signal('a')
|
|
288
|
+
const { parentCalls, cleanup: c } = mountAndExpectOnce(
|
|
289
|
+
() =>
|
|
290
|
+
h(
|
|
291
|
+
DocList as any,
|
|
292
|
+
{ ordered: true },
|
|
293
|
+
h(DocListItem as any, null, () => item()),
|
|
294
|
+
),
|
|
295
|
+
() => {
|
|
296
|
+
item.set('b')
|
|
297
|
+
item.set('c')
|
|
298
|
+
},
|
|
299
|
+
)
|
|
300
|
+
expect(parentCalls()).toBe(1)
|
|
301
|
+
c()
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('DocQuote propagates reactive children', () => {
|
|
305
|
+
const quote = signal('Hello')
|
|
306
|
+
const { container, cleanup: c } = mountReactive(
|
|
307
|
+
h(DocQuote as any, null, h(DocText as any, null, () => quote())),
|
|
308
|
+
)
|
|
309
|
+
expect(container.textContent).toBe('Hello')
|
|
310
|
+
quote.set('World')
|
|
311
|
+
expect(container.textContent).toBe('World')
|
|
312
|
+
c()
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// ─── Leaf primitives ────────────────────────────────────────────────
|
|
317
|
+
//
|
|
318
|
+
// No children, no reactive surface. The contract here is just "the
|
|
319
|
+
// primitive mounts cleanly without throwing." A consumer that drops
|
|
320
|
+
// these into a tree shouldn't need to worry about them — they're
|
|
321
|
+
// inert dividers / spacers / page breaks. Image is also leaf because
|
|
322
|
+
// its data lives in attrs (`src`, `alt`, etc.) not children.
|
|
323
|
+
|
|
324
|
+
describe('leaf primitives — mount cleanly with no children', () => {
|
|
325
|
+
it('DocDivider mounts and produces an hr tag', () => {
|
|
326
|
+
const { container, cleanup: c } = mountReactive(h(DocDivider as any, null))
|
|
327
|
+
expect(container.querySelector('hr')).not.toBeNull()
|
|
328
|
+
c()
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('DocSpacer mounts cleanly', () => {
|
|
332
|
+
const { container, cleanup: c } = mountReactive(h(DocSpacer as any, { height: 32 }))
|
|
333
|
+
expect(container.querySelector('div')).not.toBeNull()
|
|
334
|
+
c()
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('DocPageBreak mounts cleanly', () => {
|
|
338
|
+
const { container, cleanup: c } = mountReactive(h(DocPageBreak as any, null))
|
|
339
|
+
expect(container.querySelector('div')).not.toBeNull()
|
|
340
|
+
c()
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it('DocImage mounts and produces an img tag with the src attr', () => {
|
|
344
|
+
const { container, cleanup: c } = mountReactive(
|
|
345
|
+
h(DocImage as any, { src: 'https://example.com/x.png', alt: 'X' }),
|
|
346
|
+
)
|
|
347
|
+
const img = container.querySelector('img')
|
|
348
|
+
expect(img).not.toBeNull()
|
|
349
|
+
c()
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// ─── Data-driven primitives ─────────────────────────────────────────
|
|
354
|
+
//
|
|
355
|
+
// DocTable takes `columns`/`rows` as attrs rather than JSX children.
|
|
356
|
+
// The browser render is intentionally minimal — DocTable produces a
|
|
357
|
+
// bare `<table>` element with theme styling, and the data lives in
|
|
358
|
+
// `_documentProps` for the document-export pipeline to consume.
|
|
359
|
+
// There's no DOM-level "mutate signal → assert text" contract here
|
|
360
|
+
// because the data never reaches the DOM.
|
|
361
|
+
//
|
|
362
|
+
// What we DO assert: the primitive mounts cleanly. Before this test
|
|
363
|
+
// existed, DocTable's `rows` and `columns` props collided with
|
|
364
|
+
// HTMLTableElement's read-only DOM properties of the same name —
|
|
365
|
+
// the runtime crashed with `TypeError: Cannot set property rows of
|
|
366
|
+
// [object Object] which has only a getter` whenever any consumer
|
|
367
|
+
// tried to mount a table. The fix was a `filter` option on
|
|
368
|
+
// rocketstyle's `.attrs()` that strips export-only props from the
|
|
369
|
+
// DOM forwarding. See `DocTable.ts` for the inline rationale.
|
|
370
|
+
|
|
371
|
+
describe('data-driven primitives — DocTable mounts cleanly', () => {
|
|
372
|
+
it('mounts with columns and rows arrays', () => {
|
|
373
|
+
const { container, cleanup: c } = mountReactive(
|
|
374
|
+
h(DocTable as any, {
|
|
375
|
+
columns: [{ header: 'Name' }, { header: 'Age' }],
|
|
376
|
+
rows: [
|
|
377
|
+
['Alice', 30],
|
|
378
|
+
['Bob', 25],
|
|
379
|
+
],
|
|
380
|
+
}),
|
|
381
|
+
)
|
|
382
|
+
expect(container.querySelector('table')).not.toBeNull()
|
|
383
|
+
c()
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('mounts with empty arrays (regression: no crash on empty rows)', () => {
|
|
387
|
+
const { container, cleanup: c } = mountReactive(
|
|
388
|
+
h(DocTable as any, { columns: [], rows: [] }),
|
|
389
|
+
)
|
|
390
|
+
expect(container.querySelector('table')).not.toBeNull()
|
|
391
|
+
c()
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('does not forward columns/rows to the DOM (would crash table.rows is read-only)', () => {
|
|
395
|
+
// The fix: rocketstyle's `filter` option on `.attrs()` strips
|
|
396
|
+
// these props before they reach the DOM forwarding step. Without
|
|
397
|
+
// the filter, the runtime would call `tableEl.rows = [...]`
|
|
398
|
+
// which throws on HTMLTableElement.
|
|
399
|
+
//
|
|
400
|
+
// This test exists to make the regression unmissable: if anyone
|
|
401
|
+
// removes the filter option in DocTable.ts, this test (and the
|
|
402
|
+
// two above) will start crashing again with the exact same
|
|
403
|
+
// error message that originally surfaced the bug.
|
|
404
|
+
const { container, cleanup: c } = mountReactive(
|
|
405
|
+
h(DocTable as any, { columns: [{ header: 'X' }], rows: [['y']] }),
|
|
406
|
+
)
|
|
407
|
+
const table = container.querySelector('table') as HTMLTableElement | null
|
|
408
|
+
expect(table).not.toBeNull()
|
|
409
|
+
// The native `rows` is a read-only HTMLCollection of <tr>. We
|
|
410
|
+
// shouldn't have polluted it with our prop array. (If we had,
|
|
411
|
+
// mount would have already crashed above.)
|
|
412
|
+
expect(table?.rows.length).toBe(0)
|
|
413
|
+
c()
|
|
414
|
+
})
|
|
415
|
+
})
|