@weasel-js/labkit 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_dts/{CanvasStackContext-DQgcH1ny.d.ts → CanvasStackContext-D2jnIwfk.d.ts} +1 -1
- package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
- package/dist/_dts/{LayerList.d-2c9yup5Y.d.ts → LayerList.d-DiuEArKq.d.ts} +4 -4
- package/dist/_dts/PrefsRow.d-TOZ7BgD9.d.ts +79 -0
- package/dist/_dts/PropertyPanel.d-D-1paCbE.d.ts +141 -0
- package/dist/_dts/{Select.d-BchGMC2Y.d.ts → Select.d-Bensr_MT.d.ts} +15 -6
- package/dist/_dts/Stage-CCWfQQWM.d.ts +202 -0
- package/dist/_dts/index-B2Yj5aA0.d.ts +274 -0
- package/dist/_dts/{index-B0--8PEM.d.ts → index-CsYqUHD3.d.ts} +12 -13
- package/dist/_dts/{types-B7y8hX1D.d.ts → types-CRPSGFAp.d.ts} +33 -5
- package/dist/_dts/{frac-DTRq7Xpb.d.ts → types-CSm9jgol.d.ts} +700 -650
- package/dist/_dts/{useTrialState-CP4jfuKl.d.ts → useTrialState-CTZPgeZ7.d.ts} +2 -3
- package/dist/canvas/index.d.ts +40 -8
- package/dist/canvas/index.js +5 -3
- package/dist/chrome/index.d.ts +5 -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-275JRRVD.js +728 -0
- package/dist/chunk-275JRRVD.js.map +1 -0
- package/dist/chunk-3RY6WL5T.js +38 -0
- package/dist/chunk-3RY6WL5T.js.map +1 -0
- package/dist/chunk-4LJSC7MB.js +143 -0
- package/dist/chunk-4LJSC7MB.js.map +1 -0
- package/dist/{chunk-3FI7Y4R4.js → chunk-7BO7HNGF.js} +5 -5
- package/dist/{chunk-3FI7Y4R4.js.map → chunk-7BO7HNGF.js.map} +1 -1
- package/dist/chunk-AWQG7FCC.js +19 -0
- package/dist/chunk-AWQG7FCC.js.map +1 -0
- package/dist/chunk-CZZPXUOU.js +3 -0
- package/dist/{chunk-SBXNQW4G.js.map → chunk-CZZPXUOU.js.map} +1 -1
- package/dist/{chunk-ZORVNOKX.js → chunk-FMK7UQ6V.js} +24 -145
- package/dist/chunk-FMK7UQ6V.js.map +1 -0
- package/dist/{chunk-BVL7Y574.js → chunk-FZ7YFQN7.js} +26 -10
- package/dist/chunk-FZ7YFQN7.js.map +1 -0
- package/dist/{chunk-USO6TERP.js → chunk-GOZLNM4L.js} +11 -9
- package/dist/chunk-GOZLNM4L.js.map +1 -0
- package/dist/{chunk-H5VIV7MQ.js → chunk-I5DUQ5HM.js} +88 -82
- package/dist/chunk-I5DUQ5HM.js.map +1 -0
- package/dist/chunk-J7OWWTCW.js +143 -0
- package/dist/chunk-J7OWWTCW.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-VQ47KWD5.js → chunk-KMJ7RWD3.js} +18 -7
- package/dist/chunk-KMJ7RWD3.js.map +1 -0
- package/dist/{chunk-ERFMC6WU.js → chunk-M7HP3FTK.js} +20 -11
- package/dist/chunk-M7HP3FTK.js.map +1 -0
- package/dist/chunk-MIM5R4ZT.js +14972 -0
- package/dist/chunk-MIM5R4ZT.js.map +1 -0
- package/dist/{chunk-HFRE4QQV.js → chunk-T5IBSPAM.js} +96 -32
- package/dist/chunk-T5IBSPAM.js.map +1 -0
- package/dist/chunk-UNAZSLWE.js +294 -0
- package/dist/chunk-UNAZSLWE.js.map +1 -0
- package/dist/{chunk-RS3HRQWU.js → chunk-WPLAANH4.js} +4 -3
- package/dist/chunk-WPLAANH4.js.map +1 -0
- package/dist/{chunk-AWH3SRPW.js → chunk-Y23G2VZL.js} +5 -37
- package/dist/chunk-Y23G2VZL.js.map +1 -0
- package/dist/{chunk-MOLIUNVH.js → chunk-YT75VYRD.js} +4 -4
- package/dist/{chunk-MOLIUNVH.js.map → chunk-YT75VYRD.js.map} +1 -1
- package/dist/chunk-ZIFPHIWZ.js +302 -0
- package/dist/chunk-ZIFPHIWZ.js.map +1 -0
- package/dist/config/index.d.ts +48 -40
- package/dist/config/index.js +4 -5
- package/dist/controls/index.d.ts +91 -7
- package/dist/controls/index.js +5 -4
- package/dist/dragdrop/index.d.ts +9 -7
- package/dist/dragdrop/index.js +3 -2
- package/dist/index.d.ts +294 -82
- package/dist/index.js +425 -402
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +4 -5
- package/dist/layers/index.js +2 -1
- package/dist/loupe/index.d.ts +20 -17
- package/dist/loupe/index.js +5 -3
- package/dist/overview/index.d.ts +56 -0
- package/dist/overview/index.js +289 -0
- package/dist/overview/index.js.map +1 -0
- package/dist/passthrough/weasel-ui.d.ts +239 -69
- package/dist/passthrough/weasel-ui.js +3 -2
- package/dist/primitives/index.d.ts +5 -204
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +8 -8
- package/dist/state/index.js +4 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +152 -82
- package/dist/undo/index.d.ts +4 -4
- package/package.json +17 -12
- package/src/annotations/AnnotationOverlay.tsx +2 -2
- package/src/annotations/Annotations.less +0 -10
- package/src/annotations/MarkList.tsx +4 -8
- package/src/annotations/index.ts +1 -0
- package/src/annotations/paint.test.ts +3 -14
- package/src/annotations/paint.ts +3 -19
- package/src/annotations/store.test.ts +26 -0
- package/src/annotations/store.ts +25 -6
- package/src/annotations/svgNodes.test.ts +6 -9
- package/src/annotations/svgNodes.ts +3 -3
- package/src/annotations/toolMap.test.ts +37 -2
- package/src/annotations/toolMap.ts +13 -0
- package/src/annotations/types.ts +27 -0
- package/src/canvas/AGENTS.md +9 -10
- package/src/canvas/CameraInput.test.tsx +136 -0
- package/src/canvas/CameraInput.tsx +322 -0
- package/src/canvas/CanvasStack.tsx +47 -58
- package/src/canvas/LinkedCursor.less +16 -0
- package/src/canvas/LinkedCursor.tsx +54 -0
- package/src/canvas/Stage.test.tsx +33 -4
- package/src/canvas/Stage.tsx +47 -37
- package/src/canvas/cameraRegistry.test.ts +55 -0
- package/src/canvas/cameraRegistry.ts +86 -0
- package/src/canvas/cameraView.test.ts +60 -0
- package/src/canvas/cameraView.ts +61 -0
- package/src/canvas/cameraZoom.test.ts +46 -0
- package/src/canvas/cameraZoom.ts +24 -0
- package/src/canvas/index.ts +23 -2
- package/src/canvas/sharedScope.test.tsx +94 -0
- package/src/chrome/ChromeRegions.stories.tsx +1 -0
- package/src/chrome/regions/PaletteRegion.less +0 -7
- package/src/chrome/regions/SidebarRegion.less +12 -2
- package/src/chrome/regions/SidebarRegion.test.tsx +9 -2
- package/src/config/builder.test.ts +2 -1
- package/src/config/builder.ts +15 -8
- package/src/config/entry.test.ts +3 -1
- package/src/config/fromConfigField.test.ts +14 -0
- package/src/config/fromConfigField.ts +19 -4
- package/src/config/index.ts +4 -1
- package/src/config/path.ts +2 -5
- package/src/config/resolve.ts +15 -1
- package/src/config/sectionTree.test.ts +12 -6
- package/src/config/sectionTree.ts +15 -5
- package/src/config/types.ts +34 -9
- package/src/controls/ControlPanel.less +1 -1
- package/src/controls/ControlPanel.stories.tsx +3 -2
- package/src/controls/ControlPanel.test.tsx +76 -13
- package/src/controls/ControlPanel.tsx +80 -11
- package/src/controls/inDialog.test.tsx +18 -0
- package/src/controls/inDialog.tsx +2 -10
- package/src/index.test.ts +1 -1
- package/src/index.ts +23 -5
- package/src/instrument/SineWave.smoke.test.tsx +2 -0
- package/src/instrument/types.ts +10 -1
- package/src/lab/Lab.nebula.test.tsx +11 -2
- package/src/lab/Lab.test.tsx +3 -1
- package/src/lab/Lab.tsx +87 -76
- package/src/lab/{LabFit.stories.less → LabFit.browser.test.less} +1 -1
- package/src/lab/{LabFit.stories.tsx → LabFit.browser.test.tsx} +41 -63
- package/src/lab/LabFullChrome.stories.tsx +1 -0
- package/src/lab/LabHeader.test.tsx +24 -14
- package/src/lab/LabHeader.tsx +11 -12
- package/src/lab/LabShell.less +8 -17
- package/src/lab/LabSwitcher.less +6 -6
- package/src/lab/LabZoom.test.tsx +223 -0
- package/src/lab/LabZoom.tsx +170 -0
- package/src/lab/index.ts +1 -0
- package/src/loupe/AGENTS.md +7 -6
- package/src/loupe/LoupeGestures.tsx +15 -10
- 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 +376 -0
- package/src/overview/index.ts +2 -0
- package/src/passthrough/weasel-ui.test.ts +2 -0
- package/src/passthrough/weasel-ui.ts +23 -4
- package/src/primitives/FloatingPanel.test.tsx +11 -0
- package/src/primitives/FloatingPanel.tsx +2 -5
- package/src/primitives/JobProgress.less +1 -1
- package/src/primitives/Legend.stories.tsx +1 -0
- package/src/primitives/Readout.browser.test.tsx +137 -0
- package/src/primitives/Readout.less +12 -0
- package/src/primitives/Readout.stories.tsx +58 -0
- package/src/primitives/Readout.test.tsx +93 -0
- package/src/primitives/Readout.tsx +56 -0
- package/src/primitives/index.ts +2 -0
- package/src/specimen/Specimen.stories.tsx +1 -0
- package/src/specimen/Specimen.tsx +21 -4
- package/src/state/Persistence.browser.test.tsx +47 -0
- package/src/state/labRecords.test.ts +4 -1
- package/src/state/store.test.ts +9 -5
- package/src/state/usePersistedState.test.tsx +1 -1
- package/src/state/useTrialState.test.tsx +1 -1
- package/src/styles.less +3 -0
- package/src/theme/Interstellar.stories.tsx +4 -6
- package/src/theme/base.less +21 -47
- package/src/theme/interstellar.test.ts +1 -1
- package/src/theme/interstellar.theme.json +10 -3
- package/src/tools/labTool.ts +6 -2
- package/src/trial/Trial.annotations.test.tsx +16 -0
- package/src/trial/Trial.less +0 -13
- package/src/trial/Trial.loupe.test.tsx +73 -1
- package/src/trial/Trial.stories.tsx +2 -0
- package/src/trial/Trial.tsx +23 -1
- package/src/trial/TrialChrome.tsx +21 -3
- package/src/trial/trialOps.test.ts +4 -4
- package/dist/_dts/PrefsRow.d-DFpPIeIC.d.ts +0 -39
- package/dist/_dts/inDialog-D5ZKGjiD.d.ts +0 -23
- package/dist/_dts/index-DINmCuLA.d.ts +0 -453
- package/dist/_dts/number.d-OeQYQoUO.d.ts +0 -28
- package/dist/_dts/usePanZoom-Bq73pwFe.d.ts +0 -117
- package/dist/chunk-2AYGEN57.js +0 -32
- package/dist/chunk-2AYGEN57.js.map +0 -1
- package/dist/chunk-AWH3SRPW.js.map +0 -1
- package/dist/chunk-BVL7Y574.js.map +0 -1
- package/dist/chunk-ERFMC6WU.js.map +0 -1
- package/dist/chunk-H5VIV7MQ.js.map +0 -1
- package/dist/chunk-HFRE4QQV.js.map +0 -1
- package/dist/chunk-RS3HRQWU.js.map +0 -1
- package/dist/chunk-SBXNQW4G.js +0 -3
- package/dist/chunk-TT5C5S6R.js +0 -368
- package/dist/chunk-TT5C5S6R.js.map +0 -1
- package/dist/chunk-USO6TERP.js.map +0 -1
- package/dist/chunk-VQ47KWD5.js.map +0 -1
- package/dist/chunk-WUJXJC5O.js +0 -14568
- package/dist/chunk-WUJXJC5O.js.map +0 -1
- package/dist/chunk-ZORVNOKX.js.map +0 -1
- package/src/canvas/usePanZoom.test.ts +0 -244
- package/src/canvas/usePanZoom.ts +0 -147
- package/src/state/Persistence.stories.tsx +0 -75
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
--_s-tone: var(--wzl-tone, var(--_s-surface));
|
|
24
24
|
--_s-accent: var(--wzl-tone);
|
|
25
25
|
--_s-title-font: var(--wzl-font-display);
|
|
26
|
+
--_s-title-weight: var(--wzl-font-weight-bold);
|
|
26
27
|
--_s-title-size: var(--wzl-font-size-sm);
|
|
27
28
|
--_s-title-case: uppercase;
|
|
28
29
|
--_s-title-tracking: 0.08em;
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
--_s-tone: var(--wzl-tone, var(--wzl-stance-scope-tone, var(--_s-surface)));
|
|
37
38
|
--_s-accent: var(--wzl-stance-scope-accent, var(--wzl-tone));
|
|
38
39
|
--_s-title-font: var(--wzl-stance-scope-title-font, var(--wzl-font-display));
|
|
40
|
+
--_s-title-weight: var(--wzl-stance-scope-title-weight, var(--wzl-font-weight-bold));
|
|
39
41
|
--_s-title-size: var(--wzl-stance-scope-title-size, var(--wzl-font-size-sm));
|
|
40
42
|
--_s-title-case: var(--wzl-stance-scope-title-case, uppercase);
|
|
41
43
|
--_s-title-tracking: var(--wzl-stance-scope-title-tracking, 0.08em);
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
--_s-tone: var(--wzl-tone, var(--wzl-stance-aside-tone, var(--_s-surface)));
|
|
50
52
|
--_s-accent: var(--wzl-stance-aside-accent, var(--wzl-tone));
|
|
51
53
|
--_s-title-font: var(--wzl-stance-aside-title-font, var(--wzl-font-display));
|
|
54
|
+
--_s-title-weight: var(--wzl-stance-aside-title-weight, var(--wzl-font-weight-bold));
|
|
52
55
|
--_s-title-size: var(--wzl-stance-aside-title-size, var(--wzl-font-size-sm));
|
|
53
56
|
--_s-title-case: var(--wzl-stance-aside-title-case, uppercase);
|
|
54
57
|
--_s-title-tracking: var(--wzl-stance-aside-title-tracking, 0.08em);
|
|
@@ -62,6 +65,7 @@
|
|
|
62
65
|
--_s-tone: var(--wzl-tone, var(--wzl-stance-advanced-tone, var(--_s-surface)));
|
|
63
66
|
--_s-accent: var(--wzl-stance-advanced-accent, var(--wzl-tone));
|
|
64
67
|
--_s-title-font: var(--wzl-stance-advanced-title-font, var(--wzl-font-display));
|
|
68
|
+
--_s-title-weight: var(--wzl-stance-advanced-title-weight, var(--wzl-font-weight-bold));
|
|
65
69
|
--_s-title-size: var(--wzl-stance-advanced-title-size, var(--wzl-font-size-sm));
|
|
66
70
|
--_s-title-case: var(--wzl-stance-advanced-title-case, uppercase);
|
|
67
71
|
--_s-title-tracking: var(--wzl-stance-advanced-title-tracking, 0.08em);
|
|
@@ -75,6 +79,7 @@
|
|
|
75
79
|
--_s-tone: var(--wzl-tone, var(--wzl-stance-debug-tone, var(--_s-surface)));
|
|
76
80
|
--_s-accent: var(--wzl-stance-debug-accent, var(--wzl-tone));
|
|
77
81
|
--_s-title-font: var(--wzl-stance-debug-title-font, var(--wzl-font-display));
|
|
82
|
+
--_s-title-weight: var(--wzl-stance-debug-title-weight, var(--wzl-font-weight-bold));
|
|
78
83
|
--_s-title-size: var(--wzl-stance-debug-title-size, var(--wzl-font-size-sm));
|
|
79
84
|
--_s-title-case: var(--wzl-stance-debug-title-case, uppercase);
|
|
80
85
|
--_s-title-tracking: var(--wzl-stance-debug-title-tracking, 0.08em);
|
|
@@ -88,6 +93,7 @@
|
|
|
88
93
|
--_s-tone: var(--wzl-tone, var(--wzl-stance-danger-tone, var(--_s-surface)));
|
|
89
94
|
--_s-accent: var(--wzl-stance-danger-accent, var(--wzl-tone));
|
|
90
95
|
--_s-title-font: var(--wzl-stance-danger-title-font, var(--wzl-font-display));
|
|
96
|
+
--_s-title-weight: var(--wzl-stance-danger-title-weight, var(--wzl-font-weight-bold));
|
|
91
97
|
--_s-title-size: var(--wzl-stance-danger-title-size, var(--wzl-font-size-sm));
|
|
92
98
|
--_s-title-case: var(--wzl-stance-danger-title-case, uppercase);
|
|
93
99
|
--_s-title-tracking: var(--wzl-stance-danger-title-tracking, 0.08em);
|
|
@@ -101,6 +107,7 @@
|
|
|
101
107
|
--_s-tone: var(--wzl-tone, var(--wzl-stance-notice-tone, var(--_s-surface)));
|
|
102
108
|
--_s-accent: var(--wzl-stance-notice-accent, var(--wzl-tone));
|
|
103
109
|
--_s-title-font: var(--wzl-stance-notice-title-font, var(--wzl-font-display));
|
|
110
|
+
--_s-title-weight: var(--wzl-stance-notice-title-weight, var(--wzl-font-weight-bold));
|
|
104
111
|
--_s-title-size: var(--wzl-stance-notice-title-size, var(--wzl-font-size-sm));
|
|
105
112
|
--_s-title-case: var(--wzl-stance-notice-title-case, uppercase);
|
|
106
113
|
--_s-title-tracking: var(--wzl-stance-notice-title-tracking, 0.08em);
|
|
@@ -114,6 +121,7 @@
|
|
|
114
121
|
--_s-tone: var(--wzl-tone, var(--wzl-stance-important-tone, var(--_s-surface)));
|
|
115
122
|
--_s-accent: var(--wzl-stance-important-accent, var(--wzl-tone));
|
|
116
123
|
--_s-title-font: var(--wzl-stance-important-title-font, var(--wzl-font-display));
|
|
124
|
+
--_s-title-weight: var(--wzl-stance-important-title-weight, var(--wzl-font-weight-bold));
|
|
117
125
|
--_s-title-size: var(--wzl-stance-important-title-size, var(--wzl-font-size-sm));
|
|
118
126
|
--_s-title-case: var(--wzl-stance-important-title-case, uppercase);
|
|
119
127
|
--_s-title-tracking: var(--wzl-stance-important-title-tracking, 0.08em);
|
|
@@ -127,6 +135,7 @@
|
|
|
127
135
|
--_s-tone: var(--wzl-tone, var(--wzl-stance-preview-tone, var(--_s-surface)));
|
|
128
136
|
--_s-accent: var(--wzl-stance-preview-accent, var(--wzl-tone));
|
|
129
137
|
--_s-title-font: var(--wzl-stance-preview-title-font, var(--wzl-font-display));
|
|
138
|
+
--_s-title-weight: var(--wzl-stance-preview-title-weight, var(--wzl-font-weight-bold));
|
|
130
139
|
--_s-title-size: var(--wzl-stance-preview-title-size, var(--wzl-font-size-sm));
|
|
131
140
|
--_s-title-case: var(--wzl-stance-preview-title-case, uppercase);
|
|
132
141
|
--_s-title-tracking: var(--wzl-stance-preview-title-tracking, 0.08em);
|
|
@@ -174,12 +183,13 @@
|
|
|
174
183
|
.lk-sidebar-section__head {
|
|
175
184
|
flex: 1;
|
|
176
185
|
width: 100%;
|
|
177
|
-
padding: var(--wzl-space-xs);
|
|
186
|
+
padding: var(--wzl-space-xs) var(--wzl-space-sm);
|
|
178
187
|
border: 0;
|
|
179
188
|
background: transparent;
|
|
180
189
|
color: var(--_s-title-color);
|
|
181
190
|
font-family: var(--_s-title-font);
|
|
182
191
|
font-size: var(--_s-title-size);
|
|
192
|
+
font-weight: var(--_s-title-weight);
|
|
183
193
|
letter-spacing: var(--_s-title-tracking);
|
|
184
194
|
text-align: left;
|
|
185
195
|
text-transform: var(--_s-title-case);
|
|
@@ -193,5 +203,5 @@
|
|
|
193
203
|
// The section body is the only thing that insets a panel — the panels
|
|
194
204
|
// themselves carry no padding, so this is the whole gutter.
|
|
195
205
|
.lk-sidebar-section__body {
|
|
196
|
-
padding: 0 var(--wzl-space-
|
|
206
|
+
padding: 0 var(--wzl-space-sm) var(--wzl-space-sm);
|
|
197
207
|
}
|
|
@@ -3,13 +3,20 @@ import { describe, expect, it } from 'vitest';
|
|
|
3
3
|
import type { RegionContribution, TrialChromeContext } from '../types';
|
|
4
4
|
import { SidebarRegion } from './SidebarRegion';
|
|
5
5
|
|
|
6
|
-
const ctx = {
|
|
6
|
+
const ctx = {
|
|
7
|
+
collapsedSections: {},
|
|
8
|
+
setSectionCollapsed: () => {},
|
|
9
|
+
} as unknown as TrialChromeContext;
|
|
7
10
|
|
|
8
11
|
describe('<SidebarRegion> stance and tone', () => {
|
|
9
12
|
it('puts a section’s stance and tone on the section', () => {
|
|
10
13
|
const contributions: RegionContribution<TrialChromeContext>[] = [
|
|
11
14
|
{ id: 'a', region: 'sidebar', item: { title: 'Plain', body: 'x' } },
|
|
12
|
-
{
|
|
15
|
+
{
|
|
16
|
+
id: 'b',
|
|
17
|
+
region: 'sidebar',
|
|
18
|
+
item: { title: 'Debug', body: 'y', stance: 'debug', tone: '#224a63' },
|
|
19
|
+
},
|
|
13
20
|
];
|
|
14
21
|
const { container } = render(<SidebarRegion contributions={contributions} ctx={ctx} />);
|
|
15
22
|
const [plain, debug] = [...container.querySelectorAll<HTMLElement>('.lk-sidebar-section')];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { compact } from '@weasel-js/quantity';
|
|
1
2
|
import { describe, expect, it } from 'vitest';
|
|
2
3
|
import { auto } from './auto';
|
|
3
4
|
import { f } from './builder';
|
|
@@ -37,7 +38,7 @@ describe('builder', () => {
|
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
it('annotates a number with a display format', () => {
|
|
40
|
-
expect(f.number(0).
|
|
41
|
+
expect(f.number(0).display(compact()).annotations.display).toEqual(compact());
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
it('keeps a custom node kind across a chain', () => {
|
package/src/config/builder.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { Display } from '@weasel-js/quantity';
|
|
2
|
+
import type { PrefLeaf, PrefNumberUnit } from '@weasel-js/ui';
|
|
3
3
|
import { type Auto, isAuto } from './auto';
|
|
4
4
|
import type {
|
|
5
5
|
Annotations,
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ConfigSchema,
|
|
13
13
|
ConfigShape,
|
|
14
14
|
ControlRenderer,
|
|
15
|
+
InDialogOptions,
|
|
15
16
|
InferConfig,
|
|
16
17
|
NodeOptions,
|
|
17
18
|
SectionOption,
|
|
@@ -89,13 +90,13 @@ export abstract class BaseNode<T> implements ConfigNode<T> {
|
|
|
89
90
|
|
|
90
91
|
/** Draw this one row yourself, keeping the kind, default and validation. */
|
|
91
92
|
render(renderer: ControlRenderer): this {
|
|
92
|
-
return this.opt({ render: renderer });
|
|
93
|
+
return this.opt({ render: renderer, dialog: undefined });
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
/** Draw this row as a button that opens a dialog, with `body` as the
|
|
96
97
|
* dialog's content — for an editor too big for the row. */
|
|
97
98
|
dialog(body: ControlRenderer, opts?: InDialogOptions): this {
|
|
98
|
-
return this.
|
|
99
|
+
return this.opt({ dialog: { ...opts, body }, render: undefined });
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
/**
|
|
@@ -141,10 +142,10 @@ export class NumberNode extends BaseNode<number> {
|
|
|
141
142
|
return this.ann({ suffix });
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
/** Show the value
|
|
145
|
-
* only: the stored value stays a plain number
|
|
146
|
-
|
|
147
|
-
return this.ann({
|
|
145
|
+
/** Show, speak and read the value through a display — `compact()` reads
|
|
146
|
+
* `2.00M`. Presentation only: the stored value stays a plain number. */
|
|
147
|
+
display(display: Display): this {
|
|
148
|
+
return this.ann({ display });
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
/**
|
|
@@ -286,6 +287,12 @@ export class GroupNode<S extends ConfigShape> implements ConfigBranch<S> {
|
|
|
286
287
|
showIf(predicate: (config: Record<string, unknown>) => boolean): GroupNode<S> {
|
|
287
288
|
return this.with(this.annotations, { ...this.options, showIf: predicate });
|
|
288
289
|
}
|
|
290
|
+
|
|
291
|
+
/** Put a button on the group's heading that returns every value beneath it
|
|
292
|
+
* to its default, leaving the rest of the config alone. */
|
|
293
|
+
resettable(): GroupNode<S> {
|
|
294
|
+
return this.with(this.annotations, { ...this.options, resettable: true });
|
|
295
|
+
}
|
|
289
296
|
}
|
|
290
297
|
|
|
291
298
|
/** Every default in a shape, nested the way the shape is. */
|
package/src/config/entry.test.ts
CHANGED
|
@@ -39,7 +39,9 @@ describe('@weasel-js/labkit/config', () => {
|
|
|
39
39
|
// A forge frame loads this entry for every story, and ui's barrel is 167 modules on the dev server.
|
|
40
40
|
it('loads nothing from @weasel-js/ui at runtime', () => {
|
|
41
41
|
const importers = [...walk(resolve(__dirname, 'index.ts'))].filter((file) =>
|
|
42
|
-
[...readFileSync(file, 'utf8').matchAll(IMPORT)].some((m) =>
|
|
42
|
+
[...readFileSync(file, 'utf8').matchAll(IMPORT)].some((m) =>
|
|
43
|
+
(m[1] ?? m[2] ?? m[3])?.startsWith('@weasel-js/ui'),
|
|
44
|
+
),
|
|
43
45
|
);
|
|
44
46
|
expect(importers).toEqual([]);
|
|
45
47
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import type { ConfigField } from '../controls/types';
|
|
3
3
|
import { fromConfigFields } from './fromConfigField';
|
|
4
|
+
import { schemaNodeAtPath } from './path';
|
|
4
5
|
import { isLeafVisible } from './visible';
|
|
5
6
|
|
|
6
7
|
const leaf = (fields: ConfigField[], k: string) =>
|
|
@@ -76,6 +77,19 @@ describe('fromConfigFields', () => {
|
|
|
76
77
|
]);
|
|
77
78
|
expect(Object.keys(r.group.children)).toEqual(['z', 'a']);
|
|
78
79
|
});
|
|
80
|
+
|
|
81
|
+
it('files a dotted key at the path it names, the one config reads and writes use', () => {
|
|
82
|
+
const r = fromConfigFields([
|
|
83
|
+
{ type: 'number', key: 'pose.x', label: 'X', default: 0 },
|
|
84
|
+
{ type: 'number', key: 'pose.y', label: 'Y', default: 0 },
|
|
85
|
+
{ type: 'checkbox', key: 'on', label: 'On', default: true },
|
|
86
|
+
]);
|
|
87
|
+
expect(schemaNodeAtPath(r.group, 'pose.x')).toMatchObject({ kind: 'number', name: 'X' });
|
|
88
|
+
expect(schemaNodeAtPath(r.group, 'pose.y')).toMatchObject({ kind: 'number', name: 'Y' });
|
|
89
|
+
// The intermediate is headless: a flat field list never asked for a heading.
|
|
90
|
+
expect(schemaNodeAtPath(r.group, 'pose')).toMatchObject({ name: '' });
|
|
91
|
+
expect(Object.keys(r.group.children)).toEqual(['pose', 'on']);
|
|
92
|
+
});
|
|
79
93
|
});
|
|
80
94
|
|
|
81
95
|
describe('isLeafVisible', () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PrefLeaf } from '@weasel-js/ui';
|
|
1
|
+
import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';
|
|
2
2
|
import type { ConfigField } from '../controls/types';
|
|
3
3
|
import type { LeafPatch, ResolvedConfig } from './types';
|
|
4
4
|
|
|
@@ -53,9 +53,13 @@ function toLeaf(field: ConfigField): LeafPatch {
|
|
|
53
53
|
*
|
|
54
54
|
* Rules do not run here: a hand-written `ConfigField` already states its label
|
|
55
55
|
* and control, which is everything the rule chain would have inferred.
|
|
56
|
+
*
|
|
57
|
+
* A dotted `key` is a config path, as it is to every read and write, so its
|
|
58
|
+
* leaf is filed under headless groups at that path rather than as one child
|
|
59
|
+
* whose name contains dots — which no path lookup would find.
|
|
56
60
|
*/
|
|
57
61
|
export function fromConfigFields(fields: readonly ConfigField[]): ResolvedConfig {
|
|
58
|
-
const
|
|
62
|
+
const root: PrefGroup = { name: '', children: {} };
|
|
59
63
|
for (const field of fields) {
|
|
60
64
|
const patch = toLeaf(field);
|
|
61
65
|
for (const key of Object.keys(patch)) {
|
|
@@ -63,12 +67,23 @@ export function fromConfigFields(fields: readonly ConfigField[]): ResolvedConfig
|
|
|
63
67
|
delete (patch as Record<string, unknown>)[key];
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
|
-
|
|
70
|
+
const segments = field.key.split('.');
|
|
71
|
+
const leafKey = segments.pop() as string;
|
|
72
|
+
let group = root;
|
|
73
|
+
for (const segment of segments) {
|
|
74
|
+
const existing = group.children[segment];
|
|
75
|
+
if (existing === undefined || !('children' in existing) || 'kind' in existing) {
|
|
76
|
+
group.children[segment] = { name: '', children: {} };
|
|
77
|
+
}
|
|
78
|
+
group = group.children[segment] as PrefGroup;
|
|
79
|
+
}
|
|
80
|
+
group.children[leafKey] = { ...patch, default: field.default } as PrefLeaf;
|
|
67
81
|
}
|
|
68
82
|
return {
|
|
69
|
-
group:
|
|
83
|
+
group: root,
|
|
70
84
|
sections: [],
|
|
71
85
|
showIf: new Map(),
|
|
72
86
|
renderers: {},
|
|
87
|
+
dialogs: {},
|
|
73
88
|
};
|
|
74
89
|
}
|
package/src/config/index.ts
CHANGED
|
@@ -23,8 +23,8 @@ export {
|
|
|
23
23
|
withValueAtPath,
|
|
24
24
|
} from './path';
|
|
25
25
|
export { resolveConfigSchema } from './resolve';
|
|
26
|
-
export { type SectionTree, sectionTree } from './sectionTree';
|
|
27
26
|
export { applyRules, builtinRules, titleCase } from './rules';
|
|
27
|
+
export { type SectionTree, sectionTree } from './sectionTree';
|
|
28
28
|
export type {
|
|
29
29
|
Annotations,
|
|
30
30
|
BranchAnnotations,
|
|
@@ -40,12 +40,15 @@ export type {
|
|
|
40
40
|
ConfigSchema,
|
|
41
41
|
ConfigShape,
|
|
42
42
|
ControlRenderer,
|
|
43
|
+
DialogSpec,
|
|
43
44
|
EntryValue,
|
|
45
|
+
InDialogOptions,
|
|
44
46
|
InferConfig,
|
|
45
47
|
LeafPatch,
|
|
46
48
|
NodeOptions,
|
|
47
49
|
NodeValue,
|
|
48
50
|
ResolvedConfig,
|
|
51
|
+
SectionOption,
|
|
49
52
|
SectionSpec,
|
|
50
53
|
ValueAtPath,
|
|
51
54
|
} from './types';
|
package/src/config/path.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPlainObject } from '@weasel-js/core';
|
|
1
2
|
import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -9,11 +10,7 @@ import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';
|
|
|
9
10
|
/** A plain object a config tree can be walked into. An array, a `Date`, a
|
|
10
11
|
* `Map` or a class instance is a leaf value: a walk that descended into one
|
|
11
12
|
* would rebuild it as a bare `{}`. */
|
|
12
|
-
export
|
|
13
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
14
|
-
const proto = Object.getPrototypeOf(value);
|
|
15
|
-
return proto === Object.prototype || proto === null;
|
|
16
|
-
}
|
|
13
|
+
export const isRecord = isPlainObject;
|
|
17
14
|
|
|
18
15
|
/** The value at a dotted path. `undefined` when a segment is missing or the
|
|
19
16
|
* walk hits something that is not a record. */
|
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,8 @@ 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>;
|
|
30
|
+
resettable: Set<string>;
|
|
28
31
|
chain: readonly ConfigRule[];
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -45,10 +48,19 @@ export function resolveConfigSchema<TC>(
|
|
|
45
48
|
sections: [],
|
|
46
49
|
showIf: new Map(),
|
|
47
50
|
renderers: {},
|
|
51
|
+
dialogs: {},
|
|
52
|
+
resettable: new Set(),
|
|
48
53
|
chain: [...rules, ...builtinRules],
|
|
49
54
|
};
|
|
50
55
|
const group = resolveShape(schema.nodes, '', '', sink);
|
|
51
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
group,
|
|
58
|
+
sections: sink.sections,
|
|
59
|
+
showIf: sink.showIf,
|
|
60
|
+
renderers: sink.renderers,
|
|
61
|
+
dialogs: sink.dialogs,
|
|
62
|
+
resettable: sink.resettable,
|
|
63
|
+
};
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
/** A section while it is still being filled. */
|
|
@@ -80,7 +92,9 @@ function resolveShape(shape: ConfigShape, at: string, name: string, sink: Sink):
|
|
|
80
92
|
if (section.pack !== undefined && spec.pack === undefined) spec.pack = section.pack;
|
|
81
93
|
}
|
|
82
94
|
if (predicate) sink.showIf.set(path, predicate);
|
|
95
|
+
if (isConfigBranch(entry) && entry.options.resettable) sink.resettable.add(path);
|
|
83
96
|
if (!isConfigBranch(entry) && entry.options.render) sink.renderers[path] = entry.options.render;
|
|
97
|
+
if (!isConfigBranch(entry) && entry.options.dialog) sink.dialogs[path] = entry.options.dialog;
|
|
84
98
|
}
|
|
85
99
|
|
|
86
100
|
return { name, children };
|
|
@@ -70,14 +70,19 @@ describe('sectionTree', () => {
|
|
|
70
70
|
'weight',
|
|
71
71
|
]);
|
|
72
72
|
const off = sectionTree(resolved, { lineNumbers: false, listing: { dim: 1, weight: 0 } });
|
|
73
|
-
expect(Object.keys(groupAt(groupAt(off.group, 'view'), 'listing').children)).toEqual([
|
|
73
|
+
expect(Object.keys(groupAt(groupAt(off.group, 'view'), 'listing').children)).toEqual([
|
|
74
|
+
'weight',
|
|
75
|
+
]);
|
|
74
76
|
});
|
|
75
77
|
|
|
76
78
|
it('drops a section nothing in it is showing, rather than opening it onto nothing', () => {
|
|
77
79
|
const resolved = resolveConfigSchema(
|
|
78
80
|
f.schema({
|
|
79
81
|
mode: f.enum<string>('a', ['a', 'b']).section('View'),
|
|
80
|
-
glow: f
|
|
82
|
+
glow: f
|
|
83
|
+
.number(1)
|
|
84
|
+
.section('Diff')
|
|
85
|
+
.showIf((c) => c.mode === 'b'),
|
|
81
86
|
}),
|
|
82
87
|
);
|
|
83
88
|
expect(Object.keys(sectionTree(resolved, { mode: 'b', glow: 1 }).group.children)).toEqual([
|
|
@@ -91,7 +96,10 @@ describe('sectionTree', () => {
|
|
|
91
96
|
|
|
92
97
|
it('keeps a hidden leaf out unless asked for it', () => {
|
|
93
98
|
const resolved = resolveConfigSchema(
|
|
94
|
-
f.schema({
|
|
99
|
+
f.schema({
|
|
100
|
+
seed: f.number(1).hidden().section('Debug'),
|
|
101
|
+
route: f.boolean(true).section('Debug'),
|
|
102
|
+
}),
|
|
95
103
|
);
|
|
96
104
|
expect(Object.keys(groupAt(sectionTree(resolved, {}).group, 'debug').children)).toEqual([
|
|
97
105
|
'route',
|
|
@@ -134,9 +142,7 @@ describe('sectionTree', () => {
|
|
|
134
142
|
it('ignores a section nested under a group, which buckets rows in a pane', () => {
|
|
135
143
|
const resolved = resolveConfigSchema(
|
|
136
144
|
f.schema({
|
|
137
|
-
cards: f
|
|
138
|
-
.group({ floor: f.number(1).section('Size') })
|
|
139
|
-
.section('View'),
|
|
145
|
+
cards: f.group({ floor: f.number(1).section('Size') }).section('View'),
|
|
140
146
|
}),
|
|
141
147
|
);
|
|
142
148
|
const { group } = sectionTree(resolved, { cards: { floor: 1 } });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';
|
|
2
2
|
import { valueAtPath } from './path';
|
|
3
3
|
import type { ResolvedConfig } from './types';
|
|
4
4
|
import { isLeafVisible } from './visible';
|
|
@@ -22,10 +22,20 @@ export interface SectionTree {
|
|
|
22
22
|
pathAt: (railPath: string) => string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Structural rather than `isPrefLeaf`, so the config entry stays free of a
|
|
26
|
+
* runtime import from ui. */
|
|
27
|
+
function isLeaf(node: PrefLeaf | PrefGroup): node is PrefLeaf {
|
|
28
|
+
return 'kind' in node;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
/** A section label as an object key: no dots, since a dotted path is how every
|
|
26
32
|
* caller addresses a node, and a label is free to hold one. */
|
|
27
33
|
function slugOf(label: string, taken: ReadonlySet<string>): string {
|
|
28
|
-
const base =
|
|
34
|
+
const base =
|
|
35
|
+
label
|
|
36
|
+
.toLowerCase()
|
|
37
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
38
|
+
.replace(/^-|-$/g, '') || 'section';
|
|
29
39
|
if (!taken.has(base)) return base;
|
|
30
40
|
for (let n = 2; ; n += 1) if (!taken.has(`${base}-${n}`)) return `${base}-${n}`;
|
|
31
41
|
}
|
|
@@ -44,7 +54,7 @@ function prune(
|
|
|
44
54
|
for (const [key, child] of Object.entries(group.children)) {
|
|
45
55
|
const path = at === '' ? key : `${at}.${key}`;
|
|
46
56
|
if (!isLeafVisible(resolved, path, config, showHidden)) continue;
|
|
47
|
-
if (
|
|
57
|
+
if (isLeaf(child)) {
|
|
48
58
|
children[key] = child;
|
|
49
59
|
continue;
|
|
50
60
|
}
|
|
@@ -81,7 +91,7 @@ export function sectionTree(
|
|
|
81
91
|
claimed.add(path);
|
|
82
92
|
const child = resolved.group.children[path];
|
|
83
93
|
if (!child || !isLeafVisible(resolved, path, config, showHidden)) continue;
|
|
84
|
-
if (
|
|
94
|
+
if (isLeaf(child)) {
|
|
85
95
|
kept[path] = child;
|
|
86
96
|
} else {
|
|
87
97
|
const subtree = prune(resolved, child, path, config, showHidden);
|
|
@@ -101,7 +111,7 @@ export function sectionTree(
|
|
|
101
111
|
// gathers loose leaves into one item named for the root.
|
|
102
112
|
for (const [key, child] of Object.entries(resolved.group.children)) {
|
|
103
113
|
if (claimed.has(key) || !isLeafVisible(resolved, key, config, showHidden)) continue;
|
|
104
|
-
if (
|
|
114
|
+
if (isLeaf(child)) {
|
|
105
115
|
children[key] = child;
|
|
106
116
|
} else {
|
|
107
117
|
const subtree = prune(resolved, child, key, config, showHidden);
|
package/src/config/types.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
PrefNumberFormat,
|
|
5
|
-
PrefNumberUnit,
|
|
6
|
-
PrefRenderer,
|
|
7
|
-
} from '@weasel-js/ui';
|
|
1
|
+
import type { Display } from '@weasel-js/quantity';
|
|
2
|
+
import type { PrefGroup, PrefLeaf, PrefNumberUnit, PrefRenderer } from '@weasel-js/ui';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
6
|
* Renders the control cell for one config leaf. Identical to weasel-ui's
|
|
@@ -17,6 +13,9 @@ export type ControlRenderer = PrefRenderer;
|
|
|
17
13
|
export interface ConfigOption {
|
|
18
14
|
value: string;
|
|
19
15
|
label: string;
|
|
16
|
+
/** A glyph in weasel-ui's icon set (`IconName`). A segmented row draws it in
|
|
17
|
+
* place of `label`, which stays the accessible name and becomes the tooltip. */
|
|
18
|
+
icon?: string;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
/** Everything a leaf can carry beyond its kind and default. A flat union of
|
|
@@ -31,8 +30,8 @@ export interface Annotations {
|
|
|
31
30
|
max?: number;
|
|
32
31
|
step?: number;
|
|
33
32
|
suffix?: string;
|
|
34
|
-
/** How a number's value
|
|
35
|
-
|
|
33
|
+
/** How a number's value shows, speaks and reads back — `compact()` reads `2.00M`. */
|
|
34
|
+
display?: Display;
|
|
36
35
|
/** Display-unit conversion for a number stored in a canonical unit — the
|
|
37
36
|
* value, its bounds and its step all convert at the control's edge. */
|
|
38
37
|
unit?: PrefNumberUnit;
|
|
@@ -104,6 +103,26 @@ export interface ResolvedConfig {
|
|
|
104
103
|
showIf: ReadonlyMap<string, (config: Record<string, unknown>) => boolean>;
|
|
105
104
|
/** Node-level `.render` overrides, keyed by path. */
|
|
106
105
|
renderers: Readonly<Record<string, ControlRenderer>>;
|
|
106
|
+
/** Node-level `.dialog` rows, keyed by path. */
|
|
107
|
+
dialogs: Readonly<Record<string, DialogSpec>>;
|
|
108
|
+
/** Paths of the groups marked `.resettable()`, whose heading carries a
|
|
109
|
+
* button that puts every value beneath it back to its default. */
|
|
110
|
+
resettable?: ReadonlySet<string>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Options for a row drawn as a button that opens a dialog. */
|
|
114
|
+
export interface InDialogOptions {
|
|
115
|
+
/** What the row's button shows of the value. Defaults to a list joined with
|
|
116
|
+
* commas, or the value itself. */
|
|
117
|
+
summary?: (value: unknown) => ReactNode;
|
|
118
|
+
/** The dialog's heading. Defaults to the row's label. */
|
|
119
|
+
title?: ReactNode;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** A `.dialog` row: `body` is the dialog's content, handed the context a row
|
|
123
|
+
* renderer gets. */
|
|
124
|
+
export interface DialogSpec extends InDialogOptions {
|
|
125
|
+
body: ControlRenderer;
|
|
107
126
|
}
|
|
108
127
|
|
|
109
128
|
/** How a node names the section it belongs to: the heading, and how that
|
|
@@ -121,6 +140,9 @@ export interface SectionOption {
|
|
|
121
140
|
/** Per-node extras that do not belong on a `PrefLeaf`. */
|
|
122
141
|
export interface NodeOptions extends BranchOptions {
|
|
123
142
|
render?: ControlRenderer;
|
|
143
|
+
/** Draw the row as a button that opens a dialog. Set by `.dialog`; the
|
|
144
|
+
* config entry records it, and `ControlPanel` draws it. */
|
|
145
|
+
dialog?: DialogSpec;
|
|
124
146
|
/** Errors for this leaf under `config`, the instrument's whole config. */
|
|
125
147
|
validate?: (leaf: PrefLeaf, config: Record<string, unknown>) => string[];
|
|
126
148
|
/** Computes this leaf's value while it is auto. Attaching one is what gives
|
|
@@ -141,6 +163,9 @@ export interface BranchOptions {
|
|
|
141
163
|
/** Show this node only while the predicate holds. On a group it hides the
|
|
142
164
|
* whole subtree; the values stay in config either way. */
|
|
143
165
|
showIf?: (config: Record<string, unknown>) => boolean;
|
|
166
|
+
/** Put a button on this group's heading that returns every value beneath it
|
|
167
|
+
* to its default. Groups only. */
|
|
168
|
+
resettable?: boolean;
|
|
144
169
|
}
|
|
145
170
|
|
|
146
171
|
/** A group's own annotations: what it is called and, optionally, why. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ANGLE_RADIANS, prefUnit } from '@weasel-js/core';
|
|
2
2
|
import type { Meta, StoryObj } from '@weasel-js/forge';
|
|
3
|
+
import { compact } from '@weasel-js/quantity';
|
|
3
4
|
import { type PrefNumberUnit, PropertyRow } from '@weasel-js/ui';
|
|
4
5
|
import { useState } from 'react';
|
|
5
6
|
import { f } from '../config/builder';
|
|
@@ -278,7 +279,7 @@ export const Presentation: Story = {
|
|
|
278
279
|
|
|
279
280
|
const inline = f.schema({
|
|
280
281
|
folderPad: f.number(1.1).range(0, 4).step(0.1).label('Folder pad'),
|
|
281
|
-
glyphs: f.number(1_000_000).range(0, 2_000_000).
|
|
282
|
+
glyphs: f.number(1_000_000).range(0, 2_000_000).display(compact()),
|
|
282
283
|
names: f.number(60).range(0, 100),
|
|
283
284
|
labels: f.number(0).range(0, 100),
|
|
284
285
|
placement: f.enum('spread', ['spread', 'stack', 'ring']),
|
|
@@ -337,7 +338,7 @@ const sidebarSliders = f.schema({
|
|
|
337
338
|
pad: f.number(0.5).range(0, 4).step(0.25).label('Window pad'),
|
|
338
339
|
folderPad: f.number(1).range(0, 4).step(0.1).label('Folder pad'),
|
|
339
340
|
levels: f.number(2).range(0, 6).step(1).label('Levels'),
|
|
340
|
-
glyphs: f.number(1_000_000).range(0, 2_000_000).step(2000).
|
|
341
|
+
glyphs: f.number(1_000_000).range(0, 2_000_000).step(2000).display(compact()).label('Glyphs'),
|
|
341
342
|
names: f.number(40).range(0, 200).step(5).label('Names'),
|
|
342
343
|
labels: f.number(0).range(0, 30).step(1).label('Labels'),
|
|
343
344
|
text: f.number(100).range(50, 250).step(5).unit(shownIn('%')).label('Text'),
|