@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.
Files changed (222) hide show
  1. package/dist/_dts/{CanvasStackContext-DQgcH1ny.d.ts → CanvasStackContext-D2jnIwfk.d.ts} +1 -1
  2. package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
  3. package/dist/_dts/{LayerList.d-2c9yup5Y.d.ts → LayerList.d-DiuEArKq.d.ts} +4 -4
  4. package/dist/_dts/PrefsRow.d-TOZ7BgD9.d.ts +79 -0
  5. package/dist/_dts/PropertyPanel.d-D-1paCbE.d.ts +141 -0
  6. package/dist/_dts/{Select.d-BchGMC2Y.d.ts → Select.d-Bensr_MT.d.ts} +15 -6
  7. package/dist/_dts/Stage-CCWfQQWM.d.ts +202 -0
  8. package/dist/_dts/index-B2Yj5aA0.d.ts +274 -0
  9. package/dist/_dts/{index-B0--8PEM.d.ts → index-CsYqUHD3.d.ts} +12 -13
  10. package/dist/_dts/{types-B7y8hX1D.d.ts → types-CRPSGFAp.d.ts} +33 -5
  11. package/dist/_dts/{frac-DTRq7Xpb.d.ts → types-CSm9jgol.d.ts} +700 -650
  12. package/dist/_dts/{useTrialState-CP4jfuKl.d.ts → useTrialState-CTZPgeZ7.d.ts} +2 -3
  13. package/dist/canvas/index.d.ts +40 -8
  14. package/dist/canvas/index.js +5 -3
  15. package/dist/chrome/index.d.ts +5 -6
  16. package/dist/chrome/index.js +9 -7
  17. package/dist/chunk-24W5VEJW.js +18 -0
  18. package/dist/chunk-24W5VEJW.js.map +1 -0
  19. package/dist/chunk-275JRRVD.js +728 -0
  20. package/dist/chunk-275JRRVD.js.map +1 -0
  21. package/dist/chunk-3RY6WL5T.js +38 -0
  22. package/dist/chunk-3RY6WL5T.js.map +1 -0
  23. package/dist/chunk-4LJSC7MB.js +143 -0
  24. package/dist/chunk-4LJSC7MB.js.map +1 -0
  25. package/dist/{chunk-3FI7Y4R4.js → chunk-7BO7HNGF.js} +5 -5
  26. package/dist/{chunk-3FI7Y4R4.js.map → chunk-7BO7HNGF.js.map} +1 -1
  27. package/dist/chunk-AWQG7FCC.js +19 -0
  28. package/dist/chunk-AWQG7FCC.js.map +1 -0
  29. package/dist/chunk-CZZPXUOU.js +3 -0
  30. package/dist/{chunk-SBXNQW4G.js.map → chunk-CZZPXUOU.js.map} +1 -1
  31. package/dist/{chunk-ZORVNOKX.js → chunk-FMK7UQ6V.js} +24 -145
  32. package/dist/chunk-FMK7UQ6V.js.map +1 -0
  33. package/dist/{chunk-BVL7Y574.js → chunk-FZ7YFQN7.js} +26 -10
  34. package/dist/chunk-FZ7YFQN7.js.map +1 -0
  35. package/dist/{chunk-USO6TERP.js → chunk-GOZLNM4L.js} +11 -9
  36. package/dist/chunk-GOZLNM4L.js.map +1 -0
  37. package/dist/{chunk-H5VIV7MQ.js → chunk-I5DUQ5HM.js} +88 -82
  38. package/dist/chunk-I5DUQ5HM.js.map +1 -0
  39. package/dist/chunk-J7OWWTCW.js +143 -0
  40. package/dist/chunk-J7OWWTCW.js.map +1 -0
  41. package/dist/{chunk-H6ZAOWNE.js → chunk-JVBYV6TL.js} +3 -3
  42. package/dist/{chunk-H6ZAOWNE.js.map → chunk-JVBYV6TL.js.map} +1 -1
  43. package/dist/{chunk-VQ47KWD5.js → chunk-KMJ7RWD3.js} +18 -7
  44. package/dist/chunk-KMJ7RWD3.js.map +1 -0
  45. package/dist/{chunk-ERFMC6WU.js → chunk-M7HP3FTK.js} +20 -11
  46. package/dist/chunk-M7HP3FTK.js.map +1 -0
  47. package/dist/chunk-MIM5R4ZT.js +14972 -0
  48. package/dist/chunk-MIM5R4ZT.js.map +1 -0
  49. package/dist/{chunk-HFRE4QQV.js → chunk-T5IBSPAM.js} +96 -32
  50. package/dist/chunk-T5IBSPAM.js.map +1 -0
  51. package/dist/chunk-UNAZSLWE.js +294 -0
  52. package/dist/chunk-UNAZSLWE.js.map +1 -0
  53. package/dist/{chunk-RS3HRQWU.js → chunk-WPLAANH4.js} +4 -3
  54. package/dist/chunk-WPLAANH4.js.map +1 -0
  55. package/dist/{chunk-AWH3SRPW.js → chunk-Y23G2VZL.js} +5 -37
  56. package/dist/chunk-Y23G2VZL.js.map +1 -0
  57. package/dist/{chunk-MOLIUNVH.js → chunk-YT75VYRD.js} +4 -4
  58. package/dist/{chunk-MOLIUNVH.js.map → chunk-YT75VYRD.js.map} +1 -1
  59. package/dist/chunk-ZIFPHIWZ.js +302 -0
  60. package/dist/chunk-ZIFPHIWZ.js.map +1 -0
  61. package/dist/config/index.d.ts +48 -40
  62. package/dist/config/index.js +4 -5
  63. package/dist/controls/index.d.ts +91 -7
  64. package/dist/controls/index.js +5 -4
  65. package/dist/dragdrop/index.d.ts +9 -7
  66. package/dist/dragdrop/index.js +3 -2
  67. package/dist/index.d.ts +294 -82
  68. package/dist/index.js +425 -402
  69. package/dist/index.js.map +1 -1
  70. package/dist/layers/index.d.ts +4 -5
  71. package/dist/layers/index.js +2 -1
  72. package/dist/loupe/index.d.ts +20 -17
  73. package/dist/loupe/index.js +5 -3
  74. package/dist/overview/index.d.ts +56 -0
  75. package/dist/overview/index.js +289 -0
  76. package/dist/overview/index.js.map +1 -0
  77. package/dist/passthrough/weasel-ui.d.ts +239 -69
  78. package/dist/passthrough/weasel-ui.js +3 -2
  79. package/dist/primitives/index.d.ts +5 -204
  80. package/dist/primitives/index.js +6 -4
  81. package/dist/state/index.d.ts +8 -8
  82. package/dist/state/index.js +4 -5
  83. package/dist/state/index.js.map +1 -1
  84. package/dist/styles.css +152 -82
  85. package/dist/undo/index.d.ts +4 -4
  86. package/package.json +17 -12
  87. package/src/annotations/AnnotationOverlay.tsx +2 -2
  88. package/src/annotations/Annotations.less +0 -10
  89. package/src/annotations/MarkList.tsx +4 -8
  90. package/src/annotations/index.ts +1 -0
  91. package/src/annotations/paint.test.ts +3 -14
  92. package/src/annotations/paint.ts +3 -19
  93. package/src/annotations/store.test.ts +26 -0
  94. package/src/annotations/store.ts +25 -6
  95. package/src/annotations/svgNodes.test.ts +6 -9
  96. package/src/annotations/svgNodes.ts +3 -3
  97. package/src/annotations/toolMap.test.ts +37 -2
  98. package/src/annotations/toolMap.ts +13 -0
  99. package/src/annotations/types.ts +27 -0
  100. package/src/canvas/AGENTS.md +9 -10
  101. package/src/canvas/CameraInput.test.tsx +136 -0
  102. package/src/canvas/CameraInput.tsx +322 -0
  103. package/src/canvas/CanvasStack.tsx +47 -58
  104. package/src/canvas/LinkedCursor.less +16 -0
  105. package/src/canvas/LinkedCursor.tsx +54 -0
  106. package/src/canvas/Stage.test.tsx +33 -4
  107. package/src/canvas/Stage.tsx +47 -37
  108. package/src/canvas/cameraRegistry.test.ts +55 -0
  109. package/src/canvas/cameraRegistry.ts +86 -0
  110. package/src/canvas/cameraView.test.ts +60 -0
  111. package/src/canvas/cameraView.ts +61 -0
  112. package/src/canvas/cameraZoom.test.ts +46 -0
  113. package/src/canvas/cameraZoom.ts +24 -0
  114. package/src/canvas/index.ts +23 -2
  115. package/src/canvas/sharedScope.test.tsx +94 -0
  116. package/src/chrome/ChromeRegions.stories.tsx +1 -0
  117. package/src/chrome/regions/PaletteRegion.less +0 -7
  118. package/src/chrome/regions/SidebarRegion.less +12 -2
  119. package/src/chrome/regions/SidebarRegion.test.tsx +9 -2
  120. package/src/config/builder.test.ts +2 -1
  121. package/src/config/builder.ts +15 -8
  122. package/src/config/entry.test.ts +3 -1
  123. package/src/config/fromConfigField.test.ts +14 -0
  124. package/src/config/fromConfigField.ts +19 -4
  125. package/src/config/index.ts +4 -1
  126. package/src/config/path.ts +2 -5
  127. package/src/config/resolve.ts +15 -1
  128. package/src/config/sectionTree.test.ts +12 -6
  129. package/src/config/sectionTree.ts +15 -5
  130. package/src/config/types.ts +34 -9
  131. package/src/controls/ControlPanel.less +1 -1
  132. package/src/controls/ControlPanel.stories.tsx +3 -2
  133. package/src/controls/ControlPanel.test.tsx +76 -13
  134. package/src/controls/ControlPanel.tsx +80 -11
  135. package/src/controls/inDialog.test.tsx +18 -0
  136. package/src/controls/inDialog.tsx +2 -10
  137. package/src/index.test.ts +1 -1
  138. package/src/index.ts +23 -5
  139. package/src/instrument/SineWave.smoke.test.tsx +2 -0
  140. package/src/instrument/types.ts +10 -1
  141. package/src/lab/Lab.nebula.test.tsx +11 -2
  142. package/src/lab/Lab.test.tsx +3 -1
  143. package/src/lab/Lab.tsx +87 -76
  144. package/src/lab/{LabFit.stories.less → LabFit.browser.test.less} +1 -1
  145. package/src/lab/{LabFit.stories.tsx → LabFit.browser.test.tsx} +41 -63
  146. package/src/lab/LabFullChrome.stories.tsx +1 -0
  147. package/src/lab/LabHeader.test.tsx +24 -14
  148. package/src/lab/LabHeader.tsx +11 -12
  149. package/src/lab/LabShell.less +8 -17
  150. package/src/lab/LabSwitcher.less +6 -6
  151. package/src/lab/LabZoom.test.tsx +223 -0
  152. package/src/lab/LabZoom.tsx +170 -0
  153. package/src/lab/index.ts +1 -0
  154. package/src/loupe/AGENTS.md +7 -6
  155. package/src/loupe/LoupeGestures.tsx +15 -10
  156. package/src/loupe/TrialLoupe.tsx +5 -5
  157. package/src/loupe/index.ts +1 -0
  158. package/src/loupe/loupeActions.ts +6 -2
  159. package/src/overview/AGENTS.md +38 -0
  160. package/src/overview/OverviewMarks.test.tsx +100 -0
  161. package/src/overview/OverviewMarks.tsx +93 -0
  162. package/src/overview/TrialOverview.less +53 -0
  163. package/src/overview/TrialOverview.stories.tsx +50 -0
  164. package/src/overview/TrialOverview.test.tsx +116 -0
  165. package/src/overview/TrialOverview.tsx +376 -0
  166. package/src/overview/index.ts +2 -0
  167. package/src/passthrough/weasel-ui.test.ts +2 -0
  168. package/src/passthrough/weasel-ui.ts +23 -4
  169. package/src/primitives/FloatingPanel.test.tsx +11 -0
  170. package/src/primitives/FloatingPanel.tsx +2 -5
  171. package/src/primitives/JobProgress.less +1 -1
  172. package/src/primitives/Legend.stories.tsx +1 -0
  173. package/src/primitives/Readout.browser.test.tsx +137 -0
  174. package/src/primitives/Readout.less +12 -0
  175. package/src/primitives/Readout.stories.tsx +58 -0
  176. package/src/primitives/Readout.test.tsx +93 -0
  177. package/src/primitives/Readout.tsx +56 -0
  178. package/src/primitives/index.ts +2 -0
  179. package/src/specimen/Specimen.stories.tsx +1 -0
  180. package/src/specimen/Specimen.tsx +21 -4
  181. package/src/state/Persistence.browser.test.tsx +47 -0
  182. package/src/state/labRecords.test.ts +4 -1
  183. package/src/state/store.test.ts +9 -5
  184. package/src/state/usePersistedState.test.tsx +1 -1
  185. package/src/state/useTrialState.test.tsx +1 -1
  186. package/src/styles.less +3 -0
  187. package/src/theme/Interstellar.stories.tsx +4 -6
  188. package/src/theme/base.less +21 -47
  189. package/src/theme/interstellar.test.ts +1 -1
  190. package/src/theme/interstellar.theme.json +10 -3
  191. package/src/tools/labTool.ts +6 -2
  192. package/src/trial/Trial.annotations.test.tsx +16 -0
  193. package/src/trial/Trial.less +0 -13
  194. package/src/trial/Trial.loupe.test.tsx +73 -1
  195. package/src/trial/Trial.stories.tsx +2 -0
  196. package/src/trial/Trial.tsx +23 -1
  197. package/src/trial/TrialChrome.tsx +21 -3
  198. package/src/trial/trialOps.test.ts +4 -4
  199. package/dist/_dts/PrefsRow.d-DFpPIeIC.d.ts +0 -39
  200. package/dist/_dts/inDialog-D5ZKGjiD.d.ts +0 -23
  201. package/dist/_dts/index-DINmCuLA.d.ts +0 -453
  202. package/dist/_dts/number.d-OeQYQoUO.d.ts +0 -28
  203. package/dist/_dts/usePanZoom-Bq73pwFe.d.ts +0 -117
  204. package/dist/chunk-2AYGEN57.js +0 -32
  205. package/dist/chunk-2AYGEN57.js.map +0 -1
  206. package/dist/chunk-AWH3SRPW.js.map +0 -1
  207. package/dist/chunk-BVL7Y574.js.map +0 -1
  208. package/dist/chunk-ERFMC6WU.js.map +0 -1
  209. package/dist/chunk-H5VIV7MQ.js.map +0 -1
  210. package/dist/chunk-HFRE4QQV.js.map +0 -1
  211. package/dist/chunk-RS3HRQWU.js.map +0 -1
  212. package/dist/chunk-SBXNQW4G.js +0 -3
  213. package/dist/chunk-TT5C5S6R.js +0 -368
  214. package/dist/chunk-TT5C5S6R.js.map +0 -1
  215. package/dist/chunk-USO6TERP.js.map +0 -1
  216. package/dist/chunk-VQ47KWD5.js.map +0 -1
  217. package/dist/chunk-WUJXJC5O.js +0 -14568
  218. package/dist/chunk-WUJXJC5O.js.map +0 -1
  219. package/dist/chunk-ZORVNOKX.js.map +0 -1
  220. package/src/canvas/usePanZoom.test.ts +0 -244
  221. package/src/canvas/usePanZoom.ts +0 -147
  222. package/src/state/Persistence.stories.tsx +0 -75
@@ -1,453 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as react from 'react';
3
- import { ReactNode } from 'react';
4
- import { S as StanceProps } from './PrefsRow.d-DFpPIeIC.js';
5
- import { ToolPrefNumberFormat, FillStyle, PaintKind } from '@weasel-js/core';
6
- import { U as UnitTable } from './number.d-OeQYQoUO.js';
7
- import { R as ResolvedConfig, b as ConfigField, C as ControlRenderer } from './types-B7y8hX1D.js';
8
- import './inDialog-D5ZKGjiD.js';
9
-
10
- /**
11
- * How much room a container gives its rows — gaps, padding, and field height,
12
- * moved together. Set on any container in the family; it reaches every
13
- * descendant, so an inner group can differ from the panel around it.
14
- */
15
- type PropertyDensity = 'tight' | 'normal' | 'roomy';
16
- /** Where an inline row's label and control sit on the row's cross axis. Set on
17
- * a container to line a whole column of them up. */
18
- type PropertyAlign = 'start' | 'center' | 'end' | 'baseline';
19
- /** Metric props every container in the family takes. Both reach descendants,
20
- * so the nearest container that states one wins. */
21
- interface PropertyMetricProps {
22
- /** Room the rows get. Unset inherits from an outer container, else `normal`. */
23
- density?: PropertyDensity;
24
- /**
25
- * Cross-axis alignment of an inline row's label and control. `baseline` sits
26
- * the control on the label's first-line baseline, which is what lines a
27
- * column of swatches up against labels of different heights. Unset keeps each
28
- * variant's own alignment — a color row centers its label and swatch and
29
- * sinks the pair to the row's bottom edge, which keeps a paired alpha track
30
- * level with its neighbour.
31
- */
32
- align?: PropertyAlign;
33
- }
34
- /** Props for `<PropertyPanel>`. */
35
- interface PropertyPanelProps extends PropertyMetricProps, StanceProps {
36
- title?: ReactNode;
37
- /** Controls on the trailing edge of the title row — a switch, a clear
38
- * button. They sit outside the heading, so they neither take its type nor
39
- * join its accessible name. */
40
- actions?: ReactNode;
41
- /**
42
- * Lets text inside the panel be selected and copied. A panel is chrome by
43
- * default, so a drag across it moves a slider rather than painting a
44
- * selection; a `debug` panel defaults to selectable, since what it shows —
45
- * ids, keys, traces — is there to be copied.
46
- */
47
- selectable?: boolean;
48
- children: ReactNode;
49
- className?: string;
50
- }
51
- /**
52
- * A titled panel holding property rows — the sidebar container the rest of
53
- * this module's components fill.
54
- *
55
- * `stance` says what kind of content it holds and `tone` which of its peers it
56
- * is; the theme's `--wzl-panel-*` and `--wzl-stance-*` slots decide how each looks. A stanced
57
- * panel's title takes the row-label recipe rather than the display title.
58
- */
59
- declare function PropertyPanel({ title, actions, selectable, children, className, density, align, stance, tone, }: PropertyPanelProps): react_jsx_runtime.JSX.Element;
60
- /** How a property list packs its rows into two columns. */
61
- type PropertyListPack = 'auto-color' | 'pairs' | 'one-up';
62
- /** Props for `<PropertyList>`. */
63
- interface PropertyListProps extends PropertyMetricProps {
64
- children: ReactNode;
65
- className?: string;
66
- /**
67
- * How rows pack into the 2-column grid.
68
- * - `'auto-color'` (default): only color rows pair side-by-side; everything
69
- * else spans the full width. Right for sparse top-level panels.
70
- * - `'pairs'`: every row auto-places into the 2-column grid two-per-row.
71
- * Headers and subpanels still span the full width; wrap any other
72
- * full-width child in `<PropertySpan>`. Right for dense effect bodies.
73
- * - `'one-up'`: every row spans the full width, color rows included. Right
74
- * for a palette — a column of swatches read as a group.
75
- */
76
- pack?: PropertyListPack;
77
- }
78
- /**
79
- * Grid container for PropertyRows. Use standalone for chrome-less layouts, or
80
- * nest inside <PropertyPanel/> for the standard glass card.
81
- */
82
- declare const PropertyList: react.ForwardRefExoticComponent<PropertyListProps & react.RefAttributes<HTMLDivElement>>;
83
- /** Props for `<PropertySpan>`. */
84
- interface PropertySpanProps {
85
- children: ReactNode;
86
- className?: string;
87
- }
88
- /**
89
- * Wrapper that makes an arbitrary child span both columns of a
90
- * `<PropertyList pack="pairs">`, a `<PropertyGroup pack="pairs">` body, or a
91
- * `<Subpanel>`. `<PropertyRow span>` covers the row case; this covers
92
- * everything else a consumer puts in the grid.
93
- */
94
- declare function PropertySpan({ children, className }: PropertySpanProps): react_jsx_runtime.JSX.Element;
95
- /** Which control shape a row holds, which decides its intrinsic layout. */
96
- type PropertyRowVariant = 'default' | 'color' | 'checkbox';
97
- /** Whether a row's label sits above its control or beside it. */
98
- type PropertyRowLayout = 'block' | 'inline';
99
- /** Props for `<PropertyRow>`. */
100
- interface PropertyRowProps extends PropertyMetricProps {
101
- label: ReactNode;
102
- /** Right-aligned readout shown next to the label (e.g. current value). */
103
- readout?: ReactNode;
104
- /**
105
- * Help text for the row, shown in a tooltip off an ⓘ affordance beside the
106
- * label. Empty or absent renders no affordance.
107
- */
108
- description?: string;
109
- variant?: PropertyRowVariant;
110
- /**
111
- * Label position relative to the control. Unset takes the variant's own
112
- * orientation: `block` — label above control — for the default variant, and
113
- * `inline` for the color and checkbox variants, which read as a row.
114
- */
115
- layout?: PropertyRowLayout;
116
- /**
117
- * Take the full width of the enclosing grid. Only has an effect inside a
118
- * `<PropertyList pack="pairs">` or a `<Subpanel>`; elsewhere rows are already
119
- * full width.
120
- */
121
- span?: boolean;
122
- children: ReactNode;
123
- htmlFor?: string;
124
- /** The control is a group of several (a segmented toggle), not one element.
125
- * The row then renders as a `<div>`: a `<label>` hands a click on its text to
126
- * its first control, which in a group means selecting the first option. Each
127
- * member has to carry its own name. */
128
- group?: boolean;
129
- className?: string;
130
- /** The row is auto: its value is not pinned and the owner computes it. The
131
- * control is hidden — its box kept, so the row does not resize — and the
132
- * row reads out the word instead. */
133
- auto?: boolean;
134
- /** Given, the row's label toggles `auto` when it is clicked. Omitted, the row
135
- * can show an auto state but not change it.
136
- *
137
- * The label then names that toggle, so a control passed as `children` has to
138
- * carry its own accessible name — every `<PropertyField>` does. */
139
- onAutoChange?: (next: boolean) => void;
140
- /**
141
- * What draws the row's control. `bare` (the default) is a native input the
142
- * row's stylesheet dresses; `framed` is a kit field bringing its own frame,
143
- * which the row then leaves alone — see {@link PropertyFieldChrome}.
144
- */
145
- chrome?: PropertyFieldChrome;
146
- }
147
- /** The label-plus-control frame `<PropertyField>` draws its rows in. Use it
148
- * directly for a control no field kind covers. */
149
- declare function PropertyRow({ label, readout, description, variant, layout, span, children, htmlFor, group, className, density, align, auto, onAutoChange, chrome, }: PropertyRowProps): react_jsx_runtime.JSX.Element;
150
-
151
- /**
152
- * Which controls a field is drawn with.
153
- *
154
- * - `'bare'` (the default): native inputs the property row's own stylesheet
155
- * draws — the compact controls of an inspector sidebar or a lab panel.
156
- * - `'framed'`: the kit's field components, each bringing its own frame —
157
- * for a surface whose row does not style its controls (`SelectionPanel`,
158
- * `ToolOptionsBar`), or that needs what only they do, such as reading a
159
- * unit typed into a number (`accepts`).
160
- *
161
- * The two differ only where the kit still has two widgets for one job; a
162
- * slider, a switch, a font picker and a paint are the same in both.
163
- */
164
- type PropertyFieldChrome = 'bare' | 'framed';
165
- /** One choice of an enum field. */
166
- interface PropertyOption<T extends string> {
167
- value: T;
168
- /** The option's name. A string also names its segment for a screen reader. */
169
- label: ReactNode;
170
- /** What a segment shows in place of `label` when the full one would not fit
171
- * — a glyph or a letter. `label` stays the accessible name. */
172
- glyph?: ReactNode;
173
- /** Shown but not choosable: a value the control reports and cannot author. */
174
- disabled?: boolean;
175
- }
176
- /** Props every kind of field shares. */
177
- interface FieldBase {
178
- /** The control's accessible name. A `<PropertyField>` takes it from a string
179
- * `label` when this is omitted. */
180
- name?: string;
181
- /** The sources the field reads disagree — a multi-selection whose nodes
182
- * differ. The control shows no value, in whatever form it has for that. */
183
- mixed?: boolean;
184
- /**
185
- * The sources agree on holding nothing: whatever is in effect comes from a
186
- * fallback further down. The control shows no chosen value rather than
187
- * inventing one, since the next edit would write the invention back.
188
- */
189
- unset?: boolean;
190
- chrome?: PropertyFieldChrome;
191
- /** Class on the control element itself. */
192
- className?: string;
193
- /** Id for the control element, for a `<label htmlFor>` outside it. */
194
- id?: string;
195
- }
196
- /** A boolean field. */
197
- interface PropertyBooleanFieldProps extends FieldBase {
198
- kind: 'boolean';
199
- /** `checkbox` (default), `switch`, or `toggle` — a one-segment pressable bar. */
200
- control?: 'checkbox' | 'switch' | 'toggle';
201
- /** Absent reads as off. */
202
- value: boolean | undefined;
203
- onChange: (next: boolean) => void;
204
- /** A `toggle`'s segment content. Defaults to the first letter of `name`. */
205
- glyph?: ReactNode;
206
- }
207
- /** A number field. */
208
- interface PropertyNumberFieldProps extends FieldBase {
209
- kind: 'number';
210
- /** `input` (default) for a typed number; `slider` for a track with an
211
- * editable readout, where the range matters more than the exact value. */
212
- control?: 'input' | 'slider';
213
- /** Absent reads as an empty field. */
214
- value: number | null | undefined;
215
- /**
216
- * The committed value. Given `onInput` as well, it fires once a drag ends,
217
- * or on blur or Enter in a typed field; on its own it fires on every move
218
- * and keystroke. A `framed` typed field commits only on blur or Enter,
219
- * whichever callbacks it has.
220
- */
221
- onChange: (next: number) => void;
222
- /** The live value, fired continuously. Pass it alongside `onChange` when the
223
- * write is expensive — cheap state here, the costly work there. */
224
- onInput?: (next: number) => void;
225
- /** A slider's track runs 0..100 where these are omitted. */
226
- min?: number;
227
- max?: number;
228
- step?: number;
229
- /**
230
- * Suffix after the value. A string becomes a dim word unit (`px`); pass JSX
231
- * like `<sup>°</sup>` for a symbol. Display only — the value stays a number.
232
- */
233
- unit?: ReactNode;
234
- /** Units a person may type into a `framed` field, each mapped to the factor
235
- * that turns it into the unit shown: `{ mm: 0.1, cm: 1 }`. */
236
- accepts?: Readonly<UnitTable>;
237
- /** A slider readout's display. Defaults to the value at `step`'s precision. */
238
- format?: (value: number) => ReactNode;
239
- /** A named display for the value: `compact` reads `2.00M`. `format` wins. */
240
- notation?: ToolPrefNumberFormat;
241
- placeholder?: string;
242
- /** A `framed` unitless field's stepper buttons. Default `true`. */
243
- steppers?: boolean;
244
- }
245
- /** A text field. */
246
- interface PropertyStringFieldProps extends FieldBase {
247
- kind: 'string';
248
- control?: 'input' | 'textarea';
249
- /** Absent reads as an empty field. */
250
- value: string | null | undefined;
251
- /**
252
- * The committed text. On its own it fires on every keystroke. Given
253
- * `onInput` as well, the field drafts: `onInput` gets each keystroke and
254
- * this fires once, on blur or Enter, and only if the text changed.
255
- */
256
- onChange: (next: string) => void;
257
- onInput?: (next: string) => void;
258
- placeholder?: string;
259
- maxLength?: number;
260
- }
261
- /** A choice among fixed options. */
262
- interface PropertyEnumFieldProps<T extends string = string> extends FieldBase {
263
- kind: 'enum';
264
- /**
265
- * `select` (default) is a dropdown. `radio` and `toggle` show every option
266
- * at once — a radio group chooses, and a toggle is a row of pressable
267
- * segments. Drawn `bare`, both are segments.
268
- */
269
- control?: 'select' | 'radio' | 'toggle';
270
- /** Absent, or not one of `options`, chooses nothing. */
271
- value: T | undefined;
272
- options: ReadonlyArray<PropertyOption<T>>;
273
- onChange: (next: T) => void;
274
- /** Shown while no option is chosen. Defaults to "Choose option…". */
275
- placeholder?: string;
276
- }
277
- /** A single color. */
278
- interface PropertyColorFieldProps extends FieldBase {
279
- kind: 'color';
280
- /** `#rrggbb`, or `#rrggbbaa` when `alpha` is `true`. */
281
- value: string | undefined;
282
- /**
283
- * The committed color. `bare`: given `onInput` as well, it fires once the
284
- * picker closes; on its own it fires on every move. `framed`: it fires once
285
- * per gesture, whichever callbacks it has.
286
- */
287
- onChange: (next: string) => void;
288
- onInput?: (next: string) => void;
289
- /**
290
- * Offer an opacity track. `true` keeps the alpha in `value` as `#rrggbbaa`;
291
- * a number is the 0..1 alpha held apart from it, reported through
292
- * `onAlphaChange` / `onAlphaInput`.
293
- */
294
- alpha?: boolean | number;
295
- onAlphaChange?: (next: number) => void;
296
- onAlphaInput?: (next: number) => void;
297
- /** Draw the opacity track inert, for a consumer that drops alpha. */
298
- alphaDisabled?: boolean;
299
- }
300
- /** A whole paint: a solid, a gradient, a pattern, or none. */
301
- interface PropertyPaintFieldProps extends FieldBase {
302
- kind: 'paint';
303
- /** `swatch` (default) opens the editor in a popover, for a narrow slot;
304
- * `inline` draws the kind bar and its editor in place. */
305
- control?: 'swatch' | 'inline';
306
- /** `null` is an explicit "no paint"; `undefined` is nothing to show. */
307
- value: FillStyle | null | undefined;
308
- onChange: (next: FillStyle | null) => void;
309
- onInput?: (next: FillStyle | null) => void;
310
- /** Offer "None". Default `true`. */
311
- allowNone?: boolean;
312
- /** Restrict the kinds offered. Default: every registered kind. */
313
- kinds?: readonly PaintKind[];
314
- }
315
- /** A font family, picked from the live font registry. */
316
- interface PropertyFontFamilyFieldProps extends FieldBase {
317
- kind: 'font-family';
318
- value: string | undefined;
319
- onChange: (next: string) => void;
320
- /** The weight and slant the family is drawn at, so the picker names the
321
- * variant that will actually paint. */
322
- weight?: number;
323
- fontStyle?: 'normal' | 'italic';
324
- }
325
- /**
326
- * One field for {@link PropertyControl} and {@link PropertyField}, keyed by
327
- * `kind`.
328
- */
329
- type PropertyControlProps<T extends string = string> = PropertyBooleanFieldProps | PropertyNumberFieldProps | PropertyStringFieldProps | PropertyEnumFieldProps<T> | PropertyColorFieldProps | PropertyPaintFieldProps | PropertyFontFamilyFieldProps;
330
- /** Row props {@link PropertyField} adds to the field it draws. */
331
- interface PropertyFieldRowProps extends PropertyMetricProps {
332
- label: ReactNode;
333
- /**
334
- * Text beside the label. On a slider it replaces the editable readout —
335
- * use it when the row is not showing a number a caller could type back,
336
- * such as an auto row's `auto`.
337
- */
338
- readout?: ReactNode;
339
- description?: string;
340
- layout?: PropertyRowLayout;
341
- /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
342
- span?: boolean;
343
- /** The row is auto — see `<PropertyRow auto>`. */
344
- auto?: boolean;
345
- /** Toggles `auto` from the row's label — see `<PropertyRow onAutoChange>`. */
346
- onAutoChange?: (next: boolean) => void;
347
- /** Class on the row. `controlClassName` reaches the control. */
348
- rowClassName?: string;
349
- controlClassName?: string;
350
- }
351
- /** Props for {@link PropertyField}: one field, and the row around it. */
352
- type PropertyFieldProps<T extends string = string> = PropertyFieldRowProps & DistributiveOmit<PropertyControlProps<T>, 'className' | 'id'>;
353
- type DistributiveOmit<U, K extends PropertyKey> = U extends unknown ? Omit<U, K> : never;
354
- /**
355
- * A labeled settings row for one field, its control chosen by `kind` (and
356
- * `control`): `<PropertyField kind="number" control="slider" label="Blur" …>`.
357
- *
358
- * The row is a {@link PropertyRow}; the control is the one
359
- * {@link PropertyControl} draws for the same props. Every schema-driven
360
- * settings surface in the kit — `ControlPanel`, `PrefsForm`, `SelectionPanel`
361
- * — draws its built-in kinds through that one mapping, so a kind's control
362
- * is decided once.
363
- *
364
- * Callbacks follow the field's shape: a discrete control (a box, a choice)
365
- * reports each pick; a continuous or typed one reports every change through
366
- * `onChange` alone, or splits them into live `onInput` and settled
367
- * `onChange` when given both. `mixed` and `unset` show a field whose sources
368
- * disagree, or agree on holding nothing.
369
- */
370
- declare function PropertyField<T extends string = string>(props: PropertyFieldProps<T>): react_jsx_runtime.JSX.Element;
371
- /**
372
- * The control for one field, with no row around it — for a cell that shares
373
- * a row with others, or a surface that draws its own row. Keyed by `kind`
374
- * (and `control`), the same mapping {@link PropertyField} draws.
375
- *
376
- * A slider brings its readout beside the track. Several elements come back
377
- * as siblings rather than wrapped, so the surrounding row lays them out.
378
- */
379
- declare function PropertyControl<T extends string = string>(typed: PropertyControlProps<T>): react_jsx_runtime.JSX.Element;
380
-
381
- /** How a panel packs its rows into the two-column property grid.
382
- * - `'auto'`: narrow controls (numbers, checkboxes, dropdowns, colors) sit
383
- * two per row; the ones that need the width — text, sliders, segmented
384
- * toggles — span it.
385
- * - `'pairs'`: every row pairs. A control that needs the full width says so
386
- * itself, with `<PropertyRow span>`.
387
- * - `'one-up'`: one control per row, colors excepted.
388
- */
389
- type ControlPack = 'auto' | 'pairs' | 'one-up';
390
- interface ControlPanelProps<TC extends Record<string, unknown>> extends StanceProps {
391
- /** The instrument's resolved config schema. */
392
- schema?: ResolvedConfig;
393
- /** @deprecated Pass `schema`. A field list is adapted into one internally. */
394
- fields?: ConfigField[];
395
- config: TC;
396
- /** Writes one value. The path is dotted for a leaf inside an `f.group`, and
397
- * the bare key for one at the root. */
398
- setConfig: (path: string, value: unknown) => void;
399
- /**
400
- * Control overrides and app-defined kinds, PrefsForm-style. Keys are config
401
- * paths (checked first) or leaf kinds. A renderer returning `null` collapses
402
- * the row; entries override the built-in rows on collision.
403
- */
404
- renderers?: Record<string, ControlRenderer>;
405
- /** How rows pack into the two-column grid. Defaults to `'pairs'`. */
406
- pack?: ControlPack;
407
- /**
408
- * Where a row's label sits relative to its control. Unset takes each row's
409
- * own orientation — `block` (label above, which is what leaves a paired row
410
- * room for its value) for most, `inline` for colors and checkboxes.
411
- */
412
- layout?: PropertyRowLayout;
413
- /** Room the rows get. Passed straight to the property list. */
414
- density?: PropertyDensity;
415
- /** Cross-axis alignment of an inline row's label and control. */
416
- align?: PropertyAlign;
417
- /**
418
- * Fold each section away behind a twisty, starting `'open'` or `'closed'`.
419
- * Unset draws the heading alone, as the panel always has — unless a section
420
- * in the schema declares how it opens, which makes that section foldable on
421
- * its own and outranks this for that one section.
422
- */
423
- collapse?: 'open' | 'closed';
424
- /**
425
- * Which sections are folded, keyed as `onCollapse` reports them. Given, the
426
- * panel keeps no state of its own: every toggle arrives at `onCollapse`
427
- * instead, which is where a lab that remembers a trial's sections writes
428
- * them.
429
- */
430
- collapsed?: Readonly<Record<string, boolean>>;
431
- /** A fold moved. The key is a section's label — prefixed by its group's
432
- * dotted path when the section sits inside one — or a group's own path. */
433
- onCollapse?: (key: string, collapsed: boolean) => void;
434
- /** Dotted paths currently unpinned. A row in this set hides its control and
435
- * reads out `auto`, and clicking its label writes the sentinel back through
436
- * `setConfig`. Omitted, the panel keeps the set itself: the labels still
437
- * toggle and the sentinel never reaches `setConfig`, which would otherwise
438
- * store it as the row's value. */
439
- auto?: ReadonlySet<string>;
440
- /** Draw leaves marked `hidden`. */
441
- showHidden?: boolean;
442
- /** Heads the panel. With `title`, `stance` or `tone` given, the rows sit in a
443
- * `<PropertyPanel>` carrying them; with none, they are the bare list. */
444
- title?: ReactNode;
445
- className?: string;
446
- }
447
- /** Render an instrument's config schema as a stack of controls, each writing
448
- * back through `setConfig`. Built on the property rows, so a lab's controls
449
- * are the same aligned, themed rows the rest of the kit uses. */
450
- declare function ControlPanel<TC extends Record<string, unknown>>({ schema, fields, config, setConfig, renderers, pack, layout, density, align, collapse, collapsed, onCollapse, auto: given, showHidden, title, stance, tone, className, }: ControlPanelProps<TC>): react_jsx_runtime.JSX.Element;
451
-
452
- export { ControlPanel as C, PropertyControl as b, PropertyField as d, PropertyList as f, PropertyPanel as i, PropertyRow as k, PropertySpan as o };
453
- export type { PropertyMetricProps as P, PropertyListPack as a, PropertyControlProps as c, PropertyFieldProps as e, PropertyListProps as g, PropertyOption as h, PropertyPanelProps as j, PropertyRowLayout as l, PropertyRowProps as m, PropertyRowVariant as n, PropertySpanProps as p };
@@ -1,28 +0,0 @@
1
- /**
2
- * Display-formatter for numbers. Use this anywhere a number is shown to
3
- * a user. The whole point: negative values get prefixed with the real
4
- * MINUS SIGN (U+2212) instead of the ASCII HYPHEN-MINUS (U+002D) that
5
- * `toLocaleString` and template literals produce by default.
6
- *
7
- * U+2212 is the same visual width as `+` and reads as a sign rather
8
- * than a hyphen — columns of signed numbers align cleanly and the
9
- * glyph doesn't get confused with a bullet or list dash.
10
- */
11
- declare const MINUS_SIGN = "\u2212";
12
- /**
13
- * Formats a number for display, substituting {@link MINUS_SIGN} for the ASCII
14
- * hyphen `toLocaleString` emits. Non-finite values stringify as-is.
15
- */
16
- declare function formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
17
- /** One unit's conversion into the shown unit: a bare factor, or a scale that
18
- * also moves zero. Structurally the kit's `UnitEntry`, kept local so this
19
- * formatting helper stays free of the scene vocabulary. */
20
- type UnitTableEntry = number | {
21
- factor: number;
22
- offset?: number;
23
- };
24
- /** Suffixes a person may type, each mapped to its conversion. */
25
- type UnitTable = Record<string, UnitTableEntry>;
26
-
27
- export { MINUS_SIGN as M, formatNumber as f };
28
- export type { UnitTable as U };
@@ -1,117 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode, WheelEvent, PointerEvent as PointerEvent$1 } from 'react';
3
- import { m as ViewTransform, b as WorldSpec, V as ViewportSize, n as Point, W as WorldFrame } from './frac-DTRq7Xpb.js';
4
- import { C as CanvasLayerDescriptor } from './CanvasStackContext-DQgcH1ny.js';
5
-
6
- /** Props for `<CanvasStack>`. */
7
- interface CanvasStackProps {
8
- layers: CanvasLayerDescriptor[];
9
- view: ViewTransform;
10
- onViewChange: (v: ViewTransform) => void;
11
- /** The instrument's coordinate system. Omitted, world (0,0) sits at the
12
- * element's top-left with y running down. */
13
- worldSpec?: WorldSpec;
14
- minZoom?: number;
15
- maxZoom?: number;
16
- width?: number | string;
17
- height?: number | string;
18
- className?: string;
19
- /** Fired whenever the stack is measured, so a consumer can place a view that
20
- * only makes sense in terms of the viewport. */
21
- onResize?: (size: ViewportSize) => void;
22
- onHitTest?: (worldPos: Point) => void;
23
- children?: ReactNode;
24
- }
25
- /** Stacks one `<canvas>` per layer and drives them from a shared view, so a
26
- * layer that changes rarely is not redrawn with one that changes every frame.
27
- * Handles sizing, device pixel ratio, and pan/zoom. */
28
- declare function CanvasStack({ layers, view: viewProp, onViewChange, worldSpec, minZoom, maxZoom, width, height, className, onResize, onHitTest, children, }: CanvasStackProps): react_jsx_runtime.JSX.Element;
29
-
30
- /** Bounds and coordinate system a {@link zoomAt} works in. */
31
- interface ZoomAtOptions {
32
- /** The instrument's coordinate system, resolved against the viewport.
33
- * Omitted, `at` is measured from the element's top-left. */
34
- frame?: WorldFrame;
35
- min?: number;
36
- max?: number;
37
- }
38
- /**
39
- * Zoom a view by `factor` about a fixed point, so whatever world coordinate
40
- * `at` was over stays under it.
41
- *
42
- * `at` is in the element's own pixels — a cursor position, or the centre of a
43
- * lens. The clamp is applied before the pan is solved, so a zoom that hits a
44
- * bound still anchors on the zoom it actually reached.
45
- */
46
- declare function zoomAt(view: ViewTransform, factor: number, at: Point, { frame, min, max }?: ZoomAtOptions): ViewTransform;
47
- /** The view that puts a world point at the centre of a `size` viewport, at
48
- * `zoom`. What a lens showing a region around one point renders through. */
49
- declare function centerOn(world: Point, zoom: number, size: ViewportSize, frame?: WorldFrame): ViewTransform;
50
-
51
- /** Project a world point into screen coordinates under a view.
52
- *
53
- * `frame` is the instrument's declared coordinate system, resolved against the
54
- * viewport; omitting it gives labkit's original one — origin at the top-left,
55
- * y downward. */
56
- declare function worldToScreen(world: Point, view: ViewTransform, frame?: WorldFrame): Point;
57
- /** Unproject a screen point back to world coordinates under a view. */
58
- declare function screenToWorld(screen: Point, view: ViewTransform, frame?: WorldFrame): Point;
59
-
60
- /** Props for `<Stage>`. */
61
- interface StageProps {
62
- /** The content's own size in CSS pixels, at zoom 1. */
63
- size: ViewportSize;
64
- view: ViewTransform;
65
- onViewChange: (v: ViewTransform) => void;
66
- minZoom?: number;
67
- maxZoom?: number;
68
- /** Fired whenever the stage's viewport is measured at a non-zero size. */
69
- onResize?: (viewport: ViewportSize) => void;
70
- /** Handed the viewport element, for a consumer tracking the pointer over it. */
71
- hostRef?: {
72
- current: HTMLDivElement | null;
73
- };
74
- /** Drawn over the content in viewport pixels, outside the camera. */
75
- overlay?: ReactNode;
76
- children?: ReactNode;
77
- }
78
- /**
79
- * The camera a stage opens at: the content centered in its viewport, shrunk to
80
- * fit if it has to be and never enlarged past its own size.
81
- */
82
- declare function fitStage(size: ViewportSize, viewport: ViewportSize): ViewTransform;
83
- /**
84
- * DOM content of a fixed size, panned and zoomed by a camera — what
85
- * `<CanvasStack>` is for layers drawn to a canvas. The content is laid out at
86
- * its own size and transformed, so everything inside keeps its layout and a
87
- * measurement of any element in it reports where it is actually drawn.
88
- */
89
- declare function Stage({ size, view: viewProp, onViewChange, minZoom, maxZoom, onResize, hostRef, overlay, children, }: StageProps): react_jsx_runtime.JSX.Element;
90
-
91
- interface UsePanZoomOptions {
92
- view: ViewTransform;
93
- onViewChange: (v: ViewTransform) => void;
94
- /** The opening `view.zoom` always stays reachable, widening these past
95
- * whatever is passed here if it would otherwise exclude it. */
96
- minZoom?: number;
97
- maxZoom?: number;
98
- /** The instrument's coordinate system, resolved against the viewport. The
99
- * wheel anchors in this frame; omitting it anchors at the element's
100
- * top-left, which is what labkit did before a frame could be declared. */
101
- frame?: WorldFrame;
102
- /** A primary-button press released without ever crossing the drag
103
- * threshold. The pointer session owns the release, so the gesture's end is
104
- * reported here rather than left for a consumer's own `onPointerUp`. */
105
- onTap?: (e: PointerEvent) => void;
106
- }
107
- interface PanZoomHandlers {
108
- /** Zoom about the pointer. `anchor` is the element the pointer position is
109
- * measured against, for an event whose `currentTarget` is some other box. */
110
- onWheel: (e: WheelEvent<HTMLElement> | globalThis.WheelEvent, anchor?: Element) => void;
111
- onPointerDown: (e: PointerEvent$1<HTMLElement>) => void;
112
- isDragging: () => boolean;
113
- }
114
- declare function usePanZoom({ view, onViewChange, minZoom, maxZoom, frame, onTap, }: UsePanZoomOptions): PanZoomHandlers;
115
-
116
- export { CanvasStack as C, Stage as S, centerOn as c, fitStage as f, screenToWorld as s, usePanZoom as u, worldToScreen as w, zoomAt as z };
117
- export type { PanZoomHandlers as P, UsePanZoomOptions as U, ZoomAtOptions as Z, CanvasStackProps as a, StageProps as b };
@@ -1,32 +0,0 @@
1
- // src/canvas/worldSpec.ts
2
- var DEFAULT_FRAME = { originPx: { x: 0, y: 0 }, yDir: 1 };
3
- function resolveFrame(spec, size) {
4
- if (!spec) return DEFAULT_FRAME;
5
- const origin = spec.origin ?? { x: 0, y: 0 };
6
- return {
7
- originPx: { x: origin.x * size.width, y: origin.y * size.height },
8
- yDir: spec.yAxis === "up" ? -1 : 1
9
- };
10
- }
11
- function applyCamera(ctx, view, frame = DEFAULT_FRAME) {
12
- ctx.translate(frame.originPx.x + view.pan.x, frame.originPx.y + view.pan.y);
13
- ctx.scale(view.zoom, view.zoom * frame.yDir);
14
- }
15
-
16
- // src/canvas/canvasCoords.ts
17
- function worldToScreen(world, view, frame = DEFAULT_FRAME) {
18
- return {
19
- x: frame.originPx.x + view.pan.x + world.x * view.zoom,
20
- y: frame.originPx.y + view.pan.y + world.y * view.zoom * frame.yDir
21
- };
22
- }
23
- function screenToWorld(screen, view, frame = DEFAULT_FRAME) {
24
- return {
25
- x: (screen.x - frame.originPx.x - view.pan.x) / view.zoom,
26
- y: (screen.y - frame.originPx.y - view.pan.y) / (view.zoom * frame.yDir)
27
- };
28
- }
29
-
30
- export { DEFAULT_FRAME, applyCamera, resolveFrame, screenToWorld, worldToScreen };
31
- //# sourceMappingURL=chunk-2AYGEN57.js.map
32
- //# sourceMappingURL=chunk-2AYGEN57.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/canvas/worldSpec.ts","../src/canvas/canvasCoords.ts"],"names":[],"mappings":";AA2BO,IAAM,aAAA,GAA4B,EAAE,QAAA,EAAU,EAAE,CAAA,EAAG,GAAG,CAAA,EAAG,CAAA,EAAE,EAAG,IAAA,EAAM,CAAA;AAEpE,SAAS,YAAA,CAAa,MAA6B,IAAA,EAAgC;AACxF,EAAA,IAAI,CAAC,MAAM,OAAO,aAAA;AAClB,EAAA,MAAM,SAAS,IAAA,CAAK,MAAA,IAAU,EAAE,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAE;AAC3C,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,EAAE,CAAA,EAAG,MAAA,CAAO,CAAA,GAAI,IAAA,CAAK,KAAA,EAAO,CAAA,EAAG,MAAA,CAAO,CAAA,GAAI,IAAA,CAAK,MAAA,EAAO;AAAA,IAChE,IAAA,EAAM,IAAA,CAAK,KAAA,KAAU,IAAA,GAAO,EAAA,GAAK;AAAA,GACnC;AACF;AAIO,SAAS,WAAA,CACd,GAAA,EACA,IAAA,EACA,KAAA,GAAoB,aAAA,EACd;AACN,EAAA,GAAA,CAAI,SAAA,CAAU,KAAA,CAAM,QAAA,CAAS,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,CAAM,QAAA,CAAS,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA;AAC1E,EAAA,GAAA,CAAI,MAAM,IAAA,CAAK,IAAA,EAAM,IAAA,CAAK,IAAA,GAAO,MAAM,IAAI,CAAA;AAC7C;;;ACvCO,SAAS,aAAA,CACd,KAAA,EACA,IAAA,EACA,KAAA,GAAoB,aAAA,EACb;AACP,EAAA,OAAO;AAAA,IACL,CAAA,EAAG,MAAM,QAAA,CAAS,CAAA,GAAI,KAAK,GAAA,CAAI,CAAA,GAAI,KAAA,CAAM,CAAA,GAAI,IAAA,CAAK,IAAA;AAAA,IAClD,CAAA,EAAG,KAAA,CAAM,QAAA,CAAS,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,KAAA,CAAM,CAAA,GAAI,IAAA,CAAK,IAAA,GAAO,KAAA,CAAM;AAAA,GACjE;AACF;AAGO,SAAS,aAAA,CACd,MAAA,EACA,IAAA,EACA,KAAA,GAAoB,aAAA,EACb;AACP,EAAA,OAAO;AAAA,IACL,CAAA,EAAA,CAAI,OAAO,CAAA,GAAI,KAAA,CAAM,SAAS,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAA,IAAK,IAAA,CAAK,IAAA;AAAA,IACrD,CAAA,EAAA,CAAI,MAAA,CAAO,CAAA,GAAI,KAAA,CAAM,QAAA,CAAS,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAA,KAAM,IAAA,CAAK,IAAA,GAAO,KAAA,CAAM,IAAA;AAAA,GACrE;AACF","file":"chunk-2AYGEN57.js","sourcesContent":["import type { Point, ViewTransform } from '../instrument/types';\n\n/** How an instrument's world sits on the canvas, independent of the camera.\n * The defaults reproduce the convention labkit shipped before this existed:\n * world (0,0) at the element's top-left, y growing downward. */\nexport interface WorldSpec {\n /** Where world (0,0) sits at `pan` zero, as a fraction of the viewport.\n * `{x:0,y:0}` top-left (default); `{x:0.5,y:0.5}` centre. */\n origin?: Point;\n /** Which way the world y axis runs on screen. Default `'down'`. */\n yAxis?: 'down' | 'up';\n}\n\n/** A viewport's CSS-pixel size. */\nexport interface ViewportSize {\n width: number;\n height: number;\n}\n\n/** A `WorldSpec` resolved against a viewport: what the coordinate helpers,\n * the camera and the wheel all read. */\nexport interface WorldFrame {\n /** Screen position of world (0,0) at `pan` zero. */\n originPx: Point;\n yDir: 1 | -1;\n}\n\nexport const DEFAULT_FRAME: WorldFrame = { originPx: { x: 0, y: 0 }, yDir: 1 };\n\nexport function resolveFrame(spec: WorldSpec | undefined, size: ViewportSize): WorldFrame {\n if (!spec) return DEFAULT_FRAME;\n const origin = spec.origin ?? { x: 0, y: 0 };\n return {\n originPx: { x: origin.x * size.width, y: origin.y * size.height },\n yDir: spec.yAxis === 'up' ? -1 : 1,\n };\n}\n\n/** Put a 2D context into the instrument's world coordinates, so a layer's\n * `draw` works in them. Must stay the exact inverse of `screenToWorld`. */\nexport function applyCamera(\n ctx: CanvasRenderingContext2D,\n view: ViewTransform,\n frame: WorldFrame = DEFAULT_FRAME,\n): void {\n ctx.translate(frame.originPx.x + view.pan.x, frame.originPx.y + view.pan.y);\n ctx.scale(view.zoom, view.zoom * frame.yDir);\n}\n","import type { Point, ViewTransform } from '../instrument/types';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\n/** Project a world point into screen coordinates under a view.\n *\n * `frame` is the instrument's declared coordinate system, resolved against the\n * viewport; omitting it gives labkit's original one — origin at the top-left,\n * y downward. */\nexport function worldToScreen(\n world: Point,\n view: ViewTransform,\n frame: WorldFrame = DEFAULT_FRAME,\n): Point {\n return {\n x: frame.originPx.x + view.pan.x + world.x * view.zoom,\n y: frame.originPx.y + view.pan.y + world.y * view.zoom * frame.yDir,\n };\n}\n\n/** Unproject a screen point back to world coordinates under a view. */\nexport function screenToWorld(\n screen: Point,\n view: ViewTransform,\n frame: WorldFrame = DEFAULT_FRAME,\n): Point {\n return {\n x: (screen.x - frame.originPx.x - view.pan.x) / view.zoom,\n y: (screen.y - frame.originPx.y - view.pan.y) / (view.zoom * frame.yDir),\n };\n}\n"]}