@weasel-js/labkit 1.6.1 → 1.7.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 (162) hide show
  1. package/dist/_dts/{CanvasStackContext-Wo6OwIVM.d.ts → CanvasStackContext-D2jnIwfk.d.ts} +1 -1
  2. package/dist/_dts/{LayerList.d-BoELAMsM.d.ts → LayerList.d-DiuEArKq.d.ts} +2 -2
  3. package/dist/_dts/PrefsRow.d-TOZ7BgD9.d.ts +79 -0
  4. package/dist/_dts/PropertyPanel.d-D-1paCbE.d.ts +141 -0
  5. package/dist/_dts/{Select.d-Cr8-zoup.d.ts → Select.d-Bensr_MT.d.ts} +11 -1
  6. package/dist/_dts/{Stage-DWBBi8IU.d.ts → Stage-CCWfQQWM.d.ts} +58 -18
  7. package/dist/_dts/index-B2Yj5aA0.d.ts +274 -0
  8. package/dist/_dts/{index-CKyX0ME_.d.ts → index-CsYqUHD3.d.ts} +2 -2
  9. package/dist/_dts/{types-DTuViBvc.d.ts → types-CRPSGFAp.d.ts} +13 -4
  10. package/dist/_dts/{types-B7yBTTF9.d.ts → types-CSm9jgol.d.ts} +2 -2
  11. package/dist/_dts/{useTrialState-CMaV6UD4.d.ts → useTrialState-CTZPgeZ7.d.ts} +1 -1
  12. package/dist/canvas/index.d.ts +6 -6
  13. package/dist/canvas/index.js +2 -2
  14. package/dist/chrome/index.d.ts +5 -5
  15. package/dist/chrome/index.js +8 -8
  16. package/dist/{chunk-QGMOA2YE.js → chunk-275JRRVD.js} +67 -70
  17. package/dist/chunk-275JRRVD.js.map +1 -0
  18. package/dist/{chunk-KHK5FGER.js → chunk-4LJSC7MB.js} +8 -10
  19. package/dist/chunk-4LJSC7MB.js.map +1 -0
  20. package/dist/{chunk-SLAQPUI4.js → chunk-7BO7HNGF.js} +5 -5
  21. package/dist/{chunk-SLAQPUI4.js.map → chunk-7BO7HNGF.js.map} +1 -1
  22. package/dist/chunk-CZZPXUOU.js +3 -0
  23. package/dist/{chunk-SBXNQW4G.js.map → chunk-CZZPXUOU.js.map} +1 -1
  24. package/dist/{chunk-6FLXL3I7.js → chunk-FMK7UQ6V.js} +19 -4
  25. package/dist/chunk-FMK7UQ6V.js.map +1 -0
  26. package/dist/{chunk-O6FFFOHS.js → chunk-FZ7YFQN7.js} +16 -8
  27. package/dist/chunk-FZ7YFQN7.js.map +1 -0
  28. package/dist/{chunk-GHGO52SO.js → chunk-GOZLNM4L.js} +5 -5
  29. package/dist/{chunk-GHGO52SO.js.map → chunk-GOZLNM4L.js.map} +1 -1
  30. package/dist/{chunk-4TT3PNPR.js → chunk-I5DUQ5HM.js} +33 -36
  31. package/dist/chunk-I5DUQ5HM.js.map +1 -0
  32. package/dist/{chunk-NEXR37YJ.js → chunk-J7OWWTCW.js} +4 -5
  33. package/dist/chunk-J7OWWTCW.js.map +1 -0
  34. package/dist/{chunk-IRTGUQ5O.js → chunk-KMJ7RWD3.js} +16 -6
  35. package/dist/chunk-KMJ7RWD3.js.map +1 -0
  36. package/dist/{chunk-S4NCI32V.js → chunk-M7HP3FTK.js} +4 -4
  37. package/dist/{chunk-S4NCI32V.js.map → chunk-M7HP3FTK.js.map} +1 -1
  38. package/dist/chunk-MIM5R4ZT.js +14972 -0
  39. package/dist/chunk-MIM5R4ZT.js.map +1 -0
  40. package/dist/{chunk-ASOK76ZH.js → chunk-T5IBSPAM.js} +59 -26
  41. package/dist/chunk-T5IBSPAM.js.map +1 -0
  42. package/dist/{chunk-DWMSB4CA.js → chunk-UNAZSLWE.js} +82 -16
  43. package/dist/chunk-UNAZSLWE.js.map +1 -0
  44. package/dist/{chunk-TJ77A4A3.js → chunk-Y23G2VZL.js} +5 -9
  45. package/dist/chunk-Y23G2VZL.js.map +1 -0
  46. package/dist/{chunk-HKV7LRHY.js → chunk-YT75VYRD.js} +4 -4
  47. package/dist/{chunk-HKV7LRHY.js.map → chunk-YT75VYRD.js.map} +1 -1
  48. package/dist/{chunk-CGHF6RZV.js → chunk-ZIFPHIWZ.js} +9 -7
  49. package/dist/chunk-ZIFPHIWZ.js.map +1 -0
  50. package/dist/config/index.d.ts +16 -9
  51. package/dist/config/index.js +4 -4
  52. package/dist/controls/index.d.ts +2 -2
  53. package/dist/controls/index.js +5 -5
  54. package/dist/dragdrop/index.d.ts +3 -3
  55. package/dist/index.d.ts +263 -24
  56. package/dist/index.js +376 -237
  57. package/dist/index.js.map +1 -1
  58. package/dist/layers/index.d.ts +4 -4
  59. package/dist/layers/index.js +2 -2
  60. package/dist/loupe/index.d.ts +4 -4
  61. package/dist/loupe/index.js +2 -2
  62. package/dist/overview/index.d.ts +2 -2
  63. package/dist/overview/index.js +9 -8
  64. package/dist/overview/index.js.map +1 -1
  65. package/dist/passthrough/weasel-ui.d.ts +208 -38
  66. package/dist/passthrough/weasel-ui.js +3 -3
  67. package/dist/primitives/index.d.ts +3 -178
  68. package/dist/primitives/index.js +6 -6
  69. package/dist/state/index.d.ts +5 -5
  70. package/dist/state/index.js +4 -4
  71. package/dist/styles.css +91 -83
  72. package/dist/undo/index.d.ts +3 -3
  73. package/package.json +13 -12
  74. package/src/annotations/AnnotationOverlay.tsx +2 -2
  75. package/src/annotations/Annotations.less +0 -10
  76. package/src/annotations/MarkList.tsx +4 -8
  77. package/src/annotations/paint.test.ts +3 -14
  78. package/src/annotations/paint.ts +3 -19
  79. package/src/annotations/svgNodes.test.ts +6 -9
  80. package/src/annotations/svgNodes.ts +3 -3
  81. package/src/canvas/AGENTS.md +1 -0
  82. package/src/canvas/CameraInput.tsx +44 -33
  83. package/src/canvas/CanvasStack.tsx +6 -1
  84. package/src/canvas/LinkedCursor.less +1 -1
  85. package/src/canvas/Stage.test.tsx +33 -4
  86. package/src/canvas/Stage.tsx +6 -1
  87. package/src/canvas/cameraRegistry.test.ts +55 -0
  88. package/src/canvas/cameraRegistry.ts +86 -0
  89. package/src/canvas/cameraZoom.test.ts +46 -0
  90. package/src/canvas/cameraZoom.ts +24 -0
  91. package/src/canvas/index.ts +14 -2
  92. package/src/canvas/sharedScope.test.tsx +94 -0
  93. package/src/chrome/ChromeRegions.stories.tsx +1 -0
  94. package/src/chrome/regions/PaletteRegion.less +0 -7
  95. package/src/chrome/regions/SidebarRegion.less +12 -2
  96. package/src/config/builder.test.ts +2 -1
  97. package/src/config/builder.ts +12 -5
  98. package/src/config/fromConfigField.test.ts +14 -0
  99. package/src/config/fromConfigField.ts +18 -4
  100. package/src/config/path.ts +2 -5
  101. package/src/config/resolve.ts +4 -0
  102. package/src/config/types.ts +13 -9
  103. package/src/controls/ControlPanel.less +1 -1
  104. package/src/controls/ControlPanel.stories.tsx +3 -2
  105. package/src/controls/ControlPanel.test.tsx +64 -11
  106. package/src/controls/ControlPanel.tsx +52 -2
  107. package/src/index.ts +15 -1
  108. package/src/lab/Lab.tsx +83 -72
  109. package/src/lab/{LabFit.stories.less → LabFit.browser.test.less} +1 -1
  110. package/src/lab/{LabFit.stories.tsx → LabFit.browser.test.tsx} +41 -63
  111. package/src/lab/LabFullChrome.stories.tsx +1 -0
  112. package/src/lab/LabHeader.test.tsx +24 -14
  113. package/src/lab/LabHeader.tsx +11 -12
  114. package/src/lab/LabShell.less +8 -17
  115. package/src/lab/LabSwitcher.less +6 -6
  116. package/src/lab/LabZoom.test.tsx +223 -0
  117. package/src/lab/LabZoom.tsx +170 -0
  118. package/src/lab/index.ts +1 -0
  119. package/src/loupe/LoupeGestures.tsx +1 -1
  120. package/src/overview/TrialOverview.tsx +7 -5
  121. package/src/passthrough/weasel-ui.test.ts +2 -0
  122. package/src/passthrough/weasel-ui.ts +16 -4
  123. package/src/primitives/FloatingPanel.test.tsx +11 -0
  124. package/src/primitives/FloatingPanel.tsx +2 -5
  125. package/src/primitives/JobProgress.less +1 -1
  126. package/src/primitives/Legend.stories.tsx +1 -0
  127. package/src/primitives/Readout.browser.test.tsx +137 -0
  128. package/src/primitives/Readout.less +12 -0
  129. package/src/primitives/Readout.stories.tsx +58 -0
  130. package/src/primitives/Readout.test.tsx +93 -0
  131. package/src/primitives/Readout.tsx +56 -0
  132. package/src/primitives/index.ts +2 -0
  133. package/src/specimen/Specimen.stories.tsx +1 -0
  134. package/src/specimen/Specimen.tsx +2 -2
  135. package/src/state/Persistence.browser.test.tsx +47 -0
  136. package/src/state/store.test.ts +5 -1
  137. package/src/state/usePersistedState.test.tsx +1 -1
  138. package/src/styles.less +1 -0
  139. package/src/theme/Interstellar.stories.tsx +3 -1
  140. package/src/theme/base.less +21 -47
  141. package/src/theme/interstellar.test.ts +1 -1
  142. package/src/theme/interstellar.theme.json +10 -3
  143. package/src/trial/Trial.less +0 -13
  144. package/src/trial/Trial.tsx +12 -6
  145. package/src/trial/TrialChrome.tsx +21 -3
  146. package/dist/_dts/PrefsRow.d-DFpPIeIC.d.ts +0 -39
  147. package/dist/_dts/PropertyField.d-D0ndfIIt.d.ts +0 -404
  148. package/dist/chunk-4TT3PNPR.js.map +0 -1
  149. package/dist/chunk-6FLXL3I7.js.map +0 -1
  150. package/dist/chunk-ASOK76ZH.js.map +0 -1
  151. package/dist/chunk-CGHF6RZV.js.map +0 -1
  152. package/dist/chunk-DWMSB4CA.js.map +0 -1
  153. package/dist/chunk-IRTGUQ5O.js.map +0 -1
  154. package/dist/chunk-KHK5FGER.js.map +0 -1
  155. package/dist/chunk-NEXR37YJ.js.map +0 -1
  156. package/dist/chunk-O6FFFOHS.js.map +0 -1
  157. package/dist/chunk-QGMOA2YE.js.map +0 -1
  158. package/dist/chunk-SBXNQW4G.js +0 -3
  159. package/dist/chunk-TJ77A4A3.js.map +0 -1
  160. package/dist/chunk-TYN6MLXX.js +0 -13864
  161. package/dist/chunk-TYN6MLXX.js.map +0 -1
  162. package/src/state/Persistence.stories.tsx +0 -75
@@ -1,6 +1,10 @@
1
1
  // The Oswald and Inter faces come from @weasel-js/theme/faces.css, imported at
2
2
  // the top of styles.less.
3
3
 
4
+ // weasel-ui's native range skin, read for `:extend` below and emitted only
5
+ // through it.
6
+ @import (reference, less) '../../../ui/src/components/range.module.css';
7
+
4
8
  // Resets and global element defaults.
5
9
  .lk-root {
6
10
  font-family: var(--wzl-font-ui);
@@ -21,7 +25,10 @@
21
25
  // Deliberate overrides of a weasel-ui component go the other way — see the
22
26
  // `.lk-root` prefix convention below.
23
27
  :where(.lk-root) {
24
- :where(button) {
28
+ // Only a button with no class or a labkit one. Specificity cannot keep this
29
+ // off a weasel-ui button: whatever its module leaves unset — a height, a
30
+ // backdrop blur — the default fills in.
31
+ :where(button:not([class]), button[class^='lk-'], button[class*=' lk-']) {
25
32
  font: inherit;
26
33
  color: inherit;
27
34
  background: var(--wzl-surface-raised);
@@ -33,63 +40,32 @@
33
40
  backdrop-filter: blur(var(--wzl-glass-blur));
34
41
  -webkit-backdrop-filter: blur(var(--wzl-glass-blur));
35
42
 
36
- &:hover {
43
+ &:where(:hover) {
37
44
  border-color: var(--wzl-accent);
38
45
  color: var(--wzl-accent-fg);
39
46
  }
40
47
 
41
- &:focus-visible {
48
+ &:where(:focus-visible) {
42
49
  outline: 2px solid var(--wzl-focus-ring);
43
50
  outline-offset: 1px;
44
51
  }
45
52
 
46
- &:disabled {
53
+ &:where(:disabled) {
47
54
  color: var(--wzl-fg-subtle);
48
55
  cursor: not-allowed;
49
56
  }
50
57
  }
51
58
 
52
- // Mirrors packages/ui/src/components/range.module.css so a consumer's bare
53
- // range matches the kit's. A weasel-ui component's own module class outranks
54
- // this, which is what keeps InlineRange's gradient track intact.
55
- :where(input[type="range"]) {
56
- appearance: none;
57
- -webkit-appearance: none;
58
- height: 12px;
59
- background: transparent;
60
- cursor: pointer;
61
-
62
- &::-webkit-slider-runnable-track {
63
- height: var(--wzl-slider-track-h);
64
- border-radius: var(--wzl-radius-sm);
65
- background: color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-track-mix), transparent);
66
- }
67
-
68
- &::-moz-range-track {
69
- height: var(--wzl-slider-track-h);
70
- border-radius: var(--wzl-radius-sm);
71
- background: color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-track-mix), transparent);
72
- }
73
-
74
- &::-webkit-slider-thumb {
75
- appearance: none;
76
- -webkit-appearance: none;
77
- width: var(--wzl-slider-thumb-size);
78
- height: var(--wzl-slider-thumb-size);
79
- border: none;
80
- border-radius: 50%;
81
- background: color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-thumb-mix), transparent);
82
- margin-top: calc((var(--wzl-slider-track-h) - var(--wzl-slider-thumb-size)) / 2);
83
- }
84
-
85
- &::-moz-range-thumb {
86
- width: var(--wzl-slider-thumb-size);
87
- height: var(--wzl-slider-thumb-size);
88
- border: none;
89
- border-radius: 50%;
90
- background: color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-thumb-mix), transparent);
91
- }
59
+ // The browser sets these in its own `monospace`, which no font token reaches.
60
+ :where(code, kbd, samp, pre) {
61
+ font-family: var(--wzl-font-mono);
92
62
  }
63
+
64
+ // A consumer's bare range wears weasel-ui's range skin, extended from its one
65
+ // definition rather than copied. The skin keeps its pseudo-classes inside
66
+ // :where(), so nothing this emits outranks a single class and a weasel-ui
67
+ // component's own module class still wins.
68
+ :where(input[type="range"]):extend(.range all) {}
93
69
  }
94
70
 
95
71
  // The `.lk-root` prefix convention: a labkit class handed to a weasel-ui
@@ -151,9 +127,7 @@
151
127
  }
152
128
 
153
129
  // Headline / display text uses Oswald light at tighter line-height.
154
- .lk-lab h1,
155
- .lk-lab h2,
156
- .lk-lab h3 {
130
+ :where(.lk-lab) :where(h1, h2, h3) {
157
131
  font-family: var(--wzl-font-display);
158
132
  font-weight: var(--wzl-font-weight-light);
159
133
  letter-spacing: 0.01em;
@@ -18,7 +18,7 @@ describe('interstellarTheme', () => {
18
18
 
19
19
  // Values carried over verbatim from the retired Less.
20
20
  expect(dark['--wzl-surface']).toBe('#0a0a14');
21
- expect(dark['--wzl-accent']).toBe('#b08adb');
21
+ expect(dark['--wzl-accent']).toBe('#4f46e5');
22
22
  // Light mode is weasel's own: interstellar restyles the dark only.
23
23
  const weasel = resolveTheme(weaselTheme, { mode: 'light' });
24
24
  expect(light['--wzl-surface']).toBe(weasel['--wzl-surface']);
@@ -81,21 +81,28 @@
81
81
  "accent": {
82
82
  "by": "mode",
83
83
  "dark": {
84
- "value": "#b08adb",
84
+ "value": "#4f46e5",
85
85
  "type": "color"
86
86
  }
87
87
  },
88
88
  "accent-hover": {
89
89
  "by": "mode",
90
90
  "dark": {
91
- "value": "#c6a3ee",
91
+ "value": "#6d5dfc",
92
+ "type": "color"
93
+ }
94
+ },
95
+ "accent-strong": {
96
+ "by": "mode",
97
+ "dark": {
98
+ "value": "#7474fe",
92
99
  "type": "color"
93
100
  }
94
101
  },
95
102
  "focus-ring": {
96
103
  "by": "mode",
97
104
  "dark": {
98
- "value": "rgba(176, 138, 219, 0.45)",
105
+ "value": "rgba(116, 116, 254, 0.5)",
99
106
  "type": "color"
100
107
  }
101
108
  },
@@ -109,19 +109,6 @@
109
109
  }
110
110
  }
111
111
 
112
- .lk-toolbar__zoom-label {
113
- font-size: var(--wzl-font-size-sm);
114
- color: var(--wzl-fg-muted);
115
- padding: 0 var(--wzl-space-sm);
116
- }
117
-
118
- .lk-sidebar__placeholder {
119
- padding: var(--wzl-space-sm);
120
- color: var(--wzl-fg-muted);
121
- font-size: var(--wzl-font-size-sm);
122
- }
123
-
124
-
125
112
  .lk-trial__titlebar {
126
113
  display: flex;
127
114
  align-items: center;
@@ -6,9 +6,10 @@ import { AnnotationTargets } from '../annotations/AnnotationTargets';
6
6
  import { AnnotationPreloadContext } from '../annotations/preload';
7
7
  import { annotationsFromJSON } from '../annotations/store';
8
8
  import type { AnnotationStorage, AnnotationTargetInfo } from '../annotations/types';
9
- import { CameraScopeContext } from '../canvas/CameraInput';
9
+ import type { CameraView } from '../canvas/CameraInput';
10
10
  import { CameraWheelContext, type CameraWheelSlot } from '../canvas/CameraWheelContext';
11
11
  import { CanvasStack } from '../canvas/CanvasStack';
12
+ import { CameraPublishContext, CameraRegistryContext } from '../canvas/cameraRegistry';
12
13
  import { fitStage, Stage } from '../canvas/Stage';
13
14
  import type { CanvasLayerDescriptor } from '../canvas/useLayerScheduler';
14
15
  import { applyCamera, type ViewportSize } from '../canvas/worldSpec';
@@ -136,6 +137,12 @@ function TrialRuntime({
136
137
  suppress,
137
138
  }: TrialRuntimeProps) {
138
139
  const wheelSlot = useRef<CameraWheelSlot['current']>(null);
140
+ const cameras = useContext(CameraRegistryContext);
141
+ const trialId = record.id;
142
+ const publishCamera = useMemo(
143
+ () => (cameras ? (camera: CameraView) => cameras.register(trialId, camera) : null),
144
+ [cameras, trialId],
145
+ );
139
146
  const [pointer] = useState(createPointerStore);
140
147
  const canvasContainerRef = useRef<HTMLDivElement | null>(null);
141
148
  const loupeHostRef = useRef<HTMLDivElement | null>(null);
@@ -593,14 +600,13 @@ function TrialRuntime({
593
600
  />
594
601
  ) : null;
595
602
 
596
- // One input scope for the trial's body: the camera, the loupe and an
597
- // overview share its actions, its deps and its pointer. Isolated, because an
598
- // actions registry holds one dispatcher and the trial beside this one has
599
- // its own.
603
+ // The trial's root registries, isolated from the trial beside it. Each
604
+ // camera inside mounts an input scope under them; chrome here reaches the
605
+ // one last used. The pointer store is the trial's, shared by all of them.
600
606
  const scopedBody = (
601
607
  <WeaselProvider isolate>
602
608
  <PointerContextProvider store={pointer}>
603
- <CameraScopeContext.Provider value={true}>{body}</CameraScopeContext.Provider>
609
+ <CameraPublishContext.Provider value={publishCamera}>{body}</CameraPublishContext.Provider>
604
610
  </PointerContextProvider>
605
611
  </WeaselProvider>
606
612
  );
@@ -1,5 +1,13 @@
1
- import { type KeyboardEvent, type ReactNode, useContext, useMemo } from 'react';
1
+ import {
2
+ type KeyboardEvent,
3
+ type ReactNode,
4
+ useContext,
5
+ useMemo,
6
+ useSyncExternalStore,
7
+ } from 'react';
2
8
  import { useStore } from 'zustand/react';
9
+ import { CameraRegistryContext } from '../canvas/cameraRegistry';
10
+ import { zoomCameraTo } from '../canvas/cameraZoom';
3
11
  import { builtinContributions } from '../chrome/builtins';
4
12
  import { mergeContributions, suppressContributions } from '../chrome/merge';
5
13
  import { PaletteRegion } from '../chrome/regions/PaletteRegion';
@@ -68,6 +76,7 @@ const NO_OP = (): void => {};
68
76
  /** One frozen empty map, so a trial that has folded nothing keeps a stable
69
77
  * context identity across renders. */
70
78
  const NO_SECTIONS: Readonly<Record<string, boolean>> = Object.freeze({});
79
+ const noSubscribe = (): (() => void) => NO_OP;
71
80
 
72
81
  /** The frame around a running instrument. Builds one chrome context, assembles
73
82
  * the contributions the instrument, the runtime and the lab declare, and hands
@@ -123,10 +132,18 @@ export function TrialChrome({
123
132
  const title = record.title ?? instrument.title ?? instrument.name;
124
133
  const collapsedSections = record.collapsedSections ?? NO_SECTIONS;
125
134
 
135
+ // The trial's own camera, where it has one: zoom goes through it, about the
136
+ // middle of the view and inside the instrument's range, as the lab's does.
137
+ const cameras = useContext(CameraRegistryContext);
138
+ const camera = useSyncExternalStore(
139
+ cameras?.subscribe ?? noSubscribe,
140
+ () => cameras?.get(trialId) ?? null,
141
+ );
142
+
126
143
  const ctx = useMemo<TrialChromeContext>(() => {
127
144
  const setZoom = (z: number): void => {
128
- if (!view2d) return;
129
- updateTrialView(trialId, withZoom(view2d, z));
145
+ if (camera) zoomCameraTo(camera, z);
146
+ else if (view2d) updateTrialView(trialId, withZoom(view2d, z));
130
147
  };
131
148
  return {
132
149
  trialId,
@@ -203,6 +220,7 @@ export function TrialChrome({
203
220
  setTrialTitle,
204
221
  setTrialSectionCollapsed,
205
222
  collapsedSections,
223
+ camera,
206
224
  ]);
207
225
 
208
226
  const contributions = useMemo(
@@ -1,39 +0,0 @@
1
- import { Stance } from '@weasel-js/theme';
2
- import { ReactNode } from 'react';
3
- import { ToolPrefLeaf } from '@weasel-js/core';
4
-
5
- /**
6
- * What a surface holds, and which of its peers it is. Every surface that takes
7
- * these takes both, and the theme decides how each looks.
8
- */
9
- interface StanceProps {
10
- /** The class of content the surface holds. Omitted, it draws as it always has. */
11
- stance?: Stance;
12
- /**
13
- * Which of its peers this is: an index into the theme's tone list, wrapping,
14
- * or a color given directly. Independent of `stance`, and allowed without one.
15
- */
16
- tone?: number | string;
17
- }
18
-
19
- /** What a {@link PrefRenderer} is given for the leaf it is rendering. */
20
- interface PrefRenderContext {
21
- /** Dotted path of the leaf within the schema root. */
22
- path: string;
23
- /** The schema node. App renderers narrow this to their own kind shape. */
24
- pref: ToolPrefLeaf;
25
- /** Current value — `values` at `path`, falling back to `pref.default`. */
26
- value: unknown;
27
- setValue: (value: unknown) => void;
28
- /** Whether this leaf is currently auto — not pinned, computed by the owner. */
29
- auto: boolean;
30
- /** Toggle this leaf's auto state. */
31
- setAuto: (next: boolean) => void;
32
- }
33
- /**
34
- * Renders the control cell for one preference leaf. Returning `null`
35
- * collapses the row.
36
- */
37
- type PrefRenderer = (ctx: PrefRenderContext) => ReactNode;
38
-
39
- export type { PrefRenderer as P, StanceProps as S, PrefRenderContext as a };