@weasel-js/labkit 1.3.0-pre.0 → 1.3.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 (110) hide show
  1. package/dist/_dts/{index-DgcNdEdh.d.ts → types-BttTIent.d.ts} +2 -2
  2. package/dist/_dts/{types-AHlQxBNN.d.ts → types-DCGEcMO_.d.ts} +1 -1
  3. package/dist/canvas/index.d.ts +2 -2
  4. package/dist/canvas/index.js +3 -2
  5. package/dist/chrome/index.d.ts +3 -3
  6. package/dist/chrome/index.js +6 -5
  7. package/dist/chunk-3XXPU73K.js +138 -0
  8. package/dist/chunk-3XXPU73K.js.map +1 -0
  9. package/dist/{chunk-PO6L3NSH.js → chunk-6X5RBC6G.js} +17 -17
  10. package/dist/chunk-6X5RBC6G.js.map +1 -0
  11. package/dist/{chunk-NS54R52R.js → chunk-BDJXIBRZ.js} +5 -17
  12. package/dist/chunk-BDJXIBRZ.js.map +1 -0
  13. package/dist/{chunk-V7PRSIRQ.js → chunk-BMW4TDP5.js} +4 -4
  14. package/dist/chunk-BMW4TDP5.js.map +1 -0
  15. package/dist/{chunk-O7NVSCLN.js → chunk-JDROYQM3.js} +8 -8
  16. package/dist/chunk-JDROYQM3.js.map +1 -0
  17. package/dist/{chunk-HFDVGF4X.js → chunk-LGKRIUSW.js} +3 -3
  18. package/dist/{chunk-HFDVGF4X.js.map → chunk-LGKRIUSW.js.map} +1 -1
  19. package/dist/{chunk-Z6HJ5FEM.js → chunk-QCICIHEO.js} +19 -7
  20. package/dist/{chunk-Z6HJ5FEM.js.map → chunk-QCICIHEO.js.map} +1 -1
  21. package/dist/{chunk-RVNN4CHQ.js → chunk-QRYPXSGP.js} +5 -5
  22. package/dist/{chunk-RVNN4CHQ.js.map → chunk-QRYPXSGP.js.map} +1 -1
  23. package/dist/chunk-RGT4EJIB.js +6961 -0
  24. package/dist/chunk-RGT4EJIB.js.map +1 -0
  25. package/dist/chunk-SBXNQW4G.js +3 -0
  26. package/dist/chunk-SBXNQW4G.js.map +1 -0
  27. package/dist/chunk-T66HU3HX.js +17 -0
  28. package/dist/chunk-T66HU3HX.js.map +1 -0
  29. package/dist/chunk-WTI26YTM.js +266 -0
  30. package/dist/chunk-WTI26YTM.js.map +1 -0
  31. package/dist/controls/index.d.ts +2 -2
  32. package/dist/controls/index.js +3 -3
  33. package/dist/dragdrop/index.d.ts +44 -3
  34. package/dist/dragdrop/index.js +3 -1
  35. package/dist/index.d.ts +571 -541
  36. package/dist/index.js +69 -541
  37. package/dist/index.js.map +1 -1
  38. package/dist/layers/index.d.ts +3 -3
  39. package/dist/layers/index.js +4 -2
  40. package/dist/passthrough/weasel-canvas.js +1 -1
  41. package/dist/passthrough/weasel-ui.d.ts +15 -2
  42. package/dist/passthrough/weasel-ui.js +3 -2
  43. package/dist/primitives/index.js +5 -4
  44. package/dist/styles.css +1 -703
  45. package/dist/surface/index.js +2 -2
  46. package/dist/ui/layers/index.d.ts +8 -2
  47. package/dist/ui/layers/index.js +2 -4
  48. package/dist/undo/index.d.ts +2 -2
  49. package/package.json +5 -5
  50. package/src/config/builder.test.ts +7 -1
  51. package/src/config/builder.ts +1 -5
  52. package/src/config/resolve.test.ts +1 -2
  53. package/src/controls/ControlPanel.stories.tsx +41 -2
  54. package/src/controls/ControlPanel.test.tsx +31 -9
  55. package/src/controls/ControlPanel.tsx +56 -22
  56. package/src/dragdrop/index.ts +11 -0
  57. package/src/index.ts +49 -2
  58. package/src/lab/index.ts +1 -1
  59. package/src/layers/AGENTS.md +1 -1
  60. package/src/layers/LayerList.tsx +1 -1
  61. package/src/passthrough/weasel-ui.ts +22 -17
  62. package/src/styles.less +0 -4
  63. package/src/theme/Interstellar.stories.tsx +1 -1
  64. package/src/trial/Trial.config.test.tsx +3 -8
  65. package/src/ui/layers/index.ts +5 -1
  66. package/dist/chunk-NS54R52R.js.map +0 -1
  67. package/dist/chunk-O2BULFHX.js +0 -152
  68. package/dist/chunk-O2BULFHX.js.map +0 -1
  69. package/dist/chunk-O7NVSCLN.js.map +0 -1
  70. package/dist/chunk-PO6L3NSH.js.map +0 -1
  71. package/dist/chunk-PQJ5B2U2.js +0 -27
  72. package/dist/chunk-PQJ5B2U2.js.map +0 -1
  73. package/dist/chunk-TLGRYALP.js +0 -6151
  74. package/dist/chunk-TLGRYALP.js.map +0 -1
  75. package/dist/chunk-V7PRSIRQ.js.map +0 -1
  76. package/dist/chunk-ZZAYVX4X.js +0 -505
  77. package/dist/chunk-ZZAYVX4X.js.map +0 -1
  78. package/src/primitives/DragHandleGlyph.tsx +0 -29
  79. package/src/ui/format.test.ts +0 -33
  80. package/src/ui/format.ts +0 -30
  81. package/src/ui/layers/LayerStack.less +0 -143
  82. package/src/ui/layers/LayerStack.stories.tsx +0 -45
  83. package/src/ui/layers/LayerStack.test.tsx +0 -170
  84. package/src/ui/layers/LayerStack.tsx +0 -208
  85. package/src/ui/properties/CheckboxRow.stories.tsx +0 -27
  86. package/src/ui/properties/ColorRow.stories.tsx +0 -106
  87. package/src/ui/properties/CurveField.less +0 -48
  88. package/src/ui/properties/CurveField.stories.tsx +0 -20
  89. package/src/ui/properties/CurveField.test.tsx +0 -127
  90. package/src/ui/properties/CurveField.tsx +0 -184
  91. package/src/ui/properties/EffectCard.tsx +0 -324
  92. package/src/ui/properties/Gallery.stories.tsx +0 -178
  93. package/src/ui/properties/NumberRow.stories.tsx +0 -65
  94. package/src/ui/properties/PropertyGroup.less +0 -50
  95. package/src/ui/properties/PropertyGroup.stories.tsx +0 -34
  96. package/src/ui/properties/PropertyGroup.test.tsx +0 -33
  97. package/src/ui/properties/PropertyGroup.tsx +0 -45
  98. package/src/ui/properties/PropertyList.stories.tsx +0 -83
  99. package/src/ui/properties/PropertyPanel.less +0 -606
  100. package/src/ui/properties/PropertyPanel.stories.tsx +0 -190
  101. package/src/ui/properties/PropertyPanel.test.tsx +0 -193
  102. package/src/ui/properties/PropertyPanel.tsx +0 -469
  103. package/src/ui/properties/PropertyRow.stories.tsx +0 -53
  104. package/src/ui/properties/SelectRow.stories.tsx +0 -58
  105. package/src/ui/properties/SliderRow.stories.tsx +0 -74
  106. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +0 -398
  107. package/src/ui/properties/TextRow.stories.tsx +0 -62
  108. package/src/ui/properties/ToggleRow.stories.tsx +0 -57
  109. package/src/ui/properties/index.ts +0 -39
  110. package/src/ui/properties/storyLayouts.tsx +0 -36
@@ -1,4 +1,4 @@
1
- export { SurfaceContext, composeRects, rectsEqual, toDeviceRect, useSurface, useSurfaceOptional, useSurfaceTile, useTiledSurface } from '../chunk-HFDVGF4X.js';
2
- import '../chunk-Z6HJ5FEM.js';
1
+ export { SurfaceContext, composeRects, rectsEqual, toDeviceRect, useSurface, useSurfaceOptional, useSurfaceTile, useTiledSurface } from '../chunk-LGKRIUSW.js';
2
+ import '../chunk-QCICIHEO.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -14,7 +14,9 @@ interface LayerStackItem {
14
14
  * can switch mode/shape without expanding. */
15
15
  primaryValue?: string;
16
16
  primaryOptions?: string[];
17
- /** Accent CSS color used as the left border / index-badge fill. */
17
+ /** Accent CSS color used as the left border / index-badge fill. Sets
18
+ * --wzl-layer-stack-accent on the card, which re-binds --wzl-accent
19
+ * for everything inside it. */
18
20
  accent?: string;
19
21
  /** Optional badge text rendered before the primary control
20
22
  * (e.g. tail index "1", "2", "3"). When omitted a drag handle
@@ -38,10 +40,14 @@ interface LayerStackProps {
38
40
  /** Hide the title + palette row (used when an outer wrap renders its
39
41
  * own head — see speech-balloons Tails panel). */
40
42
  hideHead?: boolean;
43
+ /** Appended to the root element's class list. The module's own class names
44
+ * are hashed, so this is the supported way to reach the stack from a
45
+ * consumer stylesheet. */
46
+ className?: string;
41
47
  }
42
48
  /** A drag-reorderable stack of expandable cards, with a palette in the header
43
49
  * for adding more. The body of each card is the caller's to render. */
44
- declare function LayerStack({ title, items, paletteKinds, onAdd, onRemove, onReorder, onPrimaryChange, renderBody, hideHead, }: LayerStackProps): react_jsx_runtime.JSX.Element;
50
+ declare function LayerStack({ className, title, items, paletteKinds, onAdd, onRemove, onReorder, onPrimaryChange, renderBody, hideHead, }: LayerStackProps): react_jsx_runtime.JSX.Element;
45
51
 
46
52
  export { LayerStack };
47
53
  export type { LayerStackItem, LayerStackProps };
@@ -1,6 +1,4 @@
1
- export { LayerStack } from '../../chunk-O2BULFHX.js';
2
- import '../../chunk-TLGRYALP.js';
3
- import '../../chunk-Z6HJ5FEM.js';
4
- import '../../chunk-PQJ5B2U2.js';
1
+ export { c3 as LayerStack } from '../../chunk-RGT4EJIB.js';
2
+ import '../../chunk-QCICIHEO.js';
5
3
  //# sourceMappingURL=index.js.map
6
4
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
- export { U as UndoCapability } from '../_dts/index-DgcNdEdh.js';
1
+ export { U as UndoCapability } from '../_dts/types-BttTIent.js';
2
2
  import 'react';
3
- import '../_dts/types-AHlQxBNN.js';
3
+ import '../_dts/types-DCGEcMO_.js';
4
4
  import '../_dts/PrefsForm-BYa6cWnO.js';
5
5
  import '../_dts/types-1Sdxy_Pv.js';
6
6
  import '../_dts/types-DJ79Tg5J.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weasel-js/labkit",
3
- "version": "1.3.0-pre.0",
3
+ "version": "1.3.0",
4
4
  "description": "React widgets for building self-contained interactive lab pages",
5
5
  "license": "MIT",
6
6
  "author": "orochi235",
@@ -109,9 +109,9 @@
109
109
  "react-dom": "^19.0.0"
110
110
  },
111
111
  "dependencies": {
112
- "@weasel-js/core": "1.3.0-pre.0",
113
- "@weasel-js/theme": "1.3.0-pre.0",
114
- "@weasel-js/ui": "1.3.0-pre.0",
112
+ "@weasel-js/core": "1.3.0",
113
+ "@weasel-js/theme": "1.3.0",
114
+ "@weasel-js/ui": "1.3.0",
115
115
  "earcut": "2.2.4",
116
116
  "polygon-clipping": "^0.15.7",
117
117
  "react-aria-components": "^1.5.0",
@@ -122,7 +122,7 @@
122
122
  "@biomejs/biome": "^2.4.12",
123
123
  "@rollup/plugin-alias": "^6.0.0",
124
124
  "@testing-library/jest-dom": "^6.6.0",
125
- "@weasel-js/modes": "1.3.0-pre.0",
125
+ "@weasel-js/modes": "1.3.0",
126
126
  "less": "^4.2.0",
127
127
  "rollup-plugin-dts": "^6.4.1",
128
128
  "tsx": "^4.19.0",
@@ -20,7 +20,13 @@ describe('builder', () => {
20
20
  it('keeps the subclass across a chain, so kind-specific methods survive', () => {
21
21
  const n = f.number(20).label('A').range(5, 80).describe('help').step(5);
22
22
  expect(n.kind).toBe('number');
23
- expect(n.annotations).toMatchObject({ name: 'A', description: 'help', min: 5, max: 80, step: 5 });
23
+ expect(n.annotations).toMatchObject({
24
+ name: 'A',
25
+ description: 'help',
26
+ min: 5,
27
+ max: 80,
28
+ step: 5,
29
+ });
24
30
  });
25
31
 
26
32
  it('keeps a custom node kind across a chain', () => {
@@ -29,11 +29,7 @@ abstract class BaseNode<T> implements ConfigNode<T> {
29
29
  /** Clone into the same subclass. Subclasses with extra constructor
30
30
  * arguments override this. */
31
31
  protected with(annotations: Annotations, options: NodeOptions): this {
32
- const Ctor = this.constructor as new (
33
- d: T,
34
- a: Annotations,
35
- o: NodeOptions,
36
- ) => this;
32
+ const Ctor = this.constructor as new (d: T, a: Annotations, o: NodeOptions) => this;
37
33
  return new Ctor(this.default, annotations, options);
38
34
  }
39
35
 
@@ -54,8 +54,7 @@ describe('resolveConfigSchema', () => {
54
54
  });
55
55
 
56
56
  it('a consumer rule claims an f.value kind before the built-in', () => {
57
- const colorByName: ConfigRule = (ctx) =>
58
- ctx.key.endsWith('Color') ? { kind: 'color' } : null;
57
+ const colorByName: ConfigRule = (ctx) => (ctx.key.endsWith('Color') ? { kind: 'color' } : null);
59
58
  const r = resolveConfigSchema(f.schema({ tintColor: f.value('#fff') }), [colorByName]);
60
59
  expect(leafAt(r, 'tintColor').kind).toBe('color');
61
60
  });
@@ -1,9 +1,9 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { PropertyRow } from '@weasel-js/ui';
2
3
  import { useState } from 'react';
3
4
  import { f } from '../config/builder';
4
5
  import { resolveConfigSchema } from '../config/resolve';
5
6
  import type { ConfigRule, ControlRenderer } from '../config/types';
6
- import { PropertyRow } from '../ui/properties/PropertyPanel';
7
7
  import { ControlPanel } from './ControlPanel';
8
8
  import type { ConfigField } from './types';
9
9
 
@@ -141,9 +141,48 @@ export const CustomControl: Story = {
141
141
  ),
142
142
  };
143
143
 
144
+ const described = f.schema({
145
+ frequency: f
146
+ .number(2)
147
+ .range(0.1, 10)
148
+ .step(0.1)
149
+ .describe('Cycles per second the oscillator runs at.'),
150
+ amplitude: f.number(0.5).range(0, 1).step(0.05),
151
+ showGrid: f.boolean(true).describe('Draw the alignment grid behind the plot.'),
152
+ wave: f
153
+ .enum('sine', [
154
+ { value: 'sine', label: 'Sine' },
155
+ { value: 'square', label: 'Square' },
156
+ ])
157
+ .describe('Waveform the generator emits.'),
158
+ title: f.string('My experiment'),
159
+ tint: f.color('#3a86ff').describe('Plot line color.'),
160
+ });
161
+
162
+ /** A leaf with a `describe(...)` gets an \u24d8 beside its label whose tooltip
163
+ * carries the text; leaves without one stay bare. */
164
+ export const Described: Story = {
165
+ render: () => {
166
+ const schema = resolveConfigSchema(described, []);
167
+ const [config, setConfig] = useState<Record<string, unknown>>(described.defaults());
168
+ return (
169
+ <ControlPanel
170
+ schema={schema}
171
+ config={config}
172
+ setConfig={(key, value) => setConfig((prev) => ({ ...prev, [key as string]: value }))}
173
+ />
174
+ );
175
+ },
176
+ };
177
+
144
178
  const conditional = f.schema({
145
179
  showGrid: f.boolean(true),
146
- cellSize: f.number(20).range(5, 80).step(5).label('Grid spacing').showIf((c) => c.showGrid === true),
180
+ cellSize: f
181
+ .number(20)
182
+ .range(5, 80)
183
+ .step(5)
184
+ .label('Grid spacing')
185
+ .showIf((c) => c.showGrid === true),
147
186
  });
148
187
 
149
188
  /** `showIf` hides the row while the value stays in config. Toggle the
@@ -1,10 +1,10 @@
1
1
  import { fireEvent, render, screen } from '@testing-library/react';
2
- import { afterEach, describe, expect, it, vi } from 'vitest';
3
2
  import { TOOL_PREF_KINDS, type ToolPrefKind } from '@weasel-js/core';
4
3
  import type { PrefLeaf } from '@weasel-js/ui';
5
- import type { ResolvedConfig } from '../config/types';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
6
5
  import { f } from '../config/builder';
7
6
  import { resolveConfigSchema } from '../config/resolve';
7
+ import type { ResolvedConfig } from '../config/types';
8
8
  import { ControlPanel } from './ControlPanel';
9
9
  import type { ConfigField } from './types';
10
10
 
@@ -188,6 +188,21 @@ describe('<ControlPanel> schema', () => {
188
188
  render(<ControlPanel schema={schema} config={{}} setConfig={vi.fn()} />);
189
189
  expect(screen.getByLabelText('Tint')).toHaveValue('#123456');
190
190
  });
191
+
192
+ it('hangs a described leaf\u2019s description off a help affordance, and leaves an undescribed leaf bare', () => {
193
+ const schema = resolveConfigSchema(
194
+ f.schema({
195
+ showGrid: f.boolean(true).describe('Draw the alignment grid.'),
196
+ snap: f.boolean(true),
197
+ }),
198
+ [],
199
+ );
200
+ render(
201
+ <ControlPanel schema={schema} config={{ showGrid: true, snap: true }} setConfig={vi.fn()} />,
202
+ );
203
+ expect(screen.getByRole('button', { name: 'About Show grid' })).toBeInTheDocument();
204
+ expect(screen.getAllByRole('button', { name: /^About / })).toHaveLength(1);
205
+ });
191
206
  });
192
207
 
193
208
  describe('<ControlPanel> renderers', () => {
@@ -205,7 +220,7 @@ describe('<ControlPanel> renderers', () => {
205
220
  expect(screen.queryByText('by-kind')).not.toBeInTheDocument();
206
221
  });
207
222
 
208
- it('a lab renderer for the path beats the node\'s own .render', () => {
223
+ it("a lab renderer for the path beats the node's own .render", () => {
209
224
  const schema = resolveConfigSchema(
210
225
  f.schema({ tint: f.color('#ffffff').render(() => <span>by-node</span>) }),
211
226
  [],
@@ -252,7 +267,7 @@ describe('<ControlPanel> renderers', () => {
252
267
 
253
268
  it('a renderer returning null collapses the row', () => {
254
269
  const schema = resolveConfigSchema(f.schema({ tint: f.color('#ffffff') }), []);
255
- const { container } = render(
270
+ render(
256
271
  <ControlPanel
257
272
  schema={schema}
258
273
  config={{ tint: '#ffffff' }}
@@ -260,7 +275,7 @@ describe('<ControlPanel> renderers', () => {
260
275
  renderers={{ color: () => null }}
261
276
  />,
262
277
  );
263
- expect(container.querySelector('.lk-property-row')).toBeNull();
278
+ expect(screen.queryByText('Tint')).toBeNull();
264
279
  });
265
280
  });
266
281
 
@@ -274,7 +289,11 @@ describe('<ControlPanel> visibility and sections', () => {
274
289
  [],
275
290
  );
276
291
  const { rerender } = render(
277
- <ControlPanel schema={schema} config={{ showGrid: true, cellSize: 20 }} setConfig={vi.fn()} />,
292
+ <ControlPanel
293
+ schema={schema}
294
+ config={{ showGrid: true, cellSize: 20 }}
295
+ setConfig={vi.fn()}
296
+ />,
278
297
  );
279
298
  expect(screen.getByLabelText('Cell size')).toBeInTheDocument();
280
299
  rerender(
@@ -302,11 +321,11 @@ describe('<ControlPanel> visibility and sections', () => {
302
321
  f.schema({ showGrid: f.boolean(true), seed: f.number(0).section('Advanced') }),
303
322
  [],
304
323
  );
305
- const { container } = render(
324
+ render(
306
325
  <ControlPanel schema={schema} config={{ showGrid: true, seed: 0 }} setConfig={vi.fn()} />,
307
326
  );
308
327
  expect(screen.getByText('Advanced')).toBeInTheDocument();
309
- const group = container.querySelector('.lk-property-group');
328
+ const group = screen.getByRole('heading', { name: 'Advanced' }).parentElement;
310
329
  expect(group).not.toBeNull();
311
330
  expect(group?.textContent).toContain('Seed');
312
331
  expect(group?.textContent).not.toContain('Show grid');
@@ -335,7 +354,10 @@ describe('<ControlPanel> built-in kind coverage', () => {
335
354
  schema={schemaWith(
336
355
  { kind: 'paint', name: 'Fill', description: '', default: null },
337
356
  {
338
- kind: 'object', name: 'Stroke', description: '', default: {},
357
+ kind: 'object',
358
+ name: 'Stroke',
359
+ description: '',
360
+ default: {},
339
361
  children: { width: { kind: 'number', name: 'Width', description: '', default: 1 } },
340
362
  },
341
363
  )}
@@ -1,21 +1,21 @@
1
1
  import { isBuiltinToolPref } from '@weasel-js/core';
2
- import type { PrefLeaf } from '@weasel-js/ui';
3
- import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
4
- import { fromConfigFields } from '../config/fromConfigField';
5
- import type { ControlRenderer, ResolvedConfig } from '../config/types';
6
- import { isLeafVisible } from '../config/visible';
7
- import { PropertyGroup } from '../ui/properties/PropertyGroup';
8
2
  import {
9
3
  CheckboxRow,
10
4
  ColorRow,
11
5
  NumberRow,
6
+ type PrefLeaf,
7
+ PropertyGroup,
12
8
  PropertyList,
13
9
  PropertyRow,
14
10
  SelectRow,
11
+ SliderRow,
15
12
  TextRow,
16
13
  ToggleRow,
17
- SliderRow,
18
- } from '../ui/properties/PropertyPanel';
14
+ } from '@weasel-js/ui';
15
+ import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
16
+ import { fromConfigFields } from '../config/fromConfigField';
17
+ import type { ControlRenderer, ResolvedConfig } from '../config/types';
18
+ import { isLeafVisible } from '../config/visible';
19
19
  import type { ConfigField } from './types';
20
20
 
21
21
  export interface ControlPanelProps<TC extends Record<string, unknown>> {
@@ -48,10 +48,7 @@ export function ControlPanel<TC extends Record<string, unknown>>({
48
48
  showHidden = false,
49
49
  className,
50
50
  }: ControlPanelProps<TC>) {
51
- const resolved = useMemo(
52
- () => schema ?? fromConfigFields(fields ?? []),
53
- [schema, fields],
54
- );
51
+ const resolved = useMemo(() => schema ?? fromConfigFields(fields ?? []), [schema, fields]);
55
52
 
56
53
  const paths = Object.keys(resolved.group.children);
57
54
  const sectioned = new Set(resolved.sections.flatMap((s) => s.paths));
@@ -114,14 +111,15 @@ function ControlRow<TC extends Record<string, unknown>>({
114
111
 
115
112
  // Most specific wins, and within a tier the lab's entry beats the
116
113
  // instrument's: controls[path] -> node .render -> controls[kind] -> built-in.
117
- const custom =
118
- renderers?.[path] ?? resolved.renderers[path] ?? renderers?.[leaf.kind];
114
+ const custom = renderers?.[path] ?? resolved.renderers[path] ?? renderers?.[leaf.kind];
119
115
  if (custom) return custom({ path, pref: leaf, value, setValue: write });
120
116
 
121
117
  const label = leaf.name;
118
+ const description = leaf.description;
122
119
  const read = <T,>(): T => value as T;
123
120
 
124
- if (!isBuiltinToolPref(leaf)) return <UnwiredRow label={label} kind={leaf.kind} />;
121
+ if (!isBuiltinToolPref(leaf))
122
+ return <UnwiredRow label={label} kind={leaf.kind} description={description} />;
125
123
 
126
124
  switch (leaf.kind) {
127
125
  case 'number': {
@@ -137,6 +135,7 @@ function ControlRow<TC extends Record<string, unknown>>({
137
135
  max={max}
138
136
  step={step}
139
137
  onChange={write}
138
+ description={description}
140
139
  />
141
140
  );
142
141
  }
@@ -153,28 +152,52 @@ function ControlRow<TC extends Record<string, unknown>>({
153
152
  max={max}
154
153
  step={step}
155
154
  onChange={(n) => write(Math.min(hi, Math.max(lo, n)))}
155
+ description={description}
156
156
  />
157
157
  );
158
158
  }
159
159
  case 'boolean':
160
- return <CheckboxRow label={label} value={read<boolean>()} onChange={write} />;
160
+ return (
161
+ <CheckboxRow
162
+ label={label}
163
+ value={read<boolean>()}
164
+ onChange={write}
165
+ description={description}
166
+ />
167
+ );
161
168
  case 'enum': {
162
169
  const options = extra<readonly { value: string; label: string }[]>(leaf, 'options') ?? [];
163
170
  const Row = extra<string>(leaf, 'control') === 'radio' ? ToggleRow : SelectRow;
164
171
  return (
165
- <Row label={label} value={read<string>()} options={options} onChange={write} />
172
+ <Row
173
+ label={label}
174
+ value={read<string>()}
175
+ options={options}
176
+ onChange={write}
177
+ description={description}
178
+ />
166
179
  );
167
180
  }
168
181
  case 'string':
169
- return <DebouncedTextRow leaf={leaf} label={label} value={read<string>()} write={write} />;
182
+ return (
183
+ <DebouncedTextRow
184
+ leaf={leaf}
185
+ label={label}
186
+ value={read<string>()}
187
+ write={write}
188
+ description={description}
189
+ />
190
+ );
170
191
  case 'color':
171
- return <ColorRow label={label} value={read<string>()} onChange={write} />;
192
+ return (
193
+ <ColorRow label={label} value={read<string>()} onChange={write} description={description} />
194
+ );
172
195
  case 'paint':
173
196
  case 'object':
174
197
  // Declined: a hex swatch would write a solid over a gradient, and a flat
175
198
  // row would write one field into a half-built object. Override with
176
199
  // `render` to edit either.
177
- return <UnwiredRow label={label} kind={leaf.kind} />;
200
+ return <UnwiredRow label={label} kind={leaf.kind} description={description} />;
178
201
  default: {
179
202
  // Not reachable while every built-in kind has an arm — and a new kind
180
203
  // that lacks one is a compile error here, never a blank row.
@@ -188,9 +211,17 @@ function ControlRow<TC extends Record<string, unknown>>({
188
211
 
189
212
  /** A leaf this panel has no control for is named rather than dropped: a silent
190
213
  * gap reads as "this control does not exist". */
191
- function UnwiredRow({ label, kind }: { label: string; kind: string }) {
214
+ function UnwiredRow({
215
+ label,
216
+ kind,
217
+ description,
218
+ }: {
219
+ label: string;
220
+ kind: string;
221
+ description?: string;
222
+ }) {
192
223
  return (
193
- <PropertyRow label={label}>
224
+ <PropertyRow label={label} description={description}>
194
225
  <span className="lk-control-panel__unknown">no control for “{kind}”</span>
195
226
  </PropertyRow>
196
227
  );
@@ -204,11 +235,13 @@ function DebouncedTextRow({
204
235
  label,
205
236
  value,
206
237
  write,
238
+ description,
207
239
  }: {
208
240
  leaf: PrefLeaf;
209
241
  label: string;
210
242
  value: string;
211
243
  write: (value: unknown) => void;
244
+ description?: string;
212
245
  }) {
213
246
  const debounceMs = extra<number>(leaf, 'debounceMs') ?? 150;
214
247
  const [local, setLocal] = useState(value);
@@ -232,6 +265,7 @@ function DebouncedTextRow({
232
265
  return (
233
266
  <TextRow
234
267
  label={label}
268
+ description={description}
235
269
  value={local}
236
270
  placeholder={extra<string>(leaf, 'placeholder')}
237
271
  maxLength={extra<number>(leaf, 'maxLength')}
@@ -1 +1,12 @@
1
1
  export type { DragDropCapability, DragFeedback, PaletteItem } from '../instrument/types';
2
+ export type {
3
+ DragOverlayProps,
4
+ DragState,
5
+ UseDragDropArgs,
6
+ UseDragDropResult,
7
+ } from './DragDropRuntime';
8
+ export { DragOverlay, useDragDrop } from './DragDropRuntime';
9
+ export type { DragGhostProps } from './DragGhost';
10
+ export { DragGhost } from './DragGhost';
11
+ export type { PaletteProps } from './Palette';
12
+ export { Palette } from './Palette';
package/src/index.ts CHANGED
@@ -1,3 +1,52 @@
1
+ // The property-panel family and LayerStack live in `@weasel-js/ui` now; labkit
2
+ // re-exports them so existing `@weasel-js/labkit` imports keep resolving. Named,
3
+ // not `export *` — a star re-export of an external package emits no binding in
4
+ // the bundle.
5
+ export type {
6
+ CheckboxRowProps,
7
+ ColorRowProps,
8
+ CurveFieldProps,
9
+ CurveMark,
10
+ EffectCardListItem,
11
+ EffectCardListProps,
12
+ EffectCardProps,
13
+ LayerStackItem,
14
+ LayerStackProps,
15
+ NumberRowProps,
16
+ PropertyGroupProps,
17
+ PropertyListPack,
18
+ PropertyListProps,
19
+ PropertyOption,
20
+ PropertyPanelProps,
21
+ PropertyRowLayout,
22
+ PropertyRowProps,
23
+ PropertyRowVariant,
24
+ PropertySpanProps,
25
+ SelectRowProps,
26
+ SliderRowProps,
27
+ SubpanelProps,
28
+ TextRowProps,
29
+ ToggleRowProps,
30
+ } from '@weasel-js/ui';
31
+ export {
32
+ CheckboxRow,
33
+ ColorRow,
34
+ CurveField,
35
+ EffectCard,
36
+ EffectCardList,
37
+ LayerStack,
38
+ NumberRow,
39
+ PropertyGroup,
40
+ PropertyList,
41
+ PropertyPanel,
42
+ PropertyRow,
43
+ PropertySpan,
44
+ SelectRow,
45
+ SliderRow,
46
+ Subpanel,
47
+ TextRow,
48
+ ToggleRow,
49
+ } from '@weasel-js/ui';
1
50
  export type { CanvasLayerDescriptor, CanvasStackContextValue, CanvasStackProps } from './canvas';
2
51
  export { CanvasStack, CanvasStackContext, screenToWorld, worldToScreen } from './canvas';
3
52
  export type { OrbitHandlers, OrbitView, UseOrbitOptions, Vec3 } from './canvas/useOrbit';
@@ -105,7 +154,5 @@ export * from './surface';
105
154
  export { interstellarTheme } from './theme/interstellar';
106
155
  export type { ToolCapability, TrialTool } from './tools/types';
107
156
  export * from './trial';
108
- export * from './ui/layers';
109
- export * from './ui/properties';
110
157
  export type { EventBus, EventListener } from './undo';
111
158
  export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './undo';
package/src/lab/index.ts CHANGED
@@ -6,5 +6,5 @@ export type { LabPaletteProps } from './LabPalette';
6
6
  export { LabPalette } from './LabPalette';
7
7
  export type { LabShellProps } from './LabShell';
8
8
  export { LabShell } from './LabShell';
9
- export type { WorkspaceProps } from './Workspace';
9
+ export type { TrialLayout, WorkspaceProps } from './Workspace';
10
10
  export { Workspace } from './Workspace';
@@ -46,7 +46,7 @@ This is intended for legend/HUD layers that should never be toggled off. Visibil
46
46
 
47
47
  ## Drag handle customization
48
48
 
49
- The drag handle is a button with class `lk-layer-list__handle` holding `<DragHandleGlyph>`, the same grip `LayerStack` uses. Its padding is transparent and cancelled by an equal negative margin, so the grab target is larger than the drawn dots without widening the row. Row pitch — height plus row gap — is measured from the DOM when a drag starts, so restyling the row does not skew drag distance. It used to be a hardcoded `28`, which had already drifted from the rendered height by the time it was found.
49
+ The drag handle is a button with class `lk-layer-list__handle` holding `<DragHandleGlyph>` (from `@weasel-js/ui`), the same grip `LayerStack` uses. Its padding is transparent and cancelled by an equal negative margin, so the grab target is larger than the drawn dots without widening the row. Row pitch — height plus row gap — is measured from the DOM when a drag starts, so restyling the row does not skew drag distance. It used to be a hardcoded `28`, which had already drifted from the rendered height by the time it was found.
50
50
 
51
51
  Pointer capture is acquired on `pointerdown` and released on `pointerup`, so dragging works across the document without requiring window-level listeners.
52
52
 
@@ -1,6 +1,6 @@
1
1
  import { type PointerEvent, useRef, useState } from 'react';
2
2
  import type { LayerDescriptor } from '../instrument/types';
3
- import { DragHandleGlyph } from '../primitives/DragHandleGlyph';
3
+ import { DragHandleGlyph } from '../passthrough/weasel-ui';
4
4
 
5
5
  /** Props for `<LayerList>`. */
6
6
  export interface LayerListProps {
@@ -24,6 +24,7 @@ export {
24
24
  type BadgeVariant,
25
25
  type BoundsCtx,
26
26
  type BuiltInEdgeName,
27
+ type BuiltinPref,
27
28
  Button,
28
29
  type ButtonProps,
29
30
  type ButtonSize,
@@ -47,6 +48,8 @@ export {
47
48
  type DataGridProps,
48
49
  Dialog,
49
50
  type DialogProps,
51
+ DragHandleGlyph,
52
+ type DragHandleGlyphProps,
50
53
  detectPlatform,
51
54
  dlog,
52
55
  EDGE_PROFILES,
@@ -68,6 +71,7 @@ export {
68
71
  type InterpolationMode,
69
72
  inferKeycapKind,
70
73
  isDebugEnabled,
74
+ isPrefLeaf,
71
75
  KeyCap,
72
76
  type KeyCapProps,
73
77
  type KeyCapVariant,
@@ -79,6 +83,9 @@ export {
79
83
  keySpecFromKey,
80
84
  keySpecsFromMods,
81
85
  type LayerListItem,
86
+ LayerStack,
87
+ type LayerStackItem,
88
+ type LayerStackProps,
82
89
  type LogicalMod,
83
90
  type LogicalModSpec,
84
91
  MINUS_SIGN,
@@ -100,22 +107,6 @@ export {
100
107
  Powerline,
101
108
  type PowerlineProps,
102
109
  type PowerlineSegment,
103
- paintGradientTrack,
104
- Radio,
105
- RadioGroup,
106
- type RadioGroupProps,
107
- type RadioProps,
108
- RangeSlider,
109
- type RangeSliderProps,
110
- type ReorderDragHandlers,
111
- type ReorderDragState,
112
- type RovingItem,
113
- type RovingTabIndex,
114
- Select,
115
- SelectItem,
116
- type SelectItemProps,
117
- type BuiltinPref,
118
- isPrefLeaf,
119
110
  type PrefBoolean,
120
111
  type PrefBooleanControl,
121
112
  type PrefColor,
@@ -135,8 +126,21 @@ export {
135
126
  type PrefRenderer,
136
127
  type PrefString,
137
128
  type PrefStringControl,
129
+ paintGradientTrack,
138
130
  prefValueAtPath,
139
- visiblePrefSubtree,
131
+ Radio,
132
+ RadioGroup,
133
+ type RadioGroupProps,
134
+ type RadioProps,
135
+ RangeSlider,
136
+ type RangeSliderProps,
137
+ type ReorderDragHandlers,
138
+ type ReorderDragState,
139
+ type RovingItem,
140
+ type RovingTabIndex,
141
+ Select,
142
+ SelectItem,
143
+ type SelectItemProps,
140
144
  type SelectOption,
141
145
  type SelectProps,
142
146
  Sidebar,
@@ -174,4 +178,5 @@ export {
174
178
  type UseRovingTabIndexOptions,
175
179
  useReorderDragList,
176
180
  useRovingTabIndex,
181
+ visiblePrefSubtree,
177
182
  } from '@weasel-js/ui';
package/src/styles.less CHANGED
@@ -20,7 +20,3 @@
20
20
  @import './layers/LayerList.less';
21
21
  @import './dragdrop/Palette.less';
22
22
  @import './dragdrop/DragGhost.less';
23
- @import './ui/properties/PropertyPanel.less';
24
- @import './ui/properties/PropertyGroup.less';
25
- @import './ui/properties/CurveField.less';
26
- @import './ui/layers/LayerStack.less';
@@ -8,7 +8,7 @@ import {
8
8
  PropertyPanel,
9
9
  SliderRow,
10
10
  TextRow,
11
- } from '../ui/properties/PropertyPanel';
11
+ } from '@weasel-js/ui';
12
12
  import './Interstellar.stories.less';
13
13
 
14
14
  const meta: Meta = {