@weasel-js/labkit 1.5.1 → 1.6.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 (160) hide show
  1. package/README.md +3 -7
  2. package/dist/_dts/{CanvasStackContext-CJNMS6mz.d.ts → CanvasStackContext-DQgcH1ny.d.ts} +1 -1
  3. package/dist/_dts/LayerList.d-2c9yup5Y.d.ts +83 -0
  4. package/dist/_dts/{PrefsForm.d-DPy9I8It.d.ts → PrefsRow.d-DFpPIeIC.d.ts} +16 -1
  5. package/dist/_dts/{Select.d-Gwr8fLpK.d.ts → Select.d-BchGMC2Y.d.ts} +129 -12
  6. package/dist/_dts/{frac-B4okC2lX.d.ts → frac-DTRq7Xpb.d.ts} +31 -13
  7. package/dist/_dts/inDialog-D5ZKGjiD.d.ts +23 -0
  8. package/dist/_dts/{index-CSJoj4LC.d.ts → index-B0--8PEM.d.ts} +5 -4
  9. package/dist/_dts/index-DINmCuLA.d.ts +453 -0
  10. package/dist/_dts/number.d-OeQYQoUO.d.ts +28 -0
  11. package/dist/_dts/{types-DzoNY1pO.d.ts → types-B7y8hX1D.d.ts} +6 -6
  12. package/dist/_dts/{usePanZoom-Pzf9f2jh.d.ts → usePanZoom-Bq73pwFe.d.ts} +4 -47
  13. package/dist/_dts/{useTrialState-uLlmaccn.d.ts → useTrialState-CP4jfuKl.d.ts} +1 -1
  14. package/dist/canvas/index.d.ts +6 -5
  15. package/dist/canvas/index.js +1 -1
  16. package/dist/chrome/index.d.ts +5 -4
  17. package/dist/chrome/index.js +7 -7
  18. package/dist/chunk-2RVCB2PT.js +3 -0
  19. package/dist/chunk-2RVCB2PT.js.map +1 -0
  20. package/dist/{chunk-UN4U4P63.js → chunk-3FI7Y4R4.js} +9 -9
  21. package/dist/chunk-3FI7Y4R4.js.map +1 -0
  22. package/dist/{chunk-7PFH3CNM.js → chunk-AWH3SRPW.js} +31 -5
  23. package/dist/chunk-AWH3SRPW.js.map +1 -0
  24. package/dist/{chunk-ZDHY47EV.js → chunk-BVL7Y574.js} +25 -9
  25. package/dist/chunk-BVL7Y574.js.map +1 -0
  26. package/dist/{chunk-IHUVQ3TX.js → chunk-H5VIV7MQ.js} +31 -27
  27. package/dist/chunk-H5VIV7MQ.js.map +1 -0
  28. package/dist/chunk-HFRE4QQV.js +400 -0
  29. package/dist/chunk-HFRE4QQV.js.map +1 -0
  30. package/dist/{chunk-IXTQI7JX.js → chunk-MOLIUNVH.js} +4 -4
  31. package/dist/{chunk-IXTQI7JX.js.map → chunk-MOLIUNVH.js.map} +1 -1
  32. package/dist/{chunk-CEIUBPPW.js → chunk-TT5C5S6R.js} +3 -86
  33. package/dist/chunk-TT5C5S6R.js.map +1 -0
  34. package/dist/chunk-USO6TERP.js +90 -0
  35. package/dist/chunk-USO6TERP.js.map +1 -0
  36. package/dist/{chunk-PHK4W562.js → chunk-VQ47KWD5.js} +3 -3
  37. package/dist/{chunk-PHK4W562.js.map → chunk-VQ47KWD5.js.map} +1 -1
  38. package/dist/chunk-WUJXJC5O.js +14568 -0
  39. package/dist/chunk-WUJXJC5O.js.map +1 -0
  40. package/dist/{chunk-EHGVROVV.js → chunk-ZORVNOKX.js} +6 -25
  41. package/dist/chunk-ZORVNOKX.js.map +1 -0
  42. package/dist/config/index.d.ts +53 -11
  43. package/dist/config/index.js +5 -5
  44. package/dist/controls/index.d.ts +6 -3
  45. package/dist/controls/index.js +4 -4
  46. package/dist/dragdrop/index.d.ts +4 -3
  47. package/dist/index.d.ts +139 -114
  48. package/dist/index.js +470 -479
  49. package/dist/index.js.map +1 -1
  50. package/dist/layers/index.d.ts +6 -35
  51. package/dist/layers/index.js +2 -3
  52. package/dist/loupe/index.d.ts +6 -5
  53. package/dist/passthrough/weasel-ui.d.ts +421 -215
  54. package/dist/passthrough/weasel-ui.js +1 -1
  55. package/dist/primitives/index.d.ts +2 -13
  56. package/dist/primitives/index.js +4 -4
  57. package/dist/state/index.d.ts +6 -5
  58. package/dist/state/index.js +5 -5
  59. package/dist/styles.css +410 -190
  60. package/dist/undo/index.d.ts +4 -3
  61. package/package.json +9 -13
  62. package/src/annotations/frac.test.ts +7 -0
  63. package/src/annotations/paint.test.ts +21 -0
  64. package/src/annotations/paint.ts +12 -0
  65. package/src/annotations/store.test.ts +31 -0
  66. package/src/annotations/types.ts +3 -2
  67. package/src/canvas/Stage.test.tsx +18 -0
  68. package/src/canvas/index.ts +0 -9
  69. package/src/chrome/labTypes.ts +1 -1
  70. package/src/chrome/regions/PaletteRegion.test.tsx +18 -0
  71. package/src/chrome/regions/PaletteRegion.tsx +5 -4
  72. package/src/chrome/regions/SidebarRegion.less +197 -0
  73. package/src/chrome/regions/SidebarRegion.test.tsx +20 -0
  74. package/src/chrome/regions/SidebarRegion.tsx +7 -2
  75. package/src/chrome/regions/TitleBarRegion.tsx +1 -1
  76. package/src/chrome/types.ts +13 -4
  77. package/src/config/autoConfig.ts +2 -2
  78. package/src/config/builder.ts +23 -5
  79. package/src/config/entry.test.ts +8 -0
  80. package/src/config/index.ts +2 -0
  81. package/src/config/rules.ts +2 -0
  82. package/src/config/sectionTree.test.ts +146 -0
  83. package/src/config/sectionTree.ts +124 -0
  84. package/src/config/types.ts +4 -4
  85. package/src/controls/AutoControls.stories.tsx +6 -6
  86. package/src/controls/ControlPanel.less +27 -0
  87. package/src/controls/ControlPanel.stories.tsx +95 -1
  88. package/src/controls/ControlPanel.test.tsx +82 -112
  89. package/src/controls/ControlPanel.tsx +159 -396
  90. package/src/controls/inDialog.test.tsx +82 -0
  91. package/src/controls/inDialog.tsx +46 -0
  92. package/src/controls/index.ts +1 -0
  93. package/src/index.test.ts +0 -2
  94. package/src/index.ts +14 -35
  95. package/src/instrument/types.ts +5 -2
  96. package/src/lab/Lab.nebula.test.tsx +31 -0
  97. package/src/lab/Lab.tsx +57 -18
  98. package/src/lab/LabBoundary.test.tsx +66 -0
  99. package/src/lab/LabBoundary.tsx +56 -0
  100. package/src/lab/LabHeader.tsx +4 -23
  101. package/src/lab/LabRoot.tsx +2 -3
  102. package/src/lab/LabShell.less +0 -1
  103. package/src/lab/LabShell.stories.tsx +15 -0
  104. package/src/lab/LabShell.test.tsx +18 -0
  105. package/src/lab/LabShell.tsx +14 -1
  106. package/src/lab/LabSwitcher.less +1 -1
  107. package/src/lab/LabSwitcher.test.tsx +41 -0
  108. package/src/lab/LabSwitcher.tsx +30 -8
  109. package/src/lab/index.ts +1 -0
  110. package/src/layers/AGENTS.md +18 -74
  111. package/src/layers/index.ts +4 -2
  112. package/src/passthrough/weasel-ui.ts +25 -10
  113. package/src/primitives/FpsMeter.less +6 -1
  114. package/src/primitives/FpsMeter.test.tsx +12 -4
  115. package/src/primitives/FpsMeter.tsx +6 -1
  116. package/src/primitives/JobProgress.stories.tsx +1 -1
  117. package/src/primitives/StatusBar.less +6 -0
  118. package/src/primitives/index.ts +0 -2
  119. package/src/specimen/Specimen.tsx +42 -56
  120. package/src/state/types.ts +8 -2
  121. package/src/state/view.test.ts +4 -4
  122. package/src/styles.less +4 -2
  123. package/src/theme/Interstellar.stories.tsx +8 -9
  124. package/src/theme/base.less +3 -20
  125. package/src/theme/interstellar.test.ts +6 -3
  126. package/src/theme/interstellar.theme.json +0 -56
  127. package/src/theme/interstellar.ts +2 -2
  128. package/src/trial/Trial.canvas.test.tsx +9 -5
  129. package/src/trial/Trial.config.test.tsx +1 -1
  130. package/src/trial/Trial.tsx +30 -8
  131. package/dist/_dts/index-BNdz-tNd.d.ts +0 -423
  132. package/dist/chunk-7PFH3CNM.js.map +0 -1
  133. package/dist/chunk-CEIUBPPW.js.map +0 -1
  134. package/dist/chunk-EH7UEQV7.js +0 -590
  135. package/dist/chunk-EH7UEQV7.js.map +0 -1
  136. package/dist/chunk-EHGVROVV.js.map +0 -1
  137. package/dist/chunk-IHUVQ3TX.js.map +0 -1
  138. package/dist/chunk-MDVTK5U3.js +0 -170
  139. package/dist/chunk-MDVTK5U3.js.map +0 -1
  140. package/dist/chunk-PJ7WEQBJ.js +0 -18
  141. package/dist/chunk-PJ7WEQBJ.js.map +0 -1
  142. package/dist/chunk-T3EC2DXD.js +0 -12628
  143. package/dist/chunk-T3EC2DXD.js.map +0 -1
  144. package/dist/chunk-UN4U4P63.js.map +0 -1
  145. package/dist/chunk-ZDHY47EV.js.map +0 -1
  146. package/dist/ui/layers/index.d.ts +0 -66
  147. package/dist/ui/layers/index.js +0 -3
  148. package/dist/ui/layers/index.js.map +0 -1
  149. package/src/canvas/useOrbit.test.ts +0 -167
  150. package/src/canvas/useOrbit.ts +0 -156
  151. package/src/lab/useSystemMode.ts +0 -30
  152. package/src/layers/LayerList.less +0 -93
  153. package/src/layers/LayerList.stories.tsx +0 -66
  154. package/src/layers/LayerList.test.tsx +0 -232
  155. package/src/layers/LayerList.tsx +0 -268
  156. package/src/primitives/Sidebar.less +0 -112
  157. package/src/primitives/Sidebar.stories.tsx +0 -38
  158. package/src/primitives/Sidebar.test.tsx +0 -74
  159. package/src/primitives/Sidebar.tsx +0 -35
  160. package/src/ui/layers/index.ts +0 -5
@@ -1,15 +1,13 @@
1
1
  import type { FillStyle, GradientFill, Track } from '@weasel-js/core';
2
2
  import {
3
- ActionsBar,
4
3
  Badge,
5
4
  type Band,
6
5
  BandEditor,
7
6
  Button,
7
+ ButtonBar,
8
8
  Callout,
9
9
  Checkbox,
10
- CheckboxRow,
11
10
  ColorField,
12
- ColorRow,
13
11
  ComboBox,
14
12
  type ControlPoint,
15
13
  CurveEditor,
@@ -24,11 +22,11 @@ import {
24
22
  ItemList,
25
23
  KeyCap,
26
24
  KeySequence,
27
- LayerStack,
28
- type LayerStackItem,
25
+ LayerList,
26
+ type LayerListItem,
29
27
  MenuButton,
28
+ moveLayers,
30
29
  NumberField,
31
- NumberRow,
32
30
  OptionsBar,
33
31
  PaintInput,
34
32
  Plot2D,
@@ -36,6 +34,7 @@ import {
36
34
  Powerline,
37
35
  type PrefGroup,
38
36
  PrefsForm,
37
+ PropertyField,
39
38
  PropertyGroup,
40
39
  PropertyList,
41
40
  PropertyPanel,
@@ -44,10 +43,9 @@ import {
44
43
  RangeSlider,
45
44
  ResizeHandle,
46
45
  Select,
47
- SelectRow,
46
+ Sidebar,
48
47
  SidebarPanel,
49
48
  Slider,
50
- SliderRow,
51
49
  StatusBarItem,
52
50
  StatusBarSpacer,
53
51
  Switch,
@@ -55,18 +53,15 @@ import {
55
53
  TabList,
56
54
  TabPanel,
57
55
  Tabs,
58
- TextRow,
59
56
  type Thumb,
60
57
  Timeline,
61
58
  ToastRegion,
62
59
  ToggleBar,
63
- ToggleRow,
64
60
  ToolButton,
65
61
  ToolGroup,
66
62
  ToolOptionsBar,
67
63
  Tooltip,
68
64
  TooltipTrigger,
69
- Sidebar as UiSidebar,
70
65
  StatusBar as UiStatusBar,
71
66
  } from '@weasel-js/ui';
72
67
  import { type CSSProperties, type ReactNode, useId, useMemo, useRef, useState } from 'react';
@@ -76,7 +71,6 @@ import { FloatingPanel } from '../primitives/FloatingPanel';
76
71
  import { JobProgress } from '../primitives/JobProgress';
77
72
  import { Legend, type LegendEntry } from '../primitives/Legend';
78
73
  import { ScaleIndicator } from '../primitives/ScaleIndicator';
79
- import { Sidebar } from '../primitives/Sidebar';
80
74
  import { Split } from '../primitives/Split';
81
75
  import { StatusBar } from '../primitives/StatusBar';
82
76
  import { Toolbar } from '../primitives/Toolbar';
@@ -213,19 +207,19 @@ function ButtonsAndToggles() {
213
207
  </Cell>
214
208
  <Cell label="Badge">
215
209
  <div className="lk-specimen__row">
216
- <Badge shape="pill" tone="accent" variant="outline" size="sm">
210
+ <Badge shape="pill" status="accent" variant="outline" size="sm">
217
211
  ACCENT
218
212
  </Badge>
219
- <Badge tone="info" variant="subtle">
213
+ <Badge status="info" variant="subtle">
220
214
  INFO
221
215
  </Badge>
222
- <Badge tone="warn" variant="solid">
216
+ <Badge status="warn" variant="solid">
223
217
  WARN
224
218
  </Badge>
225
- <Badge tone="danger" variant="solid">
219
+ <Badge status="danger" variant="solid">
226
220
  DANGER
227
221
  </Badge>
228
- <Badge tone="muted">MUTED</Badge>
222
+ <Badge status="muted">MUTED</Badge>
229
223
  </div>
230
224
  </Cell>
231
225
  <Cell label="Checkbox, Switch">
@@ -269,7 +263,7 @@ function ButtonsAndToggles() {
269
263
  ]}
270
264
  />
271
265
  </Cell>
272
- <Cell label="OptionsBar, ActionsBar">
266
+ <Cell label="OptionsBar, ButtonBar">
273
267
  <div className="lk-specimen__row">
274
268
  <OptionsBar
275
269
  ariaLabel="Text style"
@@ -278,7 +272,7 @@ function ButtonsAndToggles() {
278
272
  { value: 'i', label: 'I', selected: italic, onChange: setItalic },
279
273
  ]}
280
274
  />
281
- <ActionsBar
275
+ <ButtonBar
282
276
  items={[
283
277
  { value: 'undo', label: 'Undo', onAction: () => {} },
284
278
  { value: 'redo', label: 'Redo', onAction: () => {} },
@@ -297,7 +291,6 @@ function ButtonsAndToggles() {
297
291
  ]}
298
292
  />
299
293
  <Disclosure open={open} onToggle={() => setOpen((o) => !o)} label="Shapes" />
300
- <Disclosure open={false} onToggle={() => {}} label="Down" direction="down" />
301
294
  </div>
302
295
  </Cell>
303
296
  <Cell label="Tabs">
@@ -436,7 +429,7 @@ function PanelsAndRows() {
436
429
  const [label, setLabel] = useState('Badge');
437
430
  const [visible, setVisible] = useState(true);
438
431
  const [prefs, setPrefs] = useState<Record<string, unknown>>({});
439
- const [layers, setLayers] = useState<LayerStackItem[]>([
432
+ const [layers, setLayers] = useState<LayerListItem[]>([
440
433
  { id: 'base', label: 'Base coat' },
441
434
  { id: 'wash', label: 'Wash' },
442
435
  ]);
@@ -446,7 +439,9 @@ function PanelsAndRows() {
446
439
  <Cell label="PropertyPanel">
447
440
  <PropertyPanel title="Shape">
448
441
  <PropertyList>
449
- <SliderRow
442
+ <PropertyField
443
+ kind="number"
444
+ control="slider"
450
445
  label="Opacity"
451
446
  value={opacity}
452
447
  min={0}
@@ -454,7 +449,9 @@ function PanelsAndRows() {
454
449
  step={0.01}
455
450
  onChange={setOpacity}
456
451
  />
457
- <SliderRow
452
+ <PropertyField
453
+ kind="number"
454
+ control="slider"
458
455
  label="Radius"
459
456
  value={radius}
460
457
  min={0}
@@ -462,16 +459,18 @@ function PanelsAndRows() {
462
459
  unit="px"
463
460
  onChange={setRadius}
464
461
  />
465
- <ColorRow
462
+ <PropertyField
463
+ kind="color"
466
464
  label="Fill"
467
465
  value={fill}
468
466
  onChange={setFill}
469
467
  alpha={alpha}
470
468
  onAlphaChange={setAlpha}
471
469
  />
472
- <ColorRow label="Stroke" value={stroke} onChange={setStroke} />
473
- <NumberRow label="Count" value={count} min={0} max={100} step={1} onChange={setCount} />
474
- <SelectRow
470
+ <PropertyField kind="color" label="Stroke" value={stroke} onChange={setStroke} />
471
+ <PropertyField kind="number" label="Count" value={count} min={0} max={100} step={1} onChange={setCount} />
472
+ <PropertyField
473
+ kind="enum"
475
474
  label="Line cap"
476
475
  value={cap}
477
476
  onChange={setCap}
@@ -480,7 +479,9 @@ function PanelsAndRows() {
480
479
  { value: 'round', label: 'Round' },
481
480
  ]}
482
481
  />
483
- <ToggleRow
482
+ <PropertyField
483
+ kind="enum"
484
+ control="toggle"
484
485
  label="Align"
485
486
  value={side}
486
487
  onChange={setSide}
@@ -490,10 +491,10 @@ function PanelsAndRows() {
490
491
  { value: 'right', label: 'R' },
491
492
  ]}
492
493
  />
493
- <TextRow label="Label" value={label} onChange={setLabel} />
494
- <CheckboxRow label="Visible" value={visible} onChange={setVisible} />
494
+ <PropertyField kind="string" label="Label" value={label} onChange={setLabel} />
495
+ <PropertyField kind="boolean" label="Visible" value={visible} onChange={setVisible} />
495
496
  <PropertyGroup title="Bevel" defaultCollapsed>
496
- <SliderRow label="Rings" value={32} min={4} max={96} step={1} onChange={() => {}} />
497
+ <PropertyField kind="number" control="slider" label="Rings" value={32} min={4} max={96} step={1} onChange={() => {}} />
497
498
  </PropertyGroup>
498
499
  </PropertyList>
499
500
  </PropertyPanel>
@@ -505,18 +506,16 @@ function PanelsAndRows() {
505
506
  onChange={(path, value) => setPrefs((prev) => setAt(prev, path, value))}
506
507
  />
507
508
  </Cell>
508
- <Cell label="LayerStack">
509
- <LayerStack
509
+ <Cell label="LayerList">
510
+ <LayerList
510
511
  items={layers}
511
- onReorder={(ids) =>
512
- setLayers(ids.flatMap((id) => layers.filter((item) => item.id === id)))
513
- }
512
+ onReorder={(move) => setLayers(moveLayers(layers, move))}
514
513
  renderBody={(item) => <div>settings for {item.label}</div>}
515
514
  />
516
515
  </Cell>
517
516
  <Cell label="Sidebar, SidebarPanel">
518
517
  <div className="lk-specimen__box">
519
- <UiSidebar side="right" ariaLabel="Specimen sidebar">
518
+ <Sidebar side="right" ariaLabel="Specimen sidebar">
520
519
  <SidebarPanel
521
520
  title="Selection"
522
521
  collapsed={panelCollapsed}
@@ -525,7 +524,7 @@ function PanelsAndRows() {
525
524
  >
526
525
  <div className="lk-specimen__pad">2 items selected</div>
527
526
  </SidebarPanel>
528
- </UiSidebar>
527
+ </Sidebar>
529
528
  </div>
530
529
  </Cell>
531
530
  </Section>
@@ -554,8 +553,8 @@ function OverlaysAndFeedback() {
554
553
  triggerRef={calloutAnchor}
555
554
  isOpen={calloutOpen}
556
555
  onOpenChange={setCalloutOpen}
557
- tone="info"
558
- title="Info"
556
+ stance="notice"
557
+ title="Notice"
559
558
  >
560
559
  Neutral guidance.
561
560
  </Callout>
@@ -587,9 +586,9 @@ function OverlaysAndFeedback() {
587
586
  variant="solid"
588
587
  size="sm"
589
588
  segments={[
590
- { text: 'main', tone: 'accent', endCap: 'chevron' },
591
- { text: '✓ 12', tone: 'info', endCap: 'slant' },
592
- { text: '~/proj', tone: 'muted' },
589
+ { text: 'main', status: 'accent', endCap: 'chevron' },
590
+ { text: '✓ 12', status: 'info', endCap: 'slant' },
591
+ { text: '~/proj', status: 'muted' },
593
592
  ]}
594
593
  />
595
594
  </Cell>
@@ -799,7 +798,6 @@ function EditorsAndPlots() {
799
798
  }
800
799
 
801
800
  function LabChrome() {
802
- const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
803
801
  const [zoom, setZoom] = useState(1);
804
802
  return (
805
803
  <Section title="Lab chrome">
@@ -823,18 +821,6 @@ function LabChrome() {
823
821
  <StatusBar.Section>Zoom: 100%</StatusBar.Section>
824
822
  </StatusBar>
825
823
  </Cell>
826
- <Cell label="Sidebar">
827
- <div className="lk-specimen__box">
828
- <div className="lk-specimen__box-main">main</div>
829
- <Sidebar
830
- title="Controls"
831
- collapsed={sidebarCollapsed}
832
- onToggle={() => setSidebarCollapsed((v) => !v)}
833
- >
834
- <p>One slider</p>
835
- </Sidebar>
836
- </div>
837
- </Cell>
838
824
  <Cell label="Legend, JobProgress">
839
825
  <Legend entries={LEGEND} />
840
826
  <JobProgress job={JOB} />
@@ -1,3 +1,4 @@
1
+ import type { ColorModePreference } from '@weasel-js/theme';
1
2
  import type { Auto } from '../config/auto';
2
3
  import type { ConfigPath, ResolvedConfig, ValueAtPath } from '../config/types';
3
4
  import type { InstrumentList } from '../instrument/types';
@@ -78,7 +79,12 @@ export interface SavedSnapshot {
78
79
  }
79
80
 
80
81
  /** `auto` follows the OS; the other two are an explicit choice. */
81
- export type LabMode = 'auto' | 'light' | 'dark';
82
+ export type LabMode = ColorModePreference;
83
+
84
+ /** How much room the lab's own chrome takes — the theme's density axis, named
85
+ * the way the theme names it. A lab's instruments are unaffected: this sizes
86
+ * the shell around them. */
87
+ export type LabDensity = 'compact' | 'comfortable' | 'roomy';
82
88
 
83
89
  /** Everything a lab persists: its trials, its saved snapshots, and the
84
90
  * chosen color mode. */
@@ -126,7 +132,7 @@ export interface TrialStateHandle<TS, TC> {
126
132
  setState: (next: TS | ((prev: TS) => TS)) => void;
127
133
  config: TC;
128
134
  /** The config as stored, before any auto path is resolved. What the control
129
- * panel renders, so a ghosted control sits at the value it pins back to. */
135
+ * panel renders, so an auto row pins back to the value it was holding. */
130
136
  raw: TC;
131
137
  /** Dotted paths this trial has unpinned. */
132
138
  auto: ReadonlySet<string>;
@@ -5,13 +5,13 @@ import { openLabStore } from './openLabStore';
5
5
  import { createLabStore } from './store';
6
6
  import { as2DView, DEFAULT_VIEW, normalize2DView, withZoom } from './view';
7
7
 
8
- interface OrbitView {
8
+ interface ArbitraryTrialView {
9
9
  yaw: number;
10
10
  pitch: number;
11
11
  distance: number;
12
12
  }
13
13
 
14
- const orbit: OrbitView = { yaw: 1.1, pitch: 0.3, distance: 9 };
14
+ const orbit: ArbitraryTrialView = { yaw: 1.1, pitch: 0.3, distance: 9 };
15
15
 
16
16
  describe('as2DView', () => {
17
17
  it('accepts the 2D shape', () => {
@@ -54,7 +54,7 @@ describe('a trial view labkit does not interpret', () => {
54
54
 
55
55
  store.getState().updateTrialView('w1', orbit);
56
56
 
57
- expect(store.getState().trials[0]?.view as OrbitView).toEqual(orbit);
57
+ expect(store.getState().trials[0]?.view as ArbitraryTrialView).toEqual(orbit);
58
58
  });
59
59
 
60
60
  it('round-trips that view through persistence', async () => {
@@ -76,7 +76,7 @@ describe('a trial view labkit does not interpret', () => {
76
76
  storageKey: 'view-b',
77
77
  storage: createMemoryAdapter(backing),
78
78
  });
79
- expect(hydrated.store.getState().trials[0]?.view as OrbitView).toEqual(orbit);
79
+ expect(hydrated.store.getState().trials[0]?.view as ArbitraryTrialView).toEqual(orbit);
80
80
  });
81
81
  });
82
82
 
package/src/styles.less CHANGED
@@ -1,10 +1,12 @@
1
1
  // Imports compile-time only. Component .less files are added in later steps.
2
+ // Left as a CSS @import for the consumer's bundler to resolve; build-css.mjs
3
+ // swaps it for the faces themselves, pointed at dist/fonts/.
4
+ @import (css) '@weasel-js/theme/faces.css';
2
5
  @import './theme/base.less';
3
6
  @import './lab/Workspace.less';
4
7
  @import './lab/LabShell.less';
5
8
  @import './lab/LabSwitcher.less';
6
9
  @import './primitives/Toolbar.less';
7
- @import './primitives/Sidebar.less';
8
10
  @import './primitives/Split.less';
9
11
  @import './primitives/ZoomControl.less';
10
12
  @import './primitives/StatusBar.less';
@@ -17,11 +19,11 @@
17
19
  @import './trial/Trial.less';
18
20
  @import './chrome/regions/ViewportRegion.less';
19
21
  @import './chrome/regions/PaletteRegion.less';
22
+ @import './chrome/regions/SidebarRegion.less';
20
23
  @import './controls/ControlPanel.less';
21
24
  @import './canvas/CanvasStack.less';
22
25
  @import './canvas/Stage.less';
23
26
  @import './loupe/Loupe.less';
24
- @import './layers/LayerList.less';
25
27
  @import './annotations/Annotations.less';
26
28
  @import './dragdrop/Palette.less';
27
29
  @import './dragdrop/DragGhost.less';
@@ -2,12 +2,9 @@ import type { Meta, StoryObj } from '@weasel-js/forge';
2
2
  import type { TokenName } from '@weasel-js/theme';
3
3
  import { useTheme } from '@weasel-js/theme/react';
4
4
  import {
5
- CheckboxRow,
6
- ColorRow,
5
+ PropertyField,
7
6
  PropertyList,
8
7
  PropertyPanel,
9
- SliderRow,
10
- TextRow,
11
8
  } from '@weasel-js/ui';
12
9
  import './Interstellar.stories.less';
13
10
 
@@ -148,7 +145,9 @@ function LivePanelPreview() {
148
145
  <h3 className="lk-theme-doc__heading">Live panel</h3>
149
146
  <PropertyPanel title="Sample">
150
147
  <PropertyList>
151
- <SliderRow
148
+ <PropertyField
149
+ kind="number"
150
+ control="slider"
152
151
  label="Opacity"
153
152
  value={0.65}
154
153
  min={0}
@@ -157,10 +156,10 @@ function LivePanelPreview() {
157
156
  onChange={() => {}}
158
157
  format={(v) => v.toFixed(2)}
159
158
  />
160
- <ColorRow label="Fill" value="#b08adb" onChange={() => {}} />
161
- <ColorRow label="Stroke" value="#1a1428" onChange={() => {}} />
162
- <TextRow label="Name" value="Untitled" onChange={() => {}} />
163
- <CheckboxRow label="Visible" value={true} onChange={() => {}} />
159
+ <PropertyField kind="color" label="Fill" value="#b08adb" onChange={() => {}} />
160
+ <PropertyField kind="color" label="Stroke" value="#1a1428" onChange={() => {}} />
161
+ <PropertyField kind="string" label="Name" value="Untitled" onChange={() => {}} />
162
+ <PropertyField kind="boolean" label="Visible" value={true} onChange={() => {}} />
164
163
  </PropertyList>
165
164
  </PropertyPanel>
166
165
  </div>
@@ -1,25 +1,8 @@
1
- // Self-hosted Oswald (variable font, wght axis 200–700) for the headline
2
- // typeface. Sourced from @weasel-js/theme; build:fonts copies it into dist/fonts.
3
- @font-face {
4
- font-family: 'Oswald';
5
- font-style: normal;
6
- font-weight: 200 700;
7
- font-display: swap;
8
- // URL is resolved relative to the COMPILED stylesheet at dist/styles.css,
9
- // so this points to dist/fonts/. Source-tree usage (less compilation
10
- // happens before the file is moved) doesn't need to resolve the URL.
11
- src: url('./fonts/oswald-latin-variable.woff2') format('woff2');
12
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
13
- U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
14
- U+2212, U+2215, U+FEFF, U+FFFD;
15
- }
1
+ // The Oswald and Inter faces come from @weasel-js/theme/faces.css, imported at
2
+ // the top of styles.less.
16
3
 
17
4
  // Resets and global element defaults.
18
5
  .lk-root {
19
- // labkit's own vocabulary over the theme's raw scale — named for the thing
20
- // rather than the step, so "the workspace is too tight" has one place to go.
21
- --lk-workspace-pad: var(--wzl-space-sm);
22
-
23
6
  font-family: var(--wzl-font-ui);
24
7
  font-size: var(--wzl-font-size);
25
8
  font-weight: var(--wzl-font-weight-light);
@@ -52,7 +35,7 @@
52
35
 
53
36
  &:hover {
54
37
  border-color: var(--wzl-accent);
55
- color: var(--wzl-accent);
38
+ color: var(--wzl-accent-fg);
56
39
  }
57
40
 
58
41
  &:focus-visible {
@@ -1,4 +1,4 @@
1
- import { resolveTheme, THEME_SOURCES, type ThemeDefinition } from '@weasel-js/theme';
1
+ import { resolveTheme, THEME_SOURCES, type ThemeDefinition, weaselTheme } from '@weasel-js/theme';
2
2
  import { derive } from '@weasel-js/theme/engine';
3
3
  import { describe, expect, it } from 'vitest';
4
4
  import { interstellarTheme } from './interstellar';
@@ -19,8 +19,11 @@ describe('interstellarTheme', () => {
19
19
  // Values carried over verbatim from the retired Less.
20
20
  expect(dark['--wzl-surface']).toBe('#0a0a14');
21
21
  expect(dark['--wzl-accent']).toBe('#b08adb');
22
- expect(light['--wzl-surface']).toBe('#fafaf7');
23
- expect(light['--wzl-accent']).toBe('#a86f3c');
22
+ // Light mode is weasel's own: interstellar restyles the dark only.
23
+ const weasel = resolveTheme(weaselTheme, { mode: 'light' });
24
+ expect(light['--wzl-surface']).toBe(weasel['--wzl-surface']);
25
+ expect(light['--wzl-accent']).toBe(weasel['--wzl-accent']);
26
+ expect(light['--wzl-border']).toBe(weasel['--wzl-border']);
24
27
 
25
28
  // Inherited from weasel — interstellar overrides values, not the token set.
26
29
  expect(dark['--wzl-space-md']).toBe('12px');
@@ -27,10 +27,6 @@
27
27
  "dark": {
28
28
  "value": "#0a0a14",
29
29
  "type": "color"
30
- },
31
- "light": {
32
- "value": "#fafaf7",
33
- "type": "color"
34
30
  }
35
31
  },
36
32
  "surface-raised": {
@@ -38,10 +34,6 @@
38
34
  "dark": {
39
35
  "value": "rgba(20, 20, 36, 0.55)",
40
36
  "type": "color"
41
- },
42
- "light": {
43
- "value": "#ffffff",
44
- "type": "color"
45
37
  }
46
38
  },
47
39
  "surface-sunken": {
@@ -49,10 +41,6 @@
49
41
  "dark": {
50
42
  "value": "#02020a",
51
43
  "type": "color"
52
- },
53
- "light": {
54
- "value": "#f0ebe2",
55
- "type": "color"
56
44
  }
57
45
  },
58
46
  "border": {
@@ -60,10 +48,6 @@
60
48
  "dark": {
61
49
  "value": "rgba(255, 255, 255, 0.14)",
62
50
  "type": "color"
63
- },
64
- "light": {
65
- "value": "#c9bba5",
66
- "type": "color"
67
51
  }
68
52
  },
69
53
  "line-subtle": {
@@ -71,10 +55,6 @@
71
55
  "dark": {
72
56
  "value": "rgba(255, 255, 255, 0.08)",
73
57
  "type": "color"
74
- },
75
- "light": {
76
- "value": "rgba(0, 0, 0, 0.08)",
77
- "type": "color"
78
58
  }
79
59
  },
80
60
  "fg": {
@@ -82,10 +62,6 @@
82
62
  "dark": {
83
63
  "value": "#e8e8f0",
84
64
  "type": "color"
85
- },
86
- "light": {
87
- "value": "#2a2018",
88
- "type": "color"
89
65
  }
90
66
  },
91
67
  "fg-muted": {
@@ -93,10 +69,6 @@
93
69
  "dark": {
94
70
  "value": "#a8a8c0",
95
71
  "type": "color"
96
- },
97
- "light": {
98
- "value": "#6b5c4a",
99
- "type": "color"
100
72
  }
101
73
  },
102
74
  "fg-subtle": {
@@ -104,10 +76,6 @@
104
76
  "dark": {
105
77
  "value": "#5a5a78",
106
78
  "type": "color"
107
- },
108
- "light": {
109
- "value": "#a89b85",
110
- "type": "color"
111
79
  }
112
80
  },
113
81
  "accent": {
@@ -115,10 +83,6 @@
115
83
  "dark": {
116
84
  "value": "#b08adb",
117
85
  "type": "color"
118
- },
119
- "light": {
120
- "value": "#a86f3c",
121
- "type": "color"
122
86
  }
123
87
  },
124
88
  "accent-hover": {
@@ -126,10 +90,6 @@
126
90
  "dark": {
127
91
  "value": "#c6a3ee",
128
92
  "type": "color"
129
- },
130
- "light": {
131
- "value": "#c2854f",
132
- "type": "color"
133
93
  }
134
94
  },
135
95
  "focus-ring": {
@@ -137,10 +97,6 @@
137
97
  "dark": {
138
98
  "value": "rgba(176, 138, 219, 0.45)",
139
99
  "type": "color"
140
- },
141
- "light": {
142
- "value": "rgba(168, 111, 60, 0.35)",
143
- "type": "color"
144
100
  }
145
101
  },
146
102
  "shadow": {
@@ -148,10 +104,6 @@
148
104
  "dark": {
149
105
  "value": "rgba(0, 0, 0, 0.7)",
150
106
  "type": "color"
151
- },
152
- "light": {
153
- "value": "rgba(80, 60, 40, 0.18)",
154
- "type": "color"
155
107
  }
156
108
  },
157
109
  "border-strong": {
@@ -159,10 +111,6 @@
159
111
  "dark": {
160
112
  "value": "rgba(255, 255, 255, 0.36)",
161
113
  "type": "color"
162
- },
163
- "light": {
164
- "value": "#9e8b71",
165
- "type": "color"
166
114
  }
167
115
  },
168
116
  "backdrop": {
@@ -170,10 +118,6 @@
170
118
  "dark": {
171
119
  "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%)",
172
120
  "type": "gradient"
173
- },
174
- "light": {
175
- "value": "none",
176
- "type": "gradient"
177
121
  }
178
122
  }
179
123
  }
@@ -2,8 +2,8 @@ import { defineTheme, type Theme, type ThemeInput, weaselTheme } from '@weasel-j
2
2
  import definition from './interstellar.theme.json' with { type: 'json' };
3
3
 
4
4
  /**
5
- * labkit's theme: a cosmic dark and a warm parchment light, extending the
6
- * built-in weasel theme. A pins-only theme definition: nothing in it is
5
+ * labkit's theme: a cosmic dark over the built-in weasel theme, whose light
6
+ * mode it keeps as is. A pins-only theme definition: nothing in it is
7
7
  * derived, so it loads without the engine.
8
8
  */
9
9
  export const interstellarTheme: Theme = defineTheme({
@@ -93,16 +93,20 @@ describe('an auto config path', () => {
93
93
  render: () => null,
94
94
  canvas: { layers: [{ id: 'main', draw }] },
95
95
  });
96
- render(<Lab instruments={[instrument as Instrument]} defaultInstrument="Auto" />);
96
+ const { container } = render(
97
+ <Lab instruments={[instrument as Instrument]} defaultInstrument="Auto" />,
98
+ );
97
99
  await new Promise((r) => requestAnimationFrame(() => r(null)));
98
100
 
99
101
  expect(draw).toHaveBeenCalledWith(
100
102
  expect.anything(),
101
103
  expect.objectContaining({ config: { gap: 18 } }),
102
104
  );
103
- // The panel is handed the raw config, but a ghosted control draws what the
104
- // resolver decided — a handle at 12 under a readout saying 18 reads as a bug.
105
- expect(screen.getByRole('slider', { name: 'Gap' })).toHaveValue('18');
105
+ // The panel is handed the raw config, but an auto row holds what the
106
+ // resolver decided, so pinning it keeps the number the instrument drew.
107
+ // The row hides its control while auto, which takes it out of the
108
+ // accessibility tree — so this reads the element rather than the role.
109
+ expect(container.querySelector('input[type=range]')).toHaveValue('18');
106
110
  });
107
111
  });
108
112
 
@@ -181,7 +185,7 @@ describe('LabProps.instruments', () => {
181
185
  render(<Lab instruments={[instrument]} defaultInstrument="Probe" />);
182
186
  expect([...seen]).toEqual(['grid', 'glyph']);
183
187
 
184
- await userEvent.click(screen.getByRole('checkbox', { name: 'Toggle grid' }));
188
+ await userEvent.click(screen.getByRole('checkbox', { name: 'Show grid' }));
185
189
  expect([...seen]).toEqual(['glyph']);
186
190
  });
187
191
  });