@weasel-js/labkit 0.8.0 → 1.0.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.
Files changed (74) hide show
  1. package/README.md +11 -3
  2. package/dist/_dts/{DrawCommand-DACv9lhe.d.ts → DrawCommand-CitWPxMY.d.ts} +80 -7
  3. package/dist/_dts/{useExperimentState-eSvpwV_P.d.ts → useExperimentState-CJn2hHzd.d.ts} +6 -4
  4. package/dist/{chunk-SSMHPMDV.js → chunk-53XSBIUK.js} +12 -11
  5. package/dist/chunk-53XSBIUK.js.map +1 -0
  6. package/dist/{chunk-OHGEOTCV.js → chunk-EXBV7A6X.js} +3 -3
  7. package/dist/{chunk-OHGEOTCV.js.map → chunk-EXBV7A6X.js.map} +1 -1
  8. package/dist/{chunk-6V7ROR7G.js → chunk-ISEK5LXT.js} +1931 -904
  9. package/dist/chunk-ISEK5LXT.js.map +1 -0
  10. package/dist/chunk-NRD3TDNQ.js +5946 -0
  11. package/dist/chunk-NRD3TDNQ.js.map +1 -0
  12. package/dist/index.d.ts +37 -13
  13. package/dist/index.js +776 -27
  14. package/dist/index.js.map +1 -1
  15. package/dist/passthrough/weasel-canvas.d.ts +29 -6
  16. package/dist/passthrough/weasel-canvas.js +1 -1
  17. package/dist/passthrough/weasel-ui.d.ts +169 -79
  18. package/dist/passthrough/weasel-ui.js +2 -2
  19. package/dist/state/index.d.ts +2 -2
  20. package/dist/state/index.js +2 -2
  21. package/dist/styles.css +228 -287
  22. package/dist/ui/layers/index.js +3 -3
  23. package/package.json +4 -6
  24. package/src/canvas/CanvasStack.less +1 -1
  25. package/src/controls/ControlPanel.less +12 -12
  26. package/src/dragdrop/DragGhost.less +2 -2
  27. package/src/dragdrop/Palette.less +6 -6
  28. package/src/index.ts +2 -0
  29. package/src/lab/Lab.test.tsx +20 -15
  30. package/src/lab/Lab.tsx +33 -32
  31. package/src/lab/LabContext.ts +3 -3
  32. package/src/lab/LabShell.less +14 -14
  33. package/src/lab/LabShell.test.tsx +7 -20
  34. package/src/lab/LabShell.tsx +22 -13
  35. package/src/lab/WorkspaceGrid.less +1 -1
  36. package/src/lab/WorkspaceGrid.stories.tsx +4 -4
  37. package/src/lab/index.ts +1 -1
  38. package/src/lab/useSystemMode.ts +30 -0
  39. package/src/layers/LayerList.less +9 -9
  40. package/src/passthrough/weasel-ui.ts +4 -0
  41. package/src/primitives/FpsMeter.less +5 -5
  42. package/src/primitives/ScaleIndicator.less +7 -7
  43. package/src/primitives/Sidebar.less +12 -12
  44. package/src/primitives/StatusBar.less +10 -10
  45. package/src/primitives/Toolbar.less +16 -16
  46. package/src/state/store.test.ts +17 -9
  47. package/src/state/store.ts +13 -10
  48. package/src/state/types.ts +5 -2
  49. package/src/theme/Interstellar.stories.less +20 -20
  50. package/src/theme/Interstellar.stories.tsx +44 -59
  51. package/src/theme/base.less +26 -26
  52. package/src/theme/interstellar.test.ts +30 -0
  53. package/src/theme/interstellar.tokens.json +57 -0
  54. package/src/theme/interstellar.ts +9 -0
  55. package/src/ui/layers/LayerStack.less +21 -21
  56. package/src/ui/properties/CurveField.less +5 -5
  57. package/src/ui/properties/EffectCard.tsx +2 -2
  58. package/src/ui/properties/PropertyGroup.less +6 -6
  59. package/src/ui/properties/PropertyPanel.less +70 -70
  60. package/src/ui/properties/PropertyPanel.stories.tsx +0 -1
  61. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +1 -1
  62. package/src/ui/properties/storyLayouts.tsx +1 -1
  63. package/src/workspace/Workspace.less +14 -14
  64. package/src/workspace/Workspace.stories.tsx +2 -2
  65. package/src/workspace/Workspace.test.tsx +2 -2
  66. package/dist/chunk-6V7ROR7G.js.map +0 -1
  67. package/dist/chunk-FBZCQXI2.js +0 -6037
  68. package/dist/chunk-FBZCQXI2.js.map +0 -1
  69. package/dist/chunk-SSMHPMDV.js.map +0 -1
  70. package/dist/theme-interstellar.css +0 -25
  71. package/dist/theme-light.css +0 -13
  72. package/src/theme/interstellar.less +0 -34
  73. package/src/theme/light.less +0 -14
  74. package/src/theme/tokens.less +0 -85
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- export { LayerStack } from './chunk-OHGEOTCV.js';
2
- import { p, F } from './chunk-FBZCQXI2.js';
3
- import './chunk-6V7ROR7G.js';
1
+ export { LayerStack } from './chunk-EXBV7A6X.js';
2
+ import { p, F } from './chunk-NRD3TDNQ.js';
3
+ import './chunk-ISEK5LXT.js';
4
4
  import { Sidebar, StatusBar, Toolbar } from './chunk-HXZHVU4G.js';
5
5
  export { FpsMeter, ScaleIndicator, Sidebar, StatusBar, Toolbar } from './chunk-HXZHVU4G.js';
6
- import { LabStoreContext, createLabStore, noneAdapter } from './chunk-SSMHPMDV.js';
7
- export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createMemoryAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId } from './chunk-SSMHPMDV.js';
6
+ import { LabStoreContext, createLabStore, noneAdapter } from './chunk-53XSBIUK.js';
7
+ export { LabStoreContext, LabStoreProvider, WorkspaceIdContext, WorkspaceIdProvider, createMemoryAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, useExperimentState, useLabStore, useWorkspaceId } from './chunk-53XSBIUK.js';
8
8
  import { ControlPanel } from './chunk-VLAHRJOC.js';
9
9
  export { ControlPanel } from './chunk-VLAHRJOC.js';
10
10
  import { CanvasStack, screenToWorld } from './chunk-54IQ2DX7.js';
@@ -14,10 +14,10 @@ import { LayerList } from './chunk-2QNYYL3V.js';
14
14
  export { LayerList } from './chunk-2QNYYL3V.js';
15
15
  import { createEventBus, redo, undo, pushSnapshot } from './chunk-7BKDG73Z.js';
16
16
  export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-7BKDG73Z.js';
17
- import { createContext, useContext, useMemo, useRef, useState, Children, useEffect, useCallback } from 'react';
17
+ import { createContext, useContext, useMemo, useRef, useState, Children, useEffect, useCallback, useLayoutEffect } from 'react';
18
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
18
19
  import { useStore } from 'zustand/react';
19
20
  import { createPortal } from 'react-dom';
20
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
21
21
 
22
22
  // src/instrument/capabilityDetector.ts
23
23
  function detectCapabilities(instrument) {
@@ -95,6 +95,726 @@ function gridDims(count) {
95
95
  const rows = Math.ceil(count / cols);
96
96
  return { cols, rows };
97
97
  }
98
+
99
+ // ../theme/dist/chunk-RATE4LL3.js
100
+ var THEME_SOURCES = {
101
+ "weasel": {
102
+ "primitives": {
103
+ "gray-50": {
104
+ "type": "color",
105
+ "value": "#f5f5f6"
106
+ },
107
+ "gray-100": {
108
+ "type": "color",
109
+ "value": "#e6e7e9"
110
+ },
111
+ "gray-200": {
112
+ "type": "color",
113
+ "value": "#c9cbcf"
114
+ },
115
+ "gray-300": {
116
+ "type": "color",
117
+ "value": "#9ea1a8"
118
+ },
119
+ "gray-400": {
120
+ "type": "color",
121
+ "value": "#6f737b"
122
+ },
123
+ "gray-500": {
124
+ "type": "color",
125
+ "value": "#4d5058"
126
+ },
127
+ "gray-600": {
128
+ "type": "color",
129
+ "value": "#383b42"
130
+ },
131
+ "gray-700": {
132
+ "type": "color",
133
+ "value": "#25272c"
134
+ },
135
+ "gray-800": {
136
+ "type": "color",
137
+ "value": "#181a1e"
138
+ },
139
+ "gray-900": {
140
+ "type": "color",
141
+ "value": "#0e0f12"
142
+ },
143
+ "accent-soft": {
144
+ "type": "color",
145
+ "value": "#1d1454"
146
+ },
147
+ "accent-base": {
148
+ "type": "color",
149
+ "value": "#2e1f7a",
150
+ "description": "Midnight blue-violet. Deep and saturated; anchors primary actions on light and dark surfaces alike."
151
+ },
152
+ "accent-strong": {
153
+ "type": "color",
154
+ "value": "#5841b8"
155
+ },
156
+ "danger-base": {
157
+ "type": "color",
158
+ "value": "#d94a3f"
159
+ },
160
+ "warning-base": {
161
+ "type": "color",
162
+ "value": "#d99a3f"
163
+ },
164
+ "success-base": {
165
+ "type": "color",
166
+ "value": "#2ec27e"
167
+ },
168
+ "fg-on-accent": {
169
+ "type": "color",
170
+ "value": "{color.gray-50}"
171
+ },
172
+ "line-subtle": {
173
+ "type": "color",
174
+ "value": "{color.fg}",
175
+ "alpha": 0.1,
176
+ "description": "Barely-there separators (table rows)."
177
+ },
178
+ "line": {
179
+ "type": "color",
180
+ "value": "{color.fg}",
181
+ "alpha": 0.2,
182
+ "description": "Primary gridlines on sunken surfaces."
183
+ },
184
+ "line-strong": {
185
+ "type": "color",
186
+ "value": "{color.fg}",
187
+ "alpha": 0.4,
188
+ "description": "Axes and emphasized dividers."
189
+ },
190
+ "curve-color": {
191
+ "type": "color",
192
+ "value": "{color.accent-strong}",
193
+ "description": "Drawn data curves. Routes through the bright accent because accent-base is too dim on sunken surfaces."
194
+ },
195
+ "accent": {
196
+ "type": "color",
197
+ "value": "{color.accent-base}"
198
+ },
199
+ "accent-hover": {
200
+ "type": "color",
201
+ "value": "{color.accent-strong}"
202
+ },
203
+ "danger": {
204
+ "type": "color",
205
+ "value": "{color.danger-base}"
206
+ },
207
+ "warning": {
208
+ "type": "color",
209
+ "value": "{color.warning-base}"
210
+ },
211
+ "success": {
212
+ "type": "color",
213
+ "value": "{color.success-base}"
214
+ },
215
+ "focus-ring": {
216
+ "type": "color",
217
+ "value": "{color.accent-strong}"
218
+ },
219
+ "glass-tint": {
220
+ "type": "color",
221
+ "value": "{color.accent}",
222
+ "description": "Frosted-glass tint. Components compose it as color-mix(in srgb, var(--wzl-glass-tint) 78%, transparent) with backdrop-filter: blur(3px)."
223
+ },
224
+ "surface-hover": {
225
+ "type": "color",
226
+ "value": "{color.fg}",
227
+ "alpha": 0.1,
228
+ "description": "Hover wash over a raised surface. Same formula as line-subtle, different job \u2014 do not collapse them."
229
+ },
230
+ "surface-pressed": {
231
+ "type": "color",
232
+ "value": "{color.fg}",
233
+ "alpha": 0.18,
234
+ "description": "Pressed wash over a raised surface."
235
+ },
236
+ "swatch-green": {
237
+ "type": "color",
238
+ "value": "#2fdd18",
239
+ "description": "First of ten categorical spot colors for plotting, status and data viz. Ordered \u2014 take them in sequence. Mode-invariant on purpose: a series keeps its color when the mode flips."
240
+ },
241
+ "swatch-pink": {
242
+ "type": "color",
243
+ "value": "#ff5885"
244
+ },
245
+ "swatch-cyan": {
246
+ "type": "color",
247
+ "value": "#00dfff"
248
+ },
249
+ "swatch-gold": {
250
+ "type": "color",
251
+ "value": "#dcb700"
252
+ },
253
+ "swatch-amber": {
254
+ "type": "color",
255
+ "value": "#ff8c00"
256
+ },
257
+ "swatch-violet": {
258
+ "type": "color",
259
+ "value": "#a497ff"
260
+ },
261
+ "swatch-mint": {
262
+ "type": "color",
263
+ "value": "#00e7af"
264
+ },
265
+ "swatch-sky": {
266
+ "type": "color",
267
+ "value": "#00b8ff"
268
+ },
269
+ "swatch-orange": {
270
+ "type": "color",
271
+ "value": "#ff6b00"
272
+ },
273
+ "swatch-magenta": {
274
+ "type": "color",
275
+ "value": "#ff6eff"
276
+ },
277
+ "radius-sm": {
278
+ "type": "dimension",
279
+ "value": "3px"
280
+ },
281
+ "radius-md": {
282
+ "type": "dimension",
283
+ "value": "5px"
284
+ },
285
+ "border-w": {
286
+ "type": "dimension",
287
+ "value": "1px"
288
+ },
289
+ "line-width": {
290
+ "type": "dimension",
291
+ "value": "2px",
292
+ "description": "Structural lines \u2014 gridlines, dividers, axes."
293
+ },
294
+ "curve-width": {
295
+ "type": "dimension",
296
+ "value": "3px",
297
+ "description": "Drawn data curves. Heavier than structural lines so data reads as primary content."
298
+ },
299
+ "tb-height": {
300
+ "type": "dimension",
301
+ "value": "28px",
302
+ "description": "Height of a horizontal toolbar strip (ActionsBar, OptionsBar, ToggleBar, ToolOptionsBar)."
303
+ },
304
+ "radius-lg": {
305
+ "type": "dimension",
306
+ "value": "14px"
307
+ },
308
+ "control-h": {
309
+ "type": "dimension",
310
+ "value": "28px",
311
+ "description": "Height of an interactive control \u2014 button, input, select. Distinct from tb-height, which sizes the strip a row of them sits in."
312
+ },
313
+ "glass-blur": {
314
+ "type": "dimension",
315
+ "value": "3px",
316
+ "description": "Backdrop blur radius for frosted surfaces. Pairs with glass-tint."
317
+ },
318
+ "font-size": {
319
+ "type": "dimension",
320
+ "value": "13px"
321
+ },
322
+ "font-size-sm": {
323
+ "type": "dimension",
324
+ "value": "11px"
325
+ },
326
+ "space-xs": {
327
+ "type": "dimension",
328
+ "value": "4px"
329
+ },
330
+ "space-sm": {
331
+ "type": "dimension",
332
+ "value": "8px"
333
+ },
334
+ "space-md": {
335
+ "type": "dimension",
336
+ "value": "12px"
337
+ },
338
+ "space-lg": {
339
+ "type": "dimension",
340
+ "value": "16px"
341
+ },
342
+ "z-toolbar": {
343
+ "type": "number",
344
+ "value": 10
345
+ },
346
+ "z-overlay": {
347
+ "type": "number",
348
+ "value": 20
349
+ },
350
+ "z-modal": {
351
+ "type": "number",
352
+ "value": 30
353
+ },
354
+ "backdrop": {
355
+ "type": "gradient",
356
+ "value": "none",
357
+ "description": "Layered backdrop fill for a page or lab root. `none` here; a theme with a signature background sets it."
358
+ },
359
+ "motion-fast": {
360
+ "type": "duration",
361
+ "value": "120ms",
362
+ "description": "Micro-interactions \u2014 hover, focus, pressed."
363
+ },
364
+ "motion-medium": {
365
+ "type": "duration",
366
+ "value": "240ms",
367
+ "description": "Entrances and exits \u2014 toasts, popovers, panels."
368
+ },
369
+ "ease-in-cubic": {
370
+ "type": "cubicBezier",
371
+ "value": [
372
+ 0.32,
373
+ 0,
374
+ 0.67,
375
+ 0
376
+ ]
377
+ },
378
+ "ease-out-cubic": {
379
+ "type": "cubicBezier",
380
+ "value": [
381
+ 0.33,
382
+ 1,
383
+ 0.68,
384
+ 1
385
+ ]
386
+ },
387
+ "ease-in-out-cubic": {
388
+ "type": "cubicBezier",
389
+ "value": [
390
+ 0.65,
391
+ 0,
392
+ 0.35,
393
+ 1
394
+ ]
395
+ },
396
+ "ease-out-back": {
397
+ "type": "cubicBezier",
398
+ "value": [
399
+ 0.34,
400
+ 1.56,
401
+ 0.64,
402
+ 1
403
+ ]
404
+ },
405
+ "font-ui": {
406
+ "type": "fontFamily",
407
+ "value": [
408
+ "Oswald",
409
+ "Helvetica Neue Condensed",
410
+ "Arial Narrow",
411
+ "system-ui",
412
+ "sans-serif"
413
+ ],
414
+ "description": "Condensed UI/display face. The heavier cuts get blocky at any size, so the weight tokens stay in the 200-400 range."
415
+ },
416
+ "font-display": {
417
+ "type": "fontFamily",
418
+ "value": [
419
+ "Oswald",
420
+ "Helvetica Neue Condensed",
421
+ "Arial Narrow",
422
+ "system-ui",
423
+ "sans-serif"
424
+ ]
425
+ },
426
+ "font-body": {
427
+ "type": "fontFamily",
428
+ "value": [
429
+ "Inter",
430
+ "system-ui",
431
+ "-apple-system",
432
+ "Segoe UI",
433
+ "sans-serif"
434
+ ]
435
+ },
436
+ "font-mono": {
437
+ "type": "fontFamily",
438
+ "value": [
439
+ "ui-monospace",
440
+ "SFMono-Regular",
441
+ "Menlo",
442
+ "Consolas",
443
+ "monospace"
444
+ ]
445
+ },
446
+ "font-weight-light": {
447
+ "type": "fontWeight",
448
+ "value": 200
449
+ },
450
+ "font-weight-normal": {
451
+ "type": "fontWeight",
452
+ "value": 300
453
+ },
454
+ "font-weight-medium": {
455
+ "type": "fontWeight",
456
+ "value": 350
457
+ },
458
+ "font-weight-bold": {
459
+ "type": "fontWeight",
460
+ "value": 400
461
+ }
462
+ },
463
+ "modes": {
464
+ "dark": {
465
+ "surface": {
466
+ "type": "color",
467
+ "value": "{color.gray-800}"
468
+ },
469
+ "surface-raised": {
470
+ "type": "color",
471
+ "value": "{color.gray-700}"
472
+ },
473
+ "surface-sunken": {
474
+ "type": "color",
475
+ "value": "{color.gray-900}"
476
+ },
477
+ "fg": {
478
+ "type": "color",
479
+ "value": "{color.gray-100}"
480
+ },
481
+ "fg-muted": {
482
+ "type": "color",
483
+ "value": "{color.gray-300}"
484
+ },
485
+ "fg-subtle": {
486
+ "type": "color",
487
+ "value": "{color.gray-400}"
488
+ },
489
+ "border": {
490
+ "type": "color",
491
+ "value": "{color.gray-700}"
492
+ },
493
+ "border-strong": {
494
+ "type": "color",
495
+ "value": "{color.gray-600}"
496
+ },
497
+ "accent-fg": {
498
+ "type": "color",
499
+ "value": "{color.accent-strong}",
500
+ "description": "Accent for foreground use \u2014 text, icons, selected labels drawn on a surface. accent is a fill; the midnight base is too dim to read as text on dark surfaces."
501
+ },
502
+ "fg-inverse": {
503
+ "type": "color",
504
+ "value": "{color.gray-900}",
505
+ "description": "Text drawn on a filled foreground-colored element (e.g. a slider thumb). Flips with the mode, so it is a semantic, not an alias to a primitive."
506
+ }
507
+ },
508
+ "light": {
509
+ "surface": {
510
+ "type": "color",
511
+ "value": "{color.gray-50}"
512
+ },
513
+ "surface-raised": {
514
+ "type": "color",
515
+ "value": "{color.gray-100}"
516
+ },
517
+ "surface-sunken": {
518
+ "type": "color",
519
+ "value": "{color.gray-200}"
520
+ },
521
+ "fg": {
522
+ "type": "color",
523
+ "value": "{color.gray-900}"
524
+ },
525
+ "fg-muted": {
526
+ "type": "color",
527
+ "value": "{color.gray-600}"
528
+ },
529
+ "fg-subtle": {
530
+ "type": "color",
531
+ "value": "{color.gray-500}"
532
+ },
533
+ "border": {
534
+ "type": "color",
535
+ "value": "{color.gray-200}"
536
+ },
537
+ "border-strong": {
538
+ "type": "color",
539
+ "value": "{color.gray-300}"
540
+ },
541
+ "accent-fg": {
542
+ "type": "color",
543
+ "value": "{color.accent-base}",
544
+ "description": "Flips to the deep base in light mode, where it is the legible one."
545
+ },
546
+ "fg-inverse": {
547
+ "type": "color",
548
+ "value": "{color.gray-50}",
549
+ "description": "Flips with the mode \u2014 see the dark layer."
550
+ }
551
+ }
552
+ }
553
+ }
554
+ };
555
+ var weaselTheme = {
556
+ name: "weasel",
557
+ extends: null,
558
+ defaultMode: "dark",
559
+ tokens: THEME_SOURCES.weasel.primitives,
560
+ modes: THEME_SOURCES.weasel.modes
561
+ };
562
+ function hexToRgba(hex, alpha) {
563
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
564
+ if (!m) throw new Error(`Expected a hex color, got "${hex}"`);
565
+ const body = m[1];
566
+ const full = body.length === 3 ? body.replace(/./g, (c) => c + c) : body;
567
+ const r = Number.parseInt(full.slice(0, 2), 16);
568
+ const g = Number.parseInt(full.slice(2, 4), 16);
569
+ const b = Number.parseInt(full.slice(4, 6), 16);
570
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
571
+ }
572
+ var REF = /^\{([^}]+)\}$/;
573
+ function refTarget(value) {
574
+ if (typeof value !== "string") return null;
575
+ const m = REF.exec(value.trim());
576
+ if (!m) return null;
577
+ const path = m[1];
578
+ const dot = path.indexOf(".");
579
+ return dot === -1 ? path : path.slice(dot + 1);
580
+ }
581
+ function fontStack(value) {
582
+ return value.map((f) => typeof f === "string" && /\s/.test(f) ? `'${f}'` : String(f)).join(", ");
583
+ }
584
+ function serialize(type, value) {
585
+ if (Array.isArray(value)) {
586
+ if (type === "cubicBezier") return `cubic-bezier(${value.join(", ")})`;
587
+ if (type === "fontFamily") return fontStack(value);
588
+ return value.join(", ");
589
+ }
590
+ return String(value);
591
+ }
592
+ function resolveTokens(tokens) {
593
+ const out = {};
594
+ const inProgress = /* @__PURE__ */ new Set();
595
+ const resolveOne = (name) => {
596
+ const cached = out[name];
597
+ if (cached !== void 0) return cached;
598
+ const token = tokens[name];
599
+ if (!token) throw new Error(`Unknown token "${name}"`);
600
+ if (inProgress.has(name)) {
601
+ throw new Error(`Reference cycle at token "${name}" (${[...inProgress].join(" \u2192 ")})`);
602
+ }
603
+ inProgress.add(name);
604
+ const target = refTarget(token.value);
605
+ let value;
606
+ if (target === null) {
607
+ value = serialize(token.type, token.value);
608
+ } else if (!tokens[target]) {
609
+ throw new Error(`Token "${name}" references "${target}", which is not defined`);
610
+ } else {
611
+ value = resolveOne(target);
612
+ }
613
+ if (token.alpha !== void 0) value = hexToRgba(value, token.alpha);
614
+ inProgress.delete(name);
615
+ out[name] = value;
616
+ return value;
617
+ };
618
+ for (const name of Object.keys(tokens)) resolveOne(name);
619
+ return out;
620
+ }
621
+ function chain(theme) {
622
+ const out = [];
623
+ for (let t = theme; t; t = t.extends) out.unshift(t);
624
+ return out;
625
+ }
626
+ function resolveTheme(theme, mode) {
627
+ const themes = chain(theme);
628
+ const effectiveMode = themes.some((t) => mode in t.modes) ? mode : theme.defaultMode;
629
+ let merged = {};
630
+ for (const t of themes) {
631
+ merged = { ...merged, ...t.tokens, ...t.modes[effectiveMode] ?? {} };
632
+ }
633
+ const resolved = resolveTokens(merged);
634
+ const out = {};
635
+ for (const [name, value] of Object.entries(resolved)) out[`--wzl-${name}`] = value;
636
+ return out;
637
+ }
638
+ var STYLE_ID = "wzl-themes";
639
+ var emitted = /* @__PURE__ */ new Set();
640
+ var sheet = null;
641
+ var styleEl = null;
642
+ function canAdopt() {
643
+ return typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && Array.isArray(document.adoptedStyleSheets);
644
+ }
645
+ function appendRule(css) {
646
+ if (canAdopt()) {
647
+ if (!sheet) {
648
+ sheet = new CSSStyleSheet();
649
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
650
+ }
651
+ sheet.insertRule(css, sheet.cssRules.length);
652
+ return;
653
+ }
654
+ if (!styleEl) {
655
+ styleEl = document.createElement("style");
656
+ styleEl.id = STYLE_ID;
657
+ document.head.appendChild(styleEl);
658
+ }
659
+ styleEl.textContent += `${css}
660
+ `;
661
+ }
662
+ function applyTheme(el, theme, mode) {
663
+ if (typeof document === "undefined") return;
664
+ const key = `${theme.name}::${mode}`;
665
+ if (!emitted.has(key)) {
666
+ const resolved = resolveTheme(theme, mode);
667
+ const body = Object.entries(resolved).map(([name, value]) => `${name}: ${value};`).join(" ");
668
+ appendRule(`[data-wzl-theme='${theme.name}'][data-wzl-mode='${mode}'] { ${body} }`);
669
+ emitted.add(key);
670
+ }
671
+ el.setAttribute("data-wzl-theme", theme.name);
672
+ el.setAttribute("data-wzl-mode", mode);
673
+ }
674
+ var ThemeContext = createContext(null);
675
+ function ThemeProvider({
676
+ theme = weaselTheme,
677
+ mode,
678
+ className,
679
+ style,
680
+ children
681
+ }) {
682
+ const ref = useRef(null);
683
+ const effectiveMode = mode ?? theme.defaultMode;
684
+ const value = useMemo(
685
+ () => ({ theme, mode: effectiveMode, resolved: resolveTheme(theme, effectiveMode) }),
686
+ [theme, effectiveMode]
687
+ );
688
+ useLayoutEffect(() => {
689
+ if (ref.current) applyTheme(ref.current, theme, effectiveMode);
690
+ }, [theme, effectiveMode]);
691
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx(
692
+ "div",
693
+ {
694
+ ref,
695
+ className,
696
+ style,
697
+ "data-wzl-theme": theme.name,
698
+ "data-wzl-mode": effectiveMode,
699
+ children
700
+ }
701
+ ) });
702
+ }
703
+ function useThemeOptional() {
704
+ return useContext(ThemeContext);
705
+ }
706
+
707
+ // ../theme/dist/index.js
708
+ var ALPHA_EXT = "com.weasel.alpha";
709
+ var isToken = (v) => typeof v === "object" && v !== null && "$value" in v;
710
+ function flattenTokens(doc) {
711
+ const out = {};
712
+ for (const [groupName, group] of Object.entries(doc)) {
713
+ if (groupName.startsWith("$")) continue;
714
+ if (typeof group !== "object" || group === null) continue;
715
+ const g = group;
716
+ const groupType = g.$type;
717
+ for (const [leaf, node] of Object.entries(g)) {
718
+ if (leaf.startsWith("$")) continue;
719
+ if (!isToken(node)) continue;
720
+ const type = node.$type ?? groupType;
721
+ if (!type) {
722
+ throw new Error(`Token "${leaf}" has no $type and its group "${groupName}" declares none`);
723
+ }
724
+ if (leaf in out) {
725
+ throw new Error(`Duplicate token name "${leaf}" \u2014 leaf keys must be unique across type groups`);
726
+ }
727
+ const alphaRaw = node.$extensions?.[ALPHA_EXT];
728
+ const token = {
729
+ type,
730
+ value: node.$value,
731
+ alpha: typeof alphaRaw === "number" ? alphaRaw : void 0,
732
+ description: node.$description
733
+ };
734
+ out[leaf] = token;
735
+ }
736
+ }
737
+ return out;
738
+ }
739
+ function loadDTCG(doc) {
740
+ if (typeof doc.name !== "string" || doc.name === "") {
741
+ throw new Error('DTCG document needs a string "name"');
742
+ }
743
+ const modes = {};
744
+ for (const [mode, body] of Object.entries(doc.modes ?? {})) {
745
+ modes[mode] = flattenTokens(body);
746
+ }
747
+ const base = doc.extends === void 0 ? weaselTheme : doc.extends;
748
+ return {
749
+ name: doc.name,
750
+ extends: base,
751
+ defaultMode: typeof doc.defaultMode === "string" ? doc.defaultMode : base?.defaultMode ?? "dark",
752
+ tokens: flattenTokens(doc.primitives ?? {}),
753
+ modes
754
+ };
755
+ }
756
+
757
+ // src/theme/interstellar.tokens.json
758
+ var interstellar_tokens_default = {
759
+ name: "interstellar",
760
+ defaultMode: "dark",
761
+ primitives: {
762
+ dimension: {
763
+ $type: "dimension",
764
+ "radius-md": { $value: "6px" },
765
+ "glass-blur": { $value: "12px" }
766
+ },
767
+ fontWeight: {
768
+ $type: "fontWeight",
769
+ "font-weight-light": { $value: 300 },
770
+ "font-weight-medium": { $value: 500 },
771
+ "font-weight-bold": { $value: 700 }
772
+ }
773
+ },
774
+ modes: {
775
+ dark: {
776
+ color: {
777
+ $type: "color",
778
+ surface: { $value: "#0a0a14" },
779
+ "surface-raised": { $value: "rgba(20, 20, 36, 0.55)" },
780
+ "surface-sunken": { $value: "#02020a" },
781
+ border: { $value: "rgba(255, 255, 255, 0.14)" },
782
+ "line-subtle": { $value: "rgba(255, 255, 255, 0.08)" },
783
+ fg: { $value: "#e8e8f0" },
784
+ "fg-muted": { $value: "#a8a8c0" },
785
+ "fg-subtle": { $value: "#5a5a78" },
786
+ accent: { $value: "#b08adb" },
787
+ "accent-hover": { $value: "#c6a3ee" },
788
+ "focus-ring": { $value: "rgba(176, 138, 219, 0.45)" }
789
+ },
790
+ gradient: {
791
+ $type: "gradient",
792
+ backdrop: {
793
+ $value: "radial-gradient(ellipse 60% 80% at 18% 30%, rgba(150, 80, 200, 0.22), transparent 70%), radial-gradient(ellipse 55% 90% at 78% 70%, rgba(220, 100, 140, 0.18), transparent 65%), radial-gradient(ellipse 50% 70% at 50% 50%, rgba(60, 110, 200, 0.18), transparent 75%), radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)"
794
+ }
795
+ }
796
+ },
797
+ light: {
798
+ color: {
799
+ $type: "color",
800
+ surface: { $value: "#fafaf7" },
801
+ "surface-raised": { $value: "#ffffff" },
802
+ "surface-sunken": { $value: "#f0ebe2" },
803
+ border: { $value: "#c9bba5" },
804
+ "line-subtle": { $value: "rgba(0, 0, 0, 0.08)" },
805
+ fg: { $value: "#2a2018" },
806
+ "fg-muted": { $value: "#6b5c4a" },
807
+ "fg-subtle": { $value: "#a89b85" },
808
+ accent: { $value: "#a86f3c" },
809
+ "accent-hover": { $value: "#c2854f" },
810
+ "focus-ring": { $value: "rgba(168, 111, 60, 0.35)" }
811
+ }
812
+ }
813
+ }
814
+ };
815
+
816
+ // src/theme/interstellar.ts
817
+ var interstellarTheme = loadDTCG(interstellar_tokens_default);
98
818
  function DragGhost({ item, screenPos }) {
99
819
  const [mounted, setMounted] = useState(false);
100
820
  useEffect(() => setMounted(true), []);
@@ -670,9 +1390,28 @@ function resetWorkspace(workspaces, id, instruments) {
670
1390
  };
671
1391
  return [...workspaces.slice(0, idx), reset, ...workspaces.slice(idx + 1)];
672
1392
  }
673
- function LabShell({ title, children, header, footer, theme = "auto" }) {
674
- const themeClass = theme === "light" ? " lk-theme-light" : theme === "interstellar" ? " lk-theme-interstellar" : "";
675
- return /* @__PURE__ */ jsxs("div", { className: `lk-root lk-shell${themeClass}`, children: [
1393
+ function query() {
1394
+ if (typeof window === "undefined" || !window.matchMedia) return null;
1395
+ return window.matchMedia("(prefers-color-scheme: light)");
1396
+ }
1397
+ function useResolvedMode(mode) {
1398
+ const [system, setSystem] = useState(
1399
+ () => query()?.matches ? "light" : "dark"
1400
+ );
1401
+ useEffect(() => {
1402
+ if (mode !== "auto") return;
1403
+ const mq = query();
1404
+ if (!mq) return;
1405
+ const onChange = (e) => setSystem(e.matches ? "light" : "dark");
1406
+ mq.addEventListener("change", onChange);
1407
+ return () => mq.removeEventListener("change", onChange);
1408
+ }, [mode]);
1409
+ return mode === "auto" ? system : mode;
1410
+ }
1411
+ function LabShell({ title, children, header, footer, mode = "auto" }) {
1412
+ const resolved = useResolvedMode(mode);
1413
+ const outer = useThemeOptional();
1414
+ const shell = /* @__PURE__ */ jsxs("div", { className: "lk-root lk-shell", children: [
676
1415
  /* @__PURE__ */ jsxs("header", { className: "lk-shell-header", children: [
677
1416
  /* @__PURE__ */ jsx("h1", { className: "lk-shell-title", children: title }),
678
1417
  header && /* @__PURE__ */ jsx("div", { className: "lk-shell-header-actions", children: header })
@@ -680,6 +1419,7 @@ function LabShell({ title, children, header, footer, theme = "auto" }) {
680
1419
  /* @__PURE__ */ jsx("main", { className: "lk-shell-body", children }),
681
1420
  footer && /* @__PURE__ */ jsx("footer", { className: "lk-shell-footer", children: footer })
682
1421
  ] });
1422
+ return outer ? shell : /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, mode: resolved, children: shell });
683
1423
  }
684
1424
  function WorkspaceGrid({ children }) {
685
1425
  const count = Children.count(children);
@@ -690,8 +1430,8 @@ function WorkspaceGrid({ children }) {
690
1430
  };
691
1431
  return /* @__PURE__ */ jsx("div", { className: "lk-workspace-grid", style, children });
692
1432
  }
693
- function buildStore(instruments, defaultInstrument, storage, storageKey, initialTheme) {
694
- const store = createLabStore({ storageKey, storage, initialTheme });
1433
+ function buildStore(instruments, defaultInstrument, storage, storageKey, initialMode) {
1434
+ const store = createLabStore({ storageKey, storage, initialMode });
695
1435
  if (store.getState().workspaces.length === 0) {
696
1436
  const seeded = addWorkspace([], instruments, defaultInstrument);
697
1437
  const record = seeded[0];
@@ -722,7 +1462,7 @@ function Lab({
722
1462
  defaultInstrument,
723
1463
  storage,
724
1464
  storageKey,
725
- theme,
1465
+ mode,
726
1466
  nebula,
727
1467
  title,
728
1468
  children
@@ -737,24 +1477,25 @@ function Lab({
737
1477
  defaultInstrument,
738
1478
  storage ?? noneAdapter,
739
1479
  storageKey ?? "labkit",
740
- theme ?? "auto"
1480
+ mode ?? "auto"
741
1481
  );
742
1482
  }
743
1483
  const store = storeRef.current;
744
1484
  const workspaces = useStore(store, (s) => s.workspaces);
745
1485
  const savedSnapshots = useStore(store, (s) => s.savedSnapshots);
746
- const themeValue = useStore(store, (s) => s.theme);
1486
+ const modeValue = useStore(store, (s) => s.mode);
1487
+ const resolvedMode = useResolvedMode(modeValue);
747
1488
  useEffect(() => {
748
- if (theme && theme !== store.getState().theme) {
749
- store.getState().setTheme(theme);
1489
+ if (mode && mode !== store.getState().mode) {
1490
+ store.getState().setMode(mode);
750
1491
  }
751
- }, [theme, store]);
1492
+ }, [mode, store]);
752
1493
  const contextValue = useMemo(() => {
753
1494
  const replaceWorkspaces = (next) => {
754
1495
  const currentById = new Map(store.getState().workspaces.map((w) => [w.id, w]));
755
1496
  const merged = next.map((w) => currentById.get(w.id) ?? w);
756
1497
  store.setState({ workspaces: merged });
757
- store.getState().setTheme(store.getState().theme);
1498
+ store.getState().setMode(store.getState().mode);
758
1499
  };
759
1500
  return {
760
1501
  instruments,
@@ -792,15 +1533,23 @@ function Lab({
792
1533
  deleteSnapshot: (snapshotId) => {
793
1534
  store.getState().deleteSnapshot(snapshotId);
794
1535
  },
795
- theme: themeValue,
796
- setTheme: (t) => {
797
- store.getState().setTheme(t);
1536
+ mode: modeValue,
1537
+ setMode: (m) => {
1538
+ store.getState().setMode(m);
798
1539
  }
799
1540
  };
800
- }, [instruments, workspaces, savedSnapshots, themeValue, store]);
801
- const themeClass = themeValue === "light" ? "lk-theme-light" : themeValue === "interstellar" ? "lk-theme-interstellar" : "";
802
- const nebulaStyle = themeValue === "interstellar" && nebula && nebula.length > 0 ? { ["--lk-space-nebula"]: buildNebula(nebula) } : void 0;
803
- return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { className: `lk-lab ${themeClass}`.trim(), style: nebulaStyle, children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", theme: themeValue, header: children, children: /* @__PURE__ */ jsx(WorkspaceGrid, { children: workspaces.map((w) => /* @__PURE__ */ jsx(Workspace, { id: w.id }, w.id)) }) }) }) }) });
1541
+ }, [instruments, workspaces, savedSnapshots, modeValue, store]);
1542
+ const backdropStyle = resolvedMode === "dark" && nebula && nebula.length > 0 ? { ["--wzl-backdrop"]: buildNebula(nebula) } : void 0;
1543
+ return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
1544
+ ThemeProvider,
1545
+ {
1546
+ theme: interstellarTheme,
1547
+ mode: resolvedMode,
1548
+ className: "lk-lab",
1549
+ style: backdropStyle,
1550
+ children: /* @__PURE__ */ jsx(LabShell, { title: title ?? "Labkit", mode: modeValue, header: children, children: /* @__PURE__ */ jsx(WorkspaceGrid, { children: workspaces.map((w) => /* @__PURE__ */ jsx(Workspace, { id: w.id }, w.id)) }) })
1551
+ }
1552
+ ) }) });
804
1553
  }
805
1554
  function CurveField({
806
1555
  values,
@@ -1420,6 +2169,6 @@ function ToggleRow({
1420
2169
  }) }) });
1421
2170
  }
1422
2171
 
1423
- export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Workspace, WorkspaceChrome, WorkspaceGrid, addWorkspace, cloneWorkspace, closeWorkspace, defineInstrument, detectCapabilities, gridDims, resetWorkspace, useLabContext, validateConfigSchema };
2172
+ export { CheckboxRow, ColorRow, CurveField, DefaultSidebar, DefaultStatusBar, DefaultToolbar, EffectCard, EffectCardList, Lab, LabContext, LabShell, NumberRow, PropertyGroup, PropertyList, PropertyPanel, PropertyRow, SelectRow, SliderRow, Subpanel, TextRow, ToggleRow, Workspace, WorkspaceChrome, WorkspaceGrid, addWorkspace, cloneWorkspace, closeWorkspace, defineInstrument, detectCapabilities, gridDims, interstellarTheme, resetWorkspace, useLabContext, validateConfigSchema };
1424
2173
  //# sourceMappingURL=index.js.map
1425
2174
  //# sourceMappingURL=index.js.map