@needle-tools/engine 4.14.0 → 4.15.0-next.f391a30

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 (198) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{gltf-progressive-BttGBXw6.umd.cjs → gltf-progressive-CMwJPwEt.umd.cjs} +1 -1
  4. package/dist/{gltf-progressive-Bm_6aEi4.js → gltf-progressive-CTlvpS3A.js} +1 -1
  5. package/dist/{gltf-progressive-T5WKTux5.min.js → gltf-progressive-DYL3SLVb.min.js} +1 -1
  6. package/dist/materialx-4jJLLe9Q.js +4174 -0
  7. package/dist/materialx-Bt9FHwco.min.js +158 -0
  8. package/dist/materialx-NDD0y4JY.umd.cjs +158 -0
  9. package/dist/{needle-engine.bundle-COL2Bar3.umd.cjs → needle-engine.bundle-C1BFRZDF.umd.cjs} +150 -140
  10. package/dist/{needle-engine.bundle-Z_gAD7Kg.js → needle-engine.bundle-DB4kLWO_.js} +6651 -6400
  11. package/dist/{needle-engine.bundle-NolzHLqO.min.js → needle-engine.bundle-DsTdfmeb.min.js} +151 -141
  12. package/dist/needle-engine.d.ts +345 -88
  13. package/dist/needle-engine.js +322 -322
  14. package/dist/needle-engine.min.js +1 -1
  15. package/dist/needle-engine.umd.cjs +1 -1
  16. package/dist/{postprocessing-06AXuvdv.min.js → postprocessing-BN-f4viE.min.js} +1 -1
  17. package/dist/{postprocessing-CPDcA21P.umd.cjs → postprocessing-DYmYOVm4.umd.cjs} +1 -1
  18. package/dist/{postprocessing-CI2x8Cln.js → postprocessing-De9ZpJrk.js} +1 -1
  19. package/dist/{three-examples-BMmNgNCN.umd.cjs → three-examples-BHqRVpO_.umd.cjs} +12 -12
  20. package/dist/{three-examples-CMYCd5nH.js → three-examples-C0ZCCA_K.js} +182 -192
  21. package/dist/{three-examples-CQl1fFZp.min.js → three-examples-DmTY8tGr.min.js} +14 -14
  22. package/lib/engine/api.d.ts +0 -2
  23. package/lib/engine/api.js +0 -2
  24. package/lib/engine/api.js.map +1 -1
  25. package/lib/engine/debug/debug.js +1 -1
  26. package/lib/engine/debug/debug.js.map +1 -1
  27. package/lib/engine/debug/debug_spatial_console.js +1 -1
  28. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  29. package/lib/engine/engine_accessibility.d.ts +77 -0
  30. package/lib/engine/engine_accessibility.js +162 -0
  31. package/lib/engine/engine_accessibility.js.map +1 -0
  32. package/lib/engine/engine_context.d.ts +2 -0
  33. package/lib/engine/engine_context.js +8 -1
  34. package/lib/engine/engine_context.js.map +1 -1
  35. package/lib/engine/engine_create_objects.js +1 -1
  36. package/lib/engine/engine_create_objects.js.map +1 -1
  37. package/lib/engine/engine_gizmos.js +1 -1
  38. package/lib/engine/engine_gizmos.js.map +1 -1
  39. package/lib/engine/engine_license.js +7 -2
  40. package/lib/engine/engine_license.js.map +1 -1
  41. package/lib/engine/engine_materialpropertyblock.d.ts +90 -4
  42. package/lib/engine/engine_materialpropertyblock.js +97 -7
  43. package/lib/engine/engine_materialpropertyblock.js.map +1 -1
  44. package/lib/engine/engine_math.d.ts +34 -1
  45. package/lib/engine/engine_math.js +34 -1
  46. package/lib/engine/engine_math.js.map +1 -1
  47. package/lib/engine/engine_networking.js +1 -1
  48. package/lib/engine/engine_networking.js.map +1 -1
  49. package/lib/engine/engine_types.d.ts +2 -0
  50. package/lib/engine/engine_types.js +2 -0
  51. package/lib/engine/engine_types.js.map +1 -1
  52. package/lib/engine/engine_utils.js +2 -2
  53. package/lib/engine/engine_utils.js.map +1 -1
  54. package/lib/engine/export/gltf/EXT_mesh_gpu_instancing_exporter.js.map +1 -0
  55. package/lib/engine/export/gltf/index.js +1 -1
  56. package/lib/engine/export/gltf/index.js.map +1 -1
  57. package/lib/engine/webcomponents/icons.js +3 -0
  58. package/lib/engine/webcomponents/icons.js.map +1 -1
  59. package/lib/engine/webcomponents/logo-element.d.ts +7 -3
  60. package/lib/engine/webcomponents/logo-element.js +21 -1
  61. package/lib/engine/webcomponents/logo-element.js.map +1 -1
  62. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  63. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  64. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +10 -7
  65. package/lib/engine/webcomponents/needle menu/needle-menu.js +14 -4
  66. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  67. package/lib/engine/webcomponents/needle-button.d.ts +37 -11
  68. package/lib/engine/webcomponents/needle-button.js +42 -11
  69. package/lib/engine/webcomponents/needle-button.js.map +1 -1
  70. package/lib/engine/webcomponents/needle-engine.ar-overlay.js +10 -1
  71. package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
  72. package/lib/engine/webcomponents/needle-engine.d.ts +13 -2
  73. package/lib/engine/webcomponents/needle-engine.js +23 -3
  74. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  75. package/lib/engine-components/Component.d.ts +1 -2
  76. package/lib/engine-components/Component.js +1 -3
  77. package/lib/engine-components/Component.js.map +1 -1
  78. package/lib/engine-components/DragControls.d.ts +1 -0
  79. package/lib/engine-components/DragControls.js +21 -0
  80. package/lib/engine-components/DragControls.js.map +1 -1
  81. package/lib/engine-components/NeedleMenu.d.ts +2 -0
  82. package/lib/engine-components/NeedleMenu.js +2 -0
  83. package/lib/engine-components/NeedleMenu.js.map +1 -1
  84. package/lib/engine-components/Networking.d.ts +28 -3
  85. package/lib/engine-components/Networking.js +28 -3
  86. package/lib/engine-components/Networking.js.map +1 -1
  87. package/lib/engine-components/ReflectionProbe.d.ts +25 -2
  88. package/lib/engine-components/ReflectionProbe.js +46 -2
  89. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  90. package/lib/engine-components/Skybox.js +4 -2
  91. package/lib/engine-components/Skybox.js.map +1 -1
  92. package/lib/engine-components/export/gltf/GltfExport.js +1 -1
  93. package/lib/engine-components/export/gltf/GltfExport.js.map +1 -1
  94. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +2 -2
  95. package/lib/engine-components/export/usdz/USDZExporter.js +1 -1
  96. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  97. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +15 -0
  98. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +77 -0
  99. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
  100. package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js +2 -2
  101. package/lib/engine-components/export/usdz/extensions/behavior/PhysicsExtension.js.map +1 -1
  102. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  103. package/lib/engine-components/ui/Button.d.ts +1 -0
  104. package/lib/engine-components/ui/Button.js +11 -0
  105. package/lib/engine-components/ui/Button.js.map +1 -1
  106. package/lib/engine-components/ui/Text.d.ts +1 -0
  107. package/lib/engine-components/ui/Text.js +11 -0
  108. package/lib/engine-components/ui/Text.js.map +1 -1
  109. package/package.json +18 -14
  110. package/plugins/common/buildinfo.js +46 -10
  111. package/plugins/common/files.js +2 -1
  112. package/plugins/common/license.js +144 -69
  113. package/plugins/common/logger.js +172 -11
  114. package/plugins/common/needle-engine-skill.md +175 -0
  115. package/plugins/common/worker.js +5 -4
  116. package/plugins/types/userconfig.d.ts +40 -2
  117. package/plugins/vite/ai.js +71 -0
  118. package/plugins/vite/alias.js +6 -5
  119. package/plugins/vite/asap.js +6 -5
  120. package/plugins/vite/build-pipeline.js +224 -41
  121. package/plugins/vite/buildinfo.js +66 -6
  122. package/plugins/vite/copyfiles.js +41 -12
  123. package/plugins/vite/custom-element-data.js +26 -16
  124. package/plugins/vite/defines.js +8 -5
  125. package/plugins/vite/dependencies.js +16 -10
  126. package/plugins/vite/dependency-watcher.js +35 -7
  127. package/plugins/vite/drop-client.js +7 -5
  128. package/plugins/vite/drop.js +16 -14
  129. package/plugins/vite/editor-connection.js +18 -16
  130. package/plugins/vite/imports-logger.js +12 -2
  131. package/plugins/vite/index.js +8 -3
  132. package/plugins/vite/local-files-analysis.js +789 -0
  133. package/plugins/vite/local-files-core.js +992 -0
  134. package/plugins/vite/local-files-internals.js +28 -0
  135. package/plugins/vite/local-files-types.d.ts +111 -0
  136. package/plugins/vite/local-files-utils.js +359 -0
  137. package/plugins/vite/local-files.js +2 -441
  138. package/plugins/vite/logger.client.js +45 -35
  139. package/plugins/vite/logger.js +6 -3
  140. package/plugins/vite/logging.js +129 -0
  141. package/plugins/vite/meta.js +18 -4
  142. package/plugins/vite/needle-app.js +4 -3
  143. package/plugins/vite/peer.js +2 -1
  144. package/plugins/vite/pwa.js +33 -17
  145. package/plugins/vite/reload.js +24 -2
  146. package/src/engine/api.ts +0 -3
  147. package/src/engine/debug/debug.ts +1 -1
  148. package/src/engine/debug/debug_spatial_console.ts +5 -1
  149. package/src/engine/engine_accessibility.ts +198 -0
  150. package/src/engine/engine_context.ts +10 -1
  151. package/src/engine/engine_create_objects.ts +1 -1
  152. package/src/engine/engine_gizmos.ts +9 -5
  153. package/src/engine/engine_license.ts +7 -2
  154. package/src/engine/engine_materialpropertyblock.ts +102 -11
  155. package/src/engine/engine_math.ts +34 -1
  156. package/src/engine/engine_networking.ts +1 -1
  157. package/src/engine/engine_types.ts +5 -0
  158. package/src/engine/engine_utils.ts +2 -2
  159. package/src/engine/export/gltf/index.ts +1 -1
  160. package/src/engine/webcomponents/icons.ts +3 -0
  161. package/src/engine/webcomponents/logo-element.ts +24 -4
  162. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +6 -2
  163. package/src/engine/webcomponents/needle menu/needle-menu.ts +23 -11
  164. package/src/engine/webcomponents/needle-button.ts +44 -13
  165. package/src/engine/webcomponents/needle-engine.ar-overlay.ts +13 -2
  166. package/src/engine/webcomponents/needle-engine.ts +31 -8
  167. package/src/engine-components/Component.ts +2 -5
  168. package/src/engine-components/DragControls.ts +29 -4
  169. package/src/engine-components/NeedleMenu.ts +5 -3
  170. package/src/engine-components/Networking.ts +29 -4
  171. package/src/engine-components/ReflectionProbe.ts +52 -9
  172. package/src/engine-components/Skybox.ts +4 -2
  173. package/src/engine-components/export/gltf/GltfExport.ts +1 -1
  174. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +2 -2
  175. package/src/engine-components/export/usdz/USDZExporter.ts +1 -1
  176. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +108 -32
  177. package/src/engine-components/export/usdz/extensions/behavior/PhysicsExtension.ts +2 -2
  178. package/src/engine-components/ui/Button.ts +12 -0
  179. package/src/engine-components/ui/Text.ts +13 -0
  180. package/dist/materialx-CJyQZtjt.min.js +0 -90
  181. package/dist/materialx-DMs1E08Z.js +0 -4636
  182. package/dist/materialx-DaKKOoVk.umd.cjs +0 -90
  183. package/lib/engine/engine_test_utils.d.ts +0 -39
  184. package/lib/engine/engine_test_utils.js +0 -84
  185. package/lib/engine/engine_test_utils.js.map +0 -1
  186. package/lib/include/three/EXT_mesh_gpu_instancing_exporter.js.map +0 -1
  187. package/src/engine/engine_test_utils.ts +0 -109
  188. package/src/include/draco/draco_decoder.js +0 -34
  189. package/src/include/draco/draco_decoder.wasm +0 -0
  190. package/src/include/draco/draco_wasm_wrapper.js +0 -117
  191. package/src/include/ktx2/basis_transcoder.js +0 -19
  192. package/src/include/ktx2/basis_transcoder.wasm +0 -0
  193. package/src/include/needle/arial-msdf.json +0 -1472
  194. package/src/include/needle/arial.png +0 -0
  195. package/src/include/needle/poweredbyneedle.webp +0 -0
  196. /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.d.ts +0 -0
  197. /package/lib/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
  198. /package/src/{include/three → engine/export/gltf}/EXT_mesh_gpu_instancing_exporter.js +0 -0
@@ -302,7 +302,7 @@ export class NeedleSpatialMenu {
302
302
  // logoObject.position.y = 1;
303
303
  // this._context.scene.add(logoObject);
304
304
  const textureLoader = new TextureLoader();
305
- textureLoader.load("./include/needle/poweredbyneedle.webp", (texture) => {
305
+ textureLoader.load("https://cdn.needle.tools/static/branding/poweredbyneedle.webp", (texture) => {
306
306
  if (texture) {
307
307
  onClick.allowModifyUI = false;
308
308
  firstLabel.removeFromParent();
@@ -343,7 +343,11 @@ export class NeedleSpatialMenu {
343
343
 
344
344
  if (!fontFamily) {
345
345
  fontFamily = ThreeMeshUI.FontLibrary.addFontFamily(this.familyName);
346
- const normal = fontFamily.addVariant("normal", "normal", "./include/needle/arial-msdf.json", "./include/needle/arial.png") as any as ThreeMeshUI.FontVariant;
346
+ const normal = fontFamily.addVariant(
347
+ "normal",
348
+ "normal",
349
+ "https://cdn.needle.tools/static/fonts/msdf/arial/arial-msdf.json",
350
+ "https://cdn.needle.tools/static/fonts/msdf/arial/arial.png") as any as ThreeMeshUI.FontVariant;
347
351
  /** @ts-ignore */
348
352
  normal?.addEventListener('ready', () => {
349
353
  this.markDirty();
@@ -129,6 +129,7 @@ export class NeedleMenu {
129
129
 
130
130
  constructor(context: Context) {
131
131
  this._menu = NeedleMenuElement.getOrCreate(context.domElement, context);
132
+ this._menu.ensureInitialized();
132
133
  this._context = context;
133
134
  this._spatialMenu = new NeedleSpatialMenu(context, this._menu);
134
135
  window.addEventListener("message", this.onPostMessage);
@@ -309,8 +310,7 @@ export class NeedleMenu {
309
310
  export class NeedleMenuElement extends HTMLElement {
310
311
 
311
312
  static create() {
312
- // https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement#is
313
- return document.createElement(elementName, { is: elementName });
313
+ return document.createElement(elementName);
314
314
  }
315
315
 
316
316
  static getOrCreate(domElement: HTMLElement, context: Context) {
@@ -337,9 +337,13 @@ export class NeedleMenuElement extends HTMLElement {
337
337
 
338
338
  private _domElement: HTMLElement | null = null;
339
339
  private _context: Context | null = null;
340
+ private _didInitialize = false;
340
341
 
341
342
  constructor() {
342
343
  super();
344
+ }
345
+
346
+ private initializeDom() {
343
347
 
344
348
  const template = document.createElement('template');
345
349
  // TODO: make host full size again and move the buttons to a wrapper so that we can later easily open e.g. foldouts/dropdowns / use the whole canvas space
@@ -795,7 +799,7 @@ export class NeedleMenuElement extends HTMLElement {
795
799
 
796
800
 
797
801
  let context = this._context;
798
- // we need to assign it in the timeout because the reference is set *after* the constructor did run
802
+ // we need to assign it in the timeout because the reference is set *after* the element is initialized
799
803
  setTimeout(() => context = this._context);
800
804
 
801
805
  // watch changes
@@ -864,13 +868,21 @@ export class NeedleMenuElement extends HTMLElement {
864
868
  }
865
869
  }
866
870
 
871
+ ensureInitialized() {
872
+ if (!this._didInitialize) {
873
+ this._didInitialize = true;
874
+ this.initializeDom();
875
+ }
876
+ }
877
+
867
878
  private _sizeChangeInterval;
868
879
 
869
880
  connectedCallback() {
881
+ this.ensureInitialized();
870
882
  window.addEventListener("resize", this.handleSizeChange);
871
883
  this.handleMenuVisible();
872
884
  this._sizeChangeInterval = setInterval(() => this.handleSizeChange(undefined, false), 5000);
873
- // the dom element is set after the constructor runs
885
+ // the dom element is set after initialization runs
874
886
  setTimeout(() => {
875
887
  this._domElement?.addEventListener("resize", this.handleSizeChange);
876
888
  this._domElement?.addEventListener("click", this.#onClick);
@@ -958,19 +970,19 @@ export class NeedleMenuElement extends HTMLElement {
958
970
 
959
971
  // private _root: ShadowRoot | null = null;
960
972
  /** @private root container element inside shadow DOM */
961
- private readonly root: HTMLDivElement;
973
+ private root!: HTMLDivElement;
962
974
  /** @private wraps the whole content (internal layout) */
963
- private readonly wrapper: HTMLDivElement;
975
+ private wrapper!: HTMLDivElement;
964
976
  /** @private contains the buttons and dynamic elements */
965
- private readonly options: HTMLDivElement;
977
+ private options!: HTMLDivElement;
966
978
  /** @private contains options visible when in compact mode */
967
- private readonly optionsCompactMode: HTMLDivElement;
979
+ private optionsCompactMode!: HTMLDivElement;
968
980
  /** @private contains the needle-logo html element */
969
- private readonly logoContainer: HTMLDivElement;
981
+ private logoContainer!: HTMLDivElement;
970
982
  /** @private compact menu button element */
971
- private readonly compactMenuButton: HTMLButtonElement;
983
+ private compactMenuButton!: HTMLButtonElement;
972
984
  /** @private foldout container used in compact mode */
973
- private readonly foldout: HTMLDivElement;
985
+ private foldout!: HTMLDivElement;
974
986
 
975
987
 
976
988
  private readonly trackedElements: WeakSet<Node> = new WeakSet();
@@ -13,32 +13,58 @@ const htmlTagName = "needle-button";
13
13
  const isDev = isDevEnvironment();
14
14
 
15
15
  /**
16
- * A <needle-button> can be used to simply add VR, AR or Quicklook buttons to your website without having to write any code.
17
- * @example
16
+ * [&lt;needle-button&gt;](https://engine.needle.tools/docs/api/NeedleButtonElement) is a web component for easily adding AR, VR, Quicklook, or QR code buttons to your website without writing JavaScript code.
17
+ *
18
+ * The button automatically handles session management and displays appropriate UI based on device capabilities.
19
+ * It comes with default styling (glassmorphism design) but can be fully customized with CSS.
20
+ *
21
+ * **Supported button types:**
22
+ * - `ar` - WebXR AR session button
23
+ * - `vr` - WebXR VR session button
24
+ * - `quicklook` - Apple AR Quick Look button (iOS only)
25
+ * - `qrcode` - QR code sharing button
26
+ *
27
+ * @example Basic AR/VR buttons
18
28
  * ```html
29
+ * <needle-engine src="scene.glb"></needle-engine>
19
30
  * <needle-button ar></needle-button>
20
31
  * <needle-button vr></needle-button>
21
32
  * <needle-button quicklook></needle-button>
22
33
  * ```
23
- *
24
- * @example custom label
34
+ *
35
+ * @example Custom button labels
25
36
  * ```html
26
- * <needle-button ar>Start AR</needle-button>
27
- * <needle-button vr>Start VR</needle-button>
37
+ * <needle-button ar>Start AR Experience</needle-button>
38
+ * <needle-button vr>Enter VR Mode</needle-button>
28
39
  * <needle-button quicklook>View in AR</needle-button>
29
40
  * ```
30
- *
31
- * @example custom styling
41
+ *
42
+ * @example Custom styling
32
43
  * ```html
33
- * <!-- You can either style the element directly or use a CSS stylesheet -->
34
44
  * <style>
35
- * needle-button {
36
- * background-color: red;
37
- * color: white;
38
- * }
45
+ * needle-button {
46
+ * background-color: #ff6b6b;
47
+ * color: white;
48
+ * border-radius: 8px;
49
+ * padding: 1rem 2rem;
50
+ * }
51
+ * needle-button:hover {
52
+ * background-color: #ff5252;
53
+ * }
39
54
  * </style>
40
55
  * <needle-button ar>Start AR</needle-button>
41
56
  * ```
57
+ *
58
+ * @example Unstyled button (for complete custom styling)
59
+ * ```html
60
+ * <needle-button ar unstyled>
61
+ * <span class="my-icon">🥽</span>
62
+ * Launch AR
63
+ * </needle-button>
64
+ * ```
65
+ *
66
+ * @see {@link NeedleEngineWebComponent} for the main &lt;needle-engine&gt; element
67
+ * @see {@link NeedleMenu} for the built-in menu component that can display similar buttons
42
68
  */
43
69
  export class NeedleButtonElement extends HTMLElement {
44
70
 
@@ -73,18 +99,22 @@ export class NeedleButtonElement extends HTMLElement {
73
99
  if (this.getAttribute("ar") != null) {
74
100
  this.#webxrfactory ??= new WebXRButtonFactory()
75
101
  this.#button = this.#webxrfactory.createARButton();
102
+ this.setAttribute("aria-label", "Enter augmented reality mode");
76
103
  }
77
104
  else if (this.getAttribute("vr") != null) {
78
105
  this.#webxrfactory ??= new WebXRButtonFactory()
79
106
  this.#button = this.#webxrfactory.createVRButton();
107
+ this.setAttribute("aria-label", "Enter virtual reality mode");
80
108
  }
81
109
  else if (this.getAttribute("quicklook") != null) {
82
110
  this.#webxrfactory ??= new WebXRButtonFactory()
83
111
  this.#button = this.#webxrfactory.createQuicklookButton();
112
+ this.setAttribute("aria-label", "View in AR with Apple Quick Look");
84
113
  }
85
114
  else if (this.getAttribute("qrcode") != null) {
86
115
  this.#buttonfactory ??= new ButtonsFactory();
87
116
  this.#button = this.#buttonfactory.createQRCode({ anchorElement: this });
117
+ this.setAttribute("aria-label", "Share application with QR code");
88
118
  }
89
119
  else {
90
120
  if (isDev) {
@@ -93,6 +123,7 @@ export class NeedleButtonElement extends HTMLElement {
93
123
  else {
94
124
  console.debug("No button type specified for <needle-button>. Use either ar, vr or quicklook attribute.")
95
125
  }
126
+ this.setAttribute("aria-label", "Needle Button with no specified type");
96
127
  return;
97
128
  }
98
129
 
@@ -121,7 +121,7 @@ export class AROverlayHandler {
121
121
 
122
122
  const quitARSlot = document.createElement("slot");
123
123
  quitARSlot.style.display = "contents";
124
- quitARSlot.style.padding = "10px";
124
+ quitARSlot.style.padding = "10px";
125
125
  quitARSlot.setAttribute("name", "quit-ar");
126
126
  this.appendElement(quitARSlot, element);
127
127
  this._createdAROnlyElements.push(quitARSlot);
@@ -131,7 +131,16 @@ export class AROverlayHandler {
131
131
  // No default quit button in the top right corner in app clips
132
132
  // we provide one via the native UI
133
133
  if (DeviceUtilities.isNeedleAppClip()) {
134
- quitARSlot.style.display = "none";
134
+
135
+ // quitARSlot.style.display = "none";
136
+ globalThis["NEEDLE_ENGINE_APPCLIP_DISABLE_MENU"] = true;
137
+
138
+ // respect the UI bar at the top of the screen and add some padding to the quit button container
139
+ // @TODO: this should be done in CSS in one place and not here and in debug overlay
140
+ const meta = document.querySelector('meta[name="viewport"]');
141
+ if (meta && !meta.getAttribute("content")?.includes("viewport-fit=")) {
142
+ meta.setAttribute("content", meta.getAttribute("content") + ",viewport-fit=cover");
143
+ }
135
144
  }
136
145
 
137
146
  // We want to search the document if there's a quit-ar button
@@ -155,6 +164,8 @@ export class AROverlayHandler {
155
164
  right: 0;
156
165
  z-index: 600;
157
166
  pointer-events: all;
167
+ padding-top: env(safe-area-inset-top, 0px);
168
+ padding-right: calc(env(safe-area-inset-right, 0px) + 10px);
158
169
  `;
159
170
  this.appendElement(fixedButtonContainer, quitARSlot);
160
171
 
@@ -87,17 +87,25 @@ const observedAttributes = [
87
87
 
88
88
  // https://developers.google.com/web/fundamentals/web-components/customelements
89
89
 
90
- /**
91
- * The `<needle-engine>` web component. See {@link NeedleEngineAttributes} attributes for supported attributes
92
- * The web component creates and manages a Needle Engine context, which is responsible for rendering a 3D scene using threejs.
93
- * The context is created when the `src` attribute is set, and disposed when the element is removed from the DOM. You can prevent cleanup by setting the `keep-alive` attribute to `true`.
90
+ /**
91
+ * The `<needle-engine>` web component. See {@link NeedleEngineAttributes} attributes for supported attributes
92
+ * The web component creates and manages a Needle Engine context, which is responsible for rendering a 3D scene using threejs.
93
+ * The context is created when the `src` attribute is set, and disposed when the element is removed from the DOM. You can prevent cleanup by setting the `keep-alive` attribute to `true`.
94
94
  * The context is accessible from the `<needle-engine>` element: `document.querySelector("needle-engine").context`.
95
95
  * See {@link https://engine.needle.tools/docs/reference/needle-engine-attributes}
96
96
  *
97
- * @example
97
+ * @example Basic usage
98
+ * ```html
98
99
  * <needle-engine src="https://example.com/scene.glb"></needle-engine>
99
- * @example
100
+ * ```
101
+ *
102
+ * @example With camera controls disabled
103
+ * ```html
100
104
  * <needle-engine src="https://example.com/scene.glb" camera-controls="false"></needle-engine>
105
+ * ```
106
+ *
107
+ * @see {@link NeedleButtonElement} for adding AR/VR/Quicklook buttons via &lt;needle-button&gt;
108
+ * @see {@link NeedleMenu} for the built-in menu configuration component
101
109
  */
102
110
  export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngineComponent {
103
111
 
@@ -160,23 +168,36 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
160
168
  */
161
169
  public get context() { return this._context; }
162
170
 
163
- private _context: Context;
171
+ private _context!: Context;
164
172
  private _overlay_ar: AROverlayHandler;
165
173
  private _loadingProgress01: number = 0;
166
174
  private _loadingView?: ILoadingViewHandler;
167
175
  private _previousSrc: string | null | string[] = null;
168
176
  /** @private set to true after <needle-engine> did load completely at least once. Set to false when < to false when <needle-engine> is removed from the document removed from the document */
169
177
  private _didFullyLoad: boolean = false;
178
+ private _didInitialize = false;
170
179
 
171
180
  constructor() {
172
181
  super();
173
182
  this._overlay_ar = new AROverlayHandler();
174
183
  // TODO: do we want to rename this event?
175
184
  this.addEventListener("ready", this.onReady);
185
+ }
186
+
187
+ private ensureInitialized() {
188
+ if (!this._didInitialize) {
189
+ this._didInitialize = true;
190
+ this.initializeDom();
191
+ }
192
+ }
193
+
194
+ private initializeDom() {
176
195
 
177
196
  ensureFonts();
178
197
 
179
- this.attachShadow({ mode: 'open' });
198
+ this.attachShadow({ mode: 'open', delegatesFocus: true });
199
+ this.setAttribute("role", "application");
200
+ this.setAttribute("aria-label", "Needle Engine 3D scene");
180
201
  const template = document.createElement('template');
181
202
  // #region CSS
182
203
  template.innerHTML = `<style>
@@ -271,6 +292,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
271
292
  * @internal
272
293
  */
273
294
  async connectedCallback() {
295
+ this.ensureInitialized();
274
296
  if (debug) {
275
297
  console.log("<needle-engine> connected");
276
298
  }
@@ -282,6 +304,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
282
304
  if (this.getAttribute("tabindex") === null || this.getAttribute("tabindex") === undefined)
283
305
  this.setAttribute("tabindex", "0");
284
306
 
307
+
285
308
  this.addEventListener("xr-session-started", this.onXRSessionStarted);
286
309
  this.onSetupDesktop();
287
310
 
@@ -10,7 +10,7 @@ import * as main from "../engine/engine_mainloop_utils.js";
10
10
  import { syncDestroy, syncInstantiate, SyncInstantiateOptions } from "../engine/engine_networking_instantiate.js";
11
11
  import { Context, FrameEvent } from "../engine/engine_setup.js";
12
12
  import * as threeutils from "../engine/engine_three_utils.js";
13
- import type { Collision, ComponentInit, Constructor, ConstructorConcrete, GuidsMap, ICollider, IComponent, IGameObject, SourceIdentifier } from "../engine/engine_types.js";
13
+ import { $componentName, type Collision, type ComponentInit, type Constructor, type ConstructorConcrete, type GuidsMap, type ICollider, type IComponent, type IGameObject, type SourceIdentifier } from "../engine/engine_types.js";
14
14
  import { TypeStore } from "../engine/engine_typestore.js";
15
15
  import type { INeedleXRSessionEventReceiver, NeedleXRControllerEventArgs, NeedleXREventArgs } from "../engine/engine_xr.js";
16
16
  import { type IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
@@ -49,7 +49,7 @@ export abstract class GameObject extends Object3D implements Object3D, IGameObje
49
49
  abstract activeSelf: boolean;
50
50
 
51
51
  /** @deprecated Use {@link addComponent} instead */
52
- // eslint-disable-next-line deprecation/deprecation
52
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
53
53
  abstract addNewComponent<T extends IComponent>(type: ConstructorConcrete<T>, init?: ComponentInit<T>): T;
54
54
 
55
55
  /**
@@ -364,7 +364,6 @@ export abstract class GameObject extends Object3D implements Object3D, IGameObje
364
364
  }
365
365
 
366
366
  /** @deprecated use `addComponent` */
367
- // eslint-disable-next-line deprecation/deprecation
368
367
  public static addNewComponent<T extends IComponent>(go: IGameObject | Object3D, type: T | ConstructorConcrete<T>, init?: ComponentInit<T>, callAwake: boolean = true): T {
369
368
  return addComponent(go, type, init, { callAwake });
370
369
  }
@@ -542,8 +541,6 @@ export abstract class GameObject extends Object3D implements Object3D, IGameObje
542
541
  }
543
542
  }
544
543
 
545
- // DO NOT CHANGE THE SYMBOL NAME
546
- const $componentName = Symbol("component-name");
547
544
 
548
545
  /**
549
546
  * Needle Engine component's are the main building blocks of the Needle Engine.
@@ -193,7 +193,7 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
193
193
  }
194
194
  }
195
195
  }
196
-
196
+
197
197
  private _rigidbody: Rigidbody | null = null;
198
198
 
199
199
  // future:
@@ -235,12 +235,22 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
235
235
  /** @internal */
236
236
  onEnable(): void {
237
237
  DragControls._instances.push(this);
238
+ this.context.accessibility.updateElement(this, {
239
+ role: "button",
240
+ label: "Drag " + (this.gameObject.name || "object"),
241
+ hidden: false,
242
+ });
238
243
  }
239
244
  /** @internal */
240
245
  onDisable(): void {
246
+ this.context.accessibility.updateElement(this, { hidden: true });
241
247
  DragControls._instances = DragControls._instances.filter(i => i !== this);
242
248
  }
243
249
 
250
+ onDestroy(): void {
251
+ this.context.accessibility.removeElement(this);
252
+ }
253
+
244
254
  /**
245
255
  * Checks if editing is allowed for the current networking connection.
246
256
  * @param _obj Optional object to check edit permissions for
@@ -268,6 +278,8 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
268
278
  if (!dc || dc !== this) return;
269
279
  DragControls.lastHovered = evt.object;
270
280
  this.context.domElement.style.cursor = 'pointer';
281
+
282
+ this.context.accessibility.hover(this, `Draggable ${evt.object?.name}`);
271
283
  }
272
284
 
273
285
  /**
@@ -339,6 +351,14 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
339
351
  }
340
352
 
341
353
  args.use();
354
+
355
+ this.context.accessibility.updateElement(this, {
356
+ role: "button",
357
+ label: "Dragging " + (this.gameObject.name || "object"),
358
+ hidden: false,
359
+ busy: true,
360
+ });
361
+ this.context.accessibility.focus(this);
342
362
  }
343
363
  }
344
364
 
@@ -375,6 +395,11 @@ export class DragControls extends Behaviour implements IPointerEventHandler {
375
395
  }
376
396
  args.use();
377
397
  }
398
+
399
+ this.context.accessibility.unfocus(this);
400
+ this.context.accessibility.updateElement(this, {
401
+ busy: false,
402
+ });
378
403
  }
379
404
 
380
405
  /**
@@ -779,12 +804,12 @@ class DragPointerHandler implements IDragHandler {
779
804
  * Used for determining if enough motion has occurred to start a drag.
780
805
  */
781
806
  getTotalMovement(): Vector3 { return this._totalMovement; }
782
-
807
+
783
808
  /**
784
809
  * Returns the object that follows the pointer during dragging operations.
785
810
  */
786
811
  get followObject(): GameObject { return this._followObject; }
787
-
812
+
788
813
  /**
789
814
  * Returns the point where the pointer initially hit the object in local space.
790
815
  */
@@ -1377,7 +1402,7 @@ class LegacyDragVisualsHelper {
1377
1402
 
1378
1403
  /** Controls whether visual helpers like lines and markers are displayed */
1379
1404
  showGizmo: boolean = true;
1380
-
1405
+
1381
1406
  /** When true, drag plane alignment changes based on view angle */
1382
1407
  useViewAngle: boolean = true;
1383
1408
 
@@ -9,8 +9,8 @@ import type { Voip } from './Voip.js';
9
9
  /**
10
10
  * [NeedleMenu](https://engine.needle.tools/docs/api/NeedleMenu) provides configuration for the built-in UI menu.
11
11
  * The menu renders as HTML overlay in browser mode and automatically
12
- * switches to a 3D spatial menu in VR/AR.
13
- *
12
+ * switches to a 3D spatial menu in VR/AR.
13
+ *
14
14
  * ![](https://cloud.needle.tools/-/media/YKleg1oPy_I8Hv8sg_k40Q.png)
15
15
  *
16
16
  * **Features:**
@@ -18,7 +18,7 @@ import type { Voip } from './Voip.js';
18
18
  * - Audio mute/unmute button
19
19
  * - QR code sharing (desktop only)
20
20
  * - Spatial menu in XR (appears when looking up)
21
- * - Custom positioning (top/bottom)
21
+ * - Custom positioning (top/bottom)
22
22
  *
23
23
  * **Programmatic access:**
24
24
  * Access the menu API via `this.context.menu` to add custom buttons,
@@ -36,6 +36,8 @@ import type { Voip } from './Voip.js';
36
36
  * @category User Interface
37
37
  * @group Components
38
38
  * @see {@link Context.menu} for programmatic menu control
39
+ * @see {@link NeedleButtonElement} for standalone &lt;needle-button&gt; web component
40
+ * @see {@link NeedleEngineWebComponent} for the main &lt;needle-engine&gt; element
39
41
  * @see {@link Voip} adds a microphone button to the menu
40
42
  * @see {@link ScreenCapture} adds a screen sharing button
41
43
  **/
@@ -7,10 +7,34 @@ import { Behaviour } from "./Component.js";
7
7
  const debug = getParam("debugnet");
8
8
 
9
9
  /**
10
- * Provides configuration to the built-in networking system.
11
- * This component supplies websocket URLs for establishing connections.
12
- * It implements the {@link INetworkingWebsocketUrlProvider} interface.
13
- *
10
+ * Provides websocket URL configuration for the built-in networking system.
11
+ * Add this component to override the default networking backend URL used by {@link NetworkConnection} (`this.context.connection`).
12
+ *
13
+ * The component registers itself as a URL provider on `awake()`. When the networking system connects,
14
+ * it queries this provider for the websocket URL to use instead of the default Needle networking backend.
15
+ *
16
+ * **URL resolution order:**
17
+ * 1. If `urlParameterName` is set and the corresponding URL parameter exists in the browser URL, that value is used
18
+ * 2. If running on a local network and `localhost` is set, the `localhost` URL is used
19
+ * 3. Otherwise, the `url` field is used
20
+ *
21
+ * Without this component, the default backend URL `wss://networking-2.needle.tools/socket` is used.
22
+ *
23
+ * **Note:** This component only configures the websocket URL. To actually join a networked room,
24
+ * use a `SyncedRoom` component or call `this.context.connection.joinRoom("room-name")` directly.
25
+ *
26
+ * @example Overriding the URL via browser parameter
27
+ * ```ts
28
+ * // With urlParameterName="server", visiting:
29
+ * // https://myapp.com/?server=wss://my-server.com/socket
30
+ * // will connect to that server instead
31
+ * ```
32
+ *
33
+ * @see {@link NetworkConnection} for the main networking API (`this.context.connection`)
34
+ * @see {@link SyncedRoom} for automatic room joining
35
+ * @see {@link OwnershipModel} for networked object ownership
36
+ * @see {@link RoomEvents} for room lifecycle events
37
+ * @link https://engine.needle.tools/docs/how-to-guides/networking/
14
38
  * @summary Networking configuration
15
39
  * @category Networking
16
40
  * @group Components
@@ -20,6 +44,7 @@ export class Networking extends Behaviour implements INetworkingWebsocketUrlProv
20
44
  /**
21
45
  * The websocket URL to connect to for networking functionality.
22
46
  * Can be a complete URL or a relative path that will be resolved against the current origin.
47
+ * @default null
23
48
  */
24
49
  @serializable()
25
50
  url: string | null = null;