@needle-tools/engine 5.1.0-alpha.1 → 5.1.0-alpha.3

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 (250) hide show
  1. package/.needle/generated/needle-bindings.gen.d.ts +5 -0
  2. package/CHANGELOG.md +52 -0
  3. package/components.needle.json +1 -1
  4. package/dist/{gltf-progressive-DJBMx-zB.umd.cjs → gltf-progressive-BmblPzFj.umd.cjs} +4 -4
  5. package/dist/{gltf-progressive-BryRjllq.min.js → gltf-progressive-CN_mbb66.min.js} +2 -2
  6. package/dist/{gltf-progressive-Cl167Vjx.js → gltf-progressive-DUlhxdv4.js} +5 -2
  7. package/dist/needle-engine.bundle-C-ixARur.umd.cjs +1733 -0
  8. package/dist/needle-engine.bundle-CHmXdnE1.min.js +1733 -0
  9. package/dist/{needle-engine.bundle-BGyKqxBH.js → needle-engine.bundle-DF01sSGQ.js} +10841 -10434
  10. package/dist/needle-engine.d.ts +244 -54
  11. package/dist/needle-engine.js +541 -536
  12. package/dist/needle-engine.min.js +1 -1
  13. package/dist/needle-engine.umd.cjs +1 -1
  14. package/dist/{postprocessing-B_9sKVU7.min.js → postprocessing-B571qGWR.min.js} +34 -34
  15. package/dist/{postprocessing-WDc9WwI3.js → postprocessing-CfrLAbLX.js} +0 -1
  16. package/dist/{postprocessing-B2wb6pzI.umd.cjs → postprocessing-CiGkAeM9.umd.cjs} +17 -17
  17. package/dist/{vendor-CAcsI0eU.js → vendor-BFrMaK9q.js} +8983 -9136
  18. package/dist/vendor-CJmyOrCq.min.js +1116 -0
  19. package/dist/vendor-DkMW3WY4.umd.cjs +1116 -0
  20. package/lib/engine/api.d.ts +14 -0
  21. package/lib/engine/api.js +4 -0
  22. package/lib/engine/api.js.map +1 -1
  23. package/lib/engine/debug/debug_environment.js +1 -1
  24. package/lib/engine/debug/debug_environment.js.map +1 -1
  25. package/lib/engine/debug/debug_spatial_console.d.ts +2 -0
  26. package/lib/engine/debug/debug_spatial_console.js +10 -7
  27. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  28. package/lib/engine/engine_addressables.d.ts +2 -0
  29. package/lib/engine/engine_addressables.js +6 -3
  30. package/lib/engine/engine_addressables.js.map +1 -1
  31. package/lib/engine/engine_application.js +8 -6
  32. package/lib/engine/engine_application.js.map +1 -1
  33. package/lib/engine/engine_audio.d.ts +68 -0
  34. package/lib/engine/engine_audio.js +172 -0
  35. package/lib/engine/engine_audio.js.map +1 -1
  36. package/lib/engine/engine_components.js +1 -1
  37. package/lib/engine/engine_components.js.map +1 -1
  38. package/lib/engine/engine_constants.js +6 -0
  39. package/lib/engine/engine_constants.js.map +1 -1
  40. package/lib/engine/engine_context.d.ts +33 -7
  41. package/lib/engine/engine_context.js +40 -2
  42. package/lib/engine/engine_context.js.map +1 -1
  43. package/lib/engine/engine_context_registry.js +1 -1
  44. package/lib/engine/engine_context_registry.js.map +1 -1
  45. package/lib/engine/engine_gameobject.js +2 -2
  46. package/lib/engine/engine_gameobject.js.map +1 -1
  47. package/lib/engine/engine_init.js +16 -1
  48. package/lib/engine/engine_init.js.map +1 -1
  49. package/lib/engine/engine_input.d.ts +3 -2
  50. package/lib/engine/engine_input.js +3 -2
  51. package/lib/engine/engine_input.js.map +1 -1
  52. package/lib/engine/engine_license.d.ts +2 -0
  53. package/lib/engine/engine_license.js +25 -15
  54. package/lib/engine/engine_license.js.map +1 -1
  55. package/lib/engine/engine_lifecycle_functions_internal.js +5 -0
  56. package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
  57. package/lib/engine/engine_mainloop_utils.js +5 -2
  58. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  59. package/lib/engine/engine_networking_blob.d.ts +1 -1
  60. package/lib/engine/engine_networking_blob.js +5 -11
  61. package/lib/engine/engine_networking_blob.js.map +1 -1
  62. package/lib/engine/engine_physics_rapier.js +0 -1
  63. package/lib/engine/engine_physics_rapier.js.map +1 -1
  64. package/lib/engine/engine_pmrem.js +2 -2
  65. package/lib/engine/engine_pmrem.js.map +1 -1
  66. package/lib/engine/engine_scenedata.d.ts +32 -0
  67. package/lib/engine/engine_scenedata.js +138 -0
  68. package/lib/engine/engine_scenedata.js.map +1 -0
  69. package/lib/engine/engine_serialization_builtin_serializer.d.ts +10 -16
  70. package/lib/engine/engine_serialization_builtin_serializer.js +55 -41
  71. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  72. package/lib/engine/engine_ssr.d.ts +18 -0
  73. package/lib/engine/engine_ssr.js +40 -0
  74. package/lib/engine/engine_ssr.js.map +1 -0
  75. package/lib/engine/engine_three_utils.d.ts +14 -7
  76. package/lib/engine/engine_three_utils.js +14 -7
  77. package/lib/engine/engine_three_utils.js.map +1 -1
  78. package/lib/engine/engine_types.d.ts +2 -0
  79. package/lib/engine/engine_types.js.map +1 -1
  80. package/lib/engine/engine_utils.js +2 -0
  81. package/lib/engine/engine_utils.js.map +1 -1
  82. package/lib/engine/engine_utils_hash.d.ts +9 -0
  83. package/lib/engine/engine_utils_hash.js +112 -0
  84. package/lib/engine/engine_utils_hash.js.map +1 -0
  85. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  86. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  87. package/lib/engine/webcomponents/jsx.d.ts +51 -0
  88. package/lib/engine/webcomponents/logo-element.d.ts +2 -1
  89. package/lib/engine/webcomponents/logo-element.js +2 -1
  90. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  91. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +4 -4
  92. package/lib/engine/webcomponents/needle menu/needle-menu.js +2 -1
  93. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  94. package/lib/engine/webcomponents/needle-button.d.ts +2 -1
  95. package/lib/engine/webcomponents/needle-button.js +2 -1
  96. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  97. package/lib/engine/webcomponents/needle-engine.d.ts +11 -4
  98. package/lib/engine/webcomponents/needle-engine.js +2 -1
  99. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  100. package/lib/engine/xr/NeedleXRSession.d.ts +3 -2
  101. package/lib/engine/xr/NeedleXRSession.js +51 -15
  102. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  103. package/lib/engine/xr/events.d.ts +1 -1
  104. package/lib/engine/xr/events.js.map +1 -1
  105. package/lib/engine-components/Animation.js +17 -16
  106. package/lib/engine-components/Animation.js.map +1 -1
  107. package/lib/engine-components/AnimatorController.d.ts +2 -0
  108. package/lib/engine-components/AnimatorController.js +4 -1
  109. package/lib/engine-components/AnimatorController.js.map +1 -1
  110. package/lib/engine-components/AudioSource.d.ts +19 -3
  111. package/lib/engine-components/AudioSource.js +121 -68
  112. package/lib/engine-components/AudioSource.js.map +1 -1
  113. package/lib/engine-components/DragControls.d.ts +7 -0
  114. package/lib/engine-components/DragControls.js +19 -0
  115. package/lib/engine-components/DragControls.js.map +1 -1
  116. package/lib/engine-components/Light.d.ts +6 -8
  117. package/lib/engine-components/Light.js +40 -27
  118. package/lib/engine-components/Light.js.map +1 -1
  119. package/lib/engine-components/Networking.d.ts +1 -1
  120. package/lib/engine-components/Networking.js +1 -1
  121. package/lib/engine-components/OrbitControls.js +16 -11
  122. package/lib/engine-components/OrbitControls.js.map +1 -1
  123. package/lib/engine-components/ReflectionProbe.js +2 -0
  124. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  125. package/lib/engine-components/RigidBody.js +3 -3
  126. package/lib/engine-components/RigidBody.js.map +1 -1
  127. package/lib/engine-components/SceneSwitcher.js +2 -0
  128. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  129. package/lib/engine-components/SeeThrough.js +2 -2
  130. package/lib/engine-components/SeeThrough.js.map +1 -1
  131. package/lib/engine-components/api.d.ts +1 -1
  132. package/lib/engine-components/api.js +1 -1
  133. package/lib/engine-components/api.js.map +1 -1
  134. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  135. package/lib/engine-components/postprocessing/Effects/Sharpening.js +1 -2
  136. package/lib/engine-components/postprocessing/Effects/Sharpening.js.map +1 -1
  137. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  138. package/lib/engine-components/postprocessing/PostProcessingHandler.js +5 -6
  139. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  140. package/lib/engine-components/postprocessing/VolumeParameter.d.ts +2 -0
  141. package/lib/engine-components/postprocessing/VolumeParameter.js +4 -1
  142. package/lib/engine-components/postprocessing/VolumeParameter.js.map +1 -1
  143. package/lib/engine-components/ui/Canvas.d.ts +1 -1
  144. package/lib/engine-components/ui/Canvas.js +2 -8
  145. package/lib/engine-components/ui/Canvas.js.map +1 -1
  146. package/lib/engine-components/ui/Text.d.ts +1 -0
  147. package/lib/engine-components/ui/Text.js +10 -7
  148. package/lib/engine-components/ui/Text.js.map +1 -1
  149. package/lib/engine-components/web/CursorFollow.js +21 -12
  150. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  151. package/lib/engine-components/web/ScrollFollow.d.ts +0 -1
  152. package/lib/engine-components/web/ScrollFollow.js +3 -2
  153. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  154. package/lib/engine-components/webxr/WebXRImageTracking.js +4 -0
  155. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  156. package/lib/needle-engine.d.ts +2 -0
  157. package/lib/needle-engine.js +2 -0
  158. package/lib/needle-engine.js.map +1 -1
  159. package/package.json +6 -4
  160. package/plugins/dts-generator/dts.codegen.js +334 -0
  161. package/plugins/dts-generator/dts.scan.js +99 -0
  162. package/plugins/dts-generator/dts.writer.js +59 -0
  163. package/plugins/dts-generator/glb.discovery.js +279 -0
  164. package/plugins/dts-generator/glb.extractor.js +215 -0
  165. package/plugins/dts-generator/glb.reader.js +167 -0
  166. package/plugins/dts-generator/index.js +36 -0
  167. package/plugins/dts-generator/manifest.types.js +174 -0
  168. package/plugins/types/index.d.ts +2 -1
  169. package/plugins/types/needle-bindings.d.ts +30 -0
  170. package/plugins/types/userconfig.d.ts +21 -2
  171. package/plugins/vite/asap.js +18 -9
  172. package/plugins/vite/dependencies.js +29 -0
  173. package/plugins/vite/dependency-watcher.d.ts +2 -2
  174. package/plugins/vite/dependency-watcher.js +3 -4
  175. package/plugins/vite/drop.d.ts +2 -2
  176. package/plugins/vite/drop.js +3 -4
  177. package/plugins/vite/dts-generator.d.ts +7 -0
  178. package/plugins/vite/dts-generator.js +191 -0
  179. package/plugins/vite/index.d.ts +10 -3
  180. package/plugins/vite/index.js +27 -10
  181. package/plugins/vite/local-files-core.js +3 -3
  182. package/plugins/vite/local-files-utils.d.ts +3 -1
  183. package/plugins/vite/local-files-utils.js +29 -5
  184. package/plugins/vite/logging.js +2 -2
  185. package/plugins/vite/meta.js +4 -2
  186. package/plugins/vite/poster.d.ts +2 -2
  187. package/plugins/vite/poster.js +3 -5
  188. package/plugins/vite/reload.d.ts +2 -2
  189. package/plugins/vite/reload.js +23 -22
  190. package/src/engine/api.ts +18 -1
  191. package/src/engine/debug/debug_environment.ts +1 -1
  192. package/src/engine/debug/debug_spatial_console.ts +10 -7
  193. package/src/engine/engine_addressables.ts +6 -3
  194. package/src/engine/engine_application.ts +8 -6
  195. package/src/engine/engine_audio.ts +184 -0
  196. package/src/engine/engine_components.ts +1 -1
  197. package/src/engine/engine_constants.ts +11 -6
  198. package/src/engine/engine_context.ts +50 -7
  199. package/src/engine/engine_context_registry.ts +1 -1
  200. package/src/engine/engine_gameobject.ts +2 -2
  201. package/src/engine/engine_init.ts +15 -1
  202. package/src/engine/engine_input.ts +3 -2
  203. package/src/engine/engine_license.ts +23 -19
  204. package/src/engine/engine_lifecycle_functions_internal.ts +7 -0
  205. package/src/engine/engine_mainloop_utils.ts +5 -2
  206. package/src/engine/engine_networking_blob.ts +5 -11
  207. package/src/engine/engine_physics_rapier.ts +0 -3
  208. package/src/engine/engine_pmrem.ts +3 -3
  209. package/src/engine/engine_scenedata.ts +136 -0
  210. package/src/engine/engine_serialization_builtin_serializer.ts +63 -46
  211. package/src/engine/engine_ssr.ts +48 -0
  212. package/src/engine/engine_three_utils.ts +15 -7
  213. package/src/engine/engine_types.ts +2 -0
  214. package/src/engine/engine_utils.ts +1 -0
  215. package/src/engine/engine_utils_hash.ts +65 -0
  216. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  217. package/src/engine/webcomponents/jsx.d.ts +51 -0
  218. package/src/engine/webcomponents/logo-element.ts +3 -1
  219. package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -2
  220. package/src/engine/webcomponents/needle-button.ts +3 -1
  221. package/src/engine/webcomponents/needle-engine.ts +12 -4
  222. package/src/engine/xr/NeedleXRSession.ts +49 -14
  223. package/src/engine/xr/events.ts +1 -1
  224. package/src/engine-components/Animation.ts +19 -16
  225. package/src/engine-components/AnimatorController.ts +4 -1
  226. package/src/engine-components/AudioSource.ts +130 -79
  227. package/src/engine-components/DragControls.ts +18 -2
  228. package/src/engine-components/Light.ts +40 -26
  229. package/src/engine-components/Networking.ts +1 -1
  230. package/src/engine-components/OrbitControls.ts +18 -9
  231. package/src/engine-components/ReflectionProbe.ts +2 -0
  232. package/src/engine-components/RigidBody.ts +3 -3
  233. package/src/engine-components/SceneSwitcher.ts +1 -0
  234. package/src/engine-components/SeeThrough.ts +2 -2
  235. package/src/engine-components/api.ts +1 -1
  236. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +1 -1
  237. package/src/engine-components/postprocessing/Effects/Sharpening.ts +1 -2
  238. package/src/engine-components/postprocessing/PostProcessingHandler.ts +4 -8
  239. package/src/engine-components/postprocessing/VolumeParameter.ts +4 -1
  240. package/src/engine-components/ui/Canvas.ts +2 -8
  241. package/src/engine-components/ui/Text.ts +12 -8
  242. package/src/engine-components/web/CursorFollow.ts +21 -13
  243. package/src/engine-components/web/ScrollFollow.ts +2 -2
  244. package/src/engine-components/webxr/WebXRImageTracking.ts +2 -0
  245. package/src/needle-engine.ts +3 -0
  246. package/src/vite-env.d.ts +16 -0
  247. package/dist/needle-engine.bundle-CiYtOO2O.min.js +0 -1732
  248. package/dist/needle-engine.bundle-DzVx9Z8D.umd.cjs +0 -1732
  249. package/dist/vendor-CEM38hLE.umd.cjs +0 -1116
  250. package/dist/vendor-HRlxIBga.min.js +0 -1116
@@ -1,11 +1,9 @@
1
- import * as _md5 from "md5";
2
- // CJS interop: md5 may appear as { default: fn } or fn depending on bundler
3
- const md5 = typeof _md5 === "function" ? _md5 : (_md5 as any).default;
4
1
  import { FileLoader } from "three";
5
2
 
6
3
  import { showBalloonWarning } from "./debug/index.js";
7
4
  import { hasCommercialLicense } from "./engine_license.js";
8
5
  import { delay } from "./engine_utils.js";
6
+ import { md5AsBytes, md5Hex, sha256Base64 } from "./engine_utils_hash.js";
9
7
 
10
8
 
11
9
  export namespace BlobStorage {
@@ -22,21 +20,17 @@ export namespace BlobStorage {
22
20
  /**
23
21
  * Generates an md5 hash from a given buffer
24
22
  * @param buffer The buffer to hash
25
- * @returns The md5 hash
23
+ * @returns The md5 hash as a hex string
26
24
  */
27
25
  export function hashMD5(buffer: ArrayBuffer): string {
28
- return md5(new Uint8Array(buffer))
26
+ return md5Hex(new Uint8Array(buffer));
29
27
  }
30
28
  export function hashMD5_Base64(buffer: ArrayBuffer): string {
31
- const bytes = md5(new Uint8Array(buffer), { encoding: "binary", asBytes: true });
29
+ const bytes = md5AsBytes(new Uint8Array(buffer));
32
30
  return btoa(String.fromCharCode(...bytes));
33
31
  }
34
32
  export function hashSha256(buffer: ArrayBuffer): Promise<string> {
35
- const bytes = new Uint8Array(buffer);
36
- const hash = crypto.subtle.digest('SHA-256', bytes).then(res => {
37
- return btoa(String.fromCharCode(...new Uint8Array(res)));
38
- })
39
- return hash;
33
+ return sha256Base64(buffer);
40
34
  }
41
35
 
42
36
  export type Upload_Result = {
@@ -44,9 +44,6 @@ const $bodyKey = Symbol("physics body");
44
44
  const $colliderRigidbody = Symbol("rigidbody");
45
45
 
46
46
 
47
- declare const NEEDLE_USE_RAPIER: boolean;
48
- globalThis["NEEDLE_USE_RAPIER"] = globalThis["NEEDLE_USE_RAPIER"] !== undefined ? globalThis["NEEDLE_USE_RAPIER"] : true;
49
-
50
47
  /** Register the Rapier physics backend. Called from {@link initEngine}
51
48
  * to ensure it runs regardless of tree-shaking. */
52
49
  export function initPhysics() {
@@ -1,8 +1,8 @@
1
1
  import { createLoaders } from "@needle-tools/gltf-progressive";
2
2
  import { CubeUVReflectionMapping, EquirectangularRefractionMapping, SRGBColorSpace, Texture, TextureLoader, WebGLRenderer } from "three";
3
- import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader";
4
- import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader";
5
- import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
3
+ import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
4
+ import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
5
+ import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
6
6
 
7
7
  const running: Map<string, Promise<Texture | null>> = new Map();
8
8
 
@@ -0,0 +1,136 @@
1
+ import type { SceneData } from "needle-bindings";
2
+ export type { SceneData };
3
+ import { isDevEnvironment } from "./debug/index.js";
4
+ import { getComponent } from "./engine_components.js";
5
+ import { ContextRegistry } from "./engine_context_registry.js";
6
+ import type { IContext } from "./engine_types.js";
7
+ import { TypeStore } from "./engine_typestore.js";
8
+
9
+ /**
10
+ * Quick access to the current Needle Engine context from anywhere — no need to pass `ctx` around.
11
+ * Use it in React/Svelte/Vue components, button handlers, or plain JavaScript.
12
+ *
13
+ * Safe to import at module level, including in SSR environments.
14
+ * For pages with multiple `<needle-engine>` elements, use `ctx` directly instead.
15
+ *
16
+ * @experimental This API may change in future releases.
17
+ *
18
+ * @example
19
+ * import { needle } from "@needle-tools/engine";
20
+ * button.onclick = () => {
21
+ * needle.sceneData.Camera.OrbitControls.autoRotate = true;
22
+ * };
23
+ */
24
+ export const needle: IContext = new Proxy({} as IContext, {
25
+ get(_target, prop: string) {
26
+ if (prop === "then") return undefined; // not a Promise
27
+ const ctx = ContextRegistry.Current;
28
+ if (!ctx) {
29
+ const fn = isDevEnvironment() ? console.error : console.warn;
30
+ fn(`[needle] needle.${prop} was accessed before the scene started. Use "needle" inside event handlers or callbacks, not at module top-level. For setup code use: onStart(ctx => { ... })`);
31
+ return makeErrorProxy(`needle not ready — scene hasn't started yet`);
32
+ }
33
+ const val = (ctx as any)[prop];
34
+ return typeof val === "function" ? val.bind(ctx) : val;
35
+ },
36
+ set(_target, prop: string, value: unknown) {
37
+ const ctx = ContextRegistry.Current;
38
+ if (!ctx) {
39
+ const fn = isDevEnvironment() ? console.error : console.warn;
40
+ fn(`[needle] needle.${prop} was set before the scene started. Use "needle" inside event handlers or callbacks, not at module top-level. For setup code use: onStart(ctx => { ... })`);
41
+ return true;
42
+ }
43
+ (ctx as any)[prop] = value;
44
+ return true;
45
+ },
46
+ });
47
+
48
+ const cache = new WeakMap<IContext, SceneData>();
49
+
50
+ /**
51
+ * Returns a proxy that silently absorbs any property get/set and logs a
52
+ * developer-friendly warning. Used when a node or component lookup fails so
53
+ * that chained access like `ctx.sceneData.Foo.Bar.baz = 1` never throws.
54
+ */
55
+ function makeErrorProxy(message: string): object {
56
+ const handler: ProxyHandler<object> = {
57
+ get(_t, prop: string) {
58
+ if (prop === "then") return undefined; // not a Promise
59
+ const fn = isDevEnvironment() ? console.error : console.warn;
60
+ fn(`[SceneData] ${message}`);
61
+ return makeErrorProxy(message);
62
+ },
63
+ set(_t, prop: string) {
64
+ const fn = isDevEnvironment() ? console.error : console.warn;
65
+ fn(`[SceneData] ${message} (tried to set "${prop}")`);
66
+ return true; // suppress TypeError
67
+ },
68
+ };
69
+ return new Proxy({}, handler);
70
+ }
71
+
72
+ /**
73
+ * Returns a proxy for a scene node that exposes `$object`, `$components`,
74
+ * and child nodes as nested properties.
75
+ */
76
+ function makeNodeProxy(ctx: IContext, node: import("three").Object3D): object {
77
+ return new Proxy({}, {
78
+ get(_t, prop: string) {
79
+ if (prop === "$object") return node;
80
+ if (prop === "$components") {
81
+ return new Proxy({}, {
82
+ get(_t2, compName: string) {
83
+ if (compName === "then") return undefined;
84
+ const ctor = TypeStore.get(compName);
85
+ if (!ctor) return makeErrorProxy(`Component type "${compName}" not registered (node "${node.name}")`);
86
+ const comp = getComponent(node, ctor);
87
+ if (!comp) return makeErrorProxy(`Component "${compName}" not found on node "${node.name}"`);
88
+ return comp;
89
+ }
90
+ });
91
+ }
92
+ if (prop === "then") return undefined; // not a Promise
93
+ // Child node lookup by name
94
+ const child = node.children.find(c => c.name === prop) ?? null;
95
+ if (!child) {
96
+ const fn = isDevEnvironment() ? console.error : console.warn;
97
+ fn(`[SceneData] "${prop}" is not a child of "${node.name}". Use .$object to get the Three.js object or .$components.Name to access a component.`);
98
+ return makeErrorProxy(`"${prop}" not found on node "${node.name}"`);
99
+ }
100
+ return makeNodeProxy(ctx, child);
101
+ }
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Returns a lazily-resolved proxy typed as {@link SceneData}.
107
+ * The proxy is cached per context — each context gets exactly one instance.
108
+ *
109
+ * Shape mirrors the generated `needle-bindings.gen.d.ts`:
110
+ * ctx.sceneData.MyGlb.Camera.$components.OrbitControls.autoRotate = true;
111
+ * ctx.sceneData.MyGlb.Camera.$object // → THREE.Camera
112
+ * ctx.sceneData.MyGlb.UI.Button.$components.Button // → Needle Button component
113
+ *
114
+ * GLB name is ignored at runtime (scene is already loaded).
115
+ * Node lookup starts at the scene root.
116
+ */
117
+ export function getSceneData(ctx: IContext): SceneData {
118
+ let proxy = cache.get(ctx);
119
+ if (!proxy) {
120
+ proxy = new Proxy({} as SceneData, {
121
+ get(_target, _glbName: string) {
122
+ // GLB name level — ignored at runtime, return node-name proxy
123
+ return new Proxy({}, {
124
+ get(_target, nodeName: string) {
125
+ if (nodeName === "then") return undefined;
126
+ const node = ctx.scene.getObjectByName(nodeName) ?? null;
127
+ if (!node) return makeErrorProxy(`Node "${nodeName}" not found in scene`);
128
+ return makeNodeProxy(ctx, node);
129
+ }
130
+ });
131
+ }
132
+ });
133
+ cache.set(ctx, proxy);
134
+ }
135
+ return proxy;
136
+ }
@@ -4,6 +4,7 @@ import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../eng
4
4
  import { Behaviour, Component, GameObject } from "../engine-components/Component.js";
5
5
  import { CallInfo, EventList } from "../engine-components/EventList.js";
6
6
  import { AssetReference } from "./engine_addressables.js";
7
+ import { AudioClip } from "./engine_audio.js";
7
8
  import { debugExtension } from "./engine_default_parameters.js";
8
9
  import { SerializationContext, TypeSerializer } from "./engine_serialization_core.js";
9
10
  import { RenderTexture } from "./engine_texture.js";
@@ -11,25 +12,7 @@ import { IComponent } from "./engine_types.js";
11
12
  import { resolveUrl } from "./engine_utils.js";
12
13
  import { RGBAColor } from "./js-extensions/index.js";
13
14
 
14
- // export class SourcePath {
15
- // src?:string
16
- // };
17
-
18
- // class SourcePathSerializer extends TypeSerializer{
19
- // constructor(){
20
- // super(SourcePath);
21
- // }
22
- // onDeserialize(data: any, _context: SerializationContext) {
23
- // if(data.src && typeof data.src === "string"){
24
- // return data.src;
25
- // }
26
- // }
27
- // onSerialize(_data: any, _context: SerializationContext) {
28
-
29
- // }
30
- // }
31
- // new SourcePathSerializer();
32
-
15
+ // #region Color
33
16
  class ColorSerializer extends TypeSerializer {
34
17
  constructor() {
35
18
  super([Color, RGBAColor], "ColorSerializer")
@@ -52,7 +35,8 @@ class ColorSerializer extends TypeSerializer {
52
35
  return { r: data.r, g: data.g, b: data.b }
53
36
  }
54
37
  }
55
- export const colorSerializer = new ColorSerializer();
38
+
39
+ // #region Euler
56
40
 
57
41
  class EulerSerializer extends TypeSerializer {
58
42
  constructor() {
@@ -72,12 +56,13 @@ class EulerSerializer extends TypeSerializer {
72
56
  return { x: data.x, y: data.y, z: data.z, order: data.order };
73
57
  }
74
58
  }
75
- export const euler = new EulerSerializer();
76
59
 
77
60
  declare type ObjectData = {
78
61
  node?: number;
79
62
  guid?: string;
80
63
  }
64
+
65
+ // #region ObjectSerializer
81
66
  class ObjectSerializer extends TypeSerializer {
82
67
  constructor() {
83
68
  super(Object3D, "ObjectSerializer");
@@ -107,16 +92,6 @@ class ObjectSerializer extends TypeSerializer {
107
92
  console.warn(`Wrong usage of @serializable detected in your script \"${scriptname}\"\n\nIt looks like you used @serializable(Object3D) or @serializable(GameObject) for a prefab or scene reference which is exported to a separate glTF file.\n\nTo fix this please change your code to:\n\n@serializable(AssetReference)\n${context.path}! : AssetReference;\n\0`);
108
93
  }
109
94
  // ACTUALLY: this is already handled by the extension_utils where we resolve json pointers recursively
110
- // if(data.startsWith("/nodes/")){
111
- // const node = parseInt(data.substring("/nodes/".length));
112
- // if (context.nodeToObject) {
113
- // const res = context.nodeToObject[node];
114
- // if (debugExtension)
115
- // console.log("Deserialized object reference?", data, res, context?.nodeToObject);
116
- // if (!res) console.warn("Did not find node: " + data, context.nodeToObject, context.object);
117
- // return res;
118
- // }
119
- // }
120
95
  return undefined;
121
96
  }
122
97
 
@@ -166,9 +141,8 @@ class ObjectSerializer extends TypeSerializer {
166
141
  return undefined;
167
142
  }
168
143
  }
169
- export const objectSerializer = new ObjectSerializer();
170
-
171
144
 
145
+ // #region ComponentSerializer
172
146
  class ComponentSerializer extends TypeSerializer {
173
147
 
174
148
  constructor() {
@@ -241,7 +215,6 @@ class ComponentSerializer extends TypeSerializer {
241
215
  }
242
216
  }
243
217
  }
244
- export const componentSerializer = new ComponentSerializer();
245
218
 
246
219
 
247
220
  declare class EventListData {
@@ -258,6 +231,7 @@ declare type EventListCall = {
258
231
 
259
232
  const $eventListDebugInfo = Symbol("eventListDebugInfo");
260
233
 
234
+ // #region EventListSerializer
261
235
  class EventListSerializer extends TypeSerializer {
262
236
  constructor() {
263
237
  super([EventList]);
@@ -396,22 +370,14 @@ class EventListSerializer extends TypeSerializer {
396
370
  // };
397
371
  // }
398
372
  }
399
- export const eventListSerializer = new EventListSerializer();
400
373
 
401
374
 
402
375
  /** Map<Clone, Original> texture. This is used for compressed textures (or when the GLTFLoader is cloning RenderTextures)
403
376
  * It's a weak map so we don't have to worry about memory leaks
404
377
  */
405
378
  const cloneOriginalMap = new WeakMap<Texture, Texture>();
406
- const textureClone = Texture.prototype.clone;
407
- Texture.prototype.clone = function () {
408
- const clone = textureClone.call(this);
409
- if (!cloneOriginalMap.has(clone)) {
410
- cloneOriginalMap.set(clone, this);
411
- }
412
- return clone;
413
- }
414
379
 
380
+ // #region RenderTextureSerializer
415
381
  export class RenderTextureSerializer extends TypeSerializer {
416
382
  constructor() {
417
383
  super([RenderTexture, WebGLRenderTarget]);
@@ -452,9 +418,8 @@ export class RenderTextureSerializer extends TypeSerializer {
452
418
  return undefined;
453
419
  }
454
420
  }
455
- new RenderTextureSerializer();
456
-
457
421
 
422
+ // #region UriSerializer
458
423
  export class UriSerializer extends TypeSerializer {
459
424
  constructor() {
460
425
  super([URL]);
@@ -471,4 +436,56 @@ export class UriSerializer extends TypeSerializer {
471
436
  return undefined;
472
437
  }
473
438
  }
474
- new UriSerializer();
439
+
440
+ // #region AudioClipSerializer
441
+ class AudioClipSerializer extends TypeSerializer {
442
+ constructor() {
443
+ super([AudioClip]);
444
+ }
445
+
446
+ onSerialize(_data: AudioClip, _context: SerializationContext) {
447
+ return null;
448
+ }
449
+
450
+ onDeserialize(data: string, context: SerializationContext) {
451
+ if (typeof data === "string" && data.length > 0) {
452
+ const url = resolveUrl(context.gltfId, data);
453
+ if (url) return new AudioClip(url);
454
+ }
455
+ return undefined;
456
+ }
457
+ }
458
+
459
+
460
+ // #region Init serializer
461
+ // Module-level references used by EventListSerializer internally
462
+ export let colorSerializer: ColorSerializer;
463
+ export let objectSerializer: ObjectSerializer;
464
+ export let componentSerializer: ComponentSerializer;
465
+ export let eventListSerializer: EventListSerializer;
466
+
467
+ /** Register all builtin serializers and prototype patches.
468
+ * Must be called from {@link initEngine} so the registrations survive tree-shaking
469
+ * when the package declares `sideEffects: false`.
470
+ */
471
+ export function initBuiltinSerializers() {
472
+ // Prototype patches
473
+ const textureClone = Texture.prototype.clone;
474
+ Texture.prototype.clone = function () {
475
+ const clone = textureClone.call(this);
476
+ if (!cloneOriginalMap.has(clone)) {
477
+ cloneOriginalMap.set(clone, this);
478
+ }
479
+ return clone;
480
+ }
481
+
482
+ // Register all serializers
483
+ colorSerializer = new ColorSerializer();
484
+ new EulerSerializer();
485
+ objectSerializer = new ObjectSerializer();
486
+ componentSerializer = new ComponentSerializer();
487
+ eventListSerializer = new EventListSerializer();
488
+ new RenderTextureSerializer();
489
+ new UriSerializer();
490
+ new AudioClipSerializer();
491
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * SSR-safe base classes for browser globals that are not available in Node/SSR environments.
3
+ *
4
+ * Use these instead of extending browser globals directly so that class definitions
5
+ * do not throw a ReferenceError at module evaluation time in SSR/Node contexts
6
+ * (SvelteKit, Next.js, etc.).
7
+ *
8
+ * In browser environments each constant is the real global; in SSR it falls back
9
+ * to a plain empty class so that `class Foo extends HTMLElementBase` is valid.
10
+ */
11
+
12
+
13
+ /** True when running in an SSR/Node environment (no browser globals). */
14
+ export const SSR: boolean = typeof window === "undefined";
15
+
16
+ /** SSR-safe base class for web components. */
17
+ export const HTMLElementBase: typeof HTMLElement =
18
+ typeof HTMLElement !== "undefined" ? HTMLElement : (class { } as unknown as typeof HTMLElement);
19
+
20
+ /** SSR-safe base class for pointer events. */
21
+ export const PointerEventBase: typeof PointerEvent =
22
+ typeof PointerEvent !== "undefined" ? PointerEvent : (class { } as unknown as typeof PointerEvent);
23
+
24
+ /** SSR-safe base class for keyboard events. */
25
+ export const KeyboardEventBase: typeof KeyboardEvent =
26
+ typeof KeyboardEvent !== "undefined" ? KeyboardEvent : (class { } as unknown as typeof KeyboardEvent);
27
+
28
+
29
+
30
+
31
+ // #region minimal polyfills
32
+ // Three.js FileLoader uses ProgressEvent in fetch stream callbacks.
33
+ // It doesn't exist in Node — install a minimal stub so SSR doesn't crash.
34
+ if (typeof globalThis.ProgressEvent === "undefined") {
35
+ (globalThis as any).ProgressEvent = class ProgressEvent {
36
+ readonly type: string;
37
+ readonly lengthComputable: boolean;
38
+ readonly loaded: number;
39
+ readonly total: number;
40
+ constructor(type: string, init?: { lengthComputable?: boolean; loaded?: number; total?: number }) {
41
+ this.type = type;
42
+ this.lengthComputable = init?.lengthComputable ?? false;
43
+ this.loaded = init?.loaded ?? 0;
44
+ this.total = init?.total ?? 0;
45
+ }
46
+ };
47
+ }
48
+ // #endregion
@@ -493,13 +493,21 @@ void main(){
493
493
  * Utility class to perform various graphics operations like copying textures to canvas
494
494
  */
495
495
  export class Graphics {
496
- private static readonly planeGeometry = new PlaneGeometry(2, 2, 1, 1);
497
- private static readonly renderer: WebGLRenderer = new WebGLRenderer({ antialias: false, alpha: true });
498
- private static readonly perspectiveCam = new PerspectiveCamera();
499
- private static readonly orthographicCam = new OrthographicCamera();
500
- private static readonly scene = new Scene();
501
- private static readonly blitMaterial: BlitMaterial = new BlitMaterial();
502
- private static readonly mesh: Mesh = new Mesh(Graphics.planeGeometry, Graphics.blitMaterial);
496
+ private static _planeGeometry: PlaneGeometry | undefined;
497
+ private static _renderer: WebGLRenderer | undefined;
498
+ private static _perspectiveCam: PerspectiveCamera | undefined;
499
+ private static _orthographicCam: OrthographicCamera | undefined;
500
+ private static _scene: Scene | undefined;
501
+ private static _blitMaterial: BlitMaterial | undefined;
502
+ private static _mesh: Mesh | undefined;
503
+
504
+ private static get planeGeometry() { return this._planeGeometry ??= new PlaneGeometry(2, 2, 1, 1); }
505
+ private static get renderer() { return this._renderer ??= new WebGLRenderer({ antialias: false, alpha: true }); }
506
+ private static get perspectiveCam() { return this._perspectiveCam ??= new PerspectiveCamera(); }
507
+ private static get orthographicCam() { return this._orthographicCam ??= new OrthographicCamera(); }
508
+ private static get scene() { return this._scene ??= new Scene(); }
509
+ private static get blitMaterial() { return this._blitMaterial ??= new BlitMaterial(); }
510
+ private static get mesh() { return this._mesh ??= new Mesh(Graphics.planeGeometry, Graphics.blitMaterial); }
503
511
 
504
512
 
505
513
  /**
@@ -258,6 +258,8 @@ export declare interface ICameraController {
258
258
  export declare interface ILight extends IComponent {
259
259
  intensity: number;
260
260
  color: Color;
261
+ /** The type of light as a lowercase string: `"directional"`, `"point"`, `"spot"` */
262
+ readonly type: "directional" | "point" | "spot";
261
263
  }
262
264
 
263
265
  export declare interface ISharedMaterials {
@@ -597,6 +597,7 @@ export namespace DeviceUtilities {
597
597
  /** @returns `true` for MacOS or Windows devices. `false` for Hololens and other headsets. */
598
598
  export function isDesktop() {
599
599
  if (_isDesktop !== undefined) return _isDesktop;
600
+ if (typeof window === "undefined") return _isDesktop = false;
600
601
  const ua = window.navigator.userAgent;
601
602
  const standalone = /Windows|MacOS|Mac OS/.test(ua);
602
603
  const isHololens = /Windows NT/.test(ua) && /Edg/.test(ua) && !/Win64/.test(ua);
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pure-JS hashing utilities — no external dependencies, works in browser and Node/SSR.
3
+ */
4
+
5
+ // MD5 based on the public-domain RSA Data Security MD5 algorithm.
6
+ function md5Bytes(input: Uint8Array): number[] {
7
+ function safeAdd(x: number, y: number) { const lsw = (x & 0xffff) + (y & 0xffff); return ((x >> 16) + (y >> 16) + (lsw >> 16)) << 16 | lsw & 0xffff; }
8
+ function bitRotateLeft(num: number, cnt: number) { return num << cnt | num >>> 32 - cnt; }
9
+ function md5cmn(q: number, a: number, b: number, x: number, s: number, t: number) { return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b); }
10
+ function md5ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { return md5cmn(b & c | ~b & d, a, b, x, s, t); }
11
+ function md5gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { return md5cmn(b & d | c & ~d, a, b, x, s, t); }
12
+ function md5hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { return md5cmn(b ^ c ^ d, a, b, x, s, t); }
13
+ function md5ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number) { return md5cmn(c ^ (b | ~d), a, b, x, s, t); }
14
+
15
+ const len8 = input.length;
16
+ const padded = new Uint8Array((Math.ceil((len8 + 9) / 64) * 64));
17
+ padded.set(input);
18
+ padded[len8] = 0x80;
19
+ const view = new DataView(padded.buffer);
20
+ view.setUint32(padded.length - 8, len8 << 3, true);
21
+ view.setUint32(padded.length - 4, (len8 / 0x20000000) | 0, true);
22
+
23
+ let a = 0x67452301, b = 0xefcdab89, c = 0x98badcfe, d = 0x10325476;
24
+ for (let i = 0; i < padded.length >> 2; i += 16) {
25
+ const M = (j: number) => view.getInt32((i + j) * 4, true);
26
+ const [oa, ob, oc, od] = [a, b, c, d];
27
+ a = md5ff(a,b,c,d,M(0),7,-680876936); d=md5ff(d,a,b,c,M(1),12,-389564586); c=md5ff(c,d,a,b,M(2),17,606105819); b=md5ff(b,c,d,a,M(3),22,-1044525330);
28
+ a=md5ff(a,b,c,d,M(4),7,-176418897); d=md5ff(d,a,b,c,M(5),12,1200080426); c=md5ff(c,d,a,b,M(6),17,-1473231341); b=md5ff(b,c,d,a,M(7),22,-45705983);
29
+ a=md5ff(a,b,c,d,M(8),7,1770035416); d=md5ff(d,a,b,c,M(9),12,-1958414417); c=md5ff(c,d,a,b,M(10),17,-42063); b=md5ff(b,c,d,a,M(11),22,-1990404162);
30
+ a=md5ff(a,b,c,d,M(12),7,1804603682); d=md5ff(d,a,b,c,M(13),12,-40341101); c=md5ff(c,d,a,b,M(14),17,-1502002290); b=md5ff(b,c,d,a,M(15),22,1236535329);
31
+ a=md5gg(a,b,c,d,M(1),5,-165796510); d=md5gg(d,a,b,c,M(6),9,-1069501632); c=md5gg(c,d,a,b,M(11),14,643717713); b=md5gg(b,c,d,a,M(0),20,-373897302);
32
+ a=md5gg(a,b,c,d,M(5),5,-701558691); d=md5gg(d,a,b,c,M(10),9,38016083); c=md5gg(c,d,a,b,M(15),14,-660478335); b=md5gg(b,c,d,a,M(4),20,-405537848);
33
+ a=md5gg(a,b,c,d,M(9),5,568446438); d=md5gg(d,a,b,c,M(14),9,-1019803690); c=md5gg(c,d,a,b,M(3),14,-187363961); b=md5gg(b,c,d,a,M(8),20,1163531501);
34
+ a=md5gg(a,b,c,d,M(13),5,-1444681467); d=md5gg(d,a,b,c,M(2),9,-51403784); c=md5gg(c,d,a,b,M(7),14,1735328473); b=md5gg(b,c,d,a,M(12),20,-1926607734);
35
+ a=md5hh(a,b,c,d,M(5),4,-378558); d=md5hh(d,a,b,c,M(8),11,-2022574463); c=md5hh(c,d,a,b,M(11),16,1839030562); b=md5hh(b,c,d,a,M(14),23,-35309556);
36
+ a=md5hh(a,b,c,d,M(1),4,-1530992060); d=md5hh(d,a,b,c,M(4),11,1272893353); c=md5hh(c,d,a,b,M(7),16,-155497632); b=md5hh(b,c,d,a,M(10),23,-1094730640);
37
+ a=md5hh(a,b,c,d,M(13),4,681279174); d=md5hh(d,a,b,c,M(0),11,-358537222); c=md5hh(c,d,a,b,M(3),16,-722521979); b=md5hh(b,c,d,a,M(6),23,76029189);
38
+ a=md5hh(a,b,c,d,M(9),4,-640364487); d=md5hh(d,a,b,c,M(12),11,-421815835); c=md5hh(c,d,a,b,M(15),16,530742520); b=md5hh(b,c,d,a,M(2),23,-995338651);
39
+ a=md5ii(a,b,c,d,M(0),6,-198630844); d=md5ii(d,a,b,c,M(7),10,1126891415); c=md5ii(c,d,a,b,M(14),15,-1416354905); b=md5ii(b,c,d,a,M(5),21,-57434055);
40
+ a=md5ii(a,b,c,d,M(12),6,1700485571); d=md5ii(d,a,b,c,M(3),10,-1894986606); c=md5ii(c,d,a,b,M(10),15,-1051523); b=md5ii(b,c,d,a,M(1),21,-2054922799);
41
+ a=md5ii(a,b,c,d,M(8),6,1873313359); d=md5ii(d,a,b,c,M(15),10,-30611744); c=md5ii(c,d,a,b,M(6),15,-1560198380); b=md5ii(b,c,d,a,M(13),21,1309151649);
42
+ a=md5ii(a,b,c,d,M(4),6,-145523070); d=md5ii(d,a,b,c,M(11),10,-1120210379); c=md5ii(c,d,a,b,M(2),15,718787259); b=md5ii(b,c,d,a,M(9),21,-343485551);
43
+ a=safeAdd(a,oa); b=safeAdd(b,ob); c=safeAdd(c,oc); d=safeAdd(d,od);
44
+ }
45
+ const result = new DataView(new ArrayBuffer(16));
46
+ result.setUint32(0, a, true); result.setUint32(4, b, true); result.setUint32(8, c, true); result.setUint32(12, d, true);
47
+ return Array.from(new Uint8Array(result.buffer));
48
+ }
49
+
50
+ /** Returns the MD5 hash of the given bytes as a lowercase hex string. */
51
+ export function md5Hex(input: Uint8Array): string {
52
+ return md5Bytes(input).map(b => b.toString(16).padStart(2, "0")).join("");
53
+ }
54
+
55
+ /** Returns the MD5 hash of the given bytes as a raw byte array. */
56
+ export function md5AsBytes(input: Uint8Array): number[] {
57
+ return md5Bytes(input);
58
+ }
59
+
60
+ /** Returns the SHA-256 hash of the given buffer as a base64 string. */
61
+ export function sha256Base64(buffer: ArrayBuffer): Promise<string> {
62
+ return crypto.subtle.digest("SHA-256", new Uint8Array(buffer)).then(res =>
63
+ btoa(String.fromCharCode(...new Uint8Array(res)))
64
+ );
65
+ }
@@ -35,7 +35,7 @@ export class GenerateMeshBVHWorker extends WorkerBase {
35
35
 
36
36
  worker.onerror = e => {
37
37
 
38
- reject(new Error(`[GenerateMeshBVHWorker] ${e.message || "Unknown error. Please check the server console. If you're using vite try adding 'three-mesh-bvh' to 'optimizeDeps.exclude' in your vite.config.js"}`));
38
+ reject(new Error(`[GenerateMeshBVHWorker] ${e.message || "Could not load worker."}`));
39
39
 
40
40
  };
41
41
 
@@ -0,0 +1,51 @@
1
+ /**
2
+ * JSX IntrinsicElements declarations for Needle Engine web components.
3
+ *
4
+ * Covers both the global JSX namespace (Preact, SolidJS, Svelte, vanilla TS)
5
+ * and React's module-scoped JSX namespace (react-jsx / react-jsxdev transform).
6
+ *
7
+ * IMPORTANT: This file must be a module (has a top-level import/export) so that
8
+ * `declare module "react"` is treated as a *module augmentation* (merging into
9
+ * the existing React types) rather than an *ambient module declaration* (which
10
+ * would shadow/replace @types/react).
11
+ */
12
+
13
+ import type { NeedleEngineAttributes } from "./needle-engine.js";
14
+
15
+ interface NeedleButtonJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
16
+ style?: Partial<CSSStyleDeclaration>;
17
+ ar?: boolean | string;
18
+ vr?: boolean | string;
19
+ quicklook?: boolean | string;
20
+ qrcode?: boolean | string;
21
+ unstyled?: boolean | string;
22
+ }
23
+
24
+ interface NeedleMenuJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
25
+ style?: Partial<CSSStyleDeclaration>;
26
+ }
27
+
28
+ interface NeedleLogoElementJSXAttributes extends Partial<Omit<HTMLElement, "style" | "children">> {
29
+ style?: Partial<CSSStyleDeclaration>;
30
+ }
31
+
32
+ interface NeedleElements {
33
+ "needle-engine": Partial<NeedleEngineAttributes>;
34
+ "needle-button": NeedleButtonJSXAttributes;
35
+ "needle-menu": NeedleMenuJSXAttributes;
36
+ "needle-logo-element": NeedleLogoElementJSXAttributes;
37
+ }
38
+
39
+ // Global JSX namespace — Preact, SolidJS, Svelte, vanilla TS
40
+ declare global {
41
+ namespace JSX {
42
+ interface IntrinsicElements extends NeedleElements {}
43
+ }
44
+ }
45
+
46
+ // React module-scoped JSX namespace (module augmentation, not ambient declaration)
47
+ declare module "react" {
48
+ namespace JSX {
49
+ interface IntrinsicElements extends NeedleElements {}
50
+ }
51
+ }
@@ -1,7 +1,9 @@
1
1
  import { madeWithNeedleSVG, needleLogoOnlySVG, needleLogoSVG } from "../assets/index.js";
2
+ import { HTMLElementBase } from "../engine_ssr.js";
2
3
 
3
4
  const elementName = "needle-logo-element";
4
5
 
6
+ // NOTE: keep JSX attributes in sync with ./jsx.d.ts
5
7
  declare global {
6
8
  interface HTMLElementTagNameMap {
7
9
  "needle-logo-element": NeedleLogoElement;
@@ -12,7 +14,7 @@ declare global {
12
14
  * Needle logo web component used in the hosting UI (small, compact logo or full)
13
15
  * @element needle-logo-element
14
16
  */
15
- export class NeedleLogoElement extends HTMLElement {
17
+ export class NeedleLogoElement extends HTMLElementBase {
16
18
 
17
19
  static get elementName() { return elementName; }
18
20
 
@@ -2,6 +2,7 @@ import { showBalloonMessage } from "../../debug/debug.js";
2
2
  import type { Context } from "../../engine_context.js";
3
3
  import { hasCommercialLicense, onLicenseCheckResultChanged, Telemetry } from "../../engine_license.js";
4
4
  import { isLocalNetwork } from "../../engine_networking_utils.js";
5
+ import { HTMLElementBase } from "../../engine_ssr.js";
5
6
  import { DeviceUtilities, getParam } from "../../engine_utils.js";
6
7
  import { onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
7
8
  import { ButtonsFactory } from "../buttons.js";
@@ -11,9 +12,10 @@ import { NeedleLogoElement } from "../logo-element.js";
11
12
  import { getElementPriority, setElementPriority as _setElementPriority } from "./menu-priority.js";
12
13
  import { NeedleSpatialMenu } from "./needle-menu-spatial.js";
13
14
 
15
+ // NOTE: keep JSX attributes in sync with ../jsx.d.ts
14
16
  declare global {
15
17
  interface HTMLElementTagNameMap {
16
- "needle-logo-element": NeedleLogoElement;
18
+ "needle-menu": NeedleMenuElement;
17
19
  }
18
20
  }
19
21
 
@@ -303,7 +305,7 @@ export class NeedleMenu {
303
305
  *
304
306
  * @element needle-menu
305
307
  */
306
- export class NeedleMenuElement extends HTMLElement {
308
+ export class NeedleMenuElement extends HTMLElementBase {
307
309
 
308
310
  static create() {
309
311
  // Ensure the element is registered before creating — guards against