@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.
- package/README.md +24 -0
- package/dist/_dts/{CanvasStackContext-Cjy6I-Iv.d.ts → CanvasStackContext-B05ZM04l.d.ts} +3 -1
- package/dist/_dts/{Stage-D6t7o0pF.d.ts → Stage-CIJlbjrE.d.ts} +5 -3
- package/dist/_dts/{index-DCMA4Fy1.d.ts → index-8geQNQet.d.ts} +84 -12
- package/dist/_dts/{index-BTmaRy6a.d.ts → index-DRi3V5sN.d.ts} +6 -4
- package/dist/_dts/{types-BUBSIZuV.d.ts → types-DUfQ0isU.d.ts} +5 -1
- package/dist/_dts/{useTrialState-Bh8bzFgE.d.ts → useTrialState-Ar0AFpCe.d.ts} +94 -87
- package/dist/canvas/index.d.ts +5 -5
- package/dist/canvas/index.js +2 -2
- package/dist/chrome/index.d.ts +3 -3
- package/dist/chrome/index.js +7 -7
- package/dist/{chunk-HBGH344L.js → chunk-4RCVWX4M.js} +55 -18
- package/dist/chunk-4RCVWX4M.js.map +1 -0
- package/dist/{chunk-WIG6XHJ7.js → chunk-6IIKLXBZ.js} +13 -4
- package/dist/chunk-6IIKLXBZ.js.map +1 -0
- package/dist/{chunk-F4ACIK7Z.js → chunk-6Q6ACJ3H.js} +3 -3
- package/dist/{chunk-F4ACIK7Z.js.map → chunk-6Q6ACJ3H.js.map} +1 -1
- package/dist/{chunk-PMLZP7RT.js → chunk-7ALJOVGP.js} +5 -9
- package/dist/chunk-7ALJOVGP.js.map +1 -0
- package/dist/{chunk-XE5HJOPL.js → chunk-7FMZT2WL.js} +3 -3
- package/dist/{chunk-XE5HJOPL.js.map → chunk-7FMZT2WL.js.map} +1 -1
- package/dist/{chunk-XVUKDSYV.js → chunk-AVOILDN2.js} +2 -2
- package/dist/{chunk-XVUKDSYV.js.map → chunk-AVOILDN2.js.map} +1 -1
- package/dist/{chunk-2N7UBSS2.js → chunk-J5MDJEOZ.js} +3 -3
- package/dist/{chunk-2N7UBSS2.js.map → chunk-J5MDJEOZ.js.map} +1 -1
- package/dist/{chunk-LPGBC63T.js → chunk-KHB3KRRQ.js} +7 -7
- package/dist/chunk-KHB3KRRQ.js.map +1 -0
- package/dist/{chunk-IBVRMHW5.js → chunk-NHIO2O3O.js} +31 -30
- package/dist/chunk-NHIO2O3O.js.map +1 -0
- package/dist/{chunk-EESJUSYR.js → chunk-QMAY74TP.js} +56 -5
- package/dist/chunk-QMAY74TP.js.map +1 -0
- package/dist/{chunk-4X44DMRS.js → chunk-RN7T7OBO.js} +46 -24
- package/dist/chunk-RN7T7OBO.js.map +1 -0
- package/dist/{chunk-4TJ34FXA.js → chunk-RXO5UEGT.js} +5 -12
- package/dist/chunk-RXO5UEGT.js.map +1 -0
- package/dist/{chunk-E6NARGU7.js → chunk-SQX35X5V.js} +3 -3
- package/dist/{chunk-E6NARGU7.js.map → chunk-SQX35X5V.js.map} +1 -1
- package/dist/{chunk-YCFKW5RX.js → chunk-T5OEUNCB.js} +16 -2
- package/dist/chunk-T5OEUNCB.js.map +1 -0
- package/dist/{chunk-RL2LOLNI.js → chunk-UX66USTN.js} +2 -2
- package/dist/chunk-UX66USTN.js.map +1 -0
- package/dist/{chunk-IRMPZIYU.js → chunk-V4SMGEYY.js} +6732 -6507
- package/dist/chunk-V4SMGEYY.js.map +1 -0
- package/dist/{chunk-E23Q6A3Y.js → chunk-VCK5T6EP.js} +3 -3
- package/dist/{chunk-E23Q6A3Y.js.map → chunk-VCK5T6EP.js.map} +1 -1
- package/dist/{chunk-T6DLGEJB.js → chunk-XWR2P6MR.js} +23 -6
- package/dist/chunk-XWR2P6MR.js.map +1 -0
- package/dist/{chunk-XSJJTXNA.js → chunk-YZA73U4A.js} +19 -19
- package/dist/chunk-YZA73U4A.js.map +1 -0
- package/dist/config/index.d.ts +12 -5
- package/dist/config/index.js +2 -2
- package/dist/controls/index.d.ts +2 -2
- package/dist/controls/index.js +5 -5
- package/dist/dragdrop/index.d.ts +2 -2
- package/dist/dragdrop/index.js +2 -2
- package/dist/index.d.ts +158 -16
- package/dist/index.js +1036 -415
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +2 -2
- package/dist/layers/index.js +3 -3
- package/dist/loupe/index.d.ts +176 -28
- package/dist/loupe/index.js +547 -141
- package/dist/loupe/index.js.map +1 -1
- package/dist/overview/index.d.ts +1 -1
- package/dist/overview/index.js +5 -5
- package/dist/passthrough/weasel-ui.js +3 -3
- package/dist/primitives/index.d.ts +4 -19
- package/dist/primitives/index.js +7 -7
- package/dist/state/index.d.ts +6 -8
- package/dist/state/index.js +5 -5
- package/dist/styles.css +181 -44
- package/dist/surface/index.d.ts +20 -1
- package/dist/surface/index.js +2 -2
- package/dist/undo/index.d.ts +19 -8
- package/dist/undo/index.js +1 -1
- package/package.json +11 -11
- package/src/annotations/AnnotationOverlay.abandon.test.tsx +4 -0
- package/src/annotations/AnnotationOverlay.tsx +10 -1
- package/src/annotations/Annotations.less +2 -0
- package/src/annotations/Annotations.overlay.test.tsx +29 -0
- package/src/canvas/CanvasStack.test.tsx +35 -0
- package/src/canvas/CanvasStack.tsx +12 -1
- package/src/canvas/useLayerScheduler.ts +14 -0
- package/src/chrome/builtins.test.ts +14 -0
- package/src/chrome/builtins.tsx +17 -2
- package/src/chrome/regions/StatusRegion.tsx +20 -14
- package/src/chrome/regions/regions.test.tsx +28 -0
- package/src/chrome/types.ts +8 -0
- package/src/clock/clock.browser.test.less +5 -0
- package/src/clock/clock.browser.test.tsx +77 -0
- package/src/clock/clock.integration.test.tsx +95 -0
- package/src/clock/clockRegistry.test.ts +33 -0
- package/src/clock/clockRegistry.ts +46 -0
- package/src/clock/hooks.ts +44 -0
- package/src/clock/index.ts +2 -0
- package/src/clock/trialClock.test.ts +265 -0
- package/src/clock/trialClock.ts +245 -0
- package/src/clock/useClockLoop.ts +45 -0
- package/src/config/builder.ts +11 -1
- package/src/config/resolve.test.ts +12 -0
- package/src/config/resolve.ts +5 -0
- package/src/config/types.ts +5 -1
- package/src/controls/ControlMatrix.test.tsx +10 -0
- package/src/controls/ControlMatrix.tsx +110 -58
- package/src/controls/ControlPanel.test.tsx +30 -0
- package/src/index.ts +3 -2
- package/src/instrument/types.ts +21 -1
- package/src/lab/Lab.chrome.test.tsx +8 -1
- package/src/lab/Lab.presentation.test.tsx +113 -0
- package/src/lab/Lab.surface.test.tsx +20 -9
- package/src/lab/Lab.tsx +132 -257
- package/src/lab/LabLightbox.browser.test.less +17 -0
- package/src/lab/LabLightbox.browser.test.tsx +216 -0
- package/src/lab/LabShell.less +10 -3
- package/src/lab/LabSurface.tsx +129 -0
- package/src/lab/Workspace.stories.tsx +18 -0
- package/src/lab/Workspace.surface.test.tsx +4 -0
- package/src/lab/Workspace.test.tsx +37 -1
- package/src/lab/Workspace.tsx +34 -6
- package/src/lab/index.ts +2 -0
- package/src/lab/nebula.ts +30 -0
- package/src/lab/openLab.test.ts +81 -0
- package/src/lab/openLab.ts +119 -0
- package/src/lab/presentation.browser.test.less +5 -0
- package/src/lab/presentation.browser.test.tsx +119 -0
- package/src/lab/presentation.less +64 -0
- package/src/lab/presentation.tsx +47 -0
- package/src/lab/useFocusPick.ts +43 -0
- package/src/lightbox/Lightbox.browser.test.less +14 -0
- package/src/lightbox/Lightbox.browser.test.tsx +113 -0
- package/src/lightbox/Lightbox.less +128 -0
- package/src/lightbox/Lightbox.test.tsx +195 -0
- package/src/lightbox/Lightbox.tsx +205 -0
- package/src/lightbox/LightboxContext.ts +38 -0
- package/src/lightbox/LightboxLayers.tsx +40 -0
- package/src/lightbox/index.ts +8 -0
- package/src/lightbox/modal.ts +94 -0
- package/src/loupe/AGENTS.md +40 -2
- package/src/loupe/CanvasLoupe.tsx +5 -4
- package/src/loupe/DomLoupe.tsx +7 -5
- package/src/loupe/Loupe.less +15 -0
- package/src/loupe/LoupeBubble.tsx +35 -13
- package/src/loupe/SourceLoupe.browser.test.tsx +204 -0
- package/src/loupe/SourceLoupe.tsx +60 -0
- package/src/loupe/TrialLoupe.browser.test.tsx +342 -0
- package/src/loupe/TrialLoupe.tsx +126 -28
- package/src/loupe/canvasLens.ts +20 -17
- package/src/loupe/index.ts +26 -2
- package/src/loupe/sourceLens.test.ts +65 -0
- package/src/loupe/sourceLens.ts +97 -0
- package/src/loupe/types.test.ts +6 -0
- package/src/loupe/types.ts +109 -7
- package/src/loupe/useLoupe.test.tsx +188 -3
- package/src/loupe/useLoupe.ts +106 -11
- package/src/primitives/index.ts +2 -2
- package/src/specimen/Specimen.tsx +0 -7
- package/src/state/helpers.test.ts +4 -11
- package/src/state/helpers.ts +2 -8
- package/src/state/index.ts +0 -2
- package/src/state/openLabStore.test.ts +10 -8
- package/src/state/openLabStore.ts +1 -1
- package/src/state/store.test.ts +2 -2
- package/src/state/store.ts +15 -16
- package/src/state/types.ts +9 -9
- package/src/styles.less +2 -1
- package/src/surface/AGENTS.md +13 -0
- package/src/surface/index.ts +1 -0
- package/src/surface/useSurfaceTile.test.tsx +4 -0
- package/src/surface/useSurfaceTile.ts +17 -1
- package/src/surface/useTiledSurface.test.tsx +65 -0
- package/src/surface/useTiledSurface.ts +68 -1
- package/src/theme/interstellar.theme.json +0 -14
- package/src/trial/Trial.stories.tsx +0 -1
- package/src/trial/Trial.test.tsx +40 -3
- package/src/trial/Trial.tsx +87 -43
- package/src/trial/TrialChrome.tsx +20 -2
- package/src/trial/trialOps.test.ts +1 -2
- package/src/trial/trialOps.ts +2 -3
- package/src/undo/stateOp.ts +21 -0
- package/src/undo/undoStack.ts +18 -6
- package/dist/chunk-4TJ34FXA.js.map +0 -1
- package/dist/chunk-4X44DMRS.js.map +0 -1
- package/dist/chunk-EESJUSYR.js.map +0 -1
- package/dist/chunk-HBGH344L.js.map +0 -1
- package/dist/chunk-IBVRMHW5.js.map +0 -1
- package/dist/chunk-IRMPZIYU.js.map +0 -1
- package/dist/chunk-LPGBC63T.js.map +0 -1
- package/dist/chunk-PMLZP7RT.js.map +0 -1
- package/dist/chunk-RL2LOLNI.js.map +0 -1
- package/dist/chunk-T6DLGEJB.js.map +0 -1
- package/dist/chunk-WIG6XHJ7.js.map +0 -1
- package/dist/chunk-XSJJTXNA.js.map +0 -1
- package/dist/chunk-YCFKW5RX.js.map +0 -1
- package/src/primitives/StatusBar.less +0 -34
- package/src/primitives/StatusBar.stories.tsx +0 -24
- package/src/primitives/StatusBar.test.tsx +0 -25
- package/src/primitives/StatusBar.tsx +0 -28
package/dist/loupe/index.js
CHANGED
|
@@ -1,27 +1,266 @@
|
|
|
1
1
|
import { LoupeSwitchContext } from '../chunk-2ES4W47N.js';
|
|
2
2
|
import { centerOn, zoomAt } from '../chunk-ROGJWDQA.js';
|
|
3
3
|
import { screenToWorld } from '../chunk-FG3EXZDV.js';
|
|
4
|
+
import { useSurfaceOptional } from '../chunk-6IIKLXBZ.js';
|
|
4
5
|
import { CameraContext, CameraScope, fromCameraView } from '../chunk-VYVGVBGY.js';
|
|
5
6
|
import { resolveFrame, DEFAULT_FRAME } from '../chunk-ULYYNWGP.js';
|
|
6
7
|
import { CanvasStackContext } from '../chunk-ZQIHJXP4.js';
|
|
8
|
+
import '../chunk-2YILQ7Y5.js';
|
|
7
9
|
import { useLatest, useVisibleRaf, useActionsRegistry, useGestureDispatcher, normalizeZoom } from '@weasel-js/core';
|
|
8
10
|
import { useRef, useEffect, useContext, useReducer, useCallback, useMemo, useState } from 'react';
|
|
9
11
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
10
12
|
|
|
13
|
+
function loupeExtent(size) {
|
|
14
|
+
return typeof size === "number" ? { width: size, height: size } : size;
|
|
15
|
+
}
|
|
16
|
+
function loupeSourcePoint(p, rect, aim, factor) {
|
|
17
|
+
return {
|
|
18
|
+
x: aim.x + (p.x - (rect.x + rect.w / 2)) / factor,
|
|
19
|
+
y: aim.y + (p.y - (rect.y + rect.h / 2)) / factor
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function placeBand({ band, factor, maxWidth, maxHeight }) {
|
|
23
|
+
let fit = factor;
|
|
24
|
+
if (band.w > 0) fit = Math.min(fit, maxWidth / band.w);
|
|
25
|
+
if (maxHeight !== void 0 && band.h > 0) fit = Math.min(fit, maxHeight / band.h);
|
|
26
|
+
const width = band.w * fit;
|
|
27
|
+
const height = band.h * fit;
|
|
28
|
+
const shows = { x: band.x + band.w / 2, y: band.y + band.h / 2 };
|
|
29
|
+
const within = (c, half, max) => Math.min(Math.max(c, half), Math.max(half, max - half));
|
|
30
|
+
return {
|
|
31
|
+
center: {
|
|
32
|
+
x: within(shows.x, width / 2, maxWidth),
|
|
33
|
+
y: maxHeight === void 0 ? shows.y : within(shows.y, height / 2, maxHeight)
|
|
34
|
+
},
|
|
35
|
+
shows,
|
|
36
|
+
width,
|
|
37
|
+
height,
|
|
38
|
+
factor: fit
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function createLoupeModel(opts) {
|
|
42
|
+
const { surface } = opts;
|
|
43
|
+
let mode = opts.mode ?? "vector";
|
|
44
|
+
let factor = normalizeZoom(opts.factor ?? 8);
|
|
45
|
+
let aim = { x: 0, y: 0 };
|
|
46
|
+
let color = null;
|
|
47
|
+
let disposed = false;
|
|
48
|
+
let aimUnsampled = false;
|
|
49
|
+
const clamp = (n) => normalizeZoom(Math.min(
|
|
50
|
+
opts.maxFactor ?? Number.POSITIVE_INFINITY,
|
|
51
|
+
Math.max(opts.minFactor ?? Number.NEGATIVE_INFINITY, n)
|
|
52
|
+
));
|
|
53
|
+
const sampleColor = () => {
|
|
54
|
+
const hex = surface.sample(aim);
|
|
55
|
+
if (hex === null || hex === color) return;
|
|
56
|
+
color = hex;
|
|
57
|
+
opts.onColorChange?.(hex);
|
|
58
|
+
};
|
|
59
|
+
const unsubscribeFrame = surface.subscribeFrame?.(() => {
|
|
60
|
+
if (!aimUnsampled) return;
|
|
61
|
+
aimUnsampled = false;
|
|
62
|
+
sampleColor();
|
|
63
|
+
});
|
|
64
|
+
const teardown = () => {
|
|
65
|
+
if (disposed) return;
|
|
66
|
+
disposed = true;
|
|
67
|
+
unsubscribeFrame?.();
|
|
68
|
+
opts.onDispose?.();
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
get mode() {
|
|
72
|
+
return mode;
|
|
73
|
+
},
|
|
74
|
+
get factor() {
|
|
75
|
+
return factor;
|
|
76
|
+
},
|
|
77
|
+
get aim() {
|
|
78
|
+
return aim;
|
|
79
|
+
},
|
|
80
|
+
get color() {
|
|
81
|
+
return color;
|
|
82
|
+
},
|
|
83
|
+
setMode(next) {
|
|
84
|
+
if (disposed || next === mode) return;
|
|
85
|
+
mode = next;
|
|
86
|
+
surface.changed();
|
|
87
|
+
},
|
|
88
|
+
setFactor(next) {
|
|
89
|
+
if (disposed) return;
|
|
90
|
+
factor = clamp(next);
|
|
91
|
+
surface.changed();
|
|
92
|
+
},
|
|
93
|
+
aimAt(p) {
|
|
94
|
+
if (disposed) return;
|
|
95
|
+
if (surface.gone()) {
|
|
96
|
+
teardown();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (surface.hidden()) return;
|
|
100
|
+
if (surface.covers(p)) return;
|
|
101
|
+
aim = p;
|
|
102
|
+
if (unsubscribeFrame) aimUnsampled = true;
|
|
103
|
+
else sampleColor();
|
|
104
|
+
surface.changed();
|
|
105
|
+
},
|
|
106
|
+
pick(p) {
|
|
107
|
+
if (disposed) return null;
|
|
108
|
+
let at = aim;
|
|
109
|
+
if (p) {
|
|
110
|
+
const rect = surface.lens();
|
|
111
|
+
if (!rect) return null;
|
|
112
|
+
at = loupeSourcePoint(p, rect, rect.shows ?? aim, rect.factor ?? factor);
|
|
113
|
+
if (surface.covers(at)) return null;
|
|
114
|
+
}
|
|
115
|
+
const hex = surface.sample(at);
|
|
116
|
+
if (hex !== null) opts.onPick?.(hex);
|
|
117
|
+
return hex;
|
|
118
|
+
},
|
|
119
|
+
resample() {
|
|
120
|
+
if (disposed || surface.hidden()) return;
|
|
121
|
+
aimUnsampled = false;
|
|
122
|
+
sampleColor();
|
|
123
|
+
},
|
|
124
|
+
dispose: teardown
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
var MISSED_FRAMES_BEFORE_WARNING = 120;
|
|
128
|
+
function isWebGL(ctx) {
|
|
129
|
+
return "drawArrays" in ctx;
|
|
130
|
+
}
|
|
131
|
+
function isContext(target) {
|
|
132
|
+
return "canvas" in target && !("getContext" in target);
|
|
133
|
+
}
|
|
134
|
+
function makeCanvas(width, height) {
|
|
135
|
+
if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(width, height);
|
|
136
|
+
const c = document.createElement("canvas");
|
|
137
|
+
c.width = width;
|
|
138
|
+
c.height = height;
|
|
139
|
+
return c;
|
|
140
|
+
}
|
|
141
|
+
function context2d(canvas, willReadFrequently = false) {
|
|
142
|
+
return canvas.getContext("2d", { willReadFrequently });
|
|
143
|
+
}
|
|
144
|
+
function createCanvasSource(target, options = {}) {
|
|
145
|
+
const canvas = isContext(target) ? target.canvas : target;
|
|
146
|
+
const mustCapture = isContext(target) && isWebGL(target) && !target.getContextAttributes()?.preserveDrawingBuffer;
|
|
147
|
+
let captured = mustCapture;
|
|
148
|
+
let readers = 0;
|
|
149
|
+
let snapshot = null;
|
|
150
|
+
let current = false;
|
|
151
|
+
let missed = 0;
|
|
152
|
+
let warned = false;
|
|
153
|
+
let probe = null;
|
|
154
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
155
|
+
const source = {
|
|
156
|
+
canvas,
|
|
157
|
+
get captured() {
|
|
158
|
+
return captured;
|
|
159
|
+
},
|
|
160
|
+
box: options.box,
|
|
161
|
+
capture() {
|
|
162
|
+
captured = true;
|
|
163
|
+
if (readers === 0) return;
|
|
164
|
+
const { width, height } = canvas;
|
|
165
|
+
if (width === 0 || height === 0) return;
|
|
166
|
+
if (!snapshot) snapshot = makeCanvas(width, height);
|
|
167
|
+
else if (snapshot.width !== width || snapshot.height !== height) {
|
|
168
|
+
snapshot.width = width;
|
|
169
|
+
snapshot.height = height;
|
|
170
|
+
}
|
|
171
|
+
const ctx = context2d(snapshot);
|
|
172
|
+
if (!ctx) return;
|
|
173
|
+
ctx.globalCompositeOperation = "copy";
|
|
174
|
+
ctx.drawImage(canvas, 0, 0);
|
|
175
|
+
current = true;
|
|
176
|
+
missed = 0;
|
|
177
|
+
for (const fn of listeners) fn();
|
|
178
|
+
},
|
|
179
|
+
frame() {
|
|
180
|
+
if (!captured) return canvas.width > 0 && canvas.height > 0 ? canvas : null;
|
|
181
|
+
if (current && snapshot) return snapshot;
|
|
182
|
+
if (!warned && ++missed >= MISSED_FRAMES_BEFORE_WARNING) {
|
|
183
|
+
warned = true;
|
|
184
|
+
console.warn(
|
|
185
|
+
"[weasel loupe] A WebGL canvas without preserveDrawingBuffer is being read, and nothing has called capture() on its source. Call source.capture() right after drawing each frame, or pass requestRedraw if the canvas draws on demand."
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
},
|
|
190
|
+
sample(p) {
|
|
191
|
+
const frame = source.frame();
|
|
192
|
+
if (!frame) return null;
|
|
193
|
+
const x = Math.floor(p.x);
|
|
194
|
+
const y = Math.floor(p.y);
|
|
195
|
+
if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) return null;
|
|
196
|
+
if (!probe) {
|
|
197
|
+
const c = makeCanvas(1, 1);
|
|
198
|
+
probe = context2d(c, true);
|
|
199
|
+
if (!probe) return null;
|
|
200
|
+
}
|
|
201
|
+
probe.globalCompositeOperation = "copy";
|
|
202
|
+
probe.drawImage(frame, x, y, 1, 1, 0, 0, 1, 1);
|
|
203
|
+
const [r, g, b, a] = probe.getImageData(0, 0, 1, 1).data;
|
|
204
|
+
if (a === 0) return null;
|
|
205
|
+
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
206
|
+
},
|
|
207
|
+
retain() {
|
|
208
|
+
readers++;
|
|
209
|
+
if (readers === 1 && captured) {
|
|
210
|
+
current = false;
|
|
211
|
+
options.requestRedraw?.();
|
|
212
|
+
}
|
|
213
|
+
let held = true;
|
|
214
|
+
return () => {
|
|
215
|
+
if (!held) return;
|
|
216
|
+
held = false;
|
|
217
|
+
readers--;
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
subscribeFrame(fn) {
|
|
221
|
+
listeners.add(fn);
|
|
222
|
+
return () => {
|
|
223
|
+
listeners.delete(fn);
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
return source;
|
|
228
|
+
}
|
|
229
|
+
function sourceRegion(aim, factor, size, box, backing) {
|
|
230
|
+
const { width, height } = loupeExtent(size);
|
|
231
|
+
const spanX = width / factor;
|
|
232
|
+
const spanY = height / factor;
|
|
233
|
+
const kx = box.width > 0 ? backing.width / box.width : 1;
|
|
234
|
+
const ky = box.height > 0 ? backing.height / box.height : 1;
|
|
235
|
+
return {
|
|
236
|
+
sx: (aim.x - spanX / 2 - box.x) * kx,
|
|
237
|
+
sy: (aim.y - spanY / 2 - box.y) * ky,
|
|
238
|
+
sw: spanX * kx,
|
|
239
|
+
sh: spanY * ky
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function sourcePixel(p, box, backing) {
|
|
243
|
+
const kx = box.width > 0 ? backing.width / box.width : 1;
|
|
244
|
+
const ky = box.height > 0 ? backing.height / box.height : 1;
|
|
245
|
+
return { x: (p.x - box.x) * kx, y: (p.y - box.y) * ky };
|
|
246
|
+
}
|
|
247
|
+
|
|
11
248
|
// src/loupe/canvasLens.ts
|
|
12
|
-
function lensCamera(aim, outer, outerFrame, factor,
|
|
13
|
-
const
|
|
14
|
-
const frame = resolveFrame(worldSpec,
|
|
249
|
+
function lensCamera(aim, outer, outerFrame, factor, size, worldSpec) {
|
|
250
|
+
const extent = loupeExtent(size);
|
|
251
|
+
const frame = resolveFrame(worldSpec, extent);
|
|
15
252
|
const world = screenToWorld(aim, outer, outerFrame);
|
|
16
|
-
return { view: centerOn(world, outer.zoom * factor,
|
|
253
|
+
return { view: centerOn(world, outer.zoom * factor, extent, frame), frame };
|
|
17
254
|
}
|
|
18
|
-
function lensSourceRect(aim, factor,
|
|
19
|
-
const
|
|
255
|
+
function lensSourceRect(aim, factor, size, dpr) {
|
|
256
|
+
const { width, height } = loupeExtent(size);
|
|
257
|
+
const spanX = width / factor;
|
|
258
|
+
const spanY = height / factor;
|
|
20
259
|
return {
|
|
21
|
-
sx: (aim.x -
|
|
22
|
-
sy: (aim.y -
|
|
23
|
-
sw:
|
|
24
|
-
sh:
|
|
260
|
+
sx: (aim.x - spanX / 2) * dpr,
|
|
261
|
+
sy: (aim.y - spanY / 2) * dpr,
|
|
262
|
+
sw: spanX * dpr,
|
|
263
|
+
sh: spanY * dpr
|
|
25
264
|
};
|
|
26
265
|
}
|
|
27
266
|
function sampleStack(layers, canvases, p, dpr) {
|
|
@@ -43,9 +282,10 @@ function sampleStack(layers, canvases, p, dpr) {
|
|
|
43
282
|
}
|
|
44
283
|
function drawCanvasLens(ctx, opts) {
|
|
45
284
|
const { aim, factor, diameter, dpr, mode, outer, layers, canvases } = opts;
|
|
285
|
+
const { width, height } = loupeExtent(diameter);
|
|
46
286
|
ctx.save();
|
|
47
287
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
48
|
-
ctx.clearRect(0, 0,
|
|
288
|
+
ctx.clearRect(0, 0, width, height);
|
|
49
289
|
if (mode === "pixel") {
|
|
50
290
|
const { sx, sy, sw, sh } = lensSourceRect(aim, factor, diameter, dpr);
|
|
51
291
|
ctx.imageSmoothingEnabled = false;
|
|
@@ -53,7 +293,7 @@ function drawCanvasLens(ctx, opts) {
|
|
|
53
293
|
if (!layer.visible) continue;
|
|
54
294
|
const canvas = canvases.get(layer.id);
|
|
55
295
|
if (!canvas || canvas.width === 0 || canvas.height === 0) continue;
|
|
56
|
-
ctx.drawImage(canvas, sx, sy, sw, sh, 0, 0,
|
|
296
|
+
ctx.drawImage(canvas, sx, sy, sw, sh, 0, 0, width, height);
|
|
57
297
|
}
|
|
58
298
|
ctx.restore();
|
|
59
299
|
return;
|
|
@@ -120,8 +360,8 @@ function CanvasLoupe({
|
|
|
120
360
|
{
|
|
121
361
|
ref: canvasRef,
|
|
122
362
|
className: "lk-loupe__canvas",
|
|
123
|
-
width: Math.max(1, Math.round(diameter * dpr)),
|
|
124
|
-
height: Math.max(1, Math.round(diameter * dpr))
|
|
363
|
+
width: Math.max(1, Math.round(loupeExtent(diameter).width * dpr)),
|
|
364
|
+
height: Math.max(1, Math.round(loupeExtent(diameter).height * dpr))
|
|
125
365
|
}
|
|
126
366
|
);
|
|
127
367
|
}
|
|
@@ -135,10 +375,11 @@ function DomLoupe({
|
|
|
135
375
|
frame,
|
|
136
376
|
render
|
|
137
377
|
}) {
|
|
378
|
+
const { width, height } = loupeExtent(diameter);
|
|
138
379
|
const style = {
|
|
139
380
|
width: `${size.width}px`,
|
|
140
381
|
height: `${size.height}px`,
|
|
141
|
-
transform: `translate(${
|
|
382
|
+
transform: `translate(${width / 2 - aim.x}px, ${height / 2 - aim.y}px)`
|
|
142
383
|
};
|
|
143
384
|
return /* @__PURE__ */ jsx("div", { className: "lk-loupe__stage", style, children: render({
|
|
144
385
|
view: zoomAt(view, factor, aim, { frame }),
|
|
@@ -147,13 +388,31 @@ function DomLoupe({
|
|
|
147
388
|
size
|
|
148
389
|
}) });
|
|
149
390
|
}
|
|
150
|
-
function LoupeBubble({
|
|
391
|
+
function LoupeBubble({
|
|
392
|
+
aim,
|
|
393
|
+
diameter,
|
|
394
|
+
shape = "circle",
|
|
395
|
+
hollow,
|
|
396
|
+
hostRef,
|
|
397
|
+
children
|
|
398
|
+
}) {
|
|
399
|
+
const { width, height } = loupeExtent(diameter);
|
|
400
|
+
const square = shape === "square";
|
|
151
401
|
const style = {
|
|
152
|
-
width: `${
|
|
153
|
-
height: `${
|
|
154
|
-
transform: `translate(${aim.x -
|
|
402
|
+
"--lk-loupe-width": `${width}px`,
|
|
403
|
+
"--lk-loupe-height": `${height}px`,
|
|
404
|
+
transform: `translate(${aim.x - width / 2}px, ${aim.y - height / 2}px)`
|
|
155
405
|
};
|
|
156
|
-
return /* @__PURE__ */ jsx(
|
|
406
|
+
return /* @__PURE__ */ jsx(
|
|
407
|
+
"div",
|
|
408
|
+
{
|
|
409
|
+
ref: hostRef,
|
|
410
|
+
className: ["lk-loupe", square && "lk-loupe--square", hollow && "lk-loupe--hollow"].filter(Boolean).join(" "),
|
|
411
|
+
style,
|
|
412
|
+
"aria-hidden": "true",
|
|
413
|
+
children
|
|
414
|
+
}
|
|
415
|
+
);
|
|
157
416
|
}
|
|
158
417
|
|
|
159
418
|
// src/loupe/loupeActions.ts
|
|
@@ -252,6 +511,95 @@ function LoupeGestures(props) {
|
|
|
252
511
|
return /* @__PURE__ */ jsx(LoupeDispatch, { ...props, registry, ownDispatcher: camera === null });
|
|
253
512
|
}
|
|
254
513
|
|
|
514
|
+
// src/loupe/sourceLens.ts
|
|
515
|
+
function isCanvasSource(t) {
|
|
516
|
+
return "capture" in t && "retain" in t;
|
|
517
|
+
}
|
|
518
|
+
var made = /* @__PURE__ */ new WeakMap();
|
|
519
|
+
function resolveLoupeSource(source) {
|
|
520
|
+
if (!source) return null;
|
|
521
|
+
const target = typeof source === "function" ? source() : source;
|
|
522
|
+
if (!target) return null;
|
|
523
|
+
if (isCanvasSource(target)) return target;
|
|
524
|
+
let s = made.get(target);
|
|
525
|
+
if (!s) {
|
|
526
|
+
s = createCanvasSource(target);
|
|
527
|
+
made.set(target, s);
|
|
528
|
+
}
|
|
529
|
+
return s;
|
|
530
|
+
}
|
|
531
|
+
function sourceBoxIn(source, host) {
|
|
532
|
+
const own = source.box?.();
|
|
533
|
+
if (own) return own;
|
|
534
|
+
const hostRect = host?.getBoundingClientRect();
|
|
535
|
+
const c = source.canvas;
|
|
536
|
+
if (typeof HTMLCanvasElement !== "undefined" && c instanceof HTMLCanvasElement && c.isConnected) {
|
|
537
|
+
const r = c.getBoundingClientRect();
|
|
538
|
+
if (r.width > 0 && r.height > 0) {
|
|
539
|
+
return {
|
|
540
|
+
x: r.left - (hostRect?.left ?? 0),
|
|
541
|
+
y: r.top - (hostRect?.top ?? 0),
|
|
542
|
+
width: r.width,
|
|
543
|
+
height: r.height
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return { x: 0, y: 0, width: hostRect?.width ?? c.width, height: hostRect?.height ?? c.height };
|
|
548
|
+
}
|
|
549
|
+
function sampleSource(source, p, box) {
|
|
550
|
+
return source.sample(sourcePixel(p, box, source.canvas));
|
|
551
|
+
}
|
|
552
|
+
function drawSourceLens(ctx, opts) {
|
|
553
|
+
const { aim, factor, diameter, dpr, source, box } = opts;
|
|
554
|
+
const { width, height } = loupeExtent(diameter);
|
|
555
|
+
ctx.save();
|
|
556
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
557
|
+
ctx.clearRect(0, 0, width, height);
|
|
558
|
+
const frame = source.frame();
|
|
559
|
+
if (frame) {
|
|
560
|
+
const { sx, sy, sw, sh } = sourceRegion(aim, factor, diameter, box, source.canvas);
|
|
561
|
+
ctx.imageSmoothingEnabled = false;
|
|
562
|
+
ctx.drawImage(frame, sx, sy, sw, sh, 0, 0, width, height);
|
|
563
|
+
}
|
|
564
|
+
ctx.restore();
|
|
565
|
+
}
|
|
566
|
+
function SourceLoupe({ aim, factor, diameter, source, hostRef }) {
|
|
567
|
+
const canvasRef = useRef(null);
|
|
568
|
+
const dpr = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
|
569
|
+
const argsRef = useLatest({ aim, factor, diameter, dpr, source });
|
|
570
|
+
const loop = useVisibleRaf(
|
|
571
|
+
() => {
|
|
572
|
+
loop.request();
|
|
573
|
+
const ctx = canvasRef.current?.getContext("2d");
|
|
574
|
+
const a = argsRef.current;
|
|
575
|
+
const src = resolveLoupeSource(a.source);
|
|
576
|
+
if (!ctx || !src) return;
|
|
577
|
+
drawSourceLens(ctx, {
|
|
578
|
+
aim: a.aim,
|
|
579
|
+
factor: a.factor,
|
|
580
|
+
diameter: a.diameter,
|
|
581
|
+
dpr: a.dpr,
|
|
582
|
+
source: src,
|
|
583
|
+
box: sourceBoxIn(src, hostRef.current)
|
|
584
|
+
});
|
|
585
|
+
},
|
|
586
|
+
{ target: canvasRef }
|
|
587
|
+
);
|
|
588
|
+
useEffect(() => {
|
|
589
|
+
loop.request();
|
|
590
|
+
return () => loop.cancel();
|
|
591
|
+
}, [loop]);
|
|
592
|
+
return /* @__PURE__ */ jsx(
|
|
593
|
+
"canvas",
|
|
594
|
+
{
|
|
595
|
+
ref: canvasRef,
|
|
596
|
+
className: "lk-loupe__canvas",
|
|
597
|
+
width: Math.max(1, Math.round(loupeExtent(diameter).width * dpr)),
|
|
598
|
+
height: Math.max(1, Math.round(loupeExtent(diameter).height * dpr))
|
|
599
|
+
}
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
255
603
|
// src/loupe/types.ts
|
|
256
604
|
var LOUPE_DEFAULTS = {
|
|
257
605
|
factor: 6,
|
|
@@ -259,6 +607,8 @@ var LOUPE_DEFAULTS = {
|
|
|
259
607
|
maxFactor: 32,
|
|
260
608
|
mode: "vector",
|
|
261
609
|
diameter: 200,
|
|
610
|
+
shape: "circle",
|
|
611
|
+
hollow: false,
|
|
262
612
|
peekKey: "Alt"
|
|
263
613
|
};
|
|
264
614
|
function resolveLoupe(options = {}) {
|
|
@@ -266,14 +616,19 @@ function resolveLoupe(options = {}) {
|
|
|
266
616
|
const maxFactor = options.maxFactor ?? LOUPE_DEFAULTS.maxFactor;
|
|
267
617
|
return {
|
|
268
618
|
render: options.render,
|
|
619
|
+
source: options.source,
|
|
269
620
|
onColorChange: options.onColorChange,
|
|
270
621
|
minFactor,
|
|
271
622
|
maxFactor,
|
|
272
623
|
factor: Math.min(maxFactor, Math.max(minFactor, options.factor ?? LOUPE_DEFAULTS.factor)),
|
|
273
624
|
diameter: options.diameter ?? LOUPE_DEFAULTS.diameter,
|
|
625
|
+
shape: options.shape ?? LOUPE_DEFAULTS.shape,
|
|
626
|
+
place: options.place,
|
|
627
|
+
hollow: options.hollow ?? LOUPE_DEFAULTS.hollow,
|
|
628
|
+
onLens: options.onLens,
|
|
274
629
|
peekKey: options.peekKey === void 0 ? LOUPE_DEFAULTS.peekKey : options.peekKey,
|
|
275
630
|
// A DOM loupe has no framebuffer, so `pixel` would have nothing to enlarge.
|
|
276
|
-
mode: options.render ? "vector" : options.mode ?? LOUPE_DEFAULTS.mode
|
|
631
|
+
mode: options.render ? "vector" : options.source ? "pixel" : options.mode ?? LOUPE_DEFAULTS.mode
|
|
277
632
|
};
|
|
278
633
|
}
|
|
279
634
|
var ZERO = { width: 0, height: 0 };
|
|
@@ -296,99 +651,19 @@ function useHostSize(ref) {
|
|
|
296
651
|
}, [ref]);
|
|
297
652
|
return size;
|
|
298
653
|
}
|
|
299
|
-
function loupeSourcePoint(p, rect, aim, factor) {
|
|
300
|
-
return {
|
|
301
|
-
x: aim.x + (p.x - (rect.x + rect.w / 2)) / factor,
|
|
302
|
-
y: aim.y + (p.y - (rect.y + rect.h / 2)) / factor
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
function createLoupeModel(opts) {
|
|
306
|
-
const { surface } = opts;
|
|
307
|
-
let mode = opts.mode ?? "vector";
|
|
308
|
-
let factor = normalizeZoom(opts.factor ?? 8);
|
|
309
|
-
let aim = { x: 0, y: 0 };
|
|
310
|
-
let color = null;
|
|
311
|
-
let disposed = false;
|
|
312
|
-
let aimUnsampled = false;
|
|
313
|
-
const clamp = (n) => normalizeZoom(Math.min(
|
|
314
|
-
opts.maxFactor ?? Number.POSITIVE_INFINITY,
|
|
315
|
-
Math.max(opts.minFactor ?? Number.NEGATIVE_INFINITY, n)
|
|
316
|
-
));
|
|
317
|
-
const sampleColor = () => {
|
|
318
|
-
const hex = surface.sample(aim);
|
|
319
|
-
if (hex === null || hex === color) return;
|
|
320
|
-
color = hex;
|
|
321
|
-
opts.onColorChange?.(hex);
|
|
322
|
-
};
|
|
323
|
-
const unsubscribeFrame = surface.subscribeFrame?.(() => {
|
|
324
|
-
if (!aimUnsampled) return;
|
|
325
|
-
aimUnsampled = false;
|
|
326
|
-
sampleColor();
|
|
327
|
-
});
|
|
328
|
-
const teardown = () => {
|
|
329
|
-
if (disposed) return;
|
|
330
|
-
disposed = true;
|
|
331
|
-
unsubscribeFrame?.();
|
|
332
|
-
opts.onDispose?.();
|
|
333
|
-
};
|
|
334
|
-
return {
|
|
335
|
-
get mode() {
|
|
336
|
-
return mode;
|
|
337
|
-
},
|
|
338
|
-
get factor() {
|
|
339
|
-
return factor;
|
|
340
|
-
},
|
|
341
|
-
get aim() {
|
|
342
|
-
return aim;
|
|
343
|
-
},
|
|
344
|
-
get color() {
|
|
345
|
-
return color;
|
|
346
|
-
},
|
|
347
|
-
setMode(next) {
|
|
348
|
-
if (disposed || next === mode) return;
|
|
349
|
-
mode = next;
|
|
350
|
-
surface.changed();
|
|
351
|
-
},
|
|
352
|
-
setFactor(next) {
|
|
353
|
-
if (disposed) return;
|
|
354
|
-
factor = clamp(next);
|
|
355
|
-
surface.changed();
|
|
356
|
-
},
|
|
357
|
-
aimAt(p) {
|
|
358
|
-
if (disposed) return;
|
|
359
|
-
if (surface.gone()) {
|
|
360
|
-
teardown();
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
if (surface.hidden()) return;
|
|
364
|
-
if (surface.covers(p)) return;
|
|
365
|
-
aim = p;
|
|
366
|
-
if (unsubscribeFrame) aimUnsampled = true;
|
|
367
|
-
else sampleColor();
|
|
368
|
-
surface.changed();
|
|
369
|
-
},
|
|
370
|
-
pick(p) {
|
|
371
|
-
if (disposed) return null;
|
|
372
|
-
let at = aim;
|
|
373
|
-
if (p) {
|
|
374
|
-
const rect = surface.lens();
|
|
375
|
-
if (!rect) return null;
|
|
376
|
-
at = loupeSourcePoint(p, rect, aim, factor);
|
|
377
|
-
if (surface.covers(at)) return null;
|
|
378
|
-
}
|
|
379
|
-
const hex = surface.sample(at);
|
|
380
|
-
if (hex !== null) opts.onPick?.(hex);
|
|
381
|
-
return hex;
|
|
382
|
-
},
|
|
383
|
-
dispose: teardown
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
654
|
function lensShown(over, enabled, peeking) {
|
|
387
655
|
return over && (enabled || peeking);
|
|
388
656
|
}
|
|
389
|
-
function useLoupe({
|
|
657
|
+
function useLoupe({
|
|
658
|
+
options,
|
|
659
|
+
hostRef,
|
|
660
|
+
enabled,
|
|
661
|
+
sample,
|
|
662
|
+
subscribeResample
|
|
663
|
+
}) {
|
|
390
664
|
const [, bump] = useReducer((n) => n + 1, 0);
|
|
391
665
|
const overRef = useRef(false);
|
|
666
|
+
const pointerRef = useRef(null);
|
|
392
667
|
const peekingRef = useRef(false);
|
|
393
668
|
const enabledRef = useLatest(enabled);
|
|
394
669
|
const sampleRef = useLatest(sample);
|
|
@@ -407,9 +682,15 @@ function useLoupe({ options, hostRef, enabled, sample }) {
|
|
|
407
682
|
onColorChange: (hex) => onColorChangeRef.current?.(hex),
|
|
408
683
|
surface: {
|
|
409
684
|
lens: () => {
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
685
|
+
const { center, shows, width, height, factor } = lensRef.current;
|
|
686
|
+
return {
|
|
687
|
+
x: center.x - width / 2,
|
|
688
|
+
y: center.y - height / 2,
|
|
689
|
+
w: width,
|
|
690
|
+
h: height,
|
|
691
|
+
shows,
|
|
692
|
+
factor
|
|
693
|
+
};
|
|
413
694
|
},
|
|
414
695
|
// The lens follows the pointer and is painted over the host rather than
|
|
415
696
|
// into it, so it is never part of the picture being magnified.
|
|
@@ -422,7 +703,6 @@ function useLoupe({ options, hostRef, enabled, sample }) {
|
|
|
422
703
|
});
|
|
423
704
|
}
|
|
424
705
|
const model = modelRef.current;
|
|
425
|
-
const diameterRef = useLatest(options.diameter);
|
|
426
706
|
const goneRef = useRef(false);
|
|
427
707
|
useEffect(() => {
|
|
428
708
|
goneRef.current = false;
|
|
@@ -430,14 +710,24 @@ function useLoupe({ options, hostRef, enabled, sample }) {
|
|
|
430
710
|
goneRef.current = true;
|
|
431
711
|
};
|
|
432
712
|
}, []);
|
|
713
|
+
const hostRefRef = useLatest(hostRef);
|
|
714
|
+
const aimAtPointer = useCallback(() => {
|
|
715
|
+
const host = hostRefRef.current.current;
|
|
716
|
+
const p = pointerRef.current;
|
|
717
|
+
if (!host || !p) return;
|
|
718
|
+
const rect = host.getBoundingClientRect();
|
|
719
|
+
modelRef.current?.aimAt({ x: p.clientX - rect.left, y: p.clientY - rect.top });
|
|
720
|
+
}, [hostRefRef]);
|
|
433
721
|
useEffect(() => {
|
|
434
|
-
if (
|
|
435
|
-
|
|
722
|
+
if (enabled) aimAtPointer();
|
|
723
|
+
else if (!peekingRef.current) bump();
|
|
724
|
+
}, [enabled, aimAtPointer]);
|
|
436
725
|
useEffect(() => {
|
|
437
726
|
const host = hostRef.current;
|
|
438
727
|
if (!host) return;
|
|
439
728
|
const onPointerMove = (e) => {
|
|
440
729
|
overRef.current = true;
|
|
730
|
+
pointerRef.current = { clientX: e.clientX, clientY: e.clientY };
|
|
441
731
|
const rect = host.getBoundingClientRect();
|
|
442
732
|
model.aimAt({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
443
733
|
if (!shown()) return;
|
|
@@ -445,6 +735,7 @@ function useLoupe({ options, hostRef, enabled, sample }) {
|
|
|
445
735
|
};
|
|
446
736
|
const onPointerLeave = () => {
|
|
447
737
|
overRef.current = false;
|
|
738
|
+
pointerRef.current = null;
|
|
448
739
|
bump();
|
|
449
740
|
};
|
|
450
741
|
host.addEventListener("pointermove", onPointerMove);
|
|
@@ -454,6 +745,45 @@ function useLoupe({ options, hostRef, enabled, sample }) {
|
|
|
454
745
|
host.removeEventListener("pointerleave", onPointerLeave);
|
|
455
746
|
};
|
|
456
747
|
}, [hostRef, model, shown]);
|
|
748
|
+
const visible = lensShown(overRef.current, enabled, peekingRef.current);
|
|
749
|
+
const placed = visible ? options.place?.({ aim: model.aim, factor: model.factor }) ?? null : null;
|
|
750
|
+
const lens = placed ? {
|
|
751
|
+
center: placed.center,
|
|
752
|
+
shows: placed.shows ?? placed.center,
|
|
753
|
+
width: placed.width,
|
|
754
|
+
height: placed.height,
|
|
755
|
+
factor: placed.factor ?? model.factor,
|
|
756
|
+
shape: options.shape
|
|
757
|
+
} : {
|
|
758
|
+
center: model.aim,
|
|
759
|
+
shows: model.aim,
|
|
760
|
+
width: options.diameter,
|
|
761
|
+
height: options.diameter,
|
|
762
|
+
factor: model.factor,
|
|
763
|
+
shape: options.shape
|
|
764
|
+
};
|
|
765
|
+
const lensRef = useLatest(lens);
|
|
766
|
+
const onLensRef = useLatest(options.onLens);
|
|
767
|
+
const reportedRef = useRef(JSON.stringify(null));
|
|
768
|
+
const report = useCallback(
|
|
769
|
+
(next) => {
|
|
770
|
+
const key = JSON.stringify(next);
|
|
771
|
+
if (key === reportedRef.current) return;
|
|
772
|
+
reportedRef.current = key;
|
|
773
|
+
onLensRef.current?.(next);
|
|
774
|
+
},
|
|
775
|
+
[onLensRef]
|
|
776
|
+
);
|
|
777
|
+
const shownLens = visible ? lens : null;
|
|
778
|
+
const shownKey = JSON.stringify(shownLens);
|
|
779
|
+
useEffect(() => {
|
|
780
|
+
report(JSON.parse(shownKey));
|
|
781
|
+
}, [shownKey, report]);
|
|
782
|
+
useEffect(() => () => report(null), [report]);
|
|
783
|
+
useEffect(() => {
|
|
784
|
+
if (!visible || !subscribeResample) return;
|
|
785
|
+
return subscribeResample(() => model.resample());
|
|
786
|
+
}, [visible, subscribeResample, model]);
|
|
457
787
|
const inputRef = useRef(null);
|
|
458
788
|
if (inputRef.current === null) {
|
|
459
789
|
inputRef.current = {
|
|
@@ -461,6 +791,7 @@ function useLoupe({ options, hostRef, enabled, sample }) {
|
|
|
461
791
|
setPeeking: (on) => {
|
|
462
792
|
if (peekingRef.current === on) return;
|
|
463
793
|
peekingRef.current = on;
|
|
794
|
+
if (on) aimAtPointer();
|
|
464
795
|
bump();
|
|
465
796
|
},
|
|
466
797
|
magnifyBy: (deltaY) => {
|
|
@@ -470,14 +801,15 @@ function useLoupe({ options, hostRef, enabled, sample }) {
|
|
|
470
801
|
};
|
|
471
802
|
}
|
|
472
803
|
return {
|
|
473
|
-
|
|
474
|
-
visible: lensShown(overRef.current, enabled, peekingRef.current),
|
|
804
|
+
visible,
|
|
475
805
|
aim: model.aim,
|
|
476
806
|
factor: model.factor,
|
|
807
|
+
lens,
|
|
477
808
|
mode: model.mode,
|
|
478
809
|
color: model.color,
|
|
479
810
|
setMode: model.setMode,
|
|
480
811
|
setFactor: model.setFactor,
|
|
812
|
+
resample: model.resample,
|
|
481
813
|
pick: model.pick,
|
|
482
814
|
input: inputRef.current
|
|
483
815
|
};
|
|
@@ -489,31 +821,23 @@ function TrialLoupe({
|
|
|
489
821
|
hostRef,
|
|
490
822
|
children,
|
|
491
823
|
render,
|
|
824
|
+
source,
|
|
492
825
|
factor,
|
|
493
826
|
minFactor,
|
|
494
827
|
maxFactor,
|
|
495
828
|
mode,
|
|
496
829
|
diameter,
|
|
830
|
+
shape,
|
|
831
|
+
place,
|
|
832
|
+
hollow,
|
|
833
|
+
onLens,
|
|
497
834
|
peekKey,
|
|
498
835
|
onColorChange
|
|
499
836
|
}) {
|
|
500
|
-
const options = useMemo(
|
|
501
|
-
() => resolveLoupe({
|
|
502
|
-
render,
|
|
503
|
-
factor,
|
|
504
|
-
minFactor,
|
|
505
|
-
maxFactor,
|
|
506
|
-
mode,
|
|
507
|
-
diameter,
|
|
508
|
-
peekKey,
|
|
509
|
-
onColorChange
|
|
510
|
-
}),
|
|
511
|
-
[render, factor, minFactor, maxFactor, mode, diameter, peekKey, onColorChange]
|
|
512
|
-
);
|
|
513
837
|
const stack = useContext(CanvasStackContext);
|
|
514
838
|
const camera = useContext(CameraContext);
|
|
515
839
|
const loupeSwitch = useContext(LoupeSwitchContext);
|
|
516
|
-
const surface =
|
|
840
|
+
const surface = render || source || hollow ? void 0 : stack?.surface;
|
|
517
841
|
const ownHost = useRef(null);
|
|
518
842
|
const cameraHost = useMemo(
|
|
519
843
|
() => camera ? {
|
|
@@ -525,43 +849,125 @@ function TrialLoupe({
|
|
|
525
849
|
);
|
|
526
850
|
const host = surface?.element ?? hostRef ?? cameraHost ?? ownHost;
|
|
527
851
|
const wraps = host === ownHost;
|
|
852
|
+
const hostLatest = useLatest(host);
|
|
853
|
+
const tiles = useSurfaceOptional();
|
|
854
|
+
const onLensLatest = useLatest(onLens);
|
|
855
|
+
const was = useRef(null);
|
|
856
|
+
const damage = hollow ? tiles : null;
|
|
857
|
+
const reportLens = useCallback(
|
|
858
|
+
(lens2) => {
|
|
859
|
+
onLensLatest.current?.(lens2);
|
|
860
|
+
if (!damage) return;
|
|
861
|
+
const at = hostLatest.current.current?.getBoundingClientRect();
|
|
862
|
+
const now = lens2 && at ? {
|
|
863
|
+
left: at.left + lens2.center.x - lens2.width / 2,
|
|
864
|
+
top: at.top + lens2.center.y - lens2.height / 2,
|
|
865
|
+
width: lens2.width,
|
|
866
|
+
height: lens2.height
|
|
867
|
+
} : null;
|
|
868
|
+
if (was.current) damage.invalidateBox(was.current);
|
|
869
|
+
if (now) damage.invalidateBox(now);
|
|
870
|
+
was.current = now;
|
|
871
|
+
},
|
|
872
|
+
[damage, onLensLatest, hostLatest]
|
|
873
|
+
);
|
|
874
|
+
const options = useMemo(
|
|
875
|
+
() => resolveLoupe({
|
|
876
|
+
render,
|
|
877
|
+
source,
|
|
878
|
+
factor,
|
|
879
|
+
minFactor,
|
|
880
|
+
maxFactor,
|
|
881
|
+
mode,
|
|
882
|
+
diameter,
|
|
883
|
+
shape,
|
|
884
|
+
place,
|
|
885
|
+
hollow,
|
|
886
|
+
onLens: onLens || damage ? reportLens : void 0,
|
|
887
|
+
peekKey,
|
|
888
|
+
onColorChange
|
|
889
|
+
}),
|
|
890
|
+
[
|
|
891
|
+
render,
|
|
892
|
+
source,
|
|
893
|
+
factor,
|
|
894
|
+
minFactor,
|
|
895
|
+
maxFactor,
|
|
896
|
+
mode,
|
|
897
|
+
diameter,
|
|
898
|
+
shape,
|
|
899
|
+
place,
|
|
900
|
+
hollow,
|
|
901
|
+
onLens,
|
|
902
|
+
damage,
|
|
903
|
+
reportLens,
|
|
904
|
+
peekKey,
|
|
905
|
+
onColorChange
|
|
906
|
+
]
|
|
907
|
+
);
|
|
528
908
|
const mount = enabled === void 0 ? loupeSwitch?.mount : void 0;
|
|
529
909
|
useEffect(() => mount?.(), [mount]);
|
|
530
910
|
const sample = useMemo(() => {
|
|
911
|
+
if (options.source) {
|
|
912
|
+
const input = options.source;
|
|
913
|
+
return (p) => {
|
|
914
|
+
const src = resolveLoupeSource(input);
|
|
915
|
+
return src ? sampleSource(src, p, sourceBoxIn(src, host.current)) : null;
|
|
916
|
+
};
|
|
917
|
+
}
|
|
531
918
|
if (!surface) return void 0;
|
|
532
919
|
return (p) => {
|
|
533
920
|
const canvases = surface.canvases.current;
|
|
534
921
|
return canvases ? sampleStack(surface.layers, canvases, p, surface.size.dpr) : null;
|
|
535
922
|
};
|
|
536
|
-
}, [surface]);
|
|
923
|
+
}, [surface, options.source, host]);
|
|
924
|
+
const resolved = resolveLoupeSource(options.source);
|
|
925
|
+
const subscribeResample = useMemo(
|
|
926
|
+
() => resolved ? (fn) => resolved.subscribeFrame(fn) : void 0,
|
|
927
|
+
[resolved]
|
|
928
|
+
);
|
|
537
929
|
const loupe = useLoupe({
|
|
538
930
|
options,
|
|
539
931
|
hostRef: host,
|
|
540
932
|
enabled: enabled ?? loupeSwitch?.on ?? false,
|
|
541
|
-
sample
|
|
933
|
+
sample,
|
|
934
|
+
subscribeResample
|
|
542
935
|
});
|
|
936
|
+
const reading = loupe.visible ? resolved : null;
|
|
937
|
+
useEffect(() => reading?.retain(), [reading]);
|
|
543
938
|
const measured = useHostSize(host);
|
|
544
939
|
const size = surface?.size ?? measured;
|
|
545
940
|
const gestures = /* @__PURE__ */ jsx(CameraScope, { children: /* @__PURE__ */ jsx(LoupeGestures, { hostRef: host, input: loupe.input, peekKey: options.peekKey ?? null }) });
|
|
546
|
-
const
|
|
941
|
+
const { center, shows, width, height, factor: shownFactor, shape: lensShape } = loupe.lens;
|
|
942
|
+
const box = { width, height };
|
|
943
|
+
const lens = loupe.visible ? /* @__PURE__ */ jsx(LoupeBubble, { aim: center, diameter: box, shape: lensShape, hollow: options.hollow, children: options.hollow ? null : options.render ? /* @__PURE__ */ jsx(
|
|
547
944
|
DomLoupe,
|
|
548
945
|
{
|
|
549
|
-
aim:
|
|
550
|
-
factor:
|
|
946
|
+
aim: shows,
|
|
947
|
+
factor: shownFactor,
|
|
551
948
|
mode: loupe.mode,
|
|
552
|
-
diameter:
|
|
949
|
+
diameter: box,
|
|
553
950
|
size,
|
|
554
951
|
view: view ?? stack?.view ?? (camera ? fromCameraView(camera.view.get(), camera.frame) : IDENTITY),
|
|
555
952
|
frame: stack?.frame ?? camera?.frame,
|
|
556
953
|
render: options.render
|
|
557
954
|
}
|
|
955
|
+
) : options.source ? /* @__PURE__ */ jsx(
|
|
956
|
+
SourceLoupe,
|
|
957
|
+
{
|
|
958
|
+
aim: shows,
|
|
959
|
+
factor: shownFactor,
|
|
960
|
+
diameter: box,
|
|
961
|
+
source: options.source,
|
|
962
|
+
hostRef: host
|
|
963
|
+
}
|
|
558
964
|
) : surface && stack ? /* @__PURE__ */ jsx(
|
|
559
965
|
CanvasLoupe,
|
|
560
966
|
{
|
|
561
|
-
aim:
|
|
562
|
-
factor:
|
|
967
|
+
aim: shows,
|
|
968
|
+
factor: shownFactor,
|
|
563
969
|
mode: loupe.mode,
|
|
564
|
-
diameter:
|
|
970
|
+
diameter: box,
|
|
565
971
|
surface,
|
|
566
972
|
view: stack.view,
|
|
567
973
|
frame: stack.frame,
|
|
@@ -582,6 +988,6 @@ function TrialLoupe({
|
|
|
582
988
|
] });
|
|
583
989
|
}
|
|
584
990
|
|
|
585
|
-
export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, TrialLoupe, createLoupeActions, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe };
|
|
991
|
+
export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, SourceLoupe, TrialLoupe, createCanvasSource, createLoupeActions, drawCanvasLens, drawSourceLens, lensCamera, lensSourceRect, loupeExtent, placeBand, resolveLoupe, resolveLoupeSource, sampleSource, sampleStack, sourceBoxIn, useLoupe };
|
|
586
992
|
//# sourceMappingURL=index.js.map
|
|
587
993
|
//# sourceMappingURL=index.js.map
|