@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.
- package/CHANGELOG.md +61 -0
- package/README.md +10 -0
- package/dist/JSBCommand.d.ts +398 -0
- package/dist/JSBCommand.js +668 -0
- package/dist/JSBCommand.js.map +1 -0
- package/dist/Spatial.d.ts +63 -0
- package/dist/Spatial.js +60 -0
- package/dist/Spatial.js.map +1 -0
- package/dist/SpatialObject.d.ts +19 -0
- package/dist/SpatialObject.js +44 -0
- package/dist/SpatialObject.js.map +1 -0
- package/dist/SpatialScene.d.ts +51 -0
- package/dist/SpatialScene.js +74 -0
- package/dist/SpatialScene.js.map +1 -0
- package/dist/SpatialSession.d.ts +143 -0
- package/dist/SpatialSession.js +173 -0
- package/dist/SpatialSession.js.map +1 -0
- package/dist/SpatialWebEvent.d.ts +8 -0
- package/dist/SpatialWebEvent.js +18 -0
- package/dist/SpatialWebEvent.js.map +1 -0
- package/dist/SpatialWebEventCreator.d.ts +11 -0
- package/dist/SpatialWebEventCreator.js +11 -0
- package/dist/SpatialWebEventCreator.js.map +1 -0
- package/dist/Spatialized2DElement.d.ts +42 -0
- package/dist/Spatialized2DElement.js +47 -0
- package/dist/Spatialized2DElement.js.map +1 -0
- package/dist/SpatializedDynamic3DElement.d.ts +7 -0
- package/dist/SpatializedDynamic3DElement.js +43 -0
- package/dist/SpatializedDynamic3DElement.js.map +1 -0
- package/dist/SpatializedElement.d.ts +7 -0
- package/dist/SpatializedElement.js +206 -0
- package/dist/SpatializedElement.js.map +1 -0
- package/dist/SpatializedElementCreator.d.ts +15 -0
- package/dist/SpatializedElementCreator.js +47 -0
- package/dist/SpatializedElementCreator.js.map +1 -0
- package/dist/SpatializedStatic3DElement.d.ts +195 -0
- package/dist/SpatializedStatic3DElement.js +327 -0
- package/dist/SpatializedStatic3DElement.js.map +1 -0
- package/dist/WebMsgCommand.d.ts +7 -0
- package/dist/WebMsgCommand.js +23 -0
- package/dist/WebMsgCommand.js.map +1 -0
- package/dist/index.d.ts +35 -1251
- package/dist/index.js +21 -3650
- package/dist/index.js.map +1 -1
- package/dist/install-polyfills.d.ts +2 -0
- package/dist/install-polyfills.js +13 -0
- package/dist/install-polyfills.js.map +1 -0
- package/dist/isSSREnv.d.ts +3 -0
- package/dist/isSSREnv.js +6 -0
- package/dist/isSSREnv.js.map +1 -0
- package/dist/motion/AnimationObject.d.ts +7 -0
- package/dist/motion/AnimationObject.js +140 -0
- package/dist/motion/AnimationObject.js.map +1 -0
- package/dist/motion/compute/index.d.ts +10 -0
- package/dist/motion/compute/index.js +19 -0
- package/dist/motion/compute/index.js.map +1 -0
- package/dist/motion/compute/motionPropertyValues.d.ts +19 -0
- package/dist/motion/compute/motionPropertyValues.js +50 -0
- package/dist/motion/compute/motionPropertyValues.js.map +1 -0
- package/dist/motion/compute/normalize.d.ts +31 -0
- package/dist/motion/compute/normalize.js +201 -0
- package/dist/motion/compute/normalize.js.map +1 -0
- package/dist/motion/compute/validate.d.ts +22 -0
- package/dist/motion/compute/validate.js +103 -0
- package/dist/motion/compute/validate.js.map +1 -0
- package/dist/motion/index.d.ts +8 -0
- package/dist/motion/index.js +6 -0
- package/dist/motion/index.js.map +1 -0
- package/dist/motion/native/serializeMotionTimeline.d.ts +12 -0
- package/dist/motion/native/serializeMotionTimeline.js +21 -0
- package/dist/motion/native/serializeMotionTimeline.js.map +1 -0
- package/dist/physicalMetrics-W5uZ5xKb.d.ts +48 -0
- package/dist/physicalMetrics.d.ts +1 -0
- package/dist/physicalMetrics.js +58 -0
- package/dist/physicalMetrics.js.map +1 -0
- package/dist/platform-adapter/CommandResultUtils.d.ts +12 -0
- package/dist/platform-adapter/CommandResultUtils.js +21 -0
- package/dist/platform-adapter/CommandResultUtils.js.map +1 -0
- package/dist/platform-adapter/createPlatformSync.d.ts +16 -0
- package/dist/platform-adapter/createPlatformSync.js +30 -0
- package/dist/platform-adapter/createPlatformSync.js.map +1 -0
- package/dist/platform-adapter/index.d.ts +12 -0
- package/dist/platform-adapter/index.js +10 -0
- package/dist/platform-adapter/index.js.map +1 -0
- package/dist/platform-adapter/interface.d.ts +7 -0
- package/dist/platform-adapter/interface.js +1 -0
- package/dist/platform-adapter/interface.js.map +1 -0
- package/dist/platform-adapter/pico-os/PicoOSPlatform.d.ts +25 -0
- package/dist/platform-adapter/pico-os/PicoOSPlatform.js +168 -0
- package/dist/platform-adapter/pico-os/PicoOSPlatform.js.map +1 -0
- package/dist/platform-adapter/puppeteer/PuppeteerPlatform.d.ts +66 -0
- package/dist/platform-adapter/puppeteer/PuppeteerPlatform.js +372 -0
- package/dist/platform-adapter/puppeteer/PuppeteerPlatform.js.map +1 -0
- package/dist/platform-adapter/spatialRequestMetadata.d.ts +6 -0
- package/dist/platform-adapter/spatialRequestMetadata.js +39 -0
- package/dist/platform-adapter/spatialRequestMetadata.js.map +1 -0
- package/dist/platform-adapter/spatialSceneQuery.d.ts +12 -0
- package/dist/platform-adapter/spatialSceneQuery.js +12 -0
- package/dist/platform-adapter/spatialSceneQuery.js.map +1 -0
- package/dist/platform-adapter/vision-os/VisionOSPlatform.d.ts +19 -0
- package/dist/platform-adapter/vision-os/VisionOSPlatform.js +77 -0
- package/dist/platform-adapter/vision-os/VisionOSPlatform.js.map +1 -0
- package/dist/platform-runtime.d.ts +12 -0
- package/dist/platform-runtime.js +14 -0
- package/dist/platform-runtime.js.map +1 -0
- package/dist/reality/Attachment.d.ts +19 -0
- package/dist/reality/Attachment.js +39 -0
- package/dist/reality/Attachment.js.map +1 -0
- package/dist/reality/component/ModelComponent.d.ts +14 -0
- package/dist/reality/component/ModelComponent.js +11 -0
- package/dist/reality/component/ModelComponent.js.map +1 -0
- package/dist/reality/component/SpatialComponent.d.ts +8 -0
- package/dist/reality/component/SpatialComponent.js +19 -0
- package/dist/reality/component/SpatialComponent.js.map +1 -0
- package/dist/reality/component/index.d.ts +8 -0
- package/dist/reality/component/index.js +3 -0
- package/dist/reality/component/index.js.map +1 -0
- package/dist/reality/entity/SpatialEntity.d.ts +7 -0
- package/dist/reality/entity/SpatialEntity.js +291 -0
- package/dist/reality/entity/SpatialEntity.js.map +1 -0
- package/dist/reality/entity/SpatialModelEntity.d.ts +17 -0
- package/dist/reality/entity/SpatialModelEntity.js +17 -0
- package/dist/reality/entity/SpatialModelEntity.js.map +1 -0
- package/dist/reality/entity/index.d.ts +8 -0
- package/dist/reality/entity/index.js +3 -0
- package/dist/reality/entity/index.js.map +1 -0
- package/dist/reality/geometry/SpatialBoxGeometry.d.ts +16 -0
- package/dist/reality/geometry/SpatialBoxGeometry.js +13 -0
- package/dist/reality/geometry/SpatialBoxGeometry.js.map +1 -0
- package/dist/reality/geometry/SpatialConeGeometry.d.ts +16 -0
- package/dist/reality/geometry/SpatialConeGeometry.js +13 -0
- package/dist/reality/geometry/SpatialConeGeometry.js.map +1 -0
- package/dist/reality/geometry/SpatialCylinderGeometry.d.ts +16 -0
- package/dist/reality/geometry/SpatialCylinderGeometry.js +13 -0
- package/dist/reality/geometry/SpatialCylinderGeometry.js.map +1 -0
- package/dist/reality/geometry/SpatialGeometry.d.ts +7 -0
- package/dist/reality/geometry/SpatialGeometry.js +13 -0
- package/dist/reality/geometry/SpatialGeometry.js.map +1 -0
- package/dist/reality/geometry/SpatialPlaneGeometry.d.ts +16 -0
- package/dist/reality/geometry/SpatialPlaneGeometry.js +13 -0
- package/dist/reality/geometry/SpatialPlaneGeometry.js.map +1 -0
- package/dist/reality/geometry/SpatialSphereGeometry.d.ts +16 -0
- package/dist/reality/geometry/SpatialSphereGeometry.js +13 -0
- package/dist/reality/geometry/SpatialSphereGeometry.js.map +1 -0
- package/dist/reality/geometry/index.d.ts +12 -0
- package/dist/reality/geometry/index.js +7 -0
- package/dist/reality/geometry/index.js.map +1 -0
- package/dist/reality/index.d.ts +18 -0
- package/dist/reality/index.js +7 -0
- package/dist/reality/index.js.map +1 -0
- package/dist/reality/material/SpatialMaterial.d.ts +7 -0
- package/dist/reality/material/SpatialMaterial.js +13 -0
- package/dist/reality/material/SpatialMaterial.js.map +1 -0
- package/dist/reality/material/SpatialUnlitMaterial.d.ts +16 -0
- package/dist/reality/material/SpatialUnlitMaterial.js +16 -0
- package/dist/reality/material/SpatialUnlitMaterial.js.map +1 -0
- package/dist/reality/material/index.d.ts +8 -0
- package/dist/reality/material/index.js +3 -0
- package/dist/reality/material/index.js.map +1 -0
- package/dist/reality/realityCreator.d.ts +22 -0
- package/dist/reality/realityCreator.js +89 -0
- package/dist/reality/realityCreator.js.map +1 -0
- package/dist/reality/resource/SpatialModelAsset.d.ts +15 -0
- package/dist/reality/resource/SpatialModelAsset.js +12 -0
- package/dist/reality/resource/SpatialModelAsset.js.map +1 -0
- package/dist/reality/resource/SpatialTextureResource.d.ts +16 -0
- package/dist/reality/resource/SpatialTextureResource.js +16 -0
- package/dist/reality/resource/SpatialTextureResource.js.map +1 -0
- package/dist/reality/resource/index.d.ts +9 -0
- package/dist/reality/resource/index.js +3 -0
- package/dist/reality/resource/index.js.map +1 -0
- package/dist/runtime/WebSpatialRuntimeError.d.ts +10 -0
- package/dist/runtime/WebSpatialRuntimeError.js +14 -0
- package/dist/runtime/WebSpatialRuntimeError.js.map +1 -0
- package/dist/runtime/capability-data.d.ts +12 -0
- package/dist/runtime/capability-data.js +134 -0
- package/dist/runtime/capability-data.js.map +1 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.js +27 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/jsbAdapterPlatform.d.ts +9 -0
- package/dist/runtime/jsbAdapterPlatform.js +38 -0
- package/dist/runtime/jsbAdapterPlatform.js.map +1 -0
- package/dist/runtime/keys.d.ts +34 -0
- package/dist/runtime/keys.js +122 -0
- package/dist/runtime/keys.js.map +1 -0
- package/dist/runtime/semver.d.ts +8 -0
- package/dist/runtime/semver.js +32 -0
- package/dist/runtime/semver.js.map +1 -0
- package/dist/runtime/supports.d.ts +22 -0
- package/dist/runtime/supports.js +78 -0
- package/dist/runtime/supports.js.map +1 -0
- package/dist/runtime/types.d.ts +12 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/userAgent.d.ts +20 -0
- package/dist/runtime/userAgent.js +41 -0
- package/dist/runtime/userAgent.js.map +1 -0
- package/dist/scene-polyfill.d.ts +18 -0
- package/dist/scene-polyfill.js +480 -0
- package/dist/scene-polyfill.js.map +1 -0
- package/dist/spatial-host.d.ts +13 -0
- package/dist/spatial-host.js +18 -0
- package/dist/spatial-host.js.map +1 -0
- package/dist/spatial-window-polyfill.d.ts +3 -0
- package/dist/spatial-window-polyfill.js +155 -0
- package/dist/spatial-window-polyfill.js.map +1 -0
- package/dist/spatializedElementMotion-_0XWPQCE.d.ts +1009 -0
- package/dist/types/animation.d.ts +7 -0
- package/dist/types/animation.js +1 -0
- package/dist/types/animation.js.map +1 -0
- package/dist/types/global.d.ts +38 -0
- package/dist/types/global.js +1 -0
- package/dist/types/global.js.map +1 -0
- package/dist/types/internal.d.ts +7 -0
- package/dist/types/internal.js +12 -0
- package/dist/types/internal.js.map +1 -0
- package/dist/types/motion/index.d.ts +7 -0
- package/dist/types/motion/index.js +3 -0
- package/dist/types/motion/index.js.map +1 -0
- package/dist/types/motion/spatializedElementMotion.d.ts +7 -0
- package/dist/types/motion/spatializedElementMotion.js +1 -0
- package/dist/types/motion/spatializedElementMotion.js.map +1 -0
- package/dist/types/motion/spatializedMotion.d.ts +7 -0
- package/dist/types/motion/spatializedMotion.js +16 -0
- package/dist/types/motion/spatializedMotion.js.map +1 -0
- package/dist/types/motion/spatializedPlayback.d.ts +11 -0
- package/dist/types/motion/spatializedPlayback.js +1 -0
- package/dist/types/motion/spatializedPlayback.js.map +1 -0
- package/dist/types/motion/spatializedVisual.d.ts +35 -0
- package/dist/types/motion/spatializedVisual.js +1 -0
- package/dist/types/motion/spatializedVisual.js.map +1 -0
- package/dist/types/types.d.ts +7 -0
- package/dist/types/types.js +117 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils.d.ts +44 -0
- package/dist/utils.js +86 -0
- package/dist/utils.js.map +1 -0
- package/package.json +20 -4
- package/src/JSBCommand.ts +132 -5
- package/src/SpatialObject.ts +10 -0
- package/src/Spatialized2DElement.ts +5 -0
- package/src/SpatializedDynamic3DElement.ts +5 -0
- package/src/SpatializedElement.ts +29 -0
- package/src/SpatializedStatic3DElement.ts +42 -3
- package/src/WebMsgCommand.ts +40 -0
- package/src/coverage-boost.test.ts +239 -216
- package/src/index.ts +25 -11
- package/src/install-polyfills.ts +38 -0
- package/src/jsbcommand.coverage.test.ts +23 -0
- package/src/motion/AnimationObject.test.ts +421 -0
- package/src/motion/AnimationObject.ts +208 -0
- package/src/motion/compute/index.ts +10 -0
- package/src/motion/compute/motionPropertyValues.test.ts +55 -0
- package/src/motion/compute/motionPropertyValues.ts +70 -0
- package/src/motion/compute/normalize.test.ts +99 -0
- package/src/motion/compute/normalize.ts +289 -0
- package/src/motion/compute/validate.test.ts +229 -0
- package/src/motion/compute/validate.ts +156 -0
- package/src/motion/index.ts +2 -0
- package/src/motion/native/serializeMotionTimeline.test.ts +61 -0
- package/src/motion/native/serializeMotionTimeline.ts +27 -0
- package/src/platform-adapter/createPlatformSync.ts +5 -3
- package/src/platform-adapter/pico-os/PicoOSPlatform.ts +76 -11
- package/src/platform-adapter/spatialRequestMetadata.test.ts +175 -0
- package/src/platform-adapter/spatialRequestMetadata.ts +48 -0
- package/src/platform-adapter/vision-os/VisionOSPlatform.ts +6 -1
- package/src/reality/entity/SpatialEntity.ts +127 -0
- package/src/runtime/capability-data.ts +104 -4
- package/src/runtime/index.ts +0 -11
- package/src/runtime/keys.ts +12 -1
- package/src/runtime/supports.test.ts +143 -4
- package/src/runtime/supports.ts +3 -1
- package/src/runtime/useAnimation.capability.test.ts +275 -0
- package/src/scene-polyfill.manifest.test.ts +306 -5
- package/src/scene-polyfill.request-metadata.test.ts +39 -0
- package/src/scene-polyfill.test.ts +33 -94
- package/src/scene-polyfill.ts +117 -87
- package/src/spatial-window-polyfill.ts +18 -2
- package/src/transform-roundtrip.test.ts +90 -0
- package/src/types/animation.ts +224 -0
- package/src/types/global.ts +1 -8
- package/src/types/motion/index.ts +10 -0
- package/src/types/motion/spatializedElementMotion.ts +49 -0
- package/src/types/motion/spatializedMotion.ts +147 -0
- package/src/types/motion/spatializedPlayback.ts +17 -0
- package/src/types/motion/spatializedVisual.ts +24 -0
- package/src/types/types.ts +3 -0
- package/src/utils.ts +54 -0
- package/tsup.config.ts +61 -36
- package/vitest.config.ts +5 -1
- package/dist/iife/index.d.ts +0 -1251
- package/dist/iife/index.global.js +0 -75
- package/dist/iife/index.global.js.map +0 -1
- package/src/platform-adapter/ssr/SSRPlatform.ts +0 -52
- /package/src/{ssr-polyfill.ts → isSSREnv.ts} +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
validateNormalizedMotionConfig,
|
|
4
|
+
validateSpatializedMotionConfig,
|
|
5
|
+
} from './validate'
|
|
6
|
+
|
|
7
|
+
describe('validateNormalizedMotionConfig', () => {
|
|
8
|
+
test('accepts a canonical track-based motion config', () => {
|
|
9
|
+
expect(() =>
|
|
10
|
+
validateNormalizedMotionConfig({
|
|
11
|
+
duration: 1,
|
|
12
|
+
tracks: [
|
|
13
|
+
{
|
|
14
|
+
property: 'transform.translate.x',
|
|
15
|
+
keyframes: [
|
|
16
|
+
{ at: 0, value: 0 },
|
|
17
|
+
{ at: 1, value: 10 },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}),
|
|
22
|
+
).not.toThrow()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('accepts opacity tracks', () => {
|
|
26
|
+
expect(() =>
|
|
27
|
+
validateNormalizedMotionConfig({
|
|
28
|
+
duration: 1,
|
|
29
|
+
tracks: [
|
|
30
|
+
{
|
|
31
|
+
property: 'opacity',
|
|
32
|
+
keyframes: [
|
|
33
|
+
{ at: 0, value: 0 },
|
|
34
|
+
{ at: 1, value: 1 },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
}),
|
|
39
|
+
).not.toThrow()
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('validateSpatializedMotionConfig', () => {
|
|
44
|
+
test('accepts complete top-level segment authoring', () => {
|
|
45
|
+
expect(() =>
|
|
46
|
+
validateSpatializedMotionConfig({
|
|
47
|
+
from: { opacity: 0 },
|
|
48
|
+
to: { opacity: 1 },
|
|
49
|
+
}),
|
|
50
|
+
).not.toThrow()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test.each([{ to: { opacity: 1 } }, { from: { opacity: 0 } }])(
|
|
54
|
+
'rejects incomplete top-level segment authoring',
|
|
55
|
+
config => {
|
|
56
|
+
expect(() => validateSpatializedMotionConfig(config as never)).toThrow(
|
|
57
|
+
/top-level from and to are both required/,
|
|
58
|
+
)
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
test('accepts a timeline without top-level boundaries', () => {
|
|
63
|
+
expect(() =>
|
|
64
|
+
validateSpatializedMotionConfig({
|
|
65
|
+
duration: 1,
|
|
66
|
+
timeline: {
|
|
67
|
+
from: { opacity: 0 },
|
|
68
|
+
'50%': { opacity: 0.5 },
|
|
69
|
+
to: { opacity: 1 },
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
).not.toThrow()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('accepts decimal spellings of percentage boundaries', () => {
|
|
76
|
+
expect(() =>
|
|
77
|
+
validateSpatializedMotionConfig({
|
|
78
|
+
duration: 1,
|
|
79
|
+
timeline: {
|
|
80
|
+
'0.0%': { opacity: 0 },
|
|
81
|
+
'100.0%': { opacity: 1 },
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
).not.toThrow()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('ignores top-level boundaries when timeline is present', () => {
|
|
88
|
+
expect(() =>
|
|
89
|
+
validateSpatializedMotionConfig({
|
|
90
|
+
duration: 1,
|
|
91
|
+
from: { opacity: Number.NaN },
|
|
92
|
+
to: { opacity: Number.NaN },
|
|
93
|
+
timeline: {
|
|
94
|
+
'0%': { opacity: 0 },
|
|
95
|
+
'100%': { opacity: 1 },
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
).not.toThrow()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('rejects a timeline missing the start frame', () => {
|
|
102
|
+
expect(() =>
|
|
103
|
+
validateSpatializedMotionConfig({
|
|
104
|
+
duration: 1,
|
|
105
|
+
timeline: { '50%': { opacity: 0.5 }, to: { opacity: 1 } },
|
|
106
|
+
}),
|
|
107
|
+
).toThrow(/must define a start frame/)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('rejects a timeline missing the end frame', () => {
|
|
111
|
+
expect(() =>
|
|
112
|
+
validateSpatializedMotionConfig({
|
|
113
|
+
duration: 1,
|
|
114
|
+
timeline: { from: { opacity: 0 }, '50%': { opacity: 0.5 } },
|
|
115
|
+
}),
|
|
116
|
+
).toThrow(/must define an end frame/)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('warns when top-level from/to coexist with a timeline', () => {
|
|
120
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
121
|
+
validateSpatializedMotionConfig({
|
|
122
|
+
duration: 1,
|
|
123
|
+
from: { opacity: 0.25 },
|
|
124
|
+
to: { opacity: 0.75 },
|
|
125
|
+
timeline: {
|
|
126
|
+
'0%': { opacity: 0 },
|
|
127
|
+
'100%': { opacity: 1 },
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
131
|
+
expect.stringContaining('top-level from/to are ignored'),
|
|
132
|
+
)
|
|
133
|
+
warnSpy.mockRestore()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('rejects a property with only one keyframe', () => {
|
|
137
|
+
// Timeline has overall start/end frames (opacity at 0% and 100%), but the
|
|
138
|
+
// rotate.z property appears only once at 50%, which is fewer than two keyframes.
|
|
139
|
+
expect(() =>
|
|
140
|
+
validateSpatializedMotionConfig({
|
|
141
|
+
duration: 1,
|
|
142
|
+
timeline: {
|
|
143
|
+
'0%': { opacity: 0 },
|
|
144
|
+
'50%': { transform: { rotate: { z: 45 } } },
|
|
145
|
+
'100%': { opacity: 1 },
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
).toThrow(/track "transform.rotate.z" needs at least 2 keyframes/)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test.each([
|
|
152
|
+
{
|
|
153
|
+
duration: 1,
|
|
154
|
+
timeline: {
|
|
155
|
+
from: { opacity: 0 },
|
|
156
|
+
'0%': { opacity: 0 },
|
|
157
|
+
to: { opacity: 1 },
|
|
158
|
+
},
|
|
159
|
+
expected: /duplicate start boundary.*opacity/,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
duration: 1,
|
|
163
|
+
timeline: {
|
|
164
|
+
from: { opacity: 0 },
|
|
165
|
+
to: { opacity: 1 },
|
|
166
|
+
'100%': { opacity: 1 },
|
|
167
|
+
},
|
|
168
|
+
expected: /duplicate end boundary.*opacity/,
|
|
169
|
+
},
|
|
170
|
+
])('rejects duplicate property boundaries', config => {
|
|
171
|
+
expect(() => validateSpatializedMotionConfig(config as never)).toThrow(
|
|
172
|
+
config.expected,
|
|
173
|
+
)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test.each([
|
|
177
|
+
['0%', '00%'],
|
|
178
|
+
['0%', '0.0%'],
|
|
179
|
+
['50%', '050.0%'],
|
|
180
|
+
])(
|
|
181
|
+
'rejects equivalent percentage keys %s and %s for the same property',
|
|
182
|
+
(firstKey, secondKey) => {
|
|
183
|
+
expect(() =>
|
|
184
|
+
validateSpatializedMotionConfig({
|
|
185
|
+
duration: 1,
|
|
186
|
+
timeline: {
|
|
187
|
+
'0%': { opacity: 0 },
|
|
188
|
+
[firstKey]: { opacity: 0.25 },
|
|
189
|
+
[secondKey]: { opacity: 0.25 },
|
|
190
|
+
'100%': { opacity: 1 },
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
).toThrow(/keyframe times must be strictly increasing.*opacity/)
|
|
194
|
+
},
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
test('accepts equivalent percentage keys for different properties', () => {
|
|
198
|
+
expect(() =>
|
|
199
|
+
validateSpatializedMotionConfig({
|
|
200
|
+
duration: 1,
|
|
201
|
+
timeline: {
|
|
202
|
+
'0%': { opacity: 0 },
|
|
203
|
+
'0.0%': { transform: { translate: { x: 0 } } },
|
|
204
|
+
'100%': {
|
|
205
|
+
opacity: 1,
|
|
206
|
+
transform: { translate: { x: 1 } },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
}),
|
|
210
|
+
).not.toThrow()
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
test('rejects public tracks authoring', () => {
|
|
214
|
+
expect(() =>
|
|
215
|
+
validateSpatializedMotionConfig({
|
|
216
|
+
duration: 1,
|
|
217
|
+
tracks: [
|
|
218
|
+
{
|
|
219
|
+
property: 'opacity',
|
|
220
|
+
keyframes: [
|
|
221
|
+
{ at: 0, value: 0 },
|
|
222
|
+
{ at: 1, value: 1 },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
} as never),
|
|
227
|
+
).toThrow(/tracks authoring is internal/)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { SPATIALIZED_MOTION_PROPERTIES } from '../../types/motion/spatializedMotion'
|
|
2
|
+
import type {
|
|
3
|
+
NormalizedSpatializedMotionConfig,
|
|
4
|
+
SpatializedMotionProperty,
|
|
5
|
+
SpatializedMotionConfig,
|
|
6
|
+
SpatializedMotionTrack,
|
|
7
|
+
} from '../../types/motion/spatializedMotion'
|
|
8
|
+
import { normalizeMotionConfig } from './normalize'
|
|
9
|
+
const ALLOWED_PROPERTIES = new Set<SpatializedMotionProperty>(
|
|
10
|
+
SPATIALIZED_MOTION_PROPERTIES,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const FORBIDDEN_PREFIXES = [
|
|
14
|
+
'width',
|
|
15
|
+
'height',
|
|
16
|
+
'back',
|
|
17
|
+
'backOffset',
|
|
18
|
+
'depth',
|
|
19
|
+
] as const
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates any supported motion config shape before playback or serialization.
|
|
23
|
+
*
|
|
24
|
+
* @param config Motion config in any supported input shape.
|
|
25
|
+
*/
|
|
26
|
+
export function validateSpatializedMotionConfig(
|
|
27
|
+
config: SpatializedMotionConfig,
|
|
28
|
+
): void {
|
|
29
|
+
if (config.tracks !== undefined) {
|
|
30
|
+
throw new Error('[SpatializedMotion] tracks authoring is internal')
|
|
31
|
+
}
|
|
32
|
+
const normalized = normalizeMotionConfig(config)
|
|
33
|
+
validateNormalizedMotionConfig(normalized)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validates the canonical track-based motion config form.
|
|
38
|
+
*
|
|
39
|
+
* @param config Canonical motion config to validate.
|
|
40
|
+
*/
|
|
41
|
+
export function validateNormalizedMotionConfig(
|
|
42
|
+
config: NormalizedSpatializedMotionConfig,
|
|
43
|
+
): void {
|
|
44
|
+
const { duration, tracks } = config
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
typeof duration !== 'number' ||
|
|
48
|
+
!Number.isFinite(duration) ||
|
|
49
|
+
duration <= 0
|
|
50
|
+
) {
|
|
51
|
+
throw new Error('[SpatializedMotion] duration must be a finite number > 0')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!Array.isArray(tracks) || tracks.length === 0) {
|
|
55
|
+
throw new Error('[SpatializedMotion] tracks must be a non-empty array')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const seen = new Set<string>()
|
|
59
|
+
for (const track of tracks) {
|
|
60
|
+
validateTrack(track, duration, seen)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (config.delay !== undefined) {
|
|
64
|
+
const d = config.delay
|
|
65
|
+
if (typeof d !== 'number' || !Number.isFinite(d) || d < 0) {
|
|
66
|
+
throw new Error('[SpatializedMotion] delay must be >= 0 and finite')
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (config.playbackRate !== undefined) {
|
|
71
|
+
const r = config.playbackRate
|
|
72
|
+
if (typeof r !== 'number' || !Number.isFinite(r) || r <= 0) {
|
|
73
|
+
throw new Error('[SpatializedMotion] playbackRate must be > 0 and finite')
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validates a single track against property and keyframe invariants.
|
|
80
|
+
*
|
|
81
|
+
* @param track Motion track to validate.
|
|
82
|
+
* @param duration Parent motion duration.
|
|
83
|
+
* @param seen Track-property set used to detect duplicates.
|
|
84
|
+
*/
|
|
85
|
+
function validateTrack(
|
|
86
|
+
track: SpatializedMotionTrack,
|
|
87
|
+
duration: number,
|
|
88
|
+
seen: Set<string>,
|
|
89
|
+
): void {
|
|
90
|
+
const prop = track.property as string
|
|
91
|
+
for (const forbidden of FORBIDDEN_PREFIXES) {
|
|
92
|
+
if (prop === forbidden || prop.startsWith(`${forbidden}.`)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`[SpatializedMotion] property "${prop}" is not animatable`,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!ALLOWED_PROPERTIES.has(track.property)) {
|
|
100
|
+
throw new Error(`[SpatializedMotion] unknown property "${prop}"`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (seen.has(prop)) {
|
|
104
|
+
throw new Error(`[SpatializedMotion] duplicate track for "${prop}"`)
|
|
105
|
+
}
|
|
106
|
+
seen.add(prop)
|
|
107
|
+
|
|
108
|
+
const kf = track.keyframes
|
|
109
|
+
if (!Array.isArray(kf) || kf.length < 2) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`[SpatializedMotion] track "${prop}" needs at least 2 keyframes`,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let prevAt = -Infinity
|
|
116
|
+
for (const frame of kf) {
|
|
117
|
+
if (typeof frame.at !== 'number' || !Number.isFinite(frame.at)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`[SpatializedMotion] keyframe.at must be finite for "${prop}"`,
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
if (frame.at < 0 || frame.at > duration) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`[SpatializedMotion] keyframe.at must be within [0, duration] for "${prop}"`,
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
if (frame.at <= prevAt) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`[SpatializedMotion] keyframe times must be strictly increasing for "${prop}"`,
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
prevAt = frame.at
|
|
133
|
+
validateScalar(frame.value, track.property)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Validates a numeric sample for a specific motion property.
|
|
139
|
+
*
|
|
140
|
+
* @param value Numeric sample value to validate.
|
|
141
|
+
* @param property Motion property the value belongs to.
|
|
142
|
+
*/
|
|
143
|
+
function validateScalar(
|
|
144
|
+
value: number,
|
|
145
|
+
property: SpatializedMotionProperty,
|
|
146
|
+
): void {
|
|
147
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
148
|
+
throw new Error(`[SpatializedMotion] invalid value for ${property}`)
|
|
149
|
+
}
|
|
150
|
+
if (property === 'opacity' && (value < 0 || value > 1)) {
|
|
151
|
+
throw new Error('[SpatializedMotion] opacity must be in [0, 1]')
|
|
152
|
+
}
|
|
153
|
+
if (property.startsWith('transform.scale.') && value < 0) {
|
|
154
|
+
throw new Error('[SpatializedMotion] scale must be >= 0')
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import { motionConfigToNativeTimeline } from './serializeMotionTimeline'
|
|
3
|
+
|
|
4
|
+
describe('motionConfigToNativeTimeline', () => {
|
|
5
|
+
test('builds timeline payload for multi-track', () => {
|
|
6
|
+
const tl = motionConfigToNativeTimeline({
|
|
7
|
+
duration: 5,
|
|
8
|
+
tracks: [
|
|
9
|
+
{
|
|
10
|
+
property: 'transform.translate.x',
|
|
11
|
+
keyframes: [
|
|
12
|
+
{ at: 0, value: 0 },
|
|
13
|
+
{ at: 5, value: 100 },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
property: 'opacity',
|
|
18
|
+
timingFunction: 'easeOut',
|
|
19
|
+
keyframes: [
|
|
20
|
+
{ at: 3, value: 0 },
|
|
21
|
+
{ at: 5, value: 1 },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
})
|
|
26
|
+
expect(tl.duration).toBe(5)
|
|
27
|
+
expect(tl.tracks).toHaveLength(2)
|
|
28
|
+
expect(tl.tracks[1].timingFunction).toBe('easeOut')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('serializes keyframe and config timingFunction propagation', () => {
|
|
32
|
+
const tl = motionConfigToNativeTimeline({
|
|
33
|
+
duration: 4,
|
|
34
|
+
timingFunction: 'easeInOut',
|
|
35
|
+
tracks: [
|
|
36
|
+
{
|
|
37
|
+
property: 'transform.translate.x',
|
|
38
|
+
keyframes: [
|
|
39
|
+
{ at: 0, value: 0, timingFunction: 'easeOut' },
|
|
40
|
+
{ at: 2, value: 100 },
|
|
41
|
+
{ at: 4, value: 200 },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
property: 'opacity',
|
|
46
|
+
keyframes: [
|
|
47
|
+
{ at: 0, value: 0 },
|
|
48
|
+
{ at: 4, value: 1 },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(tl.tracks[0].keyframes[0]).toMatchObject({
|
|
55
|
+
at: 0,
|
|
56
|
+
value: 0,
|
|
57
|
+
timingFunction: 'easeOut',
|
|
58
|
+
})
|
|
59
|
+
expect(tl.tracks[1].timingFunction).toBe('easeInOut')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NormalizedSpatializedMotionConfig,
|
|
3
|
+
SpatializedMotionTimeline,
|
|
4
|
+
} from '../../types/motion/spatializedMotion'
|
|
5
|
+
|
|
6
|
+
/** Full timeline payload for native. */
|
|
7
|
+
export function motionConfigToNativeTimeline(
|
|
8
|
+
config: NormalizedSpatializedMotionConfig,
|
|
9
|
+
): SpatializedMotionTimeline {
|
|
10
|
+
return {
|
|
11
|
+
duration: config.duration,
|
|
12
|
+
delay: config.delay,
|
|
13
|
+
playbackRate: config.playbackRate,
|
|
14
|
+
loop: config.loop,
|
|
15
|
+
tracks: config.tracks.map(track => ({
|
|
16
|
+
property: track.property,
|
|
17
|
+
keyframes: track.keyframes.map(k => ({
|
|
18
|
+
at: k.at,
|
|
19
|
+
value: k.value,
|
|
20
|
+
...(k.timingFunction !== undefined
|
|
21
|
+
? { timingFunction: k.timingFunction }
|
|
22
|
+
: {}),
|
|
23
|
+
})),
|
|
24
|
+
timingFunction: track.timingFunction ?? config.timingFunction ?? 'linear',
|
|
25
|
+
})),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { resolveJsbAdapterPlatform } from '../runtime/jsbAdapterPlatform'
|
|
2
|
-
import { isSSREnv } from '../
|
|
2
|
+
import { isSSREnv } from '../isSSREnv'
|
|
3
3
|
import { PlatformAbility } from './interface'
|
|
4
4
|
import { PicoOSPlatform } from './pico-os/PicoOSPlatform'
|
|
5
5
|
import { PuppeteerPlatform } from './puppeteer/PuppeteerPlatform'
|
|
6
|
-
import { SSRPlatform } from './ssr/SSRPlatform'
|
|
7
6
|
import { VisionOSPlatform } from './vision-os/VisionOSPlatform'
|
|
8
7
|
|
|
8
|
+
const SSR_PLATFORM_ERROR =
|
|
9
|
+
'WebSpatial platform APIs (JSB / openSpatialScene) cannot run during SSR or without window. Use @webspatial/react-sdk default entry with facades, or CSR-gate spatial UI on the client.'
|
|
10
|
+
|
|
9
11
|
function assertNever(_: never): never {
|
|
10
12
|
throw new Error('Unhandled jsb adapter platform kind')
|
|
11
13
|
}
|
|
@@ -17,7 +19,7 @@ function assertNever(_: never): never {
|
|
|
17
19
|
*/
|
|
18
20
|
export function createPlatformSync(): PlatformAbility {
|
|
19
21
|
if (isSSREnv()) {
|
|
20
|
-
|
|
22
|
+
throw new Error(SSR_PLATFORM_ERROR)
|
|
21
23
|
}
|
|
22
24
|
const userAgent = window.navigator.userAgent
|
|
23
25
|
const kind = resolveJsbAdapterPlatform(userAgent)
|
|
@@ -11,6 +11,11 @@ import { buildSpatialSceneQuery } from '../spatialSceneQuery'
|
|
|
11
11
|
import { SpatialWebEvent } from '../../SpatialWebEvent'
|
|
12
12
|
import type { SpatialSceneCreationOptionsInternal } from '../../types/internal'
|
|
13
13
|
import type { AttachmentEntityOptions } from '../../types/types'
|
|
14
|
+
import {
|
|
15
|
+
buildSpatialRequestQuery,
|
|
16
|
+
createSpatialRequestId,
|
|
17
|
+
DEFAULT_SPATIAL_REQUEST_TIMEOUT_MS,
|
|
18
|
+
} from '../spatialRequestMetadata'
|
|
14
19
|
|
|
15
20
|
interface JSBResponse {
|
|
16
21
|
success: boolean
|
|
@@ -95,26 +100,86 @@ export class PicoOSPlatform implements PlatformAbility {
|
|
|
95
100
|
command: string,
|
|
96
101
|
): Promise<WebSpatialProtocolResult> {
|
|
97
102
|
return new Promise(resolve => {
|
|
98
|
-
const createdId =
|
|
103
|
+
const createdId = createSpatialRequestId()
|
|
104
|
+
let settled = false
|
|
105
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
106
|
+
let cleanupDocumentReadyListener: (() => void) | undefined
|
|
107
|
+
|
|
108
|
+
// Native creation may respond synchronously, asynchronously, or never.
|
|
109
|
+
// Funnel every path through one settle() helper so receiver cleanup is
|
|
110
|
+
// exactly-once.
|
|
111
|
+
const settle = (result: WebSpatialProtocolResult) => {
|
|
112
|
+
if (settled) return
|
|
113
|
+
settled = true
|
|
114
|
+
if (timeoutId !== undefined) {
|
|
115
|
+
clearTimeout(timeoutId)
|
|
116
|
+
}
|
|
117
|
+
cleanupDocumentReadyListener?.()
|
|
118
|
+
cleanupDocumentReadyListener = undefined
|
|
119
|
+
SpatialWebEvent.removeEventReceiver(createdId)
|
|
120
|
+
resolve(result)
|
|
121
|
+
}
|
|
122
|
+
|
|
99
123
|
try {
|
|
100
124
|
let windowProxy: WindowProxy | null = null
|
|
125
|
+
const settleWhenDocumentComplete = (spatialId: string) => {
|
|
126
|
+
const successResult = CommandResultSuccess({
|
|
127
|
+
windowProxy: windowProxy,
|
|
128
|
+
id: spatialId,
|
|
129
|
+
})
|
|
130
|
+
const childDocument = windowProxy?.document
|
|
131
|
+
|
|
132
|
+
if (!childDocument) {
|
|
133
|
+
settle(
|
|
134
|
+
CommandResultFailure(
|
|
135
|
+
'E_SPATIAL_DOCUMENT_UNAVAILABLE',
|
|
136
|
+
`${command} document is unavailable`,
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (childDocument.readyState === 'complete') {
|
|
143
|
+
settle(successResult)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const onReadyStateChange = () => {
|
|
148
|
+
if (childDocument.readyState !== 'complete') return
|
|
149
|
+
settle(successResult)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
cleanupDocumentReadyListener = () => {
|
|
153
|
+
childDocument.removeEventListener(
|
|
154
|
+
'readystatechange',
|
|
155
|
+
onReadyStateChange,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
childDocument.addEventListener('readystatechange', onReadyStateChange)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
timeoutId = setTimeout(() => {
|
|
162
|
+
settle(
|
|
163
|
+
CommandResultFailure(
|
|
164
|
+
'E_SPATIAL_REQUEST_TIMEOUT',
|
|
165
|
+
`${command} request timed out`,
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
}, DEFAULT_SPATIAL_REQUEST_TIMEOUT_MS)
|
|
169
|
+
|
|
101
170
|
SpatialWebEvent.addEventReceiver(
|
|
102
171
|
createdId,
|
|
103
172
|
(result: { spatialId: string }) => {
|
|
104
|
-
|
|
105
|
-
CommandResultSuccess({
|
|
106
|
-
windowProxy: windowProxy,
|
|
107
|
-
id: result.spatialId,
|
|
108
|
-
}),
|
|
109
|
-
)
|
|
110
|
-
SpatialWebEvent.removeEventReceiver(createdId)
|
|
173
|
+
settleWhenDocumentComplete(result.spatialId)
|
|
111
174
|
},
|
|
112
175
|
)
|
|
113
|
-
windowProxy = this.openWindow(
|
|
176
|
+
windowProxy = this.openWindow(
|
|
177
|
+
command,
|
|
178
|
+
buildSpatialRequestQuery(createdId),
|
|
179
|
+
).windowProxy
|
|
114
180
|
} catch (error: unknown) {
|
|
115
181
|
const { code, message } = this.parseJSBError(error)
|
|
116
|
-
|
|
117
|
-
resolve(CommandResultFailure(code, message))
|
|
182
|
+
settle(CommandResultFailure(code, message))
|
|
118
183
|
}
|
|
119
184
|
})
|
|
120
185
|
}
|