@deckio/deck-engine 1.7.7 → 1.7.8
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/components/Navigation.jsx +1 -1
- package/components/exportDeckPdf.js +87 -88
- package/package.json +3 -3
|
@@ -121,7 +121,7 @@ export default function Navigation({ pdfPath = null, pdfLabel = 'Deck PDF' }) {
|
|
|
121
121
|
<path d="M9 15l3 3 3-3" />
|
|
122
122
|
<path d="M8 10h8" />
|
|
123
123
|
</svg>
|
|
124
|
-
<span className={styles.exportLabel}>{exportStatus}</span>
|
|
124
|
+
<span className={styles.exportLabel}>{isExporting ? exportStatus : '⬇ PDF'}</span>
|
|
125
125
|
</button>
|
|
126
126
|
)}
|
|
127
127
|
|
|
@@ -1,57 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Export deck slides to PDF — direct download, no dialogs.
|
|
3
|
+
*
|
|
4
|
+
* Uses modern-screenshot (SVG foreignObject) + jspdf.
|
|
5
|
+
* The browser's own renderer handles all CSS natively:
|
|
6
|
+
* - background-clip: text ✅ (explicit fix in modern-screenshot)
|
|
7
|
+
* - filter: blur() ✅ (native foreignObject rendering)
|
|
8
|
+
* - gradients, shadows ✅
|
|
9
|
+
* - animations paused before capture
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const PAGE_W = 1920
|
|
13
|
+
const PAGE_H = 1080
|
|
14
|
+
const SETTLE_MS = 600
|
|
15
|
+
|
|
16
|
+
const wait = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
10
17
|
|
|
11
18
|
async function waitForPaint() {
|
|
12
|
-
await new Promise((
|
|
13
|
-
await new Promise((
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function waitForFonts() {
|
|
17
|
-
if (document.fonts?.ready) {
|
|
18
|
-
await document.fonts.ready
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function sanitizeFileName(value) {
|
|
23
|
-
return String(value || 'deck')
|
|
24
|
-
.trim()
|
|
25
|
-
.toLowerCase()
|
|
26
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
27
|
-
.replace(/^-|-$/g, '') || 'deck'
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getDeckElement() {
|
|
31
|
-
return document.querySelector('.deck')
|
|
19
|
+
await new Promise((r) => requestAnimationFrame(() => r()))
|
|
20
|
+
await new Promise((r) => requestAnimationFrame(() => r()))
|
|
32
21
|
}
|
|
33
22
|
|
|
34
|
-
function
|
|
35
|
-
return
|
|
23
|
+
function sanitize(v) {
|
|
24
|
+
return String(v || 'deck').trim().toLowerCase()
|
|
25
|
+
.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'deck'
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
function
|
|
39
|
-
const
|
|
28
|
+
function buildFileName({ project, selectedCustomer }) {
|
|
29
|
+
const base = selectedCustomer
|
|
40
30
|
? `${selectedCustomer} ${document.title || project || 'deck'}`
|
|
41
31
|
: document.title || project || 'deck'
|
|
42
|
-
|
|
43
|
-
return `${sanitizeFileName(baseName)}.pdf`
|
|
32
|
+
return `${sanitize(base)}.pdf`
|
|
44
33
|
}
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Pause animations on a slide for deterministic capture. Returns restore fn.
|
|
37
|
+
*/
|
|
38
|
+
function pauseAnimations(slide) {
|
|
39
|
+
const undo = []
|
|
40
|
+
const pause = (el) => {
|
|
41
|
+
const orig = el.style.animationPlayState
|
|
42
|
+
el.style.animationPlayState = 'paused'
|
|
43
|
+
undo.push(() => { el.style.animationPlayState = orig })
|
|
44
|
+
}
|
|
45
|
+
pause(slide)
|
|
46
|
+
slide.querySelectorAll('*').forEach(pause)
|
|
47
|
+
return () => { for (let i = undo.length - 1; i >= 0; i--) undo[i]() }
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
export async function exportDeckPdf({
|
|
@@ -62,73 +55,79 @@ export async function exportDeckPdf({
|
|
|
62
55
|
totalSlides,
|
|
63
56
|
onProgress,
|
|
64
57
|
}) {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
58
|
+
const deck = document.querySelector('.deck')
|
|
59
|
+
const slides = Array.from(deck?.querySelectorAll('.slide') || [])
|
|
60
|
+
if (!deck || slides.length === 0) throw new Error('No slides found')
|
|
67
61
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const [{ default: html2canvas }, { jsPDF }] = await Promise.all([
|
|
73
|
-
import('html2canvas'),
|
|
62
|
+
// Dynamic imports — tree-shaken, only loaded on export
|
|
63
|
+
const [{ domToPng }, { jsPDF }] = await Promise.all([
|
|
64
|
+
import('modern-screenshot'),
|
|
74
65
|
import('jspdf'),
|
|
75
66
|
])
|
|
76
67
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const backgroundColor = getComputedStyle(document.documentElement)
|
|
80
|
-
.getPropertyValue('--bg-deep')
|
|
81
|
-
.trim() || '#080b10'
|
|
68
|
+
const bg = getComputedStyle(document.documentElement)
|
|
69
|
+
.getPropertyValue('--bg-deep').trim() || '#080b10'
|
|
82
70
|
const scale = Math.min(window.devicePixelRatio || 1, 2)
|
|
71
|
+
|
|
83
72
|
const pdf = new jsPDF({
|
|
84
73
|
orientation: 'landscape',
|
|
85
74
|
unit: 'px',
|
|
86
|
-
format: [
|
|
75
|
+
format: [PAGE_W, PAGE_H],
|
|
87
76
|
compress: true,
|
|
88
77
|
hotfixes: ['px_scaling'],
|
|
89
78
|
})
|
|
90
79
|
|
|
91
|
-
|
|
80
|
+
if (document.fonts?.ready) await document.fonts.ready
|
|
92
81
|
|
|
93
82
|
try {
|
|
94
|
-
for (let
|
|
95
|
-
onProgress?.({ current:
|
|
96
|
-
goTo(
|
|
83
|
+
for (let i = 0; i < totalSlides; i++) {
|
|
84
|
+
onProgress?.({ current: i + 1, total: totalSlides })
|
|
85
|
+
goTo(i)
|
|
97
86
|
await waitForPaint()
|
|
98
|
-
await wait(
|
|
87
|
+
await wait(SETTLE_MS)
|
|
99
88
|
|
|
100
|
-
const
|
|
101
|
-
if (!
|
|
102
|
-
|
|
103
|
-
|
|
89
|
+
const active = document.querySelector('.slide.active') || slides[i]
|
|
90
|
+
if (!active) throw new Error(`Slide ${i + 1} not found`)
|
|
91
|
+
|
|
92
|
+
const restore = pauseAnimations(active)
|
|
93
|
+
await waitForPaint()
|
|
104
94
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
let dataUrl
|
|
96
|
+
try {
|
|
97
|
+
dataUrl = await domToPng(active, {
|
|
98
|
+
width: active.clientWidth || PAGE_W,
|
|
99
|
+
height: active.clientHeight || PAGE_H,
|
|
100
|
+
backgroundColor: bg,
|
|
101
|
+
scale,
|
|
102
|
+
style: {
|
|
103
|
+
// Ensure the captured element is visible and static
|
|
104
|
+
opacity: '1',
|
|
105
|
+
transform: 'none',
|
|
106
|
+
transition: 'none',
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
} finally {
|
|
110
|
+
restore()
|
|
120
111
|
}
|
|
121
112
|
|
|
122
|
-
|
|
113
|
+
if (i > 0) pdf.addPage([PAGE_W, PAGE_H], 'landscape')
|
|
114
|
+
pdf.addImage(dataUrl, 'PNG', 0, 0, PAGE_W, PAGE_H, undefined, 'FAST')
|
|
123
115
|
}
|
|
124
116
|
} finally {
|
|
125
117
|
goTo(current)
|
|
126
118
|
await waitForPaint()
|
|
127
119
|
}
|
|
128
120
|
|
|
121
|
+
// Direct download — no dialog
|
|
129
122
|
const blob = pdf.output('blob')
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
123
|
+
const url = URL.createObjectURL(blob)
|
|
124
|
+
const a = document.createElement('a')
|
|
125
|
+
a.href = url
|
|
126
|
+
a.download = buildFileName({ project, selectedCustomer })
|
|
127
|
+
document.body.appendChild(a)
|
|
128
|
+
a.click()
|
|
129
|
+
a.remove()
|
|
130
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
|
131
|
+
|
|
132
|
+
return { fileName: a.download }
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deckio/deck-engine",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"**/*.css"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
38
|
+
"jspdf": "^3.0.3",
|
|
39
|
+
"modern-screenshot": "^4.6.8"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"react": "^19.1.0",
|