@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.
- package/README.md +3 -7
- package/dist/_dts/{CanvasStackContext-CJNMS6mz.d.ts → CanvasStackContext-DQgcH1ny.d.ts} +1 -1
- package/dist/_dts/LayerList.d-2c9yup5Y.d.ts +83 -0
- package/dist/_dts/{PrefsForm.d-DPy9I8It.d.ts → PrefsRow.d-DFpPIeIC.d.ts} +16 -1
- package/dist/_dts/{Select.d-Gwr8fLpK.d.ts → Select.d-BchGMC2Y.d.ts} +129 -12
- package/dist/_dts/{frac-B4okC2lX.d.ts → frac-DTRq7Xpb.d.ts} +31 -13
- package/dist/_dts/inDialog-D5ZKGjiD.d.ts +23 -0
- package/dist/_dts/{index-CSJoj4LC.d.ts → index-B0--8PEM.d.ts} +5 -4
- package/dist/_dts/index-DINmCuLA.d.ts +453 -0
- package/dist/_dts/number.d-OeQYQoUO.d.ts +28 -0
- package/dist/_dts/{types-DzoNY1pO.d.ts → types-B7y8hX1D.d.ts} +6 -6
- package/dist/_dts/{usePanZoom-Pzf9f2jh.d.ts → usePanZoom-Bq73pwFe.d.ts} +4 -47
- package/dist/_dts/{useTrialState-uLlmaccn.d.ts → useTrialState-CP4jfuKl.d.ts} +1 -1
- package/dist/canvas/index.d.ts +6 -5
- package/dist/canvas/index.js +1 -1
- package/dist/chrome/index.d.ts +5 -4
- package/dist/chrome/index.js +7 -7
- package/dist/chunk-2RVCB2PT.js +3 -0
- package/dist/chunk-2RVCB2PT.js.map +1 -0
- package/dist/{chunk-UN4U4P63.js → chunk-3FI7Y4R4.js} +9 -9
- package/dist/chunk-3FI7Y4R4.js.map +1 -0
- package/dist/{chunk-7PFH3CNM.js → chunk-AWH3SRPW.js} +31 -5
- package/dist/chunk-AWH3SRPW.js.map +1 -0
- package/dist/{chunk-ZDHY47EV.js → chunk-BVL7Y574.js} +25 -9
- package/dist/chunk-BVL7Y574.js.map +1 -0
- package/dist/{chunk-IHUVQ3TX.js → chunk-H5VIV7MQ.js} +31 -27
- package/dist/chunk-H5VIV7MQ.js.map +1 -0
- package/dist/chunk-HFRE4QQV.js +400 -0
- package/dist/chunk-HFRE4QQV.js.map +1 -0
- package/dist/{chunk-IXTQI7JX.js → chunk-MOLIUNVH.js} +4 -4
- package/dist/{chunk-IXTQI7JX.js.map → chunk-MOLIUNVH.js.map} +1 -1
- package/dist/{chunk-CEIUBPPW.js → chunk-TT5C5S6R.js} +3 -86
- package/dist/chunk-TT5C5S6R.js.map +1 -0
- package/dist/chunk-USO6TERP.js +90 -0
- package/dist/chunk-USO6TERP.js.map +1 -0
- package/dist/{chunk-PHK4W562.js → chunk-VQ47KWD5.js} +3 -3
- package/dist/{chunk-PHK4W562.js.map → chunk-VQ47KWD5.js.map} +1 -1
- package/dist/chunk-WUJXJC5O.js +14568 -0
- package/dist/chunk-WUJXJC5O.js.map +1 -0
- package/dist/{chunk-EHGVROVV.js → chunk-ZORVNOKX.js} +6 -25
- package/dist/chunk-ZORVNOKX.js.map +1 -0
- package/dist/config/index.d.ts +53 -11
- package/dist/config/index.js +5 -5
- package/dist/controls/index.d.ts +6 -3
- package/dist/controls/index.js +4 -4
- package/dist/dragdrop/index.d.ts +4 -3
- package/dist/index.d.ts +139 -114
- package/dist/index.js +470 -479
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +6 -35
- package/dist/layers/index.js +2 -3
- package/dist/loupe/index.d.ts +6 -5
- package/dist/passthrough/weasel-ui.d.ts +421 -215
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.d.ts +2 -13
- package/dist/primitives/index.js +4 -4
- package/dist/state/index.d.ts +6 -5
- package/dist/state/index.js +5 -5
- package/dist/styles.css +410 -190
- package/dist/undo/index.d.ts +4 -3
- package/package.json +9 -13
- package/src/annotations/frac.test.ts +7 -0
- package/src/annotations/paint.test.ts +21 -0
- package/src/annotations/paint.ts +12 -0
- package/src/annotations/store.test.ts +31 -0
- package/src/annotations/types.ts +3 -2
- package/src/canvas/Stage.test.tsx +18 -0
- package/src/canvas/index.ts +0 -9
- package/src/chrome/labTypes.ts +1 -1
- package/src/chrome/regions/PaletteRegion.test.tsx +18 -0
- package/src/chrome/regions/PaletteRegion.tsx +5 -4
- package/src/chrome/regions/SidebarRegion.less +197 -0
- package/src/chrome/regions/SidebarRegion.test.tsx +20 -0
- package/src/chrome/regions/SidebarRegion.tsx +7 -2
- package/src/chrome/regions/TitleBarRegion.tsx +1 -1
- package/src/chrome/types.ts +13 -4
- package/src/config/autoConfig.ts +2 -2
- package/src/config/builder.ts +23 -5
- package/src/config/entry.test.ts +8 -0
- package/src/config/index.ts +2 -0
- package/src/config/rules.ts +2 -0
- package/src/config/sectionTree.test.ts +146 -0
- package/src/config/sectionTree.ts +124 -0
- package/src/config/types.ts +4 -4
- package/src/controls/AutoControls.stories.tsx +6 -6
- package/src/controls/ControlPanel.less +27 -0
- package/src/controls/ControlPanel.stories.tsx +95 -1
- package/src/controls/ControlPanel.test.tsx +82 -112
- package/src/controls/ControlPanel.tsx +159 -396
- package/src/controls/inDialog.test.tsx +82 -0
- package/src/controls/inDialog.tsx +46 -0
- package/src/controls/index.ts +1 -0
- package/src/index.test.ts +0 -2
- package/src/index.ts +14 -35
- package/src/instrument/types.ts +5 -2
- package/src/lab/Lab.nebula.test.tsx +31 -0
- package/src/lab/Lab.tsx +57 -18
- package/src/lab/LabBoundary.test.tsx +66 -0
- package/src/lab/LabBoundary.tsx +56 -0
- package/src/lab/LabHeader.tsx +4 -23
- package/src/lab/LabRoot.tsx +2 -3
- package/src/lab/LabShell.less +0 -1
- package/src/lab/LabShell.stories.tsx +15 -0
- package/src/lab/LabShell.test.tsx +18 -0
- package/src/lab/LabShell.tsx +14 -1
- package/src/lab/LabSwitcher.less +1 -1
- package/src/lab/LabSwitcher.test.tsx +41 -0
- package/src/lab/LabSwitcher.tsx +30 -8
- package/src/lab/index.ts +1 -0
- package/src/layers/AGENTS.md +18 -74
- package/src/layers/index.ts +4 -2
- package/src/passthrough/weasel-ui.ts +25 -10
- package/src/primitives/FpsMeter.less +6 -1
- package/src/primitives/FpsMeter.test.tsx +12 -4
- package/src/primitives/FpsMeter.tsx +6 -1
- package/src/primitives/JobProgress.stories.tsx +1 -1
- package/src/primitives/StatusBar.less +6 -0
- package/src/primitives/index.ts +0 -2
- package/src/specimen/Specimen.tsx +42 -56
- package/src/state/types.ts +8 -2
- package/src/state/view.test.ts +4 -4
- package/src/styles.less +4 -2
- package/src/theme/Interstellar.stories.tsx +8 -9
- package/src/theme/base.less +3 -20
- package/src/theme/interstellar.test.ts +6 -3
- package/src/theme/interstellar.theme.json +0 -56
- package/src/theme/interstellar.ts +2 -2
- package/src/trial/Trial.canvas.test.tsx +9 -5
- package/src/trial/Trial.config.test.tsx +1 -1
- package/src/trial/Trial.tsx +30 -8
- package/dist/_dts/index-BNdz-tNd.d.ts +0 -423
- package/dist/chunk-7PFH3CNM.js.map +0 -1
- package/dist/chunk-CEIUBPPW.js.map +0 -1
- package/dist/chunk-EH7UEQV7.js +0 -590
- package/dist/chunk-EH7UEQV7.js.map +0 -1
- package/dist/chunk-EHGVROVV.js.map +0 -1
- package/dist/chunk-IHUVQ3TX.js.map +0 -1
- package/dist/chunk-MDVTK5U3.js +0 -170
- package/dist/chunk-MDVTK5U3.js.map +0 -1
- package/dist/chunk-PJ7WEQBJ.js +0 -18
- package/dist/chunk-PJ7WEQBJ.js.map +0 -1
- package/dist/chunk-T3EC2DXD.js +0 -12628
- package/dist/chunk-T3EC2DXD.js.map +0 -1
- package/dist/chunk-UN4U4P63.js.map +0 -1
- package/dist/chunk-ZDHY47EV.js.map +0 -1
- package/dist/ui/layers/index.d.ts +0 -66
- package/dist/ui/layers/index.js +0 -3
- package/dist/ui/layers/index.js.map +0 -1
- package/src/canvas/useOrbit.test.ts +0 -167
- package/src/canvas/useOrbit.ts +0 -156
- package/src/lab/useSystemMode.ts +0 -30
- package/src/layers/LayerList.less +0 -93
- package/src/layers/LayerList.stories.tsx +0 -66
- package/src/layers/LayerList.test.tsx +0 -232
- package/src/layers/LayerList.tsx +0 -268
- package/src/primitives/Sidebar.less +0 -112
- package/src/primitives/Sidebar.stories.tsx +0 -38
- package/src/primitives/Sidebar.test.tsx +0 -74
- package/src/primitives/Sidebar.tsx +0 -35
- package/src/ui/layers/index.ts +0 -5
package/dist/undo/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { au as UndoCapability } from '../_dts/frac-DTRq7Xpb.js';
|
|
2
2
|
import 'react';
|
|
3
|
-
import '../_dts/
|
|
3
|
+
import '../_dts/PrefsRow.d-DFpPIeIC.js';
|
|
4
|
+
import '@weasel-js/theme';
|
|
4
5
|
import '@weasel-js/core';
|
|
5
|
-
import '../_dts/
|
|
6
|
+
import '../_dts/types-B7y8hX1D.js';
|
|
6
7
|
import '../_dts/types-DJ79Tg5J.js';
|
|
7
8
|
|
|
8
9
|
/** A subscriber to a named event. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/labkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "React widgets for building self-contained interactive lab pages",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "orochi235",
|
|
@@ -82,10 +82,6 @@
|
|
|
82
82
|
"types": "./dist/passthrough/weasel-canvas.d.ts",
|
|
83
83
|
"import": "./dist/passthrough/weasel-canvas.js"
|
|
84
84
|
},
|
|
85
|
-
"./ui/layers": {
|
|
86
|
-
"types": "./dist/ui/layers/index.d.ts",
|
|
87
|
-
"import": "./dist/ui/layers/index.js"
|
|
88
|
-
},
|
|
89
85
|
"./styles.css": "./dist/styles.css",
|
|
90
86
|
"./loupe": {
|
|
91
87
|
"types": "./dist/loupe/index.d.ts",
|
|
@@ -115,17 +111,17 @@
|
|
|
115
111
|
"dev:3d": "LABKIT_EXAMPLE=3d-lab vite"
|
|
116
112
|
},
|
|
117
113
|
"peerDependencies": {
|
|
118
|
-
"@weasel-js/core": "1.
|
|
119
|
-
"@weasel-js/theme": "1.
|
|
114
|
+
"@weasel-js/core": "1.6.0",
|
|
115
|
+
"@weasel-js/theme": "1.6.0",
|
|
120
116
|
"react": "^19.0.0",
|
|
121
117
|
"react-dom": "^19.0.0"
|
|
122
118
|
},
|
|
123
119
|
"dependencies": {
|
|
124
|
-
"@weasel-js/geom": "1.
|
|
125
|
-
"@weasel-js/kernel3d": "1.
|
|
126
|
-
"@weasel-js/loupe": "1.
|
|
127
|
-
"@weasel-js/svg": "1.
|
|
128
|
-
"@weasel-js/ui": "1.
|
|
120
|
+
"@weasel-js/geom": "1.6.0",
|
|
121
|
+
"@weasel-js/kernel3d": "1.6.0",
|
|
122
|
+
"@weasel-js/loupe": "1.6.0",
|
|
123
|
+
"@weasel-js/svg": "1.6.0",
|
|
124
|
+
"@weasel-js/ui": "1.6.0",
|
|
129
125
|
"earcut": "2.2.4",
|
|
130
126
|
"polygon-clipping": "^0.15.7",
|
|
131
127
|
"react-aria-components": "^1.5.0",
|
|
@@ -136,7 +132,7 @@
|
|
|
136
132
|
"@biomejs/biome": "^2.4.12",
|
|
137
133
|
"@rollup/plugin-alias": "^6.0.0",
|
|
138
134
|
"@testing-library/jest-dom": "^6.6.0",
|
|
139
|
-
"@weasel-js/modes": "1.
|
|
135
|
+
"@weasel-js/modes": "1.6.0",
|
|
140
136
|
"less": "^4.2.0",
|
|
141
137
|
"rollup-plugin-dts": "^6.4.1",
|
|
142
138
|
"tsx": "^4.19.0",
|
|
@@ -51,6 +51,13 @@ describe('fraction hit geometry', () => {
|
|
|
51
51
|
expect(fracContains(box, { x: 0.18, y: 0.3 }, 0.05)).toBe(true);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
it('reaches a zero-size box through the tolerance alone', () => {
|
|
55
|
+
const dot: FracRect = { x: 0.25, y: 0.5, w: 0, h: 0 };
|
|
56
|
+
expect(fracContains(dot, { x: 0.25, y: 0.5 })).toBe(true);
|
|
57
|
+
expect(fracContains(dot, { x: 0.26, y: 0.51 })).toBe(false);
|
|
58
|
+
expect(fracContains(dot, { x: 0.26, y: 0.51 }, 0.02)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
54
61
|
it('encloses only a box that lies wholly inside', () => {
|
|
55
62
|
expect(fracEncloses({ x: 0, y: 0, w: 1, h: 1 }, box)).toBe(true);
|
|
56
63
|
// Overlapping is not containing: a marquee takes what it encloses.
|
|
@@ -99,6 +99,20 @@ describe('markCommands', () => {
|
|
|
99
99
|
it('draws an untitled text mark as nothing rather than an empty run', () => {
|
|
100
100
|
expect(markCommands(mark('text'), CONTENT)).toEqual([]);
|
|
101
101
|
});
|
|
102
|
+
|
|
103
|
+
it('draws a point as a small unfilled ring centered on its one stored point', () => {
|
|
104
|
+
const cmds = markCommands(mark('point', { points: [{ x: 0.25, y: 0.5 }] }), CONTENT);
|
|
105
|
+
expect(cmds).toHaveLength(1);
|
|
106
|
+
expect((cmds[0] as { fill?: unknown }).fill).toBeUndefined();
|
|
107
|
+
const pts = anchors(cmds);
|
|
108
|
+
const xs = pts.filter((_, i) => i % 2 === 0);
|
|
109
|
+
const ys = pts.filter((_, i) => i % 2 === 1);
|
|
110
|
+
// Centered on (50, 50) in world, radius 4.
|
|
111
|
+
expect(Math.min(...xs)).toBeCloseTo(46, 4);
|
|
112
|
+
expect(Math.max(...xs)).toBeCloseTo(54, 4);
|
|
113
|
+
expect(Math.min(...ys)).toBeCloseTo(46, 4);
|
|
114
|
+
expect(Math.max(...ys)).toBeCloseTo(54, 4);
|
|
115
|
+
});
|
|
102
116
|
});
|
|
103
117
|
|
|
104
118
|
describe('markCommands styling', () => {
|
|
@@ -117,6 +131,13 @@ describe('markCommands styling', () => {
|
|
|
117
131
|
expect((stale as { stroke?: { dash?: number[] } }).stroke?.dash).toEqual([6, 4]);
|
|
118
132
|
});
|
|
119
133
|
|
|
134
|
+
it('dashes a stale point ring like any other mark', () => {
|
|
135
|
+
const [ring] = markCommands(mark('point', { points: [{ x: 0.25, y: 0.5 }] }), CONTENT, {
|
|
136
|
+
stale: true,
|
|
137
|
+
});
|
|
138
|
+
expect((ring as { stroke?: { dash?: number[] } }).stroke?.dash).toEqual([6, 4]);
|
|
139
|
+
});
|
|
140
|
+
|
|
120
141
|
it("colors a text mark's glyphs too, not only the outlines", () => {
|
|
121
142
|
const [cmd] = markCommands(mark('text', { title: 'x' }), CONTENT, { color: '#30a46c' });
|
|
122
143
|
const runs = (cmd as { runs: { fill?: { color?: string } }[] }).runs;
|
package/src/annotations/paint.ts
CHANGED
|
@@ -17,6 +17,7 @@ const MARK_COLOR = '#e5484d';
|
|
|
17
17
|
* marker geometry can be resolved here, which needs a resolved width. */
|
|
18
18
|
const MARK_WIDTH = 2;
|
|
19
19
|
const TEXT_SIZE = 14;
|
|
20
|
+
const POINT_RADIUS = 4;
|
|
20
21
|
|
|
21
22
|
/** The subset of a mark's scene node this needs: where it is, and what it is. */
|
|
22
23
|
export interface PaintableMark {
|
|
@@ -108,6 +109,17 @@ export function markCommands(
|
|
|
108
109
|
}
|
|
109
110
|
case 'stroke':
|
|
110
111
|
return [{ kind: 'path', path: polyline(vertices(m, content)), stroke }];
|
|
112
|
+
case 'point': {
|
|
113
|
+
const stored = m.data.points?.[0];
|
|
114
|
+
const c = stored ? toWorld(stored, content) : { x: m.pose.x, y: m.pose.y };
|
|
115
|
+
const ring = {
|
|
116
|
+
x: c.x - POINT_RADIUS,
|
|
117
|
+
y: c.y - POINT_RADIUS,
|
|
118
|
+
width: 2 * POINT_RADIUS,
|
|
119
|
+
height: 2 * POINT_RADIUS,
|
|
120
|
+
};
|
|
121
|
+
return [{ kind: 'path', path: ellipsePath(ring), stroke }];
|
|
122
|
+
}
|
|
111
123
|
case 'text': {
|
|
112
124
|
const text = m.data.title;
|
|
113
125
|
if (!text) return [];
|
|
@@ -245,6 +245,37 @@ describe('the annotation store', () => {
|
|
|
245
245
|
expect(revived.query()).toHaveLength(1);
|
|
246
246
|
});
|
|
247
247
|
|
|
248
|
+
it('round-trips a point mark with its one stored point and zero-size bounds', () => {
|
|
249
|
+
const store = makeStore();
|
|
250
|
+
const id = store.add({
|
|
251
|
+
target: 'naive',
|
|
252
|
+
kind: 'point',
|
|
253
|
+
frac: { x: 0.25, y: 0.5, w: 0, h: 0 },
|
|
254
|
+
points: [{ x: 0.25, y: 0.5 }],
|
|
255
|
+
});
|
|
256
|
+
expect(store.get(id)).toMatchObject({
|
|
257
|
+
kind: 'point',
|
|
258
|
+
frac: { x: 0.25, y: 0.5, w: 0, h: 0 },
|
|
259
|
+
points: [{ x: 0.25, y: 0.5 }],
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const revived = annotationsFromJSON(JSON.parse(JSON.stringify(store.toJSON())), () => TARGETS);
|
|
263
|
+
expect(revived.get(id)).toEqual(store.get(id));
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('hit-tests a point mark through the tolerance, since its bounds have no area', () => {
|
|
267
|
+
const store = makeStore();
|
|
268
|
+
const id = store.add({
|
|
269
|
+
target: 'naive',
|
|
270
|
+
kind: 'point',
|
|
271
|
+
frac: { x: 0.25, y: 0.5, w: 0, h: 0 },
|
|
272
|
+
points: [{ x: 0.25, y: 0.5 }],
|
|
273
|
+
});
|
|
274
|
+
expect(store.hitTest('naive', { x: 0.26, y: 0.51 })).toHaveLength(0);
|
|
275
|
+
expect(store.hitTest('naive', { x: 0.26, y: 0.51 }, 0.02).map((a) => a.id)).toEqual([id]);
|
|
276
|
+
expect(store.hitTest('naive', { x: 0.3, y: 0.51 }, 0.02)).toHaveLength(0);
|
|
277
|
+
});
|
|
278
|
+
|
|
248
279
|
it('reads a meaning passed as a thunk at each capture', async () => {
|
|
249
280
|
let color = '#111111';
|
|
250
281
|
const store = annotationsFromJSON(null, () => TARGETS, {
|
package/src/annotations/types.ts
CHANGED
|
@@ -21,8 +21,9 @@ export interface FracRect {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/** What shape a mark is. The kinds map onto weasel's own tools; an arrow is a
|
|
24
|
-
* line carrying an end marker, not a separate geometry.
|
|
25
|
-
|
|
24
|
+
* line carrying an end marker, not a separate geometry. A point holds one
|
|
25
|
+
* entry in `points` and zero-size bounds at that position. */
|
|
26
|
+
export type AnnotationKind = 'stroke' | 'line' | 'arrow' | 'rect' | 'ellipse' | 'text' | 'point';
|
|
26
27
|
|
|
27
28
|
/** One selectable status in an instrument's meaning tier. */
|
|
28
29
|
export interface AnnotationStatus {
|
|
@@ -183,4 +183,22 @@ describe('an instrument that declares a stage', () => {
|
|
|
183
183
|
render(<Lab instruments={[plain]} defaultInstrument="Plain" />);
|
|
184
184
|
expect(screen.queryByRole('button', { name: 'Zoom in' })).toBeNull();
|
|
185
185
|
});
|
|
186
|
+
|
|
187
|
+
it('draws its overlay on the stage, outside the camera', () => {
|
|
188
|
+
const overlaid = defineInstrument<{ n: number }, Record<string, never>>({
|
|
189
|
+
name: 'Overlaid',
|
|
190
|
+
defaultConfig: () => ({}),
|
|
191
|
+
initialState: () => ({ n: 7 }),
|
|
192
|
+
render: () => <div data-testid="art" />,
|
|
193
|
+
stage: {
|
|
194
|
+
size: { width: 240, height: 160 },
|
|
195
|
+
overlay: ({ state }) => <div data-testid="legend">{state.n}</div>,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
const { container } = render(<Lab instruments={[overlaid]} defaultInstrument="Overlaid" />);
|
|
199
|
+
const legend = screen.getByTestId('legend');
|
|
200
|
+
expect(legend).toHaveTextContent('7');
|
|
201
|
+
expect(container.querySelector('.lk-stage')).toContainElement(legend);
|
|
202
|
+
expect(content(container)).not.toContainElement(legend);
|
|
203
|
+
});
|
|
186
204
|
});
|
package/src/canvas/index.ts
CHANGED
|
@@ -10,15 +10,6 @@ export { screenToWorld, worldToScreen } from './canvasCoords';
|
|
|
10
10
|
export type { StageProps } from './Stage';
|
|
11
11
|
export { fitStage, Stage } from './Stage';
|
|
12
12
|
export type { CanvasLayerDescriptor } from './useLayerScheduler';
|
|
13
|
-
export type { OrbitHandlers, OrbitView, UseOrbitOptions, Vec3 } from './useOrbit';
|
|
14
|
-
export {
|
|
15
|
-
clampPitch,
|
|
16
|
-
orbitAfterDrag,
|
|
17
|
-
orbitAfterWheel,
|
|
18
|
-
PITCH_LIMIT,
|
|
19
|
-
useOrbit,
|
|
20
|
-
wrapYaw,
|
|
21
|
-
} from './useOrbit';
|
|
22
13
|
export type { PanZoomHandlers, UsePanZoomOptions } from './usePanZoom';
|
|
23
14
|
export { usePanZoom } from './usePanZoom';
|
|
24
15
|
export type { ViewportSize, WorldFrame, WorldSpec } from './worldSpec';
|
package/src/chrome/labTypes.ts
CHANGED
|
@@ -40,7 +40,7 @@ export type LabContribution<TCtx = LabChromeContext> =
|
|
|
40
40
|
item: ToolbarItem<TCtx>;
|
|
41
41
|
render?: never;
|
|
42
42
|
})
|
|
43
|
-
| (ContributionBase & { region: 'palette'; item: ToolItem
|
|
43
|
+
| (ContributionBase & { region: 'palette'; item: ToolItem<TCtx>; render?: never })
|
|
44
44
|
| (ContributionBase & { region: 'sidebar' | 'aside'; item: SidebarSection; render?: never })
|
|
45
45
|
| (ContributionBase & { region: 'footer'; item: StatusReadout; render?: never })
|
|
46
46
|
| (ContributionBase & {
|
|
@@ -36,6 +36,24 @@ describe('PaletteRegion', () => {
|
|
|
36
36
|
expect(setActiveTool).toHaveBeenCalledWith('brush');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it('runs a command tool instead of selecting it, and never marks it current', () => {
|
|
40
|
+
const onActivate = vi.fn();
|
|
41
|
+
const setActiveTool = vi.fn();
|
|
42
|
+
const ctx = ctxWith('info', setActiveTool);
|
|
43
|
+
const info: RegionContribution<ToolSlotContext> = {
|
|
44
|
+
id: 'info',
|
|
45
|
+
region: 'palette',
|
|
46
|
+
item: { icon: Glyph, label: 'Info', onActivate },
|
|
47
|
+
};
|
|
48
|
+
render(<PaletteRegion contributions={[info]} ctx={ctx} />);
|
|
49
|
+
const button = screen.getByRole('button', { name: 'Info' });
|
|
50
|
+
button.click();
|
|
51
|
+
expect(onActivate).toHaveBeenCalledWith(ctx);
|
|
52
|
+
expect(setActiveTool).not.toHaveBeenCalled();
|
|
53
|
+
// The slot names this id, and a command still must not read as held down.
|
|
54
|
+
expect(button).not.toHaveAttribute('aria-current');
|
|
55
|
+
});
|
|
56
|
+
|
|
39
57
|
it('puts exactly one tool in the tab order', () => {
|
|
40
58
|
render(<PaletteRegion contributions={[tool('brush'), tool('eraser')]} ctx={ctxWith(null)} />);
|
|
41
59
|
expect(screen.getByRole('button', { name: 'brush' })).toHaveAttribute('tabindex', '0');
|
|
@@ -12,7 +12,8 @@ export interface PaletteRegionProps<TCtx extends ToolSlotContext = TrialChromeCo
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/** A tool strip: a trial's, or the lab's. Selection lives in whichever tool
|
|
15
|
-
* slot the context carries; this region only reflects it.
|
|
15
|
+
* slot the context carries; this region only reflects it. An item carrying
|
|
16
|
+
* `onActivate` is a command instead — it presses, and never latches. */
|
|
16
17
|
export function PaletteRegion<TCtx extends ToolSlotContext = TrialChromeContext>({
|
|
17
18
|
contributions,
|
|
18
19
|
ctx,
|
|
@@ -33,16 +34,16 @@ export function PaletteRegion<TCtx extends ToolSlotContext = TrialChromeContext>
|
|
|
33
34
|
{contributions.map((c) => {
|
|
34
35
|
if (c.render) return <span key={c.id}>{c.render(ctx)}</span>;
|
|
35
36
|
if (c.region !== region || !c.item) return null;
|
|
36
|
-
const { icon: Icon, label, shortcut, disabled } = c.item as ToolItem
|
|
37
|
+
const { icon: Icon, label, shortcut, disabled, onActivate } = c.item as ToolItem<TCtx>;
|
|
37
38
|
return (
|
|
38
39
|
<ToolButton
|
|
39
40
|
key={c.id}
|
|
40
41
|
icon={<Icon size={16} />}
|
|
41
42
|
label={label}
|
|
42
43
|
shortcut={shortcut}
|
|
43
|
-
active={ctx.activeToolId === c.id}
|
|
44
|
+
active={onActivate ? false : ctx.activeToolId === c.id}
|
|
44
45
|
disabled={disabled}
|
|
45
|
-
onClick={() => ctx.setActiveTool(c.id)}
|
|
46
|
+
onClick={onActivate ? () => onActivate(ctx) : () => ctx.setActiveTool(c.id)}
|
|
46
47
|
/>
|
|
47
48
|
);
|
|
48
49
|
})}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
.lk-sidebar-section {
|
|
2
|
+
background: var(--_s-fill);
|
|
3
|
+
border-bottom: var(--wzl-border-w) solid var(--_s-border-color);
|
|
4
|
+
|
|
5
|
+
// The last section runs to the bottom of its pane. Stopped at its content,
|
|
6
|
+
// its rule and the bare pane under it read as a second, empty zone.
|
|
7
|
+
&:last-child {
|
|
8
|
+
flex-grow: 1;
|
|
9
|
+
border-bottom: 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* BEGIN GENERATED stance rules: sidebar-section — `npm run gen:stances`, do not edit. */
|
|
14
|
+
|
|
15
|
+
.lk-sidebar-section {
|
|
16
|
+
--wzl-tone: initial;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.lk-sidebar-section {
|
|
20
|
+
--_s-surface: transparent;
|
|
21
|
+
--_s-border-color: var(--wzl-line-subtle);
|
|
22
|
+
--_s-tone-mix: var(--wzl-panel-tone-mix);
|
|
23
|
+
--_s-tone: var(--wzl-tone, var(--_s-surface));
|
|
24
|
+
--_s-accent: var(--wzl-tone);
|
|
25
|
+
--_s-title-font: var(--wzl-font-display);
|
|
26
|
+
--_s-title-size: var(--wzl-font-size-sm);
|
|
27
|
+
--_s-title-case: uppercase;
|
|
28
|
+
--_s-title-tracking: 0.08em;
|
|
29
|
+
--_s-title-color: var(--wzl-fg-muted);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.lk-sidebar-section[data-stance='scope'] {
|
|
33
|
+
--_s-surface: var(--wzl-stance-scope-surface, transparent);
|
|
34
|
+
--_s-border-color: var(--wzl-stance-scope-border-color, var(--wzl-line-subtle));
|
|
35
|
+
--_s-tone-mix: var(--wzl-stance-scope-tone-mix, var(--wzl-panel-tone-mix));
|
|
36
|
+
--_s-tone: var(--wzl-tone, var(--wzl-stance-scope-tone, var(--_s-surface)));
|
|
37
|
+
--_s-accent: var(--wzl-stance-scope-accent, var(--wzl-tone));
|
|
38
|
+
--_s-title-font: var(--wzl-stance-scope-title-font, var(--wzl-font-display));
|
|
39
|
+
--_s-title-size: var(--wzl-stance-scope-title-size, var(--wzl-font-size-sm));
|
|
40
|
+
--_s-title-case: var(--wzl-stance-scope-title-case, uppercase);
|
|
41
|
+
--_s-title-tracking: var(--wzl-stance-scope-title-tracking, 0.08em);
|
|
42
|
+
--_s-title-color: var(--wzl-stance-scope-title-color, var(--wzl-fg-muted));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.lk-sidebar-section[data-stance='aside'] {
|
|
46
|
+
--_s-surface: var(--wzl-stance-aside-surface, transparent);
|
|
47
|
+
--_s-border-color: var(--wzl-stance-aside-border-color, var(--wzl-line-subtle));
|
|
48
|
+
--_s-tone-mix: var(--wzl-stance-aside-tone-mix, var(--wzl-panel-tone-mix));
|
|
49
|
+
--_s-tone: var(--wzl-tone, var(--wzl-stance-aside-tone, var(--_s-surface)));
|
|
50
|
+
--_s-accent: var(--wzl-stance-aside-accent, var(--wzl-tone));
|
|
51
|
+
--_s-title-font: var(--wzl-stance-aside-title-font, var(--wzl-font-display));
|
|
52
|
+
--_s-title-size: var(--wzl-stance-aside-title-size, var(--wzl-font-size-sm));
|
|
53
|
+
--_s-title-case: var(--wzl-stance-aside-title-case, uppercase);
|
|
54
|
+
--_s-title-tracking: var(--wzl-stance-aside-title-tracking, 0.08em);
|
|
55
|
+
--_s-title-color: var(--wzl-stance-aside-title-color, var(--wzl-fg-muted));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.lk-sidebar-section[data-stance='advanced'] {
|
|
59
|
+
--_s-surface: var(--wzl-stance-advanced-surface, transparent);
|
|
60
|
+
--_s-border-color: var(--wzl-stance-advanced-border-color, var(--wzl-line-subtle));
|
|
61
|
+
--_s-tone-mix: var(--wzl-stance-advanced-tone-mix, var(--wzl-panel-tone-mix));
|
|
62
|
+
--_s-tone: var(--wzl-tone, var(--wzl-stance-advanced-tone, var(--_s-surface)));
|
|
63
|
+
--_s-accent: var(--wzl-stance-advanced-accent, var(--wzl-tone));
|
|
64
|
+
--_s-title-font: var(--wzl-stance-advanced-title-font, var(--wzl-font-display));
|
|
65
|
+
--_s-title-size: var(--wzl-stance-advanced-title-size, var(--wzl-font-size-sm));
|
|
66
|
+
--_s-title-case: var(--wzl-stance-advanced-title-case, uppercase);
|
|
67
|
+
--_s-title-tracking: var(--wzl-stance-advanced-title-tracking, 0.08em);
|
|
68
|
+
--_s-title-color: var(--wzl-stance-advanced-title-color, var(--wzl-fg-muted));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.lk-sidebar-section[data-stance='debug'] {
|
|
72
|
+
--_s-surface: var(--wzl-stance-debug-surface, transparent);
|
|
73
|
+
--_s-border-color: var(--wzl-stance-debug-border-color, var(--wzl-line-subtle));
|
|
74
|
+
--_s-tone-mix: var(--wzl-stance-debug-tone-mix, var(--wzl-panel-tone-mix));
|
|
75
|
+
--_s-tone: var(--wzl-tone, var(--wzl-stance-debug-tone, var(--_s-surface)));
|
|
76
|
+
--_s-accent: var(--wzl-stance-debug-accent, var(--wzl-tone));
|
|
77
|
+
--_s-title-font: var(--wzl-stance-debug-title-font, var(--wzl-font-display));
|
|
78
|
+
--_s-title-size: var(--wzl-stance-debug-title-size, var(--wzl-font-size-sm));
|
|
79
|
+
--_s-title-case: var(--wzl-stance-debug-title-case, uppercase);
|
|
80
|
+
--_s-title-tracking: var(--wzl-stance-debug-title-tracking, 0.08em);
|
|
81
|
+
--_s-title-color: var(--wzl-stance-debug-title-color, var(--wzl-fg-muted));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.lk-sidebar-section[data-stance='danger'] {
|
|
85
|
+
--_s-surface: var(--wzl-stance-danger-surface, transparent);
|
|
86
|
+
--_s-border-color: var(--wzl-stance-danger-border-color, var(--wzl-line-subtle));
|
|
87
|
+
--_s-tone-mix: var(--wzl-stance-danger-tone-mix, var(--wzl-panel-tone-mix));
|
|
88
|
+
--_s-tone: var(--wzl-tone, var(--wzl-stance-danger-tone, var(--_s-surface)));
|
|
89
|
+
--_s-accent: var(--wzl-stance-danger-accent, var(--wzl-tone));
|
|
90
|
+
--_s-title-font: var(--wzl-stance-danger-title-font, var(--wzl-font-display));
|
|
91
|
+
--_s-title-size: var(--wzl-stance-danger-title-size, var(--wzl-font-size-sm));
|
|
92
|
+
--_s-title-case: var(--wzl-stance-danger-title-case, uppercase);
|
|
93
|
+
--_s-title-tracking: var(--wzl-stance-danger-title-tracking, 0.08em);
|
|
94
|
+
--_s-title-color: var(--wzl-stance-danger-title-color, var(--wzl-fg-muted));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.lk-sidebar-section[data-stance='notice'] {
|
|
98
|
+
--_s-surface: var(--wzl-stance-notice-surface, transparent);
|
|
99
|
+
--_s-border-color: var(--wzl-stance-notice-border-color, var(--wzl-line-subtle));
|
|
100
|
+
--_s-tone-mix: var(--wzl-stance-notice-tone-mix, var(--wzl-panel-tone-mix));
|
|
101
|
+
--_s-tone: var(--wzl-tone, var(--wzl-stance-notice-tone, var(--_s-surface)));
|
|
102
|
+
--_s-accent: var(--wzl-stance-notice-accent, var(--wzl-tone));
|
|
103
|
+
--_s-title-font: var(--wzl-stance-notice-title-font, var(--wzl-font-display));
|
|
104
|
+
--_s-title-size: var(--wzl-stance-notice-title-size, var(--wzl-font-size-sm));
|
|
105
|
+
--_s-title-case: var(--wzl-stance-notice-title-case, uppercase);
|
|
106
|
+
--_s-title-tracking: var(--wzl-stance-notice-title-tracking, 0.08em);
|
|
107
|
+
--_s-title-color: var(--wzl-stance-notice-title-color, var(--wzl-fg-muted));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.lk-sidebar-section[data-stance='important'] {
|
|
111
|
+
--_s-surface: var(--wzl-stance-important-surface, transparent);
|
|
112
|
+
--_s-border-color: var(--wzl-stance-important-border-color, var(--wzl-line-subtle));
|
|
113
|
+
--_s-tone-mix: var(--wzl-stance-important-tone-mix, var(--wzl-panel-tone-mix));
|
|
114
|
+
--_s-tone: var(--wzl-tone, var(--wzl-stance-important-tone, var(--_s-surface)));
|
|
115
|
+
--_s-accent: var(--wzl-stance-important-accent, var(--wzl-tone));
|
|
116
|
+
--_s-title-font: var(--wzl-stance-important-title-font, var(--wzl-font-display));
|
|
117
|
+
--_s-title-size: var(--wzl-stance-important-title-size, var(--wzl-font-size-sm));
|
|
118
|
+
--_s-title-case: var(--wzl-stance-important-title-case, uppercase);
|
|
119
|
+
--_s-title-tracking: var(--wzl-stance-important-title-tracking, 0.08em);
|
|
120
|
+
--_s-title-color: var(--wzl-stance-important-title-color, var(--wzl-fg-muted));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.lk-sidebar-section[data-stance='preview'] {
|
|
124
|
+
--_s-surface: var(--wzl-stance-preview-surface, transparent);
|
|
125
|
+
--_s-border-color: var(--wzl-stance-preview-border-color, var(--wzl-line-subtle));
|
|
126
|
+
--_s-tone-mix: var(--wzl-stance-preview-tone-mix, var(--wzl-panel-tone-mix));
|
|
127
|
+
--_s-tone: var(--wzl-tone, var(--wzl-stance-preview-tone, var(--_s-surface)));
|
|
128
|
+
--_s-accent: var(--wzl-stance-preview-accent, var(--wzl-tone));
|
|
129
|
+
--_s-title-font: var(--wzl-stance-preview-title-font, var(--wzl-font-display));
|
|
130
|
+
--_s-title-size: var(--wzl-stance-preview-title-size, var(--wzl-font-size-sm));
|
|
131
|
+
--_s-title-case: var(--wzl-stance-preview-title-case, uppercase);
|
|
132
|
+
--_s-title-tracking: var(--wzl-stance-preview-title-tracking, 0.08em);
|
|
133
|
+
--_s-title-color: var(--wzl-stance-preview-title-color, var(--wzl-fg-muted));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.lk-sidebar-section {
|
|
137
|
+
--_s-fill: var(--_s-surface);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.lk-sidebar-section[data-tone],
|
|
141
|
+
.lk-sidebar-section[data-stance] {
|
|
142
|
+
--_s-fill: color-mix(in oklab, var(--_s-tone) var(--_s-tone-mix), var(--_s-surface));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.lk-sidebar-section[data-tone] {
|
|
146
|
+
--wzl-accent: var(--_s-accent);
|
|
147
|
+
--wzl-accent-base: var(--_s-accent);
|
|
148
|
+
--wzl-accent-strong: color-mix(in oklab, var(--_s-accent) 70%, var(--wzl-fg));
|
|
149
|
+
--wzl-accent-soft: color-mix(in oklab, var(--_s-accent) 45%, var(--wzl-surface));
|
|
150
|
+
--wzl-accent-hover: var(--wzl-accent-strong);
|
|
151
|
+
--wzl-accent-fg: var(--wzl-accent-strong);
|
|
152
|
+
--wzl-focus-ring: var(--wzl-accent-strong);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* END GENERATED stance rules: sidebar-section */
|
|
156
|
+
|
|
157
|
+
.lk-sidebar-section__bar {
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.lk-sidebar-section__undock {
|
|
163
|
+
padding: 0 var(--wzl-space-xs);
|
|
164
|
+
border: 0;
|
|
165
|
+
background: transparent;
|
|
166
|
+
color: var(--wzl-fg-subtle);
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
|
|
169
|
+
&:hover {
|
|
170
|
+
color: var(--wzl-fg);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.lk-sidebar-section__head {
|
|
175
|
+
flex: 1;
|
|
176
|
+
width: 100%;
|
|
177
|
+
padding: var(--wzl-space-xs);
|
|
178
|
+
border: 0;
|
|
179
|
+
background: transparent;
|
|
180
|
+
color: var(--_s-title-color);
|
|
181
|
+
font-family: var(--_s-title-font);
|
|
182
|
+
font-size: var(--_s-title-size);
|
|
183
|
+
letter-spacing: var(--_s-title-tracking);
|
|
184
|
+
text-align: left;
|
|
185
|
+
text-transform: var(--_s-title-case);
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
|
|
188
|
+
&:hover {
|
|
189
|
+
color: var(--wzl-fg);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// The section body is the only thing that insets a panel — the panels
|
|
194
|
+
// themselves carry no padding, so this is the whole gutter.
|
|
195
|
+
.lk-sidebar-section__body {
|
|
196
|
+
padding: 0 var(--wzl-space-xs) var(--wzl-space-xs);
|
|
197
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import type { RegionContribution, TrialChromeContext } from '../types';
|
|
4
|
+
import { SidebarRegion } from './SidebarRegion';
|
|
5
|
+
|
|
6
|
+
const ctx = { collapsedSections: {}, setSectionCollapsed: () => {} } as unknown as TrialChromeContext;
|
|
7
|
+
|
|
8
|
+
describe('<SidebarRegion> stance and tone', () => {
|
|
9
|
+
it('puts a section’s stance and tone on the section', () => {
|
|
10
|
+
const contributions: RegionContribution<TrialChromeContext>[] = [
|
|
11
|
+
{ id: 'a', region: 'sidebar', item: { title: 'Plain', body: 'x' } },
|
|
12
|
+
{ id: 'b', region: 'sidebar', item: { title: 'Debug', body: 'y', stance: 'debug', tone: '#224a63' } },
|
|
13
|
+
];
|
|
14
|
+
const { container } = render(<SidebarRegion contributions={contributions} ctx={ctx} />);
|
|
15
|
+
const [plain, debug] = [...container.querySelectorAll<HTMLElement>('.lk-sidebar-section')];
|
|
16
|
+
expect(plain.hasAttribute('data-stance')).toBe(false);
|
|
17
|
+
expect(debug.dataset.stance).toBe('debug');
|
|
18
|
+
expect(debug.style.getPropertyValue('--wzl-tone')).toBe('#224a63');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type StanceProps, useStance } from '@weasel-js/ui';
|
|
1
2
|
import type { ReactNode } from 'react';
|
|
2
3
|
import type {
|
|
3
4
|
RegionContribution,
|
|
@@ -20,7 +21,9 @@ function Section({
|
|
|
20
21
|
onCollapsedChange,
|
|
21
22
|
onUndock,
|
|
22
23
|
children,
|
|
23
|
-
|
|
24
|
+
stance,
|
|
25
|
+
tone,
|
|
26
|
+
}: StanceProps & {
|
|
24
27
|
title: string;
|
|
25
28
|
collapsed: boolean;
|
|
26
29
|
onCollapsedChange: (collapsed: boolean) => void;
|
|
@@ -28,7 +31,7 @@ function Section({
|
|
|
28
31
|
children: ReactNode;
|
|
29
32
|
}) {
|
|
30
33
|
return (
|
|
31
|
-
<section className="lk-sidebar-section">
|
|
34
|
+
<section className="lk-sidebar-section" {...useStance({ stance, tone })}>
|
|
32
35
|
<div className="lk-sidebar-section__bar">
|
|
33
36
|
<button
|
|
34
37
|
type="button"
|
|
@@ -76,6 +79,8 @@ export function SidebarRegion<TCtx extends SidebarSlotContext = TrialChromeConte
|
|
|
76
79
|
<Section
|
|
77
80
|
key={c.id}
|
|
78
81
|
title={item.title}
|
|
82
|
+
stance={item.stance}
|
|
83
|
+
tone={item.tone}
|
|
79
84
|
collapsed={ctx.collapsedSections[c.id] ?? item.defaultCollapsed ?? false}
|
|
80
85
|
onCollapsedChange={(next) => ctx.setSectionCollapsed(c.id, next)}
|
|
81
86
|
onUndock={
|
package/src/chrome/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StanceProps } from '@weasel-js/ui';
|
|
1
2
|
import type { ComponentType, ReactNode } from 'react';
|
|
2
3
|
import type { ResolvedConfig } from '../config/types';
|
|
3
4
|
import type { ConfigField } from '../controls/types';
|
|
@@ -31,16 +32,24 @@ export interface ToolbarItem<TCtx = TrialChromeContext> {
|
|
|
31
32
|
onActivate: (ctx: TCtx) => void;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/** A
|
|
35
|
-
|
|
35
|
+
/** A tool in the palette region: a mode by default, a command when it carries
|
|
36
|
+
* an `onActivate`. `TCtx` is the chrome context that handler is handed — a
|
|
37
|
+
* trial's, or the lab's for a lab-level rail. */
|
|
38
|
+
export interface ToolItem<TCtx = TrialChromeContext> {
|
|
36
39
|
icon: IconComponent;
|
|
37
40
|
label: string;
|
|
41
|
+
/** Shown in the tooltip. Not bound here — the trial owns its keymap. */
|
|
38
42
|
shortcut?: string;
|
|
39
43
|
disabled?: boolean;
|
|
44
|
+
/** Runs on press instead of selecting. An item with one is a command: it
|
|
45
|
+
* never writes the tool slot, and never reads as the current tool. */
|
|
46
|
+
onActivate?: (ctx: TCtx) => void;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
|
-
/** A titled block in the sidebar.
|
|
43
|
-
|
|
49
|
+
/** A titled block in the sidebar. `stance` and `tone` work as on a
|
|
50
|
+
* `<PropertyPanel>`: what kind of content the section holds, and which of its
|
|
51
|
+
* peers it is. */
|
|
52
|
+
export interface SidebarSection extends StanceProps {
|
|
44
53
|
title: string;
|
|
45
54
|
/** Starts collapsed, until the trial remembers a fold of its own. */
|
|
46
55
|
defaultCollapsed?: boolean;
|
package/src/config/autoConfig.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';
|
|
2
2
|
import { isAuto } from './auto';
|
|
3
3
|
import { isRecord, withValueAtPath } from './path';
|
|
4
4
|
import type { ResolvedConfig } from './types';
|
|
@@ -15,7 +15,7 @@ function extra<T>(leaf: PrefLeaf, key: string): T | undefined {
|
|
|
15
15
|
function* leaves(group: PrefGroup, at = ''): Generator<[string, PrefLeaf]> {
|
|
16
16
|
for (const [key, child] of Object.entries(group.children)) {
|
|
17
17
|
const path = at === '' ? key : `${at}.${key}`;
|
|
18
|
-
if (
|
|
18
|
+
if ('kind' in child) yield [path, child];
|
|
19
19
|
else yield* leaves(child, path);
|
|
20
20
|
}
|
|
21
21
|
}
|