@nowline/preview-shell 0.4.2 → 0.5.1
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/markup.d.ts +34 -7
- package/dist/markup.d.ts.map +1 -1
- package/dist/markup.js +72 -42
- package/dist/markup.js.map +1 -1
- package/dist/mount.d.ts +59 -15
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +599 -109
- package/dist/mount.js.map +1 -1
- package/dist/styles.d.ts +9 -8
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +282 -61
- package/dist/styles.js.map +1 -1
- package/package.json +2 -2
- package/src/markup.ts +109 -49
- package/src/mount.ts +741 -116
- package/src/styles.ts +282 -61
package/dist/mount.js
CHANGED
|
@@ -12,14 +12,23 @@ import { buildViewport } from './markup.js';
|
|
|
12
12
|
import { PREVIEW_SHELL_CSS } from './styles.js';
|
|
13
13
|
let stylesheetMounted = false;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* scopes every selector under `.nl-preview-root` so multiple shells
|
|
18
|
-
* can coexist with workbench / app styles.
|
|
15
|
+
* Last chrome position saved after a drag, shared across all shells in
|
|
16
|
+
* this JS session. Persists for the lifetime of the page.
|
|
19
17
|
*/
|
|
18
|
+
let savedChromePosition = null;
|
|
20
19
|
function ensureStylesheet(doc) {
|
|
21
20
|
if (stylesheetMounted)
|
|
22
21
|
return;
|
|
22
|
+
// A CSP-restricted consumer (e.g. the VS Code webview, whose
|
|
23
|
+
// `style-src` is nonce-only with no `'unsafe-inline'`) pre-injects
|
|
24
|
+
// this stylesheet into the host HTML with a nonce, marked by the
|
|
25
|
+
// `data-nl-preview-shell` attribute. A second `<style>` created here
|
|
26
|
+
// would carry no nonce and be refused by the CSP, leaving the
|
|
27
|
+
// toolbar unstyled — so defer to the host-provided copy when present.
|
|
28
|
+
if (doc.querySelector('style[data-nl-preview-shell]')) {
|
|
29
|
+
stylesheetMounted = true;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
23
32
|
const style = doc.createElement('style');
|
|
24
33
|
style.setAttribute('data-nl-preview-shell', '');
|
|
25
34
|
style.textContent = PREVIEW_SHELL_CSS;
|
|
@@ -38,9 +47,14 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
38
47
|
naturalHeight: 0,
|
|
39
48
|
defaultFit: options.initialFit ?? 'fitPage',
|
|
40
49
|
activeFit: options.initialFit === 'fitWidth' ? 'fitWidth' : 'fitPage',
|
|
50
|
+
isDirty: false,
|
|
51
|
+
lastViewportWidth: 0,
|
|
52
|
+
lastViewportHeight: 0,
|
|
41
53
|
showMinimap: options.showMinimap !== false,
|
|
42
|
-
minimapDismissedThisSession: false,
|
|
43
54
|
firstRender: true,
|
|
55
|
+
mode: 'dark',
|
|
56
|
+
locale: options.locale ?? (typeof navigator !== 'undefined' ? navigator.language : 'en'),
|
|
57
|
+
export: { format: 'svg' },
|
|
44
58
|
view: {
|
|
45
59
|
theme: 'auto',
|
|
46
60
|
now: 'today',
|
|
@@ -53,16 +67,166 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
53
67
|
target.addEventListener(type, handler, opts);
|
|
54
68
|
cleanups.push(() => target.removeEventListener(type, handler, opts));
|
|
55
69
|
}
|
|
56
|
-
// =====
|
|
70
|
+
// ===== Mode resolution =====
|
|
71
|
+
function resolveSystemMode() {
|
|
72
|
+
const body = doc.body;
|
|
73
|
+
if (body) {
|
|
74
|
+
if (body.classList.contains('vscode-dark') ||
|
|
75
|
+
body.classList.contains('vscode-high-contrast-dark'))
|
|
76
|
+
return 'dark';
|
|
77
|
+
if (body.classList.contains('vscode-light') ||
|
|
78
|
+
body.classList.contains('vscode-high-contrast-light') ||
|
|
79
|
+
body.classList.contains('vscode-high-contrast'))
|
|
80
|
+
return 'light';
|
|
81
|
+
}
|
|
82
|
+
if (typeof win.matchMedia === 'function') {
|
|
83
|
+
return win.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
84
|
+
}
|
|
85
|
+
return 'dark';
|
|
86
|
+
}
|
|
87
|
+
function applyMode(resolved) {
|
|
88
|
+
state.mode = resolved;
|
|
89
|
+
rootEl.setAttribute('data-nl-mode', resolved);
|
|
90
|
+
}
|
|
91
|
+
const rawMode = options.mode ?? 'system';
|
|
92
|
+
applyMode(rawMode === 'system' ? resolveSystemMode() : rawMode);
|
|
93
|
+
if (rawMode === 'system') {
|
|
94
|
+
if (typeof MutationObserver !== 'undefined' && doc.body) {
|
|
95
|
+
const bodyObs = new MutationObserver(() => applyMode(resolveSystemMode()));
|
|
96
|
+
bodyObs.observe(doc.body, { attributes: true, attributeFilter: ['class'] });
|
|
97
|
+
cleanups.push(() => bodyObs.disconnect());
|
|
98
|
+
}
|
|
99
|
+
const mq = typeof win.matchMedia === 'function'
|
|
100
|
+
? win.matchMedia('(prefers-color-scheme: dark)')
|
|
101
|
+
: null;
|
|
102
|
+
if (mq) {
|
|
103
|
+
const onMqChange = () => {
|
|
104
|
+
const body = doc.body;
|
|
105
|
+
const hasVsCode = body &&
|
|
106
|
+
(body.classList.contains('vscode-dark') ||
|
|
107
|
+
body.classList.contains('vscode-light') ||
|
|
108
|
+
body.classList.contains('vscode-high-contrast') ||
|
|
109
|
+
body.classList.contains('vscode-high-contrast-dark') ||
|
|
110
|
+
body.classList.contains('vscode-high-contrast-light'));
|
|
111
|
+
if (!hasVsCode)
|
|
112
|
+
applyMode(mq.matches ? 'dark' : 'light');
|
|
113
|
+
};
|
|
114
|
+
mq.addEventListener('change', onMqChange);
|
|
115
|
+
cleanups.push(() => mq.removeEventListener('change', onMqChange));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// ===== Floating UI fade =====
|
|
57
119
|
let fadeTimer = null;
|
|
58
|
-
function
|
|
120
|
+
function showFloatingUI() {
|
|
59
121
|
els.chrome.classList.remove('faded');
|
|
122
|
+
els.minimap.classList.remove('faded');
|
|
60
123
|
if (fadeTimer)
|
|
61
124
|
clearTimeout(fadeTimer);
|
|
62
|
-
fadeTimer = setTimeout(() =>
|
|
125
|
+
fadeTimer = setTimeout(() => {
|
|
126
|
+
els.chrome.classList.add('faded');
|
|
127
|
+
els.minimap.classList.add('faded');
|
|
128
|
+
}, 2000);
|
|
129
|
+
}
|
|
130
|
+
on(doc, 'mousemove', showFloatingUI);
|
|
131
|
+
showFloatingUI();
|
|
132
|
+
// ===== Collapse / restore =====
|
|
133
|
+
// « collapses the toolbar to a translucent puck (just the drag grip
|
|
134
|
+
// + a » restore arrow); » expands it back to the full toolbar.
|
|
135
|
+
on(els.collapseBtn, 'click', (e) => {
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
closeAllMenus();
|
|
138
|
+
els.chrome.classList.add('collapsed');
|
|
139
|
+
repositionChrome();
|
|
140
|
+
});
|
|
141
|
+
on(els.restoreBtn, 'click', (e) => {
|
|
142
|
+
e.stopPropagation();
|
|
143
|
+
els.chrome.classList.remove('collapsed');
|
|
144
|
+
repositionChrome();
|
|
145
|
+
});
|
|
146
|
+
// ===== Toolbar positioning + drag =====
|
|
147
|
+
// The chrome defaults to the upper-right corner and tracks it on
|
|
148
|
+
// resize (`chromePinnedRight`). Once dragged it becomes free-floating
|
|
149
|
+
// and is merely clamped back into view. Either way the position is
|
|
150
|
+
// always expressed as explicit `left`/`top` so a narrowing viewport
|
|
151
|
+
// shifts the toolbar left instead of squishing the row.
|
|
152
|
+
let chromePinnedRight = savedChromePosition === null;
|
|
153
|
+
let chromeDragStart = null;
|
|
154
|
+
function getGutter() {
|
|
155
|
+
const val = getComputedStyle(rootEl).getPropertyValue('--nl-preview-gutter').trim();
|
|
156
|
+
return parseInt(val, 10) || 8;
|
|
157
|
+
}
|
|
158
|
+
function repositionChrome() {
|
|
159
|
+
const g = getGutter();
|
|
160
|
+
const cw = els.chrome.offsetWidth;
|
|
161
|
+
const ch = els.chrome.offsetHeight;
|
|
162
|
+
const maxLeft = Math.max(g, rootEl.clientWidth - cw - g);
|
|
163
|
+
const maxTop = Math.max(g, rootEl.clientHeight - ch - g);
|
|
164
|
+
let left;
|
|
165
|
+
let top;
|
|
166
|
+
if (chromePinnedRight) {
|
|
167
|
+
left = rootEl.clientWidth - cw - g;
|
|
168
|
+
top = g;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
left = parseInt(els.chrome.style.left, 10);
|
|
172
|
+
top = parseInt(els.chrome.style.top, 10);
|
|
173
|
+
if (Number.isNaN(left))
|
|
174
|
+
left = savedChromePosition?.left ?? g;
|
|
175
|
+
if (Number.isNaN(top))
|
|
176
|
+
top = savedChromePosition?.top ?? g;
|
|
177
|
+
}
|
|
178
|
+
els.chrome.style.right = 'auto';
|
|
179
|
+
els.chrome.style.left = `${Math.max(g, Math.min(left, maxLeft))}px`;
|
|
180
|
+
els.chrome.style.top = `${Math.max(g, Math.min(top, maxTop))}px`;
|
|
63
181
|
}
|
|
64
|
-
|
|
65
|
-
|
|
182
|
+
function ensureChromeLeftTopAnchoring() {
|
|
183
|
+
if (els.chrome.style.left !== '')
|
|
184
|
+
return;
|
|
185
|
+
els.chrome.style.right = 'auto';
|
|
186
|
+
els.chrome.style.left = `${els.chrome.offsetLeft}px`;
|
|
187
|
+
els.chrome.style.top = `${els.chrome.offsetTop}px`;
|
|
188
|
+
}
|
|
189
|
+
on(els.toolbarHandle, 'pointerdown', (e) => {
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
chromePinnedRight = false;
|
|
192
|
+
ensureChromeLeftTopAnchoring();
|
|
193
|
+
chromeDragStart = {
|
|
194
|
+
pointerX: e.clientX,
|
|
195
|
+
pointerY: e.clientY,
|
|
196
|
+
chromeLeft: parseInt(els.chrome.style.left, 10) || 0,
|
|
197
|
+
chromeTop: parseInt(els.chrome.style.top, 10) || 0,
|
|
198
|
+
};
|
|
199
|
+
els.chrome.classList.add('dragging');
|
|
200
|
+
els.toolbarHandle.setPointerCapture(e.pointerId);
|
|
201
|
+
});
|
|
202
|
+
on(els.toolbarHandle, 'pointermove', (e) => {
|
|
203
|
+
if (!chromeDragStart)
|
|
204
|
+
return;
|
|
205
|
+
const dx = e.clientX - chromeDragStart.pointerX;
|
|
206
|
+
const dy = e.clientY - chromeDragStart.pointerY;
|
|
207
|
+
const rawLeft = chromeDragStart.chromeLeft + dx;
|
|
208
|
+
const rawTop = chromeDragStart.chromeTop + dy;
|
|
209
|
+
const g = getGutter();
|
|
210
|
+
const maxLeft = Math.max(g, rootEl.clientWidth - els.chrome.offsetWidth - g);
|
|
211
|
+
const maxTop = Math.max(g, rootEl.clientHeight - els.chrome.offsetHeight - g);
|
|
212
|
+
els.chrome.style.left = `${Math.max(g, Math.min(rawLeft, maxLeft))}px`;
|
|
213
|
+
els.chrome.style.top = `${Math.max(g, Math.min(rawTop, maxTop))}px`;
|
|
214
|
+
});
|
|
215
|
+
function endChromeDrag() {
|
|
216
|
+
if (!chromeDragStart)
|
|
217
|
+
return;
|
|
218
|
+
chromeDragStart = null;
|
|
219
|
+
els.chrome.classList.remove('dragging');
|
|
220
|
+
savedChromePosition = {
|
|
221
|
+
left: parseInt(els.chrome.style.left, 10) || 0,
|
|
222
|
+
top: parseInt(els.chrome.style.top, 10) || 0,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
on(els.toolbarHandle, 'pointerup', endChromeDrag);
|
|
226
|
+
on(els.toolbarHandle, 'pointercancel', () => {
|
|
227
|
+
chromeDragStart = null;
|
|
228
|
+
els.chrome.classList.remove('dragging');
|
|
229
|
+
});
|
|
66
230
|
// ===== Transform =====
|
|
67
231
|
function applyTransform() {
|
|
68
232
|
const w = Math.max(1, Math.round(state.naturalWidth * state.scale));
|
|
@@ -99,6 +263,7 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
99
263
|
if (!state.naturalWidth || !state.naturalHeight)
|
|
100
264
|
return;
|
|
101
265
|
state.activeFit = 'fitPage';
|
|
266
|
+
state.isDirty = false;
|
|
102
267
|
const sx = els.viewport.clientWidth / state.naturalWidth;
|
|
103
268
|
const sy = els.viewport.clientHeight / state.naturalHeight;
|
|
104
269
|
setScale(Math.min(sx, sy));
|
|
@@ -109,12 +274,14 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
109
274
|
if (!state.naturalWidth)
|
|
110
275
|
return;
|
|
111
276
|
state.activeFit = 'fitWidth';
|
|
277
|
+
state.isDirty = false;
|
|
112
278
|
setScale(els.viewport.clientWidth / state.naturalWidth);
|
|
113
279
|
els.viewport.scrollLeft = 0;
|
|
114
280
|
els.viewport.scrollTop = 0;
|
|
115
281
|
}
|
|
116
282
|
function actualSize() {
|
|
117
283
|
state.activeFit = 'manual';
|
|
284
|
+
state.isDirty = true;
|
|
118
285
|
setScale(1);
|
|
119
286
|
}
|
|
120
287
|
function applyDefaultFit() {
|
|
@@ -126,10 +293,26 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
126
293
|
fitPage();
|
|
127
294
|
}
|
|
128
295
|
function reapplyActiveFit() {
|
|
129
|
-
|
|
296
|
+
const newW = els.viewport.clientWidth;
|
|
297
|
+
const newH = els.viewport.clientHeight;
|
|
298
|
+
if (state.isDirty) {
|
|
299
|
+
const oldW = state.lastViewportWidth || newW;
|
|
300
|
+
const oldH = state.lastViewportHeight || newH;
|
|
301
|
+
if (state.scale > 0 && (oldW !== newW || oldH !== newH)) {
|
|
302
|
+
const cx = (els.viewport.scrollLeft + oldW / 2) / state.scale;
|
|
303
|
+
const cy = (els.viewport.scrollTop + oldH / 2) / state.scale;
|
|
304
|
+
els.viewport.scrollLeft = Math.max(0, cx * state.scale - newW / 2);
|
|
305
|
+
els.viewport.scrollTop = Math.max(0, cy * state.scale - newH / 2);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else if (state.activeFit === 'fitPage') {
|
|
130
309
|
fitPage();
|
|
131
|
-
|
|
310
|
+
}
|
|
311
|
+
else if (state.activeFit === 'fitWidth') {
|
|
132
312
|
fitWidth();
|
|
313
|
+
}
|
|
314
|
+
state.lastViewportWidth = newW;
|
|
315
|
+
state.lastViewportHeight = newH;
|
|
133
316
|
}
|
|
134
317
|
// ===== Render handling =====
|
|
135
318
|
function extractNaturalSize(svgEl) {
|
|
@@ -221,6 +404,8 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
221
404
|
els.diagSummary.classList.add('warn-only');
|
|
222
405
|
if (!errors && !warnings)
|
|
223
406
|
els.diagSummary.classList.add('clean');
|
|
407
|
+
// Hide Open Problems link when there are no issues
|
|
408
|
+
els.openProblems.hidden = errors + warnings === 0;
|
|
224
409
|
const tbody = els.diagTbody;
|
|
225
410
|
while (tbody.firstChild)
|
|
226
411
|
tbody.removeChild(tbody.firstChild);
|
|
@@ -286,8 +471,8 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
286
471
|
}
|
|
287
472
|
svgEl.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
|
288
473
|
}
|
|
289
|
-
const maxW =
|
|
290
|
-
const maxH =
|
|
474
|
+
const maxW = 120;
|
|
475
|
+
const maxH = 90;
|
|
291
476
|
if (state.naturalWidth && state.naturalHeight) {
|
|
292
477
|
const ratio = state.naturalWidth / state.naturalHeight;
|
|
293
478
|
let w = maxW;
|
|
@@ -319,7 +504,7 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
319
504
|
els.minimapRect.style.height = `${rectH}px`;
|
|
320
505
|
}
|
|
321
506
|
function updateMinimapVisibility() {
|
|
322
|
-
if (!state.showMinimap
|
|
507
|
+
if (!state.showMinimap) {
|
|
323
508
|
els.minimap.classList.add('hidden');
|
|
324
509
|
return;
|
|
325
510
|
}
|
|
@@ -333,11 +518,6 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
333
518
|
els.minimap.classList.remove('hidden');
|
|
334
519
|
}
|
|
335
520
|
}
|
|
336
|
-
on(els.minimapClose, 'click', (e) => {
|
|
337
|
-
e.stopPropagation();
|
|
338
|
-
state.minimapDismissedThisSession = true;
|
|
339
|
-
els.minimap.classList.add('hidden');
|
|
340
|
-
});
|
|
341
521
|
function panToMinimapPoint(clientX, clientY) {
|
|
342
522
|
const rect = els.minimapCanvas.getBoundingClientRect();
|
|
343
523
|
const x = (clientX - rect.left) / rect.width;
|
|
@@ -361,17 +541,31 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
361
541
|
miniDragging = false;
|
|
362
542
|
});
|
|
363
543
|
on(els.viewport, 'scroll', updateMinimapRect);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
544
|
+
let resizeTimer = null;
|
|
545
|
+
function handleResize() {
|
|
546
|
+
if (resizeTimer)
|
|
547
|
+
clearTimeout(resizeTimer);
|
|
548
|
+
resizeTimer = setTimeout(() => {
|
|
549
|
+
resizeTimer = null;
|
|
550
|
+
reapplyActiveFit();
|
|
551
|
+
updateMinimapRect();
|
|
552
|
+
updateMinimapVisibility();
|
|
553
|
+
repositionChrome();
|
|
554
|
+
}, 50);
|
|
555
|
+
}
|
|
556
|
+
on(win, 'resize', handleResize);
|
|
557
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
558
|
+
const ro = new ResizeObserver(handleResize);
|
|
559
|
+
ro.observe(els.viewport);
|
|
560
|
+
cleanups.push(() => ro.disconnect());
|
|
561
|
+
}
|
|
369
562
|
// ===== Wheel zoom =====
|
|
370
563
|
on(els.viewport, 'wheel', (e) => {
|
|
371
564
|
if (!(e.ctrlKey || e.metaKey))
|
|
372
565
|
return;
|
|
373
566
|
e.preventDefault();
|
|
374
567
|
state.activeFit = 'manual';
|
|
568
|
+
state.isDirty = true;
|
|
375
569
|
const rect = els.viewport.getBoundingClientRect();
|
|
376
570
|
const anchorX = e.clientX - rect.left;
|
|
377
571
|
const anchorY = e.clientY - rect.top;
|
|
@@ -420,6 +614,7 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
420
614
|
on(els.viewport, 'mousedown', (e) => {
|
|
421
615
|
if (!spaceDown)
|
|
422
616
|
return;
|
|
617
|
+
state.isDirty = true;
|
|
423
618
|
dragOrigin = {
|
|
424
619
|
x: e.clientX,
|
|
425
620
|
y: e.clientY,
|
|
@@ -444,96 +639,123 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
444
639
|
// ===== Toolbar buttons =====
|
|
445
640
|
on(els.zoomOut, 'click', () => {
|
|
446
641
|
state.activeFit = 'manual';
|
|
642
|
+
state.isDirty = true;
|
|
447
643
|
setScale(state.scale / 1.1);
|
|
448
644
|
});
|
|
449
645
|
on(els.zoomIn, 'click', () => {
|
|
450
646
|
state.activeFit = 'manual';
|
|
647
|
+
state.isDirty = true;
|
|
451
648
|
setScale(state.scale * 1.1);
|
|
452
649
|
});
|
|
453
650
|
on(els.zoomReset, 'click', actualSize);
|
|
454
651
|
on(els.fitWidth, 'click', fitWidth);
|
|
455
652
|
on(els.fitPage, 'click', fitPage);
|
|
456
|
-
// =====
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
els.viewMenu.hidden = true;
|
|
463
|
-
menu.hidden = !menu.hidden;
|
|
464
|
-
});
|
|
653
|
+
// ===== Menu state helpers =====
|
|
654
|
+
function closeSubMenus() {
|
|
655
|
+
els.formatMenu.hidden = true;
|
|
656
|
+
els.themeMenu.hidden = true;
|
|
657
|
+
els.nowPicker.hidden = true;
|
|
658
|
+
els.linksMenu.hidden = true;
|
|
465
659
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
on(doc, 'click', () => {
|
|
470
|
-
els.saveMenu.hidden = true;
|
|
471
|
-
els.copyMenu.hidden = true;
|
|
472
|
-
els.viewMenu.hidden = true;
|
|
473
|
-
});
|
|
474
|
-
for (const m of [els.saveMenu, els.copyMenu, els.viewMenu]) {
|
|
475
|
-
on(m, 'click', (e) => e.stopPropagation());
|
|
476
|
-
}
|
|
477
|
-
// ===== View options =====
|
|
478
|
-
function refreshViewMenu() {
|
|
479
|
-
const items = els.viewMenu.querySelectorAll('.view-opt');
|
|
480
|
-
for (const item of Array.from(items)) {
|
|
481
|
-
const opt = item.getAttribute('data-opt');
|
|
482
|
-
const value = item.getAttribute('data-value');
|
|
483
|
-
let active = false;
|
|
484
|
-
if (opt === 'theme')
|
|
485
|
-
active = state.view.theme === value;
|
|
486
|
-
else if (opt === 'now')
|
|
487
|
-
active = state.view.now === value;
|
|
488
|
-
else if (opt === 'showLinks')
|
|
489
|
-
active = state.view.showLinks;
|
|
490
|
-
item.setAttribute('data-active', active ? 'true' : 'false');
|
|
491
|
-
}
|
|
660
|
+
function closeAllMenus() {
|
|
661
|
+
els.moreMenu.hidden = true;
|
|
662
|
+
closeSubMenus();
|
|
492
663
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
664
|
+
/**
|
|
665
|
+
* Keep a just-opened flyout (more-menu, a sub-dropdown, or the
|
|
666
|
+
* calendar) inside the preview root + gutters. `defaultSide` is the
|
|
667
|
+
* edge the panel is anchored to in CSS; if opening that way would
|
|
668
|
+
* spill past the opposite gutter the panel is flipped, then any
|
|
669
|
+
* residual horizontal overflow is nudged back with a transform and
|
|
670
|
+
* excess height is capped with an internal scroll.
|
|
671
|
+
*
|
|
672
|
+
* `clampVertical` is off for the more-menu so capping its height
|
|
673
|
+
* never clips the calendar that pops out of it.
|
|
674
|
+
*/
|
|
675
|
+
function placeFlyout(panel, defaultSide, clampVertical = true) {
|
|
676
|
+
panel.classList.remove('flip');
|
|
677
|
+
panel.style.maxHeight = '';
|
|
678
|
+
panel.style.overflowY = '';
|
|
679
|
+
panel.style.transform = '';
|
|
680
|
+
const root = rootEl.getBoundingClientRect();
|
|
681
|
+
// Skip when the host hasn't laid out yet (e.g. happy-dom tests).
|
|
682
|
+
if (root.width === 0 && root.height === 0)
|
|
507
683
|
return;
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
684
|
+
const g = getGutter();
|
|
685
|
+
let box = panel.getBoundingClientRect();
|
|
686
|
+
const fitsWidth = box.width <= root.width - 2 * g;
|
|
687
|
+
if (fitsWidth && defaultSide === 'left' && box.right > root.right - g) {
|
|
688
|
+
panel.classList.add('flip');
|
|
513
689
|
}
|
|
514
|
-
else if (
|
|
515
|
-
|
|
516
|
-
state.view.overridden.now = true;
|
|
690
|
+
else if (fitsWidth && defaultSide === 'right' && box.left < root.left + g) {
|
|
691
|
+
panel.classList.add('flip');
|
|
517
692
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
693
|
+
box = panel.getBoundingClientRect();
|
|
694
|
+
let dx = 0;
|
|
695
|
+
if (box.right > root.right - g)
|
|
696
|
+
dx = root.right - g - box.right;
|
|
697
|
+
if (box.left + dx < root.left + g)
|
|
698
|
+
dx = root.left + g - box.left;
|
|
699
|
+
if (dx !== 0)
|
|
700
|
+
panel.style.transform = `translateX(${Math.round(dx)}px)`;
|
|
701
|
+
if (clampVertical) {
|
|
702
|
+
box = panel.getBoundingClientRect();
|
|
703
|
+
const avail = root.bottom - g - box.top;
|
|
704
|
+
if (box.height > avail && avail > 80) {
|
|
705
|
+
panel.style.maxHeight = `${Math.round(avail)}px`;
|
|
706
|
+
panel.style.overflowY = 'auto';
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
on(doc, 'click', closeAllMenus);
|
|
711
|
+
on(els.moreMenu, 'click', (e) => e.stopPropagation());
|
|
712
|
+
// ===== More-menu toggle =====
|
|
713
|
+
on(els.moreToggle, 'click', (e) => {
|
|
714
|
+
e.stopPropagation();
|
|
715
|
+
const opening = els.moreMenu.hidden;
|
|
716
|
+
closeAllMenus();
|
|
717
|
+
if (opening) {
|
|
718
|
+
els.moreMenu.hidden = false;
|
|
719
|
+
placeFlyout(els.moreMenu, 'right', /* clampVertical */ false);
|
|
521
720
|
}
|
|
522
|
-
refreshViewMenu();
|
|
523
|
-
els.viewMenu.hidden = true;
|
|
524
|
-
postViewOverrides();
|
|
525
721
|
});
|
|
526
|
-
// =====
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
els.
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
722
|
+
// ===== Format dropdown =====
|
|
723
|
+
on(els.formatToggle, 'click', (e) => {
|
|
724
|
+
e.stopPropagation();
|
|
725
|
+
const opening = els.formatMenu.hidden;
|
|
726
|
+
closeSubMenus();
|
|
727
|
+
if (opening) {
|
|
728
|
+
els.formatMenu.hidden = false;
|
|
729
|
+
placeFlyout(els.formatMenu, 'left');
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
on(els.formatMenu, 'click', (e) => {
|
|
733
|
+
e.stopPropagation();
|
|
734
|
+
const btn = e.target.closest('.format-opt');
|
|
735
|
+
if (!btn)
|
|
736
|
+
return;
|
|
737
|
+
const value = btn.getAttribute('data-value');
|
|
738
|
+
if (!value)
|
|
739
|
+
return;
|
|
740
|
+
state.export.format = value;
|
|
741
|
+
refreshFormatToggle();
|
|
742
|
+
els.formatMenu.hidden = true;
|
|
743
|
+
});
|
|
744
|
+
function refreshFormatToggle() {
|
|
745
|
+
els.formatToggle.textContent = `${state.export.format.toUpperCase()} \u25be`;
|
|
746
|
+
for (const btn of Array.from(els.formatMenu.querySelectorAll('.format-opt'))) {
|
|
747
|
+
btn.setAttribute('data-active', (btn.getAttribute('data-value') === state.export.format).toString());
|
|
748
|
+
}
|
|
536
749
|
}
|
|
750
|
+
// ===== Copy / export actions =====
|
|
751
|
+
on(els.copyAction, 'click', () => {
|
|
752
|
+
closeAllMenus();
|
|
753
|
+
void handleExportAction(`copy-${state.export.format}`);
|
|
754
|
+
});
|
|
755
|
+
on(els.exportAction, 'click', () => {
|
|
756
|
+
closeAllMenus();
|
|
757
|
+
void handleExportAction(`save-${state.export.format}`);
|
|
758
|
+
});
|
|
537
759
|
async function handleExportAction(action) {
|
|
538
760
|
if (!state.svgString)
|
|
539
761
|
return;
|
|
@@ -619,15 +841,264 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
619
841
|
img.src = url;
|
|
620
842
|
});
|
|
621
843
|
}
|
|
622
|
-
// =====
|
|
844
|
+
// ===== Theme dropdown =====
|
|
845
|
+
function buildThemeMenu(themes) {
|
|
846
|
+
els.themeMenu.innerHTML = '';
|
|
847
|
+
const autoLi = doc.createElement('li');
|
|
848
|
+
const autoBtn = doc.createElement('button');
|
|
849
|
+
autoBtn.className = 'btn theme-opt';
|
|
850
|
+
autoBtn.setAttribute('data-value', 'auto');
|
|
851
|
+
autoBtn.textContent = 'Auto';
|
|
852
|
+
autoLi.appendChild(autoBtn);
|
|
853
|
+
els.themeMenu.appendChild(autoLi);
|
|
854
|
+
for (const theme of themes) {
|
|
855
|
+
const li = doc.createElement('li');
|
|
856
|
+
const btn = doc.createElement('button');
|
|
857
|
+
btn.className = 'btn theme-opt';
|
|
858
|
+
btn.setAttribute('data-value', theme);
|
|
859
|
+
const chip = doc.createElement('code');
|
|
860
|
+
chip.className = 'code-chip';
|
|
861
|
+
chip.textContent = theme;
|
|
862
|
+
btn.appendChild(chip);
|
|
863
|
+
li.appendChild(btn);
|
|
864
|
+
els.themeMenu.appendChild(li);
|
|
865
|
+
}
|
|
866
|
+
refreshThemeToggle();
|
|
867
|
+
}
|
|
868
|
+
on(els.themeToggle, 'click', (e) => {
|
|
869
|
+
e.stopPropagation();
|
|
870
|
+
const opening = els.themeMenu.hidden;
|
|
871
|
+
closeSubMenus();
|
|
872
|
+
if (opening) {
|
|
873
|
+
els.themeMenu.hidden = false;
|
|
874
|
+
placeFlyout(els.themeMenu, 'left');
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
on(els.themeMenu, 'click', (e) => {
|
|
878
|
+
e.stopPropagation();
|
|
879
|
+
const btn = e.target.closest('.theme-opt');
|
|
880
|
+
if (!btn)
|
|
881
|
+
return;
|
|
882
|
+
const value = btn.getAttribute('data-value');
|
|
883
|
+
if (!value)
|
|
884
|
+
return;
|
|
885
|
+
state.view.theme = value;
|
|
886
|
+
state.view.overridden.theme = true;
|
|
887
|
+
refreshThemeToggle();
|
|
888
|
+
els.themeMenu.hidden = true;
|
|
889
|
+
postViewOverrides();
|
|
890
|
+
});
|
|
891
|
+
function refreshThemeToggle() {
|
|
892
|
+
if (state.view.theme === 'auto') {
|
|
893
|
+
els.themeToggle.textContent = `Auto \u25be`;
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
els.themeToggle.innerHTML = '';
|
|
897
|
+
const chip = doc.createElement('code');
|
|
898
|
+
chip.className = 'code-chip';
|
|
899
|
+
chip.textContent = state.view.theme;
|
|
900
|
+
els.themeToggle.appendChild(chip);
|
|
901
|
+
els.themeToggle.appendChild(doc.createTextNode(` \u25be`));
|
|
902
|
+
}
|
|
903
|
+
for (const btn of Array.from(els.themeMenu.querySelectorAll('.theme-opt'))) {
|
|
904
|
+
btn.setAttribute('data-active', (btn.getAttribute('data-value') === state.view.theme).toString());
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
// ===== Now picker (calendar) =====
|
|
908
|
+
let calendarYear = new Date().getFullYear();
|
|
909
|
+
let calendarMonth = new Date().getMonth();
|
|
910
|
+
function buildCalendar() {
|
|
911
|
+
const picker = els.nowPicker;
|
|
912
|
+
picker.innerHTML = '';
|
|
913
|
+
const year = calendarYear;
|
|
914
|
+
const month = calendarMonth;
|
|
915
|
+
const nav = doc.createElement('div');
|
|
916
|
+
nav.className = 'cal-nav';
|
|
917
|
+
const prevBtn = doc.createElement('button');
|
|
918
|
+
prevBtn.className = 'btn cal-nav-btn';
|
|
919
|
+
prevBtn.textContent = '\u2039';
|
|
920
|
+
prevBtn.addEventListener('click', (e) => {
|
|
921
|
+
e.stopPropagation();
|
|
922
|
+
calendarMonth--;
|
|
923
|
+
if (calendarMonth < 0) {
|
|
924
|
+
calendarMonth = 11;
|
|
925
|
+
calendarYear--;
|
|
926
|
+
}
|
|
927
|
+
buildCalendar();
|
|
928
|
+
});
|
|
929
|
+
const heading = doc.createElement('span');
|
|
930
|
+
heading.className = 'cal-heading';
|
|
931
|
+
heading.textContent = new Intl.DateTimeFormat(state.locale, {
|
|
932
|
+
month: 'long',
|
|
933
|
+
year: 'numeric',
|
|
934
|
+
}).format(new Date(year, month, 1));
|
|
935
|
+
const nextBtn = doc.createElement('button');
|
|
936
|
+
nextBtn.className = 'btn cal-nav-btn';
|
|
937
|
+
nextBtn.textContent = '\u203a';
|
|
938
|
+
nextBtn.addEventListener('click', (e) => {
|
|
939
|
+
e.stopPropagation();
|
|
940
|
+
calendarMonth++;
|
|
941
|
+
if (calendarMonth > 11) {
|
|
942
|
+
calendarMonth = 0;
|
|
943
|
+
calendarYear++;
|
|
944
|
+
}
|
|
945
|
+
buildCalendar();
|
|
946
|
+
});
|
|
947
|
+
nav.appendChild(prevBtn);
|
|
948
|
+
nav.appendChild(heading);
|
|
949
|
+
nav.appendChild(nextBtn);
|
|
950
|
+
picker.appendChild(nav);
|
|
951
|
+
const grid = doc.createElement('div');
|
|
952
|
+
grid.className = 'cal-grid';
|
|
953
|
+
for (const d of ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']) {
|
|
954
|
+
const cell = doc.createElement('span');
|
|
955
|
+
cell.className = 'cal-dow';
|
|
956
|
+
cell.textContent = d;
|
|
957
|
+
grid.appendChild(cell);
|
|
958
|
+
}
|
|
959
|
+
const firstDow = new Date(year, month, 1).getDay();
|
|
960
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
961
|
+
for (let i = 0; i < firstDow; i++) {
|
|
962
|
+
const empty = doc.createElement('span');
|
|
963
|
+
empty.className = 'cal-empty';
|
|
964
|
+
grid.appendChild(empty);
|
|
965
|
+
}
|
|
966
|
+
const now = new Date();
|
|
967
|
+
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
968
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
969
|
+
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
970
|
+
const btn = doc.createElement('button');
|
|
971
|
+
btn.className = 'btn cal-day';
|
|
972
|
+
btn.textContent = String(d);
|
|
973
|
+
if (dateStr === todayStr)
|
|
974
|
+
btn.classList.add('is-today');
|
|
975
|
+
if (state.view.now === dateStr)
|
|
976
|
+
btn.classList.add('is-selected');
|
|
977
|
+
btn.addEventListener('click', (e) => {
|
|
978
|
+
e.stopPropagation();
|
|
979
|
+
applyNow(dateStr);
|
|
980
|
+
});
|
|
981
|
+
grid.appendChild(btn);
|
|
982
|
+
}
|
|
983
|
+
picker.appendChild(grid);
|
|
984
|
+
const footer = doc.createElement('div');
|
|
985
|
+
footer.className = 'cal-footer';
|
|
986
|
+
const todayBtnEl = doc.createElement('button');
|
|
987
|
+
todayBtnEl.className = 'btn cal-footer-btn';
|
|
988
|
+
todayBtnEl.textContent = 'Today';
|
|
989
|
+
todayBtnEl.addEventListener('click', (e) => {
|
|
990
|
+
e.stopPropagation();
|
|
991
|
+
applyNow('today');
|
|
992
|
+
});
|
|
993
|
+
const noneBtnEl = doc.createElement('button');
|
|
994
|
+
noneBtnEl.className = 'btn cal-footer-btn';
|
|
995
|
+
noneBtnEl.textContent = 'None';
|
|
996
|
+
noneBtnEl.addEventListener('click', (e) => {
|
|
997
|
+
e.stopPropagation();
|
|
998
|
+
applyNow('hide');
|
|
999
|
+
});
|
|
1000
|
+
footer.appendChild(todayBtnEl);
|
|
1001
|
+
footer.appendChild(noneBtnEl);
|
|
1002
|
+
picker.appendChild(footer);
|
|
1003
|
+
}
|
|
1004
|
+
function applyNow(value) {
|
|
1005
|
+
state.view.now = value;
|
|
1006
|
+
state.view.overridden.now = true;
|
|
1007
|
+
refreshNowToggle();
|
|
1008
|
+
els.nowPicker.hidden = true;
|
|
1009
|
+
postViewOverrides();
|
|
1010
|
+
}
|
|
1011
|
+
on(els.nowToggle, 'click', (e) => {
|
|
1012
|
+
e.stopPropagation();
|
|
1013
|
+
const opening = els.nowPicker.hidden;
|
|
1014
|
+
closeSubMenus();
|
|
1015
|
+
if (opening) {
|
|
1016
|
+
buildCalendar();
|
|
1017
|
+
els.nowPicker.hidden = false;
|
|
1018
|
+
placeFlyout(els.nowPicker, 'left');
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
function refreshNowToggle() {
|
|
1022
|
+
let label;
|
|
1023
|
+
if (state.view.now === 'today') {
|
|
1024
|
+
label = 'Today';
|
|
1025
|
+
}
|
|
1026
|
+
else if (state.view.now === 'hide') {
|
|
1027
|
+
label = 'None';
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
try {
|
|
1031
|
+
label = new Intl.DateTimeFormat(state.locale, { dateStyle: 'medium' }).format(new Date(`${state.view.now}T00:00:00`));
|
|
1032
|
+
}
|
|
1033
|
+
catch {
|
|
1034
|
+
label = state.view.now;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
els.nowLabel.textContent = label;
|
|
1038
|
+
}
|
|
1039
|
+
// ===== Show-links dropdown =====
|
|
1040
|
+
on(els.linksToggle, 'click', (e) => {
|
|
1041
|
+
e.stopPropagation();
|
|
1042
|
+
const opening = els.linksMenu.hidden;
|
|
1043
|
+
closeSubMenus();
|
|
1044
|
+
if (opening) {
|
|
1045
|
+
els.linksMenu.hidden = false;
|
|
1046
|
+
placeFlyout(els.linksMenu, 'left');
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
on(els.linksMenu, 'click', (e) => {
|
|
1050
|
+
e.stopPropagation();
|
|
1051
|
+
const btn = e.target.closest('.links-opt');
|
|
1052
|
+
if (!btn)
|
|
1053
|
+
return;
|
|
1054
|
+
const value = btn.getAttribute('data-value');
|
|
1055
|
+
if (value === null)
|
|
1056
|
+
return;
|
|
1057
|
+
state.view.showLinks = value === 'true';
|
|
1058
|
+
state.view.overridden.showLinks = true;
|
|
1059
|
+
refreshLinksToggle();
|
|
1060
|
+
els.linksMenu.hidden = true;
|
|
1061
|
+
postViewOverrides();
|
|
1062
|
+
});
|
|
1063
|
+
function refreshLinksToggle() {
|
|
1064
|
+
els.linksToggle.textContent = `${state.view.showLinks ? 'Yes' : 'No'} \u25be`;
|
|
1065
|
+
for (const btn of Array.from(els.linksMenu.querySelectorAll('.links-opt'))) {
|
|
1066
|
+
btn.setAttribute('data-active', (btn.getAttribute('data-value') === (state.view.showLinks ? 'true' : 'false')).toString());
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// ===== View-options helpers =====
|
|
1070
|
+
function postViewOverrides() {
|
|
1071
|
+
const overrides = {};
|
|
1072
|
+
if (state.view.overridden.theme)
|
|
1073
|
+
overrides.theme = state.view.theme;
|
|
1074
|
+
if (state.view.overridden.now)
|
|
1075
|
+
overrides.now = state.view.now;
|
|
1076
|
+
if (state.view.overridden.showLinks)
|
|
1077
|
+
overrides.showLinks = state.view.showLinks;
|
|
1078
|
+
options.onViewOptions?.(overrides);
|
|
1079
|
+
}
|
|
1080
|
+
function refreshAll() {
|
|
1081
|
+
refreshThemeToggle();
|
|
1082
|
+
refreshNowToggle();
|
|
1083
|
+
refreshLinksToggle();
|
|
1084
|
+
refreshFormatToggle();
|
|
1085
|
+
}
|
|
1086
|
+
// ===== Theme control visibility =====
|
|
1087
|
+
if (options.themeControl === 'hide') {
|
|
1088
|
+
const themeRow = els.themeMenu.closest('.theme-control-row');
|
|
1089
|
+
if (themeRow)
|
|
1090
|
+
themeRow.style.display = 'none';
|
|
1091
|
+
}
|
|
1092
|
+
// ===== Apply initial theme menu + baselines =====
|
|
1093
|
+
buildThemeMenu(options.availableThemes ?? ['light', 'dark', 'grayscale']);
|
|
623
1094
|
if (options.viewBaseline) {
|
|
624
1095
|
applyBaseline(state, options.viewBaseline, true);
|
|
625
|
-
refreshViewMenu();
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
refreshViewMenu();
|
|
629
1096
|
}
|
|
1097
|
+
refreshAll();
|
|
630
1098
|
updateMinimapVisibility();
|
|
1099
|
+
// Anchor the toolbar explicitly (default: upper-right) so resize
|
|
1100
|
+
// shifts it left rather than the CSS `right` anchor squishing it.
|
|
1101
|
+
repositionChrome();
|
|
631
1102
|
// ===== Imperative API =====
|
|
632
1103
|
return {
|
|
633
1104
|
setSvg,
|
|
@@ -635,16 +1106,26 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
635
1106
|
setFatal: showFatal,
|
|
636
1107
|
setViewBaseline(baseline, resetOverrides = false) {
|
|
637
1108
|
applyBaseline(state, baseline, resetOverrides);
|
|
638
|
-
|
|
1109
|
+
refreshAll();
|
|
639
1110
|
},
|
|
640
1111
|
setDefaultFit(fit) {
|
|
641
1112
|
state.defaultFit = fit;
|
|
642
1113
|
},
|
|
643
1114
|
setShowMinimap(show) {
|
|
644
1115
|
state.showMinimap = show;
|
|
645
|
-
state.minimapDismissedThisSession = false;
|
|
646
1116
|
updateMinimapVisibility();
|
|
647
1117
|
},
|
|
1118
|
+
setMode(mode) {
|
|
1119
|
+
const resolved = mode === 'system' ? resolveSystemMode() : mode;
|
|
1120
|
+
applyMode(resolved);
|
|
1121
|
+
},
|
|
1122
|
+
setAvailableThemes(themes) {
|
|
1123
|
+
buildThemeMenu(themes);
|
|
1124
|
+
},
|
|
1125
|
+
setLocale(locale) {
|
|
1126
|
+
state.locale = locale;
|
|
1127
|
+
refreshNowToggle();
|
|
1128
|
+
},
|
|
648
1129
|
fitPage,
|
|
649
1130
|
fitWidth,
|
|
650
1131
|
actualSize,
|
|
@@ -653,15 +1134,19 @@ export function mountPreview(rootEl, options = {}) {
|
|
|
653
1134
|
},
|
|
654
1135
|
setZoom(scale) {
|
|
655
1136
|
state.activeFit = 'manual';
|
|
1137
|
+
state.isDirty = true;
|
|
656
1138
|
setScale(scale);
|
|
657
1139
|
},
|
|
658
1140
|
dispose() {
|
|
659
1141
|
if (fadeTimer)
|
|
660
1142
|
clearTimeout(fadeTimer);
|
|
1143
|
+
if (resizeTimer)
|
|
1144
|
+
clearTimeout(resizeTimer);
|
|
661
1145
|
for (const c of cleanups)
|
|
662
1146
|
c();
|
|
663
1147
|
cleanups.length = 0;
|
|
664
1148
|
rootEl.classList.remove('nl-preview-root');
|
|
1149
|
+
rootEl.removeAttribute('data-nl-mode');
|
|
665
1150
|
rootEl.innerHTML = '';
|
|
666
1151
|
},
|
|
667
1152
|
};
|
|
@@ -671,10 +1156,15 @@ function applyBaseline(state, baseline, resetOverrides) {
|
|
|
671
1156
|
state.view.theme = baseline.theme;
|
|
672
1157
|
}
|
|
673
1158
|
if (baseline.now !== undefined && !state.view.overridden.now) {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1159
|
+
if (baseline.now === 'none' || baseline.now === 'hide') {
|
|
1160
|
+
state.view.now = 'hide';
|
|
1161
|
+
}
|
|
1162
|
+
else if (typeof baseline.now === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(baseline.now)) {
|
|
1163
|
+
state.view.now = baseline.now;
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
state.view.now = 'today';
|
|
1167
|
+
}
|
|
678
1168
|
}
|
|
679
1169
|
if (baseline.showLinks !== undefined && !state.view.overridden.showLinks) {
|
|
680
1170
|
state.view.showLinks = baseline.showLinks !== false;
|