1- import { shapeDefaults } from '@shape/defaults/shapes' ;
1+ import type { UnionToIntersection } from 'ts-essentials' ;
2+ import { getSchemaWithDefaults } from '@shape/defaults/shapes' ;
23import type {
4+ EverySchemaProp ,
35 EverySchemaPropName ,
46 SchemaId ,
57 Shape ,
@@ -61,13 +63,18 @@ export const useAnimatedShapes = () => {
6163 const animations = activeAnimations . get ( schemaId ) ;
6264 if ( ! animations || animations . length === 0 ) return ;
6365
64- let outputSchema = animations [ 0 ] . schema ;
66+ let outputSchema = animations [ 0 ] . schemaWithDefaults ;
6567
6668 if ( ! outputSchema ) {
6769 console . warn ( 'animation set without a schema. this should never happen!' ) ;
6870 return ;
6971 }
7072
73+ const shapeName = schemaIdToShapeName . get ( schemaId ) ;
74+ if ( ! shapeName ) {
75+ throw new Error ( '(Internal Error) Animation set without shape name mapping. this should never happen!' ) ;
76+ }
77+
7178 for ( const animation of animations ) {
7279 const timeline = timelineIdToTimeline . get ( animation . timelineId ) ;
7380 if ( ! timeline ) throw new Error ( 'animation activated without a timeline!' ) ;
@@ -77,17 +84,9 @@ export const useAnimatedShapes = () => {
7784 ...animation ,
7885 } ;
7986
80- const shapeName = schemaIdToShapeName . get ( schemaId ) ;
81- if ( ! shapeName ) {
82- console . warn (
83- 'animation set without shape name mapping. this should never happen!' ,
84- ) ;
85- continue ;
86- }
87-
88- if ( ! animationWithTimeline . validShapes . has ( shapeName ) ) {
89- console . warn ( 'invalid shape name!' ) ;
90- continue ;
87+ const { validShapes, timelineId } = animationWithTimeline
88+ if ( ! validShapes . has ( shapeName ) ) {
89+ throw new Error ( `(Internal Error) Attempted to apply inappropriate animation to schema! Animation timeline ${ timelineId } only works for shapes ${ Array . from ( validShapes . keys ( ) ) } but schema ${ schemaId } is of shape ${ shapeName } .` ) ;
9190 }
9291
9392 // cleanup animation if expired
@@ -127,44 +126,44 @@ export const useAnimatedShapes = () => {
127126 factory : ShapeFactory < T > ,
128127 shapeName : ShapeName ,
129128 ) : ShapeFactory < WithId < T > > =>
130- ( schema ) =>
131- new Proxy ( factory ( schema ) , {
132- get : ( target , rawProp ) => {
133- const prop = rawProp as keyof Shape ;
134- if ( ! shapeProps . has ( prop ) ) return target [ prop ] ;
129+ ( schema ) =>
130+ new Proxy ( factory ( schema ) , {
131+ get : ( target , rawProp ) => {
132+ const prop = rawProp as keyof Shape ;
133+ if ( ! shapeProps . has ( prop ) ) return target [ prop ] ;
135134
136- const animations = activeAnimations . get ( schema . id ) ;
135+ const animations = activeAnimations . get ( schema . id ) ;
137136
138- const defaultResolver :
139- | ( ( schema : LooseSchema ) => LooseSchema )
140- | undefined = ( shapeDefaults as any ) ?. [ shapeName ] ;
141- if ( ! defaultResolver )
142- throw new Error ( `cant find defaults for ${ shapeName } ` ) ;
143- const schemaWithDefaults = defaultResolver ( schema ) ;
137+ const defaultResolver :
138+ | ( ( schema : LooseSchema ) => LooseSchema )
139+ | undefined = ( getSchemaWithDefaults as any ) ?. [ shapeName ] ;
140+ if ( ! defaultResolver )
141+ throw new Error ( `cant find defaults for ${ shapeName } ` ) ;
142+ const schemaWithDefaults = defaultResolver ( schema ) ;
144143
145- autoAnimate . captureSchemaState ( schemaWithDefaults , shapeName ) ;
144+ autoAnimate . captureSchemaState ( schemaWithDefaults , shapeName ) ;
146145
147- const targetMapSchema = autoAnimate . snapshotMap . get ( schema . id ) ;
148- if ( targetMapSchema )
149- return factory ( targetMapSchema as WithId < T > ) [ prop ] ;
146+ const targetMapSchema = autoAnimate . snapshotMap . get ( schema . id ) ;
147+ if ( targetMapSchema )
148+ return factory ( targetMapSchema as WithId < T > ) [ prop ] ;
150149
151- if ( ! animations || animations . length === 0 ) return target [ prop ] ;
152- if ( ! animations [ 0 ] ?. schema ) animations [ 0 ] . schema = schemaWithDefaults ;
150+ if ( ! animations || animations . length === 0 ) return target [ prop ] ;
151+ if ( ! animations [ 0 ] ?. schemaWithDefaults ) animations [ 0 ] . schemaWithDefaults = schemaWithDefaults ;
153152
154- if ( prop === 'startTextAreaEdit' )
155- return console . warn (
156- 'shapes with active animations cannot spawn text inputs' ,
157- ) ;
153+ if ( prop === 'startTextAreaEdit' )
154+ return console . warn (
155+ 'shapes with active animations cannot spawn text inputs' ,
156+ ) ;
158157
159- const hasShapeName = schemaIdToShapeName . get ( schema . id ) ;
160- if ( ! hasShapeName ) schemaIdToShapeName . set ( schema . id , shapeName ) ;
158+ const hasShapeName = schemaIdToShapeName . get ( schema . id ) ;
159+ if ( ! hasShapeName ) schemaIdToShapeName . set ( schema . id , shapeName ) ;
161160
162- const animatedSchema = getAnimatedSchema ( schema . id ) ;
163- if ( ! animatedSchema ) return target [ prop ] ;
161+ const animatedSchema = getAnimatedSchema ( schema . id ) ;
162+ if ( ! animatedSchema ) return target [ prop ] ;
164163
165- return factory ( animatedSchema as WithId < T > ) [ prop ] ;
166- } ,
167- } ) ;
164+ return factory ( animatedSchema as WithId < T > ) [ prop ] ;
165+ } ,
166+ } ) ;
168167
169168 return {
170169 shapes : {
@@ -184,6 +183,62 @@ export const useAnimatedShapes = () => {
184183 defineTimeline,
185184 autoAnimate : { captureFrame : autoAnimate . captureFrame } ,
186185 getAnimatedSchema,
186+ /**
187+ * Get the animated value of a schema property currently being animated.
188+ *
189+ * Intended for use in imperative timelines where resolving one property's animated value
190+ * depends on the animated value of another property. In these special cases, `getAnimatedSchema`
191+ * would cause a circular dependency.
192+ *
193+ * WARNING: Calling this on a property that the imperative track itself resolves
194+ * will crash your app!
195+ */
196+ getAnimatedProp : < T extends EverySchemaPropName > ( schemaId : string , inputPropName : T ) => {
197+ const animations = activeAnimations . get ( schemaId ) ;
198+ if ( ! animations || animations . length === 0 ) {
199+ throw new Error ( `Schema with id ${ schemaId } has no running animations` )
200+ } ;
201+
202+ const { schemaWithDefaults } = animations [ 0 ]
203+
204+ if ( ! schemaWithDefaults ) {
205+ throw new Error ( '(Internal Error) Animation set without a schema. this should never happen!' ) ;
206+ }
207+
208+ if ( ! ( inputPropName in schemaWithDefaults ) ) {
209+ throw new Error ( `(User Error) Input prop name ${ inputPropName } not a property on schema (${ Object . keys ( schemaWithDefaults ) } )` )
210+ }
211+
212+ const shapeName = schemaIdToShapeName . get ( schemaId ) ;
213+ if ( ! shapeName ) {
214+ throw new Error ( '(Internal Error) Animation set without shape name mapping. this should never happen!' ) ;
215+ }
216+
217+ let propVal = schemaWithDefaults [ inputPropName ] as UnionToIntersection < EverySchemaProp > [ T ]
218+
219+ for ( const animation of animations ) {
220+ const timeline = timelineIdToTimeline . get ( animation . timelineId ) ;
221+ if ( ! timeline ) throw new Error ( '(Internal Error) Animation activated without a timeline!' ) ;
222+
223+ const animationWithTimeline = {
224+ ...timeline ,
225+ ...animation ,
226+ } ;
227+
228+ const { validShapes, timelineId } = animationWithTimeline
229+ if ( ! validShapes . has ( shapeName ) ) {
230+ throw new Error ( `(Internal Error) Attempted to apply inappropriate animation to schema! Animation timeline ${ timelineId } only works for shapes ${ Array . from ( validShapes . keys ( ) ) } but schema ${ schemaId } is of shape ${ shapeName } .` ) ;
231+ }
232+
233+ const { properties } = animationWithTimeline ;
234+ const progress = getAnimationProgress ( animationWithTimeline ) ;
235+
236+ const fn = properties [ inputPropName as string ]
237+ propVal = fn ( schemaWithDefaults , progress )
238+ }
239+
240+ return propVal ;
241+ } ,
187242 activeAnimations,
188243 } ;
189244} ;
0 commit comments