@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,1546 @@
1
+ /**
2
+ * MiniToolbar - Seçili nesnenin hemen yanında beliren bağlamsal mini araç çubuğu.
3
+ * Canva / Figma deneyiminde olduğu gibi; Sahne Rolü, Nesne Görünümü, Metin Biçimlendirme,
4
+ * Kopyalama, Silme, Katman/Kilit ve Çoklu Hizalama işlemlerini nesne üzerinde yönetir.
5
+ */
6
+ class MiniToolbar {
7
+ constructor(core, objectManager, selectionManager, layerManager, stageManager, root = document, options = {}) {
8
+ this.core = core;
9
+ this.objectManager = objectManager;
10
+ this.selectionManager = selectionManager;
11
+ this.layerManager = layerManager;
12
+ this.stageManager = stageManager;
13
+ this.root = root;
14
+ this.signal = options.signal;
15
+
16
+ this.toolbarEl = null;
17
+ this.activePopup = null; // 'role' | 'style' | 'text' | 'layers' | 'align' | null
18
+ this.activeStyleTab = 'fill'; // 'fill' | 'border' | 'width' | 'borderColor' | 'radius'
19
+ this.activeTextTab = 'font'; // 'font' | 'align' | 'color'
20
+ this.activeLineTab = 'type'; // Çizgi popup'ında aktif olan sekme ('type', 'width', 'color', 'arrows', 'align')
21
+ this.isNewlyOpened = false;
22
+ this.currentPlacementAbove = false;
23
+
24
+ this.init();
25
+ }
26
+
27
+ init() {
28
+ this.createDOM();
29
+ this.bindEvents();
30
+
31
+ // 1. Sahneye basıldığı anda mini toolbar hemen gizlenir (basıldığı an değil, bırakıldığı an görünür)
32
+ this.core.on('stagePointerDown', () => {
33
+ this.hide();
34
+ });
35
+
36
+ // 2. Bırakıldığı anda: Sürükleme yapılmamışsa ve seçili nesne varsa görünür olsun
37
+ this.core.on('stagePointerUp', (data) => {
38
+ const mode = this.core.getMode();
39
+ if (mode !== 'design') {
40
+ this.hide();
41
+ return;
42
+ }
43
+
44
+ // Tutulduğu anla bırakıldığı an nesne sürükleme/taşıma yapılmışsa mini toolbar görünmesin
45
+ // Ancak seçim alanı (selection area / marquee) bırakıldığında seçili nesneler için görünsün
46
+ const isMarqueeSelection = data && (data.isMarquee || data.action === 'marquee');
47
+ if (data && data.hasDragged && !isMarqueeSelection) {
48
+ this.hide();
49
+ return;
50
+ }
51
+
52
+ // Sürükleme yapılmamışsa (üzerine basılıp bırakıldığı anda) göster
53
+ const selectedObjs = this.selectionManager.getSelectedObjects();
54
+ if (selectedObjs.length > 0) {
55
+ if (selectedObjs.length === 1 && selectedObjs[0].locked) {
56
+ this.hide();
57
+ return;
58
+ }
59
+ if (!window.app || !window.app.uiManager || !window.app.uiManager.isEditingText) {
60
+ this.show();
61
+ }
62
+ } else {
63
+ this.hide();
64
+ }
65
+ });
66
+
67
+ // 3. Seçim değiştiğinde:
68
+ this.core.on('selectionChanged', () => {
69
+ const selectedObjs = this.selectionManager.getSelectedObjects();
70
+ if (selectedObjs.length === 0 || (selectedObjs.length === 1 && selectedObjs[0].locked)) {
71
+ this.hide();
72
+ return;
73
+ }
74
+ // Toolbar zaten açıksa içeriğini ve konumunu güncelle
75
+ if (this.isVisible()) {
76
+ this.render();
77
+ this.reposition();
78
+ }
79
+ });
80
+
81
+ this.core.on('objectUpdated', (data) => {
82
+ if (!this.isVisible()) return;
83
+ // Sadece nesnenin konumu veya boyutu değiştiğinde mini araç çubuğunu yeniden konumlandır
84
+ if (data && data.partialProps) {
85
+ const p = data.partialProps;
86
+ if (p.x !== undefined || p.y !== undefined || p.width !== undefined || p.height !== undefined) {
87
+ this.reposition();
88
+ }
89
+ } else {
90
+ this.reposition();
91
+ }
92
+ });
93
+
94
+ this.core.on('stageScaleChanged', () => {
95
+ if (this.isVisible()) {
96
+ this.reposition();
97
+ }
98
+ });
99
+
100
+ this.core.on('modeChanged', () => {
101
+ if (this.core.getMode() !== 'design') {
102
+ this.hide();
103
+ }
104
+ });
105
+
106
+ // Tuvalde boşluğa tıklandığında popupları kapat
107
+ window.addEventListener('pointerdown', (e) => {
108
+ if (this.toolbarEl && !this.toolbarEl.contains(e.target) && !e.target.closest('.resize-handle, .rotate-handle')) {
109
+ this.closePopup();
110
+ }
111
+ }, this.signal ? { signal: this.signal } : undefined);
112
+
113
+ window.addEventListener('scroll', () => {
114
+ if (this.isVisible()) this.reposition();
115
+ }, this.signal ? { capture: true, signal: this.signal } : true);
116
+ window.addEventListener('resize', () => {
117
+ if (this.isVisible()) this.reposition();
118
+ }, this.signal ? { signal: this.signal } : undefined);
119
+ }
120
+
121
+ createDOM() {
122
+ this.toolbarEl = document.createElement('div');
123
+ this.toolbarEl.id = 'contextual-mini-toolbar';
124
+ this.toolbarEl.className = 'mini-toolbar-container';
125
+ this.toolbarEl.style.display = 'none';
126
+
127
+ (this.root.body || this.root).appendChild(this.toolbarEl);
128
+ }
129
+
130
+ bindEvents() {
131
+ // Toolbar içine basıldığında sahne olaylarının tetiklenmesini engelle
132
+ this.toolbarEl.addEventListener('pointerdown', (e) => {
133
+ e.stopPropagation();
134
+ });
135
+
136
+ // Toolbar içindeki tıklamalar
137
+ this.toolbarEl.addEventListener('click', (e) => {
138
+ e.stopPropagation();
139
+
140
+ const btn = e.target.closest('[data-action]');
141
+ if (!btn) return;
142
+
143
+ const action = btn.getAttribute('data-action');
144
+ this.handleAction(action, btn);
145
+ });
146
+ }
147
+
148
+ handleAction(action, btn) {
149
+ const selectedObjs = this.selectionManager.getSelectedObjects();
150
+ if (selectedObjs.length === 0) return;
151
+
152
+ if (action === 'toggle-line-popup') {
153
+ this.linePopupClosedByUser = (this.activePopup === 'line');
154
+ this.togglePopup('line');
155
+ } else if (action === 'toggle-role-popup') {
156
+ this.togglePopup('role');
157
+ } else if (action === 'toggle-style-popup') {
158
+ this.togglePopup('style');
159
+ } else if (action === 'toggle-text-popup') {
160
+ this.togglePopup('text');
161
+ } else if (action === 'toggle-layer-popup') {
162
+ this.togglePopup('layers');
163
+ } else if (action === 'toggle-align-popup') {
164
+ this.togglePopup('align');
165
+ } else if (action === 'toggle-group') {
166
+ const selected = this.selectionManager.getSelectedObjects();
167
+ const hasGroup = selected.some(o => o.groupId);
168
+ if (hasGroup) {
169
+ this.selectionManager.ungroupSelected();
170
+ } else {
171
+ this.selectionManager.groupSelected();
172
+ }
173
+ this.closePopup();
174
+ this.render();
175
+ } else if (action === 'duplicate') {
176
+ this.selectionManager.duplicateSelected();
177
+ this.closePopup();
178
+ } else if (action === 'delete') {
179
+ this.selectionManager.deleteSelected();
180
+ this.closePopup();
181
+ }
182
+ }
183
+
184
+ togglePopup(popupName) {
185
+ if (this.activePopup === popupName) {
186
+ this.activePopup = null;
187
+ this.isNewlyOpened = false;
188
+ } else {
189
+ this.activePopup = popupName;
190
+ this.isNewlyOpened = true;
191
+ }
192
+ this.render();
193
+ if (this.isNewlyOpened) {
194
+ setTimeout(() => {
195
+ const card = this.toolbarEl.querySelector('.mt-popup-card');
196
+ if (card) card.classList.remove('is-opening');
197
+ this.isNewlyOpened = false;
198
+ }, 250);
199
+ }
200
+ }
201
+
202
+ closePopup() {
203
+ if (this.activePopup) {
204
+ this.activePopup = null;
205
+ this.isNewlyOpened = false;
206
+ this.render();
207
+ }
208
+ }
209
+
210
+ isVisible() {
211
+ return this.toolbarEl && this.toolbarEl.style.display !== 'none';
212
+ }
213
+
214
+ show() {
215
+ if (!this.toolbarEl) return;
216
+ const mode = this.core.getMode();
217
+ const selectedObjs = this.selectionManager.getSelectedObjects();
218
+
219
+ if (mode !== 'design' || selectedObjs.length === 0 || (selectedObjs.length === 1 && selectedObjs[0].locked)) {
220
+ this.hide();
221
+ return;
222
+ }
223
+
224
+ this.toolbarEl.style.display = 'flex';
225
+ this.render();
226
+ this.reposition();
227
+ }
228
+
229
+ hide() {
230
+ if (!this.toolbarEl) return;
231
+ this.toolbarEl.style.display = 'none';
232
+ this.activePopup = null;
233
+ this.isNewlyOpened = false;
234
+ this.linePopupClosedByUser = false;
235
+ }
236
+
237
+ updatePositionAndVisibility() {
238
+ if (this.isVisible()) {
239
+ const mode = this.core.getMode();
240
+ const selectedObjs = this.selectionManager.getSelectedObjects();
241
+
242
+ if (mode !== 'design' || selectedObjs.length === 0 || (selectedObjs.length === 1 && selectedObjs[0].locked)) {
243
+ this.hide();
244
+ } else {
245
+ this.render();
246
+ this.reposition();
247
+ }
248
+ }
249
+ }
250
+
251
+ reposition() {
252
+ if (!this.isVisible()) return;
253
+ const selectedObjs = this.selectionManager.getSelectedObjects();
254
+ if (selectedObjs.length === 0 || (selectedObjs.length === 1 && selectedObjs[0].locked)) {
255
+ this.hide();
256
+ return;
257
+ }
258
+
259
+ // Seçili nesnelerin sahne koordinatlarında AABB sınırları
260
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
261
+ selectedObjs.forEach(o => {
262
+ minX = Math.min(minX, o.x);
263
+ minY = Math.min(minY, o.y);
264
+ maxX = Math.max(maxX, o.x + o.width);
265
+ maxY = Math.max(maxY, o.y + o.height);
266
+ });
267
+
268
+ const midStageX = (minX + maxX) / 2;
269
+ const clientTopObj = this.stageManager.stageToClient(midStageX, minY);
270
+ const clientBottomObj = this.stageManager.stageToClient(midStageX, maxY);
271
+ const clientCenterObj = this.stageManager.stageToClient(midStageX, (minY + maxY) / 2);
272
+
273
+ // Üst kısımdaki sabit başlık ve ikincil araç çubuğu (58px + 48px = 106px)
274
+ const headerOffset = 112;
275
+ const screenBottom = window.innerHeight - 15;
276
+ const screenLeft = 16;
277
+ const screenRight = window.innerWidth - 16;
278
+
279
+ // Nesnenin altındaki ve üstündeki kullanılabilir ekran dikey boşluğu
280
+ const spaceBelow = screenBottom - (clientBottomObj.clientY + 12);
281
+ const spaceAbove = (clientTopObj.clientY - 12) - headerOffset;
282
+
283
+ // Toolbar ve menü için gereken dikey boşluk (açıkken gerçek yükseklik, kapalıyken potansiyel popup için ~240px)
284
+ const tbRect = this.toolbarEl.getBoundingClientRect();
285
+ const currentHeight = tbRect.height || (this.activePopup ? 260 : 44);
286
+ const neededHeight = this.activePopup ? currentHeight : 240;
287
+
288
+ let isAbove = false;
289
+ // Eğer alt tarafta yeterli yer varsa altta aç, yetersizse ve üstte yer varsa veya üst taraf daha genişse üstte aç
290
+ if (spaceBelow >= neededHeight) {
291
+ isAbove = false;
292
+ } else if (spaceAbove >= neededHeight) {
293
+ isAbove = true;
294
+ } else {
295
+ isAbove = spaceAbove > spaceBelow;
296
+ }
297
+
298
+ this.toolbarEl.classList.toggle('placement-above', isAbove);
299
+ this.toolbarEl.classList.toggle('placement-below', !isAbove);
300
+
301
+ // Sınıf geçişinden sonra güncel boyutları oku
302
+ const updatedRect = this.toolbarEl.getBoundingClientRect();
303
+ const finalTbHeight = updatedRect.height || currentHeight;
304
+ const finalTbWidth = updatedRect.width || 280;
305
+
306
+ let top = 0;
307
+ if (isAbove) {
308
+ // Nesnenin üst kenarının 10px yukarısına yerleştir
309
+ top = clientTopObj.clientY - 10 - finalTbHeight;
310
+ top = Math.max(headerOffset, top);
311
+ if (top + finalTbHeight > screenBottom) {
312
+ top = Math.max(headerOffset, screenBottom - finalTbHeight);
313
+ }
314
+ } else {
315
+ // Nesnenin alt kenarının 10px aşağısına yerleştir
316
+ top = clientBottomObj.clientY + 10;
317
+ if (top + finalTbHeight > screenBottom) {
318
+ top = Math.max(headerOffset, screenBottom - finalTbHeight);
319
+ }
320
+ }
321
+
322
+ // Yatay konum (Nesnenin ortası, ekran sınırlarına taşmayacak şekilde)
323
+ let left = clientCenterObj.clientX - finalTbWidth / 2;
324
+ left = Math.max(screenLeft, Math.min(screenRight - finalTbWidth, left));
325
+
326
+ this.toolbarEl.style.left = `${Math.round(left)}px`;
327
+ this.toolbarEl.style.top = `${Math.round(top)}px`;
328
+
329
+ // Açık popup varsa taşma ve kaydırma çubuğu oluşmasını önle
330
+ const popupEl = this.toolbarEl.querySelector('.mt-popup-card');
331
+ if (popupEl) {
332
+ popupEl.style.maxHeight = 'none';
333
+ popupEl.style.overflow = 'visible';
334
+ }
335
+ }
336
+
337
+ render() {
338
+ const selectedObjs = this.selectionManager.getSelectedObjects();
339
+ const primary = this.selectionManager.getPrimarySelected();
340
+ if (selectedObjs.length === 0 || !primary || (selectedObjs.length === 1 && primary.locked)) {
341
+ this.toolbarEl.style.display = 'none';
342
+ return;
343
+ }
344
+ const isMulti = selectedObjs.length > 1;
345
+ const hasGroup = selectedObjs.some(o => o.groupId);
346
+ const canHaveText = selectedObjs.some(o => o.type !== 'line');
347
+ const allLines = selectedObjs.length > 0 && selectedObjs.every(o => o.type === 'line');
348
+
349
+ if (!allLines && this.activePopup === 'line') {
350
+ this.activePopup = null;
351
+ }
352
+
353
+ if (allLines) {
354
+ if (this.activePopup === null && !this.linePopupClosedByUser) {
355
+ this.activePopup = 'line';
356
+ }
357
+ let html = `
358
+ <div class="mini-toolbar-pill line-mini-toolbar-pill">
359
+ <button type="button" class="mt-btn ${this.activePopup === 'line' ? 'active' : ''}" data-action="toggle-line-popup" title="Çizgi Ayarları">
360
+ <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 18l6-6 4 4 6-8"/></svg>
361
+ </button>
362
+
363
+ <div class="mt-divider"></div>
364
+
365
+ <!-- Çoğalt Butonu -->
366
+ <button type="button" class="mt-btn" data-action="duplicate" title="Çoğalt (Ctrl+D)">
367
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
368
+ </button>
369
+
370
+ <!-- Sil Butonu -->
371
+ <button type="button" class="mt-btn mt-btn-danger" data-action="delete" title="Sil (Delete)">
372
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
373
+ </button>
374
+ </div>
375
+
376
+ <!-- Popuplar -->
377
+ ${this.renderActivePopup(primary, isMulti, hasGroup)}
378
+ `;
379
+
380
+ this.toolbarEl.innerHTML = html;
381
+ this.bindPopupInteractions(primary);
382
+ this.reposition();
383
+ return;
384
+ }
385
+
386
+ if (this.activePopup === 'text' && !canHaveText) {
387
+ this.activePopup = null;
388
+ }
389
+
390
+ let html = `
391
+ <div class="mini-toolbar-pill ${isMulti ? 'is-multi' : ''}">
392
+ ${(isMulti || hasGroup) ? `
393
+ <button type="button" class="mt-btn ${this.activePopup === 'align' ? 'active' : ''}" data-action="toggle-align-popup" title="Hizalama">
394
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><path d="M12 7v10M7 12h10"/></svg>
395
+ </button>
396
+ ` : ''}
397
+
398
+ ${(isMulti || hasGroup) ? `
399
+ <button type="button" class="mt-btn ${hasGroup ? 'active' : ''}" data-action="toggle-group" title="${hasGroup ? 'Grubu Çöz (Ctrl+Shift+G)' : 'Grupla (Ctrl+G)'}">
400
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" stroke-dasharray="3 3"/><circle cx="9" cy="9" r="2"/><circle cx="15" cy="15" r="2"/></svg>
401
+ </button>
402
+ ` : ''}
403
+
404
+ <!-- Sahne Rolü Butonu -->
405
+ <button type="button" class="mt-btn ${this.activePopup === 'role' ? 'active' : ''}" data-action="toggle-role-popup" title="Sahne Rolü">
406
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="6" height="6" rx="1"/><rect x="15" y="3" width="6" height="6" rx="1"/><rect x="15" y="15" width="6" height="6" rx="1"/><rect x="3" y="15" width="6" height="6" rx="1"/><path d="M9 6h6M6 9v6M18 9v6M9 18h6"/></svg>
407
+ </button>
408
+
409
+ <!-- Görünüm / Şekil Butonu -->
410
+ <button type="button" class="mt-btn ${this.activePopup === 'style' ? 'active' : ''}" data-action="toggle-style-popup" title="Nesne Görünümü">
411
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l4 7H8l4-7z"/><rect x="3" y="14" width="7" height="7"/><circle cx="17.5" cy="17.5" r="3.5"/></svg>
412
+ </button>
413
+
414
+ <!-- Metin Butonu (Çizgide metin olmayacağı için gizlenir) -->
415
+ ${canHaveText ? `
416
+ <button type="button" class="mt-btn ${this.activePopup === 'text' ? 'active' : ''}" data-action="toggle-text-popup" title="Metin Biçimlendirme">
417
+ <span class="mt-text-icon">Aa</span>
418
+ </button>
419
+ ` : ''}
420
+
421
+ <div class="mt-divider"></div>
422
+
423
+ <!-- Çoğalt Butonu -->
424
+ <button type="button" class="mt-btn" data-action="duplicate" title="Çoğalt (Ctrl+D)">
425
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
426
+ </button>
427
+
428
+ <!-- Sil Butonu -->
429
+ <button type="button" class="mt-btn mt-btn-danger" data-action="delete" title="Sil (Delete)">
430
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
431
+ </button>
432
+
433
+ <!-- Katmanlar & Kilit Butonu -->
434
+ <button type="button" class="mt-btn ${this.activePopup === 'layers' ? 'active' : ''}" data-action="toggle-layer-popup" title="Katman Sırası ve Kilit">
435
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg>
436
+ </button>
437
+ </div>
438
+
439
+ <!-- Popuplar -->
440
+ ${this.renderActivePopup(primary, isMulti, hasGroup)}
441
+ `;
442
+
443
+ this.toolbarEl.innerHTML = html;
444
+ this.bindPopupInteractions(primary);
445
+ this.reposition();
446
+ }
447
+
448
+ renderActivePopup(primary, isMulti, hasGroup = false) {
449
+ if (!this.activePopup || !primary) return '';
450
+ const openClass = this.isNewlyOpened ? 'is-opening' : '';
451
+
452
+ if (this.activePopup === 'line') {
453
+ const bStyle = primary.borderStyle || 'solid';
454
+ const bWidth = primary.borderWidth || 3;
455
+ const bColor = primary.borderColor || '#1e293b';
456
+ const rot = Math.round(primary.rotation || 0) % 360;
457
+ const normalizedRot = (rot + 360) % 360;
458
+ const isHoriz = normalizedRot === 0 || normalizedRot === 180;
459
+ const isVert = normalizedRot === 90 || normalizedRot === 270;
460
+ const activeTab = this.activeLineTab || 'type';
461
+
462
+ const arrowMode = (primary.arrowStart && primary.arrowEnd) ? 'both' : (primary.arrowStart ? 'start' : (primary.arrowEnd ? 'end' : 'none'));
463
+
464
+ const paletteColors = [
465
+ '#1e293b', '#475569', '#94a3b8', '#ef4444',
466
+ '#f97316', '#f59e0b', '#10b981', '#06b6d4',
467
+ '#3b82f6', '#6366f1', '#8b5cf6', '#ec4899'
468
+ ];
469
+ const paletteNames = ['Koyu gri', 'Gri', 'Açık gri', 'Kırmızı', 'Turuncu', 'Sarı', 'Yeşil', 'Turkuaz', 'Mavi', 'İndigo', 'Mor', 'Pembe'];
470
+
471
+ return `
472
+ <div class="mt-popup-card mt-line-popup ${openClass}" id="mt-line-popup">
473
+ <div class="mt-line-title">Çizgi</div>
474
+
475
+ <!-- Üst Menü Sekmeleri -->
476
+ <div class="mt-line-tabs-bar" role="group" aria-label="Çizgi ayarları">
477
+ <!-- 1. Çizgi Tipi -->
478
+ <button type="button" class="mt-line-tab-btn ${activeTab === 'type' ? 'active' : ''}" data-line-tab="type" title="Çizgi tipi">
479
+ <svg width="20" height="12" viewBox="0 0 20 12"><line x1="2" y1="6" x2="18" y2="6" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
480
+ </button>
481
+
482
+ <!-- 2. Çizgi Kalınlığı -->
483
+ <button type="button" class="mt-line-tab-btn ${activeTab === 'width' ? 'active' : ''}" data-line-tab="width" title="Çizgi kalınlığı">
484
+ <span class="mt-line-tab-width-icon">
485
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="2" y1="4" x2="10" y2="4"/><line x1="2" y1="8" x2="10" y2="8"/></svg>
486
+ </span>
487
+ <span class="mt-line-tab-width-text" id="mt-tab-width-val">${bWidth}</span>
488
+ </button>
489
+
490
+ <!-- 3. Çizgi Rengi -->
491
+ <button type="button" class="mt-line-tab-btn ${activeTab === 'color' ? 'active' : ''}" data-line-tab="color" title="Çizgi rengi">
492
+ <span class="mt-line-tab-swatch" id="mt-tab-color-swatch" style="background-color: ${bColor};"></span>
493
+ </button>
494
+
495
+ <!-- 4. Ok Uçları -->
496
+ <button type="button" class="mt-line-tab-btn ${activeTab === 'arrows' ? 'active' : ''}" data-line-tab="arrows" title="Ok uçları">
497
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="9 8 5 12 9 16"/><polyline points="15 8 19 12 15 16"/></svg>
498
+ </button>
499
+
500
+ <!-- 5. Yön -->
501
+ <button type="button" class="mt-line-tab-btn ${activeTab === 'align' ? 'active' : ''}" data-line-tab="align" title="Çizgi yönü">
502
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 18h16M6 6v14M16 14l4 4-4 4M2 10l4-4 4 4"/></svg>
503
+ </button>
504
+ </div>
505
+
506
+ <!-- Alt Kart Panelleri -->
507
+
508
+ <!-- Panel 1: Çizgi tipi -->
509
+ <div class="mt-line-panel" data-panel="type" style="display: ${activeTab === 'type' ? 'flex' : 'none'};">
510
+ <div class="mt-line-panel-title">Çizgi tipi</div>
511
+ <div class="mt-line-type-row">
512
+ <button type="button" class="mt-line-type-btn ${bStyle === 'solid' ? 'active' : ''}" data-line-style="solid" title="Düz Çizgi">
513
+ <svg width="28" height="12" viewBox="0 0 28 12"><line x1="2" y1="6" x2="26" y2="6" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>
514
+ </button>
515
+ <button type="button" class="mt-line-type-btn ${bStyle === 'dashed' ? 'active' : ''}" data-line-style="dashed" title="Kesikli Çizgi">
516
+ <svg width="28" height="12" viewBox="0 0 28 12"><line x1="2" y1="6" x2="26" y2="6" stroke="currentColor" stroke-width="3" stroke-dasharray="6 4" stroke-linecap="round"/></svg>
517
+ </button>
518
+ <button type="button" class="mt-line-type-btn ${bStyle === 'dotted' ? 'active' : ''}" data-line-style="dotted" title="Noktalı Çizgi">
519
+ <svg width="28" height="12" viewBox="0 0 28 12"><line x1="2" y1="6" x2="26" y2="6" stroke="currentColor" stroke-width="3" stroke-dasharray="1 5" stroke-linecap="round"/></svg>
520
+ </button>
521
+ </div>
522
+ </div>
523
+
524
+ <!-- Panel 2: Kalınlık -->
525
+ <div class="mt-line-panel" data-panel="width" style="display: ${activeTab === 'width' ? 'flex' : 'none'};">
526
+ <div class="mt-line-panel-title">Kalınlık <span class="mt-line-unit">px</span></div>
527
+ <div class="mt-line-chips-row">
528
+ ${[1, 2, 3, 4, 6, 8].map(w => `
529
+ <button type="button" class="mt-line-chip-btn ${bWidth === w ? 'active' : ''}" data-line-width-preset="${w}" aria-label="${w} piksel">
530
+ <span class="mt-line-width-sample" style="--line-width: ${w}px;" aria-hidden="true"></span>
531
+ <span>${w}</span>
532
+ </button>
533
+ `).join('')}
534
+ </div>
535
+ </div>
536
+
537
+ <!-- Panel 3: Renk -->
538
+ <div class="mt-line-panel" data-panel="color" style="display: ${activeTab === 'color' ? 'flex' : 'none'};">
539
+ <div class="mt-line-panel-title">Çizgi rengi</div>
540
+ <div class="mt-line-color-grid">
541
+ ${paletteColors.map((c, i) => `
542
+ <button type="button" class="mt-line-color-swatch ${bColor.toLowerCase() === c.toLowerCase() ? 'is-active' : ''}" data-line-color="${c}" style="--line-color: ${c}; --line-check-color: ${['#94a3b8', '#f59e0b', '#10b981', '#06b6d4'].includes(c) ? '#0f172a' : '#ffffff'};" title="${paletteNames[i]}" aria-label="${paletteNames[i]}">
543
+ <span class="mt-line-color-dot" aria-hidden="true"><span class="mt-line-color-check">✓</span></span>
544
+ </button>
545
+ `).join('')}
546
+ </div>
547
+ </div>
548
+
549
+ <!-- Panel 4: Ok uçları -->
550
+ <div class="mt-line-panel" data-panel="arrows" style="display: ${activeTab === 'arrows' ? 'flex' : 'none'};">
551
+ <div class="mt-line-panel-title">Ok uçları</div>
552
+ <div class="mt-line-arrow-grid">
553
+ <button type="button" class="mt-line-arrow-btn ${arrowMode === 'none' ? 'active' : ''}" data-line-arrow="none" title="Uçsuz Düz">
554
+ <svg width="24" height="14" viewBox="0 0 24 14"><line x1="2" y1="7" x2="22" y2="7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>
555
+ <span>Uçsuz</span>
556
+ </button>
557
+ <button type="button" class="mt-line-arrow-btn ${arrowMode === 'start' ? 'active' : ''}" data-line-arrow="start" title="Başlangıç Oku">
558
+ <svg width="24" height="14" viewBox="0 0 24 14"><line x1="6" y1="7" x2="22" y2="7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/><polygon points="6,3 0,7 6,11" fill="currentColor"/></svg>
559
+ <span>Başlangıç</span>
560
+ </button>
561
+ <button type="button" class="mt-line-arrow-btn ${arrowMode === 'end' ? 'active' : ''}" data-line-arrow="end" title="Bitiş Oku">
562
+ <svg width="24" height="14" viewBox="0 0 24 14"><line x1="2" y1="7" x2="18" y2="7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/><polygon points="18,3 24,7 18,11" fill="currentColor"/></svg>
563
+ <span>Bitiş</span>
564
+ </button>
565
+ <button type="button" class="mt-line-arrow-btn ${arrowMode === 'both' ? 'active' : ''}" data-line-arrow="both" title="Çift Yönlü Ok">
566
+ <svg width="24" height="14" viewBox="0 0 24 14"><line x1="6" y1="7" x2="18" y2="7" stroke="currentColor" stroke-width="2.5"/><polygon points="6,3 0,7 6,11" fill="currentColor"/><polygon points="18,3 24,7 18,11" fill="currentColor"/></svg>
567
+ <span>Çift yön</span>
568
+ </button>
569
+ </div>
570
+ </div>
571
+
572
+ <!-- Panel 5: Yön -->
573
+ <div class="mt-line-panel" data-panel="align" style="display: ${activeTab === 'align' ? 'flex' : 'none'};">
574
+ <div class="mt-line-panel-title">Yön</div>
575
+ <div class="mt-line-align-grid">
576
+ <button type="button" class="mt-line-align-btn ${isHoriz ? 'active' : ''}" data-line-align="horizontal" title="Yatay Yap">
577
+ <svg width="18" height="14" viewBox="0 0 18 14"><line x1="1" y1="7" x2="17" y2="7" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>
578
+ <span>Yatay</span>
579
+ </button>
580
+ <button type="button" class="mt-line-align-btn ${isVert ? 'active' : ''}" data-line-align="vertical" title="Dikey Yap">
581
+ <svg width="14" height="18" viewBox="0 0 14 18"><line x1="7" y1="1" x2="7" y2="17" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>
582
+ <span>Dikey</span>
583
+ </button>
584
+ </div>
585
+ </div>
586
+ </div>
587
+ `;
588
+ }
589
+
590
+ if (this.activePopup === 'role') {
591
+ const r = primary.role || 'decorative';
592
+ return `
593
+ <div class="mt-popup-card mt-role-popup ${openClass}" id="mt-role-popup">
594
+ <div class="mt-role-section">
595
+ <span class="mt-role-section-label">Sahne rolü</span>
596
+ <div class="mt-role-grid" role="radiogroup" aria-label="Sahne rolü">
597
+ <button type="button" role="radio" aria-checked="${r === 'draggable'}" class="mt-role-btn ${r === 'draggable' ? 'active' : ''}" data-role-val="draggable" title="Taşınan nesne">
598
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M2 12h20"/><path d="m8 6 4-4 4 4M8 18l4 4 4-4M6 8l-4 4 4 4M18 8l4 4-4 4"/></svg>
599
+ <span>Taşınan</span>
600
+ </button>
601
+ <button type="button" role="radio" aria-checked="${['target', 'category', 'sequence_slot'].includes(r)}" class="mt-role-btn ${['target', 'category', 'sequence_slot'].includes(r) ? 'active' : ''}" data-role-val="target" title="Hedef nesne">
602
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="4"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>
603
+ <span>Hedef</span>
604
+ </button>
605
+ <button type="button" role="radio" aria-checked="${r === 'decorative'}" class="mt-role-btn ${r === 'decorative' ? 'active' : ''}" data-role-val="decorative" title="Nötr nesne">
606
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M8 12h8"/></svg>
607
+ <span>Nötr</span>
608
+ </button>
609
+ </div>
610
+ </div>
611
+ </div>
612
+ `;
613
+ }
614
+
615
+ if (this.activePopup === 'style') {
616
+ const isLine = primary.type === 'line';
617
+ const bStyle = primary.borderStyle || 'solid';
618
+ const bWidth = primary.borderWidth ?? 2;
619
+ const bRadius = primary.borderRadius ?? 8;
620
+ const tab = isLine && ['fill', 'radius'].includes(this.activeStyleTab) ? 'border' : this.activeStyleTab;
621
+ const colors = [
622
+ ['transparent', 'Şeffaf'], ['#ffffff', 'Beyaz'], ['#cbd5e1', 'Açık gri'],
623
+ ['#64748b', 'Gri'], ['#1e293b', 'Koyu gri'], ['#ef4444', 'Kırmızı'],
624
+ ['#f97316', 'Turuncu'], ['#eab308', 'Sarı'], ['#22c55e', 'Yeşil'],
625
+ ['#14b8a6', 'Turkuaz'], ['#0ea5e9', 'Açık mavi'], ['#2563eb', 'Mavi'],
626
+ ['#8b5cf6', 'Mor'], ['#ec4899', 'Pembe']
627
+ ];
628
+ const palette = (prop) => {
629
+ const selectedColor = (primary[prop] || 'transparent').toLowerCase();
630
+ return `<div class="mt-style-color-grid" role="group" aria-label="${prop === 'backgroundColor' ? 'Dolgu rengi' : 'Kenarlık rengi'}">
631
+ ${colors.map(([color, name]) => `
632
+ <button type="button" class="mt-style-color-swatch ${selectedColor === color ? 'is-active' : ''}" data-style-color="${color}" data-style-color-prop="${prop}" title="${name}" aria-label="${name}" style="--line-color: ${color}; --line-check-color: ${['transparent', '#ffffff', '#cbd5e1', '#eab308', '#22c55e', '#14b8a6', '#0ea5e9'].includes(color) ? '#0f172a' : '#ffffff'};">
633
+ <span class="mt-style-color-dot ${color === 'transparent' ? 'is-transparent' : ''}" aria-hidden="true"><span class="mt-style-color-check">✓</span></span>
634
+ </button>`).join('')}
635
+ </div>`;
636
+ };
637
+
638
+ return `
639
+ <div class="mt-popup-card mt-style-popup ${openClass}" id="mt-style-popup">
640
+ <div class="mt-style-title">${isLine ? 'Çizgi görünümü' : 'Nesne görünümü'}</div>
641
+ <div class="mt-style-tabs-bar" role="group" aria-label="Görünüm ayarları">
642
+ ${!isLine ? `
643
+ <button type="button" class="mt-style-tab-btn ${tab === 'fill' ? 'active' : ''}" data-style-tab="fill" title="Dolgu rengi">
644
+ <span class="mt-style-tab-swatch ${!primary.backgroundColor || primary.backgroundColor === 'transparent' ? 'is-transparent' : ''}" data-style-preview="backgroundColor" style="background-color: ${primary.backgroundColor || 'transparent'};"></span>
645
+ </button>` : ''}
646
+ <button type="button" class="mt-style-tab-btn ${tab === 'border' ? 'active' : ''}" data-style-tab="border" title="Kenarlık tipi">
647
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="4" width="16" height="16" rx="3" stroke-dasharray="3 3"/></svg>
648
+ </button>
649
+ <button type="button" class="mt-style-tab-btn ${tab === 'width' ? 'active' : ''}" data-style-tab="width" title="Kenarlık kalınlığı">
650
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 4h8M2 8h8"/></svg>
651
+ <span class="mt-style-tab-width-text" data-style-value="borderWidth">${bWidth}</span>
652
+ </button>
653
+ <button type="button" class="mt-style-tab-btn ${tab === 'borderColor' ? 'active' : ''}" data-style-tab="borderColor" title="Kenarlık rengi">
654
+ <span class="mt-style-border-preview ${!primary.borderColor || primary.borderColor === 'transparent' ? 'is-transparent' : ''}" data-style-preview="borderColor" style="border-color: ${primary.borderColor || 'transparent'};"></span>
655
+ </button>
656
+ ${!isLine ? `
657
+ <button type="button" class="mt-style-tab-btn ${tab === 'radius' ? 'active' : ''}" data-style-tab="radius" title="Köşe yuvarlaklığı">
658
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 20V12a7 7 0 0 1 7-7h8"/><path d="M5 5h1M19 19h1"/></svg>
659
+ </button>` : ''}
660
+ </div>
661
+ ${!isLine ? `
662
+ <div class="mt-style-panel" data-style-panel="fill" style="display: ${tab === 'fill' ? 'flex' : 'none'};">
663
+ <div class="mt-style-panel-title">Dolgu rengi</div>
664
+ ${palette('backgroundColor')}
665
+ </div>` : ''}
666
+ <div class="mt-style-panel" data-style-panel="border" style="display: ${tab === 'border' ? 'flex' : 'none'};">
667
+ <div class="mt-style-panel-title">Kenarlık tipi</div>
668
+ <div class="mt-style-type-row" role="group" aria-label="Kenarlık tipi">
669
+ <button type="button" class="mt-style-type-btn ${bStyle === 'none' ? 'active' : ''}" data-border-style="none" title="Kenarlık yok">⊘</button>
670
+ <button type="button" class="mt-style-type-btn ${bStyle === 'solid' ? 'active' : ''}" data-border-style="solid" title="Düz kenarlık">━</button>
671
+ <button type="button" class="mt-style-type-btn ${bStyle === 'dashed' ? 'active' : ''}" data-border-style="dashed" title="Kesikli kenarlık">┅</button>
672
+ <button type="button" class="mt-style-type-btn ${bStyle === 'dotted' ? 'active' : ''}" data-border-style="dotted" title="Noktalı kenarlık">⋯</button>
673
+ </div>
674
+ </div>
675
+ <div class="mt-style-panel" data-style-panel="width" style="display: ${tab === 'width' ? 'flex' : 'none'};">
676
+ <div class="mt-style-panel-title">Kenarlık kalınlığı</div>
677
+ <div class="mt-stepper-group" role="group" aria-label="Kenarlık kalınlığı">
678
+ <button type="button" class="mt-step-btn" data-step-prop="borderWidth" data-step-dir="-1" title="Kalınlığı azalt">−</button>
679
+ <div class="mt-style-step-value"><span class="mt-step-val" id="mt-val-borderWidth" aria-live="polite">${bWidth}</span><span class="mt-step-unit">px</span></div>
680
+ <button type="button" class="mt-step-btn" data-step-prop="borderWidth" data-step-dir="1" title="Kalınlığı artır">+</button>
681
+ </div>
682
+ </div>
683
+ <div class="mt-style-panel" data-style-panel="borderColor" style="display: ${tab === 'borderColor' ? 'flex' : 'none'};">
684
+ <div class="mt-style-panel-title">Kenarlık rengi</div>
685
+ ${palette('borderColor')}
686
+ </div>
687
+ ${!isLine ? `
688
+ <div class="mt-style-panel" data-style-panel="radius" style="display: ${tab === 'radius' ? 'flex' : 'none'};">
689
+ <div class="mt-style-panel-title">Köşe yuvarlaklığı</div>
690
+ <div class="mt-stepper-group" role="group" aria-label="Köşe yuvarlaklığı">
691
+ <button type="button" class="mt-step-btn" data-step-prop="borderRadius" data-step-dir="-1" title="Köşe yuvarlaklığını azalt">−</button>
692
+ <div class="mt-style-step-value"><span class="mt-step-val" id="mt-val-borderRadius" aria-live="polite">${bRadius}</span><span class="mt-step-unit">px</span></div>
693
+ <button type="button" class="mt-step-btn" data-step-prop="borderRadius" data-step-dir="1" title="Köşe yuvarlaklığını artır">+</button>
694
+ </div>
695
+ </div>` : ''}
696
+ </div>
697
+ `;
698
+ }
699
+
700
+ if (this.activePopup === 'text') {
701
+ const textPrimary = this.selectionManager.getSelectedObjects().find(object => object.type !== 'line') || primary;
702
+ const isBold = textPrimary.fontWeight === 'bold' || textPrimary.fontWeight === '700' || textPrimary.fontWeight === '800';
703
+ const isItalic = textPrimary.fontStyle === 'italic';
704
+ const isUnderline = textPrimary.textDecoration === 'underline';
705
+ const fSize = textPrimary.fontSize || 16;
706
+ const tColor = textPrimary.textColor || '#1e293b';
707
+ const hAlign = textPrimary.textAlign || 'center';
708
+ const vAlign = textPrimary.verticalAlign || 'middle';
709
+ const tab = this.activeTextTab || 'font';
710
+
711
+ const textColors = [
712
+ ['#1e293b', 'Koyu gri'], ['#ffffff', 'Beyaz'], ['#cbd5e1', 'Açık gri'],
713
+ ['#64748b', 'Gri'], ['#ef4444', 'Kırmızı'], ['#f97316', 'Turuncu'],
714
+ ['#eab308', 'Sarı'], ['#22c55e', 'Yeşil'], ['#14b8a6', 'Turkuaz'],
715
+ ['#0ea5e9', 'Açık mavi'], ['#2563eb', 'Mavi'], ['#6366f1', 'İndigo'],
716
+ ['#8b5cf6', 'Mor'], ['#ec4899', 'Pembe']
717
+ ];
718
+
719
+ return `
720
+ <div class="mt-popup-card mt-text-popup ${openClass}" id="mt-text-popup">
721
+ <div class="mt-text-title">Metin</div>
722
+
723
+ <!-- Üst Menü / 3 Sekme Çubuğu -->
724
+ <div class="mt-text-tabs-bar" role="group" aria-label="Metin ayarları">
725
+ <!-- 1. Biçimlendirme ve Font Sekmesi -->
726
+ <button type="button" class="mt-text-tab-btn ${tab === 'font' ? 'active' : ''}" data-text-tab="font" title="Biçim ve yazı boyutu">
727
+ <span class="mt-text-tab-font-icon" style="font-weight: 700; font-size: 0.95rem; line-height: 1;">Aa</span>
728
+ </button>
729
+
730
+ <!-- 2. Hizalama Sekmesi -->
731
+ <button type="button" class="mt-text-tab-btn ${tab === 'align' ? 'active' : ''}" data-text-tab="align" title="Hizalama">
732
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/></svg>
733
+ </button>
734
+
735
+ <!-- 3. Renk Sekmesi -->
736
+ <button type="button" class="mt-text-tab-btn ${tab === 'color' ? 'active' : ''}" data-text-tab="color" title="Metin Rengi">
737
+ <span class="mt-text-tab-swatch" id="mt-tab-text-color-swatch" style="background-color: ${tColor};"></span>
738
+ </button>
739
+ </div>
740
+
741
+ <!-- Alt Kart Panelleri -->
742
+
743
+ <!-- Panel 1: Biçimlendirme ve Font -->
744
+ <div class="mt-text-panel" data-text-panel="font" style="display: ${tab === 'font' ? 'flex' : 'none'};">
745
+ <div class="mt-text-format-grid">
746
+ <div class="mt-text-format-column">
747
+ <span class="mt-text-section-label">Biçim</span>
748
+ <div class="mt-pill-group" role="group" aria-label="Metin biçimi">
749
+ <button type="button" class="mt-format-btn ${isBold ? 'active' : ''}" data-text-action="bold" title="Kalın (Bold)"><strong>B</strong></button>
750
+ <button type="button" class="mt-format-btn ${isItalic ? 'active' : ''}" data-text-action="italic" title="İtalik (Italic)"><em>I</em></button>
751
+ <button type="button" class="mt-format-btn ${isUnderline ? 'active' : ''}" data-text-action="underline" title="Altı Çizili (Underline)"><u>U</u></button>
752
+ </div>
753
+ </div>
754
+ <div class="mt-text-format-column">
755
+ <span class="mt-text-section-label">Boyut</span>
756
+ <div class="mt-stepper-group" role="group" aria-label="Yazı boyutu">
757
+ <button type="button" class="mt-step-btn" data-text-action="size-down" title="Yazı boyutunu küçült">−</button>
758
+ <div class="mt-style-step-value">
759
+ <span class="mt-step-val mt-size-val" id="mt-val-fontSize" aria-live="polite">${fSize}</span>
760
+ <span class="mt-step-unit mt-size-unit">px</span>
761
+ </div>
762
+ <button type="button" class="mt-step-btn" data-text-action="size-up" title="Yazı boyutunu büyüt">+</button>
763
+ </div>
764
+ </div>
765
+ </div>
766
+ </div>
767
+
768
+ <!-- Panel 2: Hizalama -->
769
+ <div class="mt-text-panel" data-text-panel="align" style="display: ${tab === 'align' ? 'flex' : 'none'};">
770
+ <div class="mt-text-align-grid">
771
+ <div class="mt-text-align-column">
772
+ <span class="mt-text-section-label">Yatay</span>
773
+ <div class="mt-pill-group" role="group" aria-label="Yatay hizalama">
774
+ <button type="button" class="mt-icon-btn ${hAlign === 'left' ? 'active' : ''}" data-align-h="left" title="Sola Hizala">
775
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/></svg>
776
+ </button>
777
+ <button type="button" class="mt-icon-btn ${hAlign === 'center' ? 'active' : ''}" data-align-h="center" title="Ortala">
778
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
779
+ </button>
780
+ <button type="button" class="mt-icon-btn ${hAlign === 'right' ? 'active' : ''}" data-align-h="right" title="Sağa Hizala">
781
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="6" x2="21" y2="6"/><line x1="9" y1="12" x2="21" y2="12"/><line x1="6" y1="18" x2="21" y2="18"/></svg>
782
+ </button>
783
+ </div>
784
+ </div>
785
+ <div class="mt-text-align-column">
786
+ <span class="mt-text-section-label">Dikey</span>
787
+ <div class="mt-pill-group" role="group" aria-label="Dikey hizalama">
788
+ <button type="button" class="mt-icon-btn ${vAlign === 'top' ? 'active' : ''}" data-align-v="top" title="Üste Hizala">
789
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="4" x2="21" y2="4"/><path d="M12 20V8"/><polyline points="8 12 12 8 16 12"/></svg>
790
+ </button>
791
+ <button type="button" class="mt-icon-btn ${vAlign === 'middle' ? 'active' : ''}" data-align-v="middle" title="Dikey Ortala">
792
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="12" x2="21" y2="12"/><path d="M8 7l4 5 4-5"/><path d="M8 17l4-5 4 5"/></svg>
793
+ </button>
794
+ <button type="button" class="mt-icon-btn ${vAlign === 'bottom' ? 'active' : ''}" data-align-v="bottom" title="Alta Hizala">
795
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="20" x2="21" y2="20"/><path d="M12 4v12"/><polyline points="8 12 12 16 16 12"/></svg>
796
+ </button>
797
+ </div>
798
+ </div>
799
+ </div>
800
+ </div>
801
+
802
+ <!-- Panel 3: Renk -->
803
+ <div class="mt-text-panel mt-text-palette-section" data-text-panel="color" style="display: ${tab === 'color' ? 'flex' : 'none'};">
804
+ <div class="mt-text-panel-title">Metin rengi</div>
805
+ <div class="mt-color-palette" role="group" aria-label="Metin rengi">
806
+ ${textColors.map(([c, name]) => `
807
+ <button type="button" class="mt-color-swatch ${c.toLowerCase() === (tColor || '').toLowerCase() ? 'is-active-swatch' : ''}" data-text-color-val="${c}" style="background-color: ${c};" title="${name}" aria-label="${name}">
808
+ ${c.toLowerCase() === (tColor || '').toLowerCase() ? '<span class="swatch-check">✓</span>' : ''}
809
+ </button>
810
+ `).join('')}
811
+ </div>
812
+ </div>
813
+ </div>
814
+ `;
815
+ }
816
+
817
+ if (this.activePopup === 'layers') {
818
+ const isLocked = primary.locked;
819
+ return `
820
+ <div class="mt-popup-card mt-layer-popup ${openClass}" id="mt-layer-popup">
821
+ <div class="mt-layer-section">
822
+ <span class="mt-layer-section-label">Katman sırası</span>
823
+ <div class="mt-layer-order-grid" role="group" aria-label="Katman sırası">
824
+ <button type="button" class="mt-layer-btn" data-layer-action="forward" title="Öne Getir">
825
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>
826
+ <span>Öne</span>
827
+ </button>
828
+ <button type="button" class="mt-layer-btn" data-layer-action="front" title="En Öne Getir">
829
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 11-5-5-5 5"/><path d="m17 18-5-5-5 5"/></svg>
830
+ <span>En öne</span>
831
+ </button>
832
+ <button type="button" class="mt-layer-btn" data-layer-action="backward" title="Arkaya Gönder">
833
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
834
+ <span>Arkaya</span>
835
+ </button>
836
+ <button type="button" class="mt-layer-btn" data-layer-action="back" title="En Arkaya Gönder">
837
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 13 5 5 5-5"/><path d="m7 6 5 5 5-5"/></svg>
838
+ <span>En arkaya</span>
839
+ </button>
840
+ </div>
841
+ </div>
842
+ <div class="mt-layer-section mt-layer-lock-section">
843
+ <span class="mt-layer-section-label">Kilit durumu</span>
844
+ <button type="button" class="mt-layer-btn mt-layer-lock-item ${isLocked ? 'is-locked active-lock' : ''}" data-layer-action="toggle-lock" title="${isLocked ? 'Kilidi Aç' : 'Nesneyi Kilitle'}" aria-pressed="${isLocked}">
845
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
846
+ ${isLocked
847
+ ? '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>'
848
+ : '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>'}
849
+ </svg>
850
+ <span class="mt-lock-label">${isLocked ? 'Kilidi Aç' : 'Kilit'}</span>
851
+ </button>
852
+ </div>
853
+ </div>
854
+ `;
855
+ }
856
+
857
+ if (this.activePopup === 'align') {
858
+ const selectedObjs = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
859
+ const units = this.getSelectionUnits(selectedObjs);
860
+ const canEqualize = selectedObjs.length >= 2;
861
+ const canDistribute = units.length >= 3;
862
+
863
+ return `
864
+ <div class="mt-popup-card mt-align-popup ${openClass}" id="mt-align-popup">
865
+ <div class="mt-popup-title">Hizalama</div>
866
+ <div class="mt-row">
867
+ <div class="mt-pill-group">
868
+ <button type="button" class="mt-icon-btn" data-multi-align="left" title="Sola Hizala">
869
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="4" y1="3" x2="4" y2="21"/><rect x="8" y="7" width="12" height="10" rx="1"/></svg>
870
+ </button>
871
+ <button type="button" class="mt-icon-btn" data-multi-align="center" title="Yatay Ortala">
872
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="3" x2="12" y2="21"/><rect x="6" y="7" width="12" height="10" rx="1"/></svg>
873
+ </button>
874
+ <button type="button" class="mt-icon-btn" data-multi-align="right" title="Sağa Hizala">
875
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="20" y1="3" x2="20" y2="21"/><rect x="4" y="7" width="12" height="10" rx="1"/></svg>
876
+ </button>
877
+ </div>
878
+ <div class="mt-pill-group">
879
+ <button type="button" class="mt-icon-btn" data-multi-align="top" title="Üste Hizala">
880
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="4" x2="21" y2="4"/><rect x="7" y="8" width="10" height="12" rx="1"/></svg>
881
+ </button>
882
+ <button type="button" class="mt-icon-btn" data-multi-align="middle" title="Dikey Ortala">
883
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="12" x2="21" y2="12"/><rect x="7" y="6" width="10" height="12" rx="1"/></svg>
884
+ </button>
885
+ <button type="button" class="mt-icon-btn" data-multi-align="bottom" title="Alta Hizala">
886
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="20" x2="21" y2="20"/><rect x="7" y="4" width="10" height="12" rx="1"/></svg>
887
+ </button>
888
+ </div>
889
+ </div>
890
+ <div class="mt-row" style="margin-top: 6px; justify-content: space-between;">
891
+ <div class="mt-pill-group">
892
+ <button type="button" class="mt-icon-btn" data-distribute="horizontal" ${canDistribute ? '' : 'disabled'} title="${canDistribute ? 'Yatay Eşit Dağıt' : 'Yatay Eşit Dağıt (En az 3 nesne seçilmeli)'}">
893
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="4" y1="3" x2="4" y2="21"/><line x1="20" y1="3" x2="20" y2="21"/><rect x="8.5" y="7" width="7" height="10" rx="1"/></svg>
894
+ </button>
895
+ <button type="button" class="mt-icon-btn" data-distribute="vertical" ${canDistribute ? '' : 'disabled'} title="${canDistribute ? 'Dikey Eşit Dağıt' : 'Dikey Eşit Dağıt (En az 3 nesne seçilmeli)'}">
896
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="3" y1="4" x2="21" y2="4"/><line x1="3" y1="20" x2="21" y2="20"/><rect x="7" y="8.5" width="10" height="7" rx="1"/></svg>
897
+ </button>
898
+ <button type="button" class="mt-icon-btn" data-equalize-width="min" ${canEqualize ? '' : 'disabled'} title="${canEqualize ? 'En Kısa Genişliğe Eşitle' : 'En Kısa Genişliğe Eşitle (En az 2 nesne seçilmeli)'}">
899
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="8" y="5" width="8" height="14" rx="1.5"/><path d="M2 12h4m-2-2.5l2.5 2.5-2.5 2.5M22 12h-4m2-2.5l-2.5 2.5 2.5 2.5"/></svg>
900
+ </button>
901
+ <button type="button" class="mt-icon-btn" data-equalize-width="max" ${canEqualize ? '' : 'disabled'} title="${canEqualize ? 'En Uzun Genişliğe Eşitle' : 'En Uzun Genişliğe Eşitle (En az 2 nesne seçilmeli)'}">
902
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="5" y="5" width="14" height="14" rx="1.5"/><path d="M9 12H4m2.5-2.5L4 12l2.5 2.5M15 12h5m-2.5-2.5l2.5 2.5-2.5 2.5"/></svg>
903
+ </button>
904
+ </div>
905
+ <button type="button" class="mt-group-action-btn ${hasGroup ? 'active-group' : ''}" data-action="toggle-group" title="${hasGroup ? 'Grubu Çöz (Ctrl+Shift+G)' : 'Grupla (Ctrl+G)'}">
906
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-dasharray="3 3"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>
907
+ <span>${hasGroup ? 'Grubu Çöz' : 'Grupla'}</span>
908
+ </button>
909
+ </div>
910
+ </div>
911
+ `;
912
+ }
913
+
914
+ return '';
915
+ }
916
+
917
+ bindPopupInteractions(primary) {
918
+ if (!primary) return;
919
+ const getTargets = () => this.selectionManager.getSelectedObjects();
920
+ const getTextTargets = () => getTargets().filter(o => o.type !== 'line' && (o.type === 'text' || o.text !== undefined || o.role === 'category' || o.role === 'sequence_slot'));
921
+ const textPrimary = getTextTargets()[0] || primary;
922
+
923
+ // İkonlu kontrollerin adı ve seçili durumu yardımcı teknolojilere de aktarılır.
924
+ const syncAppearanceControls = () => {
925
+ this.toolbarEl.querySelectorAll('.mt-line-popup button, .mt-style-popup button, .mt-text-popup button').forEach(button => {
926
+ if (!button.hasAttribute('data-step-prop') && !['size-up', 'size-down'].includes(button.getAttribute('data-text-action'))) {
927
+ button.setAttribute('aria-pressed', String(button.classList.contains('active') || button.classList.contains('is-active') || button.classList.contains('is-active-swatch')));
928
+ }
929
+ if (!button.hasAttribute('aria-label') && button.title) {
930
+ button.setAttribute('aria-label', button.title);
931
+ }
932
+ });
933
+ };
934
+ syncAppearanceControls();
935
+
936
+ // Görünüm sekmeleri içerikleri yerinde değiştirir, klavye odağı korunur.
937
+ this.toolbarEl.querySelectorAll('[data-style-tab]').forEach(btn => {
938
+ btn.onclick = (e) => {
939
+ e.stopPropagation();
940
+ this.activeStyleTab = btn.getAttribute('data-style-tab');
941
+ this.toolbarEl.querySelectorAll('[data-style-tab]').forEach(button => {
942
+ button.classList.toggle('active', button === btn);
943
+ });
944
+ this.toolbarEl.querySelectorAll('[data-style-panel]').forEach(panel => {
945
+ panel.style.display = panel.getAttribute('data-style-panel') === this.activeStyleTab ? 'flex' : 'none';
946
+ });
947
+ syncAppearanceControls();
948
+ this.reposition();
949
+ };
950
+ });
951
+
952
+ // Çizgi Popup Sekme Değişimi
953
+ this.toolbarEl.querySelectorAll('[data-line-tab]').forEach(btn => {
954
+ btn.onclick = (e) => {
955
+ e.stopPropagation();
956
+ const tab = btn.getAttribute('data-line-tab');
957
+ this.activeLineTab = tab;
958
+ this.toolbarEl.querySelectorAll('[data-line-tab]').forEach(b => b.classList.toggle('active', b === btn));
959
+ this.toolbarEl.querySelectorAll('.mt-line-panel').forEach(p => {
960
+ p.style.display = p.getAttribute('data-panel') === tab ? 'flex' : 'none';
961
+ });
962
+ syncAppearanceControls();
963
+ this.reposition();
964
+ };
965
+ });
966
+
967
+ // Çizgi Tipi Seçimi
968
+ this.toolbarEl.querySelectorAll('[data-line-style]').forEach(btn => {
969
+ btn.onclick = (e) => {
970
+ e.stopPropagation();
971
+ const style = btn.getAttribute('data-line-style');
972
+ this.toolbarEl.querySelectorAll('[data-line-style]').forEach(b => b.classList.remove('active'));
973
+ btn.classList.add('active');
974
+ const targets = getTargets();
975
+ targets.forEach((o, i) => this.objectManager.updateObject(o.id, { borderStyle: style }, i === targets.length - 1));
976
+ syncAppearanceControls();
977
+ };
978
+ });
979
+
980
+ // Çizgi Kalınlığı Hızlı Seçim Butonları
981
+ const updateLineWidth = (newWidth) => {
982
+ const clamped = Math.max(1, Math.min(24, Math.round(newWidth)));
983
+ const targets = getTargets();
984
+ targets.forEach((o, i) => this.objectManager.updateObject(o.id, { borderWidth: clamped }, i === targets.length - 1));
985
+ const tabValEl = this.root.getElementById('mt-tab-width-val');
986
+ if (tabValEl) tabValEl.textContent = `${clamped}`;
987
+ this.toolbarEl.querySelectorAll('[data-line-width-preset]').forEach(b => {
988
+ const pVal = parseInt(b.getAttribute('data-line-width-preset'), 10);
989
+ b.classList.toggle('active', pVal === clamped);
990
+ });
991
+ syncAppearanceControls();
992
+ };
993
+
994
+ this.toolbarEl.querySelectorAll('[data-line-width-preset]').forEach(btn => {
995
+ btn.onclick = (e) => {
996
+ e.stopPropagation();
997
+ const preset = parseInt(btn.getAttribute('data-line-width-preset'), 10);
998
+ updateLineWidth(preset);
999
+ };
1000
+ });
1001
+
1002
+ // Çizgi Rengi Seçimi (Palet Swatchları)
1003
+ const updateLineColor = (hex) => {
1004
+ const targets = getTargets();
1005
+ targets.forEach((o, i) => this.objectManager.updateObject(o.id, { borderColor: hex }, i === targets.length - 1));
1006
+ const swatchEl = this.root.getElementById('mt-tab-color-swatch');
1007
+ if (swatchEl) swatchEl.style.backgroundColor = hex;
1008
+ this.toolbarEl.querySelectorAll('[data-line-color]').forEach(b => {
1009
+ b.classList.toggle('is-active', (b.getAttribute('data-line-color') || '').toLowerCase() === hex.toLowerCase());
1010
+ });
1011
+ syncAppearanceControls();
1012
+ };
1013
+
1014
+ this.toolbarEl.querySelectorAll('[data-line-color]').forEach(btn => {
1015
+ btn.onclick = (e) => {
1016
+ e.stopPropagation();
1017
+ const color = btn.getAttribute('data-line-color');
1018
+ updateLineColor(color);
1019
+ };
1020
+ });
1021
+
1022
+ // Çizgi Hizalama (Yatay Yap / Dikey Yap)
1023
+ this.toolbarEl.querySelectorAll('[data-line-align]').forEach(btn => {
1024
+ btn.onclick = (e) => {
1025
+ e.stopPropagation();
1026
+ const align = btn.getAttribute('data-line-align');
1027
+ const rot = align === 'horizontal' ? 0 : 90;
1028
+ this.toolbarEl.querySelectorAll('[data-line-align]').forEach(b => b.classList.remove('active'));
1029
+ btn.classList.add('active');
1030
+ const targets = getTargets();
1031
+ targets.forEach((o, i) => this.objectManager.updateObject(o.id, { rotation: rot }, i === targets.length - 1));
1032
+ syncAppearanceControls();
1033
+ this.selectionManager.updateSelectionBox();
1034
+ };
1035
+ });
1036
+
1037
+ // Çizgi Ok Uçları (Uçsuz / Başlangıç / Bitiş / Çift Yön)
1038
+ this.toolbarEl.querySelectorAll('[data-line-arrow]').forEach(btn => {
1039
+ btn.onclick = (e) => {
1040
+ e.stopPropagation();
1041
+ const arrowType = btn.getAttribute('data-line-arrow');
1042
+ let start = false;
1043
+ let end = false;
1044
+ if (arrowType === 'start') {
1045
+ start = true;
1046
+ end = false;
1047
+ } else if (arrowType === 'end') {
1048
+ start = false;
1049
+ end = true;
1050
+ } else if (arrowType === 'both') {
1051
+ start = true;
1052
+ end = true;
1053
+ } else if (arrowType === 'none') {
1054
+ start = false;
1055
+ end = false;
1056
+ }
1057
+ this.toolbarEl.querySelectorAll('[data-line-arrow]').forEach(b => b.classList.remove('active'));
1058
+ btn.classList.add('active');
1059
+ const targets = getTargets();
1060
+ targets.forEach((o, i) => this.objectManager.updateObject(o.id, { arrowStart: start, arrowEnd: end }, i === targets.length - 1));
1061
+ syncAppearanceControls();
1062
+ };
1063
+ });
1064
+
1065
+ // Rol Seçimi
1066
+ this.toolbarEl.querySelectorAll('[data-role-val]').forEach(btn => {
1067
+ btn.onclick = (e) => {
1068
+ e.stopPropagation();
1069
+ const role = btn.getAttribute('data-role-val');
1070
+ this.toolbarEl.querySelectorAll('[data-role-val]').forEach(b => {
1071
+ b.classList.remove('active');
1072
+ b.setAttribute('aria-checked', 'false');
1073
+ });
1074
+ btn.classList.add('active');
1075
+ btn.setAttribute('aria-checked', 'true');
1076
+ const targets = getTargets();
1077
+ targets.forEach((o, i) => this.objectManager.updateObject(o.id, { role }, i === targets.length - 1));
1078
+ };
1079
+ });
1080
+
1081
+ // Kenarlık Stili
1082
+ this.toolbarEl.querySelectorAll('[data-border-style]').forEach(btn => {
1083
+ btn.onclick = (e) => {
1084
+ e.stopPropagation();
1085
+ const style = btn.getAttribute('data-border-style');
1086
+ this.toolbarEl.querySelectorAll('[data-border-style]').forEach(b => b.classList.remove('active'));
1087
+ btn.classList.add('active');
1088
+ const targets = getTargets();
1089
+ targets.forEach((o, i) => this.objectManager.updateObject(o.id, { borderStyle: style }, i === targets.length - 1));
1090
+ syncAppearanceControls();
1091
+ };
1092
+ });
1093
+
1094
+ // Kalınlık ve Köşe Yuvarlaklığı (+/-) Stepper Etkileşimi
1095
+ this.toolbarEl.querySelectorAll('[data-step-prop]').forEach(btn => {
1096
+ btn.onclick = (e) => {
1097
+ e.stopPropagation();
1098
+ const prop = btn.getAttribute('data-step-prop'); // 'borderWidth' | 'borderRadius'
1099
+ const dir = parseInt(btn.getAttribute('data-step-dir'), 10); // -1 | 1
1100
+ const isLine = primary.type === 'line';
1101
+ const step = prop === 'borderRadius' ? 2 : 1;
1102
+ const minVal = isLine && prop === 'borderWidth' ? 1 : 0;
1103
+ const maxVal = prop === 'borderRadius' ? 50 : 24;
1104
+
1105
+ const currentVal = primary[prop] !== undefined ? primary[prop] : (prop === 'borderRadius' ? 8 : 2);
1106
+ const newVal = Math.max(minVal, Math.min(maxVal, currentVal + (dir * step)));
1107
+
1108
+ const targets = getTargets();
1109
+ targets.forEach((o, i) => this.objectManager.updateObject(o.id, { [prop]: newVal }, i === targets.length - 1));
1110
+
1111
+ const valEl = this.toolbarEl.querySelector(`#mt-val-${prop}`);
1112
+ if (valEl) {
1113
+ valEl.textContent = newVal;
1114
+ }
1115
+ const tabValue = this.toolbarEl.querySelector(`[data-style-value="${prop}"]`);
1116
+ if (tabValue) tabValue.textContent = newVal;
1117
+ };
1118
+ });
1119
+
1120
+ // Her palet kendi özelliğini taşır; sekmeler arasında dolgu ve kenarlık karışmaz.
1121
+ this.toolbarEl.querySelectorAll('[data-style-color]').forEach(btn => {
1122
+ btn.onclick = (e) => {
1123
+ e.stopPropagation();
1124
+ const color = btn.getAttribute('data-style-color');
1125
+ const prop = btn.getAttribute('data-style-color-prop');
1126
+ const targets = getTargets();
1127
+ targets.forEach((object, i) => {
1128
+ const changes = { [prop]: color };
1129
+ if (prop === 'borderColor') {
1130
+ changes.borderStyle = color === 'transparent' ? 'none' : (object.borderStyle === 'none' ? 'solid' : object.borderStyle || 'solid');
1131
+ }
1132
+ this.objectManager.updateObject(object.id, changes, i === targets.length - 1);
1133
+ });
1134
+ this.toolbarEl.querySelectorAll(`[data-style-color-prop="${prop}"]`).forEach(button => {
1135
+ button.classList.toggle('is-active', button.getAttribute('data-style-color') === color);
1136
+ });
1137
+ const preview = this.toolbarEl.querySelector(`[data-style-preview="${prop}"]`);
1138
+ if (preview) {
1139
+ preview.style[prop] = color;
1140
+ preview.classList.toggle('is-transparent', color === 'transparent');
1141
+ }
1142
+ if (prop === 'borderColor') {
1143
+ this.toolbarEl.querySelectorAll('[data-border-style]').forEach(button => {
1144
+ button.classList.toggle('active', button.getAttribute('data-border-style') === primary.borderStyle);
1145
+ });
1146
+ }
1147
+ syncAppearanceControls();
1148
+ };
1149
+ });
1150
+
1151
+ // Metin Sekmeleri Değiştirme (Biçimlendirme/Font, Hizalama, Renk)
1152
+ this.toolbarEl.querySelectorAll('[data-text-tab]').forEach(btn => {
1153
+ btn.onclick = (e) => {
1154
+ e.stopPropagation();
1155
+ this.activeTextTab = btn.getAttribute('data-text-tab');
1156
+ this.toolbarEl.querySelectorAll('[data-text-tab]').forEach(button => {
1157
+ button.classList.toggle('active', button === btn);
1158
+ });
1159
+ this.toolbarEl.querySelectorAll('[data-text-panel]').forEach(panel => {
1160
+ panel.style.display = panel.getAttribute('data-text-panel') === this.activeTextTab ? 'flex' : 'none';
1161
+ });
1162
+ syncAppearanceControls();
1163
+ this.reposition();
1164
+ };
1165
+ });
1166
+
1167
+ // Metin Formatlama
1168
+ this.toolbarEl.querySelectorAll('[data-text-action]').forEach(btn => {
1169
+ btn.onclick = (e) => {
1170
+ e.stopPropagation();
1171
+ const act = btn.getAttribute('data-text-action');
1172
+ const tTargets = getTextTargets();
1173
+
1174
+ if (act === 'bold') {
1175
+ const isBold = textPrimary.fontWeight === 'bold' || textPrimary.fontWeight === '700' || textPrimary.fontWeight === '800';
1176
+ const newWeight = isBold ? 'normal' : '700';
1177
+ btn.classList.toggle('active', newWeight === '700');
1178
+ tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { fontWeight: newWeight }, i === tTargets.length - 1));
1179
+ } else if (act === 'italic') {
1180
+ const isItalic = textPrimary.fontStyle === 'italic';
1181
+ const newStyle = isItalic ? 'normal' : 'italic';
1182
+ btn.classList.toggle('active', newStyle === 'italic');
1183
+ tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { fontStyle: newStyle }, i === tTargets.length - 1));
1184
+ } else if (act === 'underline') {
1185
+ const isUnderline = textPrimary.textDecoration === 'underline';
1186
+ const newDecor = isUnderline ? 'none' : 'underline';
1187
+ btn.classList.toggle('active', newDecor === 'underline');
1188
+ tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { textDecoration: newDecor }, i === tTargets.length - 1));
1189
+ } else if (act === 'size-up' || act === 'size-down') {
1190
+ const delta = act === 'size-up' ? 2 : -2;
1191
+ tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { fontSize: Math.max(10, (o.fontSize || 16) + delta) }, i === tTargets.length - 1));
1192
+ const newSize = textPrimary.fontSize || 16;
1193
+ const sizeVal = this.toolbarEl.querySelector('.mt-size-val');
1194
+ if (sizeVal) sizeVal.textContent = newSize;
1195
+ }
1196
+ syncAppearanceControls();
1197
+ };
1198
+ });
1199
+
1200
+ // Metin Tanımlı Renk Seçimi
1201
+ this.toolbarEl.querySelectorAll('[data-text-color-val]').forEach(btn => {
1202
+ btn.onclick = (e) => {
1203
+ e.stopPropagation();
1204
+ const c = btn.getAttribute('data-text-color-val');
1205
+ const tabSwatch = this.toolbarEl.querySelector('#mt-tab-text-color-swatch');
1206
+ if (tabSwatch) tabSwatch.style.backgroundColor = c;
1207
+ this.toolbarEl.querySelectorAll('[data-text-color-val]').forEach(b => {
1208
+ b.classList.remove('is-active-swatch');
1209
+ b.innerHTML = '';
1210
+ });
1211
+ btn.classList.add('is-active-swatch');
1212
+ btn.innerHTML = '<span class="swatch-check">✓</span>';
1213
+ const tTargets = getTextTargets();
1214
+ tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { textColor: c }, i === tTargets.length - 1));
1215
+ syncAppearanceControls();
1216
+ };
1217
+ });
1218
+
1219
+ // Yatay Hizalama
1220
+ this.toolbarEl.querySelectorAll('[data-align-h]').forEach(btn => {
1221
+ btn.onclick = (e) => {
1222
+ e.stopPropagation();
1223
+ const align = btn.getAttribute('data-align-h');
1224
+ this.toolbarEl.querySelectorAll('[data-align-h]').forEach(b => b.classList.remove('active'));
1225
+ btn.classList.add('active');
1226
+ const tTargets = getTextTargets();
1227
+ tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { textAlign: align }, i === tTargets.length - 1));
1228
+ syncAppearanceControls();
1229
+ };
1230
+ });
1231
+
1232
+ // Dikey Hizalama
1233
+ this.toolbarEl.querySelectorAll('[data-align-v]').forEach(btn => {
1234
+ btn.onclick = (e) => {
1235
+ e.stopPropagation();
1236
+ const vAlign = btn.getAttribute('data-align-v');
1237
+ this.toolbarEl.querySelectorAll('[data-align-v]').forEach(b => b.classList.remove('active'));
1238
+ btn.classList.add('active');
1239
+ const tTargets = getTextTargets();
1240
+ tTargets.forEach((o, i) => this.objectManager.updateObject(o.id, { verticalAlign: vAlign }, i === tTargets.length - 1));
1241
+ syncAppearanceControls();
1242
+ };
1243
+ });
1244
+
1245
+ // Katman / Kilit Aksiyonları
1246
+ this.toolbarEl.querySelectorAll('[data-layer-action]').forEach(btn => {
1247
+ btn.onclick = (e) => {
1248
+ e.stopPropagation();
1249
+ const act = btn.getAttribute('data-layer-action');
1250
+ const targets = getTargets();
1251
+ if (act === 'forward') {
1252
+ const sorted = [...targets].sort((a, b) => (b.zIndex || 1) - (a.zIndex || 1));
1253
+ sorted.forEach(o => this.layerManager.bringForward(o.id, false));
1254
+ this.selectionManager.updateSelectionBox();
1255
+ this.core.pushHistoryState('Katmanı Öne Al');
1256
+ } else if (act === 'front') {
1257
+ const sorted = [...targets].sort((a, b) => (a.zIndex || 1) - (b.zIndex || 1));
1258
+ sorted.forEach(o => this.layerManager.bringToFront(o.id, false));
1259
+ this.selectionManager.updateSelectionBox();
1260
+ this.core.pushHistoryState('Katmanı En Öne Al');
1261
+ } else if (act === 'backward') {
1262
+ const sorted = [...targets].sort((a, b) => (a.zIndex || 1) - (b.zIndex || 1));
1263
+ sorted.forEach(o => this.layerManager.sendBackward(o.id, false));
1264
+ this.selectionManager.updateSelectionBox();
1265
+ this.core.pushHistoryState('Katmanı Arkaya Al');
1266
+ } else if (act === 'back') {
1267
+ const sorted = [...targets].sort((a, b) => (b.zIndex || 1) - (a.zIndex || 1));
1268
+ sorted.forEach(o => this.layerManager.sendToBack(o.id, false));
1269
+ this.selectionManager.updateSelectionBox();
1270
+ this.core.pushHistoryState('Katmanı En Arkaya Al');
1271
+ } else if (act === 'toggle-lock') {
1272
+ const allLocked = targets.every(o => o.locked);
1273
+ const newLocked = !allLocked;
1274
+ btn.classList.toggle('active-lock', newLocked);
1275
+ btn.classList.toggle('is-locked', newLocked);
1276
+ btn.setAttribute('aria-pressed', String(newLocked));
1277
+ btn.title = newLocked ? 'Kilidi Aç' : 'Nesneyi Kilitle';
1278
+ const lockIcon = btn.querySelector('svg');
1279
+ if (lockIcon) {
1280
+ lockIcon.innerHTML = newLocked
1281
+ ? '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/>'
1282
+ : '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>';
1283
+ }
1284
+ const labelSpan = btn.querySelector('.mt-lock-label') || btn.querySelector('span');
1285
+ if (labelSpan) {
1286
+ labelSpan.textContent = newLocked ? 'Kilidi Aç' : 'Kilit';
1287
+ }
1288
+ targets.forEach(o => this.objectManager.updateObject(o.id, { locked: newLocked }, false));
1289
+ this.selectionManager.updateSelectionBox();
1290
+ this.core.pushHistoryState(newLocked ? 'Nesneyi Kilitle' : 'Kilidi Aç');
1291
+ }
1292
+ };
1293
+ });
1294
+
1295
+ // Çoklu Nesne Hizalama
1296
+ this.toolbarEl.querySelectorAll('[data-multi-align]').forEach(btn => {
1297
+ btn.onclick = (e) => {
1298
+ e.stopPropagation();
1299
+ const dir = btn.getAttribute('data-multi-align');
1300
+ this.alignSelectedObjects(dir);
1301
+ this.closePopup();
1302
+ };
1303
+ });
1304
+
1305
+ // Eşit Dağıtma
1306
+ this.toolbarEl.querySelectorAll('[data-distribute]').forEach(btn => {
1307
+ btn.onclick = (e) => {
1308
+ e.stopPropagation();
1309
+ const axis = btn.getAttribute('data-distribute');
1310
+ this.distributeSelected(axis);
1311
+ this.closePopup();
1312
+ };
1313
+ });
1314
+
1315
+ // Genişlik Eşitleme (En Kısa / En Uzun)
1316
+ this.toolbarEl.querySelectorAll('[data-equalize-width]').forEach(btn => {
1317
+ btn.onclick = (e) => {
1318
+ e.stopPropagation();
1319
+ const mode = btn.getAttribute('data-equalize-width');
1320
+ this.equalizeSelectedWidth(mode);
1321
+ this.closePopup();
1322
+ };
1323
+ });
1324
+
1325
+ // Çoklu Seçim Grupla / Grubu Çöz
1326
+ this.toolbarEl.querySelectorAll('[data-action="toggle-group"]').forEach(btn => {
1327
+ btn.onclick = (e) => {
1328
+ e.stopPropagation();
1329
+ const targets = this.selectionManager.getSelectedObjects();
1330
+ const hasGroup = targets.some(o => o.groupId);
1331
+ if (hasGroup) {
1332
+ this.selectionManager.ungroupSelected();
1333
+ } else {
1334
+ this.selectionManager.groupSelected();
1335
+ }
1336
+ this.closePopup();
1337
+ this.render();
1338
+ };
1339
+ });
1340
+ }
1341
+
1342
+ getSelectionUnits(selectedObjects) {
1343
+ if (!selectedObjects || selectedObjects.length === 0) return [];
1344
+ const unitsMap = new Map();
1345
+ const standaloneUnits = [];
1346
+
1347
+ selectedObjects.forEach(obj => {
1348
+ if (obj.groupId) {
1349
+ if (!unitsMap.has(obj.groupId)) {
1350
+ unitsMap.set(obj.groupId, []);
1351
+ }
1352
+ unitsMap.get(obj.groupId).push(obj);
1353
+ } else {
1354
+ standaloneUnits.push({
1355
+ isGroup: false,
1356
+ groupId: null,
1357
+ objects: [obj],
1358
+ minX: obj.x,
1359
+ maxX: obj.x + obj.width,
1360
+ minY: obj.y,
1361
+ maxY: obj.y + obj.height,
1362
+ width: obj.width,
1363
+ height: obj.height
1364
+ });
1365
+ }
1366
+ });
1367
+
1368
+ const groupUnits = Array.from(unitsMap.entries()).map(([groupId, objs]) => {
1369
+ const minX = Math.min(...objs.map(o => o.x));
1370
+ const maxX = Math.max(...objs.map(o => o.x + o.width));
1371
+ const minY = Math.min(...objs.map(o => o.y));
1372
+ const maxY = Math.max(...objs.map(o => o.y + o.height));
1373
+ return {
1374
+ isGroup: true,
1375
+ groupId,
1376
+ objects: objs,
1377
+ minX,
1378
+ maxX,
1379
+ minY,
1380
+ maxY,
1381
+ width: maxX - minX,
1382
+ height: maxY - minY
1383
+ };
1384
+ });
1385
+
1386
+ return [...groupUnits, ...standaloneUnits];
1387
+ }
1388
+
1389
+ distributeSelected(axis) {
1390
+ const selected = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
1391
+ const units = this.getSelectionUnits(selected);
1392
+ if (units.length < 3) return;
1393
+
1394
+ if (axis === 'horizontal') {
1395
+ units.sort((a, b) => a.minX - b.minX);
1396
+ const minX = units[0].minX;
1397
+ const lastUnit = units[units.length - 1];
1398
+ const maxX = lastUnit.maxX;
1399
+ const totalWidths = units.reduce((s, u) => s + u.width, 0);
1400
+ const totalGap = (maxX - minX) - totalWidths;
1401
+ const gap = totalGap / (units.length - 1);
1402
+
1403
+ let curX = minX;
1404
+ units.forEach(u => {
1405
+ const deltaX = Math.round(curX) - u.minX;
1406
+ u.objects.forEach(o => {
1407
+ this.objectManager.updateObject(o.id, { x: Math.round(o.x + deltaX) }, false);
1408
+ });
1409
+ curX += u.width + gap;
1410
+ });
1411
+ } else if (axis === 'vertical') {
1412
+ units.sort((a, b) => a.minY - b.minY);
1413
+ const minY = units[0].minY;
1414
+ const lastUnit = units[units.length - 1];
1415
+ const maxY = lastUnit.maxY;
1416
+ const totalHeights = units.reduce((s, u) => s + u.height, 0);
1417
+ const totalGap = (maxY - minY) - totalHeights;
1418
+ const gap = totalGap / (units.length - 1);
1419
+
1420
+ let curY = minY;
1421
+ units.forEach(u => {
1422
+ const deltaY = Math.round(curY) - u.minY;
1423
+ u.objects.forEach(o => {
1424
+ this.objectManager.updateObject(o.id, { y: Math.round(o.y + deltaY) }, false);
1425
+ });
1426
+ curY += u.height + gap;
1427
+ });
1428
+ }
1429
+
1430
+ if (this.core && this.core.pushHistoryState) {
1431
+ this.core.pushHistoryState('Eşit Dağıt');
1432
+ }
1433
+ this.selectionManager.updateSelectionBox();
1434
+ }
1435
+
1436
+ equalizeSelectedWidth(mode) {
1437
+ const selected = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
1438
+ if (selected.length < 2) return;
1439
+
1440
+ const targetWidth = mode === 'min'
1441
+ ? Math.min(...selected.map(o => o.width))
1442
+ : Math.max(...selected.map(o => o.width));
1443
+
1444
+ const stageW = (this.core && this.core.data && this.core.data.stage && this.core.data.stage.width) || 1200;
1445
+
1446
+ selected.forEach(o => {
1447
+ let newX = o.x;
1448
+ if (newX + targetWidth > stageW) {
1449
+ newX = Math.max(0, stageW - targetWidth);
1450
+ }
1451
+ this.objectManager.updateObject(o.id, { width: targetWidth, x: newX }, false);
1452
+ });
1453
+
1454
+ if (this.core && this.core.pushHistoryState) {
1455
+ this.core.pushHistoryState('Genişlikleri Eşitle');
1456
+ }
1457
+ this.selectionManager.updateSelectionBox();
1458
+ }
1459
+
1460
+ alignSelectedObjects(direction) {
1461
+ const selected = this.selectionManager.getSelectedObjects().filter(o => !o.locked);
1462
+ if (!selected || selected.length === 0) return;
1463
+
1464
+ const units = this.getSelectionUnits(selected);
1465
+ if (units.length === 0) return;
1466
+
1467
+ const stageW = (this.stageManager && this.stageManager.logicalWidth) || (this.core && this.core.data && this.core.data.stage && this.core.data.stage.width) || 1200;
1468
+ const stageH = (this.stageManager && this.stageManager.logicalHeight) || (this.core && this.core.data && this.core.data.stage && this.core.data.stage.height) || 675;
1469
+
1470
+ if (units.length === 1) {
1471
+ // Tek bir birim (örneğin grup haline getirilmiş bir nesneler topluluğu) sahneye tek parça olarak hizalanır
1472
+ const unit = units[0];
1473
+ let deltaX = 0;
1474
+ let deltaY = 0;
1475
+
1476
+ if (direction === 'left') {
1477
+ deltaX = 0 - unit.minX;
1478
+ } else if (direction === 'right') {
1479
+ deltaX = (stageW - unit.width) - unit.minX;
1480
+ } else if (direction === 'center') {
1481
+ deltaX = Math.round((stageW - unit.width) / 2) - unit.minX;
1482
+ } else if (direction === 'top') {
1483
+ deltaY = 0 - unit.minY;
1484
+ } else if (direction === 'bottom') {
1485
+ deltaY = (stageH - unit.height) - unit.minY;
1486
+ } else if (direction === 'middle') {
1487
+ deltaY = Math.round((stageH - unit.height) / 2) - unit.minY;
1488
+ }
1489
+
1490
+ unit.objects.forEach(o => {
1491
+ this.objectManager.updateObject(o.id, {
1492
+ x: Math.round(o.x + deltaX),
1493
+ y: Math.round(o.y + deltaY)
1494
+ }, false);
1495
+ });
1496
+ } else {
1497
+ // Birden fazla birim seçiliyse (gruplar ve/veya bağımsız nesneler), her grup tek bir nesne gibi davranır ve birbirine hizalanır
1498
+ if (direction === 'left') {
1499
+ const minX = Math.min(...units.map(u => u.minX));
1500
+ units.forEach(u => {
1501
+ const deltaX = minX - u.minX;
1502
+ u.objects.forEach(o => this.objectManager.updateObject(o.id, { x: Math.round(o.x + deltaX) }, false));
1503
+ });
1504
+ } else if (direction === 'right') {
1505
+ const maxR = Math.max(...units.map(u => u.maxX));
1506
+ units.forEach(u => {
1507
+ const deltaX = (maxR - u.width) - u.minX;
1508
+ u.objects.forEach(o => this.objectManager.updateObject(o.id, { x: Math.round(o.x + deltaX) }, false));
1509
+ });
1510
+ } else if (direction === 'center') {
1511
+ const avgX = units.reduce((sum, u) => sum + (u.minX + u.width / 2), 0) / units.length;
1512
+ units.forEach(u => {
1513
+ const deltaX = Math.round(avgX - u.width / 2) - u.minX;
1514
+ u.objects.forEach(o => this.objectManager.updateObject(o.id, { x: Math.round(o.x + deltaX) }, false));
1515
+ });
1516
+ } else if (direction === 'top') {
1517
+ const minY = Math.min(...units.map(u => u.minY));
1518
+ units.forEach(u => {
1519
+ const deltaY = minY - u.minY;
1520
+ u.objects.forEach(o => this.objectManager.updateObject(o.id, { y: Math.round(o.y + deltaY) }, false));
1521
+ });
1522
+ } else if (direction === 'bottom') {
1523
+ const maxB = Math.max(...units.map(u => u.maxY));
1524
+ units.forEach(u => {
1525
+ const deltaY = (maxB - u.height) - u.minY;
1526
+ u.objects.forEach(o => this.objectManager.updateObject(o.id, { y: Math.round(o.y + deltaY) }, false));
1527
+ });
1528
+ } else if (direction === 'middle') {
1529
+ const avgY = units.reduce((sum, u) => sum + (u.minY + u.height / 2), 0) / units.length;
1530
+ units.forEach(u => {
1531
+ const deltaY = Math.round(avgY - u.height / 2) - u.minY;
1532
+ u.objects.forEach(o => this.objectManager.updateObject(o.id, { y: Math.round(o.y + deltaY) }, false));
1533
+ });
1534
+ }
1535
+ }
1536
+
1537
+ if (this.core && this.core.pushHistoryState) {
1538
+ this.core.pushHistoryState('Nesneleri Hizala');
1539
+ }
1540
+ this.selectionManager.updateSelectionBox();
1541
+ }
1542
+ }
1543
+
1544
+ // Global namespace
1545
+ window.DND = window.DND || {};
1546
+ window.DND.MiniToolbar = MiniToolbar;