@realitycollective/xrblocks-uiextensions 0.1.0-preview.7 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -4,10 +4,20 @@ Change log for the Reality Collective WebXR UI Extensions packages. All four pac
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Preview builds are not listed separately. The entry for a version accumulates while its previews are published, and is dated when that version is released.
6
6
 
7
- ## [0.1.0]
7
+ ## [0.1.0] - 2026-09-17
8
8
 
9
9
  ### Added
10
10
 
11
+ - `WindowManager` is now the complete state API for a window, so a hand menu, shortcut or voice command can drive a targeted window without touching the engine. New on the record: `hidden`, `region` and `chrome`. New calls: `hide(id)`, `show(id)`, `toggleHidden(id)`; `dockTo(id, regionId)`, `undock(id)`, `returnHome(id)`; `setChrome(id, partial)`. New events: `hidden`, `shown`, `regionChanged`, `returnHome`, `chromeChanged`. `OpenWindowOptions` takes `hidden`, `region` and `chrome`. A hidden window is neither drawn nor hittable but keeps its dock mode, region slot and minimized state, and `show` puts it back in front; `returnHome` announces only, because the adapter owns the spawn snapshot. Both adapters apply every event: the IWSDK `UIWindowSystem` toggles visibility and strips or restores `RayInteractable`/`PokeInteractable`, adds or removes `UIDockedTo`, and mirrors the chrome flags onto `UIWindow`; the XR Blocks host toggles the group, docks through its registry and keeps a home snapshot. The reverse direction is kept in step too: a drag-drop dock, a rejected dock (unknown or full region) and a dock-mode change applied by the ECS are written back into the record, so `windows.get(id)` is always what the scene shows.
12
+ - `dockable` on `WindowOptionsBase`, `SceneWindow`, the IWSDK `UIWindow` component and both adapters' create options. The DOCK button previously had no flag at all and was always live.
13
+ - Near drag on IWSDK. `UIDragSystem` now lets the near `grab` pointer pick a window up by its title bar: a controller squeeze, or a hand pinch while the hand is on the bar. IWSDK only ever tests the grab pointer against `OneHandGrabbable`/`TwoHandsGrabbable` entities and only enables it when `features.grabbing` is on, so the system, which runs after `InputSystem`, appends every movable title bar to `scene.grabDescendants` each frame, enables the `grab` sub-pointer itself, and forwards a pinch to it only while the grab pointer is on one of its title bars (leaving pinches elsewhere to whatever the app decided, and deferring entirely when `GrabSystem` is registered with `useHandPinchForGrab`). A near grab drags at once, with no hold delay; a ray press still waits out `dragDelay` so title-bar clicks stay clicks. Off with `registerUIExtensions(world, { nearDrag: false })` or `configData: { nearDrag: false }` on the system. Putting `OneHandGrabbable` on the window was not an option: IWSDK would grab the whole panel and set `pointerEventsType = { deny: 'ray' }` on it, ending far interaction.
14
+ - `HoldToDrag.update` takes an optional per-press `delaySeconds` override, which is what lets a near grab skip the click window without a second store.
15
+ - A sixth contract case, `closing through the manager takes the window out of onPanelReady replay`, and a required `manager` on `WindowHostContractSetup`. Both shipped adapters pass it; the IWSDK setup now registers `UIWindowSystem` so the record opens and the entity is destroyed for real in the headless world.
16
+ - Headless tests for the IWSDK systems, `packages/iwsdk-uiextensions/test/window-system.test.ts`: `UIWindowSystem`, `UIDockSystem`, `UIDockRegionSystem` and `UIDragSystem` run against a real `World` with fake documents. They cover close in both directions, hide/show, chrome gating and runtime enablement, regions through the manager and back, return-home, and the grab-list registration. Every world in a test file now registers the same components in the same order, because elics keeps `typeId` on the component object and `addComponent` only registers a component whose `typeId` is still -1: a component first registered lazily (IWSDK's `Transform`) keeps the id it got in an earlier world, and a later world that hands that id to another component makes the two share a bit.
17
+ - Hand menus, as a fourth dock mode, `hand-locked`, in the manner of MRTK 2's hand menu. A hand-locked window rides a hand and shows while that palm is raised toward the viewer. The window's `handMenu` options (on `WindowOptionsBase`, `SceneWindow`, `OpenWindowOptions` and the record, with `setHandMenu` and a `handMenuChanged` event) choose the hand (`left`, `right`, or `either` for whichever palm is raised), the anchor (`above` the fingertips, `inside` on the thumb side, `outside`, or `wrist`), the distance and an extra hand-local offset, and the palm gate with its angle; defaults are left hand, above, gated at 60 degrees. The core's `hand-menu.ts` owns the placement and the gate in the WebXR grip frame as the Device API defines it (`-Z` toward the thumb, `+Y` up the arm, the palm at `-X` on the right hand and `+X` on the left; hand joint spaces differ and are not used), so every adapter reads the same offsets: `evaluateHandMenu`, `handMenuPose`, `pickHand`, `palmFacing`, `faceViewer`. A `HandPoseSource` joins the adapter contract, with an optional `hasHands()` so a page serving both a desktop and a headset can fall back to body-follow placement outside a session. On IWSDK, `UIDockSystem` reads the player rig's grip spaces (a controller's grip or the tracked hand) and writes the gate into `UIWindowState.gateOpen`; `UIWindowSystem` combines that with `hidden` in one presentation reconcile, so a shut gate strips the interaction tags the same way `hide()` does. `UIDockSystem` now registers at priority -1 so the gate it writes is applied the same frame. The `UIWindow` component carries `hand`, `handAnchor`, `handAnchorDistance`, `handOffset`, `palmGate` and `palmAngle`. On XR Blocks, `UixWindowHost` takes an optional `handPose`, `connectUIExtensions` takes `xr: renderer.xr`, and `webxrHandPoseSource` reads the session's input sources (grip space, or the target ray space for a hand without one). `HAND_MENU_SNIPPET` is the reference markup: the same root and content ids, no title bar, a vertical stack of buttons that sizes to its content. Pinning or dragging a hand menu lands it world-locked where it was.
18
+ - `TouchPress` in `@realitycollective/webxr-uiextensions` (`core/touch-press.ts`, with `TouchPressOptions`, `DEFAULT_TOUCH_PRESS`, `resolveTouchPress`, `TouchSample`, `TouchUpdate`): the press / hold / release state machine for a near (poke) pointer over a signed distance. A press starts only when the fingertip enters the press band from the front (or is first seen in front of the surface); the touch is then held whatever the finger does until it comes back out past the release distance or contact is lost, so a second press cannot start before a release; a fingertip arriving from behind never presses unless `allowFromBehind` is set. Defaults: press at 2 cm, release at 3 cm. The target under the finger is recorded at the press and reported again at the release, because they can differ when a finger enters one button and leaves through another.
19
+ - `UITouchGuardSystem` in `@realitycollective/iwsdk-uiextensions`, registered by `registerUIExtensions` at priority -3.9 (straight after `InputSystem`) unless `touchGuard: false`, with thresholds through `touchGuard: { pressDistance, releaseDistance, allowFromBehind }`. It takes over the two IWSDK touch pointers' `down` and `up` (IWSDK's own calls, including its forced release on lost hover, become no-ops) and issues them from `TouchPress` fed with the signed distance from the fingertip to the surface it is over, using the intersection's normal in world space (a target without one keeps the unsigned behaviour). `pointerdown`, `pointerup` and `click` still reach elements through the pointer as before, so IWSDK's own panels and every `PokeInteractable` are covered. IWSDK's 800 ms click window is unchanged.
20
+ - The showcase's `Window Control` is that hand menu, on the left hand, wired in the engine-free `playground-behaviour.ts` so it runs on the IWSDK, XR Blocks and desktop pipelines alike (following the body on the desktop, where there are no hands). The scene also shows the three ways to use opt-in chrome, named in the titles: `Click Machine (all buttons)`, `Gallery (no buttons)` and `Player Status (menu-driven)`. `Examples/basic-window/` in `@realitycollective/iwsdk-uiextensions` now spawns its window with no title-bar buttons and drives it from a hand menu (HIDE, PIN, HOME, MIN, BUTTONS, CLOSE), with labels read back from the record.
11
21
  - `verify:pack` now lints the shape of what ships: publint over every package directory, with warnings counted as errors, and attw (Are The Types Wrong) over every packed tarball, resolving the published types under node10, node16 and bundler resolution. `cjs-resolves-to-esm` is ignored by design, because every package is ESM-only and a require() caller is expected to use a dynamic import. Both run offline on the tarballs the script already builds; `publint` and `@arethetypeswrong/cli` are dev dependencies. The script stays identical across the Reality Collective repositories.
12
22
  - `verify:pack` now also type-checks the published declarations themselves, with library checking on, through `scripts/declaration-check.mjs`, runnable on its own as `node scripts/declaration-check.mjs`. Each package's declaration entry is compiled as a strict consumer would compile it, with `skipLibCheck: false`, under nodenext and then bundler resolution; a diagnostic inside the package fails the run, and diagnostics inside upstream declaration files are counted and ignored, because they are not ours to fix and would drown the signal. attw proves the published types resolve; this proves they compile, which is what a consumer with library checking on, or a package emitting declarations on top of ours, needs. The check is opt-in per repository, through `declarationCheck` in `scripts/release.config.json`, because only a foreign declaration can put a name into our emitted types that the build did not already check. This repository reaches around 950 of them, from the IWSDK and three.js typings the adapter is built against, and every run prints that count so the opt-in stays measured rather than habitual. It is the check that would have caught the bare `World` under Fixed.
13
23
  - `@realitycollective/webxr-uiextensions` - engine-free core: window manager, dock state and regions, drag maths, hold-to-drag, control models (stepper/toggle/expandable/log), the `SceneDescriptor` scene format, window chrome conventions and the platform-adapter contract.
@@ -25,6 +35,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
25
35
 
26
36
  ### Changed
27
37
 
38
+ - **Every title-bar button is off by default.** `closable`, `minimizable` and `pinnable` defaulted to `true` on the IWSDK `UIWindow` component, `createUIWindow`, the scene host and the XR Blocks host, so a window showed every button its markup carried whether the app wanted it or not; the new `dockable` joins them at `false`. Pass `true` for the buttons a window should have, or enable them later with `WindowManager.setChrome`. The reference markup keeps all four elements so they can be switched on at runtime; a disabled button is hidden and its click ignored. Migration: windows that relied on the old default need `closable: true, minimizable: true, pinnable: true` (and `dockable: true` for DOCK, which was previously always on). The shipped examples, the showcase scene and the devtools playground now say which buttons they want.
39
+ - Chrome buttons are wired once, at adoption, and gated on the record at click time, so enabling one later needs no rewiring. The IWSDK X button now closes through `manager.close(id)`, which destroys the entity, rather than destroying the entity directly, so there is one teardown path. On XR Blocks, `host.dock(windowId, regionId)` is now a forwarder to `manager.dockTo`/`manager.undock`; existing callers read the same.
28
40
  - **Controls are declared as custom elements rather than attributes.** `<uix-stepper>` replaces `<div data-uix="stepper">`, and each part is its own element: `<uix-value>`, `<uix-line>`, `<uix-label>` and so on, replacing `data-uix-role`. Parameters are unchanged and stay `data-uix-*`. This is what makes one markup file portable: IWSDK 0.5 parses with `@drawcall/uikitml`, which rejects every `data-*` attribute on a built-in tag but accepts custom tags declared in a component set, while the three.js and XR Blocks parser accepts custom tags with no registration at all. Both expose the declared tag on `userData.customElement.componentName`, so the engine-free upgraders read one contract on every adapter.
29
41
  - The expandable label's `toggle` part is now `<uix-more>`. As an element the old name would have collided with the `<uix-toggle>` control.
30
42
  - `@realitycollective/iwsdk-uiextensions` targets **IWSDK 0.5.x**: peer range `>=0.5.0 <0.6.0`, developed against `@iwsdk/core` 0.5.3. `PanelUI` in 0.5 declares a single field, `config`, holding the URL of the UIKitML **source**; the compiled JSON it took on 0.4.x, and the `@iwsdk/vite-plugin-uikitml` step that produced it, are both gone. `maxWidth` and `maxHeight` were removed from the component schema, and ECS ignores unknown fields silently, so a panel that still writes them is mis-sized with no error. Size through `createUIWindow`, which routes to `UIWindow.targetWidth/Height`.
@@ -40,10 +52,11 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
40
52
  - `@realitycollective/webxr-uiextensions` takes its geometry vocabulary from `@realitycollective/webxr-input` at `^0.1.1` rather than redeclaring it. `Vec3Tuple`, `QuatTuple`, `HeadPose`, `HeadPoseSource` and `PointerSample` are now that package's types, re-exported under the same names, so no import changes for a consumer. `PointerSample` is its `RayTuple`, which is what lets an input provider written against the shared contracts feed this contract unchanged. It is the core's only runtime dependency: the contracts package is engine-free and carries none of its own, and `test/architecture.test.ts` now allows exactly that one name and fails on any other.
41
53
  - `PointerSample` states its ownership rule: a delivered sample belongs to the listener and the source never writes to it again, so the core's hold-to-drag and drag maths may keep a press-time sample without copying. It mirrors the rule `@realitycollective/webxr-input` 0.1.3 writes on `InputSourceSnapshot`, so a provider feeding both contracts has one promise to keep.
42
54
 
43
- [0.1.0]: https://github.com/realitycollective/WebXR-UIExtensions/commits/main
55
+ [0.1.0]: https://github.com/realitycollective/WebXR-UIExtensions/releases/tag/v0.1.0
44
56
 
45
57
  ### Fixed
46
58
 
59
+ - `manager.close(id)` on IWSDK removed the record and emitted `closed`, but `UIWindowSystem` had no `closed` listener, so the entity and its panel stayed in the scene, orphaned, with chrome wired to a dead id. The XR Blocks host did dispose on `closed`, so the two adapters disagreed, and the devtools playground's "replace last" path hit it on every use. The window system now destroys the entity on `closed` (guarded against the entity-side path re-entering), and the scene host forgets the window so `onPanelReady` stops replaying it.
47
60
  - `@realitycollective/iwsdk-uiextensions` shipped five declaration files (`controls-system`, `dock-region-system`, `dock-system`, `drag-system` and `window-system` under `dist/systems/`) whose `createSystem` base type named `World` without importing it, so a consumer with `skipLibCheck: false`, or one emitting declarations on top of the package, failed with TS2304 on its first import. Reported by the Anatomy Atlas XR client on 9 September 2026 against preview.4 and preview.6. The cause is upstream: `@iwsdk/core` 0.5.3's `dist/ecs/system.d.ts` imports `World` from `'./world'` with no extension, the only such import in the package, and under the NodeNext resolution this repository builds with that import does not resolve (TS2835), so `World` was an unresolved name inside the host's own declaration and TypeScript's declaration emitter preserved it verbatim. `skipLibCheck` hid the error on both sides. The adapter now takes `createSystem` from its own `src/create-system.ts`, a wrapper whose return type names `World` through `@iwsdk/core`'s barrel, so every emitted base type reads `import("@iwsdk/core").World`. The wrapper goes the day IWSDK ships `./world.js` there.
48
61
  - `createSceneHost(world)` returns the same host on every call for a world, where it previously built a new one each time. A second host registered a second readiness ECS system on the same world and repeated the panel upgrade pass, so two modules each asking for the host quietly doubled that work. It now memoises per world exactly as the window manager registry does, which also means separate modules can ask for the host without coordinating or passing it around.
49
62
  - Documented in `@realitycollective/iwsdk-uiextensions` that `onPanelReady` announces windows spawned by `createUIWindow`, not only by `host.createWindow`, so code holding factory entities has a readiness signal without changing how it spawns them. The readiness section now also warns against polling `getPanelHandle` on a timer, because a poll that gives up early leaves a window that draws and responds to nothing with no error, and records that a factory window created without an `id` is announced as `kind: 'panel'` with its config path, which a listener filtering on `kind === 'window'` will never see.
package/README.md CHANGED
@@ -9,16 +9,20 @@
9
9
  | Feature | IWSDK | XR Blocks / three.js (this package) |
10
10
  | --- | --- | --- |
11
11
  | UIKitML panel hosting (runtime interpret, scale-to-fit) | ✅ | ✅ `UixPanelDocument` |
12
- | Window lifecycle + chrome (focus/PIN/MIN/X, pin labels) | ✅ | ✅ `UixWindowHost` |
12
+ | Window lifecycle + chrome (focus/PIN/DOCK/MIN/X, all opt-in, pin labels) | ✅ | ✅ `UixWindowHost` |
13
+ | Driving windows from code (`WindowManager`: hide/show, dockTo/undock/returnHome, setChrome, close) | ✅ | ✅ every manager event applied |
13
14
  | Portable scene descriptors (`applyScene`) | ✅ | ✅ implements `SceneTarget` |
14
15
  | Panel-ready wiring (`onPanelReady`) | ✅ | ✅ implements `WindowHost` |
15
16
  | Follow mode (body-follow, yaw-only, eased) | ✅ | ✅ pure `follow-math` |
16
- | Dock regions (wall/belt, slots, follow) | ✅ | ✅ `createRegion` / `dock` |
17
+ | Hand menus (`hand-locked`, palm gate, anchors) | ✅ from the player rig | ✅ from a `HandPoseSource`; `webxrHandPoseSource(renderer.xr)` reads the session's input sources, and without hands the menu follows the body |
18
+ | Dock regions (wall/belt, slots, follow) | ✅ | ✅ `createRegion`, `manager.dockTo` (`host.dock` forwards) |
17
19
  | Desktop mouse input (hover, click, drag-to-look) | ✅ | ✅ via `@pmndrs/pointer-events` |
18
20
  | Desktop locomotion (WASD, jump, crouch, sprint) | n/a | ✅ `DesktopControls` |
19
21
  | XR select-ray click forwarding | ✅ | ✅ minimal (`forwardClick`) |
20
22
  | Bare panels (`createPanel`) | ⬜ ECS owns the lifecycle | ✅ `supportsStandalonePanels` is `true` |
21
23
  | Title-bar ray drag (`@pmndrs/handle`) | ✅ | ⬜ roadmap (`movable` is accepted and ignored) |
24
+ | Title-bar near grab (squeeze / pinch) | ✅ | ⬜ roadmap (needs drag) |
25
+ | Guarded poke (one press per touch, front only) | ✅ `UITouchGuardSystem` over IWSDK's touch pointers | ⬜ no near touch here yet; the core `TouchPress` is ready for it |
22
26
  | Drop-to-dock by dragging | ✅ | ⬜ roadmap (needs drag) |
23
27
  | System keyboard text input | ✅ | ⬜ untested on Android XR |
24
28
 
@@ -79,6 +83,8 @@ Nothing here imports `xrblocks` - the glue binds to plain three.js shapes (`scen
79
83
 
80
84
  - `id` is optional. Omit it and the window is named `uix-window-<n>`.
81
85
  - `movable` is accepted and recorded, but nothing acts on it yet: this host has no title-bar drag of its own, so there is no gate to close. It is in the options so a scene descriptor written for IWSDK loads here unchanged.
86
+ - The four chrome flags (`closable`, `minimizable`, `pinnable`, `dockable`) are off unless set, as on IWSDK; `host.manager.setChrome(id, {...})` changes them later. `host.manager.hide/show`, `dockTo/undock/returnHome` and `close` all take effect here, so a menu written against the manager needs no host-specific code.
87
+ - `handMenu` and `dockMode: 'hand-locked'` make a hand menu. Pass `xr: renderer.xr` to `connectUIExtensions` (or `handPose` to the host) so it rides the session's tracked hands; on a page that also serves a desktop the source reports no hands outside a session and the menu follows the body until one starts.
82
88
 
83
89
  The handle it returns satisfies the core `WindowHandle` and adds the three.js specifics:
84
90
 
package/dist/host.d.ts CHANGED
@@ -3,9 +3,15 @@
3
3
  *
4
4
  * Owns a core `WindowManager` + `RegionRegistry` and applies their decisions
5
5
  * to the scene graph: spawn UIKitML windows and dock regions, wire chrome
6
- * buttons (focus / PIN / MIN / X), collapse content while minimized, place
7
- * docked windows into their region's slots, and ease `body-follow` windows
8
- * (and body-locked regions) toward the viewer each frame.
6
+ * buttons (focus / PIN / DOCK / MIN / X), collapse content while minimized,
7
+ * hide and show, place docked windows into their region's slots, return a
8
+ * window to where it spawned, ease `body-follow` windows (and body-locked
9
+ * regions) toward the viewer each frame, and ride `hand-locked` windows
10
+ * (hand menus) on a tracked hand behind the palm gate.
11
+ *
12
+ * The manager is the API app code drives; every manager event is applied
13
+ * here, so `host.manager.hide(id)` or `.dockTo(id, region)` from a hand menu
14
+ * is all a caller needs.
9
15
  *
10
16
  * It implements the core's `WindowHost` (so app code can wire behaviour
11
17
  * through `onPanelReady` with no engine knowledge) and `SceneTarget` (so a
@@ -13,7 +19,7 @@
13
19
  */
14
20
  import { Group, type Object3D } from 'three';
15
21
  import { type Kit } from '@pmndrs/uikitml';
16
- import { RegionRegistry, WindowManager, type HeadPoseSource, type PanelHandle, type PanelReadyEvent, type RegionDefinition, type SceneRegion, type SceneTarget, type SceneWindow, type Vec3Tuple, type WindowHandle as CoreWindowHandle, type WindowHost, type WindowOptionsBase } from '@realitycollective/webxr-uiextensions';
22
+ import { RegionRegistry, WindowManager, type HandPoseSource, type HeadPoseSource, type PanelHandle, type PanelReadyEvent, type RegionDefinition, type SceneRegion, type SceneTarget, type SceneWindow, type Vec3Tuple, type WindowHandle as CoreWindowHandle, type WindowHost, type WindowOptionsBase } from '@realitycollective/webxr-uiextensions';
17
23
  import { UixPanelDocument } from './panel-document.js';
18
24
  /**
19
25
  * Options for {@link UixWindowHost.createWindow}.
@@ -65,6 +71,13 @@ export interface UixWindowHostOptions {
65
71
  scene: Object3D;
66
72
  /** Viewer pose provider - camera on desktop, HMD pose in XR. */
67
73
  headPose: HeadPoseSource;
74
+ /**
75
+ * Tracked-hand pose provider, for `hand-locked` windows (hand menus). See
76
+ * `webxrHandPoseSource` for one backed by a WebXR session. Leave it out
77
+ * where there are no hands (a desktop) and hand-locked windows fall back
78
+ * to body-follow placement, so the same scene still shows its menus.
79
+ */
80
+ handPose?: HandPoseSource;
68
81
  /** Optional UIKitML component kit(s) (e.g. horizon kit). */
69
82
  kit?: Kit;
70
83
  /**
@@ -85,6 +98,7 @@ export declare class UixWindowHost implements WindowHost, SceneTarget {
85
98
  readonly regions: RegionRegistry;
86
99
  private readonly scene;
87
100
  private readonly headPose;
101
+ private readonly handPose;
88
102
  private readonly kit;
89
103
  private readonly loadConfig;
90
104
  private readonly states;
@@ -115,18 +129,33 @@ export declare class UixWindowHost implements WindowHost, SceneTarget {
115
129
  /** Create a dock region anchored in the scene. */
116
130
  createRegion(options: CreateRegionOptions): RegionHandle;
117
131
  region(id: string): RegionHandle | undefined;
118
- /** Dock a window into a region (or undock it with `undefined`). */
132
+ /**
133
+ * Dock a window into a region (or undock it with `undefined`). A thin
134
+ * forwarder to the manager, kept so existing callers read the same;
135
+ * `manager.dockTo` / `manager.undock` are the portable calls.
136
+ */
119
137
  dock(windowId: string, regionId: string | undefined): void;
138
+ /** Make the registry (and the layout) agree with the record's region. */
139
+ private applyRegion;
140
+ /** Put a window back where it spawned: its region, or its placement and mode. */
141
+ private returnHome;
120
142
  /** Spawn a managed window from compiled UIKitML JSON. */
121
143
  createWindow(options: CreateWindowOptions): XrBlocksWindowHandle;
122
144
  window(id: string): XrBlocksWindowHandle | undefined;
123
145
  /** Drive per-frame from the engine loop (delta in SECONDS). */
124
146
  update(deltaSeconds: number): void;
147
+ /** This frame's tracked hands from the source, absent ones left out. */
148
+ private readHands;
149
+ private placeOnHand;
125
150
  private applyFollow;
126
151
  /** Re-place every docked window into its region's slot. */
127
152
  private layoutRegions;
128
153
  private layoutRegion;
129
154
  private wireChrome;
155
+ /** Show exactly the enabled buttons. */
156
+ private applyChrome;
157
+ /** Drawn exactly when not hidden and (for a hand menu) the palm gate is open. */
158
+ private applyPresentation;
130
159
  private setContentCollapsed;
131
160
  /** MIN when open, MAX when minimized - the label names the next action. */
132
161
  private syncMinimizeLabel;
package/dist/host.js CHANGED
@@ -3,9 +3,15 @@
3
3
  *
4
4
  * Owns a core `WindowManager` + `RegionRegistry` and applies their decisions
5
5
  * to the scene graph: spawn UIKitML windows and dock regions, wire chrome
6
- * buttons (focus / PIN / MIN / X), collapse content while minimized, place
7
- * docked windows into their region's slots, and ease `body-follow` windows
8
- * (and body-locked regions) toward the viewer each frame.
6
+ * buttons (focus / PIN / DOCK / MIN / X), collapse content while minimized,
7
+ * hide and show, place docked windows into their region's slots, return a
8
+ * window to where it spawned, ease `body-follow` windows (and body-locked
9
+ * regions) toward the viewer each frame, and ride `hand-locked` windows
10
+ * (hand menus) on a tracked hand behind the palm gate.
11
+ *
12
+ * The manager is the API app code drives; every manager event is applied
13
+ * here, so `host.manager.hide(id)` or `.dockTo(id, region)` from a hand menu
14
+ * is all a caller needs.
9
15
  *
10
16
  * It implements the core's `WindowHost` (so app code can wire behaviour
11
17
  * through `onPanelReady` with no engine knowledge) and `SceneTarget` (so a
@@ -27,7 +33,7 @@ async function loadUikitmlSource(path) {
27
33
  }
28
34
  return parse(await response.text());
29
35
  }
30
- import { DockMode, RegionRegistry, WINDOW_CHROME_IDS, WindowManager, minimizeLabelFor, pinLabelFor, slotOffset, } from '@realitycollective/webxr-uiextensions';
36
+ import { DockMode, RegionRegistry, WINDOW_CHROME_IDS, WindowManager, evaluateHandMenu, minimizeLabelFor, pinLabelFor, slotOffset, } from '@realitycollective/webxr-uiextensions';
31
37
  import { approach, approachAlpha, distanceSquared, followTarget, } from './follow-math.js';
32
38
  import { UixPanelDocument } from './panel-document.js';
33
39
  const DEFAULT_FOLLOW_OFFSET = [0, -0.15, -1.2];
@@ -39,6 +45,7 @@ export class UixWindowHost {
39
45
  regions = new RegionRegistry();
40
46
  scene;
41
47
  headPose;
48
+ handPose;
42
49
  kit;
43
50
  loadConfig;
44
51
  states = new Map();
@@ -51,6 +58,7 @@ export class UixWindowHost {
51
58
  constructor(options) {
52
59
  this.scene = options.scene;
53
60
  this.headPose = options.headPose;
61
+ this.handPose = options.handPose;
54
62
  this.kit = options.kit;
55
63
  this.loadConfig = options.loadConfig ?? loadUikitmlSource;
56
64
  this.manager.events.on('closed', (record) => {
@@ -64,6 +72,31 @@ export class UixWindowHost {
64
72
  this.layoutRegions();
65
73
  }
66
74
  });
75
+ this.manager.events.on('hidden', (record) => {
76
+ this.applyPresentation(record.id);
77
+ });
78
+ this.manager.events.on('shown', (record) => {
79
+ this.applyPresentation(record.id);
80
+ });
81
+ this.manager.events.on('dockChanged', ({ window, previous }) => {
82
+ if (previous === DockMode.HandLocked) {
83
+ // Off the hand: the gate no longer applies.
84
+ const state = this.states.get(window.id);
85
+ if (state) {
86
+ state.gateOpen = true;
87
+ this.applyPresentation(window.id);
88
+ }
89
+ }
90
+ });
91
+ this.manager.events.on('regionChanged', ({ window }) => {
92
+ this.applyRegion(window);
93
+ });
94
+ this.manager.events.on('returnHome', (record) => {
95
+ this.returnHome(record.id);
96
+ });
97
+ this.manager.events.on('chromeChanged', ({ window }) => {
98
+ this.applyChrome(window);
99
+ });
67
100
  this.manager.events.on('minimized', (record) => {
68
101
  this.setContentCollapsed(record.id, true);
69
102
  this.syncMinimizeLabel(record.id);
@@ -147,6 +180,8 @@ export class UixWindowHost {
147
180
  ? { minimizable: window.minimizable }
148
181
  : {}),
149
182
  ...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),
183
+ ...(window.dockable !== undefined ? { dockable: window.dockable } : {}),
184
+ ...(window.handMenu !== undefined ? { handMenu: window.handMenu } : {}),
150
185
  });
151
186
  })
152
187
  .catch((error) => {
@@ -175,17 +210,59 @@ export class UixWindowHost {
175
210
  region(id) {
176
211
  return this.regionStates.get(id)?.handle;
177
212
  }
178
- /** Dock a window into a region (or undock it with `undefined`). */
213
+ /**
214
+ * Dock a window into a region (or undock it with `undefined`). A thin
215
+ * forwarder to the manager, kept so existing callers read the same;
216
+ * `manager.dockTo` / `manager.undock` are the portable calls.
217
+ */
179
218
  dock(windowId, regionId) {
180
219
  if (regionId === undefined) {
181
- this.regions.undock(windowId);
220
+ this.manager.undock(windowId);
182
221
  }
183
222
  else {
184
- this.regions.dock(windowId, regionId);
185
- this.manager.setDockMode(windowId, DockMode.WorldLocked);
223
+ this.manager.dockTo(windowId, regionId);
224
+ }
225
+ }
226
+ /** Make the registry (and the layout) agree with the record's region. */
227
+ applyRegion(record) {
228
+ const current = this.regions.regionOf(record.id);
229
+ if (current === record.region) {
230
+ return;
231
+ }
232
+ if (current !== undefined) {
233
+ this.regions.undock(record.id);
234
+ }
235
+ if (record.region !== undefined) {
236
+ try {
237
+ this.regions.dock(record.id, record.region);
238
+ }
239
+ catch (error) {
240
+ // Unknown or full region: the record must not claim it.
241
+ this.manager.undock(record.id);
242
+ throw error;
243
+ }
244
+ this.manager.setDockMode(record.id, DockMode.WorldLocked);
186
245
  }
187
246
  this.layoutRegions();
188
247
  }
248
+ /** Put a window back where it spawned: its region, or its placement and mode. */
249
+ returnHome(id) {
250
+ const state = this.states.get(id);
251
+ if (!state) {
252
+ return;
253
+ }
254
+ const { home } = state;
255
+ if (home.region !== undefined) {
256
+ this.manager.dockTo(id, home.region);
257
+ return;
258
+ }
259
+ this.manager.undock(id);
260
+ this.manager.setDockMode(id, home.dockMode);
261
+ if (home.dockMode === DockMode.WorldLocked) {
262
+ state.handle.group.position.set(...home.position);
263
+ }
264
+ // Follow modes ease back to the viewer on their own in update().
265
+ }
189
266
  // --- Windows --------------------------------------------------------------
190
267
  /** Spawn a managed window from compiled UIKitML JSON. */
191
268
  createWindow(options) {
@@ -212,8 +289,14 @@ export class UixWindowHost {
212
289
  return () => { };
213
290
  },
214
291
  };
292
+ const dockMode = options.dockMode ?? DockMode.WorldLocked;
215
293
  this.states.set(id, {
216
294
  handle,
295
+ home: {
296
+ region: options.region,
297
+ position: options.position ? [...options.position] : [0, 0, 0],
298
+ dockMode,
299
+ },
217
300
  movable: options.movable ?? true,
218
301
  options: {
219
302
  followOffset: options.followOffset ?? DEFAULT_FOLLOW_OFFSET,
@@ -223,12 +306,23 @@ export class UixWindowHost {
223
306
  content: undefined,
224
307
  pin: undefined,
225
308
  minimize: undefined,
309
+ buttons: { pin: undefined, dock: undefined, minimize: undefined, close: undefined },
310
+ gateOpen: true,
226
311
  });
227
- this.manager.open(id, {
312
+ const record = this.manager.open(id, {
228
313
  title: options.title ?? '',
229
- dockMode: options.dockMode ?? DockMode.WorldLocked,
314
+ dockMode,
315
+ // Chrome buttons are opt-in - see WindowOptionsBase.
316
+ chrome: {
317
+ close: options.closable ?? false,
318
+ minimize: options.minimizable ?? false,
319
+ pin: options.pinnable ?? false,
320
+ dock: options.dockable ?? false,
321
+ },
322
+ ...(options.handMenu !== undefined ? { handMenu: options.handMenu } : {}),
230
323
  });
231
324
  this.wireChrome(options, handle);
325
+ this.applyChrome(record);
232
326
  if (options.region !== undefined) {
233
327
  this.dock(id, options.region);
234
328
  }
@@ -252,14 +346,29 @@ export class UixWindowHost {
252
346
  }
253
347
  /** Drive per-frame from the engine loop (delta in SECONDS). */
254
348
  update(deltaSeconds) {
349
+ let hands;
255
350
  for (const [id, state] of this.states) {
256
351
  state.handle.document.update(deltaSeconds);
257
352
  const record = this.manager.get(id);
353
+ if (!record) {
354
+ continue;
355
+ }
258
356
  const docked = this.regions.regionOf(id) !== undefined;
259
- if (record &&
260
- record.dockMode === DockMode.BodyFollow &&
261
- !record.dragging &&
262
- !docked) {
357
+ if (record.dockMode === DockMode.HandLocked && !record.dragging && !docked) {
358
+ if (this.handPose && (this.handPose.hasHands?.() ?? true)) {
359
+ hands ??= this.readHands();
360
+ this.placeOnHand(id, state, hands);
361
+ }
362
+ else {
363
+ // No hands here (a desktop, or no session yet): the menu follows
364
+ // the body instead, and the gate does not apply.
365
+ state.gateOpen = true;
366
+ this.applyPresentation(id);
367
+ this.applyFollow(state.handle.group, state.options, deltaSeconds, true);
368
+ }
369
+ continue;
370
+ }
371
+ if (record.dockMode === DockMode.BodyFollow && !record.dragging && !docked) {
263
372
  this.applyFollow(state.handle.group, state.options, deltaSeconds, true);
264
373
  }
265
374
  }
@@ -274,6 +383,30 @@ export class UixWindowHost {
274
383
  }
275
384
  }
276
385
  }
386
+ /** This frame's tracked hands from the source, absent ones left out. */
387
+ readHands() {
388
+ const poses = {};
389
+ for (const hand of ['left', 'right']) {
390
+ const pose = this.handPose?.getHandPose(hand);
391
+ if (pose) {
392
+ poses[hand] = pose;
393
+ }
394
+ }
395
+ return poses;
396
+ }
397
+ placeOnHand(id, state, hands) {
398
+ const record = this.manager.get(id);
399
+ if (!record) {
400
+ return;
401
+ }
402
+ const placement = evaluateHandMenu(hands, this.headPose.getHeadPose().position, record.handMenu);
403
+ state.gateOpen = placement.visible;
404
+ if (placement.pose) {
405
+ state.handle.group.position.set(...placement.pose.position);
406
+ state.handle.group.quaternion.set(...placement.pose.quaternion);
407
+ }
408
+ this.applyPresentation(id);
409
+ }
277
410
  applyFollow(group, options, deltaSeconds, faceViewer) {
278
411
  const pose = this.headPose.getHeadPose();
279
412
  const target = followTarget(pose, options.followOffset);
@@ -339,51 +472,62 @@ export class UixWindowHost {
339
472
  this.manager.focus(id);
340
473
  }
341
474
  });
342
- const close = element(WINDOW_CHROME_IDS.close);
343
- if (options.closable === false) {
344
- close?.setProperties({ display: 'none' });
345
- }
346
- else {
347
- close?.addEventListener('click', () => {
348
- if (this.manager.has(id)) {
349
- this.manager.close(id);
350
- }
351
- });
352
- }
353
- const minimize = element(WINDOW_CHROME_IDS.minimize);
354
- if (options.minimizable === false) {
355
- minimize?.setProperties({ display: 'none' });
356
- }
357
- else {
358
- minimize?.addEventListener('click', () => {
359
- if (this.manager.has(id)) {
360
- this.manager.toggleMinimized(id);
361
- }
362
- });
363
- this.syncMinimizeLabel(id);
364
- }
365
- const pin = element(WINDOW_CHROME_IDS.pin);
366
475
  const state = this.states.get(id);
367
476
  if (state) {
368
477
  state.content = element(WINDOW_CHROME_IDS.content);
369
- state.pin = pin;
478
+ state.pin = element(WINDOW_CHROME_IDS.pin);
370
479
  state.minimize = element(WINDOW_CHROME_IDS.minimize);
480
+ state.buttons = {
481
+ close: element(WINDOW_CHROME_IDS.close),
482
+ minimize: element(WINDOW_CHROME_IDS.minimize),
483
+ pin: element(WINDOW_CHROME_IDS.pin),
484
+ dock: element(WINDOW_CHROME_IDS.dock),
485
+ };
371
486
  }
372
- if (options.pinnable === false) {
373
- pin?.setProperties({ display: 'none' });
374
- }
375
- else {
376
- pin?.addEventListener('click', () => {
377
- if (!this.manager.has(id)) {
378
- return;
379
- }
487
+ // Every button is wired once and gated on the record at click time, so
488
+ // enabling a button later (manager.setChrome) needs no rewiring.
489
+ const enabled = (key) => this.manager.get(id)?.chrome[key] === true;
490
+ element(WINDOW_CHROME_IDS.close)?.addEventListener('click', () => {
491
+ if (enabled('close')) {
492
+ this.manager.close(id);
493
+ }
494
+ });
495
+ element(WINDOW_CHROME_IDS.minimize)?.addEventListener('click', () => {
496
+ if (enabled('minimize')) {
497
+ this.manager.toggleMinimized(id);
498
+ }
499
+ });
500
+ element(WINDOW_CHROME_IDS.pin)?.addEventListener('click', () => {
501
+ if (enabled('pin')) {
380
502
  // Pinning a docked window pops it out of its region first.
381
- if (this.regions.regionOf(id) !== undefined) {
382
- this.regions.undock(id);
383
- }
503
+ this.manager.undock(id);
384
504
  this.manager.togglePin(id);
385
- });
386
- this.syncPinLabel(id);
505
+ }
506
+ });
507
+ element(WINDOW_CHROME_IDS.dock)?.addEventListener('click', () => {
508
+ if (enabled('dock')) {
509
+ this.manager.returnHome(id);
510
+ }
511
+ });
512
+ this.syncMinimizeLabel(id);
513
+ this.syncPinLabel(id);
514
+ }
515
+ /** Show exactly the enabled buttons. */
516
+ applyChrome(record) {
517
+ const buttons = this.states.get(record.id)?.buttons;
518
+ if (!buttons) {
519
+ return;
520
+ }
521
+ for (const key of ['close', 'minimize', 'pin', 'dock']) {
522
+ buttons[key]?.setProperties({ display: record.chrome[key] ? 'flex' : 'none' });
523
+ }
524
+ }
525
+ /** Drawn exactly when not hidden and (for a hand menu) the palm gate is open. */
526
+ applyPresentation(id) {
527
+ const state = this.states.get(id);
528
+ const record = this.manager.get(id);
529
+ if (state && record) {
530
+ state.handle.group.visible = !record.hidden && state.gateOpen;
387
531
  }
388
532
  }
389
533
  setContentCollapsed(id, collapsed) {
package/dist/host.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UixWindowHost - the engine binding for plain three.js / XR Blocks scenes.\n *\n * Owns a core `WindowManager` + `RegionRegistry` and applies their decisions\n * to the scene graph: spawn UIKitML windows and dock regions, wire chrome\n * buttons (focus / PIN / MIN / X), collapse content while minimized, place\n * docked windows into their region's slots, and ease `body-follow` windows\n * (and body-locked regions) toward the viewer each frame.\n *\n * It implements the core's `WindowHost` (so app code can wire behaviour\n * through `onPanelReady` with no engine knowledge) and `SceneTarget` (so a\n * portable `SceneDescriptor` builds the same playground here as on IWSDK).\n */\nimport { Group, type Object3D } from 'three';\nimport { parse, type Kit } from '@pmndrs/uikitml';\n\n/**\n * Default `config` resolver: fetch UIKitML source and parse it.\n *\n * IWSDK 0.5 dropped the build-time compile step and parses `PanelUI.config`\n * as source at runtime, so descriptors carry `.uikitml` paths. This parses the\n * same file for the three.js and XR Blocks hosts, which interpret an AST.\n */\nasync function loadUikitmlSource(path: string): Promise<unknown> {\n const response = await fetch(path);\n if (!response.ok) {\n throw new Error(`UIKitML source ${path} -> HTTP ${response.status}`);\n }\n return parse(await response.text());\n}\nimport {\n DockMode,\n RegionRegistry,\n WINDOW_CHROME_IDS,\n WindowManager,\n minimizeLabelFor,\n pinLabelFor,\n slotOffset,\n type HeadPoseSource,\n type PanelHandle,\n type PanelReadyEvent,\n type RegionDefinition,\n type SceneRegion,\n type SceneTarget,\n type SceneWindow,\n type UixElement,\n type Vec3Tuple,\n type WindowHandle as CoreWindowHandle,\n type WindowHost,\n type WindowOptionsBase,\n} from '@realitycollective/webxr-uiextensions';\nimport {\n approach,\n approachAlpha,\n distanceSquared,\n followTarget,\n} from './follow-math.js';\nimport { UixPanelDocument } from './panel-document.js';\n\n/**\n * Options for {@link UixWindowHost.createWindow}.\n *\n * Everything but `config` comes from the portable {@link WindowOptionsBase},\n * so an option means the same thing here as on the IWSDK adapter. `id` is\n * optional: leave it out and the host names the window `uix-window-<n>`.\n *\n * One option is accepted but not acted on: `movable`. This host has no\n * title-bar drag of its own yet - dragging comes from the XR Blocks / desktop\n * input layer above it - so the flag is recorded and otherwise ignored.\n */\nexport interface CreateWindowOptions extends WindowOptionsBase {\n /** Compiled UIKitML JSON (the `{ element, classes }` shape). */\n config: unknown;\n}\n\nexport interface CreateRegionOptions extends Partial<RegionDefinition> {\n id: string;\n position?: Vec3Tuple;\n /** Body-lock the region so it follows the viewer. */\n follow?: boolean;\n followOffset?: Vec3Tuple;\n}\n\n/**\n * A window spawned by {@link UixWindowHost.createWindow}.\n *\n * Satisfies the core {@link CoreWindowHandle} and adds the three.js specifics.\n * `panel` is the document itself, which already implements `PanelHandle`, and\n * is never `undefined` here: uikitml interprets the markup synchronously, so\n * the panel exists the moment `createWindow` returns.\n */\nexport interface XrBlocksWindowHandle extends CoreWindowHandle {\n readonly id: string;\n /** The scene-graph node - position/rotate freely. */\n group: Group;\n document: UixPanelDocument;\n /** Same object as {@link document}, under the portable name. */\n readonly panel: UixPanelDocument;\n onReady(listener: (panel: PanelHandle) => void): () => void;\n}\n\n/** Back-compatible name for {@link XrBlocksWindowHandle}. */\nexport type WindowHandle = XrBlocksWindowHandle;\n\nexport interface RegionHandle {\n id: string;\n group: Group;\n}\n\nexport interface UixWindowHostOptions {\n /** Parent for spawned windows (the scene, or any group inside it). */\n scene: Object3D;\n /** Viewer pose provider - camera on desktop, HMD pose in XR. */\n headPose: HeadPoseSource;\n /** Optional UIKitML component kit(s) (e.g. horizon kit). */\n kit?: Kit;\n /**\n * Resolver for a window's `config` when it arrives as a string path (as\n * portable `SceneDescriptor`s carry it).\n *\n * Defaults to fetching the `.uikitml` source and parsing it, which is the\n * same artefact IWSDK loads - so one markup file serves every adapter and\n * no build step is required. Override only for an unusual transport; you do\n * not need to supply this to load a normal panel.\n */\n loadConfig?: (path: string) => Promise<unknown>;\n}\n\ninterface FollowOptions {\n followOffset: Vec3Tuple;\n followSpeed: number;\n followTolerance: number;\n}\n\ninterface WindowState {\n handle: XrBlocksWindowHandle;\n /**\n * Recorded from the create options and not acted on - see\n * {@link CreateWindowOptions}. Kept so a future drag path here, or a caller\n * inspecting the host, reads the value the scene descriptor asked for.\n */\n movable: boolean;\n options: FollowOptions;\n content: UixElement | undefined;\n pin: UixElement | undefined;\n minimize: UixElement | undefined;\n}\n\ninterface RegionState {\n handle: RegionHandle;\n follow: boolean;\n followOffset: Vec3Tuple;\n}\n\nconst DEFAULT_FOLLOW_OFFSET: Vec3Tuple = [0, -0.15, -1.2];\nconst DEFAULT_REGION_FOLLOW_OFFSET: Vec3Tuple = [0, -0.2, -1.4];\n\nexport class UixWindowHost implements WindowHost, SceneTarget {\n /** Bare panels work here - the host does not own a panel lifecycle. */\n readonly supportsStandalonePanels: boolean = true;\n readonly manager = new WindowManager();\n readonly regions = new RegionRegistry();\n private readonly scene: Object3D;\n private readonly headPose: HeadPoseSource;\n private readonly kit: Kit | undefined;\n private readonly loadConfig: (path: string) => Promise<unknown>;\n private readonly states = new Map<string, WindowState>();\n private readonly regionStates = new Map<string, RegionState>();\n private readonly readyListeners = new Set<(event: PanelReadyEvent) => void>();\n /** Panels already live - replayed to late `onPanelReady` subscribers. */\n private readonly ready = new Map<string, PanelReadyEvent>();\n /** Feeds `uix-window-<n>` ids to windows created without one. */\n private windowSequence = 0;\n\n constructor(options: UixWindowHostOptions) {\n this.scene = options.scene;\n this.headPose = options.headPose;\n this.kit = options.kit;\n this.loadConfig = options.loadConfig ?? loadUikitmlSource;\n\n this.manager.events.on('closed', (record) => {\n const state = this.states.get(record.id);\n if (state) {\n this.states.delete(record.id);\n this.ready.delete(record.id);\n this.regions.undock(record.id);\n state.handle.document.dispose();\n state.handle.group.removeFromParent();\n this.layoutRegions();\n }\n });\n this.manager.events.on('minimized', (record) => {\n this.setContentCollapsed(record.id, true);\n this.syncMinimizeLabel(record.id);\n });\n this.manager.events.on('restored', (record) => {\n this.setContentCollapsed(record.id, false);\n this.syncMinimizeLabel(record.id);\n });\n this.manager.events.on('dockChanged', ({ window }) => {\n this.syncPinLabel(window.id);\n });\n }\n\n // --- WindowHost -----------------------------------------------------------\n\n /**\n * PanelHost - bare panel, unmanaged (used by devtools and tests). Always\n * available here, which is what `supportsStandalonePanels` reports.\n */\n createPanel(configJson: unknown): UixPanelDocument {\n return new UixPanelDocument(configJson, this.kit);\n }\n\n /**\n * Observe panel readiness. Windows already live are replayed immediately,\n * so wiring code never races window creation.\n */\n onPanelReady(listener: (event: PanelReadyEvent) => void): () => void {\n this.readyListeners.add(listener);\n for (const event of this.ready.values()) {\n listener(event);\n }\n return () => void this.readyListeners.delete(listener);\n }\n\n // --- SceneTarget (portable scene descriptors) -----------------------------\n\n spawnRegion(region: SceneRegion): void {\n this.createRegion({\n id: region.id,\n ...(region.flow !== undefined ? { flow: region.flow } : {}),\n ...(region.pitch !== undefined ? { pitch: region.pitch } : {}),\n ...(region.columns !== undefined ? { columns: region.columns } : {}),\n ...(region.capacity !== undefined ? { capacity: region.capacity } : {}),\n ...(region.snapRadius !== undefined ? { snapRadius: region.snapRadius } : {}),\n ...(region.position !== undefined\n ? { position: [...region.position] as Vec3Tuple }\n : {}),\n ...(region.follow !== undefined ? { follow: region.follow } : {}),\n ...(region.followOffset !== undefined\n ? { followOffset: [...region.followOffset] as Vec3Tuple }\n : {}),\n });\n }\n\n /**\n * Scene descriptors carry config PATHS; this resolves the path and spawns\n * the window when it arrives. Fire-and-forget by design - subscribe with\n * {@link onPanelReady} to wire behaviour once the panel exists.\n */\n spawnWindow(window: SceneWindow): void {\n void this.loadConfig(window.config)\n .then((config) => {\n this.createWindow({\n id: window.id,\n config,\n title: window.title,\n ...(window.position !== undefined\n ? { position: [...window.position] as Vec3Tuple }\n : {}),\n ...(window.maxWidth !== undefined ? { maxWidth: window.maxWidth } : {}),\n ...(window.maxHeight !== undefined ? { maxHeight: window.maxHeight } : {}),\n ...(window.dockMode !== undefined ? { dockMode: window.dockMode } : {}),\n ...(window.region !== undefined ? { region: window.region } : {}),\n ...(window.followOffset !== undefined\n ? { followOffset: [...window.followOffset] as Vec3Tuple }\n : {}),\n ...(window.followSpeed !== undefined\n ? { followSpeed: window.followSpeed }\n : {}),\n ...(window.followTolerance !== undefined\n ? { followTolerance: window.followTolerance }\n : {}),\n ...(window.movable !== undefined ? { movable: window.movable } : {}),\n ...(window.closable !== undefined ? { closable: window.closable } : {}),\n ...(window.minimizable !== undefined\n ? { minimizable: window.minimizable }\n : {}),\n ...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),\n });\n })\n .catch((error) => {\n console.error(`[uix] failed to load panel \"${window.id}\":`, error);\n });\n }\n\n // --- Regions --------------------------------------------------------------\n\n /** Create a dock region anchored in the scene. */\n createRegion(options: CreateRegionOptions): RegionHandle {\n const { id, position, follow, followOffset, ...definition } = options;\n this.regions.register(id, definition);\n\n const group = new Group();\n group.name = `uix-region:${id}`;\n if (position) {\n group.position.set(...position);\n }\n this.scene.add(group);\n\n const handle: RegionHandle = { id, group };\n this.regionStates.set(id, {\n handle,\n follow: follow ?? false,\n followOffset: followOffset ?? DEFAULT_REGION_FOLLOW_OFFSET,\n });\n return handle;\n }\n\n region(id: string): RegionHandle | undefined {\n return this.regionStates.get(id)?.handle;\n }\n\n /** Dock a window into a region (or undock it with `undefined`). */\n dock(windowId: string, regionId: string | undefined): void {\n if (regionId === undefined) {\n this.regions.undock(windowId);\n } else {\n this.regions.dock(windowId, regionId);\n this.manager.setDockMode(windowId, DockMode.WorldLocked);\n }\n this.layoutRegions();\n }\n\n // --- Windows --------------------------------------------------------------\n\n /** Spawn a managed window from compiled UIKitML JSON. */\n createWindow(options: CreateWindowOptions): XrBlocksWindowHandle {\n const id = options.id ?? `uix-window-${(this.windowSequence += 1)}`;\n const document = new UixPanelDocument(options.config, this.kit);\n document.setTargetDimensions(options.maxWidth ?? 1, options.maxHeight ?? 1);\n\n const group = new Group();\n group.name = `uix-window:${id}`;\n group.add(document);\n if (options.position) {\n group.position.set(...options.position);\n }\n this.scene.add(group);\n\n const handle: XrBlocksWindowHandle = {\n id,\n group,\n document,\n panel: document,\n // The panel exists already, so readiness is immediate. The unsubscribe\n // is returned anyway, so callers can write one shape of code for both\n // adapters.\n onReady(listener: (panel: PanelHandle) => void): () => void {\n listener(document);\n return () => {};\n },\n };\n this.states.set(id, {\n handle,\n movable: options.movable ?? true,\n options: {\n followOffset: options.followOffset ?? DEFAULT_FOLLOW_OFFSET,\n followSpeed: options.followSpeed ?? 3,\n followTolerance: options.followTolerance ?? 0.35,\n },\n content: undefined,\n pin: undefined,\n minimize: undefined,\n });\n\n this.manager.open(id, {\n title: options.title ?? '',\n dockMode: options.dockMode ?? DockMode.WorldLocked,\n });\n this.wireChrome(options, handle);\n\n if (options.region !== undefined) {\n this.dock(id, options.region);\n }\n\n // The panel's element tree exists synchronously (uikitml interprets on\n // construction); only its LAYOUT is async. Announce readiness now so\n // wiring can attach handlers immediately.\n const event: PanelReadyEvent = { id, panel: document, kind: 'window' };\n this.ready.set(id, event);\n for (const listener of this.readyListeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[uix] panel-ready listener failed for \"${id}\":`, error);\n }\n }\n\n return handle;\n }\n\n window(id: string): XrBlocksWindowHandle | undefined {\n return this.states.get(id)?.handle;\n }\n\n /** Drive per-frame from the engine loop (delta in SECONDS). */\n update(deltaSeconds: number): void {\n for (const [id, state] of this.states) {\n state.handle.document.update(deltaSeconds);\n const record = this.manager.get(id);\n const docked = this.regions.regionOf(id) !== undefined;\n if (\n record &&\n record.dockMode === DockMode.BodyFollow &&\n !record.dragging &&\n !docked\n ) {\n this.applyFollow(state.handle.group, state.options, deltaSeconds, true);\n }\n }\n for (const state of this.regionStates.values()) {\n if (state.follow) {\n this.applyFollow(\n state.handle.group,\n {\n followOffset: state.followOffset,\n followSpeed: 3,\n followTolerance: 0.2,\n },\n deltaSeconds,\n false,\n );\n this.layoutRegion(state.handle.id);\n }\n }\n }\n\n private applyFollow(\n group: Group,\n options: FollowOptions,\n deltaSeconds: number,\n faceViewer: boolean,\n ): void {\n const pose = this.headPose.getHeadPose();\n const target = followTarget(pose, options.followOffset);\n const position = group.position;\n const current: Vec3Tuple = [position.x, position.y, position.z];\n const tolerance = options.followTolerance;\n if (distanceSquared(current, target) <= tolerance * tolerance) {\n return;\n }\n const next = approach(\n current,\n target,\n approachAlpha(options.followSpeed, deltaSeconds),\n );\n position.set(...next);\n if (faceViewer) {\n group.lookAt(pose.position[0], position.y, pose.position[2]);\n }\n }\n\n /** Re-place every docked window into its region's slot. */\n private layoutRegions(): void {\n for (const id of this.regionStates.keys()) {\n this.layoutRegion(id);\n }\n }\n\n private layoutRegion(regionId: string): void {\n const regionState = this.regionStates.get(regionId);\n const registered = this.regions.get(regionId);\n if (!regionState || !registered) {\n return;\n }\n const origin = regionState.handle.group.position;\n registered.members.forEach((windowId, index) => {\n const windowState = this.states.get(windowId);\n if (!windowState) {\n return;\n }\n const [dx, dy, dz] = slotOffset(registered.definition, index);\n windowState.handle.group.position.set(\n origin.x + dx,\n origin.y + dy,\n origin.z + dz,\n );\n windowState.handle.group.quaternion.copy(regionState.handle.group.quaternion);\n });\n }\n\n private wireChrome(\n options: CreateWindowOptions,\n handle: XrBlocksWindowHandle,\n ): void {\n const element = (id: string) => handle.document.getElementById(id);\n const id = handle.id;\n\n const title = element(WINDOW_CHROME_IDS.title);\n if (title && options.title) {\n title.setProperties({ text: options.title });\n }\n\n // Chrome presses must not bubble to the window root's focus handler:\n // a press on CLOSE would otherwise focus a window that is about to be\n // destroyed, and `focus()` on a removed window throws. (Same guard the\n // IWSDK adapter applies.)\n const swallowPress = (button: UixElement | undefined): void => {\n button?.addEventListener('pointerdown', (event) => {\n (event as { stopPropagation?: () => void } | undefined)?.stopPropagation?.();\n });\n };\n swallowPress(element(WINDOW_CHROME_IDS.pin));\n swallowPress(element(WINDOW_CHROME_IDS.dock));\n swallowPress(element(WINDOW_CHROME_IDS.minimize));\n swallowPress(element(WINDOW_CHROME_IDS.close));\n\n // Any press anywhere on the window brings it to the front. Guarded:\n // events can still arrive for a window that was just closed.\n element(WINDOW_CHROME_IDS.window)?.addEventListener('pointerdown', () => {\n if (this.manager.has(id)) {\n this.manager.focus(id);\n }\n });\n\n const close = element(WINDOW_CHROME_IDS.close);\n if (options.closable === false) {\n close?.setProperties({ display: 'none' });\n } else {\n close?.addEventListener('click', () => {\n if (this.manager.has(id)) {\n this.manager.close(id);\n }\n });\n }\n\n const minimize = element(WINDOW_CHROME_IDS.minimize);\n if (options.minimizable === false) {\n minimize?.setProperties({ display: 'none' });\n } else {\n minimize?.addEventListener('click', () => {\n if (this.manager.has(id)) {\n this.manager.toggleMinimized(id);\n }\n });\n this.syncMinimizeLabel(id);\n }\n\n const pin = element(WINDOW_CHROME_IDS.pin);\n const state = this.states.get(id);\n if (state) {\n state.content = element(WINDOW_CHROME_IDS.content);\n state.pin = pin;\n state.minimize = element(WINDOW_CHROME_IDS.minimize);\n }\n if (options.pinnable === false) {\n pin?.setProperties({ display: 'none' });\n } else {\n pin?.addEventListener('click', () => {\n if (!this.manager.has(id)) {\n return;\n }\n // Pinning a docked window pops it out of its region first.\n if (this.regions.regionOf(id) !== undefined) {\n this.regions.undock(id);\n }\n this.manager.togglePin(id);\n });\n this.syncPinLabel(id);\n }\n }\n\n private setContentCollapsed(id: string, collapsed: boolean): void {\n this.states\n .get(id)\n ?.content?.setProperties({ display: collapsed ? 'none' : 'flex' });\n }\n\n /** MIN when open, MAX when minimized - the label names the next action. */\n private syncMinimizeLabel(id: string): void {\n const record = this.manager.get(id);\n const minimize = this.states.get(id)?.minimize;\n if (record && minimize) {\n minimize.setProperties({ text: minimizeLabelFor(record) });\n }\n }\n\n private syncPinLabel(id: string): void {\n const record = this.manager.get(id);\n const pin = this.states.get(id)?.pin;\n if (record && pin) {\n pin.setProperties({ text: pinLabelFor(record) });\n }\n }\n}\n"]}
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UixWindowHost - the engine binding for plain three.js / XR Blocks scenes.\n *\n * Owns a core `WindowManager` + `RegionRegistry` and applies their decisions\n * to the scene graph: spawn UIKitML windows and dock regions, wire chrome\n * buttons (focus / PIN / DOCK / MIN / X), collapse content while minimized,\n * hide and show, place docked windows into their region's slots, return a\n * window to where it spawned, ease `body-follow` windows (and body-locked\n * regions) toward the viewer each frame, and ride `hand-locked` windows\n * (hand menus) on a tracked hand behind the palm gate.\n *\n * The manager is the API app code drives; every manager event is applied\n * here, so `host.manager.hide(id)` or `.dockTo(id, region)` from a hand menu\n * is all a caller needs.\n *\n * It implements the core's `WindowHost` (so app code can wire behaviour\n * through `onPanelReady` with no engine knowledge) and `SceneTarget` (so a\n * portable `SceneDescriptor` builds the same playground here as on IWSDK).\n */\nimport { Group, type Object3D } from 'three';\nimport { parse, type Kit } from '@pmndrs/uikitml';\n\n/**\n * Default `config` resolver: fetch UIKitML source and parse it.\n *\n * IWSDK 0.5 dropped the build-time compile step and parses `PanelUI.config`\n * as source at runtime, so descriptors carry `.uikitml` paths. This parses the\n * same file for the three.js and XR Blocks hosts, which interpret an AST.\n */\nasync function loadUikitmlSource(path: string): Promise<unknown> {\n const response = await fetch(path);\n if (!response.ok) {\n throw new Error(`UIKitML source ${path} -> HTTP ${response.status}`);\n }\n return parse(await response.text());\n}\nimport {\n DockMode,\n RegionRegistry,\n WINDOW_CHROME_IDS,\n WindowManager,\n evaluateHandMenu,\n minimizeLabelFor,\n pinLabelFor,\n slotOffset,\n type HandPoseSource,\n type HandPoses,\n type HeadPoseSource,\n type PanelHandle,\n type PanelReadyEvent,\n type RegionDefinition,\n type WindowChrome,\n type WindowRecord,\n type SceneRegion,\n type SceneTarget,\n type SceneWindow,\n type UixElement,\n type Vec3Tuple,\n type WindowHandle as CoreWindowHandle,\n type WindowHost,\n type WindowOptionsBase,\n} from '@realitycollective/webxr-uiextensions';\nimport {\n approach,\n approachAlpha,\n distanceSquared,\n followTarget,\n} from './follow-math.js';\nimport { UixPanelDocument } from './panel-document.js';\n\n/**\n * Options for {@link UixWindowHost.createWindow}.\n *\n * Everything but `config` comes from the portable {@link WindowOptionsBase},\n * so an option means the same thing here as on the IWSDK adapter. `id` is\n * optional: leave it out and the host names the window `uix-window-<n>`.\n *\n * One option is accepted but not acted on: `movable`. This host has no\n * title-bar drag of its own yet - dragging comes from the XR Blocks / desktop\n * input layer above it - so the flag is recorded and otherwise ignored.\n */\nexport interface CreateWindowOptions extends WindowOptionsBase {\n /** Compiled UIKitML JSON (the `{ element, classes }` shape). */\n config: unknown;\n}\n\nexport interface CreateRegionOptions extends Partial<RegionDefinition> {\n id: string;\n position?: Vec3Tuple;\n /** Body-lock the region so it follows the viewer. */\n follow?: boolean;\n followOffset?: Vec3Tuple;\n}\n\n/**\n * A window spawned by {@link UixWindowHost.createWindow}.\n *\n * Satisfies the core {@link CoreWindowHandle} and adds the three.js specifics.\n * `panel` is the document itself, which already implements `PanelHandle`, and\n * is never `undefined` here: uikitml interprets the markup synchronously, so\n * the panel exists the moment `createWindow` returns.\n */\nexport interface XrBlocksWindowHandle extends CoreWindowHandle {\n readonly id: string;\n /** The scene-graph node - position/rotate freely. */\n group: Group;\n document: UixPanelDocument;\n /** Same object as {@link document}, under the portable name. */\n readonly panel: UixPanelDocument;\n onReady(listener: (panel: PanelHandle) => void): () => void;\n}\n\n/** Back-compatible name for {@link XrBlocksWindowHandle}. */\nexport type WindowHandle = XrBlocksWindowHandle;\n\nexport interface RegionHandle {\n id: string;\n group: Group;\n}\n\nexport interface UixWindowHostOptions {\n /** Parent for spawned windows (the scene, or any group inside it). */\n scene: Object3D;\n /** Viewer pose provider - camera on desktop, HMD pose in XR. */\n headPose: HeadPoseSource;\n /**\n * Tracked-hand pose provider, for `hand-locked` windows (hand menus). See\n * `webxrHandPoseSource` for one backed by a WebXR session. Leave it out\n * where there are no hands (a desktop) and hand-locked windows fall back\n * to body-follow placement, so the same scene still shows its menus.\n */\n handPose?: HandPoseSource;\n /** Optional UIKitML component kit(s) (e.g. horizon kit). */\n kit?: Kit;\n /**\n * Resolver for a window's `config` when it arrives as a string path (as\n * portable `SceneDescriptor`s carry it).\n *\n * Defaults to fetching the `.uikitml` source and parsing it, which is the\n * same artefact IWSDK loads - so one markup file serves every adapter and\n * no build step is required. Override only for an unusual transport; you do\n * not need to supply this to load a normal panel.\n */\n loadConfig?: (path: string) => Promise<unknown>;\n}\n\ninterface FollowOptions {\n followOffset: Vec3Tuple;\n followSpeed: number;\n followTolerance: number;\n}\n\ninterface WindowState {\n handle: XrBlocksWindowHandle;\n /** Where the window spawned, for `returnHome`. */\n home: { region: string | undefined; position: Vec3Tuple; dockMode: WindowRecord['dockMode'] };\n /**\n * Recorded from the create options and not acted on - see\n * {@link CreateWindowOptions}. Kept so a future drag path here, or a caller\n * inspecting the host, reads the value the scene descriptor asked for.\n */\n movable: boolean;\n options: FollowOptions;\n content: UixElement | undefined;\n pin: UixElement | undefined;\n minimize: UixElement | undefined;\n /** Every chrome button by its role, for `chromeChanged`. */\n buttons: Record<keyof WindowChrome, UixElement | undefined>;\n /** Hand-menu palm gate this frame; always open in the other dock modes. */\n gateOpen: boolean;\n}\n\ninterface RegionState {\n handle: RegionHandle;\n follow: boolean;\n followOffset: Vec3Tuple;\n}\n\nconst DEFAULT_FOLLOW_OFFSET: Vec3Tuple = [0, -0.15, -1.2];\nconst DEFAULT_REGION_FOLLOW_OFFSET: Vec3Tuple = [0, -0.2, -1.4];\n\nexport class UixWindowHost implements WindowHost, SceneTarget {\n /** Bare panels work here - the host does not own a panel lifecycle. */\n readonly supportsStandalonePanels: boolean = true;\n readonly manager = new WindowManager();\n readonly regions = new RegionRegistry();\n private readonly scene: Object3D;\n private readonly headPose: HeadPoseSource;\n private readonly handPose: HandPoseSource | undefined;\n private readonly kit: Kit | undefined;\n private readonly loadConfig: (path: string) => Promise<unknown>;\n private readonly states = new Map<string, WindowState>();\n private readonly regionStates = new Map<string, RegionState>();\n private readonly readyListeners = new Set<(event: PanelReadyEvent) => void>();\n /** Panels already live - replayed to late `onPanelReady` subscribers. */\n private readonly ready = new Map<string, PanelReadyEvent>();\n /** Feeds `uix-window-<n>` ids to windows created without one. */\n private windowSequence = 0;\n\n constructor(options: UixWindowHostOptions) {\n this.scene = options.scene;\n this.headPose = options.headPose;\n this.handPose = options.handPose;\n this.kit = options.kit;\n this.loadConfig = options.loadConfig ?? loadUikitmlSource;\n\n this.manager.events.on('closed', (record) => {\n const state = this.states.get(record.id);\n if (state) {\n this.states.delete(record.id);\n this.ready.delete(record.id);\n this.regions.undock(record.id);\n state.handle.document.dispose();\n state.handle.group.removeFromParent();\n this.layoutRegions();\n }\n });\n this.manager.events.on('hidden', (record) => {\n this.applyPresentation(record.id);\n });\n this.manager.events.on('shown', (record) => {\n this.applyPresentation(record.id);\n });\n this.manager.events.on('dockChanged', ({ window, previous }) => {\n if (previous === DockMode.HandLocked) {\n // Off the hand: the gate no longer applies.\n const state = this.states.get(window.id);\n if (state) {\n state.gateOpen = true;\n this.applyPresentation(window.id);\n }\n }\n });\n this.manager.events.on('regionChanged', ({ window }) => {\n this.applyRegion(window);\n });\n this.manager.events.on('returnHome', (record) => {\n this.returnHome(record.id);\n });\n this.manager.events.on('chromeChanged', ({ window }) => {\n this.applyChrome(window);\n });\n this.manager.events.on('minimized', (record) => {\n this.setContentCollapsed(record.id, true);\n this.syncMinimizeLabel(record.id);\n });\n this.manager.events.on('restored', (record) => {\n this.setContentCollapsed(record.id, false);\n this.syncMinimizeLabel(record.id);\n });\n this.manager.events.on('dockChanged', ({ window }) => {\n this.syncPinLabel(window.id);\n });\n }\n\n // --- WindowHost -----------------------------------------------------------\n\n /**\n * PanelHost - bare panel, unmanaged (used by devtools and tests). Always\n * available here, which is what `supportsStandalonePanels` reports.\n */\n createPanel(configJson: unknown): UixPanelDocument {\n return new UixPanelDocument(configJson, this.kit);\n }\n\n /**\n * Observe panel readiness. Windows already live are replayed immediately,\n * so wiring code never races window creation.\n */\n onPanelReady(listener: (event: PanelReadyEvent) => void): () => void {\n this.readyListeners.add(listener);\n for (const event of this.ready.values()) {\n listener(event);\n }\n return () => void this.readyListeners.delete(listener);\n }\n\n // --- SceneTarget (portable scene descriptors) -----------------------------\n\n spawnRegion(region: SceneRegion): void {\n this.createRegion({\n id: region.id,\n ...(region.flow !== undefined ? { flow: region.flow } : {}),\n ...(region.pitch !== undefined ? { pitch: region.pitch } : {}),\n ...(region.columns !== undefined ? { columns: region.columns } : {}),\n ...(region.capacity !== undefined ? { capacity: region.capacity } : {}),\n ...(region.snapRadius !== undefined ? { snapRadius: region.snapRadius } : {}),\n ...(region.position !== undefined\n ? { position: [...region.position] as Vec3Tuple }\n : {}),\n ...(region.follow !== undefined ? { follow: region.follow } : {}),\n ...(region.followOffset !== undefined\n ? { followOffset: [...region.followOffset] as Vec3Tuple }\n : {}),\n });\n }\n\n /**\n * Scene descriptors carry config PATHS; this resolves the path and spawns\n * the window when it arrives. Fire-and-forget by design - subscribe with\n * {@link onPanelReady} to wire behaviour once the panel exists.\n */\n spawnWindow(window: SceneWindow): void {\n void this.loadConfig(window.config)\n .then((config) => {\n this.createWindow({\n id: window.id,\n config,\n title: window.title,\n ...(window.position !== undefined\n ? { position: [...window.position] as Vec3Tuple }\n : {}),\n ...(window.maxWidth !== undefined ? { maxWidth: window.maxWidth } : {}),\n ...(window.maxHeight !== undefined ? { maxHeight: window.maxHeight } : {}),\n ...(window.dockMode !== undefined ? { dockMode: window.dockMode } : {}),\n ...(window.region !== undefined ? { region: window.region } : {}),\n ...(window.followOffset !== undefined\n ? { followOffset: [...window.followOffset] as Vec3Tuple }\n : {}),\n ...(window.followSpeed !== undefined\n ? { followSpeed: window.followSpeed }\n : {}),\n ...(window.followTolerance !== undefined\n ? { followTolerance: window.followTolerance }\n : {}),\n ...(window.movable !== undefined ? { movable: window.movable } : {}),\n ...(window.closable !== undefined ? { closable: window.closable } : {}),\n ...(window.minimizable !== undefined\n ? { minimizable: window.minimizable }\n : {}),\n ...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),\n ...(window.dockable !== undefined ? { dockable: window.dockable } : {}),\n ...(window.handMenu !== undefined ? { handMenu: window.handMenu } : {}),\n });\n })\n .catch((error) => {\n console.error(`[uix] failed to load panel \"${window.id}\":`, error);\n });\n }\n\n // --- Regions --------------------------------------------------------------\n\n /** Create a dock region anchored in the scene. */\n createRegion(options: CreateRegionOptions): RegionHandle {\n const { id, position, follow, followOffset, ...definition } = options;\n this.regions.register(id, definition);\n\n const group = new Group();\n group.name = `uix-region:${id}`;\n if (position) {\n group.position.set(...position);\n }\n this.scene.add(group);\n\n const handle: RegionHandle = { id, group };\n this.regionStates.set(id, {\n handle,\n follow: follow ?? false,\n followOffset: followOffset ?? DEFAULT_REGION_FOLLOW_OFFSET,\n });\n return handle;\n }\n\n region(id: string): RegionHandle | undefined {\n return this.regionStates.get(id)?.handle;\n }\n\n /**\n * Dock a window into a region (or undock it with `undefined`). A thin\n * forwarder to the manager, kept so existing callers read the same;\n * `manager.dockTo` / `manager.undock` are the portable calls.\n */\n dock(windowId: string, regionId: string | undefined): void {\n if (regionId === undefined) {\n this.manager.undock(windowId);\n } else {\n this.manager.dockTo(windowId, regionId);\n }\n }\n\n /** Make the registry (and the layout) agree with the record's region. */\n private applyRegion(record: Pick<WindowRecord, 'id' | 'region'>): void {\n const current = this.regions.regionOf(record.id);\n if (current === record.region) {\n return;\n }\n if (current !== undefined) {\n this.regions.undock(record.id);\n }\n if (record.region !== undefined) {\n try {\n this.regions.dock(record.id, record.region);\n } catch (error) {\n // Unknown or full region: the record must not claim it.\n this.manager.undock(record.id);\n throw error;\n }\n this.manager.setDockMode(record.id, DockMode.WorldLocked);\n }\n this.layoutRegions();\n }\n\n /** Put a window back where it spawned: its region, or its placement and mode. */\n private returnHome(id: string): void {\n const state = this.states.get(id);\n if (!state) {\n return;\n }\n const { home } = state;\n if (home.region !== undefined) {\n this.manager.dockTo(id, home.region);\n return;\n }\n this.manager.undock(id);\n this.manager.setDockMode(id, home.dockMode);\n if (home.dockMode === DockMode.WorldLocked) {\n state.handle.group.position.set(...home.position);\n }\n // Follow modes ease back to the viewer on their own in update().\n }\n\n // --- Windows --------------------------------------------------------------\n\n /** Spawn a managed window from compiled UIKitML JSON. */\n createWindow(options: CreateWindowOptions): XrBlocksWindowHandle {\n const id = options.id ?? `uix-window-${(this.windowSequence += 1)}`;\n const document = new UixPanelDocument(options.config, this.kit);\n document.setTargetDimensions(options.maxWidth ?? 1, options.maxHeight ?? 1);\n\n const group = new Group();\n group.name = `uix-window:${id}`;\n group.add(document);\n if (options.position) {\n group.position.set(...options.position);\n }\n this.scene.add(group);\n\n const handle: XrBlocksWindowHandle = {\n id,\n group,\n document,\n panel: document,\n // The panel exists already, so readiness is immediate. The unsubscribe\n // is returned anyway, so callers can write one shape of code for both\n // adapters.\n onReady(listener: (panel: PanelHandle) => void): () => void {\n listener(document);\n return () => {};\n },\n };\n const dockMode = options.dockMode ?? DockMode.WorldLocked;\n this.states.set(id, {\n handle,\n home: {\n region: options.region,\n position: options.position ? [...options.position] : [0, 0, 0],\n dockMode,\n },\n movable: options.movable ?? true,\n options: {\n followOffset: options.followOffset ?? DEFAULT_FOLLOW_OFFSET,\n followSpeed: options.followSpeed ?? 3,\n followTolerance: options.followTolerance ?? 0.35,\n },\n content: undefined,\n pin: undefined,\n minimize: undefined,\n buttons: { pin: undefined, dock: undefined, minimize: undefined, close: undefined },\n gateOpen: true,\n });\n\n const record = this.manager.open(id, {\n title: options.title ?? '',\n dockMode,\n // Chrome buttons are opt-in - see WindowOptionsBase.\n chrome: {\n close: options.closable ?? false,\n minimize: options.minimizable ?? false,\n pin: options.pinnable ?? false,\n dock: options.dockable ?? false,\n },\n ...(options.handMenu !== undefined ? { handMenu: options.handMenu } : {}),\n });\n this.wireChrome(options, handle);\n this.applyChrome(record);\n\n if (options.region !== undefined) {\n this.dock(id, options.region);\n }\n\n // The panel's element tree exists synchronously (uikitml interprets on\n // construction); only its LAYOUT is async. Announce readiness now so\n // wiring can attach handlers immediately.\n const event: PanelReadyEvent = { id, panel: document, kind: 'window' };\n this.ready.set(id, event);\n for (const listener of this.readyListeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[uix] panel-ready listener failed for \"${id}\":`, error);\n }\n }\n\n return handle;\n }\n\n window(id: string): XrBlocksWindowHandle | undefined {\n return this.states.get(id)?.handle;\n }\n\n /** Drive per-frame from the engine loop (delta in SECONDS). */\n update(deltaSeconds: number): void {\n let hands: HandPoses | undefined;\n for (const [id, state] of this.states) {\n state.handle.document.update(deltaSeconds);\n const record = this.manager.get(id);\n if (!record) {\n continue;\n }\n const docked = this.regions.regionOf(id) !== undefined;\n if (record.dockMode === DockMode.HandLocked && !record.dragging && !docked) {\n if (this.handPose && (this.handPose.hasHands?.() ?? true)) {\n hands ??= this.readHands();\n this.placeOnHand(id, state, hands);\n } else {\n // No hands here (a desktop, or no session yet): the menu follows\n // the body instead, and the gate does not apply.\n state.gateOpen = true;\n this.applyPresentation(id);\n this.applyFollow(state.handle.group, state.options, deltaSeconds, true);\n }\n continue;\n }\n if (record.dockMode === DockMode.BodyFollow && !record.dragging && !docked) {\n this.applyFollow(state.handle.group, state.options, deltaSeconds, true);\n }\n }\n for (const state of this.regionStates.values()) {\n if (state.follow) {\n this.applyFollow(\n state.handle.group,\n {\n followOffset: state.followOffset,\n followSpeed: 3,\n followTolerance: 0.2,\n },\n deltaSeconds,\n false,\n );\n this.layoutRegion(state.handle.id);\n }\n }\n }\n\n /** This frame's tracked hands from the source, absent ones left out. */\n private readHands(): HandPoses {\n const poses: HandPoses = {};\n for (const hand of ['left', 'right'] as const) {\n const pose = this.handPose?.getHandPose(hand);\n if (pose) {\n poses[hand] = pose;\n }\n }\n return poses;\n }\n\n private placeOnHand(id: string, state: WindowState, hands: HandPoses): void {\n const record = this.manager.get(id);\n if (!record) {\n return;\n }\n const placement = evaluateHandMenu(hands, this.headPose.getHeadPose().position, record.handMenu);\n state.gateOpen = placement.visible;\n if (placement.pose) {\n state.handle.group.position.set(...placement.pose.position);\n state.handle.group.quaternion.set(...placement.pose.quaternion);\n }\n this.applyPresentation(id);\n }\n\n private applyFollow(\n group: Group,\n options: FollowOptions,\n deltaSeconds: number,\n faceViewer: boolean,\n ): void {\n const pose = this.headPose.getHeadPose();\n const target = followTarget(pose, options.followOffset);\n const position = group.position;\n const current: Vec3Tuple = [position.x, position.y, position.z];\n const tolerance = options.followTolerance;\n if (distanceSquared(current, target) <= tolerance * tolerance) {\n return;\n }\n const next = approach(\n current,\n target,\n approachAlpha(options.followSpeed, deltaSeconds),\n );\n position.set(...next);\n if (faceViewer) {\n group.lookAt(pose.position[0], position.y, pose.position[2]);\n }\n }\n\n /** Re-place every docked window into its region's slot. */\n private layoutRegions(): void {\n for (const id of this.regionStates.keys()) {\n this.layoutRegion(id);\n }\n }\n\n private layoutRegion(regionId: string): void {\n const regionState = this.regionStates.get(regionId);\n const registered = this.regions.get(regionId);\n if (!regionState || !registered) {\n return;\n }\n const origin = regionState.handle.group.position;\n registered.members.forEach((windowId, index) => {\n const windowState = this.states.get(windowId);\n if (!windowState) {\n return;\n }\n const [dx, dy, dz] = slotOffset(registered.definition, index);\n windowState.handle.group.position.set(\n origin.x + dx,\n origin.y + dy,\n origin.z + dz,\n );\n windowState.handle.group.quaternion.copy(regionState.handle.group.quaternion);\n });\n }\n\n private wireChrome(\n options: CreateWindowOptions,\n handle: XrBlocksWindowHandle,\n ): void {\n const element = (id: string) => handle.document.getElementById(id);\n const id = handle.id;\n\n const title = element(WINDOW_CHROME_IDS.title);\n if (title && options.title) {\n title.setProperties({ text: options.title });\n }\n\n // Chrome presses must not bubble to the window root's focus handler:\n // a press on CLOSE would otherwise focus a window that is about to be\n // destroyed, and `focus()` on a removed window throws. (Same guard the\n // IWSDK adapter applies.)\n const swallowPress = (button: UixElement | undefined): void => {\n button?.addEventListener('pointerdown', (event) => {\n (event as { stopPropagation?: () => void } | undefined)?.stopPropagation?.();\n });\n };\n swallowPress(element(WINDOW_CHROME_IDS.pin));\n swallowPress(element(WINDOW_CHROME_IDS.dock));\n swallowPress(element(WINDOW_CHROME_IDS.minimize));\n swallowPress(element(WINDOW_CHROME_IDS.close));\n\n // Any press anywhere on the window brings it to the front. Guarded:\n // events can still arrive for a window that was just closed.\n element(WINDOW_CHROME_IDS.window)?.addEventListener('pointerdown', () => {\n if (this.manager.has(id)) {\n this.manager.focus(id);\n }\n });\n\n const state = this.states.get(id);\n if (state) {\n state.content = element(WINDOW_CHROME_IDS.content);\n state.pin = element(WINDOW_CHROME_IDS.pin);\n state.minimize = element(WINDOW_CHROME_IDS.minimize);\n state.buttons = {\n close: element(WINDOW_CHROME_IDS.close),\n minimize: element(WINDOW_CHROME_IDS.minimize),\n pin: element(WINDOW_CHROME_IDS.pin),\n dock: element(WINDOW_CHROME_IDS.dock),\n };\n }\n\n // Every button is wired once and gated on the record at click time, so\n // enabling a button later (manager.setChrome) needs no rewiring.\n const enabled = (key: keyof WindowChrome): boolean =>\n this.manager.get(id)?.chrome[key] === true;\n\n element(WINDOW_CHROME_IDS.close)?.addEventListener('click', () => {\n if (enabled('close')) {\n this.manager.close(id);\n }\n });\n element(WINDOW_CHROME_IDS.minimize)?.addEventListener('click', () => {\n if (enabled('minimize')) {\n this.manager.toggleMinimized(id);\n }\n });\n element(WINDOW_CHROME_IDS.pin)?.addEventListener('click', () => {\n if (enabled('pin')) {\n // Pinning a docked window pops it out of its region first.\n this.manager.undock(id);\n this.manager.togglePin(id);\n }\n });\n element(WINDOW_CHROME_IDS.dock)?.addEventListener('click', () => {\n if (enabled('dock')) {\n this.manager.returnHome(id);\n }\n });\n this.syncMinimizeLabel(id);\n this.syncPinLabel(id);\n }\n\n /** Show exactly the enabled buttons. */\n private applyChrome(record: Pick<WindowRecord, 'id' | 'chrome'>): void {\n const buttons = this.states.get(record.id)?.buttons;\n if (!buttons) {\n return;\n }\n for (const key of ['close', 'minimize', 'pin', 'dock'] as const) {\n buttons[key]?.setProperties({ display: record.chrome[key] ? 'flex' : 'none' });\n }\n }\n\n /** Drawn exactly when not hidden and (for a hand menu) the palm gate is open. */\n private applyPresentation(id: string): void {\n const state = this.states.get(id);\n const record = this.manager.get(id);\n if (state && record) {\n state.handle.group.visible = !record.hidden && state.gateOpen;\n }\n }\n\n private setContentCollapsed(id: string, collapsed: boolean): void {\n this.states\n .get(id)\n ?.content?.setProperties({ display: collapsed ? 'none' : 'flex' });\n }\n\n /** MIN when open, MAX when minimized - the label names the next action. */\n private syncMinimizeLabel(id: string): void {\n const record = this.manager.get(id);\n const minimize = this.states.get(id)?.minimize;\n if (record && minimize) {\n minimize.setProperties({ text: minimizeLabelFor(record) });\n }\n }\n\n private syncPinLabel(id: string): void {\n const record = this.manager.get(id);\n const pin = this.states.get(id)?.pin;\n if (record && pin) {\n pin.setProperties({ text: pinLabelFor(record) });\n }\n }\n}\n"]}
@@ -19,7 +19,7 @@
19
19
  * }
20
20
  */
21
21
  import { type Object3D, type PerspectiveCamera } from 'three';
22
- import type { HeadPoseSource } from '@realitycollective/webxr-uiextensions';
22
+ import type { HandPoseSource, HeadPoseSource } from '@realitycollective/webxr-uiextensions';
23
23
  import type { Kit } from '@pmndrs/uikitml';
24
24
  import { UixWindowHost } from './host.js';
25
25
  /** The slice of an XR Blocks Script / three.js app the host binds to. */
@@ -30,7 +30,29 @@ export interface EngineContext {
30
30
  camera: Pick<PerspectiveCamera, 'getWorldPosition' | 'getWorldQuaternion'>;
31
31
  /** Optional UIKitML component kit(s). */
32
32
  kit?: Kit;
33
+ /**
34
+ * `renderer.xr`, so hand menus can ride the tracked hands in a session.
35
+ * Leave it out on a desktop; hand-locked windows then follow the body.
36
+ */
37
+ xr?: WebXRFrameAccess;
33
38
  }
39
+ /**
40
+ * The slice of three's `WebXRManager` (`renderer.xr`) a hand pose source
41
+ * needs. Structural, so anything with the same three methods will do.
42
+ */
43
+ export interface WebXRFrameAccess {
44
+ getFrame(): XRFrame | null | undefined;
45
+ getReferenceSpace(): XRReferenceSpace | null;
46
+ getSession(): XRSession | null;
47
+ }
48
+ /**
49
+ * HandPoseSource backed by a live WebXR session: each hand's pose is its
50
+ * input source's `gripSpace` (a controller's grip, or the tracked hand),
51
+ * falling back to the target ray space when a runtime gives a hand none.
52
+ * Returns `undefined` for a hand with no input source this frame, or with no
53
+ * frame at all (outside a session), so hand menus stay hidden there.
54
+ */
55
+ export declare function webxrHandPoseSource(xr: WebXRFrameAccess): HandPoseSource;
34
56
  /** HeadPoseSource backed by a three.js camera. */
35
57
  export declare function cameraHeadPoseSource(camera: EngineContext['camera']): HeadPoseSource;
36
58
  /** Create a window host bound to an XR Blocks Script / three.js context. */
package/dist/xrblocks.js CHANGED
@@ -20,6 +20,45 @@
20
20
  */
21
21
  import { Quaternion, Vector3 } from 'three';
22
22
  import { UixWindowHost } from './host.js';
23
+ /**
24
+ * HandPoseSource backed by a live WebXR session: each hand's pose is its
25
+ * input source's `gripSpace` (a controller's grip, or the tracked hand),
26
+ * falling back to the target ray space when a runtime gives a hand none.
27
+ * Returns `undefined` for a hand with no input source this frame, or with no
28
+ * frame at all (outside a session), so hand menus stay hidden there.
29
+ */
30
+ export function webxrHandPoseSource(xr) {
31
+ return {
32
+ hasHands() {
33
+ const session = xr.getSession();
34
+ return session !== null && session.inputSources.length > 0;
35
+ },
36
+ getHandPose(hand) {
37
+ const frame = xr.getFrame();
38
+ const referenceSpace = xr.getReferenceSpace();
39
+ const session = xr.getSession();
40
+ if (!frame || !referenceSpace || !session) {
41
+ return undefined;
42
+ }
43
+ for (const source of session.inputSources) {
44
+ if (source.handedness !== hand) {
45
+ continue;
46
+ }
47
+ const space = source.gripSpace ?? source.targetRaySpace;
48
+ const pose = frame.getPose(space, referenceSpace);
49
+ if (!pose) {
50
+ return undefined;
51
+ }
52
+ const { position, orientation } = pose.transform;
53
+ return {
54
+ position: [position.x, position.y, position.z],
55
+ quaternion: [orientation.x, orientation.y, orientation.z, orientation.w],
56
+ };
57
+ }
58
+ return undefined;
59
+ },
60
+ };
61
+ }
23
62
  /** HeadPoseSource backed by a three.js camera. */
24
63
  export function cameraHeadPoseSource(camera) {
25
64
  const position = new Vector3();
@@ -40,6 +79,7 @@ export function connectUIExtensions(context) {
40
79
  return new UixWindowHost({
41
80
  scene: context.scene,
42
81
  headPose: cameraHeadPoseSource(context.camera),
82
+ ...(context.xr ? { handPose: webxrHandPoseSource(context.xr) } : {}),
43
83
  ...(context.kit ? { kit: context.kit } : {}),
44
84
  });
45
85
  }
@@ -1 +1 @@
1
- {"version":3,"file":"xrblocks.js","sourceRoot":"","sources":["../src/xrblocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,UAAU,EAAE,OAAO,EAAyC,MAAM,OAAO,CAAC;AAGnF,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAY1C,kDAAkD;AAClD,MAAM,UAAU,oBAAoB,CAClC,MAA+B;IAE/B,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAE,CAAC;IAC/B,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;IACpC,OAAO;QACL,WAAW;YACT,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YAClC,MAAM,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;YACtC,OAAO;gBACL,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;gBAC9C,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;aACrE,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB,CAAC,OAAsB;IACxD,OAAO,IAAI,aAAa,CAAC;QACvB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC;QAC9C,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7C,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * XR Blocks glue.\n *\n * XR Blocks (https://github.com/google/xrblocks) is three.js-based: a\n * Script's `scene`/`camera` are ordinary three.js objects, and `update()`\n * runs per frame. This module binds a {@link UixWindowHost} to that shape\n * WITHOUT importing `xrblocks` (loose structural types), so the package\n * works identically in an XR Blocks Script and in any hand-rolled three.js\n * WebXR app.\n *\n * Typical XR Blocks usage:\n *\n * import * as xb from 'xrblocks';\n * import { connectUIExtensions } from '@realitycollective/xrblocks-uiextensions';\n *\n * class MyScript extends xb.Script {\n * init() { this.uix = connectUIExtensions({ scene: this, camera: xb.core.camera }); }\n * update() { this.uix.update(xb.core.timer.getDelta()); }\n * }\n */\nimport { Quaternion, Vector3, type Object3D, type PerspectiveCamera } from 'three';\nimport type { HeadPose, HeadPoseSource } from '@realitycollective/webxr-uiextensions';\nimport type { Kit } from '@pmndrs/uikitml';\nimport { UixWindowHost } from './host.js';\n\n/** The slice of an XR Blocks Script / three.js app the host binds to. */\nexport interface EngineContext {\n /** Where windows are parented - an XR Blocks Script IS an Object3D. */\n scene: Object3D;\n /** The rendering camera; in XR the engine keeps it at the viewer pose. */\n camera: Pick<PerspectiveCamera, 'getWorldPosition' | 'getWorldQuaternion'>;\n /** Optional UIKitML component kit(s). */\n kit?: Kit;\n}\n\n/** HeadPoseSource backed by a three.js camera. */\nexport function cameraHeadPoseSource(\n camera: EngineContext['camera'],\n): HeadPoseSource {\n const position = new Vector3();\n const quaternion = new Quaternion();\n return {\n getHeadPose(): HeadPose {\n camera.getWorldPosition(position);\n camera.getWorldQuaternion(quaternion);\n return {\n position: [position.x, position.y, position.z],\n quaternion: [quaternion.x, quaternion.y, quaternion.z, quaternion.w],\n };\n },\n };\n}\n\n/** Create a window host bound to an XR Blocks Script / three.js context. */\nexport function connectUIExtensions(context: EngineContext): UixWindowHost {\n return new UixWindowHost({\n scene: context.scene,\n headPose: cameraHeadPoseSource(context.camera),\n ...(context.kit ? { kit: context.kit } : {}),\n });\n}\n"]}
1
+ {"version":3,"file":"xrblocks.js","sourceRoot":"","sources":["../src/xrblocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,UAAU,EAAE,OAAO,EAAyC,MAAM,OAAO,CAAC;AAGnF,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AA2B1C;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAoB;IACtD,OAAO;QACL,QAAQ;YACN,MAAM,OAAO,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC;YAChC,OAAO,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;QAC7D,CAAC;QACD,WAAW,CAAC,IAAI;YACd,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;YAC5B,MAAM,cAAc,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,EAAE,CAAC,UAAU,EAAE,CAAC;YAChC,IAAI,CAAC,KAAK,IAAI,CAAC,cAAc,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC1C,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;gBAC1C,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;oBAC/B,SAAS;gBACX,CAAC;gBACD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,cAAc,CAAC;gBACxD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;gBAClD,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,OAAO,SAAS,CAAC;gBACnB,CAAC;gBACD,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;gBACjD,OAAO;oBACL,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;oBAC9C,UAAU,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;iBACzE,CAAC;YACJ,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,oBAAoB,CAClC,MAA+B;IAE/B,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAE,CAAC;IAC/B,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;IACpC,OAAO;QACL,WAAW;YACT,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YAClC,MAAM,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;YACtC,OAAO;gBACL,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;gBAC9C,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;aACrE,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB,CAAC,OAAsB;IACxD,OAAO,IAAI,aAAa,CAAC;QACvB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC;QAC9C,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7C,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * XR Blocks glue.\n *\n * XR Blocks (https://github.com/google/xrblocks) is three.js-based: a\n * Script's `scene`/`camera` are ordinary three.js objects, and `update()`\n * runs per frame. This module binds a {@link UixWindowHost} to that shape\n * WITHOUT importing `xrblocks` (loose structural types), so the package\n * works identically in an XR Blocks Script and in any hand-rolled three.js\n * WebXR app.\n *\n * Typical XR Blocks usage:\n *\n * import * as xb from 'xrblocks';\n * import { connectUIExtensions } from '@realitycollective/xrblocks-uiextensions';\n *\n * class MyScript extends xb.Script {\n * init() { this.uix = connectUIExtensions({ scene: this, camera: xb.core.camera }); }\n * update() { this.uix.update(xb.core.timer.getDelta()); }\n * }\n */\nimport { Quaternion, Vector3, type Object3D, type PerspectiveCamera } from 'three';\nimport type { HandPoseSource, HeadPose, HeadPoseSource } from '@realitycollective/webxr-uiextensions';\nimport type { Kit } from '@pmndrs/uikitml';\nimport { UixWindowHost } from './host.js';\n\n/** The slice of an XR Blocks Script / three.js app the host binds to. */\nexport interface EngineContext {\n /** Where windows are parented - an XR Blocks Script IS an Object3D. */\n scene: Object3D;\n /** The rendering camera; in XR the engine keeps it at the viewer pose. */\n camera: Pick<PerspectiveCamera, 'getWorldPosition' | 'getWorldQuaternion'>;\n /** Optional UIKitML component kit(s). */\n kit?: Kit;\n /**\n * `renderer.xr`, so hand menus can ride the tracked hands in a session.\n * Leave it out on a desktop; hand-locked windows then follow the body.\n */\n xr?: WebXRFrameAccess;\n}\n\n/**\n * The slice of three's `WebXRManager` (`renderer.xr`) a hand pose source\n * needs. Structural, so anything with the same three methods will do.\n */\nexport interface WebXRFrameAccess {\n getFrame(): XRFrame | null | undefined;\n getReferenceSpace(): XRReferenceSpace | null;\n getSession(): XRSession | null;\n}\n\n/**\n * HandPoseSource backed by a live WebXR session: each hand's pose is its\n * input source's `gripSpace` (a controller's grip, or the tracked hand),\n * falling back to the target ray space when a runtime gives a hand none.\n * Returns `undefined` for a hand with no input source this frame, or with no\n * frame at all (outside a session), so hand menus stay hidden there.\n */\nexport function webxrHandPoseSource(xr: WebXRFrameAccess): HandPoseSource {\n return {\n hasHands() {\n const session = xr.getSession();\n return session !== null && session.inputSources.length > 0;\n },\n getHandPose(hand) {\n const frame = xr.getFrame();\n const referenceSpace = xr.getReferenceSpace();\n const session = xr.getSession();\n if (!frame || !referenceSpace || !session) {\n return undefined;\n }\n for (const source of session.inputSources) {\n if (source.handedness !== hand) {\n continue;\n }\n const space = source.gripSpace ?? source.targetRaySpace;\n const pose = frame.getPose(space, referenceSpace);\n if (!pose) {\n return undefined;\n }\n const { position, orientation } = pose.transform;\n return {\n position: [position.x, position.y, position.z],\n quaternion: [orientation.x, orientation.y, orientation.z, orientation.w],\n };\n }\n return undefined;\n },\n };\n}\n\n/** HeadPoseSource backed by a three.js camera. */\nexport function cameraHeadPoseSource(\n camera: EngineContext['camera'],\n): HeadPoseSource {\n const position = new Vector3();\n const quaternion = new Quaternion();\n return {\n getHeadPose(): HeadPose {\n camera.getWorldPosition(position);\n camera.getWorldQuaternion(quaternion);\n return {\n position: [position.x, position.y, position.z],\n quaternion: [quaternion.x, quaternion.y, quaternion.z, quaternion.w],\n };\n },\n };\n}\n\n/** Create a window host bound to an XR Blocks Script / three.js context. */\nexport function connectUIExtensions(context: EngineContext): UixWindowHost {\n return new UixWindowHost({\n scene: context.scene,\n headPose: cameraHeadPoseSource(context.camera),\n ...(context.xr ? { handPose: webxrHandPoseSource(context.xr) } : {}),\n ...(context.kit ? { kit: context.kit } : {}),\n });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/xrblocks-uiextensions",
3
- "version": "0.1.0-preview.7",
3
+ "version": "0.1.0",
4
4
  "description": "EXPERIMENTAL Google XR Blocks / plain three.js adapter for the Reality Collective UI Extensions - hosts the engine-free @realitycollective/webxr-uiextensions windowing core (UIKitML panels, window chrome, follow mode) in any three.js WebXR scene, including XR Blocks Scripts.",
5
5
  "keywords": [
6
6
  "realitycollective",
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "@pmndrs/uikit": "^1.0.76",
39
39
  "@pmndrs/uikitml": "^0.1.12",
40
- "@realitycollective/webxr-uiextensions": "^0.1.0-preview.7"
40
+ "@realitycollective/webxr-uiextensions": "^0.1.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "three": ">=0.170.0"