@weasel-js/labkit 1.1.0 → 1.3.0-pre.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 +27 -0
- package/dist/_dts/{DrawCommand-BkZztJsW.d.ts → DrawCommand-DBB45NfN.d.ts} +244 -58
- package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
- package/dist/_dts/index-DgcNdEdh.d.ts +314 -0
- package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
- package/dist/_dts/types-AHlQxBNN.d.ts +162 -0
- package/dist/_dts/types-DJ79Tg5J.d.ts +56 -0
- package/dist/_dts/{useTrialState-DYe2vUwN.d.ts → useTrialState-CsGMjhu9.d.ts} +6 -92
- package/dist/canvas/index.d.ts +8 -3
- package/dist/canvas/index.js +2 -1
- package/dist/chrome/index.d.ts +86 -0
- package/dist/chrome/index.js +8 -0
- package/dist/chrome/index.js.map +1 -0
- package/dist/{chunk-3TYUJR7Z.js → chunk-4TMMVIDM.js} +9 -3
- package/dist/chunk-4TMMVIDM.js.map +1 -0
- package/dist/chunk-HFDVGF4X.js +170 -0
- package/dist/chunk-HFDVGF4X.js.map +1 -0
- package/dist/chunk-LN6JDUGB.js +106 -0
- package/dist/chunk-LN6JDUGB.js.map +1 -0
- package/dist/{chunk-574LJAV4.js → chunk-NS54R52R.js} +31 -21
- package/dist/chunk-NS54R52R.js.map +1 -0
- package/dist/{chunk-NRKWVTVT.js → chunk-O2BULFHX.js} +5 -14
- package/dist/chunk-O2BULFHX.js.map +1 -0
- package/dist/chunk-O7NVSCLN.js +266 -0
- package/dist/chunk-O7NVSCLN.js.map +1 -0
- package/dist/chunk-PO6L3NSH.js +366 -0
- package/dist/chunk-PO6L3NSH.js.map +1 -0
- package/dist/chunk-PQJ5B2U2.js +27 -0
- package/dist/chunk-PQJ5B2U2.js.map +1 -0
- package/dist/chunk-RVNN4CHQ.js +212 -0
- package/dist/chunk-RVNN4CHQ.js.map +1 -0
- package/dist/chunk-TLGRYALP.js +6151 -0
- package/dist/chunk-TLGRYALP.js.map +1 -0
- package/dist/{chunk-SFL7NFKN.js → chunk-V7PRSIRQ.js} +15 -7
- package/dist/chunk-V7PRSIRQ.js.map +1 -0
- package/dist/{chunk-DJLDIRFN.js → chunk-Z6HJ5FEM.js} +9626 -6050
- package/dist/chunk-Z6HJ5FEM.js.map +1 -0
- package/dist/chunk-ZZAYVX4X.js +505 -0
- package/dist/chunk-ZZAYVX4X.js.map +1 -0
- package/dist/controls/index.d.ts +19 -5
- package/dist/controls/index.js +3 -1
- package/dist/dragdrop/index.d.ts +5 -2
- package/dist/index.d.ts +307 -107
- package/dist/index.js +797 -540
- package/dist/index.js.map +1 -1
- package/dist/job/index.d.ts +13 -0
- package/dist/job/index.js +3 -0
- package/dist/job/index.js.map +1 -0
- package/dist/layers/index.d.ts +6 -3
- package/dist/layers/index.js +2 -1
- package/dist/passthrough/weasel-canvas.d.ts +69 -13
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +3804 -3661
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/primitives/index.d.ts +102 -8
- package/dist/primitives/index.js +4 -1
- package/dist/state/index.d.ts +3 -2
- package/dist/state/index.js +2 -2
- package/dist/styles.css +543 -144
- package/dist/surface/index.d.ts +77 -0
- package/dist/surface/index.js +4 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/ui/layers/index.js +4 -3
- package/dist/undo/index.d.ts +5 -2
- package/package.json +24 -7
- package/src/canvas/AGENTS.md +12 -0
- package/src/canvas/CanvasStack.tsx +6 -2
- package/src/canvas/useLayerScheduler.ts +22 -15
- package/src/canvas/useOrbit.test.ts +71 -0
- package/src/canvas/useOrbit.ts +149 -0
- package/src/canvas/usePanZoom.test.ts +80 -0
- package/src/canvas/usePanZoom.ts +16 -2
- package/src/chrome/ChromeRegions.stories.tsx +106 -0
- package/src/chrome/builtins.test.ts +153 -0
- package/src/chrome/builtins.tsx +205 -0
- package/src/chrome/index.ts +25 -0
- package/src/chrome/merge.test.ts +51 -0
- package/src/chrome/merge.ts +52 -0
- package/src/chrome/regions/PaletteRegion.less +14 -0
- package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
- package/src/chrome/regions/PaletteRegion.tsx +41 -0
- package/src/chrome/regions/SidebarRegion.tsx +56 -0
- package/src/chrome/regions/StatusRegion.tsx +31 -0
- package/src/chrome/regions/TitleBarRegion.tsx +41 -0
- package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
- package/src/chrome/regions/ToolbarRegion.tsx +71 -0
- package/src/chrome/regions/ViewportRegion.less +42 -0
- package/src/chrome/regions/ViewportRegion.tsx +37 -0
- package/src/chrome/regions/regions.test.tsx +81 -0
- package/src/chrome/types.ts +127 -0
- package/src/config/builder.test.ts +87 -0
- package/src/config/builder.ts +224 -0
- package/src/config/fromConfigField.test.ts +93 -0
- package/src/config/fromConfigField.ts +74 -0
- package/src/config/index.ts +22 -0
- package/src/config/resolve.test.ts +120 -0
- package/src/config/resolve.ts +67 -0
- package/src/config/rules.ts +68 -0
- package/src/config/types.ts +108 -0
- package/src/config/useConfigSchema.ts +30 -0
- package/src/config/visible.ts +20 -0
- package/src/controls/ControlPanel.less +4 -58
- package/src/controls/ControlPanel.stories.tsx +86 -0
- package/src/controls/ControlPanel.test.tsx +223 -10
- package/src/controls/ControlPanel.tsx +202 -203
- package/src/dragdrop/DragDropRuntime.tsx +36 -18
- package/src/dragdrop/DragGhost.less +1 -1
- package/src/dragdrop/Palette.less +1 -1
- package/src/index.test.ts +18 -0
- package/src/index.ts +38 -3
- package/src/instrument/SineWave.smoke.test.tsx +9 -1
- package/src/instrument/defineInstrument.test.ts +38 -1
- package/src/instrument/defineInstrument.ts +31 -4
- package/src/instrument/index.ts +1 -0
- package/src/instrument/types.ts +38 -3
- package/src/instrument/validateConfigSchema.test.ts +13 -3
- package/src/instrument/validateConfigSchema.ts +7 -10
- package/src/job/index.ts +3 -0
- package/src/job/types.ts +47 -0
- package/src/job/useJob.test.tsx +210 -0
- package/src/job/useJob.ts +134 -0
- package/src/lab/Lab.chrome.test.tsx +65 -0
- package/src/lab/Lab.stories.tsx +1 -2
- package/src/lab/Lab.tsx +58 -15
- package/src/lab/LabContext.ts +6 -0
- package/src/lab/LabFullChrome.stories.tsx +308 -0
- package/src/lab/LabHeader.test.tsx +42 -0
- package/src/lab/LabHeader.tsx +57 -0
- package/src/lab/LabPalette.test.tsx +35 -0
- package/src/lab/LabPalette.tsx +34 -0
- package/src/lab/LabShell.less +50 -3
- package/src/lab/Workspace.less +1 -30
- package/src/lab/Workspace.surface.test.tsx +49 -0
- package/src/lab/Workspace.test.tsx +20 -10
- package/src/lab/Workspace.tsx +19 -9
- package/src/lab/index.ts +2 -0
- package/src/layers/AGENTS.md +1 -1
- package/src/layers/LayerList.less +31 -7
- package/src/layers/LayerList.tsx +17 -4
- package/src/passthrough/weasel-ui.ts +23 -0
- package/src/primitives/DragHandleGlyph.tsx +29 -0
- package/src/primitives/FloatingPanel.less +28 -0
- package/src/primitives/FloatingPanel.stories.tsx +46 -0
- package/src/primitives/FloatingPanel.test.tsx +240 -0
- package/src/primitives/FloatingPanel.tsx +190 -0
- package/src/primitives/FpsMeter.test.tsx +44 -0
- package/src/primitives/FpsMeter.tsx +20 -9
- package/src/primitives/JobProgress.less +69 -0
- package/src/primitives/JobProgress.stories.tsx +48 -0
- package/src/primitives/JobProgress.tsx +65 -0
- package/src/primitives/Legend.less +47 -0
- package/src/primitives/Legend.stories.tsx +32 -0
- package/src/primitives/Legend.test.tsx +57 -0
- package/src/primitives/Legend.tsx +40 -0
- package/src/primitives/Sidebar.less +32 -2
- package/src/primitives/StatusBar.less +7 -0
- package/src/primitives/StatusBar.tsx +10 -3
- package/src/primitives/Toolbar.less +50 -3
- package/src/primitives/Toolbar.test.tsx +67 -1
- package/src/primitives/Toolbar.tsx +72 -6
- package/src/primitives/ZoomControl.less +31 -0
- package/src/primitives/ZoomControl.tsx +76 -0
- package/src/primitives/floating.entry.test.tsx +14 -0
- package/src/primitives/index.ts +10 -2
- package/src/primitives/useRovingTabIndex.test.ts +27 -0
- package/src/primitives/useRovingTabIndex.ts +79 -0
- package/src/state/store.ts +16 -2
- package/src/state/toolSlot.test.ts +36 -0
- package/src/state/types.ts +8 -2
- package/src/state/view.test.ts +127 -0
- package/src/state/view.ts +18 -0
- package/src/styles.less +6 -0
- package/src/surface/AGENTS.md +64 -0
- package/src/surface/SurfaceContext.ts +5 -0
- package/src/surface/composeRects.test.ts +50 -0
- package/src/surface/composeRects.ts +19 -0
- package/src/surface/deviceRect.test.ts +40 -0
- package/src/surface/deviceRect.ts +19 -0
- package/src/surface/index.ts +7 -0
- package/src/surface/rect.ts +16 -0
- package/src/surface/useSurfaceTile.test.tsx +67 -0
- package/src/surface/useSurfaceTile.ts +32 -0
- package/src/surface/useTiledSurface.test.tsx +231 -0
- package/src/surface/useTiledSurface.ts +161 -0
- package/src/test-setup.ts +26 -0
- package/src/theme/base.less +19 -2
- package/src/theme/interstellar.tokens.json +6 -2
- package/src/tools/types.ts +24 -0
- package/src/trial/Trial.config.test.tsx +113 -0
- package/src/trial/Trial.job.test.tsx +73 -0
- package/src/trial/Trial.less +121 -4
- package/src/trial/Trial.stories.tsx +9 -1
- package/src/trial/Trial.test.tsx +67 -96
- package/src/trial/Trial.tsx +112 -45
- package/src/trial/TrialChrome.tsx +110 -64
- package/src/trial/TrialDragContext.ts +10 -0
- package/src/trial/TrialTitleBar.tsx +41 -0
- package/src/trial/index.ts +1 -15
- package/src/trial/trialChrome.borders.test.ts +38 -0
- package/src/ui/layers/LayerStack.less +9 -9
- package/src/ui/layers/LayerStack.tsx +1 -13
- package/src/ui/properties/CurveField.less +2 -2
- package/src/ui/properties/CurveField.test.tsx +11 -11
- package/src/ui/properties/PropertyGroup.less +2 -2
- package/src/ui/properties/PropertyPanel.less +55 -41
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +4 -2
- package/src/ui/properties/storyLayouts.tsx +2 -2
- package/dist/_dts/index-iAP6XbH3.d.ts +0 -132
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/chunk-3TYUJR7Z.js.map +0 -1
- package/dist/chunk-574LJAV4.js.map +0 -1
- package/dist/chunk-5R2ATYPJ.js +0 -201
- package/dist/chunk-5R2ATYPJ.js.map +0 -1
- package/dist/chunk-C6GJKPUI.js +0 -5986
- package/dist/chunk-C6GJKPUI.js.map +0 -1
- package/dist/chunk-DJLDIRFN.js.map +0 -1
- package/dist/chunk-NRKWVTVT.js.map +0 -1
- package/dist/chunk-PMAU3SEE.js +0 -126
- package/dist/chunk-PMAU3SEE.js.map +0 -1
- package/dist/chunk-SFL7NFKN.js.map +0 -1
- package/src/instrument/capabilityDetector.test.ts +0 -64
- package/src/instrument/capabilityDetector.ts +0 -20
- package/src/trial/DefaultSidebar.tsx +0 -29
- package/src/trial/DefaultStatusBar.tsx +0 -19
- package/src/trial/DefaultToolbar.tsx +0 -92
- package/src/trial/slotTypes.ts +0 -53
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
margin: 0;
|
|
15
15
|
font-family: var(--wzl-font-display);
|
|
16
16
|
font-weight: var(--wzl-font-weight-light);
|
|
17
|
-
font-size:
|
|
17
|
+
font-size: var(--wzl-font-size-lg);
|
|
18
18
|
color: var(--wzl-fg);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
border: 1px dashed var(--wzl-border);
|
|
29
29
|
border-radius: var(--wzl-radius-sm);
|
|
30
30
|
padding: 2px 8px;
|
|
31
|
-
font-size:
|
|
31
|
+
font-size: var(--wzl-font-size-sm);
|
|
32
32
|
color: var(--wzl-fg-subtle);
|
|
33
33
|
cursor: pointer;
|
|
34
34
|
}
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
.lk-layer-stack__drop-hint {
|
|
47
47
|
height: 2px;
|
|
48
48
|
background: var(--wzl-accent);
|
|
49
|
-
border-radius:
|
|
49
|
+
border-radius: var(--wzl-radius-sm);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
.lk-layer-stack__empty {
|
|
53
53
|
color: var(--wzl-fg-subtle);
|
|
54
|
-
font-size:
|
|
54
|
+
font-size: var(--wzl-font-size);
|
|
55
55
|
padding: 8px;
|
|
56
56
|
text-align: center;
|
|
57
57
|
}
|
|
@@ -96,8 +96,8 @@
|
|
|
96
96
|
min-width: 22px;
|
|
97
97
|
height: 22px;
|
|
98
98
|
border-radius: var(--wzl-radius-sm);
|
|
99
|
-
font-weight:
|
|
100
|
-
font-size:
|
|
99
|
+
font-weight: var(--wzl-font-weight-bold);
|
|
100
|
+
font-size: var(--wzl-font-size);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
.lk-layer-card.has-accent .lk-layer-card__handle {
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
.lk-layer-card__kind,
|
|
113
113
|
.lk-layer-card__primary {
|
|
114
114
|
flex: 1;
|
|
115
|
-
font-size:
|
|
115
|
+
font-size: var(--wzl-font-size);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
.lk-layer-card__primary {
|
|
@@ -121,14 +121,14 @@
|
|
|
121
121
|
color: var(--wzl-fg);
|
|
122
122
|
font: inherit;
|
|
123
123
|
text-transform: uppercase;
|
|
124
|
-
letter-spacing:
|
|
124
|
+
letter-spacing: var(--wzl-tracking-wide);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
.lk-root .lk-layer-card__remove {
|
|
128
128
|
background: transparent;
|
|
129
129
|
border: 0;
|
|
130
130
|
color: var(--wzl-fg-subtle);
|
|
131
|
-
font-size:
|
|
131
|
+
font-size: var(--wzl-font-size-lg);
|
|
132
132
|
cursor: pointer;
|
|
133
133
|
padding: 2px 6px;
|
|
134
134
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CSSProperties, type ReactNode, type RefCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { dlog, useReorderDragList } from '../../passthrough/weasel-ui';
|
|
3
|
+
import { DragHandleGlyph } from '../../primitives/DragHandleGlyph';
|
|
3
4
|
|
|
4
5
|
/** One card in a layer stack: its identity, the label shown when collapsed,
|
|
5
6
|
* and the optional select hoisted into its header. */
|
|
@@ -205,16 +206,3 @@ export function LayerStack({
|
|
|
205
206
|
</div>
|
|
206
207
|
);
|
|
207
208
|
}
|
|
208
|
-
|
|
209
|
-
function DragHandleGlyph() {
|
|
210
|
-
return (
|
|
211
|
-
<svg width="12" height="16" viewBox="0 0 8 16" fill="currentColor" aria-hidden="true">
|
|
212
|
-
<circle cx="2" cy="3" r="1.1" />
|
|
213
|
-
<circle cx="6" cy="3" r="1.1" />
|
|
214
|
-
<circle cx="2" cy="8" r="1.1" />
|
|
215
|
-
<circle cx="6" cy="8" r="1.1" />
|
|
216
|
-
<circle cx="2" cy="13" r="1.1" />
|
|
217
|
-
<circle cx="6" cy="13" r="1.1" />
|
|
218
|
-
</svg>
|
|
219
|
-
);
|
|
220
|
-
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
display: flex;
|
|
20
20
|
justify-content: space-between;
|
|
21
21
|
font-variant-numeric: tabular-nums;
|
|
22
|
-
font-size:
|
|
22
|
+
font-size: var(--wzl-font-size);
|
|
23
23
|
color: var(--wzl-fg-subtle);
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
border-radius: var(--wzl-radius-sm);
|
|
40
40
|
color: var(--wzl-fg);
|
|
41
41
|
padding: 4px 10px;
|
|
42
|
-
font-size:
|
|
42
|
+
font-size: var(--wzl-font-size-sm);
|
|
43
43
|
cursor: pointer;
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { assert, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
|
4
4
|
// CurveEditor uses weasel's own bundled React and fails in jsdom.
|
|
5
5
|
// Mock it out — these tests only assert on readouts and the flip button.
|
|
@@ -81,13 +81,13 @@ describe('CurveField', () => {
|
|
|
81
81
|
/>,
|
|
82
82
|
);
|
|
83
83
|
const overlay = container.querySelector('.lk-curve-field__marks');
|
|
84
|
-
|
|
85
|
-
const rect = overlay
|
|
86
|
-
|
|
84
|
+
assert(overlay);
|
|
85
|
+
const rect = overlay.querySelector('rect');
|
|
86
|
+
assert(rect);
|
|
87
87
|
// x = 0.2 * 200 = 40; width = (0.4 - 0.2) * 200 = 40.
|
|
88
|
-
expect(rect
|
|
89
|
-
expect(rect
|
|
90
|
-
expect(rect
|
|
88
|
+
expect(rect.getAttribute('x')).toBe('40');
|
|
89
|
+
expect(rect.getAttribute('width')).toBe('40');
|
|
90
|
+
expect(rect.getAttribute('fill')).toBe('#ffcc00');
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
it('renders a line mark as a positioned line', () => {
|
|
@@ -104,10 +104,10 @@ describe('CurveField', () => {
|
|
|
104
104
|
/>,
|
|
105
105
|
);
|
|
106
106
|
const line = container.querySelector('.lk-curve-field__marks line');
|
|
107
|
-
|
|
108
|
-
expect(line
|
|
109
|
-
expect(line
|
|
110
|
-
expect(line
|
|
107
|
+
assert(line);
|
|
108
|
+
expect(line.getAttribute('x1')).toBe('140');
|
|
109
|
+
expect(line.getAttribute('x2')).toBe('140');
|
|
110
|
+
expect(line.getAttribute('stroke')).toBe('#ffcc00');
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
it('omits the marks overlay when marks is undefined', () => {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
margin: 0 0 8px;
|
|
14
14
|
font-family: var(--wzl-font-display);
|
|
15
15
|
font-weight: var(--wzl-font-weight-light);
|
|
16
|
-
font-size:
|
|
17
|
-
letter-spacing:
|
|
16
|
+
font-size: var(--wzl-font-size-sm);
|
|
17
|
+
letter-spacing: var(--wzl-tracking-wider);
|
|
18
18
|
text-transform: uppercase;
|
|
19
19
|
color: var(--wzl-fg-subtle);
|
|
20
20
|
}
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
.lk-property-panel__title {
|
|
16
16
|
font-family: var(--wzl-font-display);
|
|
17
17
|
font-weight: var(--wzl-font-weight-light);
|
|
18
|
-
font-size:
|
|
19
|
-
line-height:
|
|
18
|
+
font-size: var(--wzl-font-size-lg);
|
|
19
|
+
line-height: var(--wzl-leading-snug);
|
|
20
20
|
letter-spacing: 0.01em;
|
|
21
21
|
color: var(--wzl-fg);
|
|
22
22
|
margin: 0 0 12px;
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
.lk-property-row__label {
|
|
86
86
|
font-family: var(--wzl-font-display);
|
|
87
87
|
font-weight: var(--wzl-font-weight-light);
|
|
88
|
-
font-size:
|
|
89
|
-
line-height:
|
|
88
|
+
font-size: var(--wzl-font-size-sm);
|
|
89
|
+
line-height: var(--wzl-leading-snug);
|
|
90
90
|
color: var(--wzl-fg-muted);
|
|
91
91
|
text-transform: uppercase;
|
|
92
92
|
letter-spacing: 0.05em;
|
|
@@ -108,14 +108,13 @@
|
|
|
108
108
|
.lk-property-row__readout-input {
|
|
109
109
|
font: inherit;
|
|
110
110
|
font-weight: var(--wzl-font-weight-medium);
|
|
111
|
-
// Force
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
line-height: 1;
|
|
111
|
+
// Force leading-tight — the label parent inherits 1.2, which would make the
|
|
112
|
+
// input box ~3px taller than the speech-balloons reference.
|
|
113
|
+
line-height: var(--wzl-leading-tight);
|
|
115
114
|
color: var(--wzl-accent);
|
|
116
115
|
background: transparent;
|
|
117
116
|
border: 1px solid transparent;
|
|
118
|
-
border-radius:
|
|
117
|
+
border-radius: var(--wzl-radius-sm);
|
|
119
118
|
padding: 1px 0;
|
|
120
119
|
margin: 0;
|
|
121
120
|
width: calc(5em - 24px);
|
|
@@ -126,10 +125,10 @@
|
|
|
126
125
|
-moz-appearance: textfield;
|
|
127
126
|
}
|
|
128
127
|
.lk-property-row__readout-input:hover {
|
|
129
|
-
background:
|
|
128
|
+
background: color-mix(in srgb, currentColor 8%, transparent);
|
|
130
129
|
}
|
|
131
130
|
.lk-property-row__readout-input:focus {
|
|
132
|
-
background:
|
|
131
|
+
background: var(--wzl-surface-sunken);
|
|
133
132
|
border-color: var(--wzl-accent);
|
|
134
133
|
}
|
|
135
134
|
.lk-property-row__readout-input::-webkit-inner-spin-button,
|
|
@@ -148,8 +147,8 @@
|
|
|
148
147
|
// Word-style units (e.g. "px") read as a small dim suffix next to the digits.
|
|
149
148
|
.lk-property-row__readout-unit {
|
|
150
149
|
color: var(--wzl-fg-muted);
|
|
151
|
-
font-size:
|
|
152
|
-
line-height:
|
|
150
|
+
font-size: var(--wzl-font-size-2xs);
|
|
151
|
+
line-height: var(--wzl-leading-tight);
|
|
153
152
|
pointer-events: none;
|
|
154
153
|
}
|
|
155
154
|
// Symbol units (e.g. `<sup>°</sup>`) lean on the browser's native super
|
|
@@ -157,7 +156,7 @@
|
|
|
157
156
|
// would re-collapse the glyph onto the baseline.
|
|
158
157
|
.lk-property-row__readout-group sup {
|
|
159
158
|
color: var(--wzl-fg-muted);
|
|
160
|
-
font-size:
|
|
159
|
+
font-size: var(--wzl-font-size-2xs);
|
|
161
160
|
pointer-events: none;
|
|
162
161
|
}
|
|
163
162
|
|
|
@@ -174,12 +173,12 @@
|
|
|
174
173
|
}
|
|
175
174
|
.lk-property-row input[type='range']::-webkit-slider-runnable-track {
|
|
176
175
|
height: 4px;
|
|
177
|
-
border-radius:
|
|
176
|
+
border-radius: var(--wzl-radius-sm);
|
|
178
177
|
background: color-mix(in srgb, var(--wzl-accent) 18%, transparent);
|
|
179
178
|
}
|
|
180
179
|
.lk-property-row input[type='range']::-moz-range-track {
|
|
181
180
|
height: 4px;
|
|
182
|
-
border-radius:
|
|
181
|
+
border-radius: var(--wzl-radius-sm);
|
|
183
182
|
background: color-mix(in srgb, var(--wzl-accent) 18%, transparent);
|
|
184
183
|
}
|
|
185
184
|
.lk-property-row input[type='range']::-webkit-slider-thumb {
|
|
@@ -225,12 +224,15 @@
|
|
|
225
224
|
|
|
226
225
|
.lk-property-row input[type='text']:not(.lk-property-row__readout-input),
|
|
227
226
|
.lk-property-row input[type='number'] {
|
|
228
|
-
background:
|
|
227
|
+
background: var(--wzl-surface-sunken);
|
|
229
228
|
color: var(--wzl-fg);
|
|
230
229
|
border: 1px solid var(--wzl-border);
|
|
231
230
|
border-radius: var(--wzl-radius-sm);
|
|
232
231
|
padding: 6px 8px;
|
|
233
|
-
font:
|
|
232
|
+
font-family: var(--wzl-font-ui);
|
|
233
|
+
font-weight: var(--wzl-font-weight-medium);
|
|
234
|
+
font-size: var(--wzl-font-size);
|
|
235
|
+
line-height: var(--wzl-leading-tight);
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
.lk-property-row input[type='text']:not(.lk-property-row__readout-input):focus,
|
|
@@ -248,7 +250,10 @@
|
|
|
248
250
|
border: 0;
|
|
249
251
|
border-radius: 0;
|
|
250
252
|
padding: 2px 0;
|
|
251
|
-
font:
|
|
253
|
+
font-family: var(--wzl-font-ui);
|
|
254
|
+
font-weight: var(--wzl-font-weight-medium);
|
|
255
|
+
font-size: var(--wzl-font-size);
|
|
256
|
+
line-height: var(--wzl-leading-tight);
|
|
252
257
|
cursor: pointer;
|
|
253
258
|
}
|
|
254
259
|
.lk-property-row select:focus {
|
|
@@ -344,7 +349,7 @@
|
|
|
344
349
|
.lk-property-row--checkbox .lk-property-row__label {
|
|
345
350
|
text-transform: none;
|
|
346
351
|
letter-spacing: normal;
|
|
347
|
-
font-size:
|
|
352
|
+
font-size: var(--wzl-font-size);
|
|
348
353
|
color: var(--wzl-fg);
|
|
349
354
|
// Place the label after the checkbox visually.
|
|
350
355
|
order: 1;
|
|
@@ -374,7 +379,10 @@
|
|
|
374
379
|
color: var(--wzl-fg-muted);
|
|
375
380
|
border: 0;
|
|
376
381
|
border-radius: 0;
|
|
377
|
-
font:
|
|
382
|
+
font-family: var(--wzl-font-ui);
|
|
383
|
+
font-weight: var(--wzl-font-weight-medium);
|
|
384
|
+
font-size: var(--wzl-font-size-sm);
|
|
385
|
+
line-height: var(--wzl-leading-tight);
|
|
378
386
|
cursor: pointer;
|
|
379
387
|
backdrop-filter: none;
|
|
380
388
|
-webkit-backdrop-filter: none;
|
|
@@ -391,12 +399,12 @@
|
|
|
391
399
|
|
|
392
400
|
.lk-property-row__toggle-button--selected {
|
|
393
401
|
background: var(--wzl-accent);
|
|
394
|
-
color:
|
|
402
|
+
color: var(--wzl-fg-on-accent);
|
|
395
403
|
font-weight: var(--wzl-font-weight-medium);
|
|
396
404
|
}
|
|
397
405
|
|
|
398
406
|
.lk-property-row__toggle-button--selected:hover {
|
|
399
|
-
color:
|
|
407
|
+
color: var(--wzl-fg-on-accent);
|
|
400
408
|
}
|
|
401
409
|
|
|
402
410
|
// ── Subpanel ────────────────────────────────────────────────────────
|
|
@@ -420,12 +428,12 @@
|
|
|
420
428
|
}
|
|
421
429
|
.lk-subpanel__title {
|
|
422
430
|
font-family: var(--wzl-font-display);
|
|
423
|
-
font-weight:
|
|
424
|
-
font-size:
|
|
425
|
-
line-height:
|
|
431
|
+
font-weight: var(--wzl-font-weight-medium);
|
|
432
|
+
font-size: var(--wzl-font-size-sm);
|
|
433
|
+
line-height: var(--wzl-leading-tight);
|
|
426
434
|
color: var(--wzl-fg);
|
|
427
435
|
text-transform: uppercase;
|
|
428
|
-
letter-spacing:
|
|
436
|
+
letter-spacing: var(--wzl-tracking-wider);
|
|
429
437
|
margin: 0;
|
|
430
438
|
padding: 6px 0 4px;
|
|
431
439
|
display: flex;
|
|
@@ -508,24 +516,27 @@
|
|
|
508
516
|
min-width: 14px;
|
|
509
517
|
padding: 0 2px;
|
|
510
518
|
color: var(--lk-panel-accent, var(--wzl-fg-muted));
|
|
511
|
-
font-size:
|
|
512
|
-
font-weight:
|
|
513
|
-
font-family:
|
|
519
|
+
font-size: var(--wzl-font-size);
|
|
520
|
+
font-weight: var(--wzl-font-weight-bold);
|
|
521
|
+
font-family: var(--wzl-font-mono);
|
|
514
522
|
line-height: 1;
|
|
515
523
|
}
|
|
516
524
|
|
|
517
525
|
.lk-effect-card__title {
|
|
518
526
|
font-family: var(--wzl-font-display);
|
|
519
|
-
font-weight:
|
|
520
|
-
font-size:
|
|
521
|
-
line-height:
|
|
527
|
+
font-weight: var(--wzl-font-weight-medium);
|
|
528
|
+
font-size: var(--wzl-font-size);
|
|
529
|
+
line-height: var(--wzl-leading-tight);
|
|
522
530
|
color: var(--wzl-fg);
|
|
523
531
|
text-transform: uppercase;
|
|
524
532
|
letter-spacing: 0.04em;
|
|
525
533
|
}
|
|
526
534
|
|
|
527
535
|
.lk-effect-card__primary {
|
|
528
|
-
font:
|
|
536
|
+
font-family: var(--wzl-font-ui);
|
|
537
|
+
font-weight: var(--wzl-font-weight-medium);
|
|
538
|
+
font-size: var(--wzl-font-size-sm);
|
|
539
|
+
line-height: var(--wzl-leading-tight);
|
|
529
540
|
color: var(--wzl-fg);
|
|
530
541
|
background: rgba(0, 0, 0, 0.3);
|
|
531
542
|
border: 1px solid var(--wzl-border);
|
|
@@ -547,19 +558,19 @@
|
|
|
547
558
|
background: transparent;
|
|
548
559
|
color: var(--wzl-fg-muted);
|
|
549
560
|
border: 1px solid transparent;
|
|
550
|
-
border-radius:
|
|
561
|
+
border-radius: var(--wzl-radius-sm);
|
|
551
562
|
width: 22px;
|
|
552
563
|
height: 22px;
|
|
553
|
-
font-size:
|
|
564
|
+
font-size: var(--wzl-font-size);
|
|
554
565
|
cursor: pointer;
|
|
555
566
|
display: inline-flex;
|
|
556
567
|
align-items: center;
|
|
557
568
|
justify-content: center;
|
|
558
569
|
}
|
|
559
570
|
.lk-effect-card__remove:hover {
|
|
560
|
-
color:
|
|
561
|
-
border-color:
|
|
562
|
-
background:
|
|
571
|
+
color: var(--wzl-danger-fg, var(--wzl-danger));
|
|
572
|
+
border-color: color-mix(in srgb, var(--wzl-danger) 40%, transparent);
|
|
573
|
+
background: color-mix(in srgb, var(--wzl-danger) 8%, transparent);
|
|
563
574
|
}
|
|
564
575
|
|
|
565
576
|
.lk-effect-card__body {
|
|
@@ -578,12 +589,15 @@
|
|
|
578
589
|
.lk-effect-card-list__drop-hint {
|
|
579
590
|
height: 0;
|
|
580
591
|
border-top: 2px solid var(--wzl-accent);
|
|
581
|
-
border-radius:
|
|
592
|
+
border-radius: var(--wzl-radius-sm);
|
|
582
593
|
margin: 2px 0;
|
|
583
594
|
box-shadow: 0 0 6px color-mix(in srgb, var(--wzl-accent) 60%, transparent);
|
|
584
595
|
}
|
|
585
596
|
.lk-effect-card-list__empty {
|
|
586
|
-
font: var(--wzl-font-
|
|
597
|
+
font-family: var(--wzl-font-display);
|
|
598
|
+
font-weight: var(--wzl-font-weight-light);
|
|
599
|
+
font-size: var(--wzl-font-size-sm);
|
|
600
|
+
line-height: var(--wzl-leading);
|
|
587
601
|
color: var(--wzl-fg-muted);
|
|
588
602
|
text-align: center;
|
|
589
603
|
padding: 16px 12px;
|
|
@@ -299,7 +299,8 @@ export const RightSidebarTails: Story = {
|
|
|
299
299
|
const next = prev.filter((t) => t.id !== sourceId);
|
|
300
300
|
const idx = next.findIndex((t) => t.id === targetId);
|
|
301
301
|
const insertAt = position === 'before' ? idx : idx + 1;
|
|
302
|
-
const src = prev.find((t) => t.id === sourceId)
|
|
302
|
+
const src = prev.find((t) => t.id === sourceId);
|
|
303
|
+
if (!src) return prev;
|
|
303
304
|
next.splice(insertAt, 0, src);
|
|
304
305
|
return next;
|
|
305
306
|
});
|
|
@@ -348,7 +349,8 @@ export const LayerStack: Story = {
|
|
|
348
349
|
const next = prev.filter((t) => t.id !== sourceId);
|
|
349
350
|
const idx = next.findIndex((t) => t.id === targetId);
|
|
350
351
|
const insertAt = position === 'before' ? idx : idx + 1;
|
|
351
|
-
const src = prev.find((t) => t.id === sourceId)
|
|
352
|
+
const src = prev.find((t) => t.id === sourceId);
|
|
353
|
+
if (!src) return prev;
|
|
352
354
|
next.splice(insertAt, 0, src);
|
|
353
355
|
return next;
|
|
354
356
|
});
|
|
@@ -7,10 +7,10 @@ import type { ReactNode } from 'react';
|
|
|
7
7
|
*/
|
|
8
8
|
export function SideBySide({ block, inline }: { block: ReactNode; inline: ReactNode }) {
|
|
9
9
|
const labelStyle = {
|
|
10
|
-
font: '
|
|
10
|
+
font: 'var(--wzl-font-weight-normal) var(--wzl-font-size-sm)/var(--wzl-leading-tight) var(--wzl-font-ui)',
|
|
11
11
|
color: 'var(--wzl-fg-muted)',
|
|
12
12
|
textTransform: 'uppercase' as const,
|
|
13
|
-
letterSpacing: '
|
|
13
|
+
letterSpacing: 'var(--wzl-tracking-wider)',
|
|
14
14
|
margin: '0 0 8px',
|
|
15
15
|
};
|
|
16
16
|
return (
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { C as ConfigField } from './types-x92Kfeme.js';
|
|
3
|
-
|
|
4
|
-
/** What an instrument's `render` is handed: its state and config, the setters
|
|
5
|
-
* for both, the trial it is mounted in, and a way to emit named events. */
|
|
6
|
-
interface RenderContext<TS = unknown, TC = unknown> {
|
|
7
|
-
state: TS;
|
|
8
|
-
config: TC;
|
|
9
|
-
setState: (next: TS | ((prev: TS) => TS)) => void;
|
|
10
|
-
setConfig: (key: keyof TC, value: unknown) => void;
|
|
11
|
-
trial: {
|
|
12
|
-
id: string;
|
|
13
|
-
zoom: number;
|
|
14
|
-
setZoom: (z: number) => void;
|
|
15
|
-
};
|
|
16
|
-
emit: (event: string) => void;
|
|
17
|
-
}
|
|
18
|
-
/** One 2D canvas layer of an instrument, drawn in declaration order.
|
|
19
|
-
*
|
|
20
|
-
* `draw` is called with the camera already applied, so it works in world
|
|
21
|
-
* coordinates. `zoom` is passed for the things that must not scale with it —
|
|
22
|
-
* set `ctx.lineWidth = 1 / zoom` to keep a hairline hairline. */
|
|
23
|
-
interface CanvasLayer<TS = unknown, TC = unknown> {
|
|
24
|
-
id: string;
|
|
25
|
-
draw: (ctx: CanvasRenderingContext2D, args: {
|
|
26
|
-
state: TS;
|
|
27
|
-
config: TC;
|
|
28
|
-
zoom: number;
|
|
29
|
-
}) => void;
|
|
30
|
-
}
|
|
31
|
-
/** Declares that an instrument draws to a canvas: its layers, and where the
|
|
32
|
-
* view starts. */
|
|
33
|
-
interface CanvasCapability<TS = unknown, TC = unknown> {
|
|
34
|
-
layers: CanvasLayer<TS, TC>[];
|
|
35
|
-
initialView?: {
|
|
36
|
-
zoom: number;
|
|
37
|
-
pan: {
|
|
38
|
-
x: number;
|
|
39
|
-
y: number;
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/** Declares which of an instrument's layers the trial should offer
|
|
44
|
-
* show/hide controls for. */
|
|
45
|
-
interface LayerCapability {
|
|
46
|
-
ids: string[];
|
|
47
|
-
}
|
|
48
|
-
/** Declares that an instrument accepts items dragged from a palette: what the
|
|
49
|
-
* palette offers, what a drop does to the state, and — optionally — live
|
|
50
|
-
* feedback during the drag and the ability to drag existing items back out. */
|
|
51
|
-
interface DragDropCapability<TS = unknown, TC = unknown> {
|
|
52
|
-
palette: PaletteItem[] | ((state: TS, config: TC) => PaletteItem[]);
|
|
53
|
-
onDrop: (worldPos: Point, item: PaletteItem, state: TS, config: TC) => TS;
|
|
54
|
-
onDragOver?: (worldPos: Point, item: PaletteItem, state: TS, config: TC) => DragFeedback | null;
|
|
55
|
-
pickUp?: (hit: HitResult, state: TS, config: TC) => {
|
|
56
|
-
item: PaletteItem;
|
|
57
|
-
state: TS;
|
|
58
|
-
} | null;
|
|
59
|
-
}
|
|
60
|
-
/** Declares that an instrument's state is undoable: which emitted events
|
|
61
|
-
* snapshot it, and how many snapshots to keep. */
|
|
62
|
-
interface UndoCapability {
|
|
63
|
-
snapshotOn?: string[];
|
|
64
|
-
maxDepth?: number;
|
|
65
|
-
}
|
|
66
|
-
/** The name of an event an instrument emits through `RenderContext.emit`. */
|
|
67
|
-
type SystemEvent = string;
|
|
68
|
-
/** A point in world coordinates. */
|
|
69
|
-
type Point = {
|
|
70
|
-
x: number;
|
|
71
|
-
y: number;
|
|
72
|
-
};
|
|
73
|
-
/** What a hit-test found, and where. */
|
|
74
|
-
type HitResult = {
|
|
75
|
-
hit: boolean;
|
|
76
|
-
layerId?: string;
|
|
77
|
-
pointId?: string;
|
|
78
|
-
};
|
|
79
|
-
/** A trial's camera. */
|
|
80
|
-
type ViewTransform = {
|
|
81
|
-
zoom: number;
|
|
82
|
-
pan: Point;
|
|
83
|
-
};
|
|
84
|
-
/** A layer as the layer list shows it. `alwaysOn` layers cannot be hidden. */
|
|
85
|
-
type LayerDescriptor = {
|
|
86
|
-
id: string;
|
|
87
|
-
label: string;
|
|
88
|
-
alwaysOn?: boolean;
|
|
89
|
-
};
|
|
90
|
-
/** One draggable entry in an instrument's palette. */
|
|
91
|
-
type PaletteItem = {
|
|
92
|
-
id: string;
|
|
93
|
-
label: string;
|
|
94
|
-
data?: unknown;
|
|
95
|
-
};
|
|
96
|
-
/** Whether a drop would be accepted at the current position, and why not if
|
|
97
|
-
* it would not. */
|
|
98
|
-
type DragFeedback = {
|
|
99
|
-
ok: boolean;
|
|
100
|
-
reason?: string;
|
|
101
|
-
};
|
|
102
|
-
/**
|
|
103
|
-
* An instrument: one self-contained interactive experiment a lab can host.
|
|
104
|
-
*
|
|
105
|
-
* It owns two pieces of data — `config`, the settings the control panel edits,
|
|
106
|
-
* and `state`, what the experiment is currently doing — and renders from both.
|
|
107
|
-
* The optional capability fields declare what else it wants from the runtime:
|
|
108
|
-
* a canvas, a layer list, palette drag-and-drop, undo. Declaring a capability
|
|
109
|
-
* is what makes the trial provide the corresponding chrome.
|
|
110
|
-
*/
|
|
111
|
-
interface Instrument<TS = unknown, TC = unknown> {
|
|
112
|
-
name: string;
|
|
113
|
-
defaultConfig: () => TC;
|
|
114
|
-
initialState: (config: TC) => TS;
|
|
115
|
-
configSchema?: () => ConfigField[];
|
|
116
|
-
/** The instrument's DOM. With `canvas`, this renders as an overlay above the
|
|
117
|
-
* layers rather than instead of them; return `null` for canvas only. */
|
|
118
|
-
render: (ctx: RenderContext<TS, TC>) => ReactNode;
|
|
119
|
-
onConfigChange?: (config: TC, prev: TC, state: TS) => TS;
|
|
120
|
-
serialize?: (state: TS) => unknown;
|
|
121
|
-
deserialize?: (data: unknown, config: TC) => TS;
|
|
122
|
-
canvas?: CanvasCapability<TS, TC>;
|
|
123
|
-
layers?: LayerCapability;
|
|
124
|
-
dragDrop?: DragDropCapability<TS, TC>;
|
|
125
|
-
undo?: UndoCapability;
|
|
126
|
-
}
|
|
127
|
-
/** Instruments as a lab receives them. `any` rather than `unknown` because
|
|
128
|
-
* parameter contravariance keeps a `defineInstrument<TS, TC>` result out of
|
|
129
|
-
* an `Instrument<unknown, unknown>[]`; it is contained to this alias. */
|
|
130
|
-
type InstrumentList = readonly Instrument<any, any>[];
|
|
131
|
-
|
|
132
|
-
export type { CanvasCapability as C, DragDropCapability as D, HitResult as H, Instrument as I, LayerCapability as L, PaletteItem as P, RenderContext as R, SystemEvent as S, UndoCapability as U, ViewTransform as V, InstrumentList as a, CanvasLayer as b, DragFeedback as c, LayerDescriptor as d, Point as e };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/** Which control a config field is edited with. */
|
|
2
|
-
type ConfigFieldType = 'slider' | 'checkbox' | 'select' | 'number' | 'text' | 'color';
|
|
3
|
-
/** What every config field carries: the config key it writes, the label shown
|
|
4
|
-
* beside it, and which control renders it. */
|
|
5
|
-
interface ConfigFieldBase {
|
|
6
|
-
key: string;
|
|
7
|
-
label: string;
|
|
8
|
-
type: ConfigFieldType;
|
|
9
|
-
}
|
|
10
|
-
/** A bounded number edited by dragging. */
|
|
11
|
-
interface SliderField extends ConfigFieldBase {
|
|
12
|
-
type: 'slider';
|
|
13
|
-
default: number;
|
|
14
|
-
min: number;
|
|
15
|
-
max: number;
|
|
16
|
-
step?: number;
|
|
17
|
-
}
|
|
18
|
-
/** A boolean. */
|
|
19
|
-
interface CheckboxField extends ConfigFieldBase {
|
|
20
|
-
type: 'checkbox';
|
|
21
|
-
default: boolean;
|
|
22
|
-
}
|
|
23
|
-
/** One choice in a select field. */
|
|
24
|
-
interface SelectOption {
|
|
25
|
-
value: string;
|
|
26
|
-
label: string;
|
|
27
|
-
}
|
|
28
|
-
/** A fixed set of labeled choices. */
|
|
29
|
-
interface SelectField extends ConfigFieldBase {
|
|
30
|
-
type: 'select';
|
|
31
|
-
default: string;
|
|
32
|
-
options: SelectOption[];
|
|
33
|
-
}
|
|
34
|
-
/** A number typed directly, optionally bounded. Use a slider field instead
|
|
35
|
-
* when the range matters more than the exact value. */
|
|
36
|
-
interface NumberField extends ConfigFieldBase {
|
|
37
|
-
type: 'number';
|
|
38
|
-
default: number;
|
|
39
|
-
min?: number;
|
|
40
|
-
max?: number;
|
|
41
|
-
step?: number;
|
|
42
|
-
}
|
|
43
|
-
/** A free-text string. Writes are debounced so typing does not re-run the
|
|
44
|
-
* instrument on every keystroke. */
|
|
45
|
-
interface TextField extends ConfigFieldBase {
|
|
46
|
-
type: 'text';
|
|
47
|
-
default: string;
|
|
48
|
-
placeholder?: string;
|
|
49
|
-
maxLength?: number;
|
|
50
|
-
/** Milliseconds to debounce live setConfig calls. Default 150 ms. Set to 0 to disable. */
|
|
51
|
-
debounceMs?: number;
|
|
52
|
-
}
|
|
53
|
-
/** A color, as a CSS color string. */
|
|
54
|
-
interface ColorField extends ConfigFieldBase {
|
|
55
|
-
type: 'color';
|
|
56
|
-
default: string;
|
|
57
|
-
}
|
|
58
|
-
/** One field of an instrument's config schema. The schema is what the control
|
|
59
|
-
* panel renders, and what `validateConfigSchema` checks. */
|
|
60
|
-
type ConfigField = SliderField | CheckboxField | SelectField | NumberField | TextField | ColorField;
|
|
61
|
-
|
|
62
|
-
export type { ConfigField as C, NumberField as N, SelectField as S, TextField as T, CheckboxField as a, ColorField as b, ConfigFieldBase as c, ConfigFieldType as d, SelectOption as e, SliderField as f };
|