worldorbit 3.0.2 → 3.0.6

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.
@@ -66,6 +66,7 @@ export function createInteractiveViewer(container, options) {
66
66
  let cameraRoot = null;
67
67
  let runtime3d = null;
68
68
  let minimapRoot = null;
69
+ let labelRoot = null;
69
70
  let tooltipRoot = null;
70
71
  let suppressClick = false;
71
72
  let activePointerId = null;
@@ -90,7 +91,7 @@ export function createInteractiveViewer(container, options) {
90
91
  if (previousTabIndex === null) {
91
92
  container.tabIndex = 0;
92
93
  }
93
- installViewerTooltipStyles();
94
+ installViewerOverlayStyles();
94
95
  container.classList.add("wo-viewer-container");
95
96
  container.style.touchAction = behavior.touch ? "none" : previousTouchAction;
96
97
  if (!container.style.position) {
@@ -671,6 +672,8 @@ export function createInteractiveViewer(container, options) {
671
672
  stopAnimationLoop();
672
673
  runtime3d?.destroy();
673
674
  runtime3d = null;
675
+ labelRoot?.remove();
676
+ labelRoot = null;
674
677
  tooltipRoot?.remove();
675
678
  tooltipRoot = null;
676
679
  minimapRoot?.remove();
@@ -704,6 +707,7 @@ export function createInteractiveViewer(container, options) {
704
707
  svgElement = null;
705
708
  cameraRoot = null;
706
709
  minimapRoot = null;
710
+ labelRoot = null;
707
711
  tooltipRoot = null;
708
712
  if (is3DView()) {
709
713
  spatialScene = spatialScene ?? renderSpatialSceneFromInput(currentInput, renderOptions, providedSpatialScene);
@@ -723,6 +727,10 @@ export function createInteractiveViewer(container, options) {
723
727
  minimapRoot.dataset.worldorbitMinimapRoot = "true";
724
728
  container.append(minimapRoot);
725
729
  }
730
+ labelRoot = document.createElement("div");
731
+ labelRoot.className = "wo-viewer-label-root";
732
+ labelRoot.dataset.worldorbitLabelRoot = "true";
733
+ container.append(labelRoot);
726
734
  if (behavior.tooltipMode !== "disabled") {
727
735
  tooltipRoot = document.createElement("div");
728
736
  tooltipRoot.className = "wo-viewer-tooltip-root";
@@ -734,6 +742,7 @@ export function createInteractiveViewer(container, options) {
734
742
  if (!is3DView() && (!svgElement || !cameraRoot)) {
735
743
  throw new Error("Interactive viewer could not locate the rendered SVG camera root.");
736
744
  }
745
+ suppressStaticLabelLayers();
737
746
  state = resetView
738
747
  ? is3DView()
739
748
  ? { ...DEFAULT_VIEWER_STATE }
@@ -783,14 +792,15 @@ export function createInteractiveViewer(container, options) {
783
792
  return;
784
793
  }
785
794
  cameraRoot.setAttribute("transform", composeViewerTransform(scene, state));
795
+ updateScreenLabels();
786
796
  updateMinimap();
787
797
  updateTooltip();
788
798
  }
789
799
  function applySelection(objectId, emitCallback = true) {
790
800
  if (!is3DView() && state.selectedObjectId) {
791
- container
792
- .querySelector(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)
793
- ?.classList.remove("wo-object-selected");
801
+ for (const element of container.querySelectorAll(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)) {
802
+ element.classList.remove("wo-object-selected");
803
+ }
794
804
  }
795
805
  state = {
796
806
  ...state,
@@ -799,9 +809,9 @@ export function createInteractiveViewer(container, options) {
799
809
  : null,
800
810
  };
801
811
  if (!is3DView() && state.selectedObjectId) {
802
- container
803
- .querySelector(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)
804
- ?.classList.add("wo-object-selected");
812
+ for (const element of container.querySelectorAll(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)) {
813
+ element.classList.add("wo-object-selected");
814
+ }
805
815
  }
806
816
  syncAtlasHighlights();
807
817
  updateTooltip();
@@ -1160,15 +1170,18 @@ export function createInteractiveViewer(container, options) {
1160
1170
  };
1161
1171
  }
1162
1172
  function project2DTooltipPoint(renderObject) {
1163
- if (!svgElement) {
1164
- return null;
1165
- }
1166
1173
  const anchor = {
1167
1174
  x: renderObject.anchorX ?? renderObject.x,
1168
1175
  y: renderObject.anchorY ??
1169
1176
  renderObject.y - Math.max(renderObject.visualRadius, renderObject.radius),
1170
1177
  };
1171
- const viewportPoint = projectWorldPoint(anchor);
1178
+ return project2DScenePointToContainer(anchor);
1179
+ }
1180
+ function project2DScenePointToContainer(point) {
1181
+ if (!svgElement) {
1182
+ return null;
1183
+ }
1184
+ const viewportPoint = projectWorldPoint(point);
1172
1185
  const svgRect = svgElement.getBoundingClientRect();
1173
1186
  const containerRect = container.getBoundingClientRect();
1174
1187
  return {
@@ -1274,9 +1287,151 @@ export function createInteractiveViewer(container, options) {
1274
1287
  state,
1275
1288
  timeSeconds: animationState.timeSeconds,
1276
1289
  });
1290
+ updateScreenLabels();
1277
1291
  updateMinimap();
1278
1292
  updateTooltip();
1279
1293
  }
1294
+ function suppressStaticLabelLayers() {
1295
+ if (is3DView()) {
1296
+ return;
1297
+ }
1298
+ container
1299
+ .querySelector('[data-layer-id="labels"]')
1300
+ ?.setAttribute("display", "none");
1301
+ for (const element of container.querySelectorAll(".wo-event-label")) {
1302
+ element.setAttribute("display", "none");
1303
+ }
1304
+ }
1305
+ function updateScreenLabels() {
1306
+ if (!labelRoot) {
1307
+ return;
1308
+ }
1309
+ const descriptors = buildScreenLabelDescriptors();
1310
+ labelRoot.replaceChildren(...descriptors.map((descriptor) => createScreenLabelElement(descriptor)));
1311
+ labelRoot.hidden = descriptors.length === 0;
1312
+ }
1313
+ function buildScreenLabelDescriptors() {
1314
+ const descriptors = [];
1315
+ const visibleObjectIds = getVisibleObjectIds();
1316
+ if (layerEnabled("labels")) {
1317
+ for (const label of scene.labels) {
1318
+ if (label.hidden || !visibleObjectIds.has(label.objectId)) {
1319
+ continue;
1320
+ }
1321
+ const point = is3DView()
1322
+ ? runtime3d?.projectObjectToContainer(label.objectId) ?? null
1323
+ : project2DScenePointToContainer({ x: label.x, y: label.y });
1324
+ if (!point) {
1325
+ continue;
1326
+ }
1327
+ descriptors.push({
1328
+ key: `object:${label.renderId}`,
1329
+ kind: "object",
1330
+ point: is3DView()
1331
+ ? { x: point.x, y: point.y - 18 }
1332
+ : point,
1333
+ textAnchor: label.textAnchor,
1334
+ objectId: label.objectId,
1335
+ primaryText: label.label,
1336
+ secondaryText: label.secondaryLabel,
1337
+ secondaryOffset: Math.max(label.secondaryY - label.y, 12),
1338
+ });
1339
+ }
1340
+ }
1341
+ if (!is3DView() && layerEnabled("events")) {
1342
+ for (const event of scene.events) {
1343
+ if (event.hidden || !isEventVisible(event, visibleObjectIds)) {
1344
+ continue;
1345
+ }
1346
+ const point = project2DScenePointToContainer({ x: event.x, y: event.y - 10 });
1347
+ if (!point) {
1348
+ continue;
1349
+ }
1350
+ descriptors.push({
1351
+ key: `event:${event.renderId}`,
1352
+ kind: "event",
1353
+ point,
1354
+ textAnchor: "middle",
1355
+ primaryText: event.event.label || event.event.id,
1356
+ });
1357
+ }
1358
+ }
1359
+ return descriptors;
1360
+ }
1361
+ function isEventVisible(event, visibleObjectIds) {
1362
+ return event.objectIds.some((objectId) => visibleObjectIds.has(objectId));
1363
+ }
1364
+ function createScreenLabelElement(descriptor) {
1365
+ const element = document.createElement("div");
1366
+ element.className = `wo-viewer-label wo-viewer-label-${descriptor.kind}`;
1367
+ element.dataset.worldorbitScreenLabel = "true";
1368
+ element.dataset.labelKey = descriptor.key;
1369
+ element.dataset.anchor = descriptor.textAnchor;
1370
+ element.style.left = `${descriptor.point.x}px`;
1371
+ element.style.top = `${descriptor.point.y}px`;
1372
+ if (descriptor.objectId) {
1373
+ element.dataset.objectId = descriptor.objectId;
1374
+ for (const className of resolveScreenLabelClasses(descriptor.objectId)) {
1375
+ element.classList.add(className);
1376
+ }
1377
+ }
1378
+ const primary = document.createElement("span");
1379
+ primary.className = "wo-viewer-label-primary";
1380
+ if (descriptor.kind === "object") {
1381
+ primary.style.fontSize = `${14 * scene.scaleModel.labelMultiplier}px`;
1382
+ }
1383
+ primary.textContent = descriptor.primaryText;
1384
+ element.append(primary);
1385
+ if (descriptor.secondaryText) {
1386
+ const secondary = document.createElement("span");
1387
+ secondary.className = "wo-viewer-label-secondary";
1388
+ secondary.style.fontSize = `${11 * scene.scaleModel.labelMultiplier}px`;
1389
+ secondary.style.marginTop = `${Math.max(descriptor.secondaryOffset ?? 12, 10) - 10}px`;
1390
+ secondary.textContent = descriptor.secondaryText;
1391
+ element.append(secondary);
1392
+ }
1393
+ return element;
1394
+ }
1395
+ function layerEnabled(id) {
1396
+ return renderOptions.layers?.[id] !== false;
1397
+ }
1398
+ function resolveScreenLabelClasses(objectId) {
1399
+ const classes = [];
1400
+ const selectedDetails = buildObjectDetails(state.selectedObjectId);
1401
+ const hoveredDetails = buildObjectDetails(hoveredObjectId);
1402
+ if (state.selectedObjectId === objectId) {
1403
+ classes.push("wo-object-selected");
1404
+ }
1405
+ if (selectedDetails) {
1406
+ const selectedChain = new Set([
1407
+ selectedDetails.objectId,
1408
+ ...selectedDetails.renderObject.childIds,
1409
+ ...selectedDetails.renderObject.ancestorIds,
1410
+ ]);
1411
+ const selectedAncestors = new Set(selectedDetails.ancestors.map((ancestor) => ancestor.objectId));
1412
+ if (selectedChain.has(objectId)) {
1413
+ classes.push("wo-chain-selected");
1414
+ }
1415
+ if (selectedAncestors.has(objectId)) {
1416
+ classes.push("wo-ancestor-selected");
1417
+ }
1418
+ }
1419
+ if (hoveredDetails) {
1420
+ const hoveredChain = new Set([
1421
+ hoveredDetails.objectId,
1422
+ ...hoveredDetails.renderObject.childIds,
1423
+ ...hoveredDetails.renderObject.ancestorIds,
1424
+ ]);
1425
+ const hoveredAncestors = new Set(hoveredDetails.ancestors.map((ancestor) => ancestor.objectId));
1426
+ if (hoveredChain.has(objectId)) {
1427
+ classes.push("wo-chain-hover");
1428
+ }
1429
+ if (hoveredAncestors.has(objectId)) {
1430
+ classes.push("wo-ancestor-hover");
1431
+ }
1432
+ }
1433
+ return classes;
1434
+ }
1280
1435
  function create3DFocusState(objectId) {
1281
1436
  const target = spatialScene?.focusTargets.find((entry) => entry.objectId === objectId);
1282
1437
  if (!target) {
@@ -1557,7 +1712,7 @@ function cssEscape(value) {
1557
1712
  }
1558
1713
  return value.replace(/["\\]/g, "\\$&");
1559
1714
  }
1560
- function installViewerTooltipStyles() {
1715
+ function installViewerOverlayStyles() {
1561
1716
  if (typeof document === "undefined" || document.getElementById(TOOLTIP_STYLE_ID)) {
1562
1717
  return;
1563
1718
  }
@@ -1592,6 +1747,56 @@ function installViewerTooltipStyles() {
1592
1747
  height: 100%;
1593
1748
  min-height: 320px;
1594
1749
  }
1750
+ .wo-viewer-label-root {
1751
+ position: absolute;
1752
+ inset: 0;
1753
+ z-index: 8;
1754
+ pointer-events: none;
1755
+ overflow: hidden;
1756
+ }
1757
+ .wo-viewer-label {
1758
+ position: absolute;
1759
+ display: grid;
1760
+ gap: 2px;
1761
+ color: #edf6ff;
1762
+ font-family: "Segoe UI Variable", "Segoe UI", sans-serif;
1763
+ line-height: 1.15;
1764
+ text-shadow: 0 1px 2px rgba(7, 16, 25, 0.65), 0 0 18px rgba(7, 16, 25, 0.18);
1765
+ white-space: nowrap;
1766
+ }
1767
+ .wo-viewer-label[data-anchor="middle"] { transform: translate(-50%, 0); }
1768
+ .wo-viewer-label[data-anchor="end"] { transform: translate(-100%, 0); }
1769
+ .wo-viewer-label-primary {
1770
+ font-size: 14px;
1771
+ font-weight: 600;
1772
+ letter-spacing: 0.02em;
1773
+ }
1774
+ .wo-viewer-label-secondary {
1775
+ font-size: 11px;
1776
+ font-weight: 500;
1777
+ color: rgba(237, 246, 255, 0.72);
1778
+ }
1779
+ .wo-viewer-label-event {
1780
+ color: #ffce8a;
1781
+ text-transform: uppercase;
1782
+ letter-spacing: 0.04em;
1783
+ }
1784
+ .wo-viewer-label-event .wo-viewer-label-primary {
1785
+ font-size: 10px;
1786
+ font-weight: 700;
1787
+ }
1788
+ .wo-viewer-label.wo-object-selected .wo-viewer-label-primary,
1789
+ .wo-viewer-label.wo-chain-selected .wo-viewer-label-primary,
1790
+ .wo-viewer-label.wo-chain-hover .wo-viewer-label-primary {
1791
+ color: #ffce8a;
1792
+ }
1793
+ .wo-viewer-label.wo-object-selected .wo-viewer-label-secondary {
1794
+ color: #8fcaff;
1795
+ }
1796
+ .wo-viewer-label.wo-ancestor-selected .wo-viewer-label-primary,
1797
+ .wo-viewer-label.wo-ancestor-hover .wo-viewer-label-primary {
1798
+ opacity: 0.82;
1799
+ }
1595
1800
  .wo-viewer-tooltip-root {
1596
1801
  position: absolute;
1597
1802
  z-index: 12;