@reekon-tools/boldr-utils 1.6.18 → 1.6.20
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.js +79 -12
- package/dist/annotation/canvas/AnnotationCanvasInner.native.js +100 -13
- package/dist/annotation/canvas/AnnotationCanvasSkia.d.ts +5 -1
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +53 -4
- package/dist/annotation/canvas/Tool.d.ts +7 -2
- package/dist/annotation/canvas/measurementGeometry.d.ts +1 -1
- package/dist/annotation/canvas/measurementGeometry.js +7 -5
- package/dist/annotation/canvas/stampLayout.d.ts +8 -2
- package/dist/annotation/canvas/stampLayout.js +72 -9
- package/dist/annotation/canvas/tools/measurementLineTool.d.ts +12 -0
- package/dist/annotation/canvas/tools/measurementLineTool.js +95 -0
- package/dist/annotation/canvas/tools/measurementTool.js +8 -2
- package/dist/annotation/canvas/tools/panTool.js +1 -1
- package/dist/annotation/canvas/tools/selectTool.js +116 -13
- package/dist/annotation/canvas/tools/textEditing.d.ts +4 -0
- package/dist/annotation/canvas/tools/textEditing.js +36 -0
- package/dist/annotation/canvas/tools/textTool.js +3 -26
- package/dist/annotation/canvas/useAnnotationCanvasState.d.ts +2 -0
- package/dist/annotation/canvas/useAnnotationCanvasState.js +18 -0
- package/dist/exports.d.ts +1 -0
- package/dist/exports.js +1 -0
- package/dist/types/annotation.d.ts +4 -0
- package/dist/types/annotation.js +6 -0
- package/dist/types/firestore.d.ts +53 -0
- package/dist/types/firestore.js +49 -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
|
@@ -11,17 +11,80 @@
|
|
|
11
11
|
export const STAMP_TILE_SIZE = 96;
|
|
12
12
|
// Edge length for an UNASSOCIATED stamp — a measurement annotation with no
|
|
13
13
|
// measurement picked yet, which renders as a compact "+" input placeholder
|
|
14
|
-
// rather than a full readable tile.
|
|
15
|
-
// inputs read as lightweight tap targets
|
|
16
|
-
//
|
|
17
|
-
|
|
14
|
+
// rather than a full readable tile. Kept noticeably smaller than
|
|
15
|
+
// STAMP_TILE_SIZE so empty inputs read as lightweight tap targets that don't
|
|
16
|
+
// crowd the drawing; associated tiles keep STAMP_TILE_SIZE so existing saved
|
|
17
|
+
// annotations are visually unchanged. Still comfortably tappable once the
|
|
18
|
+
// viewport/tile-scale multipliers (min 1) are folded in. The single knob for #6.
|
|
19
|
+
export const STAMP_INPUT_TILE_SIZE = 44;
|
|
20
|
+
// Document-wide tile scale factor (AnnotationCanvasState.tileScaleFactor): one
|
|
21
|
+
// knob that shrinks/grows EVERY measurement tile on the canvas at once, on top
|
|
22
|
+
// of each tile's own `scale`. Lets a user pull tiles down on a dense drawing
|
|
23
|
+
// where lines crowd together, or bump them up on a sparse one. Like `scale` it
|
|
24
|
+
// is purely a screen-space multiplier — it does NOT change with zoom. Absent ===
|
|
25
|
+
// DEFAULT_TILE_SCALE (visually identical to documents written before the knob
|
|
26
|
+
// existed).
|
|
27
|
+
export const DEFAULT_TILE_SCALE = 1;
|
|
28
|
+
export const TILE_SCALE_MIN = 0.4;
|
|
29
|
+
export const TILE_SCALE_MAX = 2;
|
|
30
|
+
// Clamp a tile-scale-factor candidate to the supported range. The single guard
|
|
31
|
+
// for the value before it lands in the document (slider input, restored docs).
|
|
32
|
+
export const clampTileScale = (v) => v < TILE_SCALE_MIN ? TILE_SCALE_MIN : v > TILE_SCALE_MAX ? TILE_SCALE_MAX : v;
|
|
33
|
+
// --- Viewport-relative tile sizing ------------------------------------------
|
|
34
|
+
// A bare-pixel tile is the same size on every device, but the SAME document is
|
|
35
|
+
// fit into wildly different canvases (a phone window vs a desktop pane), so the
|
|
36
|
+
// drawing renders much larger on desktop and a fixed-px tile reads as a tiny
|
|
37
|
+
// fraction of it. To keep a tile a CONSISTENT fraction of the drawing across
|
|
38
|
+
// platforms — while staying independent of the user's live zoom — the tile
|
|
39
|
+
// footprint is multiplied by `viewportTileScale`, which tracks how large the
|
|
40
|
+
// document renders when fit to the canvas (NOT the live zoom).
|
|
41
|
+
// Rendered document width (screen px) at which a tile uses its unscaled base
|
|
42
|
+
// size. Calibrated to a large phone's width so phone canvases land at scale 1
|
|
43
|
+
// (mobile visually unchanged); larger canvases scale up proportionally.
|
|
44
|
+
const TILE_VIEWPORT_REFERENCE_PX = 430;
|
|
45
|
+
// Clamp so phone-sized canvases never shrink tiles below base (lower = 1) and
|
|
46
|
+
// very large monitors don't produce runaway tiles (upper = 4).
|
|
47
|
+
const TILE_VIEWPORT_SCALE_MIN = 1;
|
|
48
|
+
const TILE_VIEWPORT_SCALE_MAX = 4;
|
|
49
|
+
// Screen-px width the document occupies when fit to the canvas:
|
|
50
|
+
// docW * fitZoom, fitZoom = min(canvasW/docW, canvasH/docH)
|
|
51
|
+
// = min(canvasW, docW * canvasH / docH)
|
|
52
|
+
// This — not the raw canvas size — governs the tile's fraction of the drawing,
|
|
53
|
+
// so the same document at the same canvas aspect yields the same value on web
|
|
54
|
+
// and native. Falls back to canvasW when doc dimensions are unknown.
|
|
55
|
+
export const renderedDocWidthAtFit = (canvasW, canvasH, docW, docH) => {
|
|
56
|
+
if (!(docW > 0) || !(docH > 0) || !(canvasW > 0) || !(canvasH > 0)) {
|
|
57
|
+
return canvasW > 0 ? canvasW : TILE_VIEWPORT_REFERENCE_PX;
|
|
58
|
+
}
|
|
59
|
+
return Math.min(canvasW, (docW * canvasH) / docH);
|
|
60
|
+
};
|
|
61
|
+
// Multiplier folded into the tile footprint so tiles are a consistent fraction
|
|
62
|
+
// of the rendered drawing on any canvas. Zoom-INDEPENDENT (a function of canvas
|
|
63
|
+
// + doc dimensions only), so the tile still never changes size as the user
|
|
64
|
+
// pinches/wheels. Defaults to 1 (callers without canvas dimensions are
|
|
65
|
+
// unaffected — e.g. legacy tests).
|
|
66
|
+
export const viewportTileScale = (canvasW, canvasH, docW, docH) => {
|
|
67
|
+
const s = renderedDocWidthAtFit(canvasW, canvasH, docW, docH) /
|
|
68
|
+
TILE_VIEWPORT_REFERENCE_PX;
|
|
69
|
+
return s < TILE_VIEWPORT_SCALE_MIN
|
|
70
|
+
? TILE_VIEWPORT_SCALE_MIN
|
|
71
|
+
: s > TILE_VIEWPORT_SCALE_MAX
|
|
72
|
+
? TILE_VIEWPORT_SCALE_MAX
|
|
73
|
+
: s;
|
|
74
|
+
};
|
|
18
75
|
// A placed measurement is an unassociated input until a measurement reference
|
|
19
76
|
// is attached (id or path). Such stamps use STAMP_INPUT_TILE_SIZE.
|
|
20
77
|
export const isUnassociatedStamp = (m) => !m.measurementId && !m.measurementPath;
|
|
21
78
|
// Screen-space edge length for a placed stamp: the compact input size while
|
|
22
79
|
// unassociated, full size once a measurement is attached, then scaled by the
|
|
23
|
-
// per-stamp `scale
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
80
|
+
// per-stamp `scale`, the document-wide `tileScaleFactor`, and the
|
|
81
|
+
// `viewportTileScale` (so tiles read at a consistent fraction of the drawing on
|
|
82
|
+
// any canvas). The ONE source of truth for tile footprint — render overlay,
|
|
83
|
+
// hit-test, and slide-grab classification all call this so the drawn tile and
|
|
84
|
+
// its touch box always agree. `tileScaleFactor` is the canvas-level knob
|
|
85
|
+
// (default 1, from `AnnotationCanvasState.tileScaleFactor`); `viewportScale`
|
|
86
|
+
// (default 1) is the per-canvas multiplier from `viewportTileScale`.
|
|
87
|
+
export const stampTileSize = (m, tileScaleFactor = DEFAULT_TILE_SCALE, viewportScale = 1) => (isUnassociatedStamp(m) ? STAMP_INPUT_TILE_SIZE : STAMP_TILE_SIZE) *
|
|
88
|
+
(m.scale ?? 1) *
|
|
89
|
+
tileScaleFactor *
|
|
90
|
+
viewportScale;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PlacedMeasurementRef } from '../../../types/annotation.js';
|
|
2
|
+
import type { Tool } from '../Tool.js';
|
|
3
|
+
export interface MeasurementLineToolOptions {
|
|
4
|
+
id?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
minDragPx?: number;
|
|
7
|
+
autoSwitchToSelect?: boolean;
|
|
8
|
+
selectToolId?: string;
|
|
9
|
+
onAutoSwitch?: (toToolId: string) => void;
|
|
10
|
+
onPlaced?: (measurement: PlacedMeasurementRef) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const createMeasurementLineTool: (options?: MeasurementLineToolOptions) => Tool;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { DEFAULT_LAYER_ID } from '../../../types/annotation.js';
|
|
2
|
+
import { DEFAULT_LINE_POS, recomputeAnchor } from '../measurementGeometry.js';
|
|
3
|
+
let counter = 0;
|
|
4
|
+
const makeId = () => `annotation-${Date.now().toString(36)}-${(counter++).toString(36)}`;
|
|
5
|
+
const firstLayerId = (doc) => doc.layers[0]?.id ?? DEFAULT_LAYER_ID;
|
|
6
|
+
// Build the blank line-measurement annotation for a drag from `a` to `b`: a
|
|
7
|
+
// 2-point line with a value tile magnetized to its center (linePos 0.5). The
|
|
8
|
+
// payload mirrors useAnnotationCanvasState's placeAnnotationAtCenter so a drawn
|
|
9
|
+
// line and a (legacy) center-placed one are byte-identical once committed.
|
|
10
|
+
const buildLineMeasurement = (opts) => {
|
|
11
|
+
const line = { a: opts.a, b: opts.b };
|
|
12
|
+
return {
|
|
13
|
+
id: opts.id,
|
|
14
|
+
layerId: opts.layerId,
|
|
15
|
+
placement: 'line',
|
|
16
|
+
line,
|
|
17
|
+
linePos: DEFAULT_LINE_POS,
|
|
18
|
+
// Center of the line; recomputeAnchor keeps this in sync on edits.
|
|
19
|
+
anchor: recomputeAnchor(line, 'line', DEFAULT_LINE_POS, {
|
|
20
|
+
x: (opts.a.x + opts.b.x) / 2,
|
|
21
|
+
y: (opts.a.y + opts.b.y) / 2,
|
|
22
|
+
}),
|
|
23
|
+
showLabel: true,
|
|
24
|
+
showValue: true,
|
|
25
|
+
createdAt: Date.now(),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
// Drag-to-draw measurement-line tool. Press-drag rubber-bands a measurement
|
|
29
|
+
// annotation (a line with a blank value tile at its center); release commits
|
|
30
|
+
// it. Mirrors createShapeTool's interaction so "input lines" are placed the
|
|
31
|
+
// same way as shapes — draw to add, not tap-the-icon-to-add — replacing the
|
|
32
|
+
// old center-place affordance. The annotation stays blank (no measurement
|
|
33
|
+
// associated) until the user fills it via the tile / picker. Skia-free, like
|
|
34
|
+
// every tool factory; it renders its live preview through ctx.preview, so it
|
|
35
|
+
// works on web and (via the generic tool-pan dispatch) on native.
|
|
36
|
+
export const createMeasurementLineTool = (options = {}) => {
|
|
37
|
+
const minDragPx = options.minDragPx ?? 4;
|
|
38
|
+
const autoSwitchToSelect = options.autoSwitchToSelect ?? true;
|
|
39
|
+
const selectToolId = options.selectToolId ?? 'select';
|
|
40
|
+
return {
|
|
41
|
+
id: options.id ?? 'measure-line',
|
|
42
|
+
label: options.label ?? 'Measurement line',
|
|
43
|
+
cursor: 'crosshair',
|
|
44
|
+
onPointerDown(event) {
|
|
45
|
+
return {
|
|
46
|
+
kind: 'measurement-line-drawing',
|
|
47
|
+
id: makeId(),
|
|
48
|
+
startWorld: event.world,
|
|
49
|
+
startScreen: event.screen,
|
|
50
|
+
moved: false,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
onPointerMove(event, ctx, state) {
|
|
54
|
+
const s = state;
|
|
55
|
+
if (s?.kind !== 'measurement-line-drawing')
|
|
56
|
+
return s;
|
|
57
|
+
const measurement = buildLineMeasurement({
|
|
58
|
+
id: s.id,
|
|
59
|
+
layerId: firstLayerId(ctx.document),
|
|
60
|
+
a: s.startWorld,
|
|
61
|
+
b: event.world,
|
|
62
|
+
});
|
|
63
|
+
ctx.preview({ ops: [{ op: 'addMeasurement', measurement }] });
|
|
64
|
+
return { ...s, moved: true };
|
|
65
|
+
},
|
|
66
|
+
onPointerUp(event, ctx, state) {
|
|
67
|
+
const s = state;
|
|
68
|
+
if (s?.kind !== 'measurement-line-drawing')
|
|
69
|
+
return;
|
|
70
|
+
const dx = event.screen.x - s.startScreen.x;
|
|
71
|
+
const dy = event.screen.y - s.startScreen.y;
|
|
72
|
+
if (!s.moved || dx * dx + dy * dy < minDragPx * minDragPx) {
|
|
73
|
+
// Accidental tap — discard the rubber-band, commit nothing.
|
|
74
|
+
ctx.preview({ ops: [] });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const measurement = buildLineMeasurement({
|
|
78
|
+
id: s.id,
|
|
79
|
+
layerId: firstLayerId(ctx.document),
|
|
80
|
+
a: s.startWorld,
|
|
81
|
+
b: event.world,
|
|
82
|
+
});
|
|
83
|
+
ctx.commit({ ops: [{ op: 'addMeasurement', measurement }] });
|
|
84
|
+
// Leave it selected (and hand back to select) so the blank line can be
|
|
85
|
+
// filled / moved straight away — the same flow as the text tool.
|
|
86
|
+
ctx.setSelection({ ids: [measurement.id] });
|
|
87
|
+
options.onPlaced?.(measurement);
|
|
88
|
+
if (autoSwitchToSelect)
|
|
89
|
+
options.onAutoSwitch?.(selectToolId);
|
|
90
|
+
},
|
|
91
|
+
onCancel(_state, ctx) {
|
|
92
|
+
ctx.preview({ ops: [] });
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -53,10 +53,16 @@ export const createMeasurementTool = (options = {}) => {
|
|
|
53
53
|
const selectToolId = options.selectToolId ?? 'select';
|
|
54
54
|
const place = (ctx, measurement) => {
|
|
55
55
|
ctx.commit({ ops: [{ op: 'addMeasurement', measurement }] });
|
|
56
|
-
ctx.setSelection({ ids: [measurement.id] });
|
|
57
56
|
options.onPlaced?.(measurement);
|
|
58
|
-
|
|
57
|
+
// Selecting the new annotation and handing back to select only makes sense
|
|
58
|
+
// when we actually switch tools. With autoSwitchToSelect off the tool stays
|
|
59
|
+
// active and behaves like the shape tools — commit and keep drawing, with
|
|
60
|
+
// nothing selected — so placing an empty input doesn't kick you out of
|
|
61
|
+
// drawing mode (and the keypad/pill doesn't pop for the blank tile).
|
|
62
|
+
if (autoSwitchToSelect) {
|
|
63
|
+
ctx.setSelection({ ids: [measurement.id] });
|
|
59
64
|
options.onAutoSwitch?.(selectToolId);
|
|
65
|
+
}
|
|
60
66
|
};
|
|
61
67
|
// Bare stamp: tap-to-place, no rubber-band.
|
|
62
68
|
if (placement === 'none') {
|
|
@@ -51,7 +51,7 @@ export const createPanTool = (options = {}) => ({
|
|
|
51
51
|
const measurements = ctx.document.placedMeasurements;
|
|
52
52
|
for (let i = measurements.length - 1; i >= 0; i--) {
|
|
53
53
|
const m = measurements[i];
|
|
54
|
-
if (hitPlacedMeasurement(m, event.world, zoom)) {
|
|
54
|
+
if (hitPlacedMeasurement(m, event.world, zoom, ctx.document.tileScaleFactor, ctx.tileViewportScale)) {
|
|
55
55
|
ctx.setSelection({ ids: [m.id] });
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
@@ -2,6 +2,7 @@ import { stampTileSize } from '../stampLayout.js';
|
|
|
2
2
|
import { placementOf, linePosOf, snapLinePos, lerp, recomputeAnchor, rectCenter, rectCornerPoint, oppositeRectCorner, hitPlacedMeasurement, } from '../measurementGeometry.js';
|
|
3
3
|
import { hitShapeOutline } from '../shapeGeometry.js';
|
|
4
4
|
import { DEFAULT_TEXT_FONT_SIZE, resizeScaleFromDrag, textResizeGeometry, textShapeBounds, } from '../textGeometry.js';
|
|
5
|
+
import { editTextShape, findTextShapeAt } from './textEditing.js';
|
|
5
6
|
const HIT_PADDING = 6;
|
|
6
7
|
// Hit-test in doc-space. Crude but fast — good enough for v1; tools can
|
|
7
8
|
// override via `hitTest` for more precision later.
|
|
@@ -16,8 +17,14 @@ const hitStroke = (stroke, p) => {
|
|
|
16
17
|
return false;
|
|
17
18
|
};
|
|
18
19
|
// Screen-space grab tolerance (px) added around a geometric shape's outline
|
|
19
|
-
// (line/arrow/rect/ellipse/polygon), converted to doc space via zoom.
|
|
20
|
-
|
|
20
|
+
// (line/arrow/rect/ellipse/polygon), converted to doc space via zoom. A thin
|
|
21
|
+
// line gives almost nothing to aim at, so the grab corridor is deliberately
|
|
22
|
+
// wider than a fingertip — landing anywhere near the ink grabs it. Kept in sync
|
|
23
|
+
// with measurementGeometry's LINE_GRAB_PX so tap-select and drag-grab agree on
|
|
24
|
+
// what counts as "on the line". (The endpoint-resize handles use their own,
|
|
25
|
+
// tighter HANDLE_GRAB_PX and are checked first on the selected element, so a
|
|
26
|
+
// wider body grab never swallows them.)
|
|
27
|
+
const SHAPE_GRAB_PX = 32;
|
|
21
28
|
// Screen-px radius of the center snap detent when sliding a tile along its line.
|
|
22
29
|
// Converted to t-space per line via (SNAP_PX / zoom) / lineLength. The native
|
|
23
30
|
// slide worklet inlines the same value — keep them in sync.
|
|
@@ -37,11 +44,11 @@ const segmentDistanceSq = (px, py, ax, ay, bx, by) => {
|
|
|
37
44
|
const dy = py - cy;
|
|
38
45
|
return dx * dx + dy * dy;
|
|
39
46
|
};
|
|
40
|
-
const findHit = (doc, world, zoom) => {
|
|
47
|
+
const findHit = (doc, world, zoom, viewportTileScale = 1) => {
|
|
41
48
|
// Hit-test in z-order (top first): measurements > shapes > strokes.
|
|
42
49
|
for (let i = doc.placedMeasurements.length - 1; i >= 0; i--) {
|
|
43
50
|
const m = doc.placedMeasurements[i];
|
|
44
|
-
if (hitPlacedMeasurement(m, world, zoom)) {
|
|
51
|
+
if (hitPlacedMeasurement(m, world, zoom, doc.tileScaleFactor, viewportTileScale)) {
|
|
45
52
|
return { id: m.id, kind: 'measurement' };
|
|
46
53
|
}
|
|
47
54
|
}
|
|
@@ -124,16 +131,37 @@ const translatePatch = (elementKind, id, doc, delta) => {
|
|
|
124
131
|
}
|
|
125
132
|
return { op: 'updateStroke', id, patch: { points } };
|
|
126
133
|
};
|
|
134
|
+
// Whether a world point lands on a measurement's tile (its screen-constant
|
|
135
|
+
// anchor box, converted back to doc space via zoom — the same footprint
|
|
136
|
+
// classifyGrab uses). The tile is the move/slide affordance, so a grab here
|
|
137
|
+
// must win over the endpoint/corner/resize handles that appear once an element
|
|
138
|
+
// is selected; without this, a handle sitting under the tile (a rectangle
|
|
139
|
+
// tile at the rect center, a line tile slid onto an endpoint) hijacks the grab
|
|
140
|
+
// and the tile becomes unmovable until deselected.
|
|
141
|
+
const isOnMeasurementTile = (doc, id, world, zoom, viewportTileScale = 1) => {
|
|
142
|
+
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
143
|
+
if (!m)
|
|
144
|
+
return false;
|
|
145
|
+
const half = (stampTileSize(m, doc.tileScaleFactor, viewportTileScale) / 2 +
|
|
146
|
+
HIT_PADDING) /
|
|
147
|
+
zoom;
|
|
148
|
+
return (Math.abs(world.x - m.anchor.x) <= half &&
|
|
149
|
+
Math.abs(world.y - m.anchor.y) <= half);
|
|
150
|
+
};
|
|
127
151
|
// --- Measurement-annotation grab logic (shared by the native UI-thread drag
|
|
128
152
|
// via DragSelectionConfig AND the web pointer handlers — one source of truth) ---
|
|
129
153
|
// Grabbing the tile of a line annotation slides it along the line; everything
|
|
130
154
|
// else (bare stamps, grabs on the line body) is a group move.
|
|
131
|
-
const classifyGrab = (doc, id, world, zoom) => {
|
|
155
|
+
const classifyGrab = (doc, id, world, zoom, viewportTileScale = 1) => {
|
|
132
156
|
const m = doc.placedMeasurements.find((x) => x.id === id);
|
|
133
157
|
if (!m)
|
|
134
158
|
return 'move';
|
|
135
|
-
// Same footprint the tile is drawn at (smaller for unassociated inputs, #6
|
|
136
|
-
|
|
159
|
+
// Same footprint the tile is drawn at (smaller for unassociated inputs, #6;
|
|
160
|
+
// folds in the document-wide tile scale + viewport scale so slide-grab
|
|
161
|
+
// matches the draw).
|
|
162
|
+
const half = (stampTileSize(m, doc.tileScaleFactor, viewportTileScale) / 2 +
|
|
163
|
+
HIT_PADDING) /
|
|
164
|
+
zoom;
|
|
137
165
|
const onTile = Math.abs(world.x - m.anchor.x) <= half &&
|
|
138
166
|
Math.abs(world.y - m.anchor.y) <= half;
|
|
139
167
|
return onTile && placementOf(m) === 'line' && m.line ? 'slide' : 'move';
|
|
@@ -190,6 +218,48 @@ const endpointPatch = (doc, id, handle, delta) => {
|
|
|
190
218
|
const anchor = recomputeAnchor(line, 'line', linePosOf(m), m.anchor);
|
|
191
219
|
return { ops: [{ op: 'updateMeasurement', id, patch: { line, anchor } }] };
|
|
192
220
|
};
|
|
221
|
+
// Which endpoint handle of a (selected) line/arrow SHAPE is under `world`.
|
|
222
|
+
// Shape lines store their endpoints as geometry.points[0]/[1] (unlike
|
|
223
|
+
// measurement lines, which carry them on `line.a/b` alongside a tile). Prefers
|
|
224
|
+
// the nearer endpoint when both are within range.
|
|
225
|
+
const findShapeHandleHit = (doc, id, world, zoom) => {
|
|
226
|
+
const s = doc.shapes.find((x) => x.id === id);
|
|
227
|
+
if (!s || (s.kind !== 'line' && s.kind !== 'arrow'))
|
|
228
|
+
return null;
|
|
229
|
+
const [a, b] = s.geometry.points;
|
|
230
|
+
if (!a || !b)
|
|
231
|
+
return null;
|
|
232
|
+
const r2 = (HANDLE_GRAB_PX / zoom) ** 2;
|
|
233
|
+
const da = (world.x - a.x) ** 2 + (world.y - a.y) ** 2;
|
|
234
|
+
const db = (world.x - b.x) ** 2 + (world.y - b.y) ** 2;
|
|
235
|
+
if (da <= r2 && da <= db)
|
|
236
|
+
return 'a';
|
|
237
|
+
if (db <= r2)
|
|
238
|
+
return 'b';
|
|
239
|
+
return null;
|
|
240
|
+
};
|
|
241
|
+
// Move one endpoint of a line/arrow shape by a world delta (resize/rotate).
|
|
242
|
+
// Rewrites the whole geometry (updateShape merges shallowly at the top level,
|
|
243
|
+
// so `closed` and any other geometry fields must be carried through).
|
|
244
|
+
const shapeEndpointPatch = (doc, id, handle, delta) => {
|
|
245
|
+
const s = doc.shapes.find((x) => x.id === id);
|
|
246
|
+
if (!s || (s.kind !== 'line' && s.kind !== 'arrow'))
|
|
247
|
+
return null;
|
|
248
|
+
const [a, b] = s.geometry.points;
|
|
249
|
+
if (!a || !b)
|
|
250
|
+
return null;
|
|
251
|
+
const na = handle === 'a' ? { x: a.x + delta.x, y: a.y + delta.y } : a;
|
|
252
|
+
const nb = handle === 'b' ? { x: b.x + delta.x, y: b.y + delta.y } : b;
|
|
253
|
+
return {
|
|
254
|
+
ops: [
|
|
255
|
+
{
|
|
256
|
+
op: 'updateShape',
|
|
257
|
+
id,
|
|
258
|
+
patch: { geometry: { ...s.geometry, points: [na, nb] } },
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
};
|
|
193
263
|
// Which corner handle of a (selected) rectangle annotation is under `world`.
|
|
194
264
|
// Prefers the nearest corner when several are within range (small rects).
|
|
195
265
|
const findRectCornerHit = (doc, id, world, zoom) => {
|
|
@@ -272,7 +342,11 @@ const resizePatch = (doc, id, delta) => {
|
|
|
272
342
|
// Patch for the current drag mode (web pointer path), from a world-space delta.
|
|
273
343
|
const dragPatch = (s, doc, delta, zoom) => {
|
|
274
344
|
if (s.mode === 'endpoint' && s.handle) {
|
|
275
|
-
|
|
345
|
+
// Shape lines and measurement lines store endpoints differently, so the
|
|
346
|
+
// patch builder is keyed on which kind is being dragged.
|
|
347
|
+
return s.elementKind === 'shape'
|
|
348
|
+
? shapeEndpointPatch(doc, s.id, s.handle, delta)
|
|
349
|
+
: endpointPatch(doc, s.id, s.handle, delta);
|
|
276
350
|
}
|
|
277
351
|
if (s.mode === 'slide')
|
|
278
352
|
return slidePatch(doc, s.id, delta, zoom);
|
|
@@ -292,15 +366,18 @@ export const createSelectTool = () => ({
|
|
|
292
366
|
// reusing the same hit-test and translate logic the pointer handlers below
|
|
293
367
|
// use for web — one source of truth.
|
|
294
368
|
dragSelection: {
|
|
295
|
-
hitTest: (doc, world, zoom) => findHit(doc, world, zoom),
|
|
369
|
+
hitTest: (doc, world, zoom, viewportTileScale) => findHit(doc, world, zoom, viewportTileScale),
|
|
296
370
|
buildTranslatePatch: (doc, id, kind, delta) => {
|
|
297
371
|
const op = translatePatch(kind, id, doc, delta);
|
|
298
372
|
return op ? { ops: [op] } : null;
|
|
299
373
|
},
|
|
300
374
|
classifyMeasurementGrab: classifyGrab,
|
|
375
|
+
isSelectedTileGrab: isOnMeasurementTile,
|
|
301
376
|
buildSlidePatch: slidePatch,
|
|
302
377
|
hitTestHandle: findHandleHit,
|
|
303
378
|
buildEndpointPatch: endpointPatch,
|
|
379
|
+
hitTestShapeHandle: findShapeHandleHit,
|
|
380
|
+
buildShapeEndpointPatch: shapeEndpointPatch,
|
|
304
381
|
hitTestResizeHandle: findResizeHandleHit,
|
|
305
382
|
buildResizePatch: resizePatch,
|
|
306
383
|
hitTestRectCorner: findRectCornerHit,
|
|
@@ -312,9 +389,13 @@ export const createSelectTool = () => ({
|
|
|
312
389
|
onPointerDown(event, ctx) {
|
|
313
390
|
const { world } = event;
|
|
314
391
|
const zoom = ctx.viewport.state.zoom;
|
|
315
|
-
// Endpoint/resize handles show only on the selected element — check first
|
|
392
|
+
// Endpoint/resize handles show only on the selected element — check first,
|
|
393
|
+
// UNLESS the grab is on that element's tile: the tile is the move/slide
|
|
394
|
+
// affordance and must win over a handle sitting under it, so a selected
|
|
395
|
+
// tile stays draggable (otherwise it can only be moved after deselecting).
|
|
316
396
|
const selId = ctx.selection?.ids[0];
|
|
317
|
-
if (selId
|
|
397
|
+
if (selId &&
|
|
398
|
+
!isOnMeasurementTile(ctx.document, selId, world, zoom, ctx.tileViewportScale)) {
|
|
318
399
|
const handle = findHandleHit(ctx.document, selId, world, zoom);
|
|
319
400
|
if (handle) {
|
|
320
401
|
ctx.setSelection({ ids: [selId] });
|
|
@@ -328,6 +409,19 @@ export const createSelectTool = () => ({
|
|
|
328
409
|
delta: { x: 0, y: 0 },
|
|
329
410
|
};
|
|
330
411
|
}
|
|
412
|
+
const shapeHandle = findShapeHandleHit(ctx.document, selId, world, zoom);
|
|
413
|
+
if (shapeHandle) {
|
|
414
|
+
ctx.setSelection({ ids: [selId] });
|
|
415
|
+
return {
|
|
416
|
+
kind: 'dragging',
|
|
417
|
+
id: selId,
|
|
418
|
+
elementKind: 'shape',
|
|
419
|
+
mode: 'endpoint',
|
|
420
|
+
handle: shapeHandle,
|
|
421
|
+
start: world,
|
|
422
|
+
delta: { x: 0, y: 0 },
|
|
423
|
+
};
|
|
424
|
+
}
|
|
331
425
|
if (findResizeHandleHit(ctx.document, selId, world, zoom)) {
|
|
332
426
|
return {
|
|
333
427
|
kind: 'dragging',
|
|
@@ -352,14 +446,15 @@ export const createSelectTool = () => ({
|
|
|
352
446
|
};
|
|
353
447
|
}
|
|
354
448
|
}
|
|
355
|
-
const hit = findHit(ctx.document, world, zoom);
|
|
449
|
+
const hit = findHit(ctx.document, world, zoom, ctx.tileViewportScale);
|
|
356
450
|
if (!hit) {
|
|
357
451
|
ctx.setSelection(null);
|
|
358
452
|
return { kind: 'idle' };
|
|
359
453
|
}
|
|
360
454
|
ctx.setSelection({ ids: [hit.id] });
|
|
361
455
|
const mode = hit.kind === 'measurement' &&
|
|
362
|
-
classifyGrab(ctx.document, hit.id, world, zoom) ===
|
|
456
|
+
classifyGrab(ctx.document, hit.id, world, zoom, ctx.tileViewportScale) ===
|
|
457
|
+
'slide'
|
|
363
458
|
? 'slide'
|
|
364
459
|
: 'move';
|
|
365
460
|
return {
|
|
@@ -397,6 +492,14 @@ export const createSelectTool = () => ({
|
|
|
397
492
|
onCancel(_state, ctx) {
|
|
398
493
|
ctx.preview({ ops: [] });
|
|
399
494
|
},
|
|
495
|
+
// Long-pressing a placed text shape re-opens the editor (the same edit flow
|
|
496
|
+
// as tapping it with the text tool, via the shared editTextShape). A hold on
|
|
497
|
+
// any other element — or empty canvas — is ignored.
|
|
498
|
+
onLongPress(event, ctx) {
|
|
499
|
+
const shape = findTextShapeAt(ctx.document, event.world);
|
|
500
|
+
if (shape)
|
|
501
|
+
editTextShape(ctx, shape);
|
|
502
|
+
},
|
|
400
503
|
hitTest(element, p) {
|
|
401
504
|
if (element.kind === 'measurement')
|
|
402
505
|
return hitPlacedMeasurement(element, p);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnnotationCanvasState, AnnotationShape, Vec2 } from '../../../types/annotation.js';
|
|
2
|
+
import type { ToolContext } from '../Tool.js';
|
|
3
|
+
export declare const findTextShapeAt: (doc: AnnotationCanvasState, world: Vec2) => AnnotationShape | null;
|
|
4
|
+
export declare const editTextShape: (ctx: ToolContext, shape: AnnotationShape, onDone?: () => void) => void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { hitTestTextShape } from '../textGeometry.js';
|
|
2
|
+
// Topmost text shape under a world point (z-order, top first), or null. Shared
|
|
3
|
+
// by the text tool (tap-to-edit) and the select tool (long-press-to-edit) so a
|
|
4
|
+
// press resolves to the same element from either tool.
|
|
5
|
+
export const findTextShapeAt = (doc, world) => {
|
|
6
|
+
for (let i = doc.shapes.length - 1; i >= 0; i--) {
|
|
7
|
+
const s = doc.shapes[i];
|
|
8
|
+
if (s.kind === 'text' && hitTestTextShape(s, world))
|
|
9
|
+
return s;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
13
|
+
// Re-open the consumer's text input pre-filled with an existing text shape's
|
|
14
|
+
// content and commit the edit: cancelling (null) leaves it untouched, clearing
|
|
15
|
+
// the text deletes the shape, and changed text updates it. The shape is left
|
|
16
|
+
// selected (cleared on delete). `onDone` runs only after a non-cancel,
|
|
17
|
+
// non-delete resolution — the text tool uses it to switch back to select.
|
|
18
|
+
// One source of truth for editing placed text from either tool.
|
|
19
|
+
export const editTextShape = (ctx, shape, onDone) => {
|
|
20
|
+
void ctx.requestTextInput({ initialText: shape.text }).then((text) => {
|
|
21
|
+
if (text === null)
|
|
22
|
+
return;
|
|
23
|
+
if (text === '') {
|
|
24
|
+
ctx.commit({ ops: [{ op: 'removeShape', id: shape.id }] });
|
|
25
|
+
ctx.setSelection(null);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (text !== shape.text) {
|
|
29
|
+
ctx.commit({
|
|
30
|
+
ops: [{ op: 'updateShape', id: shape.id, patch: { text } }],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
ctx.setSelection({ ids: [shape.id] });
|
|
34
|
+
onDone?.();
|
|
35
|
+
});
|
|
36
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_LAYER_ID } from '../../../types/annotation.js';
|
|
2
|
-
import { DEFAULT_TEXT_FONT_SIZE
|
|
2
|
+
import { DEFAULT_TEXT_FONT_SIZE } from '../textGeometry.js';
|
|
3
|
+
import { editTextShape, findTextShapeAt } from './textEditing.js';
|
|
3
4
|
let counter = 0;
|
|
4
5
|
const makeId = () => `text-${Date.now().toString(36)}-${(counter++).toString(36)}`;
|
|
5
6
|
// Screen-px a press may travel and still count as a tap. Beyond this the
|
|
@@ -7,15 +8,6 @@ const makeId = () => `text-${Date.now().toString(36)}-${(counter++).toString(36)
|
|
|
7
8
|
// NOT open the text sheet — the cause of the stray "weird popup" on screen.
|
|
8
9
|
const TAP_SLOP_PX = 10;
|
|
9
10
|
const firstLayerId = (doc) => doc.layers[0]?.id ?? DEFAULT_LAYER_ID;
|
|
10
|
-
// Topmost text shape under a world point, for tap-to-edit.
|
|
11
|
-
const findTextShapeAt = (doc, world) => {
|
|
12
|
-
for (let i = doc.shapes.length - 1; i >= 0; i--) {
|
|
13
|
-
const s = doc.shapes[i];
|
|
14
|
-
if (s.kind === 'text' && hitTestTextShape(s, world))
|
|
15
|
-
return s;
|
|
16
|
-
}
|
|
17
|
-
return null;
|
|
18
|
-
};
|
|
19
11
|
// Tap-to-type. Tapping empty canvas opens the consumer's text input and
|
|
20
12
|
// commits a new text shape at the tap point (top-left anchored); tapping an
|
|
21
13
|
// existing text shape re-opens the input pre-filled to edit it (clearing the
|
|
@@ -51,22 +43,7 @@ export const createTextTool = (options = {}) => {
|
|
|
51
43
|
}
|
|
52
44
|
const existing = findTextShapeAt(ctx.document, event.world);
|
|
53
45
|
if (existing) {
|
|
54
|
-
|
|
55
|
-
.requestTextInput({ initialText: existing.text })
|
|
56
|
-
.then((text) => {
|
|
57
|
-
if (text === null)
|
|
58
|
-
return;
|
|
59
|
-
if (text === '') {
|
|
60
|
-
ctx.commit({ ops: [{ op: 'removeShape', id: existing.id }] });
|
|
61
|
-
ctx.setSelection(null);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (text !== existing.text) {
|
|
65
|
-
ctx.commit({
|
|
66
|
-
ops: [{ op: 'updateShape', id: existing.id, patch: { text } }],
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
ctx.setSelection({ ids: [existing.id] });
|
|
46
|
+
editTextShape(ctx, existing, () => {
|
|
70
47
|
if (autoSwitchToSelect)
|
|
71
48
|
options.onAutoSwitch?.(selectToolId);
|
|
72
49
|
});
|
|
@@ -49,12 +49,14 @@ export interface AnnotationCanvasStateApi {
|
|
|
49
49
|
activeTool: Tool | null;
|
|
50
50
|
toolState: ToolState;
|
|
51
51
|
ctx: ToolContext;
|
|
52
|
+
tileViewportScale: number;
|
|
52
53
|
penDrawingStroke: AnnotationStroke | null;
|
|
53
54
|
customPreviewState: ToolState;
|
|
54
55
|
dispatchPointerDown(event: CanvasPointerEvent): void;
|
|
55
56
|
dispatchPointerMove(event: CanvasPointerEvent): void;
|
|
56
57
|
dispatchPointerUp(event: CanvasPointerEvent): void;
|
|
57
58
|
dispatchPointerCancel(): void;
|
|
59
|
+
dispatchLongPress(event: CanvasPointerEvent): void;
|
|
58
60
|
pan(deltaScreen: Vec2): void;
|
|
59
61
|
zoom(focalScreen: Vec2, nextZoom: number): void;
|
|
60
62
|
setViewport(next: ViewportState): void;
|
|
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { applyPatch, invertPatch, DEFAULT_LAYER_ID, } from '../../types/annotation.js';
|
|
3
3
|
import { createViewportApi, panBy, zoomAt, DEFAULT_VIEWPORT, } from './viewport.js';
|
|
4
4
|
import { recomputeAnchor, rectCenter, DEFAULT_LINE_POS, } from './measurementGeometry.js';
|
|
5
|
+
import { viewportTileScale } from './stampLayout.js';
|
|
5
6
|
// Platform-agnostic state machine for the annotation canvas. Web and native
|
|
6
7
|
// inners share this hook; each wraps it with platform-specific event
|
|
7
8
|
// capture and JSX (div + DOM events vs. GestureDetector + RN Views).
|
|
@@ -21,10 +22,16 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
21
22
|
return map;
|
|
22
23
|
}, [measurements]);
|
|
23
24
|
const viewportApi = useMemo(() => createViewportApi(viewport), [viewport]);
|
|
25
|
+
// How large the document renders when fit to this canvas, as a tile-footprint
|
|
26
|
+
// multiplier (zoom-independent). Keeps tiles a consistent fraction of the
|
|
27
|
+
// drawing on a phone vs a desktop pane. Used by the overlays (drawn size) and
|
|
28
|
+
// the tools (hit box) so both agree.
|
|
29
|
+
const tileViewportScale = useMemo(() => viewportTileScale(width, height, canvas.viewport.width, canvas.viewport.height), [width, height, canvas.viewport.width, canvas.viewport.height]);
|
|
24
30
|
const ctx = useMemo(() => ({
|
|
25
31
|
document: canvas,
|
|
26
32
|
selection,
|
|
27
33
|
viewport: viewportApi,
|
|
34
|
+
tileViewportScale,
|
|
28
35
|
preview(patch) {
|
|
29
36
|
setPreviewPatch(patch);
|
|
30
37
|
},
|
|
@@ -56,6 +63,7 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
56
63
|
canvas,
|
|
57
64
|
selection,
|
|
58
65
|
viewportApi,
|
|
66
|
+
tileViewportScale,
|
|
59
67
|
onCommit,
|
|
60
68
|
onSelectionChange,
|
|
61
69
|
pickMeasurement,
|
|
@@ -113,6 +121,14 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
113
121
|
activePointerIdRef.current = null;
|
|
114
122
|
setToolState(undefined);
|
|
115
123
|
}, [activeTool, ctx, toolState]);
|
|
124
|
+
const dispatchLongPress = useCallback((event) => {
|
|
125
|
+
if (!activeTool)
|
|
126
|
+
return;
|
|
127
|
+
// Fire-and-forget: onLongPress is a discrete action (it opens the text
|
|
128
|
+
// editor), so it neither reads nor writes the gesture's tool state — the
|
|
129
|
+
// in-flight drag/select state on web stays intact underneath it.
|
|
130
|
+
activeTool.onLongPress?.(event, ctx);
|
|
131
|
+
}, [activeTool, ctx]);
|
|
116
132
|
const dispatchPointerCancel = useCallback(() => {
|
|
117
133
|
// Clear FIRST, then let the tool react: state updates batch, so a tool
|
|
118
134
|
// whose onCancel re-emits a preview (the polygon tool keeps its placed
|
|
@@ -361,12 +377,14 @@ export const useAnnotationCanvasState = (props) => {
|
|
|
361
377
|
activeTool,
|
|
362
378
|
toolState,
|
|
363
379
|
ctx,
|
|
380
|
+
tileViewportScale,
|
|
364
381
|
penDrawingStroke,
|
|
365
382
|
customPreviewState: toolState,
|
|
366
383
|
dispatchPointerDown,
|
|
367
384
|
dispatchPointerMove,
|
|
368
385
|
dispatchPointerUp,
|
|
369
386
|
dispatchPointerCancel,
|
|
387
|
+
dispatchLongPress,
|
|
370
388
|
pan,
|
|
371
389
|
zoom,
|
|
372
390
|
setViewport,
|
package/dist/exports.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type { GestureConfig, PanTrigger, AnnotationCanvasInnerProps, } from './a
|
|
|
21
21
|
export type { CanvasPointerEvent, RequestTextInput, ShapeDrawConfig, Tool, ToolContext, ToolState, } from './annotation/canvas/Tool.js';
|
|
22
22
|
export type { MeasurementRef, PickMeasurement, } from './annotation/canvas/measurementPicker.js';
|
|
23
23
|
export type { MeasurementStampRenderArgs, RenderMeasurementStamp, } from './annotation/canvas/measurementStampOverlay.js';
|
|
24
|
+
export { STAMP_TILE_SIZE, STAMP_INPUT_TILE_SIZE, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, stampTileSize, isUnassociatedStamp, } from './annotation/canvas/stampLayout.js';
|
|
24
25
|
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, type ViewportApi, type ViewportState, } from './annotation/canvas/viewport.js';
|
|
25
26
|
export { createPenTool, type PenToolOptions, } from './annotation/canvas/tools/penTool.js';
|
|
26
27
|
export { createSelectTool } from './annotation/canvas/tools/selectTool.js';
|
package/dist/exports.js
CHANGED
|
@@ -21,6 +21,7 @@ export { useAnnotationMutations, } from './annotation/data/hooks/useAnnotationMu
|
|
|
21
21
|
export { useAnnotationCanvasDoc, } from './annotation/data/hooks/useAnnotationCanvasDoc.js';
|
|
22
22
|
export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
|
|
23
23
|
export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
|
|
24
|
+
export { STAMP_TILE_SIZE, STAMP_INPUT_TILE_SIZE, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, stampTileSize, isUnassociatedStamp, } from './annotation/canvas/stampLayout.js';
|
|
24
25
|
export { createViewportApi, fitToScreen, panBy, zoomAt, DEFAULT_VIEWPORT, } from './annotation/canvas/viewport.js';
|
|
25
26
|
export { createPenTool, } from './annotation/canvas/tools/penTool.js';
|
|
26
27
|
export { createSelectTool } from './annotation/canvas/tools/selectTool.js';
|