worldorbit 2.5.1-5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/LICENSE.md +5 -0
  2. package/README.md +468 -0
  3. package/dist/browser/core/dist/index.js +4009 -0
  4. package/dist/browser/markdown/dist/index.js +3951 -0
  5. package/dist/browser/viewer/dist/index.js +5981 -0
  6. package/dist/constants.d.ts +8 -0
  7. package/dist/constants.js +84 -0
  8. package/dist/errors.d.ts +7 -0
  9. package/dist/errors.js +16 -0
  10. package/dist/index.d.ts +18 -0
  11. package/dist/index.js +25 -0
  12. package/dist/normalize.d.ts +2 -0
  13. package/dist/normalize.js +243 -0
  14. package/dist/parse.d.ts +2 -0
  15. package/dist/parse.js +126 -0
  16. package/dist/render.d.ts +6 -0
  17. package/dist/render.js +683 -0
  18. package/dist/tokenize.d.ts +4 -0
  19. package/dist/tokenize.js +68 -0
  20. package/dist/types.d.ts +208 -0
  21. package/dist/types.js +1 -0
  22. package/dist/unpkg/core/dist/index.js +4081 -0
  23. package/dist/unpkg/markdown/dist/index.js +3979 -0
  24. package/dist/unpkg/viewer/dist/index.js +6038 -0
  25. package/dist/unpkg/worldorbit-core.min.js +12 -0
  26. package/dist/unpkg/worldorbit-markdown.min.js +95 -0
  27. package/dist/unpkg/worldorbit-viewer.min.js +251 -0
  28. package/dist/unpkg/worldorbit.d.ts +2 -0
  29. package/dist/unpkg/worldorbit.esm.js +2 -0
  30. package/dist/unpkg/worldorbit.js +8524 -0
  31. package/dist/unpkg/worldorbit.min.js +255 -0
  32. package/dist/validate.d.ts +2 -0
  33. package/dist/validate.js +31 -0
  34. package/dist/viewer-state.d.ts +16 -0
  35. package/dist/viewer-state.js +130 -0
  36. package/dist/viewer.d.ts +2 -0
  37. package/dist/viewer.js +434 -0
  38. package/package.json +68 -0
  39. package/packages/core/README.md +17 -0
  40. package/packages/core/dist/atlas-edit.d.ts +11 -0
  41. package/packages/core/dist/atlas-edit.js +273 -0
  42. package/packages/core/dist/atlas-utils.d.ts +22 -0
  43. package/packages/core/dist/atlas-utils.js +189 -0
  44. package/packages/core/dist/atlas-validate.d.ts +2 -0
  45. package/packages/core/dist/atlas-validate.js +285 -0
  46. package/packages/core/dist/diagnostics.d.ts +10 -0
  47. package/packages/core/dist/diagnostics.js +109 -0
  48. package/packages/core/dist/draft-parse.d.ts +3 -0
  49. package/packages/core/dist/draft-parse.js +1275 -0
  50. package/packages/core/dist/draft.d.ts +18 -0
  51. package/packages/core/dist/draft.js +387 -0
  52. package/packages/core/dist/errors.d.ts +7 -0
  53. package/packages/core/dist/errors.js +16 -0
  54. package/packages/core/dist/format.d.ts +4 -0
  55. package/packages/core/dist/format.js +520 -0
  56. package/packages/core/dist/index.d.ts +28 -0
  57. package/packages/core/dist/index.js +44 -0
  58. package/packages/core/dist/load.d.ts +4 -0
  59. package/packages/core/dist/load.js +175 -0
  60. package/packages/core/dist/markdown.d.ts +2 -0
  61. package/packages/core/dist/markdown.js +37 -0
  62. package/packages/core/dist/normalize.d.ts +2 -0
  63. package/packages/core/dist/normalize.js +311 -0
  64. package/packages/core/dist/parse.d.ts +2 -0
  65. package/packages/core/dist/parse.js +133 -0
  66. package/packages/core/dist/scene.d.ts +3 -0
  67. package/packages/core/dist/scene.js +1565 -0
  68. package/packages/core/dist/schema.d.ts +8 -0
  69. package/packages/core/dist/schema.js +298 -0
  70. package/packages/core/dist/tokenize.d.ts +4 -0
  71. package/packages/core/dist/tokenize.js +68 -0
  72. package/packages/core/dist/types.d.ts +476 -0
  73. package/packages/core/dist/types.js +1 -0
  74. package/packages/core/dist/validate.d.ts +2 -0
  75. package/packages/core/dist/validate.js +56 -0
  76. package/packages/editor/dist/editor.d.ts +2 -0
  77. package/packages/editor/dist/editor.js +3042 -0
  78. package/packages/editor/dist/index.d.ts +2 -0
  79. package/packages/editor/dist/index.js +1 -0
  80. package/packages/editor/dist/types.d.ts +53 -0
  81. package/packages/editor/dist/types.js +1 -0
  82. package/packages/markdown/README.md +9 -0
  83. package/packages/markdown/dist/html.d.ts +3 -0
  84. package/packages/markdown/dist/html.js +57 -0
  85. package/packages/markdown/dist/index.d.ts +4 -0
  86. package/packages/markdown/dist/index.js +3 -0
  87. package/packages/markdown/dist/rehype.d.ts +10 -0
  88. package/packages/markdown/dist/rehype.js +49 -0
  89. package/packages/markdown/dist/remark.d.ts +9 -0
  90. package/packages/markdown/dist/remark.js +28 -0
  91. package/packages/markdown/dist/types.d.ts +11 -0
  92. package/packages/markdown/dist/types.js +1 -0
  93. package/packages/viewer/README.md +13 -0
  94. package/packages/viewer/dist/atlas-state.d.ts +12 -0
  95. package/packages/viewer/dist/atlas-state.js +257 -0
  96. package/packages/viewer/dist/atlas-viewer.d.ts +2 -0
  97. package/packages/viewer/dist/atlas-viewer.js +482 -0
  98. package/packages/viewer/dist/custom-element.d.ts +1 -0
  99. package/packages/viewer/dist/custom-element.js +64 -0
  100. package/packages/viewer/dist/embed.d.ts +20 -0
  101. package/packages/viewer/dist/embed.js +138 -0
  102. package/packages/viewer/dist/index.d.ts +9 -0
  103. package/packages/viewer/dist/index.js +8 -0
  104. package/packages/viewer/dist/minimap.d.ts +3 -0
  105. package/packages/viewer/dist/minimap.js +63 -0
  106. package/packages/viewer/dist/render.d.ts +6 -0
  107. package/packages/viewer/dist/render.js +641 -0
  108. package/packages/viewer/dist/theme.d.ts +4 -0
  109. package/packages/viewer/dist/theme.js +102 -0
  110. package/packages/viewer/dist/tooltip.d.ts +3 -0
  111. package/packages/viewer/dist/tooltip.js +189 -0
  112. package/packages/viewer/dist/types.d.ts +263 -0
  113. package/packages/viewer/dist/types.js +1 -0
  114. package/packages/viewer/dist/viewer-state.d.ts +19 -0
  115. package/packages/viewer/dist/viewer-state.js +162 -0
  116. package/packages/viewer/dist/viewer.d.ts +2 -0
  117. package/packages/viewer/dist/viewer.js +1175 -0
@@ -0,0 +1,2 @@
1
+ import type { WorldOrbitDocument } from "./types.js";
2
+ export declare function validateDocument(doc: WorldOrbitDocument): void;
@@ -0,0 +1,31 @@
1
+ import { WorldOrbitError } from "./errors.js";
2
+ export function validateDocument(doc) {
3
+ const knownIds = new Set();
4
+ for (const obj of doc.objects) {
5
+ if (knownIds.has(obj.id)) {
6
+ throw new WorldOrbitError(`Duplicate object id "${obj.id}"`);
7
+ }
8
+ knownIds.add(obj.id);
9
+ }
10
+ for (const obj of doc.objects) {
11
+ if (!obj.placement) {
12
+ continue;
13
+ }
14
+ if (obj.placement.mode === "orbit" || obj.placement.mode === "surface") {
15
+ if (!knownIds.has(obj.placement.target)) {
16
+ throw new WorldOrbitError(`Unknown placement target "${obj.placement.target}" on "${obj.id}"`);
17
+ }
18
+ }
19
+ if (obj.placement.mode === "at" && obj.placement.reference.kind === "lagrange") {
20
+ validateLagrangeReference(obj, obj.placement.reference, knownIds);
21
+ }
22
+ }
23
+ }
24
+ function validateLagrangeReference(obj, reference, knownIds) {
25
+ if (!knownIds.has(reference.primary)) {
26
+ throw new WorldOrbitError(`Unknown Lagrange reference "${reference.primary}" on "${obj.id}"`);
27
+ }
28
+ if (reference.secondary && !knownIds.has(reference.secondary)) {
29
+ throw new WorldOrbitError(`Unknown Lagrange reference "${reference.secondary}" on "${obj.id}"`);
30
+ }
31
+ }
@@ -0,0 +1,16 @@
1
+ import type { CoordinatePoint, RenderScene, ViewerState } from "./types.js";
2
+ export interface ViewerConstraints {
3
+ minScale: number;
4
+ maxScale: number;
5
+ fitPadding: number;
6
+ }
7
+ export declare const DEFAULT_VIEWER_STATE: ViewerState;
8
+ export declare function normalizeRotation(rotationDeg: number): number;
9
+ export declare function clampScale(scale: number, constraints: ViewerConstraints): number;
10
+ export declare function panViewerState(state: ViewerState, dx: number, dy: number): ViewerState;
11
+ export declare function rotateViewerState(state: ViewerState, deltaDeg: number): ViewerState;
12
+ export declare function zoomViewerStateAt(scene: RenderScene, state: ViewerState, factor: number, anchor: CoordinatePoint, constraints: ViewerConstraints): ViewerState;
13
+ export declare function fitViewerState(scene: RenderScene, state: ViewerState, constraints: ViewerConstraints): ViewerState;
14
+ export declare function focusViewerState(scene: RenderScene, state: ViewerState, objectId: string, constraints: ViewerConstraints): ViewerState;
15
+ export declare function composeViewerTransform(scene: RenderScene, state: ViewerState): string;
16
+ export declare function getSceneCenter(scene: RenderScene): CoordinatePoint;
@@ -0,0 +1,130 @@
1
+ export const DEFAULT_VIEWER_STATE = {
2
+ scale: 1,
3
+ rotationDeg: 0,
4
+ translateX: 0,
5
+ translateY: 0,
6
+ selectedObjectId: null,
7
+ };
8
+ export function normalizeRotation(rotationDeg) {
9
+ let normalized = rotationDeg % 360;
10
+ if (normalized > 180) {
11
+ normalized -= 360;
12
+ }
13
+ if (normalized <= -180) {
14
+ normalized += 360;
15
+ }
16
+ return normalized;
17
+ }
18
+ export function clampScale(scale, constraints) {
19
+ return Math.min(Math.max(scale, constraints.minScale), constraints.maxScale);
20
+ }
21
+ export function panViewerState(state, dx, dy) {
22
+ return {
23
+ ...state,
24
+ translateX: state.translateX + dx,
25
+ translateY: state.translateY + dy,
26
+ };
27
+ }
28
+ export function rotateViewerState(state, deltaDeg) {
29
+ return {
30
+ ...state,
31
+ rotationDeg: normalizeRotation(state.rotationDeg + deltaDeg),
32
+ };
33
+ }
34
+ export function zoomViewerStateAt(scene, state, factor, anchor, constraints) {
35
+ if (!Number.isFinite(factor) || factor <= 0) {
36
+ return state;
37
+ }
38
+ const center = getSceneCenter(scene);
39
+ const nextScale = clampScale(state.scale * factor, constraints);
40
+ if (nextScale === state.scale) {
41
+ return state;
42
+ }
43
+ const zoomRatio = nextScale / state.scale;
44
+ const anchorDx = anchor.x - center.x;
45
+ const anchorDy = anchor.y - center.y;
46
+ return {
47
+ ...state,
48
+ scale: nextScale,
49
+ translateX: (1 - zoomRatio) * anchorDx + zoomRatio * state.translateX,
50
+ translateY: (1 - zoomRatio) * anchorDy + zoomRatio * state.translateY,
51
+ };
52
+ }
53
+ export function fitViewerState(scene, state, constraints) {
54
+ const center = getSceneCenter(scene);
55
+ const rotatedBounds = rotateBounds(scene.contentBounds, center, state.rotationDeg);
56
+ const availableWidth = Math.max(scene.width - constraints.fitPadding * 2, 1);
57
+ const availableHeight = Math.max(scene.height - constraints.fitPadding * 2, 1);
58
+ const safeWidth = Math.max(rotatedBounds.width, 1);
59
+ const safeHeight = Math.max(rotatedBounds.height, 1);
60
+ const nextScale = clampScale(Math.min(availableWidth / safeWidth, availableHeight / safeHeight), constraints);
61
+ const rotatedCenter = rotatePoint({
62
+ x: scene.contentBounds.centerX,
63
+ y: scene.contentBounds.centerY,
64
+ }, center, state.rotationDeg);
65
+ return {
66
+ ...state,
67
+ scale: nextScale,
68
+ translateX: center.x - (center.x + (rotatedCenter.x - center.x) * nextScale),
69
+ translateY: center.y - (center.y + (rotatedCenter.y - center.y) * nextScale),
70
+ };
71
+ }
72
+ export function focusViewerState(scene, state, objectId, constraints) {
73
+ const target = scene.objects.find((object) => object.objectId === objectId && !object.hidden);
74
+ if (!target) {
75
+ return state;
76
+ }
77
+ const center = getSceneCenter(scene);
78
+ const nextScale = clampScale(Math.max(state.scale, 1.8), constraints);
79
+ const rotatedPoint = rotatePoint({ x: target.x, y: target.y }, center, state.rotationDeg);
80
+ return {
81
+ ...state,
82
+ scale: nextScale,
83
+ translateX: center.x - (center.x + (rotatedPoint.x - center.x) * nextScale),
84
+ translateY: center.y - (center.y + (rotatedPoint.y - center.y) * nextScale),
85
+ selectedObjectId: objectId,
86
+ };
87
+ }
88
+ export function composeViewerTransform(scene, state) {
89
+ const center = getSceneCenter(scene);
90
+ return `translate(${state.translateX} ${state.translateY}) translate(${center.x} ${center.y}) rotate(${state.rotationDeg}) scale(${state.scale}) translate(${-center.x} ${-center.y})`;
91
+ }
92
+ export function getSceneCenter(scene) {
93
+ return {
94
+ x: scene.width / 2,
95
+ y: scene.height / 2,
96
+ };
97
+ }
98
+ function rotateBounds(bounds, center, rotationDeg) {
99
+ const corners = [
100
+ { x: bounds.minX, y: bounds.minY },
101
+ { x: bounds.maxX, y: bounds.minY },
102
+ { x: bounds.maxX, y: bounds.maxY },
103
+ { x: bounds.minX, y: bounds.maxY },
104
+ ].map((corner) => rotatePoint(corner, center, rotationDeg));
105
+ const minX = Math.min(...corners.map((corner) => corner.x));
106
+ const minY = Math.min(...corners.map((corner) => corner.y));
107
+ const maxX = Math.max(...corners.map((corner) => corner.x));
108
+ const maxY = Math.max(...corners.map((corner) => corner.y));
109
+ return {
110
+ minX,
111
+ minY,
112
+ maxX,
113
+ maxY,
114
+ width: maxX - minX,
115
+ height: maxY - minY,
116
+ centerX: minX + (maxX - minX) / 2,
117
+ centerY: minY + (maxY - minY) / 2,
118
+ };
119
+ }
120
+ function rotatePoint(point, center, rotationDeg) {
121
+ const radians = (rotationDeg * Math.PI) / 180;
122
+ const cos = Math.cos(radians);
123
+ const sin = Math.sin(radians);
124
+ const dx = point.x - center.x;
125
+ const dy = point.y - center.y;
126
+ return {
127
+ x: center.x + dx * cos - dy * sin,
128
+ y: center.y + dx * sin + dy * cos,
129
+ };
130
+ }
@@ -0,0 +1,2 @@
1
+ import type { InteractiveViewerOptions, WorldOrbitViewer } from "./types.js";
2
+ export declare function createInteractiveViewer(container: HTMLElement, options: InteractiveViewerOptions): WorldOrbitViewer;
package/dist/viewer.js ADDED
@@ -0,0 +1,434 @@
1
+ import { normalizeDocument } from "./normalize.js";
2
+ import { parseWorldOrbit } from "./parse.js";
3
+ import { renderDocumentToScene, renderSceneToSvg } from "./render.js";
4
+ import { validateDocument } from "./validate.js";
5
+ import { DEFAULT_VIEWER_STATE, composeViewerTransform, fitViewerState, focusViewerState, panViewerState, rotateViewerState, zoomViewerStateAt, } from "./viewer-state.js";
6
+ const DEFAULT_VIEWER_LIMITS = {
7
+ minScale: 0.2,
8
+ maxScale: 8,
9
+ fitPadding: 48,
10
+ panStep: 40,
11
+ zoomStep: 1.2,
12
+ rotationStep: 15,
13
+ };
14
+ export function createInteractiveViewer(container, options) {
15
+ ensureBrowserEnvironment(container);
16
+ const inputCount = Number(Boolean(options.source)) + Number(Boolean(options.document)) + Number(Boolean(options.scene));
17
+ if (inputCount !== 1) {
18
+ throw new Error('Interactive viewer requires exactly one of "source", "document", or "scene".');
19
+ }
20
+ const constraints = {
21
+ minScale: options.minScale ?? DEFAULT_VIEWER_LIMITS.minScale,
22
+ maxScale: options.maxScale ?? DEFAULT_VIEWER_LIMITS.maxScale,
23
+ fitPadding: options.fitPadding ?? DEFAULT_VIEWER_LIMITS.fitPadding,
24
+ };
25
+ const behavior = {
26
+ keyboard: options.keyboard ?? true,
27
+ pointer: options.pointer ?? true,
28
+ touch: options.touch ?? true,
29
+ selection: options.selection ?? true,
30
+ panStep: options.panStep ?? DEFAULT_VIEWER_LIMITS.panStep,
31
+ zoomStep: options.zoomStep ?? DEFAULT_VIEWER_LIMITS.zoomStep,
32
+ rotationStep: options.rotationStep ?? DEFAULT_VIEWER_LIMITS.rotationStep,
33
+ };
34
+ const renderOptions = {
35
+ width: options.width,
36
+ height: options.height,
37
+ padding: options.padding,
38
+ };
39
+ const previousTabIndex = container.getAttribute("tabindex");
40
+ const previousTouchAction = container.style.touchAction;
41
+ let scene = resolveInitialScene(options, renderOptions);
42
+ let state = { ...DEFAULT_VIEWER_STATE };
43
+ let svgElement = null;
44
+ let cameraRoot = null;
45
+ let suppressClick = false;
46
+ let activePointerId = null;
47
+ let lastPointerPoint = null;
48
+ let dragDistance = 0;
49
+ let destroyed = false;
50
+ let touchPoints = new Map();
51
+ let touchGesture = null;
52
+ if (previousTabIndex === null) {
53
+ container.tabIndex = 0;
54
+ }
55
+ container.classList.add("wo-viewer-container");
56
+ container.style.touchAction = behavior.touch ? "none" : previousTouchAction;
57
+ const handleWheel = (event) => {
58
+ if (!behavior.pointer || destroyed) {
59
+ return;
60
+ }
61
+ event.preventDefault();
62
+ container.focus();
63
+ const anchor = getScenePointFromClient(event.clientX, event.clientY);
64
+ const factor = clampValue(Math.exp(-event.deltaY * 0.002), 0.6, 1.6);
65
+ updateState(zoomViewerStateAt(scene, state, factor, anchor, constraints));
66
+ };
67
+ const handlePointerDown = (event) => {
68
+ if (destroyed) {
69
+ return;
70
+ }
71
+ const isTouch = event.pointerType === "touch";
72
+ if ((isTouch && !behavior.touch) || (!isTouch && !behavior.pointer)) {
73
+ return;
74
+ }
75
+ if (!isTouch && event.button !== 0) {
76
+ return;
77
+ }
78
+ container.focus();
79
+ container.setPointerCapture?.(event.pointerId);
80
+ const point = getScenePointFromClient(event.clientX, event.clientY);
81
+ if (isTouch) {
82
+ touchPoints.set(event.pointerId, point);
83
+ if (touchPoints.size === 2) {
84
+ touchGesture = createTouchGestureState(state, touchPoints);
85
+ }
86
+ return;
87
+ }
88
+ activePointerId = event.pointerId;
89
+ lastPointerPoint = point;
90
+ dragDistance = 0;
91
+ suppressClick = false;
92
+ };
93
+ const handlePointerMove = (event) => {
94
+ if (destroyed) {
95
+ return;
96
+ }
97
+ const isTouch = event.pointerType === "touch";
98
+ if (isTouch) {
99
+ if (!behavior.touch || !touchPoints.has(event.pointerId)) {
100
+ return;
101
+ }
102
+ touchPoints.set(event.pointerId, getScenePointFromClient(event.clientX, event.clientY));
103
+ if (touchPoints.size === 2) {
104
+ if (!touchGesture) {
105
+ touchGesture = createTouchGestureState(state, touchPoints);
106
+ }
107
+ const current = getTouchCenterAndDistance(touchPoints);
108
+ const factor = current.distance / Math.max(touchGesture.startDistance, 1);
109
+ const zoomedState = zoomViewerStateAt(scene, touchGesture.startState, factor, touchGesture.startCenter, constraints);
110
+ const deltaX = current.center.x - touchGesture.startCenter.x;
111
+ const deltaY = current.center.y - touchGesture.startCenter.y;
112
+ updateState(panViewerState(zoomedState, deltaX, deltaY));
113
+ }
114
+ return;
115
+ }
116
+ if (!behavior.pointer || activePointerId !== event.pointerId || !lastPointerPoint) {
117
+ return;
118
+ }
119
+ const nextPoint = getScenePointFromClient(event.clientX, event.clientY);
120
+ const deltaX = nextPoint.x - lastPointerPoint.x;
121
+ const deltaY = nextPoint.y - lastPointerPoint.y;
122
+ dragDistance += Math.abs(deltaX) + Math.abs(deltaY);
123
+ lastPointerPoint = nextPoint;
124
+ if (dragDistance > 2) {
125
+ suppressClick = true;
126
+ }
127
+ updateState(panViewerState(state, deltaX, deltaY));
128
+ };
129
+ const handlePointerEnd = (event) => {
130
+ if (event.pointerType === "touch") {
131
+ touchPoints.delete(event.pointerId);
132
+ if (touchPoints.size < 2) {
133
+ touchGesture = null;
134
+ }
135
+ return;
136
+ }
137
+ if (activePointerId === event.pointerId) {
138
+ activePointerId = null;
139
+ lastPointerPoint = null;
140
+ }
141
+ };
142
+ const handleClick = (event) => {
143
+ if (!behavior.selection || destroyed) {
144
+ return;
145
+ }
146
+ if (suppressClick) {
147
+ suppressClick = false;
148
+ return;
149
+ }
150
+ const target = event.target;
151
+ if (!(target instanceof Element)) {
152
+ return;
153
+ }
154
+ const objectElement = target.closest("[data-object-id]");
155
+ if (!objectElement) {
156
+ applySelection(null);
157
+ return;
158
+ }
159
+ applySelection(objectElement.dataset.objectId ?? null);
160
+ };
161
+ const handleKeyDown = (event) => {
162
+ if (!behavior.keyboard || destroyed) {
163
+ return;
164
+ }
165
+ switch (event.key) {
166
+ case "+":
167
+ case "=":
168
+ event.preventDefault();
169
+ api.zoomBy(behavior.zoomStep);
170
+ return;
171
+ case "-":
172
+ event.preventDefault();
173
+ api.zoomBy(1 / behavior.zoomStep);
174
+ return;
175
+ case "ArrowLeft":
176
+ event.preventDefault();
177
+ api.panBy(-behavior.panStep, 0);
178
+ return;
179
+ case "ArrowRight":
180
+ event.preventDefault();
181
+ api.panBy(behavior.panStep, 0);
182
+ return;
183
+ case "ArrowUp":
184
+ event.preventDefault();
185
+ api.panBy(0, -behavior.panStep);
186
+ return;
187
+ case "ArrowDown":
188
+ event.preventDefault();
189
+ api.panBy(0, behavior.panStep);
190
+ return;
191
+ case "[":
192
+ event.preventDefault();
193
+ api.rotateBy(-behavior.rotationStep);
194
+ return;
195
+ case "]":
196
+ event.preventDefault();
197
+ api.rotateBy(behavior.rotationStep);
198
+ return;
199
+ case "f":
200
+ case "F":
201
+ event.preventDefault();
202
+ api.fitToSystem();
203
+ return;
204
+ case "0":
205
+ event.preventDefault();
206
+ api.resetView();
207
+ return;
208
+ }
209
+ };
210
+ container.addEventListener("wheel", handleWheel, { passive: false });
211
+ container.addEventListener("pointerdown", handlePointerDown);
212
+ container.addEventListener("pointermove", handlePointerMove);
213
+ container.addEventListener("pointerup", handlePointerEnd);
214
+ container.addEventListener("pointercancel", handlePointerEnd);
215
+ container.addEventListener("click", handleClick);
216
+ container.addEventListener("keydown", handleKeyDown);
217
+ const api = {
218
+ setSource(source) {
219
+ scene = renderDocumentToScene(parseSource(source), renderOptions);
220
+ rerenderScene(true);
221
+ },
222
+ setDocument(document) {
223
+ scene = renderDocumentToScene(document, renderOptions);
224
+ rerenderScene(true);
225
+ },
226
+ setScene(nextScene) {
227
+ scene = nextScene;
228
+ rerenderScene(true);
229
+ },
230
+ getState() {
231
+ return { ...state };
232
+ },
233
+ setState(nextState) {
234
+ updateState(sanitizeState({ ...state, ...nextState }));
235
+ },
236
+ zoomBy(factor, anchor) {
237
+ updateState(zoomViewerStateAt(scene, state, factor, anchor ?? { x: scene.width / 2, y: scene.height / 2 }, constraints));
238
+ },
239
+ panBy(dx, dy) {
240
+ updateState(panViewerState(state, dx, dy));
241
+ },
242
+ rotateBy(deg) {
243
+ updateState(rotateViewerState(state, deg));
244
+ },
245
+ fitToSystem() {
246
+ updateState(fitViewerState(scene, state, constraints));
247
+ },
248
+ focusObject(id) {
249
+ updateState(focusViewerState(scene, state, id, constraints));
250
+ applySelection(id);
251
+ },
252
+ resetView() {
253
+ const resetState = fitViewerState(scene, { ...DEFAULT_VIEWER_STATE }, constraints);
254
+ updateState(resetState);
255
+ applySelection(null);
256
+ },
257
+ destroy() {
258
+ if (destroyed) {
259
+ return;
260
+ }
261
+ destroyed = true;
262
+ container.removeEventListener("wheel", handleWheel);
263
+ container.removeEventListener("pointerdown", handlePointerDown);
264
+ container.removeEventListener("pointermove", handlePointerMove);
265
+ container.removeEventListener("pointerup", handlePointerEnd);
266
+ container.removeEventListener("pointercancel", handlePointerEnd);
267
+ container.removeEventListener("click", handleClick);
268
+ container.removeEventListener("keydown", handleKeyDown);
269
+ container.classList.remove("wo-viewer-container");
270
+ container.style.touchAction = previousTouchAction;
271
+ if (previousTabIndex === null) {
272
+ container.removeAttribute("tabindex");
273
+ }
274
+ else {
275
+ container.setAttribute("tabindex", previousTabIndex);
276
+ }
277
+ },
278
+ };
279
+ rerenderScene(true);
280
+ return api;
281
+ function rerenderScene(resetView) {
282
+ container.innerHTML = renderSceneToSvg(scene);
283
+ svgElement = container.querySelector("svg");
284
+ cameraRoot = container.querySelector(`#worldorbit-camera-root`);
285
+ if (!svgElement || !cameraRoot) {
286
+ throw new Error("Interactive viewer could not locate the rendered SVG camera root.");
287
+ }
288
+ state = resetView
289
+ ? fitViewerState(scene, { ...DEFAULT_VIEWER_STATE }, constraints)
290
+ : sanitizeState(state);
291
+ applySelection(state.selectedObjectId &&
292
+ scene.objects.some((object) => object.objectId === state.selectedObjectId && !object.hidden)
293
+ ? state.selectedObjectId
294
+ : null, false);
295
+ updateCameraTransform();
296
+ options.onViewChange?.({ ...state });
297
+ }
298
+ function updateState(nextState) {
299
+ state = sanitizeState(nextState);
300
+ updateCameraTransform();
301
+ options.onViewChange?.({ ...state });
302
+ }
303
+ function sanitizeState(nextState) {
304
+ return {
305
+ scale: clampValue(nextState.scale, constraints.minScale, constraints.maxScale),
306
+ rotationDeg: normalizeRotation(nextState.rotationDeg),
307
+ translateX: Number.isFinite(nextState.translateX) ? nextState.translateX : state.translateX,
308
+ translateY: Number.isFinite(nextState.translateY) ? nextState.translateY : state.translateY,
309
+ selectedObjectId: nextState.selectedObjectId &&
310
+ scene.objects.some((object) => object.objectId === nextState.selectedObjectId && !object.hidden)
311
+ ? nextState.selectedObjectId
312
+ : null,
313
+ };
314
+ }
315
+ function updateCameraTransform() {
316
+ if (!cameraRoot) {
317
+ return;
318
+ }
319
+ cameraRoot.setAttribute("transform", composeViewerTransform(scene, state));
320
+ }
321
+ function applySelection(objectId, emitCallback = true) {
322
+ if (state.selectedObjectId) {
323
+ const previous = container.querySelector(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`);
324
+ previous?.classList.remove("wo-object-selected");
325
+ }
326
+ state = {
327
+ ...state,
328
+ selectedObjectId: objectId &&
329
+ scene.objects.some((object) => object.objectId === objectId && !object.hidden)
330
+ ? objectId
331
+ : null,
332
+ };
333
+ if (state.selectedObjectId) {
334
+ const next = container.querySelector(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`);
335
+ next?.classList.add("wo-object-selected");
336
+ }
337
+ if (emitCallback) {
338
+ options.onSelectionChange?.(getSelectedObject());
339
+ options.onViewChange?.({ ...state });
340
+ }
341
+ }
342
+ function getSelectedObject() {
343
+ return (scene.objects.find((object) => object.objectId === state.selectedObjectId && !object.hidden) ?? null);
344
+ }
345
+ function getScenePointFromClient(clientX, clientY) {
346
+ if (!svgElement) {
347
+ return {
348
+ x: scene.width / 2,
349
+ y: scene.height / 2,
350
+ };
351
+ }
352
+ const rect = svgElement.getBoundingClientRect();
353
+ if (!rect.width || !rect.height) {
354
+ return {
355
+ x: scene.width / 2,
356
+ y: scene.height / 2,
357
+ };
358
+ }
359
+ return {
360
+ x: ((clientX - rect.left) / rect.width) * scene.width,
361
+ y: ((clientY - rect.top) / rect.height) * scene.height,
362
+ };
363
+ }
364
+ }
365
+ function resolveInitialScene(options, renderOptions) {
366
+ if (options.scene) {
367
+ return options.scene;
368
+ }
369
+ if (options.document) {
370
+ return renderDocumentToScene(options.document, renderOptions);
371
+ }
372
+ if (options.source) {
373
+ return renderDocumentToScene(parseSource(options.source), renderOptions);
374
+ }
375
+ throw new Error("Interactive viewer requires an initial render input.");
376
+ }
377
+ function parseSource(source) {
378
+ const ast = parseWorldOrbit(source);
379
+ const document = normalizeDocument(ast);
380
+ validateDocument(document);
381
+ return document;
382
+ }
383
+ function createTouchGestureState(state, touchPoints) {
384
+ const { center, distance } = getTouchCenterAndDistance(touchPoints);
385
+ return {
386
+ startState: { ...state },
387
+ startCenter: center,
388
+ startDistance: distance,
389
+ };
390
+ }
391
+ function getTouchCenterAndDistance(touchPoints) {
392
+ const points = [...touchPoints.values()];
393
+ if (points.length < 2) {
394
+ return {
395
+ center: points[0] ?? { x: 0, y: 0 },
396
+ distance: 1,
397
+ };
398
+ }
399
+ const [first, second] = points;
400
+ return {
401
+ center: {
402
+ x: (first.x + second.x) / 2,
403
+ y: (first.y + second.y) / 2,
404
+ },
405
+ distance: Math.hypot(second.x - first.x, second.y - first.y),
406
+ };
407
+ }
408
+ function ensureBrowserEnvironment(container) {
409
+ if (typeof window === "undefined" || typeof document === "undefined") {
410
+ throw new Error("createInteractiveViewer can only run in a browser environment.");
411
+ }
412
+ if (!(container instanceof HTMLElement)) {
413
+ throw new Error("Interactive viewer requires an HTMLElement container.");
414
+ }
415
+ }
416
+ function clampValue(value, min, max) {
417
+ return Math.min(Math.max(value, min), max);
418
+ }
419
+ function normalizeRotation(rotationDeg) {
420
+ let normalized = rotationDeg % 360;
421
+ if (normalized > 180) {
422
+ normalized -= 360;
423
+ }
424
+ if (normalized <= -180) {
425
+ normalized += 360;
426
+ }
427
+ return normalized;
428
+ }
429
+ function cssEscape(value) {
430
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
431
+ return CSS.escape(value);
432
+ }
433
+ return value.replace(/["\\]/g, "\\$&");
434
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "worldorbit",
3
+ "version": "2.5.15.2",
4
+ "description": "A text-based DSL and parser pipeline for orbital worldbuilding",
5
+ "type": "module",
6
+ "main": "./dist/unpkg/worldorbit.esm.js",
7
+ "module": "./dist/unpkg/worldorbit.esm.js",
8
+ "browser": "./dist/unpkg/worldorbit.js",
9
+ "unpkg": "./dist/unpkg/worldorbit.min.js",
10
+ "jsdelivr": "./dist/unpkg/worldorbit.min.js",
11
+ "types": "./dist/unpkg/worldorbit.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/unpkg/worldorbit.d.ts",
15
+ "import": "./dist/unpkg/worldorbit.esm.js",
16
+ "default": "./dist/unpkg/worldorbit.js"
17
+ },
18
+ "./min": "./dist/unpkg/worldorbit.min.js",
19
+ "./core": {
20
+ "types": "./packages/core/dist/index.d.ts",
21
+ "import": "./packages/core/dist/index.js"
22
+ },
23
+ "./viewer": {
24
+ "types": "./packages/viewer/dist/index.d.ts",
25
+ "import": "./packages/viewer/dist/index.js"
26
+ },
27
+ "./markdown": {
28
+ "types": "./packages/markdown/dist/index.d.ts",
29
+ "import": "./packages/markdown/dist/index.js"
30
+ },
31
+ "./editor": {
32
+ "types": "./packages/editor/dist/index.d.ts",
33
+ "import": "./packages/editor/dist/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "packages/core/dist",
39
+ "packages/viewer/dist",
40
+ "packages/markdown/dist",
41
+ "packages/editor/dist",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
45
+ "keywords": [
46
+ "worldbuilding",
47
+ "orbit",
48
+ "dsl",
49
+ "parser"
50
+ ],
51
+ "author": "Hanjo Winter",
52
+ "license": "MIT",
53
+ "scripts": {
54
+ "build": "node ./scripts/build.mjs",
55
+ "prepack": "node ./scripts/prepack.mjs",
56
+ "test": "npm run build && node --test test/*.test.js",
57
+ "check": "npm run test"
58
+ },
59
+ "devDependencies": {
60
+ "esbuild-wasm": "^0.27.4",
61
+ "jsdom": "^26.0.0",
62
+ "rehype-stringify": "^10.0.1",
63
+ "remark-parse": "^11.0.0",
64
+ "remark-rehype": "^11.1.2",
65
+ "typescript": "^5.6.0",
66
+ "unified": "^11.0.5"
67
+ }
68
+ }