@realitycollective/iwsdk-uiextensions 0.1.0-preview.1 → 0.1.0-preview.2

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.

Potentially problematic release.


This version of @realitycollective/iwsdk-uiextensions might be problematic. Click here for more details.

package/CHANGELOG.md CHANGED
@@ -14,6 +14,11 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
14
14
  - `@realitycollective/uix-devtools` - dev-only tooling: edit-session launch gate, runtime UIKitML compilation, and the `uix-dev` CLI (Cloudflare quick tunnel, QR onboarding, environment doctor).
15
15
  - Demo clients: the IWSDK showcase, the devtools playground, and the multiplatform lab that picks its pipeline from the hardware.
16
16
  - `uixComponentSet`, exported from `@realitycollective/iwsdk-uiextensions`. Pass it to `spatialUI.componentSets` so the IWSDK 0.5 parser accepts the `<uix-*>` control elements. Without it a panel using any control fails to parse and never attaches. No equivalent is needed on the three.js or XR Blocks adapters.
17
+ - `WindowHandle` and `WindowOptionsBase` in `@realitycollective/webxr-uiextensions`. `WindowHandle` is what every adapter's `createWindow` hands back: an `id`, a `panel` that is `undefined` until the adapter has attached the document, and `onReady(listener)`, which runs once and fires immediately if the panel is already there. `WindowOptionsBase` holds the options every adapter understands, and both adapters' option types now extend it, so an option name means the same thing on each. `createWindow` itself stays adapter-specific, because the `config` payload differs per engine.
18
+ - `WindowHost.supportsStandalonePanels`. It says whether `createPanel` works on a host, rather than leaving callers to find out by catching. `false` on the IWSDK host, where the ECS owns panel lifecycles and `createPanel` throws; `true` on the three.js / XR Blocks host.
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
+ - `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
+ - `getPanelHandle(entity)` in `@realitycollective/iwsdk-uiextensions`: the panel handle for an entity you already hold, or `undefined` while IWSDK is still loading its document.
17
22
 
18
23
  ### Changed
19
24
 
@@ -25,5 +30,9 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
25
30
  - Example and demo markup uses `rgba()` or eight-digit hex rather than `background-opacity`, which the 0.5 parser removed. Both parsers accept the replacement, so one stylesheet serves every adapter.
26
31
  - The IWSDK component kit is selected by name, `spatialUI: { kit: 'horizon' }`, rather than by passing an imported kit module. The kits now live inside the parser.
27
32
  - `@types/three` is pinned to `0.184.0` across the workspace. IWSDK 0.5.3 and xrblocks resolved different copies, and two copies of the three typings make structurally identical `Object3D` types mutually unassignable, which surfaces as spurious errors far from their cause.
33
+ - `onPanelReady` on the IWSDK host announces bare panels as well as managed windows. A `PanelUI` entity with no `UIWindow`, or with an empty `windowId`, used to be dropped silently; it is now announced with `kind: 'panel'` and its config path as the id. Listeners that only want managed windows should check `kind`.
34
+ - `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
+ - 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
+ - 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.
28
37
 
29
38
  [0.1.0]: https://github.com/realitycollective/WebXR-UIExtensions/commits/main
package/README.md CHANGED
@@ -103,6 +103,44 @@ controls.stepper('health').events.on('change', (hp) => setHealth(hp));
103
103
 
104
104
  See `Examples/` (shipped in this package) and the deployable showcase client in the repository for complete, working demonstrations of every feature.
105
105
 
106
+ ## Windows and panel readiness
107
+
108
+ `createUIWindow` returns the ECS entity, which is what you want when you are going to add components to it. When you want the PANEL, use the scene host: `createWindow` gives you a handle that resolves itself.
109
+
110
+ ```ts
111
+ import { createSceneHost, getPanelHandle } from '@realitycollective/iwsdk-uiextensions';
112
+
113
+ const host = createSceneHost(world); // call after registerUIExtensions(world)
114
+
115
+ const status = host.createWindow({
116
+ id: 'status', // optional - omit and you get uix-window-<n>
117
+ title: 'Player Status',
118
+ config: './ui/status.uikitml',
119
+ });
120
+
121
+ status.panel; // undefined until IWSDK attaches the document
122
+ status.onReady((panel) => { // runs once, immediately if it is already there
123
+ panel.getElementById('uix-title');
124
+ });
125
+ status.entity; // still the entity, for ECS work
126
+ ```
127
+
128
+ `getPanelHandle(entity)` does the same lookup for an entity you already hold, and returns `undefined` while the document is still loading.
129
+
130
+ Across a whole scene, subscribe to the host instead:
131
+
132
+ ```ts
133
+ host.onPanelReady(({ id, panel, kind }) => {
134
+ if (kind === 'panel') {
135
+ // A bare PanelUI entity with no UIWindow: `id` is its config path.
136
+ return;
137
+ }
138
+ wireMyWindow(id, panel);
139
+ });
140
+ ```
141
+
142
+ Bare panels are announced as well as managed windows, which is how devtools and hand-built `PanelUI` entities show up in the same stream. `supportsStandalonePanels` is `false` on this host: IWSDK owns panel lifecycles through the ECS, so `createPanel()` throws rather than half-working. Spawn a window instead.
143
+
106
144
  ## Headless core
107
145
 
108
146
  All decision logic (window manager, dock state machine, region slot math, drag math, control models) lives in `@realitycollective/webxr-uiextensions` - pure TypeScript with no engine imports, tested at 100% coverage. The ECS systems in this package are thin appliers of that core onto `@iwsdk/core` components.
package/dist/factory.d.ts CHANGED
@@ -5,35 +5,24 @@
5
5
  */
6
6
  import { type Entity, type World } from '@iwsdk/core';
7
7
  import { RegionFlowType } from './components.js';
8
- import { type DockModeValue } from '@realitycollective/webxr-uiextensions';
9
- export interface CreateWindowOptions {
8
+ import { type WindowOptionsBase } from '@realitycollective/webxr-uiextensions';
9
+ /**
10
+ * Options for {@link createUIWindow}.
11
+ *
12
+ * Everything but `config` comes from the portable {@link WindowOptionsBase},
13
+ * so the same option names mean the same thing on every adapter. `maxWidth`
14
+ * and `maxHeight` route to `UIWindow.targetWidth/Height` here, because IWSDK
15
+ * 0.5 removed those fields from `PanelUI`; omit them to keep the markup's
16
+ * intrinsic size and scale through the entity transform.
17
+ */
18
+ export interface CreateWindowOptions extends WindowOptionsBase {
10
19
  /**
11
20
  * URL of the UIKitML source (e.g. "/ui/my-window.uikitml"). IWSDK 0.5 parses
12
21
  * UIKitML at runtime - the build-time plugin and its generated JSON are gone.
13
22
  */
14
23
  config: string;
15
- id?: string;
16
- title?: string;
17
- dockMode?: DockModeValue;
18
- /** World position for world-locked windows. */
19
- position?: [number, number, number];
20
- /**
21
- * Fit the panel into this world-space box (meters), preserving aspect ratio.
22
- * Replaces the `PanelUI.maxWidth/maxHeight` fields removed in IWSDK 0.5.
23
- * Omit to keep the markup's intrinsic size and scale via the entity transform.
24
- */
25
- maxWidth?: number;
26
- maxHeight?: number;
27
- movable?: boolean;
28
- closable?: boolean;
29
- minimizable?: boolean;
30
- pinnable?: boolean;
24
+ /** Keep the window yawed toward the viewer while it is being dragged. */
31
25
  billboardWhileDragging?: boolean;
32
- followOffset?: [number, number, number];
33
- followSpeed?: number;
34
- followTolerance?: number;
35
- /** Dock straight into this region on spawn. */
36
- region?: string;
37
26
  }
38
27
  export declare function createUIWindow(world: World, options: CreateWindowOptions): Entity;
39
28
  export interface CreateDockRegionOptions {
package/dist/factory.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Follower, FollowBehavior, PanelUI, PokeInteractable, RayInteractable, } from '@iwsdk/core';
7
7
  import { UIDockRegion, UIDockedTo, UIWindow, RegionFlowType } from './components.js';
8
- import { DockMode } from '@realitycollective/webxr-uiextensions';
8
+ import { DockMode, } from '@realitycollective/webxr-uiextensions';
9
9
  export function createUIWindow(world, options) {
10
10
  const entity = world
11
11
  .createTransformEntity()
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Convenience factories - one call to spawn a managed window or dock region.\n * Thin sugar over `createTransformEntity().addComponent(...)`; everything\n * they do can be done manually with the components.\n */\nimport {\n Follower,\n FollowBehavior,\n PanelUI,\n PokeInteractable,\n RayInteractable,\n type Entity,\n type World,\n} from '@iwsdk/core';\nimport { UIDockRegion, UIDockedTo, UIWindow, RegionFlowType } from './components.js';\nimport { DockMode, type DockModeValue } from '@realitycollective/webxr-uiextensions';\n\nexport interface CreateWindowOptions {\n /**\n * URL of the UIKitML source (e.g. \"/ui/my-window.uikitml\"). IWSDK 0.5 parses\n * UIKitML at runtime - the build-time plugin and its generated JSON are gone.\n */\n config: string;\n id?: string;\n title?: string;\n dockMode?: DockModeValue;\n /** World position for world-locked windows. */\n position?: [number, number, number];\n /**\n * Fit the panel into this world-space box (meters), preserving aspect ratio.\n * Replaces the `PanelUI.maxWidth/maxHeight` fields removed in IWSDK 0.5.\n * Omit to keep the markup's intrinsic size and scale via the entity transform.\n */\n maxWidth?: number;\n maxHeight?: number;\n movable?: boolean;\n closable?: boolean;\n minimizable?: boolean;\n pinnable?: boolean;\n billboardWhileDragging?: boolean;\n followOffset?: [number, number, number];\n followSpeed?: number;\n followTolerance?: number;\n /** Dock straight into this region on spawn. */\n region?: string;\n}\n\nexport function createUIWindow(world: World, options: CreateWindowOptions): Entity {\n const entity = world\n .createTransformEntity()\n .addComponent(PanelUI, {\n config: options.config,\n })\n .addComponent(UIWindow, {\n // Applied by UIWindowSystem once the document loads - see components.ts.\n targetWidth: options.maxWidth ?? 0,\n targetHeight: options.maxHeight ?? 0,\n windowId: options.id ?? '',\n title: options.title ?? '',\n dockMode: options.dockMode ?? DockMode.WorldLocked,\n movable: options.movable ?? true,\n closable: options.closable ?? true,\n minimizable: options.minimizable ?? true,\n pinnable: options.pinnable ?? true,\n billboardWhileDragging: options.billboardWhileDragging ?? true,\n followOffset: options.followOffset ?? [0, -0.15, -1.2],\n followSpeed: options.followSpeed ?? 3,\n followTolerance: options.followTolerance ?? 0.35,\n })\n .addComponent(RayInteractable)\n .addComponent(PokeInteractable);\n\n if (options.position) {\n entity.object3D?.position.set(...options.position);\n }\n if (options.region) {\n entity.addComponent(UIDockedTo, { regionId: options.region });\n }\n return entity;\n}\n\nexport interface CreateDockRegionOptions {\n id: string;\n flow?: (typeof RegionFlowType)[keyof typeof RegionFlowType];\n pitch?: number;\n columns?: number;\n capacity?: number;\n snapRadius?: number;\n /** World position of the region origin. */\n position?: [number, number, number];\n /**\n * Body-lock the region itself: it follows the player's head at\n * `followOffset`, and every window docked into it comes along.\n */\n follow?: boolean;\n followOffset?: [number, number, number];\n}\n\nexport function createDockRegion(world: World, options: CreateDockRegionOptions): Entity {\n const entity = world.createTransformEntity().addComponent(UIDockRegion, {\n regionId: options.id,\n flow: options.flow ?? RegionFlowType.Column,\n pitch: options.pitch ?? 0.35,\n columns: options.columns ?? 2,\n capacity: options.capacity ?? 0,\n snapRadius: options.snapRadius ?? 0.5,\n });\n if (options.position) {\n entity.object3D?.position.set(...options.position);\n }\n if (options.follow) {\n entity.addComponent(Follower, {\n // Camera, not player.head - the head rig is origin-locked outside XR.\n target: world.camera,\n offsetPosition: options.followOffset ?? [0, -0.2, -1.4],\n behavior: FollowBehavior.PivotY,\n });\n }\n return entity;\n}\n"]}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Convenience factories - one call to spawn a managed window or dock region.\n * Thin sugar over `createTransformEntity().addComponent(...)`; everything\n * they do can be done manually with the components.\n */\nimport {\n Follower,\n FollowBehavior,\n PanelUI,\n PokeInteractable,\n RayInteractable,\n type Entity,\n type World,\n} from '@iwsdk/core';\nimport { UIDockRegion, UIDockedTo, UIWindow, RegionFlowType } from './components.js';\nimport {\n DockMode,\n type WindowOptionsBase,\n} from '@realitycollective/webxr-uiextensions';\n\n/**\n * Options for {@link createUIWindow}.\n *\n * Everything but `config` comes from the portable {@link WindowOptionsBase},\n * so the same option names mean the same thing on every adapter. `maxWidth`\n * and `maxHeight` route to `UIWindow.targetWidth/Height` here, because IWSDK\n * 0.5 removed those fields from `PanelUI`; omit them to keep the markup's\n * intrinsic size and scale through the entity transform.\n */\nexport interface CreateWindowOptions extends WindowOptionsBase {\n /**\n * URL of the UIKitML source (e.g. \"/ui/my-window.uikitml\"). IWSDK 0.5 parses\n * UIKitML at runtime - the build-time plugin and its generated JSON are gone.\n */\n config: string;\n /** Keep the window yawed toward the viewer while it is being dragged. */\n billboardWhileDragging?: boolean;\n}\n\nexport function createUIWindow(world: World, options: CreateWindowOptions): Entity {\n const entity = world\n .createTransformEntity()\n .addComponent(PanelUI, {\n config: options.config,\n })\n .addComponent(UIWindow, {\n // Applied by UIWindowSystem once the document loads - see components.ts.\n targetWidth: options.maxWidth ?? 0,\n targetHeight: options.maxHeight ?? 0,\n windowId: options.id ?? '',\n title: options.title ?? '',\n dockMode: options.dockMode ?? DockMode.WorldLocked,\n movable: options.movable ?? true,\n closable: options.closable ?? true,\n minimizable: options.minimizable ?? true,\n pinnable: options.pinnable ?? true,\n billboardWhileDragging: options.billboardWhileDragging ?? true,\n followOffset: options.followOffset ?? [0, -0.15, -1.2],\n followSpeed: options.followSpeed ?? 3,\n followTolerance: options.followTolerance ?? 0.35,\n })\n .addComponent(RayInteractable)\n .addComponent(PokeInteractable);\n\n if (options.position) {\n entity.object3D?.position.set(...options.position);\n }\n if (options.region) {\n entity.addComponent(UIDockedTo, { regionId: options.region });\n }\n return entity;\n}\n\nexport interface CreateDockRegionOptions {\n id: string;\n flow?: (typeof RegionFlowType)[keyof typeof RegionFlowType];\n pitch?: number;\n columns?: number;\n capacity?: number;\n snapRadius?: number;\n /** World position of the region origin. */\n position?: [number, number, number];\n /**\n * Body-lock the region itself: it follows the player's head at\n * `followOffset`, and every window docked into it comes along.\n */\n follow?: boolean;\n followOffset?: [number, number, number];\n}\n\nexport function createDockRegion(world: World, options: CreateDockRegionOptions): Entity {\n const entity = world.createTransformEntity().addComponent(UIDockRegion, {\n regionId: options.id,\n flow: options.flow ?? RegionFlowType.Column,\n pitch: options.pitch ?? 0.35,\n columns: options.columns ?? 2,\n capacity: options.capacity ?? 0,\n snapRadius: options.snapRadius ?? 0.5,\n });\n if (options.position) {\n entity.object3D?.position.set(...options.position);\n }\n if (options.follow) {\n entity.addComponent(Follower, {\n // Camera, not player.head - the head rig is origin-locked outside XR.\n target: world.camera,\n offsetPosition: options.followOffset ?? [0, -0.2, -1.4],\n behavior: FollowBehavior.PivotY,\n });\n }\n return entity;\n}\n"]}
@@ -11,11 +11,32 @@
11
11
  *
12
12
  * Readiness is detected with a single ECS query on `PanelUI + PanelDocument`
13
13
  * - the engine-specific mechanism stays here, behind the portable interface.
14
+ * Bare panels (a `PanelUI` entity with no `UIWindow`) are announced too, with
15
+ * `kind: 'panel'` and their config path as the id, so devtools and hand-built
16
+ * entities show up in the same stream as managed windows.
14
17
  */
15
- import { type World } from '@iwsdk/core';
16
- import { type SceneTarget, type WindowHost } from '@realitycollective/webxr-uiextensions';
18
+ import { type Entity, type World } from '@iwsdk/core';
19
+ import { type PanelHandle, type SceneTarget, type WindowHandle, type WindowHost } from '@realitycollective/webxr-uiextensions';
20
+ import { type CreateWindowOptions } from './factory.js';
21
+ /**
22
+ * The live panel on an entity, or `undefined` while IWSDK is still loading
23
+ * its document. Use it to reach a panel from an entity you already hold;
24
+ * `onPanelReady` stays the way to be TOLD when one appears.
25
+ */
26
+ export declare function getPanelHandle(entity: Entity): PanelHandle | undefined;
27
+ /** A window spawned by {@link IwsdkSceneHost.createWindow}. */
28
+ export interface IwsdkWindowHandle extends WindowHandle {
29
+ /** The ECS entity carrying `PanelUI`, `UIWindow` and the interaction tags. */
30
+ readonly entity: Entity;
31
+ }
17
32
  export interface IwsdkSceneHost extends WindowHost, SceneTarget {
18
33
  readonly world: World;
34
+ /**
35
+ * Spawn a managed window and get a handle back straight away. IWSDK loads
36
+ * the markup over later frames, so `handle.panel` is `undefined` at first
37
+ * and `handle.onReady` is how you wait for it without an ECS query.
38
+ */
39
+ createWindow(options: CreateWindowOptions): IwsdkWindowHandle;
19
40
  }
20
41
  /**
21
42
  * Create the host for a world. Call AFTER `registerUIExtensions(world)`.
@@ -11,10 +11,13 @@
11
11
  *
12
12
  * Readiness is detected with a single ECS query on `PanelUI + PanelDocument`
13
13
  * - the engine-specific mechanism stays here, behind the portable interface.
14
+ * Bare panels (a `PanelUI` entity with no `UIWindow`) are announced too, with
15
+ * `kind: 'panel'` and their config path as the id, so devtools and hand-built
16
+ * entities show up in the same stream as managed windows.
14
17
  */
15
18
  import { PanelDocument, PanelUI, createSystem, } from '@iwsdk/core';
16
19
  import { upgradePanel, } from '@realitycollective/webxr-uiextensions';
17
- import { createDockRegion, createUIWindow } from './factory.js';
20
+ import { createDockRegion, createUIWindow, } from './factory.js';
18
21
  import { UIWindow } from './components.js';
19
22
  /** Panel handle over an IWSDK `UIKitDocument`. */
20
23
  function panelHandleFor(document) {
@@ -29,6 +32,15 @@ function panelHandleFor(document) {
29
32
  },
30
33
  };
31
34
  }
35
+ /**
36
+ * The live panel on an entity, or `undefined` while IWSDK is still loading
37
+ * its document. Use it to reach a panel from an entity you already hold;
38
+ * `onPanelReady` stays the way to be TOLD when one appears.
39
+ */
40
+ export function getPanelHandle(entity) {
41
+ const document = PanelDocument.data.document[entity.index];
42
+ return document ? panelHandleFor(document) : undefined;
43
+ }
32
44
  /**
33
45
  * Bridges panel loading into `onPanelReady`. Registered once per host; the
34
46
  * query fires as each panel's document is attached by IWSDK's UI system.
@@ -56,18 +68,19 @@ export function createSceneHost(world) {
56
68
  return;
57
69
  }
58
70
  seen.add(document);
59
- // Window id comes from the UIWindow component the factory attached.
60
- const id = entity.hasComponent(UIWindow)
71
+ // Window id comes from the UIWindow component the factory attached. A
72
+ // panel without one is a bare panel - still worth announcing, keyed by
73
+ // its config path, which is the only stable id IWSDK gives it.
74
+ const windowId = entity.hasComponent(UIWindow)
61
75
  ? UIWindow.data.windowId[entity.index]
62
76
  : '';
63
- if (!id) {
64
- return; // a bare panel, not a managed window - nothing to wire by id
65
- }
77
+ const kind = windowId ? 'window' : 'panel';
78
+ const id = windowId || PanelUI.data.config[entity.index];
66
79
  const panel = panelHandleFor(document);
67
80
  // Upgrade `data-uix` controls exactly as the ECS controls system does,
68
81
  // so wiring code sees the same handles on every adapter (idempotent).
69
82
  upgradePanel(document, document.rootElement);
70
- const event = { id, panel };
83
+ const event = { id, panel, kind };
71
84
  ready.set(id, event);
72
85
  for (const listener of readyListeners) {
73
86
  try {
@@ -79,8 +92,12 @@ export function createSceneHost(world) {
79
92
  }
80
93
  };
81
94
  world.registerSystem(createReadySystem(announce));
82
- return {
95
+ let windowSequence = 0;
96
+ const host = {
83
97
  world,
98
+ // IWSDK owns panel lifecycles through the ECS, so there is no bare-panel
99
+ // path here - `createPanel` throws rather than half-working.
100
+ supportsStandalonePanels: false,
84
101
  createPanel() {
85
102
  throw new Error('[uix] createPanel() is not supported on the IWSDK host - spawn a window ' +
86
103
  '(spawnWindow/createUIWindow) so IWSDK owns the panel lifecycle.');
@@ -92,6 +109,33 @@ export function createSceneHost(world) {
92
109
  }
93
110
  return () => void readyListeners.delete(listener);
94
111
  },
112
+ createWindow(options) {
113
+ const id = options.id ?? `uix-window-${(windowSequence += 1)}`;
114
+ const entity = createUIWindow(world, { ...options, id });
115
+ return {
116
+ entity,
117
+ id,
118
+ get panel() {
119
+ return ready.get(id)?.panel;
120
+ },
121
+ onReady(listener) {
122
+ const existing = ready.get(id)?.panel;
123
+ if (existing) {
124
+ listener(existing);
125
+ return () => { };
126
+ }
127
+ // `onPanelReady` replays panels that are already live, but this id
128
+ // is not one of them, so the callback only ever runs later.
129
+ const unsubscribe = host.onPanelReady((event) => {
130
+ if (event.id === id) {
131
+ unsubscribe();
132
+ listener(event.panel);
133
+ }
134
+ });
135
+ return unsubscribe;
136
+ },
137
+ };
138
+ },
95
139
  spawnRegion(region) {
96
140
  createDockRegion(world, {
97
141
  id: region.id,
@@ -112,7 +156,7 @@ export function createSceneHost(world) {
112
156
  });
113
157
  },
114
158
  spawnWindow(window) {
115
- createUIWindow(world, {
159
+ host.createWindow({
116
160
  id: window.id,
117
161
  title: window.title,
118
162
  config: window.config,
@@ -141,5 +185,6 @@ export function createSceneHost(world) {
141
185
  });
142
186
  },
143
187
  };
188
+ return host;
144
189
  }
145
190
  //# sourceMappingURL=scene-host.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scene-host.js","sourceRoot":"","sources":["../src/scene-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of the core's `WindowHost` + `SceneTarget`.\n *\n * This is what makes a portable `SceneDescriptor` build the SAME playground\n * on IWSDK as on plain three.js, and lets app behaviour be wired through\n * `onPanelReady` instead of hand-written ECS queries:\n *\n * const host = createSceneHost(world);\n * applyScene(host, PLAYGROUND);\n * host.onPanelReady(({ id, panel }) => wireMyPanel(id, panel));\n *\n * Readiness is detected with a single ECS query on `PanelUI + PanelDocument`\n * - the engine-specific mechanism stays here, behind the portable interface.\n */\nimport {\n PanelDocument,\n PanelUI,\n UIKitDocument,\n createSystem,\n type Entity,\n type World,\n} from '@iwsdk/core';\nimport {\n upgradePanel,\n type PanelHandle,\n type PanelReadyEvent,\n type SceneRegion,\n type SceneTarget,\n type SceneWindow,\n type UixElement,\n type WindowHost,\n} from '@realitycollective/webxr-uiextensions';\nimport { createDockRegion, createUIWindow } from './factory.js';\nimport { UIWindow } from './components.js';\n\n/** Panel handle over an IWSDK `UIKitDocument`. */\nfunction panelHandleFor(document: UIKitDocument): PanelHandle {\n return {\n get root(): UixElement {\n return document.rootElement as unknown as UixElement;\n },\n getElementById: (id) =>\n (document.getElementById(id) as UixElement | null) ?? undefined,\n setTargetDimensions: (width, height) =>\n document.setTargetDimensions(width, height),\n dispose: () => {\n /* IWSDK owns the document lifecycle via the entity. */\n },\n };\n}\n\n/**\n * Bridges panel loading into `onPanelReady`. Registered once per host; the\n * query fires as each panel's document is attached by IWSDK's UI system.\n */\nfunction createReadySystem(onReady: (entity: Entity) => void) {\n return class UixPanelReadySystem extends createSystem({\n panels: { required: [PanelUI, PanelDocument] },\n }) {\n override init(): void {\n this.cleanupFuncs.push(\n this.queries.panels.subscribe('qualify', (entity) => onReady(entity)),\n );\n this.queries.panels.entities.forEach((entity) => onReady(entity));\n }\n };\n}\n\nexport interface IwsdkSceneHost extends WindowHost, SceneTarget {\n readonly world: World;\n}\n\n/**\n * Create the host for a world. Call AFTER `registerUIExtensions(world)`.\n */\nexport function createSceneHost(world: World): IwsdkSceneHost {\n const readyListeners = new Set<(event: PanelReadyEvent) => void>();\n const ready = new Map<string, PanelReadyEvent>();\n const seen = new WeakSet<object>();\n\n const announce = (entity: Entity): void => {\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n if (!document || seen.has(document)) {\n return;\n }\n seen.add(document);\n // Window id comes from the UIWindow component the factory attached.\n const id = entity.hasComponent(UIWindow)\n ? (UIWindow.data.windowId[entity.index] as string)\n : '';\n if (!id) {\n return; // a bare panel, not a managed window - nothing to wire by id\n }\n const panel = panelHandleFor(document);\n // Upgrade `data-uix` controls exactly as the ECS controls system does,\n // so wiring code sees the same handles on every adapter (idempotent).\n upgradePanel(document, document.rootElement as unknown as UixElement);\n const event: PanelReadyEvent = { id, panel };\n ready.set(id, event);\n for (const listener of readyListeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[uix] panel-ready listener failed for \"${id}\":`, error);\n }\n }\n };\n\n world.registerSystem(createReadySystem(announce));\n\n return {\n world,\n\n createPanel(): PanelHandle {\n throw new Error(\n '[uix] createPanel() is not supported on the IWSDK host - spawn a window ' +\n '(spawnWindow/createUIWindow) so IWSDK owns the panel lifecycle.',\n );\n },\n\n onPanelReady(listener) {\n readyListeners.add(listener);\n for (const event of ready.values()) {\n listener(event);\n }\n return () => void readyListeners.delete(listener);\n },\n\n spawnRegion(region: SceneRegion): void {\n createDockRegion(world, {\n id: region.id,\n ...(region.flow !== undefined ? { flow: region.flow } : {}),\n ...(region.pitch !== undefined ? { pitch: region.pitch } : {}),\n ...(region.columns !== undefined ? { columns: region.columns } : {}),\n ...(region.capacity !== undefined ? { capacity: region.capacity } : {}),\n ...(region.snapRadius !== undefined\n ? { snapRadius: region.snapRadius }\n : {}),\n ...(region.position !== undefined\n ? { position: [...region.position] as [number, number, number] }\n : {}),\n ...(region.follow !== undefined ? { follow: region.follow } : {}),\n ...(region.followOffset !== undefined\n ? { followOffset: [...region.followOffset] as [number, number, number] }\n : {}),\n });\n },\n\n spawnWindow(window: SceneWindow): void {\n createUIWindow(world, {\n id: window.id,\n title: window.title,\n config: window.config,\n ...(window.position !== undefined\n ? { position: [...window.position] as [number, number, number] }\n : {}),\n ...(window.maxWidth !== undefined ? { maxWidth: window.maxWidth } : {}),\n ...(window.maxHeight !== undefined ? { maxHeight: window.maxHeight } : {}),\n ...(window.dockMode !== undefined ? { dockMode: window.dockMode } : {}),\n ...(window.region !== undefined ? { region: window.region } : {}),\n ...(window.followOffset !== undefined\n ? { followOffset: [...window.followOffset] as [number, number, number] }\n : {}),\n ...(window.followSpeed !== undefined\n ? { followSpeed: window.followSpeed }\n : {}),\n ...(window.followTolerance !== undefined\n ? { followTolerance: window.followTolerance }\n : {}),\n ...(window.movable !== undefined ? { movable: window.movable } : {}),\n ...(window.closable !== undefined ? { closable: window.closable } : {}),\n ...(window.minimizable !== undefined\n ? { minimizable: window.minimizable }\n : {}),\n ...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),\n });\n },\n };\n}\n"]}
1
+ {"version":3,"file":"scene-host.js","sourceRoot":"","sources":["../src/scene-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IWSDK implementation of the core's `WindowHost` + `SceneTarget`.\n *\n * This is what makes a portable `SceneDescriptor` build the SAME playground\n * on IWSDK as on plain three.js, and lets app behaviour be wired through\n * `onPanelReady` instead of hand-written ECS queries:\n *\n * const host = createSceneHost(world);\n * applyScene(host, PLAYGROUND);\n * host.onPanelReady(({ id, panel }) => wireMyPanel(id, panel));\n *\n * Readiness is detected with a single ECS query on `PanelUI + PanelDocument`\n * - the engine-specific mechanism stays here, behind the portable interface.\n * Bare panels (a `PanelUI` entity with no `UIWindow`) are announced too, with\n * `kind: 'panel'` and their config path as the id, so devtools and hand-built\n * entities show up in the same stream as managed windows.\n */\nimport {\n PanelDocument,\n PanelUI,\n UIKitDocument,\n createSystem,\n type Entity,\n type World,\n} from '@iwsdk/core';\nimport {\n upgradePanel,\n type PanelHandle,\n type PanelReadyEvent,\n type SceneRegion,\n type SceneTarget,\n type SceneWindow,\n type UixElement,\n type WindowHandle,\n type WindowHost,\n} from '@realitycollective/webxr-uiextensions';\nimport {\n createDockRegion,\n createUIWindow,\n type CreateWindowOptions,\n} from './factory.js';\nimport { UIWindow } from './components.js';\n\n/** Panel handle over an IWSDK `UIKitDocument`. */\nfunction panelHandleFor(document: UIKitDocument): PanelHandle {\n return {\n get root(): UixElement {\n return document.rootElement as unknown as UixElement;\n },\n getElementById: (id) =>\n (document.getElementById(id) as UixElement | null) ?? undefined,\n setTargetDimensions: (width, height) =>\n document.setTargetDimensions(width, height),\n dispose: () => {\n /* IWSDK owns the document lifecycle via the entity. */\n },\n };\n}\n\n/**\n * The live panel on an entity, or `undefined` while IWSDK is still loading\n * its document. Use it to reach a panel from an entity you already hold;\n * `onPanelReady` stays the way to be TOLD when one appears.\n */\nexport function getPanelHandle(entity: Entity): PanelHandle | undefined {\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n return document ? panelHandleFor(document) : undefined;\n}\n\n/**\n * Bridges panel loading into `onPanelReady`. Registered once per host; the\n * query fires as each panel's document is attached by IWSDK's UI system.\n */\nfunction createReadySystem(onReady: (entity: Entity) => void) {\n return class UixPanelReadySystem extends createSystem({\n panels: { required: [PanelUI, PanelDocument] },\n }) {\n override init(): void {\n this.cleanupFuncs.push(\n this.queries.panels.subscribe('qualify', (entity) => onReady(entity)),\n );\n this.queries.panels.entities.forEach((entity) => onReady(entity));\n }\n };\n}\n\n/** A window spawned by {@link IwsdkSceneHost.createWindow}. */\nexport interface IwsdkWindowHandle extends WindowHandle {\n /** The ECS entity carrying `PanelUI`, `UIWindow` and the interaction tags. */\n readonly entity: Entity;\n}\n\nexport interface IwsdkSceneHost extends WindowHost, SceneTarget {\n readonly world: World;\n /**\n * Spawn a managed window and get a handle back straight away. IWSDK loads\n * the markup over later frames, so `handle.panel` is `undefined` at first\n * and `handle.onReady` is how you wait for it without an ECS query.\n */\n createWindow(options: CreateWindowOptions): IwsdkWindowHandle;\n}\n\n/**\n * Create the host for a world. Call AFTER `registerUIExtensions(world)`.\n */\nexport function createSceneHost(world: World): IwsdkSceneHost {\n const readyListeners = new Set<(event: PanelReadyEvent) => void>();\n const ready = new Map<string, PanelReadyEvent>();\n const seen = new WeakSet<object>();\n\n const announce = (entity: Entity): void => {\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n if (!document || seen.has(document)) {\n return;\n }\n seen.add(document);\n // Window id comes from the UIWindow component the factory attached. A\n // panel without one is a bare panel - still worth announcing, keyed by\n // its config path, which is the only stable id IWSDK gives it.\n const windowId = entity.hasComponent(UIWindow)\n ? (UIWindow.data.windowId[entity.index] as string)\n : '';\n const kind: 'window' | 'panel' = windowId ? 'window' : 'panel';\n const id = windowId || (PanelUI.data.config[entity.index] as string);\n const panel = panelHandleFor(document);\n // Upgrade `data-uix` controls exactly as the ECS controls system does,\n // so wiring code sees the same handles on every adapter (idempotent).\n upgradePanel(document, document.rootElement as unknown as UixElement);\n const event: PanelReadyEvent = { id, panel, kind };\n ready.set(id, event);\n for (const listener of readyListeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[uix] panel-ready listener failed for \"${id}\":`, error);\n }\n }\n };\n\n world.registerSystem(createReadySystem(announce));\n\n let windowSequence = 0;\n\n const host: IwsdkSceneHost = {\n world,\n\n // IWSDK owns panel lifecycles through the ECS, so there is no bare-panel\n // path here - `createPanel` throws rather than half-working.\n supportsStandalonePanels: false,\n\n createPanel(): PanelHandle {\n throw new Error(\n '[uix] createPanel() is not supported on the IWSDK host - spawn a window ' +\n '(spawnWindow/createUIWindow) so IWSDK owns the panel lifecycle.',\n );\n },\n\n onPanelReady(listener) {\n readyListeners.add(listener);\n for (const event of ready.values()) {\n listener(event);\n }\n return () => void readyListeners.delete(listener);\n },\n\n createWindow(options: CreateWindowOptions): IwsdkWindowHandle {\n const id = options.id ?? `uix-window-${(windowSequence += 1)}`;\n const entity = createUIWindow(world, { ...options, id });\n return {\n entity,\n id,\n get panel(): PanelHandle | undefined {\n return ready.get(id)?.panel;\n },\n onReady(listener: (panel: PanelHandle) => void): () => void {\n const existing = ready.get(id)?.panel;\n if (existing) {\n listener(existing);\n return () => {};\n }\n // `onPanelReady` replays panels that are already live, but this id\n // is not one of them, so the callback only ever runs later.\n const unsubscribe = host.onPanelReady((event) => {\n if (event.id === id) {\n unsubscribe();\n listener(event.panel);\n }\n });\n return unsubscribe;\n },\n };\n },\n\n spawnRegion(region: SceneRegion): void {\n createDockRegion(world, {\n id: region.id,\n ...(region.flow !== undefined ? { flow: region.flow } : {}),\n ...(region.pitch !== undefined ? { pitch: region.pitch } : {}),\n ...(region.columns !== undefined ? { columns: region.columns } : {}),\n ...(region.capacity !== undefined ? { capacity: region.capacity } : {}),\n ...(region.snapRadius !== undefined\n ? { snapRadius: region.snapRadius }\n : {}),\n ...(region.position !== undefined\n ? { position: [...region.position] as [number, number, number] }\n : {}),\n ...(region.follow !== undefined ? { follow: region.follow } : {}),\n ...(region.followOffset !== undefined\n ? { followOffset: [...region.followOffset] as [number, number, number] }\n : {}),\n });\n },\n\n spawnWindow(window: SceneWindow): void {\n host.createWindow({\n id: window.id,\n title: window.title,\n config: window.config,\n ...(window.position !== undefined\n ? { position: [...window.position] as [number, number, number] }\n : {}),\n ...(window.maxWidth !== undefined ? { maxWidth: window.maxWidth } : {}),\n ...(window.maxHeight !== undefined ? { maxHeight: window.maxHeight } : {}),\n ...(window.dockMode !== undefined ? { dockMode: window.dockMode } : {}),\n ...(window.region !== undefined ? { region: window.region } : {}),\n ...(window.followOffset !== undefined\n ? { followOffset: [...window.followOffset] as [number, number, number] }\n : {}),\n ...(window.followSpeed !== undefined\n ? { followSpeed: window.followSpeed }\n : {}),\n ...(window.followTolerance !== undefined\n ? { followTolerance: window.followTolerance }\n : {}),\n ...(window.movable !== undefined ? { movable: window.movable } : {}),\n ...(window.closable !== undefined ? { closable: window.closable } : {}),\n ...(window.minimizable !== undefined\n ? { minimizable: window.minimizable }\n : {}),\n ...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),\n });\n },\n };\n\n return host;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/iwsdk-uiextensions",
3
- "version": "0.1.0-preview.1",
3
+ "version": "0.1.0-preview.2",
4
4
  "description": "Meta IWSDK adapter for the Reality Collective UI Extensions - windowing, docking, layout regions and extra controls, binding the engine-free @realitycollective/webxr-uiextensions core onto IWSDK's ECS, UIKitML and interaction systems.",
5
5
  "keywords": [
6
6
  "realitycollective",
@@ -39,7 +39,7 @@
39
39
  "@drawcall/uikitml": "0.1.8",
40
40
  "zod": "^4.4.3",
41
41
  "@pmndrs/handle": "^6.6.16",
42
- "@realitycollective/webxr-uiextensions": "^0.1.0-preview.1"
42
+ "@realitycollective/webxr-uiextensions": "^0.1.0-preview.2"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@iwsdk/core": ">=0.5.0 <0.6.0"