@canvus/core 0.1.1 → 0.1.3
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/LICENSE +21 -0
- package/README.md +32 -1
- package/dist/drop-zone.d.ts.map +1 -1
- package/dist/drop-zone.js +16 -0
- package/dist/drop-zone.js.map +1 -1
- package/dist/handlers/clipboard.handler.d.ts +22 -0
- package/dist/handlers/clipboard.handler.d.ts.map +1 -0
- package/dist/handlers/clipboard.handler.js +349 -0
- package/dist/handlers/clipboard.handler.js.map +1 -0
- package/dist/handlers/command.handler.d.ts +18 -0
- package/dist/handlers/command.handler.d.ts.map +1 -0
- package/dist/handlers/command.handler.js +430 -0
- package/dist/handlers/command.handler.js.map +1 -0
- package/dist/handlers/drag.handler.d.ts +22 -0
- package/dist/handlers/drag.handler.d.ts.map +1 -0
- package/dist/handlers/drag.handler.js +669 -0
- package/dist/handlers/drag.handler.js.map +1 -0
- package/dist/handlers/draw.handler.d.ts +37 -0
- package/dist/handlers/draw.handler.d.ts.map +1 -0
- package/dist/handlers/draw.handler.js +210 -0
- package/dist/handlers/draw.handler.js.map +1 -0
- package/dist/handlers/index.d.ts +10 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +12 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/pan.handler.d.ts +34 -0
- package/dist/handlers/pan.handler.d.ts.map +1 -0
- package/dist/handlers/pan.handler.js +95 -0
- package/dist/handlers/pan.handler.js.map +1 -0
- package/dist/handlers/resize.handler.d.ts +26 -0
- package/dist/handlers/resize.handler.d.ts.map +1 -0
- package/dist/handlers/resize.handler.js +487 -0
- package/dist/handlers/resize.handler.js.map +1 -0
- package/dist/handlers/selection.handler.d.ts +22 -0
- package/dist/handlers/selection.handler.d.ts.map +1 -0
- package/dist/handlers/selection.handler.js +259 -0
- package/dist/handlers/selection.handler.js.map +1 -0
- package/dist/handlers/spacing.handler.d.ts +29 -0
- package/dist/handlers/spacing.handler.d.ts.map +1 -0
- package/dist/handlers/spacing.handler.js +326 -0
- package/dist/handlers/spacing.handler.js.map +1 -0
- package/dist/handlers/types.d.ts +204 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/types.js +10 -0
- package/dist/handlers/types.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/shadow-mount.d.ts.map +1 -1
- package/dist/shadow-mount.js +51 -2
- package/dist/shadow-mount.js.map +1 -1
- package/dist/workspace.d.ts +149 -68
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +349 -2208
- package/dist/workspace.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// canvus/src/handlers/selection.handler.ts
|
|
3
|
+
// Handles marquee selection (rubber band), single click, multi-select, and double-click scope drill-down.
|
|
4
|
+
// ─────────────────────────────────────────────────────────────
|
|
5
|
+
import { rectsIntersect, isPointInElement } from "../matrix.js";
|
|
6
|
+
/**
|
|
7
|
+
* Fallback interaction handler managing marquee selection, click selections, and container drill-down scoping.
|
|
8
|
+
*/
|
|
9
|
+
export class SelectionHandler {
|
|
10
|
+
id = "selection";
|
|
11
|
+
ctx;
|
|
12
|
+
_isMarqueeSelecting = false;
|
|
13
|
+
_marqueeStartCanvas = null;
|
|
14
|
+
_marqueeCurrentCanvas = null;
|
|
15
|
+
_preMarqueeSelectedIds = new Set();
|
|
16
|
+
_marqueeEnteredContainerId = null;
|
|
17
|
+
constructor(ctx) {
|
|
18
|
+
this.ctx = ctx;
|
|
19
|
+
}
|
|
20
|
+
get isMarqueeSelecting() {
|
|
21
|
+
return this._isMarqueeSelecting;
|
|
22
|
+
}
|
|
23
|
+
getMarqueeRect() {
|
|
24
|
+
if (!this._isMarqueeSelecting || !this._marqueeStartCanvas || !this._marqueeCurrentCanvas) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
x: Math.min(this._marqueeStartCanvas.x, this._marqueeCurrentCanvas.x),
|
|
29
|
+
y: Math.min(this._marqueeStartCanvas.y, this._marqueeCurrentCanvas.y),
|
|
30
|
+
width: Math.abs(this._marqueeStartCanvas.x - this._marqueeCurrentCanvas.x),
|
|
31
|
+
height: Math.abs(this._marqueeStartCanvas.y - this._marqueeCurrentCanvas.y),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// ── InteractionHandler Interface ────────────────
|
|
35
|
+
claim(e, canvasPos, hitNodeId, _containerRect) {
|
|
36
|
+
if (e.button !== 0 || this.ctx.previewMode)
|
|
37
|
+
return false;
|
|
38
|
+
const targetEl = e.composedPath()[0];
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const isSameTarget = targetEl !== null && this.ctx.lastPointerDownTarget !== null &&
|
|
41
|
+
(targetEl === this.ctx.lastPointerDownTarget || this.ctx.lastPointerDownTarget.contains(targetEl) || targetEl.contains(this.ctx.lastPointerDownTarget));
|
|
42
|
+
const isDoubleClick = (now - this.ctx.lastPointerDownTime < 350) && (hitNodeId !== null &&
|
|
43
|
+
this.ctx.lastPointerDownId !== null &&
|
|
44
|
+
(hitNodeId === this.ctx.lastPointerDownId || isSameTarget || this.ctx.tree.isAncestor(this.ctx.lastPointerDownId, hitNodeId)));
|
|
45
|
+
this.ctx.lastPointerDownTime = now;
|
|
46
|
+
this.ctx.lastPointerDownId = hitNodeId;
|
|
47
|
+
this.ctx.lastPointerDownTarget = targetEl;
|
|
48
|
+
// ── Node hit-test (select) ────────────────────
|
|
49
|
+
let targetSelectId = null;
|
|
50
|
+
let clickInsideSelection = false;
|
|
51
|
+
const hasModifier = e.shiftKey || e.metaKey || e.ctrlKey;
|
|
52
|
+
if (this.ctx.selectedIds.size > 0 && !hasModifier && !isDoubleClick) {
|
|
53
|
+
for (const selId of this.ctx.selectedIds) {
|
|
54
|
+
const selNode = this.ctx.tree.get(selId);
|
|
55
|
+
if (selNode?.currentRect && isPointInElement(canvasPos.x, canvasPos.y, selNode.currentRect)) {
|
|
56
|
+
clickInsideSelection = true;
|
|
57
|
+
targetSelectId = selId;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!clickInsideSelection) {
|
|
63
|
+
if (hitNodeId) {
|
|
64
|
+
// ── Layer lock guard ──────────────────────
|
|
65
|
+
if (this.ctx.isNodeLocked(hitNodeId)) {
|
|
66
|
+
this.ctx.callbacks.onLockedNodeInteraction?.(hitNodeId);
|
|
67
|
+
// Treat as click on empty space — fall through to deselect / marquee
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const isCmdClick = e.metaKey || e.ctrlKey;
|
|
71
|
+
if (isCmdClick) {
|
|
72
|
+
// Cmd+Click: deep select the hit element directly
|
|
73
|
+
targetSelectId = hitNodeId;
|
|
74
|
+
this.ctx.enteredContainerId = this.ctx.tree.get(hitNodeId)?.parentId ?? null;
|
|
75
|
+
}
|
|
76
|
+
else if (isDoubleClick) {
|
|
77
|
+
// Double click: Figma-like drill down
|
|
78
|
+
const path = this.ctx.tree.getPath(hitNodeId);
|
|
79
|
+
let foundSelectedIdx = -1;
|
|
80
|
+
for (let i = 0; i < path.length; i++) {
|
|
81
|
+
if (this.ctx.selectedIds.has(path[i].id)) {
|
|
82
|
+
foundSelectedIdx = i;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (foundSelectedIdx !== -1 && foundSelectedIdx < path.length - 1) {
|
|
87
|
+
// Drill down one level
|
|
88
|
+
const nextParent = path[foundSelectedIdx];
|
|
89
|
+
const nextSelect = path[foundSelectedIdx + 1];
|
|
90
|
+
this.ctx.enteredContainerId = nextParent.id;
|
|
91
|
+
targetSelectId = nextSelect.id;
|
|
92
|
+
}
|
|
93
|
+
else if (foundSelectedIdx === path.length - 1) {
|
|
94
|
+
// Leaf is already selected: keep selection on leaf to trigger text editing
|
|
95
|
+
targetSelectId = path[path.length - 1].id;
|
|
96
|
+
this.ctx.enteredContainerId = path[path.length - 2]?.id ?? null;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Nothing in the path is selected
|
|
100
|
+
if (path.length > 0) {
|
|
101
|
+
targetSelectId = path[0].id;
|
|
102
|
+
this.ctx.enteredContainerId = null;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
targetSelectId = hitNodeId;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Single Click: resolve based on current entered scope
|
|
111
|
+
const resolvedId = this.ctx.findSelectableNode(hitNodeId, this.ctx.enteredContainerId);
|
|
112
|
+
if (resolvedId) {
|
|
113
|
+
targetSelectId = resolvedId;
|
|
114
|
+
const node = this.ctx.tree.get(resolvedId);
|
|
115
|
+
this.ctx.enteredContainerId = node?.parentId ?? null;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Clicked outside currently entered container: exit scope, select root ancestor
|
|
119
|
+
this.ctx.enteredContainerId = null;
|
|
120
|
+
targetSelectId = this.ctx.findSelectableNode(hitNodeId, null);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} // end of lock guard else-block
|
|
124
|
+
}
|
|
125
|
+
if (isDoubleClick && targetSelectId && this.ctx.selectedIds.has(targetSelectId)) {
|
|
126
|
+
this.ctx.editAllowedOnDblClick = true;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.ctx.editAllowedOnDblClick = false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (targetSelectId) {
|
|
133
|
+
if (!clickInsideSelection) {
|
|
134
|
+
const prevSelection = new Set(this.ctx.selectedIds);
|
|
135
|
+
const isShift = e.shiftKey;
|
|
136
|
+
if (isShift) {
|
|
137
|
+
if (this.ctx.selectedIds.has(targetSelectId)) {
|
|
138
|
+
this.ctx.selectedIds.delete(targetSelectId);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
this.ctx.selectedIds.add(targetSelectId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
this.ctx.selectedIds.clear();
|
|
146
|
+
this.ctx.selectedIds.add(targetSelectId);
|
|
147
|
+
}
|
|
148
|
+
this.ctx.syncLazyChildren(prevSelection, this.ctx.selectedIds);
|
|
149
|
+
this.ctx.callbacks.onSelectionChange?.(this.ctx.selectedIds);
|
|
150
|
+
this.ctx.updateBreadcrumb();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Click on empty space — start marquee selection
|
|
155
|
+
const isShift = e.shiftKey;
|
|
156
|
+
if (!isShift) {
|
|
157
|
+
const prevSelection = new Set(this.ctx.selectedIds);
|
|
158
|
+
this.ctx.selectedIds.clear();
|
|
159
|
+
this.ctx.enteredContainerId = null;
|
|
160
|
+
this.ctx.guides = [];
|
|
161
|
+
this.ctx.syncLazyChildren(prevSelection, this.ctx.selectedIds);
|
|
162
|
+
this.ctx.callbacks.onSelectionChange?.(this.ctx.selectedIds);
|
|
163
|
+
this.ctx.updateBreadcrumb();
|
|
164
|
+
}
|
|
165
|
+
this._marqueeEnteredContainerId = this.ctx.enteredContainerId;
|
|
166
|
+
this._preMarqueeSelectedIds.clear();
|
|
167
|
+
for (const id of this.ctx.selectedIds) {
|
|
168
|
+
this._preMarqueeSelectedIds.add(id);
|
|
169
|
+
}
|
|
170
|
+
this._isMarqueeSelecting = true;
|
|
171
|
+
this._marqueeStartCanvas = canvasPos;
|
|
172
|
+
this._marqueeCurrentCanvas = canvasPos;
|
|
173
|
+
this.ctx.safeSetPointerCapture(e.pointerId);
|
|
174
|
+
this.ctx.emitInteraction("select-marquee");
|
|
175
|
+
}
|
|
176
|
+
this.ctx.render();
|
|
177
|
+
return true; // Fallback handler always claims the event
|
|
178
|
+
}
|
|
179
|
+
onPointerMove(e, canvasPos, _containerRect) {
|
|
180
|
+
if (this._isMarqueeSelecting && this._marqueeStartCanvas) {
|
|
181
|
+
this._marqueeCurrentCanvas = canvasPos;
|
|
182
|
+
const mRect = this.getMarqueeRect();
|
|
183
|
+
if (!mRect)
|
|
184
|
+
return;
|
|
185
|
+
// Find all selectable nodes inside or intersecting the marquee rect
|
|
186
|
+
const selectableNodes = this.ctx.getOrderedNodeList();
|
|
187
|
+
const currentMarqueeSelection = new Set();
|
|
188
|
+
const scopeId = this._marqueeEnteredContainerId;
|
|
189
|
+
for (const node of selectableNodes) {
|
|
190
|
+
if (!node.currentRect)
|
|
191
|
+
continue;
|
|
192
|
+
const treeNode = this.ctx.tree.get(node.id);
|
|
193
|
+
if (!treeNode)
|
|
194
|
+
continue;
|
|
195
|
+
// Skip locked nodes in marquee selection
|
|
196
|
+
if (this.ctx.isNodeLocked(node.id))
|
|
197
|
+
continue;
|
|
198
|
+
// Scoping constraint
|
|
199
|
+
if (scopeId !== null) {
|
|
200
|
+
if (treeNode.parentId !== scopeId)
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
if (treeNode.parentId !== null)
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (rectsIntersect(node.currentRect, mRect)) {
|
|
208
|
+
currentMarqueeSelection.add(node.id);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Compute new selection based on Shift modifier.
|
|
212
|
+
const isShift = e.shiftKey;
|
|
213
|
+
const prevSelection = new Set(this.ctx.selectedIds);
|
|
214
|
+
const selectedIds = this.ctx.selectedIds;
|
|
215
|
+
selectedIds.clear();
|
|
216
|
+
if (isShift) {
|
|
217
|
+
for (const id of this._preMarqueeSelectedIds) {
|
|
218
|
+
selectedIds.add(id);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (const id of currentMarqueeSelection) {
|
|
222
|
+
selectedIds.add(id);
|
|
223
|
+
}
|
|
224
|
+
this.ctx.syncLazyChildren(prevSelection, this.ctx.selectedIds);
|
|
225
|
+
this.ctx.callbacks.onSelectionChange?.(this.ctx.selectedIds);
|
|
226
|
+
this.ctx.updateBreadcrumb();
|
|
227
|
+
this.ctx.render();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
onPointerUp(e, _canvasPos, _containerRect) {
|
|
231
|
+
if (this._isMarqueeSelecting) {
|
|
232
|
+
this._isMarqueeSelecting = false;
|
|
233
|
+
this._marqueeStartCanvas = null;
|
|
234
|
+
this._marqueeCurrentCanvas = null;
|
|
235
|
+
this._preMarqueeSelectedIds.clear();
|
|
236
|
+
// Restore entered container scope
|
|
237
|
+
this.ctx.enteredContainerId = this._marqueeEnteredContainerId;
|
|
238
|
+
this.ctx.updateBreadcrumb();
|
|
239
|
+
}
|
|
240
|
+
this.ctx.guides = [];
|
|
241
|
+
// Release pointer capture.
|
|
242
|
+
try {
|
|
243
|
+
this.ctx.container.releasePointerCapture(e.pointerId);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// Ignore if capture was already released or lost
|
|
247
|
+
}
|
|
248
|
+
this.ctx.emitInteraction(null);
|
|
249
|
+
this.ctx.render();
|
|
250
|
+
}
|
|
251
|
+
onCancel() {
|
|
252
|
+
this._isMarqueeSelecting = false;
|
|
253
|
+
this._marqueeStartCanvas = null;
|
|
254
|
+
this._marqueeCurrentCanvas = null;
|
|
255
|
+
this._preMarqueeSelectedIds.clear();
|
|
256
|
+
this._marqueeEnteredContainerId = null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=selection.handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.handler.js","sourceRoot":"","sources":["../../src/handlers/selection.handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Rect, Vec2 } from "../types.js";
|
|
2
|
+
import type { SpacingAdjusterType } from "../renderer.js";
|
|
3
|
+
import type { InteractionHandler, WorkspaceContext } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Manages margin, padding, and corner radius adjustment gestures.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SpacingHandler implements InteractionHandler {
|
|
8
|
+
readonly id = "spacing";
|
|
9
|
+
private ctx;
|
|
10
|
+
hoveredAdjusterType: SpacingAdjusterType | null;
|
|
11
|
+
hoveredRadiusCorner: string | null;
|
|
12
|
+
private _activeAdjusterType;
|
|
13
|
+
private _adjusterStartValue;
|
|
14
|
+
private _adjusterStartValueStr;
|
|
15
|
+
private _isAdjustingRadius;
|
|
16
|
+
private _activeRadiusCorner;
|
|
17
|
+
private _radiusTargetNodeId;
|
|
18
|
+
private _radiusStartValues;
|
|
19
|
+
private _dragStartCanvas;
|
|
20
|
+
constructor(ctx: WorkspaceContext);
|
|
21
|
+
get activeAdjusterType(): SpacingAdjusterType | null;
|
|
22
|
+
get isAdjustingRadius(): boolean;
|
|
23
|
+
get activeRadiusCorner(): string | null;
|
|
24
|
+
claim(e: PointerEvent, canvasPos: Vec2, hitNodeId: string | null, containerRect: Rect): boolean;
|
|
25
|
+
onPointerMove(e: PointerEvent, canvasPos: Vec2, containerRect: Rect): void;
|
|
26
|
+
onPointerUp(_e: PointerEvent, _canvasPos: Vec2, _containerRect: Rect): void;
|
|
27
|
+
onCancel(): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=spacing.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spacing.handler.d.ts","sourceRoot":"","sources":["../../src/handlers/spacing.handler.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvE;;GAEG;AACH,qBAAa,cAAe,YAAW,kBAAkB;IACvD,QAAQ,CAAC,EAAE,aAAa;IAExB,OAAO,CAAC,GAAG,CAAmB;IAI9B,mBAAmB,EAAE,mBAAmB,GAAG,IAAI,CAAQ;IACvD,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAI1C,OAAO,CAAC,mBAAmB,CAAoC;IAC/D,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,sBAAsB,CAAuB;IAErD,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,kBAAkB,CAA6B;IAEvD,OAAO,CAAC,gBAAgB,CAAqB;gBAEjC,GAAG,EAAE,gBAAgB;IAMjC,IAAI,kBAAkB,IAAI,mBAAmB,GAAG,IAAI,CAEnD;IAED,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED,IAAI,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAEtC;IAID,KAAK,CACH,CAAC,EAAE,YAAY,EACf,SAAS,EAAE,IAAI,EACf,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,EAAE,IAAI,GAClB,OAAO;IA+GV,aAAa,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,GAAG,IAAI;IAqH1E,WAAW,CAAC,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,GAAG,IAAI;IAoF3E,QAAQ,IAAI,IAAI;CAWjB"}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// canvus/src/handlers/spacing.handler.ts
|
|
3
|
+
// Handles hit-testing and dragging for spacing adjusters and corner radius handles.
|
|
4
|
+
// ─────────────────────────────────────────────────────────────
|
|
5
|
+
import { isContainerNode } from "../renderer.js";
|
|
6
|
+
/**
|
|
7
|
+
* Manages margin, padding, and corner radius adjustment gestures.
|
|
8
|
+
*/
|
|
9
|
+
export class SpacingHandler {
|
|
10
|
+
id = "spacing";
|
|
11
|
+
ctx;
|
|
12
|
+
// ── Hover State ──────────────────────────────────
|
|
13
|
+
hoveredAdjusterType = null;
|
|
14
|
+
hoveredRadiusCorner = null;
|
|
15
|
+
// ── Active Drag State ────────────────────────────
|
|
16
|
+
_activeAdjusterType = null;
|
|
17
|
+
_adjusterStartValue = 0;
|
|
18
|
+
_adjusterStartValueStr = null;
|
|
19
|
+
_isAdjustingRadius = false;
|
|
20
|
+
_activeRadiusCorner = null;
|
|
21
|
+
_radiusTargetNodeId = null;
|
|
22
|
+
_radiusStartValues = new Map();
|
|
23
|
+
_dragStartCanvas = null;
|
|
24
|
+
constructor(ctx) {
|
|
25
|
+
this.ctx = ctx;
|
|
26
|
+
}
|
|
27
|
+
// ── Getters for Workspace Integration ────────────
|
|
28
|
+
get activeAdjusterType() {
|
|
29
|
+
return this._activeAdjusterType;
|
|
30
|
+
}
|
|
31
|
+
get isAdjustingRadius() {
|
|
32
|
+
return this._isAdjustingRadius;
|
|
33
|
+
}
|
|
34
|
+
get activeRadiusCorner() {
|
|
35
|
+
return this._activeRadiusCorner;
|
|
36
|
+
}
|
|
37
|
+
// ── InteractionHandler Interface ────────────────
|
|
38
|
+
claim(e, canvasPos, hitNodeId, containerRect) {
|
|
39
|
+
if (e.button !== 0 || this.ctx.previewMode)
|
|
40
|
+
return false;
|
|
41
|
+
// Calculate isDoubleClick early to prevent handles/adjusters from intercepting double-clicks on small/nested nodes
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const targetEl = e.composedPath()[0];
|
|
44
|
+
const isSameTarget = targetEl !== null && this.ctx.lastPointerDownTarget !== null &&
|
|
45
|
+
(targetEl === this.ctx.lastPointerDownTarget || this.ctx.lastPointerDownTarget.contains(targetEl) || targetEl.contains(this.ctx.lastPointerDownTarget));
|
|
46
|
+
const isDoubleClick = (now - this.ctx.lastPointerDownTime < 350) && (hitNodeId !== null &&
|
|
47
|
+
this.ctx.lastPointerDownId !== null &&
|
|
48
|
+
(hitNodeId === this.ctx.lastPointerDownId || isSameTarget || this.ctx.tree.isAncestor(this.ctx.lastPointerDownId, hitNodeId)));
|
|
49
|
+
if (isDoubleClick)
|
|
50
|
+
return false;
|
|
51
|
+
// ── Corner Radius handles hit-test ────────────
|
|
52
|
+
if (this.ctx.selectedIds.size > 0) {
|
|
53
|
+
const localX = e.clientX - containerRect.x;
|
|
54
|
+
const localY = e.clientY - containerRect.y;
|
|
55
|
+
let hitRadiusCorner = null;
|
|
56
|
+
let targetNodeId = null;
|
|
57
|
+
for (const selId of this.ctx.selectedIds) {
|
|
58
|
+
const selNode = this.ctx.tree.get(selId);
|
|
59
|
+
if (selNode && isContainerNode(selNode) && selNode.currentRect) {
|
|
60
|
+
const hit = this.ctx.hitTestRadiusHandle(localX, localY, selNode.currentRect, this.ctx.viewport);
|
|
61
|
+
if (hit) {
|
|
62
|
+
hitRadiusCorner = hit;
|
|
63
|
+
targetNodeId = selId;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (hitRadiusCorner && targetNodeId) {
|
|
69
|
+
// Property lock check for corner-radius (multi-node)
|
|
70
|
+
let radiusBlocked = false;
|
|
71
|
+
for (const selId of this.ctx.selectedIds) {
|
|
72
|
+
const selNode = this.ctx.tree.get(selId);
|
|
73
|
+
if (selNode && isContainerNode(selNode)) {
|
|
74
|
+
if (this.ctx.isNodeLocked(selId) || this.ctx.isPropertyLocked(selId, "border-radius")) {
|
|
75
|
+
this.ctx.notifyPropertyLockInteraction(selId, "border-radius");
|
|
76
|
+
radiusBlocked = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (radiusBlocked) {
|
|
81
|
+
return true; // Claim and block
|
|
82
|
+
}
|
|
83
|
+
this._isAdjustingRadius = true;
|
|
84
|
+
this._activeRadiusCorner = hitRadiusCorner;
|
|
85
|
+
this._radiusTargetNodeId = targetNodeId;
|
|
86
|
+
this._radiusStartValues.clear();
|
|
87
|
+
for (const selId of this.ctx.selectedIds) {
|
|
88
|
+
const selNode = this.ctx.tree.get(selId);
|
|
89
|
+
if (selNode && isContainerNode(selNode)) {
|
|
90
|
+
const contentRoot = this.ctx.mount.getContentRoot(selId);
|
|
91
|
+
let initialRadiusStr = "0px";
|
|
92
|
+
if (contentRoot) {
|
|
93
|
+
initialRadiusStr = contentRoot.style.borderRadius || window.getComputedStyle(contentRoot).borderRadius || "0px";
|
|
94
|
+
}
|
|
95
|
+
this._radiusStartValues.set(selId, initialRadiusStr);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this._dragStartCanvas = canvasPos;
|
|
99
|
+
this.ctx.safeSetPointerCapture(e.pointerId);
|
|
100
|
+
this.ctx.emitInteraction("resize-radius", { handler: this.id });
|
|
101
|
+
this.ctx.render();
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// ── Spacing Adjusters hit-test ────────────────
|
|
106
|
+
if (this.ctx.selectedIds.size === 1) {
|
|
107
|
+
const selId = this.ctx.selectedIds.values().next().value;
|
|
108
|
+
const adjusters = this.ctx.computeSpacingAdjusters(selId);
|
|
109
|
+
const hitAdjuster = adjusters.find(adj => canvasPos.x >= adj.rect.x &&
|
|
110
|
+
canvasPos.x <= adj.rect.x + adj.rect.width &&
|
|
111
|
+
canvasPos.y >= adj.rect.y &&
|
|
112
|
+
canvasPos.y <= adj.rect.y + adj.rect.height);
|
|
113
|
+
if (hitAdjuster) {
|
|
114
|
+
// Property lock check for spacing adjuster
|
|
115
|
+
if (this.ctx.isNodeLocked(selId) || this.ctx.isPropertyLocked(selId, hitAdjuster.type)) {
|
|
116
|
+
this.ctx.notifyPropertyLockInteraction(selId, hitAdjuster.type);
|
|
117
|
+
return true; // Claim and block
|
|
118
|
+
}
|
|
119
|
+
this._activeAdjusterType = hitAdjuster.type;
|
|
120
|
+
this._adjusterStartValue = hitAdjuster.value;
|
|
121
|
+
const contentRoot = this.ctx.mount.getContentRoot(selId);
|
|
122
|
+
this._adjusterStartValueStr = contentRoot ? (contentRoot.style.getPropertyValue(hitAdjuster.type) || null) : null;
|
|
123
|
+
this._dragStartCanvas = canvasPos;
|
|
124
|
+
this.ctx.safeSetPointerCapture(e.pointerId);
|
|
125
|
+
this.ctx.emitInteraction("adjust-spacing", { handler: this.id });
|
|
126
|
+
this.ctx.render();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
onPointerMove(e, canvasPos, containerRect) {
|
|
133
|
+
// ── Corner Radius Adjusting ───────────────────
|
|
134
|
+
if (this._isAdjustingRadius && this._dragStartCanvas && this._radiusTargetNodeId) {
|
|
135
|
+
const targetNode = this.ctx.tree.get(this._radiusTargetNodeId);
|
|
136
|
+
if (targetNode && targetNode.currentRect) {
|
|
137
|
+
this.ctx.safeSetPointerCapture(e.pointerId);
|
|
138
|
+
this.ctx.container.style.cursor = "pointer";
|
|
139
|
+
this.ctx.canvas.style.pointerEvents = "auto";
|
|
140
|
+
this.ctx.emitInteraction("resize-radius", { handler: this.id });
|
|
141
|
+
const bounds = targetNode.currentRect;
|
|
142
|
+
const s = this.ctx.viewport.scale;
|
|
143
|
+
const ox = this.ctx.viewport.offsetX;
|
|
144
|
+
const oy = this.ctx.viewport.offsetY;
|
|
145
|
+
const left = bounds.x * s + ox;
|
|
146
|
+
const top = bounds.y * s + oy;
|
|
147
|
+
const right = (bounds.x + bounds.width) * s + ox;
|
|
148
|
+
const bottom = (bounds.y + bounds.height) * s + oy;
|
|
149
|
+
let dragX = 0;
|
|
150
|
+
let dragY = 0;
|
|
151
|
+
if (this._activeRadiusCorner === "tl") {
|
|
152
|
+
dragX = e.clientX - containerRect.x - left;
|
|
153
|
+
dragY = e.clientY - containerRect.y - top;
|
|
154
|
+
}
|
|
155
|
+
else if (this._activeRadiusCorner === "tr") {
|
|
156
|
+
dragX = right - (e.clientX - containerRect.x);
|
|
157
|
+
dragY = e.clientY - containerRect.y - top;
|
|
158
|
+
}
|
|
159
|
+
else if (this._activeRadiusCorner === "bl") {
|
|
160
|
+
dragX = e.clientX - containerRect.x - left;
|
|
161
|
+
dragY = bottom - (e.clientY - containerRect.y);
|
|
162
|
+
}
|
|
163
|
+
else if (this._activeRadiusCorner === "br") {
|
|
164
|
+
dragX = right - (e.clientX - containerRect.x);
|
|
165
|
+
dragY = bottom - (e.clientY - containerRect.y);
|
|
166
|
+
}
|
|
167
|
+
const dragDistScreen = (dragX + dragY) / 2;
|
|
168
|
+
const dragDistCanvas = dragDistScreen / s;
|
|
169
|
+
// Apply to all selected containers
|
|
170
|
+
for (const selId of this.ctx.selectedIds) {
|
|
171
|
+
const selNode = this.ctx.tree.get(selId);
|
|
172
|
+
if (selNode && isContainerNode(selNode) && selNode.currentRect) {
|
|
173
|
+
const maxRadius = Math.min(selNode.currentRect.width, selNode.currentRect.height) / 2;
|
|
174
|
+
const newRadius = Math.max(0, Math.min(maxRadius, Math.round(dragDistCanvas)));
|
|
175
|
+
this.ctx.mount.setNodeStyle(selId, "border-radius", `${newRadius}px`);
|
|
176
|
+
this.ctx.remeasureSubtree(selId);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
this.ctx.render();
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// ── Spacing Adjusters Dragging ────────────────
|
|
184
|
+
if (this._activeAdjusterType && this._dragStartCanvas) {
|
|
185
|
+
const selId = this.ctx.selectedIds.values().next().value;
|
|
186
|
+
const node = this.ctx.tree.get(selId);
|
|
187
|
+
if (!node)
|
|
188
|
+
return;
|
|
189
|
+
this.ctx.safeSetPointerCapture(e.pointerId);
|
|
190
|
+
this.ctx.canvas.style.pointerEvents = "auto";
|
|
191
|
+
this.ctx.emitInteraction("adjust-spacing", { handler: this.id });
|
|
192
|
+
const isVertical = this._activeAdjusterType.includes("top") || this._activeAdjusterType.includes("bottom");
|
|
193
|
+
this.ctx.container.style.cursor = isVertical ? "ns-resize" : "ew-resize";
|
|
194
|
+
const dx = canvasPos.x - this._dragStartCanvas.x;
|
|
195
|
+
const dy = canvasPos.y - this._dragStartCanvas.y;
|
|
196
|
+
let delta = 0;
|
|
197
|
+
switch (this._activeAdjusterType) {
|
|
198
|
+
case "padding-top":
|
|
199
|
+
delta = dy;
|
|
200
|
+
break;
|
|
201
|
+
case "padding-bottom":
|
|
202
|
+
delta = -dy;
|
|
203
|
+
break;
|
|
204
|
+
case "padding-left":
|
|
205
|
+
delta = dx;
|
|
206
|
+
break;
|
|
207
|
+
case "padding-right":
|
|
208
|
+
delta = -dx;
|
|
209
|
+
break;
|
|
210
|
+
case "margin-top":
|
|
211
|
+
delta = -dy;
|
|
212
|
+
break;
|
|
213
|
+
case "margin-bottom":
|
|
214
|
+
delta = -dy;
|
|
215
|
+
break;
|
|
216
|
+
case "margin-left":
|
|
217
|
+
delta = -dx;
|
|
218
|
+
break;
|
|
219
|
+
case "margin-right":
|
|
220
|
+
delta = -dx;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
const contentRoot = this.ctx.mount.getContentRoot(selId);
|
|
224
|
+
const internalScale = contentRoot ? this.ctx.mount.getElementScale(contentRoot) : 1;
|
|
225
|
+
const safeScale = internalScale && !isNaN(internalScale) ? internalScale : 1;
|
|
226
|
+
const newValue = Math.max(0, Math.round(this._adjusterStartValue + delta / safeScale));
|
|
227
|
+
// Style surgery - direct DOM mutation
|
|
228
|
+
this.ctx.mount.setNodeStyle(selId, this._activeAdjusterType, `${newValue}px`);
|
|
229
|
+
// Synchronous reflow + measurement
|
|
230
|
+
this.ctx.remeasureSubtree(selId);
|
|
231
|
+
if (node.parentId) {
|
|
232
|
+
this.ctx.remeasureSubtree(node.parentId);
|
|
233
|
+
}
|
|
234
|
+
this.ctx.render();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
onPointerUp(_e, _canvasPos, _containerRect) {
|
|
238
|
+
const operations = [];
|
|
239
|
+
if (this._activeAdjusterType) {
|
|
240
|
+
if (this.ctx.selectedIds.size === 1) {
|
|
241
|
+
const selId = this.ctx.selectedIds.values().next().value;
|
|
242
|
+
const contentRoot = this.ctx.mount.getContentRoot(selId);
|
|
243
|
+
if (contentRoot && this._activeAdjusterType) {
|
|
244
|
+
const finalValueStr = contentRoot.style.getPropertyValue(this._activeAdjusterType) || null;
|
|
245
|
+
if (finalValueStr !== this._adjusterStartValueStr) {
|
|
246
|
+
operations.push({
|
|
247
|
+
type: "update-style",
|
|
248
|
+
nodeId: selId,
|
|
249
|
+
payload: { [this._activeAdjusterType]: finalValueStr },
|
|
250
|
+
undoPayload: { [this._activeAdjusterType]: this._adjusterStartValueStr }
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const node = this.ctx.tree.get(selId);
|
|
255
|
+
const commitId = (node && node.parentId !== null) ? node.parentId : selId;
|
|
256
|
+
if (operations.length > 0) {
|
|
257
|
+
this.ctx.callbacks.onOperationsGenerated?.(operations);
|
|
258
|
+
const html = this.ctx.mount.extractHTML(commitId);
|
|
259
|
+
if (html) {
|
|
260
|
+
this.ctx.callbacks.onHTMLCommit?.(commitId, html);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
this._activeAdjusterType = null;
|
|
265
|
+
this._dragStartCanvas = null;
|
|
266
|
+
this.ctx.container.style.cursor = "default";
|
|
267
|
+
this._adjusterStartValueStr = null;
|
|
268
|
+
}
|
|
269
|
+
if (this._isAdjustingRadius) {
|
|
270
|
+
const parentsToCommit = new Set();
|
|
271
|
+
for (const selId of this.ctx.selectedIds) {
|
|
272
|
+
const selNode = this.ctx.tree.get(selId);
|
|
273
|
+
if (selNode && isContainerNode(selNode)) {
|
|
274
|
+
const contentRoot = this.ctx.mount.getContentRoot(selId);
|
|
275
|
+
if (contentRoot) {
|
|
276
|
+
const finalRadiusStr = contentRoot.style.borderRadius || "";
|
|
277
|
+
const initialRadiusStr = this._radiusStartValues.get(selId) || "0px";
|
|
278
|
+
if (finalRadiusStr !== initialRadiusStr) {
|
|
279
|
+
operations.push({
|
|
280
|
+
type: "update-style",
|
|
281
|
+
nodeId: selId,
|
|
282
|
+
payload: { "border-radius": finalRadiusStr },
|
|
283
|
+
undoPayload: { "border-radius": initialRadiusStr }
|
|
284
|
+
});
|
|
285
|
+
if (selNode.parentId) {
|
|
286
|
+
parentsToCommit.add(selNode.parentId);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
parentsToCommit.add(selId);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
for (const commitId of parentsToCommit) {
|
|
296
|
+
const html = this.ctx.mount.extractHTML(commitId);
|
|
297
|
+
if (html) {
|
|
298
|
+
this.ctx.callbacks.onHTMLCommit?.(commitId, html);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (operations.length > 0) {
|
|
302
|
+
this.ctx.callbacks.onOperationsGenerated?.(operations);
|
|
303
|
+
}
|
|
304
|
+
this._isAdjustingRadius = false;
|
|
305
|
+
this._activeRadiusCorner = null;
|
|
306
|
+
this._radiusTargetNodeId = null;
|
|
307
|
+
this._radiusStartValues.clear();
|
|
308
|
+
this._dragStartCanvas = null;
|
|
309
|
+
this.ctx.container.style.cursor = "default";
|
|
310
|
+
}
|
|
311
|
+
this.ctx.emitInteraction(null);
|
|
312
|
+
this.ctx.render();
|
|
313
|
+
}
|
|
314
|
+
onCancel() {
|
|
315
|
+
this._activeAdjusterType = null;
|
|
316
|
+
this._isAdjustingRadius = false;
|
|
317
|
+
this._activeRadiusCorner = null;
|
|
318
|
+
this._radiusTargetNodeId = null;
|
|
319
|
+
this._radiusStartValues.clear();
|
|
320
|
+
this._dragStartCanvas = null;
|
|
321
|
+
this.ctx.container.style.cursor = "default";
|
|
322
|
+
this.ctx.emitInteraction(null);
|
|
323
|
+
this.ctx.render();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
//# sourceMappingURL=spacing.handler.js.map
|