@realitycollective/iwsdk-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 +15 -2
- package/Examples/README.md +1 -1
- package/Examples/basic-window/hand-menu.uikitml +37 -0
- package/Examples/basic-window/main.ts +87 -6
- package/Examples/dock-regions/main.ts +11 -1
- package/README.md +60 -2
- package/dist/components.d.ts +84 -6
- package/dist/components.js +42 -7
- package/dist/components.js.map +1 -1
- package/dist/factory.d.ts +3 -1
- package/dist/factory.js +13 -4
- package/dist/factory.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 +22 -2
- package/dist/register.js +13 -2
- package/dist/register.js.map +1 -1
- package/dist/scene-host.js +8 -0
- package/dist/scene-host.js.map +1 -1
- package/dist/systems/dock-region-system.d.ts +86 -6
- package/dist/systems/dock-region-system.js +16 -2
- package/dist/systems/dock-region-system.js.map +1 -1
- package/dist/systems/dock-system.d.ts +111 -6
- package/dist/systems/dock-system.js +80 -4
- package/dist/systems/dock-system.js.map +1 -1
- package/dist/systems/drag-system.d.ts +202 -65
- package/dist/systems/drag-system.js +145 -9
- package/dist/systems/drag-system.js.map +1 -1
- package/dist/systems/touch-guard-system.d.ts +98 -0
- package/dist/systems/touch-guard-system.js +113 -0
- package/dist/systems/touch-guard-system.js.map +1 -0
- package/dist/systems/window-system.d.ts +238 -13
- package/dist/systems/window-system.js +215 -51
- package/dist/systems/window-system.js.map +1 -1
- package/package.json +2 -2
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/
|
|
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/Examples/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Copy-paste starting points, shipped inside the npm package (Unity-style `Example
|
|
|
4
4
|
|
|
5
5
|
| Example | Shows |
|
|
6
6
|
| --- | --- |
|
|
7
|
-
| [`basic-window/`](./basic-window) | A managed window with
|
|
7
|
+
| [`basic-window/`](./basic-window) | A managed window driven from a hand menu: a `hand-locked` panel on the left hand with HIDE, PIN, HOME, MIN, BUTTONS and CLOSE, each one `WindowManager` call |
|
|
8
8
|
| [`dock-regions/`](./dock-regions) | Layout regions, drop-to-dock, a follow-region "toolbar" |
|
|
9
9
|
| [`controls/`](./controls) | `data-uix` stepper, toggle, expandable label and log view |
|
|
10
10
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.uix-hand-menu {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
background-color: rgba(16, 26, 38, 0.92);
|
|
6
|
+
border-color: #2e4a66;
|
|
7
|
+
border-width: 0.75;
|
|
8
|
+
border-radius: 10;
|
|
9
|
+
padding: 6;
|
|
10
|
+
}
|
|
11
|
+
.uix-hand-menu-stack { display: flex; flex-direction: column; }
|
|
12
|
+
.uix-hand-menu-button {
|
|
13
|
+
width: 110;
|
|
14
|
+
padding: 6;
|
|
15
|
+
margin-top: 3;
|
|
16
|
+
font-size: 10;
|
|
17
|
+
font-weight: bold;
|
|
18
|
+
text-align: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
color: #ffffff;
|
|
21
|
+
background-color: #2c4a6b;
|
|
22
|
+
border-radius: 6;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
}
|
|
25
|
+
.uix-hand-menu-button:hover { background-color: #3d648e; }
|
|
26
|
+
.uix-hand-menu-button:active { background-color: #1f3a55; }
|
|
27
|
+
</style>
|
|
28
|
+
<div id="uix-window" class="uix-hand-menu">
|
|
29
|
+
<div id="uix-content" class="uix-hand-menu-stack">
|
|
30
|
+
<div id="menu-hide" class="uix-hand-menu-button">HIDE</div>
|
|
31
|
+
<div id="menu-pin" class="uix-hand-menu-button">PIN</div>
|
|
32
|
+
<div id="menu-home" class="uix-hand-menu-button">HOME</div>
|
|
33
|
+
<div id="menu-minimize" class="uix-hand-menu-button">MIN</div>
|
|
34
|
+
<div id="menu-chrome" class="uix-hand-menu-button">BUTTONS ON</div>
|
|
35
|
+
<div id="menu-close" class="uix-hand-menu-button">CLOSE</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
@@ -1,28 +1,109 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Basic window -
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Basic window - a managed window plus a hand menu that drives it.
|
|
3
|
+
*
|
|
4
|
+
* The window spawns with NO title-bar buttons (that is the default). Its
|
|
5
|
+
* manipulation lives on a hand menu instead: a `hand-locked` window that
|
|
6
|
+
* rides the left hand above the fingertips and shows while that palm faces
|
|
7
|
+
* you. Every menu button is one `WindowManager` call on the window, and the
|
|
8
|
+
* labels read back from the record so they always name the next action.
|
|
9
|
+
*
|
|
10
|
+
* Put window.uikitml and hand-menu.uikitml in your app's public/ui/ folder -
|
|
11
|
+
* IWSDK 0.5 fetches and parses the source at runtime, so there is no
|
|
12
|
+
* compile step.
|
|
5
13
|
*/
|
|
6
14
|
import { World } from '@iwsdk/core';
|
|
7
15
|
import {
|
|
8
16
|
DockMode,
|
|
9
|
-
|
|
17
|
+
createSceneHost,
|
|
18
|
+
minimizeLabelFor,
|
|
19
|
+
pinLabelFor,
|
|
10
20
|
registerUIExtensions,
|
|
11
21
|
} from '@realitycollective/iwsdk-uiextensions';
|
|
12
22
|
|
|
23
|
+
const TARGET = 'hello';
|
|
24
|
+
|
|
25
|
+
type Uikit = {
|
|
26
|
+
addEventListener(type: string, listener: () => void): void;
|
|
27
|
+
setProperties(props: Record<string, unknown>): void;
|
|
28
|
+
};
|
|
29
|
+
|
|
13
30
|
export async function start(container: HTMLDivElement) {
|
|
14
31
|
const world = await World.create(container, {
|
|
15
32
|
features: { spatialUI: true },
|
|
16
33
|
});
|
|
17
34
|
|
|
18
35
|
const windows = registerUIExtensions(world);
|
|
36
|
+
const host = createSceneHost(world);
|
|
19
37
|
|
|
20
|
-
|
|
21
|
-
id:
|
|
38
|
+
host.createWindow({
|
|
39
|
+
id: TARGET,
|
|
22
40
|
title: 'Hello Window',
|
|
23
41
|
config: './ui/window.uikitml',
|
|
24
42
|
dockMode: DockMode.BodyFollow, // follows until pinned or dragged
|
|
25
43
|
followOffset: [0, -0.15, -1.2],
|
|
44
|
+
// Title-bar buttons are off unless asked for; the hand menu can switch
|
|
45
|
+
// them on with `setChrome` (BUTTONS ON below).
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// The hand menu. `hand`, `anchor` and the palm gate are the developer's
|
|
49
|
+
// choice; these are the defaults written out. Try `anchor: 'inside'` or
|
|
50
|
+
// `hand: 'either'`.
|
|
51
|
+
const menu = host.createWindow({
|
|
52
|
+
id: 'hand-menu',
|
|
53
|
+
config: './ui/hand-menu.uikitml',
|
|
54
|
+
dockMode: DockMode.HandLocked,
|
|
55
|
+
handMenu: { hand: 'left', anchor: 'above', palmGate: true },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
menu.onReady((panel) => {
|
|
59
|
+
const element = (id: string) => panel.getElementById(id) as Uikit | undefined;
|
|
60
|
+
|
|
61
|
+
const refresh = (): void => {
|
|
62
|
+
const record = windows.get(TARGET);
|
|
63
|
+
if (!record) {
|
|
64
|
+
return; // closed: the buttons stay, and do nothing
|
|
65
|
+
}
|
|
66
|
+
element('menu-hide')?.setProperties({ text: record.hidden ? 'SHOW' : 'HIDE' });
|
|
67
|
+
element('menu-pin')?.setProperties({ text: pinLabelFor(record) });
|
|
68
|
+
element('menu-minimize')?.setProperties({ text: minimizeLabelFor(record) });
|
|
69
|
+
element('menu-chrome')?.setProperties({
|
|
70
|
+
text: record.chrome.pin ? 'BUTTONS OFF' : 'BUTTONS ON',
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const on = (id: string, action: () => void): void => {
|
|
75
|
+
element(id)?.addEventListener('click', () => {
|
|
76
|
+
if (windows.has(TARGET)) {
|
|
77
|
+
action();
|
|
78
|
+
}
|
|
79
|
+
refresh();
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
on('menu-hide', () => windows.toggleHidden(TARGET));
|
|
83
|
+
on('menu-pin', () => windows.togglePin(TARGET));
|
|
84
|
+
on('menu-home', () => windows.returnHome(TARGET));
|
|
85
|
+
on('menu-minimize', () => windows.toggleMinimized(TARGET));
|
|
86
|
+
on('menu-chrome', () => {
|
|
87
|
+
const enable = !(windows.get(TARGET)?.chrome.pin ?? false);
|
|
88
|
+
windows.setChrome(TARGET, { pin: enable, dock: enable, minimize: enable, close: enable });
|
|
89
|
+
});
|
|
90
|
+
on('menu-close', () => windows.close(TARGET));
|
|
91
|
+
|
|
92
|
+
// The window can change under the menu (its own title bar once enabled,
|
|
93
|
+
// a drag), so re-read on every manager event.
|
|
94
|
+
for (const event of [
|
|
95
|
+
'hidden',
|
|
96
|
+
'shown',
|
|
97
|
+
'dockChanged',
|
|
98
|
+
'minimized',
|
|
99
|
+
'restored',
|
|
100
|
+
'chromeChanged',
|
|
101
|
+
'dragEnded',
|
|
102
|
+
'closed',
|
|
103
|
+
] as const) {
|
|
104
|
+
windows.events.on(event, refresh);
|
|
105
|
+
}
|
|
106
|
+
refresh();
|
|
26
107
|
});
|
|
27
108
|
|
|
28
109
|
windows.events.on('dockChanged', ({ window, previous }) => {
|
|
@@ -16,7 +16,7 @@ export async function start(container: HTMLDivElement) {
|
|
|
16
16
|
const world = await World.create(container, {
|
|
17
17
|
features: { spatialUI: true },
|
|
18
18
|
});
|
|
19
|
-
registerUIExtensions(world);
|
|
19
|
+
const windows = registerUIExtensions(world);
|
|
20
20
|
|
|
21
21
|
// A world-locked column on the wall - drop windows to stack them.
|
|
22
22
|
createDockRegion(world, {
|
|
@@ -43,6 +43,8 @@ export async function start(container: HTMLDivElement) {
|
|
|
43
43
|
title: 'Docked Window',
|
|
44
44
|
config: './ui/window.uikitml',
|
|
45
45
|
region: 'wall',
|
|
46
|
+
dockable: true, // DOCK returns it to the wall after a drag elsewhere
|
|
47
|
+
pinnable: true,
|
|
46
48
|
});
|
|
47
49
|
|
|
48
50
|
// …and one free-floating to drag into either region.
|
|
@@ -51,5 +53,13 @@ export async function start(container: HTMLDivElement) {
|
|
|
51
53
|
title: 'Drag Me Into A Region',
|
|
52
54
|
config: './ui/window.uikitml',
|
|
53
55
|
position: [0, 1.5, -1.4],
|
|
56
|
+
dockable: true,
|
|
57
|
+
pinnable: true,
|
|
54
58
|
});
|
|
59
|
+
|
|
60
|
+
// Regions are reachable from code too, no drag needed:
|
|
61
|
+
// windows.dockTo('floating', 'toolbar');
|
|
62
|
+
// windows.undock('floating');
|
|
63
|
+
// windows.returnHome('docked');
|
|
64
|
+
void windows;
|
|
55
65
|
}
|
package/README.md
CHANGED
|
@@ -8,9 +8,10 @@ Windowing, docking, layout regions and extra controls for [Meta's Immersive Web
|
|
|
8
8
|
|
|
9
9
|
| Feature | What you get |
|
|
10
10
|
| --- | --- |
|
|
11
|
-
| **Windows** | Title-bar chrome (pin / minimize / close), focus & z-ordering, a per-world `WindowManager` with typed events |
|
|
11
|
+
| **Windows** | Title-bar chrome (pin / dock / minimize / close, each opt-in), focus & z-ordering, hide/show, a per-world `WindowManager` with typed events that is also the API for driving a window from code |
|
|
12
|
+
| **Hand menus** | A `hand-locked` window rides a hand (left, right or whichever is raised), anchored above the fingertips, at the wrist, or on the thumb or little-finger side, shown while the palm faces you; a vertical stack of buttons that sizes to its content, driving other windows through the manager |
|
|
12
13
|
| **Dock states** | `world-locked` (place in space) ⇄ `body-follow` (lazy follow) ⇄ `head-locked`, realised with the IWSDK's own `Follower`/`ScreenSpace` |
|
|
13
|
-
| **Manipulation** | Drag windows by the title bar (powered by `@pmndrs/handle`, the same library behind IWSDK grabbing
|
|
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 |
|
|
14
15
|
| **Layout regions** | Named regions (row / column / grid slots) windows snap into; regions can themselves follow the player |
|
|
15
16
|
| **Controls** | `data-uix` markup 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/…) |
|
|
16
17
|
|
|
@@ -47,9 +48,18 @@ createUIWindow(world, {
|
|
|
47
48
|
title: 'Player Status',
|
|
48
49
|
config: './ui/status.uikitml', // UIKitML source; IWSDK 0.5 parses it at runtime
|
|
49
50
|
dockMode: DockMode.BodyFollow, // follows until the user pins it
|
|
51
|
+
pinnable: true, // title-bar buttons are off unless asked for
|
|
52
|
+
minimizable: true,
|
|
50
53
|
});
|
|
51
54
|
|
|
52
55
|
windows.events.on('closed', (w) => console.log(`${w.title} closed`));
|
|
56
|
+
|
|
57
|
+
// The manager is also how code changes a window - from a hand menu, say:
|
|
58
|
+
windows.hide('status'); // and show(), toggleHidden()
|
|
59
|
+
windows.togglePin('status'); // or setDockMode(id, DockMode.WorldLocked)
|
|
60
|
+
windows.dockTo('status', 'wall'); // undock(id), returnHome(id)
|
|
61
|
+
windows.setChrome('status', { close: true, dock: true });
|
|
62
|
+
windows.close('status'); // destroys the entity
|
|
53
63
|
```
|
|
54
64
|
|
|
55
65
|
### Window markup
|
|
@@ -94,12 +104,60 @@ controls.stepper('health').events.on('change', (hp) => setHealth(hp));
|
|
|
94
104
|
|
|
95
105
|
> UIKitML note: every dynamic-text element needs a literal placeholder child (`<span data-uix-role="value">.</span>`) or no Text node is created.
|
|
96
106
|
|
|
107
|
+
**Every title-bar button is off by default.** Keep all four in the markup, then enable the ones a window should have with `closable`, `minimizable`, `pinnable` and `dockable` at spawn, or later with `windows.setChrome(id, { pin: true })`. A disabled button is hidden and its click ignored; enabling one needs no rewiring.
|
|
108
|
+
|
|
97
109
|
### Interaction model
|
|
98
110
|
|
|
99
111
|
- **Drag** the title bar with the ray (or mouse on desktop) to move a window; it billboards toward you while dragging and settles facing you when released. A press only becomes a drag after `dragDelay` seconds (default 0.3, per-window on `UIWindow`) - shorter presses stay clicks, and the chrome buttons swallow their presses entirely, so PIN/DOCK/MIN/X never fight the drag gesture.
|
|
112
|
+
- **Near grab** the title bar to pick the window up: squeeze with a controller, or pinch with a tracked hand while it is on the bar. A near grab is a deliberate gesture, so it drags at once with no hold delay. This does not need `features.grabbing`: `UIDragSystem` enables IWSDK's near `grab` pointer itself, lists every movable title bar as a target for it each frame, and forwards a hand pinch to it only while the hand is on a title bar, so pinching anywhere else still means what your app decided. Pass `registerUIExtensions(world, { nearDrag: false })` to keep the ray as the only way to move windows.
|
|
100
113
|
- **Drop** a window inside a region's snap radius to dock it into the next slot; drag it out again to undock.
|
|
101
114
|
- **Pin** toggles `body-follow` ⇄ `world-locked` ("place in space").
|
|
102
115
|
- Dragging a following window implicitly places it - pin re-attaches it.
|
|
116
|
+
- **Hide** takes a window out of view and out of reach (no ray or poke can hit it) while keeping its dock mode, region slot and minimized state; **show** brings it back exactly where it was and in front. Minimize collapses the body but leaves the title bar drawn.
|
|
117
|
+
- **Poke** (near touch) is guarded. IWSDK's own touch pointer presses and releases on an unsigned distance to the panel, so a finger pushed through a panel and pulled back out fires two clicks, and a finger arriving from behind presses. `UITouchGuardSystem` drives the two touch pointers from the core's `TouchPress` state machine instead: a press only when the fingertip enters from the front (2 cm), a hold however deep it goes and whichever way it comes back, a release only on coming back out past 3 cm or on losing contact, and no second press until that release. Every poke target in the app gets this, IWSDK's own panels included. A press that starts on one button and ends over another clicks neither (the release lands where the finger is; IWSDK clicks only when both ends are the same element), and IWSDK's 800 ms click window still applies. Tune with `registerUIExtensions(world, { touchGuard: { pressDistance, releaseDistance, allowFromBehind } })`, or `touchGuard: false` for IWSDK's own behaviour.
|
|
118
|
+
|
|
119
|
+
### Hand menus
|
|
120
|
+
|
|
121
|
+
Spawn a window with `dockMode: DockMode.HandLocked` and it becomes a hand menu in the manner of MRTK 2's: it rides a hand and shows while that palm is raised toward you. `handMenu` says how:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
host.createWindow({
|
|
125
|
+
id: 'menu',
|
|
126
|
+
config: './ui/hand-menu.uikitml',
|
|
127
|
+
dockMode: DockMode.HandLocked,
|
|
128
|
+
handMenu: {
|
|
129
|
+
hand: 'left', // 'left' | 'right' | 'either' (whichever palm is raised)
|
|
130
|
+
anchor: 'above', // 'above' fingertips | 'inside' (thumb) | 'outside' | 'wrist'
|
|
131
|
+
anchorDistance: 0.12, // meters from the palm
|
|
132
|
+
offset: [0, 0, 0], // extra hand-local nudge
|
|
133
|
+
palmGate: true, // show only while the palm faces you
|
|
134
|
+
palmAngle: 60, // how far off square the palm may be, degrees
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Every field is optional; the defaults are the values shown. `windows.setHandMenu(id, { hand: 'right' })` changes them at runtime. The hand pose is the player rig's grip space, so a controller's grip or a tracked hand both work, and the panel always turns to face you. While the gate is shut the menu is neither drawn nor hittable, and `hide()` still wins over an open gate. Pinning or dragging a hand menu makes it an ordinary world-locked window where it was.
|
|
140
|
+
|
|
141
|
+
Use `HAND_MENU_SNIPPET` from the core as the markup starting point: the same `uix-window` / `uix-content` ids, no title bar, a vertical stack of buttons that sizes to its content. `Examples/basic-window/` is a window whose manipulation buttons live on such a menu.
|
|
142
|
+
|
|
143
|
+
### Driving windows from code
|
|
144
|
+
|
|
145
|
+
`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 the XR Blocks adapter:
|
|
146
|
+
|
|
147
|
+
| Call | Effect |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `hide(id)` / `show(id)` / `toggleHidden(id)` | Out of view and unhittable; back in place and in front |
|
|
150
|
+
| `minimize(id)` / `restore(id)` / `toggleMinimized(id)` | Collapse / expand the body |
|
|
151
|
+
| `togglePin(id)` / `setDockMode(id, mode)` | Follow the player or stay put |
|
|
152
|
+
| `dockTo(id, regionId)` / `undock(id)` | Into a region slot (world-locked) / out of it |
|
|
153
|
+
| `returnHome(id)` | Back to the spawn region, or the spawn placement and mode (what DOCK does) |
|
|
154
|
+
| `setChrome(id, { pin, dock, minimize, close })` | Enable or disable title-bar buttons at runtime |
|
|
155
|
+
| `focus(id)` | Bring to the front |
|
|
156
|
+
| `close(id)` | Destroy the window's entity |
|
|
157
|
+
|
|
158
|
+
| `setHandMenu(id, { hand, anchor, ... })` | Move a hand menu to the other hand or another anchor |
|
|
159
|
+
|
|
160
|
+
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/`.
|
|
103
161
|
|
|
104
162
|
See `Examples/` (shipped in this package) and the deployable showcase client in the repository for complete, working demonstrations of every feature.
|
|
105
163
|
|
package/dist/components.d.ts
CHANGED
|
@@ -8,6 +8,19 @@
|
|
|
8
8
|
* components to realise dock modes rather than re-implementing them.
|
|
9
9
|
*/
|
|
10
10
|
import { Types } from '@iwsdk/core';
|
|
11
|
+
/** `HandMenuOptions.hand` as an enum object for the component schema. */
|
|
12
|
+
export declare const HandChoice: {
|
|
13
|
+
readonly Left: "left";
|
|
14
|
+
readonly Right: "right";
|
|
15
|
+
readonly Either: "either";
|
|
16
|
+
};
|
|
17
|
+
/** `HandMenuOptions.anchor` as an enum object for the component schema. */
|
|
18
|
+
export declare const HandAnchor: {
|
|
19
|
+
readonly Above: "above";
|
|
20
|
+
readonly Inside: "inside";
|
|
21
|
+
readonly Outside: "outside";
|
|
22
|
+
readonly Wrist: "wrist";
|
|
23
|
+
};
|
|
11
24
|
/** Region flow options mirrored as an enum object for the component schema. */
|
|
12
25
|
export declare const RegionFlowType: {
|
|
13
26
|
readonly Row: "row";
|
|
@@ -37,6 +50,7 @@ export declare const UIWindow: import("elics").Component<{
|
|
|
37
50
|
readonly WorldLocked: "world-locked";
|
|
38
51
|
readonly BodyFollow: "body-follow";
|
|
39
52
|
readonly HeadLocked: "head-locked";
|
|
53
|
+
readonly HandLocked: "hand-locked";
|
|
40
54
|
};
|
|
41
55
|
default: "world-locked";
|
|
42
56
|
};
|
|
@@ -45,20 +59,28 @@ export declare const UIWindow: import("elics").Component<{
|
|
|
45
59
|
type: Types.Boolean;
|
|
46
60
|
default: true;
|
|
47
61
|
};
|
|
48
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Title-bar buttons, all OFF by default. These seed the window's chrome
|
|
64
|
+
* state on adoption; after that `WindowManager.setChrome` is the way to
|
|
65
|
+
* change them, and the values here are kept in step with it.
|
|
66
|
+
*/
|
|
49
67
|
closable: {
|
|
50
68
|
type: Types.Boolean;
|
|
51
|
-
default:
|
|
69
|
+
default: false;
|
|
52
70
|
};
|
|
53
|
-
/** Show/enable the minimize affordance. */
|
|
54
71
|
minimizable: {
|
|
55
72
|
type: Types.Boolean;
|
|
56
|
-
default:
|
|
73
|
+
default: false;
|
|
57
74
|
};
|
|
58
|
-
/**
|
|
75
|
+
/** The pin (body-follow ⇄ world-locked) affordance. */
|
|
59
76
|
pinnable: {
|
|
60
77
|
type: Types.Boolean;
|
|
61
|
-
default:
|
|
78
|
+
default: false;
|
|
79
|
+
};
|
|
80
|
+
/** The DOCK affordance, which returns the window to where it spawned. */
|
|
81
|
+
dockable: {
|
|
82
|
+
type: Types.Boolean;
|
|
83
|
+
default: false;
|
|
62
84
|
};
|
|
63
85
|
/** Keep the window yawed toward the viewer while it is being dragged. */
|
|
64
86
|
billboardWhileDragging: {
|
|
@@ -93,6 +115,48 @@ export declare const UIWindow: import("elics").Component<{
|
|
|
93
115
|
type: Types.Float32;
|
|
94
116
|
default: number;
|
|
95
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* Hand-menu placement, used while `dockMode` is `hand-locked`. These seed
|
|
120
|
+
* the record's `handMenu` on adoption; after that
|
|
121
|
+
* `WindowManager.setHandMenu` is the way to change them, and the values
|
|
122
|
+
* here are kept in step with it. See `hand-menu.ts` in the core for the
|
|
123
|
+
* hand frame and what each anchor means.
|
|
124
|
+
*/
|
|
125
|
+
hand: {
|
|
126
|
+
type: Types.Enum;
|
|
127
|
+
enum: {
|
|
128
|
+
readonly Left: "left";
|
|
129
|
+
readonly Right: "right";
|
|
130
|
+
readonly Either: "either";
|
|
131
|
+
};
|
|
132
|
+
default: "either" | import("@realitycollective/webxr-uiextensions").Hand;
|
|
133
|
+
};
|
|
134
|
+
handAnchor: {
|
|
135
|
+
type: Types.Enum;
|
|
136
|
+
enum: {
|
|
137
|
+
readonly Above: "above";
|
|
138
|
+
readonly Inside: "inside";
|
|
139
|
+
readonly Outside: "outside";
|
|
140
|
+
readonly Wrist: "wrist";
|
|
141
|
+
};
|
|
142
|
+
default: import("@realitycollective/webxr-uiextensions").HandMenuAnchor;
|
|
143
|
+
};
|
|
144
|
+
handAnchorDistance: {
|
|
145
|
+
type: Types.Float32;
|
|
146
|
+
default: number;
|
|
147
|
+
};
|
|
148
|
+
handOffset: {
|
|
149
|
+
type: Types.Vec3;
|
|
150
|
+
default: [number, number, number];
|
|
151
|
+
};
|
|
152
|
+
palmGate: {
|
|
153
|
+
type: Types.Boolean;
|
|
154
|
+
default: boolean;
|
|
155
|
+
};
|
|
156
|
+
palmAngle: {
|
|
157
|
+
type: Types.Float32;
|
|
158
|
+
default: number;
|
|
159
|
+
};
|
|
96
160
|
/**
|
|
97
161
|
* World size box in meters, applied via `UIKitDocument.setTargetDimensions`
|
|
98
162
|
* once the document loads. IWSDK 0.5 removed `PanelUI.maxWidth/maxHeight`;
|
|
@@ -122,6 +186,20 @@ export declare const UIWindowState: import("elics").Component<{
|
|
|
122
186
|
type: Types.Boolean;
|
|
123
187
|
default: false;
|
|
124
188
|
};
|
|
189
|
+
/**
|
|
190
|
+
* Whether the hand-menu palm gate is open this frame (always true in the
|
|
191
|
+
* other dock modes). `UIDockSystem` writes it; `UIWindowSystem` combines
|
|
192
|
+
* it with the record's `hidden` to decide what is drawn and hittable.
|
|
193
|
+
*/
|
|
194
|
+
gateOpen: {
|
|
195
|
+
type: Types.Boolean;
|
|
196
|
+
default: true;
|
|
197
|
+
};
|
|
198
|
+
/** What `UIWindowSystem` last applied: drawn and hittable, or not. */
|
|
199
|
+
presented: {
|
|
200
|
+
type: Types.Boolean;
|
|
201
|
+
default: true;
|
|
202
|
+
};
|
|
125
203
|
/** "Home" snapshot captured on adoption - the DOCK button returns here. */
|
|
126
204
|
homeDockMode: {
|
|
127
205
|
type: Types.String;
|
package/dist/components.js
CHANGED
|
@@ -8,7 +8,16 @@
|
|
|
8
8
|
* components to realise dock modes rather than re-implementing them.
|
|
9
9
|
*/
|
|
10
10
|
import { Types, createComponent } from '@iwsdk/core';
|
|
11
|
-
import { DockMode } from '@realitycollective/webxr-uiextensions';
|
|
11
|
+
import { DEFAULT_HAND_MENU, DockMode } from '@realitycollective/webxr-uiextensions';
|
|
12
|
+
/** `HandMenuOptions.hand` as an enum object for the component schema. */
|
|
13
|
+
export const HandChoice = { Left: 'left', Right: 'right', Either: 'either' };
|
|
14
|
+
/** `HandMenuOptions.anchor` as an enum object for the component schema. */
|
|
15
|
+
export const HandAnchor = {
|
|
16
|
+
Above: 'above',
|
|
17
|
+
Inside: 'inside',
|
|
18
|
+
Outside: 'outside',
|
|
19
|
+
Wrist: 'wrist',
|
|
20
|
+
};
|
|
12
21
|
/** Region flow options mirrored as an enum object for the component schema. */
|
|
13
22
|
export const RegionFlowType = {
|
|
14
23
|
Row: 'row',
|
|
@@ -29,12 +38,17 @@ export const UIWindow = createComponent('UIWindow', {
|
|
|
29
38
|
dockMode: { type: Types.Enum, enum: DockMode, default: DockMode.WorldLocked },
|
|
30
39
|
/** Whether the title bar drags the window. */
|
|
31
40
|
movable: { type: Types.Boolean, default: true },
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Title-bar buttons, all OFF by default. These seed the window's chrome
|
|
43
|
+
* state on adoption; after that `WindowManager.setChrome` is the way to
|
|
44
|
+
* change them, and the values here are kept in step with it.
|
|
45
|
+
*/
|
|
46
|
+
closable: { type: Types.Boolean, default: false },
|
|
47
|
+
minimizable: { type: Types.Boolean, default: false },
|
|
48
|
+
/** The pin (body-follow ⇄ world-locked) affordance. */
|
|
49
|
+
pinnable: { type: Types.Boolean, default: false },
|
|
50
|
+
/** The DOCK affordance, which returns the window to where it spawned. */
|
|
51
|
+
dockable: { type: Types.Boolean, default: false },
|
|
38
52
|
/** Keep the window yawed toward the viewer while it is being dragged. */
|
|
39
53
|
billboardWhileDragging: { type: Types.Boolean, default: true },
|
|
40
54
|
/**
|
|
@@ -50,6 +64,19 @@ export const UIWindow = createComponent('UIWindow', {
|
|
|
50
64
|
followTolerance: { type: Types.Float32, default: 0.35 },
|
|
51
65
|
/** Meters the focused window is nudged toward the viewer per focus depth. */
|
|
52
66
|
focusBias: { type: Types.Float32, default: 0.02 },
|
|
67
|
+
/**
|
|
68
|
+
* Hand-menu placement, used while `dockMode` is `hand-locked`. These seed
|
|
69
|
+
* the record's `handMenu` on adoption; after that
|
|
70
|
+
* `WindowManager.setHandMenu` is the way to change them, and the values
|
|
71
|
+
* here are kept in step with it. See `hand-menu.ts` in the core for the
|
|
72
|
+
* hand frame and what each anchor means.
|
|
73
|
+
*/
|
|
74
|
+
hand: { type: Types.Enum, enum: HandChoice, default: DEFAULT_HAND_MENU.hand },
|
|
75
|
+
handAnchor: { type: Types.Enum, enum: HandAnchor, default: DEFAULT_HAND_MENU.anchor },
|
|
76
|
+
handAnchorDistance: { type: Types.Float32, default: DEFAULT_HAND_MENU.anchorDistance },
|
|
77
|
+
handOffset: { type: Types.Vec3, default: [0, 0, 0] },
|
|
78
|
+
palmGate: { type: Types.Boolean, default: DEFAULT_HAND_MENU.palmGate },
|
|
79
|
+
palmAngle: { type: Types.Float32, default: DEFAULT_HAND_MENU.palmAngle },
|
|
53
80
|
/**
|
|
54
81
|
* World size box in meters, applied via `UIKitDocument.setTargetDimensions`
|
|
55
82
|
* once the document loads. IWSDK 0.5 removed `PanelUI.maxWidth/maxHeight`;
|
|
@@ -67,6 +94,14 @@ export const UIWindow = createComponent('UIWindow', {
|
|
|
67
94
|
export const UIWindowState = createComponent('UIWindowState', {
|
|
68
95
|
appliedDockMode: { type: Types.String, default: '' },
|
|
69
96
|
chromeWired: { type: Types.Boolean, default: false },
|
|
97
|
+
/**
|
|
98
|
+
* Whether the hand-menu palm gate is open this frame (always true in the
|
|
99
|
+
* other dock modes). `UIDockSystem` writes it; `UIWindowSystem` combines
|
|
100
|
+
* it with the record's `hidden` to decide what is drawn and hittable.
|
|
101
|
+
*/
|
|
102
|
+
gateOpen: { type: Types.Boolean, default: true },
|
|
103
|
+
/** What `UIWindowSystem` last applied: drawn and hittable, or not. */
|
|
104
|
+
presented: { type: Types.Boolean, default: true },
|
|
70
105
|
/** "Home" snapshot captured on adoption - the DOCK button returns here. */
|
|
71
106
|
homeDockMode: { type: Types.String, default: '' },
|
|
72
107
|
homeRegion: { type: Types.String, default: '' },
|
package/dist/components.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ECS components for UI Extensions.\n *\n * These compose with the IWSDK's own components - a window entity typically\n * carries `PanelUI` (the IWSDK loads/renders the panel), `UIWindow` (ours),\n * and the IWSDK interaction tags (`RayInteractable`, `PokeInteractable`).\n * The systems in this package add/remove the IWSDK `Follower` / `ScreenSpace`\n * components to realise dock modes rather than re-implementing them.\n */\nimport { Types, createComponent } from '@iwsdk/core';\nimport { DockMode } from '@realitycollective/webxr-uiextensions';\n\n/** Region flow options mirrored as an enum object for the component schema. */\nexport const RegionFlowType = {\n Row: 'row',\n Column: 'column',\n Grid: 'grid',\n} as const;\n\n/**\n * Marks a panel entity as a managed window with chrome, focus, docking and\n * drag behaviour. Pair with `PanelUI` whose markup contains the window chrome\n * elements (see `WINDOW_CHROME_IDS` / the Examples folder).\n */\nexport const UIWindow = createComponent(\n 'UIWindow',\n {\n /** Stable id; auto-generated when empty. */\n windowId: { type: Types.String, default: '' },\n /** Title text written into the `uix-title` element. */\n title: { type: Types.String, default: '' },\n /** Current dock state. */\n dockMode: { type: Types.Enum, enum: DockMode, default: DockMode.WorldLocked },\n /** Whether the title bar drags the window. */\n movable: { type: Types.Boolean, default: true },\n /** Show/enable the close affordance. */\n closable: { type: Types.Boolean, default: true },\n /** Show/enable the minimize affordance. */\n minimizable: { type: Types.Boolean, default: true },\n /** Show/enable the pin (body-follow ⇄ world-locked) affordance. */\n pinnable: { type: Types.Boolean, default: true },\n /** Keep the window yawed toward the viewer while it is being dragged. */\n billboardWhileDragging: { type: Types.Boolean, default: true },\n /**\n * Seconds a title-bar press must be held before it becomes a drag.\n * Shorter presses stay clicks, so title-bar buttons don't fight the drag.\n */\n dragDelay: { type: Types.Float32, default: 0.3 },\n /** Head-relative offset used in `body-follow` mode (meters). */\n followOffset: { type: Types.Vec3, default: [0, -0.15, -1.2] },\n /** `Follower` lerp speed for body-follow. */\n followSpeed: { type: Types.Float32, default: 3 },\n /** `Follower` positional deadzone for body-follow (meters). */\n followTolerance: { type: Types.Float32, default: 0.35 },\n /** Meters the focused window is nudged toward the viewer per focus depth. */\n focusBias: { type: Types.Float32, default: 0.02 },\n /**\n * World size box in meters, applied via `UIKitDocument.setTargetDimensions`\n * once the document loads. IWSDK 0.5 removed `PanelUI.maxWidth/maxHeight`;\n * this is the supported replacement. `0` leaves the document at its\n * intrinsic markup size, scaled only by the entity transform.\n */\n targetWidth: { type: Types.Float32, default: 0 },\n targetHeight: { type: Types.Float32, default: 0 },\n },\n 'UI Extensions managed window (chrome, focus, docking, drag)',\n);\n\n/**\n * Internal bookkeeping the systems keep on window entities - which dock mode\n * has actually been applied to engine components, and whether chrome wiring\n * has run for the current PanelDocument.\n */\nexport const UIWindowState = createComponent(\n 'UIWindowState',\n {\n appliedDockMode: { type: Types.String, default: '' },\n chromeWired: { type: Types.Boolean, default: false },\n /** \"Home\" snapshot captured on adoption - the DOCK button returns here. */\n homeDockMode: { type: Types.String, default: '' },\n homeRegion: { type: Types.String, default: '' },\n homePosition: { type: Types.Vec3, default: [0, 0, 0] },\n homeYaw: { type: Types.Float32, default: 0 },\n },\n 'Internal UI Extensions window bookkeeping',\n);\n\n/**\n * A named layout region that docked windows snap to and are laid out within.\n * Place the entity where the region should live (it can itself carry a\n * `Follower` to make a body-locked region).\n */\nexport const UIDockRegion = createComponent(\n 'UIDockRegion',\n {\n regionId: { type: Types.String, default: '' },\n flow: { type: Types.Enum, enum: RegionFlowType, default: RegionFlowType.Column },\n /** Distance between slot origins (meters). */\n pitch: { type: Types.Float32, default: 0.35 },\n /** Columns per row (grid flow only). */\n columns: { type: Types.Float32, default: 2 },\n /** Max docked windows (0 = unlimited). */\n capacity: { type: Types.Float32, default: 0 },\n /** Drop-capture radius (meters). */\n snapRadius: { type: Types.Float32, default: 0.5 },\n },\n 'UI Extensions layout region for docking windows',\n);\n\n/** Present on a window entity while it is docked into a region. */\nexport const UIDockedTo = createComponent(\n 'UIDockedTo',\n {\n regionId: { type: Types.String, default: '' },\n },\n 'Marks a UI Extensions window as docked into a region',\n);\n"]}
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ECS components for UI Extensions.\n *\n * These compose with the IWSDK's own components - a window entity typically\n * carries `PanelUI` (the IWSDK loads/renders the panel), `UIWindow` (ours),\n * and the IWSDK interaction tags (`RayInteractable`, `PokeInteractable`).\n * The systems in this package add/remove the IWSDK `Follower` / `ScreenSpace`\n * components to realise dock modes rather than re-implementing them.\n */\nimport { Types, createComponent } from '@iwsdk/core';\nimport { DEFAULT_HAND_MENU, DockMode } from '@realitycollective/webxr-uiextensions';\n\n/** `HandMenuOptions.hand` as an enum object for the component schema. */\nexport const HandChoice = { Left: 'left', Right: 'right', Either: 'either' } as const;\n/** `HandMenuOptions.anchor` as an enum object for the component schema. */\nexport const HandAnchor = {\n Above: 'above',\n Inside: 'inside',\n Outside: 'outside',\n Wrist: 'wrist',\n} as const;\n\n/** Region flow options mirrored as an enum object for the component schema. */\nexport const RegionFlowType = {\n Row: 'row',\n Column: 'column',\n Grid: 'grid',\n} as const;\n\n/**\n * Marks a panel entity as a managed window with chrome, focus, docking and\n * drag behaviour. Pair with `PanelUI` whose markup contains the window chrome\n * elements (see `WINDOW_CHROME_IDS` / the Examples folder).\n */\nexport const UIWindow = createComponent(\n 'UIWindow',\n {\n /** Stable id; auto-generated when empty. */\n windowId: { type: Types.String, default: '' },\n /** Title text written into the `uix-title` element. */\n title: { type: Types.String, default: '' },\n /** Current dock state. */\n dockMode: { type: Types.Enum, enum: DockMode, default: DockMode.WorldLocked },\n /** Whether the title bar drags the window. */\n movable: { type: Types.Boolean, default: true },\n /**\n * Title-bar buttons, all OFF by default. These seed the window's chrome\n * state on adoption; after that `WindowManager.setChrome` is the way to\n * change them, and the values here are kept in step with it.\n */\n closable: { type: Types.Boolean, default: false },\n minimizable: { type: Types.Boolean, default: false },\n /** The pin (body-follow ⇄ world-locked) affordance. */\n pinnable: { type: Types.Boolean, default: false },\n /** The DOCK affordance, which returns the window to where it spawned. */\n dockable: { type: Types.Boolean, default: false },\n /** Keep the window yawed toward the viewer while it is being dragged. */\n billboardWhileDragging: { type: Types.Boolean, default: true },\n /**\n * Seconds a title-bar press must be held before it becomes a drag.\n * Shorter presses stay clicks, so title-bar buttons don't fight the drag.\n */\n dragDelay: { type: Types.Float32, default: 0.3 },\n /** Head-relative offset used in `body-follow` mode (meters). */\n followOffset: { type: Types.Vec3, default: [0, -0.15, -1.2] },\n /** `Follower` lerp speed for body-follow. */\n followSpeed: { type: Types.Float32, default: 3 },\n /** `Follower` positional deadzone for body-follow (meters). */\n followTolerance: { type: Types.Float32, default: 0.35 },\n /** Meters the focused window is nudged toward the viewer per focus depth. */\n focusBias: { type: Types.Float32, default: 0.02 },\n /**\n * Hand-menu placement, used while `dockMode` is `hand-locked`. These seed\n * the record's `handMenu` on adoption; after that\n * `WindowManager.setHandMenu` is the way to change them, and the values\n * here are kept in step with it. See `hand-menu.ts` in the core for the\n * hand frame and what each anchor means.\n */\n hand: { type: Types.Enum, enum: HandChoice, default: DEFAULT_HAND_MENU.hand },\n handAnchor: { type: Types.Enum, enum: HandAnchor, default: DEFAULT_HAND_MENU.anchor },\n handAnchorDistance: { type: Types.Float32, default: DEFAULT_HAND_MENU.anchorDistance },\n handOffset: { type: Types.Vec3, default: [0, 0, 0] },\n palmGate: { type: Types.Boolean, default: DEFAULT_HAND_MENU.palmGate },\n palmAngle: { type: Types.Float32, default: DEFAULT_HAND_MENU.palmAngle },\n /**\n * World size box in meters, applied via `UIKitDocument.setTargetDimensions`\n * once the document loads. IWSDK 0.5 removed `PanelUI.maxWidth/maxHeight`;\n * this is the supported replacement. `0` leaves the document at its\n * intrinsic markup size, scaled only by the entity transform.\n */\n targetWidth: { type: Types.Float32, default: 0 },\n targetHeight: { type: Types.Float32, default: 0 },\n },\n 'UI Extensions managed window (chrome, focus, docking, drag)',\n);\n\n/**\n * Internal bookkeeping the systems keep on window entities - which dock mode\n * has actually been applied to engine components, and whether chrome wiring\n * has run for the current PanelDocument.\n */\nexport const UIWindowState = createComponent(\n 'UIWindowState',\n {\n appliedDockMode: { type: Types.String, default: '' },\n chromeWired: { type: Types.Boolean, default: false },\n /**\n * Whether the hand-menu palm gate is open this frame (always true in the\n * other dock modes). `UIDockSystem` writes it; `UIWindowSystem` combines\n * it with the record's `hidden` to decide what is drawn and hittable.\n */\n gateOpen: { type: Types.Boolean, default: true },\n /** What `UIWindowSystem` last applied: drawn and hittable, or not. */\n presented: { type: Types.Boolean, default: true },\n /** \"Home\" snapshot captured on adoption - the DOCK button returns here. */\n homeDockMode: { type: Types.String, default: '' },\n homeRegion: { type: Types.String, default: '' },\n homePosition: { type: Types.Vec3, default: [0, 0, 0] },\n homeYaw: { type: Types.Float32, default: 0 },\n },\n 'Internal UI Extensions window bookkeeping',\n);\n\n/**\n * A named layout region that docked windows snap to and are laid out within.\n * Place the entity where the region should live (it can itself carry a\n * `Follower` to make a body-locked region).\n */\nexport const UIDockRegion = createComponent(\n 'UIDockRegion',\n {\n regionId: { type: Types.String, default: '' },\n flow: { type: Types.Enum, enum: RegionFlowType, default: RegionFlowType.Column },\n /** Distance between slot origins (meters). */\n pitch: { type: Types.Float32, default: 0.35 },\n /** Columns per row (grid flow only). */\n columns: { type: Types.Float32, default: 2 },\n /** Max docked windows (0 = unlimited). */\n capacity: { type: Types.Float32, default: 0 },\n /** Drop-capture radius (meters). */\n snapRadius: { type: Types.Float32, default: 0.5 },\n },\n 'UI Extensions layout region for docking windows',\n);\n\n/** Present on a window entity while it is docked into a region. */\nexport const UIDockedTo = createComponent(\n 'UIDockedTo',\n {\n regionId: { type: Types.String, default: '' },\n },\n 'Marks a UI Extensions window as docked into a region',\n);\n"]}
|
package/dist/factory.d.ts
CHANGED
|
@@ -13,7 +13,9 @@ import { type WindowOptionsBase } from '@realitycollective/webxr-uiextensions';
|
|
|
13
13
|
* so the same option names mean the same thing on every adapter. `maxWidth`
|
|
14
14
|
* and `maxHeight` route to `UIWindow.targetWidth/Height` here, because IWSDK
|
|
15
15
|
* 0.5 removed those fields from `PanelUI`; omit them to keep the markup's
|
|
16
|
-
* intrinsic size and scale through the entity transform.
|
|
16
|
+
* intrinsic size and scale through the entity transform. The title-bar
|
|
17
|
+
* buttons (`closable`, `minimizable`, `pinnable`, `dockable`) are all off
|
|
18
|
+
* unless asked for.
|
|
17
19
|
*/
|
|
18
20
|
export interface CreateWindowOptions extends WindowOptionsBase {
|
|
19
21
|
/**
|