@realitycollective/iwsdk-uiextensions 0.1.0-preview.4

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/Examples/README.md +13 -0
  3. package/Examples/basic-window/main.ts +32 -0
  4. package/Examples/basic-window/window.uikitml +58 -0
  5. package/Examples/controls/controls.uikitml +49 -0
  6. package/Examples/controls/main.ts +48 -0
  7. package/Examples/dock-regions/main.ts +55 -0
  8. package/LICENSE +21 -0
  9. package/README.md +155 -0
  10. package/dist/component-set.d.ts +28 -0
  11. package/dist/component-set.js +108 -0
  12. package/dist/component-set.js.map +1 -0
  13. package/dist/components.d.ts +189 -0
  14. package/dist/components.js +97 -0
  15. package/dist/components.js.map +1 -0
  16. package/dist/factory.d.ts +44 -0
  17. package/dist/factory.js +63 -0
  18. package/dist/factory.js.map +1 -0
  19. package/dist/index.d.ts +25 -0
  20. package/dist/index.js +28 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/manager-registry.d.ts +8 -0
  23. package/dist/manager-registry.js +11 -0
  24. package/dist/manager-registry.js.map +1 -0
  25. package/dist/region-registry-store.d.ts +7 -0
  26. package/dist/region-registry-store.js +11 -0
  27. package/dist/region-registry-store.js.map +1 -0
  28. package/dist/register.d.ts +19 -0
  29. package/dist/register.js +20 -0
  30. package/dist/register.js.map +1 -0
  31. package/dist/scene-host.d.ts +44 -0
  32. package/dist/scene-host.js +190 -0
  33. package/dist/scene-host.js.map +1 -0
  34. package/dist/systems/controls-system.d.ts +24 -0
  35. package/dist/systems/controls-system.js +24 -0
  36. package/dist/systems/controls-system.js.map +1 -0
  37. package/dist/systems/dock-region-system.d.ts +230 -0
  38. package/dist/systems/dock-region-system.js +102 -0
  39. package/dist/systems/dock-region-system.js.map +1 -0
  40. package/dist/systems/dock-system.d.ts +205 -0
  41. package/dist/systems/dock-system.js +135 -0
  42. package/dist/systems/dock-system.js.map +1 -0
  43. package/dist/systems/drag-system.d.ts +280 -0
  44. package/dist/systems/drag-system.js +167 -0
  45. package/dist/systems/drag-system.js.map +1 -0
  46. package/dist/systems/window-system.d.ts +439 -0
  47. package/dist/systems/window-system.js +244 -0
  48. package/dist/systems/window-system.js.map +1 -0
  49. package/package.json +67 -0
@@ -0,0 +1,190 @@
1
+ /**
2
+ * IWSDK implementation of the core's `WindowHost` + `SceneTarget`.
3
+ *
4
+ * This is what makes a portable `SceneDescriptor` build the SAME playground
5
+ * on IWSDK as on plain three.js, and lets app behaviour be wired through
6
+ * `onPanelReady` instead of hand-written ECS queries:
7
+ *
8
+ * const host = createSceneHost(world);
9
+ * applyScene(host, PLAYGROUND);
10
+ * host.onPanelReady(({ id, panel }) => wireMyPanel(id, panel));
11
+ *
12
+ * Readiness is detected with a single ECS query on `PanelUI + PanelDocument`
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.
17
+ */
18
+ import { PanelDocument, PanelUI, createSystem, } from '@iwsdk/core';
19
+ import { upgradePanel, } from '@realitycollective/webxr-uiextensions';
20
+ import { createDockRegion, createUIWindow, } from './factory.js';
21
+ import { UIWindow } from './components.js';
22
+ /** Panel handle over an IWSDK `UIKitDocument`. */
23
+ function panelHandleFor(document) {
24
+ return {
25
+ get root() {
26
+ return document.rootElement;
27
+ },
28
+ getElementById: (id) => document.getElementById(id) ?? undefined,
29
+ setTargetDimensions: (width, height) => document.setTargetDimensions(width, height),
30
+ dispose: () => {
31
+ /* IWSDK owns the document lifecycle via the entity. */
32
+ },
33
+ };
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
+ }
44
+ /**
45
+ * Bridges panel loading into `onPanelReady`. Registered once per host; the
46
+ * query fires as each panel's document is attached by IWSDK's UI system.
47
+ */
48
+ function createReadySystem(onReady) {
49
+ return class UixPanelReadySystem extends createSystem({
50
+ panels: { required: [PanelUI, PanelDocument] },
51
+ }) {
52
+ init() {
53
+ this.cleanupFuncs.push(this.queries.panels.subscribe('qualify', (entity) => onReady(entity)));
54
+ this.queries.panels.entities.forEach((entity) => onReady(entity));
55
+ }
56
+ };
57
+ }
58
+ /**
59
+ * Create the host for a world. Call AFTER `registerUIExtensions(world)`.
60
+ */
61
+ export function createSceneHost(world) {
62
+ const readyListeners = new Set();
63
+ const ready = new Map();
64
+ const seen = new WeakSet();
65
+ const announce = (entity) => {
66
+ const document = PanelDocument.data.document[entity.index];
67
+ if (!document || seen.has(document)) {
68
+ return;
69
+ }
70
+ seen.add(document);
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)
75
+ ? UIWindow.data.windowId[entity.index]
76
+ : '';
77
+ const kind = windowId ? 'window' : 'panel';
78
+ const id = windowId || PanelUI.data.config[entity.index];
79
+ const panel = panelHandleFor(document);
80
+ // Upgrade `data-uix` controls exactly as the ECS controls system does,
81
+ // so wiring code sees the same handles on every adapter (idempotent).
82
+ upgradePanel(document, document.rootElement);
83
+ const event = { id, panel, kind };
84
+ ready.set(id, event);
85
+ for (const listener of readyListeners) {
86
+ try {
87
+ listener(event);
88
+ }
89
+ catch (error) {
90
+ console.error(`[uix] panel-ready listener failed for "${id}":`, error);
91
+ }
92
+ }
93
+ };
94
+ world.registerSystem(createReadySystem(announce));
95
+ let windowSequence = 0;
96
+ const host = {
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,
101
+ createPanel() {
102
+ throw new Error('[uix] createPanel() is not supported on the IWSDK host - spawn a window ' +
103
+ '(spawnWindow/createUIWindow) so IWSDK owns the panel lifecycle.');
104
+ },
105
+ onPanelReady(listener) {
106
+ readyListeners.add(listener);
107
+ for (const event of ready.values()) {
108
+ listener(event);
109
+ }
110
+ return () => void readyListeners.delete(listener);
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
+ },
139
+ spawnRegion(region) {
140
+ createDockRegion(world, {
141
+ id: region.id,
142
+ ...(region.flow !== undefined ? { flow: region.flow } : {}),
143
+ ...(region.pitch !== undefined ? { pitch: region.pitch } : {}),
144
+ ...(region.columns !== undefined ? { columns: region.columns } : {}),
145
+ ...(region.capacity !== undefined ? { capacity: region.capacity } : {}),
146
+ ...(region.snapRadius !== undefined
147
+ ? { snapRadius: region.snapRadius }
148
+ : {}),
149
+ ...(region.position !== undefined
150
+ ? { position: [...region.position] }
151
+ : {}),
152
+ ...(region.follow !== undefined ? { follow: region.follow } : {}),
153
+ ...(region.followOffset !== undefined
154
+ ? { followOffset: [...region.followOffset] }
155
+ : {}),
156
+ });
157
+ },
158
+ spawnWindow(window) {
159
+ host.createWindow({
160
+ id: window.id,
161
+ title: window.title,
162
+ config: window.config,
163
+ ...(window.position !== undefined
164
+ ? { position: [...window.position] }
165
+ : {}),
166
+ ...(window.maxWidth !== undefined ? { maxWidth: window.maxWidth } : {}),
167
+ ...(window.maxHeight !== undefined ? { maxHeight: window.maxHeight } : {}),
168
+ ...(window.dockMode !== undefined ? { dockMode: window.dockMode } : {}),
169
+ ...(window.region !== undefined ? { region: window.region } : {}),
170
+ ...(window.followOffset !== undefined
171
+ ? { followOffset: [...window.followOffset] }
172
+ : {}),
173
+ ...(window.followSpeed !== undefined
174
+ ? { followSpeed: window.followSpeed }
175
+ : {}),
176
+ ...(window.followTolerance !== undefined
177
+ ? { followTolerance: window.followTolerance }
178
+ : {}),
179
+ ...(window.movable !== undefined ? { movable: window.movable } : {}),
180
+ ...(window.closable !== undefined ? { closable: window.closable } : {}),
181
+ ...(window.minimizable !== undefined
182
+ ? { minimizable: window.minimizable }
183
+ : {}),
184
+ ...(window.pinnable !== undefined ? { pinnable: window.pinnable } : {}),
185
+ });
186
+ },
187
+ };
188
+ return host;
189
+ }
190
+ //# sourceMappingURL=scene-host.js.map
@@ -0,0 +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 * 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"]}
@@ -0,0 +1,24 @@
1
+ declare const UIControlsSystem_base: import("elics").SystemConstructor<import("elics").SystemSchema, {
2
+ panels: {
3
+ required: import("elics").Component<{
4
+ document: {
5
+ type: import("@iwsdk/core").Types.Object;
6
+ default: undefined;
7
+ };
8
+ }>[];
9
+ };
10
+ }, World, import("@iwsdk/core").System<import("elics").SystemSchema, {
11
+ panels: {
12
+ required: import("elics").Component<{
13
+ document: {
14
+ type: import("@iwsdk/core").Types.Object;
15
+ default: undefined;
16
+ };
17
+ }>[];
18
+ };
19
+ }>>;
20
+ export declare class UIControlsSystem extends UIControlsSystem_base {
21
+ init(): void;
22
+ private upgrade;
23
+ }
24
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * UIControlsSystem - upgrades `data-uix` controls in every loaded panel.
3
+ *
4
+ * Runs against ALL `PanelDocument` panels, not just windows: any IWSDK panel
5
+ * gains steppers, toggles, expandable labels and log views by markup alone.
6
+ */
7
+ import { PanelDocument, createSystem } from '@iwsdk/core';
8
+ import { upgradePanel } from '@realitycollective/webxr-uiextensions';
9
+ export class UIControlsSystem extends createSystem({
10
+ panels: { required: [PanelDocument] },
11
+ }) {
12
+ init() {
13
+ this.cleanupFuncs.push(this.queries.panels.subscribe('qualify', (entity) => this.upgrade(entity)));
14
+ this.queries.panels.entities.forEach((entity) => this.upgrade(entity));
15
+ }
16
+ upgrade(entity) {
17
+ const document = PanelDocument.data.document[entity.index];
18
+ if (!document?.rootElement) {
19
+ return;
20
+ }
21
+ upgradePanel(document, document.rootElement);
22
+ }
23
+ }
24
+ //# sourceMappingURL=controls-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controls-system.js","sourceRoot":"","sources":["../../src/systems/controls-system.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAiB,YAAY,EAAe,MAAM,aAAa,CAAC;AAEtF,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAErE,MAAM,OAAO,gBAAiB,SAAQ,YAAY,CAAC;IACjD,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,aAAa,CAAC,EAAE;CACtC,CAAC;IACS,IAAI;QACX,IAAI,CAAC,YAAY,CAAC,IAAI,CACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAC3E,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,CAAC;IAEO,OAAO,CAAC,MAAc;QAC5B,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAE5C,CAAC;QACd,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;YAC3B,OAAO;QACT,CAAC;QACD,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,WAAoC,CAAC,CAAC;IACxE,CAAC;CACF","sourcesContent":["/**\n * UIControlsSystem - upgrades `data-uix` controls in every loaded panel.\n *\n * Runs against ALL `PanelDocument` panels, not just windows: any IWSDK panel\n * gains steppers, toggles, expandable labels and log views by markup alone.\n */\nimport { PanelDocument, UIKitDocument, createSystem, type Entity } from '@iwsdk/core';\nimport type { UixElement } from '@realitycollective/webxr-uiextensions';\nimport { upgradePanel } from '@realitycollective/webxr-uiextensions';\n\nexport class UIControlsSystem extends createSystem({\n panels: { required: [PanelDocument] },\n}) {\n override init(): void {\n this.cleanupFuncs.push(\n this.queries.panels.subscribe('qualify', (entity) => this.upgrade(entity)),\n );\n this.queries.panels.entities.forEach((entity) => this.upgrade(entity));\n }\n\n private upgrade(entity: Entity): void {\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n if (!document?.rootElement) {\n return;\n }\n upgradePanel(document, document.rootElement as unknown as UixElement);\n }\n}\n"]}
@@ -0,0 +1,230 @@
1
+ declare const UIDockRegionSystem_base: import("elics").SystemConstructor<import("elics").SystemSchema, {
2
+ regions: {
3
+ required: import("elics").Component<{
4
+ regionId: {
5
+ type: import("@iwsdk/core").Types.String;
6
+ default: string;
7
+ };
8
+ flow: {
9
+ type: import("@iwsdk/core").Types.Enum;
10
+ enum: {
11
+ readonly Row: "row";
12
+ readonly Column: "column";
13
+ readonly Grid: "grid";
14
+ };
15
+ default: "column";
16
+ };
17
+ pitch: {
18
+ type: import("@iwsdk/core").Types.Float32;
19
+ default: number;
20
+ };
21
+ columns: {
22
+ type: import("@iwsdk/core").Types.Float32;
23
+ default: number;
24
+ };
25
+ capacity: {
26
+ type: import("@iwsdk/core").Types.Float32;
27
+ default: number;
28
+ };
29
+ snapRadius: {
30
+ type: import("@iwsdk/core").Types.Float32;
31
+ default: number;
32
+ };
33
+ }>[];
34
+ };
35
+ docked: {
36
+ required: (import("elics").Component<{
37
+ windowId: {
38
+ type: import("@iwsdk/core").Types.String;
39
+ default: string;
40
+ };
41
+ title: {
42
+ type: import("@iwsdk/core").Types.String;
43
+ default: string;
44
+ };
45
+ dockMode: {
46
+ type: import("@iwsdk/core").Types.Enum;
47
+ enum: {
48
+ readonly WorldLocked: "world-locked";
49
+ readonly BodyFollow: "body-follow";
50
+ readonly HeadLocked: "head-locked";
51
+ };
52
+ default: "world-locked";
53
+ };
54
+ movable: {
55
+ type: import("@iwsdk/core").Types.Boolean;
56
+ default: true;
57
+ };
58
+ closable: {
59
+ type: import("@iwsdk/core").Types.Boolean;
60
+ default: true;
61
+ };
62
+ minimizable: {
63
+ type: import("@iwsdk/core").Types.Boolean;
64
+ default: true;
65
+ };
66
+ pinnable: {
67
+ type: import("@iwsdk/core").Types.Boolean;
68
+ default: true;
69
+ };
70
+ billboardWhileDragging: {
71
+ type: import("@iwsdk/core").Types.Boolean;
72
+ default: true;
73
+ };
74
+ dragDelay: {
75
+ type: import("@iwsdk/core").Types.Float32;
76
+ default: number;
77
+ };
78
+ followOffset: {
79
+ type: import("@iwsdk/core").Types.Vec3;
80
+ default: [number, number, number];
81
+ };
82
+ followSpeed: {
83
+ type: import("@iwsdk/core").Types.Float32;
84
+ default: number;
85
+ };
86
+ followTolerance: {
87
+ type: import("@iwsdk/core").Types.Float32;
88
+ default: number;
89
+ };
90
+ focusBias: {
91
+ type: import("@iwsdk/core").Types.Float32;
92
+ default: number;
93
+ };
94
+ targetWidth: {
95
+ type: import("@iwsdk/core").Types.Float32;
96
+ default: number;
97
+ };
98
+ targetHeight: {
99
+ type: import("@iwsdk/core").Types.Float32;
100
+ default: number;
101
+ };
102
+ }> | import("elics").Component<{
103
+ regionId: {
104
+ type: import("@iwsdk/core").Types.String;
105
+ default: string;
106
+ };
107
+ }>)[];
108
+ };
109
+ }, World, import("@iwsdk/core").System<import("elics").SystemSchema, {
110
+ regions: {
111
+ required: import("elics").Component<{
112
+ regionId: {
113
+ type: import("@iwsdk/core").Types.String;
114
+ default: string;
115
+ };
116
+ flow: {
117
+ type: import("@iwsdk/core").Types.Enum;
118
+ enum: {
119
+ readonly Row: "row";
120
+ readonly Column: "column";
121
+ readonly Grid: "grid";
122
+ };
123
+ default: "column";
124
+ };
125
+ pitch: {
126
+ type: import("@iwsdk/core").Types.Float32;
127
+ default: number;
128
+ };
129
+ columns: {
130
+ type: import("@iwsdk/core").Types.Float32;
131
+ default: number;
132
+ };
133
+ capacity: {
134
+ type: import("@iwsdk/core").Types.Float32;
135
+ default: number;
136
+ };
137
+ snapRadius: {
138
+ type: import("@iwsdk/core").Types.Float32;
139
+ default: number;
140
+ };
141
+ }>[];
142
+ };
143
+ docked: {
144
+ required: (import("elics").Component<{
145
+ windowId: {
146
+ type: import("@iwsdk/core").Types.String;
147
+ default: string;
148
+ };
149
+ title: {
150
+ type: import("@iwsdk/core").Types.String;
151
+ default: string;
152
+ };
153
+ dockMode: {
154
+ type: import("@iwsdk/core").Types.Enum;
155
+ enum: {
156
+ readonly WorldLocked: "world-locked";
157
+ readonly BodyFollow: "body-follow";
158
+ readonly HeadLocked: "head-locked";
159
+ };
160
+ default: "world-locked";
161
+ };
162
+ movable: {
163
+ type: import("@iwsdk/core").Types.Boolean;
164
+ default: true;
165
+ };
166
+ closable: {
167
+ type: import("@iwsdk/core").Types.Boolean;
168
+ default: true;
169
+ };
170
+ minimizable: {
171
+ type: import("@iwsdk/core").Types.Boolean;
172
+ default: true;
173
+ };
174
+ pinnable: {
175
+ type: import("@iwsdk/core").Types.Boolean;
176
+ default: true;
177
+ };
178
+ billboardWhileDragging: {
179
+ type: import("@iwsdk/core").Types.Boolean;
180
+ default: true;
181
+ };
182
+ dragDelay: {
183
+ type: import("@iwsdk/core").Types.Float32;
184
+ default: number;
185
+ };
186
+ followOffset: {
187
+ type: import("@iwsdk/core").Types.Vec3;
188
+ default: [number, number, number];
189
+ };
190
+ followSpeed: {
191
+ type: import("@iwsdk/core").Types.Float32;
192
+ default: number;
193
+ };
194
+ followTolerance: {
195
+ type: import("@iwsdk/core").Types.Float32;
196
+ default: number;
197
+ };
198
+ focusBias: {
199
+ type: import("@iwsdk/core").Types.Float32;
200
+ default: number;
201
+ };
202
+ targetWidth: {
203
+ type: import("@iwsdk/core").Types.Float32;
204
+ default: number;
205
+ };
206
+ targetHeight: {
207
+ type: import("@iwsdk/core").Types.Float32;
208
+ default: number;
209
+ };
210
+ }> | import("elics").Component<{
211
+ regionId: {
212
+ type: import("@iwsdk/core").Types.String;
213
+ default: string;
214
+ };
215
+ }>)[];
216
+ };
217
+ }>>;
218
+ export declare class UIDockRegionSystem extends UIDockRegionSystem_base {
219
+ private registry;
220
+ private regionEntities;
221
+ private slotPosition;
222
+ private regionQuaternion;
223
+ init(): void;
224
+ update(): void;
225
+ private addRegion;
226
+ private removeRegion;
227
+ private dockWindow;
228
+ private undockWindow;
229
+ }
230
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * UIDockRegionSystem - keeps the region registry in sync with `UIDockRegion`
3
+ * entities and lays docked windows out in their region's slots each frame.
4
+ *
5
+ * Regions are ordinary entities: give one a `Follower` and its docked
6
+ * windows become a body-locked toolbar; leave it static for a world-locked
7
+ * console wall.
8
+ */
9
+ import { Quaternion, Vector3, createSystem } from '@iwsdk/core';
10
+ import { UIDockRegion, UIDockedTo, UIWindow } from '../components.js';
11
+ import { DockMode } from '@realitycollective/webxr-uiextensions';
12
+ import { regionRegistryFor } from '../region-registry-store.js';
13
+ let autoRegionId = 0;
14
+ export class UIDockRegionSystem extends createSystem({
15
+ regions: { required: [UIDockRegion] },
16
+ docked: { required: [UIWindow, UIDockedTo] },
17
+ }) {
18
+ registry;
19
+ regionEntities = new Map();
20
+ slotPosition = new Vector3();
21
+ regionQuaternion = new Quaternion();
22
+ init() {
23
+ this.registry = regionRegistryFor(this.world);
24
+ this.cleanupFuncs.push(this.queries.regions.subscribe('qualify', (entity) => this.addRegion(entity)), this.queries.regions.subscribe('disqualify', (entity) => this.removeRegion(entity)), this.queries.docked.subscribe('qualify', (entity) => this.dockWindow(entity)), this.queries.docked.subscribe('disqualify', (entity) => this.undockWindow(entity)));
25
+ this.queries.regions.entities.forEach((entity) => this.addRegion(entity));
26
+ this.queries.docked.entities.forEach((entity) => this.dockWindow(entity));
27
+ }
28
+ update() {
29
+ for (const entity of this.queries.docked.entities) {
30
+ const windowId = entity.getValue(UIWindow, 'windowId');
31
+ const offset = this.registry.offsetOf(windowId);
32
+ const regionId = this.registry.regionOf(windowId);
33
+ const regionEntity = regionId === undefined ? undefined : this.regionEntities.get(regionId);
34
+ const regionObject = regionEntity?.object3D;
35
+ const object = entity.object3D;
36
+ if (!offset || !regionObject || !object || !object.parent) {
37
+ continue;
38
+ }
39
+ // Slot offset is region-local; express it in the window's parent space.
40
+ this.slotPosition.set(offset[0], offset[1], offset[2]);
41
+ regionObject.localToWorld(this.slotPosition);
42
+ object.parent.worldToLocal(this.slotPosition);
43
+ object.position.copy(this.slotPosition);
44
+ regionObject.getWorldQuaternion(this.regionQuaternion);
45
+ object.quaternion.copy(this.regionQuaternion);
46
+ }
47
+ }
48
+ addRegion(entity) {
49
+ let id = entity.getValue(UIDockRegion, 'regionId');
50
+ if (!id) {
51
+ id = `uix-region-${autoRegionId++}`;
52
+ entity.setValue(UIDockRegion, 'regionId', id);
53
+ }
54
+ if (this.registry.get(id)) {
55
+ return;
56
+ }
57
+ const definition = {
58
+ flow: entity.getValue(UIDockRegion, 'flow'),
59
+ pitch: entity.getValue(UIDockRegion, 'pitch'),
60
+ columns: Math.max(1, Math.round(entity.getValue(UIDockRegion, 'columns'))),
61
+ capacity: Math.max(0, Math.round(entity.getValue(UIDockRegion, 'capacity'))),
62
+ snapRadius: entity.getValue(UIDockRegion, 'snapRadius'),
63
+ };
64
+ this.registry.register(id, definition);
65
+ this.regionEntities.set(id, entity);
66
+ }
67
+ removeRegion(entity) {
68
+ const id = entity.getValue(UIDockRegion, 'regionId');
69
+ if (id && this.regionEntities.get(id) === entity) {
70
+ this.registry.unregister(id);
71
+ this.regionEntities.delete(id);
72
+ }
73
+ }
74
+ dockWindow(entity) {
75
+ const windowId = entity.getValue(UIWindow, 'windowId');
76
+ const regionId = entity.getValue(UIDockedTo, 'regionId');
77
+ if (!windowId || !regionId || !this.registry.get(regionId)) {
78
+ // Unknown region (or the window has no id yet) - docking is meaningless.
79
+ entity.removeComponent(UIDockedTo);
80
+ return;
81
+ }
82
+ try {
83
+ this.registry.dock(windowId, regionId);
84
+ }
85
+ catch {
86
+ // Region is full: reject the dock.
87
+ entity.removeComponent(UIDockedTo);
88
+ return;
89
+ }
90
+ // Docked windows are world-locked by definition (the region may follow).
91
+ if (entity.getValue(UIWindow, 'dockMode') !== DockMode.WorldLocked) {
92
+ entity.setValue(UIWindow, 'dockMode', DockMode.WorldLocked);
93
+ }
94
+ }
95
+ undockWindow(entity) {
96
+ const windowId = entity.getValue(UIWindow, 'windowId');
97
+ if (windowId) {
98
+ this.registry.undock(windowId);
99
+ }
100
+ }
101
+ }
102
+ //# sourceMappingURL=dock-region-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dock-region-system.js","sourceRoot":"","sources":["../../src/systems/dock-region-system.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UIDockRegionSystem - keeps the region registry in sync with `UIDockRegion`\n * entities and lays docked windows out in their region's slots each frame.\n *\n * Regions are ordinary entities: give one a `Follower` and its docked\n * windows become a body-locked toolbar; leave it static for a world-locked\n * console wall.\n */\nimport { Quaternion, Vector3, createSystem, type Entity } from '@iwsdk/core';\nimport { UIDockRegion, UIDockedTo, UIWindow } from '../components.js';\nimport { DockMode } from '@realitycollective/webxr-uiextensions';\nimport type { RegionDefinition, RegionFlow } from '@realitycollective/webxr-uiextensions';\nimport { regionRegistryFor } from '../region-registry-store.js';\nimport type { RegionRegistry } from '@realitycollective/webxr-uiextensions';\n\nlet autoRegionId = 0;\n\nexport class UIDockRegionSystem extends createSystem({\n regions: { required: [UIDockRegion] },\n docked: { required: [UIWindow, UIDockedTo] },\n}) {\n private registry!: RegionRegistry;\n private regionEntities = new Map<string, Entity>();\n private slotPosition = new Vector3();\n private regionQuaternion = new Quaternion();\n\n override init(): void {\n this.registry = regionRegistryFor(this.world);\n\n this.cleanupFuncs.push(\n this.queries.regions.subscribe('qualify', (entity) => this.addRegion(entity)),\n this.queries.regions.subscribe('disqualify', (entity) => this.removeRegion(entity)),\n this.queries.docked.subscribe('qualify', (entity) => this.dockWindow(entity)),\n this.queries.docked.subscribe('disqualify', (entity) => this.undockWindow(entity)),\n );\n this.queries.regions.entities.forEach((entity) => this.addRegion(entity));\n this.queries.docked.entities.forEach((entity) => this.dockWindow(entity));\n }\n\n override update(): void {\n for (const entity of this.queries.docked.entities) {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n const offset = this.registry.offsetOf(windowId);\n const regionId = this.registry.regionOf(windowId);\n const regionEntity = regionId === undefined ? undefined : this.regionEntities.get(regionId);\n const regionObject = regionEntity?.object3D;\n const object = entity.object3D;\n if (!offset || !regionObject || !object || !object.parent) {\n continue;\n }\n // Slot offset is region-local; express it in the window's parent space.\n this.slotPosition.set(offset[0], offset[1], offset[2]);\n regionObject.localToWorld(this.slotPosition);\n object.parent.worldToLocal(this.slotPosition);\n object.position.copy(this.slotPosition);\n regionObject.getWorldQuaternion(this.regionQuaternion);\n object.quaternion.copy(this.regionQuaternion);\n }\n }\n\n private addRegion(entity: Entity): void {\n let id = entity.getValue(UIDockRegion, 'regionId') as string;\n if (!id) {\n id = `uix-region-${autoRegionId++}`;\n entity.setValue(UIDockRegion, 'regionId', id);\n }\n if (this.registry.get(id)) {\n return;\n }\n const definition: Partial<RegionDefinition> = {\n flow: entity.getValue(UIDockRegion, 'flow') as RegionFlow,\n pitch: entity.getValue(UIDockRegion, 'pitch') as number,\n columns: Math.max(1, Math.round(entity.getValue(UIDockRegion, 'columns') as number)),\n capacity: Math.max(0, Math.round(entity.getValue(UIDockRegion, 'capacity') as number)),\n snapRadius: entity.getValue(UIDockRegion, 'snapRadius') as number,\n };\n this.registry.register(id, definition);\n this.regionEntities.set(id, entity);\n }\n\n private removeRegion(entity: Entity): void {\n const id = entity.getValue(UIDockRegion, 'regionId') as string;\n if (id && this.regionEntities.get(id) === entity) {\n this.registry.unregister(id);\n this.regionEntities.delete(id);\n }\n }\n\n private dockWindow(entity: Entity): void {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n const regionId = entity.getValue(UIDockedTo, 'regionId') as string;\n if (!windowId || !regionId || !this.registry.get(regionId)) {\n // Unknown region (or the window has no id yet) - docking is meaningless.\n entity.removeComponent(UIDockedTo);\n return;\n }\n try {\n this.registry.dock(windowId, regionId);\n } catch {\n // Region is full: reject the dock.\n entity.removeComponent(UIDockedTo);\n return;\n }\n // Docked windows are world-locked by definition (the region may follow).\n if (entity.getValue(UIWindow, 'dockMode') !== DockMode.WorldLocked) {\n entity.setValue(UIWindow, 'dockMode', DockMode.WorldLocked);\n }\n }\n\n private undockWindow(entity: Entity): void {\n const windowId = entity.getValue(UIWindow, 'windowId') as string;\n if (windowId) {\n this.registry.undock(windowId);\n }\n }\n}\n"]}