@zixcel/dom-renderer 0.10.4 → 0.10.6

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 (80) hide show
  1. package/README.md +7 -1
  2. package/package.json +7 -5
  3. package/src/book-reader.d.mts +9 -1
  4. package/src/book-reader.mjs +54 -33
  5. package/src/dom-elements.d.mts +11 -0
  6. package/src/dom-elements.mjs +48 -0
  7. package/src/frame-layout.d.mts +9 -3
  8. package/src/frame-layout.mjs +22 -22
  9. package/src/frame.d.mts +22 -10
  10. package/src/frame.mjs +93 -45
  11. package/src/geometry-policy.d.mts +3 -0
  12. package/src/geometry-policy.mjs +17 -0
  13. package/src/graph-documents.d.mts +48 -0
  14. package/src/graph-documents.mjs +40 -0
  15. package/src/graphics-disposal.d.mts +2 -0
  16. package/src/graphics-disposal.mjs +14 -0
  17. package/src/index.d.mts +11 -57
  18. package/src/index.mjs +9 -205
  19. package/src/input-form.d.mts +5 -0
  20. package/src/input-form.mjs +103 -0
  21. package/src/inspection-overview.d.mts +27 -1
  22. package/src/inspection-overview.mjs +50 -33
  23. package/src/npc-motion.d.mts +1 -1
  24. package/src/npc-motion.mjs +5 -5
  25. package/src/object-presentation.d.mts +22 -26
  26. package/src/object-presentation.mjs +20 -17
  27. package/src/open-world-projection.d.mts +19 -10
  28. package/src/open-world-projection.mjs +31 -22
  29. package/src/reconcile.d.mts +1 -1
  30. package/src/reconcile.mjs +31 -19
  31. package/src/scene-items.d.mts +9 -0
  32. package/src/scene-items.mjs +29 -0
  33. package/src/scene-renderer.d.mts +37 -0
  34. package/src/scene-renderer.mjs +170 -0
  35. package/src/spatial-patterns.d.mts +22 -27
  36. package/src/spatial-patterns.mjs +63 -41
  37. package/src/spatial.d.mts +17 -6
  38. package/src/spatial.mjs +39 -25
  39. package/src/structure-graph.d.mts +5 -13
  40. package/src/structure-graph.mjs +167 -96
  41. package/src/symbol.d.mts +1 -1
  42. package/src/symbol.mjs +21 -16
  43. package/src/world-api.d.mts +56 -0
  44. package/src/world-api.mjs +8 -0
  45. package/src/world-camera.d.mts +14 -0
  46. package/src/world-camera.mjs +74 -0
  47. package/src/world-clock.d.mts +19 -0
  48. package/src/world-clock.mjs +30 -0
  49. package/src/world-commands.d.mts +39 -0
  50. package/src/world-commands.mjs +1 -0
  51. package/src/world-context.d.mts +132 -0
  52. package/src/world-context.mjs +1 -0
  53. package/src/world-controller.d.mts +43 -0
  54. package/src/world-controller.mjs +62 -0
  55. package/src/world-conversation.d.mts +3 -0
  56. package/src/world-conversation.mjs +46 -0
  57. package/src/world-events.d.mts +2 -0
  58. package/src/world-events.mjs +120 -0
  59. package/src/world-gpu.d.mts +3 -15
  60. package/src/world-gpu.mjs +159 -74
  61. package/src/world-input.d.mts +2 -2
  62. package/src/world-input.mjs +28 -18
  63. package/src/world-lifecycle.d.mts +12 -0
  64. package/src/world-lifecycle.mjs +172 -0
  65. package/src/world-model.d.mts +29 -40
  66. package/src/world-model.mjs +103 -74
  67. package/src/world-projection.d.mts +5 -0
  68. package/src/world-projection.mjs +169 -0
  69. package/src/world-render-loop.d.mts +9 -0
  70. package/src/world-render-loop.mjs +110 -0
  71. package/src/world-resources.d.mts +34 -0
  72. package/src/world-resources.mjs +8 -0
  73. package/src/world-runtime.d.mts +2 -36
  74. package/src/world-runtime.mjs +6 -311
  75. package/src/world-setup.d.mts +2 -0
  76. package/src/world-setup.mjs +179 -0
  77. package/src/world-surface.d.mts +2 -7
  78. package/src/world-surface.mjs +20 -12
  79. package/src/world-types.d.mts +74 -0
  80. package/src/world-types.mjs +1 -0
@@ -0,0 +1,56 @@
1
+ import type { Camera } from '@zixcel/projection-client';
2
+ import type { Pose, WorldObject, WorldRelation } from './world-types.mjs';
3
+ import type { SpatialState } from './spatial-patterns.mjs';
4
+ export interface RolePresentation {
5
+ state: string;
6
+ label: string;
7
+ }
8
+ export interface ActivityView {
9
+ panel: HTMLElement;
10
+ update(value: unknown): void;
11
+ close?(): void;
12
+ }
13
+ export interface ActivityPresentation {
14
+ describeRole?: (activity: unknown, id: string) => RolePresentation | null;
15
+ createView?: (document: Document, host: HTMLElement) => ActivityView;
16
+ }
17
+ export interface WorldSnapshot extends Pose {
18
+ target: string | null;
19
+ presentation: SpatialState;
20
+ navigation: string;
21
+ populated: boolean;
22
+ }
23
+ export interface WorldOptions {
24
+ objects?: WorldObject[];
25
+ relations?: WorldRelation[];
26
+ pose?: WorldSnapshot | null;
27
+ onActivate?: (id: string) => void;
28
+ conversation?: ((item: WorldObject, activate: () => void) => HTMLElement | null) | null;
29
+ activity?: unknown;
30
+ target?: string | null;
31
+ pattern?: string;
32
+ context?: string | null;
33
+ activityPresentation?: ActivityPresentation;
34
+ }
35
+ export interface World {
36
+ update(objects: WorldObject[], relations?: WorldRelation[]): void;
37
+ close(): void;
38
+ park(): void;
39
+ mount(host: HTMLElement, options: WorldOptions): void;
40
+ snapshot(): WorldSnapshot;
41
+ activity(value: unknown): void;
42
+ apply(value: Camera): void;
43
+ change(action: string): void;
44
+ pause(value: boolean): void;
45
+ }
46
+ export interface WorldLease {
47
+ world: World;
48
+ release(): void;
49
+ }
50
+ export interface WorldSurface {
51
+ attach(host: HTMLElement, options: WorldOptions): WorldLease;
52
+ close(): void;
53
+ }
54
+ export declare function record(value: unknown): Record<string, unknown>;
55
+ export declare function eventDetail(event: Event): Record<string, unknown>;
56
+ export declare function copyPose(value: Pose): Pose;
@@ -0,0 +1,8 @@
1
+ export function record(value) { return value !== null && typeof value === 'object' ? value : {}; }
2
+ export function eventDetail(event) { return record('detail' in event ? event.detail : null); }
3
+ export function copyPose(value) {
4
+ const coordinates = [value.x, value.y, value.z, value.yaw, value.pitch, ...(value.focus ? [value.focus.x, value.focus.y, value.focus.z] : [])];
5
+ if (!coordinates.every(Number.isFinite) || value.cameraMode !== undefined && !['overview', 'third-person'].includes(value.cameraMode))
6
+ throw Error('WorldPoseInvalid');
7
+ return { x: value.x, y: value.y, z: value.z, yaw: value.yaw, pitch: value.pitch, ...(value.focus ? { focus: { x: value.focus.x, y: value.focus.y, z: value.focus.z } } : {}), ...(value.cameraMode ? { cameraMode: value.cameraMode } : {}) };
8
+ }
@@ -0,0 +1,14 @@
1
+ import type { WorldContext } from './world-context.mjs';
2
+ export declare function objectRectWorld(ctx: WorldContext, id: string): {
3
+ left: number;
4
+ right: number;
5
+ top: number;
6
+ bottom: number;
7
+ } | null;
8
+ export declare function publishAnchorWorld(ctx: WorldContext): void;
9
+ export declare function syncOrbitWorld(ctx: WorldContext): void;
10
+ export declare function overviewWorld(ctx: WorldContext): void;
11
+ export declare function fitViewportWorld(ctx: WorldContext): void;
12
+ export declare function focusObjectWorld(ctx: WorldContext, id: string): void;
13
+ export declare function renderMapWorld(ctx: WorldContext): void;
14
+ export declare function positionBubbleWorld(ctx: WorldContext): void;
@@ -0,0 +1,74 @@
1
+ import { initialPose, cameraMatrix, screenPoint, fitPose, viewCamera } from './world-model.mjs';
2
+ import { objectBounds } from './object-presentation.mjs';
3
+ import { frameLayout } from './frame-layout.mjs';
4
+ import { mapProjection } from './open-world-projection.mjs';
5
+ export function objectRectWorld(ctx, id) {
6
+ const item = ctx.model.items.find(item => item.id === id), width = ctx.host.clientWidth, height = ctx.host.clientHeight;
7
+ if (!item || !width || !height)
8
+ return null;
9
+ const [w, h, d] = objectBounds[item.form], matrix = cameraMatrix(ctx.pose, width / height, ctx.zoom), points = [];
10
+ for (const x of [-w / 2, w / 2])
11
+ for (const y of [0, h])
12
+ for (const z of [-d / 2, d / 2]) {
13
+ const point = screenPoint(matrix, item.x + x, item.y + y, item.z + z, width, height);
14
+ if (point?.visible)
15
+ points.push(point);
16
+ }
17
+ if (!points.length)
18
+ return null;
19
+ return { left: Math.min(...points.map(p => p.x)), right: Math.max(...points.map(p => p.x)), top: Math.min(...points.map(p => p.y)), bottom: Math.max(...points.map(p => p.y)) };
20
+ }
21
+ export function publishAnchorWorld(ctx) { if (ctx.anchorId)
22
+ ctx.shell?.dispatchEvent(new ctx.window.CustomEvent('world-anchor', { detail: { id: ctx.anchorId, bounds: ctx.objectRect(ctx.anchorId) } })); }
23
+ export function syncOrbitWorld(ctx) { ctx.syncing = true; viewCamera(ctx.pose, Math.max(.1, ctx.host.clientWidth / Math.max(1, ctx.host.clientHeight)), ctx.zoom, ctx.orbitCamera); const focus = ctx.pose.focus ?? { x: ctx.pose.x, y: ctx.pose.y, z: ctx.pose.z - 8 }; ctx.orbit.target.set(focus.x, focus.y, focus.z); ctx.orbit.update(); ctx.syncing = false; }
24
+ export function overviewWorld(ctx) { ctx.navigation = 'overview'; ctx.navigationSelect.value = ctx.navigation; ctx.walk.hidden = true; ctx.lastWidth = ctx.host.clientWidth; ctx.lastHeight = ctx.host.clientHeight; ctx.pose = fitPose(ctx.model.items, ctx.lastWidth / Math.max(1, ctx.lastHeight)); ctx.needsFit = false; ctx.stopInput(); ctx.syncOrbit(); ctx.invalidate(); }
25
+ export function fitViewportWorld(ctx) { const width = ctx.host.clientWidth, height = ctx.host.clientHeight; if (!width || !height || (ctx.lastWidth === width && ctx.lastHeight === height))
26
+ return; ctx.lastWidth = width; ctx.lastHeight = height; if (ctx.navigation === 'overview') {
27
+ ctx.pose = fitPose(ctx.model.items, width / height);
28
+ ctx.syncOrbit();
29
+ } ctx.invalidate(); }
30
+ export function focusObjectWorld(ctx, id) {
31
+ const item = ctx.model.items.find(i => i.id === id);
32
+ if (!item)
33
+ return;
34
+ ctx.selectedId = id;
35
+ ctx.wrap.dataset.selectedId = id;
36
+ for (const [ref, node] of ctx.labelNodes)
37
+ node.dataset.selected = String(ref === id);
38
+ ctx.renderMap();
39
+ ctx.pose = ctx.navigation === 'overview' ? fitPose([item], ctx.host.clientWidth / Math.max(1, ctx.host.clientHeight)) : { ...initialPose(), cameraMode: 'third-person', x: item.x, y: item.y + 1.85, z: item.z + 6, pitch: 0 };
40
+ ctx.syncOrbit();
41
+ ctx.invalidate();
42
+ }
43
+ export function renderMapWorld(ctx) {
44
+ const projected = mapProjection(ctx.model.items, { selected: ctx.selectedId, marked: ctx.markedId }), items = projected.items;
45
+ ctx.mapNote.textContent = projected.omitted ? `${items.length} of ${projected.total} objects shown. Narrow this view to see the rest.` : `${items.length} objects in this view`;
46
+ ctx.mapButton.disabled = !ctx.selectedId || !ctx.model.items.some(item => item.id === ctx.selectedId);
47
+ ctx.mapButton.textContent = ctx.selectedId === ctx.markedId ? 'Clear marker' : 'Mark selected';
48
+ ctx.mapList.replaceChildren();
49
+ if (!items.length)
50
+ return;
51
+ const xs = items.map(item => item.x), zs = items.map(item => item.z), minX = Math.min(...xs), maxX = Math.max(...xs), minZ = Math.min(...zs), maxZ = Math.max(...zs);
52
+ for (const item of items) {
53
+ const node = ctx.make('button', 'world-map-point', (item.title ?? item.id).slice(0, 1).toUpperCase());
54
+ node.type = 'button';
55
+ node.title = item.title ?? item.id;
56
+ node.dataset.mapId = item.id;
57
+ node.dataset.selected = String(item.selected);
58
+ node.dataset.marked = String(item.marked);
59
+ node.setAttribute('aria-label', 'Find ' + item.title + ' on map');
60
+ node.style.left = (10 + 80 * (item.x - minX) / Math.max(1, maxX - minX)) + '%';
61
+ node.style.top = (10 + 80 * (item.z - minZ) / Math.max(1, maxZ - minZ)) + '%';
62
+ ctx.mapList.append(node);
63
+ }
64
+ }
65
+ export function positionBubbleWorld(ctx) {
66
+ if (!ctx.conversationId)
67
+ return;
68
+ if (!ctx.model.items.some(item => item.id === ctx.conversationId)) {
69
+ ctx.closeConversation();
70
+ return;
71
+ }
72
+ const { panel } = frameLayout(ctx.host.clientWidth, ctx.host.clientHeight, 'actions', ctx.objectRect(ctx.conversationId));
73
+ Object.assign(ctx.bubble.style, { left: panel.x + 'px', top: panel.y + 'px', width: panel.width + 'px', height: panel.height + 'px', maxHeight: panel.height + 'px' });
74
+ }
@@ -0,0 +1,19 @@
1
+ import type { Pose } from './world-types.mjs';
2
+ export interface FrameContext {
3
+ frame: number | null;
4
+ closed: boolean;
5
+ attached: boolean;
6
+ paused: boolean;
7
+ lost: boolean;
8
+ dirty: boolean;
9
+ last: number;
10
+ pose: Pose;
11
+ keys: Set<unknown>;
12
+ document: Pick<Document, 'hidden'>;
13
+ window: Pick<Window, 'requestAnimationFrame'>;
14
+ inputBlocked(): boolean;
15
+ tick(now: number): void;
16
+ draw(): void;
17
+ fail(error: unknown): void;
18
+ }
19
+ export declare function tickFrame(ctx: FrameContext, now: number): void;
@@ -0,0 +1,30 @@
1
+ import { worldLimits, movePose } from './world-model.mjs';
2
+ export function tickFrame(ctx, now) {
3
+ try {
4
+ ctx.frame = null;
5
+ if (ctx.closed || !ctx.attached || ctx.paused || ctx.lost || ctx.document.hidden)
6
+ return;
7
+ if (ctx.inputBlocked())
8
+ ctx.keys.clear();
9
+ const moving = ctx.keys.size > 0;
10
+ if (now - ctx.last < 1000 / worldLimits.fps) {
11
+ if (ctx.dirty || moving)
12
+ ctx.frame = ctx.window.requestAnimationFrame(ctx.tick);
13
+ return;
14
+ }
15
+ if (moving) {
16
+ ctx.pose = movePose(ctx.pose, Number(ctx.keys.has('forward')) - Number(ctx.keys.has('back')), Number(ctx.keys.has('right')) - Number(ctx.keys.has('left')), ctx.last ? (now - ctx.last) / 1000 : 1 / 30, Number(ctx.keys.has('rise')) - Number(ctx.keys.has('descend')));
17
+ ctx.dirty = true;
18
+ }
19
+ ctx.last = now;
20
+ if (ctx.dirty) {
21
+ ctx.dirty = false;
22
+ ctx.draw();
23
+ }
24
+ if (moving)
25
+ ctx.frame = ctx.window.requestAnimationFrame(ctx.tick);
26
+ }
27
+ catch (error) {
28
+ ctx.fail(error);
29
+ }
30
+ }
@@ -0,0 +1,39 @@
1
+ import type { WorldOptions, WorldSnapshot } from './world-api.mjs';
2
+ import type { WorldObject, WorldRelation } from './world-types.mjs';
3
+ import type { Camera } from '@zixcel/projection-client';
4
+ export interface WorldCommands {
5
+ objectRect: (id: string) => {
6
+ left: number;
7
+ right: number;
8
+ top: number;
9
+ bottom: number;
10
+ } | null;
11
+ publishAnchor: () => void;
12
+ filterDirectory: () => void;
13
+ syncOrbit: () => void;
14
+ overview: () => void;
15
+ fitViewport: () => void;
16
+ focusObject: (id: string) => void;
17
+ renderMap: () => void;
18
+ closeConversation: () => void;
19
+ activate: (id: string | null | undefined) => void;
20
+ positionBubble: () => void;
21
+ stopInput: () => void;
22
+ stopFrame: () => void;
23
+ invalidate: () => void;
24
+ tick: (now: number) => void;
25
+ draw: () => void;
26
+ fail: (error: unknown) => void;
27
+ initialize: () => void;
28
+ renderUpdates: () => void;
29
+ update: (objects: WorldObject[], relations?: WorldRelation[]) => void;
30
+ onKey: (event: KeyboardEvent) => void;
31
+ snapshot: () => WorldSnapshot;
32
+ park: () => void;
33
+ mount: (nextHost: HTMLElement, options: WorldOptions) => void;
34
+ close: () => void;
35
+ activity: (value: unknown) => void;
36
+ apply: (value: Camera) => void;
37
+ change: (action: string) => void;
38
+ pause: (value: boolean) => void;
39
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,132 @@
1
+ import type { WorldSnapshot, ActivityPresentation, RolePresentation, ActivityView } from './world-api.mjs';
2
+ import type { Pose, WorldObject, WorldRelation, WorldModel, WorldItem, Gpu, ActorRange } from './world-types.mjs';
3
+ import type { SpatialState, SpatialView } from './spatial-patterns.mjs';
4
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
5
+ import type { PerspectiveCamera } from 'three';
6
+ import type { WorldCommands } from './world-commands.mjs';
7
+ export interface WorldContext extends WorldCommands {
8
+ host: HTMLElement;
9
+ objects: WorldObject[];
10
+ relations: WorldRelation[];
11
+ restored: WorldSnapshot | null | undefined;
12
+ onActivate: (id: string) => void;
13
+ conversation: ((item: WorldObject, activate: () => void) => HTMLElement | null) | null;
14
+ workActivity: unknown;
15
+ target: string | null;
16
+ pattern: string;
17
+ context: string | null;
18
+ activityPresentation: ActivityPresentation;
19
+ document: Document;
20
+ browser: (Window & typeof globalThis) | null;
21
+ window: Window & typeof globalThis;
22
+ controller: AbortController;
23
+ signal: AbortSignal;
24
+ make: <K extends keyof HTMLElementTagNameMap>(tag: K, className: string, text?: string) => HTMLElementTagNameMap[K];
25
+ wrap: HTMLElement;
26
+ canvas: HTMLCanvasElement;
27
+ labels: HTMLDivElement;
28
+ hud: HTMLDivElement;
29
+ aim: HTMLSpanElement;
30
+ hint: HTMLButtonElement;
31
+ status: HTMLParagraphElement;
32
+ tools: HTMLDivElement;
33
+ nearby: HTMLDetailsElement;
34
+ list: HTMLDivElement;
35
+ summary: HTMLElement;
36
+ roleStatus: (value: unknown, id: string) => RolePresentation | null;
37
+ engine: ActivityView;
38
+ spacing: string;
39
+ placedSpacing: string | null;
40
+ pose: Pose;
41
+ model: WorldModel<WorldObject>;
42
+ gpu: Gpu | null;
43
+ geometry: Float32Array<ArrayBufferLike> | null;
44
+ actorRanges: ActorRange[];
45
+ closed: boolean;
46
+ attached: boolean;
47
+ lost: boolean;
48
+ dirty: boolean;
49
+ frame: number | null;
50
+ motionTimer: number | null;
51
+ last: number;
52
+ frames: number;
53
+ drag: {
54
+ id: number;
55
+ x: number;
56
+ y: number;
57
+ startX: number;
58
+ startY: number;
59
+ } | null;
60
+ hovered: string | null;
61
+ paused: boolean;
62
+ zoom: number;
63
+ projected: ({
64
+ x: number;
65
+ y: number;
66
+ depth: number;
67
+ visible: boolean;
68
+ } & {
69
+ item: WorldItem;
70
+ })[];
71
+ labelNodes: Map<string, HTMLButtonElement>;
72
+ directoryNodes: Map<string, HTMLDivElement>;
73
+ geometryKey: string | null;
74
+ uploads: number;
75
+ initializations: number;
76
+ navigation: string;
77
+ needsFit: boolean;
78
+ lastWidth: number;
79
+ lastHeight: number;
80
+ selectedId: string | null;
81
+ markedId: string | null;
82
+ recent: Map<string, {
83
+ title: string;
84
+ kind: string;
85
+ }>;
86
+ poses: Map<string | null, WorldSnapshot>;
87
+ fittedPopulation: boolean;
88
+ sourceObjects: WorldObject[];
89
+ sourceRelations: WorldRelation[];
90
+ view: SpatialState;
91
+ projection: SpatialView | null;
92
+ bubble: HTMLElement;
93
+ conversationId: string | null;
94
+ returnFocus: HTMLElement | null;
95
+ anchorId: string | null;
96
+ shell: Element | null;
97
+ map: HTMLDetailsElement;
98
+ mapList: HTMLDivElement;
99
+ mapNote: HTMLElement;
100
+ mapButton: HTMLButtonElement;
101
+ keys: Set<unknown>;
102
+ inputBlocked: () => boolean;
103
+ button: (text: string, label: string, action: () => void, parent?: HTMLElement) => HTMLButtonElement;
104
+ inspect: HTMLButtonElement;
105
+ reset: HTMLButtonElement;
106
+ settings: HTMLDivElement;
107
+ breadcrumb: HTMLDivElement;
108
+ location: HTMLOutputElement;
109
+ pagination: HTMLDivElement;
110
+ selector: (label: string, entries: readonly (readonly string[])[], change: (value: string) => void) => HTMLSelectElement;
111
+ navigationSelect: HTMLSelectElement;
112
+ patternSelect: HTMLSelectElement;
113
+ groupSelect: HTMLSelectElement;
114
+ legend: HTMLParagraphElement;
115
+ back: HTMLButtonElement;
116
+ previous: HTMLButtonElement;
117
+ pageCount: HTMLOutputElement;
118
+ nextPage: HTMLButtonElement;
119
+ updates: HTMLDetailsElement;
120
+ updatesTitle: HTMLElement;
121
+ updateList: HTMLDivElement;
122
+ updateHelp: HTMLParagraphElement;
123
+ query: HTMLInputElement;
124
+ noMatches: HTMLParagraphElement;
125
+ orbitCamera: PerspectiveCamera;
126
+ orbit: OrbitControls<PerspectiveCamera>;
127
+ syncing: boolean;
128
+ enableOrbit: () => void;
129
+ setText: (node: Element, value: string | undefined) => void;
130
+ walk: HTMLDivElement;
131
+ resize: ResizeObserver;
132
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import type { Camera } from '@zixcel/projection-client';
2
+ import type { WorldOptions, WorldSnapshot } from './world-api.mjs';
3
+ import type { WorldObject, WorldRelation } from './world-types.mjs';
4
+ import type { WorldContext } from './world-context.mjs';
5
+ export interface WorldController extends WorldContext {
6
+ }
7
+ export declare class WorldController {
8
+ constructor(host: HTMLElement, { objects, relations, pose: restored, onActivate, conversation, activity, target, pattern, context, activityPresentation }?: WorldOptions);
9
+ objectRect: (id: string) => {
10
+ left: number;
11
+ right: number;
12
+ top: number;
13
+ bottom: number;
14
+ } | null;
15
+ publishAnchor: () => void;
16
+ filterDirectory: () => void;
17
+ syncOrbit: () => void;
18
+ overview: () => void;
19
+ fitViewport: () => void;
20
+ focusObject: (id: string) => void;
21
+ renderMap: () => void;
22
+ closeConversation: () => void;
23
+ activate: (id: string | null | undefined) => void;
24
+ positionBubble: () => void;
25
+ stopInput: () => void;
26
+ stopFrame: () => void;
27
+ invalidate: () => void;
28
+ tick: (now: number) => void;
29
+ draw: () => void;
30
+ fail: (error: unknown) => void;
31
+ initialize: () => void;
32
+ renderUpdates: () => void;
33
+ update: (objects: WorldObject[], relations?: WorldRelation[]) => void;
34
+ onKey: (event: KeyboardEvent) => void;
35
+ snapshot: () => WorldSnapshot;
36
+ park: () => void;
37
+ mount: (nextHost: HTMLElement, options: WorldOptions) => void;
38
+ close: () => void;
39
+ activity: (value: unknown) => void;
40
+ apply: (value: Camera) => void;
41
+ change: (action: string) => void;
42
+ pause: (value: boolean) => void;
43
+ }
@@ -0,0 +1,62 @@
1
+ import { setupWorldView } from './world-setup.mjs';
2
+ import { objectRectWorld, publishAnchorWorld, syncOrbitWorld, overviewWorld, fitViewportWorld, focusObjectWorld, renderMapWorld, positionBubbleWorld } from './world-camera.mjs';
3
+ import { filterDirectoryWorld, renderUpdatesWorld, updateWorld } from './world-projection.mjs';
4
+ import { closeConversationWorld, activateWorld } from './world-conversation.mjs';
5
+ import { stopInputWorld, stopFrameWorld, invalidateWorld, tickWorld, drawWorld, failWorld, initializeWorld } from './world-render-loop.mjs';
6
+ import { onKeyWorld, snapshotWorld, parkWorld, mountWorld, closeWorld, activityWorld, applyWorld, changeWorld, pauseWorld } from './world-lifecycle.mjs';
7
+ export class WorldController {
8
+ constructor(host, { objects = [], relations = [], pose: restored, onActivate = () => { }, conversation = null, activity = null, target = null, pattern = 'ground', context = null, activityPresentation = {} } = {}) {
9
+ this.host = host;
10
+ this.objects = objects;
11
+ this.relations = relations;
12
+ this.restored = restored;
13
+ this.onActivate = onActivate;
14
+ this.conversation = conversation;
15
+ this.workActivity = activity;
16
+ this.target = target;
17
+ this.pattern = pattern;
18
+ this.context = context;
19
+ this.activityPresentation = activityPresentation;
20
+ try {
21
+ setupWorldView(this);
22
+ }
23
+ catch (error) {
24
+ try {
25
+ this.close();
26
+ }
27
+ catch (cleanup) {
28
+ throw new AggregateError([error, cleanup], 'World setup and cleanup failed');
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+ objectRect = (id) => objectRectWorld(this, id);
34
+ publishAnchor = () => publishAnchorWorld(this);
35
+ filterDirectory = () => filterDirectoryWorld(this);
36
+ syncOrbit = () => syncOrbitWorld(this);
37
+ overview = () => overviewWorld(this);
38
+ fitViewport = () => fitViewportWorld(this);
39
+ focusObject = (id) => focusObjectWorld(this, id);
40
+ renderMap = () => renderMapWorld(this);
41
+ closeConversation = () => closeConversationWorld(this);
42
+ activate = (id) => activateWorld(this, id);
43
+ positionBubble = () => positionBubbleWorld(this);
44
+ stopInput = () => stopInputWorld(this);
45
+ stopFrame = () => stopFrameWorld(this);
46
+ invalidate = () => invalidateWorld(this);
47
+ tick = (now) => tickWorld(this, now);
48
+ draw = () => drawWorld(this);
49
+ fail = (error) => failWorld(this, error);
50
+ initialize = () => initializeWorld(this);
51
+ renderUpdates = () => renderUpdatesWorld(this);
52
+ update = (objects, relations = []) => updateWorld(this, objects, relations);
53
+ onKey = (event) => onKeyWorld(this, event);
54
+ snapshot = () => snapshotWorld(this);
55
+ park = () => parkWorld(this);
56
+ mount = (nextHost, options) => mountWorld(this, nextHost, options);
57
+ close = () => closeWorld(this);
58
+ activity = (value) => activityWorld(this, value);
59
+ apply = (value) => applyWorld(this, value);
60
+ change = (action) => changeWorld(this, action);
61
+ pause = (value) => pauseWorld(this, value);
62
+ }
@@ -0,0 +1,3 @@
1
+ import type { WorldContext } from './world-context.mjs';
2
+ export declare function closeConversationWorld(ctx: WorldContext): void;
3
+ export declare function activateWorld(ctx: WorldContext, id: string | null | undefined): void;
@@ -0,0 +1,46 @@
1
+ export function closeConversationWorld(ctx) { ctx.conversationId = null; ctx.bubble.hidden = true; ctx.bubble.replaceChildren(); if (ctx.returnFocus?.isConnected)
2
+ ctx.returnFocus.focus({ preventScroll: true }); ctx.returnFocus = null; ctx.invalidate(); }
3
+ export function activateWorld(ctx, id) {
4
+ if (!id)
5
+ return;
6
+ const item = ctx.model.items.find(item => item.id === id);
7
+ if (!item || item.disabled)
8
+ return;
9
+ ctx.selectedId = id;
10
+ ctx.wrap.dataset.selectedId = id;
11
+ for (const [ref, node] of ctx.labelNodes)
12
+ node.dataset.selected = String(ref === id);
13
+ ctx.renderMap();
14
+ ctx.stopInput();
15
+ ctx.nearby.open = false;
16
+ ctx.map.open = false;
17
+ if (item.kind === 'group') {
18
+ ctx.view = { ...ctx.view, group: ctx.view.pattern === 'garden' && ctx.view.group === item.id ? null : item.id, page: 0 };
19
+ ctx.needsFit = ctx.view.pattern !== 'garden';
20
+ ctx.update(ctx.sourceObjects, ctx.sourceRelations);
21
+ if (ctx.view.pattern === 'garden')
22
+ ctx.focusObject(item.id);
23
+ return;
24
+ }
25
+ ctx.recent.delete(id);
26
+ ctx.renderUpdates();
27
+ const body = ctx.conversation?.(item, () => { ctx.closeConversation(); ctx.onActivate(id); });
28
+ if (!body) {
29
+ ctx.onActivate(id);
30
+ return;
31
+ }
32
+ ctx.returnFocus = ctx.document.activeElement instanceof ctx.window.HTMLElement ? ctx.document.activeElement : null;
33
+ ctx.conversationId = id;
34
+ ctx.bubble.setAttribute('aria-label', 'Conversation with ' + item.title);
35
+ const title = ctx.make('strong', '', item.title), dismiss = ctx.make('button', '', '×');
36
+ dismiss.type = 'button';
37
+ dismiss.setAttribute('aria-label', 'Close conversation');
38
+ dismiss.addEventListener('click', ctx.closeConversation, { once: true });
39
+ const head = ctx.make('header', '');
40
+ head.append(title, dismiss);
41
+ ctx.bubble.replaceChildren(head, body);
42
+ ctx.bubble.hidden = false;
43
+ ctx.bubble.focus({ preventScroll: true });
44
+ ctx.positionBubble();
45
+ ctx.invalidate();
46
+ }
@@ -0,0 +1,2 @@
1
+ import type { WorldContext } from './world-context.mjs';
2
+ export declare function bindWorldEvents(ctx: WorldContext): void;