@weasel-js/labkit 1.7.3 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.md +24 -0
  2. package/dist/_dts/{CanvasStackContext-Cjy6I-Iv.d.ts → CanvasStackContext-B05ZM04l.d.ts} +3 -1
  3. package/dist/_dts/{Stage-D6t7o0pF.d.ts → Stage-CIJlbjrE.d.ts} +5 -3
  4. package/dist/_dts/{index-DCMA4Fy1.d.ts → index-8geQNQet.d.ts} +84 -12
  5. package/dist/_dts/{index-BTmaRy6a.d.ts → index-DRi3V5sN.d.ts} +6 -4
  6. package/dist/_dts/{types-BUBSIZuV.d.ts → types-DUfQ0isU.d.ts} +5 -1
  7. package/dist/_dts/{useTrialState-Bh8bzFgE.d.ts → useTrialState-Ar0AFpCe.d.ts} +94 -87
  8. package/dist/canvas/index.d.ts +5 -5
  9. package/dist/canvas/index.js +2 -2
  10. package/dist/chrome/index.d.ts +3 -3
  11. package/dist/chrome/index.js +7 -7
  12. package/dist/{chunk-HBGH344L.js → chunk-4RCVWX4M.js} +55 -18
  13. package/dist/chunk-4RCVWX4M.js.map +1 -0
  14. package/dist/{chunk-WIG6XHJ7.js → chunk-6IIKLXBZ.js} +13 -4
  15. package/dist/chunk-6IIKLXBZ.js.map +1 -0
  16. package/dist/{chunk-F4ACIK7Z.js → chunk-6Q6ACJ3H.js} +3 -3
  17. package/dist/{chunk-F4ACIK7Z.js.map → chunk-6Q6ACJ3H.js.map} +1 -1
  18. package/dist/{chunk-PMLZP7RT.js → chunk-7ALJOVGP.js} +5 -9
  19. package/dist/chunk-7ALJOVGP.js.map +1 -0
  20. package/dist/{chunk-XE5HJOPL.js → chunk-7FMZT2WL.js} +3 -3
  21. package/dist/{chunk-XE5HJOPL.js.map → chunk-7FMZT2WL.js.map} +1 -1
  22. package/dist/{chunk-XVUKDSYV.js → chunk-AVOILDN2.js} +2 -2
  23. package/dist/{chunk-XVUKDSYV.js.map → chunk-AVOILDN2.js.map} +1 -1
  24. package/dist/{chunk-2N7UBSS2.js → chunk-J5MDJEOZ.js} +3 -3
  25. package/dist/{chunk-2N7UBSS2.js.map → chunk-J5MDJEOZ.js.map} +1 -1
  26. package/dist/{chunk-LPGBC63T.js → chunk-KHB3KRRQ.js} +7 -7
  27. package/dist/chunk-KHB3KRRQ.js.map +1 -0
  28. package/dist/{chunk-IBVRMHW5.js → chunk-NHIO2O3O.js} +31 -30
  29. package/dist/chunk-NHIO2O3O.js.map +1 -0
  30. package/dist/{chunk-EESJUSYR.js → chunk-QMAY74TP.js} +56 -5
  31. package/dist/chunk-QMAY74TP.js.map +1 -0
  32. package/dist/{chunk-4X44DMRS.js → chunk-RN7T7OBO.js} +46 -24
  33. package/dist/chunk-RN7T7OBO.js.map +1 -0
  34. package/dist/{chunk-4TJ34FXA.js → chunk-RXO5UEGT.js} +5 -12
  35. package/dist/chunk-RXO5UEGT.js.map +1 -0
  36. package/dist/{chunk-E6NARGU7.js → chunk-SQX35X5V.js} +3 -3
  37. package/dist/{chunk-E6NARGU7.js.map → chunk-SQX35X5V.js.map} +1 -1
  38. package/dist/{chunk-YCFKW5RX.js → chunk-T5OEUNCB.js} +16 -2
  39. package/dist/chunk-T5OEUNCB.js.map +1 -0
  40. package/dist/{chunk-RL2LOLNI.js → chunk-UX66USTN.js} +2 -2
  41. package/dist/chunk-UX66USTN.js.map +1 -0
  42. package/dist/{chunk-IRMPZIYU.js → chunk-V4SMGEYY.js} +6732 -6507
  43. package/dist/chunk-V4SMGEYY.js.map +1 -0
  44. package/dist/{chunk-E23Q6A3Y.js → chunk-VCK5T6EP.js} +3 -3
  45. package/dist/{chunk-E23Q6A3Y.js.map → chunk-VCK5T6EP.js.map} +1 -1
  46. package/dist/{chunk-T6DLGEJB.js → chunk-XWR2P6MR.js} +23 -6
  47. package/dist/chunk-XWR2P6MR.js.map +1 -0
  48. package/dist/{chunk-XSJJTXNA.js → chunk-YZA73U4A.js} +19 -19
  49. package/dist/chunk-YZA73U4A.js.map +1 -0
  50. package/dist/config/index.d.ts +12 -5
  51. package/dist/config/index.js +2 -2
  52. package/dist/controls/index.d.ts +2 -2
  53. package/dist/controls/index.js +5 -5
  54. package/dist/dragdrop/index.d.ts +2 -2
  55. package/dist/dragdrop/index.js +2 -2
  56. package/dist/index.d.ts +158 -16
  57. package/dist/index.js +1036 -415
  58. package/dist/index.js.map +1 -1
  59. package/dist/layers/index.d.ts +2 -2
  60. package/dist/layers/index.js +3 -3
  61. package/dist/loupe/index.d.ts +176 -28
  62. package/dist/loupe/index.js +547 -141
  63. package/dist/loupe/index.js.map +1 -1
  64. package/dist/overview/index.d.ts +1 -1
  65. package/dist/overview/index.js +5 -5
  66. package/dist/passthrough/weasel-ui.js +3 -3
  67. package/dist/primitives/index.d.ts +4 -19
  68. package/dist/primitives/index.js +7 -7
  69. package/dist/state/index.d.ts +6 -8
  70. package/dist/state/index.js +5 -5
  71. package/dist/styles.css +181 -44
  72. package/dist/surface/index.d.ts +20 -1
  73. package/dist/surface/index.js +2 -2
  74. package/dist/undo/index.d.ts +19 -8
  75. package/dist/undo/index.js +1 -1
  76. package/package.json +11 -11
  77. package/src/annotations/AnnotationOverlay.abandon.test.tsx +4 -0
  78. package/src/annotations/AnnotationOverlay.tsx +10 -1
  79. package/src/annotations/Annotations.less +2 -0
  80. package/src/annotations/Annotations.overlay.test.tsx +29 -0
  81. package/src/canvas/CanvasStack.test.tsx +35 -0
  82. package/src/canvas/CanvasStack.tsx +12 -1
  83. package/src/canvas/useLayerScheduler.ts +14 -0
  84. package/src/chrome/builtins.test.ts +14 -0
  85. package/src/chrome/builtins.tsx +17 -2
  86. package/src/chrome/regions/StatusRegion.tsx +20 -14
  87. package/src/chrome/regions/regions.test.tsx +28 -0
  88. package/src/chrome/types.ts +8 -0
  89. package/src/clock/clock.browser.test.less +5 -0
  90. package/src/clock/clock.browser.test.tsx +77 -0
  91. package/src/clock/clock.integration.test.tsx +95 -0
  92. package/src/clock/clockRegistry.test.ts +33 -0
  93. package/src/clock/clockRegistry.ts +46 -0
  94. package/src/clock/hooks.ts +44 -0
  95. package/src/clock/index.ts +2 -0
  96. package/src/clock/trialClock.test.ts +265 -0
  97. package/src/clock/trialClock.ts +245 -0
  98. package/src/clock/useClockLoop.ts +45 -0
  99. package/src/config/builder.ts +11 -1
  100. package/src/config/resolve.test.ts +12 -0
  101. package/src/config/resolve.ts +5 -0
  102. package/src/config/types.ts +5 -1
  103. package/src/controls/ControlMatrix.test.tsx +10 -0
  104. package/src/controls/ControlMatrix.tsx +110 -58
  105. package/src/controls/ControlPanel.test.tsx +30 -0
  106. package/src/index.ts +3 -2
  107. package/src/instrument/types.ts +21 -1
  108. package/src/lab/Lab.chrome.test.tsx +8 -1
  109. package/src/lab/Lab.presentation.test.tsx +113 -0
  110. package/src/lab/Lab.surface.test.tsx +20 -9
  111. package/src/lab/Lab.tsx +132 -257
  112. package/src/lab/LabLightbox.browser.test.less +17 -0
  113. package/src/lab/LabLightbox.browser.test.tsx +216 -0
  114. package/src/lab/LabShell.less +10 -3
  115. package/src/lab/LabSurface.tsx +129 -0
  116. package/src/lab/Workspace.stories.tsx +18 -0
  117. package/src/lab/Workspace.surface.test.tsx +4 -0
  118. package/src/lab/Workspace.test.tsx +37 -1
  119. package/src/lab/Workspace.tsx +34 -6
  120. package/src/lab/index.ts +2 -0
  121. package/src/lab/nebula.ts +30 -0
  122. package/src/lab/openLab.test.ts +81 -0
  123. package/src/lab/openLab.ts +119 -0
  124. package/src/lab/presentation.browser.test.less +5 -0
  125. package/src/lab/presentation.browser.test.tsx +119 -0
  126. package/src/lab/presentation.less +64 -0
  127. package/src/lab/presentation.tsx +47 -0
  128. package/src/lab/useFocusPick.ts +43 -0
  129. package/src/lightbox/Lightbox.browser.test.less +14 -0
  130. package/src/lightbox/Lightbox.browser.test.tsx +113 -0
  131. package/src/lightbox/Lightbox.less +128 -0
  132. package/src/lightbox/Lightbox.test.tsx +195 -0
  133. package/src/lightbox/Lightbox.tsx +205 -0
  134. package/src/lightbox/LightboxContext.ts +38 -0
  135. package/src/lightbox/LightboxLayers.tsx +40 -0
  136. package/src/lightbox/index.ts +8 -0
  137. package/src/lightbox/modal.ts +94 -0
  138. package/src/loupe/AGENTS.md +40 -2
  139. package/src/loupe/CanvasLoupe.tsx +5 -4
  140. package/src/loupe/DomLoupe.tsx +7 -5
  141. package/src/loupe/Loupe.less +15 -0
  142. package/src/loupe/LoupeBubble.tsx +35 -13
  143. package/src/loupe/SourceLoupe.browser.test.tsx +204 -0
  144. package/src/loupe/SourceLoupe.tsx +60 -0
  145. package/src/loupe/TrialLoupe.browser.test.tsx +342 -0
  146. package/src/loupe/TrialLoupe.tsx +126 -28
  147. package/src/loupe/canvasLens.ts +20 -17
  148. package/src/loupe/index.ts +26 -2
  149. package/src/loupe/sourceLens.test.ts +65 -0
  150. package/src/loupe/sourceLens.ts +97 -0
  151. package/src/loupe/types.test.ts +6 -0
  152. package/src/loupe/types.ts +109 -7
  153. package/src/loupe/useLoupe.test.tsx +188 -3
  154. package/src/loupe/useLoupe.ts +106 -11
  155. package/src/primitives/index.ts +2 -2
  156. package/src/specimen/Specimen.tsx +0 -7
  157. package/src/state/helpers.test.ts +4 -11
  158. package/src/state/helpers.ts +2 -8
  159. package/src/state/index.ts +0 -2
  160. package/src/state/openLabStore.test.ts +10 -8
  161. package/src/state/openLabStore.ts +1 -1
  162. package/src/state/store.test.ts +2 -2
  163. package/src/state/store.ts +15 -16
  164. package/src/state/types.ts +9 -9
  165. package/src/styles.less +2 -1
  166. package/src/surface/AGENTS.md +13 -0
  167. package/src/surface/index.ts +1 -0
  168. package/src/surface/useSurfaceTile.test.tsx +4 -0
  169. package/src/surface/useSurfaceTile.ts +17 -1
  170. package/src/surface/useTiledSurface.test.tsx +65 -0
  171. package/src/surface/useTiledSurface.ts +68 -1
  172. package/src/theme/interstellar.theme.json +0 -14
  173. package/src/trial/Trial.stories.tsx +0 -1
  174. package/src/trial/Trial.test.tsx +40 -3
  175. package/src/trial/Trial.tsx +87 -43
  176. package/src/trial/TrialChrome.tsx +20 -2
  177. package/src/trial/trialOps.test.ts +1 -2
  178. package/src/trial/trialOps.ts +2 -3
  179. package/src/undo/stateOp.ts +21 -0
  180. package/src/undo/undoStack.ts +18 -6
  181. package/dist/chunk-4TJ34FXA.js.map +0 -1
  182. package/dist/chunk-4X44DMRS.js.map +0 -1
  183. package/dist/chunk-EESJUSYR.js.map +0 -1
  184. package/dist/chunk-HBGH344L.js.map +0 -1
  185. package/dist/chunk-IBVRMHW5.js.map +0 -1
  186. package/dist/chunk-IRMPZIYU.js.map +0 -1
  187. package/dist/chunk-LPGBC63T.js.map +0 -1
  188. package/dist/chunk-PMLZP7RT.js.map +0 -1
  189. package/dist/chunk-RL2LOLNI.js.map +0 -1
  190. package/dist/chunk-T6DLGEJB.js.map +0 -1
  191. package/dist/chunk-WIG6XHJ7.js.map +0 -1
  192. package/dist/chunk-XSJJTXNA.js.map +0 -1
  193. package/dist/chunk-YCFKW5RX.js.map +0 -1
  194. package/src/primitives/StatusBar.less +0 -34
  195. package/src/primitives/StatusBar.stories.tsx +0 -24
  196. package/src/primitives/StatusBar.test.tsx +0 -25
  197. package/src/primitives/StatusBar.tsx +0 -28
@@ -1,9 +1,9 @@
1
1
  export { LayerList, LayerListItem, LayerListProps, LayerMove, moveLayers } from '@weasel-js/ui';
2
- export { ad as LayerCapability, ae as LayerDescriptor } from '../_dts/index-DCMA4Fy1.js';
2
+ export { ae as LayerCapability, af as LayerDescriptor } from '../_dts/index-8geQNQet.js';
3
3
  import '@weasel-js/core';
4
4
  import 'react';
5
5
  import '@weasel-js/theme';
6
- import '../_dts/types-BUBSIZuV.js';
6
+ import '../_dts/types-DUfQ0isU.js';
7
7
  import '@weasel-js/quantity';
8
8
  import '@weasel-js/geom';
9
9
  import '../_dts/types-DJ79Tg5J.js';
@@ -1,6 +1,6 @@
1
1
  import '../chunk-2RVCB2PT.js';
2
- export { D2 as LayerList, S2 as moveLayers } from '../chunk-IRMPZIYU.js';
3
- import '../chunk-E23Q6A3Y.js';
4
- import '../chunk-XVUKDSYV.js';
2
+ export { A2 as LayerList, T as moveLayers } from '../chunk-V4SMGEYY.js';
3
+ import '../chunk-VCK5T6EP.js';
4
+ import '../chunk-AVOILDN2.js';
5
5
  //# sourceMappingURL=index.js.map
6
6
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
- import { LoupePoint, LoupeMode } from '@weasel-js/loupe';
2
- export { LoupeMode, LoupePoint } from '@weasel-js/loupe';
1
+ import { LoupePoint, LoupeMode, LoupeSize, CanvasSource, SourceCanvas, SourceContext, SourceBox } from '@weasel-js/loupe';
2
+ export { BandPlacement, CanvasSource, CanvasSourceOptions, LoupeMode, LoupePoint, LoupeSize, PlaceBandArgs, SourceBox, SourceCanvas, SourceContext, createCanvasSource, loupeExtent, placeBand } from '@weasel-js/loupe';
3
3
  import * as react from 'react';
4
4
  import { ReactNode, RefObject } from 'react';
5
5
  import { CanvasStackSurface, CanvasLayerDescriptor } from '../canvas/index.js';
@@ -8,7 +8,7 @@ import { ViewTransform } from '../index.js';
8
8
  import { Action } from '@weasel-js/core';
9
9
  import '@weasel-js/ui';
10
10
  import '@weasel-js/theme';
11
- import '../_dts/types-BUBSIZuV.js';
11
+ import '../_dts/types-DUfQ0isU.js';
12
12
  import '@weasel-js/quantity';
13
13
  import '@weasel-js/geom';
14
14
  import '../_dts/types-DJ79Tg5J.js';
@@ -18,7 +18,8 @@ interface CanvasLoupeProps {
18
18
  aim: LoupePoint;
19
19
  factor: number;
20
20
  mode: LoupeMode;
21
- diameter: number;
21
+ /** One number for a round or square lens, or a width and a height. */
22
+ diameter: LoupeSize;
22
23
  /** The stack being magnified: its layers, its pixels, and its measured box. */
23
24
  surface: CanvasStackSurface;
24
25
  view: ViewTransform;
@@ -32,7 +33,7 @@ interface CanvasLoupeProps {
32
33
  */
33
34
  declare function CanvasLoupe({ aim, factor, mode, diameter, surface, view, frame, worldSpec, }: CanvasLoupeProps): react.JSX.Element;
34
35
 
35
- /** The camera a round lens of `diameter` shows a magnified region through, and
36
+ /** The camera a lens of `size` shows a magnified region through, and
36
37
  * the coordinate system it is read in — the instrument's own `WorldSpec`,
37
38
  * resolved against the lens rather than the stack. */
38
39
  interface LensCamera {
@@ -45,7 +46,7 @@ interface LensCamera {
45
46
  * The lens is its own viewport, so the world spec resolves against its box: an
46
47
  * instrument centred on its viewport is centred in the lens too.
47
48
  */
48
- declare function lensCamera(aim: LoupePoint, outer: ViewTransform, outerFrame: WorldFrame, factor: number, diameter: number, worldSpec?: WorldSpec): LensCamera;
49
+ declare function lensCamera(aim: LoupePoint, outer: ViewTransform, outerFrame: WorldFrame, factor: number, size: LoupeSize, worldSpec?: WorldSpec): LensCamera;
49
50
  /** A rectangle in a backing store's own device pixels. */
50
51
  interface SourceRect {
51
52
  sx: number;
@@ -54,10 +55,10 @@ interface SourceRect {
54
55
  sh: number;
55
56
  }
56
57
  /**
57
- * The region a pixel-mode lens copies out of a presented canvas: the `diameter
58
- * / factor` CSS px around `aim`, in that canvas' backing-store pixels.
58
+ * The region a pixel-mode lens copies out of a presented canvas: `size /
59
+ * factor` CSS px around `aim`, in that canvas' backing-store pixels.
59
60
  */
60
- declare function lensSourceRect(aim: LoupePoint, factor: number, diameter: number, dpr: number): SourceRect;
61
+ declare function lensSourceRect(aim: LoupePoint, factor: number, size: LoupeSize, dpr: number): SourceRect;
61
62
  /**
62
63
  * The color a stack presents at a point: the topmost visible layer with
63
64
  * anything opaque there, or `null` when every layer is transparent — which is
@@ -69,7 +70,7 @@ declare function sampleStack(layers: readonly CanvasLayerDescriptor[], canvases:
69
70
  declare function drawCanvasLens(ctx: CanvasRenderingContext2D, opts: {
70
71
  aim: LoupePoint;
71
72
  factor: number;
72
- diameter: number;
73
+ diameter: LoupeSize;
73
74
  dpr: number;
74
75
  mode: 'vector' | 'pixel';
75
76
  outer: ViewTransform;
@@ -79,6 +80,33 @@ declare function drawCanvasLens(ctx: CanvasRenderingContext2D, opts: {
79
80
  canvases: Map<string, HTMLCanvasElement>;
80
81
  }): void;
81
82
 
83
+ /** Anything a lens can be pointed at for its pixels: a `CanvasSource`, a
84
+ * canvas, a context on one, or a function returning any of those once it
85
+ * exists. */
86
+ type LoupeSourceTarget = CanvasSource | SourceCanvas | SourceContext;
87
+ type LoupeSource = LoupeSourceTarget | (() => LoupeSourceTarget | null | undefined);
88
+ /** `source` as a `CanvasSource`, or `null` while a getter has nothing yet. */
89
+ declare function resolveLoupeSource(source: LoupeSource | undefined): CanvasSource | null;
90
+ /**
91
+ * Where a source canvas sits over the lens's host, in the host's CSS px: its
92
+ * own `box` if it has one, else the canvas's laid-out box measured against the
93
+ * host, else — for an offscreen canvas, or one not on the page — the whole
94
+ * host.
95
+ */
96
+ declare function sourceBoxIn(source: CanvasSource, host: HTMLElement | null): SourceBox;
97
+ /** The color a source shows at a host point, or `null` where it shows none. */
98
+ declare function sampleSource(source: CanvasSource, p: LoupePoint, box: SourceBox): string | null;
99
+ /** Enlarge the pixels of `source` around `aim` into a lens' own canvas, with
100
+ * smoothing off. Leaves the lens clear while the source has no frame. */
101
+ declare function drawSourceLens(ctx: CanvasRenderingContext2D, opts: {
102
+ aim: LoupePoint;
103
+ factor: number;
104
+ diameter: LoupeSize;
105
+ dpr: number;
106
+ source: CanvasSource;
107
+ box: SourceBox;
108
+ }): void;
109
+
82
110
  /** What a DOM loupe's `render` is handed: the camera to draw the content
83
111
  * through again. */
84
112
  interface LoupeRenderArgs {
@@ -93,15 +121,62 @@ interface LoupeRenderArgs {
93
121
  * not the lens. The lens shows a circle cut out of it. */
94
122
  size: ViewportSize;
95
123
  }
124
+ /** The outline of a lens. On a box that is not square, `'circle'` is an ellipse. */
125
+ type LoupeShape = 'circle' | 'square';
126
+ /** Where a host puts the lens for one aim, in the host's own CSS px. */
127
+ interface LoupePlacement {
128
+ /** Where the lens is drawn: the middle of its box. */
129
+ center: LoupePoint;
130
+ /** The host point the lens shows at its middle. Omitted, `center`; it
131
+ * differs when a lens is moved to stay on the host but keeps showing what
132
+ * it was placed over. */
133
+ shows?: LoupePoint;
134
+ width: number;
135
+ height: number;
136
+ /** The magnification for this placement. Omitted, the wheel's. */
137
+ factor?: number;
138
+ }
139
+ /** Where a lens is drawn and what it shows, once `place` has had its say. */
140
+ interface LoupeLens {
141
+ /** Where the lens is drawn: the middle of its box. */
142
+ center: LoupePoint;
143
+ /** The host point the lens shows at its middle. */
144
+ shows: LoupePoint;
145
+ width: number;
146
+ height: number;
147
+ /** What it magnifies by: the wheel's, unless `place` chose another. */
148
+ factor: number;
149
+ shape: LoupeShape;
150
+ }
151
+ /** What `place` is asked with. */
152
+ interface LoupePlaceArgs {
153
+ aim: LoupePoint;
154
+ factor: number;
155
+ }
96
156
  /**
97
157
  * How a lens magnifies.
98
158
  *
99
159
  * With no `render`, the lens re-draws the canvas stack's layers through a
100
160
  * zoomed camera, so it stays sharp at any factor. Over DOM content, `render`
101
- * draws it instead: given a camera, draw me again.
161
+ * draws it instead: given a camera, draw me again. With a `source`, it
162
+ * enlarges the pixels of that canvas — any canvas, labkit's or not.
102
163
  */
103
164
  interface LoupeOptions {
104
165
  render?: (args: LoupeRenderArgs) => ReactNode;
166
+ /**
167
+ * A canvas to read pixels from instead of the canvas stack: a
168
+ * `CanvasSource` from `createCanvasSource`, a canvas, a context on one, or
169
+ * a function returning one once it exists. 2D, WebGL and WebGL2 all work.
170
+ * Without `render` the lens enlarges its pixels, so the mode is `'pixel'`;
171
+ * with `render`, `render` draws the lens and the source answers
172
+ * `onColorChange`.
173
+ *
174
+ * A WebGL canvas made without `preserveDrawingBuffer` is blank to anyone
175
+ * reading it after the frame that drew it. Make its source with
176
+ * `createCanvasSource(gl)` and call `source.capture()` right after each
177
+ * draw; a canvas that draws on demand also passes `requestRedraw`.
178
+ */
179
+ source?: LoupeSource;
105
180
  /** Opening magnification, clamped to the bounds below. Default 6. */
106
181
  factor?: number;
107
182
  /** What the wheel clamps to. Defaults 2 and 32. */
@@ -109,26 +184,61 @@ interface LoupeOptions {
109
184
  maxFactor?: number;
110
185
  /** `'vector'` (default) re-renders the content magnified; `'pixel'` blows up
111
186
  * the pixels the instrument presented. A `render` loupe is always vector —
112
- * DOM has no framebuffer to enlarge. */
187
+ * DOM has no framebuffer to enlarge — and a `source` loupe always pixel. */
113
188
  mode?: LoupeMode;
114
189
  /** Lens diameter in CSS px. Default 200. */
115
190
  diameter?: number;
191
+ /** Default `'circle'`. */
192
+ shape?: LoupeShape;
193
+ /**
194
+ * Puts the lens somewhere other than a `diameter` box on the aim: the lens is
195
+ * drawn with the returned box centered on `center`, and shows what is around
196
+ * `shows` (or `center`) magnified by the returned `factor`, or by the wheel's when it
197
+ * returns none. `factor` in the argument is always the wheel's, so a host
198
+ * lowering it to fit something in keeps the wheel as the ceiling. Returning
199
+ * `null` keeps the default. It is
200
+ * called while the lens renders, so a host whose content moves under a still
201
+ * pointer re-renders the lens to re-place it. The color under the aim is
202
+ * still the one `onColorChange` reports.
203
+ */
204
+ place?: (at: LoupePlaceArgs) => LoupePlacement | null;
205
+ /**
206
+ * Draw only the lens's outline — ring, shadow, shape — and leave its inside
207
+ * empty, so whatever is under it shows through: for a host that draws the
208
+ * magnified view itself, into the box `onLens` reports. No painter runs; a
209
+ * `source`, if given, still answers `onColorChange`. Under a tiled surface,
210
+ * each move invalidates the tiles under the box it left and the box it now
211
+ * covers, so the host redraws those and no others.
212
+ */
213
+ hollow?: boolean;
214
+ /**
215
+ * Called with where the lens is drawn and what it shows, in host CSS px,
216
+ * whenever that changes — an aim, the wheel, `place`, `shape` — and with
217
+ * `null` when a lens that was up goes away.
218
+ */
219
+ onLens?: (lens: LoupeLens | null) => void;
116
220
  /** Held for a momentary peek while the loupe is off. Default `'Alt'`; `null`
117
221
  * turns hold-to-peek off. Matched against `KeyboardEvent.key`. */
118
222
  peekKey?: string | null;
119
223
  /** Called with the color under the aim, wherever the surface can say. The
120
- * canvas painter reads it back; a DOM loupe has no pixels to sample. */
224
+ * canvas painter and a `source` read it back; a DOM loupe with no `source`
225
+ * has no pixels to sample. */
121
226
  onColorChange?: (hex: string) => void;
122
227
  }
123
228
  /** {@link LoupeOptions} with every default filled in. */
124
229
  interface ResolvedLoupe {
125
230
  render?: (args: LoupeRenderArgs) => ReactNode;
231
+ source?: LoupeSource;
126
232
  onColorChange?: (hex: string) => void;
127
233
  factor: number;
128
234
  minFactor: number;
129
235
  maxFactor: number;
130
236
  mode: LoupeMode;
131
237
  diameter: number;
238
+ shape: LoupeShape;
239
+ place?: (at: LoupePlaceArgs) => LoupePlacement | null;
240
+ hollow: boolean;
241
+ onLens?: (lens: LoupeLens | null) => void;
132
242
  peekKey: string | null;
133
243
  }
134
244
  /** What `resolveLoupe` fills in for anything a declaration leaves unset. */
@@ -138,10 +248,13 @@ declare const LOUPE_DEFAULTS: {
138
248
  readonly maxFactor: 32;
139
249
  readonly mode: "vector";
140
250
  readonly diameter: 200;
251
+ readonly shape: "circle";
252
+ readonly hollow: false;
141
253
  readonly peekKey: "Alt";
142
254
  };
143
255
  /** `options` with every default filled in and `factor` clamped to
144
- * `[minFactor, maxFactor]`. A loupe with its own `render` is always `'vector'`. */
256
+ * `[minFactor, maxFactor]`. A loupe with its own `render` is always `'vector'`,
257
+ * and one with only a `source` always `'pixel'`. */
145
258
  declare function resolveLoupe(options?: LoupeOptions): ResolvedLoupe;
146
259
 
147
260
  /** Props for `<DomLoupe>`. */
@@ -149,7 +262,8 @@ interface DomLoupeProps {
149
262
  aim: LoupePoint;
150
263
  factor: number;
151
264
  mode: LoupeMode;
152
- diameter: number;
265
+ /** One number for a round or square lens, or a width and a height. */
266
+ diameter: LoupeSize;
153
267
  /** The host the instrument draws into, which the stage reproduces. */
154
268
  size: ViewportSize;
155
269
  /** The trial's own camera. */
@@ -163,28 +277,33 @@ interface DomLoupeProps {
163
277
  *
164
278
  * The stage is a full copy of the host, so the instrument's own layout still
165
279
  * holds; the camera is composed about the aimed point, which keeps that point
166
- * where it already was, and the stage is then slid by `diameter / 2 - aim` to
167
- * bring it to the middle of the lens.
280
+ * where it already was, and the stage is then slid by half the lens less `aim`
281
+ * to bring it to the middle of the lens.
168
282
  */
169
283
  declare function DomLoupe({ aim, factor, mode, diameter, size, view, frame, render, }: DomLoupeProps): react.JSX.Element;
170
284
 
171
285
  /** Props for `<LoupeBubble>`. */
172
286
  interface LoupeBubbleProps {
173
- /** Where the lens is aimed, in its container's own pixels. */
287
+ /** Where the lens is centered, in its container's own pixels. */
174
288
  aim: LoupePoint;
175
- diameter: number;
289
+ /** One number for a round or square lens, or a width and a height. */
290
+ diameter: LoupeSize;
291
+ /** Default `'circle'`; on a box that is not square, `'circle'` is an ellipse. */
292
+ shape?: LoupeShape;
293
+ /** Only the ring and shadow: the inside is left clear for what is under it. */
294
+ hollow?: boolean;
176
295
  hostRef?: RefObject<HTMLDivElement | null>;
177
- children: ReactNode;
296
+ children?: ReactNode;
178
297
  }
179
298
  /**
180
- * The lens itself: a circle centred on the aimed point, clipping whatever a
181
- * painter draws into it.
299
+ * The lens itself: a circle or a rectangle centered on `aim`, clipping whatever
300
+ * a painter draws into it.
182
301
  *
183
302
  * It takes no pointer events, so the pan, the wheel and anything underneath
184
303
  * keep working while it is up — and it is `aria-hidden`, since it magnifies
185
304
  * content already on the page rather than adding any.
186
305
  */
187
- declare function LoupeBubble({ aim, diameter, hostRef, children }: LoupeBubbleProps): react.JSX.Element;
306
+ declare function LoupeBubble({ aim, diameter, shape, hollow, hostRef, children, }: LoupeBubbleProps): react.JSX.Element;
188
307
 
189
308
  /**
190
309
  * The loupe's two bindable interactions, as `Action` descriptors the gesture
@@ -234,6 +353,24 @@ interface LoupeGesturesProps {
234
353
  */
235
354
  declare function LoupeGestures(props: LoupeGesturesProps): react.JSX.Element | null;
236
355
 
356
+ /** Props for `<SourceLoupe>`. */
357
+ interface SourceLoupeProps {
358
+ aim: LoupePoint;
359
+ factor: number;
360
+ /** One number for a round or square lens, or a width and a height. */
361
+ diameter: LoupeSize;
362
+ /** The canvas whose pixels the lens enlarges. */
363
+ source: LoupeSource;
364
+ /** The element `aim` is measured in, which the source's box is found against. */
365
+ hostRef: RefObject<HTMLElement | null>;
366
+ }
367
+ /**
368
+ * Paints a pixel lens from any canvas — one labkit drew or one it has never
369
+ * seen, 2D or WebGL. It redraws every frame, since the canvas it reads moves
370
+ * on its own schedule.
371
+ */
372
+ declare function SourceLoupe({ aim, factor, diameter, source, hostRef }: SourceLoupeProps): react.JSX.Element;
373
+
237
374
  /** Props for `<TrialLoupe>`. */
238
375
  interface TrialLoupeProps extends LoupeOptions {
239
376
  /** Whether the lens is turned on. Omitted, it follows the trial's toolbar
@@ -256,9 +393,9 @@ interface TrialLoupeProps extends LoupeOptions {
256
393
  *
257
394
  * With no `render` inside a `<CanvasStack>`, it re-draws the stack's own
258
395
  * layers through a zoomed camera. With `render`, it asks for the content again
259
- * at a magnified camera.
396
+ * at a magnified camera. With `source`, it enlarges the pixels of any canvas.
260
397
  */
261
- declare function TrialLoupe({ enabled, view, hostRef, children, render, factor, minFactor, maxFactor, mode, diameter, peekKey, onColorChange, }: TrialLoupeProps): react.JSX.Element;
398
+ declare function TrialLoupe({ enabled, view, hostRef, children, render, source, factor, minFactor, maxFactor, mode, diameter, shape, place, hollow, onLens, peekKey, onColorChange, }: TrialLoupeProps): react.JSX.Element;
262
399
 
263
400
  /** Options for {@link useLoupe}. */
264
401
  interface UseLoupeOptions {
@@ -270,6 +407,12 @@ interface UseLoupeOptions {
270
407
  /** Hex color at a host point. Omitted, the loupe reports no color — which
271
408
  * is the honest answer for a surface with no pixels to read. */
272
409
  sample?: (p: LoupePoint) => string | null;
410
+ /** Run `fn` whenever what `sample` reads changes without the aim moving — a
411
+ * canvas that animates, or a frame that arrives after the aim did; returns
412
+ * an unsubscribe. While the lens is up, each call samples the aim again, on
413
+ * top of the sample every aim takes at once. Omitted, color changes only on
414
+ * an aim. */
415
+ subscribeResample?: (fn: () => void) => () => void;
273
416
  }
274
417
  /** A loupe as a React view reads it. */
275
418
  interface LoupeState {
@@ -277,11 +420,16 @@ interface LoupeState {
277
420
  visible: boolean;
278
421
  /** Where it is aimed, in the host's own pixels. */
279
422
  aim: LoupePoint;
423
+ /** The wheel's magnification. What the lens shows at is `lens.factor`. */
280
424
  factor: number;
425
+ /** The box the lens is drawn in, and what it shows. */
426
+ lens: LoupeLens;
281
427
  mode: LoupeMode;
282
428
  color: string | null;
283
429
  setMode: (mode: LoupeMode) => void;
284
430
  setFactor: (factor: number) => void;
431
+ /** Sample the aim again, for pixels that changed under a still aim. */
432
+ resample: () => void;
285
433
  /** Sample what the lens shows at a point inside it. */
286
434
  pick: (p?: LoupePoint) => string | null;
287
435
  /** What `<LoupeGestures>` drives the lens through. Stable for the life of
@@ -299,7 +447,7 @@ interface LoupeState {
299
447
  * has no continuous-motion entry, and inventing one to carry the lens' aim
300
448
  * would be an input taxonomy change, not a loupe change.
301
449
  */
302
- declare function useLoupe({ options, hostRef, enabled, sample }: UseLoupeOptions): LoupeState;
450
+ declare function useLoupe({ options, hostRef, enabled, sample, subscribeResample, }: UseLoupeOptions): LoupeState;
303
451
 
304
- export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, TrialLoupe, createLoupeActions, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe };
305
- export type { CanvasLoupeProps, DomLoupeProps, LensCamera, LoupeBubbleProps, LoupeGesturesProps, LoupeInputApi, LoupeOptions, LoupeRenderArgs, LoupeState, ResolvedLoupe, SourceRect, TrialLoupeProps, UseLoupeOptions };
452
+ export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, SourceLoupe, TrialLoupe, createLoupeActions, drawCanvasLens, drawSourceLens, lensCamera, lensSourceRect, resolveLoupe, resolveLoupeSource, sampleSource, sampleStack, sourceBoxIn, useLoupe };
453
+ export type { CanvasLoupeProps, DomLoupeProps, LensCamera, LoupeBubbleProps, LoupeGesturesProps, LoupeInputApi, LoupeLens, LoupeOptions, LoupePlaceArgs, LoupePlacement, LoupeRenderArgs, LoupeShape, LoupeSource, LoupeSourceTarget, LoupeState, ResolvedLoupe, SourceLoupeProps, SourceRect, TrialLoupeProps, UseLoupeOptions };