@nuxt/nitro-server 4.2.2 → 4.3.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.
Files changed (67) hide show
  1. package/README.md +4 -2
  2. package/dist/index.d.mts +280 -78
  3. package/dist/index.mjs +696 -745
  4. package/dist/runtime/handlers/{error.d.ts → error.d.mts} +1 -1
  5. package/dist/runtime/handlers/error.mjs +77 -0
  6. package/dist/runtime/handlers/island.d.mts +2 -0
  7. package/dist/runtime/handlers/island.mjs +120 -0
  8. package/dist/runtime/handlers/renderer.d.mts +2 -0
  9. package/dist/runtime/handlers/renderer.mjs +305 -0
  10. package/dist/runtime/middleware/no-ssr.d.mts +2 -0
  11. package/dist/runtime/middleware/no-ssr.mjs +7 -0
  12. package/dist/runtime/plugins/dev-server-logs.d.mts +2 -0
  13. package/dist/runtime/plugins/dev-server-logs.mjs +94 -0
  14. package/dist/runtime/templates/error-500.d.mts +2 -0
  15. package/dist/runtime/templates/error-500.mjs +15 -0
  16. package/dist/runtime/utils/app-config.d.mts +3 -0
  17. package/dist/runtime/utils/app-config.mjs +31 -0
  18. package/dist/runtime/utils/cache.d.mts +5 -0
  19. package/dist/runtime/utils/cache.mjs +20 -0
  20. package/dist/runtime/utils/config.d.mts +1 -0
  21. package/dist/runtime/utils/{dev.d.ts → dev.d.mts} +1 -1
  22. package/dist/runtime/utils/dev.mjs +985 -0
  23. package/dist/runtime/utils/error.d.mts +6 -0
  24. package/dist/runtime/utils/error.mjs +15 -0
  25. package/dist/runtime/utils/paths.mjs +19 -0
  26. package/dist/runtime/utils/renderer/{app.d.ts → app.d.mts} +4 -4
  27. package/dist/runtime/utils/renderer/app.mjs +39 -0
  28. package/dist/runtime/utils/renderer/build-files.d.mts +18 -0
  29. package/dist/runtime/utils/renderer/build-files.mjs +100 -0
  30. package/dist/runtime/utils/renderer/{inline-styles.d.ts → inline-styles.d.mts} +1 -1
  31. package/dist/runtime/utils/renderer/inline-styles.mjs +13 -0
  32. package/dist/runtime/utils/renderer/{islands.d.ts → islands.d.mts} +5 -5
  33. package/dist/runtime/utils/renderer/islands.mjs +87 -0
  34. package/dist/runtime/utils/renderer/payload.d.mts +24 -0
  35. package/dist/runtime/utils/renderer/payload.mjs +64 -0
  36. package/package.json +19 -17
  37. package/dist/index.d.ts +0 -85
  38. package/dist/runtime/handlers/error.js +0 -63
  39. package/dist/runtime/handlers/island.d.ts +0 -4
  40. package/dist/runtime/handlers/island.js +0 -100
  41. package/dist/runtime/handlers/renderer.d.ts +0 -8
  42. package/dist/runtime/handlers/renderer.js +0 -238
  43. package/dist/runtime/middleware/no-ssr.d.ts +0 -2
  44. package/dist/runtime/middleware/no-ssr.js +0 -7
  45. package/dist/runtime/plugins/dev-server-logs.d.ts +0 -3
  46. package/dist/runtime/plugins/dev-server-logs.js +0 -82
  47. package/dist/runtime/templates/error-500.d.ts +0 -2
  48. package/dist/runtime/templates/error-500.js +0 -6
  49. package/dist/runtime/utils/app-config.d.ts +0 -2
  50. package/dist/runtime/utils/app-config.js +0 -25
  51. package/dist/runtime/utils/cache-driver.d.ts +0 -6
  52. package/dist/runtime/utils/cache.d.ts +0 -8
  53. package/dist/runtime/utils/cache.js +0 -19
  54. package/dist/runtime/utils/config.d.ts +0 -1
  55. package/dist/runtime/utils/dev.js +0 -334
  56. package/dist/runtime/utils/error.d.ts +0 -6
  57. package/dist/runtime/utils/error.js +0 -11
  58. package/dist/runtime/utils/paths.js +0 -16
  59. package/dist/runtime/utils/renderer/app.js +0 -33
  60. package/dist/runtime/utils/renderer/build-files.d.ts +0 -22
  61. package/dist/runtime/utils/renderer/build-files.js +0 -85
  62. package/dist/runtime/utils/renderer/inline-styles.js +0 -13
  63. package/dist/runtime/utils/renderer/islands.js +0 -82
  64. package/dist/runtime/utils/renderer/payload.d.ts +0 -37
  65. package/dist/runtime/utils/renderer/payload.js +0 -67
  66. /package/dist/runtime/utils/{config.js → config.mjs} +0 -0
  67. /package/dist/runtime/utils/{paths.d.ts → paths.d.mts} +0 -0
@@ -0,0 +1,985 @@
1
+ const iframeStorageBridge = (nonce) => `
2
+ (function () {
3
+ const NONCE = ${JSON.stringify(nonce)};
4
+ const memoryStore = Object.create(null);
5
+
6
+ const post = (type, payload) => {
7
+ window.parent.postMessage({ type, nonce: NONCE, ...payload }, '*');
8
+ };
9
+
10
+ const isValid = (data) => data && data.nonce === NONCE;
11
+
12
+ const mockStorage = {
13
+ getItem(key) {
14
+ return Object.hasOwn(memoryStore, key)
15
+ ? memoryStore[key]
16
+ : null;
17
+ },
18
+ setItem(key, value) {
19
+ const v = String(value);
20
+ memoryStore[key] = v;
21
+ post('storage-set', { key, value: v });
22
+ },
23
+ removeItem(key) {
24
+ delete memoryStore[key];
25
+ post('storage-remove', { key });
26
+ },
27
+ clear() {
28
+ for (const key of Object.keys(memoryStore))
29
+ delete memoryStore[key];
30
+ post('storage-clear', {});
31
+ },
32
+ key(index) {
33
+ const keys = Object.keys(memoryStore);
34
+ return keys[index] ?? null;
35
+ },
36
+ get length() {
37
+ return Object.keys(memoryStore).length;
38
+ }
39
+ };
40
+
41
+ const defineLocalStorage = () => {
42
+ try {
43
+ Object.defineProperty(window, 'localStorage', {
44
+ value: mockStorage,
45
+ writable: false,
46
+ configurable: true
47
+ });
48
+ } catch {
49
+ window.localStorage = mockStorage;
50
+ }
51
+ };
52
+
53
+ defineLocalStorage();
54
+
55
+ window.addEventListener('message', (event) => {
56
+ const data = event.data;
57
+ if (!isValid(data) || data.type !== 'storage-sync-data') return;
58
+
59
+ const incoming = data.data || {};
60
+ for (const key of Object.keys(incoming))
61
+ memoryStore[key] = incoming[key];
62
+
63
+ if (typeof window.initTheme === 'function')
64
+ window.initTheme();
65
+ window.dispatchEvent(new Event('storage-ready'));
66
+ });
67
+
68
+ // Clipboard API is unavailable in data: URL iframe, so we use postMessage
69
+ document.addEventListener('DOMContentLoaded', function() {
70
+ window.copyErrorMessage = function(button) {
71
+ post('clipboard-copy', { text: button.dataset.errorText });
72
+ button.classList.add('copied');
73
+ setTimeout(function() { button.classList.remove('copied'); }, 2000);
74
+ };
75
+ });
76
+
77
+ post('storage-sync-request', {});
78
+ })();
79
+ `;
80
+ const parentStorageBridge = (nonce) => `
81
+ (function () {
82
+ const host = document.querySelector('nuxt-error-overlay');
83
+ if (!host) return;
84
+
85
+ const NONCE = ${JSON.stringify(nonce)};
86
+ const isValid = (data) => data && data.nonce === NONCE;
87
+
88
+ // Handle clipboard copy from iframe
89
+ window.addEventListener('message', function(e) {
90
+ if (isValid(e) && e.data.type === 'clipboard-copy') {
91
+ navigator.clipboard.writeText(e.data.text).catch(function() {});
92
+ }
93
+ });
94
+
95
+ const collectLocalStorage = () => {
96
+ const all = {};
97
+ for (let i = 0; i < localStorage.length; i++) {
98
+ const k = localStorage.key(i);
99
+ if (k != null) all[k] = localStorage.getItem(k);
100
+ }
101
+ return all;
102
+ };
103
+
104
+ const attachWhenReady = () => {
105
+ const root = host.shadowRoot;
106
+ if (!root)
107
+ return false;
108
+ const iframe = root.getElementById('frame');
109
+ if (!iframe || !iframe.contentWindow)
110
+ return false;
111
+
112
+ const handlers = {
113
+ 'storage-set': (d) => localStorage.setItem(d.key, d.value),
114
+ 'storage-remove': (d) => localStorage.removeItem(d.key),
115
+ 'storage-clear': () => localStorage.clear(),
116
+ 'storage-sync-request': () => {
117
+ iframe.contentWindow.postMessage({
118
+ type: 'storage-sync-data',
119
+ data: collectLocalStorage(),
120
+ nonce: NONCE
121
+ }, '*');
122
+ }
123
+ };
124
+
125
+ window.addEventListener('message', (event) => {
126
+ const data = event.data;
127
+ if (!isValid(data)) return;
128
+ const fn = handlers[data.type];
129
+ if (fn) fn(data);
130
+ });
131
+
132
+ return true;
133
+ };
134
+
135
+ if (attachWhenReady())
136
+ return;
137
+
138
+ const obs = new MutationObserver(() => {
139
+ if (attachWhenReady())
140
+ obs.disconnect();
141
+ });
142
+
143
+ obs.observe(host, { childList: true, subtree: true });
144
+ })();
145
+ `;
146
+ const errorCSS = `
147
+ :host {
148
+ --preview-width: 240px;
149
+ --preview-height: 180px;
150
+ --base-width: 1200px;
151
+ --base-height: 900px;
152
+ --z-base: 999999998;
153
+ --error-pip-left: auto;
154
+ --error-pip-top: auto;
155
+ --error-pip-right: 5px;
156
+ --error-pip-bottom: 5px;
157
+ --error-pip-origin: bottom right;
158
+ --app-preview-left: auto;
159
+ --app-preview-top: auto;
160
+ --app-preview-right: 5px;
161
+ --app-preview-bottom: 5px;
162
+ all: initial;
163
+ display: contents;
164
+ }
165
+ .sr-only {
166
+ position: absolute;
167
+ width: 1px;
168
+ height: 1px;
169
+ padding: 0;
170
+ margin: -1px;
171
+ overflow: hidden;
172
+ clip: rect(0, 0, 0, 0);
173
+ white-space: nowrap;
174
+ border-width: 0;
175
+ }
176
+ #frame {
177
+ position: fixed;
178
+ left: 0;
179
+ top: 0;
180
+ width: 100vw;
181
+ height: 100vh;
182
+ border: none;
183
+ z-index: var(--z-base);
184
+ }
185
+ #frame[inert] {
186
+ left: var(--error-pip-left);
187
+ top: var(--error-pip-top);
188
+ right: var(--error-pip-right);
189
+ bottom: var(--error-pip-bottom);
190
+ width: var(--base-width);
191
+ height: var(--base-height);
192
+ transform: scale(calc(240 / 1200));
193
+ transform-origin: var(--error-pip-origin);
194
+ overflow: hidden;
195
+ border-radius: calc(1200 * 8px / 240);
196
+ }
197
+ #preview {
198
+ position: fixed;
199
+ left: var(--app-preview-left);
200
+ top: var(--app-preview-top);
201
+ right: var(--app-preview-right);
202
+ bottom: var(--app-preview-bottom);
203
+ width: var(--preview-width);
204
+ height: var(--preview-height);
205
+ overflow: hidden;
206
+ border-radius: 6px;
207
+ pointer-events: none;
208
+ z-index: var(--z-base);
209
+ background: white;
210
+ display: none;
211
+ }
212
+ #preview iframe {
213
+ transform-origin: var(--error-pip-origin);
214
+ }
215
+ #frame:not([inert]) + #preview {
216
+ display: block;
217
+ }
218
+ #toggle {
219
+ position: fixed;
220
+ left: var(--app-preview-left);
221
+ top: var(--app-preview-top);
222
+ right: calc(var(--app-preview-right) - 3px);
223
+ bottom: calc(var(--app-preview-bottom) - 3px);
224
+ width: var(--preview-width);
225
+ height: var(--preview-height);
226
+ background: none;
227
+ border: 3px solid #00DC82;
228
+ border-radius: 8px;
229
+ cursor: pointer;
230
+ opacity: 0.8;
231
+ transition: opacity 0.2s, box-shadow 0.2s;
232
+ z-index: calc(var(--z-base) + 1);
233
+ display: flex;
234
+ align-items: center;
235
+ justify-content: center;
236
+ }
237
+ #toggle:hover,
238
+ #toggle:focus {
239
+ opacity: 1;
240
+ box-shadow: 0 0 20px rgba(0, 220, 130, 0.6);
241
+ }
242
+ #toggle:focus-visible {
243
+ outline: 3px solid #00DC82;
244
+ outline-offset: 0;
245
+ box-shadow: 0 0 24px rgba(0, 220, 130, 0.8);
246
+ }
247
+ #frame[inert] ~ #toggle {
248
+ left: var(--error-pip-left);
249
+ top: var(--error-pip-top);
250
+ right: calc(var(--error-pip-right) - 3px);
251
+ bottom: calc(var(--error-pip-bottom) - 3px);
252
+ cursor: grab;
253
+ }
254
+ :host(.dragging) #frame[inert] ~ #toggle {
255
+ cursor: grabbing;
256
+ }
257
+ #frame:not([inert]) ~ #toggle,
258
+ #frame:not([inert]) + #preview {
259
+ cursor: grab;
260
+ }
261
+ :host(.dragging-preview) #frame:not([inert]) ~ #toggle,
262
+ :host(.dragging-preview) #frame:not([inert]) + #preview {
263
+ cursor: grabbing;
264
+ }
265
+
266
+ #pip-close {
267
+ position: absolute;
268
+ top: 6px;
269
+ right: 6px;
270
+ width: 24px;
271
+ height: 24px;
272
+ border-radius: 50%;
273
+ border: none;
274
+ background: rgba(0, 0, 0, 0.75);
275
+ color: #fff;
276
+ font-size: 16px;
277
+ line-height: 1;
278
+ display: inline-flex;
279
+ align-items: center;
280
+ justify-content: center;
281
+ cursor: pointer;
282
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
283
+ pointer-events: auto;
284
+ }
285
+ #pip-close:focus-visible {
286
+ outline: 2px solid #00DC82;
287
+ outline-offset: 2px;
288
+ }
289
+
290
+ #pip-restore {
291
+ position: fixed;
292
+ right: 16px;
293
+ bottom: 16px;
294
+ padding: 8px 14px;
295
+ border-radius: 999px;
296
+ border: 2px solid #00DC82;
297
+ background: #111;
298
+ color: #fff;
299
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
300
+ font-size: 14px;
301
+ display: inline-flex;
302
+ align-items: center;
303
+ gap: 6px;
304
+ z-index: calc(var(--z-base) + 2);
305
+ cursor: grab;
306
+ }
307
+ #pip-restore:focus-visible {
308
+ outline: 2px solid #00DC82;
309
+ outline-offset: 2px;
310
+ }
311
+ :host(.dragging-restore) #pip-restore {
312
+ cursor: grabbing;
313
+ }
314
+
315
+ #frame[hidden],
316
+ #toggle[hidden],
317
+ #preview[hidden],
318
+ #pip-restore[hidden],
319
+ #pip-close[hidden] {
320
+ display: none !important;
321
+ }
322
+
323
+ @media (prefers-reduced-motion: reduce) {
324
+ #toggle {
325
+ transition: none;
326
+ }
327
+ }
328
+ `;
329
+ function webComponentScript(base64HTML, startMinimized) {
330
+ return `
331
+ (function () {
332
+ try {
333
+ // =========================
334
+ // Host + Shadow
335
+ // =========================
336
+ const host = document.querySelector('nuxt-error-overlay');
337
+ if (!host)
338
+ return;
339
+ const shadow = host.attachShadow({ mode: 'open' });
340
+
341
+ // =========================
342
+ // DOM helpers
343
+ // =========================
344
+ const el = (tag) => document.createElement(tag);
345
+ const on = (node, type, fn, opts) => node.addEventListener(type, fn, opts);
346
+ const hide = (node, v) => node.toggleAttribute('hidden', !!v);
347
+ const setVar = (name, value) => host.style.setProperty(name, value);
348
+ const unsetVar = (name) => host.style.removeProperty(name);
349
+
350
+ // =========================
351
+ // Create DOM
352
+ // =========================
353
+ const style = el('style');
354
+ style.textContent = ${JSON.stringify(errorCSS)};
355
+
356
+ const iframe = el('iframe');
357
+ iframe.id = 'frame';
358
+ iframe.src = 'data:text/html;base64,${base64HTML}';
359
+ iframe.title = 'Detailed error stack trace';
360
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
361
+
362
+ const preview = el('div');
363
+ preview.id = 'preview';
364
+
365
+ const toggle = el('div');
366
+ toggle.id = 'toggle';
367
+ toggle.setAttribute('aria-expanded', 'true');
368
+ toggle.setAttribute('role', 'button');
369
+ toggle.setAttribute('tabindex', '0');
370
+ toggle.innerHTML = '<span class="sr-only">Toggle detailed error view</span>';
371
+
372
+ const liveRegion = el('div');
373
+ liveRegion.setAttribute('role', 'status');
374
+ liveRegion.setAttribute('aria-live', 'polite');
375
+ liveRegion.className = 'sr-only';
376
+
377
+ const pipCloseButton = el('button');
378
+ pipCloseButton.id = 'pip-close';
379
+ pipCloseButton.setAttribute('type', 'button');
380
+ pipCloseButton.setAttribute('aria-label', 'Hide error preview overlay');
381
+ pipCloseButton.innerHTML = '&times;';
382
+ pipCloseButton.hidden = true;
383
+ toggle.appendChild(pipCloseButton);
384
+
385
+ const pipRestoreButton = el('button');
386
+ pipRestoreButton.id = 'pip-restore';
387
+ pipRestoreButton.setAttribute('type', 'button');
388
+ pipRestoreButton.setAttribute('aria-label', 'Show error overlay');
389
+ pipRestoreButton.innerHTML = '<span aria-hidden="true">⟲</span><span>Show error overlay</span>';
390
+ pipRestoreButton.hidden = true;
391
+
392
+ // Order matters: #frame + #preview adjacency
393
+ shadow.appendChild(style);
394
+ shadow.appendChild(liveRegion);
395
+ shadow.appendChild(iframe);
396
+ shadow.appendChild(preview);
397
+ shadow.appendChild(toggle);
398
+ shadow.appendChild(pipRestoreButton);
399
+
400
+ // =========================
401
+ // Constants / keys
402
+ // =========================
403
+ const POS_KEYS = {
404
+ position: 'nuxt-error-overlay:position',
405
+ hiddenPretty: 'nuxt-error-overlay:error-pip:hidden',
406
+ hiddenPreview: 'nuxt-error-overlay:app-preview:hidden'
407
+ };
408
+
409
+ const CSS_VARS = {
410
+ pip: {
411
+ left: '--error-pip-left',
412
+ top: '--error-pip-top',
413
+ right: '--error-pip-right',
414
+ bottom: '--error-pip-bottom'
415
+ },
416
+ preview: {
417
+ left: '--app-preview-left',
418
+ top: '--app-preview-top',
419
+ right: '--app-preview-right',
420
+ bottom: '--app-preview-bottom'
421
+ }
422
+ };
423
+
424
+ const MIN_GAP = 5;
425
+ const DRAG_THRESHOLD = 2;
426
+
427
+ // =========================
428
+ // Local storage safe access + state
429
+ // =========================
430
+ let storageReady = true;
431
+ let isPrettyHidden = false;
432
+ let isPreviewHidden = false;
433
+
434
+ const safeGet = (k) => {
435
+ try {
436
+ return localStorage.getItem(k);
437
+ } catch {
438
+ return null;
439
+ }
440
+ };
441
+
442
+ const safeSet = (k, v) => {
443
+ if (!storageReady)
444
+ return;
445
+ try {
446
+ localStorage.setItem(k, v);
447
+ } catch {}
448
+ };
449
+
450
+ // =========================
451
+ // Sizing helpers
452
+ // =========================
453
+ const vvSize = () => {
454
+ const v = window.visualViewport;
455
+ return v ? { w: v.width, h: v.height } : { w: window.innerWidth, h: window.innerHeight };
456
+ };
457
+
458
+ const previewSize = () => {
459
+ const styles = getComputedStyle(host);
460
+ const w = parseFloat(styles.getPropertyValue('--preview-width')) || 240;
461
+ const h = parseFloat(styles.getPropertyValue('--preview-height')) || 180;
462
+ return { w, h };
463
+ };
464
+
465
+ const sizeForTarget = (target) => {
466
+ if (!target)
467
+ return previewSize();
468
+ const rect = target.getBoundingClientRect();
469
+ if (rect.width && rect.height)
470
+ return { w: rect.width, h: rect.height };
471
+ return previewSize();
472
+ };
473
+
474
+ // =========================
475
+ // Dock model + offset/alignment calculations
476
+ // =========================
477
+ const dock = { edge: null, offset: null, align: null, gap: null };
478
+
479
+ const maxOffsetFor = (edge, size) => {
480
+ const vv = vvSize();
481
+ if (edge === 'left' || edge === 'right')
482
+ return Math.max(MIN_GAP, vv.h - size.h - MIN_GAP);
483
+ return Math.max(MIN_GAP, vv.w - size.w - MIN_GAP);
484
+ };
485
+
486
+ const clampOffset = (edge, value, size) => {
487
+ const max = maxOffsetFor(edge, size);
488
+ return Math.min(Math.max(value, MIN_GAP), max);
489
+ };
490
+
491
+ const updateDockAlignment = (size) => {
492
+ if (!dock.edge || dock.offset == null)
493
+ return;
494
+ const max = maxOffsetFor(dock.edge, size);
495
+ if (dock.offset <= max / 2) {
496
+ dock.align = 'start';
497
+ dock.gap = dock.offset;
498
+ } else {
499
+ dock.align = 'end';
500
+ dock.gap = Math.max(0, max - dock.offset);
501
+ }
502
+ };
503
+
504
+ const appliedOffsetFor = (size) => {
505
+ if (!dock.edge || dock.offset == null)
506
+ return null;
507
+ const max = maxOffsetFor(dock.edge, size);
508
+
509
+ if (dock.align === 'end' && typeof dock.gap === 'number') {
510
+ return clampOffset(dock.edge, max - dock.gap, size);
511
+ }
512
+ if (dock.align === 'start' && typeof dock.gap === 'number') {
513
+ return clampOffset(dock.edge, dock.gap, size);
514
+ }
515
+ return clampOffset(dock.edge, dock.offset, size);
516
+ };
517
+
518
+ const nearestEdgeAt = (x, y) => {
519
+ const { w, h } = vvSize();
520
+ const d = { left: x, right: w - x, top: y, bottom: h - y };
521
+ return Object.keys(d).reduce((a, b) => (d[a] < d[b] ? a : b));
522
+ };
523
+
524
+ const cornerDefaultDock = () => {
525
+ const vv = vvSize();
526
+ const size = previewSize();
527
+ const offset = Math.max(MIN_GAP, vv.w - size.w - MIN_GAP);
528
+ return { edge: 'bottom', offset };
529
+ };
530
+
531
+ const currentTransformOrigin = () => {
532
+ if (!dock.edge) return null;
533
+ if (dock.edge === 'left' || dock.edge === 'top')
534
+ return 'top left';
535
+ if (dock.edge === 'right')
536
+ return 'top right';
537
+ return 'bottom left';
538
+ };
539
+
540
+ // =========================
541
+ // Persist / load dock
542
+ // =========================
543
+ const loadDock = () => {
544
+ const raw = safeGet(POS_KEYS.position);
545
+ if (!raw)
546
+ return;
547
+ try {
548
+ const parsed = JSON.parse(raw);
549
+ const { edge, offset, align, gap } = parsed || {};
550
+ if (!['left', 'right', 'top', 'bottom'].includes(edge))
551
+ return;
552
+ if (typeof offset !== 'number')
553
+ return;
554
+
555
+ dock.edge = edge;
556
+ dock.offset = clampOffset(edge, offset, previewSize());
557
+ dock.align = align === 'start' || align === 'end' ? align : null;
558
+ dock.gap = typeof gap === 'number' ? gap : null;
559
+
560
+ if (!dock.align || dock.gap == null)
561
+ updateDockAlignment(previewSize());
562
+ } catch {}
563
+ };
564
+
565
+ const persistDock = () => {
566
+ if (!dock.edge || dock.offset == null)
567
+ return;
568
+ safeSet(POS_KEYS.position, JSON.stringify({
569
+ edge: dock.edge,
570
+ offset: dock.offset,
571
+ align: dock.align,
572
+ gap: dock.gap
573
+ }));
574
+ };
575
+
576
+ // =========================
577
+ // Apply dock
578
+ // =========================
579
+ const dockToVars = (vars) => ({
580
+ set: (side, v) => host.style.setProperty(vars[side], v),
581
+ clear: (side) => host.style.removeProperty(vars[side])
582
+ });
583
+
584
+ const dockToEl = (node) => ({
585
+ set: (side, v) => { node.style[side] = v; },
586
+ clear: (side) => { node.style[side] = ''; }
587
+ });
588
+
589
+ const applyDock = (target, size, opts) => {
590
+ if (!dock.edge || dock.offset == null) {
591
+ target.clear('left');
592
+ target.clear('top');
593
+ target.clear('right');
594
+ target.clear('bottom');
595
+ return;
596
+ }
597
+
598
+ target.set('left', 'auto');
599
+ target.set('top', 'auto');
600
+ target.set('right', 'auto');
601
+ target.set('bottom', 'auto');
602
+
603
+ const applied = appliedOffsetFor(size);
604
+
605
+ if (dock.edge === 'left') {
606
+ target.set('left', MIN_GAP + 'px');
607
+ target.set('top', applied + 'px');
608
+ } else if (dock.edge === 'right') {
609
+ target.set('right', MIN_GAP + 'px');
610
+ target.set('top', applied + 'px');
611
+ } else if (dock.edge === 'top') {
612
+ target.set('top', MIN_GAP + 'px');
613
+ target.set('left', applied + 'px');
614
+ } else {
615
+ target.set('bottom', MIN_GAP + 'px');
616
+ target.set('left', applied + 'px');
617
+ }
618
+
619
+ if (!opts || opts.persist !== false)
620
+ persistDock();
621
+ };
622
+
623
+ const applyDockAll = (opts) => {
624
+ applyDock(dockToVars(CSS_VARS.pip), previewSize(), opts);
625
+ applyDock(dockToVars(CSS_VARS.preview), previewSize(), opts);
626
+ applyDock(dockToEl(pipRestoreButton), sizeForTarget(pipRestoreButton), opts);
627
+ };
628
+
629
+ const repaintToDock = () => {
630
+ if (!dock.edge || dock.offset == null)
631
+ return;
632
+ const origin = currentTransformOrigin();
633
+ if (origin)
634
+ setVar('--error-pip-origin', origin);
635
+ else
636
+ unsetVar('--error-pip-origin');
637
+ applyDockAll({ persist: false });
638
+ };
639
+
640
+ // =========================
641
+ // Hidden state + UI
642
+ // =========================
643
+ const loadHidden = () => {
644
+ const rawPretty = safeGet(POS_KEYS.hiddenPretty);
645
+ if (rawPretty != null)
646
+ isPrettyHidden = rawPretty === '1' || rawPretty === 'true';
647
+ const rawPreview = safeGet(POS_KEYS.hiddenPreview);
648
+ if (rawPreview != null)
649
+ isPreviewHidden = rawPreview === '1' || rawPreview === 'true';
650
+ };
651
+
652
+ const setPrettyHidden = (v) => {
653
+ isPrettyHidden = !!v;
654
+ safeSet(POS_KEYS.hiddenPretty, isPrettyHidden ? '1' : '0');
655
+ updateUI();
656
+ };
657
+
658
+ const setPreviewHidden = (v) => {
659
+ isPreviewHidden = !!v;
660
+ safeSet(POS_KEYS.hiddenPreview, isPreviewHidden ? '1' : '0');
661
+ updateUI();
662
+ };
663
+
664
+ const isMinimized = () => iframe.hasAttribute('inert');
665
+
666
+ const setMinimized = (v) => {
667
+ if (v) {
668
+ iframe.setAttribute('inert', '');
669
+ toggle.setAttribute('aria-expanded', 'false');
670
+ } else {
671
+ iframe.removeAttribute('inert');
672
+ toggle.setAttribute('aria-expanded', 'true');
673
+ }
674
+ };
675
+
676
+ const setRestoreLabel = (kind) => {
677
+ if (kind === 'pretty') {
678
+ pipRestoreButton.innerHTML = '<span aria-hidden="true">⟲</span><span>Show error overlay</span>';
679
+ pipRestoreButton.setAttribute('aria-label', 'Show error overlay');
680
+ } else {
681
+ pipRestoreButton.innerHTML = '<span aria-hidden="true">⟲</span><span>Show error page</span>';
682
+ pipRestoreButton.setAttribute('aria-label', 'Show error page');
683
+ }
684
+ };
685
+
686
+ const updateUI = () => {
687
+ const minimized = isMinimized();
688
+ const showPiP = minimized && !isPrettyHidden;
689
+ const showPreview = !minimized && !isPreviewHidden;
690
+ const pipHiddenByUser = minimized && isPrettyHidden;
691
+ const previewHiddenByUser = !minimized && isPreviewHidden;
692
+ const showToggle = minimized ? showPiP : showPreview;
693
+ const showRestore = pipHiddenByUser || previewHiddenByUser;
694
+
695
+ hide(iframe, pipHiddenByUser);
696
+ hide(preview, !showPreview);
697
+ hide(toggle, !showToggle);
698
+ hide(pipCloseButton, !showToggle);
699
+ hide(pipRestoreButton, !showRestore);
700
+
701
+ pipCloseButton.setAttribute('aria-label', minimized ? 'Hide error overlay' : 'Hide error page preview');
702
+
703
+ if (pipHiddenByUser)
704
+ setRestoreLabel('pretty');
705
+ else if (previewHiddenByUser)
706
+ setRestoreLabel('preview');
707
+
708
+ host.classList.toggle('pip-hidden', isPrettyHidden);
709
+ host.classList.toggle('preview-hidden', isPreviewHidden);
710
+ };
711
+
712
+ // =========================
713
+ // Preview snapshot
714
+ // =========================
715
+ const updatePreview = () => {
716
+ try {
717
+ let previewIframe = preview.querySelector('iframe');
718
+ if (!previewIframe) {
719
+ previewIframe = el('iframe');
720
+ previewIframe.style.cssText = 'width: 1200px; height: 900px; transform: scale(0.2); transform-origin: top left; border: none;';
721
+ previewIframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
722
+ preview.appendChild(previewIframe);
723
+ }
724
+
725
+ const doctype = document.doctype ? '<!DOCTYPE ' + document.doctype.name + '>' : '';
726
+ const cleanedHTML = document.documentElement.outerHTML
727
+ .replace(/<nuxt-error-overlay[^>]*>.*?<\\/nuxt-error-overlay>/gs, '')
728
+ .replace(/<script[^>]*>.*?<\\/script>/gs, '');
729
+
730
+ const iframeDoc = previewIframe.contentDocument || previewIframe.contentWindow.document;
731
+ iframeDoc.open();
732
+ iframeDoc.write(doctype + cleanedHTML);
733
+ iframeDoc.close();
734
+ } catch (err) {
735
+ console.error('Failed to update preview:', err);
736
+ }
737
+ };
738
+
739
+ // =========================
740
+ // View toggling
741
+ // =========================
742
+ const toggleView = () => {
743
+ if (isMinimized()) {
744
+ updatePreview();
745
+ setMinimized(false);
746
+ liveRegion.textContent = 'Showing detailed error view';
747
+ setTimeout(() => {
748
+ try {
749
+ iframe.contentWindow.focus();
750
+ } catch {}
751
+ }, 100);
752
+ } else {
753
+ setMinimized(true);
754
+ liveRegion.textContent = 'Showing error page';
755
+ repaintToDock();
756
+ void iframe.offsetWidth;
757
+ }
758
+ updateUI();
759
+ };
760
+
761
+ // =========================
762
+ // Dragging (unified, rAF throttled)
763
+ // =========================
764
+ let drag = null;
765
+ let rafId = null;
766
+ let suppressToggleClick = false;
767
+ let suppressRestoreClick = false;
768
+
769
+ const beginDrag = (e) => {
770
+ if (drag)
771
+ return;
772
+
773
+ if (!dock.edge || dock.offset == null) {
774
+ const def = cornerDefaultDock();
775
+ dock.edge = def.edge;
776
+ dock.offset = def.offset;
777
+ updateDockAlignment(previewSize());
778
+ }
779
+
780
+ const isRestoreTarget = e.currentTarget === pipRestoreButton;
781
+
782
+ drag = {
783
+ kind: isRestoreTarget ? 'restore' : (isMinimized() ? 'pip' : 'preview'),
784
+ pointerId: e.pointerId,
785
+ startX: e.clientX,
786
+ startY: e.clientY,
787
+ lastX: e.clientX,
788
+ lastY: e.clientY,
789
+ moved: false,
790
+ target: e.currentTarget
791
+ };
792
+
793
+ drag.target.setPointerCapture(e.pointerId);
794
+
795
+ if (drag.kind === 'restore')
796
+ host.classList.add('dragging-restore');
797
+ else
798
+ host.classList.add(drag.kind === 'pip' ? 'dragging' : 'dragging-preview');
799
+
800
+ e.preventDefault();
801
+ };
802
+
803
+ const moveDrag = (e) => {
804
+ if (!drag || drag.pointerId !== e.pointerId)
805
+ return;
806
+
807
+ drag.lastX = e.clientX;
808
+ drag.lastY = e.clientY;
809
+
810
+ const dx = drag.lastX - drag.startX;
811
+ const dy = drag.lastY - drag.startY;
812
+
813
+ if (!drag.moved && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)) {
814
+ drag.moved = true;
815
+ }
816
+
817
+ if (!drag.moved)
818
+ return;
819
+ if (rafId)
820
+ return;
821
+
822
+ rafId = requestAnimationFrame(() => {
823
+ rafId = null;
824
+
825
+ const edge = nearestEdgeAt(drag.lastX, drag.lastY);
826
+ const size = sizeForTarget(drag.target);
827
+
828
+ let offset;
829
+ if (edge === 'left' || edge === 'right') {
830
+ const top = drag.lastY - (size.h / 2);
831
+ offset = clampOffset(edge, Math.round(top), size);
832
+ } else {
833
+ const left = drag.lastX - (size.w / 2);
834
+ offset = clampOffset(edge, Math.round(left), size);
835
+ }
836
+
837
+ dock.edge = edge;
838
+ dock.offset = offset;
839
+ updateDockAlignment(size);
840
+
841
+ const origin = currentTransformOrigin();
842
+ setVar('--error-pip-origin', origin || 'bottom right');
843
+
844
+ applyDockAll({ persist: false });
845
+ });
846
+ };
847
+
848
+ const endDrag = (e) => {
849
+ if (!drag || drag.pointerId !== e.pointerId)
850
+ return;
851
+
852
+ const endedKind = drag.kind;
853
+ drag.target.releasePointerCapture(e.pointerId);
854
+
855
+ if (endedKind === 'restore')
856
+ host.classList.remove('dragging-restore');
857
+ else
858
+ host.classList.remove(endedKind === 'pip' ? 'dragging' : 'dragging-preview');
859
+
860
+ const didMove = drag.moved;
861
+ drag = null;
862
+
863
+ if (didMove) {
864
+ persistDock();
865
+ if (endedKind === 'restore')
866
+ suppressRestoreClick = true;
867
+ else
868
+ suppressToggleClick = true;
869
+ e.preventDefault();
870
+ e.stopPropagation();
871
+ }
872
+ };
873
+
874
+ const bindDragTarget = (node) => {
875
+ on(node, 'pointerdown', beginDrag);
876
+ on(node, 'pointermove', moveDrag);
877
+ on(node, 'pointerup', endDrag);
878
+ on(node, 'pointercancel', endDrag);
879
+ };
880
+
881
+ bindDragTarget(toggle);
882
+ bindDragTarget(pipRestoreButton);
883
+
884
+ // =========================
885
+ // Events (toggle / close / restore)
886
+ // =========================
887
+ on(toggle, 'click', (e) => {
888
+ if (suppressToggleClick) {
889
+ e.preventDefault();
890
+ suppressToggleClick = false;
891
+ return;
892
+ }
893
+ toggleView();
894
+ });
895
+
896
+ on(toggle, 'keydown', (e) => {
897
+ if (e.key === 'Enter' || e.key === ' ') {
898
+ e.preventDefault();
899
+ toggleView();
900
+ }
901
+ });
902
+
903
+ on(pipCloseButton, 'click', (e) => {
904
+ e.preventDefault();
905
+ e.stopPropagation();
906
+ if (isMinimized())
907
+ setPrettyHidden(true);
908
+ else
909
+ setPreviewHidden(true);
910
+ });
911
+
912
+ on(pipCloseButton, 'pointerdown', (e) => {
913
+ e.stopPropagation();
914
+ });
915
+
916
+ on(pipRestoreButton, 'click', (e) => {
917
+ if (suppressRestoreClick) {
918
+ e.preventDefault();
919
+ suppressRestoreClick = false;
920
+ return;
921
+ }
922
+ e.preventDefault();
923
+ e.stopPropagation();
924
+ if (isMinimized())
925
+ setPrettyHidden(false);
926
+ else
927
+ setPreviewHidden(false);
928
+ });
929
+
930
+ // =========================
931
+ // Lifecycle: load / sync / repaint
932
+ // =========================
933
+ const loadState = () => {
934
+ loadDock();
935
+ loadHidden();
936
+
937
+ if (isPrettyHidden && !isMinimized())
938
+ setMinimized(true);
939
+
940
+ updateUI();
941
+ repaintToDock();
942
+ };
943
+
944
+ loadState();
945
+
946
+ on(window, 'storage-ready', () => {
947
+ storageReady = true;
948
+ loadState();
949
+ });
950
+
951
+ const onViewportChange = () => repaintToDock();
952
+
953
+ on(window, 'resize', onViewportChange);
954
+
955
+ if (window.visualViewport) {
956
+ on(window.visualViewport, 'resize', onViewportChange);
957
+ on(window.visualViewport, 'scroll', onViewportChange);
958
+ }
959
+
960
+ // initial preview
961
+ setTimeout(updatePreview, 100);
962
+
963
+ // initial minimized option
964
+ if (${startMinimized}) {
965
+ setMinimized(true);
966
+ repaintToDock();
967
+ void iframe.offsetWidth;
968
+ updateUI();
969
+ }
970
+ } catch (err) {
971
+ console.error('Failed to initialize Nuxt error overlay:', err);
972
+ }
973
+ })();
974
+ `;
975
+ }
976
+ export function generateErrorOverlayHTML(html, options) {
977
+ const nonce = Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, "0")).join("");
978
+ const errorPage = html.replace("<head>", `<head><script>${iframeStorageBridge(nonce)}<\/script>`);
979
+ const base64HTML = Buffer.from(errorPage, "utf8").toString("base64");
980
+ return `
981
+ <script>${parentStorageBridge(nonce)}<\/script>
982
+ <nuxt-error-overlay></nuxt-error-overlay>
983
+ <script>${webComponentScript(base64HTML, options?.startMinimized ?? false)}<\/script>
984
+ `;
985
+ }