@reekon-tools/boldr-utils 1.19.6 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/annotation/canvas/AnnotationCanvasInner.d.ts +1 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.js +10 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.native.d.ts +1 -0
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +214 -29
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +7 -1
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +11 -2
- package/dist/annotation/canvas/Tool.d.ts +6 -0
- package/dist/annotation/canvas/snapGeometry.d.ts +32 -0
- package/dist/annotation/canvas/snapGeometry.js +168 -0
- package/dist/annotation/canvas/tools/measurementTool.js +38 -4
- package/dist/annotation/canvas/tools/selectTool.js +43 -1
- package/dist/annotation/canvas/tools/shapeTool.js +27 -5
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +2 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.js +16 -1
- package/dist/exports.d.ts +2 -1
- package/dist/exports.js +1 -0
- package/package.json +1 -1
- package/dist/canvas/AnnotationCanvas.d.ts +0 -11
- package/dist/canvas/AnnotationCanvas.js +0 -10
- package/dist/canvas/AnnotationCanvas.native.d.ts +0 -8
- package/dist/canvas/AnnotationCanvas.native.js +0 -6
- package/dist/canvas/AnnotationCanvasInner.d.ts +0 -39
- package/dist/canvas/AnnotationCanvasInner.js +0 -219
- package/dist/canvas/AnnotationCanvasInner.native.d.ts +0 -35
- package/dist/canvas/AnnotationCanvasInner.native.js +0 -138
- package/dist/canvas/AnnotationCanvasSkia.d.ts +0 -27
- package/dist/canvas/AnnotationCanvasSkia.js +0 -20
- package/dist/canvas/Tool.d.ts +0 -38
- package/dist/canvas/Tool.js +0 -1
- package/dist/canvas/elements/BackgroundImageElement.d.ts +0 -9
- package/dist/canvas/elements/BackgroundImageElement.js +0 -37
- package/dist/canvas/elements/MeasurementStampElement.d.ts +0 -13
- package/dist/canvas/elements/MeasurementStampElement.js +0 -30
- package/dist/canvas/elements/ShapeElement.d.ts +0 -7
- package/dist/canvas/elements/ShapeElement.js +0 -62
- package/dist/canvas/elements/StrokeElement.d.ts +0 -7
- package/dist/canvas/elements/StrokeElement.js +0 -18
- package/dist/canvas/measurementPicker.d.ts +0 -10
- package/dist/canvas/measurementPicker.js +0 -1
- package/dist/canvas/measurementStampOverlay.d.ts +0 -11
- package/dist/canvas/measurementStampOverlay.js +0 -1
- package/dist/canvas/pointerAdapter.d.ts +0 -3
- package/dist/canvas/pointerAdapter.js +0 -19
- package/dist/canvas/stampLayout.d.ts +0 -5
- package/dist/canvas/stampLayout.js +0 -14
- package/dist/canvas/tools/measurementStampTool.d.ts +0 -9
- package/dist/canvas/tools/measurementStampTool.js +0 -37
- package/dist/canvas/tools/panTool.d.ts +0 -5
- package/dist/canvas/tools/panTool.js +0 -25
- package/dist/canvas/tools/penTool.d.ts +0 -13
- package/dist/canvas/tools/penTool.js +0 -68
- package/dist/canvas/tools/selectTool.d.ts +0 -2
- package/dist/canvas/tools/selectTool.js +0 -182
- package/dist/canvas/useAnnotationCanvasState.d.ts +0 -54
- package/dist/canvas/useAnnotationCanvasState.js +0 -210
- package/dist/canvas/viewport.d.ts +0 -16
- package/dist/canvas/viewport.js +0 -54
- package/dist/data/AnnotationDataContext.d.ts +0 -8
- package/dist/data/AnnotationDataContext.js +0 -11
- package/dist/data/AnnotationDataProvider.d.ts +0 -65
- package/dist/data/AnnotationDataProvider.js +0 -4
- package/dist/data/InMemoryAnnotationProvider.d.ts +0 -30
- package/dist/data/InMemoryAnnotationProvider.js +0 -197
- package/dist/data/canvasPersistence.d.ts +0 -3
- package/dist/data/canvasPersistence.js +0 -26
- package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +0 -33
- package/dist/data/hooks/useAnnotationCanvasDoc.js +0 -314
- package/dist/data/hooks/useAnnotationDoc.d.ts +0 -7
- package/dist/data/hooks/useAnnotationDoc.js +0 -33
- package/dist/data/hooks/useAnnotationList.d.ts +0 -7
- package/dist/data/hooks/useAnnotationList.js +0 -26
- package/dist/data/hooks/useAnnotationMutations.d.ts +0 -9
- package/dist/data/hooks/useAnnotationMutations.js +0 -11
- package/dist/hooks/useParseMeasurement.d.ts +0 -4
- package/dist/hooks/useParseMeasurement.js +0 -14
- package/dist/utils/evaluateFormula.d.ts +0 -20
- package/dist/utils/evaluateFormula.js +0 -31
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Magnetic snapping for line endpoints (Asana 1217966180270874). While an
|
|
2
|
+
// endpoint of a measurement line (`placement: 'line'`) or a line/arrow shape is
|
|
3
|
+
// being placed or dragged, it snaps to nearby geometry: other lines' endpoints
|
|
4
|
+
// and midpoints, polygon/rect vertices, and measurement-rectangle corners.
|
|
5
|
+
// Skia-free, safe in tools, hit-tests and unit tests.
|
|
6
|
+
//
|
|
7
|
+
// `snapNearest`/`snapDelta` carry 'worklet' directives: the native Inner calls
|
|
8
|
+
// them per frame on the UI thread (the live endpoint derived values and the
|
|
9
|
+
// shape rubber-band gesture) — the same mechanism planeGeometry's grid
|
|
10
|
+
// generators rely on. Keep them free of closures over non-worklet module
|
|
11
|
+
// functions (numeric constants are fine) so the workletizer can lift them.
|
|
12
|
+
//
|
|
13
|
+
// Targets travel to the UI thread as a FLAT number array (`[x, y, kind, …]`,
|
|
14
|
+
// stride SNAP_STRIDE) written once per drag — see `encodeSnapTargets`.
|
|
15
|
+
import { placementOf, normalizeRect } from './measurementGeometry.js';
|
|
16
|
+
// Screen-px capture radius: the grabbed endpoint jumps to a target once it is
|
|
17
|
+
// within this many pixels of it. Converted to doc space via `/ zoom` at drag
|
|
18
|
+
// begin (frozen for the drag, like the slide detent's SLIDE_SNAP_PX).
|
|
19
|
+
export const SNAP_RADIUS_PX = 16;
|
|
20
|
+
// Screen-px radius / ring width of the snap indicator drawn around the target
|
|
21
|
+
// while a snap is active. Larger than the endpoint handle (HANDLE_RADIUS_PX 7)
|
|
22
|
+
// so the ring frames the handle instead of hiding under it.
|
|
23
|
+
export const SNAP_RING_PX = 11;
|
|
24
|
+
export const SNAP_RING_WIDTH_PX = 2;
|
|
25
|
+
// Target kinds, ordered by priority: on an exact distance tie the LOWER kind
|
|
26
|
+
// wins, so a true endpoint/vertex beats a midpoint sitting on the same pixel.
|
|
27
|
+
export const SNAP_KIND_POINT = 0;
|
|
28
|
+
export const SNAP_KIND_MIDPOINT = 1;
|
|
29
|
+
// Flat encoding stride: [x, y, kind].
|
|
30
|
+
export const SNAP_STRIDE = 3;
|
|
31
|
+
// Every snap target in the document, in doc space. `excludeId` drops the
|
|
32
|
+
// element being edited ENTIRELY (its endpoints and midpoint) — snapping a
|
|
33
|
+
// line's endpoint onto its own other end would collapse it, and onto its own
|
|
34
|
+
// midpoint would halve it. Targets:
|
|
35
|
+
// - measurement `placement:'line'`: a, b (point) + midpoint. Plane-edge
|
|
36
|
+
// tiles included — their endpoints are real geometry even though the
|
|
37
|
+
// select tool refuses to drag them.
|
|
38
|
+
// - measurement `placement:'rectangle'`: the four normalized corners.
|
|
39
|
+
// - shape line/arrow: both endpoints + midpoint.
|
|
40
|
+
// - shape rect: the four normalized corners (stored as two opposite corners).
|
|
41
|
+
// - shape polygon (triangles included): every vertex.
|
|
42
|
+
// - ellipse, text, strokes, backgrounds: nothing.
|
|
43
|
+
// Degenerate (zero-length) lines contribute their coincident endpoints but no
|
|
44
|
+
// midpoint.
|
|
45
|
+
export const collectSnapTargets = (doc, excludeId) => {
|
|
46
|
+
const out = [];
|
|
47
|
+
const point = (p, sourceId) => out.push({ x: p.x, y: p.y, kind: SNAP_KIND_POINT, sourceId });
|
|
48
|
+
const segment = (a, b, sourceId) => {
|
|
49
|
+
point(a, sourceId);
|
|
50
|
+
point(b, sourceId);
|
|
51
|
+
if (a.x !== b.x || a.y !== b.y) {
|
|
52
|
+
out.push({
|
|
53
|
+
x: (a.x + b.x) / 2,
|
|
54
|
+
y: (a.y + b.y) / 2,
|
|
55
|
+
kind: SNAP_KIND_MIDPOINT,
|
|
56
|
+
sourceId,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const corners = (r, sourceId) => {
|
|
61
|
+
const n = normalizeRect(r);
|
|
62
|
+
point({ x: n.minX, y: n.minY }, sourceId);
|
|
63
|
+
point({ x: n.maxX, y: n.minY }, sourceId);
|
|
64
|
+
point({ x: n.maxX, y: n.maxY }, sourceId);
|
|
65
|
+
point({ x: n.minX, y: n.maxY }, sourceId);
|
|
66
|
+
};
|
|
67
|
+
for (const m of doc.placedMeasurements) {
|
|
68
|
+
if (excludeId != null && m.id === excludeId)
|
|
69
|
+
continue;
|
|
70
|
+
const placement = placementOf(m);
|
|
71
|
+
if (placement === 'line' && m.line)
|
|
72
|
+
segment(m.line.a, m.line.b, m.id);
|
|
73
|
+
else if (placement === 'rectangle' && m.rect)
|
|
74
|
+
corners(m.rect, m.id);
|
|
75
|
+
}
|
|
76
|
+
for (const s of doc.shapes) {
|
|
77
|
+
if (excludeId != null && s.id === excludeId)
|
|
78
|
+
continue;
|
|
79
|
+
const pts = s.geometry.points;
|
|
80
|
+
switch (s.kind) {
|
|
81
|
+
case 'line':
|
|
82
|
+
case 'arrow': {
|
|
83
|
+
const [a, b] = pts;
|
|
84
|
+
if (a && b)
|
|
85
|
+
segment(a, b, s.id);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'rect': {
|
|
89
|
+
const [a, b] = pts;
|
|
90
|
+
if (a && b)
|
|
91
|
+
corners({ a, b }, s.id);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case 'polygon':
|
|
95
|
+
for (const p of pts)
|
|
96
|
+
point(p, s.id);
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
};
|
|
104
|
+
// Flat `[x, y, kind, …]` buffer for UI-thread transfer (a plain number[],
|
|
105
|
+
// like the freehand `livePoints` — TypedArrays aren't proven shareable here).
|
|
106
|
+
export const encodeSnapTargets = (targets) => {
|
|
107
|
+
const flat = new Array(targets.length * SNAP_STRIDE);
|
|
108
|
+
for (let i = 0; i < targets.length; i++) {
|
|
109
|
+
const t = targets[i];
|
|
110
|
+
flat[i * SNAP_STRIDE] = t.x;
|
|
111
|
+
flat[i * SNAP_STRIDE + 1] = t.y;
|
|
112
|
+
flat[i * SNAP_STRIDE + 2] = t.kind;
|
|
113
|
+
}
|
|
114
|
+
return flat;
|
|
115
|
+
};
|
|
116
|
+
export const collectSnapTargetsFlat = (doc, excludeId) => encodeSnapTargets(collectSnapTargets(doc, excludeId));
|
|
117
|
+
// Nearest target to (x, y) within `radiusDoc` (inclusive), or null. Ties on
|
|
118
|
+
// distance go to the lower kind (point beats midpoint); remaining ties to the
|
|
119
|
+
// earlier target. `radiusDoc <= 0` disables snapping. Worklet-callable.
|
|
120
|
+
export const snapNearest = (x, y, flat, radiusDoc) => {
|
|
121
|
+
'worklet';
|
|
122
|
+
if (radiusDoc <= 0)
|
|
123
|
+
return null;
|
|
124
|
+
const r2 = radiusDoc * radiusDoc;
|
|
125
|
+
let bestD2 = Infinity;
|
|
126
|
+
let bestKind = Infinity;
|
|
127
|
+
let bestIdx = -1;
|
|
128
|
+
for (let i = 0; i + 2 < flat.length; i += 3) {
|
|
129
|
+
const dx = flat[i] - x;
|
|
130
|
+
const dy = flat[i + 1] - y;
|
|
131
|
+
const d2 = dx * dx + dy * dy;
|
|
132
|
+
if (d2 > r2)
|
|
133
|
+
continue;
|
|
134
|
+
const kind = flat[i + 2];
|
|
135
|
+
if (d2 < bestD2 || (d2 === bestD2 && kind < bestKind)) {
|
|
136
|
+
bestD2 = d2;
|
|
137
|
+
bestKind = kind;
|
|
138
|
+
bestIdx = i;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (bestIdx < 0)
|
|
142
|
+
return null;
|
|
143
|
+
return {
|
|
144
|
+
x: flat[bestIdx],
|
|
145
|
+
y: flat[bestIdx + 1],
|
|
146
|
+
kind: flat[bestIdx + 2] === 1 ? 1 : 0,
|
|
147
|
+
index: bestIdx / 3,
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
// Snap a DRAG: `base` is the grabbed endpoint's committed position and `delta`
|
|
151
|
+
// the raw world-space drag offset. Returns the effective delta that lands the
|
|
152
|
+
// endpoint exactly on the nearest target (or the raw delta when nothing is in
|
|
153
|
+
// range), so the existing delta-based patch builders stay untouched. Worklet-
|
|
154
|
+
// callable — the native select gesture runs it in onEnd so the commit matches
|
|
155
|
+
// the live preview bit-for-bit.
|
|
156
|
+
export const snapDelta = (base, delta, flat, radiusDoc) => {
|
|
157
|
+
'worklet';
|
|
158
|
+
const hit = snapNearest(base.x + delta.x, base.y + delta.y, flat, radiusDoc);
|
|
159
|
+
if (!hit)
|
|
160
|
+
return { delta, hit: null };
|
|
161
|
+
return { delta: { x: hit.x - base.x, y: hit.y - base.y }, hit };
|
|
162
|
+
};
|
|
163
|
+
// Snap an ABSOLUTE point (drag-to-create tools place raw world points).
|
|
164
|
+
export const snapPoint = (p, flat, radiusDoc) => {
|
|
165
|
+
'worklet';
|
|
166
|
+
const hit = snapNearest(p.x, p.y, flat, radiusDoc);
|
|
167
|
+
return hit ? { point: { x: hit.x, y: hit.y }, hit } : { point: p, hit: null };
|
|
168
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_LAYER_ID } from '../../../types/annotation.js';
|
|
2
2
|
import { DEFAULT_LINE_POS, recomputeAnchor, rectCenter, } from '../measurementGeometry.js';
|
|
3
|
+
import { collectSnapTargetsFlat, snapPoint } from '../snapGeometry.js';
|
|
3
4
|
// The `line*` style fields for a placed measurement, built from the tool's
|
|
4
5
|
// style options. Undefined fields are dropped (never emitted) so the render
|
|
5
6
|
// defaults apply and Firestore never sees an `undefined` value.
|
|
@@ -22,6 +23,25 @@ const lineStyleFields = (placement, style) => {
|
|
|
22
23
|
}
|
|
23
24
|
return out;
|
|
24
25
|
};
|
|
26
|
+
// Snap state for a fresh line press. The new annotation isn't in the doc yet,
|
|
27
|
+
// so nothing needs excluding. Empty when snapping is off.
|
|
28
|
+
const snapStateForPress = (ctx, placement) => placement === 'line' && ctx.snap?.enabled
|
|
29
|
+
? {
|
|
30
|
+
snap: {
|
|
31
|
+
targets: collectSnapTargetsFlat(ctx.document),
|
|
32
|
+
radiusDoc: ctx.snap.radiusPx / ctx.viewport.state.zoom,
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
: {};
|
|
36
|
+
// The world point a drag end should use: snapped when a target is in range.
|
|
37
|
+
// Reports the target (or null) to the indicator.
|
|
38
|
+
const snapEnd = (s, ctx, world) => {
|
|
39
|
+
if (!s.snap)
|
|
40
|
+
return world;
|
|
41
|
+
const r = snapPoint(world, s.snap.targets, s.snap.radiusDoc);
|
|
42
|
+
ctx.snap?.setIndicator(r.hit ? { x: r.hit.x, y: r.hit.y } : null);
|
|
43
|
+
return r.point;
|
|
44
|
+
};
|
|
25
45
|
let counter = 0;
|
|
26
46
|
const makeId = () => `annotation-${Date.now().toString(36)}-${(counter++).toString(36)}`;
|
|
27
47
|
const firstLayerId = (doc) => doc.layers[0]?.id ?? DEFAULT_LAYER_ID;
|
|
@@ -130,13 +150,21 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
130
150
|
id: options.id ?? defaultId,
|
|
131
151
|
label: options.label ?? defaultLabel,
|
|
132
152
|
cursor: 'crosshair',
|
|
133
|
-
onPointerDown(event) {
|
|
153
|
+
onPointerDown(event, ctx) {
|
|
154
|
+
const snap = snapStateForPress(ctx, placement);
|
|
155
|
+
// The START of a new line snaps too (a leader that begins exactly on a
|
|
156
|
+
// corner is the point of the feature); the ring flashes there until the
|
|
157
|
+
// first move re-evaluates it against the moving end.
|
|
158
|
+
const startWorld = snap.snap
|
|
159
|
+
? snapPoint(event.world, snap.snap.targets, snap.snap.radiusDoc).point
|
|
160
|
+
: event.world;
|
|
134
161
|
return {
|
|
135
162
|
kind: 'measurement-drawing',
|
|
136
163
|
id: makeId(),
|
|
137
|
-
startWorld
|
|
164
|
+
startWorld,
|
|
138
165
|
startScreen: event.screen,
|
|
139
166
|
moved: false,
|
|
167
|
+
...snap,
|
|
140
168
|
};
|
|
141
169
|
},
|
|
142
170
|
onPointerMove(event, ctx, state) {
|
|
@@ -149,7 +177,7 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
149
177
|
placement,
|
|
150
178
|
linePos,
|
|
151
179
|
a: s.startWorld,
|
|
152
|
-
b: event.world,
|
|
180
|
+
b: snapEnd(s, ctx, event.world),
|
|
153
181
|
style,
|
|
154
182
|
valueSource: valueSourceFor(ctx),
|
|
155
183
|
});
|
|
@@ -160,6 +188,7 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
160
188
|
const s = state;
|
|
161
189
|
if (s?.kind !== 'measurement-drawing')
|
|
162
190
|
return;
|
|
191
|
+
ctx.snap?.setIndicator(null);
|
|
163
192
|
const dx = event.screen.x - s.startScreen.x;
|
|
164
193
|
const dy = event.screen.y - s.startScreen.y;
|
|
165
194
|
if (!s.moved || dx * dx + dy * dy < minDragPx * minDragPx) {
|
|
@@ -167,18 +196,23 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
167
196
|
ctx.preview({ ops: [] });
|
|
168
197
|
return;
|
|
169
198
|
}
|
|
199
|
+
// Same snap as the last move, so the commit equals the preview.
|
|
200
|
+
const b = s.snap
|
|
201
|
+
? snapPoint(event.world, s.snap.targets, s.snap.radiusDoc).point
|
|
202
|
+
: event.world;
|
|
170
203
|
place(ctx, buildMeasurement({
|
|
171
204
|
id: s.id,
|
|
172
205
|
layerId: firstLayerId(ctx.document),
|
|
173
206
|
placement,
|
|
174
207
|
linePos,
|
|
175
208
|
a: s.startWorld,
|
|
176
|
-
b
|
|
209
|
+
b,
|
|
177
210
|
style,
|
|
178
211
|
valueSource: valueSourceFor(ctx),
|
|
179
212
|
}));
|
|
180
213
|
},
|
|
181
214
|
onCancel(_state, ctx) {
|
|
215
|
+
ctx.snap?.setIndicator(null);
|
|
182
216
|
ctx.preview({ ops: [] });
|
|
183
217
|
},
|
|
184
218
|
};
|
|
@@ -2,6 +2,7 @@ import { MAX_BACKGROUND_DOC_EXTENT, MIN_BACKGROUND_DOC_EXTENT, backgroundCropBar
|
|
|
2
2
|
import { stampTileDims } from '../stampLayout.js';
|
|
3
3
|
import { placementOf, linePosOf, snapLinePos, lerp, recomputeAnchor, rectCenter, rectCornerPoint, oppositeRectCorner, hitPlacedMeasurement, } from '../measurementGeometry.js';
|
|
4
4
|
import { hitShapeOutline, visualShapeBounds } from '../shapeGeometry.js';
|
|
5
|
+
import { collectSnapTargetsFlat, snapDelta } from '../snapGeometry.js';
|
|
5
6
|
import { DEFAULT_TEXT_FONT_SIZE, resizeScaleFromDrag, textResizeGeometry, textShapeBounds, } from '../textGeometry.js';
|
|
6
7
|
import { editTextShape } from './textEditing.js';
|
|
7
8
|
const HIT_PADDING = 6;
|
|
@@ -238,6 +239,32 @@ const slidePatch = (doc, id, delta, zoom) => {
|
|
|
238
239
|
ops: [{ op: 'updateMeasurement', id, patch: { linePos: t, anchor } }],
|
|
239
240
|
};
|
|
240
241
|
};
|
|
242
|
+
// Committed position of the grabbed endpoint — the base the drag delta (and
|
|
243
|
+
// the snap) is measured from. Measurement lines keep endpoints on `line.a/b`,
|
|
244
|
+
// shape lines on `geometry.points[0/1]`.
|
|
245
|
+
const endpointBase = (doc, id, elementKind, handle) => {
|
|
246
|
+
if (elementKind === 'shape') {
|
|
247
|
+
const sh = doc.shapes.find((x) => x.id === id);
|
|
248
|
+
const p = sh?.geometry.points[handle === 'a' ? 0 : 1];
|
|
249
|
+
return p ?? null;
|
|
250
|
+
}
|
|
251
|
+
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
252
|
+
if (!m?.line)
|
|
253
|
+
return null;
|
|
254
|
+
return handle === 'a' ? m.line.a : m.line.b;
|
|
255
|
+
};
|
|
256
|
+
// Snap bookkeeping for an endpoint grab: harvest every OTHER element's targets
|
|
257
|
+
// once (ctx.document is the committed doc, so in-flight previews never leak
|
|
258
|
+
// in) and freeze the capture radius in doc space at the grab zoom. Empty when
|
|
259
|
+
// snapping is off, so the drag stays a plain delta.
|
|
260
|
+
const snapStateForGrab = (ctx, excludeId, zoom) => ctx.snap?.enabled
|
|
261
|
+
? {
|
|
262
|
+
snap: {
|
|
263
|
+
targets: collectSnapTargetsFlat(ctx.document, excludeId),
|
|
264
|
+
radiusDoc: ctx.snap.radiusPx / zoom,
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
: {};
|
|
241
268
|
// Which endpoint handle of a (selected) line annotation is under `world`.
|
|
242
269
|
// Prefers the nearer endpoint when both are within range.
|
|
243
270
|
const findHandleHit = (doc, id, world, zoom) => {
|
|
@@ -777,6 +804,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
777
804
|
handle,
|
|
778
805
|
start: world,
|
|
779
806
|
delta: { x: 0, y: 0 },
|
|
807
|
+
...snapStateForGrab(ctx, selId, zoom),
|
|
780
808
|
};
|
|
781
809
|
}
|
|
782
810
|
const shapeHandle = findShapeHandleHit(ctx.document, selId, world, zoom);
|
|
@@ -790,6 +818,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
790
818
|
handle: shapeHandle,
|
|
791
819
|
start: world,
|
|
792
820
|
delta: { x: 0, y: 0 },
|
|
821
|
+
...snapStateForGrab(ctx, selId, zoom),
|
|
793
822
|
};
|
|
794
823
|
}
|
|
795
824
|
const resizeGeom = findResizeHandleHit(ctx.document, selId, world, zoom);
|
|
@@ -935,7 +964,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
935
964
|
}
|
|
936
965
|
if (s?.kind !== 'dragging')
|
|
937
966
|
return s;
|
|
938
|
-
|
|
967
|
+
let delta = {
|
|
939
968
|
x: event.world.x - s.start.x,
|
|
940
969
|
y: event.world.y - s.start.y,
|
|
941
970
|
};
|
|
@@ -945,12 +974,24 @@ export const createSelectTool = (options = {}) => ({
|
|
|
945
974
|
pendingTextEditId = null;
|
|
946
975
|
pendingBackgroundDeselectId = null;
|
|
947
976
|
}
|
|
977
|
+
// Magnetic endpoint snap: bend the delta so the grabbed end lands on the
|
|
978
|
+
// nearest target in range, and show the ring there. The snapped delta is
|
|
979
|
+
// what gets stored, so onPointerUp's commit reuses it unchanged.
|
|
980
|
+
if (s.mode === 'endpoint' && s.snap && s.handle) {
|
|
981
|
+
const base = endpointBase(ctx.document, s.id, s.elementKind, s.handle);
|
|
982
|
+
if (base) {
|
|
983
|
+
const r = snapDelta(base, delta, s.snap.targets, s.snap.radiusDoc);
|
|
984
|
+
delta = r.delta;
|
|
985
|
+
ctx.snap?.setIndicator(r.hit ? { x: r.hit.x, y: r.hit.y } : null);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
948
988
|
const patch = dragPatch(s, ctx.document, delta, ctx.viewport.state.zoom);
|
|
949
989
|
if (patch)
|
|
950
990
|
ctx.preview(patch);
|
|
951
991
|
return { ...s, delta };
|
|
952
992
|
},
|
|
953
993
|
onPointerUp(_event, ctx, state) {
|
|
994
|
+
ctx.snap?.setIndicator(null);
|
|
954
995
|
const editId = pendingTextEditId;
|
|
955
996
|
pendingTextEditId = null;
|
|
956
997
|
const deselectId = pendingBackgroundDeselectId;
|
|
@@ -984,6 +1025,7 @@ export const createSelectTool = (options = {}) => ({
|
|
|
984
1025
|
onCancel(_state, ctx) {
|
|
985
1026
|
pendingTextEditId = null;
|
|
986
1027
|
pendingBackgroundDeselectId = null;
|
|
1028
|
+
ctx.snap?.setIndicator(null);
|
|
987
1029
|
ctx.preview({ ops: [] });
|
|
988
1030
|
},
|
|
989
1031
|
hitTest(element, p) {
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { DEFAULT_LAYER_ID } from '../../../types/annotation.js';
|
|
2
2
|
import { annotationKindFor, shapePointsFromDrag, } from '../shapeGeometry.js';
|
|
3
|
+
import { collectSnapTargetsFlat, snapPoint } from '../snapGeometry.js';
|
|
4
|
+
const snapStateForPress = (ctx, kind) => kind === 'line' && ctx.snap?.enabled
|
|
5
|
+
? {
|
|
6
|
+
snap: {
|
|
7
|
+
targets: collectSnapTargetsFlat(ctx.document),
|
|
8
|
+
radiusDoc: ctx.snap.radiusPx / ctx.viewport.state.zoom,
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
: {};
|
|
12
|
+
const snapWorld = (s, world) => s.snap ? snapPoint(world, s.snap.targets, s.snap.radiusDoc) : null;
|
|
3
13
|
let counter = 0;
|
|
4
14
|
const makeId = (prefix) => `${prefix}-${Date.now().toString(36)}-${(counter++).toString(36)}`;
|
|
5
15
|
const DEFAULT_LABELS = {
|
|
@@ -61,12 +71,14 @@ export const createShapeTool = (options = {}) => {
|
|
|
61
71
|
dash,
|
|
62
72
|
},
|
|
63
73
|
onPointerDown(event, ctx) {
|
|
74
|
+
const snap = snapStateForPress(ctx, kind);
|
|
75
|
+
const startWorld = snapWorld(snap, event.world)?.point ?? event.world;
|
|
64
76
|
return {
|
|
65
77
|
kind: 'shape-drawing',
|
|
66
78
|
shape: buildShapeFromDrag({
|
|
67
79
|
kind,
|
|
68
|
-
a:
|
|
69
|
-
b:
|
|
80
|
+
a: startWorld,
|
|
81
|
+
b: startWorld,
|
|
70
82
|
color,
|
|
71
83
|
width,
|
|
72
84
|
cap,
|
|
@@ -74,22 +86,28 @@ export const createShapeTool = (options = {}) => {
|
|
|
74
86
|
dash,
|
|
75
87
|
layerId: firstLayerId(ctx.document),
|
|
76
88
|
}),
|
|
77
|
-
startWorld
|
|
89
|
+
startWorld,
|
|
78
90
|
startScreen: event.screen,
|
|
79
91
|
moved: false,
|
|
92
|
+
...snap,
|
|
80
93
|
};
|
|
81
94
|
},
|
|
82
95
|
onPointerMove(event, ctx, state) {
|
|
83
96
|
const s = state;
|
|
84
97
|
if (s?.kind !== 'shape-drawing')
|
|
85
98
|
return s;
|
|
99
|
+
const snapped = snapWorld(s, event.world);
|
|
100
|
+
if (s.snap) {
|
|
101
|
+
ctx.snap?.setIndicator(snapped?.hit ? { x: snapped.hit.x, y: snapped.hit.y } : null);
|
|
102
|
+
}
|
|
103
|
+
const end = snapped?.point ?? event.world;
|
|
86
104
|
const next = {
|
|
87
105
|
...s,
|
|
88
106
|
moved: true,
|
|
89
107
|
shape: {
|
|
90
108
|
...s.shape,
|
|
91
109
|
geometry: {
|
|
92
|
-
points: shapePointsFromDrag(kind, s.startWorld,
|
|
110
|
+
points: shapePointsFromDrag(kind, s.startWorld, end),
|
|
93
111
|
},
|
|
94
112
|
},
|
|
95
113
|
};
|
|
@@ -100,6 +118,7 @@ export const createShapeTool = (options = {}) => {
|
|
|
100
118
|
const s = state;
|
|
101
119
|
if (s?.kind !== 'shape-drawing')
|
|
102
120
|
return;
|
|
121
|
+
ctx.snap?.setIndicator(null);
|
|
103
122
|
const dx = event.screen.x - s.startScreen.x;
|
|
104
123
|
const dy = event.screen.y - s.startScreen.y;
|
|
105
124
|
if (!s.moved || dx * dx + dy * dy < minDragPx * minDragPx) {
|
|
@@ -107,15 +126,18 @@ export const createShapeTool = (options = {}) => {
|
|
|
107
126
|
ctx.preview({ ops: [] });
|
|
108
127
|
return;
|
|
109
128
|
}
|
|
129
|
+
// Same snap as the last move, so the commit equals the preview.
|
|
130
|
+
const end = snapWorld(s, event.world)?.point ?? event.world;
|
|
110
131
|
const shape = {
|
|
111
132
|
...s.shape,
|
|
112
133
|
geometry: {
|
|
113
|
-
points: shapePointsFromDrag(kind, s.startWorld,
|
|
134
|
+
points: shapePointsFromDrag(kind, s.startWorld, end),
|
|
114
135
|
},
|
|
115
136
|
};
|
|
116
137
|
ctx.commit({ ops: [{ op: 'addShape', shape }] });
|
|
117
138
|
},
|
|
118
139
|
onCancel(_state, ctx) {
|
|
140
|
+
ctx.snap?.setIndicator(null);
|
|
119
141
|
ctx.preview({ ops: [] });
|
|
120
142
|
},
|
|
121
143
|
};
|
|
@@ -64,6 +64,7 @@ export interface UseAnnotationCanvasStateProps {
|
|
|
64
64
|
measurements?: Measurement[];
|
|
65
65
|
pickMeasurement?: () => Promise<MeasurementRef | null>;
|
|
66
66
|
requestTextInput?: RequestTextInput;
|
|
67
|
+
snapToGeometry?: boolean;
|
|
67
68
|
width: number;
|
|
68
69
|
height: number;
|
|
69
70
|
initialViewport?: ViewportState;
|
|
@@ -94,6 +95,7 @@ export interface AnnotationCanvasStateApi {
|
|
|
94
95
|
fileTileScaleFactor: number;
|
|
95
96
|
penDrawingStroke: AnnotationStroke | null;
|
|
96
97
|
customPreviewState: ToolState;
|
|
98
|
+
snapIndicator: Vec2 | null;
|
|
97
99
|
dispatchPointerDown(event: CanvasPointerEvent): void;
|
|
98
100
|
dispatchPointerMove(event: CanvasPointerEvent): void;
|
|
99
101
|
dispatchPointerUp(event: CanvasPointerEvent): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { applyPatch, invertPatch, DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
3
3
|
import { resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, resolveTileScaleFactor, stampTileDims, STAMP_TILE_SIZE, tileDocSizeForFactor, tileScaleFromDocSize, } from './stampLayout.js';
|
|
4
|
+
import { SNAP_RADIUS_PX } from './snapGeometry.js';
|
|
4
5
|
import { createViewportApi, fitRectToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, } from './viewport.js';
|
|
5
6
|
import { computeZoomFloor, pdfFirstPageDocRect } from './pdfDocument.js';
|
|
6
7
|
import { backgroundLayerDocRect, backgroundLayersOf, backgroundLayersUnionRect, } from './backgroundLayers.js';
|
|
@@ -113,7 +114,7 @@ const defaultLeaderLenDoc = (docWidth) => Math.min(800, Math.max(240, docWidth *
|
|
|
113
114
|
// inners share this hook; each wraps it with platform-specific event
|
|
114
115
|
// capture and JSX (div + DOM events vs. GestureDetector + RN Views).
|
|
115
116
|
export const useAnnotationCanvasState = (props) => {
|
|
116
|
-
const { canvas, onCommit, tools, activeToolId, selection, onSelectionChange, measurements, pickMeasurement, requestTextInput, width, height, initialViewport, tileScalePlatform, animateViewport, imperativeRef, } = props;
|
|
117
|
+
const { canvas, onCommit, tools, activeToolId, selection, onSelectionChange, measurements, pickMeasurement, requestTextInput, snapToGeometry, width, height, initialViewport, tileScalePlatform, animateViewport, imperativeRef, } = props;
|
|
117
118
|
const [viewport, setViewport] = useState(initialViewport ?? DEFAULT_VIEWPORT);
|
|
118
119
|
const [toolState, setToolState] = useState(undefined);
|
|
119
120
|
// Synchronous mirror for the DISPATCHERS. The native tap gesture
|
|
@@ -128,6 +129,8 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
128
129
|
toolStateRef.current = next;
|
|
129
130
|
setToolState(next);
|
|
130
131
|
};
|
|
132
|
+
// Snap ring position for JS-path drags (see AnnotationCanvasStateApi).
|
|
133
|
+
const [snapIndicator, setSnapIndicator] = useState(null);
|
|
131
134
|
const [previewPatch, setPreviewPatch] = useState(null);
|
|
132
135
|
const undoStackRef = useRef([]);
|
|
133
136
|
const redoStackRef = useRef([]);
|
|
@@ -228,6 +231,11 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
228
231
|
tileScaleFactor,
|
|
229
232
|
headerTileScaleFactor,
|
|
230
233
|
fileTileScaleFactor,
|
|
234
|
+
snap: {
|
|
235
|
+
enabled: snapToGeometry === true,
|
|
236
|
+
radiusPx: SNAP_RADIUS_PX,
|
|
237
|
+
setIndicator: setSnapIndicator,
|
|
238
|
+
},
|
|
231
239
|
preview(patch) {
|
|
232
240
|
setPreviewPatch(patch);
|
|
233
241
|
},
|
|
@@ -328,6 +336,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
328
336
|
onSelectionChange,
|
|
329
337
|
pickMeasurement,
|
|
330
338
|
requestTextInput,
|
|
339
|
+
snapToGeometry,
|
|
331
340
|
]);
|
|
332
341
|
// Live ctx for imperative handle methods (which are created in an effect and
|
|
333
342
|
// would otherwise capture a stale ctx/viewport).
|
|
@@ -353,6 +362,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
353
362
|
activePointerIdRef.current = null;
|
|
354
363
|
updateToolState(undefined);
|
|
355
364
|
setPreviewPatch(null);
|
|
365
|
+
setSnapIndicator(null);
|
|
356
366
|
// updateToolState is a stable-in-effect helper (ref + setState); the
|
|
357
367
|
// effect only re-runs on a tool identity change.
|
|
358
368
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -390,6 +400,9 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
390
400
|
activeTool.onPointerUp?.(event, ctx, toolStateRef.current);
|
|
391
401
|
activePointerIdRef.current = null;
|
|
392
402
|
updateToolState(undefined);
|
|
403
|
+
// Belt-and-braces: tools clear their own ring on release, but a tool
|
|
404
|
+
// that forgets must not leave a stale ring on the canvas.
|
|
405
|
+
setSnapIndicator(null);
|
|
393
406
|
},
|
|
394
407
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
395
408
|
[activeTool, ctx]);
|
|
@@ -410,6 +423,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
410
423
|
const prevToolState = toolStateRef.current;
|
|
411
424
|
updateToolState(undefined);
|
|
412
425
|
setPreviewPatch(null);
|
|
426
|
+
setSnapIndicator(null);
|
|
413
427
|
if (activeTool)
|
|
414
428
|
activeTool.onCancel?.(prevToolState, ctx);
|
|
415
429
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -903,6 +917,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
903
917
|
fileTileScaleFactor,
|
|
904
918
|
penDrawingStroke,
|
|
905
919
|
customPreviewState: toolState,
|
|
920
|
+
snapIndicator,
|
|
906
921
|
dispatchPointerDown,
|
|
907
922
|
dispatchPointerMove,
|
|
908
923
|
dispatchPointerUp,
|
package/dist/exports.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
|
28
28
|
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
29
29
|
export type { AnnotationCanvasHandle } from './annotation/canvas/useAnnotationCanvasState.js';
|
|
30
30
|
export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './annotation/canvas/AnnotationCanvasInner.js';
|
|
31
|
-
export type { CanvasPointerEvent, RequestTextInput, ShapeDrawConfig, Tool, ToolContext, ToolState, } from './annotation/canvas/Tool.js';
|
|
31
|
+
export type { CanvasPointerEvent, RequestTextInput, ShapeDrawConfig, Tool, ToolContext, SnapContext, ToolState, } from './annotation/canvas/Tool.js';
|
|
32
32
|
export type { FileRef, MeasurementRef, PickMeasurement, } from './annotation/canvas/measurementPicker.js';
|
|
33
33
|
export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './annotation/canvas/measurementStampOverlay.js';
|
|
34
34
|
export type { CanvasBackgroundSlotArgs, RenderCanvasBackground, } from './annotation/canvas/backgroundSlot.js';
|
|
@@ -52,3 +52,4 @@ export { annotationKindFor, hitShapeOutline, shapePointsFromDrag, type ShapeTool
|
|
|
52
52
|
export { DEFAULT_TEXT_FONT_SIZE, MIN_TEXT_FONT_SIZE, MAX_TEXT_FONT_SIZE, textShapeBounds, textResizeGeometry, type ResizeGeometry, type TextBounds, } from './annotation/canvas/textGeometry.js';
|
|
53
53
|
export { recomputeAnchor, projectToLinePos, snapLinePos, lerp, lineLength, placementOf, linePosOf, normalizeRect, rectCenter, rectCornerPoint, oppositeRectCorner, hitPlacedMeasurement, DEFAULT_LINE_POS, type NormalizedRect, type RectCorner, } from './annotation/canvas/measurementGeometry.js';
|
|
54
54
|
export { toSkiaStrokeCap, arrowheadTriangle, arrowheadLength, } from './annotation/canvas/strokeGeometry.js';
|
|
55
|
+
export { SNAP_RADIUS_PX, SNAP_RING_PX, SNAP_RING_WIDTH_PX, SNAP_KIND_POINT, SNAP_KIND_MIDPOINT, SNAP_STRIDE, collectSnapTargets, collectSnapTargetsFlat, encodeSnapTargets, snapNearest, snapDelta, snapPoint, type SnapHit, type SnapKind, type SnapTarget, } from './annotation/canvas/snapGeometry.js';
|
package/dist/exports.js
CHANGED
|
@@ -56,3 +56,4 @@ export { annotationKindFor, hitShapeOutline, shapePointsFromDrag, } from './anno
|
|
|
56
56
|
export { DEFAULT_TEXT_FONT_SIZE, MIN_TEXT_FONT_SIZE, MAX_TEXT_FONT_SIZE, textShapeBounds, textResizeGeometry, } from './annotation/canvas/textGeometry.js';
|
|
57
57
|
export { recomputeAnchor, projectToLinePos, snapLinePos, lerp, lineLength, placementOf, linePosOf, normalizeRect, rectCenter, rectCornerPoint, oppositeRectCorner, hitPlacedMeasurement, DEFAULT_LINE_POS, } from './annotation/canvas/measurementGeometry.js';
|
|
58
58
|
export { toSkiaStrokeCap, arrowheadTriangle, arrowheadLength, } from './annotation/canvas/strokeGeometry.js';
|
|
59
|
+
export { SNAP_RADIUS_PX, SNAP_RING_PX, SNAP_RING_WIDTH_PX, SNAP_KIND_POINT, SNAP_KIND_MIDPOINT, SNAP_STRIDE, collectSnapTargets, collectSnapTargetsFlat, encodeSnapTargets, snapNearest, snapDelta, snapPoint, } from './annotation/canvas/snapGeometry.js';
|
package/package.json
CHANGED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
|
-
import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.js';
|
|
3
|
-
export type { AnnotationCanvasHandle, GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
|
|
4
|
-
export interface CanvasKitOpts {
|
|
5
|
-
locateFile?: (file: string) => string;
|
|
6
|
-
}
|
|
7
|
-
export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
|
|
8
|
-
fallback?: ReactNode;
|
|
9
|
-
canvasKitOpts?: CanvasKitOpts;
|
|
10
|
-
};
|
|
11
|
-
export declare const AnnotationCanvas: ({ fallback, canvasKitOpts, ...rest }: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { WithSkiaWeb } from '@shopify/react-native-skia/lib/module/web/index.js';
|
|
3
|
-
// Web-only entry point: lazy-loads CanvasKit wasm via WithSkiaWeb. Imperative
|
|
4
|
-
// API (undo/redo/zoom) is exposed through the `imperativeRef` prop rather
|
|
5
|
-
// than React refs, since WithSkiaWeb can't forward refs through its lazy
|
|
6
|
-
// component boundary. The native build sits in `AnnotationCanvas.native.tsx`
|
|
7
|
-
// and skips WithSkiaWeb entirely (no wasm to load).
|
|
8
|
-
export const AnnotationCanvas = ({ fallback, canvasKitOpts, ...rest }) => (_jsx(WithSkiaWeb, { getComponent: () => import('./AnnotationCanvasInner.js').then((m) => ({
|
|
9
|
-
default: m.AnnotationCanvasInner,
|
|
10
|
-
})), fallback: fallback ?? null, componentProps: rest, opts: canvasKitOpts }));
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.native.js';
|
|
2
|
-
export type { AnnotationCanvasHandle, } from './useAnnotationCanvasState.js';
|
|
3
|
-
export type { GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
|
|
4
|
-
export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
|
|
5
|
-
fallback?: unknown;
|
|
6
|
-
canvasKitOpts?: unknown;
|
|
7
|
-
};
|
|
8
|
-
export declare const AnnotationCanvas: (props: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { AnnotationCanvasInner } from './AnnotationCanvasInner.native.js';
|
|
3
|
-
export const AnnotationCanvas = (props) => {
|
|
4
|
-
const { fallback: _f, canvasKitOpts: _o, ...rest } = props;
|
|
5
|
-
return _jsx(AnnotationCanvasInner, { ...rest });
|
|
6
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { type CSSProperties, type MutableRefObject } from 'react';
|
|
2
|
-
import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
|
|
3
|
-
import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
|
|
4
|
-
import type { MeasurementRef } from './measurementPicker.js';
|
|
5
|
-
import type { Tool } from './Tool.js';
|
|
6
|
-
import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
|
|
7
|
-
import { type ViewportState } from './viewport.js';
|
|
8
|
-
import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
|
|
9
|
-
export type { AnnotationCanvasHandle };
|
|
10
|
-
export interface AnnotationCanvasInnerProps {
|
|
11
|
-
canvas: AnnotationCanvasState;
|
|
12
|
-
onCommit(patch: AnnotationDocumentPatch): void;
|
|
13
|
-
tools: Tool[];
|
|
14
|
-
activeToolId: string;
|
|
15
|
-
selection: Selection | null;
|
|
16
|
-
onSelectionChange(selection: Selection | null): void;
|
|
17
|
-
measurements?: Measurement[];
|
|
18
|
-
fallbackUnit?: Units;
|
|
19
|
-
fractionalTolerance?: FractionalTolerance;
|
|
20
|
-
decimalTolerance?: DecimalTolerance;
|
|
21
|
-
resolveImageUrl?: (storagePath: string) => Promise<string>;
|
|
22
|
-
pickMeasurement?: () => Promise<MeasurementRef | null>;
|
|
23
|
-
renderMeasurementStamp?: RenderMeasurementStamp;
|
|
24
|
-
stampFontSource?: unknown;
|
|
25
|
-
stampValueFontSize?: number;
|
|
26
|
-
stampLabelFontSize?: number;
|
|
27
|
-
gestures?: GestureConfig;
|
|
28
|
-
width: number;
|
|
29
|
-
height: number;
|
|
30
|
-
initialViewport?: ViewportState;
|
|
31
|
-
style?: CSSProperties;
|
|
32
|
-
imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
|
|
33
|
-
}
|
|
34
|
-
export type PanTrigger = 'middleMouse' | 'rightMouse' | 'space';
|
|
35
|
-
export interface GestureConfig {
|
|
36
|
-
wheel?: 'zoom' | 'pan' | 'auto';
|
|
37
|
-
panTriggers?: PanTrigger[];
|
|
38
|
-
}
|
|
39
|
-
export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
|