@sequent-org/moodboard 1.2.100 → 1.2.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/moodboard/MoodBoard.js +18 -0
- package/src/ui/HtmlHandlesLayer.js +73 -37
package/package.json
CHANGED
|
@@ -477,6 +477,14 @@ export class MoodBoard {
|
|
|
477
477
|
|
|
478
478
|
this._safeDestroy(this.alignmentGuides, 'alignmentGuides');
|
|
479
479
|
this.alignmentGuides = null;
|
|
480
|
+
|
|
481
|
+
// HTML-слои (текст и ручки) также нужно корректно уничтожать,
|
|
482
|
+
// чтобы удалить DOM и отписаться от глобальных слушателей resize/DPR
|
|
483
|
+
this._safeDestroy(this.htmlTextLayer, 'htmlTextLayer');
|
|
484
|
+
this.htmlTextLayer = null;
|
|
485
|
+
|
|
486
|
+
this._safeDestroy(this.htmlHandlesLayer, 'htmlHandlesLayer');
|
|
487
|
+
this.htmlHandlesLayer = null;
|
|
480
488
|
|
|
481
489
|
this._safeDestroy(this.commentPopover, 'commentPopover');
|
|
482
490
|
this.commentPopover = null;
|
|
@@ -507,6 +515,16 @@ export class MoodBoard {
|
|
|
507
515
|
this.container.classList.remove('moodboard-root');
|
|
508
516
|
}
|
|
509
517
|
this.container = null;
|
|
518
|
+
|
|
519
|
+
// Сбрасываем глобальные ссылки на слои, если они устанавливались
|
|
520
|
+
if (typeof window !== 'undefined') {
|
|
521
|
+
if (window.moodboardHtmlTextLayer === this.htmlTextLayer) {
|
|
522
|
+
window.moodboardHtmlTextLayer = null;
|
|
523
|
+
}
|
|
524
|
+
if (window.moodboardHtmlHandlesLayer === this.htmlHandlesLayer) {
|
|
525
|
+
window.moodboardHtmlHandlesLayer = null;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
510
528
|
|
|
511
529
|
// Вызываем коллбек onDestroy
|
|
512
530
|
if (typeof this.options.onDestroy === 'function') {
|
|
@@ -22,7 +22,12 @@ export class HtmlHandlesLayer {
|
|
|
22
22
|
this.target = { type: 'none', id: null, bounds: null };
|
|
23
23
|
this.handles = {};
|
|
24
24
|
this._drag = null;
|
|
25
|
-
|
|
25
|
+
this._handlesSuppressed = false; // скрывать ручки во время перетаскивания/трансформаций
|
|
26
|
+
|
|
27
|
+
// Ссылки на обработчики, чтобы корректно отписаться при destroy()
|
|
28
|
+
this._onWindowResize = null;
|
|
29
|
+
this._onDprChange = null;
|
|
30
|
+
this._dprMediaQuery = null;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
attach() {
|
|
@@ -31,16 +36,19 @@ export class HtmlHandlesLayer {
|
|
|
31
36
|
this.container.appendChild(this.layer);
|
|
32
37
|
|
|
33
38
|
// Обновление при изменении размеров окна/масштаба (DPR)
|
|
34
|
-
|
|
39
|
+
this._onWindowResize = () => this.update();
|
|
40
|
+
window.addEventListener('resize', this._onWindowResize, { passive: true });
|
|
35
41
|
// Некоторые браузеры меняют devicePixelRatio без resize — страхуемся
|
|
36
42
|
if (typeof window !== 'undefined' && 'matchMedia' in window) {
|
|
37
43
|
try {
|
|
38
44
|
// media-query, реагирующая на изменение DPR
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
const mq = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);
|
|
46
|
+
this._dprMediaQuery = mq;
|
|
47
|
+
this._onDprChange = () => this.update();
|
|
48
|
+
if (mq && mq.addEventListener) {
|
|
49
|
+
mq.addEventListener('change', this._onDprChange);
|
|
50
|
+
} else if (mq && mq.addListener) {
|
|
51
|
+
mq.addListener(this._onDprChange);
|
|
44
52
|
}
|
|
45
53
|
} catch (_) {}
|
|
46
54
|
}
|
|
@@ -102,12 +110,34 @@ export class HtmlHandlesLayer {
|
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
destroy() {
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
// Отписываемся от глобальных событий окна/DPR,
|
|
114
|
+
// чтобы старые инстансы не продолжали реагировать после destroy()
|
|
115
|
+
if (this._onWindowResize) {
|
|
116
|
+
window.removeEventListener('resize', this._onWindowResize);
|
|
117
|
+
this._onWindowResize = null;
|
|
118
|
+
}
|
|
119
|
+
if (this._dprMediaQuery && this._onDprChange) {
|
|
120
|
+
try {
|
|
121
|
+
if (this._dprMediaQuery.removeEventListener) {
|
|
122
|
+
this._dprMediaQuery.removeEventListener('change', this._onDprChange);
|
|
123
|
+
} else if (this._dprMediaQuery.removeListener) {
|
|
124
|
+
this._dprMediaQuery.removeListener(this._onDprChange);
|
|
125
|
+
}
|
|
126
|
+
} catch (_) {}
|
|
127
|
+
this._dprMediaQuery = null;
|
|
128
|
+
this._onDprChange = null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (this.layer) {
|
|
132
|
+
this.layer.remove();
|
|
133
|
+
}
|
|
134
|
+
this.layer = null;
|
|
107
135
|
}
|
|
108
136
|
|
|
109
137
|
update() {
|
|
110
|
-
|
|
138
|
+
// Дополнительная защита: если слой или core уже уничтожены,
|
|
139
|
+
// выходим, чтобы не получить ошибок при resize/смене DPR
|
|
140
|
+
if (!this.core || !this.core.pixi || !this.core.pixi.app || !this.layer) return;
|
|
111
141
|
const selectTool = this.core?.selectTool;
|
|
112
142
|
const ids = selectTool ? Array.from(selectTool.selectedObjects || []) : [];
|
|
113
143
|
if (!ids || ids.length === 0) { this.hide(); return; }
|
|
@@ -224,10 +254,13 @@ export class HtmlHandlesLayer {
|
|
|
224
254
|
isFrameTarget = mbType === 'frame';
|
|
225
255
|
}
|
|
226
256
|
|
|
227
|
-
// Вычисляем позицию и размер через математику сцены (toGlobal) и переводим в CSS px
|
|
257
|
+
// Вычисляем позицию и размер через математику сцены (toGlobal) и переводим в CSS px.
|
|
258
|
+
// Важно: toGlobal() уже возвращает координаты в логических экранных пикселях
|
|
259
|
+
// (учитывая позицию/масштаб world), поэтому ДЕЛИТЬ их на renderer.resolution не нужно.
|
|
260
|
+
// Деление приводило к эффекту 1 / resolution (например, при res = 0.8 рамка
|
|
261
|
+
// становилась больше и съезжала относительно объекта).
|
|
228
262
|
const tl = world.toGlobal(new PIXI.Point(worldBounds.x, worldBounds.y));
|
|
229
263
|
const br = world.toGlobal(new PIXI.Point(worldBounds.x + worldBounds.width, worldBounds.y + worldBounds.height));
|
|
230
|
-
// Используем координаты в CSS-пикселях напрямую (Pixi toGlobal выдаёт экранные px)
|
|
231
264
|
const cssX = offsetLeft + tl.x;
|
|
232
265
|
const cssY = offsetTop + tl.y;
|
|
233
266
|
const cssWidth = Math.max(1, (br.x - tl.x));
|
|
@@ -405,6 +438,7 @@ export class HtmlHandlesLayer {
|
|
|
405
438
|
const s = world?.scale?.x || 1;
|
|
406
439
|
const tx = world?.x || 0;
|
|
407
440
|
const ty = world?.y || 0;
|
|
441
|
+
const rendererRes = (this.core.pixi.app.renderer?.resolution) || 1;
|
|
408
442
|
const containerRect = this.container.getBoundingClientRect();
|
|
409
443
|
const view = this.core.pixi.app.view;
|
|
410
444
|
const viewRect = view.getBoundingClientRect();
|
|
@@ -423,11 +457,12 @@ export class HtmlHandlesLayer {
|
|
|
423
457
|
w: startCSS.width,
|
|
424
458
|
h: startCSS.height,
|
|
425
459
|
};
|
|
460
|
+
// Экранные координаты (CSS) → device-пиксели → world
|
|
426
461
|
const startWorld = {
|
|
427
|
-
x: (startScreen.x - tx) / s,
|
|
428
|
-
y: (startScreen.y - ty) / s,
|
|
429
|
-
width: startScreen.w / s,
|
|
430
|
-
height: startScreen.h / s,
|
|
462
|
+
x: ((startScreen.x * rendererRes) - tx) / s,
|
|
463
|
+
y: ((startScreen.y * rendererRes) - ty) / s,
|
|
464
|
+
width: (startScreen.w * rendererRes) / s,
|
|
465
|
+
height: (startScreen.h * rendererRes) / s,
|
|
431
466
|
};
|
|
432
467
|
|
|
433
468
|
let objects = [id];
|
|
@@ -568,10 +603,10 @@ export class HtmlHandlesLayer {
|
|
|
568
603
|
const screenY = (newTop - offsetTop);
|
|
569
604
|
const screenW = newW;
|
|
570
605
|
const screenH = newH;
|
|
571
|
-
const worldX = (screenX - tx) / s;
|
|
572
|
-
const worldY = (screenY - ty) / s;
|
|
573
|
-
const worldW = screenW / s;
|
|
574
|
-
const worldH = screenH / s;
|
|
606
|
+
const worldX = ((screenX * rendererRes) - tx) / s;
|
|
607
|
+
const worldY = ((screenY * rendererRes) - ty) / s;
|
|
608
|
+
const worldW = (screenW * rendererRes) / s;
|
|
609
|
+
const worldH = (screenH * rendererRes) / s;
|
|
575
610
|
|
|
576
611
|
// Определяем, изменилась ли позиция (только для левых/верхних ручек)
|
|
577
612
|
const positionChanged = (newLeft !== startCSS.left) || (newTop !== startCSS.top);
|
|
@@ -616,10 +651,10 @@ export class HtmlHandlesLayer {
|
|
|
616
651
|
const screenY = (endCSS.top - offsetTop);
|
|
617
652
|
const screenW = endCSS.width;
|
|
618
653
|
const screenH = endCSS.height;
|
|
619
|
-
const worldX = (screenX - tx) / s;
|
|
620
|
-
const worldY = (screenY - ty) / s;
|
|
621
|
-
const worldW = screenW / s;
|
|
622
|
-
const worldH = screenH / s;
|
|
654
|
+
const worldX = ((screenX * rendererRes) - tx) / s;
|
|
655
|
+
const worldY = ((screenY * rendererRes) - ty) / s;
|
|
656
|
+
const worldW = (screenW * rendererRes) / s;
|
|
657
|
+
const worldH = (screenH * rendererRes) / s;
|
|
623
658
|
|
|
624
659
|
if (isGroup) {
|
|
625
660
|
this.eventBus.emit(Events.Tool.GroupResizeEnd, { objects });
|
|
@@ -681,6 +716,7 @@ export class HtmlHandlesLayer {
|
|
|
681
716
|
const s = world?.scale?.x || 1;
|
|
682
717
|
const tx = world?.x || 0;
|
|
683
718
|
const ty = world?.y || 0;
|
|
719
|
+
const rendererRes = (this.core.pixi.app.renderer?.resolution) || 1;
|
|
684
720
|
const containerRect = this.container.getBoundingClientRect();
|
|
685
721
|
const view = this.core.pixi.app.view;
|
|
686
722
|
const viewRect = view.getBoundingClientRect();
|
|
@@ -701,10 +737,10 @@ export class HtmlHandlesLayer {
|
|
|
701
737
|
h: startCSS.height,
|
|
702
738
|
};
|
|
703
739
|
const startWorld = {
|
|
704
|
-
x: (startScreen.x - tx) / s,
|
|
705
|
-
y: (startScreen.y - ty) / s,
|
|
706
|
-
width: startScreen.w / s,
|
|
707
|
-
height: startScreen.h / s,
|
|
740
|
+
x: ((startScreen.x * rendererRes) - tx) / s,
|
|
741
|
+
y: ((startScreen.y * rendererRes) - ty) / s,
|
|
742
|
+
width: (startScreen.w * rendererRes) / s,
|
|
743
|
+
height: (startScreen.h * rendererRes) / s,
|
|
708
744
|
};
|
|
709
745
|
|
|
710
746
|
let objects = [id];
|
|
@@ -830,10 +866,10 @@ export class HtmlHandlesLayer {
|
|
|
830
866
|
const screenY = (newTop - offsetTop);
|
|
831
867
|
const screenW = newW;
|
|
832
868
|
const screenH = newH;
|
|
833
|
-
const worldX = (screenX - tx) / s;
|
|
834
|
-
const worldY = (screenY - ty) / s;
|
|
835
|
-
const worldW = screenW / s;
|
|
836
|
-
const worldH = screenH / s;
|
|
869
|
+
const worldX = ((screenX * rendererRes) - tx) / s;
|
|
870
|
+
const worldY = ((screenY * rendererRes) - ty) / s;
|
|
871
|
+
const worldW = (screenW * rendererRes) / s;
|
|
872
|
+
const worldH = (screenH * rendererRes) / s;
|
|
837
873
|
|
|
838
874
|
// Определяем, изменилась ли позиция (только для левых/верхних граней)
|
|
839
875
|
const edgePositionChanged = (newLeft !== startCSS.left) || (newTop !== startCSS.top);
|
|
@@ -867,10 +903,10 @@ export class HtmlHandlesLayer {
|
|
|
867
903
|
const screenY = (endCSS.top - offsetTop);
|
|
868
904
|
const screenW = endCSS.width;
|
|
869
905
|
const screenH = endCSS.height;
|
|
870
|
-
const worldX = (screenX - tx) / s;
|
|
871
|
-
const worldY = (screenY - ty) / s;
|
|
872
|
-
const worldW = screenW / s;
|
|
873
|
-
const worldH = screenH / s;
|
|
906
|
+
const worldX = ((screenX * rendererRes) - tx) / s;
|
|
907
|
+
const worldY = ((screenY * rendererRes) - ty) / s;
|
|
908
|
+
const worldW = (screenW * rendererRes) / s;
|
|
909
|
+
const worldH = (screenH * rendererRes) / s;
|
|
874
910
|
|
|
875
911
|
if (isGroup) {
|
|
876
912
|
this.eventBus.emit(Events.Tool.GroupResizeEnd, { objects });
|
|
@@ -888,7 +924,7 @@ export class HtmlHandlesLayer {
|
|
|
888
924
|
el.style.width = `${Math.max(1, Math.round(endCSS.width))}px`;
|
|
889
925
|
el.style.height = 'auto';
|
|
890
926
|
const measured = Math.max(1, Math.round(el.scrollHeight));
|
|
891
|
-
finalWorldH = (measured) / s;
|
|
927
|
+
finalWorldH = (measured * rendererRes) / s;
|
|
892
928
|
}
|
|
893
929
|
} catch (_) {}
|
|
894
930
|
}
|