@mhmttosun/dnd 0.0.0-stage → 1.1.1

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.
@@ -0,0 +1,632 @@
1
+ /**
2
+ * TransformEngine - Pointer Events API kullanarak tasarım modunda nesnelerin
3
+ * taşınmasını, 8 tutamaçla boyutlandırılmasını, döndürülmesini ve
4
+ * sahne üzeri çoklu seçim (marquee selection) işlemlerini yönetir.
5
+ */
6
+ class TransformEngine {
7
+ constructor(core, stageManager, objectManager, selectionManager, historyManager, stageEl, options = {}) {
8
+ this.core = core;
9
+ this.stageManager = stageManager;
10
+ this.objectManager = objectManager;
11
+ this.selectionManager = selectionManager;
12
+ this.historyManager = historyManager;
13
+ this.stage = stageEl;
14
+ this.signal = options.signal;
15
+
16
+ this.activeAction = null; // 'move' | 'resize' | 'rotate' | 'marquee'
17
+ this.activeHandle = null; // 'nw', 'n', 'se', vb.
18
+
19
+ this.startStagePos = { x: 0, y: 0 };
20
+ this.startClientPos = null;
21
+ this.hasMovedOrDragged = false;
22
+ this.isPointerDown = false;
23
+ this.startObjectsData = []; // Başlangıçtaki nesne verileri
24
+ this.activePointerId = null;
25
+
26
+ // Marquee seçim kutusu elementi
27
+ this.marqueeEl = null;
28
+
29
+ this.init();
30
+ }
31
+
32
+ init() {
33
+ const options = this.signal ? { signal: this.signal } : undefined;
34
+ this.stage.addEventListener('pointerdown', (e) => this.onPointerDown(e), options);
35
+ window.addEventListener('pointermove', (e) => this.onPointerMove(e), options);
36
+ window.addEventListener('pointerup', (e) => this.onPointerUp(e), options);
37
+ window.addEventListener('pointercancel', (e) => this.onPointerCancel(e), options);
38
+ }
39
+
40
+ onPointerDown(e) {
41
+ // Sadece tasarım modunda ve sol fare/dokunma ile çalışır
42
+ if (this.core.getMode() !== 'design') return;
43
+ if (e.button !== 0) return; // Sadece sol tık
44
+
45
+ // Eğer kullanıcı bir metin düzenleme alanına tıkladıysa, tarayıcının yerel metin imleci/seçimini engelleme
46
+ if (e.target.isContentEditable || e.target.closest('[contenteditable="true"]')) {
47
+ return;
48
+ }
49
+
50
+ // Eğer başka bir yerde metin düzenleniyorsa ve dışarı tıklandıysa, düzenlemeyi sonlandır
51
+ if (window.app && window.app.uiManager && window.app.uiManager.isEditingText) {
52
+ if (!e.target.closest('#contextual-mini-toolbar')) {
53
+ window.app.uiManager.finishInlineEditing();
54
+ }
55
+ }
56
+
57
+ const clientPos = { x: e.clientX, y: e.clientY };
58
+ const stagePos = this.stageManager.clientToStage(clientPos.x, clientPos.y);
59
+
60
+ this.startStagePos = stagePos;
61
+ this.startClientPos = clientPos;
62
+ this.activePointerId = e.pointerId;
63
+ this.hasMovedOrDragged = false;
64
+ this.isPointerDown = true;
65
+
66
+ // Sahneye basıldığı anda mini toolbar hemen gizlenir (basıldığı an değil bırakıldığı an görünür)
67
+ this.core.emit('stagePointerDown', { clientX: e.clientX, clientY: e.clientY });
68
+
69
+ // 1. Döndürme Tutamacı Kontrolü
70
+ const rotateHandle = e.target.closest('.rotate-handle');
71
+ if (rotateHandle) {
72
+ e.stopPropagation();
73
+ e.preventDefault();
74
+ this.startRotate(stagePos);
75
+ return;
76
+ }
77
+
78
+ // 2. Boyutlandırma Tutamacı Kontrolü
79
+ const resizeHandle = e.target.closest('.resize-handle');
80
+ if (resizeHandle) {
81
+ e.stopPropagation();
82
+ e.preventDefault();
83
+ const handleType = resizeHandle.getAttribute('data-handle');
84
+ this.startResize(handleType, stagePos);
85
+ return;
86
+ }
87
+
88
+ // 3. Seçim Kutusu Üzerine Tıklama (Taşıma Başlatma)
89
+ const unlockBtn = e.target.closest('.lock-pill-unlock-btn');
90
+ if (unlockBtn) {
91
+ e.stopPropagation();
92
+ e.preventDefault();
93
+ const primary = this.selectionManager.getPrimarySelected();
94
+ if (primary) {
95
+ this.objectManager.updateObject(primary.id, { locked: false });
96
+ this.selectionManager.updateSelectionBox();
97
+ this.core.emit('selectionChanged', {
98
+ selectedIds: Array.from(this.selectionManager.selectedIds),
99
+ primaryObject: this.objectManager.getObject(primary.id)
100
+ });
101
+ if (window.app && window.app.miniToolbar) {
102
+ window.app.miniToolbar.show();
103
+ }
104
+ }
105
+ return;
106
+ }
107
+
108
+ const selectionBorder = e.target.closest('.selection-border, .selection-box');
109
+ if (selectionBorder) {
110
+ e.stopPropagation();
111
+ e.preventDefault();
112
+ const primary = this.selectionManager.getPrimarySelected();
113
+ if (primary && primary.locked) {
114
+ return; // Kilitli nesne taşınamaz
115
+ }
116
+ this.startMove(stagePos);
117
+ return;
118
+ }
119
+
120
+ // 4. Sahne Nesnesine Tıklama
121
+ const objEl = e.target.closest('.stage-object');
122
+ if (objEl) {
123
+ e.stopPropagation();
124
+ e.preventDefault();
125
+
126
+ const objId = objEl.getAttribute('data-id');
127
+ const obj = this.objectManager.getObject(objId);
128
+
129
+ // Doğrudan kilit ikonuna tıklandıysa kilidi aç
130
+ if (e.target.closest('.lock-indicator, .lock-badge-btn')) {
131
+ this.objectManager.updateObject(objId, { locked: false });
132
+ this.selectionManager.select(objId, false);
133
+ return;
134
+ }
135
+
136
+ if (obj) {
137
+ if (e.shiftKey) {
138
+ this.selectionManager.toggleSelect(objId);
139
+ } else {
140
+ if (!this.selectionManager.selectedIds.has(objId)) {
141
+ this.selectionManager.select(objId, false);
142
+ }
143
+ }
144
+
145
+ // Sadece kilitli değilse taşımayı başlat
146
+ if (!obj.locked) {
147
+ this.startMove(stagePos);
148
+ }
149
+ }
150
+ return;
151
+ }
152
+
153
+ // 5. Boş Sahneye Tıklama: Marquee Seçim veya Seçimi Temizleme
154
+ if (!e.shiftKey) {
155
+ this.selectionManager.clearSelection();
156
+ }
157
+ this.startMarquee(stagePos, e.shiftKey);
158
+ }
159
+
160
+ startMove(stagePos) {
161
+ const selected = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
162
+ if (selected.length === 0) {
163
+ this.activeAction = null;
164
+ return;
165
+ }
166
+
167
+ this.activeAction = 'move';
168
+ this.startObjectsData = selected.map(o => ({
169
+ id: o.id,
170
+ x: o.x,
171
+ y: o.y,
172
+ width: o.width,
173
+ height: o.height
174
+ }));
175
+ }
176
+
177
+ startResize(handle, stagePos) {
178
+ this.activeAction = 'resize';
179
+ this.activeHandle = handle;
180
+ const selected = this.selectionManager.getSelectedObjects();
181
+ this.startObjectsData = selected.map(o => {
182
+ const item = {
183
+ id: o.id,
184
+ type: o.type,
185
+ x: o.x,
186
+ y: o.y,
187
+ width: o.width,
188
+ height: o.height,
189
+ rotation: o.rotation || 0
190
+ };
191
+ if (o.type === 'line') {
192
+ const rad = (item.rotation * Math.PI) / 180;
193
+ const cos = Math.cos(rad);
194
+ const sin = Math.sin(rad);
195
+ const cx = item.x + item.width / 2;
196
+ const cy = item.y + item.height / 2;
197
+ const hw = item.width / 2;
198
+ item.p0 = { x: cx - hw * cos, y: cy - hw * sin };
199
+ item.p1 = { x: cx + hw * cos, y: cy + hw * sin };
200
+ }
201
+ return item;
202
+ });
203
+ }
204
+
205
+ startRotate(stagePos) {
206
+ this.activeAction = 'rotate';
207
+ const primary = this.selectionManager.getPrimarySelected();
208
+ if (!primary) return;
209
+
210
+ this.startObjectsData = [{
211
+ id: primary.id,
212
+ x: primary.x,
213
+ y: primary.y,
214
+ width: primary.width,
215
+ height: primary.height,
216
+ rotation: primary.rotation || 0,
217
+ centerX: primary.x + primary.width / 2,
218
+ centerY: primary.y + primary.height / 2
219
+ }];
220
+ }
221
+
222
+ startMarquee(stagePos, shiftKey = false) {
223
+ this.activeAction = 'marquee';
224
+ this.isMarqueeShift = shiftKey;
225
+ this.initialMarqueeSelectedIds = new Set(this.selectionManager.selectedIds);
226
+
227
+ if (!this.marqueeEl) {
228
+ this.marqueeEl = document.createElement('div');
229
+ this.marqueeEl.className = 'stage-marquee-selection';
230
+ this.stage.appendChild(this.marqueeEl);
231
+ }
232
+ this.marqueeEl.style.display = 'block';
233
+ this.marqueeEl.style.left = `${stagePos.x}px`;
234
+ this.marqueeEl.style.top = `${stagePos.y}px`;
235
+ this.marqueeEl.style.width = '0px';
236
+ this.marqueeEl.style.height = '0px';
237
+ }
238
+
239
+ onPointerMove(e) {
240
+ if (!this.activeAction || this.activePointerId !== e.pointerId) return;
241
+
242
+ if (this.startClientPos) {
243
+ const clientDist = Math.hypot(e.clientX - this.startClientPos.x, e.clientY - this.startClientPos.y);
244
+ if (clientDist > 4) {
245
+ this.hasMovedOrDragged = true;
246
+ }
247
+ }
248
+
249
+ const currentStagePos = this.stageManager.clientToStage(e.clientX, e.clientY);
250
+ const dx = currentStagePos.x - this.startStagePos.x;
251
+ const dy = currentStagePos.y - this.startStagePos.y;
252
+
253
+ if (this.activeAction === 'move') {
254
+ this.handleMoveAction(dx, dy);
255
+ } else if (this.activeAction === 'resize') {
256
+ this.handleResizeAction(dx, dy, e.shiftKey);
257
+ } else if (this.activeAction === 'rotate') {
258
+ this.handleRotateAction(currentStagePos, e.shiftKey);
259
+ } else if (this.activeAction === 'marquee') {
260
+ this.handleMarqueeAction(currentStagePos);
261
+ }
262
+ }
263
+
264
+ handleMoveAction(dx, dy) {
265
+ const stageW = this.core.data.stage.width;
266
+ const stageH = this.core.data.stage.height;
267
+
268
+ // Kılavuz hizalama çizgileri hesaplama
269
+ let snappedDx = dx;
270
+ let snappedDy = dy;
271
+ const hGuides = [];
272
+ const vGuides = [];
273
+
274
+ // Birincil nesneye göre hizalama hesapla
275
+ if (this.startObjectsData.length > 0) {
276
+ const primaryStart = this.startObjectsData[0];
277
+ let targetX = primaryStart.x + dx;
278
+ let targetY = primaryStart.y + dy;
279
+
280
+ if (this.stageManager.snapToGrid) {
281
+ targetX = this.stageManager.snap(targetX);
282
+ targetY = this.stageManager.snap(targetY);
283
+ snappedDx = targetX - primaryStart.x;
284
+ snappedDy = targetY - primaryStart.y;
285
+ } else {
286
+ // Akıllı merkez ve kenar kılavuzları
287
+ const objMidX = targetX + primaryStart.width / 2;
288
+ const objMidY = targetY + primaryStart.height / 2;
289
+ const stageMidX = stageW / 2;
290
+ const stageMidY = stageH / 2;
291
+
292
+ const snapThreshold = 6;
293
+ if (Math.abs(objMidX - stageMidX) < snapThreshold) {
294
+ snappedDx = stageMidX - primaryStart.width / 2 - primaryStart.x;
295
+ vGuides.push(stageMidX);
296
+ }
297
+ if (Math.abs(objMidY - stageMidY) < snapThreshold) {
298
+ snappedDy = stageMidY - primaryStart.height / 2 - primaryStart.y;
299
+ hGuides.push(stageMidY);
300
+ }
301
+ }
302
+ }
303
+
304
+ // Sahne sınırları kısıtlaması: Seçili nesneler sahne dışına ASLA taşamaz
305
+ let minAllowedDx = -Infinity;
306
+ let maxAllowedDx = Infinity;
307
+ let minAllowedDy = -Infinity;
308
+ let maxAllowedDy = Infinity;
309
+
310
+ this.startObjectsData.forEach(item => {
311
+ minAllowedDx = Math.max(minAllowedDx, -item.x);
312
+ maxAllowedDx = Math.min(maxAllowedDx, stageW - item.width - item.x);
313
+ minAllowedDy = Math.max(minAllowedDy, -item.y);
314
+ maxAllowedDy = Math.min(maxAllowedDy, stageH - item.height - item.y);
315
+ });
316
+
317
+ if (minAllowedDx <= maxAllowedDx) {
318
+ snappedDx = Math.max(minAllowedDx, Math.min(maxAllowedDx, snappedDx));
319
+ }
320
+ if (minAllowedDy <= maxAllowedDy) {
321
+ snappedDy = Math.max(minAllowedDy, Math.min(maxAllowedDy, snappedDy));
322
+ }
323
+
324
+ this.stageManager.showGuidelines(hGuides, vGuides);
325
+
326
+ this.startObjectsData.forEach(item => {
327
+ const newX = Math.round(Math.max(0, Math.min(stageW - item.width, item.x + snappedDx)));
328
+ const newY = Math.round(Math.max(0, Math.min(stageH - item.height, item.y + snappedDy)));
329
+
330
+ this.objectManager.updateObject(item.id, { x: newX, y: newY }, false);
331
+ });
332
+
333
+ this.selectionManager.updateSelectionBox();
334
+ }
335
+
336
+ handleResizeAction(dx, dy, keepAspectRatio = false) {
337
+ if (this.startObjectsData.length === 0) return;
338
+ const start = this.startObjectsData[0];
339
+ const handle = this.activeHandle;
340
+ const stageW = this.core.data.stage.width;
341
+ const stageH = this.core.data.stage.height;
342
+
343
+ let newX = start.x;
344
+ let newY = start.y;
345
+ let newW = start.width;
346
+ let newH = start.height;
347
+
348
+ const isLine = start.type === 'line';
349
+ const minW = isLine ? 20 : 30;
350
+ const minH = isLine ? 6 : 30;
351
+
352
+ if (isLine && start.p0 && start.p1) {
353
+ const p0 = start.p0;
354
+ const p1 = start.p1;
355
+
356
+ if (handle.includes('e')) {
357
+ let p1x = Math.max(0, Math.min(stageW, p1.x + dx));
358
+ let p1y = Math.max(0, Math.min(stageH, p1.y + dy));
359
+ let vx = p1x - p0.x;
360
+ let vy = p1y - p0.y;
361
+ let len = Math.hypot(vx, vy);
362
+ if (len < minW) {
363
+ len = minW;
364
+ const curAngle = Math.atan2(vy, vx);
365
+ p1x = p0.x + len * Math.cos(curAngle);
366
+ p1y = p0.y + len * Math.sin(curAngle);
367
+ vx = p1x - p0.x;
368
+ vy = p1y - p0.y;
369
+ }
370
+ let deg = (Math.atan2(vy, vx) * 180) / Math.PI;
371
+ if (keepAspectRatio) {
372
+ deg = Math.round(deg / 15) * 15;
373
+ } else {
374
+ [-180, -90, 0, 90, 180].forEach(snapDeg => {
375
+ if (Math.abs(deg - snapDeg) < 3.5) deg = snapDeg;
376
+ });
377
+ }
378
+ const snapRad = (deg * Math.PI) / 180;
379
+ const finalP1 = {
380
+ x: p0.x + len * Math.cos(snapRad),
381
+ y: p0.y + len * Math.sin(snapRad)
382
+ };
383
+ const cx = (p0.x + finalP1.x) / 2;
384
+ const cy = (p0.y + finalP1.y) / 2;
385
+ const newW = Math.round(len);
386
+ const newH = start.height;
387
+ const newX = Math.round(cx - newW / 2);
388
+ const newY = Math.round(cy - newH / 2);
389
+ const newRot = Math.round((deg % 360 + 360) % 360);
390
+
391
+ this.objectManager.updateObject(start.id, {
392
+ x: newX,
393
+ y: newY,
394
+ width: newW,
395
+ height: newH,
396
+ rotation: newRot
397
+ }, false);
398
+ this.selectionManager.updateSelectionBox();
399
+ return;
400
+ } else if (handle.includes('w')) {
401
+ let p0x = Math.max(0, Math.min(stageW, p0.x + dx));
402
+ let p0y = Math.max(0, Math.min(stageH, p0.y + dy));
403
+ let vx = p1.x - p0x;
404
+ let vy = p1.y - p0y;
405
+ let len = Math.hypot(vx, vy);
406
+ if (len < minW) {
407
+ len = minW;
408
+ const curAngle = Math.atan2(vy, vx);
409
+ p0x = p1.x - len * Math.cos(curAngle);
410
+ p0y = p1.y - len * Math.sin(curAngle);
411
+ vx = p1.x - p0x;
412
+ vy = p1.y - p0y;
413
+ }
414
+ let deg = (Math.atan2(vy, vx) * 180) / Math.PI;
415
+ if (keepAspectRatio) {
416
+ deg = Math.round(deg / 15) * 15;
417
+ } else {
418
+ [-180, -90, 0, 90, 180].forEach(snapDeg => {
419
+ if (Math.abs(deg - snapDeg) < 3.5) deg = snapDeg;
420
+ });
421
+ }
422
+ const snapRad = (deg * Math.PI) / 180;
423
+ const finalP0 = {
424
+ x: p1.x - len * Math.cos(snapRad),
425
+ y: p1.y - len * Math.sin(snapRad)
426
+ };
427
+ const cx = (finalP0.x + p1.x) / 2;
428
+ const cy = (finalP0.y + p1.y) / 2;
429
+ const newW = Math.round(len);
430
+ const newH = start.height;
431
+ const newX = Math.round(cx - newW / 2);
432
+ const newY = Math.round(cy - newH / 2);
433
+ const newRot = Math.round((deg % 360 + 360) % 360);
434
+
435
+ this.objectManager.updateObject(start.id, {
436
+ x: newX,
437
+ y: newY,
438
+ width: newW,
439
+ height: newH,
440
+ rotation: newRot
441
+ }, false);
442
+ this.selectionManager.updateSelectionBox();
443
+ return;
444
+ }
445
+ }
446
+
447
+ // Tutamaç yönüne göre boyut hesaplama (sahne sınırları dahilinde)
448
+ if (handle.includes('e')) {
449
+ newW = Math.max(minW, Math.min(stageW - start.x, start.width + dx));
450
+ }
451
+ if (handle.includes('s')) {
452
+ newH = Math.max(minH, Math.min(stageH - start.y, start.height + dy));
453
+ }
454
+ if (handle.includes('w')) {
455
+ const maxDeltaLeft = start.x; // en fazla sahne soluna (0) kadar uzayabilir
456
+ const safeDx = Math.max(-maxDeltaLeft, dx);
457
+ const candidateW = start.width - safeDx;
458
+ if (candidateW >= minW) {
459
+ newW = candidateW;
460
+ newX = start.x + safeDx;
461
+ }
462
+ }
463
+ if (handle.includes('n')) {
464
+ const maxDeltaTop = start.y; // en fazla sahne üstüne (0) kadar uzayabilir
465
+ const safeDy = Math.max(-maxDeltaTop, dy);
466
+ const candidateH = start.height - safeDy;
467
+ if (candidateH >= minH) {
468
+ newH = candidateH;
469
+ newY = start.y + safeDy;
470
+ }
471
+ }
472
+
473
+ // En-boy oranı koruma (Shift basılıysa veya görsel ise)
474
+ if (keepAspectRatio && !isLine) {
475
+ const ratio = start.width / start.height;
476
+ if (['nw', 'ne', 'se', 'sw'].includes(handle)) {
477
+ newH = Math.max(minH, Math.min(stageH - newY, newW / ratio));
478
+ }
479
+ }
480
+
481
+ if (this.stageManager.snapToGrid) {
482
+ newW = this.stageManager.snap(newW);
483
+ newH = this.stageManager.snap(newH);
484
+ }
485
+
486
+ // Sahne dışına taşmayı kesin olarak engelle
487
+ newX = Math.max(0, Math.min(stageW - minW, newX));
488
+ newY = Math.max(0, Math.min(stageH - minH, newY));
489
+ newW = Math.max(minW, Math.min(stageW - newX, newW));
490
+ newH = Math.max(minH, Math.min(stageH - newY, newH));
491
+
492
+ this.objectManager.updateObject(start.id, {
493
+ x: Math.round(newX),
494
+ y: Math.round(newY),
495
+ width: Math.round(newW),
496
+ height: Math.round(newH)
497
+ }, false);
498
+
499
+ this.selectionManager.updateSelectionBox();
500
+ }
501
+
502
+ handleRotateAction(currentPos, snap15 = false) {
503
+ if (this.startObjectsData.length === 0) return;
504
+ const start = this.startObjectsData[0];
505
+
506
+ const dx = currentPos.x - start.centerX;
507
+ const dy = currentPos.y - start.centerY;
508
+
509
+ let deg = Math.atan2(dy, dx) * (180 / Math.PI) - 90;
510
+ deg = (deg % 360 + 360) % 360;
511
+
512
+ if (snap15) {
513
+ deg = Math.round(deg / 15) * 15;
514
+ }
515
+
516
+ this.objectManager.updateObject(start.id, {
517
+ rotation: Math.round(deg)
518
+ }, false);
519
+
520
+ this.selectionManager.updateSelectionBox();
521
+ }
522
+
523
+ handleMarqueeAction(currentPos) {
524
+ if (!this.marqueeEl) return;
525
+
526
+ const minX = Math.min(this.startStagePos.x, currentPos.x);
527
+ const minY = Math.min(this.startStagePos.y, currentPos.y);
528
+ const maxX = Math.max(this.startStagePos.x, currentPos.x);
529
+ const maxY = Math.max(this.startStagePos.y, currentPos.y);
530
+
531
+ const w = maxX - minX;
532
+ const h = maxY - minY;
533
+
534
+ this.marqueeEl.style.left = `${minX}px`;
535
+ this.marqueeEl.style.top = `${minY}px`;
536
+ this.marqueeEl.style.width = `${w}px`;
537
+ this.marqueeEl.style.height = `${h}px`;
538
+
539
+ // 1. Seçim alanı ile anlık olarak kesişen nesneleri tespit et
540
+ const objects = this.objectManager.getObjects();
541
+ const currentIntersectingIds = new Set();
542
+ const groupIdsToInclude = new Set();
543
+
544
+ objects.forEach(obj => {
545
+ if (obj.locked || obj.visible === false) return;
546
+ const intersects = (
547
+ obj.x < maxX &&
548
+ obj.x + obj.width > minX &&
549
+ obj.y < maxY &&
550
+ obj.y + obj.height > minY
551
+ );
552
+ if (intersects) {
553
+ currentIntersectingIds.add(obj.id);
554
+ if (obj.groupId) {
555
+ groupIdsToInclude.add(obj.groupId);
556
+ }
557
+ }
558
+ });
559
+
560
+ // Gruba ait nesnelerden biri seçim alanındaysa grubun tüm üyelerini dahil et
561
+ if (groupIdsToInclude.size > 0) {
562
+ objects.forEach(obj => {
563
+ if (obj.groupId && groupIdsToInclude.has(obj.groupId)) {
564
+ currentIntersectingIds.add(obj.id);
565
+ }
566
+ });
567
+ }
568
+
569
+ // 2. Seçili nesneler kümesini dinamik olarak güncelle:
570
+ // Seçim alanı değiştikçe kapsam dışına çıkan nesneler otomatik olarak seçimden çıkarılır
571
+ this.selectionManager.selectedIds.clear();
572
+
573
+ if (this.isMarqueeShift && this.initialMarqueeSelectedIds) {
574
+ this.initialMarqueeSelectedIds.forEach(id => this.selectionManager.selectedIds.add(id));
575
+ }
576
+
577
+ currentIntersectingIds.forEach(id => this.selectionManager.selectedIds.add(id));
578
+
579
+ // 3. Tuval üzerindeki seçim kutusu ve nesne vurgularını anlık canlı olarak yenile
580
+ this.selectionManager.updateSelectionBox();
581
+ }
582
+
583
+ onPointerUp(e) {
584
+ if (this.activePointerId !== e.pointerId) return;
585
+
586
+ this.stageManager.clearGuidelines();
587
+
588
+ if (this.activeAction === 'marquee' && this.marqueeEl) {
589
+ this.marqueeEl.style.display = 'none';
590
+ this.core.emit('selectionChanged', {
591
+ selectedIds: Array.from(this.selectionManager.selectedIds),
592
+ primaryObject: this.selectionManager.getPrimarySelected()
593
+ });
594
+ }
595
+
596
+ if (['move', 'resize', 'rotate'].includes(this.activeAction)) {
597
+ if (this.hasMovedOrDragged) {
598
+ // Değişiklik geçmişe kaydedilir
599
+ this.historyManager.pushState();
600
+ }
601
+ }
602
+
603
+ const prevAction = this.activeAction;
604
+ const wasDragged = this.hasMovedOrDragged;
605
+ const isMarquee = prevAction === 'marquee';
606
+ this.activeAction = null;
607
+ this.activeHandle = null;
608
+ this.activePointerId = null;
609
+ this.startObjectsData = [];
610
+ this.isPointerDown = false;
611
+ this.isMarqueeShift = false;
612
+ this.initialMarqueeSelectedIds = null;
613
+
614
+ // Bırakıldığı anda mini toolbar durumunu bildir:
615
+ // Tutulduğu anla bırakıldığı an sürükleme yapılmışsa mini toolbar görünmesin, yapılmamışsa görünsün
616
+ this.core.emit('stagePointerUp', {
617
+ hasDragged: wasDragged,
618
+ isMarquee: isMarquee,
619
+ action: prevAction,
620
+ clientX: e.clientX,
621
+ clientY: e.clientY
622
+ });
623
+ }
624
+
625
+ onPointerCancel(e) {
626
+ this.onPointerUp(e);
627
+ }
628
+ }
629
+
630
+ // Global namespace
631
+ window.DND = window.DND || {};
632
+ window.DND.TransformEngine = TransformEngine;