@relu-interactives/spatial-ecs 0.4.5 → 0.5.1

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 (47) hide show
  1. package/dist/{System-ln4Q7Eot.d.cts → System-V_mv07le.d.cts} +2 -2
  2. package/dist/{System-ln4Q7Eot.d.ts → System-V_mv07le.d.ts} +2 -2
  3. package/dist/{XRHandPose-CxPv-LBo.d.cts → XRHandPose-CQTLGxFC.d.cts} +218 -2
  4. package/dist/{XRHandPose-BImIa4ZS.d.ts → XRHandPose-CU3vNFuS.d.ts} +218 -2
  5. package/dist/{chunk-4AK4I3QF.js → chunk-BUEKOSR5.js} +102 -3
  6. package/dist/chunk-BUEKOSR5.js.map +1 -0
  7. package/dist/{chunk-QIIWFP3V.js → chunk-Q52ADLF7.js} +1255 -18
  8. package/dist/chunk-Q52ADLF7.js.map +1 -0
  9. package/dist/{chunk-SGADE55G.js → chunk-RXBLOO5B.js} +249 -90
  10. package/dist/chunk-RXBLOO5B.js.map +1 -0
  11. package/dist/{chunk-4DWZK4X4.js → chunk-WJD2VKEH.js} +3 -3
  12. package/dist/chunk-WJD2VKEH.js.map +1 -0
  13. package/dist/components/index.cjs +99 -0
  14. package/dist/components/index.cjs.map +1 -1
  15. package/dist/components/index.d.cts +3 -3
  16. package/dist/components/index.d.ts +3 -3
  17. package/dist/components/index.js +1 -1
  18. package/dist/docs/guide/spatial-ui.md +165 -0
  19. package/dist/factories/index.cjs +372 -114
  20. package/dist/factories/index.cjs.map +1 -1
  21. package/dist/factories/index.d.cts +13 -3
  22. package/dist/factories/index.d.ts +13 -3
  23. package/dist/factories/index.js +3 -3
  24. package/dist/{index-CuGqxuk-.d.ts → index-C3ik3sA1.d.ts} +16 -3
  25. package/dist/{index-BphKHaV-.d.ts → index-D2Jt_JzH.d.ts} +156 -3
  26. package/dist/{index-CH7qMsQT.d.cts → index-DaG03vyN.d.cts} +156 -3
  27. package/dist/{index-CQsHUez3.d.cts → index-Dlr-7-4m.d.cts} +16 -3
  28. package/dist/index.cjs +1864 -360
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +42 -9
  31. package/dist/index.d.ts +42 -9
  32. package/dist/index.js +10 -8
  33. package/dist/index.js.map +1 -1
  34. package/dist/systems/index.cjs +1346 -13
  35. package/dist/systems/index.cjs.map +1 -1
  36. package/dist/systems/index.d.cts +2 -2
  37. package/dist/systems/index.d.ts +2 -2
  38. package/dist/systems/index.js +3 -3
  39. package/dist/types.d.cts +1 -1
  40. package/dist/types.d.ts +1 -1
  41. package/dist/worldSnapshotUtils.d.cts +1 -1
  42. package/dist/worldSnapshotUtils.d.ts +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-4AK4I3QF.js.map +0 -1
  45. package/dist/chunk-4DWZK4X4.js.map +0 -1
  46. package/dist/chunk-QIIWFP3V.js.map +0 -1
  47. package/dist/chunk-SGADE55G.js.map +0 -1
@@ -1,6 +1,6 @@
1
- export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_POSE_CURLS, n as DEFAULT_POSE_DIRECTIONS, o as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, p as EntityType, I as ImageComponent, q as ImageTarget, r as ImageTargetAnchor, s as ImageTargetAnchorOptions, t as ImageTargetReference, u as InstanceSlotComponent, v as InstanceTransform, w as InstancedMeshComponent, x as InstancedMeshKind, L as LightComponent, y as LightValueState, M as MaterialComponent, z as MaterialTypeValue, B as MaterialValueState, F as MeshComponent, G as MeshGeometryComponent, H as MeshGeometryParameters, J as MeshGeometryParametersByKind, K as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, Q as PhysicsState, R as PlaneMeshParameters, S as RadiusHeightMeshParameters, T as RadiusMeshParameters, U as RigidbodyState, V as RigidbodyType, W as ScriptComponent, X as ScriptEntry, Y as ScriptObjectComponent, Z as ScriptObjectComponentInput, _ as ScriptObjectRuntimeState, $ as Selectable, a0 as SelectionTransformState, a1 as SpriteComponent, a2 as TextureFilteringValue, a3 as TransformModeValue, a4 as TransformSpaceValue, a5 as VRTeleportAreaComponent, a6 as VRTeleportAreaOptions, a7 as VideoComponent, a8 as VrTeleportShape, a9 as XRAnchorComponent, aa as XRAnchorOptions, ab as XRHandPoseComponent, ac as XRHandPoseOptions, ad as XRInteractableComponent, ae as XRInteractableOptions, af as XRInteractorComponent, ag as XRInteractorOptions, ah as XRInteractorSideComponent, ai as XRRigComponent, aj as XRRigOptions, ak as XrAttachPoint, al as XrDistanceGrabBehavior, am as XrEnvironmentPhysics, an as XrGrabGesture, ao as XrGrabHands, ap as XrGrabReach, aq as XrHandPoseEntry, ar as XrHandPreset, as as XrInteractorSide, at as XrInteractorType, au as XrLocomotionMode, av as XrTrackingOrigin, aw as XrTwoHandRotation, ax as createAttachPoint, ay as createHandPoseEntry, az as defaultMaterialValue, aA as handPoseDefinitionFor, aB as normalizePhysicsState, aC as setPhysicsLiveRef } from '../XRHandPose-CxPv-LBo.cjs';
2
- export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-ln4Q7Eot.cjs';
3
- export { P as PrefabInstance, a as PrefabNode, X as XREnvironmentComponent, b as XrEnvironmentKind, c as XrEnvironmentOrientation } from '../index-CQsHUez3.cjs';
1
+ export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_POSE_CURLS, n as DEFAULT_POSE_DIRECTIONS, o as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, p as EntityType, I as ImageComponent, q as ImageTarget, r as ImageTargetAnchor, s as ImageTargetAnchorOptions, t as ImageTargetReference, u as InstanceSlotComponent, v as InstanceTransform, w as InstancedMeshComponent, x as InstancedMeshKind, L as LightComponent, y as LightValueState, M as MaterialComponent, z as MaterialTypeValue, B as MaterialValueState, F as MeshComponent, G as MeshGeometryComponent, H as MeshGeometryParameters, J as MeshGeometryParametersByKind, K as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, Q as PhysicsState, R as PlaneMeshParameters, S as RadiusHeightMeshParameters, T as RadiusMeshParameters, U as RigidbodyState, V as RigidbodyType, W as ScriptComponent, X as ScriptEntry, Y as ScriptObjectComponent, Z as ScriptObjectComponentInput, _ as ScriptObjectRuntimeState, $ as Selectable, a0 as SelectionTransformState, a1 as SpriteComponent, a2 as TextureFilteringValue, a3 as TransformModeValue, a4 as TransformSpaceValue, a5 as UIButtonComponent, a6 as UIButtonOptions, a7 as UIPanelComponent, a8 as UIPanelOptions, a9 as UISliderComponent, aa as UISliderOptions, ab as UITextComponent, ac as UITextOptions, ad as UIToggleComponent, ae as UIToggleOptions, af as UiFollowMode, ag as UiTextAlign, ah as UiTextVAlign, ai as VRTeleportAreaComponent, aj as VRTeleportAreaOptions, ak as VideoComponent, al as VrTeleportShape, am as XRAnchorComponent, an as XRAnchorOptions, ao as XRHandPoseComponent, ap as XRHandPoseOptions, aq as XRInteractableComponent, ar as XRInteractableOptions, as as XRInteractorComponent, at as XRInteractorOptions, au as XRInteractorSideComponent, av as XRRigComponent, aw as XRRigOptions, ax as XrAttachPoint, ay as XrDistanceGrabBehavior, az as XrEnvironmentPhysics, aA as XrGrabGesture, aB as XrGrabHands, aC as XrGrabReach, aD as XrHandPoseEntry, aE as XrHandPreset, aF as XrInteractorSide, aG as XrInteractorType, aH as XrLocomotionMode, aI as XrTrackingOrigin, aJ as XrTwoHandRotation, aK as createAttachPoint, aL as createHandPoseEntry, aM as defaultMaterialValue, aN as handPoseDefinitionFor, aO as normalizePhysicsState, aP as setPhysicsLiveRef } from '../XRHandPose-CQTLGxFC.cjs';
2
+ export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-V_mv07le.cjs';
3
+ export { P as PrefabInstance, a as PrefabNode, X as XREnvironmentComponent, b as XrEnvironmentKind, c as XrEnvironmentOrientation } from '../index-Dlr-7-4m.cjs';
4
4
  import 'three';
5
5
  import '@dimforge/rapier3d-compat';
6
6
  import '../handPose-DlcTtqgx.cjs';
@@ -1,6 +1,6 @@
1
- export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_POSE_CURLS, n as DEFAULT_POSE_DIRECTIONS, o as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, p as EntityType, I as ImageComponent, q as ImageTarget, r as ImageTargetAnchor, s as ImageTargetAnchorOptions, t as ImageTargetReference, u as InstanceSlotComponent, v as InstanceTransform, w as InstancedMeshComponent, x as InstancedMeshKind, L as LightComponent, y as LightValueState, M as MaterialComponent, z as MaterialTypeValue, B as MaterialValueState, F as MeshComponent, G as MeshGeometryComponent, H as MeshGeometryParameters, J as MeshGeometryParametersByKind, K as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, Q as PhysicsState, R as PlaneMeshParameters, S as RadiusHeightMeshParameters, T as RadiusMeshParameters, U as RigidbodyState, V as RigidbodyType, W as ScriptComponent, X as ScriptEntry, Y as ScriptObjectComponent, Z as ScriptObjectComponentInput, _ as ScriptObjectRuntimeState, $ as Selectable, a0 as SelectionTransformState, a1 as SpriteComponent, a2 as TextureFilteringValue, a3 as TransformModeValue, a4 as TransformSpaceValue, a5 as VRTeleportAreaComponent, a6 as VRTeleportAreaOptions, a7 as VideoComponent, a8 as VrTeleportShape, a9 as XRAnchorComponent, aa as XRAnchorOptions, ab as XRHandPoseComponent, ac as XRHandPoseOptions, ad as XRInteractableComponent, ae as XRInteractableOptions, af as XRInteractorComponent, ag as XRInteractorOptions, ah as XRInteractorSideComponent, ai as XRRigComponent, aj as XRRigOptions, ak as XrAttachPoint, al as XrDistanceGrabBehavior, am as XrEnvironmentPhysics, an as XrGrabGesture, ao as XrGrabHands, ap as XrGrabReach, aq as XrHandPoseEntry, ar as XrHandPreset, as as XrInteractorSide, at as XrInteractorType, au as XrLocomotionMode, av as XrTrackingOrigin, aw as XrTwoHandRotation, ax as createAttachPoint, ay as createHandPoseEntry, az as defaultMaterialValue, aA as handPoseDefinitionFor, aB as normalizePhysicsState, aC as setPhysicsLiveRef } from '../XRHandPose-BImIa4ZS.js';
2
- export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-ln4Q7Eot.js';
3
- export { P as PrefabInstance, a as PrefabNode, X as XREnvironmentComponent, b as XrEnvironmentKind, c as XrEnvironmentOrientation } from '../index-CuGqxuk-.js';
1
+ export { A as AnimationComponent, a as AnimationComponentOptions, b as AudioComponent, C as CameraComponent, c as CameraComponentOptions, d as CameraProjection, e as CameraRuntimeType, f as ColliderShape, g as ColliderState, h as CubeMeshParameters, D as DEFAULT_COLLIDER_STATE, i as DEFAULT_INSTANCED_MESH_PARAMETERS, j as DEFAULT_INSTANCE_TRANSFORM, k as DEFAULT_MESH_GEOMETRY_PARAMETERS, l as DEFAULT_PHYSICS_STATE, m as DEFAULT_POSE_CURLS, n as DEFAULT_POSE_DIRECTIONS, o as DEFAULT_RIGIDBODY_STATE, E as EditableMeshObjectKind, p as EntityType, I as ImageComponent, q as ImageTarget, r as ImageTargetAnchor, s as ImageTargetAnchorOptions, t as ImageTargetReference, u as InstanceSlotComponent, v as InstanceTransform, w as InstancedMeshComponent, x as InstancedMeshKind, L as LightComponent, y as LightValueState, M as MaterialComponent, z as MaterialTypeValue, B as MaterialValueState, F as MeshComponent, G as MeshGeometryComponent, H as MeshGeometryParameters, J as MeshGeometryParametersByKind, K as ModelComponent, N as Name, O as Object3DRef, P as PhysicsComponent, Q as PhysicsState, R as PlaneMeshParameters, S as RadiusHeightMeshParameters, T as RadiusMeshParameters, U as RigidbodyState, V as RigidbodyType, W as ScriptComponent, X as ScriptEntry, Y as ScriptObjectComponent, Z as ScriptObjectComponentInput, _ as ScriptObjectRuntimeState, $ as Selectable, a0 as SelectionTransformState, a1 as SpriteComponent, a2 as TextureFilteringValue, a3 as TransformModeValue, a4 as TransformSpaceValue, a5 as UIButtonComponent, a6 as UIButtonOptions, a7 as UIPanelComponent, a8 as UIPanelOptions, a9 as UISliderComponent, aa as UISliderOptions, ab as UITextComponent, ac as UITextOptions, ad as UIToggleComponent, ae as UIToggleOptions, af as UiFollowMode, ag as UiTextAlign, ah as UiTextVAlign, ai as VRTeleportAreaComponent, aj as VRTeleportAreaOptions, ak as VideoComponent, al as VrTeleportShape, am as XRAnchorComponent, an as XRAnchorOptions, ao as XRHandPoseComponent, ap as XRHandPoseOptions, aq as XRInteractableComponent, ar as XRInteractableOptions, as as XRInteractorComponent, at as XRInteractorOptions, au as XRInteractorSideComponent, av as XRRigComponent, aw as XRRigOptions, ax as XrAttachPoint, ay as XrDistanceGrabBehavior, az as XrEnvironmentPhysics, aA as XrGrabGesture, aB as XrGrabHands, aC as XrGrabReach, aD as XrHandPoseEntry, aE as XrHandPreset, aF as XrInteractorSide, aG as XrInteractorType, aH as XrLocomotionMode, aI as XrTrackingOrigin, aJ as XrTwoHandRotation, aK as createAttachPoint, aL as createHandPoseEntry, aM as defaultMaterialValue, aN as handPoseDefinitionFor, aO as normalizePhysicsState, aP as setPhysicsLiveRef } from '../XRHandPose-CU3vNFuS.js';
2
+ export { D as DEFAULT_ENVIRONMENT_STATE, a as DEFAULT_POSTPROCESSING_STATE, E as Environment, b as EnvironmentFogState, c as EnvironmentFogType, d as EnvironmentGradientEffect, e as EnvironmentSkyboxState, f as EnvironmentSkyboxType, g as EnvironmentState, P as ParentId, h as Postprocessing, i as PostprocessingState, T as TransformComponent, j as TransformData, V as Vec3 } from '../System-V_mv07le.js';
3
+ export { P as PrefabInstance, a as PrefabNode, X as XREnvironmentComponent, b as XrEnvironmentKind, c as XrEnvironmentOrientation } from '../index-C3ik3sA1.js';
4
4
  import 'three';
5
5
  import '@dimforge/rapier3d-compat';
6
6
  import '../handPose-DlcTtqgx.js';
@@ -1,4 +1,4 @@
1
- export { AnimationComponent, AudioComponent, CameraComponent, DEFAULT_COLLIDER_STATE, DEFAULT_ENVIRONMENT_STATE, DEFAULT_INSTANCED_MESH_PARAMETERS, DEFAULT_INSTANCE_TRANSFORM, DEFAULT_MESH_GEOMETRY_PARAMETERS, DEFAULT_PHYSICS_STATE, DEFAULT_POSE_CURLS, DEFAULT_POSE_DIRECTIONS, DEFAULT_POSTPROCESSING_STATE, DEFAULT_RIGIDBODY_STATE, EntityType, Environment, ImageComponent, ImageTarget, ImageTargetAnchor, InstanceSlotComponent, InstancedMeshComponent, LightComponent, MaterialComponent, MeshComponent, MeshGeometryComponent, ModelComponent, Name, Object3DRef, ParentId, PhysicsComponent, Postprocessing, PrefabInstance, PrefabNode, ScriptComponent, ScriptObjectComponent, Selectable, SelectionTransformState, SpriteComponent, TransformComponent, VRTeleportAreaComponent, VideoComponent, XRAnchorComponent, XREnvironmentComponent, XRHandPoseComponent, XRInteractableComponent, XRInteractorComponent, XRInteractorSideComponent, XRRigComponent, createAttachPoint, createHandPoseEntry, defaultMaterialValue, handPoseDefinitionFor, normalizePhysicsState, setPhysicsLiveRef } from '../chunk-4AK4I3QF.js';
1
+ export { AnimationComponent, AudioComponent, CameraComponent, DEFAULT_COLLIDER_STATE, DEFAULT_ENVIRONMENT_STATE, DEFAULT_INSTANCED_MESH_PARAMETERS, DEFAULT_INSTANCE_TRANSFORM, DEFAULT_MESH_GEOMETRY_PARAMETERS, DEFAULT_PHYSICS_STATE, DEFAULT_POSE_CURLS, DEFAULT_POSE_DIRECTIONS, DEFAULT_POSTPROCESSING_STATE, DEFAULT_RIGIDBODY_STATE, EntityType, Environment, ImageComponent, ImageTarget, ImageTargetAnchor, InstanceSlotComponent, InstancedMeshComponent, LightComponent, MaterialComponent, MeshComponent, MeshGeometryComponent, ModelComponent, Name, Object3DRef, ParentId, PhysicsComponent, Postprocessing, PrefabInstance, PrefabNode, ScriptComponent, ScriptObjectComponent, Selectable, SelectionTransformState, SpriteComponent, TransformComponent, UIButtonComponent, UIPanelComponent, UISliderComponent, UITextComponent, UIToggleComponent, VRTeleportAreaComponent, VideoComponent, XRAnchorComponent, XREnvironmentComponent, XRHandPoseComponent, XRInteractableComponent, XRInteractorComponent, XRInteractorSideComponent, XRRigComponent, createAttachPoint, createHandPoseEntry, defaultMaterialValue, handPoseDefinitionFor, normalizePhysicsState, setPhysicsLiveRef } from '../chunk-BUEKOSR5.js';
2
2
  import '../chunk-OY7ZODZP.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,165 @@
1
+ # Spatial UI
2
+
3
+ Menus, HUDs, labels and settings panels that live **in the world**. UI is built
4
+ from five widget entities — **panel**, **text**, **button**, **toggle**,
5
+ **slider** — each a real entity with its own transform: move it with the gizmo,
6
+ parent it like anything else, save it with the scene.
7
+
8
+ Widgets render on both XR headsets (point with the ray or gaze, click with
9
+ trigger or pinch) and flat 3D experiences (hover and click with the mouse).
10
+ Hover and press feedback is built in — you never script your own highlight.
11
+
12
+ ## The one sizing rule
13
+
14
+ **Widget size is authored in metres on the component — never with transform
15
+ scale.** The widget's face is a texture rasterised for the authored size;
16
+ scaling the entity stretches those texels into blur. To make a panel bigger,
17
+ raise its `width`/`height` fields and it re-renders crisply.
18
+
19
+ ## Building a menu
20
+
21
+ 1. Create a **UI Panel** — the backdrop. A comfortable read is 1–2 m from the
22
+ player, centred around eye height (y ≈ 1.2–1.6).
23
+ 2. Create **Text**, **Button**, **Toggle**, **Slider** entities and parent
24
+ them under the panel. Place them with local transforms, a hair in front of
25
+ the backdrop (local z ≈ 0.01) so they draw over it.
26
+ 3. Give each interactive widget an `actionId` (empty means "use the entity's
27
+ name").
28
+ 4. Attach **one Behaviour to the panel**. UI events fire on the widget's own
29
+ entity first, then **bubble up the parent chain** — so the panel script
30
+ hears every child widget and can switch on `actionId`:
31
+
32
+ ```ts
33
+ import { Behaviour, type UiInteractionEvent } from "@relu-interactives/spatial-ecs";
34
+
35
+ export class MenuController extends Behaviour {
36
+ onUiPress(event: UiInteractionEvent) {
37
+ switch (event.actionId) {
38
+ case "start-game":
39
+ // A button under this panel was clicked.
40
+ break;
41
+ case "quit":
42
+ break;
43
+ }
44
+ }
45
+
46
+ onUiToggle(event: UiInteractionEvent) {
47
+ if (event.actionId === "music") {
48
+ const on = event.value === true;
49
+ console.log("music enabled:", on);
50
+ }
51
+ }
52
+
53
+ onUiChange(event: UiInteractionEvent) {
54
+ if (event.actionId === "volume" && typeof event.value === "number") {
55
+ console.log("volume:", event.value);
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ Bubbling — not broadcast — is what keeps two copies of the same menu prefab
62
+ independent: each panel script hears only its own children.
63
+
64
+ ## The widgets
65
+
66
+ | Widget | Component | What it is |
67
+ | --- | --- | --- |
68
+ | Panel | `UIPanelComponent` | The flat backdrop. Rounded corners, border, translucency, follow mode. |
69
+ | Text | `UITextComponent` | Wrapped, aligned text. `fontSize` is metres of glyph height. |
70
+ | Button | `UIButtonComponent` | Pressable. Fires `onUiPress` on release. |
71
+ | Toggle | `UIToggleComponent` | On/off switch. Fires `onUiToggle`; `value` persists with the scene. |
72
+ | Slider | `UISliderComponent` | Draggable value in `[min, max]`. Fires `onUiChange`. |
73
+
74
+ Images need no widget: parent an ordinary `image` entity under the panel.
75
+
76
+ ### Press semantics
77
+
78
+ A click is **press, then release while still pointing at the same widget** —
79
+ sliding off before releasing cancels, exactly like every platform's buttons.
80
+ While a press or slider drag is held, that hand cannot accidentally teleport
81
+ (suppressed the same way grabbing suppresses it).
82
+
83
+ `onUiChange` streams while a slider is dragged (only on real value changes)
84
+ and always fires once more with the final value on release.
85
+
86
+ ### Text sizing
87
+
88
+ `fontSize` is **how tall the glyphs stand in the world**, in metres —
89
+ `0.035` reads like comfortable body text at arm's length, independent of the
90
+ panel's texture density. Lines wrap automatically to the component's `width`;
91
+ overflow past `height` is clamped with an ellipsis.
92
+
93
+ ## Follow modes
94
+
95
+ - `follow: "world"` (default) — the panel stays where you put it.
96
+ - `follow: "billboard"` — the panel yaw-rotates to face the player every frame
97
+ at runtime (in the editor it holds still so you can work on it).
98
+
99
+ For a **wrist menu**, don't billboard — parent the panel under an XR
100
+ Interactor's **Left** or **Right** slot entity (the same slots used for custom
101
+ hand meshes). It then rides the hand like a watch.
102
+
103
+ ## Reading and driving widgets from scripts
104
+
105
+ Widget state is ordinary component data:
106
+
107
+ ```ts
108
+ import { Behaviour } from "@relu-interactives/spatial-ecs";
109
+ import {
110
+ UISliderComponent,
111
+ UITextComponent,
112
+ } from "@relu-interactives/spatial-ecs/components";
113
+
114
+ export class ScoreHud extends Behaviour {
115
+ private score = 0;
116
+
117
+ addPoint() {
118
+ this.score += 1;
119
+ // This entity is the HUD text; editing the component re-renders it.
120
+ const text = this.world.getComponent(this.entityId, UITextComponent);
121
+ if (text) text.text = `Score: ${this.score}`;
122
+ }
123
+
124
+ readVolume(sliderEntityId: number): number {
125
+ const slider = this.world.getComponent(sliderEntityId, UISliderComponent);
126
+ return slider ? slider.value : 0;
127
+ }
128
+ }
129
+ ```
130
+
131
+ Style/text edits re-render the widget's texture on the next frame. Toggle and
132
+ slider `value` are authored state — what you set in the editor is the state
133
+ the experience starts with.
134
+
135
+ ## Event payload
136
+
137
+ Every UI hook receives the same shape:
138
+
139
+ ```ts
140
+ import { Behaviour, type UiInteractionEvent } from "@relu-interactives/spatial-ecs";
141
+
142
+ export class Inspect extends Behaviour {
143
+ onUiPress(event: UiInteractionEvent) {
144
+ event.targetEntityId; // the widget that was activated
145
+ event.actionId; // its actionId, or its entity name
146
+ event.input; // the XR input source, or null for mouse clicks
147
+ event.value; // boolean (toggle) / number (slider); absent for presses
148
+ }
149
+
150
+ onUiHoverStart(event: UiInteractionEvent) {
151
+ // The pointer moved onto the widget (also fires for mouse hover).
152
+ console.log("hovering", event.targetEntityId);
153
+ }
154
+ }
155
+ ```
156
+
157
+ ## Performance notes
158
+
159
+ - A widget's face redraws only when its **style or content** changes — an
160
+ idle menu costs nothing per frame.
161
+ - Slider drags and toggle flips move **geometry** (the knob and fill quads),
162
+ not pixels, so dragging never re-renders the texture.
163
+ - `pixelDensity` (panel) trades texture memory for sharpness: the default
164
+ 1024 px/m is crisp at arm's length; raise it only for panels the player
165
+ inspects up close.