@realitycollective/xrblocks-uiextensions 0.1.2-preview.0 → 0.1.2-preview.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.
package/CHANGELOG.md CHANGED
@@ -8,16 +8,22 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
8
8
 
9
9
  ### Changed
10
10
 
11
+ - The "Publish to npm" workflow moves dependencies on other Reality Collective repositories before it packs. From `development` the dependency check runs `scripts/rc-dependencies.mjs --fix`: a newer version of another repository on npm, release or preview, is pinned, the lockfile is regenerated, the run installs, builds, tests and publishes against it, and the move is committed and pushed with the preview bump. Before, the check only failed the run and the move was a pull request by hand. From `main` the step still only checks, because `release.mjs prepare` has already moved every pin to the latest release.
11
12
  - Development, tests and demos run on one shared copy of Meta's `super-three` fork at 0.185.0 (root `overrides`, with `@types/three` 0.185.4), the version every WebXR repository now uses, and on `@iwsdk/core` 1.0.1. IWSDK 1.0.1 changes no runtime code from 1.0.0. XR Blocks needs three r182 or later and logged an error on the r181 copy the demos used before. Published peer ranges are unchanged.
12
13
  - `@realitycollective/iwsdk-uiextensions` - `@pmndrs/handle` floor raised to `^6.6.31`, the current release.
13
14
  - The showcase and the multiplatform lab choose their engine by asking the browser's WebXR runtime which immersive modes it can start (`probeXRSupport`, `chooseEngine(userAgent, search, xr)` in `demos/showcase/src/platform-detect.ts`). The user agent now only tells a Meta browser from any other. Before, a browser was taken for Android XR only if its user agent said so, and an XREAL Aura whose browser did not was offered the desktop pipeline.
15
+ - The showcase, the multiplatform lab and the devtools playground are Service Framework apps, on `@realitycollective/service-framework` 1.0.3-preview.1 and its three.js and IWSDK bindings. One app service, `UixAppService` in `demos/showcase/src/app-service.ts`, runs on every platform and imports only the core package. It writes the adapter's capabilities and each XR session state to the Event Log window. On the desktop pipeline the three.js `WebXRRuntimeAdapter` owns the frame loop through the renderer's `setAnimationLoop`, and the service's `render()` ticks the window host and draws. The page keeps no clock of its own any more, so three's `Clock` deprecation warning is gone. It also passes the page's visibility to the manager as focus and pause. On IWSDK the World keeps its loop, and the bridge system from `@realitycollective/service-framework-iwsdk` relays each visible frame. The UI Extensions systems there still tick from the World. On XR Blocks, which owns its own loop, the adapter has no host and is never started. The Script's `update()` drives it with `adapter.tick(performance.now())`, and the service's `render()` ticks the window host.
16
+ - `@realitycollective/threejs-uiextensions` - the README and the `connectUIExtensions` doc comment show the frame loop on three's `Timer`, which takes the animation-loop timestamp, instead of `Clock`, deprecated since r183. A plain three.js integration that owns its own loop follows that; the demos no longer own one.
14
17
 
15
18
  ### Added
16
19
 
17
- - Multiplatform lab diagnostics for framework testers (`demos/showcase/src/diagnostics.ts`), behind a hidden URL option. `?uix-log=1` records console output, errors, failed loads, WebGL context loss and every WebXR session from the first line, keeps the last three page loads on the device, and sends the log by itself 20 s after the page opens, when each session ends and when the page is left. `?uix-log=local` keeps it on the device. Without the option nothing is recorded. A Pages Function (`demos/webxr-multiplatform/functions/api/report.ts`) stores each report as one D1 row for at least 48 hours, capped per client, per day and by size; it answers 503 until a D1 database is bound. The lab README's "Diagnostics" section covers testers and maintainers.
20
+ - Multiplatform lab diagnostics for framework testers (`demos/showcase/src/diagnostics.ts`), behind a hidden URL option. `?uix-log=1` records console output, errors, failed loads, WebGL context loss and every WebXR session from the first line, keeps the last three page loads on the device, and sends the log by itself 20 s after the page opens, when each session ends and when the page is left. `?uix-log=local` keeps it on the device. Without the option nothing is recorded. Once given, the option holds for the rest of the tab's visit (session storage) and is put back in the URL after any navigation that dropped it; `?uix-log=off` ends it. A Pages Function (`demos/webxr-multiplatform/functions/api/report.ts`) stores each report as one D1 row for at least 48 hours, capped per client, per day and by size; it answers 503 until a D1 database is bound. The lab README's "Diagnostics" section covers testers and maintainers.
18
21
 
19
22
  ### Fixed
20
23
 
24
+ - `@realitycollective/xrblocks-uiextensions` - no press, click, poke or title-bar drag reached a panel on XR Blocks, with the simulator's mouse, a controller ray or a hand. The pointer bridge attached `onSelectStart`, `onObjectTouching` and `onObjectGrabStart` to every uikit object, which xrblocks 0.19 called on the hit object. xrblocks 0.21 only calls its targeted hooks on a Script (`isXRScript`, `onObjectSelectStart` and the rest; `ScriptsManager.hasTargetHandler`), so every hit fell through to the Script-level `onSelectStart`, which the lab's pipeline no longer handled. `wire` now gives each panel one `XrBlocksPanelScript` node between the panel document and the uikit root (not the root or the document, whose `update` and `dispose` xrblocks would call as hooks), takes `onObjectSelectStart`/`End` and `onObjectGrabStart`/`End` there, and drives touch from `xb.input.getFrame().directTouches` each frame, since xrblocks' own direct touch only reports inside a panel's millimetres-deep bounding box and never sees the approach the core `TouchPress` needs. The element pressed is the deepest of the coplanar elements under the bridge's own cast of the source's ray, not the object xrblocks names: uikit flags every element below a panel's root as not visible (it draws them instanced), so xrblocks' resolver skips them and names the window for a hit on a button. A click lands on release when xrblocks reports the release completed and the source's ray still rests on the element it pressed. `XrBlocksPointerBridge.updateRays` is now `updateFrame` (the old name remains, deprecated). The bridge tests drive a fake of xrblocks 0.21's resolver and prove the old per-object callbacks get nothing.
25
+ - `@realitycollective/xrblocks-uiextensions` - panel text was invisible on XR Blocks: labels, titles, input text and button captions, with only the gallery window readable by chance. uikit draws plates and glyphs as transparent meshes stacked by `renderOrder`; three's default transparent sort is by camera distance, so a plate hid its own text. The desktop pipeline calls `configureRendererForUikit` and IWSDK does the same inside its UI system; the XR Blocks pipeline never did. `connectUIExtensions` and `UixWindowHost` take `renderer` (`xb.core.renderer`) and configure it once; the lab's pipeline passes it.
26
+ - Multiplatform lab and showcase, desktop pipeline: the event-log window wrote dock and region changes with a `→`, which the bundled Inter MSDF font has no glyph for. uikit drew a missing-glyph box and warned once per line, eight times on a first desktop run. The log now writes `->`. The two `Matrix3.translate` and `.scale` deprecation warnings come from uikit 1.0.76's Image component fitting the gallery photo. They are harmless until three removes those methods, and there is nothing here to change.
21
27
  - Multiplatform lab, XR Blocks pipeline on a browser that can enter XR (Android XR, XREAL Aura): the page showed a near-empty dark view and no way in. XR Blocks skips its desktop simulator there, draws the page from its own camera at floor level, and appends an unstyled Enter XR button below the full-height canvas, off screen. The pipeline now pins the button at the bottom centre and raises the 2D view to 1.7 m, IWSDK's camera height (`demos/webxr-multiplatform/src/pipelines/xrblocks-page.ts`).
22
28
 
23
29
  ## [0.1.1] - 2026-10-01
package/README.md CHANGED
@@ -24,10 +24,10 @@ It re-exports everything from the core, so this is the only UI Extensions packag
24
24
  | Dock regions (wall/belt, slots, follow) | ✅ | ✅ `createRegion`, `manager.dockTo` (`host.dock` forwards) |
25
25
  | Desktop mouse input (hover, click, drag-to-look) | ✅ | ✅ via `@pmndrs/pointer-events` |
26
26
  | Desktop locomotion (WASD, jump, crouch, sprint) | n/a | ✅ `DesktopControls` |
27
- | Ray click on release, poke (touch), controller-tip poke | ✅ | ✅ `pointer-bridge.ts`'s `XrBlocksPointerBridge`, attached to every panel automatically - runs the core `TouchPress` and clicks on `onSelectEnd`, not on intersection |
28
- | Hover styles (`pointerenter`/`pointerleave`) | ✅ | ✅ relayed from `onHoverEnter`/`onHoverExit` |
27
+ | Ray click on release, poke (touch), controller-tip poke | ✅ | ✅ `pointer-bridge.ts`'s `XrBlocksPointerBridge`, attached to every panel automatically - clicks on `onObjectSelectEnd`, not on intersection, and runs the core `TouchPress` from `xb.input.getFrame().directTouches` |
28
+ | Hover styles (`pointerenter`/`pointerleave`) | ✅ | ✅ per source, from the bridge's own raycast of `xb.input.getFrame().raySources` |
29
29
  | Bare panels (`createPanel`) | ⬜ ECS owns the lifecycle | ✅ `supportsStandalonePanels` is `true` |
30
- | Title-bar ray drag, with a per-window `dragDelay` | ✅ (`@pmndrs/handle`) | ✅ `TitlebarDragController`, from `onSelectStart`/`onSelectEnd` on the title bar |
30
+ | Title-bar ray drag, with a per-window `dragDelay` | ✅ (`@pmndrs/handle`) | ✅ `TitlebarDragController`, from `onObjectSelectStart`/`onObjectSelectEnd` on the title bar |
31
31
  | Title-bar near grab (squeeze / pinch), starts at once | ✅ | ✅ from `onObjectGrabStart`/`onObjectGrabEnd`, gated to the title bar as on native |
32
32
  | Billboard while dragging | ✅ | ✅ `billboardWhileDragging` (default on) |
33
33
  | Drop-to-dock by dragging | ✅ | ✅ the core `RegionRegistry.capture` |
@@ -40,7 +40,9 @@ It re-exports everything from the core, so this is the only UI Extensions packag
40
40
 
41
41
  A ray drag rides the ray at a fixed grab distance, the same laser math IWSDK and native use, when `input: xb.input` is supplied to `connectUIExtensions` (or `rayInput` to the host directly) - `xb.input.getFrame()`'s `raySources` give the controller's live ray, since `SelectEvent` itself carries none. With no `rayInput` wired it falls back to the controller's own position delta instead: correct, but not laser-distance.
42
42
 
43
- Requires `xrblocks` **0.20 or later** when `input: xb.input` is supplied: the host reads rays with `xb.input.getFrame()`, and refuses an older input once, at construction, by name. Verified against `xrblocks` 0.21.1.
43
+ Requires `xrblocks` **0.21 or later**: the host reads rays and fingertips with `xb.input.getFrame()` (0.20 added it; the host refuses an older input once, at construction, by name), and presses arrive through the Script hooks 0.21 introduced, `onObjectSelectStart`/`onObjectSelectEnd` and `onObjectGrabStart`/`onObjectGrabEnd`, which 0.21 calls only on a Script. Verified against `xrblocks` 0.21.1.
44
+
45
+ Pass `renderer: xb.core.renderer` to `connectUIExtensions` (or `renderer` to the host). The host then configures it for uikit once: transparent meshes sorted by `renderOrder`, as uikit assigns, and local clipping on. XR Blocks sets neither, and without them a panel plate hides its own text.
44
46
 
45
47
  ## Required renderer setup (read this first)
46
48
 
@@ -83,7 +85,7 @@ xb.add(new MyScript());
83
85
  await xb.init();
84
86
  ```
85
87
 
86
- Nothing here imports `xrblocks` at the type level - the glue binds to plain three.js shapes (`scene: Object3D`, `camera`), so the same host works in a hand-rolled three.js WebXR app. Press, poke, drag and hover need no wiring in your own `Script`: `createWindow`/`createPanel` attach the pointer bridge to every panel automatically, driven by whichever of XR Blocks' `onSelectStart`/`onSelectEnd`, `onObjectTouchStart`/`onObjectTouching`/`onObjectTouchEnd`, `onObjectGrabStart`/`onObjectGrabEnd` and `onHoverEnter`/`onHoverExit` your scene calls.
88
+ Nothing here imports `xrblocks` at the type level - the glue binds to plain three.js shapes (`scene: Object3D`, `camera`), so the same host works in a hand-rolled three.js WebXR app. Press, poke, drag and hover need no wiring in your own `Script`: `createWindow`/`createPanel` attach the pointer bridge to every panel automatically. The bridge inserts one node per panel, `XrBlocksPanelScript`, between the panel document and the uikit root. XR Blocks 0.21 treats it as the Script for every hit inside the panel and calls `onObjectSelectStart`/`onObjectSelectEnd` and `onObjectGrabStart`/`onObjectGrabEnd` on it; rays and fingertips come from `xb.input.getFrame()` each frame through the host's `update`. XR Blocks' per-object callbacks of 0.19 (`onSelectStart` on the hit object) are no longer called by the SDK and no longer read here.
87
89
 
88
90
  ### Window options and handles
89
91
 
package/dist/host.d.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * WebXR-session raycasting bridge (`ScenePointerBridge`), which XR Blocks
14
14
  * does not need - its own interaction manager already resolves hit-testing.
15
15
  */
16
- import { UixWindowHost as ThreeJsWindowHost, type CreateRegionOptions, type CreateWindowOptions, type RegionHandle, type UixWindowHostOptions, type WindowHandle, type XrBlocksWindowHandle } from '@realitycollective/threejs-uiextensions';
16
+ import { UixWindowHost as ThreeJsWindowHost, type UikitRenderer, type CreateRegionOptions, type CreateWindowOptions, type RegionHandle, type UixWindowHostOptions, type WindowHandle, type XrBlocksWindowHandle } from '@realitycollective/threejs-uiextensions';
17
17
  import type { PointerArbiter } from '@realitycollective/webxr-input';
18
18
  export type { CreateRegionOptions, CreateWindowOptions, RegionHandle, UixWindowHostOptions, WindowHandle, XrBlocksWindowHandle };
19
19
  /** Options for the XR Blocks {@link UixWindowHost}. */
@@ -25,6 +25,16 @@ export interface XrBlocksWindowHostOptions extends UixWindowHostOptions {
25
25
  * on its own.
26
26
  */
27
27
  pointers?: PointerArbiter;
28
+ /**
29
+ * The engine's `WebGLRenderer` (`xb.core.renderer`, available from a
30
+ * Script's `init()` on). The host configures it for uikit once, at
31
+ * construction (`configureRendererForUikit`): transparent meshes sorted by
32
+ * `renderOrder`, as uikit assigns, and local clipping on. Without it three
33
+ * sorts panel plates and glyphs by camera distance and a plate hides its
34
+ * own text; XR Blocks sets neither. IWSDK does the same inside its own UI
35
+ * system. Omit it only if the app configures the renderer itself.
36
+ */
37
+ renderer?: UikitRenderer;
28
38
  }
29
39
  export declare class UixWindowHost extends ThreeJsWindowHost {
30
40
  /** The XR Blocks bridge, or `undefined` when the caller supplied its own `pointerBridgeFactory`. */
@@ -32,6 +42,6 @@ export declare class UixWindowHost extends ThreeJsWindowHost {
32
42
  /** `xb.input`, read each frame to raycast the panels along every ray source. */
33
43
  private readonly xrRayInput;
34
44
  constructor(options: XrBlocksWindowHostOptions);
35
- /** Drive per-frame from the engine loop (delta in SECONDS): the bridge raycasts this frame's rays first, so hover and the arbiter are current when the windows step. */
45
+ /** Drive per-frame from the engine loop (delta in SECONDS): the bridge reads this frame's rays and fingertips first, so hover, touch and the arbiter are current when the windows step. */
36
46
  update(deltaSeconds: number): void;
37
47
  }
package/dist/host.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * WebXR-session raycasting bridge (`ScenePointerBridge`), which XR Blocks
14
14
  * does not need - its own interaction manager already resolves hit-testing.
15
15
  */
16
- import { UixWindowHost as ThreeJsWindowHost, } from '@realitycollective/threejs-uiextensions';
16
+ import { UixWindowHost as ThreeJsWindowHost, configureRendererForUikit, } from '@realitycollective/threejs-uiextensions';
17
17
  import { XrBlocksPointerBridge } from './pointer-bridge.js';
18
18
  export class UixWindowHost extends ThreeJsWindowHost {
19
19
  /** The XR Blocks bridge, or `undefined` when the caller supplied its own `pointerBridgeFactory`. */
@@ -40,11 +40,13 @@ export class UixWindowHost extends ThreeJsWindowHost {
40
40
  });
41
41
  this.xrBridge = built.bridge;
42
42
  this.xrRayInput = options.rayInput;
43
+ if (options.renderer)
44
+ configureRendererForUikit(options.renderer);
43
45
  }
44
- /** Drive per-frame from the engine loop (delta in SECONDS): the bridge raycasts this frame's rays first, so hover and the arbiter are current when the windows step. */
46
+ /** Drive per-frame from the engine loop (delta in SECONDS): the bridge reads this frame's rays and fingertips first, so hover, touch and the arbiter are current when the windows step. */
45
47
  update(deltaSeconds) {
46
48
  if (this.xrBridge && this.xrRayInput)
47
- this.xrBridge.updateRays(this.xrRayInput.getFrame());
49
+ this.xrBridge.updateFrame(this.xrRayInput.getFrame());
48
50
  super.update(deltaSeconds);
49
51
  }
50
52
  }
package/dist/host.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,aAAa,IAAI,iBAAiB,GAOnC,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAgB5D,MAAM,OAAO,aAAc,SAAQ,iBAAiB;IAClD,oGAAoG;IACnF,QAAQ,CAAoC;IAC7D,gFAAgF;IAC/D,UAAU,CAAqC;IAEhE,YAAY,OAAkC;QAC5C,gGAAgG;QAChG,sCAAsC;QACtC,IAAI,OAAO,CAAC,QAAQ,IAAI,OAAQ,OAAO,CAAC,QAA4C,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC7G,MAAM,IAAI,KAAK,CAAC,qHAAqH,CAAC,CAAC;QACzI,CAAC;QACD,MAAM,KAAK,GAAuC,EAAE,CAAC;QACrD,KAAK,CAAC;YACJ,GAAG,OAAO;YACV,4DAA4D;YAC5D,uEAAuE;YACvE,qEAAqE;YACrE,oBAAoB,EAClB,OAAO,CAAC,oBAAoB;gBAC5B,CAAC,CAAC,MAAM,EAAE,EAAE;oBACV,KAAK,CAAC,MAAM,GAAG,IAAI,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;oBAClH,OAAO,KAAK,CAAC,MAAM,CAAC;gBACtB,CAAC,CAAC;SACL,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;IACrC,CAAC;IAED,wKAAwK;IAC/J,MAAM,CAAC,YAAoB;QAClC,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU;YAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC3F,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC7B,CAAC;CACF","sourcesContent":["/**\n * UixWindowHost - the XR Blocks binding.\n *\n * Every window/panel/region rule (spawn, chrome, follow, dock regions, hand\n * menus, drag, focus bias) lives in\n * `@realitycollective/threejs-uiextensions`'s own `UixWindowHost`, which this\n * class extends unchanged - \"one behaviour, every platform\": XR Blocks IS a\n * three.js WebXR scene, so it runs the same host as the plain three.js\n * binding. The only thing added here is XR Blocks' own interaction surface:\n * `XrBlocksPointerBridge` (`pointer-bridge.ts`) turns its select/touch/grab/\n * hover callbacks into the pointer events and touch samples the shared host\n * already knows how to apply, in place of `threejs-uiextensions`'s own\n * WebXR-session raycasting bridge (`ScenePointerBridge`), which XR Blocks\n * does not need - its own interaction manager already resolves hit-testing.\n */\nimport {\n UixWindowHost as ThreeJsWindowHost,\n type CreateRegionOptions,\n type CreateWindowOptions,\n type RegionHandle,\n type UixWindowHostOptions,\n type WindowHandle,\n type XrBlocksWindowHandle,\n} from '@realitycollective/threejs-uiextensions';\nimport type { PointerArbiter } from '@realitycollective/webxr-input';\nimport { XrBlocksPointerBridge } from './pointer-bridge.js';\nimport type { XrBlocksRayInputAccess } from './ray-input.js';\n\nexport type { CreateRegionOptions, CreateWindowOptions, RegionHandle, UixWindowHostOptions, WindowHandle, XrBlocksWindowHandle };\n\n/** Options for the XR Blocks {@link UixWindowHost}. */\nexport interface XrBlocksWindowHostOptions extends UixWindowHostOptions {\n /**\n * The app's shared pointer arbiter (the Interactions runtime's). Panels\n * then offer their touch, grab and ray candidates to it and act only with\n * a pointer kind it says owns the source. Omit it and the host arbitrates\n * on its own.\n */\n pointers?: PointerArbiter;\n}\n\nexport class UixWindowHost extends ThreeJsWindowHost {\n /** The XR Blocks bridge, or `undefined` when the caller supplied its own `pointerBridgeFactory`. */\n private readonly xrBridge: XrBlocksPointerBridge | undefined;\n /** `xb.input`, read each frame to raycast the panels along every ray source. */\n private readonly xrRayInput: XrBlocksRayInputAccess | undefined;\n\n constructor(options: XrBlocksWindowHostOptions) {\n // xrblocks added `xb.input.getFrame()` in 0.20.0. Refuse an older input here, once, rather than\n // throwing from every frame's update.\n if (options.rayInput && typeof (options.rayInput as Partial<XrBlocksRayInputAccess>).getFrame !== 'function') {\n throw new Error('@realitycollective/xrblocks-uiextensions needs xrblocks 0.20 or later: the rayInput it was handed has no getFrame()');\n }\n const built: { bridge?: XrBlocksPointerBridge } = {};\n super({\n ...options,\n // Only when the caller has not already supplied one (a bare\n // `UixWindowHost` still gets the XR Blocks bridge by default, which is\n // what makes this package's own class useful to construct directly).\n pointerBridgeFactory:\n options.pointerBridgeFactory ??\n ((isLive) => {\n built.bridge = new XrBlocksPointerBridge({ isLive, ...(options.pointers ? { pointers: options.pointers } : {}) });\n return built.bridge;\n }),\n });\n this.xrBridge = built.bridge;\n this.xrRayInput = options.rayInput;\n }\n\n /** Drive per-frame from the engine loop (delta in SECONDS): the bridge raycasts this frame's rays first, so hover and the arbiter are current when the windows step. */\n override update(deltaSeconds: number): void {\n if (this.xrBridge && this.xrRayInput) this.xrBridge.updateRays(this.xrRayInput.getFrame());\n super.update(deltaSeconds);\n }\n}\n"]}
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EACL,aAAa,IAAI,iBAAiB,EAClC,yBAAyB,GAQ1B,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AA0B5D,MAAM,OAAO,aAAc,SAAQ,iBAAiB;IAClD,oGAAoG;IACnF,QAAQ,CAAoC;IAC7D,gFAAgF;IAC/D,UAAU,CAAqC;IAEhE,YAAY,OAAkC;QAC5C,gGAAgG;QAChG,sCAAsC;QACtC,IAAI,OAAO,CAAC,QAAQ,IAAI,OAAQ,OAAO,CAAC,QAA4C,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC7G,MAAM,IAAI,KAAK,CAAC,qHAAqH,CAAC,CAAC;QACzI,CAAC;QACD,MAAM,KAAK,GAAuC,EAAE,CAAC;QACrD,KAAK,CAAC;YACJ,GAAG,OAAO;YACV,4DAA4D;YAC5D,uEAAuE;YACvE,qEAAqE;YACrE,oBAAoB,EAClB,OAAO,CAAC,oBAAoB;gBAC5B,CAAC,CAAC,MAAM,EAAE,EAAE;oBACV,KAAK,CAAC,MAAM,GAAG,IAAI,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;oBAClH,OAAO,KAAK,CAAC,MAAM,CAAC;gBACtB,CAAC,CAAC;SACL,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;QACnC,IAAI,OAAO,CAAC,QAAQ;YAAE,yBAAyB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpE,CAAC;IAED,2LAA2L;IAClL,MAAM,CAAC,YAAoB;QAClC,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU;YAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC5F,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC7B,CAAC;CACF","sourcesContent":["/**\n * UixWindowHost - the XR Blocks binding.\n *\n * Every window/panel/region rule (spawn, chrome, follow, dock regions, hand\n * menus, drag, focus bias) lives in\n * `@realitycollective/threejs-uiextensions`'s own `UixWindowHost`, which this\n * class extends unchanged - \"one behaviour, every platform\": XR Blocks IS a\n * three.js WebXR scene, so it runs the same host as the plain three.js\n * binding. The only thing added here is XR Blocks' own interaction surface:\n * `XrBlocksPointerBridge` (`pointer-bridge.ts`) turns its select/touch/grab/\n * hover callbacks into the pointer events and touch samples the shared host\n * already knows how to apply, in place of `threejs-uiextensions`'s own\n * WebXR-session raycasting bridge (`ScenePointerBridge`), which XR Blocks\n * does not need - its own interaction manager already resolves hit-testing.\n */\nimport {\n UixWindowHost as ThreeJsWindowHost,\n configureRendererForUikit,\n type UikitRenderer,\n type CreateRegionOptions,\n type CreateWindowOptions,\n type RegionHandle,\n type UixWindowHostOptions,\n type WindowHandle,\n type XrBlocksWindowHandle,\n} from '@realitycollective/threejs-uiextensions';\nimport type { PointerArbiter } from '@realitycollective/webxr-input';\nimport { XrBlocksPointerBridge } from './pointer-bridge.js';\nimport type { XrBlocksRayInputAccess } from './ray-input.js';\n\nexport type { CreateRegionOptions, CreateWindowOptions, RegionHandle, UixWindowHostOptions, WindowHandle, XrBlocksWindowHandle };\n\n/** Options for the XR Blocks {@link UixWindowHost}. */\nexport interface XrBlocksWindowHostOptions extends UixWindowHostOptions {\n /**\n * The app's shared pointer arbiter (the Interactions runtime's). Panels\n * then offer their touch, grab and ray candidates to it and act only with\n * a pointer kind it says owns the source. Omit it and the host arbitrates\n * on its own.\n */\n pointers?: PointerArbiter;\n /**\n * The engine's `WebGLRenderer` (`xb.core.renderer`, available from a\n * Script's `init()` on). The host configures it for uikit once, at\n * construction (`configureRendererForUikit`): transparent meshes sorted by\n * `renderOrder`, as uikit assigns, and local clipping on. Without it three\n * sorts panel plates and glyphs by camera distance and a plate hides its\n * own text; XR Blocks sets neither. IWSDK does the same inside its own UI\n * system. Omit it only if the app configures the renderer itself.\n */\n renderer?: UikitRenderer;\n}\n\nexport class UixWindowHost extends ThreeJsWindowHost {\n /** The XR Blocks bridge, or `undefined` when the caller supplied its own `pointerBridgeFactory`. */\n private readonly xrBridge: XrBlocksPointerBridge | undefined;\n /** `xb.input`, read each frame to raycast the panels along every ray source. */\n private readonly xrRayInput: XrBlocksRayInputAccess | undefined;\n\n constructor(options: XrBlocksWindowHostOptions) {\n // xrblocks added `xb.input.getFrame()` in 0.20.0. Refuse an older input here, once, rather than\n // throwing from every frame's update.\n if (options.rayInput && typeof (options.rayInput as Partial<XrBlocksRayInputAccess>).getFrame !== 'function') {\n throw new Error('@realitycollective/xrblocks-uiextensions needs xrblocks 0.20 or later: the rayInput it was handed has no getFrame()');\n }\n const built: { bridge?: XrBlocksPointerBridge } = {};\n super({\n ...options,\n // Only when the caller has not already supplied one (a bare\n // `UixWindowHost` still gets the XR Blocks bridge by default, which is\n // what makes this package's own class useful to construct directly).\n pointerBridgeFactory:\n options.pointerBridgeFactory ??\n ((isLive) => {\n built.bridge = new XrBlocksPointerBridge({ isLive, ...(options.pointers ? { pointers: options.pointers } : {}) });\n return built.bridge;\n }),\n });\n this.xrBridge = built.bridge;\n this.xrRayInput = options.rayInput;\n if (options.renderer) configureRendererForUikit(options.renderer);\n }\n\n /** Drive per-frame from the engine loop (delta in SECONDS): the bridge reads this frame's rays and fingertips first, so hover, touch and the arbiter are current when the windows step. */\n override update(deltaSeconds: number): void {\n if (this.xrBridge && this.xrRayInput) this.xrBridge.updateFrame(this.xrRayInput.getFrame());\n super.update(deltaSeconds);\n }\n}\n"]}
@@ -1,70 +1,112 @@
1
1
  /**
2
- * XrBlocksPointerBridge - turns XR Blocks' own per-object interaction
3
- * callbacks into the portable `pointerdown` / `pointerup` / `click` /
2
+ * XrBlocksPointerBridge - turns XR Blocks' interaction callbacks and input
3
+ * frame into the portable `pointerdown` / `pointerup` / `click` /
4
4
  * `pointerenter` / `pointerleave` events every `data-uix` control and window
5
5
  * chrome listener already reads (`UixElement.addEventListener`), and into
6
6
  * per-frame samples for the core `TouchPress`.
7
7
  *
8
- * XR Blocks 0.21.1 resolves hit-testing itself and calls `onSelectStart`,
9
- * `onSelectEnd`, `onObjectTouchStart` / `onObjectTouching` / `onObjectTouchEnd`,
10
- * `onObjectGrabStart` / `onObjectGrabEnd` and `onHoverEnter` / `onHoverExit`
11
- * directly on the object it decided is involved (see the `InteractionManager`
12
- * doc comments in the vendored `xrblocks.d.ts`), recursing to ancestors only
13
- * while a handler returns a falsy "not handled". Rather than rely on that
14
- * ancestor walk - documented only as "recursively ... until handled", with no
15
- * stated order or `typeof`-guard behaviour to build on - this bridge attaches
16
- * to every node once, at panel-creation time, and does its OWN bubbling from
17
- * the node the SDK actually calls, up through `Object3D.parent`, exactly as
18
- * `NativeWindowHost.dispatch` bubbles from a `NativeElementNode`. Every
19
- * attached handler returns `true`, so an ancestor the SDK also asks (it has
20
- * the same handler) is told the gesture is already handled and does nothing
21
- * further.
8
+ * What XR Blocks calls, and on what. XR Blocks 0.21 resolves hit-testing
9
+ * itself, then looks up the hit object's ancestors for a SCRIPT: an object
10
+ * with `isXRScript === true` that defines one of its targeted hooks
11
+ * (`onObjectSelectStart`, `onObjectSelectEnd`, `onObjectTouchStart`,
12
+ * `onObjectTouching`, `onObjectTouchEnd`, `onObjectGrabStart`,
13
+ * `onObjectGrabEnd`, `onHoverEnter`, `onHovering`, `onHoverExit`; see
14
+ * `ScriptsManager.hasTargetHandler` and `HitResolver.resolve` in xrblocks).
15
+ * The nearest such ancestor is the target, and the hook is called on it with
16
+ * an event that names the mesh actually hit (`intersection.object`,
17
+ * `surface`) and the hit point. A plain `Object3D` carrying callbacks is never
18
+ * a target: the gesture falls through to the Script-level global
19
+ * `onSelectStart`, which carries no target at all. (xrblocks 0.19, which this
20
+ * bridge was first written against, called `onSelectStart` on the hit object
21
+ * itself; that contract is gone.)
22
22
  *
23
- * Click timing. XR Blocks' select is itself edge-triggered - one
24
- * `onSelectStart` / `onSelectEnd` pair per gesture, evidently on the same
25
- * object throughout (the SDK's own manipulation manager decides the target
26
- * once, at select start) - so a press is `pointerdown` at select start and
27
- * `pointerup` + `click` at select end, with no separate edge-detection
28
- * needed. This is unlike native, whose samples arrive already resolved but
29
- * as a per-frame active flag the binding must itself edge-detect (see
30
- * `EdgePress`); XR Blocks needs no such state here. A touch (poke) IS a
31
- * continuous per-frame signal (`onObjectTouching`), so it drives the core
32
- * `TouchPress` every frame exactly as native's touch pointers do, through
33
- * `dispatchTouchUpdate`.
23
+ * So `wire(root)` inserts ONE node per panel between the panel document and
24
+ * the uikit root: a bare `Group` flagged as a Script with those hooks
25
+ * (`XrBlocksPanelScript`). Every hit inside the panel resolves to it. It is a
26
+ * fresh `Group`, not the uikit root or the document, because both of those
27
+ * have `update` and `dispose` methods, which XR Blocks would index as Script
28
+ * hooks and call every frame. The node also carries the no-op `init` and
29
+ * `dispose` XR Blocks calls on a Script it finds in, or loses from, the scene.
30
+ *
31
+ * The bridge bubbles its own pointer events from the hit mesh up through
32
+ * `Object3D.parent`, exactly as `NativeWindowHost.dispatch` bubbles from a
33
+ * `NativeElementNode`, and stops XR Blocks' own propagation to ancestor
34
+ * Scripts, which have nothing to add.
35
+ *
36
+ * Click timing. A ray press is `pointerdown` at `onObjectSelectStart` and
37
+ * `pointerup` at `onObjectSelectEnd`; `click` follows when that end is
38
+ * `completed` (XR Blocks: released over the same panel) and the source's own
39
+ * ray, as the bridge last cast it, still rests on the element it pressed. So a
40
+ * click lands on release, on the element under the ray, as every other
41
+ * platform does.
42
+ *
43
+ * Touch. XR Blocks' own direct touch only reports once a fingertip is inside
44
+ * the panel's bounding box, which a flat panel makes a few millimetres deep,
45
+ * so it never sees the approach the core `TouchPress` needs (in front, then
46
+ * through the press distance). Touch is driven instead from the input frame
47
+ * (`updateFrame`): each fingertip in `directTouches` is cast along the nearest
48
+ * wired panel's normal to the element beneath it, and its signed distance to
49
+ * that element's plane drives the core `TouchPress` every frame exactly as
50
+ * native's touch pointers do, through `dispatchTouchUpdate`. Select events XR
51
+ * Blocks raises for its own direct touch (`source.type === 'direct-touch'`)
52
+ * are ignored for the same reason.
34
53
  *
35
54
  * Shared arbitration and hover. Each touch, grab and ray candidate is offered
36
55
  * to the app's `PointerArbiter` (`PanelPointerOffers`), keyed by handedness
37
56
  * (`handIndex` 0 is left), and a press acts only while its kind owns the
38
- * source. Rays need a per-frame raycast of their own (`updateRays`), which
39
- * also raises hover per source through the core `HoverTracker`.
57
+ * source. Rays are raycast per frame by the bridge itself (`updateFrame`),
58
+ * which also raises hover per source through the core `HoverTracker`; XR
59
+ * Blocks' hover hooks are one flag per object, not per pointer, and are not
60
+ * used.
40
61
  *
41
62
  * A near (grab) press only ever begins on the title bar, exactly as native's
42
- * grab pointer: `onObjectGrabStart`/`End` do nothing unless the hit node is
43
- * inside the `uix-titlebar` element.
63
+ * grab pointer: `onObjectGrabStart`/`End` do nothing unless the element under
64
+ * the grabbing hand is inside the `uix-titlebar` element.
44
65
  */
45
- import { type Object3D } from 'three';
66
+ import { Group, type Object3D } from 'three';
46
67
  import type { PointerArbiter } from '@realitycollective/webxr-input';
47
68
  import { type TouchPressOptions, type Vec3Tuple } from '@realitycollective/webxr-uiextensions';
48
- import type { XBInputFrameLike } from './ray-input.js';
69
+ import type { XBInputFrameLike, XBVec3Like } from './ray-input.js';
49
70
  /** The slice of an XR Blocks `Controller` this bridge reads a live ray pose from. Structural - no dependency on the `xrblocks` package. */
50
71
  export type XrBlocksController = Object3D;
51
- /** `xrblocks.d.ts`'s `SelectEvent`. */
52
- export interface XrBlocksSelectEvent {
53
- target: XrBlocksController;
72
+ /** `InteractionSource` in xrblocks' `interaction/InteractionTypes.ts`: who is pressing. */
73
+ export interface XrBlocksInteractionSource {
74
+ /** `'mouse' | 'controller-ray' | 'hand-ray' | 'direct-touch' | 'gaze' | 'simulator'`. */
75
+ type: string;
76
+ handedness: 'left' | 'right' | 'none';
77
+ /** The same controller identity `xb.input.getFrame().raySources[i].controller` carries. */
78
+ controller: XrBlocksController;
54
79
  }
55
- /** `xrblocks.d.ts`'s `ObjectTouchEvent`. */
56
- export interface XrBlocksTouchEvent {
57
- handIndex: number;
58
- touchPosition: {
59
- x: number;
60
- y: number;
61
- z: number;
80
+ /** `SelectEvent` in xrblocks' `core/Script.ts`, as `onObjectSelectStart` receives it. */
81
+ export interface XrBlocksSelectEvent {
82
+ source: XrBlocksInteractionSource;
83
+ /** The Script resolved for the hit: the bridge's own panel node. */
84
+ target?: Object3D;
85
+ /** The mesh actually hit. */
86
+ surface?: Object3D;
87
+ /** The ray's hit on `surface`: the mesh, the world point and the distance along the ray. */
88
+ intersection?: {
89
+ object: Object3D;
90
+ point: XBVec3Like;
91
+ distance: number;
62
92
  };
93
+ stopPropagation(): void;
94
+ }
95
+ /** `SelectEndEvent` in xrblocks' `core/Script.ts`. `completed` is true when the source released over the same target it pressed. */
96
+ export interface XrBlocksSelectEndEvent extends XrBlocksSelectEvent {
97
+ completed: boolean;
98
+ reason: string;
63
99
  }
64
- /** `xrblocks.d.ts`'s `ObjectGrabEvent`. */
100
+ /** `ObjectGrabEvent` in xrblocks' `core/Script.ts`. */
65
101
  export interface XrBlocksGrabEvent {
102
+ source?: XrBlocksInteractionSource;
103
+ /** 0 is the left hand, anything else the right (the mapping `xrblocks-interactions` uses). */
66
104
  handIndex: number;
105
+ /** The grabbing hand (its wrist joint), moved by tracking; a title-bar grab rides it. */
67
106
  hand: Object3D;
107
+ /** The fingertip's world position at the grab. */
108
+ touchPosition: XBVec3Like;
109
+ stopPropagation?(): void;
68
110
  }
69
111
  export interface XrBlocksPointerBridgeOptions {
70
112
  /** Whether `element` should currently receive pointer events (a hidden or gated window says no). Omit to allow everything - a bare, unmanaged panel. */
@@ -79,62 +121,139 @@ export interface XrBlocksPointerBridgeOptions {
79
121
  */
80
122
  pointers?: PointerArbiter;
81
123
  }
124
+ /**
125
+ * How far in front of and behind a panel's face a fingertip is still cast to
126
+ * the element beneath it (metres). Wider than any press band the core allows
127
+ * a `TouchPress`, so the approach is seen; narrow enough that a hand resting
128
+ * well away from a panel is not "over" it.
129
+ */
130
+ export declare const TOUCH_REACH = 0.15;
131
+ /**
132
+ * Hits this close together along a ray (metres) are one surface: a uikit
133
+ * element and the elements inside it lie in one plane, offset only by the
134
+ * depth uikit gives their render order.
135
+ */
136
+ export declare const COPLANAR_TOLERANCE = 0.002;
82
137
  /** This object's current WORLD position, as a plain tuple. */
83
138
  export declare function worldPositionOf(object: Object3D): Vec3Tuple;
84
139
  /** This object's local +Z axis, rotated to world space - its "front" normal, unit length. Used both for a touch's signed distance and a title-bar ray drag's grab plane. */
85
140
  export declare function worldForwardOf(object: Object3D): Vec3Tuple;
141
+ /**
142
+ * The one node per panel XR Blocks treats as a Script. Inserted by
143
+ * {@link XrBlocksPointerBridge.wire} between the panel document and the uikit
144
+ * root, so it is an ancestor of every mesh in the panel and the Script XR
145
+ * Blocks resolves for a hit on any of them. Its hooks forward to the bridge.
146
+ */
147
+ export declare class XrBlocksPanelScript extends Group {
148
+ /** The uikit root this node wraps. */
149
+ readonly panelRoot: Object3D;
150
+ private readonly bridge;
151
+ /** What `ScriptsManager.isScript` reads. */
152
+ readonly isXRScript = true;
153
+ constructor(
154
+ /** The uikit root this node wraps. */
155
+ panelRoot: Object3D, bridge: XrBlocksPointerBridge);
156
+ /** XR Blocks calls this once when it first sees the node in the scene. Nothing to set up. */
157
+ init(): void;
158
+ /** XR Blocks calls this when the node leaves the scene (its window closed). The host disposed the panel already. */
159
+ dispose(): void;
160
+ onObjectSelectStart(event: XrBlocksSelectEvent): void;
161
+ onObjectSelectEnd(event: XrBlocksSelectEndEvent): void;
162
+ onObjectGrabStart(event: XrBlocksGrabEvent): void;
163
+ onObjectGrabEnd(event: XrBlocksGrabEvent): void;
164
+ }
86
165
  export declare class XrBlocksPointerBridge {
87
166
  private readonly touches;
88
167
  private readonly touchOptions;
89
168
  private readonly isLive;
90
169
  private readonly offers;
91
170
  private readonly hover;
171
+ /** Every wired uikit root (what the host handed `wire`). */
92
172
  private readonly roots;
93
- /** Every wired element -> the panel root it was wired under, for the arbiter's panel id. */
94
- private readonly rootOf;
173
+ /** Root -> the Script node inserted above it. */
174
+ private readonly scripts;
95
175
  private readonly raycaster;
96
- /** Element -> the source key its select press was granted to, so the release ends the SAME press. */
176
+ /** Source key -> the element its select press went down on, so the release ends the SAME press. */
97
177
  private readonly selects;
98
- /** Element -> the source key its grab was granted to. */
178
+ /** Hand key -> the element its grab went down on. */
99
179
  private readonly grabs;
100
- /** The ray sources of the last {@link updateRays}, in order: a controller with no handedness is keyed by its position here. */
180
+ /** The ray sources of the last {@link updateFrame}, in order: a controller with no handedness is keyed by its position here. */
101
181
  private raySources;
102
- /** Source keys the last {@link updateRays} offered a ray for, so a source that goes away is forgotten. */
182
+ /** Source keys the last {@link updateFrame} offered a ray for, so a source that goes away is forgotten. */
103
183
  private rayKeys;
184
+ /** Source key -> the element its ray rested on at the last {@link updateFrame}. */
185
+ private readonly rayTargets;
186
+ /** Hand indexes the last {@link updateFrame} saw a fingertip for. */
187
+ private touchHands;
104
188
  constructor(options?: XrBlocksPointerBridgeOptions);
105
189
  /**
106
- * Attach every callback this bridge understands to `root` and every
107
- * descendant, recursively. Call once per panel, right after its
108
- * `data-uix` controls are upgraded (`upgradePanel`). The root also joins the
109
- * set {@link updateRays} raycasts.
190
+ * Make `root` (a panel's uikit root) interactive to XR Blocks: insert the
191
+ * panel's Script node above it, in its parent's place, and join the set
192
+ * {@link updateFrame} raycasts. Call once per panel, right after its
193
+ * `data-uix` controls are upgraded (`upgradePanel`).
110
194
  */
111
195
  wire(root: Object3D): void;
112
- /** Stop raycasting `root` (its window closed). Its callbacks stay attached and harmless. */
196
+ /** Stop raycasting `root` (its window closed). Its Script node stays in place and harmless. */
113
197
  unwire(root: Object3D): void;
198
+ /** The Script node {@link wire} inserted above `root`, or `undefined` when `root` was never wired. */
199
+ scriptOf(root: Object3D): XrBlocksPanelScript | undefined;
114
200
  /**
115
- * Once per frame, with `xb.input.getFrame()`: raycast every wired panel
116
- * along each ray source, offer the nearest hit to the shared arbiter as that
117
- * source's ray candidate, resolve the source, and raise `pointerenter` /
118
- * `pointerleave` for what each source's ray now owns. XR Blocks' own
119
- * `onHoverEnter` / `onHoverExit` are not used: they are not per pointer and
120
- * ignore the arbiter.
201
+ * Once per frame, with `xb.input.getFrame()`. Rays: raycast every wired
202
+ * panel along each ray source, offer the nearest hit to the shared arbiter
203
+ * as that source's ray candidate, resolve the source, and raise
204
+ * `pointerenter` / `pointerleave` for what each source's ray now owns.
205
+ * Touches: cast each fingertip to the element beneath it on the nearest
206
+ * wired panel and drive that hand's `TouchPress` with its signed distance.
121
207
  */
208
+ updateFrame(frame: XBInputFrameLike): void;
209
+ /** @deprecated Renamed {@link updateFrame}: the frame now drives touch as well as rays. */
122
210
  updateRays(frame: XBInputFrameLike): void;
123
- private attach;
211
+ /** @internal `onObjectSelectStart` on a panel's Script node. */
212
+ selectStart(script: XrBlocksPanelScript, event: XrBlocksSelectEvent): void;
213
+ /** @internal `onObjectSelectEnd` on a panel's Script node. */
214
+ selectEnd(_script: XrBlocksPanelScript, event: XrBlocksSelectEndEvent): void;
215
+ /** @internal `onObjectGrabStart` on a panel's Script node. */
216
+ grabStart(script: XrBlocksPanelScript, event: XrBlocksGrabEvent): void;
217
+ /** @internal `onObjectGrabEnd` on a panel's Script node. */
218
+ grabEnd(_script: XrBlocksPanelScript, event: XrBlocksGrabEvent): void;
124
219
  /**
125
- * Whether the ray of the controller in `key` may press `element`. When
126
- * {@link updateRays} has offered for this source, its decision stands.
127
- * Otherwise (no ray input wired) XR Blocks' own hit on `element` is offered
128
- * here, at the controller's distance, so a host without ray input still
129
- * presses.
220
+ * Whether the ray of the source in `key` may press the panel at `root`.
221
+ * When {@link updateFrame} has offered for this source, its decision
222
+ * stands. Otherwise (no input frame wired) XR Blocks' own hit is offered
223
+ * here, so a host without the frame still presses.
130
224
  */
131
225
  private claimRay;
132
- /** The arbiter's name for a controller: its handedness, else `controller:<position in the ray sources>`. */
226
+ /** Whether the source's ray, as last cast by {@link updateFrame}, rests on `element`. True when the frame is not wired: XR Blocks' `completed` is then the whole answer. */
227
+ private rayRestsOn;
228
+ /** The arbiter's name for an interaction source: its handedness, else `controller:<position in the ray sources>`. */
229
+ private sourceKey;
230
+ /** The arbiter's name for a controller: its input source's handedness, else `controller:<position in the ray sources>`. */
133
231
  private controllerKey;
134
- /** The panel id for the arbiter: the uuid of the wired root `element` belongs to. */
135
- private panelIdOf;
136
232
  /** The nearest hit on any live wired panel along a world ray. */
137
233
  private rayCast;
234
+ /**
235
+ * The element of `root` beneath a world `point`, found by casting from
236
+ * {@link TOUCH_REACH} in front of the panel's face, along its normal, and
237
+ * the point's signed distance to that element's plane (positive in front).
238
+ * `undefined` when nothing of the panel lies under the point.
239
+ */
240
+ private elementUnder;
241
+ /**
242
+ * The hit on `root` along {@link raycaster}'s current ray that names the
243
+ * element a pointer is really over: of the nearest hits (within
244
+ * {@link COPLANAR_TOLERANCE}, since a uikit element and everything inside
245
+ * it share one plane) the deepest in the tree, so a button inside a panel
246
+ * wins over the panel.
247
+ *
248
+ * Every object is asked to `raycast` and every child is visited, whatever
249
+ * `raycast` returned: three's own `intersectObject` stops at a uikit
250
+ * component, whose `raycast` returns `false` to keep three out of its
251
+ * glyph meshes, and would only ever report the panel itself. XR Blocks'
252
+ * `HitRegistry.intersectTree` walks the same way.
253
+ */
254
+ private castDeepest;
255
+ /** One fingertip this frame: the element under it on the nearest live panel, offered as that hand's touch, drives the hand's `TouchPress`. */
256
+ private touchSample;
138
257
  /** One pointer's hover target this frame: `pointerleave` on what it left, `pointerenter` on what it reached (live elements only). */
139
258
  private setHover;
140
259
  /** Whether `element` sits on, or inside, a window's title bar - the only thing a grab pointer can hold. */