@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
@@ -0,0 +1,55 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import type { SpatializedMotionProperty } from '../../types/motion/spatializedMotion'
3
+ import { setMotionPropertyValue } from './motionPropertyValues'
4
+
5
+ describe('setMotionPropertyValue', () => {
6
+ test('writes opacity', () => {
7
+ const values = {}
8
+
9
+ setMotionPropertyValue(values, 'opacity', 0.5)
10
+
11
+ expect(values).toEqual({ opacity: 0.5 })
12
+ })
13
+
14
+ test.each([
15
+ ['transform.translate.x', { transform: { translate: { x: 1 } } }],
16
+ ['transform.translate.y', { transform: { translate: { y: 2 } } }],
17
+ ['transform.translate.z', { transform: { translate: { z: 3 } } }],
18
+ ['transform.rotate.x', { transform: { rotate: { x: 4 } } }],
19
+ ['transform.rotate.y', { transform: { rotate: { y: 5 } } }],
20
+ ['transform.rotate.z', { transform: { rotate: { z: 6 } } }],
21
+ ['transform.scale.x', { transform: { scale: { x: 7 } } }],
22
+ ['transform.scale.y', { transform: { scale: { y: 8 } } }],
23
+ ['transform.scale.z', { transform: { scale: { z: 9 } } }],
24
+ ] as const)('writes %s', (motionProperty, expected) => {
25
+ const values = {}
26
+ const sampledValue = Object.values(
27
+ Object.values(expected.transform ?? {})[0] ?? {},
28
+ )[0] as number
29
+
30
+ setMotionPropertyValue(
31
+ values,
32
+ motionProperty as SpatializedMotionProperty,
33
+ sampledValue,
34
+ )
35
+
36
+ expect(values).toEqual(expected)
37
+ })
38
+
39
+ test('preserves existing transform groups while writing another property', () => {
40
+ const values = {
41
+ transform: {
42
+ translate: { x: 10 },
43
+ },
44
+ }
45
+
46
+ setMotionPropertyValue(values, 'transform.rotate.y', 20)
47
+
48
+ expect(values).toEqual({
49
+ transform: {
50
+ translate: { x: 10 },
51
+ rotate: { y: 20 },
52
+ },
53
+ })
54
+ })
55
+ })
@@ -0,0 +1,70 @@
1
+ import type { SpatializedVisualValues } from '../../types/motion/spatializedVisual'
2
+ import type { SpatializedMotionProperty } from '../../types/motion/spatializedMotion'
3
+
4
+ function getTranslate(
5
+ visualValues: SpatializedVisualValues,
6
+ ): NonNullable<NonNullable<SpatializedVisualValues['transform']>['translate']> {
7
+ const transform = (visualValues.transform ??= {})
8
+ return (transform.translate ??= {})
9
+ }
10
+
11
+ function getRotate(
12
+ visualValues: SpatializedVisualValues,
13
+ ): NonNullable<NonNullable<SpatializedVisualValues['transform']>['rotate']> {
14
+ const transform = (visualValues.transform ??= {})
15
+ return (transform.rotate ??= {})
16
+ }
17
+
18
+ function getScale(
19
+ visualValues: SpatializedVisualValues,
20
+ ): NonNullable<NonNullable<SpatializedVisualValues['transform']>['scale']> {
21
+ const transform = (visualValues.transform ??= {})
22
+ return (transform.scale ??= {})
23
+ }
24
+
25
+ /**
26
+ * Writes a sampled numeric track value into the corresponding
27
+ * SpatializedVisualValues field.
28
+ *
29
+ * @param visualValues Visual value object being populated for the sampled time.
30
+ * @param motionProperty Motion property path for the sampled track.
31
+ * @param sampledValue Numeric value sampled from the track at the current time.
32
+ */
33
+ export function setMotionPropertyValue(
34
+ visualValues: SpatializedVisualValues,
35
+ motionProperty: SpatializedMotionProperty,
36
+ sampledValue: number,
37
+ ): void {
38
+ switch (motionProperty) {
39
+ case 'opacity':
40
+ visualValues.opacity = sampledValue
41
+ return
42
+ case 'transform.translate.x':
43
+ getTranslate(visualValues).x = sampledValue
44
+ return
45
+ case 'transform.translate.y':
46
+ getTranslate(visualValues).y = sampledValue
47
+ return
48
+ case 'transform.translate.z':
49
+ getTranslate(visualValues).z = sampledValue
50
+ return
51
+ case 'transform.rotate.x':
52
+ getRotate(visualValues).x = sampledValue
53
+ return
54
+ case 'transform.rotate.y':
55
+ getRotate(visualValues).y = sampledValue
56
+ return
57
+ case 'transform.rotate.z':
58
+ getRotate(visualValues).z = sampledValue
59
+ return
60
+ case 'transform.scale.x':
61
+ getScale(visualValues).x = sampledValue
62
+ return
63
+ case 'transform.scale.y':
64
+ getScale(visualValues).y = sampledValue
65
+ return
66
+ case 'transform.scale.z':
67
+ getScale(visualValues).z = sampledValue
68
+ return
69
+ }
70
+ }
@@ -0,0 +1,99 @@
1
+ import { describe, expect, test, vi } from 'vitest'
2
+ import { normalizeMotionConfig } from './normalize'
3
+
4
+ describe('normalizeMotionConfig', () => {
5
+ test('normalizes mixed timeline entries into numeric tracks', () => {
6
+ expect(
7
+ normalizeMotionConfig({
8
+ duration: 2,
9
+ timeline: {
10
+ from: { opacity: 0, transform: { translate: { x: 0 } } },
11
+ '50%': { opacity: 0.5 },
12
+ to: { opacity: 1, transform: { translate: { x: 10 } } },
13
+ },
14
+ }),
15
+ ).toMatchObject({
16
+ duration: 2,
17
+ tracks: [
18
+ {
19
+ property: 'opacity',
20
+ keyframes: [
21
+ { at: 0, value: 0 },
22
+ { at: 1, value: 0.5 },
23
+ { at: 2, value: 1 },
24
+ ],
25
+ },
26
+ {
27
+ property: 'transform.translate.x',
28
+ keyframes: [
29
+ { at: 0, value: 0 },
30
+ { at: 2, value: 10 },
31
+ ],
32
+ },
33
+ ],
34
+ })
35
+ })
36
+
37
+ test('uses only timeline values when top-level boundaries are present', () => {
38
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
39
+ expect(
40
+ normalizeMotionConfig({
41
+ from: { opacity: 0.25 },
42
+ to: { opacity: 0.75 },
43
+ timeline: {
44
+ from: { opacity: 0 },
45
+ to: { opacity: 1 },
46
+ },
47
+ }),
48
+ ).toMatchObject({
49
+ duration: 0.3,
50
+ tracks: [
51
+ {
52
+ property: 'opacity',
53
+ keyframes: [
54
+ { at: 0, value: 0 },
55
+ { at: 0.3, value: 1 },
56
+ ],
57
+ },
58
+ ],
59
+ })
60
+ expect(warnSpy).toHaveBeenCalledWith(
61
+ expect.stringContaining('top-level from/to are ignored'),
62
+ )
63
+ warnSpy.mockRestore()
64
+ })
65
+
66
+ test('preserves sparse property keyframe ranges', () => {
67
+ expect(
68
+ normalizeMotionConfig({
69
+ duration: 2,
70
+ timeline: {
71
+ '0%': { transform: { rotate: { z: -20 } } },
72
+ '50%': {
73
+ opacity: 0,
74
+ transform: { rotate: { z: 80 } },
75
+ },
76
+ '100%': { opacity: 1 },
77
+ },
78
+ }),
79
+ ).toMatchObject({
80
+ duration: 2,
81
+ tracks: [
82
+ {
83
+ property: 'transform.rotate.z',
84
+ keyframes: [
85
+ { at: 0, value: -20 },
86
+ { at: 1, value: 80 },
87
+ ],
88
+ },
89
+ {
90
+ property: 'opacity',
91
+ keyframes: [
92
+ { at: 1, value: 0 },
93
+ { at: 2, value: 1 },
94
+ ],
95
+ },
96
+ ],
97
+ })
98
+ })
99
+ })
@@ -0,0 +1,289 @@
1
+ import type { SpatializedVisualValues } from '../../types/motion/spatializedVisual'
2
+ import type { TimingFunction } from '../../types/animation'
3
+ import type {
4
+ NormalizedSpatializedMotionConfig,
5
+ SpatializedMotionConfig,
6
+ SpatializedMotionProperty,
7
+ SpatializedMotionSegmentConfig,
8
+ SpatializedMotionTrack,
9
+ SpatializedMotionTimelineConfig,
10
+ } from '../../types/motion/spatializedMotion'
11
+
12
+ /**
13
+ * Flattens visual values into motion-property/value pairs.
14
+ *
15
+ * @param values Visual values to inspect.
16
+ * @param out Collector for discovered motion property samples.
17
+ */
18
+ function collectScalars(
19
+ values: SpatializedVisualValues,
20
+ out: Array<{ property: SpatializedMotionProperty; value: number }>,
21
+ ): void {
22
+ if (values.opacity !== undefined) {
23
+ out.push({ property: 'opacity', value: values.opacity })
24
+ }
25
+ const tr = values.transform
26
+ if (!tr) return
27
+ for (const group of ['translate', 'rotate', 'scale'] as const) {
28
+ const part = tr[group]
29
+ if (!part) continue
30
+ for (const axis of ['x', 'y', 'z'] as const) {
31
+ const v = part[axis]
32
+ if (v !== undefined) {
33
+ out.push({
34
+ property: `transform.${group}.${axis}` as SpatializedMotionProperty,
35
+ value: v,
36
+ })
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Converts `0%`-style timeline keys into [0, 1] ratios.
44
+ *
45
+ * @param key Percentage key from the timeline config.
46
+ * @returns The normalized ratio represented by the key.
47
+ */
48
+ function parsePercentageKey(key: string): number {
49
+ if (!/^\d+(\.\d+)?%$/.test(key)) {
50
+ throw new Error(`[SpatializedMotion] invalid timeline key "${key}"`)
51
+ }
52
+ const ratio = Number(key.slice(0, -1))
53
+ if (!Number.isFinite(ratio) || ratio < 0 || ratio > 100) {
54
+ throw new Error(`[SpatializedMotion] invalid timeline key "${key}"`)
55
+ }
56
+ return ratio / 100
57
+ }
58
+
59
+ /**
60
+ * Desugars shorthand from/to motion config into canonical tracks.
61
+ *
62
+ * @param simple Shorthand segment config.
63
+ * @returns Canonical track-based motion config.
64
+ */
65
+ export function segmentConfigToMotionConfig(
66
+ simple: SpatializedMotionSegmentConfig,
67
+ ): NormalizedSpatializedMotionConfig {
68
+ if (!simple.from || !simple.to) {
69
+ throw new Error(
70
+ '[SpatializedMotion] top-level from and to are both required',
71
+ )
72
+ }
73
+ const duration = simple.duration ?? 0.3
74
+ const fromScalars: Array<{
75
+ property: SpatializedMotionProperty
76
+ value: number
77
+ }> = []
78
+ const toScalars: Array<{
79
+ property: SpatializedMotionProperty
80
+ value: number
81
+ }> = []
82
+
83
+ collectScalars(simple.from, fromScalars)
84
+ collectScalars(simple.to, toScalars)
85
+
86
+ const trackMap = new Map<SpatializedMotionProperty, SpatializedMotionTrack>()
87
+ const properties = new Set([
88
+ ...fromScalars.map(entry => entry.property),
89
+ ...toScalars.map(entry => entry.property),
90
+ ])
91
+
92
+ for (const property of properties) {
93
+ const fromEntry = fromScalars.find(f => f.property === property)
94
+ const toEntry = toScalars.find(f => f.property === property)
95
+ if (!fromEntry) {
96
+ throw new Error(
97
+ `[SpatializedMotion] missing start boundary for "${property}"`,
98
+ )
99
+ }
100
+ if (!toEntry) {
101
+ throw new Error(
102
+ `[SpatializedMotion] missing end boundary for "${property}"`,
103
+ )
104
+ }
105
+ trackMap.set(property, {
106
+ property,
107
+ timingFunction: simple.timingFunction,
108
+ keyframes: [
109
+ { at: 0, value: fromEntry.value },
110
+ { at: duration, value: toEntry.value },
111
+ ],
112
+ })
113
+ }
114
+
115
+ const tracks = [...trackMap.values()]
116
+ if (tracks.length === 0) {
117
+ throw new Error(
118
+ '[SpatializedMotion.simple] to must include at least one animatable field',
119
+ )
120
+ }
121
+
122
+ return {
123
+ duration,
124
+ tracks,
125
+ timingFunction: simple.timingFunction,
126
+ delay: simple.delay,
127
+ autoStart: simple.autoStart,
128
+ loop: simple.loop,
129
+ playbackRate: simple.playbackRate,
130
+ onStart: simple.onStart,
131
+ onComplete: simple.onComplete,
132
+ onStop: simple.onStop,
133
+ onReset: simple.onReset,
134
+ onError: simple.onError,
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Desugars percentage-key timeline config into canonical tracks.
140
+ *
141
+ * @param config Timeline config using percentage keys.
142
+ * @returns Canonical track-based motion config.
143
+ */
144
+ export function desugarTimelineConfig(
145
+ config: SpatializedMotionTimelineConfig,
146
+ ): NormalizedSpatializedMotionConfig {
147
+ if (Array.isArray(config.timeline) || !config.timeline) {
148
+ throw new Error('[SpatializedMotion] timeline must be an object')
149
+ }
150
+
151
+ const entries = Object.entries(config.timeline)
152
+ let hasPercentageKey = false
153
+ const frames = entries
154
+ .map(([key, values]) => {
155
+ if (!values || typeof values !== 'object' || Array.isArray(values)) {
156
+ throw new Error(`[SpatializedMotion] invalid timeline frame "${key}"`)
157
+ }
158
+ if (key === 'from') return { key, atRatio: 0, values }
159
+ if (key === 'to') return { key, atRatio: 1, values }
160
+ hasPercentageKey = true
161
+ return { key, atRatio: parsePercentageKey(key), values }
162
+ })
163
+ .sort((a, b) => a.atRatio - b.atRatio)
164
+
165
+ // Dev warn: top-level from/to are ignored once a timeline is present.
166
+ if (config.from !== undefined || config.to !== undefined) {
167
+ console.warn(
168
+ '[SpatializedMotion] top-level from/to are ignored because timeline is present',
169
+ )
170
+ }
171
+
172
+ // Start/end boundary frames are required; this change does not fill missing
173
+ // boundaries from underlying element style.
174
+ const hasStartFrame = frames.some(frame => frame.atRatio === 0)
175
+ const hasEndFrame = frames.some(frame => frame.atRatio === 1)
176
+ if (!hasStartFrame) {
177
+ throw new Error(
178
+ '[SpatializedMotion] timeline must define a start frame (from or 0%)',
179
+ )
180
+ }
181
+ if (!hasEndFrame) {
182
+ throw new Error(
183
+ '[SpatializedMotion] timeline must define an end frame (to or 100%)',
184
+ )
185
+ }
186
+
187
+ if (hasPercentageKey && config.duration === undefined) {
188
+ throw new Error(
189
+ '[SpatializedMotion] duration is required when timeline uses percentage keys',
190
+ )
191
+ }
192
+ const duration = config.duration ?? 0.3
193
+
194
+ const trackMap = new Map<
195
+ SpatializedMotionProperty,
196
+ Array<{
197
+ at: number
198
+ value: number
199
+ timingFunction?: TimingFunction
200
+ }>
201
+ >()
202
+ const fromProperties = new Set<SpatializedMotionProperty>()
203
+ const zeroProperties = new Set<SpatializedMotionProperty>()
204
+ const toProperties = new Set<SpatializedMotionProperty>()
205
+ const hundredProperties = new Set<SpatializedMotionProperty>()
206
+
207
+ for (const frame of frames) {
208
+ const at = frame.atRatio * duration
209
+ const scalars: Array<{
210
+ property: SpatializedMotionProperty
211
+ value: number
212
+ }> = []
213
+ collectScalars(frame.values, scalars)
214
+ for (const scalar of scalars) {
215
+ if (frame.key === 'from') fromProperties.add(scalar.property)
216
+ if (frame.key !== 'from' && frame.atRatio === 0) {
217
+ zeroProperties.add(scalar.property)
218
+ }
219
+ if (frame.key === 'to') toProperties.add(scalar.property)
220
+ if (frame.key !== 'to' && frame.atRatio === 1) {
221
+ hundredProperties.add(scalar.property)
222
+ }
223
+ const keyframes = trackMap.get(scalar.property) ?? []
224
+ const keyframe = {
225
+ at,
226
+ value: scalar.value,
227
+ ...(frame.values.timingFunction !== undefined
228
+ ? { timingFunction: frame.values.timingFunction }
229
+ : {}),
230
+ }
231
+ keyframes.push(keyframe)
232
+ trackMap.set(scalar.property, keyframes)
233
+ }
234
+ }
235
+
236
+ const tracks = [...trackMap.entries()].map(([property, keyframes]) => {
237
+ const startCount =
238
+ Number(fromProperties.has(property)) +
239
+ Number(zeroProperties.has(property))
240
+ const endCount =
241
+ Number(toProperties.has(property)) +
242
+ Number(hundredProperties.has(property))
243
+ if (startCount > 1) {
244
+ throw new Error(
245
+ `[SpatializedMotion] duplicate start boundary for "${property}"`,
246
+ )
247
+ }
248
+ if (endCount > 1) {
249
+ throw new Error(
250
+ `[SpatializedMotion] duplicate end boundary for "${property}"`,
251
+ )
252
+ }
253
+ return {
254
+ property,
255
+ timingFunction: config.timingFunction,
256
+ keyframes,
257
+ }
258
+ })
259
+
260
+ return {
261
+ duration,
262
+ tracks,
263
+ timingFunction: config.timingFunction,
264
+ delay: config.delay,
265
+ autoStart: config.autoStart,
266
+ loop: config.loop,
267
+ playbackRate: config.playbackRate,
268
+ onStart: config.onStart,
269
+ onComplete: config.onComplete,
270
+ onStop: config.onStop,
271
+ onReset: config.onReset,
272
+ onError: config.onError,
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Normalizes any supported motion config shape into canonical tracks.
278
+ *
279
+ * @param config Motion config in shorthand, timeline, or canonical form.
280
+ * @returns Canonical track-based motion config.
281
+ */
282
+ export function normalizeMotionConfig(
283
+ config: SpatializedMotionConfig,
284
+ ): NormalizedSpatializedMotionConfig {
285
+ if (config.timeline !== undefined) {
286
+ return desugarTimelineConfig(config as SpatializedMotionTimelineConfig)
287
+ }
288
+ return segmentConfigToMotionConfig(config as SpatializedMotionSegmentConfig)
289
+ }