@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequent-org/moodboard",
3
- "version": "1.2.100",
3
+ "version": "1.2.102",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
@@ -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
- this._handlesSuppressed = false; // скрывать ручки во время перетаскивания/трансформаций
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
- window.addEventListener('resize', () => this.update(), { passive: true });
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
- const mq = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);
40
- if (mq && mq.addEventListener) {
41
- mq.addEventListener('change', () => this.update());
42
- } else if (mq && mq.addListener) {
43
- mq.addListener(() => this.update());
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
- if (this.layer) this.layer.remove();
106
- this.layer = null;
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
- if (!this.core) return;
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
  }