@canvus/core 0.1.1 → 0.1.2

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/drop-zone.d.ts.map +1 -1
  3. package/dist/drop-zone.js +16 -0
  4. package/dist/drop-zone.js.map +1 -1
  5. package/dist/handlers/clipboard.handler.d.ts +22 -0
  6. package/dist/handlers/clipboard.handler.d.ts.map +1 -0
  7. package/dist/handlers/clipboard.handler.js +349 -0
  8. package/dist/handlers/clipboard.handler.js.map +1 -0
  9. package/dist/handlers/command.handler.d.ts +18 -0
  10. package/dist/handlers/command.handler.d.ts.map +1 -0
  11. package/dist/handlers/command.handler.js +430 -0
  12. package/dist/handlers/command.handler.js.map +1 -0
  13. package/dist/handlers/drag.handler.d.ts +22 -0
  14. package/dist/handlers/drag.handler.d.ts.map +1 -0
  15. package/dist/handlers/drag.handler.js +669 -0
  16. package/dist/handlers/drag.handler.js.map +1 -0
  17. package/dist/handlers/draw.handler.d.ts +37 -0
  18. package/dist/handlers/draw.handler.d.ts.map +1 -0
  19. package/dist/handlers/draw.handler.js +210 -0
  20. package/dist/handlers/draw.handler.js.map +1 -0
  21. package/dist/handlers/index.d.ts +10 -0
  22. package/dist/handlers/index.d.ts.map +1 -0
  23. package/dist/handlers/index.js +12 -0
  24. package/dist/handlers/index.js.map +1 -0
  25. package/dist/handlers/pan.handler.d.ts +34 -0
  26. package/dist/handlers/pan.handler.d.ts.map +1 -0
  27. package/dist/handlers/pan.handler.js +95 -0
  28. package/dist/handlers/pan.handler.js.map +1 -0
  29. package/dist/handlers/resize.handler.d.ts +26 -0
  30. package/dist/handlers/resize.handler.d.ts.map +1 -0
  31. package/dist/handlers/resize.handler.js +487 -0
  32. package/dist/handlers/resize.handler.js.map +1 -0
  33. package/dist/handlers/selection.handler.d.ts +22 -0
  34. package/dist/handlers/selection.handler.d.ts.map +1 -0
  35. package/dist/handlers/selection.handler.js +259 -0
  36. package/dist/handlers/selection.handler.js.map +1 -0
  37. package/dist/handlers/spacing.handler.d.ts +29 -0
  38. package/dist/handlers/spacing.handler.d.ts.map +1 -0
  39. package/dist/handlers/spacing.handler.js +326 -0
  40. package/dist/handlers/spacing.handler.js.map +1 -0
  41. package/dist/handlers/types.d.ts +204 -0
  42. package/dist/handlers/types.d.ts.map +1 -0
  43. package/dist/handlers/types.js +10 -0
  44. package/dist/handlers/types.js.map +1 -0
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/shadow-mount.d.ts.map +1 -1
  48. package/dist/shadow-mount.js +51 -2
  49. package/dist/shadow-mount.js.map +1 -1
  50. package/dist/workspace.d.ts +149 -68
  51. package/dist/workspace.d.ts.map +1 -1
  52. package/dist/workspace.js +349 -2208
  53. package/dist/workspace.js.map +1 -1
  54. 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