@sequent-org/ifc-viewer 1.2.4-ci.55.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.55.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 };
@@ -96,10 +111,23 @@ export class LabelPlacementController {
96
111
  try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture, { capture: true }); } catch (_) {
97
112
  try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture); } catch (_) {}
98
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 (_) {}
99
117
  try { window.removeEventListener("keydown", this._onKeyDown); } catch (_) {}
100
118
  try { window.removeEventListener("resize", this._onWindowResize); } catch (_) {}
101
119
  try { window.removeEventListener("scroll", this._onWindowScroll, true); } catch (_) {}
102
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 (_) {}
103
131
  try { this._ui?.btn?.removeEventListener("click", this._onBtnClick); } catch (_) {}
104
132
  try { this._ui?.menu?.removeEventListener("pointerdown", this._onMenuPointerDown); } catch (_) {}
105
133
  try { this._ui?.menu?.removeEventListener("click", this._onMenuClick); } catch (_) {}
@@ -116,6 +144,7 @@ export class LabelPlacementController {
116
144
  this._markers.length = 0;
117
145
 
118
146
  try { this._ui?.ghost?.remove?.(); } catch (_) {}
147
+ try { this._ui?.dragGhost?.remove?.(); } catch (_) {}
119
148
  try { this._ui?.btn?.remove?.(); } catch (_) {}
120
149
  try { this._ui?.menu?.remove?.(); } catch (_) {}
121
150
  try { this._ui?.canvasMenu?.remove?.(); } catch (_) {}
@@ -543,6 +572,20 @@ export class LabelPlacementController {
543
572
  ghost.appendChild(dot);
544
573
  ghost.appendChild(num);
545
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
+
546
589
  const menu = document.createElement("div");
547
590
  menu.className = "ifc-label-menu";
548
591
  menu.style.display = "none";
@@ -583,13 +626,14 @@ export class LabelPlacementController {
583
626
 
584
627
  canvasMenu.appendChild(menuAdd);
585
628
 
586
- return { btn, ghost, dot, num, menu, canvasMenu };
629
+ return { btn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu };
587
630
  }
588
631
 
589
632
  #attachUi() {
590
633
  // Важно: container должен быть position:relative (в index.html уже так).
591
634
  this.container.appendChild(this._ui.btn);
592
635
  this.container.appendChild(this._ui.ghost);
636
+ this.container.appendChild(this._ui.dragGhost);
593
637
  this.container.appendChild(this._ui.menu);
594
638
  this.container.appendChild(this._ui.canvasMenu);
595
639
  }
@@ -705,9 +749,64 @@ export class LabelPlacementController {
705
749
  // scroll слушаем в capture, чтобы ловить скролл вложенных контейнеров
706
750
  window.addEventListener("scroll", this._onWindowScroll, true);
707
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
+
708
799
  const dom = this.viewer?.renderer?.domElement;
709
800
  if (!dom) return;
710
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
+
711
810
  this._onPointerMove = (e) => {
712
811
  if (!this._placing) return;
713
812
  this.#updateGhostFromClient(e.clientX, e.clientY);
@@ -804,6 +903,117 @@ export class LabelPlacementController {
804
903
  this.#updateGhostFromClient(this._lastPointer.x, this._lastPointer.y);
805
904
  }
806
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
+
807
1017
  #pickModelPoint(clientX, clientY) {
808
1018
  const model = this.viewer?.activeModel;
809
1019
  const camera = this.viewer?.camera;
@@ -900,6 +1110,18 @@ export class LabelPlacementController {
900
1110
  return this._markers.find((m) => String(m?.id) === String(this._selectedId)) || null;
901
1111
  }
902
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
+
903
1125
  #buildActionPayload(marker) {
904
1126
  if (!marker) return null;
905
1127
  return {
@@ -1019,24 +1241,78 @@ export class LabelPlacementController {
1019
1241
  try { e.preventDefault(); } catch (_) {}
1020
1242
  try { e.stopPropagation(); } catch (_) {}
1021
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 (_) {}
1022
1256
  // если были в режиме постановки — выходим
1023
1257
  try { this.cancelPlacement(); } catch (_) {}
1024
1258
 
1025
1259
  if (e.button === 0) {
1026
1260
  this.#closeContextMenu();
1027
1261
  this.#setSelectedMarker(marker);
1028
- this.#dispatchLabelEvent("ifcviewer:label-click", {
1029
- id: marker.id,
1030
- sceneState: marker.sceneState || null,
1031
- }, "ifcviewer:card-click");
1032
- // "Долеталка" камеры: быстрый старт + мягкий конец
1033
- this.#animateToSceneState(marker.sceneState, 550);
1262
+ this.#beginLabelDrag(marker, e);
1034
1263
  }
1035
1264
  };
1036
1265
  // capture-phase, чтобы обогнать любые handlers на canvas
1037
1266
  try { marker.el.addEventListener("pointerdown", onMarkerPointerDown, { capture: true, passive: false }); } catch (_) {
1038
1267
  try { marker.el.addEventListener("pointerdown", onMarkerPointerDown); } catch (_) {}
1039
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 (_) {}
1040
1316
 
1041
1317
  const onMarkerContextMenu = (e) => {
1042
1318
  try { e.preventDefault(); } catch (_) {}