@sequent-org/moodboard 1.2.101 → 1.2.103
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 +48 -15
- package/src/ui/HtmlTextLayer.js +9 -6
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; }
|
|
@@ -225,13 +255,16 @@ export class HtmlHandlesLayer {
|
|
|
225
255
|
}
|
|
226
256
|
|
|
227
257
|
// Вычисляем позицию и размер через математику сцены (toGlobal) и переводим в CSS px.
|
|
228
|
-
// toGlobal() возвращает координаты в
|
|
258
|
+
// Важно: toGlobal() уже возвращает координаты в логических экранных пикселях
|
|
259
|
+
// (учитывая позицию/масштаб world), поэтому ДЕЛИТЬ их на renderer.resolution не нужно.
|
|
260
|
+
// Деление приводило к эффекту 1 / resolution (например, при res = 0.8 рамка
|
|
261
|
+
// становилась больше и съезжала относительно объекта).
|
|
229
262
|
const tl = world.toGlobal(new PIXI.Point(worldBounds.x, worldBounds.y));
|
|
230
263
|
const br = world.toGlobal(new PIXI.Point(worldBounds.x + worldBounds.width, worldBounds.y + worldBounds.height));
|
|
231
|
-
const cssX = offsetLeft + tl.x
|
|
232
|
-
const cssY = offsetTop + tl.y
|
|
233
|
-
const cssWidth = Math.max(1, (br.x - tl.x)
|
|
234
|
-
const cssHeight = Math.max(1, (br.y - tl.y)
|
|
264
|
+
const cssX = offsetLeft + tl.x;
|
|
265
|
+
const cssY = offsetTop + tl.y;
|
|
266
|
+
const cssWidth = Math.max(1, (br.x - tl.x));
|
|
267
|
+
const cssHeight = Math.max(1, (br.y - tl.y));
|
|
235
268
|
|
|
236
269
|
const left = Math.round(cssX);
|
|
237
270
|
const top = Math.round(cssY);
|
package/src/ui/HtmlTextLayer.js
CHANGED
|
@@ -325,12 +325,15 @@ export class HtmlTextLayer {
|
|
|
325
325
|
const tl = worldLayer.toGlobal(new PIXI.Point(x, y));
|
|
326
326
|
const br = worldLayer.toGlobal(new PIXI.Point(x + w, y + h));
|
|
327
327
|
|
|
328
|
-
// toGlobal() возвращает координаты в
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const
|
|
328
|
+
// ВАЖНО: toGlobal() уже возвращает координаты в логических экранных пикселях
|
|
329
|
+
// (как и для HtmlHandlesLayer), поэтому делить их на renderer.resolution
|
|
330
|
+
// при вычислении CSS-позиции и размеров не нужно. Деление приводило к тому,
|
|
331
|
+
// что при res < 1 (масштаб браузера ≠ 100%) HTML-текст уезжал относительно
|
|
332
|
+
// собственных рамок и PIXI-объекта.
|
|
333
|
+
const left = offsetLeft + tl.x;
|
|
334
|
+
const top = offsetTop + tl.y;
|
|
335
|
+
const width = Math.max(1, (br.x - tl.x));
|
|
336
|
+
const height = Math.max(1, (br.y - tl.y));
|
|
334
337
|
|
|
335
338
|
// Применяем к элементу
|
|
336
339
|
el.style.left = `${left}px`;
|