@realitycollective/xrblocks-uiextensions 0.1.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/desktop-controls.d.ts +60 -0
- package/dist/desktop-controls.js +129 -0
- package/dist/desktop-controls.js.map +1 -0
- package/dist/desktop-locomotion.d.ts +72 -0
- package/dist/desktop-locomotion.js +131 -0
- package/dist/desktop-locomotion.js.map +1 -0
- package/dist/follow-math.d.ts +24 -0
- package/dist/follow-math.js +52 -0
- package/dist/follow-math.js.map +1 -0
- package/dist/host.d.ts +112 -0
- package/dist/host.js +375 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/panel-document.d.ts +37 -0
- package/dist/panel-document.js +72 -0
- package/dist/panel-document.js.map +1 -0
- package/dist/pointer-forward.d.ts +36 -0
- package/dist/pointer-forward.js +32 -0
- package/dist/pointer-forward.js.map +1 -0
- package/dist/renderer-setup.d.ts +6 -0
- package/dist/renderer-setup.js +29 -0
- package/dist/renderer-setup.js.map +1 -0
- package/dist/scale-math.d.ts +12 -0
- package/dist/scale-math.js +25 -0
- package/dist/scale-math.js.map +1 -0
- package/dist/xrblocks.d.ts +37 -0
- package/dist/xrblocks.js +46 -0
- package/dist/xrblocks.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow-math.js","sourceRoot":"","sources":["../src/follow-math.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Follow-mode math - pure tuple arithmetic (no three.js) so it is fully\n * unit-testable. Windows in `body-follow` mode chase a target point that is\n * the viewer's head position plus a yaw-rotated offset (pitch/roll are\n * deliberately ignored so panels never tilt with the viewer's head).\n */\nimport type { HeadPose, Vec3Tuple } from '@realitycollective/webxr-uiextensions';\n\n/** Extract the yaw (rotation about +Y) from a quaternion [x, y, z, w]. */\nexport function yawFromQuaternion(q: readonly [number, number, number, number]): number {\n const [x, y, z, w] = q;\n // Yaw of the quaternion's forward vector, projected onto the XZ plane.\n const forwardX = 2 * (x * z + w * y);\n const forwardZ = 1 - 2 * (x * x + y * y);\n return Math.atan2(forwardX, forwardZ);\n}\n\n/** Rotate an offset by a yaw angle (about +Y). */\nexport function rotateOffsetByYaw(offset: Vec3Tuple, yaw: number): Vec3Tuple {\n const sin = Math.sin(yaw);\n const cos = Math.cos(yaw);\n const [x, y, z] = offset;\n return [x * cos + z * sin, y, -x * sin + z * cos];\n}\n\n/** World-space target position for a following window. */\nexport function followTarget(pose: HeadPose, offset: Vec3Tuple): Vec3Tuple {\n const rotated = rotateOffsetByYaw(offset, yawFromQuaternion(pose.quaternion));\n return [\n pose.position[0] + rotated[0],\n pose.position[1] + rotated[1],\n pose.position[2] + rotated[2],\n ];\n}\n\n/**\n * Frame-rate-independent exponential approach: fraction of the remaining\n * distance to cover this frame, for smoothing speed `speed` (1/s) over\n * `delta` seconds. 0 when delta/speed are non-positive, approaches 1 for\n * large steps.\n */\nexport function approachAlpha(speed: number, delta: number): number {\n if (!(speed > 0) || !(delta > 0)) {\n return 0;\n }\n return 1 - Math.exp(-speed * delta);\n}\n\n/** Move `current` toward `target` by `alpha`, returning the new position. */\nexport function approach(current: Vec3Tuple, target: Vec3Tuple, alpha: number): Vec3Tuple {\n return [\n current[0] + (target[0] - current[0]) * alpha,\n current[1] + (target[1] - current[1]) * alpha,\n current[2] + (target[2] - current[2]) * alpha,\n ];\n}\n\n/** Squared distance between two points (cheap tolerance checks). */\nexport function distanceSquared(a: Vec3Tuple, b: Vec3Tuple): number {\n const dx = a[0] - b[0];\n const dy = a[1] - b[1];\n const dz = a[2] - b[2];\n return dx * dx + dy * dy + dz * dz;\n}\n"]}
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UixWindowHost - the engine binding for plain three.js / XR Blocks scenes.
|
|
3
|
+
*
|
|
4
|
+
* Owns a core `WindowManager` + `RegionRegistry` and applies their decisions
|
|
5
|
+
* to the scene graph: spawn UIKitML windows and dock regions, wire chrome
|
|
6
|
+
* buttons (focus / PIN / MIN / X), collapse content while minimized, place
|
|
7
|
+
* docked windows into their region's slots, and ease `body-follow` windows
|
|
8
|
+
* (and body-locked regions) toward the viewer each frame.
|
|
9
|
+
*
|
|
10
|
+
* It implements the core's `WindowHost` (so app code can wire behaviour
|
|
11
|
+
* through `onPanelReady` with no engine knowledge) and `SceneTarget` (so a
|
|
12
|
+
* portable `SceneDescriptor` builds the same playground here as on IWSDK).
|
|
13
|
+
*/
|
|
14
|
+
import { Group, type Object3D } from 'three';
|
|
15
|
+
import type { Kit } from '@pmndrs/uikitml';
|
|
16
|
+
import { RegionRegistry, WindowManager, type DockModeValue, type HeadPoseSource, type PanelReadyEvent, type RegionDefinition, type SceneRegion, type SceneTarget, type SceneWindow, type Vec3Tuple, type WindowHost } from '@realitycollective/webxr-uiextensions';
|
|
17
|
+
import { UixPanelDocument } from './panel-document.js';
|
|
18
|
+
export interface CreateWindowOptions {
|
|
19
|
+
id: string;
|
|
20
|
+
/** Compiled UIKitML JSON (the `{ element, classes }` shape). */
|
|
21
|
+
config: unknown;
|
|
22
|
+
title?: string;
|
|
23
|
+
dockMode?: DockModeValue;
|
|
24
|
+
position?: Vec3Tuple;
|
|
25
|
+
maxWidth?: number;
|
|
26
|
+
maxHeight?: number;
|
|
27
|
+
closable?: boolean;
|
|
28
|
+
minimizable?: boolean;
|
|
29
|
+
pinnable?: boolean;
|
|
30
|
+
followOffset?: Vec3Tuple;
|
|
31
|
+
followSpeed?: number;
|
|
32
|
+
followTolerance?: number;
|
|
33
|
+
/** Dock straight into this region on spawn. */
|
|
34
|
+
region?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface CreateRegionOptions extends Partial<RegionDefinition> {
|
|
37
|
+
id: string;
|
|
38
|
+
position?: Vec3Tuple;
|
|
39
|
+
/** Body-lock the region so it follows the viewer. */
|
|
40
|
+
follow?: boolean;
|
|
41
|
+
followOffset?: Vec3Tuple;
|
|
42
|
+
}
|
|
43
|
+
export interface WindowHandle {
|
|
44
|
+
id: string;
|
|
45
|
+
/** The scene-graph node - position/rotate freely. */
|
|
46
|
+
group: Group;
|
|
47
|
+
document: UixPanelDocument;
|
|
48
|
+
}
|
|
49
|
+
export interface RegionHandle {
|
|
50
|
+
id: string;
|
|
51
|
+
group: Group;
|
|
52
|
+
}
|
|
53
|
+
export interface UixWindowHostOptions {
|
|
54
|
+
/** Parent for spawned windows (the scene, or any group inside it). */
|
|
55
|
+
scene: Object3D;
|
|
56
|
+
/** Viewer pose provider - camera on desktop, HMD pose in XR. */
|
|
57
|
+
headPose: HeadPoseSource;
|
|
58
|
+
/** Optional UIKitML component kit(s) (e.g. horizon kit). */
|
|
59
|
+
kit?: Kit;
|
|
60
|
+
/**
|
|
61
|
+
* Resolver for a window's `config` when it arrives as a string path (as
|
|
62
|
+
* portable `SceneDescriptor`s carry it). Defaults to `fetch(path).json()`.
|
|
63
|
+
*/
|
|
64
|
+
loadConfig?: (path: string) => Promise<unknown>;
|
|
65
|
+
}
|
|
66
|
+
export declare class UixWindowHost implements WindowHost, SceneTarget {
|
|
67
|
+
readonly manager: WindowManager;
|
|
68
|
+
readonly regions: RegionRegistry;
|
|
69
|
+
private readonly scene;
|
|
70
|
+
private readonly headPose;
|
|
71
|
+
private readonly kit;
|
|
72
|
+
private readonly loadConfig;
|
|
73
|
+
private readonly states;
|
|
74
|
+
private readonly regionStates;
|
|
75
|
+
private readonly readyListeners;
|
|
76
|
+
/** Panels already live - replayed to late `onPanelReady` subscribers. */
|
|
77
|
+
private readonly ready;
|
|
78
|
+
constructor(options: UixWindowHostOptions);
|
|
79
|
+
/** PanelHost - bare panel, unmanaged (used by devtools and tests). */
|
|
80
|
+
createPanel(configJson: unknown): UixPanelDocument;
|
|
81
|
+
/**
|
|
82
|
+
* Observe panel readiness. Windows already live are replayed immediately,
|
|
83
|
+
* so wiring code never races window creation.
|
|
84
|
+
*/
|
|
85
|
+
onPanelReady(listener: (event: PanelReadyEvent) => void): () => void;
|
|
86
|
+
spawnRegion(region: SceneRegion): void;
|
|
87
|
+
/**
|
|
88
|
+
* Scene descriptors carry config PATHS; this resolves the path and spawns
|
|
89
|
+
* the window when it arrives. Fire-and-forget by design - subscribe with
|
|
90
|
+
* {@link onPanelReady} to wire behaviour once the panel exists.
|
|
91
|
+
*/
|
|
92
|
+
spawnWindow(window: SceneWindow): void;
|
|
93
|
+
/** Create a dock region anchored in the scene. */
|
|
94
|
+
createRegion(options: CreateRegionOptions): RegionHandle;
|
|
95
|
+
region(id: string): RegionHandle | undefined;
|
|
96
|
+
/** Dock a window into a region (or undock it with `undefined`). */
|
|
97
|
+
dock(windowId: string, regionId: string | undefined): void;
|
|
98
|
+
/** Spawn a managed window from compiled UIKitML JSON. */
|
|
99
|
+
createWindow(options: CreateWindowOptions): WindowHandle;
|
|
100
|
+
window(id: string): WindowHandle | undefined;
|
|
101
|
+
/** Drive per-frame from the engine loop (delta in SECONDS). */
|
|
102
|
+
update(deltaSeconds: number): void;
|
|
103
|
+
private applyFollow;
|
|
104
|
+
/** Re-place every docked window into its region's slot. */
|
|
105
|
+
private layoutRegions;
|
|
106
|
+
private layoutRegion;
|
|
107
|
+
private wireChrome;
|
|
108
|
+
private setContentCollapsed;
|
|
109
|
+
/** MIN when open, MAX when minimized - the label names the next action. */
|
|
110
|
+
private syncMinimizeLabel;
|
|
111
|
+
private syncPinLabel;
|
|
112
|
+
}
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UixWindowHost - the engine binding for plain three.js / XR Blocks scenes.
|
|
3
|
+
*
|
|
4
|
+
* Owns a core `WindowManager` + `RegionRegistry` and applies their decisions
|
|
5
|
+
* to the scene graph: spawn UIKitML windows and dock regions, wire chrome
|
|
6
|
+
* buttons (focus / PIN / MIN / X), collapse content while minimized, place
|
|
7
|
+
* docked windows into their region's slots, and ease `body-follow` windows
|
|
8
|
+
* (and body-locked regions) toward the viewer each frame.
|
|
9
|
+
*
|
|
10
|
+
* It implements the core's `WindowHost` (so app code can wire behaviour
|
|
11
|
+
* through `onPanelReady` with no engine knowledge) and `SceneTarget` (so a
|
|
12
|
+
* portable `SceneDescriptor` builds the same playground here as on IWSDK).
|
|
13
|
+
*/
|
|
14
|
+
import { Group } from 'three';
|
|
15
|
+
import { DockMode, RegionRegistry, WINDOW_CHROME_IDS, WindowManager, minimizeLabelFor, pinLabelFor, slotOffset, } from '@realitycollective/webxr-uiextensions';
|
|
16
|
+
import { approach, approachAlpha, distanceSquared, followTarget, } from './follow-math.js';
|
|
17
|
+
import { UixPanelDocument } from './panel-document.js';
|
|
18
|
+
const DEFAULT_FOLLOW_OFFSET = [0, -0.15, -1.2];
|
|
19
|
+
const DEFAULT_REGION_FOLLOW_OFFSET = [0, -0.2, -1.4];
|
|
20
|
+
export class UixWindowHost {
|
|
21
|
+
manager = new WindowManager();
|
|
22
|
+
regions = new RegionRegistry();
|
|
23
|
+
scene;
|
|
24
|
+
headPose;
|
|
25
|
+
kit;
|
|
26
|
+
loadConfig;
|
|
27
|
+
states = new Map();
|
|
28
|
+
regionStates = new Map();
|
|
29
|
+
readyListeners = new Set();
|
|
30
|
+
/** Panels already live - replayed to late `onPanelReady` subscribers. */
|
|
31
|
+
ready = new Map();
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.scene = options.scene;
|
|
34
|
+
this.headPose = options.headPose;
|
|
35
|
+
this.kit = options.kit;
|
|
36
|
+
this.loadConfig =
|
|
37
|
+
options.loadConfig ??
|
|
38
|
+
((path) => fetch(path).then((response) => response.json()));
|
|
39
|
+
this.manager.events.on('closed', (record) => {
|
|
40
|
+
const state = this.states.get(record.id);
|
|
41
|
+
if (state) {
|
|
42
|
+
this.states.delete(record.id);
|
|
43
|
+
this.ready.delete(record.id);
|
|
44
|
+
this.regions.undock(record.id);
|
|
45
|
+
state.handle.document.dispose();
|
|
46
|
+
state.handle.group.removeFromParent();
|
|
47
|
+
this.layoutRegions();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
this.manager.events.on('minimized', (record) => {
|
|
51
|
+
this.setContentCollapsed(record.id, true);
|
|
52
|
+
this.syncMinimizeLabel(record.id);
|
|
53
|
+
});
|
|
54
|
+
this.manager.events.on('restored', (record) => {
|
|
55
|
+
this.setContentCollapsed(record.id, false);
|
|
56
|
+
this.syncMinimizeLabel(record.id);
|
|
57
|
+
});
|
|
58
|
+
this.manager.events.on('dockChanged', ({ window }) => {
|
|
59
|
+
this.syncPinLabel(window.id);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// --- WindowHost -----------------------------------------------------------
|
|
63
|
+
/** PanelHost - bare panel, unmanaged (used by devtools and tests). */
|
|
64
|
+
createPanel(configJson) {
|
|
65
|
+
return new UixPanelDocument(configJson, this.kit);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Observe panel readiness. Windows already live are replayed immediately,
|
|
69
|
+
* so wiring code never races window creation.
|
|
70
|
+
*/
|
|
71
|
+
onPanelReady(listener) {
|
|
72
|
+
this.readyListeners.add(listener);
|
|
73
|
+
for (const event of this.ready.values()) {
|
|
74
|
+
listener(event);
|
|
75
|
+
}
|
|
76
|
+
return () => void this.readyListeners.delete(listener);
|
|
77
|
+
}
|
|
78
|
+
// --- SceneTarget (portable scene descriptors) -----------------------------
|
|
79
|
+
spawnRegion(region) {
|
|
80
|
+
this.createRegion({
|
|
81
|
+
id: region.id,
|
|
82
|
+
...(region.flow !== undefined ? { flow: region.flow } : {}),
|
|
83
|
+
...(region.pitch !== undefined ? { pitch: region.pitch } : {}),
|
|
84
|
+
...(region.columns !== undefined ? { columns: region.columns } : {}),
|
|
85
|
+
...(region.capacity !== undefined ? { capacity: region.capacity } : {}),
|
|
86
|
+
...(region.snapRadius !== undefined ? { snapRadius: region.snapRadius } : {}),
|
|
87
|
+
...(region.position !== undefined
|
|
88
|
+
? { position: [...region.position] }
|
|
89
|
+
: {}),
|
|
90
|
+
...(region.follow !== undefined ? { follow: region.follow } : {}),
|
|
91
|
+
...(region.followOffset !== undefined
|
|
92
|
+
? { followOffset: [...region.followOffset] }
|
|
93
|
+
: {}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Scene descriptors carry config PATHS; this resolves the path and spawns
|
|
98
|
+
* the window when it arrives. Fire-and-forget by design - subscribe with
|
|
99
|
+
* {@link onPanelReady} to wire behaviour once the panel exists.
|
|
100
|
+
*/
|
|
101
|
+
spawnWindow(window) {
|
|
102
|
+
void this.loadConfig(window.config)
|
|
103
|
+
.then((config) => {
|
|
104
|
+
this.createWindow({
|
|
105
|
+
id: window.id,
|
|
106
|
+
config,
|
|
107
|
+
title: window.title,
|
|
108
|
+
...(window.position !== undefined
|
|
109
|
+
? { position: [...window.position] }
|
|
110
|
+
: {}),
|
|
111
|
+
...(window.maxWidth !== undefined ? { maxWidth: window.maxWidth } : {}),
|
|
112
|
+
...(window.maxHeight !== undefined ? { maxHeight: window.maxHeight } : {}),
|
|
113
|
+
...(window.dockMode !== undefined ? { dockMode: window.dockMode } : {}),
|
|
114
|
+
...(window.region !== undefined ? { region: window.region } : {}),
|
|
115
|
+
...(window.followOffset !== undefined
|
|
116
|
+
? { followOffset: [...window.followOffset] }
|
|
117
|
+
: {}),
|
|
118
|
+
...(window.followSpeed !== undefined
|
|
119
|
+
? { followSpeed: window.followSpeed }
|
|
120
|
+
: {}),
|
|
121
|
+
...(window.followTolerance !== undefined
|
|
122
|
+
? { followTolerance: window.followTolerance }
|
|
123
|
+
: {}),
|
|
124
|
+
...(window.closable !== undefined ? { closable: window.closable } : {}),
|
|
125
|
+
...(window.minimizable !== undefined
|
|
126
|
+
? { minimizable: window.minimizable }
|
|
127
|
+
: {}),
|
|
128
|
+
...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),
|
|
129
|
+
});
|
|
130
|
+
})
|
|
131
|
+
.catch((error) => {
|
|
132
|
+
console.error(`[uix] failed to load panel "${window.id}":`, error);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// --- Regions --------------------------------------------------------------
|
|
136
|
+
/** Create a dock region anchored in the scene. */
|
|
137
|
+
createRegion(options) {
|
|
138
|
+
const { id, position, follow, followOffset, ...definition } = options;
|
|
139
|
+
this.regions.register(id, definition);
|
|
140
|
+
const group = new Group();
|
|
141
|
+
group.name = `uix-region:${id}`;
|
|
142
|
+
if (position) {
|
|
143
|
+
group.position.set(...position);
|
|
144
|
+
}
|
|
145
|
+
this.scene.add(group);
|
|
146
|
+
const handle = { id, group };
|
|
147
|
+
this.regionStates.set(id, {
|
|
148
|
+
handle,
|
|
149
|
+
follow: follow ?? false,
|
|
150
|
+
followOffset: followOffset ?? DEFAULT_REGION_FOLLOW_OFFSET,
|
|
151
|
+
});
|
|
152
|
+
return handle;
|
|
153
|
+
}
|
|
154
|
+
region(id) {
|
|
155
|
+
return this.regionStates.get(id)?.handle;
|
|
156
|
+
}
|
|
157
|
+
/** Dock a window into a region (or undock it with `undefined`). */
|
|
158
|
+
dock(windowId, regionId) {
|
|
159
|
+
if (regionId === undefined) {
|
|
160
|
+
this.regions.undock(windowId);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
this.regions.dock(windowId, regionId);
|
|
164
|
+
this.manager.setDockMode(windowId, DockMode.WorldLocked);
|
|
165
|
+
}
|
|
166
|
+
this.layoutRegions();
|
|
167
|
+
}
|
|
168
|
+
// --- Windows --------------------------------------------------------------
|
|
169
|
+
/** Spawn a managed window from compiled UIKitML JSON. */
|
|
170
|
+
createWindow(options) {
|
|
171
|
+
const document = new UixPanelDocument(options.config, this.kit);
|
|
172
|
+
document.setTargetDimensions(options.maxWidth ?? 1, options.maxHeight ?? 1);
|
|
173
|
+
const group = new Group();
|
|
174
|
+
group.name = `uix-window:${options.id}`;
|
|
175
|
+
group.add(document);
|
|
176
|
+
if (options.position) {
|
|
177
|
+
group.position.set(...options.position);
|
|
178
|
+
}
|
|
179
|
+
this.scene.add(group);
|
|
180
|
+
const handle = { id: options.id, group, document };
|
|
181
|
+
this.states.set(options.id, {
|
|
182
|
+
handle,
|
|
183
|
+
options: {
|
|
184
|
+
followOffset: options.followOffset ?? DEFAULT_FOLLOW_OFFSET,
|
|
185
|
+
followSpeed: options.followSpeed ?? 3,
|
|
186
|
+
followTolerance: options.followTolerance ?? 0.35,
|
|
187
|
+
},
|
|
188
|
+
content: undefined,
|
|
189
|
+
pin: undefined,
|
|
190
|
+
minimize: undefined,
|
|
191
|
+
});
|
|
192
|
+
this.manager.open(options.id, {
|
|
193
|
+
title: options.title ?? '',
|
|
194
|
+
dockMode: options.dockMode ?? DockMode.WorldLocked,
|
|
195
|
+
});
|
|
196
|
+
this.wireChrome(options, handle);
|
|
197
|
+
if (options.region !== undefined) {
|
|
198
|
+
this.dock(options.id, options.region);
|
|
199
|
+
}
|
|
200
|
+
// The panel's element tree exists synchronously (uikitml interprets on
|
|
201
|
+
// construction); only its LAYOUT is async. Announce readiness now so
|
|
202
|
+
// wiring can attach handlers immediately.
|
|
203
|
+
const event = { id: options.id, panel: handle.document };
|
|
204
|
+
this.ready.set(options.id, event);
|
|
205
|
+
for (const listener of this.readyListeners) {
|
|
206
|
+
try {
|
|
207
|
+
listener(event);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
console.error(`[uix] panel-ready listener failed for "${options.id}":`, error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return handle;
|
|
214
|
+
}
|
|
215
|
+
window(id) {
|
|
216
|
+
return this.states.get(id)?.handle;
|
|
217
|
+
}
|
|
218
|
+
/** Drive per-frame from the engine loop (delta in SECONDS). */
|
|
219
|
+
update(deltaSeconds) {
|
|
220
|
+
for (const [id, state] of this.states) {
|
|
221
|
+
state.handle.document.update(deltaSeconds);
|
|
222
|
+
const record = this.manager.get(id);
|
|
223
|
+
const docked = this.regions.regionOf(id) !== undefined;
|
|
224
|
+
if (record &&
|
|
225
|
+
record.dockMode === DockMode.BodyFollow &&
|
|
226
|
+
!record.dragging &&
|
|
227
|
+
!docked) {
|
|
228
|
+
this.applyFollow(state.handle.group, state.options, deltaSeconds, true);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
for (const state of this.regionStates.values()) {
|
|
232
|
+
if (state.follow) {
|
|
233
|
+
this.applyFollow(state.handle.group, {
|
|
234
|
+
followOffset: state.followOffset,
|
|
235
|
+
followSpeed: 3,
|
|
236
|
+
followTolerance: 0.2,
|
|
237
|
+
}, deltaSeconds, false);
|
|
238
|
+
this.layoutRegion(state.handle.id);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
applyFollow(group, options, deltaSeconds, faceViewer) {
|
|
243
|
+
const pose = this.headPose.getHeadPose();
|
|
244
|
+
const target = followTarget(pose, options.followOffset);
|
|
245
|
+
const position = group.position;
|
|
246
|
+
const current = [position.x, position.y, position.z];
|
|
247
|
+
const tolerance = options.followTolerance;
|
|
248
|
+
if (distanceSquared(current, target) <= tolerance * tolerance) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const next = approach(current, target, approachAlpha(options.followSpeed, deltaSeconds));
|
|
252
|
+
position.set(...next);
|
|
253
|
+
if (faceViewer) {
|
|
254
|
+
group.lookAt(pose.position[0], position.y, pose.position[2]);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/** Re-place every docked window into its region's slot. */
|
|
258
|
+
layoutRegions() {
|
|
259
|
+
for (const id of this.regionStates.keys()) {
|
|
260
|
+
this.layoutRegion(id);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
layoutRegion(regionId) {
|
|
264
|
+
const regionState = this.regionStates.get(regionId);
|
|
265
|
+
const registered = this.regions.get(regionId);
|
|
266
|
+
if (!regionState || !registered) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const origin = regionState.handle.group.position;
|
|
270
|
+
registered.members.forEach((windowId, index) => {
|
|
271
|
+
const windowState = this.states.get(windowId);
|
|
272
|
+
if (!windowState) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const [dx, dy, dz] = slotOffset(registered.definition, index);
|
|
276
|
+
windowState.handle.group.position.set(origin.x + dx, origin.y + dy, origin.z + dz);
|
|
277
|
+
windowState.handle.group.quaternion.copy(regionState.handle.group.quaternion);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
wireChrome(options, handle) {
|
|
281
|
+
const element = (id) => handle.document.getElementById(id);
|
|
282
|
+
const id = options.id;
|
|
283
|
+
const title = element(WINDOW_CHROME_IDS.title);
|
|
284
|
+
if (title && options.title) {
|
|
285
|
+
title.setProperties({ text: options.title });
|
|
286
|
+
}
|
|
287
|
+
// Chrome presses must not bubble to the window root's focus handler:
|
|
288
|
+
// a press on CLOSE would otherwise focus a window that is about to be
|
|
289
|
+
// destroyed, and `focus()` on a removed window throws. (Same guard the
|
|
290
|
+
// IWSDK adapter applies.)
|
|
291
|
+
const swallowPress = (button) => {
|
|
292
|
+
button?.addEventListener('pointerdown', (event) => {
|
|
293
|
+
event?.stopPropagation?.();
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
swallowPress(element(WINDOW_CHROME_IDS.pin));
|
|
297
|
+
swallowPress(element(WINDOW_CHROME_IDS.dock));
|
|
298
|
+
swallowPress(element(WINDOW_CHROME_IDS.minimize));
|
|
299
|
+
swallowPress(element(WINDOW_CHROME_IDS.close));
|
|
300
|
+
// Any press anywhere on the window brings it to the front. Guarded:
|
|
301
|
+
// events can still arrive for a window that was just closed.
|
|
302
|
+
element(WINDOW_CHROME_IDS.window)?.addEventListener('pointerdown', () => {
|
|
303
|
+
if (this.manager.has(id)) {
|
|
304
|
+
this.manager.focus(id);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
const close = element(WINDOW_CHROME_IDS.close);
|
|
308
|
+
if (options.closable === false) {
|
|
309
|
+
close?.setProperties({ display: 'none' });
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
close?.addEventListener('click', () => {
|
|
313
|
+
if (this.manager.has(id)) {
|
|
314
|
+
this.manager.close(id);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const minimize = element(WINDOW_CHROME_IDS.minimize);
|
|
319
|
+
if (options.minimizable === false) {
|
|
320
|
+
minimize?.setProperties({ display: 'none' });
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
minimize?.addEventListener('click', () => {
|
|
324
|
+
if (this.manager.has(id)) {
|
|
325
|
+
this.manager.toggleMinimized(id);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
this.syncMinimizeLabel(id);
|
|
329
|
+
}
|
|
330
|
+
const pin = element(WINDOW_CHROME_IDS.pin);
|
|
331
|
+
const state = this.states.get(id);
|
|
332
|
+
if (state) {
|
|
333
|
+
state.content = element(WINDOW_CHROME_IDS.content);
|
|
334
|
+
state.pin = pin;
|
|
335
|
+
state.minimize = element(WINDOW_CHROME_IDS.minimize);
|
|
336
|
+
}
|
|
337
|
+
if (options.pinnable === false) {
|
|
338
|
+
pin?.setProperties({ display: 'none' });
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
pin?.addEventListener('click', () => {
|
|
342
|
+
if (!this.manager.has(id)) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// Pinning a docked window pops it out of its region first.
|
|
346
|
+
if (this.regions.regionOf(id) !== undefined) {
|
|
347
|
+
this.regions.undock(id);
|
|
348
|
+
}
|
|
349
|
+
this.manager.togglePin(id);
|
|
350
|
+
});
|
|
351
|
+
this.syncPinLabel(id);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
setContentCollapsed(id, collapsed) {
|
|
355
|
+
this.states
|
|
356
|
+
.get(id)
|
|
357
|
+
?.content?.setProperties({ display: collapsed ? 'none' : 'flex' });
|
|
358
|
+
}
|
|
359
|
+
/** MIN when open, MAX when minimized - the label names the next action. */
|
|
360
|
+
syncMinimizeLabel(id) {
|
|
361
|
+
const record = this.manager.get(id);
|
|
362
|
+
const minimize = this.states.get(id)?.minimize;
|
|
363
|
+
if (record && minimize) {
|
|
364
|
+
minimize.setProperties({ text: minimizeLabelFor(record) });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
syncPinLabel(id) {
|
|
368
|
+
const record = this.manager.get(id);
|
|
369
|
+
const pin = this.states.get(id)?.pin;
|
|
370
|
+
if (record && pin) {
|
|
371
|
+
pin.setProperties({ text: pinLabelFor(record) });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
//# sourceMappingURL=host.js.map
|
package/dist/host.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UixWindowHost - the engine binding for plain three.js / XR Blocks scenes.\n *\n * Owns a core `WindowManager` + `RegionRegistry` and applies their decisions\n * to the scene graph: spawn UIKitML windows and dock regions, wire chrome\n * buttons (focus / PIN / MIN / X), collapse content while minimized, place\n * docked windows into their region's slots, and ease `body-follow` windows\n * (and body-locked regions) toward the viewer each frame.\n *\n * It implements the core's `WindowHost` (so app code can wire behaviour\n * through `onPanelReady` with no engine knowledge) and `SceneTarget` (so a\n * portable `SceneDescriptor` builds the same playground here as on IWSDK).\n */\nimport { Group, type Object3D } from 'three';\nimport type { Kit } from '@pmndrs/uikitml';\nimport {\n DockMode,\n RegionRegistry,\n WINDOW_CHROME_IDS,\n WindowManager,\n minimizeLabelFor,\n pinLabelFor,\n slotOffset,\n type DockModeValue,\n type HeadPoseSource,\n type PanelReadyEvent,\n type RegionDefinition,\n type SceneRegion,\n type SceneTarget,\n type SceneWindow,\n type UixElement,\n type Vec3Tuple,\n type WindowHost,\n} from '@realitycollective/webxr-uiextensions';\nimport {\n approach,\n approachAlpha,\n distanceSquared,\n followTarget,\n} from './follow-math.js';\nimport { UixPanelDocument } from './panel-document.js';\n\nexport interface CreateWindowOptions {\n id: string;\n /** Compiled UIKitML JSON (the `{ element, classes }` shape). */\n config: unknown;\n title?: string;\n dockMode?: DockModeValue;\n position?: Vec3Tuple;\n maxWidth?: number;\n maxHeight?: number;\n closable?: boolean;\n minimizable?: boolean;\n pinnable?: boolean;\n followOffset?: Vec3Tuple;\n followSpeed?: number;\n followTolerance?: number;\n /** Dock straight into this region on spawn. */\n region?: string;\n}\n\nexport interface CreateRegionOptions extends Partial<RegionDefinition> {\n id: string;\n position?: Vec3Tuple;\n /** Body-lock the region so it follows the viewer. */\n follow?: boolean;\n followOffset?: Vec3Tuple;\n}\n\nexport interface WindowHandle {\n id: string;\n /** The scene-graph node - position/rotate freely. */\n group: Group;\n document: UixPanelDocument;\n}\n\nexport interface RegionHandle {\n id: string;\n group: Group;\n}\n\nexport interface UixWindowHostOptions {\n /** Parent for spawned windows (the scene, or any group inside it). */\n scene: Object3D;\n /** Viewer pose provider - camera on desktop, HMD pose in XR. */\n headPose: HeadPoseSource;\n /** Optional UIKitML component kit(s) (e.g. horizon kit). */\n kit?: Kit;\n /**\n * Resolver for a window's `config` when it arrives as a string path (as\n * portable `SceneDescriptor`s carry it). Defaults to `fetch(path).json()`.\n */\n loadConfig?: (path: string) => Promise<unknown>;\n}\n\ninterface FollowOptions {\n followOffset: Vec3Tuple;\n followSpeed: number;\n followTolerance: number;\n}\n\ninterface WindowState {\n handle: WindowHandle;\n options: FollowOptions;\n content: UixElement | undefined;\n pin: UixElement | undefined;\n minimize: UixElement | undefined;\n}\n\ninterface RegionState {\n handle: RegionHandle;\n follow: boolean;\n followOffset: Vec3Tuple;\n}\n\nconst DEFAULT_FOLLOW_OFFSET: Vec3Tuple = [0, -0.15, -1.2];\nconst DEFAULT_REGION_FOLLOW_OFFSET: Vec3Tuple = [0, -0.2, -1.4];\n\nexport class UixWindowHost implements WindowHost, SceneTarget {\n readonly manager = new WindowManager();\n readonly regions = new RegionRegistry();\n private readonly scene: Object3D;\n private readonly headPose: HeadPoseSource;\n private readonly kit: Kit | undefined;\n private readonly loadConfig: (path: string) => Promise<unknown>;\n private readonly states = new Map<string, WindowState>();\n private readonly regionStates = new Map<string, RegionState>();\n private readonly readyListeners = new Set<(event: PanelReadyEvent) => void>();\n /** Panels already live - replayed to late `onPanelReady` subscribers. */\n private readonly ready = new Map<string, PanelReadyEvent>();\n\n constructor(options: UixWindowHostOptions) {\n this.scene = options.scene;\n this.headPose = options.headPose;\n this.kit = options.kit;\n this.loadConfig =\n options.loadConfig ??\n ((path: string) => fetch(path).then((response) => response.json()));\n\n this.manager.events.on('closed', (record) => {\n const state = this.states.get(record.id);\n if (state) {\n this.states.delete(record.id);\n this.ready.delete(record.id);\n this.regions.undock(record.id);\n state.handle.document.dispose();\n state.handle.group.removeFromParent();\n this.layoutRegions();\n }\n });\n this.manager.events.on('minimized', (record) => {\n this.setContentCollapsed(record.id, true);\n this.syncMinimizeLabel(record.id);\n });\n this.manager.events.on('restored', (record) => {\n this.setContentCollapsed(record.id, false);\n this.syncMinimizeLabel(record.id);\n });\n this.manager.events.on('dockChanged', ({ window }) => {\n this.syncPinLabel(window.id);\n });\n }\n\n // --- WindowHost -----------------------------------------------------------\n\n /** PanelHost - bare panel, unmanaged (used by devtools and tests). */\n createPanel(configJson: unknown): UixPanelDocument {\n return new UixPanelDocument(configJson, this.kit);\n }\n\n /**\n * Observe panel readiness. Windows already live are replayed immediately,\n * so wiring code never races window creation.\n */\n onPanelReady(listener: (event: PanelReadyEvent) => void): () => void {\n this.readyListeners.add(listener);\n for (const event of this.ready.values()) {\n listener(event);\n }\n return () => void this.readyListeners.delete(listener);\n }\n\n // --- SceneTarget (portable scene descriptors) -----------------------------\n\n spawnRegion(region: SceneRegion): void {\n this.createRegion({\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 ? { snapRadius: region.snapRadius } : {}),\n ...(region.position !== undefined\n ? { position: [...region.position] as Vec3Tuple }\n : {}),\n ...(region.follow !== undefined ? { follow: region.follow } : {}),\n ...(region.followOffset !== undefined\n ? { followOffset: [...region.followOffset] as Vec3Tuple }\n : {}),\n });\n }\n\n /**\n * Scene descriptors carry config PATHS; this resolves the path and spawns\n * the window when it arrives. Fire-and-forget by design - subscribe with\n * {@link onPanelReady} to wire behaviour once the panel exists.\n */\n spawnWindow(window: SceneWindow): void {\n void this.loadConfig(window.config)\n .then((config) => {\n this.createWindow({\n id: window.id,\n config,\n title: window.title,\n ...(window.position !== undefined\n ? { position: [...window.position] as Vec3Tuple }\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 Vec3Tuple }\n : {}),\n ...(window.followSpeed !== undefined\n ? { followSpeed: window.followSpeed }\n : {}),\n ...(window.followTolerance !== undefined\n ? { followTolerance: window.followTolerance }\n : {}),\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 .catch((error) => {\n console.error(`[uix] failed to load panel \"${window.id}\":`, error);\n });\n }\n\n // --- Regions --------------------------------------------------------------\n\n /** Create a dock region anchored in the scene. */\n createRegion(options: CreateRegionOptions): RegionHandle {\n const { id, position, follow, followOffset, ...definition } = options;\n this.regions.register(id, definition);\n\n const group = new Group();\n group.name = `uix-region:${id}`;\n if (position) {\n group.position.set(...position);\n }\n this.scene.add(group);\n\n const handle: RegionHandle = { id, group };\n this.regionStates.set(id, {\n handle,\n follow: follow ?? false,\n followOffset: followOffset ?? DEFAULT_REGION_FOLLOW_OFFSET,\n });\n return handle;\n }\n\n region(id: string): RegionHandle | undefined {\n return this.regionStates.get(id)?.handle;\n }\n\n /** Dock a window into a region (or undock it with `undefined`). */\n dock(windowId: string, regionId: string | undefined): void {\n if (regionId === undefined) {\n this.regions.undock(windowId);\n } else {\n this.regions.dock(windowId, regionId);\n this.manager.setDockMode(windowId, DockMode.WorldLocked);\n }\n this.layoutRegions();\n }\n\n // --- Windows --------------------------------------------------------------\n\n /** Spawn a managed window from compiled UIKitML JSON. */\n createWindow(options: CreateWindowOptions): WindowHandle {\n const document = new UixPanelDocument(options.config, this.kit);\n document.setTargetDimensions(options.maxWidth ?? 1, options.maxHeight ?? 1);\n\n const group = new Group();\n group.name = `uix-window:${options.id}`;\n group.add(document);\n if (options.position) {\n group.position.set(...options.position);\n }\n this.scene.add(group);\n\n const handle: WindowHandle = { id: options.id, group, document };\n this.states.set(options.id, {\n handle,\n options: {\n followOffset: options.followOffset ?? DEFAULT_FOLLOW_OFFSET,\n followSpeed: options.followSpeed ?? 3,\n followTolerance: options.followTolerance ?? 0.35,\n },\n content: undefined,\n pin: undefined,\n minimize: undefined,\n });\n\n this.manager.open(options.id, {\n title: options.title ?? '',\n dockMode: options.dockMode ?? DockMode.WorldLocked,\n });\n this.wireChrome(options, handle);\n\n if (options.region !== undefined) {\n this.dock(options.id, options.region);\n }\n\n // The panel's element tree exists synchronously (uikitml interprets on\n // construction); only its LAYOUT is async. Announce readiness now so\n // wiring can attach handlers immediately.\n const event: PanelReadyEvent = { id: options.id, panel: handle.document };\n this.ready.set(options.id, event);\n for (const listener of this.readyListeners) {\n try {\n listener(event);\n } catch (error) {\n console.error(`[uix] panel-ready listener failed for \"${options.id}\":`, error);\n }\n }\n\n return handle;\n }\n\n window(id: string): WindowHandle | undefined {\n return this.states.get(id)?.handle;\n }\n\n /** Drive per-frame from the engine loop (delta in SECONDS). */\n update(deltaSeconds: number): void {\n for (const [id, state] of this.states) {\n state.handle.document.update(deltaSeconds);\n const record = this.manager.get(id);\n const docked = this.regions.regionOf(id) !== undefined;\n if (\n record &&\n record.dockMode === DockMode.BodyFollow &&\n !record.dragging &&\n !docked\n ) {\n this.applyFollow(state.handle.group, state.options, deltaSeconds, true);\n }\n }\n for (const state of this.regionStates.values()) {\n if (state.follow) {\n this.applyFollow(\n state.handle.group,\n {\n followOffset: state.followOffset,\n followSpeed: 3,\n followTolerance: 0.2,\n },\n deltaSeconds,\n false,\n );\n this.layoutRegion(state.handle.id);\n }\n }\n }\n\n private applyFollow(\n group: Group,\n options: FollowOptions,\n deltaSeconds: number,\n faceViewer: boolean,\n ): void {\n const pose = this.headPose.getHeadPose();\n const target = followTarget(pose, options.followOffset);\n const position = group.position;\n const current: Vec3Tuple = [position.x, position.y, position.z];\n const tolerance = options.followTolerance;\n if (distanceSquared(current, target) <= tolerance * tolerance) {\n return;\n }\n const next = approach(\n current,\n target,\n approachAlpha(options.followSpeed, deltaSeconds),\n );\n position.set(...next);\n if (faceViewer) {\n group.lookAt(pose.position[0], position.y, pose.position[2]);\n }\n }\n\n /** Re-place every docked window into its region's slot. */\n private layoutRegions(): void {\n for (const id of this.regionStates.keys()) {\n this.layoutRegion(id);\n }\n }\n\n private layoutRegion(regionId: string): void {\n const regionState = this.regionStates.get(regionId);\n const registered = this.regions.get(regionId);\n if (!regionState || !registered) {\n return;\n }\n const origin = regionState.handle.group.position;\n registered.members.forEach((windowId, index) => {\n const windowState = this.states.get(windowId);\n if (!windowState) {\n return;\n }\n const [dx, dy, dz] = slotOffset(registered.definition, index);\n windowState.handle.group.position.set(\n origin.x + dx,\n origin.y + dy,\n origin.z + dz,\n );\n windowState.handle.group.quaternion.copy(regionState.handle.group.quaternion);\n });\n }\n\n private wireChrome(options: CreateWindowOptions, handle: WindowHandle): void {\n const element = (id: string) => handle.document.getElementById(id);\n const id = options.id;\n\n const title = element(WINDOW_CHROME_IDS.title);\n if (title && options.title) {\n title.setProperties({ text: options.title });\n }\n\n // Chrome presses must not bubble to the window root's focus handler:\n // a press on CLOSE would otherwise focus a window that is about to be\n // destroyed, and `focus()` on a removed window throws. (Same guard the\n // IWSDK adapter applies.)\n const swallowPress = (button: UixElement | undefined): void => {\n button?.addEventListener('pointerdown', (event) => {\n (event as { stopPropagation?: () => void } | undefined)?.stopPropagation?.();\n });\n };\n swallowPress(element(WINDOW_CHROME_IDS.pin));\n swallowPress(element(WINDOW_CHROME_IDS.dock));\n swallowPress(element(WINDOW_CHROME_IDS.minimize));\n swallowPress(element(WINDOW_CHROME_IDS.close));\n\n // Any press anywhere on the window brings it to the front. Guarded:\n // events can still arrive for a window that was just closed.\n element(WINDOW_CHROME_IDS.window)?.addEventListener('pointerdown', () => {\n if (this.manager.has(id)) {\n this.manager.focus(id);\n }\n });\n\n const close = element(WINDOW_CHROME_IDS.close);\n if (options.closable === false) {\n close?.setProperties({ display: 'none' });\n } else {\n close?.addEventListener('click', () => {\n if (this.manager.has(id)) {\n this.manager.close(id);\n }\n });\n }\n\n const minimize = element(WINDOW_CHROME_IDS.minimize);\n if (options.minimizable === false) {\n minimize?.setProperties({ display: 'none' });\n } else {\n minimize?.addEventListener('click', () => {\n if (this.manager.has(id)) {\n this.manager.toggleMinimized(id);\n }\n });\n this.syncMinimizeLabel(id);\n }\n\n const pin = element(WINDOW_CHROME_IDS.pin);\n const state = this.states.get(id);\n if (state) {\n state.content = element(WINDOW_CHROME_IDS.content);\n state.pin = pin;\n state.minimize = element(WINDOW_CHROME_IDS.minimize);\n }\n if (options.pinnable === false) {\n pin?.setProperties({ display: 'none' });\n } else {\n pin?.addEventListener('click', () => {\n if (!this.manager.has(id)) {\n return;\n }\n // Pinning a docked window pops it out of its region first.\n if (this.regions.regionOf(id) !== undefined) {\n this.regions.undock(id);\n }\n this.manager.togglePin(id);\n });\n this.syncPinLabel(id);\n }\n }\n\n private setContentCollapsed(id: string, collapsed: boolean): void {\n this.states\n .get(id)\n ?.content?.setProperties({ display: collapsed ? 'none' : 'flex' });\n }\n\n /** MIN when open, MAX when minimized - the label names the next action. */\n private syncMinimizeLabel(id: string): void {\n const record = this.manager.get(id);\n const minimize = this.states.get(id)?.minimize;\n if (record && minimize) {\n minimize.setProperties({ text: minimizeLabelFor(record) });\n }\n }\n\n private syncPinLabel(id: string): void {\n const record = this.manager.get(id);\n const pin = this.states.get(id)?.pin;\n if (record && pin) {\n pin.setProperties({ text: pinLabelFor(record) });\n }\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @realitycollective/xrblocks-uiextensions - EXPERIMENTAL Google XR Blocks /
|
|
3
|
+
* plain three.js adapter for the Reality Collective UI Extensions.
|
|
4
|
+
*
|
|
5
|
+
* Binds the engine-free `@realitycollective/webxr-uiextensions` core to any
|
|
6
|
+
* three.js WebXR scene graph - which is exactly what an XR Blocks Script
|
|
7
|
+
* provides. See the README for the current feature matrix versus the IWSDK
|
|
8
|
+
* adapter.
|
|
9
|
+
*/
|
|
10
|
+
export * from '@realitycollective/webxr-uiextensions';
|
|
11
|
+
export * from './renderer-setup.js';
|
|
12
|
+
export * from './scale-math.js';
|
|
13
|
+
export * from './follow-math.js';
|
|
14
|
+
export * from './panel-document.js';
|
|
15
|
+
export * from './host.js';
|
|
16
|
+
export * from './pointer-forward.js';
|
|
17
|
+
export * from './xrblocks.js';
|
|
18
|
+
export * from './desktop-locomotion.js';
|
|
19
|
+
export * from './desktop-controls.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @realitycollective/xrblocks-uiextensions - EXPERIMENTAL Google XR Blocks /
|
|
3
|
+
* plain three.js adapter for the Reality Collective UI Extensions.
|
|
4
|
+
*
|
|
5
|
+
* Binds the engine-free `@realitycollective/webxr-uiextensions` core to any
|
|
6
|
+
* three.js WebXR scene graph - which is exactly what an XR Blocks Script
|
|
7
|
+
* provides. See the README for the current feature matrix versus the IWSDK
|
|
8
|
+
* adapter.
|
|
9
|
+
*/
|
|
10
|
+
// The portable core - re-exported for a single-dependency experience.
|
|
11
|
+
export * from '@realitycollective/webxr-uiextensions';
|
|
12
|
+
// three.js / XR Blocks binding
|
|
13
|
+
export * from './renderer-setup.js';
|
|
14
|
+
export * from './scale-math.js';
|
|
15
|
+
export * from './follow-math.js';
|
|
16
|
+
export * from './panel-document.js';
|
|
17
|
+
export * from './host.js';
|
|
18
|
+
export * from './pointer-forward.js';
|
|
19
|
+
export * from './xrblocks.js';
|
|
20
|
+
// Desktop (mouse + keyboard) support
|
|
21
|
+
export * from './desktop-locomotion.js';
|
|
22
|
+
export * from './desktop-controls.js';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,sEAAsE;AACtE,cAAc,uCAAuC,CAAC;AAEtD,+BAA+B;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAE9B,qCAAqC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC","sourcesContent":["/**\n * @realitycollective/xrblocks-uiextensions - EXPERIMENTAL Google XR Blocks /\n * plain three.js adapter for the Reality Collective UI Extensions.\n *\n * Binds the engine-free `@realitycollective/webxr-uiextensions` core to any\n * three.js WebXR scene graph - which is exactly what an XR Blocks Script\n * provides. See the README for the current feature matrix versus the IWSDK\n * adapter.\n */\n// The portable core - re-exported for a single-dependency experience.\nexport * from '@realitycollective/webxr-uiextensions';\n\n// three.js / XR Blocks binding\nexport * from './renderer-setup.js';\nexport * from './scale-math.js';\nexport * from './follow-math.js';\nexport * from './panel-document.js';\nexport * from './host.js';\nexport * from './pointer-forward.js';\nexport * from './xrblocks.js';\n\n// Desktop (mouse + keyboard) support\nexport * from './desktop-locomotion.js';\nexport * from './desktop-controls.js';\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UixPanelDocument - minimal spatial hosting for interpreted UIKitML on
|
|
3
|
+
* plain three.js (the XR Blocks scene graph is plain three.js).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the behaviour of IWSDK's UIKitDocument where it matters to the
|
|
6
|
+
* core: a `Group` wrapping the interpreted component tree, per-frame
|
|
7
|
+
* `update`, scale-to-fit via {@link fitScale}, and `getElementById` backed
|
|
8
|
+
* by the components' `userData.id` (where UIKitML places markup ids -
|
|
9
|
+
* the same `userData` surface the core's `UixElement` contract reads).
|
|
10
|
+
*/
|
|
11
|
+
import { Group, type Object3D } from 'three';
|
|
12
|
+
import { type Kit } from '@pmndrs/uikitml';
|
|
13
|
+
import type { PanelHandle, UixElement } from '@realitycollective/webxr-uiextensions';
|
|
14
|
+
/** The slice of a uikit component this adapter relies on. */
|
|
15
|
+
interface UikitComponentLike extends Object3D {
|
|
16
|
+
update?(deltaMs: number): void;
|
|
17
|
+
size?: {
|
|
18
|
+
value: unknown;
|
|
19
|
+
subscribe?(listener: () => void): () => void;
|
|
20
|
+
};
|
|
21
|
+
dispose?(): void;
|
|
22
|
+
}
|
|
23
|
+
export declare class UixPanelDocument extends Group implements PanelHandle {
|
|
24
|
+
readonly rootComponent: UikitComponentLike;
|
|
25
|
+
private targetWidth;
|
|
26
|
+
private targetHeight;
|
|
27
|
+
private unsubscribeSize;
|
|
28
|
+
constructor(configJson: unknown, kit?: Kit);
|
|
29
|
+
get root(): UixElement;
|
|
30
|
+
getElementById(id: string): UixElement | undefined;
|
|
31
|
+
setTargetDimensions(width: number, height: number): void;
|
|
32
|
+
/** Drive per-frame - uikit expects milliseconds. */
|
|
33
|
+
update(deltaSeconds: number): void;
|
|
34
|
+
dispose(): void;
|
|
35
|
+
private applyScale;
|
|
36
|
+
}
|
|
37
|
+
export {};
|