@2112-lab/central-plant 0.3.38 → 0.3.41

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.
Files changed (29) hide show
  1. package/dist/bundle/index.js +1096 -562
  2. package/dist/cjs/src/core/centralPlant.js +115 -68
  3. package/dist/cjs/src/core/centralPlantInternals.js +20 -36
  4. package/dist/cjs/src/index.js +23 -0
  5. package/dist/cjs/src/managers/behaviors/IoBehaviorManager.js +175 -234
  6. package/dist/cjs/src/managers/components/componentDataManager.js +63 -11
  7. package/dist/cjs/src/managers/scene/componentTooltipManager.js +95 -65
  8. package/dist/cjs/src/managers/scene/modelManager.js +93 -145
  9. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +8 -3
  10. package/dist/cjs/src/utils/animationTransformUtils.js +82 -0
  11. package/dist/cjs/src/utils/behaviorDispatch.js +62 -0
  12. package/dist/cjs/src/utils/behaviorRegistration.js +76 -0
  13. package/dist/cjs/src/utils/behaviorSceneUtils.js +155 -0
  14. package/dist/cjs/src/utils/behaviorSchema.js +209 -0
  15. package/dist/esm/src/core/centralPlant.js +115 -68
  16. package/dist/esm/src/core/centralPlantInternals.js +21 -37
  17. package/dist/esm/src/index.js +5 -0
  18. package/dist/esm/src/managers/behaviors/IoBehaviorManager.js +176 -235
  19. package/dist/esm/src/managers/components/componentDataManager.js +63 -11
  20. package/dist/esm/src/managers/scene/componentTooltipManager.js +95 -65
  21. package/dist/esm/src/managers/scene/modelManager.js +94 -146
  22. package/dist/esm/src/managers/scene/sceneOperationsManager.js +8 -3
  23. package/dist/esm/src/utils/animationTransformUtils.js +56 -0
  24. package/dist/esm/src/utils/behaviorDispatch.js +56 -0
  25. package/dist/esm/src/utils/behaviorRegistration.js +71 -0
  26. package/dist/esm/src/utils/behaviorSceneUtils.js +147 -0
  27. package/dist/esm/src/utils/behaviorSchema.js +201 -0
  28. package/dist/index.d.ts +186 -1
  29. package/package.json +1 -1
@@ -375,6 +375,14 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
375
375
  if (!_this3.componentDictionary[key]) {
376
376
  newComponents[key] = component;
377
377
  } else {
378
+ var _component$behaviors;
379
+ var existing = _this3.componentDictionary[key];
380
+ if (component.behaviorConfig) existing.behaviorConfig = component.behaviorConfig;
381
+ if (component.meshNameMap) existing.meshNameMap = component.meshNameMap;
382
+ if ((_component$behaviors = component.behaviors) !== null && _component$behaviors !== void 0 && _component$behaviors.length) existing.behaviors = component.behaviors;
383
+ if (component.attachedDevices) {
384
+ existing.attachedDevices = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, existing.attachedDevices || {}), component.attachedDevices);
385
+ }
378
386
  console.log("\u26A0\uFE0F Skipping duplicate component: ".concat(key, " (").concat(component.name || 'unnamed', ")"));
379
387
  }
380
388
  });
@@ -406,17 +414,61 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
406
414
  }
407
415
  return extendComponentDictionary;
408
416
  }()
417
+ /**
418
+ * Merge mesh animation configs from host persistence into the live dictionary.
419
+ * @param {Object<string, { behaviorConfig?: Array, meshNameMap?: Object, behaviors?: Array }>} configsByAssetId
420
+ * @returns {number} Count of entries updated
421
+ */
422
+ )
423
+ }, {
424
+ key: "mergeBehaviorConfigsIntoDictionary",
425
+ value: function mergeBehaviorConfigsIntoDictionary(configsByAssetId) {
426
+ var _this$sceneViewer2;
427
+ if (!configsByAssetId || !this.componentDictionary) return 0;
428
+ var merged = 0;
429
+ for (var _i2 = 0, _Object$entries2 = Object.entries(configsByAssetId); _i2 < _Object$entries2.length; _i2++) {
430
+ var _Object$entries2$_i = _rollupPluginBabelHelpers.slicedToArray(_Object$entries2[_i2], 2),
431
+ assetId = _Object$entries2$_i[0],
432
+ attrs = _Object$entries2$_i[1];
433
+ var entry = this.componentDictionary[assetId];
434
+ if (!entry || !attrs) continue;
435
+ var updated = false;
436
+ if (attrs.behaviorConfig) {
437
+ entry.behaviorConfig = attrs.behaviorConfig;
438
+ updated = true;
439
+ }
440
+ if (attrs.meshNameMap) {
441
+ entry.meshNameMap = attrs.meshNameMap;
442
+ updated = true;
443
+ }
444
+ if ('behaviors' in attrs) {
445
+ var _attrs$behaviors;
446
+ if ((_attrs$behaviors = attrs.behaviors) !== null && _attrs$behaviors !== void 0 && _attrs$behaviors.length) {
447
+ entry.behaviors = attrs.behaviors;
448
+ } else {
449
+ delete entry.behaviors;
450
+ }
451
+ updated = true;
452
+ }
453
+ if (updated) merged++;
454
+ }
455
+ var modelPreloader = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.centralPlant) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.utilities) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.modelPreloader;
456
+ if (modelPreloader) {
457
+ modelPreloader.componentDictionary = this.componentDictionary;
458
+ }
459
+ return merged;
460
+ }
461
+
409
462
  /**
410
463
  * Remove S3 components from the component dictionary
411
464
  * @returns {Promise<boolean>} True if components were removed successfully
412
465
  */
413
- )
414
466
  }, {
415
467
  key: "removeS3Components",
416
468
  value: (function () {
417
469
  var _removeS3Components = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee4() {
418
470
  var _this4 = this;
419
- var _this$sceneViewer2, keysToRemove, _i2, _Object$entries2, _Object$entries2$_i, key, component, modelPreloader, _t2;
471
+ var _this$sceneViewer3, keysToRemove, _i3, _Object$entries3, _Object$entries3$_i, key, component, modelPreloader, _t2;
420
472
  return _rollupPluginBabelHelpers.regenerator().w(function (_context4) {
421
473
  while (1) switch (_context4.n) {
422
474
  case 0:
@@ -433,8 +485,8 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
433
485
  _context4.p = 2;
434
486
  // Find and remove all S3 components
435
487
  keysToRemove = [];
436
- for (_i2 = 0, _Object$entries2 = Object.entries(this.componentDictionary); _i2 < _Object$entries2.length; _i2++) {
437
- _Object$entries2$_i = _rollupPluginBabelHelpers.slicedToArray(_Object$entries2[_i2], 2), key = _Object$entries2$_i[0], component = _Object$entries2$_i[1];
488
+ for (_i3 = 0, _Object$entries3 = Object.entries(this.componentDictionary); _i3 < _Object$entries3.length; _i3++) {
489
+ _Object$entries3$_i = _rollupPluginBabelHelpers.slicedToArray(_Object$entries3[_i3], 2), key = _Object$entries3$_i[0], component = _Object$entries3$_i[1];
438
490
  if (component.isS3Component) {
439
491
  keysToRemove.push(key);
440
492
  }
@@ -446,7 +498,7 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
446
498
  });
447
499
 
448
500
  // Update ModelPreloader's dictionary reference
449
- modelPreloader = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.centralPlant) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.utilities) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.modelPreloader;
501
+ modelPreloader = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.centralPlant) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.utilities) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.modelPreloader;
450
502
  if (modelPreloader) {
451
503
  modelPreloader.componentDictionary = this.componentDictionary;
452
504
  console.log("\uD83D\uDD04 Updated ModelPreloader's dictionary reference (".concat(Object.keys(this.componentDictionary).length, " total components)"));
@@ -479,7 +531,7 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
479
531
  key: "removeComponentFromDictionary",
480
532
  value: (function () {
481
533
  var _removeComponentFromDictionary = _rollupPluginBabelHelpers.asyncToGenerator(/*#__PURE__*/_rollupPluginBabelHelpers.regenerator().m(function _callee5(componentKey) {
482
- var _this$sceneViewer3, modelPreloader, _t3;
534
+ var _this$sceneViewer4, modelPreloader, _t3;
483
535
  return _rollupPluginBabelHelpers.regenerator().w(function (_context5) {
484
536
  while (1) switch (_context5.n) {
485
537
  case 0:
@@ -512,7 +564,7 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
512
564
  delete this.componentDictionary[componentKey];
513
565
 
514
566
  // Update ModelPreloader's dictionary reference
515
- modelPreloader = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.centralPlant) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.utilities) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.modelPreloader;
567
+ modelPreloader = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.centralPlant) === null || _this$sceneViewer4 === void 0 || (_this$sceneViewer4 = _this$sceneViewer4.utilities) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.modelPreloader;
516
568
  if (modelPreloader) {
517
569
  modelPreloader.componentDictionary = this.componentDictionary;
518
570
  console.log("\uD83D\uDD04 Updated ModelPreloader's dictionary reference (".concat(Object.keys(this.componentDictionary).length, " total components)"));
@@ -612,10 +664,10 @@ var ComponentDataManager = /*#__PURE__*/function (_BaseDisposable) {
612
664
  if (!this.componentDictionary) return 'unknown';
613
665
 
614
666
  // Look for component in dictionary
615
- for (var _i3 = 0, _Object$entries3 = Object.entries(this.componentDictionary); _i3 < _Object$entries3.length; _i3++) {
616
- var _Object$entries3$_i = _rollupPluginBabelHelpers.slicedToArray(_Object$entries3[_i3], 2),
617
- key = _Object$entries3$_i[0],
618
- component = _Object$entries3$_i[1];
667
+ for (var _i4 = 0, _Object$entries4 = Object.entries(this.componentDictionary); _i4 < _Object$entries4.length; _i4++) {
668
+ var _Object$entries4$_i = _rollupPluginBabelHelpers.slicedToArray(_Object$entries4[_i4], 2),
669
+ key = _Object$entries4$_i[0],
670
+ component = _Object$entries4$_i[1];
619
671
  if (key === 'categories') continue;
620
672
  if (name && name.toLowerCase().includes(key.toLowerCase())) {
621
673
  return component.category || 'unknown';
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var _rollupPluginBabelHelpers = require('../../../_virtual/_rollupPluginBabelHelpers.js');
6
6
  var THREE = require('three');
7
7
  var baseDisposable = require('../../core/baseDisposable.js');
8
+ var behaviorDispatch = require('../../utils/behaviorDispatch.js');
8
9
 
9
10
  function _interopNamespace(e) {
10
11
  if (e && e.__esModule) return e;
@@ -35,6 +36,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
35
36
  * @param {Object} sceneViewer - The sceneViewer instance
36
37
  */
37
38
  function ComponentTooltipManager(sceneViewer) {
39
+ var _this$sceneViewer, _this$sceneViewer$on;
38
40
  var _this;
39
41
  _rollupPluginBabelHelpers.classCallCheck(this, ComponentTooltipManager);
40
42
  _this = _rollupPluginBabelHelpers.callSuper(this, ComponentTooltipManager);
@@ -59,29 +61,38 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
59
61
  * Map of live DOM elements that display current data point values.
60
62
  * Key: `${attachmentId}::${dataPointId}`
61
63
  * Value: { el: HTMLElement, dp: Object, isInput: boolean }
62
- * Populated during _buildTooltip; polled each frame in _refreshStateDisplays.
64
+ * Populated during _buildTooltip; refreshed on io-device-state-changed events.
63
65
  * @type {Map<string, {el: HTMLElement, dp: Object, isInput: boolean}>}
64
66
  */
65
67
  _this._stateElements = new Map();
68
+ _this._onIoStateChanged = _this._onIoStateChanged.bind(_this);
66
69
  _this._injectStyles();
67
70
  _this._onKeyDown = _this._onKeyDown.bind(_this);
68
71
  document.addEventListener('keydown', _this._onKeyDown);
72
+ (_this$sceneViewer = _this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer$on = _this$sceneViewer.on) === null || _this$sceneViewer$on === void 0 || _this$sceneViewer$on.call(_this$sceneViewer, 'io-device-state-changed', _this._onIoStateChanged);
69
73
  return _this;
70
74
  }
71
-
72
- // -----------------------------------------------------------------------
73
- // Lifecycle
74
- // -----------------------------------------------------------------------
75
-
76
- /**
77
- * Called automatically by BaseDisposable.dispose()
78
- * @override
79
- */
80
75
  _rollupPluginBabelHelpers.inherits(ComponentTooltipManager, _BaseDisposable);
81
76
  return _rollupPluginBabelHelpers.createClass(ComponentTooltipManager, [{
77
+ key: "_onIoStateChanged",
78
+ value: function _onIoStateChanged() {
79
+ this._refreshStateDisplays();
80
+ }
81
+
82
+ // -----------------------------------------------------------------------
83
+ // Lifecycle
84
+ // -----------------------------------------------------------------------
85
+
86
+ /**
87
+ * Called automatically by BaseDisposable.dispose()
88
+ * @override
89
+ */
90
+ }, {
82
91
  key: "_doDispose",
83
92
  value: function _doDispose() {
93
+ var _this$sceneViewer2, _this$sceneViewer2$of;
84
94
  document.removeEventListener('keydown', this._onKeyDown);
95
+ (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2$of = _this$sceneViewer2.off) === null || _this$sceneViewer2$of === void 0 || _this$sceneViewer2$of.call(_this$sceneViewer2, 'io-device-state-changed', this._onIoStateChanged);
85
96
  this.hide();
86
97
  this._removeStyleTag();
87
98
  this._stateElements.clear();
@@ -129,7 +140,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
129
140
  }, {
130
141
  key: "toggleIODeviceBinaryState",
131
142
  value: function toggleIODeviceBinaryState(ioDeviceObject) {
132
- var _ref, _this$sceneViewer;
143
+ var _ref;
133
144
  if (!ioDeviceObject || !this._stateAdapter) return;
134
145
  var ud = ioDeviceObject.userData;
135
146
  var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
@@ -150,7 +161,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
150
161
 
151
162
  // Create a scoped attachment key to prevent state sharing between instances
152
163
  // of the same smart component that share the same attachmentId
153
- var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
164
+ var scopedAttachmentId = behaviorDispatch.getScopedAttachmentKey(attachmentId, parentUuid);
154
165
 
155
166
  // Find the first binary state
156
167
  var binaryState = dataPoints.find(function (dp) {
@@ -162,8 +173,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
162
173
  // Fall back to defaultValue when state is uninitialized (null/undefined)
163
174
  var currentVal = (_ref = storedVal !== null && storedVal !== void 0 ? storedVal : binaryState.defaultValue) !== null && _ref !== void 0 ? _ref : false;
164
175
  var newVal = !Boolean(currentVal);
165
- this._stateAdapter.setState(scopedAttachmentId, dpId, newVal);
166
- (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.ioBehaviorManager) === null || _this$sceneViewer === void 0 || _this$sceneViewer.triggerState(attachmentId, dpId, newVal, parentUuid);
176
+ this._dispatchIoState(attachmentId, dpId, newVal, parentUuid);
167
177
  console.log("\uD83D\uDD04 [IODevice] Toggled ".concat(scopedAttachmentId, ".").concat(dpId, ": ").concat(currentVal, " \u2192 ").concat(newVal));
168
178
  }
169
179
 
@@ -179,8 +189,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
179
189
  }, {
180
190
  key: "startIODeviceDrag",
181
191
  value: function startIODeviceDrag(ioDeviceObject, hitMesh) {
182
- var _this$sceneViewer2,
183
- _this2 = this;
192
+ var _this2 = this;
184
193
  if (!ioDeviceObject || !this._stateAdapter) return;
185
194
  var ud = ioDeviceObject.userData;
186
195
  var attachmentId = ud === null || ud === void 0 ? void 0 : ud.attachmentId;
@@ -195,15 +204,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
195
204
  }
196
205
  obj = obj.parent;
197
206
  }
198
- var scopedAttachmentId = this._getScopedAttachmentKey(attachmentId, parentUuid);
199
- var ioBehavMgr = (_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 || (_this$sceneViewer2 = _this$sceneViewer2.managers) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.ioBehaviorManager;
200
- var dataPoints = ((ioBehavMgr === null || ioBehavMgr === void 0 ? void 0 : ioBehavMgr.getAnimationDataPoints(parentUuid, attachmentId, hitMesh)) || []).concat((ud === null || ud === void 0 ? void 0 : ud.dataPoints) || [])
201
- // deduplicate by id
202
- .filter(function (dp, i, arr) {
203
- return arr.findIndex(function (d) {
204
- return d.id === dp.id;
205
- }) === i;
206
- });
207
+ var scopedAttachmentId = behaviorDispatch.getScopedAttachmentKey(attachmentId, parentUuid);
208
+ var dataPoints = behaviorDispatch.resolveDataPoints(parentUuid, attachmentId, ud, behaviorDispatch.getIoBehaviorManager(this.sceneViewer), hitMesh);
207
209
  var dpSessions = [];
208
210
  var _iterator = _rollupPluginBabelHelpers.createForOfIteratorHelper(dataPoints),
209
211
  _step;
@@ -212,7 +214,6 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
212
214
  var _this2$_stateAdapter$;
213
215
  var dp = _step.value;
214
216
  var stateType = (dp.stateType || '').toLowerCase();
215
- if (stateType !== 'binary' && stateType !== 'boolean' && stateType !== 'enum') return 1; // continue
216
217
  var curVal = (_this2$_stateAdapter$ = _this2._stateAdapter.getState(scopedAttachmentId, dp.id)) !== null && _this2$_stateAdapter$ !== void 0 ? _this2$_stateAdapter$ : dp.defaultValue;
217
218
  if (stateType === 'binary' || stateType === 'boolean') {
218
219
  dpSessions.push({
@@ -223,7 +224,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
223
224
  stateType: 'binary',
224
225
  lastApplied: curVal
225
226
  });
226
- } else {
227
+ } else if (stateType === 'enum') {
227
228
  var _dp$stateConfig;
228
229
  var opts = ((_dp$stateConfig = dp.stateConfig) === null || _dp$stateConfig === void 0 ? void 0 : _dp$stateConfig.options) || [];
229
230
  var curIdx = opts.findIndex(function (o) {
@@ -239,10 +240,27 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
239
240
  startIdx: curIdx >= 0 ? curIdx : 0,
240
241
  lastAppliedIdx: curIdx >= 0 ? curIdx : 0
241
242
  });
243
+ } else if (stateType === 'number' || stateType === 'continuous') {
244
+ var _dp$stateConfig$min, _dp$stateConfig2, _dp$stateConfig$max, _dp$stateConfig3;
245
+ var min = (_dp$stateConfig$min = (_dp$stateConfig2 = dp.stateConfig) === null || _dp$stateConfig2 === void 0 ? void 0 : _dp$stateConfig2.min) !== null && _dp$stateConfig$min !== void 0 ? _dp$stateConfig$min : 0;
246
+ var max = (_dp$stateConfig$max = (_dp$stateConfig3 = dp.stateConfig) === null || _dp$stateConfig3 === void 0 ? void 0 : _dp$stateConfig3.max) !== null && _dp$stateConfig$max !== void 0 ? _dp$stateConfig$max : 1;
247
+ var parsed = Number(curVal);
248
+ var startVal = Number.isFinite(parsed) ? Math.max(min, Math.min(max, parsed)) : min;
249
+ dpSessions.push({
250
+ dp: dp,
251
+ scopedAttachmentId: scopedAttachmentId,
252
+ attachmentId: attachmentId,
253
+ parentUuid: parentUuid,
254
+ stateType: 'number',
255
+ min: min,
256
+ max: max,
257
+ startVal: startVal,
258
+ lastApplied: startVal
259
+ });
242
260
  }
243
261
  };
244
262
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
245
- if (_loop()) continue;
263
+ _loop();
246
264
  }
247
265
  } catch (err) {
248
266
  _iterator.e(err);
@@ -259,8 +277,9 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
259
277
  * Called continuously during pointermove.
260
278
  *
261
279
  * Sign convention: up/right = positive `signedDelta`.
262
- * - Binary: > +20 px → true/on state, < −20 px → false/off state.
263
- * - Enum: each ±30 px step advances/retreats one option in the list.
280
+ * - Binary: > +20 px → true/on state, < −20 px → false/off state.
281
+ * - Enum: each ±30 px step advances/retreats one option in the list.
282
+ * - Continuous: ±200 px spans the configured min→max range.
264
283
  *
265
284
  * @param {number} signedDelta - Cumulative signed pixel displacement since drag start
266
285
  */
@@ -271,6 +290,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
271
290
  if (!session) return;
272
291
  var BINARY_THRESHOLD = 20;
273
292
  var ENUM_STEP_PX = 30;
293
+ var RANGE_PX = 200;
274
294
  var _iterator2 = _rollupPluginBabelHelpers.createForOfIteratorHelper(session.dpSessions),
275
295
  _step2;
276
296
  try {
@@ -294,6 +314,13 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
294
314
  if (newIdx === dps.lastAppliedIdx) continue;
295
315
  dps.lastAppliedIdx = newIdx;
296
316
  this._applyDpState(dps, dps.opts[newIdx]);
317
+ } else if (dps.stateType === 'number') {
318
+ var span = dps.max - dps.min || 1;
319
+ var delta = signedDelta / RANGE_PX * span;
320
+ var _newVal = Math.max(dps.min, Math.min(dps.max, dps.startVal + delta));
321
+ if (Math.abs(_newVal - dps.lastApplied) < span * 0.001) continue;
322
+ dps.lastApplied = _newVal;
323
+ this._applyDpState(dps, _newVal);
297
324
  }
298
325
  }
299
326
  } catch (err) {
@@ -319,14 +346,35 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
319
346
  }, {
320
347
  key: "_applyDpState",
321
348
  value: function _applyDpState(_ref2, newVal) {
322
- var _this$_stateAdapter, _this$sceneViewer3;
323
- var scopedAttachmentId = _ref2.scopedAttachmentId,
324
- attachmentId = _ref2.attachmentId,
349
+ _ref2.scopedAttachmentId;
350
+ var attachmentId = _ref2.attachmentId,
325
351
  parentUuid = _ref2.parentUuid,
326
352
  dp = _ref2.dp;
327
- var dpId = dp.id;
328
- (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 || _this$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
329
- (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.managers) === null || _this$sceneViewer3 === void 0 || (_this$sceneViewer3 = _this$sceneViewer3.ioBehaviorManager) === null || _this$sceneViewer3 === void 0 || _this$sceneViewer3.triggerState(attachmentId, dpId, newVal, parentUuid);
353
+ this._dispatchIoState(attachmentId, dp.id, newVal, parentUuid);
354
+ }
355
+
356
+ /**
357
+ * Unified I/O state dispatch with fallback when centralPlant is unavailable.
358
+ * @private
359
+ */
360
+ }, {
361
+ key: "_dispatchIoState",
362
+ value: function _dispatchIoState(attachmentId, stateId, value, parentUuid) {
363
+ var _this$sceneViewer3, _this$_stateAdapter, _getIoBehaviorManager, _this$sceneViewer4;
364
+ var cp = (_this$sceneViewer3 = this.sceneViewer) === null || _this$sceneViewer3 === void 0 ? void 0 : _this$sceneViewer3.centralPlant;
365
+ if (cp && typeof cp._dispatchIoState === 'function') {
366
+ cp._dispatchIoState(attachmentId, stateId, value, parentUuid);
367
+ return;
368
+ }
369
+ var scopedKey = behaviorDispatch.getScopedAttachmentKey(attachmentId, parentUuid);
370
+ (_this$_stateAdapter = this._stateAdapter) === null || _this$_stateAdapter === void 0 || _this$_stateAdapter.setState(scopedKey, stateId, value);
371
+ (_getIoBehaviorManager = behaviorDispatch.getIoBehaviorManager(this.sceneViewer)) === null || _getIoBehaviorManager === void 0 || _getIoBehaviorManager.triggerState(attachmentId, stateId, value, parentUuid);
372
+ (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 || _this$sceneViewer4.emit('io-device-state-changed', {
373
+ attachmentId: attachmentId,
374
+ stateId: stateId,
375
+ value: value,
376
+ parentUuid: parentUuid || null
377
+ });
330
378
  }
331
379
 
332
380
  /**
@@ -359,7 +407,6 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
359
407
  value: function update() {
360
408
  if (!this.tooltipEl || !this.selectedObject) return;
361
409
  this._positionTooltip();
362
- this._refreshStateDisplays();
363
410
  }
364
411
 
365
412
  /**
@@ -418,21 +465,6 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
418
465
  this._styleInjected = false;
419
466
  }
420
467
 
421
- /**
422
- * Generate a scoped attachment key that includes the parent component UUID.
423
- * This ensures each instance of a smart component has isolated IO device state.
424
- * @param {string} attachmentId - The original attachment ID from the component data
425
- * @param {string|null} parentUuid - The UUID of the parent smart component instance
426
- * @returns {string} A scoped key in the format "parentUuid::attachmentId" or just attachmentId if no parent
427
- * @private
428
- */
429
- }, {
430
- key: "_getScopedAttachmentKey",
431
- value: function _getScopedAttachmentKey(attachmentId, parentUuid) {
432
- if (!parentUuid) return attachmentId;
433
- return "".concat(parentUuid, "::").concat(attachmentId);
434
- }
435
-
436
468
  /**
437
469
  * Gather I/O device children from a component's Three.js hierarchy.
438
470
  * Returns richer data including attachmentId and data point definitions.
@@ -448,12 +480,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
448
480
  object.traverse(function (child) {
449
481
  var _child$userData;
450
482
  if (((_child$userData = child.userData) === null || _child$userData === void 0 ? void 0 : _child$userData.objectType) === 'io-device') {
451
- var _this3$sceneViewer$ma, _this3$sceneViewer;
452
483
  var attachmentId = child.userData.attachmentId || '';
453
484
 
454
485
  // Use only data points from the animate window (behaviorConfig).
455
486
  // The static ioConfig.states[] snapshot on userData is intentionally ignored.
456
- var dataPoints = (_this3$sceneViewer$ma = (_this3$sceneViewer = _this3.sceneViewer) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.managers) === null || _this3$sceneViewer === void 0 || (_this3$sceneViewer = _this3$sceneViewer.ioBehaviorManager) === null || _this3$sceneViewer === void 0 ? void 0 : _this3$sceneViewer.getAnimationDataPoints(parentUuid, attachmentId)) !== null && _this3$sceneViewer$ma !== void 0 ? _this3$sceneViewer$ma : [];
487
+ var dataPoints = behaviorDispatch.resolveDataPoints(parentUuid, attachmentId, child.userData, behaviorDispatch.getIoBehaviorManager(_this3.sceneViewer));
457
488
 
458
489
  // When data points come from behaviorConfig they already carry direction:'input'.
459
490
  // Pass null so _buildDataPointRow uses the per-dp direction instead of the
@@ -463,7 +494,7 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
463
494
  label: child.userData.attachmentLabel || child.name || child.userData.deviceId || 'Unknown Device',
464
495
  deviceId: child.userData.deviceId || '',
465
496
  attachmentId: attachmentId,
466
- scopedAttachmentId: _this3._getScopedAttachmentKey(attachmentId, parentUuid),
497
+ scopedAttachmentId: behaviorDispatch.getScopedAttachmentKey(attachmentId, parentUuid),
467
498
  dataPoints: dataPoints,
468
499
  direction: deviceDirection
469
500
  });
@@ -609,11 +640,11 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
609
640
  }, {
610
641
  key: "_positionTooltip",
611
642
  value: function _positionTooltip() {
612
- var _this$sceneViewer4, _this$sceneViewer5;
643
+ var _this$sceneViewer5, _this$sceneViewer6;
613
644
  if (!this.tooltipEl || !this.selectedObject) return;
614
645
  var container = this._getContainer();
615
- var camera = (_this$sceneViewer4 = this.sceneViewer) === null || _this$sceneViewer4 === void 0 ? void 0 : _this$sceneViewer4.camera;
616
- var renderer = (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.renderer;
646
+ var camera = (_this$sceneViewer5 = this.sceneViewer) === null || _this$sceneViewer5 === void 0 ? void 0 : _this$sceneViewer5.camera;
647
+ var renderer = (_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.renderer;
617
648
  if (!container || !camera || !renderer) return;
618
649
 
619
650
  // Compute bounding box to position above the component
@@ -650,8 +681,8 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
650
681
  }, {
651
682
  key: "_getContainer",
652
683
  value: function _getContainer() {
653
- var _this$sceneViewer6;
654
- return ((_this$sceneViewer6 = this.sceneViewer) === null || _this$sceneViewer6 === void 0 || (_this$sceneViewer6 = _this$sceneViewer6.renderer) === null || _this$sceneViewer6 === void 0 || (_this$sceneViewer6 = _this$sceneViewer6.domElement) === null || _this$sceneViewer6 === void 0 ? void 0 : _this$sceneViewer6.parentElement) || null;
684
+ var _this$sceneViewer7;
685
+ return ((_this$sceneViewer7 = this.sceneViewer) === null || _this$sceneViewer7 === void 0 || (_this$sceneViewer7 = _this$sceneViewer7.renderer) === null || _this$sceneViewer7 === void 0 || (_this$sceneViewer7 = _this$sceneViewer7.domElement) === null || _this$sceneViewer7 === void 0 ? void 0 : _this$sceneViewer7.parentElement) || null;
655
686
  }
656
687
 
657
688
  // -----------------------------------------------------------------------
@@ -691,10 +722,9 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
691
722
  var currentVal = (_ref3 = (_this$_stateAdapter$g = (_this$_stateAdapter2 = this._stateAdapter) === null || _this$_stateAdapter2 === void 0 ? void 0 : _this$_stateAdapter2.getState(scopedAttachmentId, dpId)) !== null && _this$_stateAdapter$g !== void 0 ? _this$_stateAdapter$g : dp.defaultValue) !== null && _ref3 !== void 0 ? _ref3 : null;
692
723
  if (isInput) {
693
724
  var ctrl = this._buildInputControl(dp, currentVal, function (newVal) {
694
- var _this5$_stateAdapter, _this5$selectedObject, _this5$sceneViewer;
695
- (_this5$_stateAdapter = _this5._stateAdapter) === null || _this5$_stateAdapter === void 0 || _this5$_stateAdapter.setState(scopedAttachmentId, dpId, newVal);
725
+ var _this5$selectedObject;
696
726
  var parentUuid = ((_this5$selectedObject = _this5.selectedObject) === null || _this5$selectedObject === void 0 ? void 0 : _this5$selectedObject.uuid) || null;
697
- (_this5$sceneViewer = _this5.sceneViewer) === null || _this5$sceneViewer === void 0 || (_this5$sceneViewer = _this5$sceneViewer.managers) === null || _this5$sceneViewer === void 0 || (_this5$sceneViewer = _this5$sceneViewer.ioBehaviorManager) === null || _this5$sceneViewer === void 0 || _this5$sceneViewer.triggerState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
727
+ _this5._dispatchIoState(originalAttachmentId || scopedAttachmentId, dpId, newVal, parentUuid);
698
728
  });
699
729
  row.appendChild(ctrl);
700
730
  this._stateElements.set(key, {
@@ -703,9 +733,9 @@ var ComponentTooltipManager = /*#__PURE__*/function (_BaseDisposable) {
703
733
  isInput: true
704
734
  });
705
735
  } else {
706
- var _dp$stateConfig2;
736
+ var _dp$stateConfig4;
707
737
  // unit suffix (optional, shown between name and badge)
708
- var unit = (_dp$stateConfig2 = dp.stateConfig) === null || _dp$stateConfig2 === void 0 ? void 0 : _dp$stateConfig2.unit;
738
+ var unit = (_dp$stateConfig4 = dp.stateConfig) === null || _dp$stateConfig4 === void 0 ? void 0 : _dp$stateConfig4.unit;
709
739
  if (unit) {
710
740
  var unitEl = document.createElement('span');
711
741
  unitEl.className = 'cp-tooltip__dp-unit';