@sequent-org/moodboard 1.4.30 → 1.4.32

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 (61) hide show
  1. package/package.json +3 -1
  2. package/src/core/PixiEngine.js +34 -5
  3. package/src/core/bootstrap/CoreInitializer.js +4 -0
  4. package/src/core/commands/CreateConnectorCommand.js +25 -0
  5. package/src/core/commands/GroupMoveCommand.js +2 -2
  6. package/src/core/commands/MoveObjectCommand.js +1 -1
  7. package/src/core/commands/UpdateConnectorCommand.js +38 -0
  8. package/src/core/events/Events.js +1 -0
  9. package/src/mindmap/MindmapCompoundContract.js +1 -0
  10. package/src/moodboard/bootstrap/MoodBoardUiFactory.js +14 -0
  11. package/src/moodboard/lifecycle/MoodBoardDestroyer.js +18 -0
  12. package/src/objects/ConnectorObject.js +85 -0
  13. package/src/objects/DrawingObject.js +47 -0
  14. package/src/objects/MindmapObject.js +21 -3
  15. package/src/objects/NoteObject.js +16 -8
  16. package/src/objects/ObjectFactory.js +3 -1
  17. package/src/objects/ShapeObject.js +1 -1
  18. package/src/services/ConnectorBindingResolver.js +204 -0
  19. package/src/services/ai/AiClient.js +30 -2
  20. package/src/services/ai/ChatSessionController.js +1 -0
  21. package/src/tools/ToolManager.js +3 -0
  22. package/src/tools/manager/PointerGestureController.js +206 -0
  23. package/src/tools/manager/ToolEventRouter.js +10 -0
  24. package/src/tools/manager/ToolManagerGuards.js +3 -1
  25. package/src/tools/manager/ToolManagerLifecycle.js +70 -58
  26. package/src/tools/object-tools/ConnectorTool.js +147 -0
  27. package/src/tools/object-tools/PlacementTool.js +2 -2
  28. package/src/tools/object-tools/connector/ConnectorDragController.js +296 -0
  29. package/src/tools/object-tools/connector/connectorGesture.js +108 -0
  30. package/src/tools/object-tools/placement/GhostController.js +4 -4
  31. package/src/tools/object-tools/placement/PlacementEventsBridge.js +2 -2
  32. package/src/tools/object-tools/placement/PlacementInputRouter.js +5 -5
  33. package/src/tools/object-tools/selection/MindmapInlineEditorController.js +11 -2
  34. package/src/tools/object-tools/selection/SelectInputRouter.js +33 -4
  35. package/src/tools/object-tools/selection/SelectToolLifecycleController.js +12 -0
  36. package/src/tools/object-tools/selection/SelectToolSetup.js +3 -0
  37. package/src/tools/object-tools/selection/TextEditorDomFactory.js +1 -2
  38. package/src/tools/object-tools/selection/TextEditorSyncService.js +4 -4
  39. package/src/tools/object-tools/selection/TextInlineEditorController.js +21 -3
  40. package/src/tools/object-tools/selection/TransformInteractionController.js +4 -6
  41. package/src/ui/HtmlTextLayer.js +212 -5
  42. package/src/ui/animation/HoverLiftController.js +395 -0
  43. package/src/ui/chat/ChatComposer.js +1 -10
  44. package/src/ui/chat/ChatExtendedPromptModal.js +1 -12
  45. package/src/ui/chat/ChatWindow.js +167 -36
  46. package/src/ui/chat/ChatWindowRenderer.js +1 -8
  47. package/src/ui/chat/icons.js +17 -5
  48. package/src/ui/connectors/ConnectionAnchorsLayer.js +231 -0
  49. package/src/ui/connectors/ConnectorLayer.js +251 -0
  50. package/src/ui/handles/HandlesDomRenderer.js +11 -7
  51. package/src/ui/handles/HandlesInteractionController.js +65 -34
  52. package/src/ui/handles/HandlesPositioningService.js +41 -6
  53. package/src/ui/mindmap/MindmapCollapseGraph.js +169 -0
  54. package/src/ui/mindmap/MindmapCollapseLayer.js +380 -0
  55. package/src/ui/mindmap/MindmapConnectionLayer.js +50 -25
  56. package/src/ui/mindmap/MindmapHtmlTextLayer.js +223 -2
  57. package/src/ui/mindmap/MindmapLayoutConfig.js +12 -0
  58. package/src/ui/styles/chat.css +2 -37
  59. package/src/ui/styles/toolbar.css +6 -0
  60. package/src/ui/styles/workspace.css +83 -21
  61. package/src/ui/toolbar/ToolbarPopupsController.js +1 -1
@@ -0,0 +1,231 @@
1
+ import { Events } from '../../core/events/Events.js';
2
+ import { HandlesPositioningService } from '../handles/HandlesPositioningService.js';
3
+ import { ConnectorDragController } from '../../tools/object-tools/connector/ConnectorDragController.js';
4
+
5
+ const ALLOWED_TYPES = new Set(['shape', 'note', 'image', 'text', 'simple-text', 'file']);
6
+
7
+ export class ConnectionAnchorsLayer {
8
+ constructor(container, eventBus, core) {
9
+ this.container = container;
10
+ this.eventBus = eventBus;
11
+ this.core = core;
12
+ this.layer = null;
13
+ this.positioningService = new HandlesPositioningService(this);
14
+
15
+ this.subscriptions = [];
16
+ this._eventsAttached = false;
17
+
18
+ this.hoveredObjectId = null;
19
+ this._dragController = null;
20
+ this._onAnchorPointerDown = null;
21
+ }
22
+
23
+ attach() {
24
+ if (!this.layer) {
25
+ this.layer = document.createElement('div');
26
+ this.layer.className = 'mb-connection-anchors-layer';
27
+ Object.assign(this.layer.style, {
28
+ position: 'absolute',
29
+ left: '0',
30
+ top: '0',
31
+ width: '100%',
32
+ height: '100%',
33
+ pointerEvents: 'none',
34
+ zIndex: '35'
35
+ });
36
+ this.container.appendChild(this.layer);
37
+
38
+ this._dragController = new ConnectorDragController(this.core, this.eventBus);
39
+ this._onAnchorPointerDown = (e) => {
40
+ if (!e.target.dataset.connectorAnchor) return;
41
+ e.preventDefault();
42
+ e.stopPropagation();
43
+ this._dragController.startFromAnchor(e);
44
+ };
45
+ this.layer.addEventListener('pointerdown', this._onAnchorPointerDown);
46
+ }
47
+
48
+ this._attachEvents();
49
+ this.update();
50
+ }
51
+
52
+ destroy() {
53
+ this._detachEvents();
54
+ if (this._onAnchorPointerDown && this.layer) {
55
+ this.layer.removeEventListener('pointerdown', this._onAnchorPointerDown);
56
+ this._onAnchorPointerDown = null;
57
+ }
58
+ if (this._dragController) {
59
+ this._dragController.destroy();
60
+ this._dragController = null;
61
+ }
62
+ if (this.layer && this.layer.parentNode) {
63
+ this.layer.parentNode.removeChild(this.layer);
64
+ }
65
+ this.layer = null;
66
+ this.eventBus = null;
67
+ this.core = null;
68
+ this.container = null;
69
+ }
70
+
71
+ _attachEvents() {
72
+ if (this._eventsAttached) return;
73
+
74
+ const bindings = [
75
+ [Events.Object.Hover, (e) => {
76
+ this.hoveredObjectId = e.objectId || null;
77
+ this.update();
78
+ }],
79
+ [Events.Tool.SelectionAdd, () => this.update()],
80
+ [Events.Tool.SelectionRemove, () => this.update()],
81
+ [Events.Tool.SelectionClear, () => this.update()],
82
+ [Events.Object.Created, () => this.update()],
83
+ [Events.Object.Deleted, () => this.update()],
84
+ [Events.Object.Updated, () => this.update()],
85
+ [Events.Object.StateChanged, () => this.update()],
86
+ [Events.Tool.DragUpdate, () => this.update()],
87
+ [Events.Tool.DragEnd, () => this.update()],
88
+ [Events.Tool.ResizeUpdate, () => this.update()],
89
+ [Events.Tool.ResizeEnd, () => this.update()],
90
+ [Events.Tool.GroupDragUpdate, () => this.update()],
91
+ [Events.Tool.GroupResizeUpdate, () => this.update()],
92
+ [Events.Tool.RotateUpdate, () => this.update()],
93
+ [Events.Tool.PanUpdate, () => this.update()],
94
+ [Events.UI.ZoomPercent, () => this.update()],
95
+ [Events.History.Changed, () => this.update()],
96
+ [Events.Board.Loaded, () => this.update()]
97
+ ];
98
+
99
+ bindings.forEach(([event, handler]) => {
100
+ this.eventBus.on(event, handler);
101
+ this.subscriptions.push([event, handler]);
102
+ });
103
+
104
+ this._eventsAttached = true;
105
+ }
106
+
107
+ _detachEvents() {
108
+ if (typeof this.eventBus?.off !== 'function') {
109
+ this.subscriptions = [];
110
+ this._eventsAttached = false;
111
+ return;
112
+ }
113
+ this.subscriptions.forEach(([event, handler]) => {
114
+ this.eventBus.off(event, handler);
115
+ });
116
+ this.subscriptions = [];
117
+ this._eventsAttached = false;
118
+ }
119
+
120
+ _getSingleSelectionWorldBounds(id) {
121
+ const positionData = { objectId: id, position: null };
122
+ const sizeData = { objectId: id, size: null };
123
+ this.eventBus.emit(Events.Tool.GetObjectPosition, positionData);
124
+ this.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
125
+
126
+ if (positionData.position && sizeData.size) {
127
+ return {
128
+ x: positionData.position.x,
129
+ y: positionData.position.y,
130
+ width: sizeData.size.width,
131
+ height: sizeData.size.height,
132
+ };
133
+ }
134
+ return null;
135
+ }
136
+
137
+ update() {
138
+ if (!this.layer) return;
139
+ this.layer.innerHTML = '';
140
+
141
+ const selection = Array.from(this.core?.selectTool?.selectedObjects || []);
142
+ let selectedId = null;
143
+ if (selection.length === 1) {
144
+ selectedId = selection[0];
145
+ }
146
+
147
+ const targets = new Set();
148
+ if (this.hoveredObjectId) targets.add(this.hoveredObjectId);
149
+ if (selectedId) targets.add(selectedId);
150
+
151
+ targets.forEach(id => {
152
+ this._renderAnchorsFor(id);
153
+ });
154
+ }
155
+
156
+ _renderAnchorsFor(id) {
157
+ const req = { objectId: id, pixiObject: null };
158
+ this.eventBus.emit(Events.Tool.GetObjectPixi, req);
159
+ const mbType = req.pixiObject?._mb?.type;
160
+
161
+ if (!mbType || !ALLOWED_TYPES.has(mbType)) {
162
+ return;
163
+ }
164
+
165
+ const worldBounds = this._getSingleSelectionWorldBounds(id);
166
+ if (!worldBounds) return;
167
+
168
+ const cssRect = this.positioningService.worldBoundsToCssRect(worldBounds);
169
+
170
+ const left = Math.round(cssRect.left);
171
+ const top = Math.round(cssRect.top);
172
+ const width = Math.max(1, Math.round(cssRect.width));
173
+ const height = Math.max(1, Math.round(cssRect.height));
174
+
175
+ const rotationData = { objectId: id, rotation: 0 };
176
+ this.eventBus.emit(Events.Tool.GetObjectRotation, rotationData);
177
+ const rotation = rotationData.rotation || 0;
178
+
179
+ const wrapper = document.createElement('div');
180
+ Object.assign(wrapper.style, {
181
+ position: 'absolute',
182
+ left: `${left}px`,
183
+ top: `${top}px`,
184
+ width: `${width}px`,
185
+ height: `${height}px`,
186
+ pointerEvents: 'none',
187
+ transformOrigin: 'center center',
188
+ transform: `rotate(${rotation}deg)`,
189
+ boxSizing: 'border-box'
190
+ });
191
+
192
+ const offset = 12;
193
+ const radius = 5;
194
+ const dotSize = radius * 2;
195
+
196
+ const createDot = (side, x, y, ax, ay) => {
197
+ const dot = document.createElement('div');
198
+ dot.className = 'mb-connection-anchor';
199
+ Object.assign(dot.style, {
200
+ position: 'absolute',
201
+ left: `${Math.round(x - radius)}px`,
202
+ top: `${Math.round(y - radius)}px`,
203
+ width: `${dotSize}px`,
204
+ height: `${dotSize}px`,
205
+ backgroundColor: '#2563EB',
206
+ borderRadius: '50%',
207
+ pointerEvents: 'auto',
208
+ boxSizing: 'border-box',
209
+ border: '2px solid #ffffff'
210
+ });
211
+
212
+ dot.dataset.connectorAnchor = "1";
213
+ dot.dataset.id = id;
214
+ dot.dataset.side = side;
215
+ dot.dataset.anchorX = ax;
216
+ dot.dataset.anchorY = ay;
217
+
218
+ wrapper.appendChild(dot);
219
+ };
220
+
221
+ const cx = Math.round(width / 2);
222
+ const cy = Math.round(height / 2);
223
+
224
+ createDot('top', cx, -offset, 0.5, 0);
225
+ createDot('right', width + offset, cy, 1, 0.5);
226
+ createDot('bottom', cx, height + offset, 0.5, 1);
227
+ createDot('left', -offset, cy, 0, 0.5);
228
+
229
+ this.layer.appendChild(wrapper);
230
+ }
231
+ }
@@ -0,0 +1,251 @@
1
+ import * as PIXI from 'pixi.js';
2
+ import { Events } from '../../core/events/Events.js';
3
+ import { ConnectorBindingResolver, distanceToSegment } from '../../services/ConnectorBindingResolver.js';
4
+
5
+ const HIT_TEST_SCREEN_PX = 8;
6
+ const ARROW_LEN = 12;
7
+ const ARROW_HALF = 4;
8
+ const DASH_LEN = 8;
9
+ const GAP_LEN = 5;
10
+
11
+ function asArray(value) {
12
+ return Array.isArray(value) ? value : [];
13
+ }
14
+
15
+ /**
16
+ * Рисует пунктирную линию через последовательность moveTo/lineTo.
17
+ * @param {PIXI.Graphics} g
18
+ */
19
+ function drawDashedLine(g, x1, y1, x2, y2) {
20
+ const dx = x2 - x1;
21
+ const dy = y2 - y1;
22
+ const len = Math.hypot(dx, dy);
23
+ if (len < 1e-6) return;
24
+ const ux = dx / len;
25
+ const uy = dy / len;
26
+ let dist = 0;
27
+ let drawing = true;
28
+ g.moveTo(Math.round(x1), Math.round(y1));
29
+ while (dist < len) {
30
+ const step = drawing ? DASH_LEN : GAP_LEN;
31
+ const next = Math.min(dist + step, len);
32
+ const px = x1 + ux * next;
33
+ const py = y1 + uy * next;
34
+ if (drawing) {
35
+ g.lineTo(Math.round(px), Math.round(py));
36
+ } else {
37
+ g.moveTo(Math.round(px), Math.round(py));
38
+ }
39
+ dist = next;
40
+ drawing = !drawing;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Рисует треугольный наконечник стрелки в точке `to`, направление from→to.
46
+ * Вызывать после g.lineStyle(0) не нужно — сбрасывает линию сам.
47
+ * @param {PIXI.Graphics} g
48
+ * @param {{ x: number, y: number }} from
49
+ * @param {{ x: number, y: number }} to
50
+ * @param {number} color PIXI-цвет
51
+ */
52
+ function drawArrow(g, from, to, color) {
53
+ const dx = to.x - from.x;
54
+ const dy = to.y - from.y;
55
+ const len = Math.hypot(dx, dy);
56
+ if (len < 1e-6) return;
57
+ const ux = dx / len;
58
+ const uy = dy / len;
59
+ // перпендикуляр
60
+ const px = -uy;
61
+ const py = ux;
62
+ const bx = to.x - ux * ARROW_LEN;
63
+ const by = to.y - uy * ARROW_LEN;
64
+ g.lineStyle(0);
65
+ g.beginFill(color, 1);
66
+ g.drawPolygon([
67
+ Math.round(to.x), Math.round(to.y),
68
+ Math.round(bx + px * ARROW_HALF), Math.round(by + py * ARROW_HALF),
69
+ Math.round(bx - px * ARROW_HALF), Math.round(by - py * ARROW_HALF),
70
+ ]);
71
+ g.endFill();
72
+ }
73
+
74
+ /**
75
+ * ConnectorLayer — слой рендера универсальных коннекторов.
76
+ *
77
+ * Паттерн: MindmapConnectionLayer (один PIXI.Graphics, полная перерисовка на события).
78
+ * Рисует connector-объекты из state.objects в worldLayer.
79
+ * Резолвинг end-point: ConnectorBindingResolver.resolve() двумя проходами
80
+ * (грубый → точный) для корректной проекции на кромку при isExact=false.
81
+ */
82
+ export class ConnectorLayer {
83
+ /**
84
+ * @param {Object} eventBus Экземпляр EventBus
85
+ * @param {Object} core Экземпляр CoreMoodBoard
86
+ */
87
+ constructor(eventBus, core) {
88
+ this.eventBus = eventBus;
89
+ this.core = core;
90
+ this.graphics = null;
91
+ this.subscriptions = [];
92
+ this._eventsAttached = false;
93
+ /** @type {Array<{ id: string, start: {x,y}, end: {x,y} }>} */
94
+ this._lastSegments = [];
95
+ }
96
+
97
+ /** Инициализирует слой: подписки на события и первый рендер. */
98
+ attach() {
99
+ if (!this.core?.pixi) return;
100
+ if (!this._eventsAttached) {
101
+ this._attachEvents();
102
+ }
103
+ this.updateAll();
104
+ }
105
+
106
+ /** Уничтожает слой: отписка от событий и очистка PIXI-объектов. */
107
+ destroy() {
108
+ this._detachEvents();
109
+ if (this.graphics) {
110
+ this.graphics.clear();
111
+ this.graphics.removeFromParent();
112
+ this.graphics.destroy();
113
+ this.graphics = null;
114
+ }
115
+ this._lastSegments = [];
116
+ this.eventBus = null;
117
+ this.core = null;
118
+ }
119
+
120
+ _attachEvents() {
121
+ if (this._eventsAttached) return;
122
+ const bindings = [
123
+ [Events.Object.Created, () => this.updateAll()],
124
+ [Events.Object.Deleted, () => this.updateAll()],
125
+ [Events.Object.Updated, () => this.updateAll()],
126
+ [Events.Object.StateChanged, () => this.updateAll()],
127
+ [Events.Tool.DragUpdate, () => this.updateAll()],
128
+ [Events.Tool.DragEnd, () => this.updateAll()],
129
+ [Events.Tool.ResizeUpdate, () => this.updateAll()],
130
+ [Events.Tool.ResizeEnd, () => this.updateAll()],
131
+ [Events.Tool.GroupDragUpdate, () => this.updateAll()],
132
+ [Events.Tool.GroupResizeUpdate, () => this.updateAll()],
133
+ [Events.Tool.RotateUpdate, () => this.updateAll()],
134
+ [Events.Tool.PanUpdate, () => this.updateAll()],
135
+ [Events.UI.ZoomPercent, () => this.updateAll()],
136
+ [Events.History.Changed, () => this.updateAll()],
137
+ [Events.Board.Loaded, () => this.updateAll()],
138
+ ];
139
+ bindings.forEach(([event, handler]) => {
140
+ this.eventBus.on(event, handler);
141
+ this.subscriptions.push([event, handler]);
142
+ });
143
+ this._eventsAttached = true;
144
+ }
145
+
146
+ _detachEvents() {
147
+ if (typeof this.eventBus?.off !== 'function') {
148
+ this.subscriptions = [];
149
+ this._eventsAttached = false;
150
+ return;
151
+ }
152
+ this.subscriptions.forEach(([event, handler]) => this.eventBus.off(event, handler));
153
+ this.subscriptions = [];
154
+ this._eventsAttached = false;
155
+ }
156
+
157
+ /** Перерисовывает все коннекторы из state. */
158
+ updateAll() {
159
+ const objects = asArray(this.core?.state?.state?.objects);
160
+ const connectors = objects.filter((o) => o?.type === 'connector');
161
+
162
+ if (connectors.length === 0) {
163
+ if (this.graphics) this.graphics.clear();
164
+ this._lastSegments = [];
165
+ return;
166
+ }
167
+
168
+ if (!this.graphics) {
169
+ this.graphics = new PIXI.Graphics();
170
+ this.graphics.name = 'connector-layer';
171
+ this.graphics.zIndex = 3;
172
+ const world = this.core?.pixi?.worldLayer || this.core?.pixi?.app?.stage;
173
+ world?.addChild?.(this.graphics);
174
+ }
175
+
176
+ const byId = new Map(objects.map((o) => [o.id, o]));
177
+ const g = this.graphics;
178
+ g.clear();
179
+ this._lastSegments = [];
180
+
181
+ connectors.forEach((connector) => {
182
+ const style = connector?.properties?.style ?? {};
183
+ const startTerm = connector?.properties?.start;
184
+ const endTerm = connector?.properties?.end;
185
+ if (!startTerm || !endTerm) return;
186
+
187
+ const startTarget = startTerm.boundId ? (byId.get(startTerm.boundId) ?? null) : null;
188
+ const endTarget = endTerm.boundId ? (byId.get(endTerm.boundId) ?? null) : null;
189
+
190
+ // Двухпроходное резолвание для корректной проекции isExact=false:
191
+ // проход 1 — грубые точки (без взаимной информации)
192
+ const roughStart = ConnectorBindingResolver.resolve(startTerm, startTarget, null);
193
+ const roughEnd = ConnectorBindingResolver.resolve(endTerm, endTarget, null);
194
+ // проход 2 — уточнение с кромочной проекцией
195
+ const start = ConnectorBindingResolver.resolve(startTerm, startTarget, roughEnd);
196
+ const end = ConnectorBindingResolver.resolve(endTerm, endTarget, start);
197
+
198
+ const sx = Math.round(start.x);
199
+ const sy = Math.round(start.y);
200
+ const ex = Math.round(end.x);
201
+ const ey = Math.round(end.y);
202
+
203
+ const color = style.stroke ?? 0x2563EB;
204
+ const width = style.width ?? 2;
205
+ const isDash = !!style.dash;
206
+ const head = style.head ?? { start: false, end: true };
207
+
208
+ try {
209
+ g.lineStyle({ width, color, alpha: 1, alignment: 0, cap: 'round', join: 'round' });
210
+ } catch (_) {
211
+ g.lineStyle(width, color, 1, 0);
212
+ }
213
+
214
+ if (isDash) {
215
+ drawDashedLine(g, sx, sy, ex, ey);
216
+ } else {
217
+ g.moveTo(sx, sy);
218
+ g.lineTo(ex, ey);
219
+ }
220
+
221
+ if (head?.end) drawArrow(g, { x: sx, y: sy }, { x: ex, y: ey }, color);
222
+ if (head?.start) drawArrow(g, { x: ex, y: ey }, { x: sx, y: sy }, color);
223
+
224
+ this._lastSegments.push({ id: connector.id, start: { x: sx, y: sy }, end: { x: ex, y: ey } });
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Возвращает id ближайшего коннектора, если worldPoint в пределах порога.
230
+ * Порог задан в экранных пикселях, пересчитывается в world через текущий scale.
231
+ *
232
+ * @param {{ x: number, y: number }} worldPoint
233
+ * @returns {string|null}
234
+ */
235
+ hitTest(worldPoint) {
236
+ if (this._lastSegments.length === 0) return null;
237
+ // worldLayer.scale.x = zoom; 1 screen px = 1/scale world units
238
+ const scale = this.core?.pixi?.worldLayer?.scale?.x ?? 1;
239
+ const worldThreshold = HIT_TEST_SCREEN_PX / scale;
240
+ let closest = null;
241
+ let minDist = worldThreshold;
242
+ for (const seg of this._lastSegments) {
243
+ const d = distanceToSegment(worldPoint, seg.start, seg.end);
244
+ if (d < minDist) {
245
+ minDist = d;
246
+ closest = seg.id;
247
+ }
248
+ }
249
+ return closest;
250
+ }
251
+ }
@@ -1223,6 +1223,8 @@ export class HandlesDomRenderer {
1223
1223
  transformOrigin: 'center center',
1224
1224
  transform: `rotate(${rotation}deg)`,
1225
1225
  });
1226
+ box.style.setProperty('--box-w', `${width}px`);
1227
+ box.style.setProperty('--box-h', `${height}px`);
1226
1228
  this.host.layer.appendChild(box);
1227
1229
  if (this.host._handlesSuppressed) {
1228
1230
  this.host.visible = true;
@@ -1252,12 +1254,12 @@ export class HandlesDomRenderer {
1252
1254
  h.style.cursor = cursor;
1253
1255
  });
1254
1256
  h.addEventListener('mouseleave', () => {
1255
- h.style.background = HANDLES_ACCENT_COLOR;
1257
+ h.style.background = '#ffffff';
1256
1258
  h.style.borderColor = HANDLES_ACCENT_COLOR;
1257
1259
  });
1258
1260
 
1259
1261
  if (!isNonResizableTarget) {
1260
- h.addEventListener('mousedown', (e) => this.host._onHandleDown(e, box));
1262
+ h.addEventListener('pointerdown', (e) => this.host._onHandleDown(e, box));
1261
1263
  }
1262
1264
 
1263
1265
  box.appendChild(h);
@@ -1286,7 +1288,7 @@ export class HandlesDomRenderer {
1286
1288
  });
1287
1289
  if (isNonResizableTarget) e.dataset.lockedHidden = '1';
1288
1290
  if (!isNonResizableTarget) {
1289
- e.addEventListener('mousedown', (evt) => this.host._onEdgeResizeDown(evt));
1291
+ e.addEventListener('pointerdown', (evt) => this.host._onEdgeResizeDown(evt));
1290
1292
  }
1291
1293
  box.appendChild(e);
1292
1294
  };
@@ -1341,7 +1343,7 @@ export class HandlesDomRenderer {
1341
1343
  svgEl.style.height = '100%';
1342
1344
  svgEl.style.display = 'block';
1343
1345
  }
1344
- rotateHandle.addEventListener('mousedown', (e) => this.host._onRotateHandleDown(e, box));
1346
+ rotateHandle.addEventListener('pointerdown', (e) => this.host._onRotateHandleDown(e, box));
1345
1347
  }
1346
1348
  box.appendChild(rotateHandle);
1347
1349
 
@@ -1626,7 +1628,7 @@ export class HandlesDomRenderer {
1626
1628
  btn.style.left = `${Math.round(left + width + centerOffset)}px`;
1627
1629
  }
1628
1630
  btn.style.top = `${centerY}px`;
1629
- btn.addEventListener('mousedown', (evt) => {
1631
+ btn.addEventListener('pointerdown', (evt) => {
1630
1632
  evt.preventDefault();
1631
1633
  evt.stopPropagation();
1632
1634
  });
@@ -1651,7 +1653,7 @@ export class HandlesDomRenderer {
1651
1653
  const centerOffset = edgeGap + buttonRadius;
1652
1654
  btn.style.left = `${centerX}px`;
1653
1655
  btn.style.top = `${Math.round(top + height + centerOffset)}px`;
1654
- btn.addEventListener('mousedown', (evt) => {
1656
+ btn.addEventListener('pointerdown', (evt) => {
1655
1657
  evt.preventDefault();
1656
1658
  evt.stopPropagation();
1657
1659
  });
@@ -1680,7 +1682,7 @@ export class HandlesDomRenderer {
1680
1682
  showInModelButton.innerHTML = `${REVIT_SHOW_IN_MODEL_ICON_SVG}<span>Показать в модели</span>`;
1681
1683
  showInModelButton.style.left = `${Math.round(left + width / 2)}px`;
1682
1684
  showInModelButton.style.top = `${Math.round(top - 34)}px`;
1683
- showInModelButton.addEventListener('mousedown', (evt) => {
1685
+ showInModelButton.addEventListener('pointerdown', (evt) => {
1684
1686
  evt.preventDefault();
1685
1687
  evt.stopPropagation();
1686
1688
  });
@@ -1702,6 +1704,8 @@ export class HandlesDomRenderer {
1702
1704
  repositionBoxChildren(box) {
1703
1705
  const width = parseFloat(box.style.width);
1704
1706
  const height = parseFloat(box.style.height);
1707
+ box.style.setProperty('--box-w', `${width}px`);
1708
+ box.style.setProperty('--box-h', `${height}px`);
1705
1709
  const cx = width / 2;
1706
1710
  const cy = height / 2;
1707
1711