@number10/jsx-core 5.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 (340) hide show
  1. package/LICENSE +667 -0
  2. package/README.md +38 -0
  3. package/dist/animation/animated-signal.cjs +19 -0
  4. package/dist/animation/animated-signal.cjs.map +1 -0
  5. package/dist/animation/animated-signal.d.ts +35 -0
  6. package/dist/animation/animated-signal.d.ts.map +1 -0
  7. package/dist/animation/animated-signal.js +19 -0
  8. package/dist/animation/animated-signal.js.map +1 -0
  9. package/dist/animation/create-spring.cjs +80 -0
  10. package/dist/animation/create-spring.cjs.map +1 -0
  11. package/dist/animation/create-spring.d.ts +56 -0
  12. package/dist/animation/create-spring.d.ts.map +1 -0
  13. package/dist/animation/create-spring.js +80 -0
  14. package/dist/animation/create-spring.js.map +1 -0
  15. package/dist/animation/easing.cjs +76 -0
  16. package/dist/animation/easing.cjs.map +1 -0
  17. package/dist/animation/easing.d.ts +45 -0
  18. package/dist/animation/easing.d.ts.map +1 -0
  19. package/dist/animation/easing.js +76 -0
  20. package/dist/animation/easing.js.map +1 -0
  21. package/dist/animation/effect-definitions.cjs +441 -0
  22. package/dist/animation/effect-definitions.cjs.map +1 -0
  23. package/dist/animation/effect-definitions.d.ts +60 -0
  24. package/dist/animation/effect-definitions.d.ts.map +1 -0
  25. package/dist/animation/effect-definitions.js +441 -0
  26. package/dist/animation/effect-definitions.js.map +1 -0
  27. package/dist/animation/effect-engine.cjs +189 -0
  28. package/dist/animation/effect-engine.cjs.map +1 -0
  29. package/dist/animation/effect-engine.d.ts +70 -0
  30. package/dist/animation/effect-engine.d.ts.map +1 -0
  31. package/dist/animation/effect-engine.js +189 -0
  32. package/dist/animation/effect-engine.js.map +1 -0
  33. package/dist/animation/index.d.ts +24 -0
  34. package/dist/animation/index.d.ts.map +1 -0
  35. package/dist/animation/spring-physics.cjs +94 -0
  36. package/dist/animation/spring-physics.cjs.map +1 -0
  37. package/dist/animation/spring-physics.d.ts +145 -0
  38. package/dist/animation/spring-physics.d.ts.map +1 -0
  39. package/dist/animation/spring-physics.js +94 -0
  40. package/dist/animation/spring-physics.js.map +1 -0
  41. package/dist/animation/tween.cjs +67 -0
  42. package/dist/animation/tween.cjs.map +1 -0
  43. package/dist/animation/tween.d.ts +60 -0
  44. package/dist/animation/tween.d.ts.map +1 -0
  45. package/dist/animation/tween.js +67 -0
  46. package/dist/animation/tween.js.map +1 -0
  47. package/dist/animation/types.d.ts +31 -0
  48. package/dist/animation/types.d.ts.map +1 -0
  49. package/dist/animation/useSpring.cjs +152 -0
  50. package/dist/animation/useSpring.cjs.map +1 -0
  51. package/dist/animation/useSpring.d.ts +55 -0
  52. package/dist/animation/useSpring.d.ts.map +1 -0
  53. package/dist/animation/useSpring.js +149 -0
  54. package/dist/animation/useSpring.js.map +1 -0
  55. package/dist/animation/useTween.cjs +208 -0
  56. package/dist/animation/useTween.cjs.map +1 -0
  57. package/dist/animation/useTween.d.ts +116 -0
  58. package/dist/animation/useTween.d.ts.map +1 -0
  59. package/dist/animation/useTween.js +208 -0
  60. package/dist/animation/useTween.js.map +1 -0
  61. package/dist/border-geometry.cjs +447 -0
  62. package/dist/border-geometry.cjs.map +1 -0
  63. package/dist/border-geometry.d.ts +114 -0
  64. package/dist/border-geometry.d.ts.map +1 -0
  65. package/dist/border-geometry.js +447 -0
  66. package/dist/border-geometry.js.map +1 -0
  67. package/dist/colors/color-presets.cjs +236 -0
  68. package/dist/colors/color-presets.cjs.map +1 -0
  69. package/dist/colors/color-presets.d.ts +89 -0
  70. package/dist/colors/color-presets.d.ts.map +1 -0
  71. package/dist/colors/color-presets.js +236 -0
  72. package/dist/colors/color-presets.js.map +1 -0
  73. package/dist/colors/color-theme-helpers.cjs +32 -0
  74. package/dist/colors/color-theme-helpers.cjs.map +1 -0
  75. package/dist/colors/color-theme-helpers.d.ts +65 -0
  76. package/dist/colors/color-theme-helpers.d.ts.map +1 -0
  77. package/dist/colors/color-theme-helpers.js +32 -0
  78. package/dist/colors/color-theme-helpers.js.map +1 -0
  79. package/dist/colors/color-types.cjs +38 -0
  80. package/dist/colors/color-types.cjs.map +1 -0
  81. package/dist/colors/color-types.d.ts +112 -0
  82. package/dist/colors/color-types.d.ts.map +1 -0
  83. package/dist/colors/color-types.js +38 -0
  84. package/dist/colors/color-types.js.map +1 -0
  85. package/dist/colors/color-utils.cjs +197 -0
  86. package/dist/colors/color-utils.cjs.map +1 -0
  87. package/dist/colors/color-utils.d.ts +216 -0
  88. package/dist/colors/color-utils.d.ts.map +1 -0
  89. package/dist/colors/color-utils.js +197 -0
  90. package/dist/colors/color-utils.js.map +1 -0
  91. package/dist/colors/contrast-contracts.cjs +265 -0
  92. package/dist/colors/contrast-contracts.cjs.map +1 -0
  93. package/dist/colors/contrast-contracts.d.ts +289 -0
  94. package/dist/colors/contrast-contracts.d.ts.map +1 -0
  95. package/dist/colors/contrast-contracts.js +265 -0
  96. package/dist/colors/contrast-contracts.js.map +1 -0
  97. package/dist/colors/index.cjs +55 -0
  98. package/dist/colors/index.cjs.map +1 -0
  99. package/dist/colors/index.d.ts +16 -0
  100. package/dist/colors/index.d.ts.map +1 -0
  101. package/dist/colors/index.js +55 -0
  102. package/dist/colors/index.js.map +1 -0
  103. package/dist/core-props.cjs +66 -0
  104. package/dist/core-props.cjs.map +1 -0
  105. package/dist/core-props.d.ts +395 -0
  106. package/dist/core-props.d.ts.map +1 -0
  107. package/dist/core-props.js +66 -0
  108. package/dist/core-props.js.map +1 -0
  109. package/dist/design-tokens/design-token-presets.cjs +91 -0
  110. package/dist/design-tokens/design-token-presets.cjs.map +1 -0
  111. package/dist/design-tokens/design-token-presets.d.ts +34 -0
  112. package/dist/design-tokens/design-token-presets.d.ts.map +1 -0
  113. package/dist/design-tokens/design-token-presets.js +91 -0
  114. package/dist/design-tokens/design-token-presets.js.map +1 -0
  115. package/dist/design-tokens/design-token-types.d.ts +97 -0
  116. package/dist/design-tokens/design-token-types.d.ts.map +1 -0
  117. package/dist/design-tokens/index.cjs +10 -0
  118. package/dist/design-tokens/index.cjs.map +1 -0
  119. package/dist/design-tokens/index.d.ts +9 -0
  120. package/dist/design-tokens/index.d.ts.map +1 -0
  121. package/dist/design-tokens/index.js +10 -0
  122. package/dist/design-tokens/index.js.map +1 -0
  123. package/dist/dev-config.cjs +296 -0
  124. package/dist/dev-config.cjs.map +1 -0
  125. package/dist/dev-config.d.ts +238 -0
  126. package/dist/dev-config.d.ts.map +1 -0
  127. package/dist/dev-config.js +296 -0
  128. package/dist/dev-config.js.map +1 -0
  129. package/dist/effects.cjs +69 -0
  130. package/dist/effects.cjs.map +1 -0
  131. package/dist/effects.d.ts +152 -0
  132. package/dist/effects.d.ts.map +1 -0
  133. package/dist/effects.js +69 -0
  134. package/dist/effects.js.map +1 -0
  135. package/dist/global-registry.cjs +25 -0
  136. package/dist/global-registry.cjs.map +1 -0
  137. package/dist/global-registry.d.ts +19 -0
  138. package/dist/global-registry.d.ts.map +1 -0
  139. package/dist/global-registry.js +25 -0
  140. package/dist/global-registry.js.map +1 -0
  141. package/dist/hooks.cjs +378 -0
  142. package/dist/hooks.cjs.map +1 -0
  143. package/dist/hooks.d.ts +322 -0
  144. package/dist/hooks.d.ts.map +1 -0
  145. package/dist/hooks.js +378 -0
  146. package/dist/hooks.js.map +1 -0
  147. package/dist/host-adapter.d.ts +414 -0
  148. package/dist/host-adapter.d.ts.map +1 -0
  149. package/dist/index.cjs +229 -0
  150. package/dist/index.cjs.map +1 -0
  151. package/dist/index.d.ts +32 -0
  152. package/dist/index.d.ts.map +1 -0
  153. package/dist/index.js +226 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/jsx-dev-runtime.cjs +10 -0
  156. package/dist/jsx-dev-runtime.cjs.map +1 -0
  157. package/dist/jsx-dev-runtime.d.ts +5 -0
  158. package/dist/jsx-dev-runtime.d.ts.map +1 -0
  159. package/dist/jsx-dev-runtime.js +11 -0
  160. package/dist/jsx-dev-runtime.js.map +1 -0
  161. package/dist/jsx-runtime.cjs +44 -0
  162. package/dist/jsx-runtime.cjs.map +1 -0
  163. package/dist/jsx-runtime.d.ts +24 -0
  164. package/dist/jsx-runtime.d.ts.map +1 -0
  165. package/dist/jsx-runtime.js +44 -0
  166. package/dist/jsx-runtime.js.map +1 -0
  167. package/dist/jsx-types.d.ts +25 -0
  168. package/dist/jsx-types.d.ts.map +1 -0
  169. package/dist/layout/appliers/container-applier.cjs +20 -0
  170. package/dist/layout/appliers/container-applier.cjs.map +1 -0
  171. package/dist/layout/appliers/container-applier.d.ts +9 -0
  172. package/dist/layout/appliers/container-applier.d.ts.map +1 -0
  173. package/dist/layout/appliers/container-applier.js +20 -0
  174. package/dist/layout/appliers/container-applier.js.map +1 -0
  175. package/dist/layout/appliers/position-applier.cjs +31 -0
  176. package/dist/layout/appliers/position-applier.cjs.map +1 -0
  177. package/dist/layout/appliers/position-applier.d.ts +8 -0
  178. package/dist/layout/appliers/position-applier.d.ts.map +1 -0
  179. package/dist/layout/appliers/position-applier.js +31 -0
  180. package/dist/layout/appliers/position-applier.js.map +1 -0
  181. package/dist/layout/layout-engine-test-utils.d.ts +26 -0
  182. package/dist/layout/layout-engine-test-utils.d.ts.map +1 -0
  183. package/dist/layout/layout-engine.cjs +628 -0
  184. package/dist/layout/layout-engine.cjs.map +1 -0
  185. package/dist/layout/layout-engine.d.ts +140 -0
  186. package/dist/layout/layout-engine.d.ts.map +1 -0
  187. package/dist/layout/layout-engine.js +628 -0
  188. package/dist/layout/layout-engine.js.map +1 -0
  189. package/dist/layout/strategies/base-strategy.cjs +31 -0
  190. package/dist/layout/strategies/base-strategy.cjs.map +1 -0
  191. package/dist/layout/strategies/base-strategy.d.ts +59 -0
  192. package/dist/layout/strategies/base-strategy.d.ts.map +1 -0
  193. package/dist/layout/strategies/base-strategy.js +31 -0
  194. package/dist/layout/strategies/base-strategy.js.map +1 -0
  195. package/dist/layout/strategies/column-layout.cjs +145 -0
  196. package/dist/layout/strategies/column-layout.cjs.map +1 -0
  197. package/dist/layout/strategies/column-layout.d.ts +38 -0
  198. package/dist/layout/strategies/column-layout.d.ts.map +1 -0
  199. package/dist/layout/strategies/column-layout.js +145 -0
  200. package/dist/layout/strategies/column-layout.js.map +1 -0
  201. package/dist/layout/strategies/row-layout.cjs +145 -0
  202. package/dist/layout/strategies/row-layout.cjs.map +1 -0
  203. package/dist/layout/strategies/row-layout.d.ts +38 -0
  204. package/dist/layout/strategies/row-layout.d.ts.map +1 -0
  205. package/dist/layout/strategies/row-layout.js +145 -0
  206. package/dist/layout/strategies/row-layout.js.map +1 -0
  207. package/dist/layout/strategies/stack-layout.cjs +94 -0
  208. package/dist/layout/strategies/stack-layout.cjs.map +1 -0
  209. package/dist/layout/strategies/stack-layout.d.ts +30 -0
  210. package/dist/layout/strategies/stack-layout.d.ts.map +1 -0
  211. package/dist/layout/strategies/stack-layout.js +94 -0
  212. package/dist/layout/strategies/stack-layout.js.map +1 -0
  213. package/dist/layout/types.d.ts +134 -0
  214. package/dist/layout/types.d.ts.map +1 -0
  215. package/dist/layout/utils/child-utils.cjs +129 -0
  216. package/dist/layout/utils/child-utils.cjs.map +1 -0
  217. package/dist/layout/utils/child-utils.d.ts +54 -0
  218. package/dist/layout/utils/child-utils.d.ts.map +1 -0
  219. package/dist/layout/utils/child-utils.js +129 -0
  220. package/dist/layout/utils/child-utils.js.map +1 -0
  221. package/dist/layout/utils/dimension-calculator.cjs +109 -0
  222. package/dist/layout/utils/dimension-calculator.cjs.map +1 -0
  223. package/dist/layout/utils/dimension-calculator.d.ts +59 -0
  224. package/dist/layout/utils/dimension-calculator.d.ts.map +1 -0
  225. package/dist/layout/utils/dimension-calculator.js +109 -0
  226. package/dist/layout/utils/dimension-calculator.js.map +1 -0
  227. package/dist/layout/utils/flex-distributor.cjs +173 -0
  228. package/dist/layout/utils/flex-distributor.cjs.map +1 -0
  229. package/dist/layout/utils/flex-distributor.d.ts +18 -0
  230. package/dist/layout/utils/flex-distributor.d.ts.map +1 -0
  231. package/dist/layout/utils/flex-distributor.js +173 -0
  232. package/dist/layout/utils/flex-distributor.js.map +1 -0
  233. package/dist/layout/utils/size-resolver.cjs +280 -0
  234. package/dist/layout/utils/size-resolver.cjs.map +1 -0
  235. package/dist/layout/utils/size-resolver.d.ts +84 -0
  236. package/dist/layout/utils/size-resolver.d.ts.map +1 -0
  237. package/dist/layout/utils/size-resolver.js +280 -0
  238. package/dist/layout/utils/size-resolver.js.map +1 -0
  239. package/dist/layout/utils/spacing-calculator.cjs +50 -0
  240. package/dist/layout/utils/spacing-calculator.cjs.map +1 -0
  241. package/dist/layout/utils/spacing-calculator.d.ts +27 -0
  242. package/dist/layout/utils/spacing-calculator.d.ts.map +1 -0
  243. package/dist/layout/utils/spacing-calculator.js +50 -0
  244. package/dist/layout/utils/spacing-calculator.js.map +1 -0
  245. package/dist/memo.cjs +18 -0
  246. package/dist/memo.cjs.map +1 -0
  247. package/dist/memo.d.ts +29 -0
  248. package/dist/memo.d.ts.map +1 -0
  249. package/dist/memo.js +18 -0
  250. package/dist/memo.js.map +1 -0
  251. package/dist/nineslice-contract.cjs +60 -0
  252. package/dist/nineslice-contract.cjs.map +1 -0
  253. package/dist/nineslice-contract.d.ts +107 -0
  254. package/dist/nineslice-contract.d.ts.map +1 -0
  255. package/dist/nineslice-contract.js +60 -0
  256. package/dist/nineslice-contract.js.map +1 -0
  257. package/dist/placeholder.cjs +12 -0
  258. package/dist/placeholder.cjs.map +1 -0
  259. package/dist/placeholder.d.ts +44 -0
  260. package/dist/placeholder.d.ts.map +1 -0
  261. package/dist/placeholder.js +12 -0
  262. package/dist/placeholder.js.map +1 -0
  263. package/dist/portal.cjs +89 -0
  264. package/dist/portal.cjs.map +1 -0
  265. package/dist/portal.d.ts +62 -0
  266. package/dist/portal.d.ts.map +1 -0
  267. package/dist/portal.js +89 -0
  268. package/dist/portal.js.map +1 -0
  269. package/dist/primitive-contracts.cjs +12 -0
  270. package/dist/primitive-contracts.cjs.map +1 -0
  271. package/dist/primitive-contracts.d.ts +186 -0
  272. package/dist/primitive-contracts.d.ts.map +1 -0
  273. package/dist/primitive-contracts.js +12 -0
  274. package/dist/primitive-contracts.js.map +1 -0
  275. package/dist/theme.cjs +332 -0
  276. package/dist/theme.cjs.map +1 -0
  277. package/dist/theme.d.ts +204 -0
  278. package/dist/theme.d.ts.map +1 -0
  279. package/dist/theme.js +332 -0
  280. package/dist/theme.js.map +1 -0
  281. package/dist/types.d.ts +86 -0
  282. package/dist/types.d.ts.map +1 -0
  283. package/dist/vdom/create-runtime.cjs +79 -0
  284. package/dist/vdom/create-runtime.cjs.map +1 -0
  285. package/dist/vdom/create-runtime.d.ts +7 -0
  286. package/dist/vdom/create-runtime.d.ts.map +1 -0
  287. package/dist/vdom/create-runtime.js +79 -0
  288. package/dist/vdom/create-runtime.js.map +1 -0
  289. package/dist/vdom/index.cjs +8 -0
  290. package/dist/vdom/index.cjs.map +1 -0
  291. package/dist/vdom/index.d.ts +4 -0
  292. package/dist/vdom/index.d.ts.map +1 -0
  293. package/dist/vdom/index.js +8 -0
  294. package/dist/vdom/index.js.map +1 -0
  295. package/dist/vdom/mount.cjs +161 -0
  296. package/dist/vdom/mount.cjs.map +1 -0
  297. package/dist/vdom/mount.d.ts +6 -0
  298. package/dist/vdom/mount.d.ts.map +1 -0
  299. package/dist/vdom/mount.js +161 -0
  300. package/dist/vdom/mount.js.map +1 -0
  301. package/dist/vdom/patch.cjs +163 -0
  302. package/dist/vdom/patch.cjs.map +1 -0
  303. package/dist/vdom/patch.d.ts +3 -0
  304. package/dist/vdom/patch.d.ts.map +1 -0
  305. package/dist/vdom/patch.js +163 -0
  306. package/dist/vdom/patch.js.map +1 -0
  307. package/dist/vdom/registry.cjs +85 -0
  308. package/dist/vdom/registry.cjs.map +1 -0
  309. package/dist/vdom/registry.d.ts +28 -0
  310. package/dist/vdom/registry.d.ts.map +1 -0
  311. package/dist/vdom/registry.js +85 -0
  312. package/dist/vdom/registry.js.map +1 -0
  313. package/dist/vdom/runtime-helpers.cjs +23 -0
  314. package/dist/vdom/runtime-helpers.cjs.map +1 -0
  315. package/dist/vdom/runtime-helpers.d.ts +8 -0
  316. package/dist/vdom/runtime-helpers.d.ts.map +1 -0
  317. package/dist/vdom/runtime-helpers.js +23 -0
  318. package/dist/vdom/runtime-helpers.js.map +1 -0
  319. package/dist/vdom/surface.cjs +107 -0
  320. package/dist/vdom/surface.cjs.map +1 -0
  321. package/dist/vdom/surface.d.ts +9 -0
  322. package/dist/vdom/surface.d.ts.map +1 -0
  323. package/dist/vdom/surface.js +107 -0
  324. package/dist/vdom/surface.js.map +1 -0
  325. package/dist/vdom/types.d.ts +104 -0
  326. package/dist/vdom/types.d.ts.map +1 -0
  327. package/dist/vdom/utils.cjs +146 -0
  328. package/dist/vdom/utils.cjs.map +1 -0
  329. package/dist/vdom/utils.d.ts +24 -0
  330. package/dist/vdom/utils.d.ts.map +1 -0
  331. package/dist/vdom/utils.js +146 -0
  332. package/dist/vdom/utils.js.map +1 -0
  333. package/dist/vdom.d.ts +4 -0
  334. package/dist/vdom.d.ts.map +1 -0
  335. package/dist/vector-draw-context.d.ts +63 -0
  336. package/dist/vector-draw-context.d.ts.map +1 -0
  337. package/package.json +161 -0
  338. package/vdom/index.cjs +1 -0
  339. package/vdom/index.d.ts +1 -0
  340. package/vdom/index.js +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-engine.cjs","sources":["../../src/animation/effect-engine.ts"],"sourcesContent":["/**\n * Central effect engine — runs EffectDefinitions against EffectTargets.\n *\n * The effect engine is the heart of the motion system. It interprets\n * effect definitions (pure data) and drives them against renderer-specific\n * targets using the shared tween/physics primitives.\n *\n * Renderers only implement EffectTarget — the engine handles everything else.\n */\n\nimport type { HostScheduler } from '../host-adapter'\nimport { createSpring, type SpringHandle } from './create-spring'\nimport type { EffectDefinition, EffectStep, NoiseStep } from './effect-definitions'\nimport { createTween, type TweenHandle } from './tween'\nimport type { AnimValues, EffectConfig } from './types'\n\n// ---------------------------------------------------------------------------\n// EffectTarget — what renderers implement\n// ---------------------------------------------------------------------------\n\n/**\n * Contract between the central effect engine and a renderer.\n *\n * Renderers (Pixi, Phaser) implement this to connect Core effects\n * to native nodes. The engine calls these methods to read/apply values.\n *\n * Portable components can also implement EffectTarget to use effects\n * without depending on any renderer bridge. See `applyEffect()` in\n * `@number10/jsx-core` for the 4-arg overload.\n *\n * @example\n * ```typescript\n * // Portable component effect target\n * const target: EffectTarget = {\n * onUpdate: (values) => {\n * if (values.x !== undefined) node.x = values.x\n * if (values.y !== undefined) node.y = values.y\n * if (values.alpha !== undefined) node.alpha = values.alpha\n * },\n * getSnapshot: () => ({\n * x: node.x, y: node.y, alpha: node.alpha,\n * scaleX: node.scaleX, scaleY: node.scaleY,\n * rotation: node.rotation,\n * }),\n * getScheduler: () => surface.scheduler,\n * getSize: () => ({ width: node.width, height: node.height }),\n * }\n * ```\n */\nexport interface EffectTarget {\n /** Apply partial values to the native node */\n onUpdate(values: Partial<AnimValues>): void\n /** Read current values from the native node (for snapshot-based effects) */\n getSnapshot(): AnimValues\n /** Get the HostScheduler for frame updates */\n getScheduler(): HostScheduler\n /** Optional: size for origin-aware transform calculation */\n getSize?(): { width: number; height: number }\n}\n\n// ---------------------------------------------------------------------------\n// EffectHandle\n// ---------------------------------------------------------------------------\n\n/**\n * Handle to an active effect — allows stopping and checking status.\n */\nexport interface EffectHandle {\n /** Stop the effect immediately. onComplete is NOT called. */\n stop(): void\n /** Whether the effect is still running */\n readonly isActive: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Internal state management\n// ---------------------------------------------------------------------------\n\ninterface EffectState {\n activeTweens: Set<TweenHandle<AnimValues>>\n activeSprings: Set<SpringHandle<AnimValues>>\n base: AnimValues\n animatedKeys: Set<keyof AnimValues>\n token: number\n}\n\nconst effectStates = new WeakMap<object, EffectState>()\n\nfunction getState(key: object, snapshot: AnimValues): EffectState {\n let state = effectStates.get(key)\n if (!state) {\n state = {\n activeTweens: new Set(),\n activeSprings: new Set(),\n base: { ...snapshot },\n animatedKeys: new Set(),\n token: 0,\n }\n effectStates.set(key, state)\n }\n return state\n}\n\nfunction pickAnimatedValues(\n values: Partial<AnimValues>,\n keys: Iterable<keyof AnimValues>\n): Partial<AnimValues> {\n const partial: Partial<AnimValues> = {}\n for (const key of keys) {\n const value = values[key]\n if (value !== undefined) {\n partial[key] = value\n }\n }\n return partial\n}\n\nfunction collectStepAnimatedKeys(step: EffectStep): Set<keyof AnimValues> {\n if (step.type === 'noise') {\n return new Set<keyof AnimValues>(['x', 'y'])\n }\n\n const keys = new Set<keyof AnimValues>()\n for (const key of Object.keys(step.to) as (keyof AnimValues)[]) {\n keys.add(key)\n }\n\n const fromValues = 'from' in step ? step.from : undefined\n if (fromValues) {\n for (const key of Object.keys(fromValues) as (keyof AnimValues)[]) {\n keys.add(key)\n }\n }\n\n return keys\n}\n\nfunction stopTargetEffects(state: EffectState): void {\n for (const t of state.activeTweens) t.stop()\n state.activeTweens.clear()\n for (const s of state.activeSprings) s.stop()\n state.activeSprings.clear()\n}\n\n// ---------------------------------------------------------------------------\n// runEffect\n// ---------------------------------------------------------------------------\n\n/**\n * Run an effect against a target.\n *\n * @param targetKey - Object used as WeakMap key (typically the renderer node)\n * @param target - EffectTarget implementation\n * @param definition - Effect definition (steps, loop, origin)\n * @param config - Runtime configuration (intensity, time, etc.)\n * @returns EffectHandle for stopping and status checking\n */\nexport function runEffect(\n targetKey: object,\n target: EffectTarget,\n definition: EffectDefinition,\n config: EffectConfig\n): EffectHandle {\n const scheduler = target.getScheduler()\n\n // Preserve original base from any existing effect — this is critical\n // for correct \"return to original\" behavior when effects are restarted\n // mid-animation (e.g., rapid button clicks).\n const existingState = effectStates.get(targetKey)\n const originalBase: AnimValues = existingState\n ? { ...existingState.base }\n : { ...target.getSnapshot() }\n\n // Stop any running effect and reset target to original base\n if (existingState) {\n stopTargetEffects(existingState)\n target.onUpdate(pickAnimatedValues(originalBase, existingState.animatedKeys))\n effectStates.delete(targetKey)\n }\n\n // Create fresh state with the preserved original base\n const freshState = getState(targetKey, originalBase)\n freshState.base = { ...originalBase }\n freshState.token++\n\n const token = freshState.token\n const steps: EffectStep[] =\n typeof definition.steps === 'function'\n ? definition.steps(freshState.base, config)\n : definition.steps\n const effectAnimatedKeys = new Set<keyof AnimValues>()\n for (const step of steps) {\n for (const key of collectStepAnimatedKeys(step)) {\n effectAnimatedKeys.add(key)\n }\n }\n freshState.animatedKeys = effectAnimatedKeys\n\n let completed = false\n\n const runSteps = (index: number): void => {\n if (completed || freshState.token !== token) return\n if (index >= steps.length) {\n // Finite effect: reset to base values\n if (!definition.loop) {\n target.onUpdate(pickAnimatedValues(freshState.base, freshState.animatedKeys))\n }\n completed = true\n config.onComplete?.()\n return\n }\n\n const step = steps[index]\n if (!step) {\n runSteps(index + 1)\n return\n }\n const stepAnimatedKeys = collectStepAnimatedKeys(step)\n\n const currentSnapshot = target.getSnapshot()\n const fromValues = 'from' in step ? step.from : undefined\n const start = {\n ...currentSnapshot,\n ...(fromValues ?? {}),\n } as AnimValues\n\n // Apply optional \"from\" values immediately\n if (fromValues) {\n target.onUpdate(fromValues)\n }\n\n if (step.type === 'noise') {\n runNoiseStep(freshState, token, target, scheduler, step, () => runSteps(index + 1))\n } else if (step.type === 'tween') {\n const handle = createTween(\n start,\n {\n to: step.to,\n duration: step.duration,\n ease: step.ease,\n onUpdate: (values) => {\n if (freshState.token !== token) {\n handle.stop()\n return\n }\n target.onUpdate(pickAnimatedValues(values, stepAnimatedKeys))\n },\n onComplete: () => {\n freshState.activeTweens.delete(handle)\n if (freshState.token === token) runSteps(index + 1)\n },\n },\n scheduler\n )\n freshState.activeTweens.add(handle)\n } else if (step.type === 'spring') {\n const handle = createSpring(\n start,\n {\n to: step.to,\n config: step.config,\n onUpdate: (values) => {\n if (freshState.token !== token) {\n handle.stop()\n return\n }\n target.onUpdate(pickAnimatedValues(values, stepAnimatedKeys))\n },\n onComplete: () => {\n freshState.activeSprings.delete(handle)\n if (freshState.token === token) runSteps(index + 1)\n },\n },\n scheduler\n )\n freshState.activeSprings.add(handle)\n }\n }\n\n // Start the first step synchronously (frame scheduling is inside createTween)\n runSteps(0)\n\n return {\n stop: () => {\n if (completed) return\n completed = true\n stopTargetEffects(freshState)\n // Reset target to original base values before cleaning up,\n // otherwise rapid re-triggers would snapshot an intermediate position.\n target.onUpdate(pickAnimatedValues(freshState.base, freshState.animatedKeys))\n effectStates.delete(targetKey)\n },\n get isActive() {\n return !completed\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Noise step runner\n// ---------------------------------------------------------------------------\n\nfunction runNoiseStep(\n state: EffectState,\n token: number,\n target: EffectTarget,\n scheduler: HostScheduler,\n step: NoiseStep,\n onDone: () => void\n): void {\n const startTime = performance.now()\n const handle = scheduler.onFrame(() => {\n if (state.token !== token) {\n handle.cancel()\n return\n }\n const elapsed = performance.now() - startTime\n const progress = Math.min(elapsed / step.duration, 1)\n const mag = step.magnitude * step.decay(progress)\n target.onUpdate({\n x: state.base.x + (Math.random() * 2 - 1) * mag,\n y: state.base.y + (Math.random() * 2 - 1) * mag,\n })\n if (progress >= 1) {\n handle.cancel()\n onDone()\n }\n })\n}\n\n// ---------------------------------------------------------------------------\n// stopEffect\n// ---------------------------------------------------------------------------\n\n/**\n * Stop all active effects for the given target key.\n * Resets the target to its original base values.\n */\nexport function stopEffect(targetKey: object): void {\n const state = effectStates.get(targetKey)\n if (state) {\n stopTargetEffects(state)\n effectStates.delete(targetKey)\n }\n}\n"],"names":["createTween","createSpring"],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import { HostScheduler } from '../host-adapter';
2
+ import { EffectDefinition } from './effect-definitions';
3
+ import { AnimValues, EffectConfig } from './types';
4
+ /**
5
+ * Contract between the central effect engine and a renderer.
6
+ *
7
+ * Renderers (Pixi, Phaser) implement this to connect Core effects
8
+ * to native nodes. The engine calls these methods to read/apply values.
9
+ *
10
+ * Portable components can also implement EffectTarget to use effects
11
+ * without depending on any renderer bridge. See `applyEffect()` in
12
+ * `@number10/jsx-core` for the 4-arg overload.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Portable component effect target
17
+ * const target: EffectTarget = {
18
+ * onUpdate: (values) => {
19
+ * if (values.x !== undefined) node.x = values.x
20
+ * if (values.y !== undefined) node.y = values.y
21
+ * if (values.alpha !== undefined) node.alpha = values.alpha
22
+ * },
23
+ * getSnapshot: () => ({
24
+ * x: node.x, y: node.y, alpha: node.alpha,
25
+ * scaleX: node.scaleX, scaleY: node.scaleY,
26
+ * rotation: node.rotation,
27
+ * }),
28
+ * getScheduler: () => surface.scheduler,
29
+ * getSize: () => ({ width: node.width, height: node.height }),
30
+ * }
31
+ * ```
32
+ */
33
+ export interface EffectTarget {
34
+ /** Apply partial values to the native node */
35
+ onUpdate(values: Partial<AnimValues>): void;
36
+ /** Read current values from the native node (for snapshot-based effects) */
37
+ getSnapshot(): AnimValues;
38
+ /** Get the HostScheduler for frame updates */
39
+ getScheduler(): HostScheduler;
40
+ /** Optional: size for origin-aware transform calculation */
41
+ getSize?(): {
42
+ width: number;
43
+ height: number;
44
+ };
45
+ }
46
+ /**
47
+ * Handle to an active effect — allows stopping and checking status.
48
+ */
49
+ export interface EffectHandle {
50
+ /** Stop the effect immediately. onComplete is NOT called. */
51
+ stop(): void;
52
+ /** Whether the effect is still running */
53
+ readonly isActive: boolean;
54
+ }
55
+ /**
56
+ * Run an effect against a target.
57
+ *
58
+ * @param targetKey - Object used as WeakMap key (typically the renderer node)
59
+ * @param target - EffectTarget implementation
60
+ * @param definition - Effect definition (steps, loop, origin)
61
+ * @param config - Runtime configuration (intensity, time, etc.)
62
+ * @returns EffectHandle for stopping and status checking
63
+ */
64
+ export declare function runEffect(targetKey: object, target: EffectTarget, definition: EffectDefinition, config: EffectConfig): EffectHandle;
65
+ /**
66
+ * Stop all active effects for the given target key.
67
+ * Resets the target to its original base values.
68
+ */
69
+ export declare function stopEffect(targetKey: object): void;
70
+ //# sourceMappingURL=effect-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-engine.d.ts","sourceRoot":"","sources":["../../src/animation/effect-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAyB,MAAM,sBAAsB,CAAA;AAEnF,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAMvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAAA;IAC3C,4EAA4E;IAC5E,WAAW,IAAI,UAAU,CAAA;IACzB,8CAA8C;IAC9C,YAAY,IAAI,aAAa,CAAA;IAC7B,4DAA4D;IAC5D,OAAO,CAAC,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAMD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,IAAI,IAAI,IAAI,CAAA;IACZ,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAC3B;AA4ED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,YAAY,GACnB,YAAY,CAsId;AAsCD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAMlD"}
@@ -0,0 +1,189 @@
1
+ import { createSpring } from "./create-spring.js";
2
+ import { createTween } from "./tween.js";
3
+ const effectStates = /* @__PURE__ */ new WeakMap();
4
+ function getState(key, snapshot) {
5
+ let state = effectStates.get(key);
6
+ if (!state) {
7
+ state = {
8
+ activeTweens: /* @__PURE__ */ new Set(),
9
+ activeSprings: /* @__PURE__ */ new Set(),
10
+ base: { ...snapshot },
11
+ animatedKeys: /* @__PURE__ */ new Set(),
12
+ token: 0
13
+ };
14
+ effectStates.set(key, state);
15
+ }
16
+ return state;
17
+ }
18
+ function pickAnimatedValues(values, keys) {
19
+ const partial = {};
20
+ for (const key of keys) {
21
+ const value = values[key];
22
+ if (value !== void 0) {
23
+ partial[key] = value;
24
+ }
25
+ }
26
+ return partial;
27
+ }
28
+ function collectStepAnimatedKeys(step) {
29
+ if (step.type === "noise") {
30
+ return /* @__PURE__ */ new Set(["x", "y"]);
31
+ }
32
+ const keys = /* @__PURE__ */ new Set();
33
+ for (const key of Object.keys(step.to)) {
34
+ keys.add(key);
35
+ }
36
+ const fromValues = "from" in step ? step.from : void 0;
37
+ if (fromValues) {
38
+ for (const key of Object.keys(fromValues)) {
39
+ keys.add(key);
40
+ }
41
+ }
42
+ return keys;
43
+ }
44
+ function stopTargetEffects(state) {
45
+ for (const t of state.activeTweens) t.stop();
46
+ state.activeTweens.clear();
47
+ for (const s of state.activeSprings) s.stop();
48
+ state.activeSprings.clear();
49
+ }
50
+ function runEffect(targetKey, target, definition, config) {
51
+ const scheduler = target.getScheduler();
52
+ const existingState = effectStates.get(targetKey);
53
+ const originalBase = existingState ? { ...existingState.base } : { ...target.getSnapshot() };
54
+ if (existingState) {
55
+ stopTargetEffects(existingState);
56
+ target.onUpdate(pickAnimatedValues(originalBase, existingState.animatedKeys));
57
+ effectStates.delete(targetKey);
58
+ }
59
+ const freshState = getState(targetKey, originalBase);
60
+ freshState.base = { ...originalBase };
61
+ freshState.token++;
62
+ const token = freshState.token;
63
+ const steps = typeof definition.steps === "function" ? definition.steps(freshState.base, config) : definition.steps;
64
+ const effectAnimatedKeys = /* @__PURE__ */ new Set();
65
+ for (const step of steps) {
66
+ for (const key of collectStepAnimatedKeys(step)) {
67
+ effectAnimatedKeys.add(key);
68
+ }
69
+ }
70
+ freshState.animatedKeys = effectAnimatedKeys;
71
+ let completed = false;
72
+ const runSteps = (index) => {
73
+ if (completed || freshState.token !== token) return;
74
+ if (index >= steps.length) {
75
+ if (!definition.loop) {
76
+ target.onUpdate(pickAnimatedValues(freshState.base, freshState.animatedKeys));
77
+ }
78
+ completed = true;
79
+ config.onComplete?.();
80
+ return;
81
+ }
82
+ const step = steps[index];
83
+ if (!step) {
84
+ runSteps(index + 1);
85
+ return;
86
+ }
87
+ const stepAnimatedKeys = collectStepAnimatedKeys(step);
88
+ const currentSnapshot = target.getSnapshot();
89
+ const fromValues = "from" in step ? step.from : void 0;
90
+ const start = {
91
+ ...currentSnapshot,
92
+ ...fromValues ?? {}
93
+ };
94
+ if (fromValues) {
95
+ target.onUpdate(fromValues);
96
+ }
97
+ if (step.type === "noise") {
98
+ runNoiseStep(freshState, token, target, scheduler, step, () => runSteps(index + 1));
99
+ } else if (step.type === "tween") {
100
+ const handle = createTween(
101
+ start,
102
+ {
103
+ to: step.to,
104
+ duration: step.duration,
105
+ ease: step.ease,
106
+ onUpdate: (values) => {
107
+ if (freshState.token !== token) {
108
+ handle.stop();
109
+ return;
110
+ }
111
+ target.onUpdate(pickAnimatedValues(values, stepAnimatedKeys));
112
+ },
113
+ onComplete: () => {
114
+ freshState.activeTweens.delete(handle);
115
+ if (freshState.token === token) runSteps(index + 1);
116
+ }
117
+ },
118
+ scheduler
119
+ );
120
+ freshState.activeTweens.add(handle);
121
+ } else if (step.type === "spring") {
122
+ const handle = createSpring(
123
+ start,
124
+ {
125
+ to: step.to,
126
+ config: step.config,
127
+ onUpdate: (values) => {
128
+ if (freshState.token !== token) {
129
+ handle.stop();
130
+ return;
131
+ }
132
+ target.onUpdate(pickAnimatedValues(values, stepAnimatedKeys));
133
+ },
134
+ onComplete: () => {
135
+ freshState.activeSprings.delete(handle);
136
+ if (freshState.token === token) runSteps(index + 1);
137
+ }
138
+ },
139
+ scheduler
140
+ );
141
+ freshState.activeSprings.add(handle);
142
+ }
143
+ };
144
+ runSteps(0);
145
+ return {
146
+ stop: () => {
147
+ if (completed) return;
148
+ completed = true;
149
+ stopTargetEffects(freshState);
150
+ target.onUpdate(pickAnimatedValues(freshState.base, freshState.animatedKeys));
151
+ effectStates.delete(targetKey);
152
+ },
153
+ get isActive() {
154
+ return !completed;
155
+ }
156
+ };
157
+ }
158
+ function runNoiseStep(state, token, target, scheduler, step, onDone) {
159
+ const startTime = performance.now();
160
+ const handle = scheduler.onFrame(() => {
161
+ if (state.token !== token) {
162
+ handle.cancel();
163
+ return;
164
+ }
165
+ const elapsed = performance.now() - startTime;
166
+ const progress = Math.min(elapsed / step.duration, 1);
167
+ const mag = step.magnitude * step.decay(progress);
168
+ target.onUpdate({
169
+ x: state.base.x + (Math.random() * 2 - 1) * mag,
170
+ y: state.base.y + (Math.random() * 2 - 1) * mag
171
+ });
172
+ if (progress >= 1) {
173
+ handle.cancel();
174
+ onDone();
175
+ }
176
+ });
177
+ }
178
+ function stopEffect(targetKey) {
179
+ const state = effectStates.get(targetKey);
180
+ if (state) {
181
+ stopTargetEffects(state);
182
+ effectStates.delete(targetKey);
183
+ }
184
+ }
185
+ export {
186
+ runEffect,
187
+ stopEffect
188
+ };
189
+ //# sourceMappingURL=effect-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effect-engine.js","sources":["../../src/animation/effect-engine.ts"],"sourcesContent":["/**\n * Central effect engine — runs EffectDefinitions against EffectTargets.\n *\n * The effect engine is the heart of the motion system. It interprets\n * effect definitions (pure data) and drives them against renderer-specific\n * targets using the shared tween/physics primitives.\n *\n * Renderers only implement EffectTarget — the engine handles everything else.\n */\n\nimport type { HostScheduler } from '../host-adapter'\nimport { createSpring, type SpringHandle } from './create-spring'\nimport type { EffectDefinition, EffectStep, NoiseStep } from './effect-definitions'\nimport { createTween, type TweenHandle } from './tween'\nimport type { AnimValues, EffectConfig } from './types'\n\n// ---------------------------------------------------------------------------\n// EffectTarget — what renderers implement\n// ---------------------------------------------------------------------------\n\n/**\n * Contract between the central effect engine and a renderer.\n *\n * Renderers (Pixi, Phaser) implement this to connect Core effects\n * to native nodes. The engine calls these methods to read/apply values.\n *\n * Portable components can also implement EffectTarget to use effects\n * without depending on any renderer bridge. See `applyEffect()` in\n * `@number10/jsx-core` for the 4-arg overload.\n *\n * @example\n * ```typescript\n * // Portable component effect target\n * const target: EffectTarget = {\n * onUpdate: (values) => {\n * if (values.x !== undefined) node.x = values.x\n * if (values.y !== undefined) node.y = values.y\n * if (values.alpha !== undefined) node.alpha = values.alpha\n * },\n * getSnapshot: () => ({\n * x: node.x, y: node.y, alpha: node.alpha,\n * scaleX: node.scaleX, scaleY: node.scaleY,\n * rotation: node.rotation,\n * }),\n * getScheduler: () => surface.scheduler,\n * getSize: () => ({ width: node.width, height: node.height }),\n * }\n * ```\n */\nexport interface EffectTarget {\n /** Apply partial values to the native node */\n onUpdate(values: Partial<AnimValues>): void\n /** Read current values from the native node (for snapshot-based effects) */\n getSnapshot(): AnimValues\n /** Get the HostScheduler for frame updates */\n getScheduler(): HostScheduler\n /** Optional: size for origin-aware transform calculation */\n getSize?(): { width: number; height: number }\n}\n\n// ---------------------------------------------------------------------------\n// EffectHandle\n// ---------------------------------------------------------------------------\n\n/**\n * Handle to an active effect — allows stopping and checking status.\n */\nexport interface EffectHandle {\n /** Stop the effect immediately. onComplete is NOT called. */\n stop(): void\n /** Whether the effect is still running */\n readonly isActive: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Internal state management\n// ---------------------------------------------------------------------------\n\ninterface EffectState {\n activeTweens: Set<TweenHandle<AnimValues>>\n activeSprings: Set<SpringHandle<AnimValues>>\n base: AnimValues\n animatedKeys: Set<keyof AnimValues>\n token: number\n}\n\nconst effectStates = new WeakMap<object, EffectState>()\n\nfunction getState(key: object, snapshot: AnimValues): EffectState {\n let state = effectStates.get(key)\n if (!state) {\n state = {\n activeTweens: new Set(),\n activeSprings: new Set(),\n base: { ...snapshot },\n animatedKeys: new Set(),\n token: 0,\n }\n effectStates.set(key, state)\n }\n return state\n}\n\nfunction pickAnimatedValues(\n values: Partial<AnimValues>,\n keys: Iterable<keyof AnimValues>\n): Partial<AnimValues> {\n const partial: Partial<AnimValues> = {}\n for (const key of keys) {\n const value = values[key]\n if (value !== undefined) {\n partial[key] = value\n }\n }\n return partial\n}\n\nfunction collectStepAnimatedKeys(step: EffectStep): Set<keyof AnimValues> {\n if (step.type === 'noise') {\n return new Set<keyof AnimValues>(['x', 'y'])\n }\n\n const keys = new Set<keyof AnimValues>()\n for (const key of Object.keys(step.to) as (keyof AnimValues)[]) {\n keys.add(key)\n }\n\n const fromValues = 'from' in step ? step.from : undefined\n if (fromValues) {\n for (const key of Object.keys(fromValues) as (keyof AnimValues)[]) {\n keys.add(key)\n }\n }\n\n return keys\n}\n\nfunction stopTargetEffects(state: EffectState): void {\n for (const t of state.activeTweens) t.stop()\n state.activeTweens.clear()\n for (const s of state.activeSprings) s.stop()\n state.activeSprings.clear()\n}\n\n// ---------------------------------------------------------------------------\n// runEffect\n// ---------------------------------------------------------------------------\n\n/**\n * Run an effect against a target.\n *\n * @param targetKey - Object used as WeakMap key (typically the renderer node)\n * @param target - EffectTarget implementation\n * @param definition - Effect definition (steps, loop, origin)\n * @param config - Runtime configuration (intensity, time, etc.)\n * @returns EffectHandle for stopping and status checking\n */\nexport function runEffect(\n targetKey: object,\n target: EffectTarget,\n definition: EffectDefinition,\n config: EffectConfig\n): EffectHandle {\n const scheduler = target.getScheduler()\n\n // Preserve original base from any existing effect — this is critical\n // for correct \"return to original\" behavior when effects are restarted\n // mid-animation (e.g., rapid button clicks).\n const existingState = effectStates.get(targetKey)\n const originalBase: AnimValues = existingState\n ? { ...existingState.base }\n : { ...target.getSnapshot() }\n\n // Stop any running effect and reset target to original base\n if (existingState) {\n stopTargetEffects(existingState)\n target.onUpdate(pickAnimatedValues(originalBase, existingState.animatedKeys))\n effectStates.delete(targetKey)\n }\n\n // Create fresh state with the preserved original base\n const freshState = getState(targetKey, originalBase)\n freshState.base = { ...originalBase }\n freshState.token++\n\n const token = freshState.token\n const steps: EffectStep[] =\n typeof definition.steps === 'function'\n ? definition.steps(freshState.base, config)\n : definition.steps\n const effectAnimatedKeys = new Set<keyof AnimValues>()\n for (const step of steps) {\n for (const key of collectStepAnimatedKeys(step)) {\n effectAnimatedKeys.add(key)\n }\n }\n freshState.animatedKeys = effectAnimatedKeys\n\n let completed = false\n\n const runSteps = (index: number): void => {\n if (completed || freshState.token !== token) return\n if (index >= steps.length) {\n // Finite effect: reset to base values\n if (!definition.loop) {\n target.onUpdate(pickAnimatedValues(freshState.base, freshState.animatedKeys))\n }\n completed = true\n config.onComplete?.()\n return\n }\n\n const step = steps[index]\n if (!step) {\n runSteps(index + 1)\n return\n }\n const stepAnimatedKeys = collectStepAnimatedKeys(step)\n\n const currentSnapshot = target.getSnapshot()\n const fromValues = 'from' in step ? step.from : undefined\n const start = {\n ...currentSnapshot,\n ...(fromValues ?? {}),\n } as AnimValues\n\n // Apply optional \"from\" values immediately\n if (fromValues) {\n target.onUpdate(fromValues)\n }\n\n if (step.type === 'noise') {\n runNoiseStep(freshState, token, target, scheduler, step, () => runSteps(index + 1))\n } else if (step.type === 'tween') {\n const handle = createTween(\n start,\n {\n to: step.to,\n duration: step.duration,\n ease: step.ease,\n onUpdate: (values) => {\n if (freshState.token !== token) {\n handle.stop()\n return\n }\n target.onUpdate(pickAnimatedValues(values, stepAnimatedKeys))\n },\n onComplete: () => {\n freshState.activeTweens.delete(handle)\n if (freshState.token === token) runSteps(index + 1)\n },\n },\n scheduler\n )\n freshState.activeTweens.add(handle)\n } else if (step.type === 'spring') {\n const handle = createSpring(\n start,\n {\n to: step.to,\n config: step.config,\n onUpdate: (values) => {\n if (freshState.token !== token) {\n handle.stop()\n return\n }\n target.onUpdate(pickAnimatedValues(values, stepAnimatedKeys))\n },\n onComplete: () => {\n freshState.activeSprings.delete(handle)\n if (freshState.token === token) runSteps(index + 1)\n },\n },\n scheduler\n )\n freshState.activeSprings.add(handle)\n }\n }\n\n // Start the first step synchronously (frame scheduling is inside createTween)\n runSteps(0)\n\n return {\n stop: () => {\n if (completed) return\n completed = true\n stopTargetEffects(freshState)\n // Reset target to original base values before cleaning up,\n // otherwise rapid re-triggers would snapshot an intermediate position.\n target.onUpdate(pickAnimatedValues(freshState.base, freshState.animatedKeys))\n effectStates.delete(targetKey)\n },\n get isActive() {\n return !completed\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Noise step runner\n// ---------------------------------------------------------------------------\n\nfunction runNoiseStep(\n state: EffectState,\n token: number,\n target: EffectTarget,\n scheduler: HostScheduler,\n step: NoiseStep,\n onDone: () => void\n): void {\n const startTime = performance.now()\n const handle = scheduler.onFrame(() => {\n if (state.token !== token) {\n handle.cancel()\n return\n }\n const elapsed = performance.now() - startTime\n const progress = Math.min(elapsed / step.duration, 1)\n const mag = step.magnitude * step.decay(progress)\n target.onUpdate({\n x: state.base.x + (Math.random() * 2 - 1) * mag,\n y: state.base.y + (Math.random() * 2 - 1) * mag,\n })\n if (progress >= 1) {\n handle.cancel()\n onDone()\n }\n })\n}\n\n// ---------------------------------------------------------------------------\n// stopEffect\n// ---------------------------------------------------------------------------\n\n/**\n * Stop all active effects for the given target key.\n * Resets the target to its original base values.\n */\nexport function stopEffect(targetKey: object): void {\n const state = effectStates.get(targetKey)\n if (state) {\n stopTargetEffects(state)\n effectStates.delete(targetKey)\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Animation utilities — @number10/jsx-core
3
+ *
4
+ * Three complementary animation approaches:
5
+ * - createTween / useTween: time-based duration+easing interpolation
6
+ * - createSpring / useSpring: physics-based spring animation via signals
7
+ *
8
+ * Effect system (migration target):
9
+ * - types: AnimValues, EffectConfig
10
+ * - easing: Complete Penner equations
11
+ * - effect-definitions: 23 built-in effects as pure data
12
+ * - effect-engine: Central effect runner with EffectTarget contract
13
+ */
14
+ export * from './animated-signal';
15
+ export { createSpring, type SpringAnimationConfig, type SpringHandle } from './create-spring';
16
+ export { Easing, type EasingFn } from './easing';
17
+ export { EFFECT_DEFINITIONS, resolveEffectTransformOrigin, type EffectDefinition, type EffectStep, type EffectTransformOrigin, type NoiseStep, type SpringStep, type TweenStep, } from './effect-definitions';
18
+ export { runEffect, stopEffect, type EffectHandle, type EffectTarget } from './effect-engine';
19
+ export * from './spring-physics';
20
+ export * from './tween';
21
+ export type { AnimValues, EffectConfig } from './types';
22
+ export * from './useSpring';
23
+ export { TWEEN_PRESETS, useTween, useTweens, type TweenAnimationStep, type TweenConfig, type TweenPreset, } from './useTween';
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/animation/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,mBAAmB,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,KAAK,qBAAqB,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC7F,OAAO,EAAE,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC7F,cAAc,kBAAkB,CAAA;AAChC,cAAc,SAAS,CAAA;AACvB,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACvD,cAAc,aAAa,CAAA;AAC3B,OAAO,EACL,aAAa,EACb,QAAQ,EACR,SAAS,EACT,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAA"}
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const DEFAULT_SPRING_CONFIG = {
4
+ tension: 170,
5
+ friction: 26,
6
+ mass: 1,
7
+ restVelocity: 0.01,
8
+ restDelta: 0.01,
9
+ clamp: false
10
+ };
11
+ const SPRING_PRESETS = {
12
+ /** Gentle, but still responsive animation */
13
+ gentle: { tension: 350, friction: 24, restVelocity: 0.5, restDelta: 0.1 },
14
+ /** Moderate, balanced animation (default) */
15
+ default: { tension: 600, friction: 40, restVelocity: 0.5, restDelta: 0.1 },
16
+ /** Bouncy, playful animation tuned for quick UI feedback */
17
+ wobbly: { tension: 900, friction: 35, restVelocity: 0.5, restDelta: 0.1 },
18
+ /** Very fast, snappy response for press/pulse-style interactions */
19
+ stiff: { tension: 1800, friction: 60, restVelocity: 0.5, restDelta: 0.1 },
20
+ /** Slow, deliberate animation */
21
+ slow: { tension: 350, friction: 48, restVelocity: 0.5, restDelta: 0.1 },
22
+ /** Nearly instant transition */
23
+ instant: { tension: 2500, friction: 90, restVelocity: 1, restDelta: 0.02, clamp: true }
24
+ };
25
+ class SpringPhysics {
26
+ config;
27
+ /**
28
+ * Creates a new spring physics engine
29
+ * @param config - Spring configuration
30
+ */
31
+ constructor(config = {}) {
32
+ this.config = { ...DEFAULT_SPRING_CONFIG, ...config };
33
+ }
34
+ /**
35
+ * Updates spring configuration
36
+ * @param config - Partial config to merge with current
37
+ */
38
+ updateConfig(config) {
39
+ this.config = { ...this.config, ...config };
40
+ }
41
+ /**
42
+ * Performs one physics step
43
+ * Uses semi-implicit Euler integration for stability
44
+ * @param state - Current spring state
45
+ * @param target - Target value
46
+ * @param deltaTime - Time delta in seconds
47
+ * @returns New spring state
48
+ */
49
+ step(state, target, deltaTime) {
50
+ const { tension, friction, mass, clamp } = this.config;
51
+ const springForce = -tension * (state.value - target);
52
+ const dampingForce = -friction * state.velocity;
53
+ const acceleration = (springForce + dampingForce) / mass;
54
+ const newVelocity = state.velocity + acceleration * deltaTime;
55
+ let newValue = state.value + newVelocity * deltaTime;
56
+ if (clamp) {
57
+ const min = Math.min(state.value, target);
58
+ const max = Math.max(state.value, target);
59
+ newValue = Math.max(min, Math.min(max, newValue));
60
+ }
61
+ return {
62
+ value: newValue,
63
+ velocity: newVelocity
64
+ };
65
+ }
66
+ /**
67
+ * Checks if spring has reached rest state
68
+ * @param state - Current spring state
69
+ * @param target - Target value
70
+ * @returns True if spring is at rest
71
+ */
72
+ isAtRest(state, target) {
73
+ const { restVelocity, restDelta } = this.config;
74
+ const isVelocitySmall = Math.abs(state.velocity) <= restVelocity;
75
+ const isDisplacementSmall = Math.abs(state.value - target) <= restDelta;
76
+ return isVelocitySmall && isDisplacementSmall;
77
+ }
78
+ /**
79
+ * Snaps spring to target if at rest
80
+ * @param state - Current spring state
81
+ * @param target - Target value
82
+ * @returns Adjusted state (snapped if at rest)
83
+ */
84
+ snapIfAtRest(state, target) {
85
+ if (this.isAtRest(state, target)) {
86
+ return { value: target, velocity: 0 };
87
+ }
88
+ return state;
89
+ }
90
+ }
91
+ exports.DEFAULT_SPRING_CONFIG = DEFAULT_SPRING_CONFIG;
92
+ exports.SPRING_PRESETS = SPRING_PRESETS;
93
+ exports.SpringPhysics = SpringPhysics;
94
+ //# sourceMappingURL=spring-physics.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spring-physics.cjs","sources":["../../src/animation/spring-physics.ts"],"sourcesContent":["/**\n * Spring physics engine for smooth, natural animations\n * Based on spring-damper system with configurable tension, friction, and mass\n *\n * @migration(audit:surface-access) Phase 7 — moved to @number10/jsx-core ✅\n */\n\n/**\n * Configuration for spring physics behavior\n */\nexport interface SpringConfig {\n /** Spring stiffness (higher = faster, snappier). Default: 170 */\n tension?: number\n /** Damping force (higher = less oscillation). Default: 26 */\n friction?: number\n /** Mass of the animated object (higher = slower). Default: 1 */\n mass?: number\n /** Velocity threshold below which animation stops. Default: 0.01 */\n restVelocity?: number\n /** Distance threshold below which animation stops. Default: 0.01 */\n restDelta?: number\n /** Clamp values to prevent overshoot. Default: false */\n clamp?: boolean\n /**\n * Called once when the spring starts moving from rest.\n * Fires BEFORE the first physics step.\n */\n onStart?: (() => void) | undefined\n /**\n * Called on every frame with the current spring value.\n * Useful for syncing non-signal state.\n */\n onUpdate?: ((value: number) => void) | undefined\n}\n\n/**\n * Spring physics state\n */\nexport interface SpringState {\n /** Current value */\n value: number\n /** Current velocity */\n velocity: number\n}\n\n/**\n * Default spring configuration (similar to react-spring)\n */\nexport const DEFAULT_SPRING_CONFIG: Required<Omit<SpringConfig, 'onStart' | 'onUpdate'>> = {\n tension: 170,\n friction: 26,\n mass: 1,\n restVelocity: 0.01,\n restDelta: 0.01,\n clamp: false,\n}\n\n/**\n * Preset spring configurations for common use cases\n * Note: clamp prevents overshoot but can make animations feel less natural\n * For UI animations, consider enabling clamp to prevent visual artifacts\n */\nexport const SPRING_PRESETS = {\n /** Gentle, but still responsive animation */\n gentle: { tension: 350, friction: 24, restVelocity: 0.5, restDelta: 0.1 },\n /** Moderate, balanced animation (default) */\n default: { tension: 600, friction: 40, restVelocity: 0.5, restDelta: 0.1 },\n /** Bouncy, playful animation tuned for quick UI feedback */\n wobbly: { tension: 900, friction: 35, restVelocity: 0.5, restDelta: 0.1 },\n /** Very fast, snappy response for press/pulse-style interactions */\n stiff: { tension: 1800, friction: 60, restVelocity: 0.5, restDelta: 0.1 },\n /** Slow, deliberate animation */\n slow: { tension: 350, friction: 48, restVelocity: 0.5, restDelta: 0.1 },\n /** Nearly instant transition */\n instant: { tension: 2500, friction: 90, restVelocity: 1, restDelta: 0.02, clamp: true },\n} as const\n\n/**\n * Type for animation preset keys\n */\nexport type AnimationPreset = keyof typeof SPRING_PRESETS\n/**\n * Type for animation configuration (preset name or custom config)\n */\nexport type AnimationConfig = AnimationPreset | SpringConfig\n\n/**\n * Spring physics engine\n * Implements spring-damper physics for smooth, natural animations\n */\nexport class SpringPhysics {\n private config: Required<Omit<SpringConfig, 'onStart' | 'onUpdate'>> &\n Pick<SpringConfig, 'onStart' | 'onUpdate'>\n\n /**\n * Creates a new spring physics engine\n * @param config - Spring configuration\n */\n constructor(config: SpringConfig = {}) {\n this.config = { ...DEFAULT_SPRING_CONFIG, ...config } as typeof this.config\n }\n\n /**\n * Updates spring configuration\n * @param config - Partial config to merge with current\n */\n updateConfig(config: Partial<SpringConfig>): void {\n this.config = { ...this.config, ...config } as typeof this.config\n }\n\n /**\n * Performs one physics step\n * Uses semi-implicit Euler integration for stability\n * @param state - Current spring state\n * @param target - Target value\n * @param deltaTime - Time delta in seconds\n * @returns New spring state\n */\n step(state: SpringState, target: number, deltaTime: number): SpringState {\n const { tension, friction, mass, clamp } = this.config\n\n // Calculate spring force (Hooke's law)\n const springForce = -tension * (state.value - target)\n\n // Calculate damping force\n const dampingForce = -friction * state.velocity\n\n // Calculate acceleration (F = ma → a = F/m)\n const acceleration = (springForce + dampingForce) / mass\n\n // Update velocity (semi-implicit Euler)\n const newVelocity = state.velocity + acceleration * deltaTime\n\n // Update position\n let newValue = state.value + newVelocity * deltaTime\n\n // Clamp if enabled\n if (clamp) {\n const min = Math.min(state.value, target)\n const max = Math.max(state.value, target)\n newValue = Math.max(min, Math.min(max, newValue))\n }\n\n return {\n value: newValue,\n velocity: newVelocity,\n }\n }\n\n /**\n * Checks if spring has reached rest state\n * @param state - Current spring state\n * @param target - Target value\n * @returns True if spring is at rest\n */\n isAtRest(state: SpringState, target: number): boolean {\n const { restVelocity, restDelta } = this.config\n const isVelocitySmall = Math.abs(state.velocity) <= restVelocity\n const isDisplacementSmall = Math.abs(state.value - target) <= restDelta\n return isVelocitySmall && isDisplacementSmall\n }\n\n /**\n * Snaps spring to target if at rest\n * @param state - Current spring state\n * @param target - Target value\n * @returns Adjusted state (snapped if at rest)\n */\n snapIfAtRest(state: SpringState, target: number): SpringState {\n if (this.isAtRest(state, target)) {\n return { value: target, velocity: 0 }\n }\n return state\n }\n}\n"],"names":[],"mappings":";;AAgDO,MAAM,wBAA8E;AAAA,EACzF,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,cAAc;AAAA,EACd,WAAW;AAAA,EACX,OAAO;AACT;AAOO,MAAM,iBAAiB;AAAA;AAAA,EAE5B,QAAQ,EAAE,SAAS,KAAK,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAEpE,SAAS,EAAE,SAAS,KAAK,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAErE,QAAQ,EAAE,SAAS,KAAK,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAEpE,OAAO,EAAE,SAAS,MAAM,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAEpE,MAAM,EAAE,SAAS,KAAK,UAAU,IAAI,cAAc,KAAK,WAAW,IAAA;AAAA;AAAA,EAElE,SAAS,EAAE,SAAS,MAAM,UAAU,IAAI,cAAc,GAAG,WAAW,MAAM,OAAO,KAAA;AACnF;AAeO,MAAM,cAAc;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAOR,YAAY,SAAuB,IAAI;AACrC,SAAK,SAAS,EAAE,GAAG,uBAAuB,GAAG,OAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,QAAqC;AAChD,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAA;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,KAAK,OAAoB,QAAgB,WAAgC;AACvE,UAAM,EAAE,SAAS,UAAU,MAAM,MAAA,IAAU,KAAK;AAGhD,UAAM,cAAc,CAAC,WAAW,MAAM,QAAQ;AAG9C,UAAM,eAAe,CAAC,WAAW,MAAM;AAGvC,UAAM,gBAAgB,cAAc,gBAAgB;AAGpD,UAAM,cAAc,MAAM,WAAW,eAAe;AAGpD,QAAI,WAAW,MAAM,QAAQ,cAAc;AAG3C,QAAI,OAAO;AACT,YAAM,MAAM,KAAK,IAAI,MAAM,OAAO,MAAM;AACxC,YAAM,MAAM,KAAK,IAAI,MAAM,OAAO,MAAM;AACxC,iBAAW,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;AAAA,IAClD;AAEA,WAAO;AAAA,MACL,OAAO;AAAA,MACP,UAAU;AAAA,IAAA;AAAA,EAEd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,SAAS,OAAoB,QAAyB;AACpD,UAAM,EAAE,cAAc,UAAA,IAAc,KAAK;AACzC,UAAM,kBAAkB,KAAK,IAAI,MAAM,QAAQ,KAAK;AACpD,UAAM,sBAAsB,KAAK,IAAI,MAAM,QAAQ,MAAM,KAAK;AAC9D,WAAO,mBAAmB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,OAAoB,QAA6B;AAC5D,QAAI,KAAK,SAAS,OAAO,MAAM,GAAG;AAChC,aAAO,EAAE,OAAO,QAAQ,UAAU,EAAA;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AACF;;;;"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Spring physics engine for smooth, natural animations
3
+ * Based on spring-damper system with configurable tension, friction, and mass
4
+ *
5
+ * @migration(audit:surface-access) Phase 7 — moved to @number10/jsx-core ✅
6
+ */
7
+ /**
8
+ * Configuration for spring physics behavior
9
+ */
10
+ export interface SpringConfig {
11
+ /** Spring stiffness (higher = faster, snappier). Default: 170 */
12
+ tension?: number;
13
+ /** Damping force (higher = less oscillation). Default: 26 */
14
+ friction?: number;
15
+ /** Mass of the animated object (higher = slower). Default: 1 */
16
+ mass?: number;
17
+ /** Velocity threshold below which animation stops. Default: 0.01 */
18
+ restVelocity?: number;
19
+ /** Distance threshold below which animation stops. Default: 0.01 */
20
+ restDelta?: number;
21
+ /** Clamp values to prevent overshoot. Default: false */
22
+ clamp?: boolean;
23
+ /**
24
+ * Called once when the spring starts moving from rest.
25
+ * Fires BEFORE the first physics step.
26
+ */
27
+ onStart?: (() => void) | undefined;
28
+ /**
29
+ * Called on every frame with the current spring value.
30
+ * Useful for syncing non-signal state.
31
+ */
32
+ onUpdate?: ((value: number) => void) | undefined;
33
+ }
34
+ /**
35
+ * Spring physics state
36
+ */
37
+ export interface SpringState {
38
+ /** Current value */
39
+ value: number;
40
+ /** Current velocity */
41
+ velocity: number;
42
+ }
43
+ /**
44
+ * Default spring configuration (similar to react-spring)
45
+ */
46
+ export declare const DEFAULT_SPRING_CONFIG: Required<Omit<SpringConfig, 'onStart' | 'onUpdate'>>;
47
+ /**
48
+ * Preset spring configurations for common use cases
49
+ * Note: clamp prevents overshoot but can make animations feel less natural
50
+ * For UI animations, consider enabling clamp to prevent visual artifacts
51
+ */
52
+ export declare const SPRING_PRESETS: {
53
+ /** Gentle, but still responsive animation */
54
+ readonly gentle: {
55
+ readonly tension: 350;
56
+ readonly friction: 24;
57
+ readonly restVelocity: 0.5;
58
+ readonly restDelta: 0.1;
59
+ };
60
+ /** Moderate, balanced animation (default) */
61
+ readonly default: {
62
+ readonly tension: 600;
63
+ readonly friction: 40;
64
+ readonly restVelocity: 0.5;
65
+ readonly restDelta: 0.1;
66
+ };
67
+ /** Bouncy, playful animation tuned for quick UI feedback */
68
+ readonly wobbly: {
69
+ readonly tension: 900;
70
+ readonly friction: 35;
71
+ readonly restVelocity: 0.5;
72
+ readonly restDelta: 0.1;
73
+ };
74
+ /** Very fast, snappy response for press/pulse-style interactions */
75
+ readonly stiff: {
76
+ readonly tension: 1800;
77
+ readonly friction: 60;
78
+ readonly restVelocity: 0.5;
79
+ readonly restDelta: 0.1;
80
+ };
81
+ /** Slow, deliberate animation */
82
+ readonly slow: {
83
+ readonly tension: 350;
84
+ readonly friction: 48;
85
+ readonly restVelocity: 0.5;
86
+ readonly restDelta: 0.1;
87
+ };
88
+ /** Nearly instant transition */
89
+ readonly instant: {
90
+ readonly tension: 2500;
91
+ readonly friction: 90;
92
+ readonly restVelocity: 1;
93
+ readonly restDelta: 0.02;
94
+ readonly clamp: true;
95
+ };
96
+ };
97
+ /**
98
+ * Type for animation preset keys
99
+ */
100
+ export type AnimationPreset = keyof typeof SPRING_PRESETS;
101
+ /**
102
+ * Type for animation configuration (preset name or custom config)
103
+ */
104
+ export type AnimationConfig = AnimationPreset | SpringConfig;
105
+ /**
106
+ * Spring physics engine
107
+ * Implements spring-damper physics for smooth, natural animations
108
+ */
109
+ export declare class SpringPhysics {
110
+ private config;
111
+ /**
112
+ * Creates a new spring physics engine
113
+ * @param config - Spring configuration
114
+ */
115
+ constructor(config?: SpringConfig);
116
+ /**
117
+ * Updates spring configuration
118
+ * @param config - Partial config to merge with current
119
+ */
120
+ updateConfig(config: Partial<SpringConfig>): void;
121
+ /**
122
+ * Performs one physics step
123
+ * Uses semi-implicit Euler integration for stability
124
+ * @param state - Current spring state
125
+ * @param target - Target value
126
+ * @param deltaTime - Time delta in seconds
127
+ * @returns New spring state
128
+ */
129
+ step(state: SpringState, target: number, deltaTime: number): SpringState;
130
+ /**
131
+ * Checks if spring has reached rest state
132
+ * @param state - Current spring state
133
+ * @param target - Target value
134
+ * @returns True if spring is at rest
135
+ */
136
+ isAtRest(state: SpringState, target: number): boolean;
137
+ /**
138
+ * Snaps spring to target if at rest
139
+ * @param state - Current spring state
140
+ * @param target - Target value
141
+ * @returns Adjusted state (snapped if at rest)
142
+ */
143
+ snapIfAtRest(state: SpringState, target: number): SpringState;
144
+ }
145
+ //# sourceMappingURL=spring-physics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spring-physics.d.ts","sourceRoot":"","sources":["../../src/animation/spring-physics.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,UAAU,CAAC,CAOtF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc;IACzB,6CAA6C;;;;;;;IAE7C,6CAA6C;;;;;;;IAE7C,4DAA4D;;;;;;;IAE5D,oEAAoE;;;;;;;IAEpE,iCAAiC;;;;;;;IAEjC,gCAAgC;;;;;;;;CAExB,CAAA;AAEV;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,cAAc,CAAA;AACzD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,YAAY,CAAA;AAE5D;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAC8B;IAE5C;;;OAGG;gBACS,MAAM,GAAE,YAAiB;IAIrC;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAIjD;;;;;;;OAOG;IACH,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW;IA+BxE;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAOrD;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW;CAM9D"}