@fieldnotes/core 0.63.0 → 0.64.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/index.cjs +1037 -329
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +347 -17
- package/dist/index.d.ts +347 -17
- package/dist/index.js +1026 -329
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29,6 +29,310 @@ function smartSnap(point, ctx) {
|
|
|
29
29
|
}
|
|
30
30
|
return snapPoint(point, ctx.gridSize);
|
|
31
31
|
}
|
|
32
|
+
function footprintOf(footprint) {
|
|
33
|
+
return typeof footprint === "number" ? { w: footprint, h: footprint } : footprint;
|
|
34
|
+
}
|
|
35
|
+
function footprintFromSize(size, gridSize) {
|
|
36
|
+
if (!(gridSize > 0)) return 1;
|
|
37
|
+
return {
|
|
38
|
+
w: Math.max(1, Math.round(size.w / gridSize)),
|
|
39
|
+
h: Math.max(1, Math.round(size.h / gridSize))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function snapAxisToCell(value, gridSize, cells) {
|
|
43
|
+
const n2 = Math.max(1, Math.round(cells));
|
|
44
|
+
if (n2 % 2 === 0) return Math.round(value / gridSize) * gridSize || 0;
|
|
45
|
+
return (Math.round((value - gridSize / 2) / gridSize) + 0.5) * gridSize || 0;
|
|
46
|
+
}
|
|
47
|
+
function snapToCellCenter(point, gridSize, footprint = 1) {
|
|
48
|
+
const { w, h } = footprintOf(footprint);
|
|
49
|
+
return { x: snapAxisToCell(point.x, gridSize, w), y: snapAxisToCell(point.y, gridSize, h) };
|
|
50
|
+
}
|
|
51
|
+
function snapFootprintCenter(point, footprint, ctx) {
|
|
52
|
+
if (!ctx.snapToGrid || !ctx.gridSize) return point;
|
|
53
|
+
if (ctx.gridType === "hex" && ctx.hexOrientation) {
|
|
54
|
+
return snapToHexCenter(point, ctx.gridSize, ctx.hexOrientation);
|
|
55
|
+
}
|
|
56
|
+
return snapToCellCenter(point, ctx.gridSize, footprint);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/elements/hex-fill.ts
|
|
60
|
+
function offsetToCube(col, row, orientation) {
|
|
61
|
+
if (orientation === "pointy") {
|
|
62
|
+
return { q: col - (row - (row & 1)) / 2, r: row };
|
|
63
|
+
}
|
|
64
|
+
return { q: col, r: row - (col - (col & 1)) / 2 };
|
|
65
|
+
}
|
|
66
|
+
function cubeToOffset(q, r, orientation) {
|
|
67
|
+
if (orientation === "pointy") {
|
|
68
|
+
return { col: q + (r - (r & 1)) / 2, row: r };
|
|
69
|
+
}
|
|
70
|
+
return { col: q, row: r + (q - (q & 1)) / 2 };
|
|
71
|
+
}
|
|
72
|
+
function offsetToPixel(col, row, cellSize, orientation) {
|
|
73
|
+
if (orientation === "pointy") {
|
|
74
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
75
|
+
const rowH = 1.5 * cellSize;
|
|
76
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
77
|
+
return { x: col * hexW + offsetX, y: row * rowH };
|
|
78
|
+
}
|
|
79
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
80
|
+
const colW = 1.5 * cellSize;
|
|
81
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
82
|
+
return { x: col * colW, y: row * hexH + offsetY };
|
|
83
|
+
}
|
|
84
|
+
function pixelToOffset(x, y, cellSize, orientation) {
|
|
85
|
+
if (orientation === "pointy") {
|
|
86
|
+
const hexW = Math.sqrt(3) * cellSize;
|
|
87
|
+
const rowH = 1.5 * cellSize;
|
|
88
|
+
const row = Math.round(y / rowH);
|
|
89
|
+
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
90
|
+
return { col: Math.round((x - offsetX) / hexW), row };
|
|
91
|
+
}
|
|
92
|
+
const hexH = Math.sqrt(3) * cellSize;
|
|
93
|
+
const colW = 1.5 * cellSize;
|
|
94
|
+
const col = Math.round(x / colW);
|
|
95
|
+
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
96
|
+
return { col, row: Math.round((y - offsetY) / hexH) };
|
|
97
|
+
}
|
|
98
|
+
function enumerateHexRing(centerQ, centerR, n2, orientation, cellSize) {
|
|
99
|
+
const cells = [];
|
|
100
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
101
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
102
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
103
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
104
|
+
const absQ = centerQ + dq;
|
|
105
|
+
const absR = centerR + dr;
|
|
106
|
+
const off = cubeToOffset(absQ, absR, orientation);
|
|
107
|
+
cells.push(offsetToPixel(off.col, off.row, cellSize, orientation));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return cells;
|
|
111
|
+
}
|
|
112
|
+
function getHexDistance(a, b, cellSize, orientation) {
|
|
113
|
+
const offA = pixelToOffset(a.x, a.y, cellSize, orientation);
|
|
114
|
+
const offB = pixelToOffset(b.x, b.y, cellSize, orientation);
|
|
115
|
+
const cubeA = offsetToCube(offA.col, offA.row, orientation);
|
|
116
|
+
const cubeB = offsetToCube(offB.col, offB.row, orientation);
|
|
117
|
+
const dq = cubeA.q - cubeB.q;
|
|
118
|
+
const dr = cubeA.r - cubeB.r;
|
|
119
|
+
const ds = -dq - dr;
|
|
120
|
+
return Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds));
|
|
121
|
+
}
|
|
122
|
+
function getHexCellsInRadius(center2, radiusCells, cellSize, orientation) {
|
|
123
|
+
const n2 = Math.round(radiusCells);
|
|
124
|
+
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
125
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
126
|
+
if (n2 <= 0) {
|
|
127
|
+
return [offsetToPixel(off.col, off.row, cellSize, orientation)];
|
|
128
|
+
}
|
|
129
|
+
return enumerateHexRing(cube.q, cube.r, n2, orientation, cellSize);
|
|
130
|
+
}
|
|
131
|
+
function getHexCellsInCone(center2, angle, radiusCells, cellSize, orientation) {
|
|
132
|
+
const n2 = Math.round(radiusCells);
|
|
133
|
+
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
134
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
135
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
136
|
+
if (n2 <= 0) return [centerPixel];
|
|
137
|
+
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
138
|
+
const step = Math.PI / 3;
|
|
139
|
+
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
140
|
+
const halfAngle = Math.PI / 6 + 1e-6;
|
|
141
|
+
const cells = [centerPixel];
|
|
142
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
143
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
144
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
145
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
146
|
+
if (dq === 0 && dr === 0) continue;
|
|
147
|
+
const absQ = cube.q + dq;
|
|
148
|
+
const absR = cube.r + dr;
|
|
149
|
+
const pixel = offsetToPixel(
|
|
150
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
151
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
152
|
+
cellSize,
|
|
153
|
+
orientation
|
|
154
|
+
);
|
|
155
|
+
const dx = pixel.x - centerPixel.x;
|
|
156
|
+
const dy = pixel.y - centerPixel.y;
|
|
157
|
+
let diff = Math.atan2(dy, dx) - snappedAngle;
|
|
158
|
+
if (diff > Math.PI) diff -= 2 * Math.PI;
|
|
159
|
+
if (diff < -Math.PI) diff += 2 * Math.PI;
|
|
160
|
+
if (Math.abs(diff) <= halfAngle) {
|
|
161
|
+
cells.push(pixel);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return cells;
|
|
166
|
+
}
|
|
167
|
+
function getHexCellsInLine(center2, angle, radiusCells, cellSize, orientation) {
|
|
168
|
+
const n2 = Math.round(radiusCells);
|
|
169
|
+
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
170
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
171
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
172
|
+
if (n2 <= 0) return [centerPixel];
|
|
173
|
+
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
174
|
+
const step = Math.PI / 3;
|
|
175
|
+
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
176
|
+
const cos = Math.cos(snappedAngle);
|
|
177
|
+
const sin = Math.sin(snappedAngle);
|
|
178
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
179
|
+
const lineLength = n2 * snapUnit;
|
|
180
|
+
const halfWidth = snapUnit * 0.5 + 1e-6;
|
|
181
|
+
const cells = [];
|
|
182
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
183
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
184
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
185
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
186
|
+
const absQ = cube.q + dq;
|
|
187
|
+
const absR = cube.r + dr;
|
|
188
|
+
const pixel = offsetToPixel(
|
|
189
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
190
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
191
|
+
cellSize,
|
|
192
|
+
orientation
|
|
193
|
+
);
|
|
194
|
+
const dx = pixel.x - centerPixel.x;
|
|
195
|
+
const dy = pixel.y - centerPixel.y;
|
|
196
|
+
const along = dx * cos + dy * sin;
|
|
197
|
+
const perp = Math.abs(-dx * sin + dy * cos);
|
|
198
|
+
if (along >= -snapUnit * 0.1 && along <= lineLength + snapUnit * 0.1 && perp <= halfWidth) {
|
|
199
|
+
cells.push(pixel);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return cells;
|
|
204
|
+
}
|
|
205
|
+
function getHexCellsInRectangle(center2, angle, lengthCells, widthCells, cellSize, orientation) {
|
|
206
|
+
const nLen = Math.round(lengthCells);
|
|
207
|
+
const wCells = Math.max(1, Math.round(widthCells));
|
|
208
|
+
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
209
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
210
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
211
|
+
if (nLen <= 0) return [centerPixel];
|
|
212
|
+
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
213
|
+
const step = Math.PI / 3;
|
|
214
|
+
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
215
|
+
const cos = Math.cos(snappedAngle);
|
|
216
|
+
const sin = Math.sin(snappedAngle);
|
|
217
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
218
|
+
const lineLength = nLen * snapUnit;
|
|
219
|
+
const halfWidth = wCells * snapUnit / 2 + 1e-6;
|
|
220
|
+
const iterM = nLen + Math.ceil(wCells / 2) + 1;
|
|
221
|
+
const cells = [];
|
|
222
|
+
for (let dq = -iterM; dq <= iterM; dq++) {
|
|
223
|
+
const rMin = Math.max(-iterM, -dq - iterM);
|
|
224
|
+
const rMax = Math.min(iterM, -dq + iterM);
|
|
225
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
226
|
+
const absQ = cube.q + dq;
|
|
227
|
+
const absR = cube.r + dr;
|
|
228
|
+
const pixel = offsetToPixel(
|
|
229
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
230
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
231
|
+
cellSize,
|
|
232
|
+
orientation
|
|
233
|
+
);
|
|
234
|
+
const dx = pixel.x - centerPixel.x;
|
|
235
|
+
const dy = pixel.y - centerPixel.y;
|
|
236
|
+
const along = dx * cos + dy * sin;
|
|
237
|
+
const perp = Math.abs(-dx * sin + dy * cos);
|
|
238
|
+
if (along >= -snapUnit * 0.1 && along <= lineLength + snapUnit * 0.1 && perp <= halfWidth) {
|
|
239
|
+
cells.push(pixel);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return cells;
|
|
244
|
+
}
|
|
245
|
+
function getHexCellsInSquare(center2, radiusCells, cellSize, orientation) {
|
|
246
|
+
const n2 = Math.round(radiusCells);
|
|
247
|
+
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
248
|
+
const cube = offsetToCube(off.col, off.row, orientation);
|
|
249
|
+
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
250
|
+
if (n2 <= 0) return [centerPixel];
|
|
251
|
+
const snapUnit = Math.sqrt(3) * cellSize;
|
|
252
|
+
const halfSide = n2 * snapUnit / 2;
|
|
253
|
+
const cells = [];
|
|
254
|
+
for (let dq = -n2; dq <= n2; dq++) {
|
|
255
|
+
const rMin = Math.max(-n2, -dq - n2);
|
|
256
|
+
const rMax = Math.min(n2, -dq + n2);
|
|
257
|
+
for (let dr = rMin; dr <= rMax; dr++) {
|
|
258
|
+
const absQ = cube.q + dq;
|
|
259
|
+
const absR = cube.r + dr;
|
|
260
|
+
const pixel = offsetToPixel(
|
|
261
|
+
cubeToOffset(absQ, absR, orientation).col,
|
|
262
|
+
cubeToOffset(absQ, absR, orientation).row,
|
|
263
|
+
cellSize,
|
|
264
|
+
orientation
|
|
265
|
+
);
|
|
266
|
+
if (Math.abs(pixel.x - centerPixel.x) <= halfSide && Math.abs(pixel.y - centerPixel.y) <= halfSide) {
|
|
267
|
+
cells.push(pixel);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return cells;
|
|
272
|
+
}
|
|
273
|
+
function drawHexPath(ctx, cx, cy, cellSize, orientation) {
|
|
274
|
+
const angleOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
275
|
+
ctx.moveTo(cx + cellSize * Math.cos(angleOffset), cy + cellSize * Math.sin(angleOffset));
|
|
276
|
+
for (let i = 1; i < 6; i++) {
|
|
277
|
+
const a = angleOffset + Math.PI / 3 * i;
|
|
278
|
+
ctx.lineTo(cx + cellSize * Math.cos(a), cy + cellSize * Math.sin(a));
|
|
279
|
+
}
|
|
280
|
+
ctx.closePath();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/core/grid-metric.ts
|
|
284
|
+
function squareCost(rule, straight, diag) {
|
|
285
|
+
switch (rule) {
|
|
286
|
+
case "alternate":
|
|
287
|
+
return straight + diag + Math.floor(diag / 2);
|
|
288
|
+
case "manhattan":
|
|
289
|
+
return straight + 2 * diag;
|
|
290
|
+
case "chebyshev":
|
|
291
|
+
case "euclidean":
|
|
292
|
+
default:
|
|
293
|
+
return straight + diag;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function pathDistanceCells(points, grid) {
|
|
297
|
+
const cumulative = [];
|
|
298
|
+
const first = points[0];
|
|
299
|
+
if (first === void 0) return { total: 0, cumulative };
|
|
300
|
+
const gridSize = grid.gridSize > 0 ? grid.gridSize : 1;
|
|
301
|
+
const rule = grid.diagonalRule ?? "euclidean";
|
|
302
|
+
const orientation = grid.hexOrientation;
|
|
303
|
+
const hex = grid.gridType === "hex" && orientation !== void 0;
|
|
304
|
+
const squareRule = grid.gridType === "square" && rule !== "euclidean";
|
|
305
|
+
let straight = 0;
|
|
306
|
+
let diag = 0;
|
|
307
|
+
let euclid = 0;
|
|
308
|
+
let hexCells = 0;
|
|
309
|
+
cumulative.push(0);
|
|
310
|
+
let prev = first;
|
|
311
|
+
for (let i = 1; i < points.length; i++) {
|
|
312
|
+
const next = points[i];
|
|
313
|
+
if (next === void 0) break;
|
|
314
|
+
if (hex && orientation !== void 0) {
|
|
315
|
+
hexCells += getHexDistance(prev, next, gridSize, orientation);
|
|
316
|
+
cumulative.push(hexCells);
|
|
317
|
+
} else if (squareRule) {
|
|
318
|
+
const dx = Math.abs(Math.round((next.x - prev.x) / gridSize));
|
|
319
|
+
const dy = Math.abs(Math.round((next.y - prev.y) / gridSize));
|
|
320
|
+
const d = Math.min(dx, dy);
|
|
321
|
+
diag += d;
|
|
322
|
+
straight += Math.max(dx, dy) - d;
|
|
323
|
+
cumulative.push(squareCost(rule, straight, diag));
|
|
324
|
+
} else {
|
|
325
|
+
euclid += Math.hypot(next.x - prev.x, next.y - prev.y) / gridSize;
|
|
326
|
+
cumulative.push(euclid);
|
|
327
|
+
}
|
|
328
|
+
prev = next;
|
|
329
|
+
}
|
|
330
|
+
const total = cumulative[cumulative.length - 1] ?? 0;
|
|
331
|
+
return { total, cumulative };
|
|
332
|
+
}
|
|
333
|
+
function gridDistanceCells(a, b, grid) {
|
|
334
|
+
return pathDistanceCells([a, b], grid).total;
|
|
335
|
+
}
|
|
32
336
|
|
|
33
337
|
// src/elements/note-sanitizer.ts
|
|
34
338
|
var BOLD_TAGS = /* @__PURE__ */ new Set(["b", "strong"]);
|
|
@@ -1555,6 +1859,12 @@ var KeyboardHandler = class {
|
|
|
1555
1859
|
if (e.key === " ") {
|
|
1556
1860
|
this.deps.setSpaceHeld(true);
|
|
1557
1861
|
}
|
|
1862
|
+
const tool = this.deps.getActiveTool();
|
|
1863
|
+
const ctx = this.deps.getToolContext();
|
|
1864
|
+
if (tool?.onKeyDown && ctx && tool.onKeyDown(e, ctx)) {
|
|
1865
|
+
e.preventDefault();
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1558
1868
|
const action = this.shortcutMap.match(e);
|
|
1559
1869
|
if (action !== null) {
|
|
1560
1870
|
this.runAction(action, e);
|
|
@@ -1850,6 +2160,7 @@ var InputHandler = class {
|
|
|
1850
2160
|
abortSignal: this.abortController.signal,
|
|
1851
2161
|
getToolContext: () => this.toolContext,
|
|
1852
2162
|
getIsToolActive: () => this.isToolActive,
|
|
2163
|
+
getActiveTool: () => this.toolManager?.activeTool ?? null,
|
|
1853
2164
|
getLastPointerEvent: () => this.lastPointerEvent,
|
|
1854
2165
|
setSpaceHeld: (v) => {
|
|
1855
2166
|
this.spaceHeld = v;
|
|
@@ -1931,7 +2242,7 @@ var InputHandler = class {
|
|
|
1931
2242
|
this.element.addEventListener("pointermove", this.onPointerMove, opts);
|
|
1932
2243
|
this.element.addEventListener("pointerup", this.onPointerUp, opts);
|
|
1933
2244
|
this.element.addEventListener("pointerleave", this.onPointerLeave, opts);
|
|
1934
|
-
this.element.addEventListener("pointercancel", this.
|
|
2245
|
+
this.element.addEventListener("pointercancel", this.onPointerCancel, opts);
|
|
1935
2246
|
this.element.addEventListener("contextmenu", this.onContextMenu, opts);
|
|
1936
2247
|
window.addEventListener("blur", this.onCoastInterrupt, opts);
|
|
1937
2248
|
window.addEventListener("visibilitychange", this.onCoastInterrupt, opts);
|
|
@@ -2010,6 +2321,12 @@ var InputHandler = class {
|
|
|
2010
2321
|
}
|
|
2011
2322
|
};
|
|
2012
2323
|
onPointerUp = (e) => {
|
|
2324
|
+
this.finishPointer(e, false);
|
|
2325
|
+
};
|
|
2326
|
+
onPointerCancel = (e) => {
|
|
2327
|
+
this.finishPointer(e, true);
|
|
2328
|
+
};
|
|
2329
|
+
finishPointer(e, cancelled) {
|
|
2013
2330
|
this.cancelLongPress();
|
|
2014
2331
|
try {
|
|
2015
2332
|
this.element.releasePointerCapture(e.pointerId);
|
|
@@ -2032,16 +2349,18 @@ var InputHandler = class {
|
|
|
2032
2349
|
if (this.activePointers.size === 0) this.coastStoppedByPointer = false;
|
|
2033
2350
|
const upResult = this.inputFilter.filterUp(e);
|
|
2034
2351
|
if (this.isToolActive) {
|
|
2035
|
-
this.
|
|
2352
|
+
if (cancelled) this.dispatchToolCancel(e);
|
|
2353
|
+
else this.dispatchToolUp(e);
|
|
2036
2354
|
this.isToolActive = false;
|
|
2037
2355
|
} else if (this.deferredDown && upResult.pendingTap) {
|
|
2038
2356
|
this.dispatchToolDown(this.deferredDown);
|
|
2039
|
-
this.
|
|
2357
|
+
if (cancelled) this.dispatchToolCancel(e);
|
|
2358
|
+
else this.dispatchToolUp(e);
|
|
2040
2359
|
this.deferredDown = null;
|
|
2041
2360
|
} else {
|
|
2042
2361
|
this.deferredDown = null;
|
|
2043
2362
|
}
|
|
2044
|
-
}
|
|
2363
|
+
}
|
|
2045
2364
|
runAction(action, e) {
|
|
2046
2365
|
this.keyboard.runAction(action, e);
|
|
2047
2366
|
}
|
|
@@ -2148,6 +2467,11 @@ var InputHandler = class {
|
|
|
2148
2467
|
this.toolManager.handlePointerUp(this.toPointerState(e), this.toolContext);
|
|
2149
2468
|
this.historyRecorder?.commit();
|
|
2150
2469
|
}
|
|
2470
|
+
dispatchToolCancel(e) {
|
|
2471
|
+
if (!this.toolManager || !this.toolContext) return;
|
|
2472
|
+
this.toolManager.handlePointerCancel(this.toPointerState(e), this.toolContext);
|
|
2473
|
+
this.historyRecorder?.commit();
|
|
2474
|
+
}
|
|
2151
2475
|
isInScope() {
|
|
2152
2476
|
if (this.scope === "window") return true;
|
|
2153
2477
|
const active = document.activeElement;
|
|
@@ -2160,7 +2484,7 @@ var InputHandler = class {
|
|
|
2160
2484
|
cancelToolIfActive(e) {
|
|
2161
2485
|
this.cancelLongPress();
|
|
2162
2486
|
if (this.isToolActive) {
|
|
2163
|
-
this.
|
|
2487
|
+
this.dispatchToolCancel(e);
|
|
2164
2488
|
this.isToolActive = false;
|
|
2165
2489
|
}
|
|
2166
2490
|
this.deferredDown = null;
|
|
@@ -3258,230 +3582,6 @@ function getImage(src, imageCache, onImageLoad, onImageError) {
|
|
|
3258
3582
|
return null;
|
|
3259
3583
|
}
|
|
3260
3584
|
|
|
3261
|
-
// src/elements/hex-fill.ts
|
|
3262
|
-
function offsetToCube(col, row, orientation) {
|
|
3263
|
-
if (orientation === "pointy") {
|
|
3264
|
-
return { q: col - (row - (row & 1)) / 2, r: row };
|
|
3265
|
-
}
|
|
3266
|
-
return { q: col, r: row - (col - (col & 1)) / 2 };
|
|
3267
|
-
}
|
|
3268
|
-
function cubeToOffset(q, r, orientation) {
|
|
3269
|
-
if (orientation === "pointy") {
|
|
3270
|
-
return { col: q + (r - (r & 1)) / 2, row: r };
|
|
3271
|
-
}
|
|
3272
|
-
return { col: q, row: r + (q - (q & 1)) / 2 };
|
|
3273
|
-
}
|
|
3274
|
-
function offsetToPixel(col, row, cellSize, orientation) {
|
|
3275
|
-
if (orientation === "pointy") {
|
|
3276
|
-
const hexW = Math.sqrt(3) * cellSize;
|
|
3277
|
-
const rowH = 1.5 * cellSize;
|
|
3278
|
-
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
3279
|
-
return { x: col * hexW + offsetX, y: row * rowH };
|
|
3280
|
-
}
|
|
3281
|
-
const hexH = Math.sqrt(3) * cellSize;
|
|
3282
|
-
const colW = 1.5 * cellSize;
|
|
3283
|
-
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
3284
|
-
return { x: col * colW, y: row * hexH + offsetY };
|
|
3285
|
-
}
|
|
3286
|
-
function pixelToOffset(x, y, cellSize, orientation) {
|
|
3287
|
-
if (orientation === "pointy") {
|
|
3288
|
-
const hexW = Math.sqrt(3) * cellSize;
|
|
3289
|
-
const rowH = 1.5 * cellSize;
|
|
3290
|
-
const row = Math.round(y / rowH);
|
|
3291
|
-
const offsetX = row % 2 !== 0 ? hexW / 2 : 0;
|
|
3292
|
-
return { col: Math.round((x - offsetX) / hexW), row };
|
|
3293
|
-
}
|
|
3294
|
-
const hexH = Math.sqrt(3) * cellSize;
|
|
3295
|
-
const colW = 1.5 * cellSize;
|
|
3296
|
-
const col = Math.round(x / colW);
|
|
3297
|
-
const offsetY = col % 2 !== 0 ? hexH / 2 : 0;
|
|
3298
|
-
return { col, row: Math.round((y - offsetY) / hexH) };
|
|
3299
|
-
}
|
|
3300
|
-
function enumerateHexRing(centerQ, centerR, n2, orientation, cellSize) {
|
|
3301
|
-
const cells = [];
|
|
3302
|
-
for (let dq = -n2; dq <= n2; dq++) {
|
|
3303
|
-
const rMin = Math.max(-n2, -dq - n2);
|
|
3304
|
-
const rMax = Math.min(n2, -dq + n2);
|
|
3305
|
-
for (let dr = rMin; dr <= rMax; dr++) {
|
|
3306
|
-
const absQ = centerQ + dq;
|
|
3307
|
-
const absR = centerR + dr;
|
|
3308
|
-
const off = cubeToOffset(absQ, absR, orientation);
|
|
3309
|
-
cells.push(offsetToPixel(off.col, off.row, cellSize, orientation));
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
return cells;
|
|
3313
|
-
}
|
|
3314
|
-
function getHexDistance(a, b, cellSize, orientation) {
|
|
3315
|
-
const offA = pixelToOffset(a.x, a.y, cellSize, orientation);
|
|
3316
|
-
const offB = pixelToOffset(b.x, b.y, cellSize, orientation);
|
|
3317
|
-
const cubeA = offsetToCube(offA.col, offA.row, orientation);
|
|
3318
|
-
const cubeB = offsetToCube(offB.col, offB.row, orientation);
|
|
3319
|
-
const dq = cubeA.q - cubeB.q;
|
|
3320
|
-
const dr = cubeA.r - cubeB.r;
|
|
3321
|
-
const ds = -dq - dr;
|
|
3322
|
-
return Math.max(Math.abs(dq), Math.abs(dr), Math.abs(ds));
|
|
3323
|
-
}
|
|
3324
|
-
function getHexCellsInRadius(center2, radiusCells, cellSize, orientation) {
|
|
3325
|
-
const n2 = Math.round(radiusCells);
|
|
3326
|
-
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
3327
|
-
const cube = offsetToCube(off.col, off.row, orientation);
|
|
3328
|
-
if (n2 <= 0) {
|
|
3329
|
-
return [offsetToPixel(off.col, off.row, cellSize, orientation)];
|
|
3330
|
-
}
|
|
3331
|
-
return enumerateHexRing(cube.q, cube.r, n2, orientation, cellSize);
|
|
3332
|
-
}
|
|
3333
|
-
function getHexCellsInCone(center2, angle, radiusCells, cellSize, orientation) {
|
|
3334
|
-
const n2 = Math.round(radiusCells);
|
|
3335
|
-
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
3336
|
-
const cube = offsetToCube(off.col, off.row, orientation);
|
|
3337
|
-
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
3338
|
-
if (n2 <= 0) return [centerPixel];
|
|
3339
|
-
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
3340
|
-
const step = Math.PI / 3;
|
|
3341
|
-
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
3342
|
-
const halfAngle = Math.PI / 6 + 1e-6;
|
|
3343
|
-
const cells = [centerPixel];
|
|
3344
|
-
for (let dq = -n2; dq <= n2; dq++) {
|
|
3345
|
-
const rMin = Math.max(-n2, -dq - n2);
|
|
3346
|
-
const rMax = Math.min(n2, -dq + n2);
|
|
3347
|
-
for (let dr = rMin; dr <= rMax; dr++) {
|
|
3348
|
-
if (dq === 0 && dr === 0) continue;
|
|
3349
|
-
const absQ = cube.q + dq;
|
|
3350
|
-
const absR = cube.r + dr;
|
|
3351
|
-
const pixel = offsetToPixel(
|
|
3352
|
-
cubeToOffset(absQ, absR, orientation).col,
|
|
3353
|
-
cubeToOffset(absQ, absR, orientation).row,
|
|
3354
|
-
cellSize,
|
|
3355
|
-
orientation
|
|
3356
|
-
);
|
|
3357
|
-
const dx = pixel.x - centerPixel.x;
|
|
3358
|
-
const dy = pixel.y - centerPixel.y;
|
|
3359
|
-
let diff = Math.atan2(dy, dx) - snappedAngle;
|
|
3360
|
-
if (diff > Math.PI) diff -= 2 * Math.PI;
|
|
3361
|
-
if (diff < -Math.PI) diff += 2 * Math.PI;
|
|
3362
|
-
if (Math.abs(diff) <= halfAngle) {
|
|
3363
|
-
cells.push(pixel);
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
}
|
|
3367
|
-
return cells;
|
|
3368
|
-
}
|
|
3369
|
-
function getHexCellsInLine(center2, angle, radiusCells, cellSize, orientation) {
|
|
3370
|
-
const n2 = Math.round(radiusCells);
|
|
3371
|
-
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
3372
|
-
const cube = offsetToCube(off.col, off.row, orientation);
|
|
3373
|
-
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
3374
|
-
if (n2 <= 0) return [centerPixel];
|
|
3375
|
-
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
3376
|
-
const step = Math.PI / 3;
|
|
3377
|
-
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
3378
|
-
const cos = Math.cos(snappedAngle);
|
|
3379
|
-
const sin = Math.sin(snappedAngle);
|
|
3380
|
-
const snapUnit = Math.sqrt(3) * cellSize;
|
|
3381
|
-
const lineLength = n2 * snapUnit;
|
|
3382
|
-
const halfWidth = snapUnit * 0.5 + 1e-6;
|
|
3383
|
-
const cells = [];
|
|
3384
|
-
for (let dq = -n2; dq <= n2; dq++) {
|
|
3385
|
-
const rMin = Math.max(-n2, -dq - n2);
|
|
3386
|
-
const rMax = Math.min(n2, -dq + n2);
|
|
3387
|
-
for (let dr = rMin; dr <= rMax; dr++) {
|
|
3388
|
-
const absQ = cube.q + dq;
|
|
3389
|
-
const absR = cube.r + dr;
|
|
3390
|
-
const pixel = offsetToPixel(
|
|
3391
|
-
cubeToOffset(absQ, absR, orientation).col,
|
|
3392
|
-
cubeToOffset(absQ, absR, orientation).row,
|
|
3393
|
-
cellSize,
|
|
3394
|
-
orientation
|
|
3395
|
-
);
|
|
3396
|
-
const dx = pixel.x - centerPixel.x;
|
|
3397
|
-
const dy = pixel.y - centerPixel.y;
|
|
3398
|
-
const along = dx * cos + dy * sin;
|
|
3399
|
-
const perp = Math.abs(-dx * sin + dy * cos);
|
|
3400
|
-
if (along >= -snapUnit * 0.1 && along <= lineLength + snapUnit * 0.1 && perp <= halfWidth) {
|
|
3401
|
-
cells.push(pixel);
|
|
3402
|
-
}
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
return cells;
|
|
3406
|
-
}
|
|
3407
|
-
function getHexCellsInRectangle(center2, angle, lengthCells, widthCells, cellSize, orientation) {
|
|
3408
|
-
const nLen = Math.round(lengthCells);
|
|
3409
|
-
const wCells = Math.max(1, Math.round(widthCells));
|
|
3410
|
-
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
3411
|
-
const cube = offsetToCube(off.col, off.row, orientation);
|
|
3412
|
-
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
3413
|
-
if (nLen <= 0) return [centerPixel];
|
|
3414
|
-
const vertexOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
3415
|
-
const step = Math.PI / 3;
|
|
3416
|
-
const snappedAngle = Math.round((angle - vertexOffset) / step) * step + vertexOffset;
|
|
3417
|
-
const cos = Math.cos(snappedAngle);
|
|
3418
|
-
const sin = Math.sin(snappedAngle);
|
|
3419
|
-
const snapUnit = Math.sqrt(3) * cellSize;
|
|
3420
|
-
const lineLength = nLen * snapUnit;
|
|
3421
|
-
const halfWidth = wCells * snapUnit / 2 + 1e-6;
|
|
3422
|
-
const iterM = nLen + Math.ceil(wCells / 2) + 1;
|
|
3423
|
-
const cells = [];
|
|
3424
|
-
for (let dq = -iterM; dq <= iterM; dq++) {
|
|
3425
|
-
const rMin = Math.max(-iterM, -dq - iterM);
|
|
3426
|
-
const rMax = Math.min(iterM, -dq + iterM);
|
|
3427
|
-
for (let dr = rMin; dr <= rMax; dr++) {
|
|
3428
|
-
const absQ = cube.q + dq;
|
|
3429
|
-
const absR = cube.r + dr;
|
|
3430
|
-
const pixel = offsetToPixel(
|
|
3431
|
-
cubeToOffset(absQ, absR, orientation).col,
|
|
3432
|
-
cubeToOffset(absQ, absR, orientation).row,
|
|
3433
|
-
cellSize,
|
|
3434
|
-
orientation
|
|
3435
|
-
);
|
|
3436
|
-
const dx = pixel.x - centerPixel.x;
|
|
3437
|
-
const dy = pixel.y - centerPixel.y;
|
|
3438
|
-
const along = dx * cos + dy * sin;
|
|
3439
|
-
const perp = Math.abs(-dx * sin + dy * cos);
|
|
3440
|
-
if (along >= -snapUnit * 0.1 && along <= lineLength + snapUnit * 0.1 && perp <= halfWidth) {
|
|
3441
|
-
cells.push(pixel);
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3444
|
-
}
|
|
3445
|
-
return cells;
|
|
3446
|
-
}
|
|
3447
|
-
function getHexCellsInSquare(center2, radiusCells, cellSize, orientation) {
|
|
3448
|
-
const n2 = Math.round(radiusCells);
|
|
3449
|
-
const off = pixelToOffset(center2.x, center2.y, cellSize, orientation);
|
|
3450
|
-
const cube = offsetToCube(off.col, off.row, orientation);
|
|
3451
|
-
const centerPixel = offsetToPixel(off.col, off.row, cellSize, orientation);
|
|
3452
|
-
if (n2 <= 0) return [centerPixel];
|
|
3453
|
-
const snapUnit = Math.sqrt(3) * cellSize;
|
|
3454
|
-
const halfSide = n2 * snapUnit / 2;
|
|
3455
|
-
const cells = [];
|
|
3456
|
-
for (let dq = -n2; dq <= n2; dq++) {
|
|
3457
|
-
const rMin = Math.max(-n2, -dq - n2);
|
|
3458
|
-
const rMax = Math.min(n2, -dq + n2);
|
|
3459
|
-
for (let dr = rMin; dr <= rMax; dr++) {
|
|
3460
|
-
const absQ = cube.q + dq;
|
|
3461
|
-
const absR = cube.r + dr;
|
|
3462
|
-
const pixel = offsetToPixel(
|
|
3463
|
-
cubeToOffset(absQ, absR, orientation).col,
|
|
3464
|
-
cubeToOffset(absQ, absR, orientation).row,
|
|
3465
|
-
cellSize,
|
|
3466
|
-
orientation
|
|
3467
|
-
);
|
|
3468
|
-
if (Math.abs(pixel.x - centerPixel.x) <= halfSide && Math.abs(pixel.y - centerPixel.y) <= halfSide) {
|
|
3469
|
-
cells.push(pixel);
|
|
3470
|
-
}
|
|
3471
|
-
}
|
|
3472
|
-
}
|
|
3473
|
-
return cells;
|
|
3474
|
-
}
|
|
3475
|
-
function drawHexPath(ctx, cx, cy, cellSize, orientation) {
|
|
3476
|
-
const angleOffset = orientation === "pointy" ? Math.PI / 6 : 0;
|
|
3477
|
-
ctx.moveTo(cx + cellSize * Math.cos(angleOffset), cy + cellSize * Math.sin(angleOffset));
|
|
3478
|
-
for (let i = 1; i < 6; i++) {
|
|
3479
|
-
const a = angleOffset + Math.PI / 3 * i;
|
|
3480
|
-
ctx.lineTo(cx + cellSize * Math.cos(a), cy + cellSize * Math.sin(a));
|
|
3481
|
-
}
|
|
3482
|
-
ctx.closePath();
|
|
3483
|
-
}
|
|
3484
|
-
|
|
3485
3585
|
// src/elements/renderers/template-measure.ts
|
|
3486
3586
|
function renderTemplateFeetLabel(ctx, p) {
|
|
3487
3587
|
if (p.feet <= 0) return;
|
|
@@ -5456,6 +5556,13 @@ var ToolManager = class {
|
|
|
5456
5556
|
handlePointerUp(state, ctx) {
|
|
5457
5557
|
this.current?.onPointerUp(state, ctx);
|
|
5458
5558
|
}
|
|
5559
|
+
/** Cancels the active gesture; falls back to `onPointerUp` for tools without `onPointerCancel`. */
|
|
5560
|
+
handlePointerCancel(state, ctx) {
|
|
5561
|
+
const tool = this.current;
|
|
5562
|
+
if (!tool) return;
|
|
5563
|
+
if (tool.onPointerCancel) tool.onPointerCancel(state, ctx);
|
|
5564
|
+
else tool.onPointerUp(state, ctx);
|
|
5565
|
+
}
|
|
5459
5566
|
onChange(listener) {
|
|
5460
5567
|
this.changeListeners.add(listener);
|
|
5461
5568
|
return () => this.changeListeners.delete(listener);
|
|
@@ -11065,145 +11172,119 @@ function drawMeasurement(ctx, m, opts = {}) {
|
|
|
11065
11172
|
ctx.restore();
|
|
11066
11173
|
}
|
|
11067
11174
|
|
|
11068
|
-
// src/canvas/
|
|
11069
|
-
var MEASURE_PRESENCE_KIND = "measure";
|
|
11070
|
-
function isFinitePoint2(value) {
|
|
11071
|
-
if (typeof value !== "object" || value === null) return false;
|
|
11072
|
-
const point = value;
|
|
11073
|
-
return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
|
|
11074
|
-
}
|
|
11075
|
-
function isMeasurePresence(data) {
|
|
11076
|
-
if (typeof data !== "object" || data === null) return false;
|
|
11077
|
-
const payload = data;
|
|
11078
|
-
if (payload.kind !== MEASURE_PRESENCE_KIND) return false;
|
|
11079
|
-
if ("cleared" in payload) return payload.cleared === true;
|
|
11080
|
-
if (!isFinitePoint2(payload.start) || !isFinitePoint2(payload.end)) return false;
|
|
11081
|
-
if (typeof payload.cells !== "number" || !Number.isFinite(payload.cells)) return false;
|
|
11082
|
-
if (typeof payload.feet !== "number" || !Number.isFinite(payload.feet)) return false;
|
|
11083
|
-
if (payload.color !== void 0 && typeof payload.color !== "string") return false;
|
|
11084
|
-
return true;
|
|
11085
|
-
}
|
|
11086
|
-
function toMeasurePresence(emission) {
|
|
11087
|
-
if (emission === null) return { kind: MEASURE_PRESENCE_KIND, cleared: true };
|
|
11088
|
-
return {
|
|
11089
|
-
kind: MEASURE_PRESENCE_KIND,
|
|
11090
|
-
start: emission.start,
|
|
11091
|
-
end: emission.end,
|
|
11092
|
-
cells: emission.cells,
|
|
11093
|
-
feet: emission.feet,
|
|
11094
|
-
color: emission.color
|
|
11095
|
-
};
|
|
11096
|
-
}
|
|
11097
|
-
var DEFAULT_COLOR3 = "#FF5722";
|
|
11175
|
+
// src/canvas/linger-overlay.ts
|
|
11098
11176
|
var DEFAULT_HOLD_MS = 1500;
|
|
11099
11177
|
var DEFAULT_FADE_MS2 = 400;
|
|
11100
11178
|
var DEFAULT_MAX_AGE_MS = 3e4;
|
|
11101
|
-
var
|
|
11179
|
+
var LingerOverlay = class {
|
|
11102
11180
|
host;
|
|
11103
|
-
|
|
11181
|
+
draw;
|
|
11182
|
+
clock;
|
|
11104
11183
|
holdMs;
|
|
11105
11184
|
fadeMs;
|
|
11106
11185
|
maxAgeMs;
|
|
11107
|
-
|
|
11186
|
+
entries = /* @__PURE__ */ new Map();
|
|
11108
11187
|
unregister;
|
|
11109
11188
|
rafId = null;
|
|
11110
|
-
|
|
11111
|
-
|
|
11189
|
+
isDisposed = false;
|
|
11190
|
+
/**
|
|
11191
|
+
* `clock` exists only as a test seam; production callers leave it at
|
|
11192
|
+
* `performance.now`. Owners that expose their own `now()` seam pass it
|
|
11193
|
+
* through so a spy on the owner still drives this overlay's timing.
|
|
11194
|
+
*/
|
|
11195
|
+
constructor(host, options, draw, clock = () => performance.now()) {
|
|
11112
11196
|
this.host = host;
|
|
11113
|
-
this.
|
|
11197
|
+
this.draw = draw;
|
|
11198
|
+
this.clock = clock;
|
|
11114
11199
|
this.holdMs = options.holdMs ?? DEFAULT_HOLD_MS;
|
|
11115
11200
|
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS2;
|
|
11116
11201
|
this.maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
11117
|
-
this.unregister = host.registerOverlay((ctx) => this.
|
|
11202
|
+
this.unregister = host.registerOverlay((ctx) => this.render(ctx));
|
|
11118
11203
|
}
|
|
11119
11204
|
now() {
|
|
11120
|
-
return
|
|
11205
|
+
return this.clock();
|
|
11121
11206
|
}
|
|
11122
11207
|
/**
|
|
11123
|
-
*
|
|
11124
|
-
*
|
|
11125
|
-
* reported as `false`, so hosts can feed every presence frame through.
|
|
11208
|
+
* Stores `sender`'s current entry as active, replacing any previous one and
|
|
11209
|
+
* cancelling an in-flight linger, and restarts the `maxAgeMs` expiry timer.
|
|
11126
11210
|
*/
|
|
11127
|
-
|
|
11128
|
-
if (this.
|
|
11129
|
-
|
|
11130
|
-
this.beginLinger(sender);
|
|
11131
|
-
return true;
|
|
11132
|
-
}
|
|
11133
|
-
const existing = this.measurements.get(sender);
|
|
11211
|
+
set(sender, entry) {
|
|
11212
|
+
if (this.isDisposed) return;
|
|
11213
|
+
const existing = this.entries.get(sender);
|
|
11134
11214
|
if (existing?.expiryTimer != null) clearTimeout(existing.expiryTimer);
|
|
11135
|
-
this.
|
|
11136
|
-
|
|
11137
|
-
end: data.end,
|
|
11138
|
-
feet: data.feet,
|
|
11139
|
-
color: data.color ?? this.color,
|
|
11215
|
+
this.entries.set(sender, {
|
|
11216
|
+
entry,
|
|
11140
11217
|
clearedAt: null,
|
|
11141
|
-
expiryTimer: setTimeout(() => this.
|
|
11218
|
+
expiryTimer: setTimeout(() => this.linger(sender), this.maxAgeMs)
|
|
11142
11219
|
});
|
|
11143
11220
|
this.host.requestRender();
|
|
11144
|
-
return true;
|
|
11145
11221
|
}
|
|
11146
|
-
/**
|
|
11222
|
+
/** Starts the hold-then-fade lifetime for `sender`'s entry. */
|
|
11223
|
+
linger(sender) {
|
|
11224
|
+
const entry = this.entries.get(sender);
|
|
11225
|
+
if (!entry || entry.clearedAt !== null) return;
|
|
11226
|
+
if (entry.expiryTimer != null) {
|
|
11227
|
+
clearTimeout(entry.expiryTimer);
|
|
11228
|
+
entry.expiryTimer = null;
|
|
11229
|
+
}
|
|
11230
|
+
entry.clearedAt = this.now();
|
|
11231
|
+
this.ensureAnimating();
|
|
11232
|
+
this.host.requestRender();
|
|
11233
|
+
}
|
|
11234
|
+
/** Removes a sender's entry immediately (presence-leave/disconnect). */
|
|
11147
11235
|
remove(sender) {
|
|
11148
|
-
const entry = this.
|
|
11236
|
+
const entry = this.entries.get(sender);
|
|
11149
11237
|
if (!entry) return;
|
|
11150
11238
|
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
11151
|
-
this.
|
|
11239
|
+
this.entries.delete(sender);
|
|
11152
11240
|
this.host.requestRender();
|
|
11153
11241
|
}
|
|
11154
|
-
/** Removes every
|
|
11242
|
+
/** Removes every entry immediately. */
|
|
11155
11243
|
clear() {
|
|
11156
|
-
if (this.
|
|
11157
|
-
for (const entry of this.
|
|
11244
|
+
if (this.entries.size === 0) return;
|
|
11245
|
+
for (const entry of this.entries.values()) {
|
|
11158
11246
|
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
11159
11247
|
}
|
|
11160
|
-
this.
|
|
11248
|
+
this.entries.clear();
|
|
11161
11249
|
this.host.requestRender();
|
|
11162
11250
|
}
|
|
11163
|
-
/** Number of senders with a visible (active or lingering)
|
|
11251
|
+
/** Number of senders with a visible (active or lingering) entry. */
|
|
11164
11252
|
get activeSenderCount() {
|
|
11165
|
-
return this.
|
|
11253
|
+
return this.entries.size;
|
|
11254
|
+
}
|
|
11255
|
+
/** True once `dispose` has run; the overlay accepts no further entries. */
|
|
11256
|
+
get disposed() {
|
|
11257
|
+
return this.isDisposed;
|
|
11166
11258
|
}
|
|
11167
11259
|
/** Unregisters the overlay, cancels timers, stops animating. Idempotent. */
|
|
11168
11260
|
dispose() {
|
|
11169
|
-
if (this.
|
|
11170
|
-
this.
|
|
11261
|
+
if (this.isDisposed) return;
|
|
11262
|
+
this.isDisposed = true;
|
|
11171
11263
|
if (this.rafId !== null) {
|
|
11172
11264
|
cancelAnimationFrame(this.rafId);
|
|
11173
11265
|
this.rafId = null;
|
|
11174
11266
|
}
|
|
11175
|
-
for (const entry of this.
|
|
11267
|
+
for (const entry of this.entries.values()) {
|
|
11176
11268
|
if (entry.expiryTimer != null) clearTimeout(entry.expiryTimer);
|
|
11177
11269
|
}
|
|
11178
|
-
this.
|
|
11270
|
+
this.entries.clear();
|
|
11179
11271
|
this.unregister?.();
|
|
11180
11272
|
this.unregister = null;
|
|
11181
11273
|
this.host.requestRender();
|
|
11182
11274
|
}
|
|
11183
|
-
beginLinger(sender) {
|
|
11184
|
-
const entry = this.measurements.get(sender);
|
|
11185
|
-
if (!entry || entry.clearedAt !== null) return;
|
|
11186
|
-
if (entry.expiryTimer != null) {
|
|
11187
|
-
clearTimeout(entry.expiryTimer);
|
|
11188
|
-
entry.expiryTimer = null;
|
|
11189
|
-
}
|
|
11190
|
-
entry.clearedAt = this.now();
|
|
11191
|
-
this.ensureAnimating();
|
|
11192
|
-
this.host.requestRender();
|
|
11193
|
-
}
|
|
11194
11275
|
ensureAnimating() {
|
|
11195
11276
|
if (this.rafId === null) {
|
|
11196
11277
|
this.rafId = requestAnimationFrame(() => this.tick());
|
|
11197
11278
|
}
|
|
11198
11279
|
}
|
|
11199
11280
|
tick() {
|
|
11200
|
-
if (this.
|
|
11281
|
+
if (this.isDisposed) return;
|
|
11201
11282
|
const now = this.now();
|
|
11202
11283
|
let lingering = 0;
|
|
11203
|
-
for (const [sender, entry] of this.
|
|
11284
|
+
for (const [sender, entry] of this.entries) {
|
|
11204
11285
|
if (entry.clearedAt === null) continue;
|
|
11205
11286
|
if (now - entry.clearedAt >= this.holdMs + this.fadeMs) {
|
|
11206
|
-
this.
|
|
11287
|
+
this.entries.delete(sender);
|
|
11207
11288
|
} else {
|
|
11208
11289
|
lingering += 1;
|
|
11209
11290
|
}
|
|
@@ -11211,24 +11292,278 @@ var RemoteMeasureOverlay = class {
|
|
|
11211
11292
|
this.host.requestRender();
|
|
11212
11293
|
this.rafId = lingering > 0 ? requestAnimationFrame(() => this.tick()) : null;
|
|
11213
11294
|
}
|
|
11214
|
-
|
|
11215
|
-
if (this.
|
|
11295
|
+
render(ctx) {
|
|
11296
|
+
if (this.entries.size === 0) return;
|
|
11216
11297
|
const now = this.now();
|
|
11217
|
-
for (const entry of this.
|
|
11298
|
+
for (const entry of this.entries.values()) {
|
|
11218
11299
|
let alpha = 1;
|
|
11219
11300
|
if (entry.clearedAt !== null) {
|
|
11220
11301
|
const fadeAge = now - entry.clearedAt - this.holdMs;
|
|
11221
11302
|
if (fadeAge > 0) alpha = Math.max(0, 1 - fadeAge / this.fadeMs);
|
|
11222
11303
|
}
|
|
11223
|
-
|
|
11304
|
+
this.draw(ctx, entry.entry, alpha);
|
|
11305
|
+
}
|
|
11306
|
+
}
|
|
11307
|
+
};
|
|
11308
|
+
|
|
11309
|
+
// src/canvas/remote-measure-overlay.ts
|
|
11310
|
+
var MEASURE_PRESENCE_KIND = "measure";
|
|
11311
|
+
function isFinitePoint2(value) {
|
|
11312
|
+
if (typeof value !== "object" || value === null) return false;
|
|
11313
|
+
const point = value;
|
|
11314
|
+
return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
|
|
11315
|
+
}
|
|
11316
|
+
function isMeasurePresence(data) {
|
|
11317
|
+
if (typeof data !== "object" || data === null) return false;
|
|
11318
|
+
const payload = data;
|
|
11319
|
+
if (payload.kind !== MEASURE_PRESENCE_KIND) return false;
|
|
11320
|
+
if ("cleared" in payload) return payload.cleared === true;
|
|
11321
|
+
if (!isFinitePoint2(payload.start) || !isFinitePoint2(payload.end)) return false;
|
|
11322
|
+
if (typeof payload.cells !== "number" || !Number.isFinite(payload.cells)) return false;
|
|
11323
|
+
if (typeof payload.feet !== "number" || !Number.isFinite(payload.feet)) return false;
|
|
11324
|
+
if (payload.color !== void 0 && typeof payload.color !== "string") return false;
|
|
11325
|
+
return true;
|
|
11326
|
+
}
|
|
11327
|
+
function toMeasurePresence(emission) {
|
|
11328
|
+
if (emission === null) return { kind: MEASURE_PRESENCE_KIND, cleared: true };
|
|
11329
|
+
return {
|
|
11330
|
+
kind: MEASURE_PRESENCE_KIND,
|
|
11331
|
+
start: emission.start,
|
|
11332
|
+
end: emission.end,
|
|
11333
|
+
cells: emission.cells,
|
|
11334
|
+
feet: emission.feet,
|
|
11335
|
+
color: emission.color
|
|
11336
|
+
};
|
|
11337
|
+
}
|
|
11338
|
+
var DEFAULT_COLOR3 = "#FF5722";
|
|
11339
|
+
var RemoteMeasureOverlay = class {
|
|
11340
|
+
color;
|
|
11341
|
+
overlay;
|
|
11342
|
+
constructor(host, options = {}) {
|
|
11343
|
+
this.color = options.color ?? DEFAULT_COLOR3;
|
|
11344
|
+
this.overlay = new LingerOverlay(
|
|
11345
|
+
host,
|
|
11346
|
+
options,
|
|
11347
|
+
(ctx, entry, alpha) => drawMeasurement(ctx, entry, { alpha }),
|
|
11348
|
+
() => this.now()
|
|
11349
|
+
);
|
|
11350
|
+
}
|
|
11351
|
+
now() {
|
|
11352
|
+
return performance.now();
|
|
11353
|
+
}
|
|
11354
|
+
/**
|
|
11355
|
+
* Applies a presence payload from `sender` (any opaque per-sender key, e.g.
|
|
11356
|
+
* the envelope `from`). Non-measure or malformed payloads are ignored and
|
|
11357
|
+
* reported as `false`, so hosts can feed every presence frame through.
|
|
11358
|
+
*/
|
|
11359
|
+
apply(sender, data) {
|
|
11360
|
+
if (this.overlay.disposed || !isMeasurePresence(data)) return false;
|
|
11361
|
+
if ("cleared" in data) {
|
|
11362
|
+
this.overlay.linger(sender);
|
|
11363
|
+
return true;
|
|
11364
|
+
}
|
|
11365
|
+
this.overlay.set(sender, {
|
|
11366
|
+
start: data.start,
|
|
11367
|
+
end: data.end,
|
|
11368
|
+
feet: data.feet,
|
|
11369
|
+
color: data.color ?? this.color
|
|
11370
|
+
});
|
|
11371
|
+
return true;
|
|
11372
|
+
}
|
|
11373
|
+
/** Removes a sender's ruler immediately (presence-leave/disconnect). */
|
|
11374
|
+
remove(sender) {
|
|
11375
|
+
this.overlay.remove(sender);
|
|
11376
|
+
}
|
|
11377
|
+
/** Removes every ruler immediately. */
|
|
11378
|
+
clear() {
|
|
11379
|
+
this.overlay.clear();
|
|
11380
|
+
}
|
|
11381
|
+
/** Number of senders with a visible (active or lingering) ruler. */
|
|
11382
|
+
get activeSenderCount() {
|
|
11383
|
+
return this.overlay.activeSenderCount;
|
|
11384
|
+
}
|
|
11385
|
+
/** Unregisters the overlay, cancels timers, stops animating. Idempotent. */
|
|
11386
|
+
dispose() {
|
|
11387
|
+
this.overlay.dispose();
|
|
11388
|
+
}
|
|
11389
|
+
};
|
|
11390
|
+
|
|
11391
|
+
// src/canvas/path-render.ts
|
|
11392
|
+
function resolveSegmentColors(cumulativeFeet, bands, color) {
|
|
11393
|
+
const sorted = [...bands].sort((a, b) => a.feet - b.feet);
|
|
11394
|
+
const out = [];
|
|
11395
|
+
for (let i = 1; i < cumulativeFeet.length; i++) {
|
|
11396
|
+
const end = cumulativeFeet[i] ?? 0;
|
|
11397
|
+
const band = sorted.find((b) => b.feet >= end);
|
|
11398
|
+
out.push(band ? band.color : color);
|
|
11399
|
+
}
|
|
11400
|
+
return out;
|
|
11401
|
+
}
|
|
11402
|
+
var LABEL_OFFSET_Y = 18;
|
|
11403
|
+
function drawPath(ctx, m, opts = {}) {
|
|
11404
|
+
const first = m.points[0];
|
|
11405
|
+
if (first === void 0) return;
|
|
11406
|
+
ctx.save();
|
|
11407
|
+
if (opts.alpha !== void 0) ctx.globalAlpha = opts.alpha;
|
|
11408
|
+
ctx.lineWidth = 2;
|
|
11409
|
+
let prev = first;
|
|
11410
|
+
for (let i = 1; i < m.points.length; i++) {
|
|
11411
|
+
const next = m.points[i];
|
|
11412
|
+
if (next === void 0) break;
|
|
11413
|
+
ctx.strokeStyle = m.segmentColors[i - 1] ?? m.color;
|
|
11414
|
+
ctx.setLineDash([8, 4]);
|
|
11415
|
+
ctx.beginPath();
|
|
11416
|
+
ctx.moveTo(prev.x, prev.y);
|
|
11417
|
+
ctx.lineTo(next.x, next.y);
|
|
11418
|
+
ctx.stroke();
|
|
11419
|
+
prev = next;
|
|
11420
|
+
}
|
|
11421
|
+
ctx.setLineDash([]);
|
|
11422
|
+
ctx.fillStyle = m.color;
|
|
11423
|
+
for (const p of m.points) {
|
|
11424
|
+
ctx.beginPath();
|
|
11425
|
+
ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);
|
|
11426
|
+
ctx.fill();
|
|
11427
|
+
}
|
|
11428
|
+
if (m.points.length >= 2) {
|
|
11429
|
+
const last = prev;
|
|
11430
|
+
const label = formatMeasureLabel(m.feet);
|
|
11431
|
+
ctx.font = "14px sans-serif";
|
|
11432
|
+
const metrics = ctx.measureText(label);
|
|
11433
|
+
const padX = 6;
|
|
11434
|
+
const padY = 4;
|
|
11435
|
+
const textH = 14;
|
|
11436
|
+
const cx = last.x;
|
|
11437
|
+
const cy = last.y - LABEL_OFFSET_Y;
|
|
11438
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.75)";
|
|
11439
|
+
ctx.beginPath();
|
|
11440
|
+
ctx.roundRect(
|
|
11441
|
+
cx - metrics.width / 2 - padX,
|
|
11442
|
+
cy - textH / 2 - padY,
|
|
11443
|
+
metrics.width + padX * 2,
|
|
11444
|
+
textH + padY * 2,
|
|
11445
|
+
4
|
|
11446
|
+
);
|
|
11447
|
+
ctx.fill();
|
|
11448
|
+
ctx.fillStyle = "#FFFFFF";
|
|
11449
|
+
ctx.textAlign = "center";
|
|
11450
|
+
ctx.textBaseline = "middle";
|
|
11451
|
+
ctx.fillText(label, cx, cy);
|
|
11452
|
+
}
|
|
11453
|
+
ctx.restore();
|
|
11454
|
+
}
|
|
11455
|
+
|
|
11456
|
+
// src/canvas/remote-path-overlay.ts
|
|
11457
|
+
var PATH_PRESENCE_KIND = "path";
|
|
11458
|
+
var PATH_PRESENCE_MAX_POINTS = 256;
|
|
11459
|
+
var MAX_COLOR_LENGTH = 64;
|
|
11460
|
+
var EPS = 1e-6;
|
|
11461
|
+
function isFinitePoint3(value) {
|
|
11462
|
+
if (typeof value !== "object" || value === null) return false;
|
|
11463
|
+
const point = value;
|
|
11464
|
+
return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
|
|
11465
|
+
}
|
|
11466
|
+
function isPathPresence(data) {
|
|
11467
|
+
if (typeof data !== "object" || data === null) return false;
|
|
11468
|
+
const payload = data;
|
|
11469
|
+
if (payload.kind !== PATH_PRESENCE_KIND) return false;
|
|
11470
|
+
if ("cleared" in payload) return payload.cleared === true;
|
|
11471
|
+
if (!Array.isArray(payload.points)) return false;
|
|
11472
|
+
if (payload.points.length === 0 || payload.points.length > PATH_PRESENCE_MAX_POINTS) return false;
|
|
11473
|
+
for (const point of payload.points) {
|
|
11474
|
+
if (!isFinitePoint3(point)) return false;
|
|
11475
|
+
}
|
|
11476
|
+
if (!Array.isArray(payload.segmentColors)) return false;
|
|
11477
|
+
if (payload.segmentColors.length !== payload.points.length - 1) return false;
|
|
11478
|
+
for (const color of payload.segmentColors) {
|
|
11479
|
+
if (typeof color !== "string" || color.length > MAX_COLOR_LENGTH) return false;
|
|
11480
|
+
}
|
|
11481
|
+
if (typeof payload.feet !== "number" || !Number.isFinite(payload.feet)) return false;
|
|
11482
|
+
if (payload.color !== void 0) {
|
|
11483
|
+
if (typeof payload.color !== "string" || payload.color.length > MAX_COLOR_LENGTH) return false;
|
|
11484
|
+
}
|
|
11485
|
+
return true;
|
|
11486
|
+
}
|
|
11487
|
+
function samePoint(a, b) {
|
|
11488
|
+
return Math.abs(a.x - b.x) < EPS && Math.abs(a.y - b.y) < EPS;
|
|
11489
|
+
}
|
|
11490
|
+
function toPathPresence(emission) {
|
|
11491
|
+
if (emission === null) return { kind: PATH_PRESENCE_KIND, cleared: true };
|
|
11492
|
+
const points = emission.waypoints.map((point) => ({ x: point.x, y: point.y }));
|
|
11493
|
+
const last = points[points.length - 1];
|
|
11494
|
+
const cursor = emission.cursor;
|
|
11495
|
+
if (cursor && (!last || !samePoint(cursor, last))) points.push({ x: cursor.x, y: cursor.y });
|
|
11496
|
+
const cumulativeFeet = [0];
|
|
11497
|
+
let total = 0;
|
|
11498
|
+
for (let i = 1; i < points.length; i++) {
|
|
11499
|
+
total += emission.segments[i - 1]?.feet ?? 0;
|
|
11500
|
+
cumulativeFeet.push(total);
|
|
11501
|
+
}
|
|
11502
|
+
return {
|
|
11503
|
+
kind: PATH_PRESENCE_KIND,
|
|
11504
|
+
points,
|
|
11505
|
+
segmentColors: resolveSegmentColors(cumulativeFeet, emission.rangeBands, emission.color),
|
|
11506
|
+
feet: emission.totalFeet,
|
|
11507
|
+
color: emission.color
|
|
11508
|
+
};
|
|
11509
|
+
}
|
|
11510
|
+
var DEFAULT_COLOR4 = "#FF5722";
|
|
11511
|
+
var RemotePathOverlay = class {
|
|
11512
|
+
color;
|
|
11513
|
+
overlay;
|
|
11514
|
+
constructor(host, options = {}) {
|
|
11515
|
+
this.color = options.color ?? DEFAULT_COLOR4;
|
|
11516
|
+
this.overlay = new LingerOverlay(
|
|
11517
|
+
host,
|
|
11518
|
+
options,
|
|
11519
|
+
(ctx, entry, alpha) => drawPath(ctx, entry, { alpha }),
|
|
11520
|
+
() => this.now()
|
|
11521
|
+
);
|
|
11522
|
+
}
|
|
11523
|
+
now() {
|
|
11524
|
+
return performance.now();
|
|
11525
|
+
}
|
|
11526
|
+
/**
|
|
11527
|
+
* Applies a presence payload from `sender` (any opaque per-sender key, e.g.
|
|
11528
|
+
* the envelope `from`). Non-path or malformed payloads are ignored and
|
|
11529
|
+
* reported as `false`, so hosts can feed every presence frame through.
|
|
11530
|
+
*/
|
|
11531
|
+
apply(sender, data) {
|
|
11532
|
+
if (this.overlay.disposed || !isPathPresence(data)) return false;
|
|
11533
|
+
if ("cleared" in data) {
|
|
11534
|
+
this.overlay.linger(sender);
|
|
11535
|
+
return true;
|
|
11224
11536
|
}
|
|
11537
|
+
this.overlay.set(sender, {
|
|
11538
|
+
points: data.points,
|
|
11539
|
+
segmentColors: data.segmentColors,
|
|
11540
|
+
feet: data.feet,
|
|
11541
|
+
color: data.color ?? this.color
|
|
11542
|
+
});
|
|
11543
|
+
return true;
|
|
11544
|
+
}
|
|
11545
|
+
/** Removes a sender's path immediately (presence-leave/disconnect). */
|
|
11546
|
+
remove(sender) {
|
|
11547
|
+
this.overlay.remove(sender);
|
|
11548
|
+
}
|
|
11549
|
+
/** Removes every path immediately. */
|
|
11550
|
+
clear() {
|
|
11551
|
+
this.overlay.clear();
|
|
11552
|
+
}
|
|
11553
|
+
/** Number of senders with a visible (active or lingering) path. */
|
|
11554
|
+
get activeSenderCount() {
|
|
11555
|
+
return this.overlay.activeSenderCount;
|
|
11556
|
+
}
|
|
11557
|
+
/** Unregisters the overlay, cancels timers, stops animating. Idempotent. */
|
|
11558
|
+
dispose() {
|
|
11559
|
+
this.overlay.dispose();
|
|
11225
11560
|
}
|
|
11226
11561
|
};
|
|
11227
11562
|
|
|
11228
11563
|
// src/canvas/ping-input.ts
|
|
11229
11564
|
var DEFAULT_LONG_PRESS_MS = 600;
|
|
11230
11565
|
var DEFAULT_SLOP_PX = 8;
|
|
11231
|
-
var
|
|
11566
|
+
var DEFAULT_COLOR5 = "#ff3b30";
|
|
11232
11567
|
var DEFAULT_DURATION_MS2 = 1800;
|
|
11233
11568
|
var DEFAULT_RADIUS2 = 48;
|
|
11234
11569
|
var DEFAULT_MIN_INTERVAL_MS = 300;
|
|
@@ -11261,7 +11596,7 @@ var PingInput = class {
|
|
|
11261
11596
|
this.longPressEnabled = options.longPressEnabled ?? false;
|
|
11262
11597
|
this.longPressMs = options.longPressMs ?? DEFAULT_LONG_PRESS_MS;
|
|
11263
11598
|
this.slopPx = options.slopPx ?? DEFAULT_SLOP_PX;
|
|
11264
|
-
this.color = options.color ??
|
|
11599
|
+
this.color = options.color ?? DEFAULT_COLOR5;
|
|
11265
11600
|
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS2;
|
|
11266
11601
|
this.radius = options.radius ?? DEFAULT_RADIUS2;
|
|
11267
11602
|
this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS;
|
|
@@ -12597,7 +12932,8 @@ var SelectTool = class {
|
|
|
12597
12932
|
} else if (!ctx.smartGuides && ctx.gridType && "size" in el) {
|
|
12598
12933
|
const centerX = el.position.x + el.size.w / 2 + adjDx;
|
|
12599
12934
|
const centerY = el.position.y + el.size.h / 2 + adjDy;
|
|
12600
|
-
const
|
|
12935
|
+
const footprint = footprintFromSize(el.size, ctx.gridSize ?? 0);
|
|
12936
|
+
const snappedCenter = snapFootprintCenter({ x: centerX, y: centerY }, footprint, ctx);
|
|
12601
12937
|
ctx.store.update(id, {
|
|
12602
12938
|
position: {
|
|
12603
12939
|
x: snappedCenter.x - el.size.w / 2,
|
|
@@ -13175,15 +13511,15 @@ var ShapeTool = class {
|
|
|
13175
13511
|
}
|
|
13176
13512
|
onActivate(_ctx) {
|
|
13177
13513
|
if (typeof window !== "undefined") {
|
|
13178
|
-
window.addEventListener("keydown", this.
|
|
13179
|
-
window.addEventListener("keyup", this.
|
|
13514
|
+
window.addEventListener("keydown", this.trackShiftKeyDown);
|
|
13515
|
+
window.addEventListener("keyup", this.trackShiftKeyUp);
|
|
13180
13516
|
}
|
|
13181
13517
|
}
|
|
13182
13518
|
onDeactivate(_ctx) {
|
|
13183
13519
|
this.shiftHeld = false;
|
|
13184
13520
|
if (typeof window !== "undefined") {
|
|
13185
|
-
window.removeEventListener("keydown", this.
|
|
13186
|
-
window.removeEventListener("keyup", this.
|
|
13521
|
+
window.removeEventListener("keydown", this.trackShiftKeyDown);
|
|
13522
|
+
window.removeEventListener("keyup", this.trackShiftKeyUp);
|
|
13187
13523
|
}
|
|
13188
13524
|
}
|
|
13189
13525
|
onPointerDown(state, ctx) {
|
|
@@ -13276,10 +13612,13 @@ var ShapeTool = class {
|
|
|
13276
13612
|
snap(point, ctx) {
|
|
13277
13613
|
return smartSnap(point, ctx);
|
|
13278
13614
|
}
|
|
13279
|
-
onKeyDown
|
|
13615
|
+
// Deliberately NOT migrated to `Tool.onKeyDown`: that hook is gated by
|
|
13616
|
+
// editable-target and keyboard-scope checks, and a Shift release must be
|
|
13617
|
+
// observed wherever it happens or the constraint sticks after the key is up.
|
|
13618
|
+
trackShiftKeyDown = (e) => {
|
|
13280
13619
|
if (e.key === "Shift") this.shiftHeld = true;
|
|
13281
13620
|
};
|
|
13282
|
-
|
|
13621
|
+
trackShiftKeyUp = (e) => {
|
|
13283
13622
|
if (e.key === "Shift") this.shiftHeld = false;
|
|
13284
13623
|
};
|
|
13285
13624
|
};
|
|
@@ -13423,6 +13762,353 @@ var MeasureTool = class {
|
|
|
13423
13762
|
}
|
|
13424
13763
|
};
|
|
13425
13764
|
|
|
13765
|
+
// src/tools/path-tool.ts
|
|
13766
|
+
var EPS2 = 1e-6;
|
|
13767
|
+
var DEFAULT_COMMIT_TAP_RADIUS_PX = 12;
|
|
13768
|
+
function samePoint2(a, b) {
|
|
13769
|
+
return Math.abs(a.x - b.x) < EPS2 && Math.abs(a.y - b.y) < EPS2;
|
|
13770
|
+
}
|
|
13771
|
+
var PathTool = class {
|
|
13772
|
+
name = "path";
|
|
13773
|
+
feetPerCell;
|
|
13774
|
+
color;
|
|
13775
|
+
diagonalRule;
|
|
13776
|
+
footprintOption;
|
|
13777
|
+
rangeBands;
|
|
13778
|
+
commitTapRadiusPx;
|
|
13779
|
+
resolveStart;
|
|
13780
|
+
waypoints = [];
|
|
13781
|
+
cursor = null;
|
|
13782
|
+
anchorKey;
|
|
13783
|
+
footprint = 1;
|
|
13784
|
+
pointerDown = false;
|
|
13785
|
+
commitOnUp = false;
|
|
13786
|
+
// Grid state is captured at the opening pointer-down so a path measured on
|
|
13787
|
+
// one grid keeps that metric even if the host reconfigures mid-gesture.
|
|
13788
|
+
gridSize = 0;
|
|
13789
|
+
gridType;
|
|
13790
|
+
hexOrientation;
|
|
13791
|
+
snapEnabled = false;
|
|
13792
|
+
optionListeners = /* @__PURE__ */ new Set();
|
|
13793
|
+
pathListeners = /* @__PURE__ */ new Set();
|
|
13794
|
+
commitListeners = /* @__PURE__ */ new Set();
|
|
13795
|
+
emissionRafId = null;
|
|
13796
|
+
constructor(options = {}) {
|
|
13797
|
+
this.feetPerCell = options.feetPerCell ?? 5;
|
|
13798
|
+
this.color = options.color ?? "#FF5722";
|
|
13799
|
+
this.diagonalRule = options.diagonalRule ?? "euclidean";
|
|
13800
|
+
this.footprintOption = options.footprint ?? 1;
|
|
13801
|
+
this.rangeBands = options.rangeBands ?? [];
|
|
13802
|
+
this.commitTapRadiusPx = options.commitTapRadiusPx ?? DEFAULT_COMMIT_TAP_RADIUS_PX;
|
|
13803
|
+
this.resolveStart = options.resolveStart;
|
|
13804
|
+
}
|
|
13805
|
+
getOptions() {
|
|
13806
|
+
return {
|
|
13807
|
+
feetPerCell: this.feetPerCell,
|
|
13808
|
+
color: this.color,
|
|
13809
|
+
diagonalRule: this.diagonalRule,
|
|
13810
|
+
footprint: this.footprintOption,
|
|
13811
|
+
rangeBands: this.rangeBands,
|
|
13812
|
+
commitTapRadiusPx: this.commitTapRadiusPx,
|
|
13813
|
+
resolveStart: this.resolveStart
|
|
13814
|
+
};
|
|
13815
|
+
}
|
|
13816
|
+
setOptions(options) {
|
|
13817
|
+
if (options.feetPerCell !== void 0) this.feetPerCell = options.feetPerCell;
|
|
13818
|
+
if (options.color !== void 0) this.color = options.color;
|
|
13819
|
+
if (options.diagonalRule !== void 0) this.diagonalRule = options.diagonalRule;
|
|
13820
|
+
if (options.footprint !== void 0) this.footprintOption = options.footprint;
|
|
13821
|
+
if (options.rangeBands !== void 0) this.rangeBands = options.rangeBands;
|
|
13822
|
+
if (options.commitTapRadiusPx !== void 0) {
|
|
13823
|
+
this.commitTapRadiusPx = options.commitTapRadiusPx;
|
|
13824
|
+
}
|
|
13825
|
+
if (options.resolveStart !== void 0) this.resolveStart = options.resolveStart;
|
|
13826
|
+
this.notifyOptionsChange();
|
|
13827
|
+
}
|
|
13828
|
+
onOptionsChange(listener) {
|
|
13829
|
+
this.optionListeners.add(listener);
|
|
13830
|
+
return () => this.optionListeners.delete(listener);
|
|
13831
|
+
}
|
|
13832
|
+
/**
|
|
13833
|
+
* Subscribes to raf-coalesced path snapshots. While a path is open,
|
|
13834
|
+
* listeners receive at most one snapshot per animation frame carrying the
|
|
13835
|
+
* latest state; `null` is delivered synchronously when the path closes
|
|
13836
|
+
* (commit, cancel, or deactivate).
|
|
13837
|
+
*/
|
|
13838
|
+
onPath(listener) {
|
|
13839
|
+
this.pathListeners.add(listener);
|
|
13840
|
+
return () => this.pathListeners.delete(listener);
|
|
13841
|
+
}
|
|
13842
|
+
/**
|
|
13843
|
+
* Subscribes to finished paths. The emission carries `cursor: null` and the
|
|
13844
|
+
* final waypoints; applying the move (and recording history for it) is the
|
|
13845
|
+
* host's job — the tool has already forgotten the path by then.
|
|
13846
|
+
*/
|
|
13847
|
+
onCommit(listener) {
|
|
13848
|
+
this.commitListeners.add(listener);
|
|
13849
|
+
return () => this.commitListeners.delete(listener);
|
|
13850
|
+
}
|
|
13851
|
+
get isOpen() {
|
|
13852
|
+
return this.waypoints.length > 0;
|
|
13853
|
+
}
|
|
13854
|
+
/** Synchronous snapshot of the open path; `null` when no path is open. */
|
|
13855
|
+
getEmission() {
|
|
13856
|
+
if (!this.isOpen) return null;
|
|
13857
|
+
return this.buildEmission(this.cursor);
|
|
13858
|
+
}
|
|
13859
|
+
onPointerDown(state, ctx) {
|
|
13860
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
13861
|
+
if (!this.isOpen) {
|
|
13862
|
+
const anchor = this.resolveStart ? this.resolveStart(world, ctx) : { origin: world };
|
|
13863
|
+
if (!anchor) return;
|
|
13864
|
+
this.gridSize = ctx.gridSize ?? 0;
|
|
13865
|
+
this.gridType = ctx.gridType;
|
|
13866
|
+
this.hexOrientation = ctx.hexOrientation;
|
|
13867
|
+
this.snapEnabled = ctx.snapToGrid === true;
|
|
13868
|
+
this.footprint = anchor.footprint ?? this.footprintOption;
|
|
13869
|
+
const origin = this.snap(anchor.origin);
|
|
13870
|
+
this.waypoints = [origin];
|
|
13871
|
+
this.cursor = { ...origin };
|
|
13872
|
+
this.anchorKey = anchor.anchorKey;
|
|
13873
|
+
this.pointerDown = true;
|
|
13874
|
+
this.scheduleEmission();
|
|
13875
|
+
ctx.requestRender();
|
|
13876
|
+
return;
|
|
13877
|
+
}
|
|
13878
|
+
const point = this.snap(world);
|
|
13879
|
+
const last = this.lastWaypoint();
|
|
13880
|
+
if (last && this.withinCommitRadius(point, last, ctx)) {
|
|
13881
|
+
this.commitOnUp = true;
|
|
13882
|
+
this.cursor = { ...last };
|
|
13883
|
+
} else {
|
|
13884
|
+
this.cursor = point;
|
|
13885
|
+
}
|
|
13886
|
+
this.pointerDown = true;
|
|
13887
|
+
this.scheduleEmission();
|
|
13888
|
+
ctx.requestRender();
|
|
13889
|
+
}
|
|
13890
|
+
onPointerMove(state, ctx) {
|
|
13891
|
+
if (!this.isOpen) return;
|
|
13892
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
13893
|
+
const point = this.snap(world);
|
|
13894
|
+
const last = this.lastWaypoint();
|
|
13895
|
+
if (this.commitOnUp && last && this.withinCommitRadius(point, last, ctx)) {
|
|
13896
|
+
this.cursor = { ...last };
|
|
13897
|
+
} else {
|
|
13898
|
+
this.commitOnUp = false;
|
|
13899
|
+
this.cursor = point;
|
|
13900
|
+
}
|
|
13901
|
+
this.scheduleEmission();
|
|
13902
|
+
ctx.requestRender();
|
|
13903
|
+
}
|
|
13904
|
+
onPointerUp(_state, ctx) {
|
|
13905
|
+
if (!this.isOpen) return;
|
|
13906
|
+
this.pointerDown = false;
|
|
13907
|
+
if (this.commitOnUp) {
|
|
13908
|
+
this.commit(ctx);
|
|
13909
|
+
return;
|
|
13910
|
+
}
|
|
13911
|
+
const last = this.lastWaypoint();
|
|
13912
|
+
if (this.cursor && last && !samePoint2(this.cursor, last)) {
|
|
13913
|
+
this.waypoints.push({ ...this.cursor });
|
|
13914
|
+
}
|
|
13915
|
+
this.scheduleEmission();
|
|
13916
|
+
ctx.requestRender();
|
|
13917
|
+
}
|
|
13918
|
+
onHover(state, ctx) {
|
|
13919
|
+
if (!this.isOpen || this.pointerDown) return;
|
|
13920
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
13921
|
+
this.cursor = this.snap(world);
|
|
13922
|
+
this.scheduleEmission();
|
|
13923
|
+
ctx.requestRender();
|
|
13924
|
+
}
|
|
13925
|
+
/** A takeover (second pointer, platform cancel) abandons the path outright. */
|
|
13926
|
+
onPointerCancel(_state, ctx) {
|
|
13927
|
+
this.cancel(ctx);
|
|
13928
|
+
}
|
|
13929
|
+
onDeactivate(ctx) {
|
|
13930
|
+
this.cancel(ctx);
|
|
13931
|
+
}
|
|
13932
|
+
onKeyDown(event, ctx) {
|
|
13933
|
+
if (!this.isOpen) return false;
|
|
13934
|
+
if (event.key === "Enter") {
|
|
13935
|
+
this.commit(ctx);
|
|
13936
|
+
return true;
|
|
13937
|
+
}
|
|
13938
|
+
if (event.key === "Escape") {
|
|
13939
|
+
this.cancel(ctx);
|
|
13940
|
+
return true;
|
|
13941
|
+
}
|
|
13942
|
+
return false;
|
|
13943
|
+
}
|
|
13944
|
+
renderOverlay(ctx) {
|
|
13945
|
+
if (!this.isOpen) return;
|
|
13946
|
+
const { points, cumulative, total } = this.measure(this.cursor);
|
|
13947
|
+
const cumulativeFeet = cumulative.map((cells) => cells * this.feetPerCell);
|
|
13948
|
+
drawPath(ctx, {
|
|
13949
|
+
points,
|
|
13950
|
+
segmentColors: resolveSegmentColors(cumulativeFeet, this.rangeBands, this.color),
|
|
13951
|
+
color: this.color,
|
|
13952
|
+
feet: total * this.feetPerCell
|
|
13953
|
+
});
|
|
13954
|
+
}
|
|
13955
|
+
lastWaypoint() {
|
|
13956
|
+
return this.waypoints[this.waypoints.length - 1];
|
|
13957
|
+
}
|
|
13958
|
+
/**
|
|
13959
|
+
* Is `point` close enough to `last` to mean "finish here"? EXACT match always
|
|
13960
|
+
* qualifies. The screen-space tolerance is added ONLY when this path has no
|
|
13961
|
+
* active snapping grid: on a snapping grid (`gridType === 'square'`, or
|
|
13962
|
+
* `'hex'` with a captured orientation, and `gridSize > 0`) the adjacent
|
|
13963
|
+
* cell/hex centre can be as little as one grid cell away, and a radius
|
|
13964
|
+
* computed from CSS pixels has no relationship to that distance — at a
|
|
13965
|
+
* sufficiently zoomed-out camera the radius would swallow a whole neighbour
|
|
13966
|
+
* cell and turn a deliberate next-waypoint tap into an unwanted commit.
|
|
13967
|
+
* Snapping already makes a fingertip tap land exactly on the last waypoint,
|
|
13968
|
+
* so the tolerance is unnecessary there; it exists for the gridless and
|
|
13969
|
+
* `snapPoint`/identity paths, where a fingertip can never land on an exact
|
|
13970
|
+
* world point.
|
|
13971
|
+
*/
|
|
13972
|
+
withinCommitRadius(point, last, ctx) {
|
|
13973
|
+
if (samePoint2(point, last)) return true;
|
|
13974
|
+
if (this.hasSnappingGrid()) return false;
|
|
13975
|
+
const zoom = ctx.camera.zoom;
|
|
13976
|
+
if (!(zoom > 0)) return false;
|
|
13977
|
+
const radius = this.commitTapRadiusPx / zoom;
|
|
13978
|
+
if (radius <= 0) return false;
|
|
13979
|
+
const dx = point.x - last.x;
|
|
13980
|
+
const dy = point.y - last.y;
|
|
13981
|
+
return dx * dx + dy * dy <= radius * radius;
|
|
13982
|
+
}
|
|
13983
|
+
/**
|
|
13984
|
+
* True when this path's captured grid state snaps every waypoint onto a
|
|
13985
|
+
* cell/hex centre: a `square` grid, or a `hex` grid with a captured
|
|
13986
|
+
* orientation, both with a usable `gridSize`. Mirrors the unconditional
|
|
13987
|
+
* branches of `snap` below — NOT the `snapToGrid`-gated fallback, which
|
|
13988
|
+
* leaves waypoints unsnapped when the user turns snapping off.
|
|
13989
|
+
*/
|
|
13990
|
+
hasSnappingGrid() {
|
|
13991
|
+
if (!(this.gridSize > 0)) return false;
|
|
13992
|
+
if (this.gridType === "square") return true;
|
|
13993
|
+
return this.gridType === "hex" && this.hexOrientation !== void 0;
|
|
13994
|
+
}
|
|
13995
|
+
/**
|
|
13996
|
+
* Snapping to cell or hex centres is UNCONDITIONAL for a `square` grid, or a
|
|
13997
|
+
* `hex` grid WITH a captured orientation: a movement path measures in cells,
|
|
13998
|
+
* so an unsnapped waypoint would report a distance the grid does not agree
|
|
13999
|
+
* with. A `hex` grid WITHOUT an orientation falls through to the
|
|
14000
|
+
* `snapToGrid`-gated `snapPoint` (intersection) branch below, same as the
|
|
14001
|
+
* gridType-less case — unlike `smartSnap`, which is off entirely when the
|
|
14002
|
+
* user turns snapping off.
|
|
14003
|
+
*/
|
|
14004
|
+
snap(point) {
|
|
14005
|
+
if (this.gridSize <= 0) return point;
|
|
14006
|
+
if (this.gridType === "hex" && this.hexOrientation) {
|
|
14007
|
+
return snapToHexCenter(point, this.gridSize, this.hexOrientation);
|
|
14008
|
+
}
|
|
14009
|
+
if (this.gridType === "square") {
|
|
14010
|
+
return snapToCellCenter(point, this.gridSize, this.footprint);
|
|
14011
|
+
}
|
|
14012
|
+
if (this.snapEnabled) {
|
|
14013
|
+
return snapPoint(point, this.gridSize);
|
|
14014
|
+
}
|
|
14015
|
+
return point;
|
|
14016
|
+
}
|
|
14017
|
+
/** The measured polyline: waypoints, plus the cursor when it adds a leg. */
|
|
14018
|
+
measure(cursor) {
|
|
14019
|
+
const points = this.waypoints.map((p) => ({ ...p }));
|
|
14020
|
+
const last = points[points.length - 1];
|
|
14021
|
+
if (cursor && (!last || !samePoint2(cursor, last))) points.push({ ...cursor });
|
|
14022
|
+
const { total, cumulative } = pathDistanceCells(points, {
|
|
14023
|
+
gridSize: this.gridSize,
|
|
14024
|
+
gridType: this.gridType,
|
|
14025
|
+
hexOrientation: this.hexOrientation,
|
|
14026
|
+
diagonalRule: this.diagonalRule
|
|
14027
|
+
});
|
|
14028
|
+
return { points, cumulative, total };
|
|
14029
|
+
}
|
|
14030
|
+
buildEmission(cursor) {
|
|
14031
|
+
const { cumulative, total } = this.measure(cursor);
|
|
14032
|
+
const segments = [];
|
|
14033
|
+
for (let i = 1; i < cumulative.length; i++) {
|
|
14034
|
+
const cells = (cumulative[i] ?? 0) - (cumulative[i - 1] ?? 0);
|
|
14035
|
+
segments.push({ cells, feet: cells * this.feetPerCell });
|
|
14036
|
+
}
|
|
14037
|
+
return {
|
|
14038
|
+
anchorKey: this.anchorKey,
|
|
14039
|
+
waypoints: this.waypoints.map((p) => ({ ...p })),
|
|
14040
|
+
cursor: cursor ? { ...cursor } : null,
|
|
14041
|
+
segments,
|
|
14042
|
+
totalCells: total,
|
|
14043
|
+
totalFeet: total * this.feetPerCell,
|
|
14044
|
+
color: this.color,
|
|
14045
|
+
rangeBands: this.rangeBands
|
|
14046
|
+
};
|
|
14047
|
+
}
|
|
14048
|
+
commit(ctx) {
|
|
14049
|
+
if (this.waypoints.length < 2) {
|
|
14050
|
+
this.cancel(ctx);
|
|
14051
|
+
return;
|
|
14052
|
+
}
|
|
14053
|
+
const emission = this.buildEmission(null);
|
|
14054
|
+
this.reset();
|
|
14055
|
+
this.emitCommit(emission);
|
|
14056
|
+
this.emitClear();
|
|
14057
|
+
ctx.requestRender();
|
|
14058
|
+
}
|
|
14059
|
+
cancel(ctx) {
|
|
14060
|
+
if (!this.isOpen) return;
|
|
14061
|
+
this.reset();
|
|
14062
|
+
this.emitClear();
|
|
14063
|
+
ctx.requestRender();
|
|
14064
|
+
}
|
|
14065
|
+
reset() {
|
|
14066
|
+
this.waypoints = [];
|
|
14067
|
+
this.cursor = null;
|
|
14068
|
+
this.anchorKey = void 0;
|
|
14069
|
+
this.footprint = this.footprintOption;
|
|
14070
|
+
this.pointerDown = false;
|
|
14071
|
+
this.commitOnUp = false;
|
|
14072
|
+
}
|
|
14073
|
+
notifyOptionsChange() {
|
|
14074
|
+
for (const listener of this.optionListeners) listener();
|
|
14075
|
+
}
|
|
14076
|
+
scheduleEmission() {
|
|
14077
|
+
if (this.pathListeners.size === 0) return;
|
|
14078
|
+
if (this.emissionRafId !== null) return;
|
|
14079
|
+
this.emissionRafId = requestAnimationFrame(() => {
|
|
14080
|
+
this.emissionRafId = null;
|
|
14081
|
+
const emission = this.getEmission();
|
|
14082
|
+
if (!emission) return;
|
|
14083
|
+
this.emitPath(emission);
|
|
14084
|
+
});
|
|
14085
|
+
}
|
|
14086
|
+
emitClear() {
|
|
14087
|
+
if (this.emissionRafId !== null) {
|
|
14088
|
+
cancelAnimationFrame(this.emissionRafId);
|
|
14089
|
+
this.emissionRafId = null;
|
|
14090
|
+
}
|
|
14091
|
+
if (this.pathListeners.size === 0) return;
|
|
14092
|
+
this.emitPath(null);
|
|
14093
|
+
}
|
|
14094
|
+
emitPath(emission) {
|
|
14095
|
+
for (const listener of this.pathListeners) {
|
|
14096
|
+
try {
|
|
14097
|
+
listener(emission);
|
|
14098
|
+
} catch {
|
|
14099
|
+
}
|
|
14100
|
+
}
|
|
14101
|
+
}
|
|
14102
|
+
emitCommit(emission) {
|
|
14103
|
+
for (const listener of this.commitListeners) {
|
|
14104
|
+
try {
|
|
14105
|
+
listener(emission);
|
|
14106
|
+
} catch {
|
|
14107
|
+
}
|
|
14108
|
+
}
|
|
14109
|
+
}
|
|
14110
|
+
};
|
|
14111
|
+
|
|
13426
14112
|
// src/tools/template-tool.ts
|
|
13427
14113
|
var MIN_RECT_WIDTH = 20;
|
|
13428
14114
|
function defaultRectWidth(radius, scaleUnit) {
|
|
@@ -13716,7 +14402,7 @@ var TemplateTool = class {
|
|
|
13716
14402
|
};
|
|
13717
14403
|
|
|
13718
14404
|
// src/tools/laser-tool.ts
|
|
13719
|
-
var
|
|
14405
|
+
var DEFAULT_COLOR6 = "#ff3b30";
|
|
13720
14406
|
var DEFAULT_WIDTH3 = 4;
|
|
13721
14407
|
var DEFAULT_FADE_MS3 = 1200;
|
|
13722
14408
|
var LaserTool = class {
|
|
@@ -13732,7 +14418,7 @@ var LaserTool = class {
|
|
|
13732
14418
|
pendingEmission = [];
|
|
13733
14419
|
constructor(options = {}) {
|
|
13734
14420
|
this.name = options.name ?? "laser";
|
|
13735
|
-
this.color = options.color ??
|
|
14421
|
+
this.color = options.color ?? DEFAULT_COLOR6;
|
|
13736
14422
|
this.width = options.width ?? DEFAULT_WIDTH3;
|
|
13737
14423
|
this.fadeMs = options.fadeMs ?? DEFAULT_FADE_MS3;
|
|
13738
14424
|
}
|
|
@@ -13860,7 +14546,7 @@ var LaserTool = class {
|
|
|
13860
14546
|
};
|
|
13861
14547
|
|
|
13862
14548
|
// src/tools/ping-tool.ts
|
|
13863
|
-
var
|
|
14549
|
+
var DEFAULT_COLOR7 = "#ff3b30";
|
|
13864
14550
|
var DEFAULT_DURATION_MS4 = 1800;
|
|
13865
14551
|
var DEFAULT_RADIUS4 = 48;
|
|
13866
14552
|
var DEFAULT_MIN_INTERVAL_MS2 = 300;
|
|
@@ -13877,7 +14563,7 @@ var PingTool = class {
|
|
|
13877
14563
|
pingListeners = /* @__PURE__ */ new Set();
|
|
13878
14564
|
constructor(options = {}) {
|
|
13879
14565
|
this.name = options.name ?? "ping";
|
|
13880
|
-
this.color = options.color ??
|
|
14566
|
+
this.color = options.color ?? DEFAULT_COLOR7;
|
|
13881
14567
|
this.durationMs = options.durationMs ?? DEFAULT_DURATION_MS4;
|
|
13882
14568
|
this.radius = options.radius ?? DEFAULT_RADIUS4;
|
|
13883
14569
|
this.minIntervalMs = options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS2;
|
|
@@ -13980,7 +14666,7 @@ var PingTool = class {
|
|
|
13980
14666
|
};
|
|
13981
14667
|
|
|
13982
14668
|
// src/index.ts
|
|
13983
|
-
var VERSION = "0.
|
|
14669
|
+
var VERSION = "0.64.0";
|
|
13984
14670
|
export {
|
|
13985
14671
|
ArrowTool,
|
|
13986
14672
|
AutoSave,
|
|
@@ -14006,13 +14692,17 @@ export {
|
|
|
14006
14692
|
MemoryAdapter,
|
|
14007
14693
|
MinimapController,
|
|
14008
14694
|
NoteTool,
|
|
14695
|
+
PATH_PRESENCE_KIND,
|
|
14696
|
+
PATH_PRESENCE_MAX_POINTS,
|
|
14009
14697
|
PING_PRESENCE_KIND,
|
|
14698
|
+
PathTool,
|
|
14010
14699
|
PencilTool,
|
|
14011
14700
|
PingInput,
|
|
14012
14701
|
PingTool,
|
|
14013
14702
|
RemoteFocusReceiver,
|
|
14014
14703
|
RemoteLaserOverlay,
|
|
14015
14704
|
RemoteMeasureOverlay,
|
|
14705
|
+
RemotePathOverlay,
|
|
14016
14706
|
RemotePingOverlay,
|
|
14017
14707
|
SelectTool,
|
|
14018
14708
|
ShapeTool,
|
|
@@ -14040,6 +14730,7 @@ export {
|
|
|
14040
14730
|
exportImage,
|
|
14041
14731
|
exportSvg,
|
|
14042
14732
|
fitZoomForView,
|
|
14733
|
+
footprintFromSize,
|
|
14043
14734
|
getActiveFormats,
|
|
14044
14735
|
getArrowBounds,
|
|
14045
14736
|
getArrowControlPoint,
|
|
@@ -14055,20 +14746,26 @@ export {
|
|
|
14055
14746
|
getHexCellsInRectangle,
|
|
14056
14747
|
getHexCellsInSquare,
|
|
14057
14748
|
getHexDistance,
|
|
14749
|
+
gridDistanceCells,
|
|
14058
14750
|
isFocusPresence,
|
|
14059
14751
|
isLaserTrailPresence,
|
|
14060
14752
|
isMeasurePresence,
|
|
14061
14753
|
isNearBezier,
|
|
14754
|
+
isPathPresence,
|
|
14062
14755
|
isPingPresence,
|
|
14756
|
+
pathDistanceCells,
|
|
14063
14757
|
resolveHtmlRouting,
|
|
14064
14758
|
setFontSize,
|
|
14065
14759
|
smartSnap,
|
|
14760
|
+
snapFootprintCenter,
|
|
14066
14761
|
snapPoint,
|
|
14762
|
+
snapToCellCenter,
|
|
14067
14763
|
snapToHexCenter,
|
|
14068
14764
|
styleToPatch,
|
|
14069
14765
|
toFocusPresence,
|
|
14070
14766
|
toLaserTrailPresence,
|
|
14071
14767
|
toMeasurePresence,
|
|
14768
|
+
toPathPresence,
|
|
14072
14769
|
toPingPresence,
|
|
14073
14770
|
toggleBold,
|
|
14074
14771
|
toggleItalic,
|