@oh-just-another/state 0.57.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 +18 -0
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/actions/actionClipboard.d.ts +6 -0
- package/dist/actions/actionClipboard.d.ts.map +1 -0
- package/dist/actions/actionClipboard.js +26 -0
- package/dist/actions/actionClipboard.js.map +1 -0
- package/dist/actions/actionGrouping.d.ts +5 -0
- package/dist/actions/actionGrouping.d.ts.map +1 -0
- package/dist/actions/actionGrouping.js +26 -0
- package/dist/actions/actionGrouping.js.map +1 -0
- package/dist/actions/actionHistory.d.ts +5 -0
- package/dist/actions/actionHistory.d.ts.map +1 -0
- package/dist/actions/actionHistory.js +25 -0
- package/dist/actions/actionHistory.js.map +1 -0
- package/dist/actions/actionKeyboard.d.ts +3 -0
- package/dist/actions/actionKeyboard.d.ts.map +1 -0
- package/dist/actions/actionKeyboard.js +130 -0
- package/dist/actions/actionKeyboard.js.map +1 -0
- package/dist/actions/actionLayout.d.ts +3 -0
- package/dist/actions/actionLayout.d.ts.map +1 -0
- package/dist/actions/actionLayout.js +65 -0
- package/dist/actions/actionLayout.js.map +1 -0
- package/dist/actions/actionMode.d.ts +13 -0
- package/dist/actions/actionMode.d.ts.map +1 -0
- package/dist/actions/actionMode.js +116 -0
- package/dist/actions/actionMode.js.map +1 -0
- package/dist/actions/actionSelection.d.ts +9 -0
- package/dist/actions/actionSelection.d.ts.map +1 -0
- package/dist/actions/actionSelection.js +57 -0
- package/dist/actions/actionSelection.js.map +1 -0
- package/dist/actions/actionView.d.ts +7 -0
- package/dist/actions/actionView.d.ts.map +1 -0
- package/dist/actions/actionView.js +14 -0
- package/dist/actions/actionView.js.map +1 -0
- package/dist/actions/actionZOrder.d.ts +7 -0
- package/dist/actions/actionZOrder.d.ts.map +1 -0
- package/dist/actions/actionZOrder.js +42 -0
- package/dist/actions/actionZOrder.js.map +1 -0
- package/dist/actions/actionZoom.d.ts +8 -0
- package/dist/actions/actionZoom.d.ts.map +1 -0
- package/dist/actions/actionZoom.js +61 -0
- package/dist/actions/actionZoom.js.map +1 -0
- package/dist/actions/index.d.ts +12 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +11 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/registry.d.ts +68 -0
- package/dist/actions/registry.d.ts.map +1 -0
- package/dist/actions/registry.js +300 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/actions/types.d.ts +103 -0
- package/dist/actions/types.d.ts.map +1 -0
- package/dist/actions/types.js +10 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/animation-tick.d.ts +28 -0
- package/dist/animation-tick.d.ts.map +1 -0
- package/dist/animation-tick.js +50 -0
- package/dist/animation-tick.js.map +1 -0
- package/dist/auto-compact.d.ts +27 -0
- package/dist/auto-compact.d.ts.map +1 -0
- package/dist/auto-compact.js +46 -0
- package/dist/auto-compact.js.map +1 -0
- package/dist/auto-layout-scheduler.d.ts +50 -0
- package/dist/auto-layout-scheduler.d.ts.map +1 -0
- package/dist/auto-layout-scheduler.js +94 -0
- package/dist/auto-layout-scheduler.js.map +1 -0
- package/dist/built-in-handlers.d.ts +24 -0
- package/dist/built-in-handlers.d.ts.map +1 -0
- package/dist/built-in-handlers.js +210 -0
- package/dist/built-in-handlers.js.map +1 -0
- package/dist/clipboard.d.ts +51 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +105 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/constants.d.ts +544 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +537 -0
- package/dist/constants.js.map +1 -0
- package/dist/dom-events.d.ts +13 -0
- package/dist/dom-events.d.ts.map +1 -0
- package/dist/dom-events.js +70 -0
- package/dist/dom-events.js.map +1 -0
- package/dist/editor/anchor-points.d.ts +22 -0
- package/dist/editor/anchor-points.d.ts.map +1 -0
- package/dist/editor/anchor-points.js +28 -0
- package/dist/editor/anchor-points.js.map +1 -0
- package/dist/editor/applies/create.d.ts +60 -0
- package/dist/editor/applies/create.d.ts.map +1 -0
- package/dist/editor/applies/create.js +107 -0
- package/dist/editor/applies/create.js.map +1 -0
- package/dist/editor/applies/edge.d.ts +32 -0
- package/dist/editor/applies/edge.d.ts.map +1 -0
- package/dist/editor/applies/edge.js +44 -0
- package/dist/editor/applies/edge.js.map +1 -0
- package/dist/editor/applies/link-move.d.ts +53 -0
- package/dist/editor/applies/link-move.d.ts.map +1 -0
- package/dist/editor/applies/link-move.js +160 -0
- package/dist/editor/applies/link-move.js.map +1 -0
- package/dist/editor/applies/move.d.ts +38 -0
- package/dist/editor/applies/move.d.ts.map +1 -0
- package/dist/editor/applies/move.js +83 -0
- package/dist/editor/applies/move.js.map +1 -0
- package/dist/editor/applies/resize.d.ts +68 -0
- package/dist/editor/applies/resize.d.ts.map +1 -0
- package/dist/editor/applies/resize.js +199 -0
- package/dist/editor/applies/resize.js.map +1 -0
- package/dist/editor/applies/selection.d.ts +34 -0
- package/dist/editor/applies/selection.d.ts.map +1 -0
- package/dist/editor/applies/selection.js +65 -0
- package/dist/editor/applies/selection.js.map +1 -0
- package/dist/editor/applies/snap-grid.d.ts +45 -0
- package/dist/editor/applies/snap-grid.d.ts.map +1 -0
- package/dist/editor/applies/snap-grid.js +90 -0
- package/dist/editor/applies/snap-grid.js.map +1 -0
- package/dist/editor/container-ops.d.ts +74 -0
- package/dist/editor/container-ops.d.ts.map +1 -0
- package/dist/editor/container-ops.js +262 -0
- package/dist/editor/container-ops.js.map +1 -0
- package/dist/editor/event-fanout.d.ts +48 -0
- package/dist/editor/event-fanout.d.ts.map +1 -0
- package/dist/editor/event-fanout.js +53 -0
- package/dist/editor/event-fanout.js.map +1 -0
- package/dist/editor/gesture-tx.d.ts +77 -0
- package/dist/editor/gesture-tx.d.ts.map +1 -0
- package/dist/editor/gesture-tx.js +93 -0
- package/dist/editor/gesture-tx.js.map +1 -0
- package/dist/editor/hit-test.d.ts +178 -0
- package/dist/editor/hit-test.d.ts.map +1 -0
- package/dist/editor/hit-test.js +460 -0
- package/dist/editor/hit-test.js.map +1 -0
- package/dist/editor/long-press.d.ts +34 -0
- package/dist/editor/long-press.d.ts.map +1 -0
- package/dist/editor/long-press.js +58 -0
- package/dist/editor/long-press.js.map +1 -0
- package/dist/editor/pinch.d.ts +37 -0
- package/dist/editor/pinch.d.ts.map +1 -0
- package/dist/editor/pinch.js +90 -0
- package/dist/editor/pinch.js.map +1 -0
- package/dist/editor/pointer-binding.d.ts +18 -0
- package/dist/editor/pointer-binding.d.ts.map +1 -0
- package/dist/editor/pointer-binding.js +1087 -0
- package/dist/editor/pointer-binding.js.map +1 -0
- package/dist/editor/public/annotations.d.ts +54 -0
- package/dist/editor/public/annotations.d.ts.map +1 -0
- package/dist/editor/public/annotations.js +106 -0
- package/dist/editor/public/annotations.js.map +1 -0
- package/dist/editor/public/arrange-group.d.ts +81 -0
- package/dist/editor/public/arrange-group.d.ts.map +1 -0
- package/dist/editor/public/arrange-group.js +193 -0
- package/dist/editor/public/arrange-group.js.map +1 -0
- package/dist/editor/public/brush.d.ts +33 -0
- package/dist/editor/public/brush.d.ts.map +1 -0
- package/dist/editor/public/brush.js +54 -0
- package/dist/editor/public/brush.js.map +1 -0
- package/dist/editor/public/clipboard.d.ts +31 -0
- package/dist/editor/public/clipboard.d.ts.map +1 -0
- package/dist/editor/public/clipboard.js +35 -0
- package/dist/editor/public/clipboard.js.map +1 -0
- package/dist/editor/public/image-insert.d.ts +30 -0
- package/dist/editor/public/image-insert.d.ts.map +1 -0
- package/dist/editor/public/image-insert.js +57 -0
- package/dist/editor/public/image-insert.js.map +1 -0
- package/dist/editor/public/layers.d.ts +56 -0
- package/dist/editor/public/layers.d.ts.map +1 -0
- package/dist/editor/public/layers.js +104 -0
- package/dist/editor/public/layers.js.map +1 -0
- package/dist/editor/public/link.d.ts +25 -0
- package/dist/editor/public/link.d.ts.map +1 -0
- package/dist/editor/public/link.js +74 -0
- package/dist/editor/public/link.js.map +1 -0
- package/dist/editor/public/placement.d.ts +60 -0
- package/dist/editor/public/placement.d.ts.map +1 -0
- package/dist/editor/public/placement.js +111 -0
- package/dist/editor/public/placement.js.map +1 -0
- package/dist/editor/public/selection-ops.d.ts +78 -0
- package/dist/editor/public/selection-ops.d.ts.map +1 -0
- package/dist/editor/public/selection-ops.js +226 -0
- package/dist/editor/public/selection-ops.js.map +1 -0
- package/dist/editor/public/text-edit.d.ts +17 -0
- package/dist/editor/public/text-edit.d.ts.map +1 -0
- package/dist/editor/public/text-edit.js +28 -0
- package/dist/editor/public/text-edit.js.map +1 -0
- package/dist/editor/public/z-order.d.ts +47 -0
- package/dist/editor/public/z-order.d.ts.map +1 -0
- package/dist/editor/public/z-order.js +151 -0
- package/dist/editor/public/z-order.js.map +1 -0
- package/dist/editor/public/zoom-pan.d.ts +44 -0
- package/dist/editor/public/zoom-pan.d.ts.map +1 -0
- package/dist/editor/public/zoom-pan.js +116 -0
- package/dist/editor/public/zoom-pan.js.map +1 -0
- package/dist/editor/render-orchestrator.d.ts +11 -0
- package/dist/editor/render-orchestrator.d.ts.map +1 -0
- package/dist/editor/render-orchestrator.js +473 -0
- package/dist/editor/render-orchestrator.js.map +1 -0
- package/dist/editor/resize-helpers.d.ts +30 -0
- package/dist/editor/resize-helpers.d.ts.map +1 -0
- package/dist/editor/resize-helpers.js +121 -0
- package/dist/editor/resize-helpers.js.map +1 -0
- package/dist/editor/shape-filters.d.ts +11 -0
- package/dist/editor/shape-filters.d.ts.map +1 -0
- package/dist/editor/shape-filters.js +19 -0
- package/dist/editor/shape-filters.js.map +1 -0
- package/dist/editor/shape-traits.d.ts +16 -0
- package/dist/editor/shape-traits.d.ts.map +1 -0
- package/dist/editor/shape-traits.js +29 -0
- package/dist/editor/shape-traits.js.map +1 -0
- package/dist/editor/viewport-helpers.d.ts +24 -0
- package/dist/editor/viewport-helpers.d.ts.map +1 -0
- package/dist/editor/viewport-helpers.js +63 -0
- package/dist/editor/viewport-helpers.js.map +1 -0
- package/dist/editor-events.d.ts +33 -0
- package/dist/editor-events.d.ts.map +1 -0
- package/dist/editor-events.js +2 -0
- package/dist/editor-events.js.map +1 -0
- package/dist/editor.d.ts +1841 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +4803 -0
- package/dist/editor.js.map +1 -0
- package/dist/file-drop.d.ts +99 -0
- package/dist/file-drop.d.ts.map +1 -0
- package/dist/file-drop.js +198 -0
- package/dist/file-drop.js.map +1 -0
- package/dist/frame-helpers.d.ts +30 -0
- package/dist/frame-helpers.d.ts.map +1 -0
- package/dist/frame-helpers.js +105 -0
- package/dist/frame-helpers.js.map +1 -0
- package/dist/group-helpers.d.ts +39 -0
- package/dist/group-helpers.d.ts.map +1 -0
- package/dist/group-helpers.js +116 -0
- package/dist/group-helpers.js.map +1 -0
- package/dist/handle.d.ts +73 -0
- package/dist/handle.d.ts.map +1 -0
- package/dist/handle.js +184 -0
- package/dist/handle.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive.d.ts +21 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +11 -0
- package/dist/interactive.js.map +1 -0
- package/dist/link-selection.d.ts +21 -0
- package/dist/link-selection.d.ts.map +1 -0
- package/dist/link-selection.js +37 -0
- package/dist/link-selection.js.map +1 -0
- package/dist/machine.d.ts +322 -0
- package/dist/machine.d.ts.map +1 -0
- package/dist/machine.js +568 -0
- package/dist/machine.js.map +1 -0
- package/dist/modes.d.ts +24 -0
- package/dist/modes.d.ts.map +1 -0
- package/dist/modes.js +2 -0
- package/dist/modes.js.map +1 -0
- package/dist/overlay.d.ts +262 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +673 -0
- package/dist/overlay.js.map +1 -0
- package/dist/platform.d.ts +70 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +115 -0
- package/dist/platform.js.map +1 -0
- package/dist/selection.d.ts +18 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +31 -0
- package/dist/selection.js.map +1 -0
- package/package.json +59 -0
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,1841 @@
|
|
|
1
|
+
import { type Actor } from "xstate";
|
|
2
|
+
import type { Bounds, FileId, ElementId, Vec2 } from "@oh-just-another/types";
|
|
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";
|
|
6
|
+
import { ElementCache, type RenderTarget, type TextShaper, type Rasterizer } from "@oh-just-another/renderer-core";
|
|
7
|
+
import { type HistoryOptions, type HistoryProvider, type TransactionHandle } from "@oh-just-another/history";
|
|
8
|
+
import { type FileDropHandler } from "./file-drop.js";
|
|
9
|
+
import { interactionMachine, type InteractionContext, type InteractionEmit, type PressTarget } from "./machine.js";
|
|
10
|
+
import type { Mode } from "./modes.js";
|
|
11
|
+
import type { EditorEvents } from "./editor-events.js";
|
|
12
|
+
import { LongPressController } from "./editor/long-press.js";
|
|
13
|
+
import { PinchController } from "./editor/pinch.js";
|
|
14
|
+
import { type BrushStrokeState } from "./editor/public/brush.js";
|
|
15
|
+
import { type PeerCursor, type PeerSelection } from "./overlay.js";
|
|
16
|
+
import * as Selection from "./selection.js";
|
|
17
|
+
import * as LinkSelection from "./link-selection.js";
|
|
18
|
+
export interface LoadSceneOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Keep the existing undo/redo stack when swapping scenes. Used by
|
|
21
|
+
* `@collab/bindEditor` when a peer update arrives — the user's
|
|
22
|
+
* local history must survive remote edits. Default `false`:
|
|
23
|
+
* top-level callers loading a saved scene get a clean slate.
|
|
24
|
+
*
|
|
25
|
+
* When `true`, history patches that reference shapes removed by the
|
|
26
|
+
* remote peer become un-applicable: the local user sees an undo no-op
|
|
27
|
+
* or an exception on that step.
|
|
28
|
+
*/
|
|
29
|
+
readonly preserveHistory?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface EditorOptions {
|
|
32
|
+
readonly host: HTMLElement;
|
|
33
|
+
readonly mainTarget: RenderTarget;
|
|
34
|
+
readonly overlayTarget: RenderTarget;
|
|
35
|
+
/**
|
|
36
|
+
* Optional dedicated background target — when provided, the editor
|
|
37
|
+
* paints the grid (`renderGrid`) onto it. Hosts without a background
|
|
38
|
+
* layer can omit this; in that case the grid is drawn on `mainTarget`
|
|
39
|
+
* before shapes.
|
|
40
|
+
*/
|
|
41
|
+
readonly backgroundTarget?: RenderTarget;
|
|
42
|
+
/**
|
|
43
|
+
* Called synchronously at the END of every render pass, right after the
|
|
44
|
+
* targets have been painted. Hosts whose surface defers submission
|
|
45
|
+
* (WebGL2 flush, OffscreenCanvas worker replay) MUST present here — not
|
|
46
|
+
* on `subscribe()`, which fires on `notify()` BEFORE the rAF-scheduled
|
|
47
|
+
* paint, leaving the surface one frame behind. No-op surfaces (Canvas2D)
|
|
48
|
+
* can omit it.
|
|
49
|
+
*/
|
|
50
|
+
readonly onAfterRender?: () => void;
|
|
51
|
+
readonly initialScene: Scene;
|
|
52
|
+
readonly initialMode?: Mode;
|
|
53
|
+
/**
|
|
54
|
+
* Pre-existing history backend, or options for the default
|
|
55
|
+
* `History` (linear stack). Any `HistoryProvider` implementation
|
|
56
|
+
* works — `@oh-just-another/collab` ships `YjsHistory` that wraps
|
|
57
|
+
* `Y.UndoManager` for CRDT-aware undo in collaborative sessions.
|
|
58
|
+
*/
|
|
59
|
+
readonly history?: HistoryProvider | HistoryOptions;
|
|
60
|
+
/**
|
|
61
|
+
* Primary input modality. Affects hit-test slop on handles and edges
|
|
62
|
+
* so a finger can grab them without precision-pointing.
|
|
63
|
+
*
|
|
64
|
+
* - `"mouse"` — pixel-accurate hit zones (default for desktop).
|
|
65
|
+
* - `"touch"` — 44 px+ touch targets (Apple HIG, WCAG AAA).
|
|
66
|
+
* - `"auto"` — pick `"touch"` if `matchMedia('(pointer: coarse)')`
|
|
67
|
+
* reports a coarse primary pointer, else `"mouse"`. Default.
|
|
68
|
+
*/
|
|
69
|
+
readonly inputMode?: "mouse" | "touch" | "auto";
|
|
70
|
+
/**
|
|
71
|
+
* Optional text shaper. When supplied, replaces the renderer's
|
|
72
|
+
* default Canvas2D `measureText` path for wrap / layout. Plug
|
|
73
|
+
* `WasmTextShaper.loadBundled()` from `@oh-just-another/text-wasm`
|
|
74
|
+
* for deterministic browser-vs-Node parity (Roboto Regular
|
|
75
|
+
* embedded; advance widths match across environments).
|
|
76
|
+
*/
|
|
77
|
+
readonly textShaper?: TextShaper;
|
|
78
|
+
/**
|
|
79
|
+
* Optional rasterizer. When supplied, hosts of `renderLinks` /
|
|
80
|
+
* future path-heavy code can opt in to WASM bezier / stroke-to-
|
|
81
|
+
* fill via `WasmRasterizer.loadBundled()` from
|
|
82
|
+
* `@oh-just-another/raster-wasm`. The kernel itself doesn't consume
|
|
83
|
+
* this directly today — exposed here so the field travels with
|
|
84
|
+
* `EditorOptions` and hosts have a single config surface.
|
|
85
|
+
*/
|
|
86
|
+
readonly rasterizer?: Rasterizer;
|
|
87
|
+
/**
|
|
88
|
+
* When `true`, the editor routes per-frame rendering through a
|
|
89
|
+
* tile compositor (`renderViaTiles` in renderer-canvas) backed
|
|
90
|
+
* by an InMemoryTileCache. Designed for very-large scenes
|
|
91
|
+
* (10 K+ shapes) where re-rasterising every visible shape per
|
|
92
|
+
* frame dominates frame budget. Below ~5 K shapes the plain
|
|
93
|
+
* scene-renderer is usually faster — leave this off.
|
|
94
|
+
*
|
|
95
|
+
* Host must supply a tile compositor function via
|
|
96
|
+
* `tileCompose`; the kernel doesn't import renderer-canvas
|
|
97
|
+
* directly. Pattern:
|
|
98
|
+
*
|
|
99
|
+
* import { renderViaTiles } from "@oh-just-another/renderer-canvas";
|
|
100
|
+
* new Editor({ ..., useTileCache: true, tileCompose: renderViaTiles });
|
|
101
|
+
*/
|
|
102
|
+
readonly useTileCache?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Compositor function called per frame when `useTileCache` is on.
|
|
105
|
+
* Receives the scene, main target, and dirty bookkeeping; should
|
|
106
|
+
* handle caching internally.
|
|
107
|
+
*/
|
|
108
|
+
readonly tileCompose?: TileComposeFn;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Signature of the tile compositor injected via EditorOptions. Editor
|
|
112
|
+
* stays decoupled from renderer-canvas; hosts wire the concrete
|
|
113
|
+
* implementation (`renderViaTiles`).
|
|
114
|
+
*/
|
|
115
|
+
export type TileComposeFn = (scene: Scene, mainTarget: RenderTarget, options: {
|
|
116
|
+
readonly viewport: Bounds;
|
|
117
|
+
/**
|
|
118
|
+
* Per-shape change record (before/after world bbox) since the
|
|
119
|
+
* last frame. Compositors route by case (add / remove / move).
|
|
120
|
+
* `null` on one side = added / removed.
|
|
121
|
+
*/
|
|
122
|
+
readonly changedElements: ReadonlyMap<ElementId, {
|
|
123
|
+
before: Bounds | null;
|
|
124
|
+
after: Bounds | null;
|
|
125
|
+
}>;
|
|
126
|
+
readonly zoomBucket: number;
|
|
127
|
+
}) => void;
|
|
128
|
+
/**
|
|
129
|
+
* Top-level interaction controller. Owns the scene + selection state, wires
|
|
130
|
+
* pointer events from the host element into the interaction machine, applies
|
|
131
|
+
* the machine's emitted effects back to the scene, and re-renders main and
|
|
132
|
+
* overlay on every change.
|
|
133
|
+
*/
|
|
134
|
+
/** Outcome of `Editor.groupSelected`. `noop` when nothing was selected. */
|
|
135
|
+
export type GroupSelectedResult = {
|
|
136
|
+
readonly kind: "noop";
|
|
137
|
+
} | {
|
|
138
|
+
readonly kind: "grouped";
|
|
139
|
+
readonly groupId: ElementId;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Stable keys for cursor states a host can override with a custom image via
|
|
143
|
+
* {@link Editor.setCursorOverride}. Each maps to one outcome of `computeCursor`.
|
|
144
|
+
*/
|
|
145
|
+
export type CursorRole = "default" | "pan-ready" | "pan-active" | "move" | "draw" | "text" | "link-start" | "link-handle" | "annotation" | "resize-nwse" | "resize-nesw" | "resize-ns" | "resize-ew";
|
|
146
|
+
/**
|
|
147
|
+
* A custom cursor: either a raw CSS `cursor` value, or an image with an
|
|
148
|
+
* optional `@2x` variant (DPR-aware via `image-set`), hotspot, and keyword
|
|
149
|
+
* fallback.
|
|
150
|
+
*/
|
|
151
|
+
export type CursorSpec = string | {
|
|
152
|
+
/** 1x image URL or data-URL. */
|
|
153
|
+
readonly url: string;
|
|
154
|
+
/** Optional 2x image for hi-DPI (retina) — emitted via `image-set`. */
|
|
155
|
+
readonly url2x?: string;
|
|
156
|
+
/** Hotspot offset (px) within the image; defaults to (0, 0). */
|
|
157
|
+
readonly hotspot?: {
|
|
158
|
+
readonly x: number;
|
|
159
|
+
readonly y: number;
|
|
160
|
+
};
|
|
161
|
+
/** Keyword shown if the image can't load / is too large. */
|
|
162
|
+
readonly fallback?: string;
|
|
163
|
+
};
|
|
164
|
+
export declare class Editor {
|
|
165
|
+
readonly host: HTMLElement;
|
|
166
|
+
readonly mainTarget: RenderTarget;
|
|
167
|
+
readonly overlayTarget: RenderTarget;
|
|
168
|
+
readonly backgroundTarget: RenderTarget | null;
|
|
169
|
+
private readonly onAfterRender;
|
|
170
|
+
/**
|
|
171
|
+
* Debug: when true the overlay paints every element's mouse hit-zones
|
|
172
|
+
* (handle slop / edge endpoint / edge body). Toggled by the host
|
|
173
|
+
* debug panel via `setDebugHitZones`; read by the render orchestrator.
|
|
174
|
+
* View-only — never persisted or recorded in history.
|
|
175
|
+
*/
|
|
176
|
+
debugHitZones: boolean;
|
|
177
|
+
/**
|
|
178
|
+
* When false the background grid is not painted. Toggled via `toggleGrid`
|
|
179
|
+
* (`g` hotkey, standard parity). View-only — never persisted or in history.
|
|
180
|
+
*/
|
|
181
|
+
gridVisible: boolean;
|
|
182
|
+
readonly actor: Actor<typeof interactionMachine>;
|
|
183
|
+
private readonly listeners;
|
|
184
|
+
/**
|
|
185
|
+
* Typed event surface. Specific events (`mode`, `selection`,
|
|
186
|
+
* `scene`, `history`, `viewport`) fan out of `notify()` based on
|
|
187
|
+
* what actually changed since the last fire, so subscribers only
|
|
188
|
+
* wake up when their slice flips. `change` still fires once per
|
|
189
|
+
* `notify()` for callers that don't care which slice.
|
|
190
|
+
*
|
|
191
|
+
* The `subscribe()` set runs in lockstep with the typed events.
|
|
192
|
+
*/
|
|
193
|
+
private readonly events;
|
|
194
|
+
/**
|
|
195
|
+
* Last-emitted snapshot of every observable slice. Used by
|
|
196
|
+
* `fanOutEvents` (in `editor/event-fanout.ts`) to decide which
|
|
197
|
+
* typed events to fire on each `notify()` — only the slices
|
|
198
|
+
* whose identity changed since the previous notify get an event.
|
|
199
|
+
*/
|
|
200
|
+
private readonly eventCache;
|
|
201
|
+
private readonly unbind;
|
|
202
|
+
_scene: Scene;
|
|
203
|
+
_selection: Selection.Selection;
|
|
204
|
+
/**
|
|
205
|
+
* Snapshot of an in-progress annotation drag (press on pin → move
|
|
206
|
+
* pointer → release). `originPosition` is the annotation's stored
|
|
207
|
+
* position at press time; per-move handler computes a delta from
|
|
208
|
+
* the current pointer in world space and writes it back.
|
|
209
|
+
*/
|
|
210
|
+
annotationDrag: {
|
|
211
|
+
id: AnnotationId;
|
|
212
|
+
originPosition: Vec2;
|
|
213
|
+
originWorldPoint: Vec2;
|
|
214
|
+
moved: boolean;
|
|
215
|
+
} | null;
|
|
216
|
+
/** Live preview while drawing a new shape; null when not drawing. */
|
|
217
|
+
drawingPreview: Bounds | null;
|
|
218
|
+
edgePreview: {
|
|
219
|
+
from: Vec2;
|
|
220
|
+
to: Vec2;
|
|
221
|
+
points?: readonly Vec2[];
|
|
222
|
+
} | null;
|
|
223
|
+
/**
|
|
224
|
+
* Active "drag a link from a start-anchor" gesture. Set when a
|
|
225
|
+
* press lands on one of the selected element's link-start dots; lets
|
|
226
|
+
* the user draw a link straight from the dot without switching to the
|
|
227
|
+
* draw-edge tool. `fromWorld` is the true anchor world point (the link
|
|
228
|
+
* origin, un-offset); `origin` is the press point (for the drag
|
|
229
|
+
* threshold). Read by the pointer handlers (drive preview / commit on
|
|
230
|
+
* up) and the render orchestrator (keep the source's start dots visible
|
|
231
|
+
* during the drag). Null when no such drag is in flight. */
|
|
232
|
+
linkDragFromAnchor: {
|
|
233
|
+
fromElement: ElementId;
|
|
234
|
+
fromWorld: Vec2;
|
|
235
|
+
/** Named anchor the gesture started on — drives the click-to-create
|
|
236
|
+
* direction (outward normal) and the source link endpoint. */
|
|
237
|
+
anchorName: string;
|
|
238
|
+
origin: Vec2;
|
|
239
|
+
moved: boolean;
|
|
240
|
+
} | null;
|
|
241
|
+
/**
|
|
242
|
+
* Element being hovered while draw-edge mode is active. Drives the port-
|
|
243
|
+
* overlay render so the user sees attachment points. `null` outside
|
|
244
|
+
* draw-edge mode or when the pointer is over empty canvas.
|
|
245
|
+
*/
|
|
246
|
+
hoveredLinkTarget: {
|
|
247
|
+
elementId: ElementId;
|
|
248
|
+
activeAnchor: string | null;
|
|
249
|
+
outlinePoint?: Vec2 | undefined;
|
|
250
|
+
/**
|
|
251
|
+
* What the drop will produce, for clear pre-drop feedback (standard):
|
|
252
|
+
* - `"point"` → fixed attach to a specific dot (highlight the dot);
|
|
253
|
+
* - `"element"` → floating attach to the whole shape (highlight the
|
|
254
|
+
* element). Mirrors `snapLinkEndpoint`: an anchor within threshold →
|
|
255
|
+
* point, otherwise floating.
|
|
256
|
+
*/
|
|
257
|
+
mode: "point" | "element";
|
|
258
|
+
} | null;
|
|
259
|
+
/**
|
|
260
|
+
* Last idle cursor position (world) in select mode — the overlay grows the
|
|
261
|
+
* SINGLE selected element's link-start dot nearest it
|
|
262
|
+
* (`ANCHOR_DOT_HOVER_GROW_RADIUS`). Reset to null on press / gesture.
|
|
263
|
+
*/
|
|
264
|
+
hoverCursorWorld: Vec2 | null;
|
|
265
|
+
/**
|
|
266
|
+
* When a link is dropped on empty canvas, the edge is created with a
|
|
267
|
+
* free `point` end and this records where, so the host can pop a
|
|
268
|
+
* mini shape-picker at that spot (standard). Picking a shape re-points the
|
|
269
|
+
* end to the new element; dismissing (Esc / click-away) leaves the free
|
|
270
|
+
* end on the canvas. `null` when no menu is pending.
|
|
271
|
+
*/
|
|
272
|
+
private pendingLinkDropMenu;
|
|
273
|
+
/**
|
|
274
|
+
* Currently selected links (connectors). Links are first-class members
|
|
275
|
+
* of the selection: they coexist with selected elements, join Cmd+A and
|
|
276
|
+
* marquee, and multi-select via Shift-click. Endpoint drag handles show
|
|
277
|
+
* only when EXACTLY one link is selected and no elements are (see
|
|
278
|
+
* `selectedLink`). Empty set = no link selected.
|
|
279
|
+
*/
|
|
280
|
+
_selectedLinks: LinkSelection.LinkSelection;
|
|
281
|
+
/**
|
|
282
|
+
* Currently focused annotation thread — overlay highlights its pin
|
|
283
|
+
* with an accent ring and hosts (e.g. `<CommentsPopover>`) render
|
|
284
|
+
* the thread for this id. Independent of shape / edge selection so
|
|
285
|
+
* users can edit shapes while a comment thread is open.
|
|
286
|
+
*/
|
|
287
|
+
_selectedAnnotation: AnnotationId | null;
|
|
288
|
+
/**
|
|
289
|
+
* Author identity used for comments posted via `addComment` /
|
|
290
|
+
* `addAnnotation` without an explicit author. Hosts set this once
|
|
291
|
+
* (typically from the same user object passed to `bindAwareness`).
|
|
292
|
+
* Defaults to a synthetic local user.
|
|
293
|
+
*/
|
|
294
|
+
private commentAuthor;
|
|
295
|
+
/**
|
|
296
|
+
* Mid-drag preview state when the user is dragging an edge endpoint.
|
|
297
|
+
* Drawn as an overlay line + handle dot so the user sees the target.
|
|
298
|
+
*/
|
|
299
|
+
linkEndpointDrag: {
|
|
300
|
+
linkId: LinkId;
|
|
301
|
+
side: "from" | "to";
|
|
302
|
+
toPoint: Vec2;
|
|
303
|
+
} | null;
|
|
304
|
+
/**
|
|
305
|
+
* Host-managed waypoint (bend-point) drag of the selected link. `index`
|
|
306
|
+
* is the position in `edge.waypoints`. `pendingInsert` means the gesture
|
|
307
|
+
* began on a segment midpoint and will splice a new waypoint on the
|
|
308
|
+
* first move (so a no-move click adds nothing). Live-mutated through the
|
|
309
|
+
* gesture transaction → one undo step per drag.
|
|
310
|
+
*/
|
|
311
|
+
linkWaypointDrag: {
|
|
312
|
+
linkId: LinkId;
|
|
313
|
+
index: number;
|
|
314
|
+
pendingInsert: boolean;
|
|
315
|
+
} | null;
|
|
316
|
+
/**
|
|
317
|
+
* Host-managed elbow segment drag. `index` is the segment in the routed
|
|
318
|
+
* chain `[from, ...routedPoints, to]`; `axis` is its orientation. Dragging
|
|
319
|
+
* pins the segment's perpendicular coordinate into `Link.fixedSegments`;
|
|
320
|
+
* the reroute pass re-flows the rest. One undo step via the gesture tx.
|
|
321
|
+
*/
|
|
322
|
+
linkSegmentDrag: {
|
|
323
|
+
linkId: LinkId;
|
|
324
|
+
axis: "h" | "v";
|
|
325
|
+
at: number;
|
|
326
|
+
} | null;
|
|
327
|
+
/** Live lasso bounds during a rubber-band select gesture. */
|
|
328
|
+
lassoPreview: Bounds | null;
|
|
329
|
+
/**
|
|
330
|
+
* Selection captured at lasso-press time. Used to compute the live
|
|
331
|
+
* preview correctly: in `replace` mode the lasso starts from empty
|
|
332
|
+
* each frame; in `add` mode it starts from this snapshot so shapes
|
|
333
|
+
* the user already had selected don't blink out and back.
|
|
334
|
+
*/
|
|
335
|
+
private lassoBaseSelection;
|
|
336
|
+
/** Link-selection counterpart of `lassoBaseSelection` for the marquee. */
|
|
337
|
+
private lassoBaseLinks;
|
|
338
|
+
/**
|
|
339
|
+
* Snapshot of every selected shape's `position` at press-down. Used to
|
|
340
|
+
* translate the whole group additively during a multi-shape drag. The
|
|
341
|
+
* machine still emits per-shape MOVE_SHAPE — the editor intercepts and
|
|
342
|
+
* fans out when this map is populated.
|
|
343
|
+
*/
|
|
344
|
+
groupMoveOrigin: ReadonlyMap<ElementId, Vec2> | null;
|
|
345
|
+
/**
|
|
346
|
+
* Press-time snapshot of connectors that must follow a multi-element
|
|
347
|
+
* drag rigidly — both endpoints bound to moved elements, carrying
|
|
348
|
+
* absolute geometry (waypoints / fixedSegments / routedPoints). Each
|
|
349
|
+
* frame translates from these originals so the shift never compounds.
|
|
350
|
+
* Cleared on gesture commit / cancel alongside `groupMoveOrigin`.
|
|
351
|
+
*/
|
|
352
|
+
groupLinkMoveOrigin: ReadonlyMap<LinkId, Link> | null;
|
|
353
|
+
/**
|
|
354
|
+
* Per-shape snapshot for a group-resize gesture — `bounds` is the
|
|
355
|
+
* shape's world AABB at press-down. Editor scales the relative
|
|
356
|
+
* position / size against the combined bounds delta each frame.
|
|
357
|
+
*/
|
|
358
|
+
groupResizeOrigin: {
|
|
359
|
+
readonly combined: Bounds;
|
|
360
|
+
readonly elements: ReadonlyMap<ElementId, {
|
|
361
|
+
readonly position: Vec2;
|
|
362
|
+
readonly bounds: Bounds;
|
|
363
|
+
readonly scale: Vec2;
|
|
364
|
+
}>;
|
|
365
|
+
readonly links: ReadonlyMap<LinkId, Link>;
|
|
366
|
+
} | null;
|
|
367
|
+
/**
|
|
368
|
+
* Pristine shape snapshot for a single-shape text resize, captured on
|
|
369
|
+
* the gesture's first tick. Font scaling is computed against this base
|
|
370
|
+
* so it never compounds across pointermove ticks. Cleared on gesture
|
|
371
|
+
* end (commit / cancel).
|
|
372
|
+
*/
|
|
373
|
+
private _resizeOriginElement;
|
|
374
|
+
/**
|
|
375
|
+
* Active layer — new shapes created via `addElement` / `applyCreate` land
|
|
376
|
+
* here when their input doesn't specify a `layerId`. Defaults to the
|
|
377
|
+
* scene's `DEFAULT_LAYER_ID`; hosts switch via `setActiveLayer`.
|
|
378
|
+
*/
|
|
379
|
+
_activeLayerId: LayerId;
|
|
380
|
+
private nextId;
|
|
381
|
+
/** Generate a short unique id with a stable prefix. */
|
|
382
|
+
private uniqueId;
|
|
383
|
+
/**
|
|
384
|
+
* Snap engine — defaults to grid + anchor + outline contributors.
|
|
385
|
+
*/
|
|
386
|
+
private readonly snapEngine;
|
|
387
|
+
/** Snap threshold in world units. */
|
|
388
|
+
private readonly snapThreshold;
|
|
389
|
+
/**
|
|
390
|
+
* Transient flag set by the host while a snap-suppress modifier
|
|
391
|
+
* (Cmd / Ctrl) is held during a drag — lets the user pull a shape off
|
|
392
|
+
* the grid for one gesture without toggling snap off. Read by the
|
|
393
|
+
* move / resize / create wrappers; never persisted.
|
|
394
|
+
*/
|
|
395
|
+
private snapSuppressed;
|
|
396
|
+
/**
|
|
397
|
+
* Persistent world-bounds cache shared with `renderScene` for viewport
|
|
398
|
+
* culling. Object-identity keyed — invalidates automatically whenever
|
|
399
|
+
* a scene op replaces the shape ref. Could be exposed for hit-test
|
|
400
|
+
* sharing in a follow-up.
|
|
401
|
+
*/
|
|
402
|
+
readonly boundsCache: ElementCache<Bounds>;
|
|
403
|
+
/**
|
|
404
|
+
* Lazy SpatialGrid for hit-test acceleration in large scenes.
|
|
405
|
+
* Built on demand when `scene.elements.size >= LARGE_SCENE_HIT_THRESHOLD`
|
|
406
|
+
* and the cached index's source-scene reference is stale (any scene
|
|
407
|
+
* op replaces the `_scene` field, invalidating identity).
|
|
408
|
+
*/
|
|
409
|
+
private spatialIndexCache;
|
|
410
|
+
/**
|
|
411
|
+
* The group the user has "entered" via double-click. While set, the
|
|
412
|
+
* hit-test stops promoting children of this group to the group root,
|
|
413
|
+
* letting the user directly manipulate inner shapes. Cleared on
|
|
414
|
+
* escape, click outside the group's descendants, or `cancelInteraction`.
|
|
415
|
+
*/
|
|
416
|
+
_enteredGroup: ElementId | null;
|
|
417
|
+
/**
|
|
418
|
+
* Double-click detection state. Updated on every non-drag pointer
|
|
419
|
+
* up; the next pointer-up within `DOUBLE_CLICK_MS` and within
|
|
420
|
+
* `DOUBLE_CLICK_TOLERANCE_PX` of `lastClickWorldPoint` counts as a
|
|
421
|
+
* double-click. Used to trigger group drill-down (enter isolation).
|
|
422
|
+
*/
|
|
423
|
+
private lastClickAt;
|
|
424
|
+
private lastClickWorldPoint;
|
|
425
|
+
/**
|
|
426
|
+
* Separate double-click tracker for link edit handles (waypoint /
|
|
427
|
+
* segment). Kept apart from `lastClickAt` because a handle press
|
|
428
|
+
* returns early in `onDown` (begin-drag) and never reaches the up-side
|
|
429
|
+
* double-click path that updates `lastClickAt`. Updated by
|
|
430
|
+
* `isHandleDoubleClick` on each handle press.
|
|
431
|
+
*/
|
|
432
|
+
private lastHandleClickAt;
|
|
433
|
+
private lastHandleClickWorld;
|
|
434
|
+
/**
|
|
435
|
+
* In-progress brush stroke. Hosts push points via
|
|
436
|
+
* `extendBrushStroke`; the overlay reads it through
|
|
437
|
+
* `pendingBrushStroke` to draw a live preview.
|
|
438
|
+
*/
|
|
439
|
+
brushStroke: BrushStrokeState | null;
|
|
440
|
+
/**
|
|
441
|
+
* Last world-space pointer position observed by the host's onMove
|
|
442
|
+
* handler. `paste()` uses it as the default drop target so a fresh
|
|
443
|
+
* paste lands under the cursor instead of overlapping the originals.
|
|
444
|
+
* `null` until the pointer first enters the host.
|
|
445
|
+
*/
|
|
446
|
+
lastPointerWorld: Vec2 | null;
|
|
447
|
+
/** Host-registered custom cursor images per role (see `setCursorOverride`). */
|
|
448
|
+
private readonly cursorOverrides;
|
|
449
|
+
/**
|
|
450
|
+
* Scene rendered on the last frame. Used to compute a dirty rect by
|
|
451
|
+
* identity-diffing against the current scene — every shape / edge
|
|
452
|
+
* whose ref didn't change is also pixel-identical to its last paint
|
|
453
|
+
* and gets skipped together with the surrounding clear. `null` until
|
|
454
|
+
* the first render.
|
|
455
|
+
*/
|
|
456
|
+
lastRenderedScene: Scene | null;
|
|
457
|
+
/**
|
|
458
|
+
* Last-painted isolation root — paired with `lastRenderedScene` so
|
|
459
|
+
* the dirty-rect optimization invalidates when the user enters or
|
|
460
|
+
* exits a group, even when the scene reference is unchanged. Without
|
|
461
|
+
* this, drilling into a group never triggers a redraw → the dim
|
|
462
|
+
* pass would never visibly apply.
|
|
463
|
+
*/
|
|
464
|
+
lastRenderedEnteredGroup: ElementId | null;
|
|
465
|
+
/**
|
|
466
|
+
* Fractional-order compaction scheduler (microtask-coalesced).
|
|
467
|
+
* Triggered from every `notify()`; only does real work when at
|
|
468
|
+
* least one shape/edge order string crossed AUTO_COMPACT_THRESHOLD.
|
|
469
|
+
* See `./auto-compact.ts` for the extracted logic.
|
|
470
|
+
*/
|
|
471
|
+
private readonly autoCompactScheduler;
|
|
472
|
+
/**
|
|
473
|
+
* Auto-layout scheduler — microtask-coalesced re-run of every
|
|
474
|
+
* shape carrying `metadata.autoLayout`. See
|
|
475
|
+
* `./auto-layout-scheduler.ts` for the extracted logic.
|
|
476
|
+
*/
|
|
477
|
+
private readonly autoLayoutScheduler;
|
|
478
|
+
/**
|
|
479
|
+
* Element id that the user started dragging on press-down. Tracked
|
|
480
|
+
* separately from the state machine so the editor knows what to
|
|
481
|
+
* (re)parent / drop into a container on pointerup. `null` between
|
|
482
|
+
* gestures, set in onDown when press lands on a shape and cleared
|
|
483
|
+
* in onUp / cancel.
|
|
484
|
+
*/
|
|
485
|
+
dragElementId: ElementId | null;
|
|
486
|
+
/**
|
|
487
|
+
* Element that the current press added to the selection additively
|
|
488
|
+
* (shift / meta click on an unselected shape). The press promotes it
|
|
489
|
+
* so a subsequent drag moves it; on a *tap* the up-handler would
|
|
490
|
+
* otherwise `SELECT_TOGGLE` it straight back off, so it consults this
|
|
491
|
+
* to skip that redundant toggle. Reset at every press-down.
|
|
492
|
+
*/
|
|
493
|
+
additivePressAdded: ElementId | null;
|
|
494
|
+
/**
|
|
495
|
+
* Live container highlight: the container shape the dragged item is
|
|
496
|
+
* currently hovering over. Drawn by the overlay as a dashed
|
|
497
|
+
* accent rect on the container's drop-zone so the user sees where the
|
|
498
|
+
* shape will land after release.
|
|
499
|
+
*/
|
|
500
|
+
containerHover: {
|
|
501
|
+
id: ElementId;
|
|
502
|
+
dropZone: Bounds;
|
|
503
|
+
} | null;
|
|
504
|
+
/**
|
|
505
|
+
* Remote peer cursors / selections, pushed in by the host (typically
|
|
506
|
+
* a `bindAwareness(editor, awareness)` helper in `@collab`). The
|
|
507
|
+
* editor only renders them; it doesn't fetch or interpret. Each
|
|
508
|
+
* setter triggers `render()` so the overlay updates immediately.
|
|
509
|
+
*/
|
|
510
|
+
_peerCursors: readonly PeerCursor[];
|
|
511
|
+
_peerSelections: readonly PeerSelection[];
|
|
512
|
+
/**
|
|
513
|
+
* Subscribers notified on every host pointer move (world-space). Used
|
|
514
|
+
* by `@collab` to broadcast the local cursor into awareness.
|
|
515
|
+
*/
|
|
516
|
+
readonly cursorListeners: Set<(point: Vec2) => void>;
|
|
517
|
+
/**
|
|
518
|
+
* Active screen-space pointer positions keyed by `pointerId`. With
|
|
519
|
+
* one entry the editor's normal single-pointer flow applies. With
|
|
520
|
+
* two or more entries we enter a pinch / pan gesture and bypass the
|
|
521
|
+
* interaction machine — `pinchOrigin` holds the baseline.
|
|
522
|
+
*/
|
|
523
|
+
readonly activePointers: Map<number, Vec2>;
|
|
524
|
+
/**
|
|
525
|
+
* One-finger-pan candidate: set at pointer-down when a TOUCH press lands
|
|
526
|
+
* on empty canvas in select mode. A tap (no movement) still falls through
|
|
527
|
+
* to select/deselect; once the finger drags past slop, onMove promotes
|
|
528
|
+
* this to a real pan instead of a marquee lasso (mobile convention).
|
|
529
|
+
* Screen-space origin point.
|
|
530
|
+
*/
|
|
531
|
+
touchPanCandidate: Vec2 | null;
|
|
532
|
+
pinch: PinchController;
|
|
533
|
+
/** Bridge for `editor/container-ops.ts`. Built lazily in constructor. */
|
|
534
|
+
private containerOpsRef;
|
|
535
|
+
/**
|
|
536
|
+
* Space-bar held → next pointer drag pans the canvas instead of
|
|
537
|
+
* doing whatever the current mode would do. Visual cursor goes to
|
|
538
|
+
* "grab" / "grabbing". Wires a window-level keydown/keyup listener
|
|
539
|
+
* in `bindPointerEvents`.
|
|
540
|
+
*/
|
|
541
|
+
spaceHeld: boolean;
|
|
542
|
+
/**
|
|
543
|
+
* Host-supplied tile compositor — when set (via
|
|
544
|
+
* `EditorOptions.useTileCache` + `tileCompose`), the per-frame
|
|
545
|
+
* render path delegates to it instead of `renderScene`. Stays
|
|
546
|
+
* `null` for the typical small-scene case.
|
|
547
|
+
*/
|
|
548
|
+
readonly tileComposeFn: TileComposeFn | null;
|
|
549
|
+
/**
|
|
550
|
+
* Per-shape change record (before/after world bbox) since the last
|
|
551
|
+
* tile-cache invalidation pass. Populated by `computeDirtyWorld`'s
|
|
552
|
+
* diff loop when `tileComposeFn` is on; forwarded to the compositor
|
|
553
|
+
* each frame so it can invalidate by add / remove / move correctly.
|
|
554
|
+
* (A plain id set lost adds — new id wasn't in the tile reverse
|
|
555
|
+
* index yet.)
|
|
556
|
+
*/
|
|
557
|
+
tileDirtyElements: Map<ElementId, {
|
|
558
|
+
before: Bounds | null;
|
|
559
|
+
after: Bounds | null;
|
|
560
|
+
}>;
|
|
561
|
+
/**
|
|
562
|
+
* Tool-lock flag (standard model). When `false` (default), a
|
|
563
|
+
* draw-mode (`draw-rect` / `draw-ellipse` / `draw-edge` / `brush`)
|
|
564
|
+
* auto-reverts to `select` after a successful create. When `true`,
|
|
565
|
+
* the mode persists so the user can draw many shapes in a row
|
|
566
|
+
* without re-selecting the tool.
|
|
567
|
+
*
|
|
568
|
+
* Toggled via `Editor.setToolLocked(bool)` and surfaced in the
|
|
569
|
+
* toolbar as a lock affordance next to the active tool.
|
|
570
|
+
*/
|
|
571
|
+
private _toolLocked;
|
|
572
|
+
/**
|
|
573
|
+
* Host-extensible file-drop dispatch. Built-ins (image / scene
|
|
574
|
+
* JSON) register themselves at editor construction; hosts add
|
|
575
|
+
* more via `registerFileDropHandler`.
|
|
576
|
+
*/
|
|
577
|
+
private readonly fileDropRegistry;
|
|
578
|
+
/**
|
|
579
|
+
* Active pan gesture (right-click drag or Space + left drag).
|
|
580
|
+
* `pointerId` is captured by the host so move/up events keep
|
|
581
|
+
* arriving even after the cursor leaves the host bounds.
|
|
582
|
+
* `startPoint` is the press position — used to decide "click vs
|
|
583
|
+
* drag" at pointerup (a near-zero displacement right-click is a
|
|
584
|
+
* context-menu request, not a pan). `lastPoint` tracks the
|
|
585
|
+
* previous move so per-frame delta is correct.
|
|
586
|
+
*
|
|
587
|
+
* `button` records which mouse button started the gesture so we
|
|
588
|
+
* only treat right-click releases as potential context-menu
|
|
589
|
+
* triggers (Space + left-drag never opens a menu).
|
|
590
|
+
*/
|
|
591
|
+
panGesture: {
|
|
592
|
+
pointerId: number;
|
|
593
|
+
button: number;
|
|
594
|
+
startPoint: Vec2;
|
|
595
|
+
lastPoint: Vec2;
|
|
596
|
+
moved: boolean;
|
|
597
|
+
} | null;
|
|
598
|
+
/**
|
|
599
|
+
* Set on a right-click pointerdown so the upcoming native
|
|
600
|
+
* `contextmenu` event can be unconditionally preventDefault'ed
|
|
601
|
+
* (the gesture decides whether to fire the menu manually on
|
|
602
|
+
* pointerup based on whether the user dragged).
|
|
603
|
+
*/
|
|
604
|
+
suppressNextContextMenu: boolean;
|
|
605
|
+
/**
|
|
606
|
+
* Long-press tracking. Starts on `pointerdown`; cancelled on
|
|
607
|
+
* `pointermove > LONG_PRESS_MAX_MOVEMENT_PX` or `pointerup` before
|
|
608
|
+
* the timer fires. Hosts subscribe via `onLongPress` to surface a
|
|
609
|
+
* context menu (mobile alternative to right-click).
|
|
610
|
+
*/
|
|
611
|
+
longPress: LongPressController;
|
|
612
|
+
private readonly longPressListeners;
|
|
613
|
+
/**
|
|
614
|
+
* Live-region announcements for assistive tech. The editor pushes
|
|
615
|
+
* short, human-readable strings ("Selected Rectangle", "Moved 5 px
|
|
616
|
+
* right") that hosts pipe into an `aria-live=polite` region.
|
|
617
|
+
*/
|
|
618
|
+
private readonly announceListeners;
|
|
619
|
+
/**
|
|
620
|
+
* Resolved primary input modality + derived hit slops. Computed once
|
|
621
|
+
* in the constructor from `EditorOptions.inputMode` (default `"auto"`
|
|
622
|
+
* uses `matchMedia('(pointer: coarse)')`).
|
|
623
|
+
*/
|
|
624
|
+
private readonly inputMode;
|
|
625
|
+
private readonly handleHitSlop;
|
|
626
|
+
private readonly edgeHandleHitSlop;
|
|
627
|
+
private readonly edgeHitThreshold;
|
|
628
|
+
/** Link-start anchor-dot grab/click hit radii — touch-enlarged in touch mode. */
|
|
629
|
+
readonly anchorStartHitSlop: number;
|
|
630
|
+
readonly anchorClickRadius: number;
|
|
631
|
+
readonly _history: HistoryProvider;
|
|
632
|
+
/** Open transaction during a single drag/resize gesture. */
|
|
633
|
+
gestureTx: TransactionHandle | null;
|
|
634
|
+
/**
|
|
635
|
+
* Immutable snapshot of `_scene` taken when a gesture transaction opens.
|
|
636
|
+
* The history transaction only records patches for undo — cancelling it does
|
|
637
|
+
* NOT roll back `_scene`. Keeping the pre-gesture scene lets Escape (and any
|
|
638
|
+
* cancel) restore it, so a drag/move/resize/endpoint-rebind aborted with Esc
|
|
639
|
+
* leaves the scene exactly as it was. Cleared on commit.
|
|
640
|
+
*/
|
|
641
|
+
private gestureStartScene;
|
|
642
|
+
/**
|
|
643
|
+
* Wraps gesture lifecycle (transaction open/commit/cancel +
|
|
644
|
+
* post-create mode revert) so editor.ts doesn't carry the bodies.
|
|
645
|
+
* Implementation lives in `./editor/gesture-tx.ts`; the
|
|
646
|
+
* controller calls back through the narrow `GestureRef` bridge
|
|
647
|
+
* built lazily below.
|
|
648
|
+
*/
|
|
649
|
+
private readonly gestures;
|
|
650
|
+
constructor(options: EditorOptions);
|
|
651
|
+
/** Snapshot used by event-fanout. Kept private — internal API. */
|
|
652
|
+
private observableSnapshot;
|
|
653
|
+
get scene(): Scene;
|
|
654
|
+
get selection(): Selection.Selection;
|
|
655
|
+
get mode(): Mode;
|
|
656
|
+
get history(): HistoryProvider;
|
|
657
|
+
get canUndo(): boolean;
|
|
658
|
+
get canRedo(): boolean;
|
|
659
|
+
/**
|
|
660
|
+
* The DOM element the editor was mounted onto. Read-only — external
|
|
661
|
+
* code reads it for screen-↔-world coordinate conversions on events
|
|
662
|
+
* whose coordinates are in client-space (e.g. global `contextmenu`).
|
|
663
|
+
*/
|
|
664
|
+
get hostElement(): HTMLElement;
|
|
665
|
+
/** Subscribe to scene/selection/mode/history changes. */
|
|
666
|
+
subscribe(fn: () => void): () => void;
|
|
667
|
+
/**
|
|
668
|
+
* Subscribe to local world-space pointer movement. Fires on every
|
|
669
|
+
* `pointermove` over the host. `@collab` uses this to broadcast the
|
|
670
|
+
* local cursor into the awareness room.
|
|
671
|
+
*/
|
|
672
|
+
onCursorMove(fn: (point: Vec2) => void): () => void;
|
|
673
|
+
/** Set the local user's identity for comments authored via this editor. */
|
|
674
|
+
setCommentAuthor(author: {
|
|
675
|
+
id: string;
|
|
676
|
+
name: string;
|
|
677
|
+
}): void;
|
|
678
|
+
/** Currently focused annotation id (or null when nothing is open). */
|
|
679
|
+
get selectedAnnotation(): AnnotationId | null;
|
|
680
|
+
/**
|
|
681
|
+
* Open or close an annotation thread. `null` clears the focus. The
|
|
682
|
+
* overlay highlights the pin; `<CommentsPopover>` reads this and
|
|
683
|
+
* renders the thread.
|
|
684
|
+
*/
|
|
685
|
+
setSelectedAnnotation(id: AnnotationId | null): void;
|
|
686
|
+
addAnnotation(opts: {
|
|
687
|
+
position: Vec2;
|
|
688
|
+
elementId?: ElementId | null;
|
|
689
|
+
firstComment?: string;
|
|
690
|
+
}): AnnotationId;
|
|
691
|
+
removeAnnotation(id: AnnotationId): void;
|
|
692
|
+
toggleAnnotationResolved(id: AnnotationId): void;
|
|
693
|
+
addComment(annotationId: AnnotationId, body: string, author?: {
|
|
694
|
+
id: string;
|
|
695
|
+
name: string;
|
|
696
|
+
}): void;
|
|
697
|
+
removeComment(annotationId: AnnotationId, commentId: CommentId): void;
|
|
698
|
+
hitAnnotation(worldPoint: Vec2): AnnotationId | null;
|
|
699
|
+
/**
|
|
700
|
+
* Subscribe to long-press events — a stationary touch / mouse-press
|
|
701
|
+
* held longer than `LONG_PRESS_DELAY_MS`. The mobile-equivalent of
|
|
702
|
+
* right-click; hosts open a context menu from this. The event
|
|
703
|
+
* carries both screen-space (for menu positioning) and world-space
|
|
704
|
+
* (for target hit-test) coordinates.
|
|
705
|
+
*/
|
|
706
|
+
onLongPress(fn: (payload: {
|
|
707
|
+
screenPoint: Vec2;
|
|
708
|
+
worldPoint: Vec2;
|
|
709
|
+
}) => void): () => void;
|
|
710
|
+
/**
|
|
711
|
+
* Subscribe to accessibility live-region announcements. The host
|
|
712
|
+
* pipes these strings into an `aria-live="polite"` element so a
|
|
713
|
+
* screen-reader user hears the editor's status changes. Strings
|
|
714
|
+
* are short and pre-localised by the caller of `announce`.
|
|
715
|
+
*/
|
|
716
|
+
onAnnounce(fn: (message: string) => void): () => void;
|
|
717
|
+
/**
|
|
718
|
+
* Push a live-region message to all `onAnnounce` listeners. Hosts
|
|
719
|
+
* (and plugins) call this when something happened that an SR user
|
|
720
|
+
* should hear: selection changed, shape moved, mode switched, etc.
|
|
721
|
+
* The editor itself emits a small set of canonical messages from
|
|
722
|
+
* `focusCycle` / `moveSelectionBy` / `cancelInteraction`.
|
|
723
|
+
*/
|
|
724
|
+
announce(message: string): void;
|
|
725
|
+
/**
|
|
726
|
+
* Replace the remote peer cursors painted by the overlay. Pass an
|
|
727
|
+
* empty array to clear. The host is expected to filter out the
|
|
728
|
+
* local user's cursor before calling.
|
|
729
|
+
*/
|
|
730
|
+
setPeerCursors(cursors: readonly PeerCursor[]): void;
|
|
731
|
+
/**
|
|
732
|
+
* Replace the remote peer selections painted by the overlay. The
|
|
733
|
+
* host resolves a peer's `selection: ElementId[]` into world bounds
|
|
734
|
+
* before passing them in.
|
|
735
|
+
*/
|
|
736
|
+
setPeerSelections(selections: readonly PeerSelection[]): void;
|
|
737
|
+
/**
|
|
738
|
+
* Toggle the debug hit-zone overlay (host debug panel). When on, the
|
|
739
|
+
* overlay paints every element's mouse hit-targets so the tuned slop
|
|
740
|
+
* values can be eyeballed. View-only — not recorded in history.
|
|
741
|
+
*/
|
|
742
|
+
setDebugHitZones(on: boolean): void;
|
|
743
|
+
/** Show/hide the background grid (standard `g`). View-only — not in history. */
|
|
744
|
+
setGridVisible(on: boolean): void;
|
|
745
|
+
/** Toggle background grid visibility. */
|
|
746
|
+
toggleGrid(): void;
|
|
747
|
+
/** Whether the active draw-mode sticks after a create (toolbar lock). */
|
|
748
|
+
get toolLocked(): boolean;
|
|
749
|
+
/** All currently-selected link (connector) ids. */
|
|
750
|
+
get selectedLinks(): LinkSelection.LinkSelection;
|
|
751
|
+
/**
|
|
752
|
+
* The sole selected link — non-null ONLY when exactly one link and no
|
|
753
|
+
* elements are selected. Drives endpoint handles, the edge-style panel,
|
|
754
|
+
* caption edit and `updateSelectedLink`; a multi/mixed selection yields
|
|
755
|
+
* null so those single-link affordances stay hidden.
|
|
756
|
+
*/
|
|
757
|
+
get selectedLink(): LinkId | null;
|
|
758
|
+
/**
|
|
759
|
+
* Apply an in-place mutation to the currently-selected edge as a
|
|
760
|
+
* single history step. The `updater` receives a clone of the edge
|
|
761
|
+
* and returns the next version (callers should produce a new
|
|
762
|
+
* object — Link is readonly). No-op when no edge is selected.
|
|
763
|
+
*/
|
|
764
|
+
updateSelectedLink(updater: (edge: Link) => Link): void;
|
|
765
|
+
/**
|
|
766
|
+
* Register a file-drop handler. Handlers are tried in registration
|
|
767
|
+
* order; the first whose `accept(file)` returns true takes the
|
|
768
|
+
* file. Repeated calls with the same `id` replace the previous
|
|
769
|
+
* handler (idempotent for module-load wiring).
|
|
770
|
+
*/
|
|
771
|
+
registerFileDropHandler(handler: FileDropHandler): void;
|
|
772
|
+
/** Drop a registered handler. */
|
|
773
|
+
unregisterFileDropHandler(id: string): void;
|
|
774
|
+
/**
|
|
775
|
+
* Dispatch a dropped file (or pasted file from clipboard) into
|
|
776
|
+
* the registered handlers. Returns `true` when a handler accepted
|
|
777
|
+
* the file, `false` otherwise — UI can show an "unsupported file"
|
|
778
|
+
* toast on `false`. `worldPoint` is where the file should land
|
|
779
|
+
* (drop-point projected to world coords; for paste, host can use
|
|
780
|
+
* cursor world point or viewport centre).
|
|
781
|
+
*/
|
|
782
|
+
dispatchFileDrop(file: File, worldPoint: Vec2): Promise<boolean>;
|
|
783
|
+
/**
|
|
784
|
+
* Toggle the tool-lock affordance. With `true`, draw-modes persist
|
|
785
|
+
* after each successful shape create — the user keeps drawing
|
|
786
|
+
* rectangles without re-pressing R. With `false` (default), the
|
|
787
|
+
* editor reverts to `select` after each create.
|
|
788
|
+
*/
|
|
789
|
+
setToolLocked(locked: boolean): void;
|
|
790
|
+
private maybeRevertModeAfterCreate;
|
|
791
|
+
setMode(mode: Mode): void;
|
|
792
|
+
/** Undo the latest record. No-op if there is nothing to undo. */
|
|
793
|
+
undo(): boolean;
|
|
794
|
+
/** Redo the undone record. */
|
|
795
|
+
redo(): boolean;
|
|
796
|
+
/**
|
|
797
|
+
* Add a shape to the scene and push a single record onto the history stack.
|
|
798
|
+
* Returns the resulting patch (useful for tests). Intended for code paths
|
|
799
|
+
* that create shapes outside of a pointer gesture — drag-from-palette,
|
|
800
|
+
* paste, programmatic insert.
|
|
801
|
+
*/
|
|
802
|
+
addElement(shape: Element, options?: {
|
|
803
|
+
select?: boolean;
|
|
804
|
+
}): Patch;
|
|
805
|
+
/**
|
|
806
|
+
* Insert an image at the given world position. Wraps `addElement`
|
|
807
|
+
* with the image-shape boilerplate (id, layer, order, scale=1,
|
|
808
|
+
* rotation=0). Returns the new shape's id so callers can chain
|
|
809
|
+
* (e.g. immediately reparent into a container).
|
|
810
|
+
*
|
|
811
|
+
* `src` is anything the renderer accepts — a data-URL from the
|
|
812
|
+
* file-drop handler, a host CDN URL, an SVG string in
|
|
813
|
+
* `image/svg+xml;base64,...` form.
|
|
814
|
+
*/
|
|
815
|
+
insertImage(input: {
|
|
816
|
+
src: string;
|
|
817
|
+
width: number;
|
|
818
|
+
height: number;
|
|
819
|
+
position: Vec2;
|
|
820
|
+
image?: HTMLImageElement;
|
|
821
|
+
animated?: boolean;
|
|
822
|
+
fileId?: FileId;
|
|
823
|
+
animationKind?: string;
|
|
824
|
+
animationData?: unknown;
|
|
825
|
+
}): ElementId;
|
|
826
|
+
addBinaryFile(blob: Blob, name?: string): Promise<FileId>;
|
|
827
|
+
/**
|
|
828
|
+
* Animation tick — runs while any shape carries
|
|
829
|
+
* `metadata.animated` (GIFs today; future video / lottie).
|
|
830
|
+
* Forces a full re-render every frame so drawImage picks up the
|
|
831
|
+
* current frame of natively-animated elements. Self-terminates
|
|
832
|
+
* when no animated shapes remain.
|
|
833
|
+
*
|
|
834
|
+
* Lifecycle managed by the `AnimationTick` helper (see
|
|
835
|
+
* `./animation-tick.ts`). `insertImage({animated:true})` and
|
|
836
|
+
* `loadScene` start the tick; `dispose()` stops it.
|
|
837
|
+
*/
|
|
838
|
+
/** EMA of animation-tick render cost (ms) — drives the adaptive throttle. */
|
|
839
|
+
private gifRenderCostEma;
|
|
840
|
+
/** Wall-clock of the last animation-tick render — for the interval throttle. */
|
|
841
|
+
private lastGifTickMs;
|
|
842
|
+
private readonly animationTick;
|
|
843
|
+
private hasAnimatedElement;
|
|
844
|
+
/**
|
|
845
|
+
* True when at least one animated shape's world AABB intersects the
|
|
846
|
+
* current viewport. Drives viewport-culling of the animation tick —
|
|
847
|
+
* off-screen GIFs don't burn decode / render cost, and the wall-clock
|
|
848
|
+
* frame selection means they show the right frame the moment they
|
|
849
|
+
* scroll back in.
|
|
850
|
+
*/
|
|
851
|
+
private hasVisibleAnimatedElement;
|
|
852
|
+
/**
|
|
853
|
+
* Re-arm the animation tick after a change that may have brought an
|
|
854
|
+
* animated shape into (or out of) view — pan / zoom / scene edit.
|
|
855
|
+
* `AnimationTick.start()` no-ops when already running or when
|
|
856
|
+
* `isAnimated()` is false, so this is cheap to call from `notify()`.
|
|
857
|
+
*/
|
|
858
|
+
private maybeAnimate;
|
|
859
|
+
/** Bound `visibilitychange` handler — pause/resume the tick. */
|
|
860
|
+
private readonly onVisibilityChange;
|
|
861
|
+
/**
|
|
862
|
+
* Transient per-shape playback state for animated images. `originMs`
|
|
863
|
+
* is the wall-clock the current play run started; `frozenMs` is the
|
|
864
|
+
* playback offset a paused shape is held at. Not serialised — purely
|
|
865
|
+
* a runtime view, rebuilt on insert / rehydrate.
|
|
866
|
+
*/
|
|
867
|
+
private readonly playbackState;
|
|
868
|
+
/** Element id currently hovered — a hovered heavy GIF keeps playing
|
|
869
|
+
* (its auto-stop timer is held off). Set by the pointer hover path. */
|
|
870
|
+
private hoveredAnimatedId;
|
|
871
|
+
private static nowMs;
|
|
872
|
+
private static prefersReducedMotion;
|
|
873
|
+
/**
|
|
874
|
+
* Seed playback for a freshly-animated shape. Start paused (frozen on
|
|
875
|
+
* frame 0) when the user prefers reduced motion; playing otherwise.
|
|
876
|
+
*/
|
|
877
|
+
private initPlayback;
|
|
878
|
+
/** Playback timestamp fed to the renderer's animation clock for a
|
|
879
|
+
* shape: wall-clock when unmanaged, play offset when playing, the
|
|
880
|
+
* frozen frame when paused. */
|
|
881
|
+
private playbackClock;
|
|
882
|
+
/**
|
|
883
|
+
* Toggle GIF playback for a shape — wired to a click on an animated
|
|
884
|
+
* image (resume after auto-stop, play after reduced-motion). Resuming
|
|
885
|
+
* continues from the frozen frame.
|
|
886
|
+
*/
|
|
887
|
+
togglePlayback(id: ElementId): void;
|
|
888
|
+
/**
|
|
889
|
+
* Hover entered an animated shape: resume it if paused and hold off
|
|
890
|
+
* its auto-stop timer while the pointer stays over it (the auto-stop
|
|
891
|
+
* pass keeps pushing `playStartMs` forward for the hovered shape).
|
|
892
|
+
* Pass `null` when the pointer leaves all shapes.
|
|
893
|
+
*/
|
|
894
|
+
hoverAnimatedElement(id: ElementId | null): void;
|
|
895
|
+
/** True when the shape's GIF is paused (drives the overlay badge). */
|
|
896
|
+
isPlaybackPaused(id: ElementId): boolean;
|
|
897
|
+
/**
|
|
898
|
+
* Record the idle cursor position so the overlay can grow the SINGLE
|
|
899
|
+
* selected element's link-start dot nearest the cursor. Only the selected
|
|
900
|
+
* element shows start dots (connecting from an unselected element on hover
|
|
901
|
+
* was a cancelled product decision). Pass `null` to clear.
|
|
902
|
+
*/
|
|
903
|
+
setHoverCursorWorld(cursor: Vec2 | null): void;
|
|
904
|
+
/** Live link-draw preview polyline (elbow), or null when not drawing. */
|
|
905
|
+
get linkPreviewPath(): readonly Vec2[] | null;
|
|
906
|
+
/** Current connector attach target + mode (point=fixed / element=floating). */
|
|
907
|
+
get linkAttachTarget(): {
|
|
908
|
+
elementId: ElementId;
|
|
909
|
+
mode: "point" | "element";
|
|
910
|
+
} | null;
|
|
911
|
+
/**
|
|
912
|
+
* Freeze heavy GIFs after `GIF_AUTOSTOP_MS` of continuous play.
|
|
913
|
+
* Light GIFs (small byte payload) loop forever. Called from the tick
|
|
914
|
+
* before each animation render.
|
|
915
|
+
*/
|
|
916
|
+
private autoStopHeavyGifs;
|
|
917
|
+
/**
|
|
918
|
+
* Restore transient `animationData` for animated image shapes after
|
|
919
|
+
* a scene load. The raw GIF bytes don't survive serialisation
|
|
920
|
+
* (`serializeScene` strips the ArrayBuffer), but they're persisted
|
|
921
|
+
* in `Scene.files` via the shape's `fileId`. Here we copy the bytes
|
|
922
|
+
* back onto `shape.animationData` so the registered animation
|
|
923
|
+
* adapter (host-side, e.g. the gifuct decoder) can produce frames.
|
|
924
|
+
*
|
|
925
|
+
* Applied directly to `_scene` (no history entry — this is an
|
|
926
|
+
* internal rehydration, not a user edit). No-op for shapes that
|
|
927
|
+
* already carry live `animationData` or lack a resolvable file.
|
|
928
|
+
*/
|
|
929
|
+
private rehydrateAnimatedImages;
|
|
930
|
+
/**
|
|
931
|
+
* Drag-to-place flow for palette templates. Adds the shape to the
|
|
932
|
+
* scene immediately so the user sees it dragging under the cursor,
|
|
933
|
+
* but defers the history entry until `commit()` is called. `update`
|
|
934
|
+
* re-positions without writing per-move patches; `cancel` removes
|
|
935
|
+
* the shape entirely and leaves history untouched (no undo entry).
|
|
936
|
+
*
|
|
937
|
+
* Typical wiring: HTML5 dragenter starts the placement, dragover
|
|
938
|
+
* updates, drop commits, dragleave / window keydown(Escape) cancel.
|
|
939
|
+
*/
|
|
940
|
+
beginPlacement(shape: Element): {
|
|
941
|
+
update: (worldCenter: Vec2) => void;
|
|
942
|
+
commit: () => void;
|
|
943
|
+
cancel: () => void;
|
|
944
|
+
};
|
|
945
|
+
deleteSelected(): void;
|
|
946
|
+
/**
|
|
947
|
+
* Currently edited text shape (or null). Set by `beginTextEdit`;
|
|
948
|
+
* cleared by `commitTextEdit` / `cancelTextEdit`. The host overlay
|
|
949
|
+
* (`<TextEditorOverlay>` in `@react-ui`) subscribes via `editor`
|
|
950
|
+
* and renders a `<textarea>` positioned over the shape.
|
|
951
|
+
*/
|
|
952
|
+
private _editingTextElement;
|
|
953
|
+
get editingTextElement(): ElementId | null;
|
|
954
|
+
/** Link whose caption is being edited inline (double-click), or null. */
|
|
955
|
+
private _editingLinkCaption;
|
|
956
|
+
get editingLinkCaption(): LinkId | null;
|
|
957
|
+
/**
|
|
958
|
+
* Frame whose NAME (header label) is being edited inline (double-click
|
|
959
|
+
* the header), or null. The host overlay (`<FrameNameEditorOverlay>` in
|
|
960
|
+
* `@react-ui`) renders an input over the header and commits the name.
|
|
961
|
+
*/
|
|
962
|
+
private _editingFrameName;
|
|
963
|
+
get editingFrameName(): ElementId | null;
|
|
964
|
+
/**
|
|
965
|
+
* When the `draw-text` tool just placed a shape and opened its
|
|
966
|
+
* editor, this holds that shape's id until the first commit. A
|
|
967
|
+
* pending creation isn't in history yet: committing non-empty text
|
|
968
|
+
* records a single add patch (whole shape = one undo); committing
|
|
969
|
+
* empty / cancelling removes it with no history entry at all.
|
|
970
|
+
*/
|
|
971
|
+
private _pendingTextCreate;
|
|
972
|
+
/**
|
|
973
|
+
* Snapshot of the shape at edit start. Used to revert on cancel and
|
|
974
|
+
* as the `before` of the single commit patch. `null` for a pending
|
|
975
|
+
* creation (the shape didn't exist yet).
|
|
976
|
+
*/
|
|
977
|
+
private _textEditOrigin;
|
|
978
|
+
/**
|
|
979
|
+
* Live selection inside the edited text, mirrored from the hidden
|
|
980
|
+
* `<textarea>` (`start`/`end` are source offsets, `dir` is the
|
|
981
|
+
* anchored end). The caret is `dir === "backward" ? start : end`.
|
|
982
|
+
*/
|
|
983
|
+
private _textSel;
|
|
984
|
+
/** Anchor offset for a canvas drag-select inside the edited text. */
|
|
985
|
+
private _textDragAnchor;
|
|
986
|
+
private _caretBlinkOn;
|
|
987
|
+
private _caretBlinkTimer;
|
|
988
|
+
get editingTextSelection(): {
|
|
989
|
+
start: number;
|
|
990
|
+
end: number;
|
|
991
|
+
dir: "forward" | "backward";
|
|
992
|
+
} | null;
|
|
993
|
+
/** Caret offset = the moving end of the selection. */
|
|
994
|
+
get editingTextCaret(): number | null;
|
|
995
|
+
get caretBlinkOn(): boolean;
|
|
996
|
+
/** `true` while a canvas drag-select inside the edited text is active. */
|
|
997
|
+
get isTextDragging(): boolean;
|
|
998
|
+
private startCaretBlink;
|
|
999
|
+
private stopCaretBlink;
|
|
1000
|
+
/** Reset the caret to solid (called on type / move so it never blinks off mid-action). */
|
|
1001
|
+
private wakeCaret;
|
|
1002
|
+
/**
|
|
1003
|
+
* Begin editing a text shape's body. No-op when the shape doesn't
|
|
1004
|
+
* exist or isn't a text shape. Concurrent edits commit themselves
|
|
1005
|
+
* (only one shape at a time). Caret defaults to the end of the text.
|
|
1006
|
+
*/
|
|
1007
|
+
/** Open inline caption editing for a link (double-click). */
|
|
1008
|
+
beginLinkCaptionEdit(id: LinkId): void;
|
|
1009
|
+
/**
|
|
1010
|
+
* Commit the link caption. Empty / whitespace text removes the label;
|
|
1011
|
+
* otherwise the label text is set, preserving any existing position /
|
|
1012
|
+
* styling. One undo step. Clears caption-edit mode.
|
|
1013
|
+
*/
|
|
1014
|
+
commitLinkCaptionEdit(text: string): void;
|
|
1015
|
+
/** Cancel link caption editing without changing the label. */
|
|
1016
|
+
cancelLinkCaptionEdit(): void;
|
|
1017
|
+
/** World-space anchor point for a link's caption (midpoint of its path). */
|
|
1018
|
+
linkLabelWorld(id: LinkId): Vec2 | null;
|
|
1019
|
+
beginTextEdit(id: ElementId): void;
|
|
1020
|
+
/**
|
|
1021
|
+
* Start editing a frame's header name. No-op unless `id` is a frame on
|
|
1022
|
+
* an unlocked layer. Commits any in-flight text edit first.
|
|
1023
|
+
*/
|
|
1024
|
+
beginFrameNameEdit(id: ElementId): void;
|
|
1025
|
+
/**
|
|
1026
|
+
* Commit the edited frame name. Empty / whitespace-only clears the
|
|
1027
|
+
* stored name (the renderer falls back to "Frame"). One history step;
|
|
1028
|
+
* no-op when the name is unchanged. Always clears the editing state.
|
|
1029
|
+
*/
|
|
1030
|
+
commitFrameNameEdit(name: string): void;
|
|
1031
|
+
/** Abandon the frame-name edit without changing the name. */
|
|
1032
|
+
cancelFrameNameEdit(): void;
|
|
1033
|
+
/**
|
|
1034
|
+
* Frame whose header strip (the label bar ABOVE the body) contains the
|
|
1035
|
+
* world point — top-most by z-order. Used to route a double-click on the
|
|
1036
|
+
* header to a name edit, since the header sits outside the frame's
|
|
1037
|
+
* hit-test bounds. Assumes unrotated frames (the common case).
|
|
1038
|
+
*/
|
|
1039
|
+
private frameHeaderAt;
|
|
1040
|
+
/**
|
|
1041
|
+
* Live edit transport from the hidden `<textarea>`: replace the
|
|
1042
|
+
* shape's text + selection as the user types / pastes / composes.
|
|
1043
|
+
* Mutates the scene WITHOUT a history entry — history is recorded
|
|
1044
|
+
* once on commit. No-op when not editing.
|
|
1045
|
+
*/
|
|
1046
|
+
setEditingText(value: string, selStart: number, selEnd: number, dir?: "forward" | "backward"): void;
|
|
1047
|
+
/** Selection-only update (arrows / shift-select / click) — no text change. */
|
|
1048
|
+
setEditingSelection(selStart: number, selEnd: number, dir?: "forward" | "backward"): void;
|
|
1049
|
+
/**
|
|
1050
|
+
* Map a world-space point to a caret offset in the edited text. Used
|
|
1051
|
+
* to place / extend the caret from canvas clicks. Returns `null` when
|
|
1052
|
+
* not editing or the shape is gone.
|
|
1053
|
+
*/
|
|
1054
|
+
caretIndexAtWorldPoint(worldPoint: Vec2): number | null;
|
|
1055
|
+
/**
|
|
1056
|
+
* `true` when a point is inside the currently-edited text shape's
|
|
1057
|
+
* world bounds. Used by the pointer binding to decide between
|
|
1058
|
+
* repositioning the caret (inside) and committing (outside).
|
|
1059
|
+
*/
|
|
1060
|
+
editedElementContainsPoint(worldPoint: Vec2): boolean;
|
|
1061
|
+
/** Place a collapsed caret at the clicked point and start a drag-select. */
|
|
1062
|
+
setTextCaretFromPoint(worldPoint: Vec2): void;
|
|
1063
|
+
/** Extend the selection from the drag anchor to the current point. */
|
|
1064
|
+
extendTextSelectionToPoint(worldPoint: Vec2): void;
|
|
1065
|
+
/** End a canvas drag-select (clears the drag anchor). */
|
|
1066
|
+
endTextDragSelect(): void;
|
|
1067
|
+
/** Build the editable layout for a text shape using the main target's metrics. */
|
|
1068
|
+
private editingTextLayout;
|
|
1069
|
+
/**
|
|
1070
|
+
* A measure callback bound to a shape's font, using the main target's
|
|
1071
|
+
* `measureText` — the SAME source the renderer draws with (WebGL2
|
|
1072
|
+
* reports MSDF advances) and the bounder measures with. Caret /
|
|
1073
|
+
* selection geometry therefore lines up exactly with the glyphs.
|
|
1074
|
+
*/
|
|
1075
|
+
private measureFor;
|
|
1076
|
+
/**
|
|
1077
|
+
* World-space caret + selection geometry for the overlay pass.
|
|
1078
|
+
* Returns `null` when not editing. The caret is `null` while blinked
|
|
1079
|
+
* off so the overlay can simply skip drawing it.
|
|
1080
|
+
*/
|
|
1081
|
+
editingTextOverlay(): {
|
|
1082
|
+
caret: {
|
|
1083
|
+
x: number;
|
|
1084
|
+
y: number;
|
|
1085
|
+
height: number;
|
|
1086
|
+
} | null;
|
|
1087
|
+
caretColor: string;
|
|
1088
|
+
selectionRects: readonly Bounds[];
|
|
1089
|
+
} | null;
|
|
1090
|
+
commitTextEdit(next?: string): void;
|
|
1091
|
+
cancelTextEdit(): void;
|
|
1092
|
+
/**
|
|
1093
|
+
* Translate every selected shape by the given world-space delta.
|
|
1094
|
+
* Single undo step. No-op when selection is empty. Used by arrow-key
|
|
1095
|
+
* keyboard navigation; hosts pass `{ x: 1, y: 0 }` for fine nudge
|
|
1096
|
+
* and `{ x: 10, y: 0 }` for shift-arrow.
|
|
1097
|
+
*/
|
|
1098
|
+
moveSelectionBy(delta: Vec2): void;
|
|
1099
|
+
/**
|
|
1100
|
+
* Keyboard-friendly creation flow. Picks the shape type from the
|
|
1101
|
+
* current `mode` ("draw-rect" / "draw-ellipse" / fallback to
|
|
1102
|
+
* rectangle) and inserts a sensible default-sized shape at the
|
|
1103
|
+
* viewport center. Returns the new shape's id, or `null` when the
|
|
1104
|
+
* scene has no active layer.
|
|
1105
|
+
*
|
|
1106
|
+
* Hosts can bind this to "Enter" while in a draw mode, providing a
|
|
1107
|
+
* mouse-free alternative to drag-out creation.
|
|
1108
|
+
*/
|
|
1109
|
+
createElementAtCursor(): ElementId | null;
|
|
1110
|
+
/**
|
|
1111
|
+
* `draw-text` tool: drop an empty text shape at `worldPoint`, select
|
|
1112
|
+
* it and open its inline editor immediately. The add is a single undo
|
|
1113
|
+
* step; the subsequent text typed in is committed (or the empty shape
|
|
1114
|
+
* removed) by `commitTextEdit`. Reverts to `select` afterwards unless
|
|
1115
|
+
* the tool is locked.
|
|
1116
|
+
*/
|
|
1117
|
+
createTextAt(worldPoint: Vec2): ElementId;
|
|
1118
|
+
beginBrushStroke(world: Vec2, pressure?: number): void;
|
|
1119
|
+
extendBrushStroke(world: Vec2, pressure?: number): void;
|
|
1120
|
+
commitBrushStroke(): ElementId | null;
|
|
1121
|
+
cancelBrushStroke(): void;
|
|
1122
|
+
/** Current in-progress brush stroke, exposed for the overlay preview. */
|
|
1123
|
+
get pendingBrushStroke(): {
|
|
1124
|
+
readonly origin: Vec2;
|
|
1125
|
+
readonly points: readonly BrushPoint[];
|
|
1126
|
+
} | null;
|
|
1127
|
+
arrangeAsGrid(opts?: {
|
|
1128
|
+
cols?: number;
|
|
1129
|
+
gap?: number;
|
|
1130
|
+
}): void;
|
|
1131
|
+
arrangeAsStack(opts?: {
|
|
1132
|
+
direction?: "horizontal" | "vertical";
|
|
1133
|
+
gap?: number;
|
|
1134
|
+
}): void;
|
|
1135
|
+
groupSelected(): GroupSelectedResult;
|
|
1136
|
+
ungroup(): void;
|
|
1137
|
+
private selectionRoots;
|
|
1138
|
+
expandSelectionWithDescendants(): ReadonlySet<ElementId>;
|
|
1139
|
+
focusCycle(direction: "next" | "prev"): void;
|
|
1140
|
+
/**
|
|
1141
|
+
* Clear selection + cancel any in-progress drag / draw gesture.
|
|
1142
|
+
* Bound to Escape in default keyboard nav.
|
|
1143
|
+
*/
|
|
1144
|
+
cancelInteraction(): void;
|
|
1145
|
+
/**
|
|
1146
|
+
* Duplicate the selected shapes 10 px down-right of the originals.
|
|
1147
|
+
* Links between selected shapes are NOT cloned. Single undo step.
|
|
1148
|
+
*/
|
|
1149
|
+
duplicateSelected(): void;
|
|
1150
|
+
/**
|
|
1151
|
+
* Clone the selection IN PLACE (zero offset), select the clones, and return
|
|
1152
|
+
* the clone of `anchorId` (or null). Unlike `duplicateSelected` this also
|
|
1153
|
+
* clones group descendants and frame members, remapping `parentId`/`frameId`
|
|
1154
|
+
* among the clones so a duplicated frame keeps its contents. Used by
|
|
1155
|
+
* `⌥`-drag duplicate — the caller then drags the clones, leaving the
|
|
1156
|
+
* originals. One undo step.
|
|
1157
|
+
*/
|
|
1158
|
+
duplicateSelectedInPlace(anchorId?: ElementId | null): ElementId | null;
|
|
1159
|
+
setSelection(ids: Iterable<ElementId>): void;
|
|
1160
|
+
selectAll(): void;
|
|
1161
|
+
/**
|
|
1162
|
+
* Internal clipboard. Stored as deep-cloned snapshots so subsequent
|
|
1163
|
+
* mutations don't affect the buffer. Survives across editor calls
|
|
1164
|
+
* within the same session; cross-tab paste uses host-level
|
|
1165
|
+
* `navigator.clipboard` (out of scope for the editor).
|
|
1166
|
+
*/
|
|
1167
|
+
private clipboard;
|
|
1168
|
+
copySelected(): void;
|
|
1169
|
+
cutSelected(): void;
|
|
1170
|
+
/**
|
|
1171
|
+
* Paste clipboard contents into the scene. The cluster lands so that
|
|
1172
|
+
* its centroid sits at `targetWorld` (defaults to the last tracked
|
|
1173
|
+
* cursor position; when even that is unavailable, falls back to a
|
|
1174
|
+
* +10 px nudge so duplicates stay visible). Relative offsets
|
|
1175
|
+
* between clipboard items are preserved.
|
|
1176
|
+
*
|
|
1177
|
+
* New shapes get fresh ids and end up selected. Single undo step.
|
|
1178
|
+
*/
|
|
1179
|
+
paste(targetWorld?: Vec2): void;
|
|
1180
|
+
/**
|
|
1181
|
+
* Merge `partial` into the `style` of every shape in `ids`. Useful
|
|
1182
|
+
* for the inspector / PropertyPanel: flipping `roundness`, swapping
|
|
1183
|
+
* `lineJoin`, changing `stroke` colour across a multi-selection,
|
|
1184
|
+
* etc. All changes go through one history record (single undo).
|
|
1185
|
+
*
|
|
1186
|
+
* No-op when `ids` is empty or none of the targeted shapes exist.
|
|
1187
|
+
*/
|
|
1188
|
+
updateStyle(ids: Iterable<ElementId>, partial: Partial<TextStyle>): void;
|
|
1189
|
+
/**
|
|
1190
|
+
* Update non-style text properties (`fontSize`, `fontFamily`,
|
|
1191
|
+
* `maxWidth`) on every selected text shape. Non-text shapes are
|
|
1192
|
+
* skipped. Single undo step. Used by the text contextual panel.
|
|
1193
|
+
*/
|
|
1194
|
+
updateTextProps(ids: Iterable<ElementId>, partial: {
|
|
1195
|
+
fontSize?: number;
|
|
1196
|
+
fontFamily?: string;
|
|
1197
|
+
maxWidth?: number;
|
|
1198
|
+
}): void;
|
|
1199
|
+
/**
|
|
1200
|
+
* Set (or clear, with `null`) the element-level hyperlink (`href`) on
|
|
1201
|
+
* every shape in `ids`. Single undo step. Pass a raw user string — it
|
|
1202
|
+
* is normalised here (`normalizeHref`: adds `https://`, `mailto:`,
|
|
1203
|
+
* rejects `javascript:`/`data:`); a string that normalises to nothing
|
|
1204
|
+
* clears the link. The host opens it on Cmd/Ctrl-click or the
|
|
1205
|
+
* hover link-popup.
|
|
1206
|
+
*/
|
|
1207
|
+
setLink(ids: Iterable<ElementId>, href: string | null): void;
|
|
1208
|
+
/**
|
|
1209
|
+
* Open an element hyperlink in a new tab. Re-validates the scheme
|
|
1210
|
+
* (`safeHref`) before navigating — only `http`/`https`/`mailto`, never
|
|
1211
|
+
* `javascript:` / `data:` — and uses `noopener,noreferrer`. No-op for
|
|
1212
|
+
* an unsafe / empty href or outside a browser.
|
|
1213
|
+
*/
|
|
1214
|
+
openLink(href: string | undefined | null): void;
|
|
1215
|
+
/** The element-level href of a shape, if safe to open; else `null`. */
|
|
1216
|
+
elementLink(id: ElementId): string | null;
|
|
1217
|
+
/**
|
|
1218
|
+
* Topmost interactable shape at `worldPoint` that carries a safe link,
|
|
1219
|
+
* with its world bounds — for the hover link-popup. `null` when none.
|
|
1220
|
+
*/
|
|
1221
|
+
linkAt(worldPoint: Vec2): {
|
|
1222
|
+
id: ElementId;
|
|
1223
|
+
href: string;
|
|
1224
|
+
bounds: Bounds;
|
|
1225
|
+
} | null;
|
|
1226
|
+
bringToFront(id?: ElementId): void;
|
|
1227
|
+
sendToBack(id?: ElementId): void;
|
|
1228
|
+
/** Move the target shape one step toward the top of its layer. */
|
|
1229
|
+
bringForward(id?: ElementId): void;
|
|
1230
|
+
/** Move the target shape one step toward the bottom of its layer. */
|
|
1231
|
+
sendBackward(id?: ElementId): void;
|
|
1232
|
+
compactLayerZOrder(layerId?: LayerId, options?: {
|
|
1233
|
+
recordHistory?: boolean;
|
|
1234
|
+
}): void;
|
|
1235
|
+
/**
|
|
1236
|
+
* Wipe every shape + edge from the scene. Layers and viewport survive.
|
|
1237
|
+
* Clears history — restoring an empty scene through undo would be
|
|
1238
|
+
* surprising and the operation is rarely chained with other edits.
|
|
1239
|
+
*/
|
|
1240
|
+
clear(): void;
|
|
1241
|
+
/** Currently active layer — new shapes default into it. */
|
|
1242
|
+
get activeLayerId(): LayerId;
|
|
1243
|
+
/** Switch the active layer. Hosts call this from a layer panel click. */
|
|
1244
|
+
setActiveLayer(id: LayerId): void;
|
|
1245
|
+
createLayer(name: string): LayerId;
|
|
1246
|
+
removeLayer(id: LayerId): void;
|
|
1247
|
+
renameLayer(id: LayerId, name: string): void;
|
|
1248
|
+
toggleLayerVisibility(id: LayerId): void;
|
|
1249
|
+
toggleLayerLock(id: LayerId): void;
|
|
1250
|
+
moveSelectionToLayer(targetLayer: LayerId): void;
|
|
1251
|
+
/**
|
|
1252
|
+
* Pan the camera by a screen-space delta. Drives both wheel-pan and
|
|
1253
|
+
* the two-finger pan gesture; deltas are in screen pixels (positive
|
|
1254
|
+
* x → shapes move right relative to the user). Not recorded in
|
|
1255
|
+
* history — viewport state is editor-local.
|
|
1256
|
+
*/
|
|
1257
|
+
panBy(deltaScreen: Vec2): void;
|
|
1258
|
+
zoomIn(): void;
|
|
1259
|
+
zoomOut(): void;
|
|
1260
|
+
private zoomStep;
|
|
1261
|
+
resetZoom(): void;
|
|
1262
|
+
zoomToFit(padding?: number): void;
|
|
1263
|
+
/** Fit the camera to the current selection (standard `⌥2`). No-op when empty. */
|
|
1264
|
+
zoomToSelection(padding?: number): void;
|
|
1265
|
+
/**
|
|
1266
|
+
* Select the nearest interactable top-level element in `direction` from the
|
|
1267
|
+
* current selection's centre (or the viewport centre when nothing is
|
|
1268
|
+
* selected). standard `⌘`+arrows. Candidates must lie within a 45° cone of the
|
|
1269
|
+
* direction; the closest by along+lateral distance wins. No-op when nothing
|
|
1270
|
+
* qualifies.
|
|
1271
|
+
*/
|
|
1272
|
+
selectClosest(direction: "left" | "right" | "up" | "down"): void;
|
|
1273
|
+
zoomAt(factor: number, anchorWorld: Vec2): void;
|
|
1274
|
+
setViewportSize(width: number, height: number): void;
|
|
1275
|
+
setGrid(patch: {
|
|
1276
|
+
size?: number;
|
|
1277
|
+
style?: GridStyle;
|
|
1278
|
+
snap?: boolean;
|
|
1279
|
+
}): void;
|
|
1280
|
+
/** Whether snap-to-grid is currently enabled (default on). */
|
|
1281
|
+
get snapToGridEnabled(): boolean;
|
|
1282
|
+
/** Toggle snap-to-grid on/off. Persists in the viewport. */
|
|
1283
|
+
setSnapToGrid(enabled: boolean): void;
|
|
1284
|
+
/**
|
|
1285
|
+
* Host hook: while held, the next move/resize/create gesture ignores
|
|
1286
|
+
* snap-to-grid (Cmd/Ctrl modifier). The app wires keydown/keyup
|
|
1287
|
+
* of the modifier to this. Idempotent; never touches history.
|
|
1288
|
+
*/
|
|
1289
|
+
setSnapSuppressed(suppressed: boolean): void;
|
|
1290
|
+
/**
|
|
1291
|
+
* True when a gesture should snap: feature on, grid visible, AND the
|
|
1292
|
+
* suppress modifier not held. Hiding the grid (`toggleGrid` / `g`) also
|
|
1293
|
+
* disables snap-to-grid — snapping to an invisible grid is confusing.
|
|
1294
|
+
* Re-showing the grid restores the stored snap preference.
|
|
1295
|
+
*/
|
|
1296
|
+
private snapActive;
|
|
1297
|
+
/** World-unit spacing the current gesture snaps to. */
|
|
1298
|
+
private snapSpacing;
|
|
1299
|
+
/**
|
|
1300
|
+
* Replace the entire scene (e.g. after `parseScene`). Clears history,
|
|
1301
|
+
* selection and any open gesture. Use to load a saved document.
|
|
1302
|
+
*/
|
|
1303
|
+
loadScene(scene: Scene, options?: LoadSceneOptions): void;
|
|
1304
|
+
/** Detach all DOM listeners and stop the actor. */
|
|
1305
|
+
dispose(): void;
|
|
1306
|
+
private bindPointerEvents;
|
|
1307
|
+
/**
|
|
1308
|
+
* Open a pan gesture: capture the pointer so subsequent move / up
|
|
1309
|
+
* events arrive even outside the host bounds, cancel anything the
|
|
1310
|
+
* machine might have started this tick, and switch the cursor.
|
|
1311
|
+
*/
|
|
1312
|
+
beginPanGesture(pointerId: number, button: number, point: Vec2): void;
|
|
1313
|
+
/**
|
|
1314
|
+
* End an in-progress pan gesture. Restores the cursor unless Space
|
|
1315
|
+
* is still held (then we drop back to "grab" so the user knows
|
|
1316
|
+
* another drag is armed). For right-click that didn't move past
|
|
1317
|
+
* the slop threshold, fires the long-press callback so the context
|
|
1318
|
+
* menu opens at the click position — that's the "right-click =
|
|
1319
|
+
* menu, right-drag = pan" decision rule.
|
|
1320
|
+
*/
|
|
1321
|
+
endPanGesture(): void;
|
|
1322
|
+
isDrawingPhase(ctx: InteractionContext): boolean;
|
|
1323
|
+
startLongPress(screenPoint: Vec2): void;
|
|
1324
|
+
cancelLongPress(): void;
|
|
1325
|
+
beginPinch(): void;
|
|
1326
|
+
applyPinch(): void;
|
|
1327
|
+
/**
|
|
1328
|
+
* Convert a point in the host element's CSS-pixel coordinate space into
|
|
1329
|
+
* world coordinates. Public so drop handlers (drag-from-palette, paste)
|
|
1330
|
+
* can map pointer positions back to scene space.
|
|
1331
|
+
*/
|
|
1332
|
+
screenToWorld(point: Vec2): Vec2;
|
|
1333
|
+
/**
|
|
1334
|
+
* Attach target under `worldPoint` for an endpoint-rebind drop: the topmost
|
|
1335
|
+
* interactable ELEMENT (group-promoted), ignoring link bodies and the dragged
|
|
1336
|
+
* link's own endpoint handle. Used instead of {@link hitTest} when finishing
|
|
1337
|
+
* an endpoint drag — `hitTest` would return the (now live, cursor-tracking)
|
|
1338
|
+
* endpoint handle and shadow the element beneath it, blocking re-binding.
|
|
1339
|
+
* `undefined` → dropped on empty space (the end stays a free point).
|
|
1340
|
+
*/
|
|
1341
|
+
linkAttachTargetAt(worldPoint: Vec2): PressTarget | undefined;
|
|
1342
|
+
hitTest(worldPoint: Vec2): PressTarget;
|
|
1343
|
+
/**
|
|
1344
|
+
* Recompute the canvas cursor from the current interaction state and apply
|
|
1345
|
+
* it to the host element. Single chokepoint — called from pointer-move
|
|
1346
|
+
* (hover), gesture begin/end, and mode changes so the cursor never drifts
|
|
1347
|
+
* out of sync. `worldPoint` defaults to the last known pointer position.
|
|
1348
|
+
*/
|
|
1349
|
+
refreshCursor(worldPoint?: Vec2): void;
|
|
1350
|
+
/**
|
|
1351
|
+
* The CSS cursor for the current state. Priority: active gesture → text edit → pan affordance →
|
|
1352
|
+
* draw tool → idle hover hit-test. Pure read of editor state; no side effects.
|
|
1353
|
+
*/
|
|
1354
|
+
private computeCursor;
|
|
1355
|
+
/**
|
|
1356
|
+
* Resolve a cursor role to a CSS `cursor` value: a host-registered custom
|
|
1357
|
+
* image (via {@link setCursorOverride}) if present, else `fallbackKeyword`.
|
|
1358
|
+
*/
|
|
1359
|
+
private resolveCursor;
|
|
1360
|
+
/**
|
|
1361
|
+
* Register (or clear, with `null`) a custom cursor image for a state role.
|
|
1362
|
+
* The image is shown wherever `computeCursor` resolves that role; pass a
|
|
1363
|
+
* `CursorSpec` object for a DPR-aware image (`image-set(1x, 2x)`) with a
|
|
1364
|
+
* hotspot + keyword fallback, or a raw CSS cursor string. Host-only view
|
|
1365
|
+
* state — not persisted.
|
|
1366
|
+
*/
|
|
1367
|
+
setCursorOverride(role: CursorRole, spec: CursorSpec | null): void;
|
|
1368
|
+
/**
|
|
1369
|
+
* True when `p` is within the grab radius of one of the single selected
|
|
1370
|
+
* element's link-start dots — used to show a `crosshair` (start a link).
|
|
1371
|
+
* Mirrors the anchor-drag hit-test in pointer-binding so the cursor matches
|
|
1372
|
+
* exactly where a press would begin a link.
|
|
1373
|
+
*/
|
|
1374
|
+
private isOverLinkStartDot;
|
|
1375
|
+
/** True when the given layer exists and is marked `locked`. */
|
|
1376
|
+
private isLayerLocked;
|
|
1377
|
+
/**
|
|
1378
|
+
* Combined interactivity check: false when the shape's layer is
|
|
1379
|
+
* locked, or when the shape itself or any ancestor via `parentId`
|
|
1380
|
+
* carries `locked: true` (group lock propagation). Hit-test treats
|
|
1381
|
+
* non-interactable hits as misses; render still draws them so the
|
|
1382
|
+
* user can see what's locked.
|
|
1383
|
+
*/
|
|
1384
|
+
private isElementInteractable;
|
|
1385
|
+
/**
|
|
1386
|
+
* Can this shape be moved / resized? False when the shape (or an ancestor)
|
|
1387
|
+
* is `locked`, its layer is locked, or it's hidden. Distinct from
|
|
1388
|
+
* `isElementInteractable`, which still allows selecting a locked shape so it
|
|
1389
|
+
* can be unlocked.
|
|
1390
|
+
*/
|
|
1391
|
+
isElementManipulable(shape: Element): boolean;
|
|
1392
|
+
/**
|
|
1393
|
+
* Toggle the `locked` flag on the selection (standard `⌘⇧L`). If any selected
|
|
1394
|
+
* element is currently unlocked, lock all; otherwise unlock all. One undo
|
|
1395
|
+
* step. A locked element stays selectable (click → select → unlock) but
|
|
1396
|
+
* can't be moved or resized.
|
|
1397
|
+
*/
|
|
1398
|
+
toggleLockSelection(): void;
|
|
1399
|
+
/**
|
|
1400
|
+
* Promote a hit shape to the topmost ancestor whose group we have NOT
|
|
1401
|
+
* "entered" yet. Only **`group`**-typed parents promote — containers
|
|
1402
|
+
* (swim-lane, frame) intentionally let click hits land on their
|
|
1403
|
+
* children. Group is an abstract wrapper that has no visual identity,
|
|
1404
|
+
* so promoting up to it is the only way to select it; a container has
|
|
1405
|
+
* its own body, header, etc. and clicking inside it should let users
|
|
1406
|
+
* pick the actual child shape (rectangle, sticky, …) — same affordance
|
|
1407
|
+
* as standard.
|
|
1408
|
+
*
|
|
1409
|
+
* Stops at the first non-`group` parent. With `_enteredGroup` set,
|
|
1410
|
+
* the walk also stops just below that group so children can be edited
|
|
1411
|
+
* directly.
|
|
1412
|
+
*/
|
|
1413
|
+
private promoteToGroupRoot;
|
|
1414
|
+
/**
|
|
1415
|
+
* Topmost group ancestor of `shape` (walks parentId chain, returns
|
|
1416
|
+
* the highest `type === "group"` parent). `null` if `shape` has no
|
|
1417
|
+
* group ancestor. Used by drill-down: a double-click on a shape
|
|
1418
|
+
* with a group ancestor enters that group. Body extracted to
|
|
1419
|
+
* `./group-helpers.ts`.
|
|
1420
|
+
*/
|
|
1421
|
+
private topGroupAncestor;
|
|
1422
|
+
/**
|
|
1423
|
+
* True when `elementId`'s parent chain contains `groupId`. Used by the
|
|
1424
|
+
* isolation exit path: a click on a shape whose parent chain *does
|
|
1425
|
+
* not* lead through the entered group is a click "outside" the
|
|
1426
|
+
* group, which exits isolation.
|
|
1427
|
+
*/
|
|
1428
|
+
private isDescendantOfGroup;
|
|
1429
|
+
/**
|
|
1430
|
+
* Compute the dim set for isolation rendering: every shape whose
|
|
1431
|
+
* parent chain does NOT pass through `enteredGroupId`. The entered
|
|
1432
|
+
* group itself is treated as "inside" (returns true from
|
|
1433
|
+
* isDescendantOfGroup) so it stays at full alpha — but groups have
|
|
1434
|
+
* no intrinsic geometry, so this only matters for the
|
|
1435
|
+
* group-bounds-outline overlay path, not the shape render.
|
|
1436
|
+
*
|
|
1437
|
+
* Defensive: shapes in the current selection are never dimmed. The
|
|
1438
|
+
* focus shape (drilled-into child) is always a group descendant in
|
|
1439
|
+
* practice, but the guard keeps the contract simple — "what you've
|
|
1440
|
+
* selected, you can see".
|
|
1441
|
+
*/
|
|
1442
|
+
computeHiddenElements(): ReadonlySet<ElementId> | undefined;
|
|
1443
|
+
computeDimElements(enteredGroupId: ElementId): ReadonlySet<ElementId>;
|
|
1444
|
+
/**
|
|
1445
|
+
* Enter a group — subsequent hits inside this group return children
|
|
1446
|
+
* directly instead of the group root. `null` exits group-edit mode.
|
|
1447
|
+
* Bound to double-click on a group in the default handler.
|
|
1448
|
+
*/
|
|
1449
|
+
enterGroup(groupId: ElementId | null): void;
|
|
1450
|
+
/**
|
|
1451
|
+
* Enter the single selected container — select its contents (standard `⌘⇧↓`).
|
|
1452
|
+
* Members are children via `parentId` (group / template container) or via
|
|
1453
|
+
* `frameId` (frame). For a group we also set `enteredGroup` so subsequent
|
|
1454
|
+
* clicks land on children. No-op unless exactly one container with members
|
|
1455
|
+
* is selected.
|
|
1456
|
+
*/
|
|
1457
|
+
enterContainer(): void;
|
|
1458
|
+
/**
|
|
1459
|
+
* Exit to the container of the current selection — select the parent group /
|
|
1460
|
+
* template container (`parentId`) or frame (`frameId`) when every selected
|
|
1461
|
+
* element shares one (standard `⌘⇧↑`). Clears `enteredGroup`. No-op when there
|
|
1462
|
+
* is no single common container.
|
|
1463
|
+
*/
|
|
1464
|
+
exitContainer(): void;
|
|
1465
|
+
/** Currently "entered" group, if any. */
|
|
1466
|
+
get enteredGroup(): ElementId | null;
|
|
1467
|
+
/**
|
|
1468
|
+
* SpatialGrid-accelerated topmost-shape lookup. Linear scan for small
|
|
1469
|
+
* scenes; for larger scenes builds a grid lazily, keyed by current
|
|
1470
|
+
* scene-identity. Scene operations replace `_scene` (immutable patches),
|
|
1471
|
+
* so reference-equality is a sufficient invalidation signal.
|
|
1472
|
+
*/
|
|
1473
|
+
acceleratedElementAt(worldPoint: Vec2): Element | undefined;
|
|
1474
|
+
/**
|
|
1475
|
+
* Build (or return the cached) `SpatialGrid` for the current scene.
|
|
1476
|
+
* Re-built only when `_scene` reference changes — scene operations
|
|
1477
|
+
* always produce a fresh object, so reference equality is a
|
|
1478
|
+
* sufficient invalidation signal.
|
|
1479
|
+
*
|
|
1480
|
+
* Shared between the hit-test path (`acceleratedElementAt`) and the
|
|
1481
|
+
* renderer pass (passed to `renderScene` as `spatialIndex`), so
|
|
1482
|
+
* the grid build cost is amortised across both consumers.
|
|
1483
|
+
*/
|
|
1484
|
+
ensureSpatialIndex(): SpatialGrid;
|
|
1485
|
+
/**
|
|
1486
|
+
* Group-isolation click routing. Returns `true` if the click was
|
|
1487
|
+
* handled (caller should skip the default applyEmit), `false` if the
|
|
1488
|
+
* normal selection emit should still run.
|
|
1489
|
+
*
|
|
1490
|
+
* Three paths fire here:
|
|
1491
|
+
* 1. **Double-click on a grouped shape (not yet in isolation):**
|
|
1492
|
+
* enter that group; select the raw inner shape (bypassing the
|
|
1493
|
+
* group-root promotion that ran in hitTest).
|
|
1494
|
+
* 2. **Inside isolation, click on a non-descendant shape OR empty
|
|
1495
|
+
* space:** exit isolation. Let the normal click then run so the
|
|
1496
|
+
* newly clicked element / empty selection takes hold.
|
|
1497
|
+
* 3. **Inside isolation, double-click on the entered group's own
|
|
1498
|
+
* child group:** drill another level deeper. (Implicit: same as
|
|
1499
|
+
* case 1 but topGroupAncestor here returns the inner child
|
|
1500
|
+
* group because the outer group is already entered.)
|
|
1501
|
+
*
|
|
1502
|
+
* Side-effect: updates `lastClickAt` / `lastClickWorldPoint`
|
|
1503
|
+
* regardless of result, so subsequent calls can detect a double-
|
|
1504
|
+
* click against this event.
|
|
1505
|
+
*/
|
|
1506
|
+
routeIsolationClick(clickEffect: InteractionEmit | null, worldPoint: Vec2): boolean;
|
|
1507
|
+
/**
|
|
1508
|
+
* Given the raw shape under the cursor and its topmost group
|
|
1509
|
+
* ancestor, pick which group to "enter" on a drill-down.
|
|
1510
|
+
*
|
|
1511
|
+
* - No group ancestor → null (drill-down doesn't apply).
|
|
1512
|
+
* - Top group not yet entered → enter top.
|
|
1513
|
+
* - Top group already entered → walk down the chain to find the
|
|
1514
|
+
* next group inward (one level deeper).
|
|
1515
|
+
*/
|
|
1516
|
+
private pickDrillTarget;
|
|
1517
|
+
applyEmit(emit: InteractionEmit): void;
|
|
1518
|
+
/**
|
|
1519
|
+
* Drag handler for annotation pins. Moves the pin to
|
|
1520
|
+
* `origin + delta`. Anchor semantics: for shape-anchored
|
|
1521
|
+
* annotations the `position` field is shape-local, so dragging
|
|
1522
|
+
* still updates the same field — the editor doesn't try to
|
|
1523
|
+
* reparent the anchor mid-drag; user wants the pin under the
|
|
1524
|
+
* cursor and that's exactly what `position + (delta in world)`
|
|
1525
|
+
* gives, regardless of which space the position is interpreted
|
|
1526
|
+
* in (`getAnnotationWorldPosition` already adds the shape's
|
|
1527
|
+
* world position when anchored).
|
|
1528
|
+
*
|
|
1529
|
+
* Wrapped in a single gestureTx so per-move updates collapse
|
|
1530
|
+
* into one undo step.
|
|
1531
|
+
*/
|
|
1532
|
+
private applyAnnotationMove;
|
|
1533
|
+
private readonly templateTapListeners;
|
|
1534
|
+
private readonly templateDropListeners;
|
|
1535
|
+
/**
|
|
1536
|
+
* Subscribe to template button taps. Returns an unsubscribe function.
|
|
1537
|
+
* Hosts use this to route template button clicks to their own actions.
|
|
1538
|
+
*/
|
|
1539
|
+
onTemplateTap(fn: (emit: Extract<InteractionEmit, {
|
|
1540
|
+
type: "TEMPLATE_TAP";
|
|
1541
|
+
}>) => void): () => void;
|
|
1542
|
+
/**
|
|
1543
|
+
* Subscribe to drops onto template drop-zones. Returns an unsubscribe fn.
|
|
1544
|
+
* Hosts decide what to do with the drop (e.g. add a child shape, link
|
|
1545
|
+
* templates together).
|
|
1546
|
+
*/
|
|
1547
|
+
onTemplateDrop(fn: (emit: Extract<InteractionEmit, {
|
|
1548
|
+
type: "TEMPLATE_DROP";
|
|
1549
|
+
}>) => void): () => void;
|
|
1550
|
+
/**
|
|
1551
|
+
* Dispatch a TEMPLATE_DROP emit programmatically. Hosts call this from their
|
|
1552
|
+
* own DOM `drop` listener after looking up which drop-zone (if any) is
|
|
1553
|
+
* under the pointer via `findDropZoneAt`.
|
|
1554
|
+
*/
|
|
1555
|
+
dispatchTemplateDrop(emit: Extract<InteractionEmit, {
|
|
1556
|
+
type: "TEMPLATE_DROP";
|
|
1557
|
+
}>): void;
|
|
1558
|
+
private applyMove;
|
|
1559
|
+
private applyGroupMove;
|
|
1560
|
+
computeViewportWorld(): Bounds | null;
|
|
1561
|
+
/**
|
|
1562
|
+
* Identity-diff the current scene against the last rendered one and
|
|
1563
|
+
* return the union AABB of every shape/edge that changed reference
|
|
1564
|
+
* (added, removed, or replaced). Returns `null` to force a full
|
|
1565
|
+
* clear when something that affects the entire surface changes —
|
|
1566
|
+
* viewport pan/zoom/resize, layer visibility, or the first frame.
|
|
1567
|
+
*
|
|
1568
|
+
* Empty union (returned as a zero-area bbox far off-screen) means
|
|
1569
|
+
* nothing changed; renderScene will cull every shape via its
|
|
1570
|
+
* `dirtyWorld` filter — effectively a no-op main pass.
|
|
1571
|
+
*/
|
|
1572
|
+
computeDirtyWorld(): Bounds | null;
|
|
1573
|
+
combinedSelectionBounds(): Bounds | null;
|
|
1574
|
+
private groupChildrenUnion;
|
|
1575
|
+
/**
|
|
1576
|
+
* True when the current selection should be treated as aspect-
|
|
1577
|
+
* locked for group-handle resize. Currently: a single `group`-typed
|
|
1578
|
+
* shape selected. Multi-selection of free shapes keeps the default
|
|
1579
|
+
* 8-handle / free-aspect behaviour (matches user expectation:
|
|
1580
|
+
* grouping is the explicit "lock the ratio" gesture).
|
|
1581
|
+
*/
|
|
1582
|
+
selectionIsAspectLocked(): boolean;
|
|
1583
|
+
private applyGroupResize;
|
|
1584
|
+
private applyResize;
|
|
1585
|
+
private applyCreate;
|
|
1586
|
+
/** Generate the next "Frame N" name based on existing frames. */
|
|
1587
|
+
private nextFrameName;
|
|
1588
|
+
/**
|
|
1589
|
+
* Assign frameId to every shape (except the frame itself) whose
|
|
1590
|
+
* centre falls inside the frame's world bounds. Runs as a single
|
|
1591
|
+
* undo step in the same gesture transaction as the create.
|
|
1592
|
+
*/
|
|
1593
|
+
private assignFrameMembers;
|
|
1594
|
+
/**
|
|
1595
|
+
* Re-evaluate frame membership at the end of a move / resize gesture —
|
|
1596
|
+
* elements dropped inside a frame join it, those dragged out are
|
|
1597
|
+
* released (standard "membership on drop"). Runs inside the gesture
|
|
1598
|
+
* transaction (called from pointer-up before `commitGesture`) so the
|
|
1599
|
+
* frameId changes undo together with the drag. No-op when nothing
|
|
1600
|
+
* changed.
|
|
1601
|
+
*/
|
|
1602
|
+
reconcileFrameMembership(): void;
|
|
1603
|
+
private applyCreateLink;
|
|
1604
|
+
/** Pending shape-picker after a link was dropped on empty canvas. */
|
|
1605
|
+
get linkDropMenu(): {
|
|
1606
|
+
linkId: LinkId;
|
|
1607
|
+
side: "from" | "to";
|
|
1608
|
+
world: Vec2;
|
|
1609
|
+
} | null;
|
|
1610
|
+
/**
|
|
1611
|
+
* Resolve a pending link-drop shape-picker by creating an element from
|
|
1612
|
+
* `factory` centred at the drop point and re-pointing the dropped link
|
|
1613
|
+
* end to float against it. Element + re-point land in one undo step; the
|
|
1614
|
+
* new element becomes the selection. No-op when no menu is pending.
|
|
1615
|
+
*/
|
|
1616
|
+
placeShapeAtLinkDrop(factory: (ctx: {
|
|
1617
|
+
id: ElementId;
|
|
1618
|
+
layerId: LayerId;
|
|
1619
|
+
position: Vec2;
|
|
1620
|
+
order: FractionalIndex;
|
|
1621
|
+
}) => Element): void;
|
|
1622
|
+
/** Dismiss the link-drop shape-picker, leaving the free-ended link. */
|
|
1623
|
+
dismissLinkDropMenu(): void;
|
|
1624
|
+
/**
|
|
1625
|
+
* standard "click a link-start dot" gesture: spawn a new element in that
|
|
1626
|
+
* dot's outward direction and link the source to it. The clone copies
|
|
1627
|
+
* the source's type / style / size but NOT its text (a fresh blank of
|
|
1628
|
+
* the same kind). Direction is source → new; the new element becomes the
|
|
1629
|
+
* selection. Element + link land in one undo step.
|
|
1630
|
+
*/
|
|
1631
|
+
createLinkedElementFromAnchor(fromElement: ElementId, anchorName: string): void;
|
|
1632
|
+
/**
|
|
1633
|
+
* Ghost geometry for what clicking a start dot would create (standard hover
|
|
1634
|
+
* preview): the would-be new element's world bounds + the connector path
|
|
1635
|
+
* from the dot to it. Pure — no mutation. Mirrors the placement in
|
|
1636
|
+
* `createLinkedElementFromAnchor`.
|
|
1637
|
+
*/
|
|
1638
|
+
previewClickCreate(fromElement: ElementId, anchorName: string): {
|
|
1639
|
+
bounds: Bounds;
|
|
1640
|
+
path: readonly Vec2[];
|
|
1641
|
+
element: Element;
|
|
1642
|
+
ghostScene: Scene;
|
|
1643
|
+
ghostLinkId: LinkId;
|
|
1644
|
+
} | null;
|
|
1645
|
+
/**
|
|
1646
|
+
* Build an `LinkEndpoint` for a draw-edge / re-bind gesture. Runs the
|
|
1647
|
+
* scene's snap engine for the probe point, prefers anchor snap when
|
|
1648
|
+
* close enough, falls back to outline snap (so the user can attach
|
|
1649
|
+
* "anywhere on the right edge"), then `point` for the free-floating
|
|
1650
|
+
* case.
|
|
1651
|
+
*
|
|
1652
|
+
* `pressTargetElement` is the shape the gesture originated from or
|
|
1653
|
+
* landed on (used as a strong hint — we don't snap onto unrelated
|
|
1654
|
+
* shapes when the user clearly aimed for this one).
|
|
1655
|
+
*/
|
|
1656
|
+
private snapLinkEndpoint;
|
|
1657
|
+
private applySelectByBounds;
|
|
1658
|
+
private applyLassoLiveSelection;
|
|
1659
|
+
/**
|
|
1660
|
+
* Live endpoint-rebind move: re-point the dragged end to the cursor in the
|
|
1661
|
+
* scene (a free `point` endpoint), recorded in the gesture transaction so the
|
|
1662
|
+
* WHOLE link redraws under the cursor with full fidelity — real style,
|
|
1663
|
+
* arrowhead, curved bow, and (via `rerouteElbows` in `render`) a live elbow
|
|
1664
|
+
* re-route. One undo step on commit; Escape cancels the transaction and the
|
|
1665
|
+
* link snaps back to where it was. The handle dot follows via `linkEndpointDrag`.
|
|
1666
|
+
*/
|
|
1667
|
+
private applyLinkEndpointMove;
|
|
1668
|
+
private applyLinkEndpointUpdate;
|
|
1669
|
+
/** True while a waypoint of the selected link is being dragged. */
|
|
1670
|
+
get isDraggingWaypoint(): boolean;
|
|
1671
|
+
/**
|
|
1672
|
+
* Begin a host-managed waypoint drag. `insert` splices a new waypoint at
|
|
1673
|
+
* `index` on the first move (segment-midpoint "add" handle); otherwise an
|
|
1674
|
+
* existing waypoint at `index` is moved. Live-mutated through the gesture
|
|
1675
|
+
* transaction so the whole drag is one undo step.
|
|
1676
|
+
*/
|
|
1677
|
+
beginWaypointDrag(linkId: LinkId, index: number, insert: boolean): void;
|
|
1678
|
+
/** Live update of the dragged waypoint to `world`. */
|
|
1679
|
+
updateWaypointDrag(world: Vec2): void;
|
|
1680
|
+
/**
|
|
1681
|
+
* Finish the waypoint drag. If the dragged waypoint landed within
|
|
1682
|
+
* `WAYPOINT_COLLAPSE_RADIUS` of an adjacent path point, it is removed
|
|
1683
|
+
* (drag-onto-the-line to delete). A no-move insert adds nothing.
|
|
1684
|
+
*/
|
|
1685
|
+
endWaypointDrag(): void;
|
|
1686
|
+
/** True while an elbow segment is being dragged. */
|
|
1687
|
+
get isDraggingSegment(): boolean;
|
|
1688
|
+
/**
|
|
1689
|
+
* Begin a host-managed elbow segment drag. `axis` is the segment's
|
|
1690
|
+
* orientation; `at` is its centre along its own axis (used to re-identify it
|
|
1691
|
+
* across re-routes).
|
|
1692
|
+
*/
|
|
1693
|
+
beginSegmentDrag(linkId: LinkId, axis: "h" | "v", at: number): void;
|
|
1694
|
+
/**
|
|
1695
|
+
* Move the dragged elbow segment perpendicular to its axis: pin its
|
|
1696
|
+
* perpendicular coordinate to the cursor. The reroute pass re-flows the
|
|
1697
|
+
* rest around the pin (one undo step via the gesture transaction).
|
|
1698
|
+
*/
|
|
1699
|
+
updateSegmentDrag(world: Vec2): void;
|
|
1700
|
+
/** Finish the elbow segment drag (commit the gesture as one undo step). */
|
|
1701
|
+
endSegmentDrag(): void;
|
|
1702
|
+
/**
|
|
1703
|
+
* Double-click detector for link edit handles (waypoint / segment).
|
|
1704
|
+
* Returns true when this press follows the previous handle press within
|
|
1705
|
+
* the double-click window + tolerance. Updates state every call. Kept
|
|
1706
|
+
* separate from the up-side double-click path (handles return early in
|
|
1707
|
+
* `onDown`, so that path never sees them).
|
|
1708
|
+
*/
|
|
1709
|
+
isHandleDoubleClick(world: Vec2): boolean;
|
|
1710
|
+
/**
|
|
1711
|
+
* Delete a free bend point (waypoint) from a straight / bezier link by
|
|
1712
|
+
* index — double-click a waypoint handle to remove it. One undo step.
|
|
1713
|
+
*/
|
|
1714
|
+
deleteWaypoint(linkId: LinkId, index: number): void;
|
|
1715
|
+
/**
|
|
1716
|
+
* Remove the pinned (fixed) elbow segment that matches the given
|
|
1717
|
+
* geometry — double-click a segment handle to return it to the auto
|
|
1718
|
+
* route. Matches by axis + nearest pinned perpendicular `pos` (exact for
|
|
1719
|
+
* a pinned segment), `at` as tiebreak. The reroute pass re-flows on the
|
|
1720
|
+
* next render (fixedSegments is part of the elbow signature). One undo
|
|
1721
|
+
* step.
|
|
1722
|
+
*/
|
|
1723
|
+
resetSegmentPin(linkId: LinkId, axis: "h" | "v", pos: number, at: number): void;
|
|
1724
|
+
/** Whether the selected link has obstacle-avoidance routing enabled. */
|
|
1725
|
+
get selectedLinkAvoidsObstacles(): boolean;
|
|
1726
|
+
/**
|
|
1727
|
+
* Toggle persistent "route around shapes" on the selected link (standard
|
|
1728
|
+
* model). Enabling sets `avoidObstacles` and forces `orthogonal` routing —
|
|
1729
|
+
* the elbow router then keeps the path clear of EVERY scene shape and
|
|
1730
|
+
* re-routes whenever an obstacle moves into the way (see
|
|
1731
|
+
* `routeElbowLink` / `elbowSignature`). Disabling drops the flag; the
|
|
1732
|
+
* routing type is left as-is. One undo step; the routed path itself is
|
|
1733
|
+
* derived (recomputed by `rerouteElbows`). No-op when no link is selected.
|
|
1734
|
+
*/
|
|
1735
|
+
setSelectedLinkAvoidObstacles(enabled: boolean): void;
|
|
1736
|
+
updateHoveredLinkTarget(worldPoint: Vec2): void;
|
|
1737
|
+
applyLinkPreview(fromElement: ElementId | null, fromPoint: Vec2, toPoint: Vec2): void;
|
|
1738
|
+
private recordGesturePatch;
|
|
1739
|
+
commitGesture(): void;
|
|
1740
|
+
private finalizeOpenGestureTx;
|
|
1741
|
+
/**
|
|
1742
|
+
* End-of-drag container hookup. Runs after the state machine has
|
|
1743
|
+
* received POINTER_UP but before the gesture transaction commits,
|
|
1744
|
+
* so reparent + auto-grow land in one undo step with the drag itself.
|
|
1745
|
+
*
|
|
1746
|
+
* Rules:
|
|
1747
|
+
* - If the shape was dropped over a container and is not yet its child →
|
|
1748
|
+
* set `parentId`. If the shape extends past the dropZone, the zone is
|
|
1749
|
+
* grown (and the container's size with it).
|
|
1750
|
+
* - If the shape was someone's child but its final world bounds no longer
|
|
1751
|
+
* intersect the parent's drop-zone → clear `parentId` (drag-out).
|
|
1752
|
+
* - Cycles (a container inside its own descendant) are prevented by the
|
|
1753
|
+
* `containerHover` pipeline above — the exclude set rules them out.
|
|
1754
|
+
*/
|
|
1755
|
+
applyContainerDrop(worldPoint: Vec2): void;
|
|
1756
|
+
private maybeGrowContainer;
|
|
1757
|
+
private clampContainerToChildren;
|
|
1758
|
+
/**
|
|
1759
|
+
* Return the running gesture tx, or open a new one if the drag finished
|
|
1760
|
+
* with an empty transaction (a move-by-zero-pixels gesture can still
|
|
1761
|
+
* carry a container reparent).
|
|
1762
|
+
*/
|
|
1763
|
+
private beginOrAttachGesture;
|
|
1764
|
+
cancelGesture(): void;
|
|
1765
|
+
/**
|
|
1766
|
+
* Drop ids from the selection that no longer exist in the scene. Needed
|
|
1767
|
+
* after undoing a CREATE — the shape goes away and the selection becomes
|
|
1768
|
+
* stale.
|
|
1769
|
+
*/
|
|
1770
|
+
private pruneSelection;
|
|
1771
|
+
notify(): void;
|
|
1772
|
+
/**
|
|
1773
|
+
* Pending `requestAnimationFrame` id for the next render, or null
|
|
1774
|
+
* when no render is scheduled. Used to coalesce bursts of `notify()`
|
|
1775
|
+
* calls (drag-pan, drag shape, multi-key, scripted batch mutations)
|
|
1776
|
+
* into a single render per frame.
|
|
1777
|
+
*/
|
|
1778
|
+
private renderRafId;
|
|
1779
|
+
/** Unsubscribe for the animation-content-ready listener (decode → re-render). */
|
|
1780
|
+
private animationContentOff;
|
|
1781
|
+
/**
|
|
1782
|
+
* Schedule a render on the next animation frame. Idempotent —
|
|
1783
|
+
* multiple calls within the same frame collapse to one render.
|
|
1784
|
+
*
|
|
1785
|
+
* Falls back to a synchronous render when `requestAnimationFrame`
|
|
1786
|
+
* is unavailable (Node without jsdom, SSR). Browser / test environments
|
|
1787
|
+
* with rAF get the coalesced path.
|
|
1788
|
+
*
|
|
1789
|
+
* Use {@link forceRender} when you need the render to happen
|
|
1790
|
+
* immediately (PNG export, screenshot, visual-regression tests that
|
|
1791
|
+
* compare bitmap output after a mutation).
|
|
1792
|
+
*/
|
|
1793
|
+
private scheduleRender;
|
|
1794
|
+
/**
|
|
1795
|
+
* Synchronously render the current state. Cancels any pending
|
|
1796
|
+
* rAF-scheduled render so the next browser frame doesn't paint
|
|
1797
|
+
* a stale state on top.
|
|
1798
|
+
*
|
|
1799
|
+
* Hosts only need this when they read back the rendered bitmap
|
|
1800
|
+
* immediately after a mutation — `editor.toPng()`, custom
|
|
1801
|
+
* `canvas.toDataURL()` flows, visual-regression test asserts.
|
|
1802
|
+
* Normal interactive flows should let `scheduleRender` do its job.
|
|
1803
|
+
*/
|
|
1804
|
+
forceRender(): void;
|
|
1805
|
+
/**
|
|
1806
|
+
* Typed event surface — subscribe to a specific slice (`mode`,
|
|
1807
|
+
* `selection`, `scene`, `history`, `viewport`) or the umbrella
|
|
1808
|
+
* `change`. Replaces ad-hoc selectors over the coarse `subscribe()`
|
|
1809
|
+
* for callers that only care about one dimension. The legacy
|
|
1810
|
+
* `subscribe()` still works and fires in lock-step.
|
|
1811
|
+
*/
|
|
1812
|
+
on<K extends keyof EditorEvents>(event: K, fn: EditorEvents[K]): () => void;
|
|
1813
|
+
off<K extends keyof EditorEvents>(event: K, fn: EditorEvents[K]): void;
|
|
1814
|
+
/**
|
|
1815
|
+
* Public command — re-run the auto-layout spec on the given
|
|
1816
|
+
* container shape, regardless of whether the children set actually
|
|
1817
|
+
* changed. Useful as an "auto-arrange" toolbar action or for hosts
|
|
1818
|
+
* adopting auto-layout after creating shapes programmatically.
|
|
1819
|
+
* Returns the patch that was applied, or `null` when nothing
|
|
1820
|
+
* changed (no spec, no children, or children already in position).
|
|
1821
|
+
* Single undo step.
|
|
1822
|
+
*/
|
|
1823
|
+
runLayout(parentId: ElementId): Patch | null;
|
|
1824
|
+
/**
|
|
1825
|
+
* Per-link signature of the inputs that determine an elbow route
|
|
1826
|
+
* (endpoint refs + bound-shape bounds + fixedSegments). When unchanged
|
|
1827
|
+
* between frames the A* route is reused — see `rerouteElbows`.
|
|
1828
|
+
*/
|
|
1829
|
+
private readonly elbowRouteSig;
|
|
1830
|
+
private elbowSignature;
|
|
1831
|
+
/**
|
|
1832
|
+
* Choke-point reroute (standard model): recompute `routedPoints` for
|
|
1833
|
+
* every orthogonal link whose inputs changed since the last pass, and
|
|
1834
|
+
* bake the result into `_scene`. Runs once per frame before paint —
|
|
1835
|
+
* derived state, so no history push / notify (would loop). Cheap when
|
|
1836
|
+
* nothing moved (signature short-circuit).
|
|
1837
|
+
*/
|
|
1838
|
+
private rerouteElbows;
|
|
1839
|
+
private render;
|
|
1840
|
+
}
|
|
1841
|
+
//# sourceMappingURL=editor.d.ts.map
|