@2112-lab/central-plant 0.3.36 → 0.3.37

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.
@@ -36542,7 +36542,7 @@ var SceneTooltipsManager = /*#__PURE__*/function (_BaseDisposable) {
36542
36542
  // ---------------------------------------------------------------------------
36543
36543
  // Inline styles (injected once into the document head)
36544
36544
  // ---------------------------------------------------------------------------
36545
- var TOOLTIP_STYLES = "\n.cp-tooltip {\n position: absolute;\n pointer-events: auto;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n user-select: none;\n min-width: 200px;\n max-width: 280px;\n transform: translate(-50%, -100%);\n margin-top: -12px;\n transition: opacity 0.15s ease;\n}\n\n.cp-tooltip__card {\n background: rgba(28, 32, 40, 0.95);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 10px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);\n overflow: hidden;\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__header {\n padding: 10px 14px;\n font-weight: 600;\n font-size: 14px;\n color: #e8ecf1;\n background: rgba(255, 255, 255, 0.04);\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 I/O Devices section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__io-section {\n position: relative;\n}\n\n.cp-tooltip__io-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n color: #a4adba;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease;\n}\n\n.cp-tooltip__io-trigger:hover {\n background: rgba(255, 255, 255, 0.06);\n color: #e8ecf1;\n}\n\n.cp-tooltip__io-trigger-label {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n font-weight: 500;\n}\n\n.cp-tooltip__io-arrow {\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__io-arrow {\n transform: rotate(90deg);\n}\n\n/* \u2500\u2500 Device list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__device-list {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__device-list {\n max-height: 300px;\n}\n\n.cp-tooltip__device-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 14px 6px 20px;\n color: #c1c8d1;\n font-size: 12px;\n border-top: 1px solid rgba(255, 255, 255, 0.04);\n transition: background 0.1s ease;\n}\n\n.cp-tooltip__device-item:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n\n.cp-tooltip__device-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #4fc3f7;\n flex-shrink: 0;\n}\n\n.cp-tooltip__device-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__no-devices {\n padding: 8px 14px;\n color: #6b7280;\n font-size: 12px;\n font-style: italic;\n}\n\n/* \u2500\u2500 Caret arrow pointing down \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__caret {\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 7px solid rgba(28, 32, 40, 0.95);\n margin: 0 auto;\n position: relative;\n top: -1px;\n}\n\n/* \u2500\u2500 Data point rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__dp-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 12px 4px 30px;\n font-size: 11.5px;\n border-top: 1px solid rgba(255, 255, 255, 0.025);\n min-height: 24px;\n}\n\n.cp-tooltip__dp-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #7c8d9e;\n}\n\n/* Read-only state badge */\n.cp-tooltip__dp-badge {\n font-size: 10.5px;\n font-weight: 600;\n padding: 1px 7px;\n border-radius: 8px;\n flex-shrink: 0;\n background: rgba(255, 255, 255, 0.07);\n color: #9aabb8;\n min-width: 30px;\n text-align: center;\n letter-spacing: 0.2px;\n}\n\n.cp-tooltip__dp-badge.active {\n background: rgba(76, 175, 80, 0.28);\n color: #a5d6a7;\n}\n\n/* Binary toggle button */\n.cp-tooltip__dp-toggle {\n font-size: 10.5px;\n font-weight: 600;\n padding: 2px 9px;\n border-radius: 8px;\n flex-shrink: 0;\n border: 1px solid rgba(255, 255, 255, 0.14);\n background: rgba(255, 255, 255, 0.06);\n color: #c1c8d1;\n cursor: pointer;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n\n.cp-tooltip__dp-toggle:hover {\n background: rgba(255, 255, 255, 0.12);\n border-color: rgba(255, 255, 255, 0.25);\n}\n\n.cp-tooltip__dp-toggle.on {\n background: rgba(76, 175, 80, 0.28);\n border-color: rgba(76, 175, 80, 0.55);\n color: #a5d6a7;\n}\n\n/* Number / float input */\n.cp-tooltip__dp-input {\n width: 62px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 4px;\n color: #e8ecf1;\n font-size: 11px;\n padding: 2px 5px;\n text-align: right;\n outline: none;\n flex-shrink: 0;\n}\n\n.cp-tooltip__dp-input:focus {\n border-color: rgba(79, 195, 247, 0.55);\n}\n\n/* Enum select */\n.cp-tooltip__dp-select {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 4px;\n color: #e8ecf1;\n font-size: 11px;\n padding: 2px 4px;\n outline: none;\n flex-shrink: 0;\n max-width: 96px;\n cursor: pointer;\n}\n\n.cp-tooltip__dp-select:focus {\n border-color: rgba(79, 195, 247, 0.55);\n}\n\n/* Unit suffix */\n.cp-tooltip__dp-unit {\n font-size: 10px;\n color: #505c68;\n flex-shrink: 0;\n margin-left: -4px;\n}\n";
36545
+ var TOOLTIP_STYLES = "\n.cp-tooltip {\n position: absolute;\n pointer-events: auto;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n user-select: none;\n min-width: 200px;\n max-width: 280px;\n transform: translate(-50%, -100%);\n margin-top: -12px;\n transition: opacity 0.15s ease;\n color-scheme: light;\n}\n\n.cp-tooltip__card {\n background: rgba(255, 255, 255, 0.97);\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n overflow: hidden;\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__header {\n padding: 10px 14px;\n font-weight: 600;\n font-size: 14px;\n color: #2c3e50;\n background: rgba(0, 0, 0, 0.02);\n border-bottom: 1px solid rgba(0, 0, 0, 0.08);\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 I/O Devices section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__io-section {\n position: relative;\n}\n\n.cp-tooltip__io-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n color: #666666;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease;\n}\n\n.cp-tooltip__io-trigger:hover {\n background: rgba(0, 0, 0, 0.04);\n color: #333333;\n}\n\n.cp-tooltip__io-trigger-label {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n font-weight: 500;\n}\n\n.cp-tooltip__io-arrow {\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__io-arrow {\n transform: rotate(90deg);\n}\n\n/* \u2500\u2500 Device list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__device-list {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__device-list {\n max-height: 300px;\n}\n\n.cp-tooltip__device-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 14px 6px 20px;\n color: #555555;\n font-size: 12px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n transition: background 0.1s ease;\n}\n\n.cp-tooltip__device-item:hover {\n background: rgba(0, 0, 0, 0.03);\n}\n\n.cp-tooltip__device-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #1976d2;\n flex-shrink: 0;\n}\n\n.cp-tooltip__device-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__no-devices {\n padding: 8px 14px;\n color: #888888;\n font-size: 12px;\n font-style: italic;\n}\n\n/* \u2500\u2500 Caret arrow pointing down \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__caret {\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 7px solid rgba(255, 255, 255, 0.97);\n margin: 0 auto;\n position: relative;\n top: -1px;\n filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.1));\n}\n\n/* \u2500\u2500 Data point rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__dp-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 12px 4px 30px;\n font-size: 11.5px;\n border-top: 1px solid rgba(0, 0, 0, 0.05);\n min-height: 24px;\n}\n\n.cp-tooltip__dp-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #666666;\n}\n\n/* Read-only state badge */\n.cp-tooltip__dp-badge {\n font-size: 10.5px;\n font-weight: 600;\n padding: 1px 7px;\n border-radius: 8px;\n flex-shrink: 0;\n background: #f0f2f5;\n color: #666666;\n min-width: 30px;\n text-align: center;\n letter-spacing: 0.2px;\n}\n\n.cp-tooltip__dp-badge.active {\n background: rgba(76, 175, 80, 0.15);\n color: #2e7d32;\n}\n\n/* Binary toggle button */\n.cp-tooltip__dp-toggle {\n font-size: 10.5px;\n font-weight: 600;\n padding: 2px 9px;\n border-radius: 8px;\n flex-shrink: 0;\n border: 1px solid #dddddd;\n background: #f8f9fa;\n color: #555555;\n cursor: pointer;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n\n.cp-tooltip__dp-toggle:hover {\n background: #eeeeee;\n border-color: #cccccc;\n}\n\n.cp-tooltip__dp-toggle.on {\n background: rgba(76, 175, 80, 0.15);\n border-color: rgba(76, 175, 80, 0.45);\n color: #2e7d32;\n}\n\n/* Number / float input */\n.cp-tooltip__dp-input {\n width: 62px;\n background: #f8f9fa;\n border: 1px solid #dddddd;\n border-radius: 4px;\n color: #333333;\n font-size: 11px;\n padding: 2px 5px;\n text-align: right;\n outline: none;\n flex-shrink: 0;\n}\n\n.cp-tooltip__dp-input:focus {\n border-color: #1976d2;\n}\n\n/* Enum select */\n.cp-tooltip__dp-select {\n background: #f8f9fa;\n border: 1px solid #dddddd;\n border-radius: 4px;\n color: #333333;\n font-size: 11px;\n padding: 2px 4px;\n outline: none;\n flex-shrink: 0;\n max-width: 96px;\n cursor: pointer;\n}\n\n.cp-tooltip__dp-select:focus {\n border-color: #1976d2;\n}\n\n/* Unit suffix */\n.cp-tooltip__dp-unit {\n font-size: 10px;\n color: #888888;\n flex-shrink: 0;\n margin-left: -4px;\n}\n";
36546
36546
  var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
36547
36547
  /**
36548
36548
  * @param {Object} sceneViewer - The sceneViewer instance
@@ -37474,6 +37474,7 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
37474
37474
  entries.push({
37475
37475
  anim: anim,
37476
37476
  mesh: mesh,
37477
+ deviceModelRoot: deviceModelRoot,
37477
37478
  origPos: mesh.position.clone(),
37478
37479
  origRot: mesh.rotation.clone(),
37479
37480
  origWorldPos: worldPos,
@@ -38224,13 +38225,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38224
38225
  value: function _applyAnimation(entry, value) {
38225
38226
  var anim = entry.anim,
38226
38227
  mesh = entry.mesh,
38227
- origPos = entry.origPos,
38228
- origRot = entry.origRot,
38229
- origWorldPos = entry.origWorldPos,
38230
- origWorldQuat = entry.origWorldQuat,
38231
- origWorldCenter = entry.origWorldCenter,
38232
- deviceWorldQuat = entry.deviceWorldQuat,
38233
- viewerMaxDim = entry.viewerMaxDim;
38228
+ origPos = entry.origPos;
38229
+ entry.origRot;
38230
+ var viewerMaxDim = entry.viewerMaxDim;
38234
38231
  var mapping = this._resolveMapping(anim, value);
38235
38232
  if (!mapping) return;
38236
38233
  var types = anim.transformTypes || [];
@@ -38242,7 +38239,7 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38242
38239
  if (type === 'translation') {
38243
38240
  this._applyTranslation(mesh, origPos, mapping.transform);
38244
38241
  } else if (type === 'rotation') {
38245
- this._applyRotation(mesh, origPos, origRot, anim, mapping.rotationTransform, origWorldPos, origWorldQuat, origWorldCenter, deviceWorldQuat, viewerMaxDim);
38242
+ this._applyRotation(entry, anim, mapping.rotationTransform, viewerMaxDim);
38246
38243
  } else if (type === 'color') {
38247
38244
  this._applyColor(mesh, mapping.colorTransform);
38248
38245
  }
@@ -38386,25 +38383,71 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
38386
38383
  mesh.position.set(origPos.x - ((_transform$x = transform.x) !== null && _transform$x !== void 0 ? _transform$x : 0), origPos.y - ((_transform$y = transform.y) !== null && _transform$y !== void 0 ? _transform$y : 0), origPos.z + ((_transform$z = transform.z) !== null && _transform$z !== void 0 ? _transform$z : 0));
38387
38384
  }
38388
38385
 
38386
+ /**
38387
+ * Recompute world-space rest-pose transforms for a behavior entry using the
38388
+ * current parent/device world matrix. origPos/origRot are local rest pose and
38389
+ * stay valid after parent moves; world anchors must be derived at apply time.
38390
+ *
38391
+ * @param {{ mesh, origPos, origRot, deviceModelRoot }} entry
38392
+ * @returns {{ origWorldPos: THREE.Vector3, origWorldQuat: THREE.Quaternion, origWorldCenter: THREE.Vector3, deviceWorldQuat: THREE.Quaternion }|null}
38393
+ */
38394
+ }, {
38395
+ key: "_getRestWorldTransforms",
38396
+ value: function _getRestWorldTransforms(entry) {
38397
+ var mesh = entry.mesh,
38398
+ origPos = entry.origPos,
38399
+ origRot = entry.origRot,
38400
+ deviceModelRoot = entry.deviceModelRoot;
38401
+ if (!mesh || !origPos || !origRot) return null;
38402
+ var savedPos = mesh.position.clone();
38403
+ var savedQuat = mesh.quaternion.clone();
38404
+ mesh.position.copy(origPos);
38405
+ mesh.rotation.copy(origRot);
38406
+ mesh.updateMatrixWorld(true);
38407
+ var origWorldPos = new THREE__namespace.Vector3();
38408
+ mesh.getWorldPosition(origWorldPos);
38409
+ var origWorldQuat = new THREE__namespace.Quaternion();
38410
+ mesh.getWorldQuaternion(origWorldQuat);
38411
+ var box = new THREE__namespace.Box3().setFromObject(mesh);
38412
+ var origWorldCenter = new THREE__namespace.Vector3();
38413
+ if (!box.isEmpty()) box.getCenter(origWorldCenter);else origWorldCenter.copy(origWorldPos);
38414
+ var deviceWorldQuat = new THREE__namespace.Quaternion();
38415
+ if (deviceModelRoot) {
38416
+ deviceModelRoot.getWorldQuaternion(deviceWorldQuat);
38417
+ }
38418
+ mesh.position.copy(savedPos);
38419
+ mesh.quaternion.copy(savedQuat);
38420
+ return {
38421
+ origWorldPos: origWorldPos,
38422
+ origWorldQuat: origWorldQuat,
38423
+ origWorldCenter: origWorldCenter,
38424
+ deviceWorldQuat: deviceWorldQuat
38425
+ };
38426
+ }
38427
+
38389
38428
  /**
38390
38429
  * Apply a rotation around an arbitrary pivot point using world-space quaternion
38391
38430
  * math matching the ReconstructionViewer's setMeshPreviewRotationAxis approach.
38392
38431
  * This ensures the runtime axis/pivot matches what the user configured in the
38393
38432
  * animation dialog, regardless of the device's parent transform in the scene.
38394
38433
  *
38395
- * @param {THREE.Object3D} mesh
38396
- * @param {THREE.Vector3} origPos - local position at load time (unused, kept for signature compat)
38397
- * @param {THREE.Euler} origRot - local rotation at load time (unused)
38434
+ * @param {{ mesh, origPos, origRot, deviceModelRoot }} entry
38398
38435
  * @param {Object} anim
38399
38436
  * @param {number} angleDeg - Degrees
38400
- * @param {THREE.Vector3} origWorldPos - world position at load time
38401
- * @param {THREE.Quaternion} origWorldQuat - world quaternion at load time
38402
- * @param {THREE.Vector3} origWorldCenter - world bounding-box center at load time
38437
+ * @param {number} viewerMaxDim
38403
38438
  */
38404
38439
  }, {
38405
38440
  key: "_applyRotation",
38406
- value: function _applyRotation(mesh, origPos, origRot, anim, angleDeg, origWorldPos, origWorldQuat, origWorldCenter, deviceWorldQuat, viewerMaxDim) {
38441
+ value: function _applyRotation(entry, anim, angleDeg, viewerMaxDim) {
38407
38442
  var _anim$rotAxis, _anim$rotAxisOffset;
38443
+ var mesh = entry.mesh,
38444
+ origPos = entry.origPos,
38445
+ origRot = entry.origRot;
38446
+ var restWorld = this._getRestWorldTransforms(entry);
38447
+ var origWorldPos = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldPos;
38448
+ var origWorldQuat = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldQuat;
38449
+ var origWorldCenter = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldCenter;
38450
+ var deviceWorldQuat = restWorld === null || restWorld === void 0 ? void 0 : restWorld.deviceWorldQuat;
38408
38451
  var angle = THREE__namespace.MathUtils.degToRad(typeof angleDeg === 'number' ? angleDeg : 0);
38409
38452
  var axis = ((_anim$rotAxis = anim.rotAxis) !== null && _anim$rotAxis !== void 0 ? _anim$rotAxis : 'x').toLowerCase();
38410
38453
  var off = (_anim$rotAxisOffset = anim.rotAxisOffset) !== null && _anim$rotAxisOffset !== void 0 ? _anim$rotAxisOffset : {
@@ -40151,7 +40194,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
40151
40194
  * Initialize the CentralPlant manager
40152
40195
  *
40153
40196
  * @constructor
40154
- * @version 0.3.36
40197
+ * @version 0.3.37
40155
40198
  * @updated 2025-10-22
40156
40199
  *
40157
40200
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -35,7 +35,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
35
35
  * Initialize the CentralPlant manager
36
36
  *
37
37
  * @constructor
38
- * @version 0.3.36
38
+ * @version 0.3.37
39
39
  * @updated 2025-10-22
40
40
  *
41
41
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -121,6 +121,7 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
121
121
  entries.push({
122
122
  anim: anim,
123
123
  mesh: mesh,
124
+ deviceModelRoot: deviceModelRoot,
124
125
  origPos: mesh.position.clone(),
125
126
  origRot: mesh.rotation.clone(),
126
127
  origWorldPos: worldPos,
@@ -871,13 +872,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
871
872
  value: function _applyAnimation(entry, value) {
872
873
  var anim = entry.anim,
873
874
  mesh = entry.mesh,
874
- origPos = entry.origPos,
875
- origRot = entry.origRot,
876
- origWorldPos = entry.origWorldPos,
877
- origWorldQuat = entry.origWorldQuat,
878
- origWorldCenter = entry.origWorldCenter,
879
- deviceWorldQuat = entry.deviceWorldQuat,
880
- viewerMaxDim = entry.viewerMaxDim;
875
+ origPos = entry.origPos;
876
+ entry.origRot;
877
+ var viewerMaxDim = entry.viewerMaxDim;
881
878
  var mapping = this._resolveMapping(anim, value);
882
879
  if (!mapping) return;
883
880
  var types = anim.transformTypes || [];
@@ -889,7 +886,7 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
889
886
  if (type === 'translation') {
890
887
  this._applyTranslation(mesh, origPos, mapping.transform);
891
888
  } else if (type === 'rotation') {
892
- this._applyRotation(mesh, origPos, origRot, anim, mapping.rotationTransform, origWorldPos, origWorldQuat, origWorldCenter, deviceWorldQuat, viewerMaxDim);
889
+ this._applyRotation(entry, anim, mapping.rotationTransform, viewerMaxDim);
893
890
  } else if (type === 'color') {
894
891
  this._applyColor(mesh, mapping.colorTransform);
895
892
  }
@@ -1033,25 +1030,71 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
1033
1030
  mesh.position.set(origPos.x - ((_transform$x = transform.x) !== null && _transform$x !== void 0 ? _transform$x : 0), origPos.y - ((_transform$y = transform.y) !== null && _transform$y !== void 0 ? _transform$y : 0), origPos.z + ((_transform$z = transform.z) !== null && _transform$z !== void 0 ? _transform$z : 0));
1034
1031
  }
1035
1032
 
1033
+ /**
1034
+ * Recompute world-space rest-pose transforms for a behavior entry using the
1035
+ * current parent/device world matrix. origPos/origRot are local rest pose and
1036
+ * stay valid after parent moves; world anchors must be derived at apply time.
1037
+ *
1038
+ * @param {{ mesh, origPos, origRot, deviceModelRoot }} entry
1039
+ * @returns {{ origWorldPos: THREE.Vector3, origWorldQuat: THREE.Quaternion, origWorldCenter: THREE.Vector3, deviceWorldQuat: THREE.Quaternion }|null}
1040
+ */
1041
+ }, {
1042
+ key: "_getRestWorldTransforms",
1043
+ value: function _getRestWorldTransforms(entry) {
1044
+ var mesh = entry.mesh,
1045
+ origPos = entry.origPos,
1046
+ origRot = entry.origRot,
1047
+ deviceModelRoot = entry.deviceModelRoot;
1048
+ if (!mesh || !origPos || !origRot) return null;
1049
+ var savedPos = mesh.position.clone();
1050
+ var savedQuat = mesh.quaternion.clone();
1051
+ mesh.position.copy(origPos);
1052
+ mesh.rotation.copy(origRot);
1053
+ mesh.updateMatrixWorld(true);
1054
+ var origWorldPos = new THREE__namespace.Vector3();
1055
+ mesh.getWorldPosition(origWorldPos);
1056
+ var origWorldQuat = new THREE__namespace.Quaternion();
1057
+ mesh.getWorldQuaternion(origWorldQuat);
1058
+ var box = new THREE__namespace.Box3().setFromObject(mesh);
1059
+ var origWorldCenter = new THREE__namespace.Vector3();
1060
+ if (!box.isEmpty()) box.getCenter(origWorldCenter);else origWorldCenter.copy(origWorldPos);
1061
+ var deviceWorldQuat = new THREE__namespace.Quaternion();
1062
+ if (deviceModelRoot) {
1063
+ deviceModelRoot.getWorldQuaternion(deviceWorldQuat);
1064
+ }
1065
+ mesh.position.copy(savedPos);
1066
+ mesh.quaternion.copy(savedQuat);
1067
+ return {
1068
+ origWorldPos: origWorldPos,
1069
+ origWorldQuat: origWorldQuat,
1070
+ origWorldCenter: origWorldCenter,
1071
+ deviceWorldQuat: deviceWorldQuat
1072
+ };
1073
+ }
1074
+
1036
1075
  /**
1037
1076
  * Apply a rotation around an arbitrary pivot point using world-space quaternion
1038
1077
  * math matching the ReconstructionViewer's setMeshPreviewRotationAxis approach.
1039
1078
  * This ensures the runtime axis/pivot matches what the user configured in the
1040
1079
  * animation dialog, regardless of the device's parent transform in the scene.
1041
1080
  *
1042
- * @param {THREE.Object3D} mesh
1043
- * @param {THREE.Vector3} origPos - local position at load time (unused, kept for signature compat)
1044
- * @param {THREE.Euler} origRot - local rotation at load time (unused)
1081
+ * @param {{ mesh, origPos, origRot, deviceModelRoot }} entry
1045
1082
  * @param {Object} anim
1046
1083
  * @param {number} angleDeg - Degrees
1047
- * @param {THREE.Vector3} origWorldPos - world position at load time
1048
- * @param {THREE.Quaternion} origWorldQuat - world quaternion at load time
1049
- * @param {THREE.Vector3} origWorldCenter - world bounding-box center at load time
1084
+ * @param {number} viewerMaxDim
1050
1085
  */
1051
1086
  }, {
1052
1087
  key: "_applyRotation",
1053
- value: function _applyRotation(mesh, origPos, origRot, anim, angleDeg, origWorldPos, origWorldQuat, origWorldCenter, deviceWorldQuat, viewerMaxDim) {
1088
+ value: function _applyRotation(entry, anim, angleDeg, viewerMaxDim) {
1054
1089
  var _anim$rotAxis, _anim$rotAxisOffset;
1090
+ var mesh = entry.mesh,
1091
+ origPos = entry.origPos,
1092
+ origRot = entry.origRot;
1093
+ var restWorld = this._getRestWorldTransforms(entry);
1094
+ var origWorldPos = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldPos;
1095
+ var origWorldQuat = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldQuat;
1096
+ var origWorldCenter = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldCenter;
1097
+ var deviceWorldQuat = restWorld === null || restWorld === void 0 ? void 0 : restWorld.deviceWorldQuat;
1055
1098
  var angle = THREE__namespace.MathUtils.degToRad(typeof angleDeg === 'number' ? angleDeg : 0);
1056
1099
  var axis = ((_anim$rotAxis = anim.rotAxis) !== null && _anim$rotAxis !== void 0 ? _anim$rotAxis : 'x').toLowerCase();
1057
1100
  var off = (_anim$rotAxisOffset = anim.rotAxisOffset) !== null && _anim$rotAxisOffset !== void 0 ? _anim$rotAxisOffset : {
@@ -29,7 +29,7 @@ var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
29
29
  // ---------------------------------------------------------------------------
30
30
  // Inline styles (injected once into the document head)
31
31
  // ---------------------------------------------------------------------------
32
- var TOOLTIP_STYLES = "\n.cp-tooltip {\n position: absolute;\n pointer-events: auto;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n user-select: none;\n min-width: 200px;\n max-width: 280px;\n transform: translate(-50%, -100%);\n margin-top: -12px;\n transition: opacity 0.15s ease;\n}\n\n.cp-tooltip__card {\n background: rgba(28, 32, 40, 0.95);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 10px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);\n overflow: hidden;\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__header {\n padding: 10px 14px;\n font-weight: 600;\n font-size: 14px;\n color: #e8ecf1;\n background: rgba(255, 255, 255, 0.04);\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 I/O Devices section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__io-section {\n position: relative;\n}\n\n.cp-tooltip__io-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n color: #a4adba;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease;\n}\n\n.cp-tooltip__io-trigger:hover {\n background: rgba(255, 255, 255, 0.06);\n color: #e8ecf1;\n}\n\n.cp-tooltip__io-trigger-label {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n font-weight: 500;\n}\n\n.cp-tooltip__io-arrow {\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__io-arrow {\n transform: rotate(90deg);\n}\n\n/* \u2500\u2500 Device list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__device-list {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__device-list {\n max-height: 300px;\n}\n\n.cp-tooltip__device-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 14px 6px 20px;\n color: #c1c8d1;\n font-size: 12px;\n border-top: 1px solid rgba(255, 255, 255, 0.04);\n transition: background 0.1s ease;\n}\n\n.cp-tooltip__device-item:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n\n.cp-tooltip__device-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #4fc3f7;\n flex-shrink: 0;\n}\n\n.cp-tooltip__device-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__no-devices {\n padding: 8px 14px;\n color: #6b7280;\n font-size: 12px;\n font-style: italic;\n}\n\n/* \u2500\u2500 Caret arrow pointing down \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__caret {\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 7px solid rgba(28, 32, 40, 0.95);\n margin: 0 auto;\n position: relative;\n top: -1px;\n}\n\n/* \u2500\u2500 Data point rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__dp-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 12px 4px 30px;\n font-size: 11.5px;\n border-top: 1px solid rgba(255, 255, 255, 0.025);\n min-height: 24px;\n}\n\n.cp-tooltip__dp-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #7c8d9e;\n}\n\n/* Read-only state badge */\n.cp-tooltip__dp-badge {\n font-size: 10.5px;\n font-weight: 600;\n padding: 1px 7px;\n border-radius: 8px;\n flex-shrink: 0;\n background: rgba(255, 255, 255, 0.07);\n color: #9aabb8;\n min-width: 30px;\n text-align: center;\n letter-spacing: 0.2px;\n}\n\n.cp-tooltip__dp-badge.active {\n background: rgba(76, 175, 80, 0.28);\n color: #a5d6a7;\n}\n\n/* Binary toggle button */\n.cp-tooltip__dp-toggle {\n font-size: 10.5px;\n font-weight: 600;\n padding: 2px 9px;\n border-radius: 8px;\n flex-shrink: 0;\n border: 1px solid rgba(255, 255, 255, 0.14);\n background: rgba(255, 255, 255, 0.06);\n color: #c1c8d1;\n cursor: pointer;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n\n.cp-tooltip__dp-toggle:hover {\n background: rgba(255, 255, 255, 0.12);\n border-color: rgba(255, 255, 255, 0.25);\n}\n\n.cp-tooltip__dp-toggle.on {\n background: rgba(76, 175, 80, 0.28);\n border-color: rgba(76, 175, 80, 0.55);\n color: #a5d6a7;\n}\n\n/* Number / float input */\n.cp-tooltip__dp-input {\n width: 62px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 4px;\n color: #e8ecf1;\n font-size: 11px;\n padding: 2px 5px;\n text-align: right;\n outline: none;\n flex-shrink: 0;\n}\n\n.cp-tooltip__dp-input:focus {\n border-color: rgba(79, 195, 247, 0.55);\n}\n\n/* Enum select */\n.cp-tooltip__dp-select {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 4px;\n color: #e8ecf1;\n font-size: 11px;\n padding: 2px 4px;\n outline: none;\n flex-shrink: 0;\n max-width: 96px;\n cursor: pointer;\n}\n\n.cp-tooltip__dp-select:focus {\n border-color: rgba(79, 195, 247, 0.55);\n}\n\n/* Unit suffix */\n.cp-tooltip__dp-unit {\n font-size: 10px;\n color: #505c68;\n flex-shrink: 0;\n margin-left: -4px;\n}\n";
32
+ var TOOLTIP_STYLES = "\n.cp-tooltip {\n position: absolute;\n pointer-events: auto;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n user-select: none;\n min-width: 200px;\n max-width: 280px;\n transform: translate(-50%, -100%);\n margin-top: -12px;\n transition: opacity 0.15s ease;\n color-scheme: light;\n}\n\n.cp-tooltip__card {\n background: rgba(255, 255, 255, 0.97);\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n overflow: hidden;\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__header {\n padding: 10px 14px;\n font-weight: 600;\n font-size: 14px;\n color: #2c3e50;\n background: rgba(0, 0, 0, 0.02);\n border-bottom: 1px solid rgba(0, 0, 0, 0.08);\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 I/O Devices section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__io-section {\n position: relative;\n}\n\n.cp-tooltip__io-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n color: #666666;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease;\n}\n\n.cp-tooltip__io-trigger:hover {\n background: rgba(0, 0, 0, 0.04);\n color: #333333;\n}\n\n.cp-tooltip__io-trigger-label {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n font-weight: 500;\n}\n\n.cp-tooltip__io-arrow {\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__io-arrow {\n transform: rotate(90deg);\n}\n\n/* \u2500\u2500 Device list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__device-list {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__device-list {\n max-height: 300px;\n}\n\n.cp-tooltip__device-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 14px 6px 20px;\n color: #555555;\n font-size: 12px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n transition: background 0.1s ease;\n}\n\n.cp-tooltip__device-item:hover {\n background: rgba(0, 0, 0, 0.03);\n}\n\n.cp-tooltip__device-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #1976d2;\n flex-shrink: 0;\n}\n\n.cp-tooltip__device-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__no-devices {\n padding: 8px 14px;\n color: #888888;\n font-size: 12px;\n font-style: italic;\n}\n\n/* \u2500\u2500 Caret arrow pointing down \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__caret {\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 7px solid rgba(255, 255, 255, 0.97);\n margin: 0 auto;\n position: relative;\n top: -1px;\n filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.1));\n}\n\n/* \u2500\u2500 Data point rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__dp-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 12px 4px 30px;\n font-size: 11.5px;\n border-top: 1px solid rgba(0, 0, 0, 0.05);\n min-height: 24px;\n}\n\n.cp-tooltip__dp-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #666666;\n}\n\n/* Read-only state badge */\n.cp-tooltip__dp-badge {\n font-size: 10.5px;\n font-weight: 600;\n padding: 1px 7px;\n border-radius: 8px;\n flex-shrink: 0;\n background: #f0f2f5;\n color: #666666;\n min-width: 30px;\n text-align: center;\n letter-spacing: 0.2px;\n}\n\n.cp-tooltip__dp-badge.active {\n background: rgba(76, 175, 80, 0.15);\n color: #2e7d32;\n}\n\n/* Binary toggle button */\n.cp-tooltip__dp-toggle {\n font-size: 10.5px;\n font-weight: 600;\n padding: 2px 9px;\n border-radius: 8px;\n flex-shrink: 0;\n border: 1px solid #dddddd;\n background: #f8f9fa;\n color: #555555;\n cursor: pointer;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n\n.cp-tooltip__dp-toggle:hover {\n background: #eeeeee;\n border-color: #cccccc;\n}\n\n.cp-tooltip__dp-toggle.on {\n background: rgba(76, 175, 80, 0.15);\n border-color: rgba(76, 175, 80, 0.45);\n color: #2e7d32;\n}\n\n/* Number / float input */\n.cp-tooltip__dp-input {\n width: 62px;\n background: #f8f9fa;\n border: 1px solid #dddddd;\n border-radius: 4px;\n color: #333333;\n font-size: 11px;\n padding: 2px 5px;\n text-align: right;\n outline: none;\n flex-shrink: 0;\n}\n\n.cp-tooltip__dp-input:focus {\n border-color: #1976d2;\n}\n\n/* Enum select */\n.cp-tooltip__dp-select {\n background: #f8f9fa;\n border: 1px solid #dddddd;\n border-radius: 4px;\n color: #333333;\n font-size: 11px;\n padding: 2px 4px;\n outline: none;\n flex-shrink: 0;\n max-width: 96px;\n cursor: pointer;\n}\n\n.cp-tooltip__dp-select:focus {\n border-color: #1976d2;\n}\n\n/* Unit suffix */\n.cp-tooltip__dp-unit {\n font-size: 10px;\n color: #888888;\n flex-shrink: 0;\n margin-left: -4px;\n}\n";
33
33
  var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
34
34
  /**
35
35
  * @param {Object} sceneViewer - The sceneViewer instance
@@ -31,7 +31,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
31
31
  * Initialize the CentralPlant manager
32
32
  *
33
33
  * @constructor
34
- * @version 0.3.36
34
+ * @version 0.3.37
35
35
  * @updated 2025-10-22
36
36
  *
37
37
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -97,6 +97,7 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
97
97
  entries.push({
98
98
  anim: anim,
99
99
  mesh: mesh,
100
+ deviceModelRoot: deviceModelRoot,
100
101
  origPos: mesh.position.clone(),
101
102
  origRot: mesh.rotation.clone(),
102
103
  origWorldPos: worldPos,
@@ -847,13 +848,9 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
847
848
  value: function _applyAnimation(entry, value) {
848
849
  var anim = entry.anim,
849
850
  mesh = entry.mesh,
850
- origPos = entry.origPos,
851
- origRot = entry.origRot,
852
- origWorldPos = entry.origWorldPos,
853
- origWorldQuat = entry.origWorldQuat,
854
- origWorldCenter = entry.origWorldCenter,
855
- deviceWorldQuat = entry.deviceWorldQuat,
856
- viewerMaxDim = entry.viewerMaxDim;
851
+ origPos = entry.origPos;
852
+ entry.origRot;
853
+ var viewerMaxDim = entry.viewerMaxDim;
857
854
  var mapping = this._resolveMapping(anim, value);
858
855
  if (!mapping) return;
859
856
  var types = anim.transformTypes || [];
@@ -865,7 +862,7 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
865
862
  if (type === 'translation') {
866
863
  this._applyTranslation(mesh, origPos, mapping.transform);
867
864
  } else if (type === 'rotation') {
868
- this._applyRotation(mesh, origPos, origRot, anim, mapping.rotationTransform, origWorldPos, origWorldQuat, origWorldCenter, deviceWorldQuat, viewerMaxDim);
865
+ this._applyRotation(entry, anim, mapping.rotationTransform, viewerMaxDim);
869
866
  } else if (type === 'color') {
870
867
  this._applyColor(mesh, mapping.colorTransform);
871
868
  }
@@ -1009,25 +1006,71 @@ var IoBehaviorManager = /*#__PURE__*/function (_BaseDisposable) {
1009
1006
  mesh.position.set(origPos.x - ((_transform$x = transform.x) !== null && _transform$x !== void 0 ? _transform$x : 0), origPos.y - ((_transform$y = transform.y) !== null && _transform$y !== void 0 ? _transform$y : 0), origPos.z + ((_transform$z = transform.z) !== null && _transform$z !== void 0 ? _transform$z : 0));
1010
1007
  }
1011
1008
 
1009
+ /**
1010
+ * Recompute world-space rest-pose transforms for a behavior entry using the
1011
+ * current parent/device world matrix. origPos/origRot are local rest pose and
1012
+ * stay valid after parent moves; world anchors must be derived at apply time.
1013
+ *
1014
+ * @param {{ mesh, origPos, origRot, deviceModelRoot }} entry
1015
+ * @returns {{ origWorldPos: THREE.Vector3, origWorldQuat: THREE.Quaternion, origWorldCenter: THREE.Vector3, deviceWorldQuat: THREE.Quaternion }|null}
1016
+ */
1017
+ }, {
1018
+ key: "_getRestWorldTransforms",
1019
+ value: function _getRestWorldTransforms(entry) {
1020
+ var mesh = entry.mesh,
1021
+ origPos = entry.origPos,
1022
+ origRot = entry.origRot,
1023
+ deviceModelRoot = entry.deviceModelRoot;
1024
+ if (!mesh || !origPos || !origRot) return null;
1025
+ var savedPos = mesh.position.clone();
1026
+ var savedQuat = mesh.quaternion.clone();
1027
+ mesh.position.copy(origPos);
1028
+ mesh.rotation.copy(origRot);
1029
+ mesh.updateMatrixWorld(true);
1030
+ var origWorldPos = new THREE.Vector3();
1031
+ mesh.getWorldPosition(origWorldPos);
1032
+ var origWorldQuat = new THREE.Quaternion();
1033
+ mesh.getWorldQuaternion(origWorldQuat);
1034
+ var box = new THREE.Box3().setFromObject(mesh);
1035
+ var origWorldCenter = new THREE.Vector3();
1036
+ if (!box.isEmpty()) box.getCenter(origWorldCenter);else origWorldCenter.copy(origWorldPos);
1037
+ var deviceWorldQuat = new THREE.Quaternion();
1038
+ if (deviceModelRoot) {
1039
+ deviceModelRoot.getWorldQuaternion(deviceWorldQuat);
1040
+ }
1041
+ mesh.position.copy(savedPos);
1042
+ mesh.quaternion.copy(savedQuat);
1043
+ return {
1044
+ origWorldPos: origWorldPos,
1045
+ origWorldQuat: origWorldQuat,
1046
+ origWorldCenter: origWorldCenter,
1047
+ deviceWorldQuat: deviceWorldQuat
1048
+ };
1049
+ }
1050
+
1012
1051
  /**
1013
1052
  * Apply a rotation around an arbitrary pivot point using world-space quaternion
1014
1053
  * math matching the ReconstructionViewer's setMeshPreviewRotationAxis approach.
1015
1054
  * This ensures the runtime axis/pivot matches what the user configured in the
1016
1055
  * animation dialog, regardless of the device's parent transform in the scene.
1017
1056
  *
1018
- * @param {THREE.Object3D} mesh
1019
- * @param {THREE.Vector3} origPos - local position at load time (unused, kept for signature compat)
1020
- * @param {THREE.Euler} origRot - local rotation at load time (unused)
1057
+ * @param {{ mesh, origPos, origRot, deviceModelRoot }} entry
1021
1058
  * @param {Object} anim
1022
1059
  * @param {number} angleDeg - Degrees
1023
- * @param {THREE.Vector3} origWorldPos - world position at load time
1024
- * @param {THREE.Quaternion} origWorldQuat - world quaternion at load time
1025
- * @param {THREE.Vector3} origWorldCenter - world bounding-box center at load time
1060
+ * @param {number} viewerMaxDim
1026
1061
  */
1027
1062
  }, {
1028
1063
  key: "_applyRotation",
1029
- value: function _applyRotation(mesh, origPos, origRot, anim, angleDeg, origWorldPos, origWorldQuat, origWorldCenter, deviceWorldQuat, viewerMaxDim) {
1064
+ value: function _applyRotation(entry, anim, angleDeg, viewerMaxDim) {
1030
1065
  var _anim$rotAxis, _anim$rotAxisOffset;
1066
+ var mesh = entry.mesh,
1067
+ origPos = entry.origPos,
1068
+ origRot = entry.origRot;
1069
+ var restWorld = this._getRestWorldTransforms(entry);
1070
+ var origWorldPos = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldPos;
1071
+ var origWorldQuat = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldQuat;
1072
+ var origWorldCenter = restWorld === null || restWorld === void 0 ? void 0 : restWorld.origWorldCenter;
1073
+ var deviceWorldQuat = restWorld === null || restWorld === void 0 ? void 0 : restWorld.deviceWorldQuat;
1031
1074
  var angle = THREE.MathUtils.degToRad(typeof angleDeg === 'number' ? angleDeg : 0);
1032
1075
  var axis = ((_anim$rotAxis = anim.rotAxis) !== null && _anim$rotAxis !== void 0 ? _anim$rotAxis : 'x').toLowerCase();
1033
1076
  var off = (_anim$rotAxisOffset = anim.rotAxisOffset) !== null && _anim$rotAxisOffset !== void 0 ? _anim$rotAxisOffset : {
@@ -5,7 +5,7 @@ import { BaseDisposable } from '../../core/baseDisposable.js';
5
5
  // ---------------------------------------------------------------------------
6
6
  // Inline styles (injected once into the document head)
7
7
  // ---------------------------------------------------------------------------
8
- var TOOLTIP_STYLES = "\n.cp-tooltip {\n position: absolute;\n pointer-events: auto;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n user-select: none;\n min-width: 200px;\n max-width: 280px;\n transform: translate(-50%, -100%);\n margin-top: -12px;\n transition: opacity 0.15s ease;\n}\n\n.cp-tooltip__card {\n background: rgba(28, 32, 40, 0.95);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 10px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);\n overflow: hidden;\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__header {\n padding: 10px 14px;\n font-weight: 600;\n font-size: 14px;\n color: #e8ecf1;\n background: rgba(255, 255, 255, 0.04);\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 I/O Devices section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__io-section {\n position: relative;\n}\n\n.cp-tooltip__io-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n color: #a4adba;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease;\n}\n\n.cp-tooltip__io-trigger:hover {\n background: rgba(255, 255, 255, 0.06);\n color: #e8ecf1;\n}\n\n.cp-tooltip__io-trigger-label {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n font-weight: 500;\n}\n\n.cp-tooltip__io-arrow {\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__io-arrow {\n transform: rotate(90deg);\n}\n\n/* \u2500\u2500 Device list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__device-list {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__device-list {\n max-height: 300px;\n}\n\n.cp-tooltip__device-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 14px 6px 20px;\n color: #c1c8d1;\n font-size: 12px;\n border-top: 1px solid rgba(255, 255, 255, 0.04);\n transition: background 0.1s ease;\n}\n\n.cp-tooltip__device-item:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n\n.cp-tooltip__device-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #4fc3f7;\n flex-shrink: 0;\n}\n\n.cp-tooltip__device-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__no-devices {\n padding: 8px 14px;\n color: #6b7280;\n font-size: 12px;\n font-style: italic;\n}\n\n/* \u2500\u2500 Caret arrow pointing down \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__caret {\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 7px solid rgba(28, 32, 40, 0.95);\n margin: 0 auto;\n position: relative;\n top: -1px;\n}\n\n/* \u2500\u2500 Data point rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__dp-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 12px 4px 30px;\n font-size: 11.5px;\n border-top: 1px solid rgba(255, 255, 255, 0.025);\n min-height: 24px;\n}\n\n.cp-tooltip__dp-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #7c8d9e;\n}\n\n/* Read-only state badge */\n.cp-tooltip__dp-badge {\n font-size: 10.5px;\n font-weight: 600;\n padding: 1px 7px;\n border-radius: 8px;\n flex-shrink: 0;\n background: rgba(255, 255, 255, 0.07);\n color: #9aabb8;\n min-width: 30px;\n text-align: center;\n letter-spacing: 0.2px;\n}\n\n.cp-tooltip__dp-badge.active {\n background: rgba(76, 175, 80, 0.28);\n color: #a5d6a7;\n}\n\n/* Binary toggle button */\n.cp-tooltip__dp-toggle {\n font-size: 10.5px;\n font-weight: 600;\n padding: 2px 9px;\n border-radius: 8px;\n flex-shrink: 0;\n border: 1px solid rgba(255, 255, 255, 0.14);\n background: rgba(255, 255, 255, 0.06);\n color: #c1c8d1;\n cursor: pointer;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n\n.cp-tooltip__dp-toggle:hover {\n background: rgba(255, 255, 255, 0.12);\n border-color: rgba(255, 255, 255, 0.25);\n}\n\n.cp-tooltip__dp-toggle.on {\n background: rgba(76, 175, 80, 0.28);\n border-color: rgba(76, 175, 80, 0.55);\n color: #a5d6a7;\n}\n\n/* Number / float input */\n.cp-tooltip__dp-input {\n width: 62px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 4px;\n color: #e8ecf1;\n font-size: 11px;\n padding: 2px 5px;\n text-align: right;\n outline: none;\n flex-shrink: 0;\n}\n\n.cp-tooltip__dp-input:focus {\n border-color: rgba(79, 195, 247, 0.55);\n}\n\n/* Enum select */\n.cp-tooltip__dp-select {\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 4px;\n color: #e8ecf1;\n font-size: 11px;\n padding: 2px 4px;\n outline: none;\n flex-shrink: 0;\n max-width: 96px;\n cursor: pointer;\n}\n\n.cp-tooltip__dp-select:focus {\n border-color: rgba(79, 195, 247, 0.55);\n}\n\n/* Unit suffix */\n.cp-tooltip__dp-unit {\n font-size: 10px;\n color: #505c68;\n flex-shrink: 0;\n margin-left: -4px;\n}\n";
8
+ var TOOLTIP_STYLES = "\n.cp-tooltip {\n position: absolute;\n pointer-events: auto;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n user-select: none;\n min-width: 200px;\n max-width: 280px;\n transform: translate(-50%, -100%);\n margin-top: -12px;\n transition: opacity 0.15s ease;\n color-scheme: light;\n}\n\n.cp-tooltip__card {\n background: rgba(255, 255, 255, 0.97);\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n overflow: hidden;\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__header {\n padding: 10px 14px;\n font-weight: 600;\n font-size: 14px;\n color: #2c3e50;\n background: rgba(0, 0, 0, 0.02);\n border-bottom: 1px solid rgba(0, 0, 0, 0.08);\n letter-spacing: 0.2px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 I/O Devices section \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__io-section {\n position: relative;\n}\n\n.cp-tooltip__io-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 14px;\n color: #666666;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease;\n}\n\n.cp-tooltip__io-trigger:hover {\n background: rgba(0, 0, 0, 0.04);\n color: #333333;\n}\n\n.cp-tooltip__io-trigger-label {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n font-weight: 500;\n}\n\n.cp-tooltip__io-arrow {\n font-size: 10px;\n transition: transform 0.2s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__io-arrow {\n transform: rotate(90deg);\n}\n\n/* \u2500\u2500 Device list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__device-list {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.25s ease;\n}\n\n.cp-tooltip__io-section.expanded .cp-tooltip__device-list {\n max-height: 300px;\n}\n\n.cp-tooltip__device-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 14px 6px 20px;\n color: #555555;\n font-size: 12px;\n border-top: 1px solid rgba(0, 0, 0, 0.06);\n transition: background 0.1s ease;\n}\n\n.cp-tooltip__device-item:hover {\n background: rgba(0, 0, 0, 0.03);\n}\n\n.cp-tooltip__device-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #1976d2;\n flex-shrink: 0;\n}\n\n.cp-tooltip__device-name {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__no-devices {\n padding: 8px 14px;\n color: #888888;\n font-size: 12px;\n font-style: italic;\n}\n\n/* \u2500\u2500 Caret arrow pointing down \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__caret {\n width: 0;\n height: 0;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-top: 7px solid rgba(255, 255, 255, 0.97);\n margin: 0 auto;\n position: relative;\n top: -1px;\n filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.1));\n}\n\n/* \u2500\u2500 Data point rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.cp-tooltip__dp-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 4px 12px 4px 30px;\n font-size: 11.5px;\n border-top: 1px solid rgba(0, 0, 0, 0.05);\n min-height: 24px;\n}\n\n.cp-tooltip__dp-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: #666666;\n}\n\n/* Read-only state badge */\n.cp-tooltip__dp-badge {\n font-size: 10.5px;\n font-weight: 600;\n padding: 1px 7px;\n border-radius: 8px;\n flex-shrink: 0;\n background: #f0f2f5;\n color: #666666;\n min-width: 30px;\n text-align: center;\n letter-spacing: 0.2px;\n}\n\n.cp-tooltip__dp-badge.active {\n background: rgba(76, 175, 80, 0.15);\n color: #2e7d32;\n}\n\n/* Binary toggle button */\n.cp-tooltip__dp-toggle {\n font-size: 10.5px;\n font-weight: 600;\n padding: 2px 9px;\n border-radius: 8px;\n flex-shrink: 0;\n border: 1px solid #dddddd;\n background: #f8f9fa;\n color: #555555;\n cursor: pointer;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n\n.cp-tooltip__dp-toggle:hover {\n background: #eeeeee;\n border-color: #cccccc;\n}\n\n.cp-tooltip__dp-toggle.on {\n background: rgba(76, 175, 80, 0.15);\n border-color: rgba(76, 175, 80, 0.45);\n color: #2e7d32;\n}\n\n/* Number / float input */\n.cp-tooltip__dp-input {\n width: 62px;\n background: #f8f9fa;\n border: 1px solid #dddddd;\n border-radius: 4px;\n color: #333333;\n font-size: 11px;\n padding: 2px 5px;\n text-align: right;\n outline: none;\n flex-shrink: 0;\n}\n\n.cp-tooltip__dp-input:focus {\n border-color: #1976d2;\n}\n\n/* Enum select */\n.cp-tooltip__dp-select {\n background: #f8f9fa;\n border: 1px solid #dddddd;\n border-radius: 4px;\n color: #333333;\n font-size: 11px;\n padding: 2px 4px;\n outline: none;\n flex-shrink: 0;\n max-width: 96px;\n cursor: pointer;\n}\n\n.cp-tooltip__dp-select:focus {\n border-color: #1976d2;\n}\n\n/* Unit suffix */\n.cp-tooltip__dp-unit {\n font-size: 10px;\n color: #888888;\n flex-shrink: 0;\n margin-left: -4px;\n}\n";
9
9
  var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
10
10
  /**
11
11
  * @param {Object} sceneViewer - The sceneViewer instance
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.3.36",
3
+ "version": "0.3.37",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/src/index.js",