@realitycollective/iwsdk-uiextensions 0.1.1-preview.2 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -2
- package/README.md +7 -6
- package/dist/component-set.js +2 -22
- package/dist/component-set.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/register.d.ts +7 -0
- package/dist/register.js +5 -0
- package/dist/register.js.map +1 -1
- package/dist/systems/pointer-offer-system.d.ts +55 -0
- package/dist/systems/pointer-offer-system.js +98 -0
- package/dist/systems/pointer-offer-system.js.map +1 -0
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,10 +4,21 @@ Change log for the Reality Collective WebXR UI Extensions packages. All five 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.1]
|
|
7
|
+
## [0.1.1] - 2026-10-01
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
10
|
|
|
11
|
+
- Pointers for an app with windows and no interactables. IWSDK draws every hand's ray and cursor itself, whichever packages an app uses; a native host draws only what it is told, and nothing told it, so a native app with windows alone showed no ray and no cursor and could not be aimed.
|
|
12
|
+
- `@realitycollective/webxr-uiextensions` - `PanelPointerOffers.present(sourceKey, hasRay, selecting, display)` and `PresentedPointer`: the drawing for one source, the arbiter's visuals through the app's `PointerDisplay` settings, with what the cursor sits on. The rule it states: whoever owns the arbiter presents every source. With a shared arbiter that is the Interactions runtime; with its own, the window host.
|
|
13
|
+
- `@realitycollective/native-uiextensions` - `NativeWindowHost` presents while it owns the arbiter: once per frame for every source that reported a pointer sample it calls the `input` slice's `applyPointerVisuals`, and once more with nothing to draw when a source stops reporting. New option `pointerDisplay` (the settings, or a `PointerDisplay` the app keeps), `getPointerDisplay()`, and the optional `applyPointerVisuals` and `applyPointerDisplay` members on `NativeUiInputHost`, the same members `@realitycollective/native-interactions` defines on that slice. With `pointers` given nothing changes: the Interactions binding presents.
|
|
14
|
+
- `@realitycollective/webxr-uiextensions` - `UIX_CONTROL_TAGS`, `UIX_ROLE_TAGS` and `UIX_ELEMENT_TAGS`: the `<uix-*>` element vocabulary a panel loader must accept, published by the core. It was declared twice, privately, in the core's upgrader and in the IWSDK binding's component set; `uixComponentSet` now reads it, and the native contract states what a host's panel loader (a build-time cook or a run-time parser) must do with each tag.
|
|
15
|
+
- Native test harness (`harness/native`): a native app project for the WebXR-to-native conversion pipeline that runs `nativeUiHostConformanceCases()`, `windowHostContractCases()`, `sceneTargetContractCases()` and `uixElementContractCases()` against a native host's test slices (or this repository's reference fakes under Node), and plays the showcase scene over the live `__rcHost` with every window and control event logged as a JSON line. `npm run harness:ui` fills its panel folder from the showcase and the core's window chrome; `npm run harness:compile` typechecks it, bundles it through the engine and browser-global gates and compiles Hermes bytecode. CI compiles it and never runs it.
|
|
16
|
+
- Native parity round (Pale Signal handover of 29 September 2026, G4 and G7): one pointer decision per source across UI panels AND interactables, as Meta IWSDK's `MultiPointer` runs over every pointer-event object.
|
|
17
|
+
- `@realitycollective/webxr-uiextensions` - `PanelPointerOffers` (`core/pointer-offers.ts`, `UIX_POINTER_SET`, `PANEL_GRAB_RADIUS`): a window host registers a `"panel"` target set with the `PointerArbiter` from `@realitycollective/webxr-input`, offers per source and per pointer kind the nearest panel its ray, fingertip or grip reaches, and hovers, presses, touches and drags with a pointer kind only while that kind owns the source with the host's candidate. A host used on its own gets an arbiter of its own and behaves as before. Keys are the source's handedness or the input provider's id; the Interactions runtime aliases one to the other.
|
|
18
|
+
- `@realitycollective/native-uiextensions` - `NativeWindowHost` takes `pointers`; every `onPointerSample` is offered (a new optional `NativePointerSample.distance` is the distance IWSDK's pointer of that kind compares, else derived), the press machines run only while the pointer owns its source, and the panel cursor now reaches the host through `@realitycollective/native-interactions`' `applyPointerVisuals` (`targetKind: "panel"`), so a host stopgap for panel cursors is no longer needed. `showKeyboard` states what a host that cannot observe the keyboard's dismissal (an Android `NativeActivity`) reports as `"keyboardclosed"`: its own `hideKeyboard`, or a key event that ends entry.
|
|
19
|
+
- `@realitycollective/threejs-uiextensions` - `connectUIExtensions({ pointers })` and `connectWebXrPointerInput({ pointers })`: each slot's ray hit, fingertip panel and grip panel (within `PANEL_GRAB_RADIUS`) are offered under the session input source's handedness before hover, press, squeeze and touch act, and with a shared arbiter this host draws no cursor of its own (the Interactions binding draws every cursor, panels included). `ScenePointerBridge.rootOf` and `isTouchPressed` support it.
|
|
20
|
+
- `@realitycollective/xrblocks-uiextensions` - `connectUIExtensions({ pointers })` and `XrBlocksPointerBridge({ pointers })`: touch, grab and ray candidates are offered (the ray from this binding's own raycast of the wired panels each frame), presses and touches are gated on ownership, and hover is per pointer from that raycast rather than XR Blocks' per-element `onHoverEnter`/`onHoverExit` (which stay as no-ops), closing the "XR Blocks hover is not per pointer" item.
|
|
21
|
+
- `@realitycollective/iwsdk-uiextensions` - `registerUIExtensions(world, { pointers })` registers `UIPointerOfferSystem` (priority -3.95, after IWSDK's InputSystem), which offers each hand's touch, grab and ray intersections on this world's panels to the shared arbiter; IWSDK arbitrates its own pointers, so nothing is gated there, and the core's published visuals name panels on IWSDK as on every other platform. `pointerOffersFor(world)` reaches the offers.
|
|
11
22
|
- `@realitycollective/webxr-uiextensions` - hover per element as one core decision (`core/hover.ts`, `HoverTracker`): each pointer raises `pointerenter` on the element it moves onto and `pointerleave` on the one it left, once per change, and an element's hover style holds while any pointer is over it, as IWSDK's pointer events drive uikit's `:hover`. The three.js binding raises hover from it, and the native binding decides hover from its pointer samples and tells the host through the new `NativeUiHost.setHover`; a `pointerenter` or `pointerleave` a native host raises itself is ignored.
|
|
12
23
|
- `@realitycollective/webxr-uiextensions` - scrolling as a core rule (`core/scroll.ts`, `ScrollState`, `SCROLL_DEFAULTS`): uikit's drag, coast (0.9 damping per frame, stopping below 10 px/s), rubber band (100 px, 30 % return per frame) and wheel, restated so a native host only draws the offset it is handed (`NativeUiHost.setScroll`; a node declares `scroll` and a ray sample carries `localPoint`). Text entry as a core rule (`core/text-entry.ts`, `TextEntry`) over a platform keyboard facility (`TextInputFacility`): a click on a text field asks for the keyboard with the field's value, `multiline` and `type`; every reported value replaces the field's and raises `valueChanged`. The web default is uikit's `Input` over a hidden HTML input and the browser's keyboard; the native default is the system keyboard (`NativeUiHost.showKeyboard`/`hideKeyboard`, host events `input` and `keyboardclosed`). Kit cases: `ui/hover is decided by the binding...`, `ui/the host scrolls an element's content exactly as told`, `ui/the host shows its keyboard for a text field when told...`.
|
|
13
24
|
- `@realitycollective/webxr-uiextensions` - `WindowManager.setFollow(id, options)` and `OpenWindowOptions.follow`: a following window's offset (in the viewer's yaw frame, metres), speed, dead zone and angle live on the `WindowRecord` (`follow`, `DEFAULT_WINDOW_FOLLOW` unless set) and can change after spawn; the `followChanged` event carries the previous values. Every binding steps the core follow rule from the record: the IWSDK dock system writes the live `Follower`, the three.js and native hosts re-aim at the new offset.
|
|
@@ -31,6 +42,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
31
42
|
|
|
32
43
|
### Changed
|
|
33
44
|
|
|
45
|
+
- Every package now requires `@realitycollective/webxr-input` at `^0.1.8-preview.0`: the shared pointer arbiter the hosts offer their panels to exists only from that release.
|
|
46
|
+
|
|
34
47
|
- The reference is now IWSDK 1.0. `@realitycollective/iwsdk-uiextensions` peers on `@iwsdk/core >=1.0.0 <2.0.0` (was `>=0.5.0 <0.6.0`) and is built and tested against 1.0.0. IWSDK 1.0's FollowSystem honours the height of the follow offset, where 0.5 set a following window at head height, so the core `stepFollow` now does the same on every platform: a body-follow or head-locked window sits 0.15 m below eye level by default, and a following region 0.2 m below. `follow-reference.test.ts` proves the core equal to IWSDK 1.0's own FollowSystem frame by frame.
|
|
35
48
|
- `@realitycollective/xrblocks-uiextensions` - follows by the core rule. `body-follow` and `head-locked` windows now move by `stepFollow`, the same lazy yaw follow IWSDK applies: a snap on entering the mode, the 0.35 m dead zone with a 30 degree angle limit, a lerp of `dt * speed`, and following from where the window was left when it is unpinned. Before this XR Blocks eased with its own exponential approach, had no angle limit or snap, and did not move a `head-locked` window at all. A following region now uses IWSDK's region defaults (speed 1, tolerance 0.4 m) rather than speed 3 and 0.2 m. A window with no title is titled with its id, as on every platform, and every panel's `data-uix` controls are upgraded automatically, as IWSDK's `UIControlsSystem` does (`controls: false` opts out). `follow-math.ts` stays exported and unchanged for existing callers.
|
|
36
49
|
- **Breaking for native hosts:** `@realitycollective/native-uiextensions` now applies every UI rule itself, and a native host is handed results. Before this, the binding mirrored `WindowManager` records to the host and left the host to place windows, write chrome, run the touch press and decide clicks, and a native host that re-derived those rules drifted from the web on chrome, dock modes, touch press, click timing and more. Now the binding runs, from the core, what IWSDK's `UIWindowSystem`, `UIDockSystem`, `FollowSystem`, `UIDockRegionSystem`, `UIDragSystem`, `UITouchGuardSystem` and `UIControlsSystem` do. What a native host must absorb:
|
|
@@ -46,9 +59,12 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
46
59
|
- `WindowManager` holds one rule for regions: a window in a region is always world-locked, because the region places it. Opening a window into a region, or `dockTo`, makes it world-locked whatever dock mode it asked for; `dockTo` emits `regionChanged` and then, if the mode changed, `dockChanged`. Setting a follow mode with `setDockMode` or `togglePin` takes a docked window out of its region first. The hosts disagreed before this: the XR Blocks host forced a docked window to world-locked on its own, while the IWSDK and native hosts left the manager's record in the requested follow mode. The same scene descriptor therefore laid out differently by platform. The XR Blocks host's own copy of the rule is gone. The native host now sends the native app the manager's dock mode for a new window, not the one the options asked for.
|
|
47
60
|
- `@realitycollective/iwsdk-uiextensions` - `TouchPointerLike` (the parameter type of `sampleOf`) is exported, and `UITouchGuardSystem` uses the core's `Hand` type rather than a private duplicate. `@realitycollective/xrblocks-uiextensions` exports `UikitComponentLike` (`UixPanelDocument.rootComponent`) and `InteractiveLike` (the return type of `pickInteractive`); `@realitycollective/uix-devtools` exports `CompileOptions` (the options of `compilePanelSource`). Each was a module-private type that appeared in an exported signature, so a consumer had nothing to name and TypeDoc reported it as referenced but undocumented.
|
|
48
61
|
- **Breaking:** `HandPoseSource.hasHands()` is gone. It existed only to let `@realitycollective/xrblocks-uiextensions` keep a hand-locked window shown, by body-follow, when no hand was tracked - a behaviour IWSDK and native never had. A hand-locked window with no tracked hand is now hidden on every platform, as `evaluateHandMenu` already decides: a platform never decides a behaviour the reference has not. `webxrHandPoseSource` no longer implements the method.
|
|
62
|
+
- Dependencies on other Reality Collective repositories now follow one rule, checked by `scripts/rc-dependencies.mjs`. Going into `main`, each names the release npm tags `latest` and never a preview. Going into any other branch, each names the newest version on npm, release or preview. The rule covers every `package.json` in the repository (published packages, demos, harnesses and examples) and every copy `package-lock.json` records. A range passes only when its lowest version is the target, so `^0.1.8` passes for 0.1.8 and `^0.1.4` does not. CI runs it before the install, against the branch a pull request targets, and the publish workflow runs it before publishing. `release.mjs status` reports it. `release.mjs prepare` stops before cutting a release when a dependency names an unreleased preview of another repository, because that repository has to release first, and moves any range that is only behind. Other dependencies are out of scope. The script stays identical across the Reality Collective repositories.
|
|
49
63
|
|
|
50
64
|
### Fixed
|
|
51
65
|
|
|
66
|
+
- `@realitycollective/native-uiextensions` - a text field bound the way the web binds it, `setProperties({ onValueChange })`, never heard typed text on native: the binding handed the function to the host as element state and raised only a `valueChanged` event. A function-valued property now stays in the binding as a callback, only plain data crosses to the host, and the keyboard's text calls `onValueChange` as uikit's `Input` does, then raises `valueChanged`.
|
|
67
|
+
- `@realitycollective/xrblocks-uiextensions` - the host reads rays with `xb.input.getFrame()`, which xrblocks added in 0.20.0. Handed the input of an older xrblocks, the host now refuses it once at construction, naming the version it needs, instead of throwing from every frame's update. The multiplatform lab moves to xrblocks `^0.21.1` (it declared `^0.19.0`, whose input has no `getFrame`), and mounts XR Blocks' canvas inside `#scene-container` as its other two pipelines do.
|
|
52
68
|
- `@realitycollective/native-uiextensions` - a window spawned without a title showed a blank title bar until a later record change. The plain options the host received defaulted the title to `''` while the record defaulted it to the window id. Both now use the id, and the binding writes it to `uix-title` itself.
|
|
53
69
|
- `@realitycollective/native-uiextensions` - `data-uix` controls were never upgraded on native: nothing called `upgradePanel`, so steppers, toggles, expandable labels and log views rendered but did nothing. Every panel, window or bare, is now upgraded when it is built.
|
|
54
70
|
- `@realitycollective/native-uiextensions` - the `data-uix-*` attributes of a native panel never reached the controls. `NativeElementNode` had no field for them, so on native every control ran on its defaults: a stepper lost its range, step and starting value, a toggle its labels and colours, and every control its `data-uix-id`, so `controls.stepper("count")` found nothing. `NativeElementNode` gains `attributes`, the element's `data-*` attributes as the markup wrote them, and the proxy lifts them into `userData` with `dataAttributeKey()`. The native app must send them.
|
|
@@ -106,7 +122,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
106
122
|
- `@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.
|
|
107
123
|
- `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.
|
|
108
124
|
|
|
109
|
-
[0.1.1]: https://github.com/realitycollective/WebXR-UIExtensions/
|
|
125
|
+
[0.1.1]: https://github.com/realitycollective/WebXR-UIExtensions/releases/tag/v0.1.1
|
|
110
126
|
[0.1.0]: https://github.com/realitycollective/WebXR-UIExtensions/releases/tag/v0.1.0
|
|
111
127
|
|
|
112
128
|
### Fixed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Windowing, docking, layout regions and extra controls for [Meta's Immersive Web SDK](https://iwsdk.dev) (`@iwsdk/core`).
|
|
4
4
|
|
|
5
|
-
**This is the Meta IWSDK adapter** - and the reference implementation - for the engine-free [`@realitycollective/webxr-uiextensions`](../webxr-uiextensions/README.md) core, which it re-exports in full: one dependency gets IWSDK apps the whole surface.
|
|
5
|
+
**This is the Meta IWSDK adapter** - and the reference implementation - for the engine-free [`@realitycollective/webxr-uiextensions`](../webxr-uiextensions/README.md) core, which it re-exports in full: one dependency gets IWSDK apps the whole surface. Sibling adapters bind the same core to plain three.js ([`@realitycollective/threejs-uiextensions`](../threejs-uiextensions/README.md)), Google XR Blocks ([`@realitycollective/xrblocks-uiextensions`](../xrblocks-uiextensions/README.md), experimental) and native apps ([`@realitycollective/native-uiextensions`](../native-uiextensions/README.md)).
|
|
6
6
|
|
|
7
7
|
**Reuse, not recreation.** The IWSDK already ships an excellent spatial UI stack - UIKitML markup, `@pmndrs/uikit` rendering, `Follower`/`ScreenSpace` anchoring, grab/ray/poke interaction. This package adds the missing layer above it:
|
|
8
8
|
|
|
@@ -13,6 +13,8 @@ Windowing, docking, layout regions and extra controls for [Meta's Immersive Web
|
|
|
13
13
|
| **Dock states** | `world-locked` (place in space) ⇄ `body-follow` (lazy follow) ⇄ `head-locked`, realised with the IWSDK's own `Follower`/`ScreenSpace` |
|
|
14
14
|
| **Manipulation** | Drag windows by the title bar with the far ray or a near grab (controller squeeze, hand pinch), powered by `@pmndrs/handle`, the same library behind IWSDK grabbing; billboard-while-dragging, drop-to-dock |
|
|
15
15
|
| **Layout regions** | Named regions (row / column / grid slots) windows snap into; regions can themselves follow the player |
|
|
16
|
+
| **Shared pointers** | `registerUIExtensions(world, { pointers })` registers `UIPointerOfferSystem`, which offers each hand's panel candidates to the `PointerArbiter` the Interactions binding uses, so one pointer per hand is decided across panels and interactables, as IWSDK's own `MultiPointer` does |
|
|
17
|
+
| **Follow and placement** | `WindowManager.setFollow(id, options)` and the core's `stepFollow`, proved frame for frame against IWSDK's own `Follower`; focus bias and region slots by the same core rules every platform runs |
|
|
16
18
|
| **Controls** | `<uix-*>` custom-element upgrades: **stepper**, **toggle**, **expandable multi-line label**, **log/list view** - plus everything UIKitML already has (buttons, inputs, textareas, images, and the horizon kit's Slider/Checkbox/…) |
|
|
17
19
|
|
|
18
20
|
Everything is authored in plain UIKitML (HTML/CSS-like) - no new markup language, no custom renderer, no wrapper widgets around things the IWSDK already does.
|
|
@@ -46,7 +48,7 @@ createDockRegion(world, { id: 'wall', flow: 'column', position: [1.5, 1.8, -1.5]
|
|
|
46
48
|
createUIWindow(world, {
|
|
47
49
|
id: 'status',
|
|
48
50
|
title: 'Player Status',
|
|
49
|
-
config: './ui/status.uikitml', // UIKitML source; IWSDK
|
|
51
|
+
config: './ui/status.uikitml', // UIKitML source; IWSDK parses it at runtime
|
|
50
52
|
dockMode: DockMode.BodyFollow, // follows until the user pins it
|
|
51
53
|
pinnable: true, // title-bar buttons are off unless asked for
|
|
52
54
|
minimizable: true,
|
|
@@ -95,7 +97,7 @@ A control is a custom element, `<uix-stepper>`, `<uix-toggle>`, `<uix-expandable
|
|
|
95
97
|
</uix-stepper>
|
|
96
98
|
```
|
|
97
99
|
|
|
98
|
-
IWSDK
|
|
100
|
+
IWSDK validates every tag against a component schema (since 0.5), so a world whose panels use controls must register the set, or the panel fails to parse and never attaches:
|
|
99
101
|
|
|
100
102
|
```ts
|
|
101
103
|
import { uixComponentSet } from '@realitycollective/iwsdk-uiextensions';
|
|
@@ -152,7 +154,7 @@ Use `HAND_MENU_SNIPPET` from the core as the markup starting point: the same `ui
|
|
|
152
154
|
|
|
153
155
|
### Driving windows from code
|
|
154
156
|
|
|
155
|
-
`registerUIExtensions` returns the `WindowManager`, and it is the one API app code needs to change a window - a hand menu, a keyboard shortcut, a voice command. Every call is applied by the systems, and the same calls work on
|
|
157
|
+
`registerUIExtensions` returns the `WindowManager`, and it is the one API app code needs to change a window - a hand menu, a keyboard shortcut, a voice command. Every call is applied by the systems, and the same calls work on every adapter:
|
|
156
158
|
|
|
157
159
|
| Call | Effect |
|
|
158
160
|
| --- | --- |
|
|
@@ -164,7 +166,6 @@ Use `HAND_MENU_SNIPPET` from the core as the markup starting point: the same `ui
|
|
|
164
166
|
| `setChrome(id, { pin, dock, minimize, close })` | Enable or disable title-bar buttons at runtime |
|
|
165
167
|
| `focus(id)` | Bring to the front |
|
|
166
168
|
| `close(id)` | Destroy the window's entity |
|
|
167
|
-
|
|
168
169
|
| `setHandMenu(id, { hand, anchor, ... })` | Move a hand menu to the other hand or another anchor |
|
|
169
170
|
|
|
170
171
|
The record is always what the scene shows: a drag that docks a window, or a PIN click, is written back into `windows.get(id)`, and every change emits a typed event (`hidden`, `shown`, `regionChanged`, `returnHome`, `chromeChanged`, `handMenuChanged`, alongside the existing ones) so a menu can keep its labels honest. See `Examples/basic-window/`.
|
|
@@ -236,4 +237,4 @@ All decision logic (window manager, dock state machine, region slot math, drag m
|
|
|
236
237
|
|
|
237
238
|
## License
|
|
238
239
|
|
|
239
|
-
MIT
|
|
240
|
+
MIT - see [LICENSE](./LICENSE).
|
package/dist/component-set.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { htmlComponentSet } from '@drawcall/uikitml';
|
|
24
24
|
import { z } from 'zod';
|
|
25
|
-
import { dataAttributeKey } from '@realitycollective/webxr-uiextensions';
|
|
25
|
+
import { UIX_ELEMENT_TAGS, dataAttributeKey } from '@realitycollective/webxr-uiextensions';
|
|
26
26
|
/**
|
|
27
27
|
* Base container, taken from the parser's own `<div>` definition rather than
|
|
28
28
|
* imported from `@pmndrs/uikit` directly - that keeps a single copy of uikit
|
|
@@ -30,26 +30,6 @@ import { dataAttributeKey } from '@realitycollective/webxr-uiextensions';
|
|
|
30
30
|
*/
|
|
31
31
|
const BaseContainer = htmlComponentSet['div']
|
|
32
32
|
?.component;
|
|
33
|
-
/** Control elements an author writes, and the parts that live inside them. */
|
|
34
|
-
const CONTROL_TAGS = [
|
|
35
|
-
'uix-stepper',
|
|
36
|
-
'uix-toggle',
|
|
37
|
-
'uix-expandable-label',
|
|
38
|
-
'uix-log-view',
|
|
39
|
-
];
|
|
40
|
-
const ROLE_TAGS = [
|
|
41
|
-
'uix-decrement',
|
|
42
|
-
'uix-value',
|
|
43
|
-
'uix-increment',
|
|
44
|
-
'uix-label',
|
|
45
|
-
'uix-line',
|
|
46
|
-
'uix-up',
|
|
47
|
-
'uix-down',
|
|
48
|
-
'uix-clear',
|
|
49
|
-
'uix-status',
|
|
50
|
-
'uix-text',
|
|
51
|
-
'uix-more',
|
|
52
|
-
];
|
|
53
33
|
/**
|
|
54
34
|
* A plain uikit Container that records what it was declared as.
|
|
55
35
|
*
|
|
@@ -90,5 +70,5 @@ function define(tag) {
|
|
|
90
70
|
* Pass to `spatialUI.componentSets` so `<uix-*>` elements parse on IWSDK 0.5.
|
|
91
71
|
* Without it, a panel using any control fails to parse and never attaches.
|
|
92
72
|
*/
|
|
93
|
-
export const uixComponentSet = Object.fromEntries(
|
|
73
|
+
export const uixComponentSet = Object.fromEntries(UIX_ELEMENT_TAGS.map((tag) => [tag, define(tag)]));
|
|
94
74
|
//# sourceMappingURL=component-set.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-set.js","sourceRoot":"","sources":["../src/component-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,gBAAgB,EAA+C,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"component-set.js","sourceRoot":"","sources":["../src/component-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,gBAAgB,EAA+C,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE3F;;;;GAIG;AACH,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,CAAC;IAC3C,EAAE,SAEH,CAAC;AAEF;;;;;;GAMG;AACH,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,MAAM,mBAAoB,SAAQ,aAAa;QACpD,YAAY,aAAsC,EAAE;YAClD,MAAM,UAAU,GAA4B,EAAE,CAAC;YAC/C,MAAM,QAAQ,GAA4B,EAAE,CAAC;YAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACtD,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACtD,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;gBAC1C,CAAC;qBAAM,CAAC;oBACN,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAC1B,CAAC;YACH,CAAC;YACD,KAAK,CAAC,UAAU,CAAC,CAAC;YAClB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE;gBACrC,aAAa,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE;aACtD,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAE1C,SAAS,MAAM,CAAC,GAAW;IACzB,OAAO;QACL,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAgD;QAC/E,MAAM;QACN,eAAe,EAAE,IAAI;KACtB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAiB,MAAM,CAAC,WAAW,CAC7D,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAClD,CAAC","sourcesContent":["/**\n * The UIKitML component set for `<uix-*>` control elements.\n *\n * IWSDK 0.5 parses panels with `@drawcall/uikitml`, which validates every tag\n * against a component schema and rejects anything it does not know - including\n * every `data-*` attribute on a built-in tag. Declaring the control elements\n * here is the supported way to extend that vocabulary.\n *\n * The three.js and XR Blocks adapters need no equivalent: their parser accepts\n * custom tags unregistered, exposing the tag on\n * `userData.customElement.componentName`. This set reproduces that convention\n * on the IWSDK side - the constructor below stamps the same key and lifts\n * `data-uix-*` attributes into `userData` - so ONE markup file drives every\n * adapter and the engine-free upgraders read it identically everywhere.\n *\n * Register it when creating the world:\n * ```ts\n * const world = await World.create(container, {\n * features: { spatialUI: { kit: 'horizon', componentSets: [uixComponentSet] } },\n * });\n * ```\n */\nimport { htmlComponentSet, type ComponentSet, type ComponentDefinition } from '@drawcall/uikitml';\nimport { z } from 'zod';\nimport { UIX_ELEMENT_TAGS, dataAttributeKey } from '@realitycollective/webxr-uiextensions';\n\n/**\n * Base container, taken from the parser's own `<div>` definition rather than\n * imported from `@pmndrs/uikit` directly - that keeps a single copy of uikit\n * in the tree, whichever version `@iwsdk/core` resolves.\n */\nconst BaseContainer = htmlComponentSet['div']\n ?.component as unknown as new (properties?: Record<string, unknown>) => {\n userData: Record<string, unknown>;\n};\n\n/**\n * A plain uikit Container that records what it was declared as.\n *\n * Anything the schema let through that is not a uikit property - the\n * `data-uix-*` parameters - is moved into `userData` rather than handed to\n * uikit, which would warn about properties it does not recognise.\n */\nfunction controlComponent(tag: string): typeof BaseContainer {\n return class UixControlContainer extends BaseContainer {\n constructor(properties: Record<string, unknown> = {}) {\n const uikitProps: Record<string, unknown> = {};\n const userData: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (key.startsWith('data-') || /^data[A-Z]/.test(key)) {\n userData[dataAttributeKey(key)] = value;\n } else {\n uikitProps[key] = value;\n }\n }\n super(uikitProps);\n Object.assign(this.userData, userData, {\n customElement: { componentName: tag, sourceTag: tag },\n });\n }\n };\n}\n\n/** Permissive: the parameters are ours, and the upgraders validate them. */\nconst schema = z.object({}).passthrough();\n\nfunction define(tag: string): ComponentDefinition {\n return {\n component: controlComponent(tag) as unknown as ComponentDefinition['component'],\n schema,\n canHaveChildren: true,\n };\n}\n\n/**\n * Pass to `spatialUI.componentSets` so `<uix-*>` elements parse on IWSDK 0.5.\n * Without it, a panel using any control fails to parse and never attaches.\n */\nexport const uixComponentSet: ComponentSet = Object.fromEntries(\n UIX_ELEMENT_TAGS.map((tag) => [tag, define(tag)]),\n);\n"]}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -26,4 +26,5 @@ export * from './systems/drag-system.js';
|
|
|
26
26
|
export * from './systems/dock-region-system.js';
|
|
27
27
|
export * from './systems/controls-system.js';
|
|
28
28
|
export * from './systems/touch-guard-system.js';
|
|
29
|
+
export * from './systems/pointer-offer-system.js';
|
|
29
30
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,sEAAsE;AACtE,cAAc,uCAAuC,CAAC;AAEtD,oBAAoB;AACpB,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC","sourcesContent":["/**\n * @realitycollective/iwsdk-uiextensions - the Meta IWSDK adapter.\n *\n * The engine-free UX core (window manager, dock state, region math, drag\n * math, control models, markup upgraders, adapter interfaces) lives in\n * `@realitycollective/webxr-uiextensions` and is re-exported here in full,\n * so this package remains a drop-in single dependency for IWSDK apps.\n *\n * What this package adds is the IWSDK binding: ECS components + systems\n * that apply the core onto `@iwsdk/core` entities, plus the one-call\n * factories and `registerUIExtensions`.\n */\n// The portable core - re-exported for a single-dependency experience.\nexport * from '@realitycollective/webxr-uiextensions';\n\n// IWSDK ECS binding\nexport * from './components.js';\nexport * from './manager-registry.js';\nexport * from './region-registry-store.js';\nexport * from './component-set.js';\nexport * from './factory.js';\nexport * from './scene-host.js';\nexport * from './register.js';\nexport * from './systems/window-system.js';\nexport * from './systems/dock-system.js';\nexport * from './systems/drag-system.js';\nexport * from './systems/dock-region-system.js';\nexport * from './systems/controls-system.js';\nexport * from './systems/touch-guard-system.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,sEAAsE;AACtE,cAAc,uCAAuC,CAAC;AAEtD,oBAAoB;AACpB,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC","sourcesContent":["/**\n * @realitycollective/iwsdk-uiextensions - the Meta IWSDK adapter.\n *\n * The engine-free UX core (window manager, dock state, region math, drag\n * math, control models, markup upgraders, adapter interfaces) lives in\n * `@realitycollective/webxr-uiextensions` and is re-exported here in full,\n * so this package remains a drop-in single dependency for IWSDK apps.\n *\n * What this package adds is the IWSDK binding: ECS components + systems\n * that apply the core onto `@iwsdk/core` entities, plus the one-call\n * factories and `registerUIExtensions`.\n */\n// The portable core - re-exported for a single-dependency experience.\nexport * from '@realitycollective/webxr-uiextensions';\n\n// IWSDK ECS binding\nexport * from './components.js';\nexport * from './manager-registry.js';\nexport * from './region-registry-store.js';\nexport * from './component-set.js';\nexport * from './factory.js';\nexport * from './scene-host.js';\nexport * from './register.js';\nexport * from './systems/window-system.js';\nexport * from './systems/dock-system.js';\nexport * from './systems/drag-system.js';\nexport * from './systems/dock-region-system.js';\nexport * from './systems/controls-system.js';\nexport * from './systems/touch-guard-system.js';\nexport * from './systems/pointer-offer-system.js';\n"]}
|
package/dist/register.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* those to the entities.
|
|
14
14
|
*/
|
|
15
15
|
import type { World } from '@iwsdk/core';
|
|
16
|
+
import { type PointerArbiterLike } from './systems/pointer-offer-system.js';
|
|
16
17
|
import type { TouchPressOptions, WindowManager } from '@realitycollective/webxr-uiextensions';
|
|
17
18
|
export interface RegisterOptions {
|
|
18
19
|
/** Disable the title-bar drag system. */
|
|
@@ -35,5 +36,11 @@ export interface RegisterOptions {
|
|
|
35
36
|
* unsigned-distance behaviour. See `UITouchGuardSystem`.
|
|
36
37
|
*/
|
|
37
38
|
touchGuard?: boolean | Partial<TouchPressOptions>;
|
|
39
|
+
/**
|
|
40
|
+
* The shared pointer arbiter (the Interactions runtime's), so panels take
|
|
41
|
+
* part in the one pointer decision per hand. Without it the panel offers go
|
|
42
|
+
* to an arbiter of their own, which is harmless. See `UIPointerOfferSystem`.
|
|
43
|
+
*/
|
|
44
|
+
pointers?: PointerArbiterLike;
|
|
38
45
|
}
|
|
39
46
|
export declare function registerUIExtensions(world: World, options?: RegisterOptions): WindowManager;
|
package/dist/register.js
CHANGED
|
@@ -4,6 +4,7 @@ import { UIDragSystem } from './systems/drag-system.js';
|
|
|
4
4
|
import { UIDockRegionSystem } from './systems/dock-region-system.js';
|
|
5
5
|
import { UIControlsSystem } from './systems/controls-system.js';
|
|
6
6
|
import { UITouchGuardSystem } from './systems/touch-guard-system.js';
|
|
7
|
+
import { UIPointerOfferSystem, pointerOffersFor } from './systems/pointer-offer-system.js';
|
|
7
8
|
import { windowManagerFor } from './manager-registry.js';
|
|
8
9
|
export function registerUIExtensions(world, options = {}) {
|
|
9
10
|
// The dock system runs first: the hand-menu palm gate it writes each frame
|
|
@@ -26,6 +27,10 @@ export function registerUIExtensions(world, options = {}) {
|
|
|
26
27
|
// reacts to the presses.
|
|
27
28
|
world.registerSystem(UITouchGuardSystem, { priority: -3.9, configData: thresholds });
|
|
28
29
|
}
|
|
30
|
+
// Straight after InputSystem (-4), before the touch guard (-3.9): offers
|
|
31
|
+
// what each pointer reaches to the shared arbiter. Gates nothing.
|
|
32
|
+
pointerOffersFor(world, options.pointers);
|
|
33
|
+
world.registerSystem(UIPointerOfferSystem, { priority: -3.95 });
|
|
29
34
|
return windowManagerFor(world);
|
|
30
35
|
}
|
|
31
36
|
//# sourceMappingURL=register.js.map
|
package/dist/register.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAA2B,MAAM,mCAAmC,CAAC;AACpH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAgCzD,MAAM,UAAU,oBAAoB,CAClC,KAAY,EACZ,UAA2B,EAAE;IAE7B,2EAA2E;IAC3E,0DAA0D;IAC1D,KAAK,CAAC,cAAc,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;IACpF,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3B,KAAK,CAAC,cAAc,CAAC,YAAY,EAAE;YACjC,UAAU,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE;SACrD,CAAC,CAAC;IACL,CAAC;IACD,IAAI,OAAO,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QAC9B,KAAK,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC/B,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,KAAK,KAAK,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACpF,0EAA0E;QAC1E,yBAAyB;QACzB,KAAK,CAAC,cAAc,CAAC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,yEAAyE;IACzE,kEAAkE;IAClE,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1C,KAAK,CAAC,cAAc,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC","sourcesContent":["/**\n * One-call setup: register every UI Extensions system on a world.\n *\n * ```ts\n * const world = await World.create(container, { features: { spatialUI: true } });\n * const windows = registerUIExtensions(world);\n * ```\n *\n * The returned `WindowManager` is the API for changing window state from\n * app code (a hand menu, a keyboard shortcut): `hide`/`show`, `dockTo`/\n * `undock`/`returnHome`, `togglePin`/`setDockMode`, `minimize`/`restore`,\n * `setChrome` and `close`. The systems registered here apply every one of\n * those to the entities.\n */\nimport type { World } from '@iwsdk/core';\nimport { UIWindowSystem } from './systems/window-system.js';\nimport { UIDockSystem } from './systems/dock-system.js';\nimport { UIDragSystem } from './systems/drag-system.js';\nimport { UIDockRegionSystem } from './systems/dock-region-system.js';\nimport { UIControlsSystem } from './systems/controls-system.js';\nimport { UITouchGuardSystem } from './systems/touch-guard-system.js';\nimport { UIPointerOfferSystem, pointerOffersFor, type PointerArbiterLike } from './systems/pointer-offer-system.js';\nimport { windowManagerFor } from './manager-registry.js';\nimport type { TouchPressOptions, WindowManager } from '@realitycollective/webxr-uiextensions';\n\nexport interface RegisterOptions {\n /** Disable the title-bar drag system. */\n drag?: boolean;\n /**\n * Disable near dragging (controller squeeze or hand pinch on the title\n * bar), leaving the far ray as the only way to move a window. On by\n * default; see `UIDragSystem` for what it does to IWSDK's pointers.\n */\n nearDrag?: boolean;\n /** Disable dock regions. */\n regions?: boolean;\n /** Disable the `data-uix` control upgrades. */\n controls?: boolean;\n /**\n * Press / hold / release for IWSDK's near (poke) pointers, so a finger\n * pushed through a panel and pulled back is one click, and a finger\n * arriving from behind is none. On by default; pass thresholds to tune\n * (meters, signed, positive in front), or `false` to keep IWSDK's own\n * unsigned-distance behaviour. See `UITouchGuardSystem`.\n */\n touchGuard?: boolean | Partial<TouchPressOptions>;\n /**\n * The shared pointer arbiter (the Interactions runtime's), so panels take\n * part in the one pointer decision per hand. Without it the panel offers go\n * to an arbiter of their own, which is harmless. See `UIPointerOfferSystem`.\n */\n pointers?: PointerArbiterLike;\n}\n\nexport function registerUIExtensions(\n world: World,\n options: RegisterOptions = {},\n): WindowManager {\n // The dock system runs first: the hand-menu palm gate it writes each frame\n // is then applied by the window system in the same frame.\n world.registerSystem(UIDockSystem, { priority: -1 }).registerSystem(UIWindowSystem);\n if (options.drag !== false) {\n world.registerSystem(UIDragSystem, {\n configData: { nearDrag: options.nearDrag !== false },\n });\n }\n if (options.regions !== false) {\n world.registerSystem(UIDockRegionSystem);\n }\n if (options.controls !== false) {\n world.registerSystem(UIControlsSystem);\n }\n if (options.touchGuard !== false) {\n const thresholds = typeof options.touchGuard === 'object' ? options.touchGuard : {};\n // Straight after InputSystem (-4) has moved the pointers, before anything\n // reacts to the presses.\n world.registerSystem(UITouchGuardSystem, { priority: -3.9, configData: thresholds });\n }\n // Straight after InputSystem (-4), before the touch guard (-3.9): offers\n // what each pointer reaches to the shared arbiter. Gates nothing.\n pointerOffersFor(world, options.pointers);\n world.registerSystem(UIPointerOfferSystem, { priority: -3.95 });\n return windowManagerFor(world);\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UIPointerOfferSystem - tells the shared pointer arbiter which panels
|
|
3
|
+
* IWSDK's pointers reach.
|
|
4
|
+
*
|
|
5
|
+
* IWSDK already arbitrates its own pointers: per hand the first of touch,
|
|
6
|
+
* grab and ray with a hit owns the hand. This system gates nothing. Once per
|
|
7
|
+
* frame, right after `InputSystem` has moved the pointers, it reads what each
|
|
8
|
+
* of a hand's three pointers intersects and OFFERS the ones that sit under a
|
|
9
|
+
* panel (a `PanelUI` entity's object, or any descendant) to the family's
|
|
10
|
+
* `PanelPointerOffers`. The shared arbiter, and the visuals the Interactions
|
|
11
|
+
* runtime publishes from it, then know about panels on IWSDK exactly as they
|
|
12
|
+
* do on every other platform.
|
|
13
|
+
*
|
|
14
|
+
* Offers are keyed by the hand name ('left', 'right'), which the Interactions
|
|
15
|
+
* runtime aliases to its own source ids. The panel id is the window id when
|
|
16
|
+
* the entity is a managed window, else its `PanelUI` config path (the same id
|
|
17
|
+
* the scene host announces), else the entity index. A kind with no panel hit
|
|
18
|
+
* is offered as `undefined`. A pointer that is pressing (`getButtonsDown()`
|
|
19
|
+
* not empty) sets the selection lock for that kind.
|
|
20
|
+
*/
|
|
21
|
+
import { type World } from '@iwsdk/core';
|
|
22
|
+
import { PanelPointerOffers } from '@realitycollective/webxr-uiextensions';
|
|
23
|
+
/** The shared arbiter type, taken from the core so this package needs no direct dependency on it. */
|
|
24
|
+
export type PointerArbiterLike = NonNullable<ConstructorParameters<typeof PanelPointerOffers>[0]>;
|
|
25
|
+
/**
|
|
26
|
+
* The `PanelPointerOffers` for a world: built on the first call (with
|
|
27
|
+
* `pointers` when given, else an arbiter of its own) and the same instance
|
|
28
|
+
* after. `registerUIExtensions(world, { pointers })` makes the first call.
|
|
29
|
+
*/
|
|
30
|
+
export declare function pointerOffersFor(world: World, pointers?: PointerArbiterLike): PanelPointerOffers;
|
|
31
|
+
declare const UIPointerOfferSystem_base: import("elics").SystemConstructor<import("elics").SystemSchema, {
|
|
32
|
+
panels: {
|
|
33
|
+
required: import("elics").Component<{
|
|
34
|
+
config: {
|
|
35
|
+
type: import("@iwsdk/core").Types.String;
|
|
36
|
+
default: string;
|
|
37
|
+
};
|
|
38
|
+
}>[];
|
|
39
|
+
};
|
|
40
|
+
}, World, import("@iwsdk/core").System<import("elics").SystemSchema, {
|
|
41
|
+
panels: {
|
|
42
|
+
required: import("elics").Component<{
|
|
43
|
+
config: {
|
|
44
|
+
type: import("@iwsdk/core").Types.String;
|
|
45
|
+
default: string;
|
|
46
|
+
};
|
|
47
|
+
}>[];
|
|
48
|
+
};
|
|
49
|
+
}>>;
|
|
50
|
+
export declare class UIPointerOfferSystem extends UIPointerOfferSystem_base {
|
|
51
|
+
private offers;
|
|
52
|
+
init(): void;
|
|
53
|
+
update(): void;
|
|
54
|
+
}
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UIPointerOfferSystem - tells the shared pointer arbiter which panels
|
|
3
|
+
* IWSDK's pointers reach.
|
|
4
|
+
*
|
|
5
|
+
* IWSDK already arbitrates its own pointers: per hand the first of touch,
|
|
6
|
+
* grab and ray with a hit owns the hand. This system gates nothing. Once per
|
|
7
|
+
* frame, right after `InputSystem` has moved the pointers, it reads what each
|
|
8
|
+
* of a hand's three pointers intersects and OFFERS the ones that sit under a
|
|
9
|
+
* panel (a `PanelUI` entity's object, or any descendant) to the family's
|
|
10
|
+
* `PanelPointerOffers`. The shared arbiter, and the visuals the Interactions
|
|
11
|
+
* runtime publishes from it, then know about panels on IWSDK exactly as they
|
|
12
|
+
* do on every other platform.
|
|
13
|
+
*
|
|
14
|
+
* Offers are keyed by the hand name ('left', 'right'), which the Interactions
|
|
15
|
+
* runtime aliases to its own source ids. The panel id is the window id when
|
|
16
|
+
* the entity is a managed window, else its `PanelUI` config path (the same id
|
|
17
|
+
* the scene host announces), else the entity index. A kind with no panel hit
|
|
18
|
+
* is offered as `undefined`. A pointer that is pressing (`getButtonsDown()`
|
|
19
|
+
* not empty) sets the selection lock for that kind.
|
|
20
|
+
*/
|
|
21
|
+
import { PanelUI } from '@iwsdk/core';
|
|
22
|
+
import { createSystem } from '../create-system.js';
|
|
23
|
+
import { UIWindow } from '../components.js';
|
|
24
|
+
import { PanelPointerOffers, } from '@realitycollective/webxr-uiextensions';
|
|
25
|
+
const HANDS = ['left', 'right'];
|
|
26
|
+
const KINDS = ['touch', 'grab', 'ray'];
|
|
27
|
+
const offersByWorld = new WeakMap();
|
|
28
|
+
/**
|
|
29
|
+
* The `PanelPointerOffers` for a world: built on the first call (with
|
|
30
|
+
* `pointers` when given, else an arbiter of its own) and the same instance
|
|
31
|
+
* after. `registerUIExtensions(world, { pointers })` makes the first call.
|
|
32
|
+
*/
|
|
33
|
+
export function pointerOffersFor(world, pointers) {
|
|
34
|
+
let offers = offersByWorld.get(world);
|
|
35
|
+
if (!offers) {
|
|
36
|
+
offers = new PanelPointerOffers(pointers);
|
|
37
|
+
offersByWorld.set(world, offers);
|
|
38
|
+
}
|
|
39
|
+
return offers;
|
|
40
|
+
}
|
|
41
|
+
export class UIPointerOfferSystem extends createSystem({
|
|
42
|
+
panels: { required: [PanelUI] },
|
|
43
|
+
}) {
|
|
44
|
+
offers;
|
|
45
|
+
init() {
|
|
46
|
+
this.offers = pointerOffersFor(this.world);
|
|
47
|
+
const offers = this.offers;
|
|
48
|
+
this.cleanupFuncs.push(() => {
|
|
49
|
+
offers.dispose();
|
|
50
|
+
offersByWorld.delete(this.world);
|
|
51
|
+
this.offers = undefined;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
update() {
|
|
55
|
+
const offers = this.offers;
|
|
56
|
+
const xr = this.input?.xr;
|
|
57
|
+
if (!offers || !xr) {
|
|
58
|
+
return; // headless, or no XR input: nothing to offer
|
|
59
|
+
}
|
|
60
|
+
const roots = new Map();
|
|
61
|
+
for (const entity of this.queries.panels.entities) {
|
|
62
|
+
if (entity.object3D) {
|
|
63
|
+
roots.set(entity.object3D, entity);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const hand of HANDS) {
|
|
67
|
+
const multi = xr.multiPointers[hand];
|
|
68
|
+
for (const kind of KINDS) {
|
|
69
|
+
const pointer = multi.getPointer(kind);
|
|
70
|
+
const hit = pointer.getIntersection();
|
|
71
|
+
const entity = hit ? panelOf(hit.object, roots) : undefined;
|
|
72
|
+
offers.offer(hand, kind, hit && entity
|
|
73
|
+
? { panelId: panelIdOf(entity), point: [hit.point.x, hit.point.y, hit.point.z], distance: hit.distance }
|
|
74
|
+
: undefined);
|
|
75
|
+
offers.setSelecting(hand, kind, (pointer.getButtonsDown?.().size ?? 0) > 0);
|
|
76
|
+
}
|
|
77
|
+
offers.resolve(hand);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** The panel entity whose root is `object` or one of its ancestors. */
|
|
82
|
+
function panelOf(object, roots) {
|
|
83
|
+
for (let node = object; node; node = node.parent) {
|
|
84
|
+
const entity = roots.get(node);
|
|
85
|
+
if (entity) {
|
|
86
|
+
return entity;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
/** Window id, else config path, else entity index: the scene host's naming. */
|
|
92
|
+
function panelIdOf(entity) {
|
|
93
|
+
const windowId = entity.hasComponent(UIWindow)
|
|
94
|
+
? UIWindow.data.windowId[entity.index]
|
|
95
|
+
: '';
|
|
96
|
+
return windowId || PanelUI.data.config[entity.index] || String(entity.index);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=pointer-offer-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointer-offer-system.js","sourceRoot":"","sources":["../../src/systems/pointer-offer-system.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UIPointerOfferSystem - tells the shared pointer arbiter which panels\n * IWSDK's pointers reach.\n *\n * IWSDK already arbitrates its own pointers: per hand the first of touch,\n * grab and ray with a hit owns the hand. This system gates nothing. Once per\n * frame, right after `InputSystem` has moved the pointers, it reads what each\n * of a hand's three pointers intersects and OFFERS the ones that sit under a\n * panel (a `PanelUI` entity's object, or any descendant) to the family's\n * `PanelPointerOffers`. The shared arbiter, and the visuals the Interactions\n * runtime publishes from it, then know about panels on IWSDK exactly as they\n * do on every other platform.\n *\n * Offers are keyed by the hand name ('left', 'right'), which the Interactions\n * runtime aliases to its own source ids. The panel id is the window id when\n * the entity is a managed window, else its `PanelUI` config path (the same id\n * the scene host announces), else the entity index. A kind with no panel hit\n * is offered as `undefined`. A pointer that is pressing (`getButtonsDown()`\n * not empty) sets the selection lock for that kind.\n */\nimport { PanelUI, type Entity, type World } from '@iwsdk/core';\nimport type { Object3D } from 'three';\nimport { createSystem } from '../create-system.js';\nimport { UIWindow } from '../components.js';\nimport {\n PanelPointerOffers,\n type Hand,\n} from '@realitycollective/webxr-uiextensions';\n\n/** The shared arbiter type, taken from the core so this package needs no direct dependency on it. */\nexport type PointerArbiterLike = NonNullable<ConstructorParameters<typeof PanelPointerOffers>[0]>;\ntype NearPointerKind = Parameters<PanelPointerOffers['offer']>[1];\n\nconst HANDS: readonly Hand[] = ['left', 'right'];\nconst KINDS: readonly NearPointerKind[] = ['touch', 'grab', 'ray'];\n\nconst offersByWorld = new WeakMap<World, PanelPointerOffers>();\n\n/**\n * The `PanelPointerOffers` for a world: built on the first call (with\n * `pointers` when given, else an arbiter of its own) and the same instance\n * after. `registerUIExtensions(world, { pointers })` makes the first call.\n */\nexport function pointerOffersFor(world: World, pointers?: PointerArbiterLike): PanelPointerOffers {\n let offers = offersByWorld.get(world);\n if (!offers) {\n offers = new PanelPointerOffers(pointers);\n offersByWorld.set(world, offers);\n }\n return offers;\n}\n\n/** The slice of a pointer-events sub-pointer this system reads. */\ninterface OfferPointerLike {\n getIntersection():\n | { object: Object3D; point: { x: number; y: number; z: number }; distance: number }\n | undefined;\n getButtonsDown?(): ReadonlySet<number>;\n}\n\nexport class UIPointerOfferSystem extends createSystem({\n panels: { required: [PanelUI] },\n}) {\n private offers: PanelPointerOffers | undefined;\n\n override init(): void {\n this.offers = pointerOffersFor(this.world);\n const offers = this.offers;\n this.cleanupFuncs.push(() => {\n offers.dispose();\n offersByWorld.delete(this.world);\n this.offers = undefined;\n });\n }\n\n override update(): void {\n const offers = this.offers;\n const xr = (this.input as typeof this.input | undefined)?.xr;\n if (!offers || !xr) {\n return; // headless, or no XR input: nothing to offer\n }\n const roots = new Map<Object3D, Entity>();\n for (const entity of this.queries.panels.entities) {\n if (entity.object3D) {\n roots.set(entity.object3D, entity);\n }\n }\n for (const hand of HANDS) {\n const multi = xr.multiPointers[hand];\n for (const kind of KINDS) {\n const pointer = multi.getPointer(kind) as unknown as OfferPointerLike;\n const hit = pointer.getIntersection();\n const entity = hit ? panelOf(hit.object, roots) : undefined;\n offers.offer(\n hand,\n kind,\n hit && entity\n ? { panelId: panelIdOf(entity), point: [hit.point.x, hit.point.y, hit.point.z], distance: hit.distance }\n : undefined,\n );\n offers.setSelecting(hand, kind, (pointer.getButtonsDown?.().size ?? 0) > 0);\n }\n offers.resolve(hand);\n }\n }\n}\n\n/** The panel entity whose root is `object` or one of its ancestors. */\nfunction panelOf(object: Object3D, roots: ReadonlyMap<Object3D, Entity>): Entity | undefined {\n for (let node: Object3D | null = object; node; node = node.parent) {\n const entity = roots.get(node);\n if (entity) {\n return entity;\n }\n }\n return undefined;\n}\n\n/** Window id, else config path, else entity index: the scene host's naming. */\nfunction panelIdOf(entity: Entity): string {\n const windowId = entity.hasComponent(UIWindow)\n ? (UIWindow.data.windowId[entity.index] as string)\n : '';\n return windowId || (PanelUI.data.config[entity.index] as string) || String(entity.index);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@realitycollective/iwsdk-uiextensions",
|
|
3
|
-
"version": "0.1.1
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Meta IWSDK adapter for the Reality Collective UI Extensions - windowing, docking, layout regions and extra controls, binding the engine-free @realitycollective/webxr-uiextensions core onto IWSDK's ECS, UIKitML and interaction systems.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"realitycollective",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"windowing",
|
|
13
13
|
"uikitml",
|
|
14
14
|
"spatial-ui",
|
|
15
|
-
"typescript"
|
|
15
|
+
"typescript",
|
|
16
|
+
"hand-menu",
|
|
17
|
+
"pointer",
|
|
18
|
+
"docking"
|
|
16
19
|
],
|
|
17
20
|
"license": "MIT",
|
|
18
21
|
"author": "Reality Collective",
|
|
@@ -39,7 +42,8 @@
|
|
|
39
42
|
"@drawcall/uikitml": "0.1.8",
|
|
40
43
|
"zod": "^4.4.3",
|
|
41
44
|
"@pmndrs/handle": "^6.6.16",
|
|
42
|
-
"@realitycollective/webxr-
|
|
45
|
+
"@realitycollective/webxr-input": "^0.1.8",
|
|
46
|
+
"@realitycollective/webxr-uiextensions": "^0.1.1"
|
|
43
47
|
},
|
|
44
48
|
"peerDependencies": {
|
|
45
49
|
"@iwsdk/core": ">=1.0.0 <2.0.0",
|