@oh-just-another/state 0.59.0 → 0.60.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/CHANGELOG.md +91 -0
- package/README.md +2 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/actions/actionArrange.js +4 -4
- package/dist/actions/actionArrange.js.map +1 -1
- package/dist/actions/actionClipboard.d.ts.map +1 -1
- package/dist/actions/actionClipboard.js +5 -0
- package/dist/actions/actionClipboard.js.map +1 -1
- package/dist/actions/actionKeyboard.d.ts.map +1 -1
- package/dist/actions/actionKeyboard.js +77 -9
- package/dist/actions/actionKeyboard.js.map +1 -1
- package/dist/actions/actionMode.d.ts +4 -0
- package/dist/actions/actionMode.d.ts.map +1 -1
- package/dist/actions/actionMode.js +54 -0
- package/dist/actions/actionMode.js.map +1 -1
- package/dist/actions/actionSelection.d.ts.map +1 -1
- package/dist/actions/actionSelection.js +1 -0
- package/dist/actions/actionSelection.js.map +1 -1
- package/dist/actions/actionView.d.ts +6 -0
- package/dist/actions/actionView.d.ts.map +1 -1
- package/dist/actions/actionView.js +19 -1
- package/dist/actions/actionView.js.map +1 -1
- package/dist/actions/actionZoom.d.ts.map +1 -1
- package/dist/actions/actionZoom.js +5 -0
- package/dist/actions/actionZoom.js.map +1 -1
- package/dist/actions/index.d.ts +3 -3
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +3 -3
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/registry.d.ts.map +1 -1
- package/dist/actions/registry.js +13 -0
- package/dist/actions/registry.js.map +1 -1
- package/dist/actions/types.d.ts +9 -0
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js.map +1 -1
- package/dist/clipboard.d.ts +0 -14
- package/dist/clipboard.d.ts.map +1 -1
- package/dist/clipboard.js +1 -1
- package/dist/clipboard.js.map +1 -1
- package/dist/constants.d.ts +160 -18
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +162 -2
- package/dist/constants.js.map +1 -1
- package/dist/dom-events.d.ts +1 -2
- package/dist/dom-events.d.ts.map +1 -1
- package/dist/dom-events.js.map +1 -1
- package/dist/editor/animation-scene.d.ts +15 -0
- package/dist/editor/animation-scene.d.ts.map +1 -1
- package/dist/editor/animation-scene.js +123 -0
- package/dist/editor/animation-scene.js.map +1 -1
- package/dist/editor/applies/create.d.ts +0 -6
- package/dist/editor/applies/create.d.ts.map +1 -1
- package/dist/editor/applies/create.js +1 -1
- package/dist/editor/applies/create.js.map +1 -1
- package/dist/editor/applies/link-move.d.ts +0 -8
- package/dist/editor/applies/link-move.d.ts.map +1 -1
- package/dist/editor/applies/link-move.js +1 -1
- package/dist/editor/applies/link-move.js.map +1 -1
- package/dist/editor/interaction-state.d.ts +184 -0
- package/dist/editor/interaction-state.d.ts.map +1 -0
- package/dist/editor/interaction-state.js +139 -0
- package/dist/editor/interaction-state.js.map +1 -0
- package/dist/editor/link-handle-drag.d.ts +152 -0
- package/dist/editor/link-handle-drag.d.ts.map +1 -0
- package/dist/editor/link-handle-drag.js +300 -0
- package/dist/editor/link-handle-drag.js.map +1 -0
- package/dist/editor/pointer-binding.d.ts +9 -11
- package/dist/editor/pointer-binding.d.ts.map +1 -1
- package/dist/editor/pointer-binding.js +1185 -901
- package/dist/editor/pointer-binding.js.map +1 -1
- package/dist/editor/public/brush.d.ts +31 -12
- package/dist/editor/public/brush.d.ts.map +1 -1
- package/dist/editor/public/brush.js +66 -17
- package/dist/editor/public/brush.js.map +1 -1
- package/dist/editor/public/cursor.d.ts +1 -15
- package/dist/editor/public/cursor.d.ts.map +1 -1
- package/dist/editor/public/cursor.js +14 -3
- package/dist/editor/public/cursor.js.map +1 -1
- package/dist/editor/public/eraser.d.ts +55 -0
- package/dist/editor/public/eraser.d.ts.map +1 -0
- package/dist/editor/public/eraser.js +65 -0
- package/dist/editor/public/eraser.js.map +1 -0
- package/dist/editor/public/laser.d.ts +38 -0
- package/dist/editor/public/laser.d.ts.map +1 -0
- package/dist/editor/public/laser.js +42 -0
- package/dist/editor/public/laser.js.map +1 -0
- package/dist/editor/public/placement.d.ts.map +1 -1
- package/dist/editor/public/placement.js +9 -1
- package/dist/editor/public/placement.js.map +1 -1
- package/dist/editor/public/selection-ops.d.ts +12 -0
- package/dist/editor/public/selection-ops.d.ts.map +1 -1
- package/dist/editor/public/selection-ops.js +19 -1
- package/dist/editor/public/selection-ops.js.map +1 -1
- package/dist/editor/public/stroke-eraser-coverage.d.ts +37 -0
- package/dist/editor/public/stroke-eraser-coverage.d.ts.map +1 -0
- package/dist/editor/public/stroke-eraser-coverage.js +168 -0
- package/dist/editor/public/stroke-eraser-coverage.js.map +1 -0
- package/dist/editor/public/stroke-eraser.d.ts +50 -0
- package/dist/editor/public/stroke-eraser.d.ts.map +1 -0
- package/dist/editor/public/stroke-eraser.js +176 -0
- package/dist/editor/public/stroke-eraser.js.map +1 -0
- package/dist/editor/public/stroke-smoothing.d.ts +21 -0
- package/dist/editor/public/stroke-smoothing.d.ts.map +1 -0
- package/dist/editor/public/stroke-smoothing.js +37 -0
- package/dist/editor/public/stroke-smoothing.js.map +1 -0
- package/dist/editor/public/tool-ops.d.ts +130 -0
- package/dist/editor/public/tool-ops.d.ts.map +1 -0
- package/dist/editor/public/tool-ops.js +392 -0
- package/dist/editor/public/tool-ops.js.map +1 -0
- package/dist/editor/public/z-order.d.ts +1 -12
- package/dist/editor/public/z-order.d.ts.map +1 -1
- package/dist/editor/public/z-order.js +1 -1
- package/dist/editor/public/z-order.js.map +1 -1
- package/dist/editor/public/zoom-pan.d.ts +9 -0
- package/dist/editor/public/zoom-pan.d.ts.map +1 -1
- package/dist/editor/public/zoom-pan.js +31 -0
- package/dist/editor/public/zoom-pan.js.map +1 -1
- package/dist/editor/render-orchestrator.d.ts +177 -5
- package/dist/editor/render-orchestrator.d.ts.map +1 -1
- package/dist/editor/render-orchestrator.js +517 -351
- package/dist/editor/render-orchestrator.js.map +1 -1
- package/dist/editor/text-edit.d.ts +136 -0
- package/dist/editor/text-edit.d.ts.map +1 -0
- package/dist/editor/text-edit.js +328 -0
- package/dist/editor/text-edit.js.map +1 -0
- package/dist/editor.d.ts +504 -239
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +1557 -706
- package/dist/editor.js.map +1 -1
- package/dist/handle.d.ts +0 -2
- package/dist/handle.d.ts.map +1 -1
- package/dist/handle.js +0 -6
- package/dist/handle.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/modes.d.ts +15 -1
- package/dist/modes.d.ts.map +1 -1
- package/dist/modes.js.map +1 -1
- package/dist/overlay.d.ts +91 -2
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +450 -93
- package/dist/overlay.js.map +1 -1
- package/dist/search.d.ts +37 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +45 -0
- package/dist/search.js.map +1 -0
- package/package.json +4 -4
package/dist/editor.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Actor } from "xstate";
|
|
2
2
|
import type { Bounds, FileId, ElementId, Vec2 } from "@oh-just-another/types";
|
|
3
3
|
import type { SpatialGrid } from "@oh-just-another/scene";
|
|
4
|
-
import { type FractionalIndex, type BrushPoint, type Link, type Patch, type Scene, type Element, type GridStyle, type TextStyle } from "@oh-just-another/scene";
|
|
5
|
-
import { type AnnotationId, type CommentId, type LinkId, type LayerId } from "@oh-just-another/types";
|
|
4
|
+
import { type FractionalIndex, type BrushPoint, type Link, type Patch, type Scene, type Element, type GridStyle, type ImageCrop, type TextStyle } from "@oh-just-another/scene";
|
|
5
|
+
import { type AnnotationId, type Color, type CommentId, type LinkId, type LayerId } from "@oh-just-another/types";
|
|
6
6
|
import { ElementCache, type RenderTarget, type TextShaper, type Rasterizer } from "@oh-just-another/renderer-core";
|
|
7
7
|
import { type HistoryOptions, type HistoryProvider, type TransactionHandle } from "@oh-just-another/history";
|
|
8
8
|
import { type FileDropHandler } from "./file-drop.js";
|
|
@@ -12,8 +12,12 @@ import type { EditorEvents } from "./editor-events.js";
|
|
|
12
12
|
import { GifPlaybackController } from "./editor/gif-playback.js";
|
|
13
13
|
import { LongPressController } from "./editor/long-press.js";
|
|
14
14
|
import { PinchController } from "./editor/pinch.js";
|
|
15
|
-
import { type
|
|
15
|
+
import { InteractionState, type AnnotationDrag, type ContainerHover, type EdgePreview, type GroupResizeOrigin, type HoveredLinkTarget, type LinkDragFromAnchor, type PanGesture, type RotateGestureOrigin } from "./editor/interaction-state.js";
|
|
16
|
+
import { type BrushSettings, type BrushStrokeState } from "./editor/public/brush.js";
|
|
17
|
+
import { type EraseStrokeState } from "./editor/public/eraser.js";
|
|
18
|
+
import { type LaserStroke } from "./editor/public/laser.js";
|
|
16
19
|
import type { CursorRole, CursorSpec } from "./editor/public/cursor.js";
|
|
20
|
+
import { type ConvertTarget, type CropHandle, type SpawnDirection } from "./editor/public/tool-ops.js";
|
|
17
21
|
import { type AlignEdge, type DistributeAxis, type FlipAxis } from "./editor/applies/arrange.js";
|
|
18
22
|
import { type PeerCursor, type PeerSelection } from "./overlay.js";
|
|
19
23
|
import * as Selection from "./selection.js";
|
|
@@ -53,6 +57,13 @@ export interface EditorOptions {
|
|
|
53
57
|
readonly onAfterRender?: () => void;
|
|
54
58
|
readonly initialScene: Scene;
|
|
55
59
|
readonly initialMode?: Mode;
|
|
60
|
+
/**
|
|
61
|
+
* Start the editor in read-only / view mode. Pointer edits (create /
|
|
62
|
+
* move / resize / rotate / delete) and non-`viewMode` actions are gated;
|
|
63
|
+
* pan / zoom / select still work. Toggle at runtime via
|
|
64
|
+
* {@link Editor.setReadOnly}. Defaults to `false`.
|
|
65
|
+
*/
|
|
66
|
+
readonly readOnly?: boolean;
|
|
56
67
|
/**
|
|
57
68
|
* Pre-existing history backend, or options for the default
|
|
58
69
|
* `History` (linear stack). Any `HistoryProvider` implementation
|
|
@@ -127,6 +138,15 @@ export type TileComposeFn = (scene: Scene, mainTarget: RenderTarget, options: {
|
|
|
127
138
|
after: Bounds | null;
|
|
128
139
|
}>;
|
|
129
140
|
readonly zoomBucket: number;
|
|
141
|
+
/**
|
|
142
|
+
* Persistent spatial index over the scene's current element world-AABBs,
|
|
143
|
+
* when the editor maintains one (large scenes, shared with the hit-test
|
|
144
|
+
* path). A tile compositor that supports it (`renderViaTiles`) queries the
|
|
145
|
+
* index for per-tile element selection instead of scanning every shape in
|
|
146
|
+
* every layer. Omitted for small scenes; compositors must fall back to a
|
|
147
|
+
* full scan when absent.
|
|
148
|
+
*/
|
|
149
|
+
readonly index?: SpatialGrid;
|
|
130
150
|
}) => void;
|
|
131
151
|
/**
|
|
132
152
|
* Top-level interaction controller. Owns the scene + selection state, wires
|
|
@@ -155,7 +175,15 @@ export declare class Editor {
|
|
|
155
175
|
* View-only — never persisted or recorded in history.
|
|
156
176
|
*/
|
|
157
177
|
debugHitZones: boolean;
|
|
158
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Read-only / view mode. When true the pointer paths that create,
|
|
180
|
+
* move, resize, rotate or delete are gated (pan / zoom / select stay
|
|
181
|
+
* live) and the action registry only runs actions flagged
|
|
182
|
+
* `viewMode`. View-only — never persisted or recorded in history.
|
|
183
|
+
* Read via {@link readOnly}; flip via {@link setReadOnly}.
|
|
184
|
+
*/
|
|
185
|
+
private _readOnly;
|
|
186
|
+
actor: Actor<typeof interactionMachine>;
|
|
159
187
|
private readonly listeners;
|
|
160
188
|
/**
|
|
161
189
|
* Typed event surface. Specific events (`mode`, `selection`,
|
|
@@ -178,74 +206,31 @@ export declare class Editor {
|
|
|
178
206
|
_scene: Scene;
|
|
179
207
|
_selection: Selection.Selection;
|
|
180
208
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
209
|
+
* Ephemeral interaction / gesture state (previews, gesture origins,
|
|
210
|
+
* transient modifiers). Single source of truth for the short-lived fields
|
|
211
|
+
* the pointer handlers, render orchestrator and container-ops read/write
|
|
212
|
+
* while a gesture is in flight. The public fields below delegate to it so
|
|
213
|
+
* external writers keep referencing `editor.<field>` unchanged.
|
|
185
214
|
*/
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
moved: boolean;
|
|
191
|
-
} | null;
|
|
215
|
+
readonly interaction: InteractionState;
|
|
216
|
+
/** Snapshot of an in-progress annotation-pin drag. */
|
|
217
|
+
get annotationDrag(): AnnotationDrag | null;
|
|
218
|
+
set annotationDrag(v: AnnotationDrag | null);
|
|
192
219
|
/** Live preview while drawing a new shape; null when not drawing. */
|
|
193
|
-
drawingPreview: Bounds | null;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* during the drag). Null when no such drag is in flight. */
|
|
208
|
-
linkDragFromAnchor: {
|
|
209
|
-
fromElement: ElementId;
|
|
210
|
-
fromWorld: Vec2;
|
|
211
|
-
/** Named anchor the gesture started on — drives the click-to-create
|
|
212
|
-
* direction (outward normal) and the source link endpoint. */
|
|
213
|
-
anchorName: string;
|
|
214
|
-
origin: Vec2;
|
|
215
|
-
moved: boolean;
|
|
216
|
-
} | null;
|
|
217
|
-
/**
|
|
218
|
-
* Element being hovered while draw-edge mode is active. Drives the port-
|
|
219
|
-
* overlay render so the user sees attachment points. `null` outside
|
|
220
|
-
* draw-edge mode or when the pointer is over empty canvas.
|
|
221
|
-
*/
|
|
222
|
-
hoveredLinkTarget: {
|
|
223
|
-
elementId: ElementId;
|
|
224
|
-
activeAnchor: string | null;
|
|
225
|
-
outlinePoint?: Vec2 | undefined;
|
|
226
|
-
/**
|
|
227
|
-
* What the drop will produce, for clear pre-drop feedback (standard):
|
|
228
|
-
* - `"point"` → fixed attach to a specific dot (highlight the dot);
|
|
229
|
-
* - `"element"` → floating attach to the whole shape (highlight the
|
|
230
|
-
* element). Mirrors `snapLinkEndpoint`: an anchor within threshold →
|
|
231
|
-
* point, otherwise floating.
|
|
232
|
-
*/
|
|
233
|
-
mode: "point" | "element";
|
|
234
|
-
} | null;
|
|
235
|
-
/**
|
|
236
|
-
* Last idle cursor position (world) in select mode — the overlay grows the
|
|
237
|
-
* SINGLE selected element's link-start dot nearest it
|
|
238
|
-
* (`ANCHOR_DOT_HOVER_GROW_RADIUS`). Reset to null on press / gesture.
|
|
239
|
-
*/
|
|
240
|
-
hoverCursorWorld: Vec2 | null;
|
|
241
|
-
/**
|
|
242
|
-
* When a link is dropped on empty canvas, the edge is created with a
|
|
243
|
-
* free `point` end and this records where, so the host can pop a
|
|
244
|
-
* mini shape-picker at that spot (standard). Picking a shape re-points the
|
|
245
|
-
* end to the new element; dismissing (Esc / click-away) leaves the free
|
|
246
|
-
* end on the canvas. `null` when no menu is pending.
|
|
247
|
-
*/
|
|
248
|
-
private pendingLinkDropMenu;
|
|
220
|
+
get drawingPreview(): Bounds | null;
|
|
221
|
+
set drawingPreview(v: Bounds | null);
|
|
222
|
+
/** Live preview of an edge being drawn. */
|
|
223
|
+
get edgePreview(): EdgePreview | null;
|
|
224
|
+
set edgePreview(v: EdgePreview | null);
|
|
225
|
+
/** Active "drag a link from a start-anchor" gesture. */
|
|
226
|
+
get linkDragFromAnchor(): LinkDragFromAnchor | null;
|
|
227
|
+
set linkDragFromAnchor(v: LinkDragFromAnchor | null);
|
|
228
|
+
/** Element hovered while draw-edge mode is active (drives the port overlay). */
|
|
229
|
+
get hoveredLinkTarget(): HoveredLinkTarget | null;
|
|
230
|
+
set hoveredLinkTarget(v: HoveredLinkTarget | null);
|
|
231
|
+
/** Last idle cursor position (world) in select mode — grows the nearest dot. */
|
|
232
|
+
get hoverCursorWorld(): Vec2 | null;
|
|
233
|
+
set hoverCursorWorld(v: Vec2 | null);
|
|
249
234
|
/**
|
|
250
235
|
* Currently selected links (connectors). Links are first-class members
|
|
251
236
|
* of the selection: they coexist with selected elements, join Cmd+A and
|
|
@@ -271,8 +256,9 @@ export declare class Editor {
|
|
|
271
256
|
/**
|
|
272
257
|
* Mid-drag preview state when the user is dragging an edge endpoint.
|
|
273
258
|
* Drawn as an overlay line + handle dot so the user sees the target.
|
|
259
|
+
* State lives in `LinkHandleDragController`; this is a delegate.
|
|
274
260
|
*/
|
|
275
|
-
linkEndpointDrag: {
|
|
261
|
+
get linkEndpointDrag(): {
|
|
276
262
|
linkId: LinkId;
|
|
277
263
|
side: "from" | "to";
|
|
278
264
|
toPoint: Vec2;
|
|
@@ -283,83 +269,40 @@ export declare class Editor {
|
|
|
283
269
|
* began on a segment midpoint and will splice a new waypoint on the
|
|
284
270
|
* first move (so a no-move click adds nothing). Live-mutated through the
|
|
285
271
|
* gesture transaction → one undo step per drag.
|
|
272
|
+
* State lives in `LinkHandleDragController`; this is a delegate.
|
|
286
273
|
*/
|
|
287
|
-
linkWaypointDrag: {
|
|
274
|
+
get linkWaypointDrag(): {
|
|
288
275
|
linkId: LinkId;
|
|
289
276
|
index: number;
|
|
290
277
|
pendingInsert: boolean;
|
|
291
278
|
} | null;
|
|
292
279
|
/**
|
|
293
|
-
* Host-managed elbow segment drag. `
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
280
|
+
* Host-managed elbow segment drag. `axis` is the segment's orientation.
|
|
281
|
+
* Dragging pins the segment's perpendicular coordinate into
|
|
282
|
+
* `Link.fixedSegments`; the reroute pass re-flows the rest. One undo
|
|
283
|
+
* step via the gesture tx.
|
|
284
|
+
* State lives in `LinkHandleDragController`; this is a delegate.
|
|
297
285
|
*/
|
|
298
|
-
linkSegmentDrag: {
|
|
286
|
+
get linkSegmentDrag(): {
|
|
299
287
|
linkId: LinkId;
|
|
300
288
|
axis: "h" | "v";
|
|
301
289
|
at: number;
|
|
302
290
|
} | null;
|
|
303
291
|
/** Live lasso bounds during a rubber-band select gesture. */
|
|
304
|
-
lassoPreview: Bounds | null;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
* fans out when this map is populated.
|
|
319
|
-
*/
|
|
320
|
-
groupMoveOrigin: ReadonlyMap<ElementId, Vec2> | null;
|
|
321
|
-
/**
|
|
322
|
-
* Press-time snapshot of connectors that must follow a multi-element
|
|
323
|
-
* drag rigidly — both endpoints bound to moved elements, carrying
|
|
324
|
-
* absolute geometry (waypoints / fixedSegments / routedPoints). Each
|
|
325
|
-
* frame translates from these originals so the shift never compounds.
|
|
326
|
-
* Cleared on gesture commit / cancel alongside `groupMoveOrigin`.
|
|
327
|
-
*/
|
|
328
|
-
groupLinkMoveOrigin: ReadonlyMap<LinkId, Link> | null;
|
|
329
|
-
/**
|
|
330
|
-
* Per-shape snapshot for a group-resize gesture — `bounds` is the
|
|
331
|
-
* shape's world AABB at press-down. Editor scales the relative
|
|
332
|
-
* position / size against the combined bounds delta each frame.
|
|
333
|
-
*/
|
|
334
|
-
groupResizeOrigin: {
|
|
335
|
-
readonly combined: Bounds;
|
|
336
|
-
readonly elements: ReadonlyMap<ElementId, {
|
|
337
|
-
readonly position: Vec2;
|
|
338
|
-
readonly bounds: Bounds;
|
|
339
|
-
readonly scale: Vec2;
|
|
340
|
-
}>;
|
|
341
|
-
readonly links: ReadonlyMap<LinkId, Link>;
|
|
342
|
-
} | null;
|
|
343
|
-
/**
|
|
344
|
-
* Press-time snapshot for a rotate gesture: the pivot (selection bbox centre)
|
|
345
|
-
* and every member's pristine `position` / `rotation`. Each frame rotates
|
|
346
|
-
* from this baseline so the cumulative angle never drifts. Cleared on gesture
|
|
347
|
-
* end (commit / cancel).
|
|
348
|
-
*/
|
|
349
|
-
rotateGestureOrigin: {
|
|
350
|
-
readonly pivot: Vec2;
|
|
351
|
-
readonly origin: ReadonlyMap<ElementId, {
|
|
352
|
-
readonly position: Vec2;
|
|
353
|
-
readonly rotation: number;
|
|
354
|
-
}>;
|
|
355
|
-
} | null;
|
|
356
|
-
/**
|
|
357
|
-
* Pristine shape snapshot for a single-shape text resize, captured on
|
|
358
|
-
* the gesture's first tick. Font scaling is computed against this base
|
|
359
|
-
* so it never compounds across pointermove ticks. Cleared on gesture
|
|
360
|
-
* end (commit / cancel).
|
|
361
|
-
*/
|
|
362
|
-
private _resizeOriginElement;
|
|
292
|
+
get lassoPreview(): Bounds | null;
|
|
293
|
+
set lassoPreview(v: Bounds | null);
|
|
294
|
+
/** Snapshot of every selected shape's `position` at press-down (multi-drag). */
|
|
295
|
+
get groupMoveOrigin(): ReadonlyMap<ElementId, Vec2> | null;
|
|
296
|
+
set groupMoveOrigin(v: ReadonlyMap<ElementId, Vec2> | null);
|
|
297
|
+
/** Press-time snapshot of connectors that follow a multi-element drag rigidly. */
|
|
298
|
+
get groupLinkMoveOrigin(): ReadonlyMap<LinkId, Link> | null;
|
|
299
|
+
set groupLinkMoveOrigin(v: ReadonlyMap<LinkId, Link> | null);
|
|
300
|
+
/** Per-shape snapshot for a group-resize gesture. */
|
|
301
|
+
get groupResizeOrigin(): GroupResizeOrigin | null;
|
|
302
|
+
set groupResizeOrigin(v: GroupResizeOrigin | null);
|
|
303
|
+
/** Press-time snapshot for a rotate gesture. */
|
|
304
|
+
get rotateGestureOrigin(): RotateGestureOrigin | null;
|
|
305
|
+
set rotateGestureOrigin(v: RotateGestureOrigin | null);
|
|
363
306
|
/**
|
|
364
307
|
* Active layer — new shapes created via `addElement` / `applyCreate` land
|
|
365
308
|
* here when their input doesn't specify a `layerId`. Defaults to the
|
|
@@ -375,21 +318,6 @@ export declare class Editor {
|
|
|
375
318
|
private readonly snapEngine;
|
|
376
319
|
/** Snap threshold in world units. */
|
|
377
320
|
private readonly snapThreshold;
|
|
378
|
-
/**
|
|
379
|
-
* Transient flag set by the host while a snap-suppress modifier
|
|
380
|
-
* (Cmd / Ctrl) is held during a drag — lets the user pull a shape off
|
|
381
|
-
* the grid for one gesture without toggling snap off. Read by the
|
|
382
|
-
* move / resize / create wrappers; never persisted.
|
|
383
|
-
*/
|
|
384
|
-
private snapSuppressed;
|
|
385
|
-
/**
|
|
386
|
-
* Transient transform-modifier state mirrored from the host while a drag is
|
|
387
|
-
* in flight. `alt` resizes symmetrically about the centre; `shift` locks the
|
|
388
|
-
* resize aspect ratio or constrains a move to a single axis. Read by the
|
|
389
|
-
* move / resize wrappers; never persisted.
|
|
390
|
-
*/
|
|
391
|
-
private transformAltKey;
|
|
392
|
-
private transformShiftKey;
|
|
393
321
|
/**
|
|
394
322
|
* In-editor style memory for copy-style / paste-style. Holds the visual
|
|
395
323
|
* `style` (fill / stroke / dash / …) captured from a shape; `null` until a
|
|
@@ -417,36 +345,37 @@ export declare class Editor {
|
|
|
417
345
|
* escape, click outside the group's descendants, or `cancelInteraction`.
|
|
418
346
|
*/
|
|
419
347
|
_enteredGroup: ElementId | null;
|
|
420
|
-
/**
|
|
421
|
-
* Double-click detection state. Updated on every non-drag pointer
|
|
422
|
-
* up; the next pointer-up within `DOUBLE_CLICK_MS` and within
|
|
423
|
-
* `DOUBLE_CLICK_TOLERANCE_PX` of `lastClickWorldPoint` counts as a
|
|
424
|
-
* double-click. Used to trigger group drill-down (enter isolation).
|
|
425
|
-
*/
|
|
426
|
-
private lastClickAt;
|
|
427
|
-
private lastClickWorldPoint;
|
|
428
|
-
/**
|
|
429
|
-
* Separate double-click tracker for link edit handles (waypoint /
|
|
430
|
-
* segment). Kept apart from `lastClickAt` because a handle press
|
|
431
|
-
* returns early in `onDown` (begin-drag) and never reaches the up-side
|
|
432
|
-
* double-click path that updates `lastClickAt`. Updated by
|
|
433
|
-
* `isHandleDoubleClick` on each handle press.
|
|
434
|
-
*/
|
|
435
|
-
private lastHandleClickAt;
|
|
436
|
-
private lastHandleClickWorld;
|
|
437
348
|
/**
|
|
438
349
|
* In-progress brush stroke. Hosts push points via
|
|
439
350
|
* `extendBrushStroke`; the overlay reads it through
|
|
440
351
|
* `pendingBrushStroke` to draw a live preview.
|
|
441
352
|
*/
|
|
442
|
-
brushStroke: BrushStrokeState | null;
|
|
353
|
+
get brushStroke(): BrushStrokeState | null;
|
|
354
|
+
set brushStroke(v: BrushStrokeState | null);
|
|
355
|
+
/**
|
|
356
|
+
* The in-progress brush stroke with its captured vertices Catmull-Rom-smoothed
|
|
357
|
+
* for the LIVE overlay preview — the SAME resampler `commitBrushStroke` applies
|
|
358
|
+
* on release (see {@link smoothBrushPoints}), so the stroke reads smooth while
|
|
359
|
+
* drawn instead of snapping from an angular polyline to a curve on release. A
|
|
360
|
+
* fresh object each call (points diverge from `brushStroke.points`), so the
|
|
361
|
+
* overlay memo repaints every move that grows the stroke.
|
|
362
|
+
*/
|
|
363
|
+
private get brushPreviewStroke();
|
|
364
|
+
/** In-progress eraser stroke (pending-delete set), or null between strokes. */
|
|
365
|
+
get eraseStroke(): EraseStrokeState | null;
|
|
366
|
+
set eraseStroke(v: EraseStrokeState | null);
|
|
367
|
+
/** Ids swept by the current eraser stroke — previewed dimmed, deleted on release. */
|
|
368
|
+
get pendingErase(): ReadonlySet<ElementId>;
|
|
369
|
+
/** Live laser-pointer trails (ephemeral, fading). Empty when none active. */
|
|
370
|
+
get laserStrokes(): readonly LaserStroke[];
|
|
443
371
|
/**
|
|
444
372
|
* Last world-space pointer position observed by the host's onMove
|
|
445
373
|
* handler. `paste()` uses it as the default drop target so a fresh
|
|
446
374
|
* paste lands under the cursor instead of overlapping the originals.
|
|
447
375
|
* `null` until the pointer first enters the host.
|
|
448
376
|
*/
|
|
449
|
-
lastPointerWorld: Vec2 | null;
|
|
377
|
+
get lastPointerWorld(): Vec2 | null;
|
|
378
|
+
set lastPointerWorld(v: Vec2 | null);
|
|
450
379
|
/** Host-registered custom cursor images per role (see `setCursorOverride`). */
|
|
451
380
|
private readonly cursorOverrides;
|
|
452
381
|
/**
|
|
@@ -465,6 +394,23 @@ export declare class Editor {
|
|
|
465
394
|
* pass would never visibly apply.
|
|
466
395
|
*/
|
|
467
396
|
lastRenderedEnteredGroup: ElementId | null;
|
|
397
|
+
/**
|
|
398
|
+
* Whether the last paint had eraser-dim active — paired with
|
|
399
|
+
* `lastRenderedScene` like {@link lastRenderedEnteredGroup}. When an eraser
|
|
400
|
+
* stroke ENDS by cancel (Esc), the marked shapes un-dim without a scene
|
|
401
|
+
* change, so the dirty-rect diff is empty and the dim would linger on screen;
|
|
402
|
+
* this lets that active→inactive transition force one full repaint.
|
|
403
|
+
*/
|
|
404
|
+
lastRenderedEraseActive: boolean;
|
|
405
|
+
/**
|
|
406
|
+
* Set whenever an eraser move actually CHANGES the marked / cut set (a new
|
|
407
|
+
* shape marked, un-marked, or a brush point cut). Gates the forced full
|
|
408
|
+
* repaint during erasing: only the frames that change the preview repaint the
|
|
409
|
+
* whole scene; a slowly-moving or stopped cursor over already-covered area
|
|
410
|
+
* skips the expensive main pass (only the overlay cursor / trail refresh).
|
|
411
|
+
* Cleared after each paint.
|
|
412
|
+
*/
|
|
413
|
+
private eraseDirty;
|
|
468
414
|
/**
|
|
469
415
|
* Fractional-order compaction scheduler (microtask-coalesced).
|
|
470
416
|
* Triggered from every `notify()`; only does real work when at
|
|
@@ -483,7 +429,8 @@ export declare class Editor {
|
|
|
483
429
|
* gestures, set in onDown when press lands on a shape and cleared
|
|
484
430
|
* in onUp / cancel.
|
|
485
431
|
*/
|
|
486
|
-
dragElementId: ElementId | null;
|
|
432
|
+
get dragElementId(): ElementId | null;
|
|
433
|
+
set dragElementId(v: ElementId | null);
|
|
487
434
|
/**
|
|
488
435
|
* Element that the current press added to the selection additively
|
|
489
436
|
* (shift / meta click on an unselected shape). The press promotes it
|
|
@@ -491,17 +438,16 @@ export declare class Editor {
|
|
|
491
438
|
* otherwise `SELECT_TOGGLE` it straight back off, so it consults this
|
|
492
439
|
* to skip that redundant toggle. Reset at every press-down.
|
|
493
440
|
*/
|
|
494
|
-
additivePressAdded: ElementId | null;
|
|
441
|
+
get additivePressAdded(): ElementId | null;
|
|
442
|
+
set additivePressAdded(v: ElementId | null);
|
|
495
443
|
/**
|
|
496
444
|
* Live container highlight: the container shape the dragged item is
|
|
497
445
|
* currently hovering over. Drawn by the overlay as a dashed
|
|
498
446
|
* accent rect on the container's drop-zone so the user sees where the
|
|
499
447
|
* shape will land after release.
|
|
500
448
|
*/
|
|
501
|
-
containerHover:
|
|
502
|
-
|
|
503
|
-
dropZone: Bounds;
|
|
504
|
-
} | null;
|
|
449
|
+
get containerHover(): ContainerHover | null;
|
|
450
|
+
set containerHover(v: ContainerHover | null);
|
|
505
451
|
/**
|
|
506
452
|
* Remote peer cursors / selections, pushed in by the host (typically
|
|
507
453
|
* a `bindAwareness(editor, awareness)` helper in `@collab`). The
|
|
@@ -521,7 +467,7 @@ export declare class Editor {
|
|
|
521
467
|
* two or more entries we enter a pinch / pan gesture and bypass the
|
|
522
468
|
* interaction machine — `pinchOrigin` holds the baseline.
|
|
523
469
|
*/
|
|
524
|
-
|
|
470
|
+
get activePointers(): Map<number, Vec2>;
|
|
525
471
|
/**
|
|
526
472
|
* One-finger-pan candidate: set at pointer-down when a TOUCH press lands
|
|
527
473
|
* on empty canvas in select mode. A tap (no movement) still falls through
|
|
@@ -529,7 +475,8 @@ export declare class Editor {
|
|
|
529
475
|
* this to a real pan instead of a marquee lasso (mobile convention).
|
|
530
476
|
* Screen-space origin point.
|
|
531
477
|
*/
|
|
532
|
-
touchPanCandidate: Vec2 | null;
|
|
478
|
+
get touchPanCandidate(): Vec2 | null;
|
|
479
|
+
set touchPanCandidate(v: Vec2 | null);
|
|
533
480
|
pinch: PinchController;
|
|
534
481
|
/** Bridge for the container-ops helpers. Built lazily in constructor. */
|
|
535
482
|
private containerOpsRef;
|
|
@@ -539,7 +486,8 @@ export declare class Editor {
|
|
|
539
486
|
* "grab" / "grabbing". Wires a window-level keydown/keyup listener
|
|
540
487
|
* in `bindPointerEvents`.
|
|
541
488
|
*/
|
|
542
|
-
spaceHeld: boolean;
|
|
489
|
+
get spaceHeld(): boolean;
|
|
490
|
+
set spaceHeld(v: boolean);
|
|
543
491
|
/**
|
|
544
492
|
* Host-supplied tile compositor — when set (via
|
|
545
493
|
* `EditorOptions.useTileCache` + `tileCompose`), the per-frame
|
|
@@ -589,20 +537,16 @@ export declare class Editor {
|
|
|
589
537
|
* only treat right-click releases as potential context-menu
|
|
590
538
|
* triggers (Space + left-drag never opens a menu).
|
|
591
539
|
*/
|
|
592
|
-
panGesture:
|
|
593
|
-
|
|
594
|
-
button: number;
|
|
595
|
-
startPoint: Vec2;
|
|
596
|
-
lastPoint: Vec2;
|
|
597
|
-
moved: boolean;
|
|
598
|
-
} | null;
|
|
540
|
+
get panGesture(): PanGesture | null;
|
|
541
|
+
set panGesture(v: PanGesture | null);
|
|
599
542
|
/**
|
|
600
543
|
* Set on a right-click pointerdown so the upcoming native
|
|
601
544
|
* `contextmenu` event can be unconditionally preventDefault'ed
|
|
602
545
|
* (the gesture decides whether to fire the menu manually on
|
|
603
546
|
* pointerup based on whether the user dragged).
|
|
604
547
|
*/
|
|
605
|
-
suppressNextContextMenu: boolean;
|
|
548
|
+
get suppressNextContextMenu(): boolean;
|
|
549
|
+
set suppressNextContextMenu(v: boolean);
|
|
606
550
|
/**
|
|
607
551
|
* Long-press tracking. Starts on `pointerdown`; cancelled on
|
|
608
552
|
* `pointermove > LONG_PRESS_MAX_MOVEMENT_PX` or `pointerup` before
|
|
@@ -622,13 +566,13 @@ export declare class Editor {
|
|
|
622
566
|
* in the constructor from `EditorOptions.inputMode` (default `"auto"`
|
|
623
567
|
* uses `matchMedia('(pointer: coarse)')`).
|
|
624
568
|
*/
|
|
625
|
-
private
|
|
626
|
-
private
|
|
627
|
-
private
|
|
628
|
-
private
|
|
569
|
+
private inputMode;
|
|
570
|
+
private handleHitSlop;
|
|
571
|
+
private edgeHandleHitSlop;
|
|
572
|
+
private edgeHitThreshold;
|
|
629
573
|
/** Link-start anchor-dot grab/click hit radii — touch-enlarged in touch mode. */
|
|
630
|
-
|
|
631
|
-
|
|
574
|
+
anchorStartHitSlop: number;
|
|
575
|
+
anchorClickRadius: number;
|
|
632
576
|
readonly _history: HistoryProvider;
|
|
633
577
|
/** Open transaction during a single drag/resize gesture. */
|
|
634
578
|
gestureTx: TransactionHandle | null;
|
|
@@ -646,9 +590,63 @@ export declare class Editor {
|
|
|
646
590
|
* The controller calls back through the narrow `GestureRef` bridge
|
|
647
591
|
* built lazily below.
|
|
648
592
|
*/
|
|
649
|
-
private
|
|
593
|
+
private gestures;
|
|
594
|
+
/**
|
|
595
|
+
* Owns the inline text-edit session (edited shape, pending creation,
|
|
596
|
+
* origin snapshot, live selection, drag anchor and caret blink).
|
|
597
|
+
* Editor keeps thin delegate wrappers so the public API is unchanged.
|
|
598
|
+
*/
|
|
599
|
+
private textEdit;
|
|
600
|
+
/**
|
|
601
|
+
* Owns the link edit-handle drags (waypoint / segment / endpoint) and
|
|
602
|
+
* the handle double-click detector. Editor keeps thin delegate
|
|
603
|
+
* wrappers so the public API is unchanged.
|
|
604
|
+
*/
|
|
605
|
+
private linkHandles;
|
|
650
606
|
constructor(options: EditorOptions);
|
|
651
|
-
/**
|
|
607
|
+
/**
|
|
608
|
+
* Build the interaction controllers (gestures, text edit, link-handle
|
|
609
|
+
* drag, long-press, pinch) and the container-ops bridge. Each wires to a
|
|
610
|
+
* narrow getter/setter surface over the editor's mutable fields, so the
|
|
611
|
+
* controllers live in their own modules without importing Editor. The
|
|
612
|
+
* getters/setters in the object literals rebind `this`, so a single
|
|
613
|
+
* `self` alias captures the Editor reference for all of them.
|
|
614
|
+
*/
|
|
615
|
+
private initControllers;
|
|
616
|
+
/**
|
|
617
|
+
* Install process-global hooks the host opted into: a custom text shaper
|
|
618
|
+
* and rasterizer for the WebGL2 backend, plus the scene text measurer that
|
|
619
|
+
* routes through the renderer's own metrics so selection boxes hug text.
|
|
620
|
+
*/
|
|
621
|
+
private initGlobalHooks;
|
|
622
|
+
/**
|
|
623
|
+
* Resolve the input mode (`touch` vs `mouse`, `auto` reads the coarse-
|
|
624
|
+
* pointer media query) and the derived hit slops / thresholds once.
|
|
625
|
+
*/
|
|
626
|
+
private initInputMode;
|
|
627
|
+
/**
|
|
628
|
+
* Create and start the interaction state-machine actor, wire its render /
|
|
629
|
+
* emit subscriptions, register the built-in file-drop handlers, and apply
|
|
630
|
+
* the initial mode.
|
|
631
|
+
*/
|
|
632
|
+
private initActor;
|
|
633
|
+
/**
|
|
634
|
+
* Last {@link observableSnapshot} object, reused while none of its slices
|
|
635
|
+
* have flipped. `null` until the first snapshot is built.
|
|
636
|
+
*/
|
|
637
|
+
private snapshotCache;
|
|
638
|
+
/**
|
|
639
|
+
* Snapshot used by event-fanout. Kept private — internal API.
|
|
640
|
+
*
|
|
641
|
+
* Memoized by slice identity: `notify()` fires on many mutations that touch
|
|
642
|
+
* no observable slice (annotation focus, cursor pushes, viewport-only re-arm),
|
|
643
|
+
* so rebuilding the object every call is pure churn on the hot drag path.
|
|
644
|
+
* We reuse the cached object whenever all six slices compare equal (refs for
|
|
645
|
+
* mode/selection/selectedLinks/scene — scene uses structural sharing so a new
|
|
646
|
+
* ref iff something changed — plus the two history booleans), and only
|
|
647
|
+
* allocate a fresh one on a real flip. `fanOutEvents` sees identical values
|
|
648
|
+
* either way, so emitted events are unchanged.
|
|
649
|
+
*/
|
|
652
650
|
private observableSnapshot;
|
|
653
651
|
get scene(): Scene;
|
|
654
652
|
get selection(): Selection.Selection;
|
|
@@ -740,6 +738,19 @@ export declare class Editor {
|
|
|
740
738
|
* values can be eyeballed. View-only — not recorded in history.
|
|
741
739
|
*/
|
|
742
740
|
setDebugHitZones(on: boolean): void;
|
|
741
|
+
/**
|
|
742
|
+
* Read-only / view mode flag. `true` gates pointer edits and
|
|
743
|
+
* non-`viewMode` actions while leaving pan / zoom / select live.
|
|
744
|
+
*/
|
|
745
|
+
get readOnly(): boolean;
|
|
746
|
+
/**
|
|
747
|
+
* Enter / leave read-only (view) mode. Notifies subscribers so the UI
|
|
748
|
+
* can re-render disabled chrome, and repaints (no visual diff today, but
|
|
749
|
+
* keeps the contract symmetric with other view toggles). Idempotent.
|
|
750
|
+
*/
|
|
751
|
+
setReadOnly(on: boolean): void;
|
|
752
|
+
/** Toggle read-only (view) mode. */
|
|
753
|
+
toggleReadOnly(): void;
|
|
743
754
|
/** Whether the background grid is enabled for the scene. */
|
|
744
755
|
get gridEnabled(): boolean;
|
|
745
756
|
/** Show/hide the background grid (`g`). Persists in the viewport, not in history. */
|
|
@@ -902,11 +913,10 @@ export declare class Editor {
|
|
|
902
913
|
* cleared by `commitTextEdit` / `cancelTextEdit`. The host overlay
|
|
903
914
|
* (`<TextEditorOverlay>` in `@react-ui`) subscribes via `editor`
|
|
904
915
|
* and renders a `<textarea>` positioned over the shape.
|
|
916
|
+
* State lives in `TextEditController`; this is a delegate.
|
|
905
917
|
*/
|
|
906
|
-
private _editingTextElement;
|
|
907
918
|
get editingTextElement(): ElementId | null;
|
|
908
919
|
/** Link whose caption is being edited inline (double-click), or null. */
|
|
909
|
-
private _editingLinkCaption;
|
|
910
920
|
get editingLinkCaption(): LinkId | null;
|
|
911
921
|
/**
|
|
912
922
|
* Frame whose NAME (header label) is being edited inline (double-click
|
|
@@ -915,29 +925,6 @@ export declare class Editor {
|
|
|
915
925
|
*/
|
|
916
926
|
private _editingFrameName;
|
|
917
927
|
get editingFrameName(): ElementId | null;
|
|
918
|
-
/**
|
|
919
|
-
* When the `draw-text` tool just placed a shape and opened its
|
|
920
|
-
* editor, this holds that shape's id until the first commit. A
|
|
921
|
-
* pending creation isn't in history yet: committing non-empty text
|
|
922
|
-
* records a single add patch (whole shape = one undo); committing
|
|
923
|
-
* empty / cancelling removes it with no history entry at all.
|
|
924
|
-
*/
|
|
925
|
-
private _pendingTextCreate;
|
|
926
|
-
/**
|
|
927
|
-
* Snapshot of the shape at edit start. Used to revert on cancel and
|
|
928
|
-
* as the `before` of the single commit patch. `null` for a pending
|
|
929
|
-
* creation (the shape didn't exist yet).
|
|
930
|
-
*/
|
|
931
|
-
private _textEditOrigin;
|
|
932
|
-
/**
|
|
933
|
-
* Live selection inside the edited text, mirrored from the hidden
|
|
934
|
-
* `<textarea>` (`start`/`end` are source offsets, `dir` is the
|
|
935
|
-
* anchored end). The caret is `dir === "backward" ? start : end`.
|
|
936
|
-
*/
|
|
937
|
-
private _textSel;
|
|
938
|
-
/** Anchor offset for a canvas drag-select inside the edited text. */
|
|
939
|
-
private _textDragAnchor;
|
|
940
|
-
private readonly caretBlink;
|
|
941
928
|
get editingTextSelection(): {
|
|
942
929
|
start: number;
|
|
943
930
|
end: number;
|
|
@@ -1013,15 +1000,6 @@ export declare class Editor {
|
|
|
1013
1000
|
extendTextSelectionToPoint(worldPoint: Vec2): void;
|
|
1014
1001
|
/** End a canvas drag-select (clears the drag anchor). */
|
|
1015
1002
|
endTextDragSelect(): void;
|
|
1016
|
-
/** Build the editable layout for a text shape using the main target's metrics. */
|
|
1017
|
-
private editingTextLayout;
|
|
1018
|
-
/**
|
|
1019
|
-
* A measure callback bound to a shape's font, using the main target's
|
|
1020
|
-
* `measureText` — the SAME source the renderer draws with (WebGL2
|
|
1021
|
-
* reports MSDF advances) and the bounder measures with. Caret /
|
|
1022
|
-
* selection geometry therefore lines up exactly with the glyphs.
|
|
1023
|
-
*/
|
|
1024
|
-
private measureFor;
|
|
1025
1003
|
/**
|
|
1026
1004
|
* World-space caret + selection geometry for the overlay pass.
|
|
1027
1005
|
* Returns `null` when not editing. The caret is `null` while blinked
|
|
@@ -1064,6 +1042,15 @@ export declare class Editor {
|
|
|
1064
1042
|
* the tool is locked.
|
|
1065
1043
|
*/
|
|
1066
1044
|
createTextAt(worldPoint: Vec2): ElementId;
|
|
1045
|
+
/** Current brush paint settings (line colour, fill, opacity, width). */
|
|
1046
|
+
private _brushSettings;
|
|
1047
|
+
get brushSettings(): BrushSettings;
|
|
1048
|
+
/**
|
|
1049
|
+
* Update one or more brush paint settings (e.g. from the drawing panel). New
|
|
1050
|
+
* strokes pick them up on commit; the width also drives the pressure curve and
|
|
1051
|
+
* the eraser radius. Merges over the current settings.
|
|
1052
|
+
*/
|
|
1053
|
+
setBrushSettings(patch: Partial<BrushSettings>): void;
|
|
1067
1054
|
beginBrushStroke(world: Vec2, pressure?: number): void;
|
|
1068
1055
|
extendBrushStroke(world: Vec2, pressure?: number): void;
|
|
1069
1056
|
commitBrushStroke(): ElementId | null;
|
|
@@ -1073,6 +1060,53 @@ export declare class Editor {
|
|
|
1073
1060
|
readonly origin: Vec2;
|
|
1074
1061
|
readonly points: readonly BrushPoint[];
|
|
1075
1062
|
} | null;
|
|
1063
|
+
/**
|
|
1064
|
+
* Start an eraser stroke at `world`, seeding it with the shape under it so a
|
|
1065
|
+
* plain click erases. With `restore` (Alt held at press) it seeds nothing —
|
|
1066
|
+
* the gesture is in un-mark mode, and there's nothing marked yet to rescue.
|
|
1067
|
+
*/
|
|
1068
|
+
beginEraseStroke(world: Vec2, restore?: boolean, strokeErase?: boolean): void;
|
|
1069
|
+
/**
|
|
1070
|
+
* Incrementally mark the brush points erased by the eraser segment `a → b`
|
|
1071
|
+
* (world). Iterates the current brushes and grows `stroke.erased` in place —
|
|
1072
|
+
* O(points) per move (each already-erased point is skipped), so a long drag
|
|
1073
|
+
* no longer costs O(points × path length) per frame. Radius is the on-screen
|
|
1074
|
+
* eraser ring converted to world units.
|
|
1075
|
+
*/
|
|
1076
|
+
private markStrokeEraseSegment;
|
|
1077
|
+
/**
|
|
1078
|
+
* Extend the eraser stroke to `world`, sweeping shapes along the segment.
|
|
1079
|
+
* `restore` (Alt held) un-marks swept shapes instead of marking them.
|
|
1080
|
+
*/
|
|
1081
|
+
extendEraseStroke(world: Vec2, restore?: boolean): void;
|
|
1082
|
+
/**
|
|
1083
|
+
* Commit the eraser stroke — delete every swept shape in ONE undo step (with
|
|
1084
|
+
* their attached links). No-op delete when nothing was swept. Returns the
|
|
1085
|
+
* count removed.
|
|
1086
|
+
*/
|
|
1087
|
+
commitEraseStroke(): number;
|
|
1088
|
+
/** Abort the eraser stroke without deleting anything. */
|
|
1089
|
+
cancelEraseStroke(): void;
|
|
1090
|
+
/** True only while the pointer is down in laser mode (a trail is being laid). */
|
|
1091
|
+
get laserDrawing(): boolean;
|
|
1092
|
+
/** Start a laser trail at `world` (ephemeral — never enters the scene). */
|
|
1093
|
+
beginLaserStroke(world: Vec2): void;
|
|
1094
|
+
/** Append a point to the active laser trail (no-op unless drawing). */
|
|
1095
|
+
extendLaserStroke(world: Vec2): void;
|
|
1096
|
+
/** End the active laser trail — it keeps fading via the animation tick. */
|
|
1097
|
+
endLaserStroke(): void;
|
|
1098
|
+
/**
|
|
1099
|
+
* True while any laser trail OR eraser trail still has visible points (drives
|
|
1100
|
+
* the fade tick — so the trail keeps melting after the pointer stops).
|
|
1101
|
+
*/
|
|
1102
|
+
hasActiveLaser(): boolean;
|
|
1103
|
+
/** Live eraser drag trail (ephemeral, fading). Empty when none active. */
|
|
1104
|
+
get eraserTrail(): readonly LaserStroke[];
|
|
1105
|
+
/**
|
|
1106
|
+
* Drop expired laser/eraser trail points (called once per frame before paint).
|
|
1107
|
+
* Self-terminating: once both arrays empty the animation tick stops.
|
|
1108
|
+
*/
|
|
1109
|
+
private pruneLaser;
|
|
1076
1110
|
arrangeAsGrid(opts?: {
|
|
1077
1111
|
cols?: number;
|
|
1078
1112
|
gap?: number;
|
|
@@ -1106,6 +1140,14 @@ export declare class Editor {
|
|
|
1106
1140
|
*/
|
|
1107
1141
|
duplicateSelectedInPlace(anchorId?: ElementId | null): ElementId | null;
|
|
1108
1142
|
setSelection(ids: Iterable<ElementId>): void;
|
|
1143
|
+
/**
|
|
1144
|
+
* Programmatically select a single link by id (or clear the link
|
|
1145
|
+
* selection with `null`), clearing the element selection so the link
|
|
1146
|
+
* becomes the sole selection. Used by host navigation (search / jump-to)
|
|
1147
|
+
* to frame an edge with {@link zoomToSelection}. No-op when nothing
|
|
1148
|
+
* would change.
|
|
1149
|
+
*/
|
|
1150
|
+
selectLink(id: LinkId | null): void;
|
|
1109
1151
|
selectAll(): void;
|
|
1110
1152
|
/**
|
|
1111
1153
|
* Internal clipboard. Stored as deep-cloned snapshots so subsequent
|
|
@@ -1145,6 +1187,179 @@ export declare class Editor {
|
|
|
1145
1187
|
/** Whether a style has been copied and can be pasted. */
|
|
1146
1188
|
get hasStyleClipboard(): boolean;
|
|
1147
1189
|
updateStyle(ids: Iterable<ElementId>, partial: Partial<TextStyle>): void;
|
|
1190
|
+
/**
|
|
1191
|
+
* Apply a partial text style (bold / italic / colour / decoration) to the
|
|
1192
|
+
* character range `[from, to)` of a single text element, producing styled
|
|
1193
|
+
* runs (rich text). One undo step. No-op when the id isn't a text shape or
|
|
1194
|
+
* the range is empty. Read-only editors ignore it. Use this — rather than
|
|
1195
|
+
* `updateStyle` — to style only PART of a text block (e.g. the current
|
|
1196
|
+
* inline-edit selection).
|
|
1197
|
+
*/
|
|
1198
|
+
applyTextStyleToRange(id: ElementId, from: number, to: number, partial: Partial<TextStyle>): void;
|
|
1199
|
+
/**
|
|
1200
|
+
* The fill (or stroke, per `role`) colour of the top-most shape under the
|
|
1201
|
+
* world point, or `null` on empty canvas. Pure read — no mutation.
|
|
1202
|
+
*/
|
|
1203
|
+
pickColorAt(worldPoint: Vec2, role?: "fill" | "stroke"): Color | null;
|
|
1204
|
+
/** One-shot callback armed by {@link beginEyedropperPick}; consumes the next canvas click. */
|
|
1205
|
+
private pendingEyedropperPick;
|
|
1206
|
+
/** `true` while a colour-picker pipette is armed and waiting for a canvas click. */
|
|
1207
|
+
get isEyedropperArmed(): boolean;
|
|
1208
|
+
/**
|
|
1209
|
+
* Arm the eyedropper for a one-shot canvas pick that routes the sampled colour
|
|
1210
|
+
* to `onPick` (e.g. a colour-picker swatch) instead of the selection fill. Does
|
|
1211
|
+
* NOT change the tool mode — the next canvas press is intercepted by
|
|
1212
|
+
* {@link applyEyedropperAt}. Cancelled by a mode switch or an empty-canvas click.
|
|
1213
|
+
*/
|
|
1214
|
+
beginEyedropperPick(onPick: (color: Color) => void): void;
|
|
1215
|
+
/**
|
|
1216
|
+
* Sample the colour under `worldPoint`. When a pipette pick is armed (see
|
|
1217
|
+
* {@link beginEyedropperPick}), route the colour to that callback and disarm.
|
|
1218
|
+
* Otherwise (legacy tool path) apply it as the current selection's fill and
|
|
1219
|
+
* revert to `select` mode. Returns the sampled colour, or `null` on empty
|
|
1220
|
+
* canvas. Read-only editors sample but don't mutate.
|
|
1221
|
+
*/
|
|
1222
|
+
applyEyedropperAt(worldPoint: Vec2): Color | null;
|
|
1223
|
+
/**
|
|
1224
|
+
* Convert every convertible selected shape (rectangle / ellipse / diamond)
|
|
1225
|
+
* to `target`, preserving position, size and style. One undo step; no-op
|
|
1226
|
+
* when nothing applies. See {@link ConvertTarget}.
|
|
1227
|
+
*/
|
|
1228
|
+
convertSelection(target: ConvertTarget): void;
|
|
1229
|
+
/**
|
|
1230
|
+
* Flowchart auto-generate: clone the single selected node offset in
|
|
1231
|
+
* `direction` and connect the two with a fresh link. Selects the new node.
|
|
1232
|
+
* No-op unless exactly one element is selected. One undo step.
|
|
1233
|
+
*/
|
|
1234
|
+
spawnConnectedNode(direction: SpawnDirection): void;
|
|
1235
|
+
/**
|
|
1236
|
+
* Pending flowchart-create session, or `null` when idle. Holds the ORIGINAL
|
|
1237
|
+
* source id + direction, the current sibling `count`, and the pending
|
|
1238
|
+
* `elements` + `links` (a PREVIEW — not yet in the scene / history). Grown by
|
|
1239
|
+
* {@link growFlowchart}, committed by {@link commitFlowchart}, discarded by
|
|
1240
|
+
* {@link cancelFlowchart}.
|
|
1241
|
+
*/
|
|
1242
|
+
private flowchartSession;
|
|
1243
|
+
/**
|
|
1244
|
+
* Grow the flowchart-create preview one step in `direction`. Starts a session
|
|
1245
|
+
* (count = 1) when idle or when the direction changes; otherwise bumps the
|
|
1246
|
+
* sibling count up to {@link FLOWCHART_MAX_SIBLINGS}. Recomputes the pending
|
|
1247
|
+
* nodes/links from the ORIGINAL source each call. PREVIEW ONLY — never
|
|
1248
|
+
* touches the scene or history until {@link commitFlowchart}. No-op in
|
|
1249
|
+
* read-only mode or unless exactly one element is selected.
|
|
1250
|
+
*/
|
|
1251
|
+
growFlowchart(direction: SpawnDirection): void;
|
|
1252
|
+
/**
|
|
1253
|
+
* Commit the pending flowchart-create preview: add every pending node + link
|
|
1254
|
+
* to the scene as ONE undo step, select the first new node, clear the
|
|
1255
|
+
* session. Returns the first new node's id, or `null` when no session is
|
|
1256
|
+
* active.
|
|
1257
|
+
*/
|
|
1258
|
+
commitFlowchart(): ElementId | null;
|
|
1259
|
+
/** Discard the pending flowchart-create preview without committing. */
|
|
1260
|
+
cancelFlowchart(): void;
|
|
1261
|
+
/**
|
|
1262
|
+
* The pending flowchart-create preview (nodes + links), or `null` when no
|
|
1263
|
+
* session is active. Read by the render snapshot to paint the preview on the
|
|
1264
|
+
* overlay. Reference-stable between renders (only changes on grow / commit /
|
|
1265
|
+
* cancel) so the overlay memo doesn't thrash.
|
|
1266
|
+
*/
|
|
1267
|
+
get flowchartPreview(): {
|
|
1268
|
+
readonly elements: readonly Element[];
|
|
1269
|
+
readonly links: readonly Link[];
|
|
1270
|
+
} | null;
|
|
1271
|
+
/**
|
|
1272
|
+
* Move the selection to an adjacent node. With exactly one element selected,
|
|
1273
|
+
* prefers a graph neighbour (linked node) best aligned with `direction`;
|
|
1274
|
+
* falls back to the spatial {@link selectClosest} when no neighbour lies that
|
|
1275
|
+
* way. No-op unless exactly one element is selected.
|
|
1276
|
+
*/
|
|
1277
|
+
navigateFlowchart(direction: "left" | "right" | "up" | "down"): void;
|
|
1278
|
+
/**
|
|
1279
|
+
* Live image-crop session, or `null` when not cropping. Excalidraw-style:
|
|
1280
|
+
* the crop frame IS the element's visible box, and the user drags edge /
|
|
1281
|
+
* corner handles inward (hides pixels) or the image body (pans the source).
|
|
1282
|
+
*
|
|
1283
|
+
* - `id` — the image being cropped.
|
|
1284
|
+
* - `crop` — pending normalised source rect.
|
|
1285
|
+
* - `position` / `width` / `height` — the pending element box (world position
|
|
1286
|
+
* + local size); a handle drag moves them, a body pan leaves them fixed.
|
|
1287
|
+
* - `drag` — the active gesture, or `null` when only hovering.
|
|
1288
|
+
* - `dragStartWorld` — pointer world position at drag start (body pan basis).
|
|
1289
|
+
*
|
|
1290
|
+
* Seeded on {@link beginImageCrop}; committed by {@link commitImageCrop} (one
|
|
1291
|
+
* undo step), abandoned by {@link cancelImageCrop}.
|
|
1292
|
+
*/
|
|
1293
|
+
cropSession: {
|
|
1294
|
+
id: ElementId;
|
|
1295
|
+
crop: ImageCrop;
|
|
1296
|
+
position: Vec2;
|
|
1297
|
+
width: number;
|
|
1298
|
+
height: number;
|
|
1299
|
+
drag: {
|
|
1300
|
+
kind: "handle";
|
|
1301
|
+
handle: CropHandle;
|
|
1302
|
+
} | {
|
|
1303
|
+
kind: "body";
|
|
1304
|
+
} | null;
|
|
1305
|
+
dragStartWorld: Vec2 | null;
|
|
1306
|
+
} | null;
|
|
1307
|
+
/** The image-crop session (read-only accessor for UI / overlay). */
|
|
1308
|
+
get imageCropSession(): {
|
|
1309
|
+
readonly id: ElementId;
|
|
1310
|
+
readonly crop: ImageCrop;
|
|
1311
|
+
readonly position: Vec2;
|
|
1312
|
+
readonly width: number;
|
|
1313
|
+
readonly height: number;
|
|
1314
|
+
} | null;
|
|
1315
|
+
/**
|
|
1316
|
+
* Enter crop mode for the image `id`, seeding the pending crop / box from its
|
|
1317
|
+
* current state (or the full image). No-op for non-image shapes or in
|
|
1318
|
+
* read-only. Typically triggered by a double-click on an image.
|
|
1319
|
+
*/
|
|
1320
|
+
beginImageCrop(id: ElementId): void;
|
|
1321
|
+
/**
|
|
1322
|
+
* Hit-test `worldPoint` against the pending crop chrome: a crop handle when
|
|
1323
|
+
* within {@link CROP_HANDLE_HIT_RADIUS} (screen px, zoom-compensated) of one,
|
|
1324
|
+
* `"body"` when inside the window, else `null`. Returns `null` when not
|
|
1325
|
+
* cropping.
|
|
1326
|
+
*/
|
|
1327
|
+
cropHandleAtWorld(worldPoint: Vec2): CropHandle | "body" | null;
|
|
1328
|
+
/** Begin dragging crop handle `handle` from `worldPoint`. */
|
|
1329
|
+
beginImageCropHandle(handle: CropHandle, worldPoint: Vec2): void;
|
|
1330
|
+
/** Begin panning the image body under the fixed window from `worldPoint`. */
|
|
1331
|
+
beginImageCropBody(worldPoint: Vec2): void;
|
|
1332
|
+
/**
|
|
1333
|
+
* Update the active crop drag to `worldPoint` — resize the window (handle) or
|
|
1334
|
+
* pan the source (body). Geometry is recomputed from the ORIGINAL element so
|
|
1335
|
+
* it stays stable across many moves. No-op when no drag is active.
|
|
1336
|
+
*/
|
|
1337
|
+
updateImageCropDrag(worldPoint: Vec2): void;
|
|
1338
|
+
/** Finish the current crop drag (keeps the pending crop / box). */
|
|
1339
|
+
endImageCropDrag(): void;
|
|
1340
|
+
/** Apply the pending crop + box and leave crop mode. One undo step. */
|
|
1341
|
+
commitImageCrop(): void;
|
|
1342
|
+
/** Abandon the crop session without changing the image. */
|
|
1343
|
+
cancelImageCrop(): void;
|
|
1344
|
+
/**
|
|
1345
|
+
* Synthetic element carrying the PENDING crop box (position / size) over the
|
|
1346
|
+
* original element's rotation / scale — the frame the user currently sees.
|
|
1347
|
+
* Used to project the crop frame and handles.
|
|
1348
|
+
*/
|
|
1349
|
+
private pendingCropElement;
|
|
1350
|
+
/**
|
|
1351
|
+
* World-space corners (clockwise) of the pending crop frame, or `null` when
|
|
1352
|
+
* not cropping. The frame is the pending element box mapped through its
|
|
1353
|
+
* local→world transform (so rotation / scale are honoured).
|
|
1354
|
+
*/
|
|
1355
|
+
private cropFrameCorners;
|
|
1356
|
+
/**
|
|
1357
|
+
* Ghost-image overlay descriptor for the crop session: the ORIGINAL element
|
|
1358
|
+
* (its transform + live bitmap handle) and the virtual full-image LOCAL rect
|
|
1359
|
+
* the whole bitmap occupies. `null` when not cropping. The overlay paints the
|
|
1360
|
+
* full bitmap faintly over this rect so hidden parts stay visible.
|
|
1361
|
+
*/
|
|
1362
|
+
private cropGhost;
|
|
1148
1363
|
/**
|
|
1149
1364
|
* Update non-style text properties (`fontSize`, `fontFamily`,
|
|
1150
1365
|
* `maxWidth`) on every selected text shape. Non-text shapes are
|
|
@@ -1253,6 +1468,13 @@ export declare class Editor {
|
|
|
1253
1468
|
zoomToFit(padding?: number): void;
|
|
1254
1469
|
/** Fit the camera to the current selection (standard `⌥2`). No-op when empty. */
|
|
1255
1470
|
zoomToSelection(padding?: number): void;
|
|
1471
|
+
/**
|
|
1472
|
+
* Center the camera on the current selection for a reveal / jump-to (search
|
|
1473
|
+
* navigation). Unlike {@link zoomToSelection}, it does NOT fill the screen —
|
|
1474
|
+
* a small match keeps its size and is merely centered; the zoom only drops to
|
|
1475
|
+
* fit an oversized match. No-op when the selection is empty.
|
|
1476
|
+
*/
|
|
1477
|
+
revealSelection(padding?: number): void;
|
|
1256
1478
|
/**
|
|
1257
1479
|
* Select the nearest interactable top-level element in `direction` from the
|
|
1258
1480
|
* current selection's centre (or the viewport centre when nothing is
|
|
@@ -1429,7 +1651,22 @@ export declare class Editor {
|
|
|
1429
1651
|
* selected, you can see".
|
|
1430
1652
|
*/
|
|
1431
1653
|
computeHiddenElements(): ReadonlySet<ElementId> | undefined;
|
|
1654
|
+
/**
|
|
1655
|
+
* Live stroke-erase preview: while a Shift-held eraser gesture drags, the
|
|
1656
|
+
* fragments each touched brush WOULD become, plus the set of touched
|
|
1657
|
+
* originals to hide in the main pass. `null` outside a stroke-erase gesture
|
|
1658
|
+
* or when the path touches no brush. Recomputed each frame from the
|
|
1659
|
+
* path-so-far — never mutates the scene or history.
|
|
1660
|
+
*/
|
|
1661
|
+
private computeStrokeErasePreview;
|
|
1432
1662
|
computeDimElements(enteredGroupId: ElementId): ReadonlySet<ElementId>;
|
|
1663
|
+
/**
|
|
1664
|
+
* Dim set fed to the renderer: group-isolation dim UNION the eraser's
|
|
1665
|
+
* pending-delete set (shapes swept by the current eraser stroke are shown
|
|
1666
|
+
* dimmed so the user sees what release will delete). `undefined` when neither
|
|
1667
|
+
* is active, keeping the fast tile-cache render path.
|
|
1668
|
+
*/
|
|
1669
|
+
private computeDimSet;
|
|
1433
1670
|
/**
|
|
1434
1671
|
* Enter a group — subsequent hits inside this group return children
|
|
1435
1672
|
* directly instead of the group root. `null` exits group-edit mode.
|
|
@@ -1817,19 +2054,47 @@ export declare class Editor {
|
|
|
1817
2054
|
*/
|
|
1818
2055
|
runLayout(parentId: ElementId): Patch | null;
|
|
1819
2056
|
/**
|
|
1820
|
-
*
|
|
1821
|
-
*
|
|
1822
|
-
*
|
|
2057
|
+
* Derived elbow-route cache, keyed by link — the source of truth for
|
|
2058
|
+
* A*-routed corners, living OUTSIDE the immutable `Scene`. Each entry holds
|
|
2059
|
+
* the routed interior `points` (between from/to) plus the `sig` of the
|
|
2060
|
+
* inputs it was computed from (endpoint refs + bound-shape bounds +
|
|
2061
|
+
* fixedSegments), so an unchanged link short-circuits the reroute.
|
|
2062
|
+
*
|
|
2063
|
+
* `rerouteElbows` still MIRRORS `points` onto `Link.routedPoints` in
|
|
2064
|
+
* `_scene` because three readers still consume the baked field: the render
|
|
2065
|
+
* path (`getLinkPath` in `renderer-core`), the headless `getLinkPath`, and
|
|
2066
|
+
* serialization (`schema.ts`). The mirror is compat-only derived state — no
|
|
2067
|
+
* history push / notify.
|
|
2068
|
+
*
|
|
2069
|
+
* TODO(fable R7a): drop the `_scene` mirror once (a) the render path reads
|
|
2070
|
+
* routes from this cache via the `RenderSnapshot`, and (b) headless
|
|
2071
|
+
* `getLinkPath` / serialization stop depending on baked `routedPoints`.
|
|
2072
|
+
* That eviction changes headless geometry, serialized output, and
|
|
2073
|
+
* collab-synced fields, so it spans the scene / serialization / renderer /
|
|
2074
|
+
* headless goldens and must land as its own cross-package change — out of
|
|
2075
|
+
* scope for this state-only pass.
|
|
2076
|
+
*/
|
|
2077
|
+
private readonly elbowRoutes;
|
|
2078
|
+
/**
|
|
2079
|
+
* Choke-point reroute (standard model): recompute the route for every
|
|
2080
|
+
* orthogonal link whose inputs changed since the last pass, store it in the
|
|
2081
|
+
* derived {@link elbowRoutes} cache, and mirror it onto `_scene`. Runs once
|
|
2082
|
+
* per frame before paint — derived state, so no history push / notify (would
|
|
2083
|
+
* loop). Cheap when nothing moved (signature short-circuit).
|
|
1823
2084
|
*/
|
|
1824
|
-
private
|
|
2085
|
+
private rerouteElbows;
|
|
1825
2086
|
/**
|
|
1826
|
-
*
|
|
1827
|
-
*
|
|
1828
|
-
*
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
2087
|
+
* Collect everything {@link renderEditor} paints from into a flat
|
|
2088
|
+
* {@link RenderSnapshot}. Resolves the derived viewport / dirty-rect / dim /
|
|
2089
|
+
* hide inputs and the shared spatial index up front (same order the
|
|
2090
|
+
* orchestrator used to call them in), so the paint pass stays side-effect
|
|
2091
|
+
* free and the orchestrator stays decoupled from this class.
|
|
2092
|
+
*
|
|
2093
|
+
* `computeDirtyWorld` is order-sensitive (it diffs against
|
|
2094
|
+
* `lastRenderedScene` and populates `tileDirtyElements`); it runs here and
|
|
2095
|
+
* the `lastRendered*` bookkeeping is applied in `render()` after the paint.
|
|
1831
2096
|
*/
|
|
1832
|
-
private
|
|
2097
|
+
private buildRenderSnapshot;
|
|
1833
2098
|
private render;
|
|
1834
2099
|
}
|
|
1835
2100
|
/** Distance from point `p` to the finite segment `a`–`b` (world space). */
|