@webspatial/core-sdk 1.7.0 → 2.0.0

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 (296) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +10 -0
  3. package/dist/JSBCommand.d.ts +398 -0
  4. package/dist/JSBCommand.js +668 -0
  5. package/dist/JSBCommand.js.map +1 -0
  6. package/dist/Spatial.d.ts +63 -0
  7. package/dist/Spatial.js +60 -0
  8. package/dist/Spatial.js.map +1 -0
  9. package/dist/SpatialObject.d.ts +19 -0
  10. package/dist/SpatialObject.js +44 -0
  11. package/dist/SpatialObject.js.map +1 -0
  12. package/dist/SpatialScene.d.ts +51 -0
  13. package/dist/SpatialScene.js +74 -0
  14. package/dist/SpatialScene.js.map +1 -0
  15. package/dist/SpatialSession.d.ts +143 -0
  16. package/dist/SpatialSession.js +173 -0
  17. package/dist/SpatialSession.js.map +1 -0
  18. package/dist/SpatialWebEvent.d.ts +8 -0
  19. package/dist/SpatialWebEvent.js +18 -0
  20. package/dist/SpatialWebEvent.js.map +1 -0
  21. package/dist/SpatialWebEventCreator.d.ts +11 -0
  22. package/dist/SpatialWebEventCreator.js +11 -0
  23. package/dist/SpatialWebEventCreator.js.map +1 -0
  24. package/dist/Spatialized2DElement.d.ts +42 -0
  25. package/dist/Spatialized2DElement.js +47 -0
  26. package/dist/Spatialized2DElement.js.map +1 -0
  27. package/dist/SpatializedDynamic3DElement.d.ts +7 -0
  28. package/dist/SpatializedDynamic3DElement.js +43 -0
  29. package/dist/SpatializedDynamic3DElement.js.map +1 -0
  30. package/dist/SpatializedElement.d.ts +7 -0
  31. package/dist/SpatializedElement.js +206 -0
  32. package/dist/SpatializedElement.js.map +1 -0
  33. package/dist/SpatializedElementCreator.d.ts +15 -0
  34. package/dist/SpatializedElementCreator.js +47 -0
  35. package/dist/SpatializedElementCreator.js.map +1 -0
  36. package/dist/SpatializedStatic3DElement.d.ts +195 -0
  37. package/dist/SpatializedStatic3DElement.js +327 -0
  38. package/dist/SpatializedStatic3DElement.js.map +1 -0
  39. package/dist/WebMsgCommand.d.ts +7 -0
  40. package/dist/WebMsgCommand.js +23 -0
  41. package/dist/WebMsgCommand.js.map +1 -0
  42. package/dist/index.d.ts +35 -1251
  43. package/dist/index.js +21 -3650
  44. package/dist/index.js.map +1 -1
  45. package/dist/install-polyfills.d.ts +2 -0
  46. package/dist/install-polyfills.js +13 -0
  47. package/dist/install-polyfills.js.map +1 -0
  48. package/dist/isSSREnv.d.ts +3 -0
  49. package/dist/isSSREnv.js +6 -0
  50. package/dist/isSSREnv.js.map +1 -0
  51. package/dist/motion/AnimationObject.d.ts +7 -0
  52. package/dist/motion/AnimationObject.js +140 -0
  53. package/dist/motion/AnimationObject.js.map +1 -0
  54. package/dist/motion/compute/index.d.ts +10 -0
  55. package/dist/motion/compute/index.js +19 -0
  56. package/dist/motion/compute/index.js.map +1 -0
  57. package/dist/motion/compute/motionPropertyValues.d.ts +19 -0
  58. package/dist/motion/compute/motionPropertyValues.js +50 -0
  59. package/dist/motion/compute/motionPropertyValues.js.map +1 -0
  60. package/dist/motion/compute/normalize.d.ts +31 -0
  61. package/dist/motion/compute/normalize.js +201 -0
  62. package/dist/motion/compute/normalize.js.map +1 -0
  63. package/dist/motion/compute/validate.d.ts +22 -0
  64. package/dist/motion/compute/validate.js +103 -0
  65. package/dist/motion/compute/validate.js.map +1 -0
  66. package/dist/motion/index.d.ts +8 -0
  67. package/dist/motion/index.js +6 -0
  68. package/dist/motion/index.js.map +1 -0
  69. package/dist/motion/native/serializeMotionTimeline.d.ts +12 -0
  70. package/dist/motion/native/serializeMotionTimeline.js +21 -0
  71. package/dist/motion/native/serializeMotionTimeline.js.map +1 -0
  72. package/dist/physicalMetrics-W5uZ5xKb.d.ts +48 -0
  73. package/dist/physicalMetrics.d.ts +1 -0
  74. package/dist/physicalMetrics.js +58 -0
  75. package/dist/physicalMetrics.js.map +1 -0
  76. package/dist/platform-adapter/CommandResultUtils.d.ts +12 -0
  77. package/dist/platform-adapter/CommandResultUtils.js +21 -0
  78. package/dist/platform-adapter/CommandResultUtils.js.map +1 -0
  79. package/dist/platform-adapter/createPlatformSync.d.ts +16 -0
  80. package/dist/platform-adapter/createPlatformSync.js +30 -0
  81. package/dist/platform-adapter/createPlatformSync.js.map +1 -0
  82. package/dist/platform-adapter/index.d.ts +12 -0
  83. package/dist/platform-adapter/index.js +10 -0
  84. package/dist/platform-adapter/index.js.map +1 -0
  85. package/dist/platform-adapter/interface.d.ts +7 -0
  86. package/dist/platform-adapter/interface.js +1 -0
  87. package/dist/platform-adapter/interface.js.map +1 -0
  88. package/dist/platform-adapter/pico-os/PicoOSPlatform.d.ts +25 -0
  89. package/dist/platform-adapter/pico-os/PicoOSPlatform.js +168 -0
  90. package/dist/platform-adapter/pico-os/PicoOSPlatform.js.map +1 -0
  91. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.d.ts +66 -0
  92. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.js +372 -0
  93. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.js.map +1 -0
  94. package/dist/platform-adapter/spatialRequestMetadata.d.ts +6 -0
  95. package/dist/platform-adapter/spatialRequestMetadata.js +39 -0
  96. package/dist/platform-adapter/spatialRequestMetadata.js.map +1 -0
  97. package/dist/platform-adapter/spatialSceneQuery.d.ts +12 -0
  98. package/dist/platform-adapter/spatialSceneQuery.js +12 -0
  99. package/dist/platform-adapter/spatialSceneQuery.js.map +1 -0
  100. package/dist/platform-adapter/vision-os/VisionOSPlatform.d.ts +19 -0
  101. package/dist/platform-adapter/vision-os/VisionOSPlatform.js +77 -0
  102. package/dist/platform-adapter/vision-os/VisionOSPlatform.js.map +1 -0
  103. package/dist/platform-runtime.d.ts +12 -0
  104. package/dist/platform-runtime.js +14 -0
  105. package/dist/platform-runtime.js.map +1 -0
  106. package/dist/reality/Attachment.d.ts +19 -0
  107. package/dist/reality/Attachment.js +39 -0
  108. package/dist/reality/Attachment.js.map +1 -0
  109. package/dist/reality/component/ModelComponent.d.ts +14 -0
  110. package/dist/reality/component/ModelComponent.js +11 -0
  111. package/dist/reality/component/ModelComponent.js.map +1 -0
  112. package/dist/reality/component/SpatialComponent.d.ts +8 -0
  113. package/dist/reality/component/SpatialComponent.js +19 -0
  114. package/dist/reality/component/SpatialComponent.js.map +1 -0
  115. package/dist/reality/component/index.d.ts +8 -0
  116. package/dist/reality/component/index.js +3 -0
  117. package/dist/reality/component/index.js.map +1 -0
  118. package/dist/reality/entity/SpatialEntity.d.ts +7 -0
  119. package/dist/reality/entity/SpatialEntity.js +291 -0
  120. package/dist/reality/entity/SpatialEntity.js.map +1 -0
  121. package/dist/reality/entity/SpatialModelEntity.d.ts +17 -0
  122. package/dist/reality/entity/SpatialModelEntity.js +17 -0
  123. package/dist/reality/entity/SpatialModelEntity.js.map +1 -0
  124. package/dist/reality/entity/index.d.ts +8 -0
  125. package/dist/reality/entity/index.js +3 -0
  126. package/dist/reality/entity/index.js.map +1 -0
  127. package/dist/reality/geometry/SpatialBoxGeometry.d.ts +16 -0
  128. package/dist/reality/geometry/SpatialBoxGeometry.js +13 -0
  129. package/dist/reality/geometry/SpatialBoxGeometry.js.map +1 -0
  130. package/dist/reality/geometry/SpatialConeGeometry.d.ts +16 -0
  131. package/dist/reality/geometry/SpatialConeGeometry.js +13 -0
  132. package/dist/reality/geometry/SpatialConeGeometry.js.map +1 -0
  133. package/dist/reality/geometry/SpatialCylinderGeometry.d.ts +16 -0
  134. package/dist/reality/geometry/SpatialCylinderGeometry.js +13 -0
  135. package/dist/reality/geometry/SpatialCylinderGeometry.js.map +1 -0
  136. package/dist/reality/geometry/SpatialGeometry.d.ts +7 -0
  137. package/dist/reality/geometry/SpatialGeometry.js +13 -0
  138. package/dist/reality/geometry/SpatialGeometry.js.map +1 -0
  139. package/dist/reality/geometry/SpatialPlaneGeometry.d.ts +16 -0
  140. package/dist/reality/geometry/SpatialPlaneGeometry.js +13 -0
  141. package/dist/reality/geometry/SpatialPlaneGeometry.js.map +1 -0
  142. package/dist/reality/geometry/SpatialSphereGeometry.d.ts +16 -0
  143. package/dist/reality/geometry/SpatialSphereGeometry.js +13 -0
  144. package/dist/reality/geometry/SpatialSphereGeometry.js.map +1 -0
  145. package/dist/reality/geometry/index.d.ts +12 -0
  146. package/dist/reality/geometry/index.js +7 -0
  147. package/dist/reality/geometry/index.js.map +1 -0
  148. package/dist/reality/index.d.ts +18 -0
  149. package/dist/reality/index.js +7 -0
  150. package/dist/reality/index.js.map +1 -0
  151. package/dist/reality/material/SpatialMaterial.d.ts +7 -0
  152. package/dist/reality/material/SpatialMaterial.js +13 -0
  153. package/dist/reality/material/SpatialMaterial.js.map +1 -0
  154. package/dist/reality/material/SpatialUnlitMaterial.d.ts +16 -0
  155. package/dist/reality/material/SpatialUnlitMaterial.js +16 -0
  156. package/dist/reality/material/SpatialUnlitMaterial.js.map +1 -0
  157. package/dist/reality/material/index.d.ts +8 -0
  158. package/dist/reality/material/index.js +3 -0
  159. package/dist/reality/material/index.js.map +1 -0
  160. package/dist/reality/realityCreator.d.ts +22 -0
  161. package/dist/reality/realityCreator.js +89 -0
  162. package/dist/reality/realityCreator.js.map +1 -0
  163. package/dist/reality/resource/SpatialModelAsset.d.ts +15 -0
  164. package/dist/reality/resource/SpatialModelAsset.js +12 -0
  165. package/dist/reality/resource/SpatialModelAsset.js.map +1 -0
  166. package/dist/reality/resource/SpatialTextureResource.d.ts +16 -0
  167. package/dist/reality/resource/SpatialTextureResource.js +16 -0
  168. package/dist/reality/resource/SpatialTextureResource.js.map +1 -0
  169. package/dist/reality/resource/index.d.ts +9 -0
  170. package/dist/reality/resource/index.js +3 -0
  171. package/dist/reality/resource/index.js.map +1 -0
  172. package/dist/runtime/WebSpatialRuntimeError.d.ts +10 -0
  173. package/dist/runtime/WebSpatialRuntimeError.js +14 -0
  174. package/dist/runtime/WebSpatialRuntimeError.js.map +1 -0
  175. package/dist/runtime/capability-data.d.ts +12 -0
  176. package/dist/runtime/capability-data.js +134 -0
  177. package/dist/runtime/capability-data.js.map +1 -0
  178. package/dist/runtime/index.d.ts +8 -0
  179. package/dist/runtime/index.js +27 -0
  180. package/dist/runtime/index.js.map +1 -0
  181. package/dist/runtime/jsbAdapterPlatform.d.ts +9 -0
  182. package/dist/runtime/jsbAdapterPlatform.js +38 -0
  183. package/dist/runtime/jsbAdapterPlatform.js.map +1 -0
  184. package/dist/runtime/keys.d.ts +34 -0
  185. package/dist/runtime/keys.js +122 -0
  186. package/dist/runtime/keys.js.map +1 -0
  187. package/dist/runtime/semver.d.ts +8 -0
  188. package/dist/runtime/semver.js +32 -0
  189. package/dist/runtime/semver.js.map +1 -0
  190. package/dist/runtime/supports.d.ts +22 -0
  191. package/dist/runtime/supports.js +78 -0
  192. package/dist/runtime/supports.js.map +1 -0
  193. package/dist/runtime/types.d.ts +12 -0
  194. package/dist/runtime/types.js +1 -0
  195. package/dist/runtime/types.js.map +1 -0
  196. package/dist/runtime/userAgent.d.ts +20 -0
  197. package/dist/runtime/userAgent.js +41 -0
  198. package/dist/runtime/userAgent.js.map +1 -0
  199. package/dist/scene-polyfill.d.ts +18 -0
  200. package/dist/scene-polyfill.js +480 -0
  201. package/dist/scene-polyfill.js.map +1 -0
  202. package/dist/spatial-host.d.ts +13 -0
  203. package/dist/spatial-host.js +18 -0
  204. package/dist/spatial-host.js.map +1 -0
  205. package/dist/spatial-window-polyfill.d.ts +3 -0
  206. package/dist/spatial-window-polyfill.js +155 -0
  207. package/dist/spatial-window-polyfill.js.map +1 -0
  208. package/dist/spatializedElementMotion-_0XWPQCE.d.ts +1009 -0
  209. package/dist/types/animation.d.ts +7 -0
  210. package/dist/types/animation.js +1 -0
  211. package/dist/types/animation.js.map +1 -0
  212. package/dist/types/global.d.ts +38 -0
  213. package/dist/types/global.js +1 -0
  214. package/dist/types/global.js.map +1 -0
  215. package/dist/types/internal.d.ts +7 -0
  216. package/dist/types/internal.js +12 -0
  217. package/dist/types/internal.js.map +1 -0
  218. package/dist/types/motion/index.d.ts +7 -0
  219. package/dist/types/motion/index.js +3 -0
  220. package/dist/types/motion/index.js.map +1 -0
  221. package/dist/types/motion/spatializedElementMotion.d.ts +7 -0
  222. package/dist/types/motion/spatializedElementMotion.js +1 -0
  223. package/dist/types/motion/spatializedElementMotion.js.map +1 -0
  224. package/dist/types/motion/spatializedMotion.d.ts +7 -0
  225. package/dist/types/motion/spatializedMotion.js +16 -0
  226. package/dist/types/motion/spatializedMotion.js.map +1 -0
  227. package/dist/types/motion/spatializedPlayback.d.ts +11 -0
  228. package/dist/types/motion/spatializedPlayback.js +1 -0
  229. package/dist/types/motion/spatializedPlayback.js.map +1 -0
  230. package/dist/types/motion/spatializedVisual.d.ts +35 -0
  231. package/dist/types/motion/spatializedVisual.js +1 -0
  232. package/dist/types/motion/spatializedVisual.js.map +1 -0
  233. package/dist/types/types.d.ts +7 -0
  234. package/dist/types/types.js +117 -0
  235. package/dist/types/types.js.map +1 -0
  236. package/dist/utils.d.ts +44 -0
  237. package/dist/utils.js +86 -0
  238. package/dist/utils.js.map +1 -0
  239. package/package.json +20 -4
  240. package/src/JSBCommand.ts +132 -5
  241. package/src/SpatialObject.ts +10 -0
  242. package/src/Spatialized2DElement.ts +5 -0
  243. package/src/SpatializedDynamic3DElement.ts +5 -0
  244. package/src/SpatializedElement.ts +29 -0
  245. package/src/SpatializedStatic3DElement.ts +42 -3
  246. package/src/WebMsgCommand.ts +40 -0
  247. package/src/coverage-boost.test.ts +239 -216
  248. package/src/index.ts +25 -11
  249. package/src/install-polyfills.ts +38 -0
  250. package/src/jsbcommand.coverage.test.ts +23 -0
  251. package/src/motion/AnimationObject.test.ts +421 -0
  252. package/src/motion/AnimationObject.ts +208 -0
  253. package/src/motion/compute/index.ts +10 -0
  254. package/src/motion/compute/motionPropertyValues.test.ts +55 -0
  255. package/src/motion/compute/motionPropertyValues.ts +70 -0
  256. package/src/motion/compute/normalize.test.ts +99 -0
  257. package/src/motion/compute/normalize.ts +289 -0
  258. package/src/motion/compute/validate.test.ts +229 -0
  259. package/src/motion/compute/validate.ts +156 -0
  260. package/src/motion/index.ts +2 -0
  261. package/src/motion/native/serializeMotionTimeline.test.ts +61 -0
  262. package/src/motion/native/serializeMotionTimeline.ts +27 -0
  263. package/src/platform-adapter/createPlatformSync.ts +5 -3
  264. package/src/platform-adapter/pico-os/PicoOSPlatform.ts +76 -11
  265. package/src/platform-adapter/spatialRequestMetadata.test.ts +175 -0
  266. package/src/platform-adapter/spatialRequestMetadata.ts +48 -0
  267. package/src/platform-adapter/vision-os/VisionOSPlatform.ts +6 -1
  268. package/src/reality/entity/SpatialEntity.ts +127 -0
  269. package/src/runtime/capability-data.ts +104 -4
  270. package/src/runtime/index.ts +0 -11
  271. package/src/runtime/keys.ts +12 -1
  272. package/src/runtime/supports.test.ts +143 -4
  273. package/src/runtime/supports.ts +3 -1
  274. package/src/runtime/useAnimation.capability.test.ts +275 -0
  275. package/src/scene-polyfill.manifest.test.ts +306 -5
  276. package/src/scene-polyfill.request-metadata.test.ts +39 -0
  277. package/src/scene-polyfill.test.ts +33 -94
  278. package/src/scene-polyfill.ts +117 -87
  279. package/src/spatial-window-polyfill.ts +18 -2
  280. package/src/transform-roundtrip.test.ts +90 -0
  281. package/src/types/animation.ts +224 -0
  282. package/src/types/global.ts +1 -8
  283. package/src/types/motion/index.ts +10 -0
  284. package/src/types/motion/spatializedElementMotion.ts +49 -0
  285. package/src/types/motion/spatializedMotion.ts +147 -0
  286. package/src/types/motion/spatializedPlayback.ts +17 -0
  287. package/src/types/motion/spatializedVisual.ts +24 -0
  288. package/src/types/types.ts +3 -0
  289. package/src/utils.ts +54 -0
  290. package/tsup.config.ts +61 -36
  291. package/vitest.config.ts +5 -1
  292. package/dist/iife/index.d.ts +0 -1251
  293. package/dist/iife/index.global.js +0 -75
  294. package/dist/iife/index.global.js.map +0 -1
  295. package/src/platform-adapter/ssr/SSRPlatform.ts +0 -52
  296. /package/src/{ssr-polyfill.ts → isSSREnv.ts} +0 -0
@@ -128,6 +128,7 @@ describe('JSBCommand', () => {
128
128
  it('builds element commands payloads', async () => {
129
129
  const mod = await import('./JSBCommand')
130
130
  const {
131
+ CreateSpatializedElementAnimationJSBCommand,
131
132
  UpdateSpatializedElementTransform,
132
133
  UpdateSpatialized2DElementProperties,
133
134
  UpdateSpatializedDynamic3DElementProperties,
@@ -185,6 +186,18 @@ describe('JSBCommand', () => {
185
186
  JSON.stringify({ id: 'so-1', spatializedElementId: 'ele-1' }),
186
187
  )
187
188
 
189
+ await new CreateSpatializedElementAnimationJSBCommand({
190
+ elementId: 'ele-1',
191
+ timeline: { duration: 1, tracks: [] },
192
+ } as any).execute()
193
+ expect(platformSpy.callJSB).toHaveBeenCalledWith(
194
+ 'CreateSpatializedElementAnimation',
195
+ JSON.stringify({
196
+ elementId: 'ele-1',
197
+ timeline: { duration: 1, tracks: [] },
198
+ }),
199
+ )
200
+
188
201
  await new UpdateUnlitMaterialProperties(obj, { color: '#fff' }).execute()
189
202
  expect(platformSpy.callJSB).toHaveBeenCalledWith(
190
203
  'UpdateUnlitMaterialProperties',
@@ -412,12 +425,17 @@ describe('SpatializedElement', () => {
412
425
  SpatialWebEvent.init()
413
426
 
414
427
  class TestElement extends SpatializedElement {
428
+ /** Identifies the supported motion target kind for this test element. */
429
+ readonly kind = 'spatialized2d' as const
430
+
415
431
  updateProperties = vi.fn().mockResolvedValue({
416
432
  success: true,
417
433
  data: undefined,
418
434
  errorCode: '',
419
435
  errorMessage: '',
420
436
  })
437
+
438
+ motion = vi.fn()
421
439
  }
422
440
 
423
441
  const e = new TestElement('el3')
@@ -440,12 +458,17 @@ describe('SpatializedElement', () => {
440
458
  SpatialWebEvent.init()
441
459
 
442
460
  class TestElement extends SpatializedElement {
461
+ /** Identifies the supported motion target kind for this test element. */
462
+ readonly kind = 'spatialized2d' as const
463
+
443
464
  updateProperties = vi.fn().mockResolvedValue({
444
465
  success: true,
445
466
  data: undefined,
446
467
  errorCode: '',
447
468
  errorMessage: '',
448
469
  })
470
+
471
+ motion = vi.fn()
449
472
  }
450
473
 
451
474
  const e = new TestElement('el4')
@@ -0,0 +1,421 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { SpatialWebMsgType } from '../WebMsgCommand'
3
+ import { SpatialWebEvent } from '../SpatialWebEvent'
4
+ import type { SpatialAnimationStateChangedMsg } from '../types/motion/spatializedElementMotion'
5
+
6
+ const spatialAnimationStateChangedType: SpatialAnimationStateChangedMsg['type'] =
7
+ SpatialWebMsgType.animationstatechange
8
+
9
+ const spies = vi.hoisted(() => ({
10
+ createCommandSpy: vi.fn(),
11
+ controlCommandSpy: vi.fn(),
12
+ destroyCommandSpy: vi.fn(),
13
+ failNextControlType: undefined as string | undefined,
14
+ }))
15
+
16
+ vi.mock('../JSBCommand', () => {
17
+ const ok = (data: any = {}) => ({
18
+ success: true,
19
+ data,
20
+ errorCode: '',
21
+ errorMessage: '',
22
+ })
23
+
24
+ class CreateSpatializedElementAnimationJSBCommand {
25
+ constructor(public command: any) {
26
+ spies.createCommandSpy(command)
27
+ }
28
+ execute = vi.fn().mockResolvedValue(ok({ id: 'native-anim-1' }))
29
+ }
30
+
31
+ class ControlSpatializedElementAnimationJSBCommand {
32
+ constructor(public command: any) {
33
+ spies.controlCommandSpy(command)
34
+ }
35
+ execute = vi.fn().mockImplementation(() => {
36
+ if (spies.failNextControlType === this.command.type) {
37
+ spies.failNextControlType = undefined
38
+ return Promise.resolve({
39
+ success: false,
40
+ data: undefined,
41
+ errorCode: 'E_CONTROL',
42
+ errorMessage: `${this.command.type} failed`,
43
+ })
44
+ }
45
+ return Promise.resolve(ok())
46
+ })
47
+ }
48
+
49
+ class DestroyCommand {
50
+ constructor(public id: string) {
51
+ spies.destroyCommandSpy(id)
52
+ }
53
+ execute = vi.fn().mockResolvedValue(ok({ destroyed: true }))
54
+ }
55
+
56
+ class InspectCommand {
57
+ constructor(public id: string = '') {}
58
+ execute = vi.fn().mockResolvedValue(ok({ inspected: true }))
59
+ }
60
+
61
+ return {
62
+ CreateSpatializedElementAnimationJSBCommand,
63
+ ControlSpatializedElementAnimationJSBCommand,
64
+ DestroyCommand,
65
+ InspectCommand,
66
+ }
67
+ })
68
+
69
+ describe('AnimationObject', () => {
70
+ beforeEach(() => {
71
+ spies.createCommandSpy.mockClear()
72
+ spies.controlCommandSpy.mockClear()
73
+ spies.destroyCommandSpy.mockClear()
74
+ spies.failNextControlType = undefined
75
+ SpatialWebEvent.eventReceiver = {}
76
+ })
77
+
78
+ async function createAnimation() {
79
+ const { SpatializedElement } = await import('../SpatializedElement')
80
+
81
+ class TestSpatializedElement extends SpatializedElement {
82
+ /** Identifies the supported motion target kind for this test element. */
83
+ readonly kind = 'static3d' as const
84
+
85
+ async updateProperties() {
86
+ return {
87
+ success: true,
88
+ data: undefined,
89
+ errorCode: '',
90
+ errorMessage: '',
91
+ }
92
+ }
93
+ }
94
+
95
+ const element = new TestSpatializedElement('element-1')
96
+ return element.createAnimation({
97
+ duration: 1,
98
+ from: {
99
+ transform: { translate: { x: 0 } },
100
+ },
101
+ to: {
102
+ transform: { translate: { x: 10 } },
103
+ },
104
+ })
105
+ }
106
+
107
+ it('types native state events with the shared animationstatechange discriminant', () => {
108
+ expect(spatialAnimationStateChangedType).toBe(
109
+ SpatialWebMsgType.animationstatechange,
110
+ )
111
+ })
112
+
113
+ it('creates from the native create command and tracks native state directly', async () => {
114
+ const { AnimationObject } = await import('./AnimationObject')
115
+
116
+ const animation = await createAnimation()
117
+
118
+ expect(animation).toBeInstanceOf(AnimationObject)
119
+ expect(animation.id).toBe('native-anim-1')
120
+ expect('uuid' in animation).toBe(false)
121
+ expect(spies.createCommandSpy).toHaveBeenCalledWith(
122
+ expect.objectContaining({
123
+ elementId: 'element-1',
124
+ timeline: expect.any(Object),
125
+ }),
126
+ )
127
+
128
+ const receiver = SpatialWebEvent.eventReceiver[animation.id]
129
+ expect(receiver).toEqual(expect.any(Function))
130
+
131
+ const onValuesChange = vi.fn()
132
+ const onStateChange = vi.fn()
133
+ const onComplete = vi.fn()
134
+ animation.setCallbacks({
135
+ onValuesChange,
136
+ onStateChange,
137
+ onComplete,
138
+ })
139
+
140
+ receiver?.({
141
+ detail: {
142
+ animationId: 'other-animation',
143
+ action: 'play',
144
+ playState: 'running',
145
+ finished: false,
146
+ values: { transform: { translate: { x: 2.5 } } },
147
+ },
148
+ })
149
+
150
+ expect(animation.playState).toBe('idle')
151
+ expect(onValuesChange).not.toHaveBeenCalled()
152
+ expect(onStateChange).not.toHaveBeenCalled()
153
+
154
+ receiver?.({
155
+ detail: {
156
+ animationId: animation.id,
157
+ action: 'play',
158
+ playState: 'running',
159
+ finished: false,
160
+ values: { transform: { translate: { x: 5 } } },
161
+ },
162
+ })
163
+
164
+ expect(animation.playState).toBe('running')
165
+ expect(animation.isAnimating).toBe(true)
166
+ expect(animation.isPaused).toBe(false)
167
+ expect(animation.finished).toBe(false)
168
+ expect(onValuesChange).toHaveBeenCalledWith({
169
+ transform: { translate: { x: 5 } },
170
+ })
171
+ expect(onStateChange).toHaveBeenCalledTimes(1)
172
+
173
+ receiver?.({
174
+ detail: {
175
+ animationId: animation.id,
176
+ action: 'complete',
177
+ playState: 'finished',
178
+ finished: true,
179
+ values: { transform: { translate: { x: 10 } } },
180
+ },
181
+ })
182
+
183
+ expect(animation.playState).toBe('finished')
184
+ expect(animation.finished).toBe(true)
185
+ expect(onComplete).toHaveBeenCalledWith({
186
+ transform: { translate: { x: 10 } },
187
+ })
188
+ expect(onStateChange).toHaveBeenCalledTimes(2)
189
+ })
190
+
191
+ it('uses the inherited destroy path and unregisters its native WebMsg receiver', async () => {
192
+ const animation = await createAnimation()
193
+
194
+ expect(SpatialWebEvent.eventReceiver[animation.id]).toEqual(
195
+ expect.any(Function),
196
+ )
197
+
198
+ await animation.destroy()
199
+
200
+ expect(spies.destroyCommandSpy).toHaveBeenCalledWith(animation.id)
201
+ expect(spies.controlCommandSpy).not.toHaveBeenCalledWith(
202
+ expect.objectContaining({ type: 'destroy' }),
203
+ )
204
+ expect(animation.isDestroyed).toBe(true)
205
+ expect(SpatialWebEvent.eventReceiver[animation.id]).toBeUndefined()
206
+ })
207
+
208
+ it('cleans up local lifecycle when native reports destroy', async () => {
209
+ const animation = await createAnimation()
210
+ const receiver = SpatialWebEvent.eventReceiver[animation.id]
211
+
212
+ expect(receiver).toEqual(expect.any(Function))
213
+
214
+ receiver?.({
215
+ detail: {
216
+ animationId: animation.id,
217
+ action: 'destroy',
218
+ playState: 'idle',
219
+ finished: false,
220
+ },
221
+ })
222
+
223
+ expect(animation.isDestroyed).toBe(true)
224
+ expect(SpatialWebEvent.eventReceiver[animation.id]).toBeUndefined()
225
+
226
+ await animation.play()
227
+ expect(spies.controlCommandSpy).not.toHaveBeenCalled()
228
+ })
229
+
230
+ it('fires onStart only after the native start event arrives', async () => {
231
+ const animation = await createAnimation()
232
+ const onStart = vi.fn()
233
+ animation.setCallbacks({ onStart })
234
+ const receiver = SpatialWebEvent.eventReceiver[animation.id]
235
+
236
+ await animation.play()
237
+ expect(onStart).not.toHaveBeenCalled()
238
+
239
+ receiver?.({
240
+ detail: {
241
+ animationId: animation.id,
242
+ action: 'play',
243
+ playState: 'running',
244
+ finished: false,
245
+ values: { transform: { translate: { x: 1 } } },
246
+ },
247
+ })
248
+ expect(onStart).not.toHaveBeenCalled()
249
+
250
+ receiver?.({
251
+ detail: {
252
+ animationId: animation.id,
253
+ action: 'start',
254
+ playState: 'running',
255
+ finished: false,
256
+ values: { transform: { translate: { x: 1 } } },
257
+ },
258
+ })
259
+ expect(onStart).toHaveBeenCalledTimes(1)
260
+ })
261
+
262
+ it('does not fire onStart when play control fails and retries only start after native start', async () => {
263
+ const animation = await createAnimation()
264
+ const onStart = vi.fn()
265
+ const onError = vi.fn()
266
+ animation.setCallbacks({ onStart, onError })
267
+ const receiver = SpatialWebEvent.eventReceiver[animation.id]
268
+
269
+ spies.failNextControlType = 'play'
270
+
271
+ await expect(animation.play()).rejects.toThrow('play failed')
272
+ expect(onStart).not.toHaveBeenCalled()
273
+ expect(onError).not.toHaveBeenCalled()
274
+
275
+ await animation.play()
276
+ expect(onStart).not.toHaveBeenCalled()
277
+
278
+ receiver?.({
279
+ detail: {
280
+ animationId: animation.id,
281
+ action: 'start',
282
+ playState: 'running',
283
+ finished: false,
284
+ values: { transform: { translate: { x: 2 } } },
285
+ },
286
+ })
287
+ expect(onStart).toHaveBeenCalledTimes(1)
288
+ })
289
+
290
+ it('normalizes native playback errors without exposing animation id', async () => {
291
+ const animation = await createAnimation()
292
+ const onError = vi.fn()
293
+ animation.setCallbacks({ onError })
294
+ const receiver = SpatialWebEvent.eventReceiver[animation.id]
295
+
296
+ receiver?.({
297
+ detail: {
298
+ animationId: animation.id,
299
+ action: 'error',
300
+ playState: 'idle',
301
+ finished: false,
302
+ error: {
303
+ code: 'mask-conflict',
304
+ message: 'Another animation is already writing this target',
305
+ },
306
+ },
307
+ })
308
+
309
+ expect(onError).toHaveBeenCalledWith({
310
+ command: 'play',
311
+ code: 'mask-conflict',
312
+ reason: 'Another animation is already writing this target',
313
+ })
314
+ })
315
+
316
+ it.each(['stop', 'reset', 'finish'] as const)(
317
+ 'does not re-fire onStart after %s until native terminal event arrives',
318
+ async controlType => {
319
+ const animation = await createAnimation()
320
+ const onStart = vi.fn()
321
+ animation.setCallbacks({ onStart })
322
+ const receiver = SpatialWebEvent.eventReceiver[animation.id]
323
+
324
+ receiver?.({
325
+ detail: {
326
+ animationId: animation.id,
327
+ action: 'start',
328
+ playState: 'running',
329
+ finished: false,
330
+ values: { transform: { translate: { x: 1 } } },
331
+ },
332
+ })
333
+ expect(onStart).toHaveBeenCalledTimes(1)
334
+
335
+ await animation[controlType]()
336
+
337
+ receiver?.({
338
+ detail: {
339
+ animationId: animation.id,
340
+ action: 'start',
341
+ playState: 'running',
342
+ finished: false,
343
+ values: { transform: { translate: { x: 2 } } },
344
+ },
345
+ })
346
+ expect(onStart).toHaveBeenCalledTimes(1)
347
+
348
+ receiver?.({
349
+ detail: {
350
+ animationId: animation.id,
351
+ action: controlType,
352
+ playState: controlType === 'finish' ? 'finished' : 'idle',
353
+ finished: controlType === 'finish',
354
+ values: { transform: { translate: { x: 3 } } },
355
+ },
356
+ })
357
+
358
+ receiver?.({
359
+ detail: {
360
+ animationId: animation.id,
361
+ action: 'start',
362
+ playState: 'running',
363
+ finished: false,
364
+ values: { transform: { translate: { x: 4 } } },
365
+ },
366
+ })
367
+ expect(onStart).toHaveBeenCalledTimes(2)
368
+ },
369
+ )
370
+
371
+ it('sends all playback controls to the same native animation id without recreating', async () => {
372
+ const animation = await createAnimation()
373
+ spies.createCommandSpy.mockClear()
374
+
375
+ await animation.play()
376
+ await animation.pause()
377
+ await animation.play()
378
+ await animation.stop()
379
+ await animation.reset()
380
+ await animation.finish()
381
+
382
+ expect(spies.createCommandSpy).not.toHaveBeenCalled()
383
+ expect(spies.controlCommandSpy.mock.calls.map(call => call[0])).toEqual([
384
+ { animationId: animation.id, type: 'play' },
385
+ { animationId: animation.id, type: 'pause' },
386
+ { animationId: animation.id, type: 'play' },
387
+ { animationId: animation.id, type: 'stop' },
388
+ { animationId: animation.id, type: 'reset' },
389
+ { animationId: animation.id, type: 'finish' },
390
+ ])
391
+ })
392
+ })
393
+
394
+ describe('Core exports', () => {
395
+ it('keeps internal motion architecture objects out of the public Core export surface', async () => {
396
+ const mod = await import('../index')
397
+
398
+ expect((mod as any).AnimationObject).toBeUndefined()
399
+ expect((mod as any).AnimationObjectChannel).toBeUndefined()
400
+ expect((mod as any).AnimationObjectBridge).toBeUndefined()
401
+ expect((mod as any).SpatialObjectBridge).toBeUndefined()
402
+ })
403
+
404
+ it('exports AnimationObject from the motion entrypoint', async () => {
405
+ const mod = await import('./index')
406
+
407
+ expect(mod.AnimationObject).toBeDefined()
408
+ })
409
+
410
+ it('removes legacy animateMotion entry points from spatialized elements', async () => {
411
+ const mod = await import('../index')
412
+
413
+ expect('animateMotion' in mod.Spatialized2DElement.prototype).toBe(false)
414
+ expect('animateMotion' in mod.SpatializedStatic3DElement.prototype).toBe(
415
+ false,
416
+ )
417
+ expect('animateMotion' in mod.SpatializedDynamic3DElement.prototype).toBe(
418
+ false,
419
+ )
420
+ })
421
+ })
@@ -0,0 +1,208 @@
1
+ import {
2
+ ControlSpatializedElementAnimationJSBCommand,
3
+ CreateSpatializedElementAnimationJSBCommand,
4
+ } from '../JSBCommand'
5
+ import { SpatialObject } from '../SpatialObject'
6
+ import { SpatialWebEvent } from '../SpatialWebEvent'
7
+ import type {
8
+ ControlSpatializedElementAnimationCommand,
9
+ SpatialAnimationStateChangedDetail,
10
+ SpatialAnimationStateChangedMsg,
11
+ } from '../types/motion/spatializedElementMotion'
12
+ import type { SpatializedPlaybackError } from '../types/motion/spatializedPlayback'
13
+ import type { SpatializedVisualValues } from '../types/motion/spatializedVisual'
14
+ import type {
15
+ SpatializedMotionTimeline,
16
+ SpatializedPlaybackApi,
17
+ SpatializedMotionPlayState,
18
+ } from '../types/motion/spatializedMotion'
19
+
20
+ export interface AnimationObjectCreateOptions {
21
+ elementId: string
22
+ timeline: SpatializedMotionTimeline
23
+ }
24
+
25
+ export interface AnimationObjectCallbacks {
26
+ onStart?: () => void
27
+ onComplete?: (values: SpatializedVisualValues) => void
28
+ onStop?: (values: SpatializedVisualValues) => void
29
+ onReset?: (values: SpatializedVisualValues) => void
30
+ onError?: (error: SpatializedPlaybackError) => void
31
+ onValuesChange?: (values: SpatializedVisualValues) => void
32
+ onStateChange?: () => void
33
+ }
34
+
35
+ export class AnimationObject
36
+ extends SpatialObject
37
+ implements SpatializedPlaybackApi
38
+ {
39
+ readonly elementId: string
40
+ readonly timeline: SpatializedMotionTimeline
41
+
42
+ private callbacks: AnimationObjectCallbacks = {}
43
+ private _playState: SpatializedMotionPlayState = 'idle'
44
+ private _finished = false
45
+ private started = false
46
+
47
+ constructor(id: string, options: AnimationObjectCreateOptions) {
48
+ super(id)
49
+ this.elementId = options.elementId
50
+ this.timeline = options.timeline
51
+
52
+ SpatialWebEvent.addEventReceiver(id, data => this.onReceiveEvent(data))
53
+ }
54
+
55
+ static async create(options: AnimationObjectCreateOptions) {
56
+ const ret = await new CreateSpatializedElementAnimationJSBCommand({
57
+ elementId: options.elementId,
58
+ timeline: options.timeline,
59
+ }).execute()
60
+ if (!ret.success) {
61
+ throw new Error(
62
+ ret.errorMessage ?? 'CreateSpatializedElementAnimation command failed',
63
+ )
64
+ }
65
+
66
+ const data = ret.data as { id?: string } | undefined
67
+ const id = data?.id
68
+ if (!id) {
69
+ throw new Error('CreateSpatializedElementAnimation did not return an id')
70
+ }
71
+
72
+ return new AnimationObject(id, options)
73
+ }
74
+
75
+ setCallbacks(callbacks: AnimationObjectCallbacks): void {
76
+ this.callbacks = callbacks
77
+ }
78
+
79
+ get playState() {
80
+ return this._playState
81
+ }
82
+
83
+ get isAnimating() {
84
+ return this._playState === 'running' || this._playState === 'queued'
85
+ }
86
+
87
+ get isPaused() {
88
+ return this._playState === 'paused'
89
+ }
90
+
91
+ get finished() {
92
+ return this._finished
93
+ }
94
+
95
+ play(): Promise<void> {
96
+ return this.control('play')
97
+ }
98
+
99
+ pause(): Promise<void> {
100
+ return this.control('pause')
101
+ }
102
+
103
+ stop(): Promise<void> {
104
+ return this.control('stop')
105
+ }
106
+
107
+ reset(): Promise<void> {
108
+ return this.control('reset')
109
+ }
110
+
111
+ finish(): Promise<void> {
112
+ return this.control('finish')
113
+ }
114
+
115
+ protected override onDestroy(): void {
116
+ SpatialWebEvent.removeEventReceiver(this.id)
117
+ }
118
+
119
+ private async control(
120
+ type: ControlSpatializedElementAnimationCommand['type'],
121
+ ): Promise<void> {
122
+ if (this.isDestroyed) return
123
+
124
+ const ret = await new ControlSpatializedElementAnimationJSBCommand({
125
+ animationId: this.id,
126
+ type,
127
+ }).execute()
128
+
129
+ if (!ret.success) {
130
+ throw new Error(
131
+ ret.errorMessage ??
132
+ `ControlSpatializedElementAnimation(${type}) command failed`,
133
+ )
134
+ }
135
+ }
136
+
137
+ onReceiveEvent(data: SpatialAnimationStateChangedMsg | { detail?: unknown }) {
138
+ const detail = data?.detail ?? data
139
+ if (!this.isSpatialAnimationStateChangedDetail(detail)) return
140
+ if (detail.animationId !== this.id) return
141
+
142
+ this._playState = detail.playState
143
+ this._finished = detail.finished
144
+
145
+ if (detail.values) {
146
+ this.callbacks.onValuesChange?.(detail.values)
147
+ }
148
+ if (detail.action !== 'start') {
149
+ this.callbacks.onStateChange?.()
150
+ }
151
+
152
+ if (detail.error) {
153
+ this.started = false
154
+ // Native currently emits asynchronous errors only when play cannot acquire the ownership mask.
155
+ this.callbacks.onError?.({
156
+ command: 'play',
157
+ code: detail.error.code,
158
+ reason: detail.error.message,
159
+ })
160
+ return
161
+ }
162
+
163
+ if (detail.action === 'start') {
164
+ if (!this.started) {
165
+ this.started = true
166
+ this.callbacks.onStart?.()
167
+ }
168
+ return
169
+ }
170
+
171
+ if (detail.action === 'complete' || detail.action === 'finish') {
172
+ this.started = false
173
+ this.callbacks.onComplete?.(detail.values ?? {})
174
+ return
175
+ }
176
+
177
+ if (detail.action === 'stop') {
178
+ this.started = false
179
+ this.callbacks.onStop?.(detail.values ?? {})
180
+ return
181
+ }
182
+
183
+ if (detail.action === 'reset') {
184
+ this.started = false
185
+ this.callbacks.onReset?.(detail.values ?? {})
186
+ return
187
+ }
188
+
189
+ if (detail.action === 'destroy') {
190
+ this.started = false
191
+ this.isDestroyed = true
192
+ this.onDestroy()
193
+ }
194
+ }
195
+
196
+ private isSpatialAnimationStateChangedDetail(
197
+ detail: unknown,
198
+ ): detail is SpatialAnimationStateChangedDetail {
199
+ if (!detail || typeof detail !== 'object') return false
200
+ const candidate = detail as Partial<SpatialAnimationStateChangedDetail>
201
+ return (
202
+ typeof candidate.animationId === 'string' &&
203
+ typeof candidate.action === 'string' &&
204
+ typeof candidate.playState === 'string' &&
205
+ typeof candidate.finished === 'boolean'
206
+ )
207
+ }
208
+ }
@@ -0,0 +1,10 @@
1
+ export { setMotionPropertyValue } from './motionPropertyValues'
2
+ export {
3
+ validateNormalizedMotionConfig,
4
+ validateSpatializedMotionConfig,
5
+ } from './validate'
6
+ export {
7
+ desugarTimelineConfig,
8
+ normalizeMotionConfig,
9
+ segmentConfigToMotionConfig,
10
+ } from './normalize'