structured-render 0.0.3 → 0.0.5
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/dist/elements/vir-structured-render.element.d.ts +17 -1
- package/dist/elements/vir-structured-render.element.js +1 -1
- package/dist/render/browser-rendering.js +11 -1
- package/dist/render/render-html.js +41 -18
- package/dist/render/render-pdf.js +58 -5
- package/dist/ui/elements/vir-app.element.d.ts +1 -0
- package/dist/ui/elements/vir-app.element.js +9 -0
- package/package.json +2 -2
|
@@ -10,8 +10,24 @@ import { type RenderHtmlOptions, type RenderInput } from '../render/render-types
|
|
|
10
10
|
export declare const VirStructuredRender: import("element-vir").DeclarativeElementDefinition<"vir-structured-render", {
|
|
11
11
|
data: Readonly<RenderInput>;
|
|
12
12
|
options?: Readonly<PartialWithUndefined<RenderHtmlOptions & {
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* If `true`, smaller, tablet-compatible styles are used.
|
|
15
|
+
*
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
isTabletSize: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* If `true`, smaller, phone-compatible styles are used.
|
|
21
|
+
*
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
14
24
|
isPhoneSize: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* If `true`, all sections will automatically be expanded at first.
|
|
27
|
+
*
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
expandAllSections: boolean;
|
|
15
31
|
}>> | undefined;
|
|
16
32
|
}, {
|
|
17
33
|
currentlyExpanded: Record<string, boolean>;
|
|
@@ -29,7 +29,7 @@ export const VirStructuredRender = defineElement()({
|
|
|
29
29
|
},
|
|
30
30
|
hostClasses: {
|
|
31
31
|
'vir-structured-render-phone-size': ({ inputs }) => !!inputs.options?.isPhoneSize,
|
|
32
|
-
'vir-structured-render-tablet-size': ({ inputs }) => !!inputs.options?.
|
|
32
|
+
'vir-structured-render-tablet-size': ({ inputs }) => !!inputs.options?.isTabletSize,
|
|
33
33
|
},
|
|
34
34
|
styles: ({ cssVars, hostClasses }) => css `
|
|
35
35
|
:host {
|
|
@@ -132,7 +132,17 @@ export async function renderInBrowser(structuredRenderData, { fileName, outputTy
|
|
|
132
132
|
const instance = htmlToPdf().set(html2PdfOptions).from(canvas);
|
|
133
133
|
if (outputType.pdf) {
|
|
134
134
|
if (outputType.pdf === OutputPdfType.Download) {
|
|
135
|
-
|
|
135
|
+
const pdfBlob = (await instance.outputPdf(OutputPdfType.Blob));
|
|
136
|
+
const blobUrl = URL.createObjectURL(pdfBlob);
|
|
137
|
+
const anchor = globalThis.document.createElement('a');
|
|
138
|
+
anchor.href = blobUrl;
|
|
139
|
+
anchor.download = fileName.endsWith('.pdf') ? fileName : `${fileName}.pdf`;
|
|
140
|
+
anchor.style.display = 'none';
|
|
141
|
+
globalThis.document.body.append(anchor);
|
|
142
|
+
anchor.click();
|
|
143
|
+
anchor.remove();
|
|
144
|
+
URL.revokeObjectURL(blobUrl);
|
|
145
|
+
return;
|
|
136
146
|
}
|
|
137
147
|
else {
|
|
138
148
|
return await instance.outputPdf(outputType.pdf, {
|
|
@@ -371,25 +371,48 @@ function structuredRenderToHtmlArray(data, options, keyChain) {
|
|
|
371
371
|
const sectionTitle = ('sectionTitle' in data && keyChain.length > 0 && data.sectionTitle) || undefined;
|
|
372
372
|
const sectionTemplate = htmlRenderers[data.type](data, options, keyChain);
|
|
373
373
|
const sources = ('sources' in data && data.sources) || undefined;
|
|
374
|
+
const sectionContent = html `
|
|
375
|
+
<div
|
|
376
|
+
class=${classMap({
|
|
377
|
+
'section-wrapper': true,
|
|
378
|
+
'top-section-wrapper': keyChain.length === 0,
|
|
379
|
+
[structuredRenderSectionHtmlNames[data.type]]: true,
|
|
380
|
+
})}
|
|
381
|
+
${testId(structuredRenderSectionHtmlTestId)}
|
|
382
|
+
${testId(structuredRenderSectionHtmlNames[data.type])}
|
|
383
|
+
>
|
|
384
|
+
${createSourceWrapper(sectionTemplate, options, keyChain, sources)}
|
|
385
|
+
</div>
|
|
386
|
+
`;
|
|
387
|
+
if (sectionTitle) {
|
|
388
|
+
const sectionKeyChain = [
|
|
389
|
+
...keyChain,
|
|
390
|
+
'section-collapsible',
|
|
391
|
+
];
|
|
392
|
+
const sectionKey = makeChainKey(sectionKeyChain);
|
|
393
|
+
const isSectionExpanded = !!options.currentlyExpanded[sectionKey];
|
|
394
|
+
return [
|
|
395
|
+
html `
|
|
396
|
+
<${ViraCollapsibleWrapper.assign({
|
|
397
|
+
expanded: isSectionExpanded,
|
|
398
|
+
})}
|
|
399
|
+
class="section-collapsible-wrapper"
|
|
400
|
+
${listen(ViraCollapsibleWrapper.events.expandChange, (event) => {
|
|
401
|
+
const eventTarget = extractEventTarget(event, HTMLElement);
|
|
402
|
+
eventTarget.dispatchEvent(new SourceExpansionEvent({
|
|
403
|
+
expanded: event.detail,
|
|
404
|
+
key: sectionKey,
|
|
405
|
+
}));
|
|
406
|
+
})}
|
|
407
|
+
>
|
|
408
|
+
<h3 slot=${ViraCollapsibleWrapper.slotNames.header}>${sectionTitle}</h3>
|
|
409
|
+
${sectionContent}
|
|
410
|
+
</${ViraCollapsibleWrapper}>
|
|
411
|
+
`,
|
|
412
|
+
];
|
|
413
|
+
}
|
|
374
414
|
return [
|
|
375
|
-
|
|
376
|
-
? html `
|
|
377
|
-
<h3>${sectionTitle}</h3>
|
|
378
|
-
`
|
|
379
|
-
: undefined,
|
|
380
|
-
html `
|
|
381
|
-
<div
|
|
382
|
-
class=${classMap({
|
|
383
|
-
'section-wrapper': true,
|
|
384
|
-
'top-section-wrapper': keyChain.length === 0,
|
|
385
|
-
[structuredRenderSectionHtmlNames[data.type]]: true,
|
|
386
|
-
})}
|
|
387
|
-
${testId(structuredRenderSectionHtmlTestId)}
|
|
388
|
-
${testId(structuredRenderSectionHtmlNames[data.type])}
|
|
389
|
-
>
|
|
390
|
-
${createSourceWrapper(sectionTemplate, options, keyChain, sources)}
|
|
391
|
-
</div>
|
|
392
|
-
`,
|
|
415
|
+
sectionContent,
|
|
393
416
|
];
|
|
394
417
|
}
|
|
395
418
|
else if ('sections' in data) {
|
|
@@ -50,16 +50,69 @@ export async function printPdf(renderInput, params) {
|
|
|
50
50
|
...params,
|
|
51
51
|
}));
|
|
52
52
|
const blobUrl = URL.createObjectURL(pdfBlob);
|
|
53
|
+
/**
|
|
54
|
+
* Firefox uses PDF.js to render PDFs in iframes, which doesn't support printing via
|
|
55
|
+
* `contentWindow.print()` on blob URLs. Open a new window instead so Firefox's native PDF
|
|
56
|
+
* viewer handles it.
|
|
57
|
+
*/
|
|
58
|
+
if (navigator.userAgent.toLowerCase().includes('firefox')) {
|
|
59
|
+
const printWindow = globalThis.window.open(blobUrl);
|
|
60
|
+
if (!printWindow) {
|
|
61
|
+
URL.revokeObjectURL(blobUrl);
|
|
62
|
+
throw new Error('Failed to open print window. Check your popup blocker settings.');
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
53
66
|
const printFrame = globalThis.document.createElement('iframe');
|
|
54
|
-
printFrame.style.
|
|
67
|
+
printFrame.style.position = 'fixed';
|
|
68
|
+
printFrame.style.left = '-10000px';
|
|
69
|
+
printFrame.style.top = '0';
|
|
70
|
+
printFrame.style.width = '1px';
|
|
71
|
+
printFrame.style.height = '1px';
|
|
72
|
+
printFrame.style.border = 'none';
|
|
73
|
+
printFrame.style.opacity = '0';
|
|
55
74
|
printFrame.src = blobUrl;
|
|
56
75
|
globalThis.document.body.append(printFrame);
|
|
57
76
|
return new Promise((resolve) => {
|
|
58
77
|
printFrame.onload = () => {
|
|
59
|
-
printFrame.contentWindow
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
const contentWindow = printFrame.contentWindow;
|
|
79
|
+
if (!contentWindow) {
|
|
80
|
+
URL.revokeObjectURL(blobUrl);
|
|
81
|
+
printFrame.remove();
|
|
82
|
+
resolve();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
function cleanup() {
|
|
86
|
+
URL.revokeObjectURL(blobUrl);
|
|
87
|
+
printFrame.remove();
|
|
88
|
+
resolve();
|
|
89
|
+
}
|
|
90
|
+
let resolved = false;
|
|
91
|
+
function resolveOnce() {
|
|
92
|
+
if (resolved) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
resolved = true;
|
|
96
|
+
globalThis.window.removeEventListener('focus', focusFallback);
|
|
97
|
+
cleanup();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Fallback: when the print dialog closes (print or cancel), focus returns to the main
|
|
101
|
+
* window. Some browsers don't fire `afterprint` on the iframe's contentWindow when the
|
|
102
|
+
* user cancels.
|
|
103
|
+
*/
|
|
104
|
+
function focusFallback() {
|
|
105
|
+
resolveOnce();
|
|
106
|
+
}
|
|
107
|
+
contentWindow.addEventListener('afterprint', () => {
|
|
108
|
+
resolveOnce();
|
|
109
|
+
}, {
|
|
110
|
+
once: true,
|
|
111
|
+
});
|
|
112
|
+
globalThis.window.addEventListener('focus', focusFallback, {
|
|
113
|
+
once: true,
|
|
114
|
+
});
|
|
115
|
+
contentWindow.print();
|
|
63
116
|
};
|
|
64
117
|
});
|
|
65
118
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VirApp: import("element-vir").DeclarativeElementDefinition<"vir-app", {}, {}, {}, "vir-app-", "vir-app-", readonly [], readonly []>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "structured-render",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "A library for safely rendering arbitrary data generated from any source.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"render",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"object-shape-tester": "^6.11.0",
|
|
53
53
|
"theme-vir": "^28.22.0",
|
|
54
54
|
"type-fest": "^5.4.4",
|
|
55
|
-
"vira": "^29.
|
|
55
|
+
"vira": "^29.8.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@augment-vir/test": "^31.67.0",
|