@weasel-js/labkit 1.5.2 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/dist/_dts/{CanvasStackContext-CMMo180l.d.ts → CanvasStackContext-Wo6OwIVM.d.ts} +1 -1
- package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
- package/dist/_dts/LayerList.d-BoELAMsM.d.ts +83 -0
- package/dist/_dts/{PrefsForm.d-DPy9I8It.d.ts → PrefsRow.d-DFpPIeIC.d.ts} +16 -1
- package/dist/_dts/PropertyField.d-D0ndfIIt.d.ts +404 -0
- package/dist/_dts/{Select.d-ZTiwzYyT.d.ts → Select.d-Cr8-zoup.d.ts} +105 -15
- package/dist/_dts/{usePanZoom-SSTZe410.d.ts → Stage-DWBBi8IU.d.ts} +80 -35
- package/dist/_dts/{index-DlKolEIZ.d.ts → index-CKyX0ME_.d.ts} +12 -13
- package/dist/_dts/{frac-B4hFJMiN.d.ts → types-B7yBTTF9.d.ts} +709 -651
- package/dist/_dts/{types-Db-3NNk1.d.ts → types-DTuViBvc.d.ts} +21 -2
- package/dist/_dts/{useTrialState-pnAE-UBD.d.ts → useTrialState-CMaV6UD4.d.ts} +2 -3
- package/dist/canvas/index.d.ts +41 -8
- package/dist/canvas/index.js +5 -3
- package/dist/chrome/index.d.ts +6 -6
- package/dist/chrome/index.js +9 -7
- package/dist/chunk-24W5VEJW.js +18 -0
- package/dist/chunk-24W5VEJW.js.map +1 -0
- package/dist/chunk-2RVCB2PT.js +3 -0
- package/dist/chunk-2RVCB2PT.js.map +1 -0
- package/dist/chunk-3RY6WL5T.js +38 -0
- package/dist/chunk-3RY6WL5T.js.map +1 -0
- package/dist/{chunk-CKUKF72R.js → chunk-4TT3PNPR.js} +68 -55
- package/dist/chunk-4TT3PNPR.js.map +1 -0
- package/dist/{chunk-KJCUERQF.js → chunk-6FLXL3I7.js} +9 -164
- package/dist/chunk-6FLXL3I7.js.map +1 -0
- package/dist/chunk-ASOK76ZH.js +431 -0
- package/dist/chunk-ASOK76ZH.js.map +1 -0
- package/dist/chunk-AWQG7FCC.js +19 -0
- package/dist/chunk-AWQG7FCC.js.map +1 -0
- package/dist/chunk-CGHF6RZV.js +300 -0
- package/dist/chunk-CGHF6RZV.js.map +1 -0
- package/dist/chunk-DWMSB4CA.js +228 -0
- package/dist/chunk-DWMSB4CA.js.map +1 -0
- package/dist/chunk-GHGO52SO.js +92 -0
- package/dist/chunk-GHGO52SO.js.map +1 -0
- package/dist/{chunk-LRI3MBGR.js → chunk-HKV7LRHY.js} +4 -4
- package/dist/{chunk-LRI3MBGR.js.map → chunk-HKV7LRHY.js.map} +1 -1
- package/dist/{chunk-EMXAIRUJ.js → chunk-IRTGUQ5O.js} +5 -4
- package/dist/chunk-IRTGUQ5O.js.map +1 -0
- package/dist/{chunk-H6ZAOWNE.js → chunk-JVBYV6TL.js} +3 -3
- package/dist/{chunk-H6ZAOWNE.js.map → chunk-JVBYV6TL.js.map} +1 -1
- package/dist/chunk-KHK5FGER.js +145 -0
- package/dist/chunk-KHK5FGER.js.map +1 -0
- package/dist/chunk-NEXR37YJ.js +144 -0
- package/dist/chunk-NEXR37YJ.js.map +1 -0
- package/dist/{chunk-A2ACAGW6.js → chunk-O6FFFOHS.js} +32 -7
- package/dist/chunk-O6FFFOHS.js.map +1 -0
- package/dist/chunk-QGMOA2YE.js +731 -0
- package/dist/chunk-QGMOA2YE.js.map +1 -0
- package/dist/{chunk-ERFMC6WU.js → chunk-S4NCI32V.js} +19 -10
- package/dist/chunk-S4NCI32V.js.map +1 -0
- package/dist/{chunk-753Y6HE6.js → chunk-SLAQPUI4.js} +4 -4
- package/dist/chunk-SLAQPUI4.js.map +1 -0
- package/dist/{chunk-REOBGYMC.js → chunk-TJ77A4A3.js} +3 -5
- package/dist/chunk-TJ77A4A3.js.map +1 -0
- package/dist/chunk-TYN6MLXX.js +13864 -0
- package/dist/chunk-TYN6MLXX.js.map +1 -0
- package/dist/{chunk-RS3HRQWU.js → chunk-WPLAANH4.js} +4 -3
- package/dist/chunk-WPLAANH4.js.map +1 -0
- package/dist/config/index.d.ts +51 -7
- package/dist/config/index.js +4 -5
- package/dist/controls/index.d.ts +92 -5
- package/dist/controls/index.js +5 -4
- package/dist/dragdrop/index.d.ts +9 -6
- package/dist/dragdrop/index.js +3 -2
- package/dist/index.d.ts +108 -154
- package/dist/index.js +456 -600
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +5 -35
- package/dist/layers/index.js +3 -3
- package/dist/loupe/index.d.ts +21 -17
- package/dist/loupe/index.js +5 -3
- package/dist/overview/index.d.ts +56 -0
- package/dist/overview/index.js +288 -0
- package/dist/overview/index.js.map +1 -0
- package/dist/passthrough/weasel-ui.d.ts +434 -231
- package/dist/passthrough/weasel-ui.js +2 -1
- package/dist/primitives/index.d.ts +19 -54
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +8 -7
- package/dist/state/index.js +4 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +360 -174
- package/dist/undo/index.d.ts +5 -4
- package/package.json +13 -13
- package/src/annotations/frac.test.ts +7 -0
- package/src/annotations/index.ts +1 -0
- package/src/annotations/paint.test.ts +21 -0
- package/src/annotations/paint.ts +12 -0
- package/src/annotations/store.test.ts +57 -0
- package/src/annotations/store.ts +25 -6
- package/src/annotations/toolMap.test.ts +37 -2
- package/src/annotations/toolMap.ts +13 -0
- package/src/annotations/types.ts +30 -2
- package/src/canvas/AGENTS.md +8 -10
- package/src/canvas/CameraInput.test.tsx +136 -0
- package/src/canvas/CameraInput.tsx +311 -0
- package/src/canvas/CanvasStack.tsx +42 -58
- package/src/canvas/LinkedCursor.less +16 -0
- package/src/canvas/LinkedCursor.tsx +54 -0
- package/src/canvas/Stage.test.tsx +18 -0
- package/src/canvas/Stage.tsx +42 -37
- package/src/canvas/cameraView.test.ts +60 -0
- package/src/canvas/cameraView.ts +61 -0
- package/src/canvas/index.ts +11 -2
- package/src/chrome/regions/SidebarRegion.less +197 -0
- package/src/chrome/regions/SidebarRegion.test.tsx +27 -0
- package/src/chrome/regions/SidebarRegion.tsx +7 -2
- package/src/chrome/types.ts +5 -2
- package/src/config/autoConfig.ts +2 -2
- package/src/config/builder.ts +21 -2
- package/src/config/entry.test.ts +10 -0
- package/src/config/fromConfigField.ts +1 -0
- package/src/config/index.ts +5 -0
- package/src/config/resolve.ts +11 -1
- package/src/config/rules.ts +2 -0
- package/src/config/sectionTree.test.ts +152 -0
- package/src/config/sectionTree.ts +134 -0
- package/src/config/types.ts +21 -0
- package/src/controls/ControlPanel.less +4 -0
- package/src/controls/ControlPanel.stories.tsx +55 -2
- package/src/controls/ControlPanel.test.tsx +40 -3
- package/src/controls/ControlPanel.tsx +134 -336
- package/src/controls/inDialog.test.tsx +100 -0
- package/src/controls/inDialog.tsx +38 -0
- package/src/controls/index.ts +1 -0
- package/src/index.test.ts +1 -1
- package/src/index.ts +18 -29
- package/src/instrument/SineWave.smoke.test.tsx +2 -0
- package/src/instrument/types.ts +15 -3
- package/src/lab/Lab.nebula.test.tsx +40 -0
- package/src/lab/Lab.test.tsx +3 -1
- package/src/lab/Lab.tsx +42 -19
- 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/loupe/AGENTS.md +7 -6
- package/src/loupe/LoupeGestures.tsx +14 -9
- package/src/loupe/TrialLoupe.tsx +5 -5
- package/src/loupe/index.ts +1 -0
- package/src/loupe/loupeActions.ts +6 -2
- package/src/overview/AGENTS.md +38 -0
- package/src/overview/OverviewMarks.test.tsx +100 -0
- package/src/overview/OverviewMarks.tsx +93 -0
- package/src/overview/TrialOverview.less +53 -0
- package/src/overview/TrialOverview.stories.tsx +50 -0
- package/src/overview/TrialOverview.test.tsx +116 -0
- package/src/overview/TrialOverview.tsx +374 -0
- package/src/overview/index.ts +2 -0
- package/src/passthrough/weasel-ui.ts +32 -10
- package/src/primitives/index.ts +0 -2
- package/src/specimen/Specimen.tsx +59 -56
- package/src/state/labRecords.test.ts +4 -1
- package/src/state/store.test.ts +4 -4
- package/src/state/types.ts +2 -1
- package/src/state/useTrialState.test.tsx +1 -1
- package/src/styles.less +6 -2
- package/src/theme/Interstellar.stories.tsx +8 -13
- 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/tools/labTool.ts +6 -2
- package/src/trial/Trial.annotations.test.tsx +16 -0
- package/src/trial/Trial.canvas.test.tsx +1 -1
- package/src/trial/Trial.loupe.test.tsx +73 -1
- package/src/trial/Trial.stories.tsx +2 -0
- package/src/trial/Trial.tsx +47 -9
- package/src/trial/trialOps.test.ts +4 -4
- package/dist/_dts/index-8LiKe3A-.d.ts +0 -410
- package/dist/chunk-2AYGEN57.js +0 -32
- package/dist/chunk-2AYGEN57.js.map +0 -1
- package/dist/chunk-6BUQ77ZV.js +0 -18
- package/dist/chunk-6BUQ77ZV.js.map +0 -1
- package/dist/chunk-753Y6HE6.js.map +0 -1
- package/dist/chunk-A2ACAGW6.js.map +0 -1
- package/dist/chunk-CKUKF72R.js.map +0 -1
- package/dist/chunk-D35PFTS7.js +0 -170
- package/dist/chunk-D35PFTS7.js.map +0 -1
- package/dist/chunk-EMXAIRUJ.js.map +0 -1
- package/dist/chunk-ERFMC6WU.js.map +0 -1
- package/dist/chunk-FKLT3ITC.js +0 -590
- package/dist/chunk-FKLT3ITC.js.map +0 -1
- package/dist/chunk-KJCUERQF.js.map +0 -1
- package/dist/chunk-LASCBWW7.js +0 -13456
- package/dist/chunk-LASCBWW7.js.map +0 -1
- package/dist/chunk-REOBGYMC.js.map +0 -1
- package/dist/chunk-RS3HRQWU.js.map +0 -1
- package/dist/chunk-TT5C5S6R.js +0 -368
- package/dist/chunk-TT5C5S6R.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/usePanZoom.test.ts +0 -244
- package/src/canvas/usePanZoom.ts +0 -147
- 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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { View } from '@weasel-js/core';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { clampZoomAbout, frameLocalToWorld, fromCameraView, toCameraView } from './cameraView';
|
|
4
|
+
import { worldToScreen } from './canvasCoords';
|
|
5
|
+
import type { WorldFrame } from './worldSpec';
|
|
6
|
+
|
|
7
|
+
const UP: WorldFrame = { originPx: { x: 200, y: 150 }, yDir: -1 };
|
|
8
|
+
const vt = { zoom: 2.5, pan: { x: 30, y: -40 } };
|
|
9
|
+
|
|
10
|
+
/** Where a weasel View puts a frame-local point. */
|
|
11
|
+
const project = (v: View, p: { x: number; y: number }) => ({
|
|
12
|
+
x: (p.x - v.x) * v.scale.x,
|
|
13
|
+
y: (p.y - v.y) * v.scale.y,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('camera as a weasel View', () => {
|
|
17
|
+
it.each([
|
|
18
|
+
['default frame', undefined],
|
|
19
|
+
['moved origin, y up', UP],
|
|
20
|
+
] as const)('places every world point where labkit does (%s)', (_, frame) => {
|
|
21
|
+
const v = toCameraView(vt, frame);
|
|
22
|
+
for (const w of [
|
|
23
|
+
{ x: 0, y: 0 },
|
|
24
|
+
{ x: 13, y: -7 },
|
|
25
|
+
{ x: -120, y: 44 },
|
|
26
|
+
]) {
|
|
27
|
+
const local = frameLocalToWorld(w, frame);
|
|
28
|
+
const a = project(v, local);
|
|
29
|
+
const b = worldToScreen(w, vt, frame);
|
|
30
|
+
expect(a.x).toBeCloseTo(b.x);
|
|
31
|
+
expect(a.y).toBeCloseTo(b.y);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('round-trips', () => {
|
|
36
|
+
const back = fromCameraView(toCameraView(vt, UP), UP);
|
|
37
|
+
expect(back.zoom).toBeCloseTo(vt.zoom);
|
|
38
|
+
expect(back.pan.x).toBeCloseTo(vt.pan.x);
|
|
39
|
+
expect(back.pan.y).toBeCloseTo(vt.pan.y);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('clampZoomAbout', () => {
|
|
44
|
+
const prev: View = { x: 0, y: 0, scale: { x: 1, y: 1 } };
|
|
45
|
+
it('holds the anchor of a zoom that hits the bound', () => {
|
|
46
|
+
// A 4x zoom about screen (100, 50): world under it stays (100, 50).
|
|
47
|
+
const next: View = { x: 75, y: 37.5, scale: { x: 4, y: 4 } };
|
|
48
|
+
const out = clampZoomAbout(prev, next, 0.5, 2);
|
|
49
|
+
expect(out.scale.x).toBe(2);
|
|
50
|
+
expect(100 / out.scale.x + out.x).toBeCloseTo(100);
|
|
51
|
+
expect(50 / out.scale.y + out.y).toBeCloseTo(50);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('passes a zoom inside the bounds and a pan through', () => {
|
|
55
|
+
const inside: View = { x: 10, y: 5, scale: { x: 1.5, y: 1.5 } };
|
|
56
|
+
expect(clampZoomAbout(prev, inside, 0.5, 2)).toBe(inside);
|
|
57
|
+
const pan: View = { x: 10, y: 5, scale: { x: 1, y: 1 } };
|
|
58
|
+
expect(clampZoomAbout(prev, pan, 0.5, 2)).toBe(pan);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* labkit's camera as a weasel `View`, so core's viewport actions drive it.
|
|
3
|
+
*
|
|
4
|
+
* labkit places a world point on screen at
|
|
5
|
+
* `originPx + pan + zoom * (x, yDir * y)`. A weasel `View` places one at
|
|
6
|
+
* `(p - (x, y)) * scale`. Over *frame-local* coordinates — the world with `y`
|
|
7
|
+
* multiplied by `yDir` — the two are the same affine map, which is all a pan
|
|
8
|
+
* or a zoom about a screen point needs.
|
|
9
|
+
*/
|
|
10
|
+
import type { View } from '@weasel-js/core';
|
|
11
|
+
import type { Point, ViewTransform } from '../instrument/types';
|
|
12
|
+
import { normalize2DView } from '../state/view';
|
|
13
|
+
import { DEFAULT_FRAME, type WorldFrame } from './worldSpec';
|
|
14
|
+
|
|
15
|
+
/** The weasel `View` equal to `view` under `frame`. */
|
|
16
|
+
export function toCameraView(view: ViewTransform, frame: WorldFrame = DEFAULT_FRAME): View {
|
|
17
|
+
const v = normalize2DView(view);
|
|
18
|
+
return {
|
|
19
|
+
x: -(frame.originPx.x + v.pan.x) / v.zoom,
|
|
20
|
+
y: -(frame.originPx.y + v.pan.y) / v.zoom,
|
|
21
|
+
scale: { x: v.zoom, y: v.zoom },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The labkit camera equal to weasel `view` under `frame`. */
|
|
26
|
+
export function fromCameraView(view: View, frame: WorldFrame = DEFAULT_FRAME): ViewTransform {
|
|
27
|
+
const zoom = view.scale.x;
|
|
28
|
+
return {
|
|
29
|
+
zoom,
|
|
30
|
+
pan: { x: -view.x * zoom - frame.originPx.x, y: -view.y * zoom - frame.originPx.y },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A frame-local point in the instrument's world, and back — the map is its
|
|
35
|
+
* own inverse. */
|
|
36
|
+
export function frameLocalToWorld(p: Point, frame: WorldFrame = DEFAULT_FRAME): Point {
|
|
37
|
+
return { x: p.x, y: p.y * frame.yDir };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `next` with its zoom held to [`min`, `max`], about the screen point the step
|
|
42
|
+
* from `prev` was anchored on, so a zoom that hits a bound stays anchored where
|
|
43
|
+
* the pointer is. A pan (equal scales) passes through.
|
|
44
|
+
*/
|
|
45
|
+
export function clampZoomAbout(prev: View, next: View, min: number, max: number): View {
|
|
46
|
+
const s0 = prev.scale.x;
|
|
47
|
+
const s1 = next.scale.x;
|
|
48
|
+
const clamped = Math.min(max, Math.max(min, s1));
|
|
49
|
+
if (clamped === s1) return next;
|
|
50
|
+
if (s0 === s1) return { ...next, scale: { x: clamped, y: clamped } };
|
|
51
|
+
// The screen point both views agree on: p / s0 + x0 = p / s1 + x1.
|
|
52
|
+
const inv = 1 / s0 - 1 / s1;
|
|
53
|
+
const px = (next.x - prev.x) / inv;
|
|
54
|
+
const py = (next.y - prev.y) / inv;
|
|
55
|
+
// The same point under the clamped scale.
|
|
56
|
+
return {
|
|
57
|
+
x: prev.x + px / s0 - px / clamped,
|
|
58
|
+
y: prev.y + py / s0 - py / clamped,
|
|
59
|
+
scale: { x: clamped, y: clamped },
|
|
60
|
+
};
|
|
61
|
+
}
|
package/src/canvas/index.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export type { CameraContextValue, CameraInputProps, CameraViewOptions } from './CameraInput';
|
|
2
|
+
export {
|
|
3
|
+
CameraContext,
|
|
4
|
+
CameraInput,
|
|
5
|
+
CameraScope,
|
|
6
|
+
CameraScopeContext,
|
|
7
|
+
STAGE_VIEW_ID,
|
|
8
|
+
useCameraView,
|
|
9
|
+
} from './CameraInput';
|
|
1
10
|
export type { CameraWheelSlot } from './CameraWheelContext';
|
|
2
11
|
export { CameraWheelContext } from './CameraWheelContext';
|
|
3
12
|
export type { CanvasStackProps } from './CanvasStack';
|
|
@@ -6,11 +15,11 @@ export type { CanvasStackContextValue, CanvasStackSurface } from './CanvasStackC
|
|
|
6
15
|
export { CanvasStackContext } from './CanvasStackContext';
|
|
7
16
|
export type { ZoomAtOptions } from './camera';
|
|
8
17
|
export { centerOn, zoomAt } from './camera';
|
|
18
|
+
export { clampZoomAbout, frameLocalToWorld, fromCameraView, toCameraView } from './cameraView';
|
|
9
19
|
export { screenToWorld, worldToScreen } from './canvasCoords';
|
|
20
|
+
export { LinkedCursor } from './LinkedCursor';
|
|
10
21
|
export type { StageProps } from './Stage';
|
|
11
22
|
export { fitStage, Stage } from './Stage';
|
|
12
23
|
export type { CanvasLayerDescriptor } from './useLayerScheduler';
|
|
13
|
-
export type { PanZoomHandlers, UsePanZoomOptions } from './usePanZoom';
|
|
14
|
-
export { usePanZoom } from './usePanZoom';
|
|
15
24
|
export type { ViewportSize, WorldFrame, WorldSpec } from './worldSpec';
|
|
16
25
|
export { applyCamera, DEFAULT_FRAME, resolveFrame } from './worldSpec';
|
|
@@ -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,27 @@
|
|
|
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 = {
|
|
7
|
+
collapsedSections: {},
|
|
8
|
+
setSectionCollapsed: () => {},
|
|
9
|
+
} as unknown as TrialChromeContext;
|
|
10
|
+
|
|
11
|
+
describe('<SidebarRegion> stance and tone', () => {
|
|
12
|
+
it('puts a section’s stance and tone on the section', () => {
|
|
13
|
+
const contributions: RegionContribution<TrialChromeContext>[] = [
|
|
14
|
+
{ id: 'a', region: 'sidebar', item: { title: 'Plain', body: 'x' } },
|
|
15
|
+
{
|
|
16
|
+
id: 'b',
|
|
17
|
+
region: 'sidebar',
|
|
18
|
+
item: { title: 'Debug', body: 'y', stance: 'debug', tone: '#224a63' },
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
const { container } = render(<SidebarRegion contributions={contributions} ctx={ctx} />);
|
|
22
|
+
const [plain, debug] = [...container.querySelectorAll<HTMLElement>('.lk-sidebar-section')];
|
|
23
|
+
expect(plain.hasAttribute('data-stance')).toBe(false);
|
|
24
|
+
expect(debug.dataset.stance).toBe('debug');
|
|
25
|
+
expect(debug.style.getPropertyValue('--wzl-tone')).toBe('#224a63');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -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';
|
|
@@ -45,8 +46,10 @@ export interface ToolItem<TCtx = TrialChromeContext> {
|
|
|
45
46
|
onActivate?: (ctx: TCtx) => void;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
/** A titled block in the sidebar.
|
|
49
|
-
|
|
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 {
|
|
50
53
|
title: string;
|
|
51
54
|
/** Starts collapsed, until the trial remembers a fold of its own. */
|
|
52
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
|
}
|
package/src/config/builder.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ConfigSchema,
|
|
12
12
|
ConfigShape,
|
|
13
13
|
ControlRenderer,
|
|
14
|
+
InDialogOptions,
|
|
14
15
|
InferConfig,
|
|
15
16
|
NodeOptions,
|
|
16
17
|
SectionOption,
|
|
@@ -88,7 +89,13 @@ export abstract class BaseNode<T> implements ConfigNode<T> {
|
|
|
88
89
|
|
|
89
90
|
/** Draw this one row yourself, keeping the kind, default and validation. */
|
|
90
91
|
render(renderer: ControlRenderer): this {
|
|
91
|
-
return this.opt({ render: renderer });
|
|
92
|
+
return this.opt({ render: renderer, dialog: undefined });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Draw this row as a button that opens a dialog, with `body` as the
|
|
96
|
+
* dialog's content — for an editor too big for the row. */
|
|
97
|
+
dialog(body: ControlRenderer, opts?: InDialogOptions): this {
|
|
98
|
+
return this.opt({ dialog: { ...opts, body }, render: undefined });
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
/**
|
|
@@ -134,7 +141,7 @@ export class NumberNode extends BaseNode<number> {
|
|
|
134
141
|
return this.ann({ suffix });
|
|
135
142
|
}
|
|
136
143
|
|
|
137
|
-
/** Show the value in a named format — `'compact'` reads `2.
|
|
144
|
+
/** Show the value in a named format — `'compact'` reads `2.00M`. Presentation
|
|
138
145
|
* only: the stored value stays a plain number, and a typed `2.5m` reads back. */
|
|
139
146
|
format(format: PrefNumberFormat): this {
|
|
140
147
|
return this.ann({ format });
|
|
@@ -187,6 +194,15 @@ export class StringNode extends BaseNode<string> {
|
|
|
187
194
|
}
|
|
188
195
|
}
|
|
189
196
|
|
|
197
|
+
export class ListNode extends BaseNode<string[]> {
|
|
198
|
+
readonly kind = 'list';
|
|
199
|
+
|
|
200
|
+
/** Shown in an empty entry. */
|
|
201
|
+
placeholder(placeholder: string): this {
|
|
202
|
+
return this.ann({ placeholder });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
190
206
|
export class ColorNode extends BaseNode<string> {
|
|
191
207
|
readonly kind = 'color';
|
|
192
208
|
|
|
@@ -304,6 +320,9 @@ export const f = {
|
|
|
304
320
|
boolean: (def: boolean): BooleanNode => new BooleanNode(def),
|
|
305
321
|
string: (def: string): StringNode => new StringNode(def),
|
|
306
322
|
color: (def: string): ColorNode => new ColorNode(def),
|
|
323
|
+
/** A list of strings. Its row summarizes the list, and opens a dialog to
|
|
324
|
+
* edit it one entry per field. */
|
|
325
|
+
list: (def: readonly string[]): ListNode => new ListNode([...def]),
|
|
307
326
|
|
|
308
327
|
/** A fixed set of choices. The default's literal type flows into the config
|
|
309
328
|
* type, so `f.enum('fast', ['fast', 'accurate'])` gives
|
package/src/config/entry.test.ts
CHANGED
|
@@ -36,6 +36,16 @@ describe('@weasel-js/labkit/config', () => {
|
|
|
36
36
|
expect(files.filter((f) => /\.(less|css)$/.test(f))).toEqual([]);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
// A forge frame loads this entry for every story, and ui's barrel is 167 modules on the dev server.
|
|
40
|
+
it('loads nothing from @weasel-js/ui at runtime', () => {
|
|
41
|
+
const importers = [...walk(resolve(__dirname, 'index.ts'))].filter((file) =>
|
|
42
|
+
[...readFileSync(file, 'utf8').matchAll(IMPORT)].some((m) =>
|
|
43
|
+
(m[1] ?? m[2] ?? m[3])?.startsWith('@weasel-js/ui'),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
expect(importers).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
39
49
|
it('is published as an entry', async () => {
|
|
40
50
|
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../../package.json'), 'utf8'));
|
|
41
51
|
expect(pkg.exports['./config']).toEqual({
|
package/src/config/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export {
|
|
|
9
9
|
f,
|
|
10
10
|
GroupNode,
|
|
11
11
|
isConfigBranch,
|
|
12
|
+
ListNode,
|
|
12
13
|
NumberNode,
|
|
13
14
|
StringNode,
|
|
14
15
|
ValueNode,
|
|
@@ -23,6 +24,7 @@ export {
|
|
|
23
24
|
} from './path';
|
|
24
25
|
export { resolveConfigSchema } from './resolve';
|
|
25
26
|
export { applyRules, builtinRules, titleCase } from './rules';
|
|
27
|
+
export { type SectionTree, sectionTree } from './sectionTree';
|
|
26
28
|
export type {
|
|
27
29
|
Annotations,
|
|
28
30
|
BranchAnnotations,
|
|
@@ -38,12 +40,15 @@ export type {
|
|
|
38
40
|
ConfigSchema,
|
|
39
41
|
ConfigShape,
|
|
40
42
|
ControlRenderer,
|
|
43
|
+
DialogSpec,
|
|
41
44
|
EntryValue,
|
|
45
|
+
InDialogOptions,
|
|
42
46
|
InferConfig,
|
|
43
47
|
LeafPatch,
|
|
44
48
|
NodeOptions,
|
|
45
49
|
NodeValue,
|
|
46
50
|
ResolvedConfig,
|
|
51
|
+
SectionOption,
|
|
47
52
|
SectionSpec,
|
|
48
53
|
ValueAtPath,
|
|
49
54
|
} from './types';
|
package/src/config/resolve.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
ConfigSchema,
|
|
8
8
|
ConfigShape,
|
|
9
9
|
ControlRenderer,
|
|
10
|
+
DialogSpec,
|
|
10
11
|
LeafPatch,
|
|
11
12
|
ResolvedConfig,
|
|
12
13
|
SectionSpec,
|
|
@@ -25,6 +26,7 @@ interface Sink {
|
|
|
25
26
|
sections: SectionSpec[];
|
|
26
27
|
showIf: Map<string, (config: Record<string, unknown>) => boolean>;
|
|
27
28
|
renderers: Record<string, ControlRenderer>;
|
|
29
|
+
dialogs: Record<string, DialogSpec>;
|
|
28
30
|
chain: readonly ConfigRule[];
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -45,10 +47,17 @@ export function resolveConfigSchema<TC>(
|
|
|
45
47
|
sections: [],
|
|
46
48
|
showIf: new Map(),
|
|
47
49
|
renderers: {},
|
|
50
|
+
dialogs: {},
|
|
48
51
|
chain: [...rules, ...builtinRules],
|
|
49
52
|
};
|
|
50
53
|
const group = resolveShape(schema.nodes, '', '', sink);
|
|
51
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
group,
|
|
56
|
+
sections: sink.sections,
|
|
57
|
+
showIf: sink.showIf,
|
|
58
|
+
renderers: sink.renderers,
|
|
59
|
+
dialogs: sink.dialogs,
|
|
60
|
+
};
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
/** A section while it is still being filled. */
|
|
@@ -81,6 +90,7 @@ function resolveShape(shape: ConfigShape, at: string, name: string, sink: Sink):
|
|
|
81
90
|
}
|
|
82
91
|
if (predicate) sink.showIf.set(path, predicate);
|
|
83
92
|
if (!isConfigBranch(entry) && entry.options.render) sink.renderers[path] = entry.options.render;
|
|
93
|
+
if (!isConfigBranch(entry) && entry.options.dialog) sink.dialogs[path] = entry.options.dialog;
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
return { name, children };
|
package/src/config/rules.ts
CHANGED
|
@@ -15,6 +15,8 @@ export function titleCase(key: string): string {
|
|
|
15
15
|
const kindFromValue: ConfigRule = (ctx) => {
|
|
16
16
|
const t = typeof ctx.default;
|
|
17
17
|
if (t === 'boolean' || t === 'number' || t === 'string') return { kind: t };
|
|
18
|
+
const d = ctx.default;
|
|
19
|
+
if (Array.isArray(d) && d.every((v) => typeof v === 'string')) return { kind: 'list' };
|
|
18
20
|
return null;
|
|
19
21
|
};
|
|
20
22
|
|