@weasel-js/labkit 1.7.0 → 1.7.2

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 (242) hide show
  1. package/README.md +3 -3
  2. package/dist/_dts/{CanvasStackContext-D2jnIwfk.d.ts → CanvasStackContext-Cjy6I-Iv.d.ts} +4 -1
  3. package/dist/_dts/{Stage-CCWfQQWM.d.ts → Stage-D6t7o0pF.d.ts} +3 -2
  4. package/dist/_dts/{index-CsYqUHD3.d.ts → index-BTmaRy6a.d.ts} +10 -6
  5. package/dist/_dts/{types-CSm9jgol.d.ts → index-DCMA4Fy1.d.ts} +59 -113
  6. package/dist/_dts/{types-CRPSGFAp.d.ts → types-BUBSIZuV.d.ts} +8 -7
  7. package/dist/_dts/{useTrialState-CTZPgeZ7.d.ts → useTrialState-Bh8bzFgE.d.ts} +1 -1
  8. package/dist/canvas/index.d.ts +8 -6
  9. package/dist/canvas/index.js +6 -6
  10. package/dist/chrome/index.d.ts +7 -5
  11. package/dist/chrome/index.js +10 -9
  12. package/dist/chunk-2ES4W47N.js +8 -0
  13. package/dist/chunk-2ES4W47N.js.map +1 -0
  14. package/dist/{chunk-WPLAANH4.js → chunk-2N7UBSS2.js} +12 -12
  15. package/dist/chunk-2N7UBSS2.js.map +1 -0
  16. package/dist/{chunk-275JRRVD.js → chunk-E23Q6A3Y.js} +232 -37
  17. package/dist/chunk-E23Q6A3Y.js.map +1 -0
  18. package/dist/{chunk-GOZLNM4L.js → chunk-E6NARGU7.js} +5 -5
  19. package/dist/{chunk-GOZLNM4L.js.map → chunk-E6NARGU7.js.map} +1 -1
  20. package/dist/{chunk-IJJUK4FU.js → chunk-EESJUSYR.js} +5 -6
  21. package/dist/chunk-EESJUSYR.js.map +1 -0
  22. package/dist/{chunk-HXXTULDN.js → chunk-EFXRM6XQ.js} +8 -11
  23. package/dist/chunk-EFXRM6XQ.js.map +1 -0
  24. package/dist/chunk-F4ACIK7Z.js +210 -0
  25. package/dist/chunk-F4ACIK7Z.js.map +1 -0
  26. package/dist/{chunk-AWQG7FCC.js → chunk-FG3EXZDV.js} +3 -3
  27. package/dist/{chunk-AWQG7FCC.js.map → chunk-FG3EXZDV.js.map} +1 -1
  28. package/dist/{chunk-YT75VYRD.js → chunk-IBVRMHW5.js} +5 -5
  29. package/dist/{chunk-YT75VYRD.js.map → chunk-IBVRMHW5.js.map} +1 -1
  30. package/dist/{chunk-RWNDDKOH.js → chunk-PMLZP7RT.js} +18 -15
  31. package/dist/chunk-PMLZP7RT.js.map +1 -0
  32. package/dist/{chunk-Y23G2VZL.js → chunk-READTDQX.js} +2 -2
  33. package/dist/chunk-READTDQX.js.map +1 -0
  34. package/dist/{chunk-I5DUQ5HM.js → chunk-RODIY7HI.js} +75 -72
  35. package/dist/chunk-RODIY7HI.js.map +1 -0
  36. package/dist/{chunk-JVBYV6TL.js → chunk-ROGJWDQA.js} +3 -3
  37. package/dist/{chunk-JVBYV6TL.js.map → chunk-ROGJWDQA.js.map} +1 -1
  38. package/dist/{chunk-ZIFPHIWZ.js → chunk-T6DLGEJB.js} +12 -14
  39. package/dist/chunk-T6DLGEJB.js.map +1 -0
  40. package/dist/chunk-U6UQVGFP.js +15583 -0
  41. package/dist/chunk-U6UQVGFP.js.map +1 -0
  42. package/dist/{chunk-24W5VEJW.js → chunk-ULYYNWGP.js} +2 -2
  43. package/dist/{chunk-24W5VEJW.js.map → chunk-ULYYNWGP.js.map} +1 -1
  44. package/dist/{chunk-UNAZSLWE.js → chunk-VYVGVBGY.js} +17 -20
  45. package/dist/chunk-VYVGVBGY.js.map +1 -0
  46. package/dist/chunk-WFALF2X2.js +302 -0
  47. package/dist/chunk-WFALF2X2.js.map +1 -0
  48. package/dist/{chunk-KMJ7RWD3.js → chunk-WLMDTI2Q.js} +3 -3
  49. package/dist/{chunk-KMJ7RWD3.js.map → chunk-WLMDTI2Q.js.map} +1 -1
  50. package/dist/{chunk-T5IBSPAM.js → chunk-WWJDPNZZ.js} +43 -33
  51. package/dist/chunk-WWJDPNZZ.js.map +1 -0
  52. package/dist/{chunk-J7OWWTCW.js → chunk-XE5HJOPL.js} +5 -6
  53. package/dist/chunk-XE5HJOPL.js.map +1 -0
  54. package/dist/{chunk-7BO7HNGF.js → chunk-XRX5S5NT.js} +8 -61
  55. package/dist/chunk-XRX5S5NT.js.map +1 -0
  56. package/dist/chunk-XVUKDSYV.js +38 -0
  57. package/dist/chunk-XVUKDSYV.js.map +1 -0
  58. package/dist/{chunk-FMK7UQ6V.js → chunk-XZDCEMEL.js} +64 -47
  59. package/dist/chunk-XZDCEMEL.js.map +1 -0
  60. package/dist/{chunk-FZ7YFQN7.js → chunk-YCFKW5RX.js} +3 -3
  61. package/dist/chunk-YCFKW5RX.js.map +1 -0
  62. package/dist/{chunk-CQLQPQ4P.js → chunk-ZQIHJXP4.js} +2 -2
  63. package/dist/chunk-ZQIHJXP4.js.map +1 -0
  64. package/dist/config/index.d.ts +18 -10
  65. package/dist/config/index.js +4 -4
  66. package/dist/controls/index.d.ts +58 -9
  67. package/dist/controls/index.js +7 -5
  68. package/dist/dragdrop/index.d.ts +23 -3
  69. package/dist/dragdrop/index.js +4 -3
  70. package/dist/fonts/oswald-tabular-digits.woff2 +0 -0
  71. package/dist/index.d.ts +61 -715
  72. package/dist/index.js +456 -432
  73. package/dist/index.js.map +1 -1
  74. package/dist/job/index.d.ts +8 -0
  75. package/dist/job/index.js +1 -1
  76. package/dist/layers/index.d.ts +6 -5
  77. package/dist/layers/index.js +3 -2
  78. package/dist/loupe/index.d.ts +100 -30
  79. package/dist/loupe/index.js +585 -6
  80. package/dist/loupe/index.js.map +1 -1
  81. package/dist/overview/index.d.ts +4 -2
  82. package/dist/overview/index.js +15 -14
  83. package/dist/overview/index.js.map +1 -1
  84. package/dist/passthrough/weasel-canvas.d.ts +2 -0
  85. package/dist/passthrough/weasel-ui.d.ts +2 -2871
  86. package/dist/passthrough/weasel-ui.js +3 -2
  87. package/dist/primitives/index.d.ts +213 -3
  88. package/dist/primitives/index.js +8 -7
  89. package/dist/state/index.d.ts +7 -5
  90. package/dist/state/index.js +6 -6
  91. package/dist/styles.css +284 -51
  92. package/dist/surface/index.d.ts +10 -2
  93. package/dist/surface/index.js +1 -1
  94. package/dist/undo/index.d.ts +5 -3
  95. package/package.json +11 -11
  96. package/src/annotations/AnnotationOverlay.abandon.test.tsx +143 -0
  97. package/src/annotations/AnnotationOverlay.tsx +50 -31
  98. package/src/annotations/Annotations.overlay.test.tsx +31 -0
  99. package/src/annotations/capture.browser.test.ts +47 -0
  100. package/src/annotations/capture.ts +14 -7
  101. package/src/annotations/drawOne.ts +2 -2
  102. package/src/annotations/frac.test.ts +44 -1
  103. package/src/annotations/frac.ts +46 -18
  104. package/src/annotations/index.ts +1 -0
  105. package/src/annotations/paint.test.ts +74 -33
  106. package/src/annotations/paint.ts +70 -32
  107. package/src/annotations/store.test.ts +212 -2
  108. package/src/annotations/store.ts +130 -27
  109. package/src/annotations/svgNodes.test.ts +22 -10
  110. package/src/annotations/svgNodes.ts +3 -3
  111. package/src/annotations/types.ts +29 -10
  112. package/src/canvas/CameraInput.test.tsx +30 -0
  113. package/src/canvas/CameraInput.tsx +16 -15
  114. package/src/canvas/CanvasStack.tsx +5 -5
  115. package/src/canvas/CanvasStackContext.ts +4 -1
  116. package/src/canvas/Stage.test.tsx +34 -25
  117. package/src/canvas/Stage.tsx +3 -3
  118. package/src/canvas/cameraRegistry.ts +1 -0
  119. package/src/canvas/worldSpec.ts +3 -1
  120. package/src/chrome/builtins.test.ts +7 -6
  121. package/src/chrome/builtins.tsx +5 -1
  122. package/src/chrome/index.ts +1 -1
  123. package/src/chrome/labRegions.test.tsx +37 -26
  124. package/src/chrome/merge.test.ts +5 -0
  125. package/src/chrome/merge.ts +4 -2
  126. package/src/chrome/regions/PaletteRegion.test.tsx +4 -5
  127. package/src/chrome/regions/PaletteRegion.tsx +3 -4
  128. package/src/chrome/regions/ViewportRegion.browser.test.less +6 -0
  129. package/src/chrome/regions/ViewportRegion.browser.test.tsx +38 -0
  130. package/src/chrome/regions/ViewportRegion.less +6 -0
  131. package/src/chrome/regions/ViewportRegion.tsx +10 -4
  132. package/src/chrome/types.ts +4 -2
  133. package/src/config/auto.ts +1 -0
  134. package/src/config/builder.ts +6 -0
  135. package/src/config/types.ts +2 -1
  136. package/src/controls/ControlMatrix.less +245 -0
  137. package/src/controls/ControlMatrix.stories.less +13 -0
  138. package/src/controls/ControlMatrix.stories.tsx +135 -0
  139. package/src/controls/ControlMatrix.test.tsx +206 -0
  140. package/src/controls/ControlMatrix.tsx +446 -0
  141. package/src/controls/ControlPanel.test.tsx +37 -3
  142. package/src/controls/ControlPanel.tsx +16 -5
  143. package/src/controls/index.ts +6 -0
  144. package/src/dragdrop/DragDropRuntime.tsx +22 -13
  145. package/src/dragdrop/DragGhost.tsx +3 -0
  146. package/src/dragdrop/Palette.tsx +2 -0
  147. package/src/dragdrop/dragDrop.test.tsx +34 -33
  148. package/src/index.test.ts +5 -0
  149. package/src/index.ts +7 -1
  150. package/src/instrument/types.ts +0 -6
  151. package/src/job/useJob.ts +14 -9
  152. package/src/lab/Lab.chrome.test.tsx +15 -10
  153. package/src/lab/Lab.nebula.test.tsx +5 -5
  154. package/src/lab/Lab.surface.test.tsx +30 -21
  155. package/src/lab/Lab.test.tsx +75 -74
  156. package/src/lab/Lab.tsx +5 -1
  157. package/src/lab/Lab.undock.test.tsx +15 -10
  158. package/src/lab/LabHeader.test.tsx +15 -14
  159. package/src/lab/LabPalette.test.tsx +12 -7
  160. package/src/lab/LabShell.less +1 -1
  161. package/src/lab/LabSwitcher.browser.test.tsx +27 -0
  162. package/src/lab/LabSwitcher.less +2 -0
  163. package/src/lab/LabZoom.test.tsx +55 -44
  164. package/src/lab/LabZoom.tsx +4 -4
  165. package/src/lab/Workspace.surface.test.tsx +16 -13
  166. package/src/lab/Workspace.test.tsx +32 -27
  167. package/src/lab/Workspace.tsx +48 -52
  168. package/src/loupe/AGENTS.md +28 -15
  169. package/src/loupe/CanvasLoupe.tsx +2 -3
  170. package/src/loupe/DomLoupe.tsx +1 -7
  171. package/src/loupe/Loupe.less +16 -0
  172. package/src/loupe/TrialLoupe.tsx +120 -57
  173. package/src/loupe/index.ts +1 -6
  174. package/src/loupe/types.test.ts +3 -3
  175. package/src/loupe/types.ts +24 -27
  176. package/src/loupe/useLoupe.test.tsx +29 -4
  177. package/src/loupe/useLoupe.ts +20 -17
  178. package/src/overview/OverviewMarks.test.tsx +9 -8
  179. package/src/overview/OverviewMarks.tsx +12 -4
  180. package/src/overview/TrialOverview.test.tsx +18 -17
  181. package/src/overview/TrialOverview.tsx +3 -3
  182. package/src/passthrough/weasel-ui.test.ts +1 -1
  183. package/src/passthrough/weasel-ui.ts +1 -1
  184. package/src/primitives/FloatingPanel.tsx +7 -3
  185. package/src/primitives/FpsMeter.less +1 -2
  186. package/src/primitives/JobProgress.less +1 -1
  187. package/src/primitives/Readout.test.tsx +2 -2
  188. package/src/primitives/ScaleIndicator.less +1 -1
  189. package/src/primitives/Split.tsx +69 -41
  190. package/src/primitives/StatusBar.less +1 -1
  191. package/src/primitives/Toolbar.tsx +3 -3
  192. package/src/primitives/ZoomControl.less +1 -2
  193. package/src/specimen/Specimen.less +1 -0
  194. package/src/specimen/Specimen.tsx +0 -1
  195. package/src/state/undock.ts +3 -0
  196. package/src/state/useOpenOnce.ts +20 -16
  197. package/src/state/view.ts +1 -0
  198. package/src/styles.less +3 -0
  199. package/src/surface/index.ts +1 -1
  200. package/src/surface/useTiledSurface.test.tsx +14 -0
  201. package/src/surface/useTiledSurface.ts +11 -3
  202. package/src/trial/Trial.annotations.persist.test.tsx +20 -9
  203. package/src/trial/Trial.annotations.test.tsx +105 -27
  204. package/src/trial/Trial.canvas.test.tsx +21 -17
  205. package/src/trial/Trial.config.test.tsx +34 -27
  206. package/src/trial/Trial.less +0 -16
  207. package/src/trial/Trial.loupe.test.tsx +51 -25
  208. package/src/trial/Trial.targets.test.tsx +9 -8
  209. package/src/trial/Trial.test.tsx +51 -46
  210. package/src/trial/Trial.trialId.test.tsx +7 -6
  211. package/src/trial/Trial.tsx +74 -62
  212. package/src/trial/TrialChrome.tsx +6 -3
  213. package/src/trial/index.ts +2 -0
  214. package/src/trial/loupeSwitch.ts +15 -0
  215. package/dist/_dts/LayerList.d-DiuEArKq.d.ts +0 -83
  216. package/dist/_dts/PrefsRow.d-TOZ7BgD9.d.ts +0 -79
  217. package/dist/_dts/PropertyPanel.d-D-1paCbE.d.ts +0 -141
  218. package/dist/_dts/Select.d-Bensr_MT.d.ts +0 -579
  219. package/dist/_dts/index-B2Yj5aA0.d.ts +0 -274
  220. package/dist/chunk-275JRRVD.js.map +0 -1
  221. package/dist/chunk-4LJSC7MB.js +0 -143
  222. package/dist/chunk-4LJSC7MB.js.map +0 -1
  223. package/dist/chunk-7BO7HNGF.js.map +0 -1
  224. package/dist/chunk-CQLQPQ4P.js.map +0 -1
  225. package/dist/chunk-FMK7UQ6V.js.map +0 -1
  226. package/dist/chunk-FZ7YFQN7.js.map +0 -1
  227. package/dist/chunk-HXXTULDN.js.map +0 -1
  228. package/dist/chunk-I5DUQ5HM.js.map +0 -1
  229. package/dist/chunk-IJJUK4FU.js.map +0 -1
  230. package/dist/chunk-J7OWWTCW.js.map +0 -1
  231. package/dist/chunk-M7HP3FTK.js +0 -549
  232. package/dist/chunk-M7HP3FTK.js.map +0 -1
  233. package/dist/chunk-MIM5R4ZT.js +0 -14972
  234. package/dist/chunk-MIM5R4ZT.js.map +0 -1
  235. package/dist/chunk-RWNDDKOH.js.map +0 -1
  236. package/dist/chunk-T5IBSPAM.js.map +0 -1
  237. package/dist/chunk-UNAZSLWE.js.map +0 -1
  238. package/dist/chunk-WPLAANH4.js.map +0 -1
  239. package/dist/chunk-Y23G2VZL.js.map +0 -1
  240. package/dist/chunk-ZIFPHIWZ.js.map +0 -1
  241. package/src/primitives/useRovingTabIndex.test.ts +0 -48
  242. package/src/primitives/useRovingTabIndex.ts +0 -88
@@ -14,5 +14,5 @@ function applyCamera(ctx, view, frame = DEFAULT_FRAME) {
14
14
  }
15
15
 
16
16
  export { DEFAULT_FRAME, applyCamera, resolveFrame };
17
- //# sourceMappingURL=chunk-24W5VEJW.js.map
18
- //# sourceMappingURL=chunk-24W5VEJW.js.map
17
+ //# sourceMappingURL=chunk-ULYYNWGP.js.map
18
+ //# sourceMappingURL=chunk-ULYYNWGP.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/canvas/worldSpec.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","file":"chunk-24W5VEJW.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"]}
1
+ {"version":3,"sources":["../src/canvas/worldSpec.ts"],"names":[],"mappings":";AA4BO,IAAM,aAAA,GAA4B,EAAE,QAAA,EAAU,EAAE,CAAA,EAAG,GAAG,CAAA,EAAG,CAAA,EAAE,EAAG,IAAA,EAAM,CAAA;AAGpE,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","file":"chunk-ULYYNWGP.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}` center. */\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\n/** The frame an absent `WorldSpec` resolves to: origin top-left, y down. */\nexport const DEFAULT_FRAME: WorldFrame = { originPx: { x: 0, y: 0 }, yDir: 1 };\n\n/** Resolve `spec` against a viewport of `size`; `undefined` gives `DEFAULT_FRAME`. */\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"]}
@@ -1,6 +1,6 @@
1
- import { DEFAULT_FRAME } from './chunk-24W5VEJW.js';
2
- import { createContext, useContext, useEffect, useRef, useMemo } from 'react';
3
- import { normalizeZoom, useActionsRegistry, usePointerContext, PointerContextProvider, useDepSource, makeViewportZoomAction, viewportDragPanAction, useGestureDispatcher, InputScope } from '@weasel-js/core';
1
+ import { DEFAULT_FRAME } from './chunk-ULYYNWGP.js';
2
+ import { createContext, useContext, useEffect, useRef, useInsertionEffect, useMemo } from 'react';
3
+ import { normalizeZoom, useActionsRegistry, usePointerContext, PointerContextProvider, useLatest, useDepSource, makeViewportZoomAction, viewportDragPanAction, useGestureDispatcher, InputScope } from '@weasel-js/core';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  var CameraWheelContext = createContext(null);
@@ -138,14 +138,15 @@ function useCameraView({
138
138
  maxZoom = 32
139
139
  }) {
140
140
  const live = useRef({ view: normalize2DView(view), onViewChange, frame });
141
- live.current = { view: normalize2DView(view), onViewChange, frame };
141
+ useInsertionEffect(() => {
142
+ live.current = { view: normalize2DView(view), onViewChange, frame };
143
+ });
142
144
  const initialZoom = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);
143
- const bounds = useRef({ min: minZoom, max: maxZoom });
144
145
  const opening = initialZoom.current;
145
- bounds.current = {
146
+ const bounds = useLatest({
146
147
  min: opening == null ? minZoom : Math.min(minZoom, opening),
147
148
  max: opening == null ? maxZoom : Math.max(maxZoom, opening)
148
- };
149
+ });
149
150
  return useMemo(() => {
150
151
  const get = () => toCameraView(live.current.view, live.current.frame);
151
152
  return {
@@ -163,7 +164,7 @@ function useCameraView({
163
164
  },
164
165
  zoomRange: () => ({ ...bounds.current })
165
166
  };
166
- }, [hostRef]);
167
+ }, [bounds, hostRef]);
167
168
  }
168
169
  var TAP_ID = "camera.tap";
169
170
  var NO_TOOLS = /* @__PURE__ */ new Map();
@@ -180,10 +181,8 @@ function CameraDispatch({
180
181
  onTap,
181
182
  registry
182
183
  }) {
183
- const frameRef = useRef(frame);
184
- frameRef.current = frame;
185
- const onTapRef = useRef(onTap);
186
- onTapRef.current = onTap;
184
+ const frameRef = useLatest(frame);
185
+ const onTapRef = useLatest(onTap);
187
186
  useDepSource("view", () => camera);
188
187
  useDepSource("rootView", () => camera);
189
188
  const actions = useMemo(() => {
@@ -214,7 +213,7 @@ function CameraDispatch({
214
213
  { ...zoom, defaultBinding: wheelOnly },
215
214
  tap
216
215
  ];
217
- }, []);
216
+ }, [frameRef, onTapRef]);
218
217
  useEffect(() => {
219
218
  const offs = actions.map((a) => registry.register(a));
220
219
  return () => {
@@ -261,10 +260,8 @@ function CameraDispatch({
261
260
  }
262
261
  function usePublishPointer(hostRef, clientToLocal, frame, viewId) {
263
262
  const store = usePointerContext();
264
- const frameRef = useRef(frame);
265
- frameRef.current = frame;
266
- const toLocal = useRef(clientToLocal);
267
- toLocal.current = clientToLocal;
263
+ const frameRef = useLatest(frame);
264
+ const toLocal = useLatest(clientToLocal);
268
265
  useEffect(() => {
269
266
  const el = hostRef.current;
270
267
  if (!el || !store) return;
@@ -282,7 +279,7 @@ function usePublishPointer(hostRef, clientToLocal, frame, viewId) {
282
279
  el.removeEventListener("pointermove", onMove);
283
280
  el.removeEventListener("pointerleave", onLeave);
284
281
  };
285
- }, [hostRef, store, viewId]);
282
+ }, [frameRef, hostRef, store, toLocal, viewId]);
286
283
  }
287
284
  function pointerElsewhere(p, viewId, frame) {
288
285
  if (!p || p.viewId === viewId) return null;
@@ -290,5 +287,5 @@ function pointerElsewhere(p, viewId, frame) {
290
287
  }
291
288
 
292
289
  export { CameraContext, CameraInput, CameraPublishContext, CameraRegistryContext, CameraScope, CameraWheelContext, DEFAULT_VIEW, STAGE_VIEW_ID, as2DView, clampZoomAbout, createCameraRegistry, frameLocalToWorld, fromCameraView, normalize2DView, pointerElsewhere, toCameraView, useCameraView, usePublishCamera, usePublishPointer, withZoom };
293
- //# sourceMappingURL=chunk-UNAZSLWE.js.map
294
- //# sourceMappingURL=chunk-UNAZSLWE.js.map
290
+ //# sourceMappingURL=chunk-VYVGVBGY.js.map
291
+ //# sourceMappingURL=chunk-VYVGVBGY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canvas/CameraWheelContext.ts","../src/state/view.ts","../src/canvas/cameraRegistry.ts","../src/canvas/cameraView.ts","../src/canvas/CameraInput.tsx"],"names":["createContext","useContext","useActionsRegistry","useEffect"],"mappings":"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","file":"chunk-VYVGVBGY.js","sourcesContent":["import { createContext } from 'react';\n\n/** Where a trial's camera takes a wheel event from outside its own element. */\nexport type CameraWheelSlot = { current: ((e: WheelEvent) => void) | null };\n\n/**\n * The trial's camera, for input that lands outside it. An annotation target's\n * input box is portalled into the surface container, so a wheel over a mark\n * never bubbles through the stage or canvas stack it sits on; the box hands\n * the event here instead.\n */\nexport const CameraWheelContext = createContext<CameraWheelSlot | null>(null);\n","import { normalizeZoom } from '@weasel-js/core';\nimport type { ViewTransform } from '../instrument/types';\n\n/** The 2D view labkit has always shipped, and what a trial gets when it names no\n * other. Re-exported under its own name so a consumer can say which it means. */\nexport type ViewTransform2D = ViewTransform;\n\n/** Zoom 1, no pan. */\nexport const DEFAULT_VIEW: ViewTransform2D = { zoom: 1, pan: { x: 0, y: 0 } };\n\n/** A trial's view is opaque to labkit, so anything that needs the 2D shape — the\n * zoom chrome, `CanvasStack` — asks for it and handles not getting it. */\nexport function as2DView(view: unknown): ViewTransform2D | null {\n if (typeof view !== 'object' || view === null) return null;\n const v = view as Partial<ViewTransform2D>;\n if (typeof v.zoom !== 'number') return null;\n if (typeof v.pan !== 'object' || v.pan === null) return null;\n if (typeof v.pan.x !== 'number' || typeof v.pan.y !== 'number') return null;\n return normalize2DView({ zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } });\n}\n\n/** `view` inside the invariant every view holds: a zoom that is not a finite\n * number of at least `ZOOM_FLOOR` becomes the floor, and a non-finite pan\n * becomes 0. Returns `view` itself when it already holds. */\nexport function normalize2DView(view: ViewTransform2D): ViewTransform2D {\n const zoom = normalizeZoom(view.zoom);\n const x = Number.isFinite(view.pan.x) ? view.pan.x : 0;\n const y = Number.isFinite(view.pan.y) ? view.pan.y : 0;\n if (zoom === view.zoom && x === view.pan.x && y === view.pan.y) return view;\n return { zoom, pan: { x, y } };\n}\n\n/** `view` at a new zoom, through the same rule. */\nexport function withZoom(view: ViewTransform2D, zoom: number): ViewTransform2D {\n return normalize2DView({ zoom, pan: view.pan });\n}\n","import { useActionsRegistry } from '@weasel-js/core';\nimport { createContext, useContext, useEffect } from 'react';\nimport type { CameraView } from './CameraInput';\n\n/**\n * Cameras by key, for chrome that acts on a camera it does not contain — the\n * lab's zoom controls, which drive whichever trial has the focus.\n */\nexport interface CameraRegistry {\n /** Publish `camera` under `key` and return its release. Registrants for one\n * key stack, newest live. */\n register(key: string, camera: CameraView): () => void;\n get(key: string): CameraView | null;\n /** Fires after any register or release. Shaped for `useSyncExternalStore`. */\n subscribe(listener: () => void): () => void;\n}\n\n/** An empty in-memory `CameraRegistry`. */\nexport function createCameraRegistry(): CameraRegistry {\n const stacks = new Map<string, CameraView[]>();\n const listeners = new Set<() => void>();\n const notify = (): void => {\n for (const l of listeners) l();\n };\n return {\n register(key, camera) {\n const stack = stacks.get(key) ?? [];\n stack.push(camera);\n stacks.set(key, stack);\n notify();\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const cur = stacks.get(key);\n const i = cur?.lastIndexOf(camera) ?? -1;\n if (!cur || i === -1) return;\n cur.splice(i, 1);\n if (cur.length === 0) stacks.delete(key);\n notify();\n };\n },\n get: (key) => stacks.get(key)?.at(-1) ?? null,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/** The registry a lab keeps its trials' cameras in. */\nexport const CameraRegistryContext = createContext<CameraRegistry | null>(null);\n\n/** Where a camera publishes itself. The host binds the key — a trial provides\n * one that registers under its own id — so the camera needs no name. */\nexport const CameraPublishContext = createContext<((camera: CameraView) => () => void) | null>(\n null,\n);\n\n/** Publish `camera` to the host in scope for as long as the caller is mounted,\n * and again each time its input scope becomes the active one — a registry\n * key stacks newest-live, so the camera last used is the one chrome reaches.\n * Call it inside the camera's scope. */\nexport function usePublishCamera(camera: CameraView): void {\n const publish = useContext(CameraPublishContext);\n const actions = useActionsRegistry();\n useEffect(() => {\n if (!publish) return;\n let release = publish(camera);\n if (!actions) return release;\n let was = actions.isActive();\n const off = actions.subscribe(() => {\n const now = actions.isActive();\n if (now && !was) {\n release();\n release = publish(camera);\n }\n was = now;\n });\n return () => {\n off();\n release();\n };\n }, [publish, camera, actions]);\n}\n","/**\n * labkit's camera as a weasel `View`, so core's viewport actions drive it.\n *\n * labkit places a world point on screen at\n * `originPx + pan + zoom * (x, yDir * y)`. A weasel `View` places one at\n * `(p - (x, y)) * scale`. Over *frame-local* coordinates — the world with `y`\n * multiplied by `yDir` — the two are the same affine map, which is all a pan\n * or a zoom about a screen point needs.\n */\nimport type { View } from '@weasel-js/core';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { normalize2DView } from '../state/view';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\n/** The weasel `View` equal to `view` under `frame`. */\nexport function toCameraView(view: ViewTransform, frame: WorldFrame = DEFAULT_FRAME): View {\n const v = normalize2DView(view);\n return {\n x: -(frame.originPx.x + v.pan.x) / v.zoom,\n y: -(frame.originPx.y + v.pan.y) / v.zoom,\n scale: { x: v.zoom, y: v.zoom },\n };\n}\n\n/** The labkit camera equal to weasel `view` under `frame`. */\nexport function fromCameraView(view: View, frame: WorldFrame = DEFAULT_FRAME): ViewTransform {\n const zoom = view.scale.x;\n return {\n zoom,\n pan: { x: -view.x * zoom - frame.originPx.x, y: -view.y * zoom - frame.originPx.y },\n };\n}\n\n/** A frame-local point in the instrument's world, and back — the map is its\n * own inverse. */\nexport function frameLocalToWorld(p: Point, frame: WorldFrame = DEFAULT_FRAME): Point {\n return { x: p.x, y: p.y * frame.yDir };\n}\n\n/**\n * `next` with its zoom held to [`min`, `max`], about the screen point the step\n * from `prev` was anchored on, so a zoom that hits a bound stays anchored where\n * the pointer is. A pan (equal scales) passes through.\n */\nexport function clampZoomAbout(prev: View, next: View, min: number, max: number): View {\n const s0 = prev.scale.x;\n const s1 = next.scale.x;\n const clamped = Math.min(max, Math.max(min, s1));\n if (clamped === s1) return next;\n if (s0 === s1) return { ...next, scale: { x: clamped, y: clamped } };\n // The screen point both views agree on: p / s0 + x0 = p / s1 + x1.\n const inv = 1 / s0 - 1 / s1;\n const px = (next.x - prev.x) / inv;\n const py = (next.y - prev.y) / inv;\n // The same point under the clamped scale.\n return {\n x: prev.x + px / s0 - px / clamped,\n y: prev.y + py / s0 - py / clamped,\n scale: { x: clamped, y: clamped },\n };\n}\n","/**\n * A labkit camera driven by weasel's dispatcher: drag pans, the wheel zooms\n * about the pointer, a tap reports the world point, and the pointer is\n * published into the store in scope. What `usePanZoom` used to hand-roll.\n */\nimport {\n type Action,\n type ActionsRegistry,\n InputScope,\n makeViewportZoomAction,\n PointerContextProvider,\n type PointerWorldPos,\n type Tool,\n useActionsRegistry,\n useDepSource,\n useGestureDispatcher,\n useLatest,\n usePointerContext,\n type View,\n type ViewApi,\n viewportDragPanAction,\n type Yoke,\n} from '@weasel-js/core';\nimport {\n createContext,\n type ReactNode,\n type RefObject,\n useContext,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n} from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { normalize2DView } from '../state/view';\nimport { CameraWheelContext } from './CameraWheelContext';\nimport { usePublishCamera } from './cameraRegistry';\nimport { clampZoomAbout, frameLocalToWorld, fromCameraView, toCameraView } from './cameraView';\nimport type { ViewportSize, WorldFrame } from './worldSpec';\n\n/** The view id the stage publishes its pointer under. */\nexport const STAGE_VIEW_ID = 'stage';\n\n/**\n * A camera, as the things that sit beside it read it: an overview, a loupe, a\n * linked cursor. Points are *frame-local* — the world with `y` multiplied by\n * `frame.yDir` — which is the space `view` works in.\n */\nexport interface CameraContextValue {\n /** The camera as a weasel `ViewApi`, clamped to its zoom range. */\n view: ViewApi;\n frame: WorldFrame;\n /** The element the camera's dispatcher listens on. */\n element: () => HTMLElement | null;\n /** The content's own rect, frame-local, when it has one — a `<Stage>`'s. */\n content?: { x: number; y: number; width: number; height: number };\n}\n\n/** The camera around the caller, or `null` outside one. */\nexport const CameraContext = createContext<CameraContextValue | null>(null);\n\n/**\n * The input scope a camera routes through. Inside another camera — a loupe or\n * an overlay laid over a canvas stack — it joins that camera's scope, so its\n * actions reach the dispatcher already listening there. Anywhere else it\n * mounts a scope of its own, which keeps its own tool unless it joins `yoke`:\n * a camera beside another never takes that one's gestures. The pointer store is the exception — one\n * already in scope is kept, since sharing it is the point.\n */\nexport function CameraScope({ yoke, children }: { yoke?: Yoke; children: ReactNode }) {\n const around = useContext(CameraContext);\n const pointer = usePointerContext();\n if (around) return <>{children}</>;\n const scoped = <InputScope yoke={yoke}>{children}</InputScope>;\n return pointer ? scoped : <PointerContextProvider>{scoped}</PointerContextProvider>;\n}\n\n/** Options for {@link useCameraView}. */\nexport interface CameraViewOptions {\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n frame: WorldFrame;\n hostRef: RefObject<HTMLElement | null>;\n minZoom?: number;\n maxZoom?: number;\n}\n\n/** A camera's zoom limits, after widening to admit its opening zoom. */\nexport interface ZoomRange {\n min: number;\n max: number;\n}\n\n/** A labkit camera: a weasel `ViewApi` that also says how far it zooms. */\nexport interface CameraView extends ViewApi {\n zoomRange(): ZoomRange;\n}\n\nfunction isPositiveFinite(n: number): boolean {\n return Number.isFinite(n) && n > 0;\n}\n\n/**\n * A labkit camera as a stable `ViewApi`. Its zoom range is widened to keep the\n * opening zoom reachable — captured once, so a zoom-out cannot shrink the\n * range and strand the opening view behind it.\n */\nexport function useCameraView({\n view,\n onViewChange,\n frame,\n hostRef,\n minZoom = 0.1,\n maxZoom = 32,\n}: CameraViewOptions): CameraView {\n // Written by each commit and by `set`, ahead of the render it asks for —\n // two writers, so not `useLatest`.\n const live = useRef({ view: normalize2DView(view), onViewChange, frame });\n useInsertionEffect(() => {\n live.current = { view: normalize2DView(view), onViewChange, frame };\n });\n const initialZoom = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);\n const opening = initialZoom.current;\n const bounds = useLatest({\n min: opening == null ? minZoom : Math.min(minZoom, opening),\n max: opening == null ? maxZoom : Math.max(maxZoom, opening),\n });\n\n return useMemo<CameraView>(() => {\n const get = (): View => toCameraView(live.current.view, live.current.frame);\n return {\n get,\n set: (next: View) => {\n const { min, max } = bounds.current;\n const clamped = clampZoomAbout(get(), next, min, max);\n const vt = fromCameraView(clamped, live.current.frame);\n // Written ahead of the render, so a second event inside one commit\n // steps from this one.\n live.current.view = vt;\n live.current.onViewChange(vt);\n },\n hostSize: () => {\n const r = hostRef.current?.getBoundingClientRect();\n return r ? { width: r.width, height: r.height } : null;\n },\n zoomRange: () => ({ ...bounds.current }),\n };\n }, [bounds, hostRef]);\n}\n\nconst TAP_ID = 'camera.tap';\nconst NO_TOOLS: ReadonlyMap<string, Tool> = new Map();\nconst CHANNELS = { contextMenu: false, ingest: false } as const;\n\n/** Props for `<CameraInput>`. */\nexport interface CameraInputProps {\n hostRef: RefObject<HTMLElement | null>;\n /** Published to the host's camera registry while its scope is the active\n * one, so chrome acting on \"the camera\" acts on the one last used. */\n camera: CameraView;\n frame: WorldFrame;\n /** A primary-button press released without crossing the drag threshold, at\n * the world point it landed on. */\n onTap?: (world: Point) => void;\n}\n\n/**\n * Mounts the dispatcher on `hostRef` and routes the camera's gestures through\n * it. Render it inside a `<CameraScope>`; with no actions registry in scope it\n * renders nothing.\n */\nexport function CameraInput(props: CameraInputProps) {\n const registry = useActionsRegistry();\n if (!registry) return null;\n return <CameraDispatch {...props} registry={registry} />;\n}\n\nfunction CameraDispatch({\n hostRef,\n camera,\n frame,\n onTap,\n registry,\n}: CameraInputProps & { registry: ActionsRegistry }) {\n const frameRef = useLatest(frame);\n const onTapRef = useLatest(onTap);\n\n useDepSource('view', () => camera);\n useDepSource('rootView', () => camera);\n\n const actions = useMemo<Action[]>(() => {\n // Unclamped here: `camera.set` clamps, to a range widened around the\n // opening zoom, and anchors the clamp where the wheel was.\n const zoom = makeViewportZoomAction({\n wheel: 'plain',\n min: 1e-9,\n max: Number.POSITIVE_INFINITY,\n });\n // The wheel and the trackpad's two pinch forms only: the zoom keys listen\n // on the window, and every trial on a page would take them at once.\n const wheelOnly = (zoom.defaultBinding as { spec: { kind: string } }[]).filter(\n (b) => b.spec.kind === 'wheel' || b.spec.kind === 'pinch',\n );\n const tap: Action = {\n id: TAP_ID,\n label: 'Tap the canvas',\n defaultBinding: { kind: 'click' },\n invoker: {\n timing: 'immediate',\n run: (_deps, params) => {\n const p = params as { worldX?: number; worldY?: number } | undefined;\n if (p?.worldX === undefined || p.worldY === undefined) return;\n onTapRef.current?.(frameLocalToWorld({ x: p.worldX, y: p.worldY }, frameRef.current));\n },\n },\n };\n return [\n // A trial pans on a plain drag; the kit action binds none of its own.\n { ...viewportDragPanAction, defaultBinding: { kind: 'drag' } },\n { ...zoom, defaultBinding: wheelOnly as Action['defaultBinding'] },\n tap,\n ];\n }, [frameRef, onTapRef]);\n\n useEffect(() => {\n const offs = actions.map((a) => registry.register(a));\n return () => {\n for (const off of offs) off();\n };\n }, [registry, actions]);\n\n const clientToWorld = useMemo(\n () => (cx: number, cy: number) => {\n const r = hostRef.current?.getBoundingClientRect();\n const v = camera.get();\n return {\n x: (cx - (r?.left ?? 0)) / v.scale.x + v.x,\n y: (cy - (r?.top ?? 0)) / v.scale.y + v.y,\n };\n },\n [hostRef, camera],\n );\n\n useGestureDispatcher({\n canvasRef: hostRef,\n actions: registry,\n entriesById: NO_TOOLS,\n clientToWorld,\n channels: CHANNELS,\n });\n\n usePublishPointer(hostRef, clientToWorld, frame, STAGE_VIEW_ID);\n usePublishCamera(camera);\n\n // Input that lands outside the camera's element — an annotation target's\n // box, portalled into the surface — reaches the active camera's dispatcher\n // through here.\n const wheelSlot = useContext(CameraWheelContext);\n useEffect(() => {\n if (!wheelSlot) return;\n const forward = (e: WheelEvent): void => {\n hostRef.current?.dispatchEvent(new WheelEvent('wheel', e));\n };\n const claim = (): void => {\n if (registry.isActive()) wheelSlot.current = forward;\n };\n claim();\n const off = registry.subscribe(claim);\n return () => {\n off();\n if (wheelSlot.current === forward) wheelSlot.current = null;\n };\n }, [wheelSlot, hostRef, registry]);\n\n return null;\n}\n\n/**\n * Publish the pointer over `hostRef` into the store in scope, in the\n * instrument's world, as `viewId`; clear it when the pointer leaves with no\n * button held.\n */\nexport function usePublishPointer(\n hostRef: RefObject<HTMLElement | null>,\n clientToLocal: (cx: number, cy: number) => Point,\n frame: WorldFrame,\n viewId: string,\n): void {\n const store = usePointerContext();\n const frameRef = useLatest(frame);\n const toLocal = useLatest(clientToLocal);\n useEffect(() => {\n const el = hostRef.current;\n if (!el || !store) return;\n const onMove = (e: PointerEvent): void => {\n const w = frameLocalToWorld(toLocal.current(e.clientX, e.clientY), frameRef.current);\n store.set({ worldX: w.x, worldY: w.y, viewId });\n };\n const onLeave = (e: PointerEvent): void => {\n if (e.buttons !== 0) return;\n if (store.get()?.viewId === viewId) store.set(null);\n };\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerleave', onLeave);\n return () => {\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerleave', onLeave);\n };\n }, [frameRef, hostRef, store, toLocal, viewId]);\n}\n\n/** The pointer as a frame-local point, or `null` when there is none or it is\n * over `viewId` itself. */\nexport function pointerElsewhere(\n p: PointerWorldPos,\n viewId: string,\n frame: WorldFrame,\n): Point | null {\n if (!p || p.viewId === viewId) return null;\n return frameLocalToWorld({ x: p.worldX, y: p.worldY }, frame);\n}\n\nexport type { ViewportSize };\n"]}
@@ -0,0 +1,302 @@
1
+ import { ControlRow, summarizeValue } from './chunk-WWJDPNZZ.js';
2
+ import { valueAtPath, auto, schemaNodeAtPath } from './chunk-READTDQX.js';
3
+ import { l2, R, oe, c, ae2, l3, i2, u, h3, c4 } from './chunk-U6UQVGFP.js';
4
+ import { isBuiltinToolPref, toHex8, withAlpha01, getAlpha01 } from '@weasel-js/core';
5
+ import { useState, useRef } from 'react';
6
+ import { Popover, Dialog } from 'react-aria-components';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ var NO_AUTO = /* @__PURE__ */ new Set();
10
+ var noop = () => {
11
+ };
12
+ var nameOf = (column) => column.title ?? column.label;
13
+ function ControlMatrix({
14
+ title,
15
+ stance,
16
+ tone,
17
+ schema,
18
+ columns,
19
+ rows,
20
+ config,
21
+ auto: auto2,
22
+ setConfig,
23
+ onColumnClick,
24
+ inheritHint,
25
+ className
26
+ }) {
27
+ const fallback = columns[0];
28
+ const hintFor = inheritHint ?? ((_) => fallback ? `from ${nameOf(fallback)}` : "");
29
+ const [open, setOpen] = useState(null);
30
+ const triggerRef = useRef(null);
31
+ const { anchor, portalProps } = l2();
32
+ const leafAt = (path) => {
33
+ const node = schemaNodeAtPath(schema.group, path);
34
+ return node && c4(node) ? node : void 0;
35
+ };
36
+ const rowSpecs = rows.map((row) => {
37
+ const leaves = columns.map((column) => leafAt(`${column.key}.${row.key}`));
38
+ const named = leaves.find((leaf) => leaf !== void 0);
39
+ return { row, leaves, label: row.label ?? named?.name ?? row.key, about: named?.description };
40
+ });
41
+ const table = /* @__PURE__ */ jsxs("div", { className: className ? `lk-control-matrix ${className}` : "lk-control-matrix", children: [
42
+ anchor,
43
+ /* @__PURE__ */ jsxs("table", { className: "lk-control-matrix__table", children: [
44
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
45
+ /* @__PURE__ */ jsx("td", { className: "lk-control-matrix__corner" }),
46
+ columns.map((column, i2) => /* @__PURE__ */ jsx(
47
+ "th",
48
+ {
49
+ scope: "col",
50
+ className: i2 === 0 ? "lk-control-matrix__head is-first" : "lk-control-matrix__head",
51
+ children: /* @__PURE__ */ jsx(ColumnHeader, { column, onClick: onColumnClick })
52
+ },
53
+ column.key
54
+ ))
55
+ ] }) }),
56
+ /* @__PURE__ */ jsx("tbody", { children: rowSpecs.map(({ row, leaves, label, about }) => /* @__PURE__ */ jsxs("tr", { children: [
57
+ /* @__PURE__ */ jsxs("th", { scope: "row", className: "lk-control-matrix__label", children: [
58
+ /* @__PURE__ */ jsx("span", { className: "lk-control-matrix__label-text", children: label }),
59
+ about ? /* @__PURE__ */ jsx(R, { label, description: about }) : null
60
+ ] }),
61
+ columns.map((column, i2) => {
62
+ const path = `${column.key}.${row.key}`;
63
+ const leaf = leaves[i2];
64
+ if (!leaf) {
65
+ return /* @__PURE__ */ jsx("td", { className: "lk-control-matrix__cell is-missing", children: /* @__PURE__ */ jsx("span", { role: "img", "aria-label": `${nameOf(column)} has no ${label}`, children: "\u2014" }) }, column.key);
66
+ }
67
+ const first = i2 === 0;
68
+ const inherited = !first && auto2.has(path);
69
+ return /* @__PURE__ */ jsx(
70
+ "td",
71
+ {
72
+ className: inherited ? "lk-control-matrix__cell is-inherited" : "lk-control-matrix__cell",
73
+ children: /* @__PURE__ */ jsx(
74
+ Cell,
75
+ {
76
+ path,
77
+ leaf,
78
+ value: valueAtPath(config, path),
79
+ name: `${nameOf(column)} ${label}`,
80
+ hint: inherited ? hintFor(column) : void 0,
81
+ canUnpin: !first && !inherited,
82
+ setConfig,
83
+ onOpen: (el) => {
84
+ triggerRef.current = el;
85
+ setOpen({ path, leaf, column, first, label });
86
+ }
87
+ }
88
+ )
89
+ },
90
+ column.key
91
+ );
92
+ })
93
+ ] }, row.key)) })
94
+ ] }),
95
+ /* @__PURE__ */ jsx(
96
+ Popover,
97
+ {
98
+ triggerRef,
99
+ isOpen: open !== null,
100
+ onOpenChange: (next) => {
101
+ if (!next) setOpen(null);
102
+ },
103
+ placement: "bottom",
104
+ className: "lk-control-matrix__popover",
105
+ "data-weasel-overlay": "",
106
+ ...portalProps,
107
+ children: open ? /* @__PURE__ */ jsxs(
108
+ Dialog,
109
+ {
110
+ className: "lk-control-matrix__editor",
111
+ "aria-label": `${nameOf(open.column)} ${open.label}`,
112
+ children: [
113
+ /* @__PURE__ */ jsx(oe, { pack: "one-up", children: /* @__PURE__ */ jsx(
114
+ ControlRow,
115
+ {
116
+ path: open.path,
117
+ leaf: open.leaf,
118
+ resolved: schema,
119
+ config,
120
+ shown: config,
121
+ setConfig,
122
+ pack: "one-up",
123
+ auto: NO_AUTO,
124
+ setRowAuto: noop
125
+ }
126
+ ) }),
127
+ clearable(open.leaf) ? /* @__PURE__ */ jsx("div", { className: "lk-control-matrix__editor-actions", children: /* @__PURE__ */ jsx(
128
+ c,
129
+ {
130
+ size: "sm",
131
+ variant: "ghost",
132
+ disabled: !valueAtPath(config, open.path) || auto2.has(open.path),
133
+ onClick: () => setConfig(open.path, null),
134
+ children: "Clear"
135
+ }
136
+ ) }) : null,
137
+ open.first ? null : /* @__PURE__ */ jsx("div", { className: "lk-control-matrix__editor-actions", children: /* @__PURE__ */ jsx(
138
+ c,
139
+ {
140
+ size: "sm",
141
+ variant: "ghost",
142
+ disabled: auto2.has(open.path),
143
+ tooltip: `Inherit ${hintFor(open.column)}`,
144
+ onClick: () => setConfig(open.path, auto),
145
+ children: "Inherit"
146
+ }
147
+ ) })
148
+ ]
149
+ }
150
+ ) : null
151
+ }
152
+ )
153
+ ] });
154
+ if (title === void 0 && stance === void 0 && tone === void 0) return table;
155
+ return /* @__PURE__ */ jsx(ae2, { title, stance, tone, children: table });
156
+ }
157
+ function ColumnHeader({
158
+ column,
159
+ onClick
160
+ }) {
161
+ const name = nameOf(column);
162
+ if (!onClick) {
163
+ return column.title && column.title !== column.label ? /* @__PURE__ */ jsx("abbr", { title: column.title, className: "lk-control-matrix__head-text", children: column.label }) : /* @__PURE__ */ jsx("span", { className: "lk-control-matrix__head-text", children: column.label });
164
+ }
165
+ return /* @__PURE__ */ jsxs(l3, { children: [
166
+ /* @__PURE__ */ jsx(i2, { children: /* @__PURE__ */ jsx(
167
+ "button",
168
+ {
169
+ type: "button",
170
+ className: "lk-control-matrix__head-button",
171
+ "aria-label": name,
172
+ onClick: () => onClick(column),
173
+ children: column.label
174
+ }
175
+ ) }),
176
+ /* @__PURE__ */ jsx(u, { children: name })
177
+ ] });
178
+ }
179
+ var clearable = (leaf) => leaf.kind === "color" && leaf.default === "";
180
+ function decimalsOf(step) {
181
+ if (step === void 0 || !Number.isFinite(step) || step <= 0) return 0;
182
+ const text = String(step);
183
+ const exp = /e-(\d+)$/.exec(text);
184
+ if (exp) return Number(exp[1]);
185
+ const dot = text.indexOf(".");
186
+ return dot === -1 ? 0 : text.length - dot - 1;
187
+ }
188
+ function summaryOf(field, value) {
189
+ if (field?.kind === "number") {
190
+ return typeof field.value === "number" ? field.value.toFixed(decimalsOf(field.step)) : "\u2014";
191
+ }
192
+ if (field?.kind === "enum") {
193
+ const option = field.options.find((o) => o.value === field.value);
194
+ if (!option) return "\u2014";
195
+ return typeof option.glyph === "string" ? option.glyph : option.label;
196
+ }
197
+ if (field?.kind === "string") return field.value || "\u2014";
198
+ return summarizeValue(value);
199
+ }
200
+ function Cell({
201
+ path,
202
+ leaf,
203
+ value,
204
+ name,
205
+ hint,
206
+ canUnpin,
207
+ setConfig,
208
+ onOpen
209
+ }) {
210
+ const shown = value ?? leaf.default;
211
+ const write = (next) => setConfig(path, next);
212
+ const field = isBuiltinToolPref(leaf) ? h3(leaf, { value: shown, setValue: write }) : null;
213
+ const label = hint ? `${name}, ${hint}` : name;
214
+ const unpinned = (e) => {
215
+ if (!e.altKey) return false;
216
+ e.preventDefault();
217
+ if (canUnpin) setConfig(path, auto);
218
+ return true;
219
+ };
220
+ let control;
221
+ if (field?.kind === "boolean") {
222
+ const on = field.value === true;
223
+ control = /* @__PURE__ */ jsx(
224
+ "button",
225
+ {
226
+ type: "button",
227
+ role: "switch",
228
+ "aria-checked": on,
229
+ "aria-label": label,
230
+ className: "lk-control-matrix__switch",
231
+ onClick: (e) => {
232
+ if (!unpinned(e)) field.onChange(!on);
233
+ },
234
+ children: /* @__PURE__ */ jsx("span", { className: "lk-control-matrix__track", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "lk-control-matrix__thumb" }) })
235
+ }
236
+ );
237
+ } else if (field?.kind === "color" && !clearable(leaf)) {
238
+ const stored = typeof field.value === "string" && field.value !== "" ? toHex8(field.value) : "#000000ff";
239
+ control = /* @__PURE__ */ jsx(
240
+ "input",
241
+ {
242
+ type: "color",
243
+ "aria-label": label,
244
+ className: "lk-control-matrix__swatch",
245
+ value: stored.slice(0, 7),
246
+ onClick: (e) => {
247
+ unpinned(e);
248
+ },
249
+ onChange: (e) => {
250
+ const rgb = e.currentTarget.value;
251
+ field.onChange(field.alpha ? withAlpha01(rgb, getAlpha01(stored)) : rgb);
252
+ }
253
+ }
254
+ );
255
+ } else if (field?.kind === "color") {
256
+ const set = typeof field.value === "string" && field.value !== "";
257
+ control = /* @__PURE__ */ jsx(
258
+ "button",
259
+ {
260
+ type: "button",
261
+ "aria-label": set ? label : `${label}, unset`,
262
+ "aria-haspopup": "dialog",
263
+ className: "lk-control-matrix__value",
264
+ onClick: (e) => {
265
+ if (!unpinned(e)) onOpen(e.currentTarget);
266
+ },
267
+ children: /* @__PURE__ */ jsx(
268
+ "span",
269
+ {
270
+ className: set ? "lk-control-matrix__chip" : "lk-control-matrix__chip is-unset",
271
+ style: set ? { "--lk-chip": field.value } : void 0,
272
+ "aria-hidden": "true"
273
+ }
274
+ )
275
+ }
276
+ );
277
+ } else {
278
+ const valueClass = field?.kind === "number" ? "lk-control-matrix__value is-number" : "lk-control-matrix__value";
279
+ control = /* @__PURE__ */ jsx(
280
+ "button",
281
+ {
282
+ type: "button",
283
+ "aria-label": label,
284
+ "aria-haspopup": "dialog",
285
+ className: valueClass,
286
+ onClick: (e) => {
287
+ if (!unpinned(e)) onOpen(e.currentTarget);
288
+ },
289
+ children: summaryOf(field, shown)
290
+ }
291
+ );
292
+ }
293
+ if (!hint) return control;
294
+ return /* @__PURE__ */ jsxs(l3, { children: [
295
+ /* @__PURE__ */ jsx(i2, { children: control }),
296
+ /* @__PURE__ */ jsx(u, { children: hint })
297
+ ] });
298
+ }
299
+
300
+ export { ControlMatrix };
301
+ //# sourceMappingURL=chunk-WFALF2X2.js.map
302
+ //# sourceMappingURL=chunk-WFALF2X2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/controls/ControlMatrix.tsx"],"names":["auto","l","c","i","RACPopover","RACDialog","ae","h"],"mappings":"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","file":"chunk-WFALF2X2.js","sourcesContent":["import { getAlpha01, isBuiltinToolPref, toHex8, withAlpha01 } from '@weasel-js/core';\nimport {\n Button,\n Focusable,\n isPrefLeaf,\n type PrefLeaf,\n type PropertyControlProps,\n PropertyHelp,\n PropertyList,\n PropertyPanel,\n prefFieldProps,\n type StanceProps,\n Tooltip,\n TooltipTrigger,\n useOverlayPortal,\n} from '@weasel-js/ui';\nimport {\n type CSSProperties,\n type DOMAttributes,\n type MouseEvent,\n type ReactElement,\n type ReactNode,\n useRef,\n useState,\n} from 'react';\nimport { Dialog as RACDialog, Popover as RACPopover } from 'react-aria-components';\nimport { auto as autoValue } from '../config/auto';\nimport { schemaNodeAtPath, valueAtPath } from '../config/path';\nimport type { ResolvedConfig } from '../config/types';\nimport { ControlRow } from './ControlPanel';\nimport { summarizeValue } from './inDialog';\n\n/** One column of a {@link ControlMatrix}: a config group, every one the same shape. */\nexport interface ControlMatrixColumn {\n /** Config path prefix of this column's group, e.g. `'looks.window'`. */\n key: string;\n /** Short header shown at narrow width, e.g. `'Win'`. */\n label: string;\n /** Full name, for the header's tooltip and accessible name, e.g. `'Window'`. */\n title?: string;\n}\n\n/** One row of a {@link ControlMatrix}: a setting every column's group may hold. */\nexport interface ControlMatrixRow {\n /** Path of the setting inside each column's group, e.g. `'background'` or `'fill.colorBy'`. */\n key: string;\n /** Row label. Defaults to the leaf's own name from the schema. */\n label?: string;\n}\n\nexport interface ControlMatrixProps extends StanceProps {\n /** Heads the matrix. With `title`, `stance` or `tone` given it sits in a\n * `<PropertyPanel>`, as a `ControlPanel` does. */\n title?: ReactNode;\n /** The whole resolved schema. A cell's leaf is looked up at\n * `${column.key}.${row.key}`; a cell with none draws a dash. */\n schema: ResolvedConfig;\n /** The scopes. The first is the fallback the others inherit from: its cells\n * never inherit, and its header is emphasized. */\n columns: readonly ControlMatrixColumn[];\n rows: readonly ControlMatrixRow[];\n /** Values to show, already resolved: an inherited cell's value is the one it\n * inherits. */\n config: Record<string, unknown>;\n /** Full dotted paths whose cells inherit, drawn ghosted. */\n auto: ReadonlySet<string>;\n /** Writes a value, which pins the cell, or labkit's `auto` sentinel, which\n * unpins it. */\n setConfig: (path: string, value: unknown) => void;\n /** Clicking a column header. Without it the headers are plain text. */\n onColumnClick?: (column: ControlMatrixColumn) => void;\n /** What an inherited cell's tooltip says. Defaults to `from <first column>`. */\n inheritHint?: (column: ControlMatrixColumn) => string;\n className?: string;\n}\n\nconst NO_AUTO: ReadonlySet<string> = new Set();\nconst noop = (): void => {};\n\nconst nameOf = (column: ControlMatrixColumn): string => column.title ?? column.label;\n\n/** The open popover: which cell, and the element it hangs off. */\ninterface OpenCell {\n path: string;\n leaf: PrefLeaf;\n column: ControlMatrixColumn;\n first: boolean;\n label: string;\n}\n\n/**\n * One setting across several config groups of the same shape: settings down\n * the side, scopes across the top, a compact read-only cell at each crossing.\n * A cell inherits from the first column until it is pinned; inherited cells\n * draw ghosted. Click a cell to edit it with the control a `ControlPanel` would\n * draw (a boolean flips and a color opens its picker in place); editing pins,\n * and the popover's Inherit button — or Option/Alt-click on the cell — unpins.\n */\nexport function ControlMatrix({\n title,\n stance,\n tone,\n schema,\n columns,\n rows,\n config,\n auto,\n setConfig,\n onColumnClick,\n inheritHint,\n className,\n}: ControlMatrixProps): ReactElement {\n const fallback = columns[0];\n const hintFor =\n inheritHint ?? ((_: ControlMatrixColumn) => (fallback ? `from ${nameOf(fallback)}` : ''));\n const [open, setOpen] = useState<OpenCell | null>(null);\n const triggerRef = useRef<HTMLElement | null>(null);\n const { anchor, portalProps } = useOverlayPortal();\n\n const leafAt = (path: string): PrefLeaf | undefined => {\n const node = schemaNodeAtPath(schema.group, path);\n return node && isPrefLeaf(node) ? node : undefined;\n };\n\n const rowSpecs = rows.map((row) => {\n const leaves = columns.map((column) => leafAt(`${column.key}.${row.key}`));\n const named = leaves.find((leaf) => leaf !== undefined);\n return { row, leaves, label: row.label ?? named?.name ?? row.key, about: named?.description };\n });\n\n const table = (\n <div className={className ? `lk-control-matrix ${className}` : 'lk-control-matrix'}>\n {anchor}\n <table className=\"lk-control-matrix__table\">\n <thead>\n <tr>\n <td className=\"lk-control-matrix__corner\" />\n {columns.map((column, i) => (\n <th\n key={column.key}\n scope=\"col\"\n className={i === 0 ? 'lk-control-matrix__head is-first' : 'lk-control-matrix__head'}\n >\n <ColumnHeader column={column} onClick={onColumnClick} />\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rowSpecs.map(({ row, leaves, label, about }) => (\n <tr key={row.key}>\n <th scope=\"row\" className=\"lk-control-matrix__label\">\n <span className=\"lk-control-matrix__label-text\">{label}</span>\n {about ? <PropertyHelp label={label} description={about} /> : null}\n </th>\n {columns.map((column, i) => {\n const path = `${column.key}.${row.key}`;\n const leaf = leaves[i];\n if (!leaf) {\n return (\n <td key={column.key} className=\"lk-control-matrix__cell is-missing\">\n <span role=\"img\" aria-label={`${nameOf(column)} has no ${label}`}>\n —\n </span>\n </td>\n );\n }\n const first = i === 0;\n const inherited = !first && auto.has(path);\n return (\n <td\n key={column.key}\n className={\n inherited ? 'lk-control-matrix__cell is-inherited' : 'lk-control-matrix__cell'\n }\n >\n <Cell\n path={path}\n leaf={leaf}\n value={valueAtPath(config, path)}\n name={`${nameOf(column)} ${label}`}\n hint={inherited ? hintFor(column) : undefined}\n canUnpin={!first && !inherited}\n setConfig={setConfig}\n onOpen={(el) => {\n triggerRef.current = el;\n setOpen({ path, leaf, column, first, label });\n }}\n />\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n <RACPopover\n triggerRef={triggerRef}\n isOpen={open !== null}\n onOpenChange={(next) => {\n if (!next) setOpen(null);\n }}\n placement=\"bottom\"\n className=\"lk-control-matrix__popover\"\n data-weasel-overlay=\"\"\n {...portalProps}\n >\n {open ? (\n <RACDialog\n className=\"lk-control-matrix__editor\"\n aria-label={`${nameOf(open.column)} ${open.label}`}\n >\n <PropertyList pack=\"one-up\">\n <ControlRow\n path={open.path}\n leaf={open.leaf}\n resolved={schema}\n config={config}\n shown={config}\n setConfig={setConfig}\n pack=\"one-up\"\n auto={NO_AUTO}\n setRowAuto={noop}\n />\n </PropertyList>\n {clearable(open.leaf) ? (\n <div className=\"lk-control-matrix__editor-actions\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={!valueAtPath(config, open.path) || auto.has(open.path)}\n onClick={() => setConfig(open.path, null)}\n >\n Clear\n </Button>\n </div>\n ) : null}\n {open.first ? null : (\n <div className=\"lk-control-matrix__editor-actions\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={auto.has(open.path)}\n tooltip={`Inherit ${hintFor(open.column)}`}\n onClick={() => setConfig(open.path, autoValue)}\n >\n Inherit\n </Button>\n </div>\n )}\n </RACDialog>\n ) : null}\n </RACPopover>\n </div>\n );\n\n if (title === undefined && stance === undefined && tone === undefined) return table;\n return (\n <PropertyPanel title={title} stance={stance} tone={tone}>\n {table}\n </PropertyPanel>\n );\n}\n\nfunction ColumnHeader({\n column,\n onClick,\n}: {\n column: ControlMatrixColumn;\n onClick?: (column: ControlMatrixColumn) => void;\n}) {\n const name = nameOf(column);\n if (!onClick) {\n return column.title && column.title !== column.label ? (\n <abbr title={column.title} className=\"lk-control-matrix__head-text\">\n {column.label}\n </abbr>\n ) : (\n <span className=\"lk-control-matrix__head-text\">{column.label}</span>\n );\n }\n return (\n <TooltipTrigger>\n <Focusable>\n <button\n type=\"button\"\n className=\"lk-control-matrix__head-button\"\n aria-label={name}\n onClick={() => onClick(column)}\n >\n {column.label}\n </button>\n </Focusable>\n <Tooltip>{name}</Tooltip>\n </TooltipTrigger>\n );\n}\n\n/** A color leaf whose default is empty: unset is a value of its own, not black. */\nconst clearable = (leaf: PrefLeaf): boolean => leaf.kind === 'color' && leaf.default === '';\n\n/** The number of decimals a step implies: `0.05` shows two, `1` none. */\nfunction decimalsOf(step: number | undefined): number {\n if (step === undefined || !Number.isFinite(step) || step <= 0) return 0;\n const text = String(step);\n const exp = /e-(\\d+)$/.exec(text);\n if (exp) return Number(exp[1]);\n const dot = text.indexOf('.');\n return dot === -1 ? 0 : text.length - dot - 1;\n}\n\n/** What a cell shows of a value it opens a popover to edit. */\nfunction summaryOf(field: PropertyControlProps | null, value: unknown): ReactNode {\n if (field?.kind === 'number') {\n return typeof field.value === 'number' ? field.value.toFixed(decimalsOf(field.step)) : '—';\n }\n if (field?.kind === 'enum') {\n const option = field.options.find((o) => o.value === field.value);\n if (!option) return '—';\n return typeof option.glyph === 'string' ? option.glyph : option.label;\n }\n if (field?.kind === 'string') return field.value || '—';\n return summarizeValue(value);\n}\n\nfunction Cell({\n path,\n leaf,\n value,\n name,\n hint,\n canUnpin,\n setConfig,\n onOpen,\n}: {\n path: string;\n leaf: PrefLeaf;\n value: unknown;\n /** The cell's accessible name: column and row. */\n name: string;\n /** Present while the cell inherits: where from. */\n hint?: string;\n canUnpin: boolean;\n setConfig: (path: string, value: unknown) => void;\n onOpen: (el: HTMLElement) => void;\n}) {\n const shown = value ?? leaf.default;\n const write = (next: unknown): void => setConfig(path, next);\n const field = isBuiltinToolPref(leaf)\n ? prefFieldProps(leaf, { value: shown, setValue: write })\n : null;\n const label = hint ? `${name}, ${hint}` : name;\n\n // Option/Alt-click hands a pinned cell back to the fallback, and does nothing\n // else on any cell — it must not also flip or open what it lands on.\n const unpinned = (e: MouseEvent): boolean => {\n if (!e.altKey) return false;\n e.preventDefault();\n if (canUnpin) setConfig(path, autoValue);\n return true;\n };\n\n // The shape `Focusable` asks of the element it makes a tooltip trigger.\n let control: ReactElement<DOMAttributes<HTMLElement>, string>;\n if (field?.kind === 'boolean') {\n const on = field.value === true;\n control = (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={on}\n aria-label={label}\n className=\"lk-control-matrix__switch\"\n onClick={(e) => {\n if (!unpinned(e)) field.onChange(!on);\n }}\n >\n <span className=\"lk-control-matrix__track\" aria-hidden=\"true\">\n <span className=\"lk-control-matrix__thumb\" />\n </span>\n </button>\n );\n } else if (field?.kind === 'color' && !clearable(leaf)) {\n const stored =\n typeof field.value === 'string' && field.value !== '' ? toHex8(field.value) : '#000000ff';\n control = (\n <input\n type=\"color\"\n aria-label={label}\n className=\"lk-control-matrix__swatch\"\n value={stored.slice(0, 7)}\n onClick={(e) => {\n unpinned(e);\n }}\n onChange={(e) => {\n const rgb = e.currentTarget.value;\n field.onChange(field.alpha ? withAlpha01(rgb, getAlpha01(stored)) : rgb);\n }}\n />\n );\n } else if (field?.kind === 'color') {\n // A color that may be unset opens the popover, which is the only place it can be cleared.\n const set = typeof field.value === 'string' && field.value !== '';\n control = (\n <button\n type=\"button\"\n aria-label={set ? label : `${label}, unset`}\n aria-haspopup=\"dialog\"\n className=\"lk-control-matrix__value\"\n onClick={(e) => {\n if (!unpinned(e)) onOpen(e.currentTarget);\n }}\n >\n <span\n className={set ? 'lk-control-matrix__chip' : 'lk-control-matrix__chip is-unset'}\n style={set ? ({ '--lk-chip': field.value } as CSSProperties) : undefined}\n aria-hidden=\"true\"\n />\n </button>\n );\n } else {\n const valueClass =\n field?.kind === 'number' ? 'lk-control-matrix__value is-number' : 'lk-control-matrix__value';\n control = (\n <button\n type=\"button\"\n aria-label={label}\n aria-haspopup=\"dialog\"\n className={valueClass}\n onClick={(e) => {\n if (!unpinned(e)) onOpen(e.currentTarget);\n }}\n >\n {summaryOf(field, shown)}\n </button>\n );\n }\n\n if (!hint) return control;\n return (\n <TooltipTrigger>\n <Focusable>{control}</Focusable>\n <Tooltip>{hint}</Tooltip>\n </TooltipTrigger>\n );\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { schemaNodeAtPath } from './chunk-Y23G2VZL.js';
1
+ import { schemaNodeAtPath } from './chunk-READTDQX.js';
2
2
 
3
3
  // src/config/fromConfigField.ts
4
4
  function toLeaf(field) {
@@ -79,5 +79,5 @@ function isLeafVisible(resolved, path, config, showHidden = false) {
79
79
  }
80
80
 
81
81
  export { fromConfigFields, isLeafVisible };
82
- //# sourceMappingURL=chunk-KMJ7RWD3.js.map
83
- //# sourceMappingURL=chunk-KMJ7RWD3.js.map
82
+ //# sourceMappingURL=chunk-WLMDTI2Q.js.map
83
+ //# sourceMappingURL=chunk-WLMDTI2Q.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/fromConfigField.ts","../src/config/visible.ts"],"names":[],"mappings":"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","file":"chunk-KMJ7RWD3.js","sourcesContent":["import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';\nimport type { ConfigField } from '../controls/types';\nimport type { LeafPatch, ResolvedConfig } from './types';\n\n/** One `ConfigField` as the `Pref*` vocabulary spells it. The two describe the\n * same thing; `ConfigField` just keys on the control where `PrefLeaf`\n * separates the value's kind from how it is presented. */\nfunction toLeaf(field: ConfigField): LeafPatch {\n const base = { name: field.label, description: '' };\n switch (field.type) {\n case 'slider':\n return {\n ...base,\n kind: 'number',\n control: 'slider',\n min: field.min,\n max: field.max,\n step: field.step,\n };\n case 'number':\n return {\n ...base,\n kind: 'number',\n control: 'input',\n min: field.min,\n max: field.max,\n step: field.step,\n };\n case 'checkbox':\n return { ...base, kind: 'boolean', control: 'checkbox' };\n case 'select':\n return { ...base, kind: 'enum', options: field.options };\n case 'text':\n return {\n ...base,\n kind: 'string',\n placeholder: field.placeholder,\n maxLength: field.maxLength,\n debounceMs: field.debounceMs,\n };\n case 'color':\n return { ...base, kind: 'color' };\n default:\n // A schema can arrive from outside TypeScript. Carry the kind through\n // so a lab-supplied renderer can still claim it.\n return { ...base, kind: (field as ConfigField).type };\n }\n}\n\n/**\n * Adapt a legacy `configSchema(): ConfigField[]` into the resolved shape, so\n * both instrument paths reach one renderer.\n *\n * Rules do not run here: a hand-written `ConfigField` already states its label\n * and control, which is everything the rule chain would have inferred.\n *\n * A dotted `key` is a config path, as it is to every read and write, so its\n * leaf is filed under headless groups at that path rather than as one child\n * whose name contains dots — which no path lookup would find.\n */\nexport function fromConfigFields(fields: readonly ConfigField[]): ResolvedConfig {\n const root: PrefGroup = { name: '', children: {} };\n for (const field of fields) {\n const patch = toLeaf(field);\n for (const key of Object.keys(patch)) {\n if ((patch as Record<string, unknown>)[key] === undefined) {\n delete (patch as Record<string, unknown>)[key];\n }\n }\n const segments = field.key.split('.');\n const leafKey = segments.pop() as string;\n let group = root;\n for (const segment of segments) {\n const existing = group.children[segment];\n if (existing === undefined || !('children' in existing) || 'kind' in existing) {\n group.children[segment] = { name: '', children: {} };\n }\n group = group.children[segment] as PrefGroup;\n }\n group.children[leafKey] = { ...patch, default: field.default } as PrefLeaf;\n }\n return {\n group: root,\n sections: [],\n showIf: new Map(),\n renderers: {},\n dialogs: {},\n };\n}\n","import { schemaNodeAtPath } from './path';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Whether the row at a dotted path should be drawn right now. A group answers\n * for its whole subtree — hiding one hides everything under it.\n *\n * `hidden` is the node's own static flag; `showIf` is the schema's predicate\n * over the live config. Both are presentational — a hidden leaf keeps its\n * value and the instrument still reads it.\n */\nexport function isLeafVisible(\n resolved: ResolvedConfig,\n path: string,\n config: Record<string, unknown>,\n showHidden = false,\n): boolean {\n const node = schemaNodeAtPath(resolved.group, path);\n if (node && 'hidden' in node && node.hidden === true && !showHidden) return false;\n const predicate = resolved.showIf.get(path);\n return predicate ? predicate(config) : true;\n}\n"]}
1
+ {"version":3,"sources":["../src/config/fromConfigField.ts","../src/config/visible.ts"],"names":[],"mappings":"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","file":"chunk-WLMDTI2Q.js","sourcesContent":["import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';\nimport type { ConfigField } from '../controls/types';\nimport type { LeafPatch, ResolvedConfig } from './types';\n\n/** One `ConfigField` as the `Pref*` vocabulary spells it. The two describe the\n * same thing; `ConfigField` just keys on the control where `PrefLeaf`\n * separates the value's kind from how it is presented. */\nfunction toLeaf(field: ConfigField): LeafPatch {\n const base = { name: field.label, description: '' };\n switch (field.type) {\n case 'slider':\n return {\n ...base,\n kind: 'number',\n control: 'slider',\n min: field.min,\n max: field.max,\n step: field.step,\n };\n case 'number':\n return {\n ...base,\n kind: 'number',\n control: 'input',\n min: field.min,\n max: field.max,\n step: field.step,\n };\n case 'checkbox':\n return { ...base, kind: 'boolean', control: 'checkbox' };\n case 'select':\n return { ...base, kind: 'enum', options: field.options };\n case 'text':\n return {\n ...base,\n kind: 'string',\n placeholder: field.placeholder,\n maxLength: field.maxLength,\n debounceMs: field.debounceMs,\n };\n case 'color':\n return { ...base, kind: 'color' };\n default:\n // A schema can arrive from outside TypeScript. Carry the kind through\n // so a lab-supplied renderer can still claim it.\n return { ...base, kind: (field as ConfigField).type };\n }\n}\n\n/**\n * Adapt a legacy `configSchema(): ConfigField[]` into the resolved shape, so\n * both instrument paths reach one renderer.\n *\n * Rules do not run here: a hand-written `ConfigField` already states its label\n * and control, which is everything the rule chain would have inferred.\n *\n * A dotted `key` is a config path, as it is to every read and write, so its\n * leaf is filed under headless groups at that path rather than as one child\n * whose name contains dots — which no path lookup would find.\n */\nexport function fromConfigFields(fields: readonly ConfigField[]): ResolvedConfig {\n const root: PrefGroup = { name: '', children: {} };\n for (const field of fields) {\n const patch = toLeaf(field);\n for (const key of Object.keys(patch)) {\n if ((patch as Record<string, unknown>)[key] === undefined) {\n delete (patch as Record<string, unknown>)[key];\n }\n }\n const segments = field.key.split('.');\n const leafKey = segments.pop() as string;\n let group = root;\n for (const segment of segments) {\n const existing = group.children[segment];\n if (existing === undefined || !('children' in existing) || 'kind' in existing) {\n group.children[segment] = { name: '', children: {} };\n }\n group = group.children[segment] as PrefGroup;\n }\n group.children[leafKey] = { ...patch, default: field.default } as PrefLeaf;\n }\n return {\n group: root,\n sections: [],\n showIf: new Map(),\n renderers: {},\n dialogs: {},\n };\n}\n","import { schemaNodeAtPath } from './path';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Whether the row at a dotted path should be drawn right now. A group answers\n * for its whole subtree — hiding one hides everything under it.\n *\n * `hidden` is the node's own static flag; `showIf` is the schema's predicate\n * over the live config. Both are presentational — a hidden leaf keeps its\n * value and the instrument still reads it.\n */\nexport function isLeafVisible(\n resolved: ResolvedConfig,\n path: string,\n config: Record<string, unknown>,\n showHidden = false,\n): boolean {\n const node = schemaNodeAtPath(resolved.group, path);\n if (node && 'hidden' in node && node.hidden === true && !showHidden) return false;\n const predicate = resolved.showIf.get(path);\n return predicate ? predicate(config) : true;\n}\n"]}