@relu-interactives/spatial-ecs 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{XRHandPose-G0Dl-F0w.d.ts → XRHandPose-CWSrsnYN.d.ts} +144 -1
  2. package/dist/{XRHandPose-VQMT1WXj.d.cts → XRHandPose-Dn2yiALk.d.cts} +144 -1
  3. package/dist/{chunk-G4XJ53LS.js → chunk-JEPUCLZX.js} +3 -3
  4. package/dist/{chunk-G4XJ53LS.js.map → chunk-JEPUCLZX.js.map} +1 -1
  5. package/dist/{chunk-NNDUOQ7O.js → chunk-JNYPJB55.js} +250 -3
  6. package/dist/chunk-JNYPJB55.js.map +1 -0
  7. package/dist/{chunk-SGF73YTF.js → chunk-NXASMBDS.js} +4 -4
  8. package/dist/{chunk-SGF73YTF.js.map → chunk-NXASMBDS.js.map} +1 -1
  9. package/dist/{chunk-JIJQFW6P.js → chunk-VYIWOXAY.js} +1377 -480
  10. package/dist/chunk-VYIWOXAY.js.map +1 -0
  11. package/dist/{chunk-U727NTWQ.js → chunk-WWP46AEF.js} +26 -4
  12. package/dist/chunk-WWP46AEF.js.map +1 -0
  13. package/dist/components/index.cjs +151 -0
  14. package/dist/components/index.cjs.map +1 -1
  15. package/dist/components/index.d.cts +2 -2
  16. package/dist/components/index.d.ts +2 -2
  17. package/dist/components/index.js +1 -1
  18. package/dist/docs/guide/spatial-ui.md +83 -7
  19. package/dist/factories/index.cjs +151 -0
  20. package/dist/factories/index.cjs.map +1 -1
  21. package/dist/factories/index.d.cts +1 -1
  22. package/dist/factories/index.d.ts +1 -1
  23. package/dist/factories/index.js +3 -3
  24. package/dist/{index-D9DjrUn3.d.cts → index-CAZHrh0q.d.cts} +5 -2
  25. package/dist/{index-DppafVu4.d.ts → index-CLtSMf2M.d.ts} +5 -2
  26. package/dist/{index-C61n65PE.d.ts → index-D5MmJNyj.d.ts} +26 -1
  27. package/dist/{index-C70N4BYG.d.cts → index-DiIjV2lm.d.cts} +26 -1
  28. package/dist/index.cjs +1787 -621
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +5 -5
  31. package/dist/index.d.ts +5 -5
  32. package/dist/index.js +10 -10
  33. package/dist/network/index.cjs +177 -4
  34. package/dist/network/index.cjs.map +1 -1
  35. package/dist/network/index.js +3 -3
  36. package/dist/systems/index.cjs +1689 -523
  37. package/dist/systems/index.cjs.map +1 -1
  38. package/dist/systems/index.d.cts +1 -1
  39. package/dist/systems/index.d.ts +1 -1
  40. package/dist/systems/index.js +4 -4
  41. package/package.json +1 -1
  42. package/dist/chunk-JIJQFW6P.js.map +0 -1
  43. package/dist/chunk-NNDUOQ7O.js.map +0 -1
  44. package/dist/chunk-U727NTWQ.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import { getRendererKindOf, readWorldRenderBackend, readWorldRendererKind } from './chunk-OHCNPPMX.js';
2
- import { resolveScriptCtor, Behaviour, normalizeScriptData, defaultModuleLoader, reportScriptError, serializeDataForStorage, toErrorMessage, readScriptPhase, withScriptPhase, mergeDataByCurrentDefaults, resolveInputData, resolvePrefabAssetUrl, reportBehaviourHookError } from './chunk-U727NTWQ.js';
3
- import { createGLTFLoader, SHARED_MODEL_RESOURCE_FLAG, applyTransform, findModelBone } from './chunk-G4XJ53LS.js';
4
- import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, normalizeEnvironmentLighting, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ScriptComponent, ScriptObjectComponent, AudioComponent, VideoComponent, XRInteractableComponent, XRRigComponent, VRTeleportAreaComponent, ParentId, XREnvironmentComponent, UIButtonComponent, UIToggleComponent, UISliderComponent, Name, UIPanelComponent, UITextComponent, XRAnchorComponent, IKComponent, XRInteractorComponent, XRInteractorSideComponent, handPoseDefinitionFor, getHandPosePreset, XRHandPoseComponent } from './chunk-NNDUOQ7O.js';
2
+ import { resolveScriptCtor, Behaviour, normalizeScriptData, defaultModuleLoader, reportScriptError, serializeDataForStorage, toErrorMessage, readScriptPhase, withScriptPhase, mergeDataByCurrentDefaults, resolveInputData, resolvePrefabAssetUrl, reportBehaviourHookError } from './chunk-WWP46AEF.js';
3
+ import { createGLTFLoader, SHARED_MODEL_RESOURCE_FLAG, applyTransform, findModelBone } from './chunk-JEPUCLZX.js';
4
+ import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, normalizeEnvironmentLighting, Environment, Postprocessing, TransformComponent, PhysicsComponent, setPhysicsLiveRef, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ScriptComponent, ScriptObjectComponent, AudioComponent, VideoComponent, UIPanelComponent, UITextComponent, UIButtonComponent, UIToggleComponent, UISliderComponent, ParentId, XRInteractableComponent, XRRigComponent, VRTeleportAreaComponent, XREnvironmentComponent, Name, resolveChrome, chromeBleed, resolveText, resolveToken, styleAssets, XRAnchorComponent, IKComponent, XRInteractorComponent, XRInteractorSideComponent, handPoseDefinitionFor, getHandPosePreset, XRHandPoseComponent } from './chunk-JNYPJB55.js';
5
5
  import { __name } from './chunk-XE4OXN2W.js';
6
- import * as THREE19 from 'three';
6
+ import * as THREE26 from 'three';
7
7
  import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
8
8
  import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js';
9
9
  import { XRHandModelFactory } from 'three/examples/jsm/webxr/XRHandModelFactory.js';
@@ -139,7 +139,7 @@ function transitionToClip(runtime, nextClipId, crossFadeDuration) {
139
139
  }
140
140
  __name(transitionToClip, "transitionToClip");
141
141
  function applyActionState(action, component, paused, shouldLoop) {
142
- const loopMode = shouldLoop ? THREE19.LoopRepeat : THREE19.LoopOnce;
142
+ const loopMode = shouldLoop ? THREE26.LoopRepeat : THREE26.LoopOnce;
143
143
  const repetitions = shouldLoop ? Infinity : 1;
144
144
  action.setLoop(loopMode, repetitions);
145
145
  action.clampWhenFinished = !shouldLoop;
@@ -312,7 +312,7 @@ var _AnimationSystem = class _AnimationSystem {
312
312
  if (runtime) {
313
313
  stopAllActions(runtime);
314
314
  }
315
- const mixer = new THREE19.AnimationMixer(root);
315
+ const mixer = new THREE26.AnimationMixer(root);
316
316
  const entries2 = readRuntimeClipEntries(animation);
317
317
  const indexedActions = createActions(mixer, entries2);
318
318
  runtime = {
@@ -438,11 +438,11 @@ function normalizeOrthoBounds(component) {
438
438
  }
439
439
  __name(normalizeOrthoBounds, "normalizeOrthoBounds");
440
440
  function isPerspectiveCamera(camera) {
441
- return camera instanceof THREE19.PerspectiveCamera;
441
+ return camera instanceof THREE26.PerspectiveCamera;
442
442
  }
443
443
  __name(isPerspectiveCamera, "isPerspectiveCamera");
444
444
  function isOrthographicCamera(camera) {
445
- return camera instanceof THREE19.OrthographicCamera;
445
+ return camera instanceof THREE26.OrthographicCamera;
446
446
  }
447
447
  __name(isOrthographicCamera, "isOrthographicCamera");
448
448
  function readWindowAspect() {
@@ -496,7 +496,7 @@ var _CameraSyncSystem = class _CameraSyncSystem {
496
496
  continue;
497
497
  }
498
498
  const camera = objectRef.object;
499
- if (!(camera instanceof THREE19.Camera)) {
499
+ if (!(camera instanceof THREE26.Camera)) {
500
500
  continue;
501
501
  }
502
502
  if (xrCamera !== null && camera === xrCamera) {
@@ -561,8 +561,8 @@ var ObjectVisibilitySystem = _ObjectVisibilitySystem;
561
561
  function getUrlExtension(url) {
562
562
  const path = url.split(/[?#]/)[0] ?? "";
563
563
  const lastSegment = path.split("/").pop() ?? "";
564
- const dot = lastSegment.lastIndexOf(".");
565
- return dot >= 0 ? lastSegment.slice(dot + 1).toLowerCase() : "";
564
+ const dot2 = lastSegment.lastIndexOf(".");
565
+ return dot2 >= 0 ? lastSegment.slice(dot2 + 1).toLowerCase() : "";
566
566
  }
567
567
  __name(getUrlExtension, "getUrlExtension");
568
568
  async function loadEquirectTexture(url) {
@@ -575,10 +575,10 @@ async function loadEquirectTexture(url) {
575
575
  const { EXRLoader } = await import('three/examples/jsm/loaders/EXRLoader.js');
576
576
  texture = await new EXRLoader().loadAsync(url);
577
577
  } else {
578
- texture = await new THREE19.TextureLoader().loadAsync(url);
579
- texture.colorSpace = THREE19.SRGBColorSpace;
578
+ texture = await new THREE26.TextureLoader().loadAsync(url);
579
+ texture.colorSpace = THREE26.SRGBColorSpace;
580
580
  }
581
- texture.mapping = THREE19.EquirectangularReflectionMapping;
581
+ texture.mapping = THREE26.EquirectangularReflectionMapping;
582
582
  texture.needsUpdate = true;
583
583
  return texture;
584
584
  }
@@ -587,13 +587,13 @@ var rendererStates = /* @__PURE__ */ new WeakMap();
587
587
  function toThreeToneMapping(mode) {
588
588
  switch (mode) {
589
589
  case "aces":
590
- return THREE19.ACESFilmicToneMapping;
590
+ return THREE26.ACESFilmicToneMapping;
591
591
  case "agx":
592
- return THREE19.AgXToneMapping;
592
+ return THREE26.AgXToneMapping;
593
593
  case "neutral":
594
- return THREE19.NeutralToneMapping;
594
+ return THREE26.NeutralToneMapping;
595
595
  default:
596
- return THREE19.NoToneMapping;
596
+ return THREE26.NoToneMapping;
597
597
  }
598
598
  }
599
599
  __name(toThreeToneMapping, "toThreeToneMapping");
@@ -614,7 +614,7 @@ function setRendererToneMappingSource(renderer, source, request) {
614
614
  if (request === null) return;
615
615
  state = {
616
616
  baseline: {
617
- toneMapping: target.toneMapping ?? THREE19.NoToneMapping,
617
+ toneMapping: target.toneMapping ?? THREE26.NoToneMapping,
618
618
  exposure: typeof target.toneMappingExposure === "number" ? target.toneMappingExposure : 1
619
619
  },
620
620
  environment: null,
@@ -645,7 +645,7 @@ var MIN_DENSITY = 0;
645
645
  function createGradientTexture(effect, colors) {
646
646
  if (typeof document === "undefined") {
647
647
  const pixel = new Uint8Array([30, 30, 30, 255]);
648
- const fallback = new THREE19.DataTexture(pixel, 1, 1, THREE19.RGBAFormat);
648
+ const fallback = new THREE26.DataTexture(pixel, 1, 1, THREE26.RGBAFormat);
649
649
  fallback.needsUpdate = true;
650
650
  return fallback;
651
651
  }
@@ -654,7 +654,7 @@ function createGradientTexture(effect, colors) {
654
654
  canvas.height = 512;
655
655
  const context = canvas.getContext("2d");
656
656
  if (!context) {
657
- const fallback = new THREE19.CanvasTexture(canvas);
657
+ const fallback = new THREE26.CanvasTexture(canvas);
658
658
  fallback.needsUpdate = true;
659
659
  return fallback;
660
660
  }
@@ -682,8 +682,8 @@ function createGradientTexture(effect, colors) {
682
682
  context.fillRect(0, 0, 512, 512);
683
683
  context.globalCompositeOperation = "source-over";
684
684
  }
685
- const texture = new THREE19.CanvasTexture(canvas);
686
- texture.colorSpace = THREE19.SRGBColorSpace;
685
+ const texture = new THREE26.CanvasTexture(canvas);
686
+ texture.colorSpace = THREE26.SRGBColorSpace;
687
687
  texture.needsUpdate = true;
688
688
  return texture;
689
689
  }
@@ -700,7 +700,7 @@ function readRenderer(world) {
700
700
  __name(readRenderer, "readRenderer");
701
701
  var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
702
702
  constructor(scene) {
703
- this.textureLoader = new THREE19.TextureLoader();
703
+ this.textureLoader = new THREE26.TextureLoader();
704
704
  /** `true` once an Environment entity has been seen (drives the reset path). */
705
705
  this.hasEnvironment = false;
706
706
  this.skyboxSignature = null;
@@ -784,7 +784,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
784
784
  if (skybox.type === "color") {
785
785
  this.disposeActiveTexture();
786
786
  this.pendingImageUrl = null;
787
- this.setSkyboxBackground(new THREE19.Color(skybox.color || "#1e1e1e"));
787
+ this.setSkyboxBackground(new THREE26.Color(skybox.color || "#1e1e1e"));
788
788
  return;
789
789
  }
790
790
  if (skybox.type === "gradient") {
@@ -819,7 +819,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
819
819
  return;
820
820
  }
821
821
  if (!equirect) {
822
- texture.colorSpace = THREE19.SRGBColorSpace;
822
+ texture.colorSpace = THREE26.SRGBColorSpace;
823
823
  }
824
824
  this.activeTexture = texture;
825
825
  this.setSkyboxBackground(texture);
@@ -847,16 +847,16 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
847
847
  this.scene.fog = null;
848
848
  return;
849
849
  }
850
- const fogColor = new THREE19.Color(fog.color || "#1e1e1e");
850
+ const fogColor = new THREE26.Color(fog.color || "#1e1e1e");
851
851
  if (fog.type === "linear") {
852
852
  const near = Number.isFinite(fog.linear.near) ? fog.linear.near : 1;
853
853
  const farCandidate = Number.isFinite(fog.linear.far) ? fog.linear.far : near + 10;
854
854
  const far = Math.max(near + MIN_FOG_DELTA, farCandidate);
855
- this.scene.fog = new THREE19.Fog(fogColor, near, far);
855
+ this.scene.fog = new THREE26.Fog(fogColor, near, far);
856
856
  return;
857
857
  }
858
858
  const density = Number.isFinite(fog.exponential.density) ? Math.max(MIN_DENSITY, fog.exponential.density) : 2e-3;
859
- this.scene.fog = new THREE19.FogExp2(fogColor, density);
859
+ this.scene.fog = new THREE26.FogExp2(fogColor, density);
860
860
  }
861
861
  // ── Image-based lighting ────────────────────────────────────────────────
862
862
  disposeEnvResources() {
@@ -918,7 +918,7 @@ var _EnvironmentSyncSystem = class _EnvironmentSyncSystem {
918
918
  if (!renderer || getRendererKindOf(renderer) === "webgpu") return null;
919
919
  let generator = null;
920
920
  try {
921
- generator = new THREE19.PMREMGenerator(renderer);
921
+ generator = new THREE26.PMREMGenerator(renderer);
922
922
  return generator.fromEquirectangular(texture);
923
923
  } catch (error) {
924
924
  console.warn(
@@ -1213,7 +1213,7 @@ function computeColliderState(colliderValues, object) {
1213
1213
  if (!object) {
1214
1214
  return colliderValues;
1215
1215
  }
1216
- const scale = new THREE19.Vector3();
1216
+ const scale = new THREE26.Vector3();
1217
1217
  object.getWorldScale(scale);
1218
1218
  return {
1219
1219
  ...colliderValues,
@@ -1264,8 +1264,8 @@ function createColliderDesc(RAPIER, values, triMeshData) {
1264
1264
  values.offsetPosition.y,
1265
1265
  values.offsetPosition.z
1266
1266
  );
1267
- const offsetQuaternion = new THREE19.Quaternion().setFromEuler(
1268
- new THREE19.Euler(
1267
+ const offsetQuaternion = new THREE26.Quaternion().setFromEuler(
1268
+ new THREE26.Euler(
1269
1269
  values.offsetRotation.x,
1270
1270
  values.offsetRotation.y,
1271
1271
  values.offsetRotation.z
@@ -1280,17 +1280,17 @@ function collectTriMeshParts(object) {
1280
1280
  return null;
1281
1281
  }
1282
1282
  object.updateWorldMatrix(true, true);
1283
- const worldScale = new THREE19.Vector3();
1283
+ const worldScale = new THREE26.Vector3();
1284
1284
  object.getWorldScale(worldScale);
1285
1285
  const scale = {
1286
1286
  x: Math.abs(worldScale.x) || 1,
1287
1287
  y: Math.abs(worldScale.y) || 1,
1288
1288
  z: Math.abs(worldScale.z) || 1
1289
1289
  };
1290
- const worldToObject = new THREE19.Matrix4().copy(object.matrixWorld).invert();
1290
+ const worldToObject = new THREE26.Matrix4().copy(object.matrixWorld).invert();
1291
1291
  const parts = [];
1292
1292
  object.traverse((child) => {
1293
- if (!(child instanceof THREE19.Mesh) || !child.geometry) {
1293
+ if (!(child instanceof THREE26.Mesh) || !child.geometry) {
1294
1294
  return;
1295
1295
  }
1296
1296
  const geometry = child.geometry;
@@ -1307,7 +1307,7 @@ function collectTriMeshParts(object) {
1307
1307
  if (indexCount < 3) {
1308
1308
  return;
1309
1309
  }
1310
- const relativeMatrix = new THREE19.Matrix4().multiplyMatrices(
1310
+ const relativeMatrix = new THREE26.Matrix4().multiplyMatrices(
1311
1311
  worldToObject,
1312
1312
  child.matrixWorld
1313
1313
  );
@@ -1348,7 +1348,7 @@ function createTriMeshData(collected) {
1348
1348
  }
1349
1349
  const vertices = new Float32Array(vertexTotal * 3);
1350
1350
  const indices = new Uint32Array(indexTotal);
1351
- const point = new THREE19.Vector3();
1351
+ const point = new THREE26.Vector3();
1352
1352
  let vertexOffset = 0;
1353
1353
  let indexOffset = 0;
1354
1354
  for (const part of parts) {
@@ -1380,8 +1380,8 @@ function createTriMeshData(collected) {
1380
1380
  }
1381
1381
  __name(createTriMeshData, "createTriMeshData");
1382
1382
  function syncBodyFromTransform(body, transform) {
1383
- const quaternion = new THREE19.Quaternion().setFromEuler(
1384
- new THREE19.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
1383
+ const quaternion = new THREE26.Quaternion().setFromEuler(
1384
+ new THREE26.Euler(transform.rotation.x, transform.rotation.y, transform.rotation.z)
1385
1385
  );
1386
1386
  body.setTranslation(
1387
1387
  {
@@ -1408,8 +1408,8 @@ function syncTransformFromBody(transform, body) {
1408
1408
  transform.position.x = position.x;
1409
1409
  transform.position.y = position.y;
1410
1410
  transform.position.z = position.z;
1411
- const euler = new THREE19.Euler().setFromQuaternion(
1412
- new THREE19.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
1411
+ const euler = new THREE26.Euler().setFromQuaternion(
1412
+ new THREE26.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w)
1413
1413
  );
1414
1414
  transform.rotation.x = euler.x;
1415
1415
  transform.rotation.y = euler.y;
@@ -1522,8 +1522,8 @@ var _PhysicsSyncSystem = class _PhysicsSyncSystem {
1522
1522
  transform.position.y,
1523
1523
  transform.position.z
1524
1524
  );
1525
- const quaternion = new THREE19.Quaternion().setFromEuler(
1526
- new THREE19.Euler(
1525
+ const quaternion = new THREE26.Quaternion().setFromEuler(
1526
+ new THREE26.Euler(
1527
1527
  transform.rotation.x,
1528
1528
  transform.rotation.y,
1529
1529
  transform.rotation.z
@@ -2254,7 +2254,7 @@ function applyLightValues(lightComponent, rendererKind) {
2254
2254
  values.castShadow
2255
2255
  );
2256
2256
  }
2257
- if (light instanceof THREE19.PointLight) {
2257
+ if (light instanceof THREE26.PointLight) {
2258
2258
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
2259
2259
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
2260
2260
  }
@@ -2262,7 +2262,7 @@ function applyLightValues(lightComponent, rendererKind) {
2262
2262
  light.decay = Math.max(0, values.decay);
2263
2263
  }
2264
2264
  }
2265
- if (light instanceof THREE19.SpotLight) {
2265
+ if (light instanceof THREE26.SpotLight) {
2266
2266
  if (typeof values.distance === "number" && Number.isFinite(values.distance)) {
2267
2267
  light.distance = Math.max(MIN_LIGHT_DISTANCE, values.distance);
2268
2268
  }
@@ -2276,7 +2276,7 @@ function applyLightValues(lightComponent, rendererKind) {
2276
2276
  light.penumbra = Math.max(0, Math.min(1, values.penumbra));
2277
2277
  }
2278
2278
  }
2279
- if (light instanceof THREE19.RectAreaLight) {
2279
+ if (light instanceof THREE26.RectAreaLight) {
2280
2280
  if (typeof values.width === "number" && Number.isFinite(values.width)) {
2281
2281
  light.width = Math.max(MIN_AREA_SIZE, values.width);
2282
2282
  }
@@ -2340,15 +2340,15 @@ function resolveMaterialPropertyKey(key) {
2340
2340
  __name(resolveMaterialPropertyKey, "resolveMaterialPropertyKey");
2341
2341
  function createMaterialByType(type) {
2342
2342
  if (type === "basic") {
2343
- return new THREE19.MeshBasicMaterial();
2343
+ return new THREE26.MeshBasicMaterial();
2344
2344
  }
2345
2345
  if (type === "phong") {
2346
- return new THREE19.MeshPhongMaterial();
2346
+ return new THREE26.MeshPhongMaterial();
2347
2347
  }
2348
2348
  if (type === "physical") {
2349
- return new THREE19.MeshPhysicalMaterial();
2349
+ return new THREE26.MeshPhysicalMaterial();
2350
2350
  }
2351
- return new THREE19.MeshStandardMaterial();
2351
+ return new THREE26.MeshStandardMaterial();
2352
2352
  }
2353
2353
  __name(createMaterialByType, "createMaterialByType");
2354
2354
  function replaceMaterialAcrossObject(object, previous, next) {
@@ -2407,12 +2407,12 @@ function applyTextureFiltering(material, filtering) {
2407
2407
  const target = material;
2408
2408
  for (const slot of materialTextureSlots) {
2409
2409
  const texture = target[slot];
2410
- if (!texture || !(texture instanceof THREE19.Texture)) {
2410
+ if (!texture || !(texture instanceof THREE26.Texture)) {
2411
2411
  continue;
2412
2412
  }
2413
2413
  const useMipmaps = texture.generateMipmaps !== false;
2414
- texture.magFilter = sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
2415
- texture.minFilter = useMipmaps ? sharp ? THREE19.NearestMipmapNearestFilter : THREE19.LinearMipmapLinearFilter : sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
2414
+ texture.magFilter = sharp ? THREE26.NearestFilter : THREE26.LinearFilter;
2415
+ texture.minFilter = useMipmaps ? sharp ? THREE26.NearestMipmapNearestFilter : THREE26.LinearMipmapLinearFilter : sharp ? THREE26.NearestFilter : THREE26.LinearFilter;
2416
2416
  texture.needsUpdate = true;
2417
2417
  }
2418
2418
  material.needsUpdate = true;
@@ -2422,12 +2422,12 @@ function setTextureMipmaps(material, enabled) {
2422
2422
  const target = material;
2423
2423
  for (const slot of materialTextureSlots) {
2424
2424
  const texture = target[slot];
2425
- if (!texture || !(texture instanceof THREE19.Texture)) {
2425
+ if (!texture || !(texture instanceof THREE26.Texture)) {
2426
2426
  continue;
2427
2427
  }
2428
2428
  texture.generateMipmaps = enabled;
2429
- const sharp = texture.magFilter === THREE19.NearestFilter;
2430
- texture.minFilter = enabled ? sharp ? THREE19.NearestMipmapNearestFilter : THREE19.LinearMipmapLinearFilter : sharp ? THREE19.NearestFilter : THREE19.LinearFilter;
2429
+ const sharp = texture.magFilter === THREE26.NearestFilter;
2430
+ texture.minFilter = enabled ? sharp ? THREE26.NearestMipmapNearestFilter : THREE26.LinearMipmapLinearFilter : sharp ? THREE26.NearestFilter : THREE26.LinearFilter;
2431
2431
  texture.needsUpdate = true;
2432
2432
  }
2433
2433
  material.needsUpdate = true;
@@ -2438,7 +2438,7 @@ function assignMaterialTexture(material, rawKey, url, textureCache) {
2438
2438
  const key = resolveMaterialPropertyKey(rawKey);
2439
2439
  let texture = textureCache.get(url);
2440
2440
  if (!texture) {
2441
- texture = new THREE19.TextureLoader().load(url);
2441
+ texture = new THREE26.TextureLoader().load(url);
2442
2442
  textureCache.set(url, texture);
2443
2443
  }
2444
2444
  target[key] = texture;
@@ -2476,7 +2476,7 @@ function applyMaterialState(material, value, textureCache) {
2476
2476
  }
2477
2477
  target.side = value.side;
2478
2478
  target.blending = value.blending;
2479
- target.premultipliedAlpha = value.blending === THREE19.MultiplyBlending || value.blending === THREE19.SubtractiveBlending;
2479
+ target.premultipliedAlpha = value.blending === THREE26.MultiplyBlending || value.blending === THREE26.SubtractiveBlending;
2480
2480
  target.depthTest = value.depthTest;
2481
2481
  target.depthWrite = value.depthWrite;
2482
2482
  target.wireframe = value.wireframe;
@@ -2637,12 +2637,12 @@ function resolvePrimaryMesh(object) {
2637
2637
  if (!object) {
2638
2638
  return null;
2639
2639
  }
2640
- if (object instanceof THREE19.Mesh) {
2640
+ if (object instanceof THREE26.Mesh) {
2641
2641
  return object;
2642
2642
  }
2643
2643
  let found = null;
2644
2644
  object.traverse((child) => {
2645
- if (!found && child instanceof THREE19.Mesh) {
2645
+ if (!found && child instanceof THREE26.Mesh) {
2646
2646
  found = child;
2647
2647
  }
2648
2648
  });
@@ -2663,22 +2663,22 @@ function applyGeometry(component) {
2663
2663
  switch (component.kind) {
2664
2664
  case "cube": {
2665
2665
  const params = sanitized;
2666
- nextGeometry = new THREE19.BoxGeometry(params.width, params.height, params.depth);
2666
+ nextGeometry = new THREE26.BoxGeometry(params.width, params.height, params.depth);
2667
2667
  break;
2668
2668
  }
2669
2669
  case "plane": {
2670
2670
  const params = sanitized;
2671
- nextGeometry = new THREE19.PlaneGeometry(params.width, params.height);
2671
+ nextGeometry = new THREE26.PlaneGeometry(params.width, params.height);
2672
2672
  break;
2673
2673
  }
2674
2674
  case "sphere": {
2675
2675
  const params = sanitized;
2676
- nextGeometry = new THREE19.SphereGeometry(params.radius, 24, 24);
2676
+ nextGeometry = new THREE26.SphereGeometry(params.radius, 24, 24);
2677
2677
  break;
2678
2678
  }
2679
2679
  case "cylinder": {
2680
2680
  const params = sanitized;
2681
- nextGeometry = new THREE19.CylinderGeometry(
2681
+ nextGeometry = new THREE26.CylinderGeometry(
2682
2682
  params.radius,
2683
2683
  params.radius,
2684
2684
  params.height,
@@ -2688,7 +2688,7 @@ function applyGeometry(component) {
2688
2688
  }
2689
2689
  case "capsule": {
2690
2690
  const params = sanitized;
2691
- nextGeometry = new THREE19.CapsuleGeometry(params.radius, params.height, 4, 8);
2691
+ nextGeometry = new THREE26.CapsuleGeometry(params.radius, params.height, 4, 8);
2692
2692
  break;
2693
2693
  }
2694
2694
  default:
@@ -2704,7 +2704,7 @@ function applyShadowFlags(component) {
2704
2704
  return;
2705
2705
  }
2706
2706
  component.object.traverse((child) => {
2707
- if (child instanceof THREE19.Mesh) {
2707
+ if (child instanceof THREE26.Mesh) {
2708
2708
  child.castShadow = component.castShadow;
2709
2709
  child.receiveShadow = component.receiveShadow;
2710
2710
  }
@@ -2780,35 +2780,35 @@ function buildGeometry(component) {
2780
2780
  switch (component.meshKind) {
2781
2781
  case "cube": {
2782
2782
  const params = p;
2783
- return new THREE19.BoxGeometry(params.width, params.height, params.depth);
2783
+ return new THREE26.BoxGeometry(params.width, params.height, params.depth);
2784
2784
  }
2785
2785
  case "plane": {
2786
2786
  const params = p;
2787
- return new THREE19.PlaneGeometry(params.width, params.height);
2787
+ return new THREE26.PlaneGeometry(params.width, params.height);
2788
2788
  }
2789
2789
  case "sphere": {
2790
2790
  const params = p;
2791
- return new THREE19.SphereGeometry(params.radius, 24, 24);
2791
+ return new THREE26.SphereGeometry(params.radius, 24, 24);
2792
2792
  }
2793
2793
  case "cylinder": {
2794
2794
  const params = p;
2795
- return new THREE19.CylinderGeometry(params.radius, params.radius, params.height, 20);
2795
+ return new THREE26.CylinderGeometry(params.radius, params.radius, params.height, 20);
2796
2796
  }
2797
2797
  case "capsule": {
2798
2798
  const params = p;
2799
- return new THREE19.CapsuleGeometry(params.radius, params.height, 4, 8);
2799
+ return new THREE26.CapsuleGeometry(params.radius, params.height, 4, 8);
2800
2800
  }
2801
2801
  default:
2802
- return new THREE19.BoxGeometry(1, 1, 1);
2802
+ return new THREE26.BoxGeometry(1, 1, 1);
2803
2803
  }
2804
2804
  }
2805
2805
  __name(buildGeometry, "buildGeometry");
2806
- var _position = new THREE19.Vector3();
2807
- var _quaternion = new THREE19.Quaternion();
2808
- var _euler = new THREE19.Euler();
2809
- var _scale = new THREE19.Vector3();
2810
- var _matrix = new THREE19.Matrix4();
2811
- var _modelMatrix = new THREE19.Matrix4();
2806
+ var _position = new THREE26.Vector3();
2807
+ var _quaternion = new THREE26.Quaternion();
2808
+ var _euler = new THREE26.Euler();
2809
+ var _scale = new THREE26.Vector3();
2810
+ var _matrix = new THREE26.Matrix4();
2811
+ var _modelMatrix = new THREE26.Matrix4();
2812
2812
  function instanceTransformFromComponent(transform, out) {
2813
2813
  _position.set(transform.position.x, transform.position.y, transform.position.z);
2814
2814
  _euler.set(transform.rotation.x, transform.rotation.y, transform.rotation.z, "XYZ");
@@ -2890,9 +2890,9 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2890
2890
  const instanceCount = comp.instanceEntityIds.length;
2891
2891
  const geometrySignature = comp.meshKind === "model" ? `model:${comp.modelPath}:${instanceCount}` : `${comp.meshKind}:${JSON.stringify(comp.meshParameters)}:${instanceCount}`;
2892
2892
  if (comp.instancedMesh === null) {
2893
- const tracked = this.trackedMeshes.get(entityId);
2894
- if (tracked) {
2895
- comp.instancedMesh = tracked;
2893
+ const tracked2 = this.trackedMeshes.get(entityId);
2894
+ if (tracked2) {
2895
+ comp.instancedMesh = tracked2;
2896
2896
  }
2897
2897
  }
2898
2898
  const pendingLoadSignature = this.loadingSignatures.get(entityId);
@@ -2961,7 +2961,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2961
2961
  const sourceMaterial = Array.isArray(meshChild.material) ? meshChild.material.map((m) => m.clone()) : meshChild.material.clone();
2962
2962
  meshChild.updateWorldMatrix(true, false);
2963
2963
  const localMatrix = meshChild.matrixWorld.clone();
2964
- const instancedMesh2 = new THREE19.InstancedMesh(
2964
+ const instancedMesh2 = new THREE26.InstancedMesh(
2965
2965
  geometry2,
2966
2966
  Array.isArray(sourceMaterial) ? sourceMaterial : sourceMaterial,
2967
2967
  count2
@@ -2986,7 +2986,7 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
2986
2986
  this.loadingSignatures.delete(targetEntityId);
2987
2987
  return;
2988
2988
  }
2989
- const group = new THREE19.Group();
2989
+ const group = new THREE26.Group();
2990
2990
  for (const entry of entries) {
2991
2991
  group.add(entry.instancedMesh);
2992
2992
  }
@@ -3004,8 +3004,8 @@ var _InstancedMeshSystem = class _InstancedMeshSystem {
3004
3004
  }
3005
3005
  const count = instanceCount;
3006
3006
  const geometry = buildGeometry(comp);
3007
- const material = new THREE19.MeshStandardMaterial({ color: "#ffffff" });
3008
- const instancedMesh = new THREE19.InstancedMesh(geometry, material, count);
3007
+ const material = new THREE26.MeshStandardMaterial({ color: "#ffffff" });
3008
+ const instancedMesh = new THREE26.InstancedMesh(geometry, material, count);
3009
3009
  instancedMesh.castShadow = comp.castShadow;
3010
3010
  instancedMesh.receiveShadow = comp.receiveShadow;
3011
3011
  for (let i = 0; i < count; i++) {
@@ -3213,8 +3213,8 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
3213
3213
  const retryDelay = options?.errorRetryDelay;
3214
3214
  this.errorRetryDelay = typeof retryDelay === "number" && Number.isFinite(retryDelay) ? Math.max(0, retryDelay) : DEFAULT_SCRIPT_ERROR_RETRY_DELAY;
3215
3215
  this.selectionCtx = selectionContext ?? null;
3216
- this.raycaster = new THREE19.Raycaster();
3217
- this.ndcPointer = new THREE19.Vector2();
3216
+ this.raycaster = new THREE26.Raycaster();
3217
+ this.ndcPointer = new THREE26.Vector2();
3218
3218
  if (this.selectionCtx) {
3219
3219
  this.selectionCtx.domElement.addEventListener("pointerdown", this.onPointerDown);
3220
3220
  this.selectionCtx.domElement.addEventListener("pointerup", this.onPointerUp);
@@ -3658,7 +3658,7 @@ function disposeBuiltObject(root) {
3658
3658
  for (const mat of materials) {
3659
3659
  if (mat.userData?.[SHARED_MODEL_RESOURCE_FLAG]) continue;
3660
3660
  for (const value of Object.values(mat)) {
3661
- if (value instanceof THREE19.Texture) value.dispose();
3661
+ if (value instanceof THREE26.Texture) value.dispose();
3662
3662
  }
3663
3663
  mat.dispose?.();
3664
3664
  }
@@ -4259,7 +4259,7 @@ var _AudioSystem = class _AudioSystem {
4259
4259
  if (!prev || prev.volume !== opts.volume) {
4260
4260
  sound.setVolume(opts.volume);
4261
4261
  }
4262
- if (opts.positional && sound instanceof THREE19.PositionalAudio) {
4262
+ if (opts.positional && sound instanceof THREE26.PositionalAudio) {
4263
4263
  if (!prev || prev.refDistance !== opts.refDistance) {
4264
4264
  sound.setRefDistance(opts.refDistance);
4265
4265
  }
@@ -4399,7 +4399,7 @@ var _VideoSystem = class _VideoSystem {
4399
4399
  spill: opts.spill
4400
4400
  });
4401
4401
  }
4402
- if (opts.positional && component.audioNode instanceof THREE19.PositionalAudio) {
4402
+ if (opts.positional && component.audioNode instanceof THREE26.PositionalAudio) {
4403
4403
  const posAudio = component.audioNode;
4404
4404
  if (!prev || prev.refDistance !== opts.refDistance) {
4405
4405
  posAudio.setRefDistance(opts.refDistance);
@@ -4439,8 +4439,8 @@ function blankState(source) {
4439
4439
  handedness: handednessOf(source),
4440
4440
  kind: kindOf(source),
4441
4441
  connected: true,
4442
- rayOrigin: new THREE19.Vector3(),
4443
- rayDirection: new THREE19.Vector3(0, 0, -1),
4442
+ rayOrigin: new THREE26.Vector3(),
4443
+ rayDirection: new THREE26.Vector3(0, 0, -1),
4444
4444
  gripPosition: null,
4445
4445
  gripQuaternion: null,
4446
4446
  select: false,
@@ -4466,7 +4466,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
4466
4466
  }
4467
4467
  if (spaces.length === 0) return null;
4468
4468
  const target = existing ?? {};
4469
- const matrix = new THREE19.Matrix4();
4469
+ const matrix = new THREE26.Matrix4();
4470
4470
  const buffer = spaces.length * 16 <= JOINT_MATRIX_BUFFER.length ? JOINT_MATRIX_BUFFER : new Float32Array(spaces.length * 16);
4471
4471
  const fillPoses = frame.fillPoses;
4472
4472
  if (typeof fillPoses === "function") {
@@ -4479,7 +4479,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
4479
4479
  if (filled) {
4480
4480
  for (let i = 0; i < names.length; i += 1) {
4481
4481
  matrix.fromArray(buffer, i * 16).premultiply(rigMatrix);
4482
- const vector = target[names[i]] ?? new THREE19.Vector3();
4482
+ const vector = target[names[i]] ?? new THREE26.Vector3();
4483
4483
  vector.setFromMatrixPosition(matrix);
4484
4484
  target[names[i]] = vector;
4485
4485
  }
@@ -4497,7 +4497,7 @@ function readHandJoints(frame, hand, referenceSpace, rigMatrix, existing) {
4497
4497
  continue;
4498
4498
  }
4499
4499
  matrix.fromArray(pose.transform.matrix).premultiply(rigMatrix);
4500
- const vector = target[names[i]] ?? new THREE19.Vector3();
4500
+ const vector = target[names[i]] ?? new THREE26.Vector3();
4501
4501
  vector.setFromMatrixPosition(matrix);
4502
4502
  target[names[i]] = vector;
4503
4503
  anyTracked = true;
@@ -4509,7 +4509,7 @@ var _XRInputSystem = class _XRInputSystem {
4509
4509
  constructor() {
4510
4510
  this.records = /* @__PURE__ */ new Map();
4511
4511
  /** Reference-space → world matrix, rebuilt each frame from the XR rig. */
4512
- this.referenceToWorld = new THREE19.Matrix4();
4512
+ this.referenceToWorld = new THREE26.Matrix4();
4513
4513
  this.session = null;
4514
4514
  this.onSelectStart = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectStart"), "onSelectStart");
4515
4515
  this.onSelectEnd = /* @__PURE__ */ __name((event) => this.mark(event, "pendingSelectEnd"), "onSelectEnd");
@@ -4607,19 +4607,19 @@ var _XRInputSystem = class _XRInputSystem {
4607
4607
  if (frame && referenceSpace && source.targetRaySpace) {
4608
4608
  const pose = frame.getPose(source.targetRaySpace, referenceSpace);
4609
4609
  if (pose) {
4610
- const m = new THREE19.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
4610
+ const m = new THREE26.Matrix4().fromArray(pose.transform.matrix).premultiply(rigMatrix);
4611
4611
  state.rayOrigin.setFromMatrixPosition(m);
4612
4612
  state.rayDirection.set(0, 0, -1).applyQuaternion(
4613
- new THREE19.Quaternion().setFromRotationMatrix(m)
4613
+ new THREE26.Quaternion().setFromRotationMatrix(m)
4614
4614
  ).normalize();
4615
4615
  }
4616
4616
  }
4617
4617
  if (frame && referenceSpace && source.gripSpace) {
4618
4618
  const gripPose = frame.getPose(source.gripSpace, referenceSpace);
4619
4619
  if (gripPose) {
4620
- const m = new THREE19.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
4621
- state.gripPosition ?? (state.gripPosition = new THREE19.Vector3());
4622
- state.gripQuaternion ?? (state.gripQuaternion = new THREE19.Quaternion());
4620
+ const m = new THREE26.Matrix4().fromArray(gripPose.transform.matrix).premultiply(rigMatrix);
4621
+ state.gripPosition ?? (state.gripPosition = new THREE26.Vector3());
4622
+ state.gripQuaternion ?? (state.gripQuaternion = new THREE26.Quaternion());
4623
4623
  state.gripPosition.setFromMatrixPosition(m);
4624
4624
  state.gripQuaternion.setFromRotationMatrix(m);
4625
4625
  }
@@ -4721,6 +4721,51 @@ var _XRInputSystem = class _XRInputSystem {
4721
4721
  };
4722
4722
  __name(_XRInputSystem, "XRInputSystem");
4723
4723
  var XRInputSystem = _XRInputSystem;
4724
+
4725
+ // core/ui/uiWidgetTree.ts
4726
+ var UI_PART_KEY = "__uiPart";
4727
+ function applyPartColor(mesh, tag) {
4728
+ const material = mesh.material;
4729
+ material.color.copy(tag.baseColor).multiplyScalar(tag.premultiplied ? tag.tint * tag.opacity : tag.tint);
4730
+ material.opacity = tag.opacity;
4731
+ }
4732
+ __name(applyPartColor, "applyPartColor");
4733
+ function isUiWidget(world, entityId) {
4734
+ return !!world.getComponent(entityId, UIPanelComponent) || !!world.getComponent(entityId, UITextComponent) || !!world.getComponent(entityId, UIButtonComponent) || !!world.getComponent(entityId, UIToggleComponent) || !!world.getComponent(entityId, UISliderComponent);
4735
+ }
4736
+ __name(isUiWidget, "isUiWidget");
4737
+ function isShownInScene(world, entityId) {
4738
+ let object = world.getComponent(entityId, Object3DRef)?.object ?? null;
4739
+ while (object) {
4740
+ if (!object.visible) return false;
4741
+ object = object.parent;
4742
+ }
4743
+ return true;
4744
+ }
4745
+ __name(isShownInScene, "isShownInScene");
4746
+ function nearestWidgetAncestor(world, entityId) {
4747
+ const visited = /* @__PURE__ */ new Set([entityId]);
4748
+ let current = entityId;
4749
+ for (; ; ) {
4750
+ const parentId = world.getComponent(current, ParentId)?.value;
4751
+ if (typeof parentId !== "number" || visited.has(parentId)) return null;
4752
+ if (isUiWidget(world, parentId)) return parentId;
4753
+ visited.add(parentId);
4754
+ current = parentId;
4755
+ }
4756
+ }
4757
+ __name(nearestWidgetAncestor, "nearestWidgetAncestor");
4758
+ function widgetTreeRoot(world, entityId) {
4759
+ const visited = /* @__PURE__ */ new Set([entityId]);
4760
+ let root = entityId;
4761
+ for (; ; ) {
4762
+ const parent = nearestWidgetAncestor(world, root);
4763
+ if (parent === null || visited.has(parent)) return root;
4764
+ visited.add(parent);
4765
+ root = parent;
4766
+ }
4767
+ }
4768
+ __name(widgetTreeRoot, "widgetTreeRoot");
4724
4769
  var VELOCITY_SAMPLES = 5;
4725
4770
  var MAX_SAMPLE_AGE_SECONDS = 0.2;
4726
4771
  var MAX_PLAUSIBLE_SPEED = 20;
@@ -4747,15 +4792,15 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
4747
4792
  const fresh = this.samples.filter(
4748
4793
  (sample) => now - sample.time <= MAX_SAMPLE_AGE_SECONDS
4749
4794
  );
4750
- if (fresh.length < 2) return new THREE19.Vector3();
4751
- let best = new THREE19.Vector3();
4795
+ if (fresh.length < 2) return new THREE26.Vector3();
4796
+ let best = new THREE26.Vector3();
4752
4797
  let bestSpeed = 0;
4753
4798
  for (let i = 1; i < fresh.length; i += 1) {
4754
4799
  const previous = fresh[i - 1];
4755
4800
  const current = fresh[i];
4756
4801
  const dt = current.time - previous.time;
4757
4802
  if (dt <= 1e-5) continue;
4758
- const velocity = new THREE19.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
4803
+ const velocity = new THREE26.Vector3().subVectors(current.position, previous.position).divideScalar(dt);
4759
4804
  const speed = velocity.length();
4760
4805
  if (speed > MAX_PLAUSIBLE_SPEED) continue;
4761
4806
  if (speed > bestSpeed) {
@@ -4772,15 +4817,15 @@ var _ThrowVelocityTracker = class _ThrowVelocityTracker {
4772
4817
  __name(_ThrowVelocityTracker, "ThrowVelocityTracker");
4773
4818
  var ThrowVelocityTracker = _ThrowVelocityTracker;
4774
4819
  function angularVelocityFrom(previous, current, deltaTime) {
4775
- if (deltaTime <= 1e-5) return new THREE19.Vector3();
4820
+ if (deltaTime <= 1e-5) return new THREE26.Vector3();
4776
4821
  const delta = current.clone().multiply(previous.clone().invert());
4777
4822
  if (delta.w < 0) {
4778
4823
  delta.set(-delta.x, -delta.y, -delta.z, -delta.w);
4779
4824
  }
4780
- const angle = 2 * Math.acos(THREE19.MathUtils.clamp(delta.w, -1, 1));
4825
+ const angle = 2 * Math.acos(THREE26.MathUtils.clamp(delta.w, -1, 1));
4781
4826
  const sinHalf = Math.sqrt(Math.max(0, 1 - delta.w * delta.w));
4782
- if (sinHalf < 1e-6 || angle < 1e-6) return new THREE19.Vector3();
4783
- return new THREE19.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
4827
+ if (sinHalf < 1e-6 || angle < 1e-6) return new THREE26.Vector3();
4828
+ return new THREE26.Vector3(delta.x, delta.y, delta.z).divideScalar(sinHalf).multiplyScalar(angle / deltaTime);
4784
4829
  }
4785
4830
  __name(angularVelocityFrom, "angularVelocityFrom");
4786
4831
  var MIN_HAND_SEPARATION = 0.06;
@@ -4788,7 +4833,7 @@ var MIN_ANCHOR_SEPARATION = 1e-3;
4788
4833
  var SCALE_DEADZONE = 0.05;
4789
4834
  function solveTwoHandTransform(primary, secondary, basis, options) {
4790
4835
  const separation = primary.position.distanceTo(secondary.position);
4791
- const anchorDelta = new THREE19.Vector3().subVectors(
4836
+ const anchorDelta = new THREE26.Vector3().subVectors(
4792
4837
  basis.secondaryLocal,
4793
4838
  basis.primaryLocal
4794
4839
  );
@@ -4812,8 +4857,8 @@ function solveTwoHandTransform(primary, secondary, basis, options) {
4812
4857
  } else {
4813
4858
  const axisLocal = anchorDelta.clone().normalize();
4814
4859
  const currentAxis = axisLocal.clone().applyQuaternion(base).normalize();
4815
- const desiredAxis = new THREE19.Vector3().subVectors(secondary.position, primary.position).normalize();
4816
- const correction = new THREE19.Quaternion().setFromUnitVectors(
4860
+ const desiredAxis = new THREE26.Vector3().subVectors(secondary.position, primary.position).normalize();
4861
+ const correction = new THREE26.Quaternion().setFromUnitVectors(
4817
4862
  currentAxis,
4818
4863
  desiredAxis
4819
4864
  );
@@ -4828,11 +4873,11 @@ __name(solveTwoHandTransform, "solveTwoHandTransform");
4828
4873
  // core/systems/XRInteractionSystem.ts
4829
4874
  var RAYLESS_HOLD_DISTANCE = 0.5;
4830
4875
  function quatFromEulerVec(r) {
4831
- return new THREE19.Quaternion().setFromEuler(new THREE19.Euler(r.x, r.y, r.z));
4876
+ return new THREE26.Quaternion().setFromEuler(new THREE26.Euler(r.x, r.y, r.z));
4832
4877
  }
4833
4878
  __name(quatFromEulerVec, "quatFromEulerVec");
4834
4879
  function writeEuler(target, q) {
4835
- const euler = new THREE19.Euler().setFromQuaternion(q);
4880
+ const euler = new THREE26.Euler().setFromQuaternion(q);
4836
4881
  target.x = euler.x;
4837
4882
  target.y = euler.y;
4838
4883
  target.z = euler.z;
@@ -4840,7 +4885,7 @@ function writeEuler(target, q) {
4840
4885
  __name(writeEuler, "writeEuler");
4841
4886
  var _XRInteractionSystem = class _XRInteractionSystem {
4842
4887
  constructor(inputSystem) {
4843
- this.raycaster = new THREE19.Raycaster();
4888
+ this.raycaster = new THREE26.Raycaster();
4844
4889
  this.grabs = /* @__PURE__ */ new Map();
4845
4890
  this.hoveredEntityId = null;
4846
4891
  /** Per-hand hover, with the hit distance so the ray can stop at the surface. */
@@ -4951,7 +4996,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
4951
4996
  const hand = this.handPosition(input);
4952
4997
  const handQuaternion = this.handQuaternion(input);
4953
4998
  const objectQuaternion = quatFromEulerVec(transform.rotation);
4954
- record.offset = new THREE19.Vector3(
4999
+ record.offset = new THREE26.Vector3(
4955
5000
  transform.position.x - hand.x,
4956
5001
  transform.position.y - hand.y,
4957
5002
  transform.position.z - hand.z
@@ -4983,7 +5028,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
4983
5028
  primary.record.twoHand = {
4984
5029
  primaryLocal,
4985
5030
  secondaryLocal,
4986
- initialScale: new THREE19.Vector3(
5031
+ initialScale: new THREE26.Vector3(
4987
5032
  transform.scale.x,
4988
5033
  transform.scale.y,
4989
5034
  transform.scale.z
@@ -5004,7 +5049,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5004
5049
  /** World point → the object's UNSCALED local space. */
5005
5050
  objectLocalPoint(transform, worldPoint) {
5006
5051
  const local = worldPoint.clone().sub(
5007
- new THREE19.Vector3(
5052
+ new THREE26.Vector3(
5008
5053
  transform.position.x,
5009
5054
  transform.position.y,
5010
5055
  transform.position.z
@@ -5062,7 +5107,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5062
5107
  );
5063
5108
  if (!interactable2) continue;
5064
5109
  if (interactable2.grabReach === "distance") continue;
5065
- const position = new THREE19.Vector3();
5110
+ const position = new THREE26.Vector3();
5066
5111
  entry.object.getWorldPosition(position);
5067
5112
  const distance = position.distanceTo(hand);
5068
5113
  if (distance <= interactable2.grabRadius && distance < nearestDistance) {
@@ -5094,6 +5139,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5094
5139
  const interactable = world.getComponent(entityId, XRInteractableComponent);
5095
5140
  if (!interactable?.enabled) continue;
5096
5141
  if (!world.isEntityVisible(entityId)) continue;
5142
+ if (!isShownInScene(world, entityId)) continue;
5097
5143
  const object = world.getComponent(entityId, Object3DRef)?.object;
5098
5144
  if (object) {
5099
5145
  out.push({ entityId, object, pointable: interactable.pointable });
@@ -5134,13 +5180,13 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5134
5180
  this.holdersOf(entityId).map((holder) => holder.record.attachPointId).filter((id) => id !== null)
5135
5181
  );
5136
5182
  const hand = this.handPosition(input);
5137
- const objectPosition = new THREE19.Vector3(
5183
+ const objectPosition = new THREE26.Vector3(
5138
5184
  transform.position.x,
5139
5185
  transform.position.y,
5140
5186
  transform.position.z
5141
5187
  );
5142
5188
  const objectQuaternion = quatFromEulerVec(transform.rotation);
5143
- const scale = new THREE19.Vector3(
5189
+ const scale = new THREE26.Vector3(
5144
5190
  transform.scale.x,
5145
5191
  transform.scale.y,
5146
5192
  transform.scale.z
@@ -5151,7 +5197,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5151
5197
  if (taken.has(point.id)) continue;
5152
5198
  if (point.hand !== "any" && point.hand !== input.handedness) continue;
5153
5199
  if (point.role !== "any" && point.role !== role) continue;
5154
- const world3 = new THREE19.Vector3(
5200
+ const world3 = new THREE26.Vector3(
5155
5201
  point.position.x,
5156
5202
  point.position.y,
5157
5203
  point.position.z
@@ -5174,21 +5220,21 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5174
5220
  let offset;
5175
5221
  let remoteDistance = 0;
5176
5222
  if (remote) {
5177
- const position = new THREE19.Vector3(
5223
+ const position = new THREE26.Vector3(
5178
5224
  transform.position.x,
5179
5225
  transform.position.y,
5180
5226
  transform.position.z
5181
5227
  );
5182
5228
  remoteDistance = position.distanceTo(input.rayOrigin);
5183
- offset = new THREE19.Vector3();
5229
+ offset = new THREE26.Vector3();
5184
5230
  } else if (viaDistance) {
5185
- offset = new THREE19.Vector3(
5231
+ offset = new THREE26.Vector3(
5186
5232
  interactable.grabOffsetPosition.x,
5187
5233
  interactable.grabOffsetPosition.y,
5188
5234
  interactable.grabOffsetPosition.z
5189
5235
  );
5190
5236
  } else {
5191
- offset = new THREE19.Vector3(
5237
+ offset = new THREE26.Vector3(
5192
5238
  transform.position.x - hand.x,
5193
5239
  transform.position.y - hand.y,
5194
5240
  transform.position.z - hand.z
@@ -5215,7 +5261,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5215
5261
  entityId,
5216
5262
  role,
5217
5263
  attachPointId: attachPoint?.id ?? null,
5218
- attachLocal: attachPoint ? new THREE19.Vector3(
5264
+ attachLocal: attachPoint ? new THREE26.Vector3(
5219
5265
  attachPoint.position.x,
5220
5266
  attachPoint.position.y,
5221
5267
  attachPoint.position.z
@@ -5224,8 +5270,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5224
5270
  handRotationOffset,
5225
5271
  twoHand: null,
5226
5272
  offset,
5227
- rotationOffset: new THREE19.Quaternion().setFromEuler(
5228
- new THREE19.Euler(
5273
+ rotationOffset: new THREE26.Quaternion().setFromEuler(
5274
+ new THREE26.Euler(
5229
5275
  interactable.grabOffsetRotation.x,
5230
5276
  interactable.grabOffsetRotation.y,
5231
5277
  interactable.grabOffsetRotation.z
@@ -5236,7 +5282,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5236
5282
  restoreBodyType,
5237
5283
  tracker,
5238
5284
  lastHandQuaternion: handQuaternion.clone(),
5239
- angularVelocity: new THREE19.Vector3()
5285
+ angularVelocity: new THREE26.Vector3()
5240
5286
  });
5241
5287
  if (role === "primary") {
5242
5288
  this.dispatch(world, entityId, "onGrab", (b) => b.onGrab(input));
@@ -5309,14 +5355,14 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5309
5355
  const handQuaternion = this.handQuaternion(input);
5310
5356
  const objectQuaternion = held.attachQuaternion ? handQuaternion.clone().multiply(held.rotationOffset).multiply(held.attachQuaternion.clone().invert()) : handQuaternion.clone().multiply(held.handRotationOffset);
5311
5357
  if (held.remote) {
5312
- const target = new THREE19.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
5358
+ const target = new THREE26.Vector3().copy(input.rayDirection).multiplyScalar(held.remoteDistance).add(input.rayOrigin);
5313
5359
  transform.position.x = target.x;
5314
5360
  transform.position.y = target.y;
5315
5361
  transform.position.z = target.z;
5316
5362
  writeEuler(transform.rotation, objectQuaternion);
5317
5363
  } else if (held.attachLocal) {
5318
5364
  const anchor = held.attachLocal.clone().multiply(
5319
- new THREE19.Vector3(
5365
+ new THREE26.Vector3(
5320
5366
  transform.scale.x,
5321
5367
  transform.scale.y,
5322
5368
  transform.scale.z
@@ -5334,7 +5380,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5334
5380
  writeEuler(transform.rotation, objectQuaternion);
5335
5381
  }
5336
5382
  held.tracker.record(
5337
- held.remote ? new THREE19.Vector3(
5383
+ held.remote ? new THREE26.Vector3(
5338
5384
  transform.position.x,
5339
5385
  transform.position.y,
5340
5386
  transform.position.z
@@ -5409,7 +5455,7 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5409
5455
  */
5410
5456
  handPosition(input) {
5411
5457
  if (input.gripPosition) return input.gripPosition;
5412
- return new THREE19.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
5458
+ return new THREE26.Vector3().copy(input.rayDirection).multiplyScalar(RAYLESS_HOLD_DISTANCE).add(input.rayOrigin);
5413
5459
  }
5414
5460
  /**
5415
5461
  * Hand orientation. Grip-less sources (gaze / transient-pointer) have none,
@@ -5417,8 +5463,8 @@ var _XRInteractionSystem = class _XRInteractionSystem {
5417
5463
  */
5418
5464
  handQuaternion(input) {
5419
5465
  if (input.gripQuaternion) return input.gripQuaternion.clone();
5420
- return new THREE19.Quaternion().setFromUnitVectors(
5421
- new THREE19.Vector3(0, 0, -1),
5466
+ return new THREE26.Quaternion().setFromUnitVectors(
5467
+ new THREE26.Vector3(0, 0, -1),
5422
5468
  input.rayDirection.clone().normalize()
5423
5469
  );
5424
5470
  }
@@ -5555,7 +5601,7 @@ function sampleTeleportArc(origin, direction, out = [], options) {
5555
5601
  for (let i = 0; i < steps; i += 1) {
5556
5602
  const t = i / (steps - 1) * maxTime;
5557
5603
  out.push(
5558
- new THREE19.Vector3(
5604
+ new THREE26.Vector3(
5559
5605
  origin.x + velocity.x * t,
5560
5606
  origin.y + velocity.y * t - 0.5 * gravity * t * t,
5561
5607
  origin.z + velocity.z * t
@@ -5572,7 +5618,7 @@ function arcGroundCrossing(points, groundY) {
5572
5618
  if (previous.y >= groundY && current.y <= groundY) {
5573
5619
  const span = previous.y - current.y;
5574
5620
  const alpha = span > 1e-6 ? (previous.y - groundY) / span : 0;
5575
- return new THREE19.Vector3().lerpVectors(previous, current, alpha);
5621
+ return new THREE26.Vector3().lerpVectors(previous, current, alpha);
5576
5622
  }
5577
5623
  }
5578
5624
  return null;
@@ -5616,11 +5662,11 @@ __name(_TeleportAimGate, "TeleportAimGate");
5616
5662
  var TeleportAimGate = _TeleportAimGate;
5617
5663
 
5618
5664
  // core/systems/XRLocomotionSystem.ts
5619
- var UP = new THREE19.Vector3(0, 1, 0);
5665
+ var UP = new THREE26.Vector3(0, 1, 0);
5620
5666
  var AXIS_DEADZONE = 0.5;
5621
5667
  var _XRLocomotionSystem = class _XRLocomotionSystem {
5622
5668
  constructor(inputSystem) {
5623
- this.raycaster = new THREE19.Raycaster();
5669
+ this.raycaster = new THREE26.Raycaster();
5624
5670
  /** Per-source latch so one stick push yields exactly one snap turn. */
5625
5671
  this.turnLatched = /* @__PURE__ */ new WeakSet();
5626
5672
  /** Reused arc buffer — aiming runs every frame. */
@@ -5746,7 +5792,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
5746
5792
  }
5747
5793
  if (this.turnLatched.has(source)) continue;
5748
5794
  this.turnLatched.add(source);
5749
- transform.rotation.y -= Math.sign(x) * THREE19.MathUtils.degToRad(rig.snapTurnDegrees);
5795
+ transform.rotation.y -= Math.sign(x) * THREE26.MathUtils.degToRad(rig.snapTurnDegrees);
5750
5796
  }
5751
5797
  }
5752
5798
  applySmoothMove(world, rig, transform, inputs, deltaTime) {
@@ -5756,12 +5802,12 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
5756
5802
  const y = input.axes[1] ?? 0;
5757
5803
  if (Math.abs(x) < 0.15 && Math.abs(y) < 0.15) continue;
5758
5804
  const camera = world.getCamera();
5759
- const forward = new THREE19.Vector3();
5805
+ const forward = new THREE26.Vector3();
5760
5806
  camera.getWorldDirection(forward);
5761
5807
  forward.y = 0;
5762
5808
  if (forward.lengthSq() < 1e-6) continue;
5763
5809
  forward.normalize();
5764
- const right = new THREE19.Vector3().crossVectors(forward, UP).normalize();
5810
+ const right = new THREE26.Vector3().crossVectors(forward, UP).normalize();
5765
5811
  const step = rig.moveSpeed * Math.max(0, deltaTime);
5766
5812
  transform.position.x += (forward.x * -y + right.x * x) * step;
5767
5813
  transform.position.z += (forward.z * -y + right.z * x) * step;
@@ -5842,7 +5888,7 @@ var _XRLocomotionSystem = class _XRLocomotionSystem {
5842
5888
  for (let i = 1; i < points.length; i += 1) {
5843
5889
  const from = points[i - 1];
5844
5890
  const to = points[i];
5845
- const delta = new THREE19.Vector3().subVectors(to, from);
5891
+ const delta = new THREE26.Vector3().subVectors(to, from);
5846
5892
  const distance = delta.length();
5847
5893
  if (distance < 1e-6) continue;
5848
5894
  this.raycaster.set(from, delta.divideScalar(distance));
@@ -6115,12 +6161,12 @@ function planeColliderFromPolygon(polygon) {
6115
6161
  }
6116
6162
  __name(planeColliderFromPolygon, "planeColliderFromPolygon");
6117
6163
  function writeXrMeshGeometry(vertices, indices, existing) {
6118
- const geometry = existing ?? new THREE19.BufferGeometry();
6164
+ const geometry = existing ?? new THREE26.BufferGeometry();
6119
6165
  geometry.setAttribute(
6120
6166
  "position",
6121
- new THREE19.BufferAttribute(vertices.slice(), 3)
6167
+ new THREE26.BufferAttribute(vertices.slice(), 3)
6122
6168
  );
6123
- geometry.setIndex(new THREE19.BufferAttribute(indices.slice(), 1));
6169
+ geometry.setIndex(new THREE26.BufferAttribute(indices.slice(), 1));
6124
6170
  geometry.deleteAttribute("normal");
6125
6171
  geometry.computeBoundingSphere();
6126
6172
  geometry.computeVertexNormals();
@@ -6128,14 +6174,14 @@ function writeXrMeshGeometry(vertices, indices, existing) {
6128
6174
  }
6129
6175
  __name(writeXrMeshGeometry, "writeXrMeshGeometry");
6130
6176
  function decomposeXrPose(matrixElements, target) {
6131
- const matrix = new THREE19.Matrix4().fromArray(
6177
+ const matrix = new THREE26.Matrix4().fromArray(
6132
6178
  matrixElements
6133
6179
  );
6134
- const position = new THREE19.Vector3();
6135
- const quaternion = new THREE19.Quaternion();
6136
- const scale = new THREE19.Vector3();
6180
+ const position = new THREE26.Vector3();
6181
+ const quaternion = new THREE26.Quaternion();
6182
+ const scale = new THREE26.Vector3();
6137
6183
  matrix.decompose(position, quaternion, scale);
6138
- const euler = new THREE19.Euler().setFromQuaternion(quaternion, "XYZ");
6184
+ const euler = new THREE26.Euler().setFromQuaternion(quaternion, "XYZ");
6139
6185
  target.position.x = position.x;
6140
6186
  target.position.y = position.y;
6141
6187
  target.position.z = position.z;
@@ -6297,8 +6343,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
6297
6343
  this.viewerPos = null;
6298
6344
  /** The ONE global room mesh currently allowed to spawn. */
6299
6345
  this.electedGlobalMesh = null;
6300
- this.tmpViewerMatrix = new THREE19.Matrix4();
6301
- this.tmpViewPos = new THREE19.Vector3();
6346
+ this.tmpViewerMatrix = new THREE26.Matrix4();
6347
+ this.tmpViewPos = new THREE26.Vector3();
6302
6348
  }
6303
6349
  update(world, deltaTime = 0) {
6304
6350
  const presenting = world.isXrPresenting();
@@ -6657,7 +6703,7 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
6657
6703
  const geometry = writeXrMeshGeometry(mesh.vertices, mesh.indices);
6658
6704
  const label = mesh.semanticLabel ?? null;
6659
6705
  const isGlobalMesh = label === "global mesh";
6660
- const material = new THREE19.MeshBasicMaterial({
6706
+ const material = new THREE26.MeshBasicMaterial({
6661
6707
  color: debugColorFor("mesh", label),
6662
6708
  wireframe: true,
6663
6709
  // The global scan is a coarse triangle soup of the whole room; at full
@@ -6678,8 +6724,8 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
6678
6724
  depthTest: true,
6679
6725
  depthWrite: false
6680
6726
  });
6681
- const root = new THREE19.Group();
6682
- const child = new THREE19.Mesh(geometry, material);
6727
+ const root = new THREE26.Group();
6728
+ const child = new THREE26.Mesh(geometry, material);
6683
6729
  child.visible = this.debugVisible;
6684
6730
  child.renderOrder = DEBUG_RENDER_ORDER;
6685
6731
  root.add(child);
@@ -6705,21 +6751,21 @@ var _XREnvironmentSystem = class _XREnvironmentSystem {
6705
6751
  }
6706
6752
  /** A thin box so a detected plane can be SEEN, not just collided with. */
6707
6753
  attachPlaneDebug(world, entry, collider, semanticLabel) {
6708
- const box = new THREE19.BoxGeometry(
6754
+ const box = new THREE26.BoxGeometry(
6709
6755
  collider.size.x,
6710
6756
  collider.size.y,
6711
6757
  collider.size.z
6712
6758
  );
6713
- const geometry = new THREE19.EdgesGeometry(box);
6759
+ const geometry = new THREE26.EdgesGeometry(box);
6714
6760
  box.dispose();
6715
- const material = new THREE19.LineBasicMaterial({
6761
+ const material = new THREE26.LineBasicMaterial({
6716
6762
  color: debugColorFor("plane", semanticLabel),
6717
6763
  // Same depth policy as the mesh overlay — see createMeshEntity.
6718
6764
  depthTest: true,
6719
6765
  depthWrite: false
6720
6766
  });
6721
- const root = new THREE19.Group();
6722
- const child = new THREE19.LineSegments(geometry, material);
6767
+ const root = new THREE26.Group();
6768
+ const child = new THREE26.LineSegments(geometry, material);
6723
6769
  child.renderOrder = DEBUG_RENDER_ORDER;
6724
6770
  child.position.set(collider.offset.x, collider.offset.y, collider.offset.z);
6725
6771
  child.visible = this.debugVisible;
@@ -6795,6 +6841,14 @@ function normalizeOrientation(value) {
6795
6841
  return value === "horizontal" || value === "vertical" ? value : null;
6796
6842
  }
6797
6843
  __name(normalizeOrientation, "normalizeOrientation");
6844
+ var isDisplayOnlySlider = /* @__PURE__ */ __name((slider) => slider.readOnly === true || slider.variant !== void 0 && slider.variant !== "slider", "isDisplayOnlySlider");
6845
+ function snapToStep(value, slider) {
6846
+ const { step, min, max } = slider;
6847
+ if (!step || step <= 0) return value;
6848
+ const snapped = min + Math.round((value - min) / step) * step;
6849
+ return Math.min(max, Math.max(min, snapped));
6850
+ }
6851
+ __name(snapToStep, "snapToStep");
6798
6852
  var MOUSE_CLICK_TRAVEL_PX = 6;
6799
6853
  var PRESS_TINT_MIN_SECONDS = 0.1;
6800
6854
  var PRESS_TINT = 0.72;
@@ -6808,11 +6862,12 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6808
6862
  constructor(interaction, options) {
6809
6863
  this.xrPresses = /* @__PURE__ */ new Map();
6810
6864
  this.mousePress = null;
6811
- this.mouseHover = null;
6865
+ /** What each pointer is on. Two hands can rest on one widget. */
6866
+ this.hovers = /* @__PURE__ */ new Map();
6812
6867
  /** entityId → seconds of pressed-tint left to display. */
6813
6868
  this.tintFloors = /* @__PURE__ */ new Map();
6814
6869
  this.tinted = /* @__PURE__ */ new Set();
6815
- this.raycaster = new THREE19.Raycaster();
6870
+ this.raycaster = new THREE26.Raycaster();
6816
6871
  this.interaction = interaction;
6817
6872
  this.inputSystem = options?.inputSystem ?? null;
6818
6873
  interaction?.registerBusyContributor(
@@ -6822,8 +6877,10 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6822
6877
  update(world, deltaTime = 0) {
6823
6878
  if (world.isEditorMode()) return;
6824
6879
  if (world.isXrPresenting()) {
6880
+ this.setHover(world, "mouse", null, null);
6825
6881
  this.updateXr(world);
6826
6882
  } else {
6883
+ this.dropGoneXrHovers(world, []);
6827
6884
  this.updateMouse(world);
6828
6885
  }
6829
6886
  this.updateTints(world, deltaTime);
@@ -6837,6 +6894,7 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6837
6894
  for (const input of inputs) {
6838
6895
  const hover = interaction.getHoverForInput(input);
6839
6896
  const hovered = hover ? this.widgetKind(world, hover.entityId) : null;
6897
+ this.setHover(world, input, hover && hovered ? hover.entityId : null, input);
6840
6898
  if (input.selectStart && hover && hovered) {
6841
6899
  this.xrPresses.set(input, {
6842
6900
  entityId: hover.entityId,
@@ -6865,6 +6923,15 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6865
6923
  for (const [input] of this.xrPresses) {
6866
6924
  if (!inputs.includes(input)) this.xrPresses.delete(input);
6867
6925
  }
6926
+ this.dropGoneXrHovers(world, inputs);
6927
+ }
6928
+ /** A transient source leaves with no select edge to clear its hover. */
6929
+ dropGoneXrHovers(world, inputs) {
6930
+ for (const source of [...this.hovers.keys()]) {
6931
+ if (source !== "mouse" && !inputs.includes(source)) {
6932
+ this.setHover(world, source, null, source);
6933
+ }
6934
+ }
6868
6935
  }
6869
6936
  // ── Mouse path (flat preview) ──────────────────────────────────────────────
6870
6937
  updateMouse(world) {
@@ -6872,22 +6939,7 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6872
6939
  if (!input) return;
6873
6940
  const hit = this.raycastMouse(world);
6874
6941
  const hovered = hit && this.widgetKind(world, hit) ? hit : null;
6875
- if (hovered !== this.mouseHover) {
6876
- if (this.mouseHover !== null) {
6877
- this.setTint(world, this.mouseHover, 1);
6878
- this.dispatchBubbling(
6879
- world,
6880
- this.mouseHover,
6881
- "onUiHoverEnd",
6882
- (b, e) => b.onUiHoverEnd(e)
6883
- );
6884
- }
6885
- if (hovered !== null) {
6886
- this.setTint(world, hovered, HOVER_TINT);
6887
- this.dispatchBubbling(world, hovered, "onUiHoverStart", (b, e) => b.onUiHoverStart(e));
6888
- }
6889
- this.mouseHover = hovered;
6890
- }
6942
+ this.setHover(world, "mouse", hovered, null);
6891
6943
  if (input.getMouseDown("left") && hovered) {
6892
6944
  const kind = this.widgetKind(world, hovered);
6893
6945
  if (kind) {
@@ -6895,12 +6947,12 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6895
6947
  this.beginPressTint(world, hovered);
6896
6948
  }
6897
6949
  }
6898
- if (this.mousePress) {
6950
+ if (this.mousePress && !input.getMouseDown("left")) {
6899
6951
  const delta = input.getMouseDelta();
6900
6952
  this.mousePress.travelPx += Math.hypot(delta.x, delta.y);
6901
- if (this.mousePress.kind === "slider") {
6902
- this.dragSliderMouse(world, this.mousePress.entityId);
6903
- }
6953
+ }
6954
+ if (this.mousePress?.kind === "slider") {
6955
+ this.dragSliderMouse(world, this.mousePress.entityId);
6904
6956
  }
6905
6957
  if (input.getMouseUp("left") && this.mousePress) {
6906
6958
  const press = this.mousePress;
@@ -6929,15 +6981,24 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6929
6981
  ndcY = -((pos.y - rect.top) / Math.max(1, rect.height) * 2 - 1);
6930
6982
  if (ndcX < -1 || ndcX > 1 || ndcY < -1 || ndcY > 1) return null;
6931
6983
  }
6932
- this.raycaster.setFromCamera(new THREE19.Vector2(ndcX, ndcY), world.getCamera());
6984
+ this.raycaster.setFromCamera(new THREE26.Vector2(ndcX, ndcY), world.getCamera());
6933
6985
  const hits = world.raycast(
6934
6986
  this.raycaster.ray.origin,
6935
6987
  this.raycaster.ray.direction
6936
6988
  );
6989
+ let surfaceTree = null;
6937
6990
  for (const hit of hits) {
6938
- if (typeof hit.entityId !== "number") continue;
6939
- if (this.widgetKind(world, hit.entityId)) return hit.entityId;
6940
- return null;
6991
+ const entityId = hit.entityId;
6992
+ if (typeof entityId !== "number") continue;
6993
+ if (!isShownInScene(world, entityId)) continue;
6994
+ if (this.widgetKind(world, entityId)) {
6995
+ const sameSurface = surfaceTree === null || widgetTreeRoot(world, entityId) === surfaceTree;
6996
+ return sameSurface ? entityId : null;
6997
+ }
6998
+ if (!isUiWidget(world, entityId)) return null;
6999
+ const tree = widgetTreeRoot(world, entityId);
7000
+ if (surfaceTree !== null && tree !== surfaceTree) return null;
7001
+ surfaceTree = tree;
6941
7002
  }
6942
7003
  return null;
6943
7004
  }
@@ -6945,7 +7006,8 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6945
7006
  widgetKind(world, entityId) {
6946
7007
  if (world.getComponent(entityId, UIButtonComponent)) return "button";
6947
7008
  if (world.getComponent(entityId, UIToggleComponent)) return "toggle";
6948
- if (world.getComponent(entityId, UISliderComponent)) return "slider";
7009
+ const slider = world.getComponent(entityId, UISliderComponent);
7010
+ if (slider && !isDisplayOnlySlider(slider)) return "slider";
6949
7011
  return null;
6950
7012
  }
6951
7013
  fire(world, press, input) {
@@ -6996,7 +7058,10 @@ var _UIInteractionSystem = class _UIInteractionSystem {
6996
7058
  const inset = slider.height / 2;
6997
7059
  const usable = Math.max(1e-3, slider.width - inset * 2);
6998
7060
  const t = Math.min(1, Math.max(0, local.x / usable + 0.5));
6999
- const next = slider.min + t * (slider.max - slider.min);
7061
+ const next = snapToStep(
7062
+ slider.min + t * (slider.max - slider.min),
7063
+ slider
7064
+ );
7000
7065
  if (Math.abs(next - slider.value) < 1e-4) return;
7001
7066
  slider.value = next;
7002
7067
  this.dispatchBubbling(
@@ -7074,6 +7139,43 @@ var _UIInteractionSystem = class _UIInteractionSystem {
7074
7139
  const name = world.getComponent(entityId, Name)?.value;
7075
7140
  return typeof name === "string" ? name : String(entityId);
7076
7141
  }
7142
+ // ── Hover ──────────────────────────────────────────────────────────────────
7143
+ setHover(world, source, next, input) {
7144
+ const previous = this.hovers.get(source) ?? null;
7145
+ if (previous === next) return;
7146
+ if (next === null) this.hovers.delete(source);
7147
+ else this.hovers.set(source, next);
7148
+ if (previous !== null && !this.isHovered(previous)) {
7149
+ if (!this.tintFloors.has(previous)) this.setTint(world, previous, 1);
7150
+ this.dispatchBubbling(
7151
+ world,
7152
+ previous,
7153
+ "onUiHoverEnd",
7154
+ (b, e) => b.onUiHoverEnd(e),
7155
+ input
7156
+ );
7157
+ }
7158
+ if (next !== null && this.hoverCount(next) === 1) {
7159
+ if (!this.tintFloors.has(next)) this.setTint(world, next, HOVER_TINT);
7160
+ this.dispatchBubbling(
7161
+ world,
7162
+ next,
7163
+ "onUiHoverStart",
7164
+ (b, e) => b.onUiHoverStart(e),
7165
+ input
7166
+ );
7167
+ }
7168
+ }
7169
+ hoverCount(entityId) {
7170
+ let count = 0;
7171
+ for (const hovered of this.hovers.values()) {
7172
+ if (hovered === entityId) count += 1;
7173
+ }
7174
+ return count;
7175
+ }
7176
+ isHovered(entityId) {
7177
+ return this.hoverCount(entityId) > 0;
7178
+ }
7077
7179
  // ── Pressed/hover tint ─────────────────────────────────────────────────────
7078
7180
  beginPressTint(world, entityId) {
7079
7181
  this.setTint(world, entityId, PRESS_TINT);
@@ -7091,11 +7193,7 @@ var _UIInteractionSystem = class _UIInteractionSystem {
7091
7193
  if (stillHeld) continue;
7092
7194
  if (next <= 0) {
7093
7195
  this.tintFloors.delete(entityId);
7094
- this.setTint(
7095
- world,
7096
- entityId,
7097
- this.mouseHover === entityId ? HOVER_TINT : 1
7098
- );
7196
+ this.setTint(world, entityId, this.isHovered(entityId) ? HOVER_TINT : 1);
7099
7197
  } else {
7100
7198
  this.tintFloors.set(entityId, next);
7101
7199
  }
@@ -7106,14 +7204,96 @@ var _UIInteractionSystem = class _UIInteractionSystem {
7106
7204
  if (!object) return;
7107
7205
  if (scalar === 1) this.tinted.delete(entityId);
7108
7206
  else this.tinted.add(entityId);
7109
- object.traverse((child) => {
7110
- const material = child.material;
7111
- if (material?.color) material.color.setScalar(scalar);
7112
- });
7207
+ for (const child of object.children) {
7208
+ const tag = child.userData[UI_PART_KEY];
7209
+ if (!tag) continue;
7210
+ tag.tint = scalar;
7211
+ applyPartColor(child, tag);
7212
+ }
7113
7213
  }
7114
7214
  };
7115
7215
  __name(_UIInteractionSystem, "UIInteractionSystem");
7116
7216
  var UIInteractionSystem = _UIInteractionSystem;
7217
+ var fonts = /* @__PURE__ */ new Map();
7218
+ var images = /* @__PURE__ */ new Map();
7219
+ function hash(text) {
7220
+ let value = 5381;
7221
+ for (let i = 0; i < text.length; i += 1) {
7222
+ value = (value * 33 ^ text.charCodeAt(i)) >>> 0;
7223
+ }
7224
+ return value.toString(36);
7225
+ }
7226
+ __name(hash, "hash");
7227
+ function tracked(url, work) {
7228
+ const manager = THREE26.DefaultLoadingManager;
7229
+ manager.itemStart(url);
7230
+ return work.catch((error) => {
7231
+ manager.itemError(url);
7232
+ throw error;
7233
+ }).finally(() => manager.itemEnd(url));
7234
+ }
7235
+ __name(tracked, "tracked");
7236
+ function uiFontFamily(src) {
7237
+ if (typeof FontFace === "undefined" || typeof document === "undefined") {
7238
+ return null;
7239
+ }
7240
+ const known = fonts.get(src);
7241
+ if (known) return known.value;
7242
+ const entry = { value: null, settled: false };
7243
+ fonts.set(src, entry);
7244
+ const url = resolvePrefabAssetUrl(src);
7245
+ const family = `relu-ui-${hash(url)}`;
7246
+ tracked(
7247
+ url,
7248
+ (async () => {
7249
+ const response = await fetch(url);
7250
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
7251
+ const buffer = await response.arrayBuffer();
7252
+ const face = new FontFace(family, buffer, { weight: "100 900" });
7253
+ await face.load();
7254
+ document.fonts.add(face);
7255
+ })()
7256
+ ).then(() => {
7257
+ entry.value = family;
7258
+ }).catch((error) => {
7259
+ console.warn(`[spatial-ecs] Couldn't load UI font ${src}.`, error);
7260
+ }).finally(() => {
7261
+ entry.settled = true;
7262
+ });
7263
+ return null;
7264
+ }
7265
+ __name(uiFontFamily, "uiFontFamily");
7266
+ function uiImage(src) {
7267
+ if (typeof Image === "undefined" || typeof fetch === "undefined") {
7268
+ return null;
7269
+ }
7270
+ const known = images.get(src);
7271
+ if (known) return known.value;
7272
+ const entry = { value: null, settled: false };
7273
+ images.set(src, entry);
7274
+ const url = resolvePrefabAssetUrl(src);
7275
+ tracked(
7276
+ url,
7277
+ (async () => {
7278
+ const response = await fetch(url);
7279
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
7280
+ const blob = await response.blob();
7281
+ const typed = /\.svg(\?|#|$)/i.test(url) ? new Blob([blob], { type: "image/svg+xml" }) : blob;
7282
+ const image = new Image();
7283
+ image.src = URL.createObjectURL(typed);
7284
+ await image.decode();
7285
+ return image;
7286
+ })()
7287
+ ).then((image) => {
7288
+ entry.value = image;
7289
+ }).catch((error) => {
7290
+ console.warn(`[spatial-ecs] Couldn't load UI image ${src}.`, error);
7291
+ }).finally(() => {
7292
+ entry.settled = true;
7293
+ });
7294
+ return null;
7295
+ }
7296
+ __name(uiImage, "uiImage");
7117
7297
 
7118
7298
  // core/ui/uiCanvas.ts
7119
7299
  var DEFAULT_PIXELS_PER_METER = 1024;
@@ -7186,18 +7366,21 @@ function clampLines(measure, lines, maxLines, maxWidthPx) {
7186
7366
  }
7187
7367
  __name(clampLines, "clampLines");
7188
7368
  var UI_FONT_STACK = '-apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
7189
- function tracePath(ctx, x, y, w, h, r) {
7190
- const radius = Math.max(0, Math.min(r, w / 2, h / 2));
7369
+ var NO_ASSETS = { image: /* @__PURE__ */ __name(() => null, "image") };
7370
+ function tracePath(ctx, x, y, w, h, radii) {
7371
+ const limit = Math.min(w / 2, h / 2);
7372
+ const fit = /* @__PURE__ */ __name((r) => Math.max(0, Math.min(r, limit)), "fit");
7373
+ const [tl, tr, br, bl] = (Array.isArray(radii) ? radii : [radii, radii, radii, radii]).map(fit);
7191
7374
  ctx.beginPath();
7192
7375
  if (typeof ctx.roundRect === "function") {
7193
- ctx.roundRect(x, y, w, h, radius);
7376
+ ctx.roundRect(x, y, w, h, [tl, tr, br, bl]);
7194
7377
  return;
7195
7378
  }
7196
- ctx.moveTo(x + radius, y);
7197
- ctx.arcTo(x + w, y, x + w, y + h, radius);
7198
- ctx.arcTo(x + w, y + h, x, y + h, radius);
7199
- ctx.arcTo(x, y + h, x, y, radius);
7200
- ctx.arcTo(x, y, x + w, y, radius);
7379
+ ctx.moveTo(x + tl, y);
7380
+ ctx.arcTo(x + w, y, x + w, y + h, tr);
7381
+ ctx.arcTo(x + w, y + h, x, y + h, br);
7382
+ ctx.arcTo(x, y + h, x, y, bl);
7383
+ ctx.arcTo(x, y, x + w, y, tl);
7201
7384
  ctx.closePath();
7202
7385
  }
7203
7386
  __name(tracePath, "tracePath");
@@ -7217,108 +7400,244 @@ function withOpacity(ctx, color, opacity) {
7217
7400
  return parsed;
7218
7401
  }
7219
7402
  __name(withOpacity, "withOpacity");
7220
- function drawChrome(ctx, pxWidth, pxHeight, density, style) {
7221
- ctx.clearRect(0, 0, pxWidth, pxHeight);
7222
- const border = Math.max(0, style.borderWidthMeters * density);
7223
- const radius = style.cornerRadiusMeters * density;
7224
- tracePath(ctx, 0, 0, pxWidth, pxHeight, radius);
7225
- ctx.fillStyle = withOpacity(
7226
- ctx,
7227
- style.backgroundColor,
7228
- Math.max(0, Math.min(1, style.backgroundOpacity))
7229
- );
7403
+ function fillStyleFor(ctx, paint, box, opacity) {
7404
+ if (paint.kind === "solid") return withOpacity(ctx, paint.color, opacity);
7405
+ const cx = box.x + box.w / 2;
7406
+ const cy = box.y + box.h / 2;
7407
+ let gradient;
7408
+ if (paint.kind === "linear") {
7409
+ const radians = paint.angle * Math.PI / 180;
7410
+ const dx = Math.sin(radians);
7411
+ const dy = -Math.cos(radians);
7412
+ const half = (Math.abs(box.w * dx) + Math.abs(box.h * dy)) / 2;
7413
+ gradient = ctx.createLinearGradient(
7414
+ cx - dx * half,
7415
+ cy - dy * half,
7416
+ cx + dx * half,
7417
+ cy + dy * half
7418
+ );
7419
+ } else {
7420
+ gradient = ctx.createRadialGradient(
7421
+ cx,
7422
+ cy,
7423
+ 0,
7424
+ cx,
7425
+ cy,
7426
+ Math.max(box.w, box.h) / 2
7427
+ );
7428
+ }
7429
+ for (const stop of paint.stops) {
7430
+ gradient.addColorStop(stop.at, withOpacity(ctx, stop.color, opacity));
7431
+ }
7432
+ return gradient;
7433
+ }
7434
+ __name(fillStyleFor, "fillStyleFor");
7435
+ function drawImagePaint(ctx, paint, box, assets) {
7436
+ const image = assets.image(paint.src);
7437
+ if (!image) return;
7438
+ const sourceW = image.naturalWidth || box.w;
7439
+ const sourceH = image.naturalHeight || box.h;
7440
+ const scale = paint.fit === "fill" ? 1 : paint.fit === "cover" ? Math.max(box.w / sourceW, box.h / sourceH) : Math.min(box.w / sourceW, box.h / sourceH);
7441
+ const w = paint.fit === "fill" ? box.w : sourceW * scale;
7442
+ const h = paint.fit === "fill" ? box.h : sourceH * scale;
7443
+ const x = box.x + (box.w - w) / 2;
7444
+ const y = box.y + (box.h - h) / 2;
7445
+ if (!paint.tint) {
7446
+ ctx.drawImage(image, x, y, w, h);
7447
+ return;
7448
+ }
7449
+ const scratch = assets.scratch?.(Math.ceil(w), Math.ceil(h));
7450
+ if (!scratch) {
7451
+ ctx.drawImage(image, x, y, w, h);
7452
+ return;
7453
+ }
7454
+ scratch.drawImage(image, 0, 0, w, h);
7455
+ scratch.globalCompositeOperation = "source-in";
7456
+ scratch.fillStyle = paint.tint;
7457
+ scratch.fillRect(0, 0, w, h);
7458
+ ctx.drawImage(scratch.canvas, x, y, w, h);
7459
+ }
7460
+ __name(drawImagePaint, "drawImagePaint");
7461
+ function pxRadii(radius, density) {
7462
+ return radius.map((r) => r * density);
7463
+ }
7464
+ __name(pxRadii, "pxRadii");
7465
+ function drawBoxShadow(ctx, box, radii, shadow, density) {
7466
+ ctx.save();
7467
+ ctx.shadowColor = shadow.color;
7468
+ ctx.shadowBlur = shadow.blur * density;
7469
+ ctx.shadowOffsetX = shadow.x * density;
7470
+ ctx.shadowOffsetY = -shadow.y * density;
7471
+ ctx.fillStyle = "#000000";
7472
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7230
7473
  ctx.fill();
7231
- if (border > 0) {
7474
+ ctx.restore();
7475
+ ctx.save();
7476
+ ctx.globalCompositeOperation = "destination-out";
7477
+ ctx.fillStyle = "#000000";
7478
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7479
+ ctx.fill();
7480
+ ctx.restore();
7481
+ }
7482
+ __name(drawBoxShadow, "drawBoxShadow");
7483
+ function drawChrome(ctx, box, density, chrome, assets = NO_ASSETS) {
7484
+ const radii = pxRadii(chrome.radius, density);
7485
+ for (const shadow of chrome.shadows) {
7486
+ drawBoxShadow(ctx, box, radii, shadow, density);
7487
+ }
7488
+ const opacity = Math.max(0, Math.min(1, chrome.fillOpacity));
7489
+ if (chrome.fill?.kind === "image") {
7490
+ ctx.save();
7491
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7492
+ ctx.clip();
7493
+ ctx.globalAlpha = opacity;
7494
+ drawImagePaint(ctx, chrome.fill, box, assets);
7495
+ ctx.restore();
7496
+ } else if (chrome.fill) {
7497
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7498
+ ctx.fillStyle = fillStyleFor(ctx, chrome.fill, box, opacity);
7499
+ ctx.fill();
7500
+ }
7501
+ if (chrome.edge) {
7502
+ const width = chrome.edge.width * density;
7503
+ ctx.save();
7504
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7505
+ ctx.clip();
7506
+ const fade = ctx.createLinearGradient(0, box.y, 0, box.y + box.h / 2);
7507
+ fade.addColorStop(0, chrome.edge.color);
7508
+ fade.addColorStop(1, withOpacity(ctx, chrome.edge.color, 0));
7509
+ ctx.strokeStyle = fade;
7510
+ ctx.lineWidth = width * 2;
7511
+ tracePath(ctx, box.x, box.y, box.w, box.h, radii);
7512
+ ctx.stroke();
7513
+ ctx.restore();
7514
+ }
7515
+ if (chrome.border) {
7516
+ const border = chrome.border.width * density;
7232
7517
  tracePath(
7233
7518
  ctx,
7234
- border / 2,
7235
- border / 2,
7236
- pxWidth - border,
7237
- pxHeight - border,
7238
- Math.max(0, radius - border / 2)
7519
+ box.x + border / 2,
7520
+ box.y + border / 2,
7521
+ box.w - border,
7522
+ box.h - border,
7523
+ radii.map((r) => Math.max(0, r - border / 2))
7239
7524
  );
7240
- ctx.strokeStyle = style.borderColor;
7525
+ ctx.strokeStyle = chrome.border.color;
7241
7526
  ctx.lineWidth = border;
7242
7527
  ctx.stroke();
7243
7528
  }
7244
7529
  }
7245
7530
  __name(drawChrome, "drawChrome");
7246
- function drawTextBlock(ctx, pxWidth, pxHeight, density, text, style) {
7247
- const fontPx = Math.max(4, style.fontSizeMeters * density);
7248
- const paddingPx = style.paddingMeters * density;
7249
- const contentW = Math.max(1, pxWidth - paddingPx * 2);
7250
- const contentH = Math.max(1, pxHeight - paddingPx * 2);
7531
+ var applyTransform2 = /* @__PURE__ */ __name((text, transform) => transform === "upper" ? text.toUpperCase() : transform === "lower" ? text.toLowerCase() : text, "applyTransform");
7532
+ function fillTracked(ctx, line, x, y, trackingPx, width) {
7533
+ let cursor = ctx.textAlign === "center" ? x - width / 2 : ctx.textAlign === "right" ? x - width : x;
7534
+ const align = ctx.textAlign;
7535
+ ctx.textAlign = "left";
7536
+ for (const glyph of line) {
7537
+ ctx.fillText(glyph, cursor, y);
7538
+ cursor += ctx.measureText(glyph).width + trackingPx;
7539
+ }
7540
+ ctx.textAlign = align;
7541
+ }
7542
+ __name(fillTracked, "fillTracked");
7543
+ function drawTextBlock(ctx, box, density, text, style, family = null, assets = NO_ASSETS) {
7544
+ const fontPx = Math.max(4, style.fontSize * density);
7545
+ const paddingPx = style.padding * density;
7546
+ const contentW = Math.max(1, box.w - paddingPx * 2);
7547
+ const contentH = Math.max(1, box.h - paddingPx * 2);
7251
7548
  const lineHeightPx = fontPx * style.lineHeight;
7252
- ctx.font = `${style.fontWeight} ${fontPx}px ${UI_FONT_STACK}`;
7253
- const measure = /* @__PURE__ */ __name((s) => ctx.measureText(s).width, "measure");
7254
- const maxLines = Math.max(1, Math.floor(contentH / lineHeightPx));
7549
+ const trackingPx = style.tracking * density;
7550
+ const stack = family ? `"${family}", ${UI_FONT_STACK}` : UI_FONT_STACK;
7551
+ ctx.font = `${style.fontWeight} ${fontPx}px ${stack}`;
7552
+ const nativeTracking = trackingPx !== 0 && "letterSpacing" in ctx;
7553
+ if (nativeTracking) {
7554
+ ctx.letterSpacing = `${trackingPx}px`;
7555
+ }
7556
+ const manualTracking = trackingPx !== 0 && !nativeTracking;
7557
+ const measure = /* @__PURE__ */ __name((s) => ctx.measureText(s).width + (manualTracking ? trackingPx * s.length : 0), "measure");
7558
+ const fits = Math.max(1, Math.floor(contentH / lineHeightPx));
7559
+ const maxLines = style.maxLines ? Math.min(fits, style.maxLines) : fits;
7255
7560
  const lines = clampLines(
7256
7561
  measure,
7257
- wrapText(measure, text, contentW),
7562
+ wrapText(measure, applyTransform2(text, style.transform), contentW),
7258
7563
  maxLines,
7259
7564
  contentW
7260
7565
  );
7261
7566
  const blockH = lines.length * lineHeightPx;
7262
- const top = style.valign === "top" ? paddingPx : style.valign === "bottom" ? pxHeight - paddingPx - blockH : (pxHeight - blockH) / 2;
7263
- const anchorX = style.align === "left" ? paddingPx : style.align === "right" ? pxWidth - paddingPx : pxWidth / 2;
7567
+ const top = style.valign === "top" ? box.y + paddingPx : style.valign === "bottom" ? box.y + box.h - paddingPx - blockH : box.y + (box.h - blockH) / 2;
7568
+ const anchorX = style.align === "left" ? box.x + paddingPx : style.align === "right" ? box.x + box.w - paddingPx : box.x + box.w / 2;
7264
7569
  ctx.save();
7265
- tracePath(ctx, 0, 0, pxWidth, pxHeight, 0);
7266
- ctx.clip();
7267
- ctx.fillStyle = style.color;
7570
+ if (style.paint.kind === "image") {
7571
+ ctx.fillStyle = "#ffffff";
7572
+ } else {
7573
+ ctx.fillStyle = fillStyleFor(ctx, style.paint, box, 1);
7574
+ }
7575
+ if (style.shadow) {
7576
+ ctx.shadowColor = style.shadow.color;
7577
+ ctx.shadowBlur = style.shadow.blur * density;
7578
+ ctx.shadowOffsetX = style.shadow.x * density;
7579
+ ctx.shadowOffsetY = -style.shadow.y * density;
7580
+ }
7268
7581
  ctx.textAlign = style.align;
7269
7582
  ctx.textBaseline = "middle";
7270
7583
  for (let i = 0; i < lines.length; i += 1) {
7271
- ctx.fillText(lines[i], anchorX, top + (i + 0.5) * lineHeightPx);
7584
+ const y = top + (i + 0.5) * lineHeightPx;
7585
+ if (manualTracking) {
7586
+ fillTracked(ctx, lines[i], anchorX, y, trackingPx, measure(lines[i]));
7587
+ } else {
7588
+ ctx.fillText(lines[i], anchorX, y);
7589
+ }
7272
7590
  }
7273
7591
  ctx.restore();
7592
+ if (style.paint.kind === "image") {
7593
+ ctx.save();
7594
+ ctx.globalCompositeOperation = "source-atop";
7595
+ drawImagePaint(ctx, style.paint, box, assets);
7596
+ ctx.restore();
7597
+ }
7598
+ if (nativeTracking) {
7599
+ ctx.letterSpacing = "0px";
7600
+ }
7274
7601
  }
7275
7602
  __name(drawTextBlock, "drawTextBlock");
7276
- function panelSignature(widthM, heightM, size, style) {
7603
+ function panelSignature(widthM, heightM, size, chrome, loaded = null) {
7277
7604
  return JSON.stringify([
7278
7605
  widthM,
7279
7606
  heightM,
7280
7607
  size.pxWidth,
7281
7608
  size.pxHeight,
7282
- style.backgroundColor,
7283
- style.backgroundOpacity,
7284
- style.cornerRadiusMeters,
7285
- style.borderWidthMeters,
7286
- style.borderColor
7609
+ chrome,
7610
+ loaded
7287
7611
  ]);
7288
7612
  }
7289
7613
  __name(panelSignature, "panelSignature");
7290
- function textSignature(widthM, heightM, size, text, style) {
7614
+ function textSignature(widthM, heightM, size, text, style, family = null) {
7291
7615
  return JSON.stringify([
7292
7616
  widthM,
7293
7617
  heightM,
7294
7618
  size.pxWidth,
7295
7619
  size.pxHeight,
7296
7620
  text,
7297
- style.fontSizeMeters,
7298
- style.fontWeight,
7299
- style.color,
7300
- style.align,
7301
- style.valign,
7302
- style.lineHeight,
7303
- style.paddingMeters
7621
+ style,
7622
+ family
7304
7623
  ]);
7305
7624
  }
7306
7625
  __name(textSignature, "textSignature");
7307
- function buttonSignature(widthM, heightM, size, panel, label, text) {
7626
+ function buttonSignature(widthM, heightM, size, chrome, label, text, family = null, loaded = null) {
7308
7627
  return JSON.stringify([
7309
- panelSignature(widthM, heightM, size, panel),
7628
+ panelSignature(widthM, heightM, size, chrome, loaded),
7310
7629
  label,
7311
- text.fontSizeMeters,
7312
- text.fontWeight,
7313
- text.color
7630
+ text,
7631
+ family
7314
7632
  ]);
7315
7633
  }
7316
7634
  __name(buttonSignature, "buttonSignature");
7317
- function trackSignature(widthM, heightM, size, panel, accentColor, knobColor) {
7635
+ function trackSignature(widthM, heightM, size, chrome, accentColor, knobColor, variant = null) {
7318
7636
  return JSON.stringify([
7319
- panelSignature(widthM, heightM, size, panel),
7637
+ panelSignature(widthM, heightM, size, chrome),
7320
7638
  accentColor,
7321
- knobColor
7639
+ knobColor,
7640
+ variant
7322
7641
  ]);
7323
7642
  }
7324
7643
  __name(trackSignature, "trackSignature");
@@ -7328,17 +7647,226 @@ function drawRoundedBar(ctx, pxX, pxY, pxW, pxH, color) {
7328
7647
  ctx.fill();
7329
7648
  }
7330
7649
  __name(drawRoundedBar, "drawRoundedBar");
7650
+ function drawCheckMark(ctx, box, color) {
7651
+ ctx.beginPath();
7652
+ ctx.moveTo(box.x + box.w * 0.24, box.y + box.h * 0.54);
7653
+ ctx.lineTo(box.x + box.w * 0.43, box.y + box.h * 0.72);
7654
+ ctx.lineTo(box.x + box.w * 0.77, box.y + box.h * 0.3);
7655
+ ctx.strokeStyle = color;
7656
+ ctx.lineWidth = box.h * 0.13;
7657
+ ctx.lineCap = "round";
7658
+ ctx.lineJoin = "round";
7659
+ ctx.stroke();
7660
+ }
7661
+ __name(drawCheckMark, "drawCheckMark");
7662
+ function drawDot(ctx, cx, cy, radius, color) {
7663
+ ctx.beginPath();
7664
+ ctx.arc(cx, cy, radius, 0, Math.PI * 2);
7665
+ ctx.fillStyle = color;
7666
+ ctx.fill();
7667
+ }
7668
+ __name(drawDot, "drawDot");
7669
+ function drawRing(ctx, box, t, trackColor, accentColor) {
7670
+ const thickness = Math.min(box.w, box.h) * 0.14;
7671
+ const radius = Math.min(box.w, box.h) / 2 - thickness / 2;
7672
+ const cx = box.x + box.w / 2;
7673
+ const cy = box.y + box.h / 2;
7674
+ ctx.lineWidth = thickness;
7675
+ ctx.lineCap = "round";
7676
+ ctx.beginPath();
7677
+ ctx.arc(cx, cy, radius, 0, Math.PI * 2);
7678
+ ctx.strokeStyle = trackColor;
7679
+ ctx.stroke();
7680
+ if (t <= 0) return;
7681
+ const start = -Math.PI / 2;
7682
+ ctx.beginPath();
7683
+ ctx.arc(cx, cy, radius, start, start + t * Math.PI * 2);
7684
+ ctx.strokeStyle = accentColor;
7685
+ ctx.stroke();
7686
+ }
7687
+ __name(drawRing, "drawRing");
7688
+ function drawIconLabel(ctx, box, density, label, style, family, icon, position, assets) {
7689
+ const fontPx = style.fontSize * density;
7690
+ const iconPx = fontPx * 1.3;
7691
+ const gapPx = label ? fontPx * 0.5 : 0;
7692
+ const stack = family ? `"${family}", ${UI_FONT_STACK}` : UI_FONT_STACK;
7693
+ ctx.font = `${style.fontWeight} ${fontPx}px ${stack}`;
7694
+ const labelPx = label ? ctx.measureText(label).width : 0;
7695
+ const groupPx = iconPx + gapPx + labelPx;
7696
+ const startX = box.x + (box.w - groupPx) / 2;
7697
+ const iconX = position === "left" ? startX : startX + labelPx + gapPx;
7698
+ const labelX = position === "left" ? startX + iconPx + gapPx : startX;
7699
+ drawImagePaint(
7700
+ ctx,
7701
+ { src: icon, fit: "contain", tint: style.color },
7702
+ { x: iconX, y: box.y + (box.h - iconPx) / 2, w: iconPx, h: iconPx },
7703
+ assets
7704
+ );
7705
+ if (label) {
7706
+ drawTextBlock(
7707
+ ctx,
7708
+ { x: labelX, y: box.y, w: labelPx + 2, h: box.h },
7709
+ density,
7710
+ label,
7711
+ { ...style, align: "left", padding: 0 },
7712
+ family,
7713
+ assets
7714
+ );
7715
+ }
7716
+ }
7717
+ __name(drawIconLabel, "drawIconLabel");
7718
+
7719
+ // core/ui/uiPaintOrder.ts
7720
+ var BASE_RENDER_ORDER = 10;
7721
+ var MAX_RENDER_ORDER = 890;
7722
+ var MAX_TREE_RANK = MAX_RENDER_ORDER - BASE_RENDER_ORDER - 1;
7723
+ var SLOT_STEP = 1e-4;
7724
+ var SLOTS_PER_WIDGET = 3;
7725
+ var MAX_WIDGET_INDEX = Math.floor(1 / SLOT_STEP / SLOTS_PER_WIDGET) - 1;
7726
+ var PLANE_EPSILON_METERS = 1e-3;
7727
+ var PARALLEL_DOT = 1 - 1e-4;
7728
+ var dot = /* @__PURE__ */ __name((a, b) => a.x * b.x + a.y * b.y + a.z * b.z, "dot");
7729
+ var length = /* @__PURE__ */ __name((a) => Math.sqrt(dot(a, a)), "length");
7730
+ var subtract = /* @__PURE__ */ __name((a, b) => ({
7731
+ x: a.x - b.x,
7732
+ y: a.y - b.y,
7733
+ z: a.z - b.z
7734
+ }), "subtract");
7735
+ function unit(a) {
7736
+ const len = length(a);
7737
+ if (len < 1e-9) return { x: 0, y: 0, z: 0 };
7738
+ return { x: a.x / len, y: a.y / len, z: a.z / len };
7739
+ }
7740
+ __name(unit, "unit");
7741
+ function normalOf(rect) {
7742
+ const { halfX: a, halfY: b } = rect;
7743
+ return unit({
7744
+ x: a.y * b.z - a.z * b.y,
7745
+ y: a.z * b.x - a.x * b.z,
7746
+ z: a.x * b.y - a.y * b.x
7747
+ });
7748
+ }
7749
+ __name(normalOf, "normalOf");
7750
+ var planeDistance = /* @__PURE__ */ __name((rect, normal, eye) => Math.abs(dot(subtract(eye, rect.center), normal)), "planeDistance");
7751
+ function closestPointDistance(rect, eye) {
7752
+ const toEye = subtract(eye, rect.center);
7753
+ const lenX = length(rect.halfX);
7754
+ const lenY = length(rect.halfY);
7755
+ const axisX = unit(rect.halfX);
7756
+ const axisY = unit(rect.halfY);
7757
+ const s = Math.max(-lenX, Math.min(lenX, dot(toEye, axisX)));
7758
+ const t = Math.max(-lenY, Math.min(lenY, dot(toEye, axisY)));
7759
+ return length({
7760
+ x: toEye.x - axisX.x * s - axisY.x * t,
7761
+ y: toEye.y - axisX.y * s - axisY.y * t,
7762
+ z: toEye.z - axisX.z * s - axisY.z * t
7763
+ });
7764
+ }
7765
+ __name(closestPointDistance, "closestPointDistance");
7766
+ var areaOf = /* @__PURE__ */ __name((rect) => length(rect.halfX) * length(rect.halfY), "areaOf");
7767
+ function comparePaintRects(a, b, eye) {
7768
+ const normalA = normalOf(a);
7769
+ const normalB = normalOf(b);
7770
+ const parallel = Math.abs(dot(normalA, normalB)) >= PARALLEL_DOT;
7771
+ const distanceA = parallel ? planeDistance(a, normalA, eye) : closestPointDistance(a, eye);
7772
+ const distanceB = parallel ? planeDistance(b, normalB, eye) : closestPointDistance(b, eye);
7773
+ if (Math.abs(distanceA - distanceB) > PLANE_EPSILON_METERS) {
7774
+ return distanceB - distanceA;
7775
+ }
7776
+ const areaA = areaOf(a);
7777
+ const areaB = areaOf(b);
7778
+ if (areaA !== areaB) return areaB - areaA;
7779
+ return a.id - b.id;
7780
+ }
7781
+ __name(comparePaintRects, "comparePaintRects");
7782
+ function buildPaintTrees(nodes) {
7783
+ const known = new Set(nodes.map((node) => node.id));
7784
+ const children = /* @__PURE__ */ new Map();
7785
+ const roots = [];
7786
+ for (const node of nodes) {
7787
+ if (node.parent !== null && known.has(node.parent)) {
7788
+ const siblings = children.get(node.parent);
7789
+ if (siblings) siblings.push(node.id);
7790
+ else children.set(node.parent, [node.id]);
7791
+ } else {
7792
+ roots.push(node.id);
7793
+ }
7794
+ }
7795
+ const trees = [];
7796
+ const visited = /* @__PURE__ */ new Set();
7797
+ const walk = /* @__PURE__ */ __name((id, order) => {
7798
+ if (visited.has(id)) return;
7799
+ visited.add(id);
7800
+ order.push(id);
7801
+ const kids = children.get(id);
7802
+ if (!kids) return;
7803
+ for (const kid of [...kids].sort((a, b) => a - b)) walk(kid, order);
7804
+ }, "walk");
7805
+ for (const rootId of [...roots].sort((a, b) => a - b)) {
7806
+ const order = [];
7807
+ walk(rootId, order);
7808
+ trees.push({ rootId, order });
7809
+ }
7810
+ return trees;
7811
+ }
7812
+ __name(buildPaintTrees, "buildPaintTrees");
7813
+ function paintSlots(treeRank, widgetIndex) {
7814
+ const base = BASE_RENDER_ORDER + Math.min(treeRank, MAX_TREE_RANK);
7815
+ const index = Math.min(widgetIndex, MAX_WIDGET_INDEX);
7816
+ const slot = /* @__PURE__ */ __name((offset) => base + (index * SLOTS_PER_WIDGET + offset) * SLOT_STEP, "slot");
7817
+ return { face: slot(0), parts: slot(1), descendants: slot(2) };
7818
+ }
7819
+ __name(paintSlots, "paintSlots");
7820
+ function assignPaintOrder(trees, rects, eye) {
7821
+ const sorted = trees.filter((tree) => rects.has(tree.rootId)).sort(
7822
+ (a, b) => comparePaintRects(
7823
+ rects.get(a.rootId),
7824
+ rects.get(b.rootId),
7825
+ eye
7826
+ )
7827
+ );
7828
+ const slots = /* @__PURE__ */ new Map();
7829
+ sorted.forEach((tree, rank) => {
7830
+ tree.order.forEach((id, index) => {
7831
+ slots.set(id, paintSlots(rank, index));
7832
+ });
7833
+ });
7834
+ return slots;
7835
+ }
7836
+ __name(assignPaintOrder, "assignPaintOrder");
7331
7837
 
7332
7838
  // core/systems/UIPanelSystem.ts
7333
- var FACE_RENDER_ORDER = 10;
7334
7839
  var CHILD_Z = 2e-3;
7840
+ var DESCENDANT_WALK_INTERVAL = 30;
7841
+ var METER_STEPS = 200;
7842
+ var CLEAR_CHROME = {
7843
+ backgroundColor: "#000000",
7844
+ backgroundOpacity: 0,
7845
+ cornerRadius: 0,
7846
+ borderWidth: 0,
7847
+ borderColor: "#000000"
7848
+ };
7849
+ var PAINT_STAMP_KEY = "__uiPaintStamp";
7335
7850
  var _UIPanelSystem = class _UIPanelSystem {
7336
7851
  constructor(options) {
7337
7852
  this.entries = /* @__PURE__ */ new Map();
7853
+ this.framesUntilDescendantWalk = 0;
7854
+ /** Whether any panel defines tokens, so plain scenes skip the parent walk. */
7855
+ this.anyTokens = false;
7856
+ this.drawAssets = {
7857
+ image: /* @__PURE__ */ __name((src) => uiImage(src), "image"),
7858
+ scratch: /* @__PURE__ */ __name((width, height) => {
7859
+ const canvas = this.createCanvas();
7860
+ canvas.width = width;
7861
+ canvas.height = height;
7862
+ return canvas.getContext("2d");
7863
+ }, "scratch")
7864
+ };
7338
7865
  this.createCanvas = options?.createCanvas ?? (() => document.createElement("canvas"));
7339
7866
  }
7340
7867
  update(world, deltaTime = 0) {
7341
7868
  const live = /* @__PURE__ */ new Set();
7869
+ this.anyTokens = world.query(UIPanelComponent).some((id) => world.getComponent(id, UIPanelComponent)?.tokens);
7342
7870
  for (const entityId of world.query(UIPanelComponent, Object3DRef)) {
7343
7871
  const panel = world.getComponent(entityId, UIPanelComponent);
7344
7872
  if (!panel) continue;
@@ -7376,6 +7904,7 @@ var _UIPanelSystem = class _UIPanelSystem {
7376
7904
  this.entries.delete(entityId);
7377
7905
  }
7378
7906
  }
7907
+ this.applyPaintOrder(world);
7379
7908
  }
7380
7909
  /** All GPU/canvas resources; call on world teardown. */
7381
7910
  dispose() {
@@ -7384,84 +7913,188 @@ var _UIPanelSystem = class _UIPanelSystem {
7384
7913
  }
7385
7914
  // ── Widgets ────────────────────────────────────────────────────────────────
7386
7915
  syncPanel(world, entityId, panel) {
7387
- const size = resolveCanvasSize(panel.width, panel.height, panel.pixelDensity);
7388
- const style = {
7389
- backgroundColor: panel.backgroundColor,
7390
- backgroundOpacity: panel.backgroundOpacity,
7391
- cornerRadiusMeters: panel.cornerRadius,
7392
- borderWidthMeters: panel.borderWidth,
7393
- borderColor: panel.borderColor
7394
- };
7395
- const signature = panelSignature(panel.width, panel.height, size, style);
7396
- this.rebuildIfChanged(world, entityId, signature, panel.width, panel.height, size, (ctx) => {
7397
- drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, style);
7398
- });
7916
+ const chrome = resolveChrome(
7917
+ panel,
7918
+ panel.style,
7919
+ this.tokensFor(world, entityId)
7920
+ );
7921
+ const bleed = chromeBleed(chrome);
7922
+ const size = resolveCanvasSize(
7923
+ panel.width + bleed * 2,
7924
+ panel.height + bleed * 2,
7925
+ panel.pixelDensity
7926
+ );
7927
+ const signature = panelSignature(
7928
+ panel.width,
7929
+ panel.height,
7930
+ size,
7931
+ chrome,
7932
+ imagesLoaded(chrome)
7933
+ );
7934
+ this.rebuildIfChanged(
7935
+ world,
7936
+ entityId,
7937
+ signature,
7938
+ panel,
7939
+ bleed,
7940
+ size,
7941
+ (ctx) => {
7942
+ drawChrome(
7943
+ ctx,
7944
+ layoutBox(panel, bleed, size.density),
7945
+ size.density,
7946
+ chrome,
7947
+ this.drawAssets
7948
+ );
7949
+ },
7950
+ THREE26.DoubleSide
7951
+ );
7399
7952
  }
7400
7953
  syncText(world, entityId, text) {
7401
- const size = resolveCanvasSize(text.width, text.height);
7402
- const style = {
7403
- fontSizeMeters: text.fontSize,
7404
- fontWeight: text.fontWeight,
7405
- color: text.color,
7406
- align: text.align,
7407
- valign: text.valign,
7408
- lineHeight: text.lineHeight,
7409
- paddingMeters: text.padding
7410
- };
7411
- const signature = textSignature(text.width, text.height, size, text.text, style);
7412
- this.rebuildIfChanged(world, entityId, signature, text.width, text.height, size, (ctx) => {
7413
- ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
7414
- drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, text.text, style);
7415
- });
7954
+ const tokens = this.tokensFor(world, entityId);
7955
+ const chrome = text.style ? resolveChrome(CLEAR_CHROME, text.style, tokens) : null;
7956
+ const bleed = chrome ? chromeBleed(chrome) : 0;
7957
+ const size = resolveCanvasSize(
7958
+ text.width + bleed * 2,
7959
+ text.height + bleed * 2,
7960
+ text.pixelDensity
7961
+ );
7962
+ const style = resolveText(text, text.style, tokens);
7963
+ const family = familyOf(style.font);
7964
+ const signature = JSON.stringify([
7965
+ textSignature(text.width, text.height, size, text.text, style, family),
7966
+ chrome,
7967
+ chrome ? imagesLoaded(chrome) : null
7968
+ ]);
7969
+ this.rebuildIfChanged(
7970
+ world,
7971
+ entityId,
7972
+ signature,
7973
+ text,
7974
+ bleed,
7975
+ size,
7976
+ (ctx) => {
7977
+ const box = layoutBox(text, bleed, size.density);
7978
+ if (chrome) {
7979
+ drawChrome(ctx, box, size.density, chrome, this.drawAssets);
7980
+ }
7981
+ drawTextBlock(
7982
+ ctx,
7983
+ box,
7984
+ size.density,
7985
+ text.text,
7986
+ style,
7987
+ family,
7988
+ this.drawAssets
7989
+ );
7990
+ }
7991
+ );
7416
7992
  }
7417
7993
  syncButton(world, entityId, button) {
7418
- const size = resolveCanvasSize(button.width, button.height);
7419
- const chrome = {
7420
- backgroundColor: button.backgroundColor,
7421
- backgroundOpacity: button.backgroundOpacity,
7422
- cornerRadiusMeters: button.cornerRadius,
7423
- borderWidthMeters: button.borderWidth,
7424
- borderColor: button.borderColor
7425
- };
7426
- const label = {
7427
- fontSizeMeters: button.fontSize,
7428
- fontWeight: button.fontWeight,
7429
- color: button.textColor,
7430
- align: "center",
7431
- valign: "middle",
7432
- lineHeight: 1.2,
7433
- paddingMeters: 0.01
7434
- };
7994
+ const tokens = this.tokensFor(world, entityId);
7995
+ const chrome = resolveChrome(button, button.style, tokens);
7996
+ const bleed = chromeBleed(chrome);
7997
+ const size = resolveCanvasSize(
7998
+ button.width + bleed * 2,
7999
+ button.height + bleed * 2,
8000
+ button.pixelDensity
8001
+ );
8002
+ const label = resolveText(
8003
+ {
8004
+ fontSize: button.fontSize,
8005
+ fontWeight: button.fontWeight,
8006
+ color: button.textColor,
8007
+ align: "center",
8008
+ valign: "middle",
8009
+ lineHeight: 1.2,
8010
+ padding: 0.01
8011
+ },
8012
+ button.style,
8013
+ tokens
8014
+ );
8015
+ const family = familyOf(label.font);
8016
+ const icon = button.icon ? resolveToken(button.icon, tokens) : null;
7435
8017
  const signature = buttonSignature(
7436
8018
  button.width,
7437
8019
  button.height,
7438
8020
  size,
7439
8021
  chrome,
7440
8022
  button.label,
7441
- label
8023
+ label,
8024
+ family,
8025
+ [imagesLoaded(chrome), icon, icon ? !!uiImage(icon) : false]
8026
+ );
8027
+ this.rebuildIfChanged(
8028
+ world,
8029
+ entityId,
8030
+ signature,
8031
+ button,
8032
+ bleed,
8033
+ size,
8034
+ (ctx) => {
8035
+ const box = layoutBox(button, bleed, size.density);
8036
+ drawChrome(ctx, box, size.density, chrome, this.drawAssets);
8037
+ if (icon) {
8038
+ drawIconLabel(
8039
+ ctx,
8040
+ box,
8041
+ size.density,
8042
+ button.label,
8043
+ label,
8044
+ family,
8045
+ icon,
8046
+ button.iconPosition ?? "left",
8047
+ this.drawAssets
8048
+ );
8049
+ } else {
8050
+ drawTextBlock(
8051
+ ctx,
8052
+ box,
8053
+ size.density,
8054
+ button.label,
8055
+ label,
8056
+ family,
8057
+ this.drawAssets
8058
+ );
8059
+ }
8060
+ }
7442
8061
  );
7443
- this.rebuildIfChanged(world, entityId, signature, button.width, button.height, size, (ctx) => {
7444
- drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
7445
- drawTextBlock(ctx, size.pxWidth, size.pxHeight, size.density, button.label, label);
7446
- });
7447
8062
  }
7448
8063
  syncToggle(world, entityId, toggle, deltaTime) {
7449
- const size = resolveCanvasSize(toggle.width, toggle.height);
7450
- const chrome = {
7451
- backgroundColor: toggle.value ? toggle.accentColor : toggle.trackColor,
7452
- backgroundOpacity: 1,
7453
- cornerRadiusMeters: toggle.height / 2,
7454
- borderWidthMeters: 0,
7455
- borderColor: "#000000"
7456
- };
8064
+ const tokens = this.tokensFor(world, entityId);
8065
+ const accent = resolveToken(toggle.accentColor, tokens);
8066
+ const track = resolveToken(toggle.trackColor, tokens);
8067
+ const knobColor = resolveToken(toggle.knobColor, tokens);
8068
+ const size = resolveCanvasSize(
8069
+ toggle.width,
8070
+ toggle.height,
8071
+ toggle.pixelDensity
8072
+ );
8073
+ const variant = toggle.variant ?? "switch";
8074
+ if (variant !== "switch") {
8075
+ this.syncMarkToggle(world, entityId, toggle, accent, track, knobColor);
8076
+ return;
8077
+ }
8078
+ const chrome = resolveChrome(
8079
+ {
8080
+ backgroundColor: toggle.value ? accent : track,
8081
+ backgroundOpacity: 1,
8082
+ cornerRadius: toggle.height / 2,
8083
+ borderWidth: 0,
8084
+ borderColor: "#000000"
8085
+ },
8086
+ toggle.style,
8087
+ tokens
8088
+ );
7457
8089
  const signature = JSON.stringify([
7458
8090
  trackSignature(
7459
8091
  toggle.width,
7460
8092
  toggle.height,
7461
8093
  size,
7462
8094
  chrome,
7463
- toggle.accentColor,
7464
- toggle.knobColor
8095
+ accent,
8096
+ knobColor,
8097
+ variant
7465
8098
  ),
7466
8099
  toggle.value
7467
8100
  ]);
@@ -7469,18 +8102,24 @@ var _UIPanelSystem = class _UIPanelSystem {
7469
8102
  world,
7470
8103
  entityId,
7471
8104
  signature,
7472
- toggle.width,
7473
- toggle.height,
8105
+ toggle,
8106
+ 0,
7474
8107
  size,
7475
8108
  (ctx) => {
7476
- drawChrome(ctx, size.pxWidth, size.pxHeight, size.density, chrome);
8109
+ drawChrome(
8110
+ ctx,
8111
+ layoutBox(toggle, 0, size.density),
8112
+ size.density,
8113
+ chrome,
8114
+ this.drawAssets
8115
+ );
7477
8116
  }
7478
8117
  );
7479
8118
  if (!entry) return;
7480
8119
  if (!entry.knob) {
7481
8120
  entry.knob = this.makeDisc(
7482
8121
  toggle.height * 0.4,
7483
- toggle.knobColor,
8122
+ knobColor,
7484
8123
  entry.face.parent,
7485
8124
  CHILD_Z
7486
8125
  );
@@ -7492,35 +8131,95 @@ var _UIPanelSystem = class _UIPanelSystem {
7492
8131
  knob.position.x += (target - knob.position.x) * (deltaTime > 0 ? ease : 1);
7493
8132
  knob.position.z = CHILD_Z;
7494
8133
  }
8134
+ /** Checkbox and radio: a box or ring at the left of the widget's area. */
8135
+ syncMarkToggle(world, entityId, toggle, accent, track, markColor) {
8136
+ const tokens = this.tokensFor(world, entityId);
8137
+ const radio = toggle.variant === "radio";
8138
+ const size = resolveCanvasSize(
8139
+ toggle.width,
8140
+ toggle.height,
8141
+ toggle.pixelDensity
8142
+ );
8143
+ const side = toggle.height;
8144
+ const chrome = resolveChrome(
8145
+ {
8146
+ backgroundColor: toggle.value ? accent : track,
8147
+ backgroundOpacity: 1,
8148
+ cornerRadius: radio ? side / 2 : side * 0.2,
8149
+ borderWidth: 0,
8150
+ borderColor: "#000000"
8151
+ },
8152
+ toggle.style,
8153
+ tokens
8154
+ );
8155
+ const signature = JSON.stringify([
8156
+ trackSignature(
8157
+ toggle.width,
8158
+ toggle.height,
8159
+ size,
8160
+ chrome,
8161
+ accent,
8162
+ markColor,
8163
+ toggle.variant ?? null
8164
+ ),
8165
+ toggle.value
8166
+ ]);
8167
+ this.rebuildIfChanged(world, entityId, signature, toggle, 0, size, (ctx) => {
8168
+ const px = side * size.density;
8169
+ const box = { x: 0, y: 0, w: px, h: px };
8170
+ drawChrome(ctx, box, size.density, chrome, this.drawAssets);
8171
+ if (!toggle.value) return;
8172
+ if (radio) drawDot(ctx, px / 2, px / 2, px * 0.24, markColor);
8173
+ else drawCheckMark(ctx, box, markColor);
8174
+ });
8175
+ }
7495
8176
  syncSlider(world, entityId, slider) {
7496
- const size = resolveCanvasSize(slider.width, slider.height);
8177
+ const tokens = this.tokensFor(world, entityId);
8178
+ const accent = resolveToken(slider.accentColor, tokens);
8179
+ const track = resolveToken(slider.trackColor, tokens);
8180
+ const knobColor = resolveToken(slider.knobColor, tokens);
8181
+ const size = resolveCanvasSize(
8182
+ slider.width,
8183
+ slider.height,
8184
+ slider.pixelDensity
8185
+ );
8186
+ const range = Math.max(1e-6, slider.max - slider.min);
8187
+ const t = Math.min(1, Math.max(0, (slider.value - slider.min) / range));
8188
+ if (slider.variant === "progress" || slider.variant === "ring") {
8189
+ this.syncMeter(world, entityId, slider, t, accent, track, knobColor);
8190
+ return;
8191
+ }
7497
8192
  const trackThickness = slider.height * 0.32;
7498
8193
  const knobRadius = slider.height * 0.45;
7499
8194
  const usable = Math.max(1e-3, slider.width - slider.height);
7500
- const chrome = {
7501
- backgroundColor: slider.trackColor,
7502
- backgroundOpacity: 1,
7503
- cornerRadiusMeters: trackThickness / 2,
7504
- borderWidthMeters: 0,
7505
- borderColor: "#000000"
7506
- };
8195
+ const chrome = resolveChrome(
8196
+ {
8197
+ backgroundColor: track,
8198
+ backgroundOpacity: 1,
8199
+ cornerRadius: trackThickness / 2,
8200
+ borderWidth: 0,
8201
+ borderColor: "#000000"
8202
+ },
8203
+ void 0,
8204
+ null
8205
+ );
7507
8206
  const signature = trackSignature(
7508
8207
  slider.width,
7509
8208
  slider.height,
7510
8209
  size,
7511
8210
  chrome,
7512
- slider.accentColor,
7513
- slider.knobColor
8211
+ accent,
8212
+ knobColor,
8213
+ slider.variant ?? null
7514
8214
  );
7515
8215
  const entry = this.rebuildIfChanged(
7516
8216
  world,
7517
8217
  entityId,
7518
8218
  signature,
7519
- slider.width,
7520
- slider.height,
8219
+ slider,
8220
+ 0,
7521
8221
  size,
7522
8222
  (ctx) => {
7523
- ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
7524
8223
  const trackWidthM = usable + trackThickness;
7525
8224
  drawRoundedBar(
7526
8225
  ctx,
@@ -7528,7 +8227,7 @@ var _UIPanelSystem = class _UIPanelSystem {
7528
8227
  (slider.height - trackThickness) / 2 * size.density,
7529
8228
  trackWidthM * size.density,
7530
8229
  trackThickness * size.density,
7531
- slider.trackColor
8230
+ track
7532
8231
  );
7533
8232
  }
7534
8233
  );
@@ -7537,13 +8236,13 @@ var _UIPanelSystem = class _UIPanelSystem {
7537
8236
  entry.fill = this.makeQuad(
7538
8237
  1,
7539
8238
  trackThickness,
7540
- slider.accentColor,
8239
+ accent,
7541
8240
  entry.face.parent,
7542
8241
  CHILD_Z / 2
7543
8242
  );
7544
8243
  entry.cap = this.makeDisc(
7545
8244
  trackThickness / 2,
7546
- slider.accentColor,
8245
+ accent,
7547
8246
  entry.face.parent,
7548
8247
  CHILD_Z / 2,
7549
8248
  Math.PI / 2,
@@ -7554,51 +8253,124 @@ var _UIPanelSystem = class _UIPanelSystem {
7554
8253
  if (!entry.knob) {
7555
8254
  entry.knob = this.makeDisc(
7556
8255
  knobRadius,
7557
- slider.knobColor,
8256
+ knobColor,
7558
8257
  entry.face.parent,
7559
8258
  CHILD_Z
7560
8259
  );
7561
8260
  }
7562
- const range = Math.max(1e-6, slider.max - slider.min);
7563
- const t = Math.min(1, Math.max(0, (slider.value - slider.min) / range));
7564
8261
  entry.knob.position.x = -usable / 2 + t * usable;
7565
8262
  entry.knob.position.z = CHILD_Z;
7566
8263
  entry.fill.scale.x = Math.max(1e-4, t * usable);
7567
8264
  entry.fill.position.x = -usable / 2 + t * usable / 2;
7568
8265
  entry.fill.position.z = CHILD_Z / 2;
7569
8266
  }
8267
+ /**
8268
+ * Progress bars and rings. Unlike a slider these change rarely and have no
8269
+ * drag to keep smooth, so the value is drawn, in steps of half a percent.
8270
+ */
8271
+ syncMeter(world, entityId, slider, t, accent, track, knobColor) {
8272
+ const size = resolveCanvasSize(
8273
+ slider.width,
8274
+ slider.height,
8275
+ slider.pixelDensity
8276
+ );
8277
+ const shown = Math.round(t * METER_STEPS) / METER_STEPS;
8278
+ const signature = JSON.stringify([
8279
+ slider.width,
8280
+ slider.height,
8281
+ size,
8282
+ slider.variant,
8283
+ accent,
8284
+ track,
8285
+ knobColor,
8286
+ shown
8287
+ ]);
8288
+ this.rebuildIfChanged(world, entityId, signature, slider, 0, size, (ctx) => {
8289
+ const box = {
8290
+ x: 0,
8291
+ y: 0,
8292
+ w: size.pxWidth,
8293
+ h: size.pxHeight
8294
+ };
8295
+ if (slider.variant === "ring") {
8296
+ drawRing(ctx, box, shown, track, accent);
8297
+ return;
8298
+ }
8299
+ drawRoundedBar(ctx, 0, 0, box.w, box.h, track);
8300
+ if (shown <= 0) return;
8301
+ const fill = Math.max(box.h, box.w * shown);
8302
+ drawRoundedBar(ctx, 0, 0, fill, box.h, accent);
8303
+ });
8304
+ }
8305
+ /** The tokens in scope for a widget, nearest panel first. */
8306
+ tokensFor(world, entityId) {
8307
+ if (!this.anyTokens) return null;
8308
+ let merged = null;
8309
+ const visited = /* @__PURE__ */ new Set();
8310
+ let current = entityId;
8311
+ while (current !== null && !visited.has(current)) {
8312
+ visited.add(current);
8313
+ const tokens = world.getComponent(current, UIPanelComponent)?.tokens;
8314
+ if (tokens) merged = { ...tokens, ...merged ?? {} };
8315
+ const parent = world.getComponent(current, ParentId)?.value;
8316
+ current = typeof parent === "number" ? parent : null;
8317
+ }
8318
+ return merged;
8319
+ }
7570
8320
  // ── Machinery ──────────────────────────────────────────────────────────────
7571
- rebuildIfChanged(world, entityId, signature, widthM, heightM, size, draw) {
8321
+ rebuildIfChanged(world, entityId, signature, widget, bleed, size, draw, side = THREE26.FrontSide) {
7572
8322
  const root = world.getComponent(entityId, Object3DRef)?.object;
7573
8323
  if (!root) return null;
8324
+ const widthM = widget.width;
8325
+ const heightM = widget.height;
8326
+ const faceWidth = widthM + bleed * 2;
8327
+ const faceHeight = heightM + bleed * 2;
7574
8328
  let entry = this.entries.get(entityId);
8329
+ if (entry) entry.opacity = widget.style?.opacity ?? 1;
7575
8330
  if (entry && entry.signature === signature) return entry;
7576
8331
  if (entry) this.disposeMovingParts(entry);
7577
8332
  if (!entry) {
7578
8333
  const canvas2 = this.createCanvas();
7579
- const texture = new THREE19.CanvasTexture(
8334
+ const texture = new THREE26.CanvasTexture(
7580
8335
  canvas2
7581
8336
  );
7582
8337
  this.configureTexture(world, texture);
7583
- const material = new THREE19.MeshBasicMaterial({
8338
+ const material = new THREE26.MeshBasicMaterial({
7584
8339
  map: texture,
7585
8340
  transparent: true,
7586
8341
  depthWrite: false,
7587
8342
  // UI colours are authored absolutes; scene tone mapping would shift
7588
8343
  // them (the classic "why is my white UI beige" bug).
7589
8344
  toneMapped: false,
7590
- side: THREE19.DoubleSide,
7591
- premultipliedAlpha: true
8345
+ side,
8346
+ premultipliedAlpha: false,
8347
+ blending: THREE26.CustomBlending,
8348
+ blendSrc: THREE26.OneFactor,
8349
+ blendDst: THREE26.OneMinusSrcAlphaFactor,
8350
+ blendSrcAlpha: THREE26.OneFactor,
8351
+ blendDstAlpha: THREE26.OneMinusSrcAlphaFactor
7592
8352
  });
7593
- const face = new THREE19.Mesh(
7594
- new THREE19.PlaneGeometry(widthM, heightM),
8353
+ const face = new THREE26.Mesh(
8354
+ new THREE26.PlaneGeometry(faceWidth, faceHeight),
7595
8355
  material
7596
8356
  );
7597
- face.renderOrder = FACE_RENDER_ORDER;
8357
+ tagPart(face, 16777215, true);
8358
+ face.raycast = () => {
8359
+ };
7598
8360
  root.add(face);
8361
+ const hit = new THREE26.Mesh(
8362
+ new THREE26.PlaneGeometry(widthM, heightM),
8363
+ new THREE26.MeshBasicMaterial({ visible: false, side })
8364
+ );
8365
+ root.add(hit);
7599
8366
  entry = {
7600
8367
  signature: "",
8368
+ root,
8369
+ width: widthM,
8370
+ height: heightM,
8371
+ opacity: widget.style?.opacity ?? 1,
7601
8372
  face,
8373
+ hit,
7602
8374
  texture,
7603
8375
  material,
7604
8376
  canvas: canvas2,
@@ -7612,23 +8384,26 @@ var _UIPanelSystem = class _UIPanelSystem {
7612
8384
  if (canvas.width !== size.pxWidth || canvas.height !== size.pxHeight) {
7613
8385
  canvas.width = size.pxWidth;
7614
8386
  canvas.height = size.pxHeight;
8387
+ if (entry.signature) entry.texture.dispose();
7615
8388
  }
7616
- const params = entry.face.geometry.parameters;
7617
- if (params.width !== widthM || params.height !== heightM) {
7618
- entry.face.geometry.dispose();
7619
- entry.face.geometry = new THREE19.PlaneGeometry(widthM, heightM);
7620
- }
8389
+ entry.width = widthM;
8390
+ entry.height = heightM;
8391
+ resizePlane(entry.face, faceWidth, faceHeight);
8392
+ resizePlane(entry.hit, widthM, heightM);
7621
8393
  const ctx = canvas.getContext("2d");
7622
- if (ctx) draw(ctx);
8394
+ if (ctx) {
8395
+ ctx.clearRect(0, 0, size.pxWidth, size.pxHeight);
8396
+ draw(ctx);
8397
+ }
7623
8398
  entry.texture.needsUpdate = true;
7624
8399
  entry.signature = signature;
7625
8400
  return entry;
7626
8401
  }
7627
8402
  configureTexture(world, texture) {
7628
- texture.colorSpace = THREE19.SRGBColorSpace;
8403
+ texture.colorSpace = THREE26.SRGBColorSpace;
7629
8404
  texture.generateMipmaps = true;
7630
- texture.minFilter = THREE19.LinearMipmapLinearFilter;
7631
- texture.magFilter = THREE19.LinearFilter;
8405
+ texture.minFilter = THREE26.LinearMipmapLinearFilter;
8406
+ texture.magFilter = THREE26.LinearFilter;
7632
8407
  texture.premultiplyAlpha = true;
7633
8408
  try {
7634
8409
  const renderer = world.getRenderer();
@@ -7639,7 +8414,7 @@ var _UIPanelSystem = class _UIPanelSystem {
7639
8414
  }
7640
8415
  makeQuad(widthM, heightM, color, parent, z) {
7641
8416
  return this.addPart(
7642
- new THREE19.PlaneGeometry(widthM, heightM),
8417
+ new THREE26.PlaneGeometry(widthM, heightM),
7643
8418
  color,
7644
8419
  parent,
7645
8420
  z
@@ -7648,24 +8423,23 @@ var _UIPanelSystem = class _UIPanelSystem {
7648
8423
  /** A filled disc (or arc segment, for the fill's rounded cap). */
7649
8424
  makeDisc(radiusM, color, parent, z, thetaStart = 0, thetaLength = Math.PI * 2) {
7650
8425
  return this.addPart(
7651
- new THREE19.CircleGeometry(radiusM, 32, thetaStart, thetaLength),
8426
+ new THREE26.CircleGeometry(radiusM, 32, thetaStart, thetaLength),
7652
8427
  color,
7653
8428
  parent,
7654
8429
  z
7655
8430
  );
7656
8431
  }
7657
8432
  addPart(geometry, color, parent, z) {
7658
- const mesh = new THREE19.Mesh(
8433
+ const mesh = new THREE26.Mesh(
7659
8434
  geometry,
7660
- new THREE19.MeshBasicMaterial({
8435
+ new THREE26.MeshBasicMaterial({
7661
8436
  color,
7662
8437
  transparent: true,
7663
8438
  depthWrite: false,
7664
- toneMapped: false,
7665
- side: THREE19.DoubleSide
8439
+ toneMapped: false
7666
8440
  })
7667
8441
  );
7668
- mesh.renderOrder = FACE_RENDER_ORDER + 1;
8442
+ tagPart(mesh, color);
7669
8443
  mesh.position.z = z;
7670
8444
  mesh.raycast = () => {
7671
8445
  };
@@ -7696,22 +8470,144 @@ var _UIPanelSystem = class _UIPanelSystem {
7696
8470
  const object = world.getComponent(entityId, Object3DRef)?.object;
7697
8471
  if (!transform || !object) return;
7698
8472
  const camera = world.getCamera();
7699
- const cam = new THREE19.Vector3();
7700
- const self = new THREE19.Vector3();
8473
+ const cam = new THREE26.Vector3();
8474
+ const self = new THREE26.Vector3();
7701
8475
  camera.getWorldPosition(cam);
7702
8476
  object.getWorldPosition(self);
7703
8477
  transform.rotation.y = Math.atan2(cam.x - self.x, cam.z - self.z);
7704
8478
  }
8479
+ /**
8480
+ * Stamp every widget's meshes with this frame's paint order. Written each
8481
+ * frame because the eye moves: the order between trees can change at any
8482
+ * time, and a plain property write costs less than checking it.
8483
+ */
8484
+ applyPaintOrder(world) {
8485
+ if (this.entries.size === 0) return;
8486
+ const camera = world.getCamera();
8487
+ camera.getWorldPosition(eyeScratch);
8488
+ const nodes = [...this.entries.keys()].map((id) => ({
8489
+ id,
8490
+ parent: nearestWidgetAncestor(world, id)
8491
+ }));
8492
+ const trees = buildPaintTrees(nodes);
8493
+ const rects = /* @__PURE__ */ new Map();
8494
+ for (const tree of trees) {
8495
+ const entry = this.entries.get(tree.rootId);
8496
+ if (entry) rects.set(tree.rootId, rootRect(tree.rootId, entry));
8497
+ }
8498
+ const slots = assignPaintOrder(trees, rects, eyeScratch);
8499
+ this.framesUntilDescendantWalk -= 1;
8500
+ const walkDescendants = this.framesUntilDescendantWalk <= 0;
8501
+ if (walkDescendants) {
8502
+ this.framesUntilDescendantWalk = DESCENDANT_WALK_INTERVAL;
8503
+ }
8504
+ const widgetRoots = /* @__PURE__ */ new Set();
8505
+ if (walkDescendants) {
8506
+ for (const entry of this.entries.values()) widgetRoots.add(entry.root);
8507
+ }
8508
+ for (const [entityId, entry] of this.entries) {
8509
+ const slot = slots.get(entityId);
8510
+ if (!slot) continue;
8511
+ entry.face.renderOrder = slot.face;
8512
+ for (const part of [entry.knob, entry.fill, entry.cap]) {
8513
+ if (part) part.renderOrder = slot.parts;
8514
+ }
8515
+ if (walkDescendants) stampDescendants(entry.root, slot, widgetRoots);
8516
+ }
8517
+ const effective = /* @__PURE__ */ new Map();
8518
+ const parentOf = new Map(nodes.map((node) => [node.id, node.parent]));
8519
+ for (const tree of trees) {
8520
+ for (const id of tree.order) {
8521
+ const entry = this.entries.get(id);
8522
+ if (!entry) continue;
8523
+ const parent = parentOf.get(id);
8524
+ const inherited = parent != null ? effective.get(parent) ?? 1 : 1;
8525
+ const opacity = entry.opacity * inherited;
8526
+ effective.set(id, opacity);
8527
+ for (const mesh of [entry.face, entry.knob, entry.fill, entry.cap]) {
8528
+ if (!mesh) continue;
8529
+ const tag = mesh.userData[UI_PART_KEY];
8530
+ if (tag.opacity === opacity) continue;
8531
+ tag.opacity = opacity;
8532
+ applyPartColor(mesh, tag);
8533
+ }
8534
+ }
8535
+ }
8536
+ }
7705
8537
  destroyEntry(entry) {
7706
8538
  this.disposeMovingParts(entry);
7707
8539
  entry.face.removeFromParent();
7708
8540
  entry.face.geometry.dispose();
7709
8541
  entry.face.material.dispose();
8542
+ entry.hit.removeFromParent();
8543
+ entry.hit.geometry.dispose();
8544
+ entry.hit.material.dispose();
7710
8545
  entry.texture.dispose();
7711
8546
  }
7712
8547
  };
7713
8548
  __name(_UIPanelSystem, "UIPanelSystem");
7714
8549
  var UIPanelSystem = _UIPanelSystem;
8550
+ var eyeScratch = new THREE26.Vector3();
8551
+ function tagPart(mesh, color, premultiplied = false) {
8552
+ const tag = {
8553
+ baseColor: new THREE26.Color(color),
8554
+ tint: 1,
8555
+ opacity: 1,
8556
+ premultiplied
8557
+ };
8558
+ mesh.userData[UI_PART_KEY] = tag;
8559
+ }
8560
+ __name(tagPart, "tagPart");
8561
+ function resizePlane(mesh, width, height) {
8562
+ const params = mesh.geometry.parameters;
8563
+ if (params.width === width && params.height === height) return;
8564
+ mesh.geometry.dispose();
8565
+ mesh.geometry = new THREE26.PlaneGeometry(width, height);
8566
+ }
8567
+ __name(resizePlane, "resizePlane");
8568
+ function layoutBox(widget, bleed, density) {
8569
+ return {
8570
+ x: bleed * density,
8571
+ y: bleed * density,
8572
+ w: widget.width * density,
8573
+ h: widget.height * density
8574
+ };
8575
+ }
8576
+ __name(layoutBox, "layoutBox");
8577
+ function familyOf(font) {
8578
+ return font ? uiFontFamily(font) : null;
8579
+ }
8580
+ __name(familyOf, "familyOf");
8581
+ function imagesLoaded(chrome) {
8582
+ return styleAssets(chrome, null).images.map((src) => !!uiImage(src));
8583
+ }
8584
+ __name(imagesLoaded, "imagesLoaded");
8585
+ function rootRect(id, entry) {
8586
+ const e = entry.root.matrixWorld.elements;
8587
+ const halfW = entry.width / 2;
8588
+ const halfH = entry.height / 2;
8589
+ return {
8590
+ id,
8591
+ center: { x: e[12], y: e[13], z: e[14] },
8592
+ halfX: { x: e[0] * halfW, y: e[1] * halfW, z: e[2] * halfW },
8593
+ halfY: { x: e[4] * halfH, y: e[5] * halfH, z: e[6] * halfH }
8594
+ };
8595
+ }
8596
+ __name(rootRect, "rootRect");
8597
+ function stampDescendants(root, slot, widgetRoots) {
8598
+ for (const child of root.children) {
8599
+ if (widgetRoots.has(child) || child.userData[UI_PART_KEY]) continue;
8600
+ const material = child.material;
8601
+ const transparent = Array.isArray(material) ? material.some((m) => m.transparent) : material?.transparent === true;
8602
+ const untouched = child.renderOrder === 0 || child.userData[PAINT_STAMP_KEY] === child.renderOrder;
8603
+ if (transparent && untouched) {
8604
+ child.renderOrder = slot.descendants;
8605
+ child.userData[PAINT_STAMP_KEY] = slot.descendants;
8606
+ }
8607
+ stampDescendants(child, slot, widgetRoots);
8608
+ }
8609
+ }
8610
+ __name(stampDescendants, "stampDescendants");
7715
8611
  var _XRHitTestSystem = class _XRHitTestSystem {
7716
8612
  constructor() {
7717
8613
  this.sources = /* @__PURE__ */ new Map();
@@ -7748,12 +8644,12 @@ var _XRHitTestSystem = class _XRHitTestSystem {
7748
8644
  if (!hits || hits.length === 0) continue;
7749
8645
  const pose = hits[0].getPose(referenceSpace);
7750
8646
  if (!pose) continue;
7751
- const matrix = new THREE19.Matrix4().fromArray(
8647
+ const matrix = new THREE26.Matrix4().fromArray(
7752
8648
  pose.transform.matrix
7753
8649
  );
7754
- const position = new THREE19.Vector3();
7755
- const quaternion = new THREE19.Quaternion();
7756
- matrix.decompose(position, quaternion, new THREE19.Vector3());
8650
+ const position = new THREE26.Vector3();
8651
+ const quaternion = new THREE26.Quaternion();
8652
+ matrix.decompose(position, quaternion, new THREE26.Vector3());
7757
8653
  this.results.set(key, { position, quaternion });
7758
8654
  }
7759
8655
  }
@@ -7827,8 +8723,8 @@ var _XRAnchorSystem = class _XRAnchorSystem {
7827
8723
  /** Per-anchor head-glue gates — see XREnvironmentSystem's TrackedEntry. */
7828
8724
  this.gates = /* @__PURE__ */ new Map();
7829
8725
  this.presenting = false;
7830
- this.tmpViewerMatrix = new THREE19.Matrix4();
7831
- this.tmpViewPos = new THREE19.Vector3();
8726
+ this.tmpViewerMatrix = new THREE26.Matrix4();
8727
+ this.tmpViewPos = new THREE26.Vector3();
7832
8728
  }
7833
8729
  update(world, deltaTime = 0) {
7834
8730
  const presenting = world.isXrPresenting();
@@ -7902,8 +8798,8 @@ var _XRAnchorSystem = class _XRAnchorSystem {
7902
8798
  if (typeof frame.createAnchor !== "function") return;
7903
8799
  const RigidTransform = globalThis.XRRigidTransform;
7904
8800
  if (!RigidTransform) return;
7905
- const quaternion = new THREE19.Quaternion().setFromEuler(
7906
- new THREE19.Euler(
8801
+ const quaternion = new THREE26.Quaternion().setFromEuler(
8802
+ new THREE26.Euler(
7907
8803
  transform.rotation.x,
7908
8804
  transform.rotation.y,
7909
8805
  transform.rotation.z,
@@ -7946,13 +8842,13 @@ var _XRAnchorSystem = class _XRAnchorSystem {
7946
8842
  };
7947
8843
  __name(_XRAnchorSystem, "XRAnchorSystem");
7948
8844
  var XRAnchorSystem = _XRAnchorSystem;
7949
- var _q = new THREE19.Quaternion();
7950
- var _q2 = new THREE19.Quaternion();
7951
- var _s = new THREE19.Vector3();
7952
- var _e = new THREE19.Vector3();
7953
- var _w = new THREE19.Vector3();
7954
- var _v = new THREE19.Vector3();
7955
- var _p = new THREE19.Vector3();
8845
+ var _q = new THREE26.Quaternion();
8846
+ var _q2 = new THREE26.Quaternion();
8847
+ var _s = new THREE26.Vector3();
8848
+ var _e = new THREE26.Vector3();
8849
+ var _w = new THREE26.Vector3();
8850
+ var _v = new THREE26.Vector3();
8851
+ var _p = new THREE26.Vector3();
7956
8852
  function setWorldQuaternion(o, q) {
7957
8853
  if (o.parent) {
7958
8854
  o.parent.getWorldQuaternion(_q2).invert();
@@ -7977,31 +8873,31 @@ function twoBoneIK(upper, lower, end, target, pole) {
7977
8873
  if (a < 1e-6 || b < 1e-6) return target.distanceTo(_w);
7978
8874
  const toT = _v.subVectors(target, _s);
7979
8875
  const want = toT.length();
7980
- const c = THREE19.MathUtils.clamp(want, Math.abs(a - b) + 1e-4, a + b - 1e-4);
8876
+ const c = THREE26.MathUtils.clamp(want, Math.abs(a - b) + 1e-4, a + b - 1e-4);
7981
8877
  const dir = toT.divideScalar(want || 1).clone();
7982
8878
  const pv = _p.subVectors(pole, _s);
7983
8879
  pv.addScaledVector(dir, -pv.dot(dir));
7984
8880
  if (pv.lengthSq() < 1e-8) pv.set(0, -1, 0).addScaledVector(dir, dir.y);
7985
8881
  pv.normalize();
7986
- const cosA = THREE19.MathUtils.clamp((a * a + c * c - b * b) / (2 * a * c), -1, 1);
8882
+ const cosA = THREE26.MathUtils.clamp((a * a + c * c - b * b) / (2 * a * c), -1, 1);
7987
8883
  const joint = _s.clone().addScaledVector(dir, a * cosA).addScaledVector(pv, a * Math.sqrt(1 - cosA * cosA));
7988
- turn(upper, new THREE19.Quaternion().setFromUnitVectors(_e.clone().sub(_s).normalize(), joint.clone().sub(_s).normalize()));
8884
+ turn(upper, new THREE26.Quaternion().setFromUnitVectors(_e.clone().sub(_s).normalize(), joint.clone().sub(_s).normalize()));
7989
8885
  lower.getWorldPosition(_e);
7990
8886
  end.getWorldPosition(_w);
7991
8887
  const reach = _s.clone().addScaledVector(dir, c);
7992
- turn(lower, new THREE19.Quaternion().setFromUnitVectors(_w.clone().sub(_e).normalize(), reach.clone().sub(_e).normalize()));
8888
+ turn(lower, new THREE26.Quaternion().setFromUnitVectors(_w.clone().sub(_e).normalize(), reach.clone().sub(_e).normalize()));
7993
8889
  return want - c;
7994
8890
  }
7995
8891
  __name(twoBoneIK, "twoBoneIK");
7996
- function gripQuaternion(axis, back, forearm, out = new THREE19.Quaternion()) {
8892
+ function gripQuaternion(axis, back, forearm, out = new THREE26.Quaternion()) {
7997
8893
  let X = axis.clone();
7998
8894
  const Z = back.clone().addScaledVector(X, -back.dot(X)).normalize().negate();
7999
- let Y = new THREE19.Vector3().crossVectors(Z, X);
8895
+ let Y = new THREE26.Vector3().crossVectors(Z, X);
8000
8896
  if (Y.dot(forearm) < 0) {
8001
8897
  X = X.negate();
8002
8898
  Y = Y.negate();
8003
8899
  }
8004
- return out.setFromRotationMatrix(new THREE19.Matrix4().makeBasis(X, Y, Z));
8900
+ return out.setFromRotationMatrix(new THREE26.Matrix4().makeBasis(X, Y, Z));
8005
8901
  }
8006
8902
  __name(gripQuaternion, "gripQuaternion");
8007
8903
 
@@ -8012,10 +8908,10 @@ var LIMB_BONES = {
8012
8908
  leftFoot: ["leftUpperLeg", "leftLowerLeg", "leftFoot"],
8013
8909
  rightFoot: ["rightUpperLeg", "rightLowerLeg", "rightFoot"]
8014
8910
  };
8015
- var _t = new THREE19.Vector3();
8016
- var _p2 = new THREE19.Vector3();
8017
- var _q3 = new THREE19.Quaternion();
8018
- var _pq = new THREE19.Quaternion();
8911
+ var _t = new THREE26.Vector3();
8912
+ var _p2 = new THREE26.Vector3();
8913
+ var _q3 = new THREE26.Quaternion();
8914
+ var _pq = new THREE26.Quaternion();
8019
8915
  var _IKSystem = class _IKSystem {
8020
8916
  constructor() {
8021
8917
  this.runtimes = /* @__PURE__ */ new Map();
@@ -8076,7 +8972,7 @@ var _IKSystem = class _IKSystem {
8076
8972
  const [upper, lower, end] = limb.bones;
8077
8973
  for (const bone of [upper, lower, end]) {
8078
8974
  if (!runtime.written.has(bone)) {
8079
- runtime.written.set(bone, { before: bone.quaternion.clone(), written: new THREE19.Quaternion() });
8975
+ runtime.written.set(bone, { before: bone.quaternion.clone(), written: new THREE26.Quaternion() });
8080
8976
  }
8081
8977
  }
8082
8978
  const animated = [upper.quaternion.clone(), lower.quaternion.clone(), end.quaternion.clone()];
@@ -8114,9 +9010,9 @@ var IKSystem = _IKSystem;
8114
9010
  function buildGeometry2(area) {
8115
9011
  if (area.shape === "circle") {
8116
9012
  const radius = Math.max(5e-3, area.width / 2);
8117
- return new THREE19.CircleGeometry(radius, 48);
9013
+ return new THREE26.CircleGeometry(radius, 48);
8118
9014
  }
8119
- return new THREE19.PlaneGeometry(
9015
+ return new THREE26.PlaneGeometry(
8120
9016
  Math.max(0.01, area.width),
8121
9017
  Math.max(0.01, area.depth)
8122
9018
  );
@@ -8133,13 +9029,13 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
8133
9029
  update(world) {
8134
9030
  const entityIds = world.query(VRTeleportAreaComponent, Object3DRef);
8135
9031
  const alive = new Set(entityIds);
8136
- for (const tracked of this.signatures.keys()) {
8137
- if (!alive.has(tracked)) this.signatures.delete(tracked);
9032
+ for (const tracked2 of this.signatures.keys()) {
9033
+ if (!alive.has(tracked2)) this.signatures.delete(tracked2);
8138
9034
  }
8139
9035
  for (const entityId of entityIds) {
8140
9036
  const area = world.getComponent(entityId, VRTeleportAreaComponent);
8141
9037
  const object = world.getComponent(entityId, Object3DRef)?.object;
8142
- if (!area || !(object instanceof THREE19.Mesh)) continue;
9038
+ if (!area || !(object instanceof THREE26.Mesh)) continue;
8143
9039
  const signature = signatureOf(area);
8144
9040
  if (this.signatures.get(entityId) === signature) continue;
8145
9041
  this.signatures.set(entityId, signature);
@@ -8151,18 +9047,18 @@ var _VRTeleportAreaSystem = class _VRTeleportAreaSystem {
8151
9047
  __name(_VRTeleportAreaSystem, "VRTeleportAreaSystem");
8152
9048
  var VRTeleportAreaSystem = _VRTeleportAreaSystem;
8153
9049
  function createHandMaterial(preset, options) {
8154
- const color = new THREE19.Color(options.color);
9050
+ const color = new THREE26.Color(options.color);
8155
9051
  switch (preset) {
8156
9052
  case "ghost":
8157
- return new THREE19.MeshBasicMaterial({
9053
+ return new THREE26.MeshBasicMaterial({
8158
9054
  color,
8159
9055
  transparent: true,
8160
9056
  opacity: Math.min(1, Math.max(0.05, options.opacity * 0.45)),
8161
9057
  depthWrite: false,
8162
- side: THREE19.DoubleSide
9058
+ side: THREE26.DoubleSide
8163
9059
  });
8164
9060
  case "neon":
8165
- return new THREE19.MeshStandardMaterial({
9061
+ return new THREE26.MeshStandardMaterial({
8166
9062
  color,
8167
9063
  emissive: color,
8168
9064
  emissiveIntensity: Math.max(0.5, options.emissiveIntensity || 2),
@@ -8172,7 +9068,7 @@ function createHandMaterial(preset, options) {
8172
9068
  opacity: options.opacity
8173
9069
  });
8174
9070
  case "skeleton":
8175
- return new THREE19.MeshBasicMaterial({
9071
+ return new THREE26.MeshBasicMaterial({
8176
9072
  color,
8177
9073
  wireframe: true,
8178
9074
  transparent: true,
@@ -8180,7 +9076,7 @@ function createHandMaterial(preset, options) {
8180
9076
  });
8181
9077
  case "custom":
8182
9078
  case "default":
8183
- return new THREE19.MeshStandardMaterial({
9079
+ return new THREE26.MeshStandardMaterial({
8184
9080
  color,
8185
9081
  roughness: 0.85,
8186
9082
  metalness: 0,
@@ -8188,10 +9084,10 @@ function createHandMaterial(preset, options) {
8188
9084
  emissiveIntensity: Math.max(0, options.emissiveIntensity),
8189
9085
  transparent: options.opacity < 1,
8190
9086
  opacity: options.opacity,
8191
- side: THREE19.DoubleSide
9087
+ side: THREE26.DoubleSide
8192
9088
  });
8193
9089
  default: {
8194
- return new THREE19.MeshStandardMaterial({ color });
9090
+ return new THREE26.MeshStandardMaterial({ color });
8195
9091
  }
8196
9092
  }
8197
9093
  }
@@ -8216,9 +9112,9 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
8216
9112
  constructor(options) {
8217
9113
  this.controllerModelFactory = new XRControllerModelFactory();
8218
9114
  this.handModelFactory = new XRHandModelFactory();
8219
- this.group = new THREE19.Group();
9115
+ this.group = new THREE26.Group();
8220
9116
  /** Reused so a per-frame colour compare allocates nothing. */
8221
- this.rayColorScratch = new THREE19.Color();
9117
+ this.rayColorScratch = new THREE26.Color();
8222
9118
  this.slots = /* @__PURE__ */ new Map();
8223
9119
  this.attachedTo = null;
8224
9120
  /** Last applied preset signature, so materials rebuild only on real change. */
@@ -8355,8 +9251,8 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
8355
9251
  */
8356
9252
  fitRayToHover(ray, side) {
8357
9253
  const hover = this.interactionSystem?.getHoverFor(side) ?? null;
8358
- const length = hover && Number.isFinite(hover.distance) && hover.distance > 0 ? Math.min(hover.distance, RAY_LENGTH) : RAY_LENGTH;
8359
- ray.scale.z = length;
9254
+ const length2 = hover && Number.isFinite(hover.distance) && hover.distance > 0 ? Math.min(hover.distance, RAY_LENGTH) : RAY_LENGTH;
9255
+ ray.scale.z = length2;
8360
9256
  }
8361
9257
  /** Lazily build per-slot visuals; the device reports profiles late. */
8362
9258
  ensureSlots(world) {
@@ -8405,17 +9301,17 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
8405
9301
  * without pulling in a line-rendering extension.
8406
9302
  */
8407
9303
  createRay() {
8408
- const geometry = new THREE19.CylinderGeometry(RAY_RADIUS, RAY_RADIUS, 1, 8, 1);
9304
+ const geometry = new THREE26.CylinderGeometry(RAY_RADIUS, RAY_RADIUS, 1, 8, 1);
8409
9305
  geometry.rotateX(-Math.PI / 2);
8410
9306
  geometry.translate(0, 0, -0.5);
8411
- const material = new THREE19.MeshBasicMaterial({
9307
+ const material = new THREE26.MeshBasicMaterial({
8412
9308
  color: "#4da3ff",
8413
9309
  transparent: true,
8414
9310
  opacity: 0.75,
8415
9311
  depthTest: false,
8416
9312
  depthWrite: false
8417
9313
  });
8418
- const mesh = new THREE19.Mesh(geometry, material);
9314
+ const mesh = new THREE26.Mesh(geometry, material);
8419
9315
  mesh.name = "XRPointerRay";
8420
9316
  mesh.renderOrder = 999;
8421
9317
  mesh.visible = false;
@@ -8446,14 +9342,14 @@ var _XRControllerVisualSystem = class _XRControllerVisualSystem {
8446
9342
  __name(_XRControllerVisualSystem, "XRControllerVisualSystem");
8447
9343
  var XRControllerVisualSystem = _XRControllerVisualSystem;
8448
9344
  var MARKER_RADIUS = 0.28;
8449
- var ACCENT = new THREE19.Color("#4da3ff");
8450
- var SEGMENT_AXIS = new THREE19.Vector3(0, 1, 0);
9345
+ var ACCENT = new THREE26.Color("#4da3ff");
9346
+ var SEGMENT_AXIS = new THREE26.Vector3(0, 1, 0);
8451
9347
  var HEAD_CLEARANCE = 0.25;
8452
9348
  var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8453
9349
  constructor(locomotionSystem) {
8454
- this.group = new THREE19.Group();
9350
+ this.group = new THREE26.Group();
8455
9351
  /** Holds the segment chain that draws the arc. */
8456
- this.arc = new THREE19.Group();
9352
+ this.arc = new THREE26.Group();
8457
9353
  /**
8458
9354
  * Unit cylinder along +Y, reused by every segment.
8459
9355
  *
@@ -8462,14 +9358,14 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8462
9358
  * scaled cylinders gives real thickness while rebuilding no geometry: each
8463
9359
  * frame only moves, rotates and scales meshes that already exist.
8464
9360
  */
8465
- this.segmentGeometry = new THREE19.CylinderGeometry(1, 1, 1, 6, 1);
9361
+ this.segmentGeometry = new THREE26.CylinderGeometry(1, 1, 1, 6, 1);
8466
9362
  this.segments = [];
8467
- this.marker = new THREE19.Group();
9363
+ this.marker = new THREE26.Group();
8468
9364
  this.attached = false;
8469
- this.scratchDirection = new THREE19.Vector3();
8470
- this.scratchHead = new THREE19.Vector3();
8471
- this.validColor = new THREE19.Color();
8472
- this.invalidColor = new THREE19.Color();
9365
+ this.scratchDirection = new THREE26.Vector3();
9366
+ this.scratchHead = new THREE26.Vector3();
9367
+ this.validColor = new THREE26.Color();
9368
+ this.invalidColor = new THREE26.Color();
8473
9369
  this.locomotionSystem = locomotionSystem;
8474
9370
  this.group.name = "XRTeleportVisuals";
8475
9371
  this.group.userData.ecsPreviewHidden = true;
@@ -8527,8 +9423,8 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8527
9423
  const from = curve[i];
8528
9424
  const to = curve[i + 1];
8529
9425
  this.scratchDirection.subVectors(to, from);
8530
- const length = this.scratchDirection.length();
8531
- if (length < 1e-6) {
9426
+ const length2 = this.scratchDirection.length();
9427
+ if (length2 < 1e-6) {
8532
9428
  segment.visible = false;
8533
9429
  continue;
8534
9430
  }
@@ -8536,9 +9432,9 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8536
9432
  segment.position.copy(from).addScaledVector(this.scratchDirection, 0.5);
8537
9433
  segment.quaternion.setFromUnitVectors(
8538
9434
  SEGMENT_AXIS,
8539
- this.scratchDirection.divideScalar(length)
9435
+ this.scratchDirection.divideScalar(length2)
8540
9436
  );
8541
- segment.scale.set(radius, length, radius);
9437
+ segment.scale.set(radius, length2, radius);
8542
9438
  const t = needed > 1 ? i / (needed - 1) : 0;
8543
9439
  const fade = 1 - t * 0.75;
8544
9440
  segment.material.color.copy(tint).multiplyScalar(fade);
@@ -8547,12 +9443,12 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8547
9443
  /** Grow the segment pool to cover the current arc. */
8548
9444
  ensureSegments(count) {
8549
9445
  while (this.segments.length < count) {
8550
- const material = new THREE19.MeshBasicMaterial({
9446
+ const material = new THREE26.MeshBasicMaterial({
8551
9447
  transparent: true,
8552
9448
  depthTest: false,
8553
9449
  depthWrite: false
8554
9450
  });
8555
- const mesh = new THREE19.Mesh(this.segmentGeometry, material);
9451
+ const mesh = new THREE26.Mesh(this.segmentGeometry, material);
8556
9452
  mesh.frustumCulled = false;
8557
9453
  mesh.renderOrder = 998;
8558
9454
  mesh.raycast = () => {
@@ -8576,33 +9472,33 @@ var _XRTeleportVisualSystem = class _XRTeleportVisualSystem {
8576
9472
  return world.getComponent(entityId, XRRigComponent) ?? null;
8577
9473
  }
8578
9474
  createRing() {
8579
- const geometry = new THREE19.RingGeometry(
9475
+ const geometry = new THREE26.RingGeometry(
8580
9476
  MARKER_RADIUS * 0.72,
8581
9477
  MARKER_RADIUS,
8582
9478
  48
8583
9479
  );
8584
9480
  geometry.rotateX(-Math.PI / 2);
8585
- return new THREE19.Mesh(
9481
+ return new THREE26.Mesh(
8586
9482
  geometry,
8587
- new THREE19.MeshBasicMaterial({
9483
+ new THREE26.MeshBasicMaterial({
8588
9484
  color: ACCENT,
8589
9485
  transparent: true,
8590
9486
  opacity: 0.9,
8591
- side: THREE19.DoubleSide,
9487
+ side: THREE26.DoubleSide,
8592
9488
  depthTest: false
8593
9489
  })
8594
9490
  );
8595
9491
  }
8596
9492
  createDot() {
8597
- const geometry = new THREE19.CircleGeometry(MARKER_RADIUS * 0.16, 24);
9493
+ const geometry = new THREE26.CircleGeometry(MARKER_RADIUS * 0.16, 24);
8598
9494
  geometry.rotateX(-Math.PI / 2);
8599
- return new THREE19.Mesh(
9495
+ return new THREE26.Mesh(
8600
9496
  geometry,
8601
- new THREE19.MeshBasicMaterial({
9497
+ new THREE26.MeshBasicMaterial({
8602
9498
  color: ACCENT,
8603
9499
  transparent: true,
8604
9500
  opacity: 0.55,
8605
- side: THREE19.DoubleSide,
9501
+ side: THREE26.DoubleSide,
8606
9502
  depthTest: false
8607
9503
  })
8608
9504
  );
@@ -8713,7 +9609,7 @@ function boneDirections(joints, finger) {
8713
9609
  const from = joints[chain[i - 1]];
8714
9610
  const to = joints[chain[i]];
8715
9611
  if (!from || !to) return null;
8716
- const direction = new THREE19.Vector3().subVectors(to, from);
9612
+ const direction = new THREE26.Vector3().subVectors(to, from);
8717
9613
  if (direction.lengthSq() < 1e-12) return null;
8718
9614
  directions.push(direction.normalize());
8719
9615
  }
@@ -8786,8 +9682,8 @@ function computeSpread(joints, a, b) {
8786
9682
  const bBase = joints[METACARPAL_JOINTS[b]];
8787
9683
  const bTip = joints[TIP_JOINTS[b]];
8788
9684
  if (!aBase || !aTip || !bBase || !bTip) return null;
8789
- const dirA = new THREE19.Vector3().subVectors(aTip, aBase);
8790
- const dirB = new THREE19.Vector3().subVectors(bTip, bBase);
9685
+ const dirA = new THREE26.Vector3().subVectors(aTip, aBase);
9686
+ const dirB = new THREE26.Vector3().subVectors(bTip, bBase);
8791
9687
  if (dirA.lengthSq() < 1e-12 || dirB.lengthSq() < 1e-12) return null;
8792
9688
  return clamp01((1 - dirA.normalize().dot(dirB.normalize())) / 0.45);
8793
9689
  }
@@ -8797,18 +9693,18 @@ function computePalmPose(joints, handedness) {
8797
9693
  const indexBase = joints["index-finger-metacarpal"];
8798
9694
  const pinkyBase = joints["pinky-finger-metacarpal"];
8799
9695
  if (!wrist || !indexBase || !pinkyBase) return null;
8800
- const u = new THREE19.Vector3().subVectors(indexBase, wrist);
8801
- const v = new THREE19.Vector3().subVectors(pinkyBase, wrist);
8802
- const normal = new THREE19.Vector3().crossVectors(u, v);
9696
+ const u = new THREE26.Vector3().subVectors(indexBase, wrist);
9697
+ const v = new THREE26.Vector3().subVectors(pinkyBase, wrist);
9698
+ const normal = new THREE26.Vector3().crossVectors(u, v);
8803
9699
  if (normal.lengthSq() < 1e-12) return null;
8804
9700
  normal.normalize();
8805
9701
  if (handedness === "left") normal.multiplyScalar(-1);
8806
- const right = new THREE19.Vector3().subVectors(indexBase, pinkyBase);
9702
+ const right = new THREE26.Vector3().subVectors(indexBase, pinkyBase);
8807
9703
  if (right.lengthSq() < 1e-12) return null;
8808
9704
  right.normalize();
8809
9705
  if (handedness === "left") right.multiplyScalar(-1);
8810
- const up = new THREE19.Vector3().crossVectors(normal, right).normalize();
8811
- const center = new THREE19.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
9706
+ const up = new THREE26.Vector3().crossVectors(normal, right).normalize();
9707
+ const center = new THREE26.Vector3().add(wrist).add(indexBase).add(pinkyBase).divideScalar(3);
8812
9708
  return {
8813
9709
  center,
8814
9710
  normal,
@@ -8820,17 +9716,17 @@ function computePalmPose(joints, handedness) {
8820
9716
  __name(computePalmPose, "computePalmPose");
8821
9717
  function computeHandShape(joints, handedness) {
8822
9718
  const fingers = {};
8823
- let tracked = 0;
9719
+ let tracked2 = 0;
8824
9720
  for (const finger of XR_FINGERS) {
8825
9721
  const curl = computeFingerCurl(joints, finger);
8826
9722
  if (curl) {
8827
9723
  fingers[finger] = curl;
8828
- tracked += 1;
9724
+ tracked2 += 1;
8829
9725
  } else {
8830
9726
  fingers[finger] = { baseCurl: 0, tipCurl: 0, curl: 0 };
8831
9727
  }
8832
9728
  }
8833
- if (tracked === 0) return null;
9729
+ if (tracked2 === 0) return null;
8834
9730
  const pinch = {};
8835
9731
  for (const finger of PINCH_FINGERS) {
8836
9732
  pinch[finger] = computePinch(joints, finger) ?? 0;
@@ -8839,7 +9735,7 @@ function computeHandShape(joints, handedness) {
8839
9735
  const thumbTip = joints["thumb-tip"];
8840
9736
  let thumbDirection = null;
8841
9737
  if (thumbBase && thumbTip) {
8842
- const direction = new THREE19.Vector3().subVectors(thumbTip, thumbBase);
9738
+ const direction = new THREE26.Vector3().subVectors(thumbTip, thumbBase);
8843
9739
  if (direction.lengthSq() > 1e-12) thumbDirection = direction.normalize();
8844
9740
  }
8845
9741
  const fingerDirections = {};
@@ -8850,7 +9746,7 @@ function computeHandShape(joints, handedness) {
8850
9746
  fingerDirections[finger] = null;
8851
9747
  continue;
8852
9748
  }
8853
- const direction = new THREE19.Vector3().subVectors(tip, base);
9749
+ const direction = new THREE26.Vector3().subVectors(tip, base);
8854
9750
  fingerDirections[finger] = direction.lengthSq() > 1e-12 ? direction.normalize() : null;
8855
9751
  }
8856
9752
  return {
@@ -8884,12 +9780,12 @@ function scoreAgainst(actual, target, tolerance) {
8884
9780
  __name(scoreAgainst, "scoreAgainst");
8885
9781
  var DEFAULT_DIRECTION_TOLERANCE = 45;
8886
9782
  var WORLD_DIRECTIONS = {
8887
- up: new THREE19.Vector3(0, 1, 0),
8888
- down: new THREE19.Vector3(0, -1, 0),
8889
- forward: new THREE19.Vector3(0, 0, -1),
8890
- back: new THREE19.Vector3(0, 0, 1),
8891
- left: new THREE19.Vector3(-1, 0, 0),
8892
- right: new THREE19.Vector3(1, 0, 0)
9783
+ up: new THREE26.Vector3(0, 1, 0),
9784
+ down: new THREE26.Vector3(0, -1, 0),
9785
+ forward: new THREE26.Vector3(0, 0, -1),
9786
+ back: new THREE26.Vector3(0, 0, 1),
9787
+ left: new THREE26.Vector3(-1, 0, 0),
9788
+ right: new THREE26.Vector3(1, 0, 0)
8893
9789
  };
8894
9790
  function axisVector(shape, axis) {
8895
9791
  if (axis === "thumb") return shape.thumbDirection;
@@ -8904,7 +9800,7 @@ function resolveDirection(direction, shape, context) {
8904
9800
  const head = context?.headPosition;
8905
9801
  const origin = shape.palm?.center;
8906
9802
  if (!head || !origin) return null;
8907
- const toHead = new THREE19.Vector3().subVectors(head, origin);
9803
+ const toHead = new THREE26.Vector3().subVectors(head, origin);
8908
9804
  if (toHead.lengthSq() < 1e-12) return null;
8909
9805
  toHead.normalize();
8910
9806
  return direction === "toward-head" ? toHead : toHead.multiplyScalar(-1);
@@ -8916,8 +9812,8 @@ function scoreDirections(actual, accepted, shape, toleranceDegrees, context) {
8916
9812
  for (const direction of accepted) {
8917
9813
  const wanted = resolveDirection(direction, shape, context);
8918
9814
  if (!wanted) continue;
8919
- const angle = THREE19.MathUtils.radToDeg(
8920
- Math.acos(THREE19.MathUtils.clamp(actual.dot(wanted), -1, 1))
9815
+ const angle = THREE26.MathUtils.radToDeg(
9816
+ Math.acos(THREE26.MathUtils.clamp(actual.dot(wanted), -1, 1))
8921
9817
  );
8922
9818
  const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
8923
9819
  const score = clamp01(1 - normalizedError / MATCH_FALLOFF);
@@ -8982,8 +9878,8 @@ function matchHandPose(shape, definition, context) {
8982
9878
  if (!handAxis || !wanted) {
8983
9879
  consider(0, "orientation");
8984
9880
  } else {
8985
- const angle = THREE19.MathUtils.radToDeg(
8986
- Math.acos(THREE19.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
9881
+ const angle = THREE26.MathUtils.radToDeg(
9882
+ Math.acos(THREE26.MathUtils.clamp(handAxis.dot(wanted), -1, 1))
8987
9883
  );
8988
9884
  const normalizedError = Math.max(0, angle - toleranceDegrees) / 90;
8989
9885
  consider(clamp01(1 - normalizedError / MATCH_FALLOFF), "orientation");
@@ -9061,7 +9957,7 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
9061
9957
  this.trackers = /* @__PURE__ */ new Map();
9062
9958
  this.shapes = /* @__PURE__ */ new Map();
9063
9959
  /** Reused so head-relative orientation checks allocate nothing per frame. */
9064
- this.headPosition = new THREE19.Vector3();
9960
+ this.headPosition = new THREE26.Vector3();
9065
9961
  this.elapsed = 0;
9066
9962
  this.warnedNoHands = false;
9067
9963
  this.noHandSeconds = 0;
@@ -9077,8 +9973,8 @@ var _XRHandPoseSystem = class _XRHandPoseSystem {
9077
9973
  const entries = this.collectEntries(world);
9078
9974
  if (entries.length === 0) return;
9079
9975
  if (!this.warnedNoHands) {
9080
- const tracked = this.shapes.get("left") !== null || this.shapes.get("right") !== null;
9081
- if (tracked) {
9976
+ const tracked2 = this.shapes.get("left") !== null || this.shapes.get("right") !== null;
9977
+ if (tracked2) {
9082
9978
  this.warnedNoHands = true;
9083
9979
  } else {
9084
9980
  this.noHandSeconds += deltaTime;
@@ -9224,6 +10120,7 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9224
10120
  const interactable = world.getComponent(entityId, XRInteractableComponent);
9225
10121
  if (!interactable?.enabled) continue;
9226
10122
  if (interactable.hoverBehavior === "none") continue;
10123
+ if (isUiWidget(world, entityId)) continue;
9227
10124
  this.begin(world, entityId, interactable);
9228
10125
  }
9229
10126
  for (const [entityId, record] of this.records) {
@@ -9244,7 +10141,7 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9244
10141
  const wantsColor = interactable.hoverBehavior === "color" || interactable.hoverBehavior === "both";
9245
10142
  this.records.set(entityId, {
9246
10143
  shell: wantsColor ? this.addShell(world, entityId, interactable) : null,
9247
- baseScale: new THREE19.Vector3(
10144
+ baseScale: new THREE26.Vector3(
9248
10145
  transform.scale.x,
9249
10146
  transform.scale.y,
9250
10147
  transform.scale.z
@@ -9257,27 +10154,27 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9257
10154
  addShell(world, entityId, interactable) {
9258
10155
  const object = world.getComponent(entityId, Object3DRef)?.object;
9259
10156
  if (!object) return null;
9260
- const material = new THREE19.MeshBasicMaterial({
9261
- color: new THREE19.Color(interactable.hoverColor),
10157
+ const material = new THREE26.MeshBasicMaterial({
10158
+ color: new THREE26.Color(interactable.hoverColor),
9262
10159
  // Back faces only, so the shell shows just outside the silhouette instead
9263
10160
  // of covering the object.
9264
- side: THREE19.BackSide,
10161
+ side: THREE26.BackSide,
9265
10162
  transparent: true,
9266
10163
  opacity: 0.85,
9267
10164
  depthWrite: false
9268
10165
  });
9269
- const group = new THREE19.Group();
10166
+ const group = new THREE26.Group();
9270
10167
  group.name = "XRHoverShell";
9271
10168
  group.userData[XR_HOVER_SHELL_FLAG] = true;
9272
10169
  group.renderOrder = 998;
9273
10170
  object.traverse((child) => {
9274
- if (!(child instanceof THREE19.Mesh) || !child.geometry) return;
10171
+ if (!(child instanceof THREE26.Mesh) || !child.geometry) return;
9275
10172
  if (child.userData[XR_HOVER_SHELL_FLAG]) return;
9276
- const clone = new THREE19.Mesh(child.geometry, material);
10173
+ const clone = new THREE26.Mesh(child.geometry, material);
9277
10174
  child.updateWorldMatrix(true, false);
9278
10175
  object.updateWorldMatrix(true, false);
9279
10176
  clone.matrixAutoUpdate = false;
9280
- clone.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE19.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
10177
+ clone.matrix.copy(object.matrixWorld).invert().multiply(child.matrixWorld).scale(new THREE26.Vector3(SHELL_INFLATE, SHELL_INFLATE, SHELL_INFLATE));
9281
10178
  clone.userData[XR_HOVER_SHELL_FLAG] = true;
9282
10179
  clone.raycast = () => {
9283
10180
  };
@@ -9326,7 +10223,7 @@ var _XRHoverVisualSystem = class _XRHoverVisualSystem {
9326
10223
  if (!shell) return;
9327
10224
  shell.parent?.remove(shell);
9328
10225
  shell.traverse((child) => {
9329
- if (child instanceof THREE19.Mesh) {
10226
+ if (child instanceof THREE26.Mesh) {
9330
10227
  const material = child.material;
9331
10228
  if (Array.isArray(material)) {
9332
10229
  for (const entry of material) entry.dispose();
@@ -9352,5 +10249,5 @@ __name(_XRHoverVisualSystem, "XRHoverVisualSystem");
9352
10249
  var XRHoverVisualSystem = _XRHoverVisualSystem;
9353
10250
 
9354
10251
  export { ARC_GRAVITY, ARC_MAX_TIME, ARC_SPEED, ARC_STEPS, AnimationSystem, AudioMixer, AudioSystem, CURLED_COS, CameraSyncSystem, DEFAULT_DIRECTION_TOLERANCE, DEFAULT_ENTER_CONFIDENCE, DEFAULT_EXIT_CONFIDENCE, DEFAULT_LOST_GRACE_SECONDS, DEFAULT_MIN_HOLD_SECONDS, DEFAULT_SCRIPT_ERROR_RETRY_DELAY, EnvironmentSyncSystem, FALLBACK_HAND_SCALE, HandPoseTracker, IKSystem, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MATCH_FALLOFF, MAX_PLAUSIBLE_SPEED, MAX_SAMPLE_AGE_SECONDS, MaterialSyncSystem, MeshGeometrySystem, NO_XR_CAPABILITIES, ObjectVisibilitySystem, PINCH_FINGERS, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, STRAIGHT_COS, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, SlotChooser, TeleportAimGate, ThrowVelocityTracker, TransformSyncSystem, UIInteractionSystem, UIPanelSystem, VELOCITY_SAMPLES, VRTeleportAreaSystem, VideoSystem, XRAnchorSystem, XRControllerVisualSystem, XREnvironmentSystem, XRHandPoseSystem, XRHitTestSystem, XRHoverVisualSystem, XRInputSystem, XRInteractionSystem, XRLocomotionSystem, XRSessionSystem, XRTeleportVisualSystem, XR_FINGERS, XR_HOVER_SHELL_FLAG, angularVelocityFrom, applyHandMaterial, arcGroundCrossing, computeFingerCurl, computeHandShape, computePalmPose, computePalmWidth, computePinch, computeSpread, createHandMaterial, estimateHandScale, gripQuaternion, matchHandPose, readXrCapabilities, sampleTeleportArc, setWorldQuaternion, soundBankFiles, twoBoneIK };
9355
- //# sourceMappingURL=chunk-JIJQFW6P.js.map
9356
- //# sourceMappingURL=chunk-JIJQFW6P.js.map
10252
+ //# sourceMappingURL=chunk-VYIWOXAY.js.map
10253
+ //# sourceMappingURL=chunk-VYIWOXAY.js.map