@realitycollective/webxr-uiextensions 0.1.0-preview.2 → 0.1.0-preview.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -19,6 +19,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
19
19
  - `PanelReadyEvent.kind`, either `window` or `panel`. `window` means the panel came from the adapter's window factory and is managed by the window manager. `panel` means a bare panel the adapter noticed, whose `id` is then the adapter's best stable identifier for it - the config path on IWSDK. The field is optional, so existing listeners keep compiling.
20
20
  - `createWindow` on the IWSDK scene host, returning an `IwsdkWindowHandle`: a `WindowHandle` plus the ECS `entity`. It spawns through `createUIWindow`, names the window `uix-window-<n>` when no id is given, and resolves `panel` and `onReady` once the document attaches, so wiring one window needs neither an ECS query nor a filtered `onPanelReady`. `createUIWindow` is unchanged and still returns the entity.
21
21
  - `getPanelHandle(entity)` in `@realitycollective/iwsdk-uiextensions`: the panel handle for an entity you already hold, or `undefined` while IWSDK is still loading its document.
22
+ - `windowHostContractCases()` in `@realitycollective/webxr-uiextensions`, with `WindowHostContractCase` and `WindowHostContractSetup` - the `WindowHost` conformance suite as data rather than as tests. Each case is a `name` plus a `run(setup)` that throws an `Error` on failure, so an adapter runs the suite in its own test runner and needs nothing from this repository's `test/` folder. It was previously an in-repo vitest helper that only the two shipped adapters could reach, which left an adapter author outside this repository with no way to prove conformance. Both adapters now run the shipped cases through a three-line wrapper.
22
23
 
23
24
  ### Changed
24
25
 
@@ -34,5 +35,6 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
34
35
  - `id` is optional in the XR Blocks host's `CreateWindowOptions`; a window created without one is named `uix-window-<n>`, as on IWSDK. The options also accept `movable` for parity, but this host has no title-bar drag of its own yet, so the flag is recorded and not acted on.
35
36
  - The XR Blocks window handle gains `panel` - the document, which already implements `PanelHandle` - and `onReady`, which fires synchronously because uikitml interprets the markup during `createWindow`. The interface is now named `XrBlocksWindowHandle`, with `WindowHandle` kept as an alias, because the core exports a `WindowHandle` of its own.
36
37
  - Coverage gates `packages/iwsdk-uiextensions/src/factory.ts` and `src/scene-host.ts` at the same 100% as the core. `new World()` from `@iwsdk/core` constructs headlessly - no renderer, no WebGL, no XR session - so the factories, the scene host and its ECS query all run for real in a node test. The per-frame ECS systems still need a live world and stay outside the gate.
38
+ - `@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.
37
39
 
38
40
  [0.1.0]: https://github.com/realitycollective/WebXR-UIExtensions/commits/main
package/README.md CHANGED
@@ -10,7 +10,9 @@ The core of the Reality Collective UI Extensions. It provides:
10
10
  - **Markup upgrading** - code that turns a plain element carrying a `data-uix` attribute into a working control, so you write markup rather than components.
11
11
  - **Adapter interfaces** - what an engine package must implement to host all of the above.
12
12
 
13
- **This package has zero runtime dependencies and imports no 3D engine.** A test, `test/architecture.test.ts`, fails the moment `three`, `@iwsdk/*`, `@pmndrs/*` or `xrblocks` appears anywhere in `src/`. That is what lets the same interface run unchanged on every three.js WebXR runtime.
13
+ **This package imports no 3D engine.** A test, `test/architecture.test.ts`, fails the moment `three`, `@iwsdk/*`, `@pmndrs/*` or `xrblocks` appears anywhere in `src/`. That is what lets the same interface run unchanged on every three.js WebXR runtime.
14
+
15
+ It carries exactly one runtime dependency, [`@realitycollective/webxr-input`](https://www.npmjs.com/package/@realitycollective/webxr-input), and the same test fails on any other. That package is the shared contracts vocabulary: plain tuples and records, no engine imports and no dependencies of its own. `Vec3Tuple`, `QuatTuple`, `HeadPose`, `HeadPoseSource` and `PointerSample` come from there rather than being redeclared here, so a pose or a ray means the same thing to the Interactions family and to this one, and one input stack drives both. All five are re-exported from this package, so importing them from here keeps working.
14
16
 
15
17
  ## You probably want an adapter, not this package
16
18
 
@@ -34,6 +36,9 @@ src/chrome/ window chrome conventions: contractual element ids
34
36
  src/adapter.ts the platform-adapter contract: PanelHost, PanelHandle,
35
37
  WindowHost, WindowHandle, WindowOptionsBase, HeadPoseSource,
36
38
  PointerInputSource (plain tuples, no engine)
39
+ src/contract-cases.ts
40
+ windowHostContractCases() - the WindowHost conformance suite
41
+ as data, for an adapter to run in its own test runner
37
42
  ```
38
43
 
39
44
  ## Writing an adapter
@@ -44,7 +49,7 @@ An adapter supplies three capabilities and drives the core from its frame loop:
44
49
  2. **Input** - deliver press/move/release into the core's `HoldToDrag` + drag math, or wire chrome clicks straight to `WindowManager`.
45
50
  3. **Viewer pose** - implement `HeadPoseSource` for follow mode and body-locked regions.
46
51
 
47
- The IWSDK adapter is the reference implementation; the XR Blocks adapter shows the same contract bound without an ECS.
52
+ The IWSDK adapter is the reference implementation; the XR Blocks adapter shows the same contract bound without an ECS. When yours runs, prove it with the shipped conformance suite below.
48
53
 
49
54
  ### The window surface
50
55
 
@@ -58,6 +63,35 @@ The IWSDK adapter is the reference implementation; the XR Blocks adapter shows t
58
63
 
59
64
  Options are shared even though `createWindow` is not: every adapter's option type extends `WindowOptionsBase` (`id`, `title`, `dockMode`, `position`, `maxWidth`/`maxHeight`, `movable`, `closable`, `minimizable`, `pinnable`, `followOffset`/`followSpeed`/`followTolerance`, `region`). An option means the same thing everywhere, so one `SceneWindow` maps onto every adapter with no translation table.
60
65
 
66
+ ### Proving a new adapter conforms
67
+
68
+ `windowHostContractCases()` is the `WindowHost` conformance suite, shipped as data rather than as tests. Each case is a `name` plus a `run(setup)` that returns silently on success and throws an `Error` describing the failure otherwise, so an adapter runs them in whatever test runner it already has. It ships runner-free because an adapter written outside this repository cannot reach into this one's `test/` folder, and because no adapter should have to install this repo's runner to prove itself.
69
+
70
+ An adapter's test file is a loop:
71
+
72
+ ```ts
73
+ import { windowHostContractCases } from '@realitycollective/webxr-uiextensions';
74
+ import type { WindowHostContractSetup } from '@realitycollective/webxr-uiextensions';
75
+
76
+ function makeSetup(): WindowHostContractSetup {
77
+ const host = createMyHost();
78
+ return {
79
+ host,
80
+ createWindow: (id) => host.createWindow({ id, config: myConfig() }),
81
+ // Only where the panel arrives after the window does:
82
+ attach: (id) => deliverThePanelFor(id),
83
+ // Only where supportsStandalonePanels is true:
84
+ panelConfig: myConfig(),
85
+ };
86
+ }
87
+
88
+ for (const contractCase of windowHostContractCases()) {
89
+ it(contractCase.name, () => contractCase.run(makeSetup()));
90
+ }
91
+ ```
92
+
93
+ `makeSetup()` runs once per case, because the cases spawn windows of their own and do not clean up after themselves. `attach` and `panelConfig` are both optional: leave `attach` out when a window's panel exists as soon as the window does, and `panelConfig` out when the host reports `supportsStandalonePanels: false`. Both shipped adapters run this suite, so a case failing on yours is a real difference in behaviour, not a difference in test style.
94
+
61
95
  ## Testing
62
96
 
63
97
  ```bash
package/dist/adapter.d.ts CHANGED
@@ -25,23 +25,27 @@
25
25
  * - `@realitycollective/xrblocks-uiextensions` - Google XR Blocks / plain
26
26
  * three.js (experimental)
27
27
  *
28
- * The interfaces use plain tuples/records only - no engine, no three.js.
28
+ * The interfaces use plain tuples/records only - no engine, no three.js. The
29
+ * geometry vocabulary itself comes from `@realitycollective/webxr-input`, the
30
+ * engine-free contracts package both extension families share, so a pose or a
31
+ * ray means the same thing to Interactions and to UI Extensions and one input
32
+ * stack drives both. The names below are re-exported, so importing them from
33
+ * this package keeps working.
29
34
  */
35
+ import type { RayTuple, Vec3Tuple } from '@realitycollective/webxr-input';
30
36
  import type { DockModeValue } from './core/dock-state.js';
31
37
  import type { UixElement } from './controls/element.js';
32
- /** Position as [x, y, z] in meters, world space unless stated otherwise. */
33
- export type Vec3Tuple = [number, number, number];
34
- /** Orientation quaternion as [x, y, z, w]. */
35
- export type QuatTuple = [number, number, number, number];
36
- /** A viewer (head) pose sample. */
37
- export interface HeadPose {
38
- position: Vec3Tuple;
39
- quaternion: QuatTuple;
40
- }
41
- /** Supplies the viewer pose each frame - camera on desktop, HMD in XR. */
42
- export interface HeadPoseSource {
43
- getHeadPose(): HeadPose;
44
- }
38
+ /**
39
+ * The shared geometry vocabulary, re-exported so this package stays the one
40
+ * import an adapter needs:
41
+ *
42
+ * - `Vec3Tuple` - position as [x, y, z] in meters, world space unless stated
43
+ * - `QuatTuple` - orientation quaternion as [x, y, z, w]
44
+ * - `HeadPose` - a viewer (head) pose sample
45
+ * - `HeadPoseSource` - supplies that pose each frame, camera on desktop and
46
+ * HMD in XR
47
+ */
48
+ export type { HeadPose, HeadPoseSource, QuatTuple, Vec3Tuple, } from '@realitycollective/webxr-input';
45
49
  /**
46
50
  * A live spatial panel created from compiled UIKitML JSON.
47
51
  * The `root` is traversable with the core's `walk`/`findRole` helpers and
@@ -170,13 +174,13 @@ export interface WindowOptionsBase {
170
174
  /** Dock straight into this region on spawn. */
171
175
  region?: string;
172
176
  }
173
- /** One pointer/ray interaction stream, engine-normalised. */
174
- export interface PointerSample {
175
- /** Pointer world position (ray origin or touch point). */
176
- origin: Vec3Tuple;
177
- /** Normalised pointing direction. */
178
- direction: Vec3Tuple;
179
- }
177
+ /**
178
+ * One pointer/ray interaction stream, engine-normalised: a world-space
179
+ * `origin` (ray origin or touch point) and a normalised `direction`. It is
180
+ * the Input package's `RayTuple`, which is what lets a provider written
181
+ * against `@realitycollective/webxr-input` feed this contract unchanged.
182
+ */
183
+ export type PointerSample = RayTuple;
180
184
  /**
181
185
  * Delivers press-move-release for one interaction source (a controller ray,
182
186
  * a hand pinch, a mouse). The core's `hold-to-drag` and `drag-math` consume
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Platform-adapter contract.\n *\n * The core package owns every UX decision - window lifecycle, dock state,\n * region slot math, drag math, control models - and knows nothing about any\n * engine. An engine adapter supplies the three capabilities the core cannot\n * provide for itself, and drives the core from its own frame loop:\n *\n * - {@link PanelHost}: turn compiled UIKitML JSON into a live spatial panel\n * - {@link PointerInputSource}: deliver ray/pointer press-move-release\n * - {@link HeadPoseSource}: the viewer pose, for follow mode & body-lock\n *\n * On top of those, {@link WindowHost} adds the portable window surface:\n * {@link WindowHost.onPanelReady} for readiness, {@link PanelReadyEvent.kind}\n * to tell a managed window from a bare panel, and\n * {@link WindowHost.supportsStandalonePanels} to say whether `createPanel` is\n * usable at all. Each adapter's own `createWindow` returns a\n * {@link WindowHandle} and takes options extending {@link WindowOptionsBase},\n * so window code reads the same on every engine even though the `config`\n * payload does not.\n *\n * Known adapters:\n * - `@realitycollective/iwsdk-uiextensions` - Meta IWSDK (ECS systems bind\n * these capabilities to `@iwsdk/core` components)\n * - `@realitycollective/xrblocks-uiextensions` - Google XR Blocks / plain\n * three.js (experimental)\n *\n * The interfaces use plain tuples/records only - no engine, no three.js.\n */\nimport type { DockModeValue } from './core/dock-state.js';\nimport type { UixElement } from './controls/element.js';\n\n/** Position as [x, y, z] in meters, world space unless stated otherwise. */\nexport type Vec3Tuple = [number, number, number];\n\n/** Orientation quaternion as [x, y, z, w]. */\nexport type QuatTuple = [number, number, number, number];\n\n/** A viewer (head) pose sample. */\nexport interface HeadPose {\n position: Vec3Tuple;\n quaternion: QuatTuple;\n}\n\n/** Supplies the viewer pose each frame - camera on desktop, HMD in XR. */\nexport interface HeadPoseSource {\n getHeadPose(): HeadPose;\n}\n\n/**\n * A live spatial panel created from compiled UIKitML JSON.\n * The `root` is traversable with the core's `walk`/`findRole` helpers and\n * the `data-uix` control upgraders - identical markup works on every\n * adapter.\n */\nexport interface PanelHandle {\n /** Root element of the interpreted panel (UixElement-conformant). */\n readonly root: UixElement;\n /** Look up an element by its markup `id`. */\n getElementById(id: string): UixElement | undefined;\n /** Constrain the panel to fit within width × height meters. */\n setTargetDimensions(width: number, height: number): void;\n /** Release panel resources. */\n dispose(): void;\n}\n\n/** Creates spatial panels - the engine-specific half of UIKitML rendering. */\nexport interface PanelHost {\n /**\n * Create a panel from compiled UIKitML JSON (the `{ element, classes }`\n * shape produced by the build plugin or by\n * `@realitycollective/uix-devtools`' `compilePanelSource`).\n */\n createPanel(configJson: unknown): PanelHandle;\n}\n\n/**\n * A window whose panel has finished loading and is ready to be wired.\n * Delivered by {@link WindowHost.onPanelReady}.\n */\nexport interface PanelReadyEvent {\n /** The window's id, as given to the scene descriptor / create call. */\n id: string;\n /** The live panel - traverse it, or look elements up by markup id. */\n panel: PanelHandle;\n /**\n * What became ready.\n *\n * - `window` - created through the adapter's window factory and managed by\n * the window manager, so `id` is the id the caller asked for.\n * - `panel` - a bare panel the adapter noticed. `id` is then the adapter's\n * best stable identifier for it, which on IWSDK is the panel's config\n * path.\n *\n * Left optional so existing listeners keep compiling; adapters set it.\n */\n kind?: 'window' | 'panel';\n}\n\n/**\n * The engine-agnostic surface an app needs to build a UI: spawn windows and\n * regions from portable data, observe when panels become wireable, and reach\n * the shared `WindowManager`.\n *\n * Panels load asynchronously on every adapter (IWSDK fetches the config;\n * uikit lays out over following frames), so app code must never assume a\n * panel exists immediately after creating its window. {@link onPanelReady}\n * is the portable answer - it replaces engine-specific discovery (ECS\n * queries on IWSDK, polling anywhere else) and fires for panels that became\n * ready before the listener was registered, so wiring order never matters.\n */\nexport interface WindowHost extends PanelHost {\n /**\n * Whether {@link PanelHost.createPanel} works on this host. When `false`\n * the method is not available and throws; spawn a window instead, so the\n * engine owns the panel lifecycle. IWSDK is `false`, three.js/XR Blocks is\n * `true`.\n */\n readonly supportsStandalonePanels: boolean;\n /**\n * Subscribe to panel readiness. Late subscribers are replayed the windows\n * that are already live. Returns an unsubscribe function.\n */\n onPanelReady(listener: (event: PanelReadyEvent) => void): () => void;\n}\n\n/**\n * A window an adapter spawned, before its panel necessarily exists.\n *\n * `createWindow` itself stays adapter-specific because the `config` payload\n * differs per engine, but what it hands back is the same everywhere: an id, a\n * panel once there is one, and a one-shot readiness callback.\n */\nexport interface WindowHandle {\n /** The window's id - the one passed in, or one the adapter generated. */\n readonly id: string;\n /**\n * The live panel, or `undefined` until the adapter has attached the\n * document. IWSDK loads and parses the markup over later frames; the\n * three.js host interprets it during `createWindow`, so there it is set\n * straight away.\n */\n readonly panel: PanelHandle | undefined;\n /**\n * Run `listener` once, when the panel is attached. Fires immediately if it\n * already is, so wiring order never matters. Returns an unsubscribe\n * function for the case where the caller gives up first.\n */\n onReady(listener: (panel: PanelHandle) => void): () => void;\n}\n\n/**\n * The window options every adapter understands.\n *\n * An adapter's own `CreateWindowOptions` extends this and adds only what its\n * engine needs - chiefly `config`, whose type differs (IWSDK takes a source\n * path, the three.js host takes parsed markup). Keeping the rest here is what\n * lets one `SceneWindow` map onto every adapter without a translation table.\n */\nexport interface WindowOptionsBase {\n /** Stable window id. Adapters generate one when it is absent. */\n id?: string;\n /** Title text written into the window chrome's title element. */\n title?: string;\n dockMode?: DockModeValue;\n /** World position for world-locked windows. */\n position?: Vec3Tuple;\n /** Fit the panel into this box in meters, preserving aspect ratio. */\n maxWidth?: number;\n maxHeight?: number;\n /** Whether the title bar drags the window. */\n movable?: boolean;\n closable?: boolean;\n minimizable?: boolean;\n pinnable?: boolean;\n /** Head-relative offset used in body-follow mode (meters). */\n followOffset?: Vec3Tuple;\n followSpeed?: number;\n followTolerance?: number;\n /** Dock straight into this region on spawn. */\n region?: string;\n}\n\n/** One pointer/ray interaction stream, engine-normalised. */\nexport interface PointerSample {\n /** Pointer world position (ray origin or touch point). */\n origin: Vec3Tuple;\n /** Normalised pointing direction. */\n direction: Vec3Tuple;\n}\n\n/**\n * Delivers press-move-release for one interaction source (a controller ray,\n * a hand pinch, a mouse). The core's `hold-to-drag` and `drag-math` consume\n * these; the adapter decides what constitutes press/release.\n */\nexport interface PointerInputSource {\n onPress(listener: (sample: PointerSample) => void): () => void;\n onMove(listener: (sample: PointerSample) => void): () => void;\n onRelease(listener: (sample: PointerSample) => void): () => void;\n}\n"]}
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Platform-adapter contract.\n *\n * The core package owns every UX decision - window lifecycle, dock state,\n * region slot math, drag math, control models - and knows nothing about any\n * engine. An engine adapter supplies the three capabilities the core cannot\n * provide for itself, and drives the core from its own frame loop:\n *\n * - {@link PanelHost}: turn compiled UIKitML JSON into a live spatial panel\n * - {@link PointerInputSource}: deliver ray/pointer press-move-release\n * - {@link HeadPoseSource}: the viewer pose, for follow mode & body-lock\n *\n * On top of those, {@link WindowHost} adds the portable window surface:\n * {@link WindowHost.onPanelReady} for readiness, {@link PanelReadyEvent.kind}\n * to tell a managed window from a bare panel, and\n * {@link WindowHost.supportsStandalonePanels} to say whether `createPanel` is\n * usable at all. Each adapter's own `createWindow` returns a\n * {@link WindowHandle} and takes options extending {@link WindowOptionsBase},\n * so window code reads the same on every engine even though the `config`\n * payload does not.\n *\n * Known adapters:\n * - `@realitycollective/iwsdk-uiextensions` - Meta IWSDK (ECS systems bind\n * these capabilities to `@iwsdk/core` components)\n * - `@realitycollective/xrblocks-uiextensions` - Google XR Blocks / plain\n * three.js (experimental)\n *\n * The interfaces use plain tuples/records only - no engine, no three.js. The\n * geometry vocabulary itself comes from `@realitycollective/webxr-input`, the\n * engine-free contracts package both extension families share, so a pose or a\n * ray means the same thing to Interactions and to UI Extensions and one input\n * stack drives both. The names below are re-exported, so importing them from\n * this package keeps working.\n */\nimport type { RayTuple, Vec3Tuple } from '@realitycollective/webxr-input';\nimport type { DockModeValue } from './core/dock-state.js';\nimport type { UixElement } from './controls/element.js';\n\n/**\n * The shared geometry vocabulary, re-exported so this package stays the one\n * import an adapter needs:\n *\n * - `Vec3Tuple` - position as [x, y, z] in meters, world space unless stated\n * - `QuatTuple` - orientation quaternion as [x, y, z, w]\n * - `HeadPose` - a viewer (head) pose sample\n * - `HeadPoseSource` - supplies that pose each frame, camera on desktop and\n * HMD in XR\n */\nexport type {\n HeadPose,\n HeadPoseSource,\n QuatTuple,\n Vec3Tuple,\n} from '@realitycollective/webxr-input';\n\n/**\n * A live spatial panel created from compiled UIKitML JSON.\n * The `root` is traversable with the core's `walk`/`findRole` helpers and\n * the `data-uix` control upgraders - identical markup works on every\n * adapter.\n */\nexport interface PanelHandle {\n /** Root element of the interpreted panel (UixElement-conformant). */\n readonly root: UixElement;\n /** Look up an element by its markup `id`. */\n getElementById(id: string): UixElement | undefined;\n /** Constrain the panel to fit within width × height meters. */\n setTargetDimensions(width: number, height: number): void;\n /** Release panel resources. */\n dispose(): void;\n}\n\n/** Creates spatial panels - the engine-specific half of UIKitML rendering. */\nexport interface PanelHost {\n /**\n * Create a panel from compiled UIKitML JSON (the `{ element, classes }`\n * shape produced by the build plugin or by\n * `@realitycollective/uix-devtools`' `compilePanelSource`).\n */\n createPanel(configJson: unknown): PanelHandle;\n}\n\n/**\n * A window whose panel has finished loading and is ready to be wired.\n * Delivered by {@link WindowHost.onPanelReady}.\n */\nexport interface PanelReadyEvent {\n /** The window's id, as given to the scene descriptor / create call. */\n id: string;\n /** The live panel - traverse it, or look elements up by markup id. */\n panel: PanelHandle;\n /**\n * What became ready.\n *\n * - `window` - created through the adapter's window factory and managed by\n * the window manager, so `id` is the id the caller asked for.\n * - `panel` - a bare panel the adapter noticed. `id` is then the adapter's\n * best stable identifier for it, which on IWSDK is the panel's config\n * path.\n *\n * Left optional so existing listeners keep compiling; adapters set it.\n */\n kind?: 'window' | 'panel';\n}\n\n/**\n * The engine-agnostic surface an app needs to build a UI: spawn windows and\n * regions from portable data, observe when panels become wireable, and reach\n * the shared `WindowManager`.\n *\n * Panels load asynchronously on every adapter (IWSDK fetches the config;\n * uikit lays out over following frames), so app code must never assume a\n * panel exists immediately after creating its window. {@link onPanelReady}\n * is the portable answer - it replaces engine-specific discovery (ECS\n * queries on IWSDK, polling anywhere else) and fires for panels that became\n * ready before the listener was registered, so wiring order never matters.\n */\nexport interface WindowHost extends PanelHost {\n /**\n * Whether {@link PanelHost.createPanel} works on this host. When `false`\n * the method is not available and throws; spawn a window instead, so the\n * engine owns the panel lifecycle. IWSDK is `false`, three.js/XR Blocks is\n * `true`.\n */\n readonly supportsStandalonePanels: boolean;\n /**\n * Subscribe to panel readiness. Late subscribers are replayed the windows\n * that are already live. Returns an unsubscribe function.\n */\n onPanelReady(listener: (event: PanelReadyEvent) => void): () => void;\n}\n\n/**\n * A window an adapter spawned, before its panel necessarily exists.\n *\n * `createWindow` itself stays adapter-specific because the `config` payload\n * differs per engine, but what it hands back is the same everywhere: an id, a\n * panel once there is one, and a one-shot readiness callback.\n */\nexport interface WindowHandle {\n /** The window's id - the one passed in, or one the adapter generated. */\n readonly id: string;\n /**\n * The live panel, or `undefined` until the adapter has attached the\n * document. IWSDK loads and parses the markup over later frames; the\n * three.js host interprets it during `createWindow`, so there it is set\n * straight away.\n */\n readonly panel: PanelHandle | undefined;\n /**\n * Run `listener` once, when the panel is attached. Fires immediately if it\n * already is, so wiring order never matters. Returns an unsubscribe\n * function for the case where the caller gives up first.\n */\n onReady(listener: (panel: PanelHandle) => void): () => void;\n}\n\n/**\n * The window options every adapter understands.\n *\n * An adapter's own `CreateWindowOptions` extends this and adds only what its\n * engine needs - chiefly `config`, whose type differs (IWSDK takes a source\n * path, the three.js host takes parsed markup). Keeping the rest here is what\n * lets one `SceneWindow` map onto every adapter without a translation table.\n */\nexport interface WindowOptionsBase {\n /** Stable window id. Adapters generate one when it is absent. */\n id?: string;\n /** Title text written into the window chrome's title element. */\n title?: string;\n dockMode?: DockModeValue;\n /** World position for world-locked windows. */\n position?: Vec3Tuple;\n /** Fit the panel into this box in meters, preserving aspect ratio. */\n maxWidth?: number;\n maxHeight?: number;\n /** Whether the title bar drags the window. */\n movable?: boolean;\n closable?: boolean;\n minimizable?: boolean;\n pinnable?: boolean;\n /** Head-relative offset used in body-follow mode (meters). */\n followOffset?: Vec3Tuple;\n followSpeed?: number;\n followTolerance?: number;\n /** Dock straight into this region on spawn. */\n region?: string;\n}\n\n/**\n * One pointer/ray interaction stream, engine-normalised: a world-space\n * `origin` (ray origin or touch point) and a normalised `direction`. It is\n * the Input package's `RayTuple`, which is what lets a provider written\n * against `@realitycollective/webxr-input` feed this contract unchanged.\n */\nexport type PointerSample = RayTuple;\n\n/**\n * Delivers press-move-release for one interaction source (a controller ray,\n * a hand pinch, a mouse). The core's `hold-to-drag` and `drag-math` consume\n * these; the adapter decides what constitutes press/release.\n */\nexport interface PointerInputSource {\n onPress(listener: (sample: PointerSample) => void): () => void;\n onMove(listener: (sample: PointerSample) => void): () => void;\n onRelease(listener: (sample: PointerSample) => void): () => void;\n}\n"]}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The shared `WindowHost` contract, shipped as data rather than as tests.
3
+ *
4
+ * Every adapter promises the same four things, whatever engine sits behind
5
+ * it: it says whether bare panels work and behaves accordingly,
6
+ * `createWindow` hands back a {@link WindowHandle}, `onReady` fires exactly
7
+ * once and replays for a late subscriber, and `onPanelReady` replays too.
8
+ * Running one suite from every adapter is what keeps those promises from
9
+ * drifting apart, and gives a new adapter a starting test for free.
10
+ *
11
+ * The suite is runner-free on purpose. Every adapter repository already has
12
+ * its own test runner, and an adapter written outside this repository cannot
13
+ * reach into this one's `test/` folder, so the checks ship as plain objects
14
+ * that throw an `Error` on failure and the adapter iterates them.
15
+ *
16
+ * `createWindow` itself is adapter-specific - IWSDK takes a config PATH, the
17
+ * three.js host takes parsed markup - so the caller supplies a
18
+ * {@link WindowHostContractSetup} that wraps those differences.
19
+ */
20
+ import type { WindowHandle, WindowHost } from './adapter.js';
21
+ /**
22
+ * Everything a case needs to drive one adapter. Build a FRESH one per case:
23
+ * cases spawn windows of their own and do not clean up after themselves.
24
+ */
25
+ export interface WindowHostContractSetup {
26
+ /** The host under test. */
27
+ host: WindowHost;
28
+ /** Spawn one window with this id, using whatever config the adapter needs. */
29
+ createWindow(id: string): WindowHandle;
30
+ /**
31
+ * Attach the panel for a window, where the adapter attaches asynchronously.
32
+ * Omit it when the panel exists as soon as the window does.
33
+ */
34
+ attach?: (id: string) => void;
35
+ /** A config `createPanel` accepts, for hosts that support bare panels. */
36
+ panelConfig?: unknown;
37
+ }
38
+ /**
39
+ * One check a {@link WindowHost} implementation must pass. `run` returns
40
+ * silently on success and throws an `Error` describing the failure
41
+ * otherwise, so any test runner can host it.
42
+ */
43
+ export interface WindowHostContractCase {
44
+ name: string;
45
+ run(setup: WindowHostContractSetup): void;
46
+ }
47
+ /**
48
+ * The shared host conformance suite. An adapter's test file is a loop:
49
+ *
50
+ * ```ts
51
+ * for (const contractCase of windowHostContractCases()) {
52
+ * it(contractCase.name, () => contractCase.run(makeSetup()));
53
+ * }
54
+ * ```
55
+ *
56
+ * `makeSetup()` runs per case, so each case gets a host of its own.
57
+ */
58
+ export declare function windowHostContractCases(): readonly WindowHostContractCase[];
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The shared host conformance suite. An adapter's test file is a loop:
3
+ *
4
+ * ```ts
5
+ * for (const contractCase of windowHostContractCases()) {
6
+ * it(contractCase.name, () => contractCase.run(makeSetup()));
7
+ * }
8
+ * ```
9
+ *
10
+ * `makeSetup()` runs per case, so each case gets a host of its own.
11
+ */
12
+ export function windowHostContractCases() {
13
+ return CASES;
14
+ }
15
+ const CASES = [
16
+ {
17
+ name: 'reports whether bare panels work, and createPanel agrees',
18
+ run(setup) {
19
+ const supported = setup.host.supportsStandalonePanels;
20
+ assert(typeof supported === 'boolean', `supportsStandalonePanels must be a boolean, got ${typeof supported}`);
21
+ if (supported) {
22
+ try {
23
+ setup.host.createPanel(setup.panelConfig);
24
+ }
25
+ catch (error) {
26
+ throw new Error(`supportsStandalonePanels is true, so createPanel() must work, it threw: ${String(error)}`);
27
+ }
28
+ return;
29
+ }
30
+ assert(threw(() => setup.host.createPanel(setup.panelConfig)), 'supportsStandalonePanels is false, so createPanel() must throw rather than return an unusable panel');
31
+ },
32
+ },
33
+ {
34
+ name: 'createWindow returns a handle with an id and onReady',
35
+ run(setup) {
36
+ const handle = setup.createWindow('contract-a');
37
+ assert(typeof handle.id === 'string', `WindowHandle.id must be a string, got ${typeof handle.id}`);
38
+ assert(handle.id === 'contract-a', `createWindow("contract-a") must keep the id it was given, got "${handle.id}"`);
39
+ assert(typeof handle.onReady === 'function', 'a WindowHandle must implement onReady()');
40
+ },
41
+ },
42
+ {
43
+ name: 'onReady fires once when the panel attaches',
44
+ run(setup) {
45
+ const handle = setup.createWindow('contract-b');
46
+ const seen = [];
47
+ handle.onReady((panel) => seen.push(panel));
48
+ setup.attach?.('contract-b');
49
+ assert(seen.length === 1, `onReady must fire exactly once when the panel attaches, it fired ${String(seen.length)} time(s)`);
50
+ assert(handle.panel === seen[0], 'the panel passed to onReady must be the same one the handle reports');
51
+ },
52
+ },
53
+ {
54
+ name: 'onReady replays for a subscriber that arrives late',
55
+ run(setup) {
56
+ const handle = setup.createWindow('contract-c');
57
+ setup.attach?.('contract-c');
58
+ const seen = [];
59
+ const stop = handle.onReady((panel) => seen.push(panel));
60
+ assert(seen.length === 1, `onReady must replay for a subscriber that arrives after the panel, it fired ${String(seen.length)} time(s)`);
61
+ // Unsubscribing after the fact is a no-op, not an error.
62
+ detach(stop, 'onReady');
63
+ assert(seen.length === 1, 'unsubscribing after onReady has replayed must not deliver the panel again');
64
+ },
65
+ },
66
+ {
67
+ name: 'onPanelReady replays for a subscriber that arrives late',
68
+ run(setup) {
69
+ setup.createWindow('contract-d');
70
+ setup.attach?.('contract-d');
71
+ const ids = [];
72
+ const stop = setup.host.onPanelReady((event) => ids.push(event.id));
73
+ assert(ids.includes('contract-d'), `onPanelReady must replay the windows already live, expected "contract-d" among [${ids.join(', ')}]`);
74
+ detach(stop, 'onPanelReady');
75
+ },
76
+ },
77
+ ];
78
+ function assert(condition, message) {
79
+ if (!condition)
80
+ throw new Error(message);
81
+ }
82
+ /** Whether `run` threw, without caring what it threw. */
83
+ function threw(run) {
84
+ try {
85
+ run();
86
+ return false;
87
+ }
88
+ catch {
89
+ return true;
90
+ }
91
+ }
92
+ function detach(stop, label) {
93
+ try {
94
+ stop();
95
+ }
96
+ catch (error) {
97
+ throw new Error(`the unsubscribe returned by ${label}() threw: ${String(error)}`);
98
+ }
99
+ }
100
+ //# sourceMappingURL=contract-cases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-cases.js","sourceRoot":"","sources":["../src/contract-cases.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared `WindowHost` contract, shipped as data rather than as tests.\n *\n * Every adapter promises the same four things, whatever engine sits behind\n * it: it says whether bare panels work and behaves accordingly,\n * `createWindow` hands back a {@link WindowHandle}, `onReady` fires exactly\n * once and replays for a late subscriber, and `onPanelReady` replays too.\n * Running one suite from every adapter is what keeps those promises from\n * drifting apart, and gives a new adapter a starting test for free.\n *\n * The suite is runner-free on purpose. Every adapter repository already has\n * its own test runner, and an adapter written outside this repository cannot\n * reach into this one's `test/` folder, so the checks ship as plain objects\n * that throw an `Error` on failure and the adapter iterates them.\n *\n * `createWindow` itself is adapter-specific - IWSDK takes a config PATH, the\n * three.js host takes parsed markup - so the caller supplies a\n * {@link WindowHostContractSetup} that wraps those differences.\n */\nimport type { PanelHandle, WindowHandle, WindowHost } from './adapter.js';\n\n/**\n * Everything a case needs to drive one adapter. Build a FRESH one per case:\n * cases spawn windows of their own and do not clean up after themselves.\n */\nexport interface WindowHostContractSetup {\n /** The host under test. */\n host: WindowHost;\n /** Spawn one window with this id, using whatever config the adapter needs. */\n createWindow(id: string): WindowHandle;\n /**\n * Attach the panel for a window, where the adapter attaches asynchronously.\n * Omit it when the panel exists as soon as the window does.\n */\n attach?: (id: string) => void;\n /** A config `createPanel` accepts, for hosts that support bare panels. */\n panelConfig?: unknown;\n}\n\n/**\n * One check a {@link WindowHost} implementation must pass. `run` returns\n * silently on success and throws an `Error` describing the failure\n * otherwise, so any test runner can host it.\n */\nexport interface WindowHostContractCase {\n name: string;\n run(setup: WindowHostContractSetup): void;\n}\n\n/**\n * The shared host conformance suite. An adapter's test file is a loop:\n *\n * ```ts\n * for (const contractCase of windowHostContractCases()) {\n * it(contractCase.name, () => contractCase.run(makeSetup()));\n * }\n * ```\n *\n * `makeSetup()` runs per case, so each case gets a host of its own.\n */\nexport function windowHostContractCases(): readonly WindowHostContractCase[] {\n return CASES;\n}\n\nconst CASES: readonly WindowHostContractCase[] = [\n {\n name: 'reports whether bare panels work, and createPanel agrees',\n run(setup) {\n const supported = setup.host.supportsStandalonePanels;\n assert(\n typeof supported === 'boolean',\n `supportsStandalonePanels must be a boolean, got ${typeof supported}`,\n );\n if (supported) {\n try {\n setup.host.createPanel(setup.panelConfig);\n } catch (error) {\n throw new Error(\n `supportsStandalonePanels is true, so createPanel() must work, it threw: ${String(error)}`,\n );\n }\n return;\n }\n assert(\n threw(() => setup.host.createPanel(setup.panelConfig)),\n 'supportsStandalonePanels is false, so createPanel() must throw rather than return an unusable panel',\n );\n },\n },\n {\n name: 'createWindow returns a handle with an id and onReady',\n run(setup) {\n const handle = setup.createWindow('contract-a');\n assert(\n typeof handle.id === 'string',\n `WindowHandle.id must be a string, got ${typeof handle.id}`,\n );\n assert(\n handle.id === 'contract-a',\n `createWindow(\"contract-a\") must keep the id it was given, got \"${handle.id}\"`,\n );\n assert(\n typeof handle.onReady === 'function',\n 'a WindowHandle must implement onReady()',\n );\n },\n },\n {\n name: 'onReady fires once when the panel attaches',\n run(setup) {\n const handle = setup.createWindow('contract-b');\n const seen: PanelHandle[] = [];\n handle.onReady((panel) => seen.push(panel));\n setup.attach?.('contract-b');\n assert(\n seen.length === 1,\n `onReady must fire exactly once when the panel attaches, it fired ${String(seen.length)} time(s)`,\n );\n assert(\n handle.panel === seen[0],\n 'the panel passed to onReady must be the same one the handle reports',\n );\n },\n },\n {\n name: 'onReady replays for a subscriber that arrives late',\n run(setup) {\n const handle = setup.createWindow('contract-c');\n setup.attach?.('contract-c');\n const seen: PanelHandle[] = [];\n const stop = handle.onReady((panel) => seen.push(panel));\n assert(\n seen.length === 1,\n `onReady must replay for a subscriber that arrives after the panel, it fired ${String(seen.length)} time(s)`,\n );\n // Unsubscribing after the fact is a no-op, not an error.\n detach(stop, 'onReady');\n assert(\n seen.length === 1,\n 'unsubscribing after onReady has replayed must not deliver the panel again',\n );\n },\n },\n {\n name: 'onPanelReady replays for a subscriber that arrives late',\n run(setup) {\n setup.createWindow('contract-d');\n setup.attach?.('contract-d');\n const ids: string[] = [];\n const stop = setup.host.onPanelReady((event) => ids.push(event.id));\n assert(\n ids.includes('contract-d'),\n `onPanelReady must replay the windows already live, expected \"contract-d\" among [${ids.join(', ')}]`,\n );\n detach(stop, 'onPanelReady');\n },\n },\n];\n\nfunction assert(condition: boolean, message: string): void {\n if (!condition) throw new Error(message);\n}\n\n/** Whether `run` threw, without caring what it threw. */\nfunction threw(run: () => unknown): boolean {\n try {\n run();\n return false;\n } catch {\n return true;\n }\n}\n\nfunction detach(stop: () => void, label: string): void {\n try {\n stop();\n } catch (error) {\n throw new Error(\n `the unsubscribe returned by ${label}() threw: ${String(error)}`,\n );\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -27,3 +27,4 @@ export * from './controls/log-view.js';
27
27
  export * from './controls/upgrade.js';
28
28
  export * from './adapter.js';
29
29
  export * from './scene.js';
30
+ export * from './contract-cases.js';
package/dist/index.js CHANGED
@@ -31,4 +31,6 @@ export * from './controls/upgrade.js';
31
31
  // Platform-adapter contract + portable scene descriptors
32
32
  export * from './adapter.js';
33
33
  export * from './scene.js';
34
+ // The WindowHost conformance suite, as data an adapter runs in its own runner
35
+ export * from './contract-cases.js';
34
36
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,aAAa;AACb,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AAEpC,sDAAsD;AACtD,cAAc,oBAAoB,CAAC;AAEnC,qCAAqC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AAEtC,yDAAyD;AACzD,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC","sourcesContent":["/**\n * @realitycollective/webxr-uiextensions - the engine-free core.\n *\n * Everything exported here is pure TypeScript with no engine imports\n * (enforced by test/architecture.test.ts): window/dock/region/drag logic,\n * control models, the `data-uix` markup upgraders, the window chrome\n * conventions, and the platform-adapter interfaces engine packages\n * implement.\n */\n// Pure logic\nexport * from './core/events.js';\nexport * from './core/dock-state.js';\nexport * from './core/window-manager.js';\nexport * from './core/region-layout.js';\nexport * from './core/region-registry.js';\nexport * from './core/drag-math.js';\nexport * from './core/hold-to-drag.js';\nexport * from './core/stepper-model.js';\nexport * from './core/toggle-model.js';\nexport * from './core/expandable-model.js';\nexport * from './core/log-model.js';\n\n// Chrome conventions (markup ids + reference snippet)\nexport * from './chrome/markup.js';\n\n// Interface-driven control upgraders\nexport * from './controls/element.js';\nexport * from './controls/stepper.js';\nexport * from './controls/toggle.js';\nexport * from './controls/expandable-label.js';\nexport * from './controls/log-view.js';\nexport * from './controls/upgrade.js';\n\n// Platform-adapter contract + portable scene descriptors\nexport * from './adapter.js';\nexport * from './scene.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,aAAa;AACb,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AAEpC,sDAAsD;AACtD,cAAc,oBAAoB,CAAC;AAEnC,qCAAqC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AAEtC,yDAAyD;AACzD,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAE3B,8EAA8E;AAC9E,cAAc,qBAAqB,CAAC","sourcesContent":["/**\n * @realitycollective/webxr-uiextensions - the engine-free core.\n *\n * Everything exported here is pure TypeScript with no engine imports\n * (enforced by test/architecture.test.ts): window/dock/region/drag logic,\n * control models, the `data-uix` markup upgraders, the window chrome\n * conventions, and the platform-adapter interfaces engine packages\n * implement.\n */\n// Pure logic\nexport * from './core/events.js';\nexport * from './core/dock-state.js';\nexport * from './core/window-manager.js';\nexport * from './core/region-layout.js';\nexport * from './core/region-registry.js';\nexport * from './core/drag-math.js';\nexport * from './core/hold-to-drag.js';\nexport * from './core/stepper-model.js';\nexport * from './core/toggle-model.js';\nexport * from './core/expandable-model.js';\nexport * from './core/log-model.js';\n\n// Chrome conventions (markup ids + reference snippet)\nexport * from './chrome/markup.js';\n\n// Interface-driven control upgraders\nexport * from './controls/element.js';\nexport * from './controls/stepper.js';\nexport * from './controls/toggle.js';\nexport * from './controls/expandable-label.js';\nexport * from './controls/log-view.js';\nexport * from './controls/upgrade.js';\n\n// Platform-adapter contract + portable scene descriptors\nexport * from './adapter.js';\nexport * from './scene.js';\n\n// The WindowHost conformance suite, as data an adapter runs in its own runner\nexport * from './contract-cases.js';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/webxr-uiextensions",
3
- "version": "0.1.0-preview.2",
3
+ "version": "0.1.0-preview.3",
4
4
  "description": "Engine-free core of the Reality Collective UI Extensions: windowing, docking, layout regions and control models for WebXR spatial UI, driven through platform-adapter interfaces. Pair with an engine adapter - @realitycollective/iwsdk-uiextensions (Meta IWSDK) or @realitycollective/xrblocks-uiextensions (Google XR Blocks).",
5
5
  "keywords": [
6
6
  "realitycollective",
@@ -33,6 +33,9 @@
33
33
  "build": "tsc -p tsconfig.build.json",
34
34
  "typecheck": "tsc -p tsconfig.json --noEmit"
35
35
  },
36
+ "dependencies": {
37
+ "@realitycollective/webxr-input": "^0.1.1"
38
+ },
36
39
  "repository": {
37
40
  "type": "git",
38
41
  "url": "git+https://github.com/realitycollective/WebXR-UIExtensions.git",