@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/src/styles.ts CHANGED
@@ -3,15 +3,21 @@
3
3
  // without forking the package. Sensible defaults baked in so the
4
4
  // viewport works out of the box on a blank page.
5
5
  //
6
- // VS Code consumers paste `VSCODE_THEME_BRIDGE_CSS` (below) into their
7
- // webview to map `--nl-preview-*` to the matching `--vscode-*` tokens
8
- // — that keeps the viewport in lock-step with the user's workbench
9
- // theme without having to keep two sets of variables in sync.
6
+ // Chrome (toolbar, menus, minimap) uses `--nl-chrome-*` tokens driven
7
+ // exclusively by the `data-nl-mode="light|dark"` attribute on the root
8
+ // element — these intentionally do NOT inherit VS Code workbench colors
9
+ // so the toolbar stays readable regardless of the active editor theme.
10
+ //
11
+ // VS Code consumers paste `VSCODE_THEME_BRIDGE_CSS` into their webview
12
+ // to map viewport/canvas `--nl-preview-*` tokens to `--vscode-*`
13
+ // variables. This bridge no longer touches chrome tokens.
10
14
 
11
15
  /**
12
- * Bridge CSS for VS Code webview consumers. Maps every `--nl-preview-*`
13
- * token to the matching `--vscode-*` variable so the viewport renders
14
- * in the active workbench colour theme automatically.
16
+ * Bridge CSS for VS Code webview consumers. Maps viewport and
17
+ * diagnostic `--nl-preview-*` tokens to `--vscode-*` variables.
18
+ * Does NOT map chrome (toolbar/menu) tokens — those are driven by
19
+ * `data-nl-mode` so the toolbar palette is independent of the
20
+ * workbench color theme.
15
21
  *
16
22
  * Inject as a `<style>` block after the preview-shell stylesheet.
17
23
  */
@@ -23,9 +29,6 @@ export const VSCODE_THEME_BRIDGE_CSS = `
23
29
  --nl-preview-font-size: var(--vscode-font-size, 13px);
24
30
  --nl-preview-widget-bg: var(--vscode-editorWidget-background, rgba(40,40,40,0.9));
25
31
  --nl-preview-widget-border: var(--vscode-editorWidget-border, rgba(255,255,255,0.1));
26
- --nl-preview-toolbar-hover: var(--vscode-toolbar-hoverBackground, rgba(255,255,255,0.08));
27
- --nl-preview-toolbar-active: var(--vscode-toolbar-activeBackground, rgba(255,255,255,0.12));
28
- --nl-preview-list-hover: var(--vscode-list-hoverBackground, rgba(255,255,255,0.04));
29
32
  --nl-preview-description: var(--vscode-descriptionForeground, #999);
30
33
  --nl-preview-error: var(--vscode-editorError-foreground, #f48771);
31
34
  --nl-preview-warning: var(--vscode-editorWarning-foreground, #cca700);
@@ -38,9 +41,8 @@ export const VSCODE_THEME_BRIDGE_CSS = `
38
41
 
39
42
  /**
40
43
  * Default stylesheet for the viewport. Provides reasonable defaults
41
- * for every `--nl-preview-*` token so the viewport works on a blank
42
- * page; consumers override by re-declaring the variables on a
43
- * higher-specificity selector.
44
+ * for every `--nl-preview-*` token and both dark/light `--nl-chrome-*`
45
+ * palettes so the viewport works on a blank page without any bridge CSS.
44
46
  */
45
47
  export const PREVIEW_SHELL_CSS = `
46
48
  :root {
@@ -51,9 +53,6 @@ export const PREVIEW_SHELL_CSS = `
51
53
  --nl-preview-font-size: 13px;
52
54
  --nl-preview-widget-bg: rgba(40,40,40,0.9);
53
55
  --nl-preview-widget-border: rgba(255,255,255,0.1);
54
- --nl-preview-toolbar-hover: rgba(255,255,255,0.08);
55
- --nl-preview-toolbar-active: rgba(255,255,255,0.12);
56
- --nl-preview-list-hover: rgba(255,255,255,0.04);
57
56
  --nl-preview-description: #999;
58
57
  --nl-preview-error: #f48771;
59
58
  --nl-preview-warning: #cca700;
@@ -70,66 +69,233 @@ export const PREVIEW_SHELL_CSS = `
70
69
  font-family: var(--nl-preview-font-family);
71
70
  font-size: var(--nl-preview-font-size);
72
71
  overflow: hidden;
72
+ /* Shared positioning gutter for all floating elements */
73
+ --nl-preview-gutter: 8px;
74
+ /* Chrome palette — dark default */
75
+ --nl-chrome-bg: rgba(30, 30, 30, 0.93);
76
+ --nl-chrome-border: rgba(255, 255, 255, 0.11);
77
+ --nl-chrome-fg: #d4d4d4;
78
+ --nl-chrome-hover: rgba(255, 255, 255, 0.08);
79
+ --nl-chrome-active: rgba(255, 255, 255, 0.13);
80
+ --nl-chrome-muted: #888;
81
+ --nl-chrome-chip-bg: rgba(255, 255, 255, 0.07);
82
+ --nl-chrome-shadow: rgba(0, 0, 0, 0.32);
83
+ }
84
+ /* Chrome palette — light mode override (driven by data-nl-mode, never by workbench) */
85
+ .nl-preview-root[data-nl-mode="light"] {
86
+ --nl-chrome-bg: rgba(248, 248, 248, 0.96);
87
+ --nl-chrome-border: rgba(0, 0, 0, 0.13);
88
+ --nl-chrome-fg: #3c3c3c;
89
+ --nl-chrome-hover: rgba(0, 0, 0, 0.06);
90
+ --nl-chrome-active: rgba(0, 0, 0, 0.10);
91
+ --nl-chrome-muted: #717171;
92
+ --nl-chrome-chip-bg: rgba(0, 0, 0, 0.06);
93
+ --nl-chrome-shadow: rgba(0, 0, 0, 0.14);
73
94
  }
74
95
  .nl-preview-root .viewport {
75
96
  position: absolute; inset: 0;
76
97
  overflow: auto;
98
+ display: flex;
77
99
  }
78
100
  .nl-preview-root .canvas {
101
+ /* flex-shrink:0 + margin:auto: centers the diagram when it fits,
102
+ allows overflow scroll when it's larger than the viewport */
103
+ flex-shrink: 0;
104
+ margin: auto;
79
105
  transform-origin: 0 0;
80
106
  will-change: transform;
81
107
  }
82
108
  .nl-preview-root .canvas svg { display: block; max-width: none; height: auto; }
83
109
  .nl-preview-root .canvas.dimmed { opacity: 0.25; pointer-events: none; }
84
110
 
85
- /* === Toolbar (top-right) === */
111
+ /* === Toolbar (floating, repositionable) === */
86
112
  .nl-preview-root .chrome {
87
- position: absolute; top: 8px; right: 8px;
113
+ position: absolute;
114
+ top: var(--nl-preview-gutter);
115
+ right: var(--nl-preview-gutter);
88
116
  display: flex; gap: 4px;
117
+ /* Keep the toolbar at its natural width so a narrowing viewport
118
+ shifts it left (handled in JS) instead of squishing the row. */
119
+ width: max-content;
89
120
  transition: opacity 200ms ease;
90
121
  z-index: 10;
91
122
  }
92
- .nl-preview-root .chrome.faded { opacity: 0.25; }
93
- .nl-preview-root .chrome:hover { opacity: 1; }
123
+ .nl-preview-root .chrome.faded { opacity: 0.2; pointer-events: none; }
124
+ .nl-preview-root .chrome:hover { opacity: 1; pointer-events: auto; }
94
125
  .nl-preview-root .toolbar {
95
126
  display: flex; align-items: center; gap: 2px;
127
+ flex-wrap: nowrap;
128
+ white-space: nowrap;
96
129
  padding: 2px;
97
- background: var(--nl-preview-widget-bg);
98
- border: 1px solid var(--nl-preview-widget-border);
130
+ background: var(--nl-chrome-bg);
131
+ border: 1px solid var(--nl-chrome-border);
99
132
  border-radius: 6px;
100
- box-shadow: 0 2px 8px rgba(0,0,0,0.2);
133
+ box-shadow: 0 2px 8px var(--nl-chrome-shadow);
134
+ color: var(--nl-chrome-fg);
135
+ user-select: none;
136
+ }
137
+
138
+ /* Collapsed puck: hide everything but the drag grip + restore arrow,
139
+ and dim it so it stays out of the way until hovered. */
140
+ .nl-preview-root .restore-btn { display: none; }
141
+ .nl-preview-root .chrome.collapsed .toolbar > :not(.toolbar-handle):not(.restore-btn) {
142
+ display: none;
101
143
  }
144
+ .nl-preview-root .chrome.collapsed .restore-btn { display: inline-flex; }
145
+ .nl-preview-root .chrome.collapsed .toolbar { opacity: 0.55; }
146
+ .nl-preview-root .chrome.collapsed:hover .toolbar { opacity: 1; }
102
147
  .nl-preview-root .toolbar .sep {
103
- width: 1px; height: 20px;
104
- background: var(--nl-preview-widget-border);
105
- margin: 0 4px;
148
+ width: 1px; height: 18px;
149
+ background: var(--nl-chrome-border);
150
+ margin: 0 3px;
151
+ flex-shrink: 0;
106
152
  }
153
+ .nl-preview-root .toolbar-handle {
154
+ display: flex; align-items: center; justify-content: center;
155
+ padding: 4px 5px;
156
+ cursor: grab;
157
+ color: var(--nl-chrome-muted);
158
+ font-size: 13px;
159
+ line-height: 1;
160
+ opacity: 0.7;
161
+ flex-shrink: 0;
162
+ touch-action: none;
163
+ }
164
+ .nl-preview-root .toolbar-handle:hover { opacity: 1; }
165
+ .nl-preview-root .chrome.dragging .toolbar-handle { cursor: grabbing; }
107
166
  .nl-preview-root .btn {
108
167
  appearance: none;
109
168
  background: transparent;
110
169
  color: inherit;
111
170
  border: 0;
112
- padding: 4px 8px;
171
+ padding: 3px 7px;
113
172
  border-radius: 4px;
114
173
  font-family: inherit;
115
174
  font-size: inherit;
116
175
  cursor: pointer;
117
176
  line-height: 1;
118
- min-width: 28px;
177
+ min-width: 20px;
178
+ flex-shrink: 0;
179
+ /* Suppress the sticky focus ring left behind after a mouse click;
180
+ keyboard focus is restored via :focus-visible below. */
181
+ outline: none;
182
+ }
183
+ .nl-preview-root .btn:hover { background: var(--nl-chrome-hover); }
184
+ .nl-preview-root .btn:active { background: var(--nl-chrome-active); }
185
+ .nl-preview-root .btn:focus-visible {
186
+ outline: 1px solid var(--nl-preview-focus);
187
+ outline-offset: -1px;
188
+ }
189
+ .nl-preview-root .btn.zoom-label {
190
+ min-width: 44px;
191
+ font-variant-numeric: tabular-nums;
119
192
  }
120
- .nl-preview-root .btn:hover { background: var(--nl-preview-toolbar-hover); }
121
- .nl-preview-root .btn:active { background: var(--nl-preview-toolbar-active); }
122
- .nl-preview-root .btn.zoom-label { min-width: 48px; font-variant-numeric: tabular-nums; }
123
- .nl-preview-root .btn.glyph { font-size: 16px; line-height: 1; padding: 4px 6px; }
193
+ .nl-preview-root .btn.glyph { font-size: 15px; line-height: 1; padding: 3px 5px; }
124
194
  .nl-preview-root .dropdown { position: relative; }
195
+
196
+ /* === More-menu panel === */
197
+ .nl-preview-root .more-menu {
198
+ position: absolute; top: calc(100% + 4px); right: 0;
199
+ padding: 5px;
200
+ min-width: 210px;
201
+ background: var(--nl-chrome-bg);
202
+ border: 1px solid var(--nl-chrome-border);
203
+ border-radius: 6px;
204
+ box-shadow: 0 4px 14px var(--nl-chrome-shadow);
205
+ z-index: 20;
206
+ color: var(--nl-chrome-fg);
207
+ }
208
+ /* Opens right-aligned by default; flipped to left-aligned in JS when the
209
+ toolbar sits near the left edge and the menu would clip the gutter. */
210
+ .nl-preview-root .more-menu.flip { right: auto; left: 0; }
211
+ .nl-preview-root .more-row {
212
+ display: flex; align-items: center; justify-content: space-between;
213
+ padding: 2px 4px;
214
+ gap: 8px;
215
+ }
216
+ .nl-preview-root .more-row.action-row {
217
+ gap: 6px;
218
+ justify-content: stretch;
219
+ padding-top: 6px;
220
+ padding-bottom: 2px;
221
+ }
222
+ /* Copy / Export each take half the row and center their label+icon. */
223
+ .nl-preview-root .action-row .btn {
224
+ flex: 1 1 0;
225
+ display: inline-flex;
226
+ align-items: center;
227
+ justify-content: center;
228
+ gap: 5px;
229
+ }
230
+ .nl-preview-root .action-ico { display: inline-flex; line-height: 0; }
231
+ .nl-preview-root .action-ico svg { display: block; }
232
+ .nl-preview-root .more-label {
233
+ font-size: 0.88em;
234
+ color: var(--nl-chrome-muted);
235
+ white-space: nowrap;
236
+ flex-shrink: 0;
237
+ }
238
+ .nl-preview-root .more-divider {
239
+ height: 1px;
240
+ margin: 4px 0;
241
+ background: var(--nl-chrome-border);
242
+ }
243
+
244
+ /* === Sub-menus inside more-menu === */
245
+ .nl-preview-root .more-sub-toggle {
246
+ font-size: 0.88em;
247
+ padding: 3px 6px;
248
+ }
249
+ .nl-preview-root .more-sub-menu {
250
+ position: absolute; top: calc(100% + 2px); left: 0;
251
+ list-style: none; padding: 4px;
252
+ /* Size to the widest option (plus the checkmark gutter) rather than
253
+ a fixed min-width, so there's no dead whitespace to the right. */
254
+ width: max-content; min-width: 0; max-width: 280px;
255
+ background: var(--nl-chrome-bg);
256
+ border: 1px solid var(--nl-chrome-border);
257
+ border-radius: 6px;
258
+ box-shadow: 0 4px 12px var(--nl-chrome-shadow);
259
+ z-index: 25;
260
+ color: var(--nl-chrome-fg);
261
+ }
262
+ /* Flyout flipped to open toward the left (set in JS when the default
263
+ right-ward opening would overflow the viewport gutter). */
264
+ .nl-preview-root .more-sub-menu.flip { left: auto; right: 0; }
265
+ .nl-preview-root .more-sub-menu li { padding: 0; }
266
+ .nl-preview-root .more-sub-menu .btn {
267
+ display: block; width: 100%; text-align: left;
268
+ /* Generous left gutter keeps the checkmark off the label text. */
269
+ padding: 5px 12px 5px 26px;
270
+ position: relative;
271
+ font-size: 0.88em;
272
+ }
273
+ .nl-preview-root .more-sub-menu .btn[data-active="true"]::before {
274
+ content: '\\2713';
275
+ position: absolute;
276
+ left: 9px;
277
+ }
278
+
279
+ /* === Code-style chip for real theme/format tokens === */
280
+ .nl-preview-root .code-chip {
281
+ font-family: var(--nl-preview-mono);
282
+ font-size: 0.9em;
283
+ background: var(--nl-chrome-chip-bg);
284
+ border-radius: 3px;
285
+ padding: 1px 4px;
286
+ font-style: normal;
287
+ }
288
+
289
+ /* === Diagnostic overlay menu (for old .menu usage in diagnostics) === */
125
290
  .nl-preview-root .menu {
126
291
  position: absolute; top: 100%; right: 0; margin-top: 4px;
127
292
  list-style: none; padding: 4px; min-width: 140px;
128
- background: var(--nl-preview-widget-bg);
129
- border: 1px solid var(--nl-preview-widget-border);
293
+ background: var(--nl-chrome-bg);
294
+ border: 1px solid var(--nl-chrome-border);
130
295
  border-radius: 6px;
131
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
296
+ box-shadow: 0 4px 12px var(--nl-chrome-shadow);
132
297
  z-index: 20;
298
+ color: var(--nl-chrome-fg);
133
299
  }
134
300
  .nl-preview-root .menu li { padding: 0; }
135
301
  .nl-preview-root .menu .btn {
@@ -141,35 +307,30 @@ export const PREVIEW_SHELL_CSS = `
141
307
  font-size: 0.85em;
142
308
  text-transform: uppercase;
143
309
  letter-spacing: 0.05em;
144
- color: var(--nl-preview-description);
310
+ color: var(--nl-chrome-muted);
145
311
  }
146
312
  .nl-preview-root .menu li.menu-divider {
147
313
  height: 1px;
148
314
  margin: 4px 6px;
149
- background: var(--nl-preview-widget-border);
150
- }
151
- .nl-preview-root .menu .btn.view-opt {
152
- padding-left: 22px;
153
- position: relative;
154
- }
155
- .nl-preview-root .menu .btn.view-opt[data-active="true"]::before {
156
- content: '\\2713';
157
- position: absolute;
158
- left: 8px;
315
+ background: var(--nl-chrome-border);
159
316
  }
160
317
 
161
- /* === Minimap === */
318
+ /* === Minimap (subordinate floating widget) === */
162
319
  .nl-preview-root .minimap {
163
- position: absolute; bottom: 12px; right: 12px;
164
- width: 160px; max-height: 120px;
165
- background: var(--nl-preview-widget-bg);
166
- border: 1px solid var(--nl-preview-widget-border);
167
- border-radius: 4px;
320
+ position: absolute;
321
+ bottom: var(--nl-preview-gutter);
322
+ right: var(--nl-preview-gutter);
323
+ width: 120px; max-height: 90px;
324
+ background: var(--nl-chrome-bg);
325
+ border: 1px solid var(--nl-chrome-border);
326
+ border-radius: 5px;
168
327
  overflow: hidden;
169
- box-shadow: 0 2px 8px rgba(0,0,0,0.2);
328
+ box-shadow: 0 2px 6px var(--nl-chrome-shadow);
170
329
  z-index: 8;
330
+ transition: opacity 200ms ease;
171
331
  }
172
332
  .nl-preview-root .minimap.hidden { display: none; }
333
+ .nl-preview-root .minimap.faded { opacity: 0.2; pointer-events: none; }
173
334
  .nl-preview-root .minimap-canvas {
174
335
  display: block;
175
336
  cursor: pointer;
@@ -188,13 +349,73 @@ export const PREVIEW_SHELL_CSS = `
188
349
  pointer-events: none;
189
350
  box-sizing: border-box;
190
351
  }
191
- .nl-preview-root .minimap-close {
192
- position: absolute; top: 2px; right: 2px;
193
- width: 18px; height: 18px; padding: 0;
194
- border-radius: 4px;
195
- line-height: 1;
196
- font-size: 12px;
197
- z-index: 1;
352
+
353
+ /* === Calendar picker === */
354
+ .nl-preview-root .now-picker {
355
+ position: absolute; top: calc(100% + 2px); left: 0;
356
+ padding: 8px;
357
+ min-width: 200px;
358
+ background: var(--nl-chrome-bg);
359
+ border: 1px solid var(--nl-chrome-border);
360
+ border-radius: 6px;
361
+ box-shadow: 0 4px 14px var(--nl-chrome-shadow);
362
+ z-index: 26;
363
+ color: var(--nl-chrome-fg);
364
+ }
365
+ /* Calendar flips to open toward the left when opening rightward would
366
+ push it past the viewport's right gutter (see placeFlyout). */
367
+ .nl-preview-root .now-picker.flip { left: auto; right: 0; }
368
+ .nl-preview-root .cal-nav {
369
+ display: flex; align-items: center; justify-content: space-between;
370
+ margin-bottom: 6px;
371
+ }
372
+ .nl-preview-root .cal-heading {
373
+ font-size: 0.85em;
374
+ font-weight: 600;
375
+ }
376
+ .nl-preview-root .cal-nav-btn {
377
+ padding: 2px 6px;
378
+ min-width: unset;
379
+ font-size: 14px;
380
+ }
381
+ .nl-preview-root .cal-grid {
382
+ display: grid; grid-template-columns: repeat(7, 1fr);
383
+ gap: 1px;
384
+ margin-bottom: 6px;
385
+ }
386
+ .nl-preview-root .cal-dow {
387
+ text-align: center;
388
+ font-size: 0.75em;
389
+ color: var(--nl-chrome-muted);
390
+ padding: 2px 0;
391
+ }
392
+ .nl-preview-root .cal-empty {
393
+ /* placeholder for days before the 1st of the month */
394
+ }
395
+ .nl-preview-root .cal-day {
396
+ padding: 3px 2px;
397
+ min-width: unset;
398
+ font-size: 0.8em;
399
+ text-align: center;
400
+ border-radius: 3px;
401
+ }
402
+ .nl-preview-root .cal-day.is-today { font-weight: 700; }
403
+ .nl-preview-root .cal-day.is-selected {
404
+ background: var(--nl-chrome-hover);
405
+ outline: 1px solid var(--nl-chrome-border);
406
+ }
407
+ .nl-preview-root .cal-footer {
408
+ display: flex; gap: 4px;
409
+ border-top: 1px solid var(--nl-chrome-border);
410
+ padding-top: 6px;
411
+ margin-top: 2px;
412
+ }
413
+ .nl-preview-root .cal-footer-btn {
414
+ flex: 1;
415
+ text-align: center;
416
+ font-size: 0.82em;
417
+ padding: 4px 6px;
418
+ min-width: unset;
198
419
  }
199
420
 
200
421
  /* === Diagnostics overlay === */
@@ -243,7 +464,7 @@ export const PREVIEW_SHELL_CSS = `
243
464
  border-bottom: 1px solid var(--nl-preview-widget-border);
244
465
  }
245
466
  .nl-preview-root .diag-table tr:hover {
246
- background: var(--nl-preview-list-hover);
467
+ background: rgba(255,255,255,0.04);
247
468
  }
248
469
  .nl-preview-root .diag-table td {
249
470
  padding: 6px 8px;