@sequent-org/ifc-viewer 1.2.4-ci.54.0 → 1.2.4-ci.56.0

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@sequent-org/ifc-viewer",
3
3
  "private": false,
4
- "version": "1.2.4-ci.54.0",
4
+ "version": "1.2.4-ci.56.0",
5
5
  "type": "module",
6
6
  "description": "IFC 3D model viewer component for web applications - fully self-contained with local IFCLoader",
7
7
  "main": "src/index.js",
@@ -50,6 +50,21 @@ export class LabelPlacementController {
50
50
  this._ghostPos = { x: 0, y: 0 };
51
51
  this._raf = 0;
52
52
 
53
+ this._labelDrag = {
54
+ active: false,
55
+ moved: false,
56
+ pointerId: null,
57
+ id: null,
58
+ start: { x: 0, y: 0 },
59
+ last: { x: 0, y: 0 },
60
+ ghostPos: { x: 0, y: 0 },
61
+ clickMarker: null,
62
+ prevControlsEnabled: null,
63
+ threshold: 4,
64
+ };
65
+
66
+ this._labelDragDropSelector = deps?.labelDragDropSelector || null;
67
+
53
68
  this._controlsWasEnabled = null;
54
69
 
55
70
  this._containerOffset = { left: 0, top: 0 };
@@ -59,6 +74,10 @@ export class LabelPlacementController {
59
74
  open: false,
60
75
  marker: null,
61
76
  };
77
+ this._canvasMenu = {
78
+ open: false,
79
+ hit: null,
80
+ };
62
81
 
63
82
  this._fly = {
64
83
  raf: 0,
@@ -92,13 +111,31 @@ export class LabelPlacementController {
92
111
  try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture, { capture: true }); } catch (_) {
93
112
  try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture); } catch (_) {}
94
113
  }
114
+ try { dom?.removeEventListener("pointerdown", this._onDbgDomPointerDown); } catch (_) {}
115
+ try { dom?.removeEventListener("pointermove", this._onDbgDomPointerMove); } catch (_) {}
116
+ try { dom?.removeEventListener("pointerup", this._onDbgDomPointerUp); } catch (_) {}
95
117
  try { window.removeEventListener("keydown", this._onKeyDown); } catch (_) {}
96
118
  try { window.removeEventListener("resize", this._onWindowResize); } catch (_) {}
97
119
  try { window.removeEventListener("scroll", this._onWindowScroll, true); } catch (_) {}
98
120
  try { window.removeEventListener("pointerdown", this._onWindowPointerDown, true); } catch (_) {}
121
+ try { document.removeEventListener("pointerdown", this._onDbgDocPointerDown); } catch (_) {}
122
+ try { document.removeEventListener("pointermove", this._onDbgDocPointerMove); } catch (_) {}
123
+ try { document.removeEventListener("pointerup", this._onDbgDocPointerUp); } catch (_) {}
124
+ try { document.removeEventListener("dragstart", this._onDbgDocDragStart); } catch (_) {}
125
+ try { document.removeEventListener("dragend", this._onDbgDocDragEnd); } catch (_) {}
126
+ try { document.removeEventListener("dragover", this._onDbgDocDragOver); } catch (_) {}
127
+ try { document.removeEventListener("drop", this._onDbgDocDrop); } catch (_) {}
128
+ try { document.removeEventListener("pointermove", this._onLabelDragPointerMove); } catch (_) {}
129
+ try { document.removeEventListener("pointerup", this._onLabelDragPointerUp); } catch (_) {}
130
+ try { document.removeEventListener("pointercancel", this._onLabelDragPointerCancel); } catch (_) {}
99
131
  try { this._ui?.btn?.removeEventListener("click", this._onBtnClick); } catch (_) {}
100
132
  try { this._ui?.menu?.removeEventListener("pointerdown", this._onMenuPointerDown); } catch (_) {}
101
133
  try { this._ui?.menu?.removeEventListener("click", this._onMenuClick); } catch (_) {}
134
+ try { this._ui?.canvasMenu?.removeEventListener("pointerdown", this._onCanvasMenuPointerDown); } catch (_) {}
135
+ try { this._ui?.canvasMenu?.removeEventListener("click", this._onCanvasMenuClick); } catch (_) {}
136
+ try { dom?.removeEventListener("contextmenu", this._onCanvasContextMenu, { capture: true }); } catch (_) {
137
+ try { dom?.removeEventListener("contextmenu", this._onCanvasContextMenu); } catch (_) {}
138
+ }
102
139
 
103
140
  if (this._raf) cancelAnimationFrame(this._raf);
104
141
  this._raf = 0;
@@ -107,8 +144,10 @@ export class LabelPlacementController {
107
144
  this._markers.length = 0;
108
145
 
109
146
  try { this._ui?.ghost?.remove?.(); } catch (_) {}
147
+ try { this._ui?.dragGhost?.remove?.(); } catch (_) {}
110
148
  try { this._ui?.btn?.remove?.(); } catch (_) {}
111
149
  try { this._ui?.menu?.remove?.(); } catch (_) {}
150
+ try { this._ui?.canvasMenu?.remove?.(); } catch (_) {}
112
151
  }
113
152
 
114
153
  startPlacement() {
@@ -533,6 +572,20 @@ export class LabelPlacementController {
533
572
  ghost.appendChild(dot);
534
573
  ghost.appendChild(num);
535
574
 
575
+ const dragGhost = document.createElement("div");
576
+ dragGhost.className = "ifc-label-ghost ifc-label-ghost--drag";
577
+ dragGhost.setAttribute("aria-hidden", "true");
578
+ dragGhost.style.display = "none";
579
+ dragGhost.style.left = "0px";
580
+ dragGhost.style.top = "0px";
581
+
582
+ const dragDot = document.createElement("div");
583
+ dragDot.className = "ifc-label-dot";
584
+ const dragNum = document.createElement("div");
585
+ dragNum.className = "ifc-label-num";
586
+ dragGhost.appendChild(dragDot);
587
+ dragGhost.appendChild(dragNum);
588
+
536
589
  const menu = document.createElement("div");
537
590
  menu.className = "ifc-label-menu";
538
591
  menu.style.display = "none";
@@ -560,14 +613,29 @@ export class LabelPlacementController {
560
613
  menu.appendChild(menuMove);
561
614
  menu.appendChild(menuDelete);
562
615
 
563
- return { btn, ghost, dot, num, menu };
616
+ const canvasMenu = document.createElement("div");
617
+ canvasMenu.className = "ifc-label-menu";
618
+ canvasMenu.style.display = "none";
619
+ canvasMenu.setAttribute("role", "menu");
620
+
621
+ const menuAdd = document.createElement("button");
622
+ menuAdd.type = "button";
623
+ menuAdd.className = "ifc-label-menu-item";
624
+ menuAdd.textContent = "Добавить метку";
625
+ menuAdd.setAttribute("data-action", "add");
626
+
627
+ canvasMenu.appendChild(menuAdd);
628
+
629
+ return { btn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu };
564
630
  }
565
631
 
566
632
  #attachUi() {
567
633
  // Важно: container должен быть position:relative (в index.html уже так).
568
634
  this.container.appendChild(this._ui.btn);
569
635
  this.container.appendChild(this._ui.ghost);
636
+ this.container.appendChild(this._ui.dragGhost);
570
637
  this.container.appendChild(this._ui.menu);
638
+ this.container.appendChild(this._ui.canvasMenu);
571
639
  }
572
640
 
573
641
  #bindEvents() {
@@ -600,6 +668,27 @@ export class LabelPlacementController {
600
668
  };
601
669
  this._ui.menu.addEventListener("click", this._onMenuClick);
602
670
 
671
+ this._onCanvasMenuPointerDown = (e) => {
672
+ // Не даём клику меню попасть в canvas/OrbitControls
673
+ try { e.preventDefault(); } catch (_) {}
674
+ try { e.stopPropagation(); } catch (_) {}
675
+ try { e.stopImmediatePropagation?.(); } catch (_) {}
676
+ };
677
+ this._ui.canvasMenu.addEventListener("pointerdown", this._onCanvasMenuPointerDown, { passive: false });
678
+
679
+ this._onCanvasMenuClick = (e) => {
680
+ const target = e.target;
681
+ const action = target?.getAttribute?.("data-action");
682
+ if (action !== "add") return;
683
+ try { e.preventDefault(); } catch (_) {}
684
+ try { e.stopPropagation(); } catch (_) {}
685
+
686
+ const hit = this._canvasMenu?.hit || null;
687
+ if (hit) this.#createMarkerAtHit(hit);
688
+ this.#closeCanvasMenu();
689
+ };
690
+ this._ui.canvasMenu.addEventListener("click", this._onCanvasMenuClick);
691
+
603
692
  this._onKeyDown = (e) => {
604
693
  if (this._placing) {
605
694
  if (e.key === "Escape") this.cancelPlacement();
@@ -634,10 +723,13 @@ export class LabelPlacementController {
634
723
  window.addEventListener("keydown", this._onKeyDown);
635
724
 
636
725
  this._onWindowPointerDown = (e) => {
637
- if (!this._contextMenu.open) return;
726
+ if (!this._contextMenu.open && !this._canvasMenu.open) return;
638
727
  const menu = this._ui?.menu;
728
+ const canvasMenu = this._ui?.canvasMenu;
639
729
  if (menu && menu.contains(e.target)) return;
730
+ if (canvasMenu && canvasMenu.contains(e.target)) return;
640
731
  this.#closeContextMenu();
732
+ this.#closeCanvasMenu();
641
733
  };
642
734
  window.addEventListener("pointerdown", this._onWindowPointerDown, true);
643
735
 
@@ -657,9 +749,64 @@ export class LabelPlacementController {
657
749
  // scroll слушаем в capture, чтобы ловить скролл вложенных контейнеров
658
750
  window.addEventListener("scroll", this._onWindowScroll, true);
659
751
 
752
+ const logDnDEvent = (scope, e) => {
753
+ const target = e?.target || null;
754
+ const closestMarker = (target && typeof target.closest === "function")
755
+ ? target.closest(".ifc-label-marker")
756
+ : null;
757
+ if (!closestMarker) return;
758
+ const payload = {
759
+ type: e?.type,
760
+ target,
761
+ closestMarker,
762
+ button: e?.button,
763
+ buttons: e?.buttons,
764
+ clientX: e?.clientX,
765
+ clientY: e?.clientY,
766
+ defaultPrevented: !!e?.defaultPrevented,
767
+ };
768
+ try {
769
+ this.logger?.log?.("[LabelDnD]", scope, payload);
770
+ } catch (_) {
771
+ try { console.log("[LabelDnD]", scope, payload); } catch (_) {}
772
+ }
773
+ };
774
+
775
+ this._onDbgDocPointerDown = (e) => logDnDEvent("document", e);
776
+ this._onDbgDocPointerMove = (e) => logDnDEvent("document", e);
777
+ this._onDbgDocPointerUp = (e) => logDnDEvent("document", e);
778
+ this._onDbgDocDragStart = (e) => logDnDEvent("document", e);
779
+ this._onDbgDocDragEnd = (e) => logDnDEvent("document", e);
780
+ this._onDbgDocDragOver = (e) => logDnDEvent("document", e);
781
+ this._onDbgDocDrop = (e) => logDnDEvent("document", e);
782
+
783
+ document.addEventListener("pointerdown", this._onDbgDocPointerDown);
784
+ document.addEventListener("pointermove", this._onDbgDocPointerMove);
785
+ document.addEventListener("pointerup", this._onDbgDocPointerUp);
786
+ document.addEventListener("dragstart", this._onDbgDocDragStart);
787
+ document.addEventListener("dragend", this._onDbgDocDragEnd);
788
+ document.addEventListener("dragover", this._onDbgDocDragOver);
789
+ document.addEventListener("drop", this._onDbgDocDrop);
790
+
791
+ this._onLabelDragPointerMove = (e) => this.#updateLabelDrag(e);
792
+ this._onLabelDragPointerUp = (e) => this.#finishLabelDrag(e, "pointerup");
793
+ this._onLabelDragPointerCancel = (e) => this.#finishLabelDrag(e, "pointercancel");
794
+
795
+ document.addEventListener("pointermove", this._onLabelDragPointerMove, { passive: true });
796
+ document.addEventListener("pointerup", this._onLabelDragPointerUp, { passive: true });
797
+ document.addEventListener("pointercancel", this._onLabelDragPointerCancel, { passive: true });
798
+
660
799
  const dom = this.viewer?.renderer?.domElement;
661
800
  if (!dom) return;
662
801
 
802
+ this._onDbgDomPointerDown = (e) => logDnDEvent("ifc3dHost", e);
803
+ this._onDbgDomPointerMove = (e) => logDnDEvent("ifc3dHost", e);
804
+ this._onDbgDomPointerUp = (e) => logDnDEvent("ifc3dHost", e);
805
+
806
+ dom.addEventListener("pointerdown", this._onDbgDomPointerDown, { passive: true });
807
+ dom.addEventListener("pointermove", this._onDbgDomPointerMove, { passive: true });
808
+ dom.addEventListener("pointerup", this._onDbgDomPointerUp, { passive: true });
809
+
663
810
  this._onPointerMove = (e) => {
664
811
  if (!this._placing) return;
665
812
  this.#updateGhostFromClient(e.clientX, e.clientY);
@@ -692,6 +839,25 @@ export class LabelPlacementController {
692
839
  this.cancelPlacement(); // по ТЗ: “вторым кликом устанавливаем”
693
840
  };
694
841
  dom.addEventListener("pointerdown", this._onPointerDownCapture, { capture: true, passive: false });
842
+
843
+ this._onCanvasContextMenu = (e) => {
844
+ // Контекстное меню добавления метки по ПКМ на модели (если нет метки под курсором).
845
+ try { e.preventDefault(); } catch (_) {}
846
+ try { e.stopPropagation(); } catch (_) {}
847
+ try { e.stopImmediatePropagation?.(); } catch (_) {}
848
+ try { this.cancelPlacement(); } catch (_) {}
849
+
850
+ const el = document.elementFromPoint?.(e.clientX, e.clientY);
851
+ if (el && el.closest?.(".ifc-label-marker")) return;
852
+ if (el && el.closest?.(".ifc-label-menu")) return;
853
+
854
+ const hit = this.#pickModelPoint(e.clientX, e.clientY);
855
+ if (!hit) return;
856
+
857
+ this.#closeContextMenu();
858
+ this.#openCanvasMenu(hit, e.clientX, e.clientY);
859
+ };
860
+ dom.addEventListener("contextmenu", this._onCanvasContextMenu, { capture: true, passive: false });
695
861
  }
696
862
 
697
863
  #setGhostVisible(visible) {
@@ -737,6 +903,117 @@ export class LabelPlacementController {
737
903
  this.#updateGhostFromClient(this._lastPointer.x, this._lastPointer.y);
738
904
  }
739
905
 
906
+ #setDragGhostVisible(visible) {
907
+ if (!this._ui?.dragGhost) return;
908
+ this._ui.dragGhost.style.display = visible ? "block" : "none";
909
+ }
910
+
911
+ #applyDragGhostTransform() {
912
+ const g = this._ui?.dragGhost;
913
+ if (!g) return;
914
+ g.style.transform = `translate3d(${this._labelDrag.ghostPos.x}px, ${this._labelDrag.ghostPos.y}px, 0) translate(-50%, -50%)`;
915
+ }
916
+
917
+ #updateDragGhostFromClient(clientX, clientY) {
918
+ this._labelDrag.last = { x: clientX, y: clientY };
919
+ if (!this._containerOffsetValid) this.#refreshContainerOffset();
920
+ const x = (clientX - this._containerOffset.left);
921
+ const y = (clientY - this._containerOffset.top);
922
+ this._labelDrag.ghostPos.x = x;
923
+ this._labelDrag.ghostPos.y = y;
924
+ this.#applyDragGhostTransform();
925
+ }
926
+
927
+ #beginLabelDrag(marker, e) {
928
+ if (!marker || !e || e.button !== 0) return;
929
+ this._labelDrag.active = true;
930
+ this._labelDrag.moved = false;
931
+ this._labelDrag.pointerId = e.pointerId ?? null;
932
+ this._labelDrag.id = marker.id;
933
+ this._labelDrag.start = { x: e.clientX, y: e.clientY };
934
+ this._labelDrag.last = { x: e.clientX, y: e.clientY };
935
+ this._labelDrag.clickMarker = marker;
936
+
937
+ try {
938
+ this._labelDrag.prevControlsEnabled = this.viewer?.controls?.enabled ?? null;
939
+ if (this.viewer?.controls) this.viewer.controls.enabled = false;
940
+ } catch (_) {}
941
+
942
+ if (this._ui?.dragNum) this._ui.dragNum.textContent = String(marker.id);
943
+ this.#setDragGhostVisible(false);
944
+
945
+ try { marker.el?.setPointerCapture?.(e.pointerId); } catch (_) {}
946
+
947
+ this.#dispatchLabelEvent("ifcviewer:label-drag-start", {
948
+ id: marker.id,
949
+ clientX: e.clientX,
950
+ clientY: e.clientY,
951
+ }, null);
952
+ }
953
+
954
+ #updateLabelDrag(e) {
955
+ if (!this._labelDrag.active || !e) return;
956
+ if (this._labelDrag.pointerId != null && e.pointerId != null && e.pointerId !== this._labelDrag.pointerId) return;
957
+
958
+ const dx = (e.clientX - this._labelDrag.start.x);
959
+ const dy = (e.clientY - this._labelDrag.start.y);
960
+ const dist = Math.hypot(dx, dy);
961
+ if (!this._labelDrag.moved && dist >= this._labelDrag.threshold) {
962
+ this._labelDrag.moved = true;
963
+ this.#setDragGhostVisible(true);
964
+ }
965
+
966
+ if (this._labelDrag.moved) {
967
+ this.#updateDragGhostFromClient(e.clientX, e.clientY);
968
+ }
969
+ }
970
+
971
+ #resolveDropTarget(clientX, clientY) {
972
+ const el = document.elementFromPoint?.(clientX, clientY) || null;
973
+ if (!el) return { element: null, card: null };
974
+ if (!this._labelDragDropSelector) return { element: el, card: null };
975
+ const card = el.closest?.(this._labelDragDropSelector) || null;
976
+ return { element: el, card };
977
+ }
978
+
979
+ #finishLabelDrag(e, reason = "pointerup") {
980
+ if (!this._labelDrag.active) return;
981
+ const marker = this._labelDrag.clickMarker;
982
+ const moved = !!this._labelDrag.moved;
983
+ const clientX = e?.clientX ?? this._labelDrag.last.x;
984
+ const clientY = e?.clientY ?? this._labelDrag.last.y;
985
+
986
+ this._labelDrag.active = false;
987
+ this._labelDrag.moved = false;
988
+ this._labelDrag.pointerId = null;
989
+ this._labelDrag.id = null;
990
+ this._labelDrag.clickMarker = null;
991
+
992
+ this.#setDragGhostVisible(false);
993
+
994
+ try {
995
+ if (this.viewer?.controls && this._labelDrag.prevControlsEnabled != null) {
996
+ this.viewer.controls.enabled = !!this._labelDrag.prevControlsEnabled;
997
+ }
998
+ } catch (_) {}
999
+ this._labelDrag.prevControlsEnabled = null;
1000
+
1001
+ if (!moved) {
1002
+ if (marker) this.#handleMarkerClick(marker);
1003
+ return;
1004
+ }
1005
+
1006
+ const drop = this.#resolveDropTarget(clientX, clientY);
1007
+ this.#dispatchLabelEvent("ifcviewer:label-drop", {
1008
+ id: marker?.id ?? this._labelDrag.id,
1009
+ clientX,
1010
+ clientY,
1011
+ dropTarget: drop.card || null,
1012
+ elementFromPoint: drop.element || null,
1013
+ reason,
1014
+ }, null);
1015
+ }
1016
+
740
1017
  #pickModelPoint(clientX, clientY) {
741
1018
  const model = this.viewer?.activeModel;
742
1019
  const camera = this.viewer?.camera;
@@ -833,6 +1110,18 @@ export class LabelPlacementController {
833
1110
  return this._markers.find((m) => String(m?.id) === String(this._selectedId)) || null;
834
1111
  }
835
1112
 
1113
+ #handleMarkerClick(marker) {
1114
+ if (!marker) return;
1115
+ this.#closeContextMenu();
1116
+ this.#setSelectedMarker(marker);
1117
+ this.#dispatchLabelEvent("ifcviewer:label-click", {
1118
+ id: marker.id,
1119
+ sceneState: marker.sceneState || null,
1120
+ }, "ifcviewer:card-click");
1121
+ // "Долеталка" камеры: быстрый старт + мягкий конец
1122
+ this.#animateToSceneState(marker.sceneState, 550);
1123
+ }
1124
+
836
1125
  #buildActionPayload(marker) {
837
1126
  if (!marker) return null;
838
1127
  return {
@@ -892,6 +1181,46 @@ export class LabelPlacementController {
892
1181
  menu.style.display = "none";
893
1182
  }
894
1183
 
1184
+ #openCanvasMenu(hit, clientX, clientY) {
1185
+ const menu = this._ui?.canvasMenu;
1186
+ if (!menu || !hit) return;
1187
+
1188
+ this.#closeCanvasMenu();
1189
+
1190
+ this._canvasMenu.open = true;
1191
+ this._canvasMenu.hit = hit;
1192
+
1193
+ if (!this._containerOffsetValid) this.#refreshContainerOffset();
1194
+ const x = clientX - this._containerOffset.left;
1195
+ const y = clientY - this._containerOffset.top;
1196
+
1197
+ menu.style.display = "block";
1198
+ menu.style.left = "0px";
1199
+ menu.style.top = "0px";
1200
+ menu.style.transform = `translate3d(${x}px, ${y}px, 0) translate(8px, 8px)`;
1201
+
1202
+ try {
1203
+ const rect = this.container?.getBoundingClientRect?.();
1204
+ const mw = menu.offsetWidth || 0;
1205
+ const mh = menu.offsetHeight || 0;
1206
+ if (rect && mw && mh) {
1207
+ let px = x + 8;
1208
+ let py = y + 8;
1209
+ if (px + mw > rect.width) px = Math.max(0, rect.width - mw - 4);
1210
+ if (py + mh > rect.height) py = Math.max(0, rect.height - mh - 4);
1211
+ menu.style.transform = `translate3d(${px}px, ${py}px, 0)`;
1212
+ }
1213
+ } catch (_) {}
1214
+ }
1215
+
1216
+ #closeCanvasMenu() {
1217
+ const menu = this._ui?.canvasMenu;
1218
+ if (!menu) return;
1219
+ this._canvasMenu.open = false;
1220
+ this._canvasMenu.hit = null;
1221
+ menu.style.display = "none";
1222
+ }
1223
+
895
1224
  #createMarkerFromData(data, emitPlacedEvent) {
896
1225
  if (!data) return null;
897
1226
  const localPoint = data.localPoint || {};
@@ -912,24 +1241,78 @@ export class LabelPlacementController {
912
1241
  try { e.preventDefault(); } catch (_) {}
913
1242
  try { e.stopPropagation(); } catch (_) {}
914
1243
  try { e.stopImmediatePropagation?.(); } catch (_) {}
1244
+ try {
1245
+ this.logger?.log?.("[LabelDnD]", "marker", {
1246
+ type: e?.type,
1247
+ target: e?.target || null,
1248
+ closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
1249
+ button: e?.button,
1250
+ buttons: e?.buttons,
1251
+ clientX: e?.clientX,
1252
+ clientY: e?.clientY,
1253
+ defaultPrevented: !!e?.defaultPrevented,
1254
+ });
1255
+ } catch (_) {}
915
1256
  // если были в режиме постановки — выходим
916
1257
  try { this.cancelPlacement(); } catch (_) {}
917
1258
 
918
1259
  if (e.button === 0) {
919
1260
  this.#closeContextMenu();
920
1261
  this.#setSelectedMarker(marker);
921
- this.#dispatchLabelEvent("ifcviewer:label-click", {
922
- id: marker.id,
923
- sceneState: marker.sceneState || null,
924
- }, "ifcviewer:card-click");
925
- // "Долеталка" камеры: быстрый старт + мягкий конец
926
- this.#animateToSceneState(marker.sceneState, 550);
1262
+ this.#beginLabelDrag(marker, e);
927
1263
  }
928
1264
  };
929
1265
  // capture-phase, чтобы обогнать любые handlers на canvas
930
1266
  try { marker.el.addEventListener("pointerdown", onMarkerPointerDown, { capture: true, passive: false }); } catch (_) {
931
1267
  try { marker.el.addEventListener("pointerdown", onMarkerPointerDown); } catch (_) {}
932
1268
  }
1269
+ const onMarkerPointerUp = (e) => {
1270
+ try {
1271
+ this.logger?.log?.("[LabelDnD]", "marker", {
1272
+ type: e?.type,
1273
+ target: e?.target || null,
1274
+ closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
1275
+ button: e?.button,
1276
+ buttons: e?.buttons,
1277
+ clientX: e?.clientX,
1278
+ clientY: e?.clientY,
1279
+ defaultPrevented: !!e?.defaultPrevented,
1280
+ });
1281
+ } catch (_) {}
1282
+ };
1283
+ try { marker.el.addEventListener("pointerup", onMarkerPointerUp, { capture: true, passive: true }); } catch (_) {
1284
+ try { marker.el.addEventListener("pointerup", onMarkerPointerUp); } catch (_) {}
1285
+ }
1286
+ const onMarkerDragStart = (e) => {
1287
+ try {
1288
+ this.logger?.log?.("[LabelDnD]", "marker", {
1289
+ type: e?.type,
1290
+ target: e?.target || null,
1291
+ closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
1292
+ button: e?.button,
1293
+ buttons: e?.buttons,
1294
+ clientX: e?.clientX,
1295
+ clientY: e?.clientY,
1296
+ defaultPrevented: !!e?.defaultPrevented,
1297
+ });
1298
+ } catch (_) {}
1299
+ };
1300
+ const onMarkerDragEnd = (e) => {
1301
+ try {
1302
+ this.logger?.log?.("[LabelDnD]", "marker", {
1303
+ type: e?.type,
1304
+ target: e?.target || null,
1305
+ closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
1306
+ button: e?.button,
1307
+ buttons: e?.buttons,
1308
+ clientX: e?.clientX,
1309
+ clientY: e?.clientY,
1310
+ defaultPrevented: !!e?.defaultPrevented,
1311
+ });
1312
+ } catch (_) {}
1313
+ };
1314
+ try { marker.el.addEventListener("dragstart", onMarkerDragStart); } catch (_) {}
1315
+ try { marker.el.addEventListener("dragend", onMarkerDragEnd); } catch (_) {}
933
1316
 
934
1317
  const onMarkerContextMenu = (e) => {
935
1318
  try { e.preventDefault(); } catch (_) {}
@@ -213,7 +213,7 @@ export class Viewer {
213
213
 
214
214
  // RMB: перемещение модели относительно "оси" (pivot), pivot остаётся на месте
215
215
  this._rmbModelMove = {
216
- enabled: true,
216
+ enabled: false,
217
217
  debug: false,
218
218
  controller: null,
219
219
  pivotAnchor: null, // THREE.Vector3|null (фиксированная ось после ПКМ)