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