@thednp/tween 0.0.1 → 0.0.2

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 (53) hide show
  1. package/README.md +112 -109
  2. package/dist/preact/preact.d.mts +62 -0
  3. package/dist/preact/preact.d.mts.map +1 -0
  4. package/dist/preact/preact.mjs +181 -0
  5. package/dist/preact/preact.mjs.map +1 -0
  6. package/dist/react/react.d.mts +62 -0
  7. package/dist/react/react.d.mts.map +1 -0
  8. package/dist/react/react.mjs +183 -0
  9. package/dist/react/react.mjs.map +1 -0
  10. package/dist/solid/solid.d.mts +60 -0
  11. package/dist/solid/solid.d.mts.map +1 -0
  12. package/dist/solid/solid.mjs +157 -0
  13. package/dist/solid/solid.mjs.map +1 -0
  14. package/dist/svelte/svelte.d.mts.map +1 -0
  15. package/dist/svelte/svelte.mjs.map +1 -0
  16. package/dist/svelte/tween.svelte.d.ts +58 -0
  17. package/dist/svelte/tween.svelte.js +156 -0
  18. package/dist/tween/index.d.mts +939 -0
  19. package/dist/tween/index.d.mts.map +1 -0
  20. package/dist/tween/index.mjs +1929 -0
  21. package/dist/tween/index.mjs.map +1 -0
  22. package/dist/tween.min.js +8 -0
  23. package/dist/tween.min.js.map +1 -0
  24. package/dist/vue/vue.d.mts +61 -0
  25. package/dist/vue/vue.d.mts.map +1 -0
  26. package/dist/vue/vue.mjs +157 -0
  27. package/dist/vue/vue.mjs.map +1 -0
  28. package/package.json +56 -40
  29. package/dist/index.cjs +0 -621
  30. package/dist/index.cjs.map +0 -1
  31. package/dist/index.d.cts +0 -184
  32. package/dist/index.d.mts +0 -184
  33. package/dist/index.mjs +0 -610
  34. package/dist/index.mjs.map +0 -1
  35. package/dist/react.cjs +0 -61
  36. package/dist/react.cjs.map +0 -1
  37. package/dist/react.d.cts +0 -9
  38. package/dist/react.d.mts +0 -9
  39. package/dist/react.mjs +0 -55
  40. package/dist/react.mjs.map +0 -1
  41. package/dist/solid.cjs +0 -81
  42. package/dist/solid.cjs.map +0 -1
  43. package/dist/solid.d.cts +0 -9
  44. package/dist/solid.d.mts +0 -9
  45. package/dist/solid.mjs +0 -75
  46. package/dist/solid.mjs.map +0 -1
  47. package/dist/tween.iife.js +0 -2
  48. package/dist/tween.iife.js.map +0 -1
  49. package/wiki/Easing.md +0 -58
  50. package/wiki/React.md +0 -255
  51. package/wiki/Solid.md +0 -149
  52. package/wiki/Timeline.md +0 -207
  53. package/wiki/Tween.md +0 -230
package/README.md CHANGED
@@ -1,30 +1,75 @@
1
1
  ## @thednp/tween
2
+
2
3
  [![Coverage Status](https://coveralls.io/repos/github/thednp/tween/badge.svg)](https://coveralls.io/github/thednp/tween)
3
- [![ci](https://github.com/thednp/tween/actions/workflows/ci.yml/badge.svg)](https://github.com/thednp/tween/actions/workflows/ci.yml)
4
- [![typescript version](https://img.shields.io/badge/typescript-5.9.3-brightgreen)](https://www.typescriptlang.org/)
5
- [![vitest version](https://img.shields.io/badge/vitest-4.0.17-brightgreen)](https://vitest.dev/)
6
- [![vite version](https://img.shields.io/badge/vite-7.3.1-brightgreen)](https://github.com/vitejs)
4
+ [![NPM Version](https://img.shields.io/npm/v/@thednp/tween.svg)](https://www.npmjs.com/package/@thednp/tween)
5
+ [![CI](https://github.com/thednp/tween/actions/workflows/ci.yml/badge.svg)](https://github.com/thednp/tween/actions/workflows/ci.yml)
6
+
7
+
8
+ A Typescript sourced tweening engine forked from the excellent [@tweenjs/tweenjs](https://github.com/tweenjs/tween.js). This package has some strongly opinionated implementations for the best performance and DX.
9
+
10
+ Major popular frameworks supported:
11
+
12
+ [<img width="32" height="32" src="wiki/assets/react.svg" alt="React" />](wiki/React.md)
13
+ [<img width="32" height="32" src="wiki/assets/solid.svg" alt="SolidJS" />](wiki/Solid.md)
14
+ [<img width="32" height="32" src="wiki/assets/preact.svg" alt="Preact" />](wiki/Preact.md)
15
+ [<img width="32" height="32" src="wiki/assets/svelte.svg" alt="Svelte" />](wiki/Svelte.md)
16
+ [<img width="32" height="32" src="wiki/assets/vue.svg" alt="Vue" />](wiki/Vue.md)
17
+
18
+
19
+ ## Why @thednp/tween?
20
+
21
+ It seems **we got it all wrong regarding tweening** since the beginning. We've been working with unpredictable patterns and high risk for errors. We've been doing too much value checking during hot update loop or sometimes got too close to the DOM, to the point that makes it hard to integrate in modern tech. What if... we can change all that?
7
22
 
8
- A Typescript sourced `Tween` engine forked from the excellent [@tweenjs/tweenjs](https://github.com/tweenjs/tween.js).
23
+ **Why you would like it?**
9
24
 
10
- * The package includes `Tween` class for creating most simple tween objects, with most essential controls over the update loop and simple methods for sequencing.
11
- * Another major addition to this package is a simple to use `Timeline` class which enables an advanced control over the update loop, most importantly a **per-property** control for duration and easing.
12
- * There are also a series of custom hooks/promitives for popular frameworks like React/SolidJS (more to come), with proper documentation and detailed user guides.
25
+ * It's really easy to use thanks to the built in support for popular UI frameworks via SSR compatible hooks/primitives/composables (hooks only provide initial values required for server rendering runtime and initialization is skipped entirely, hooks only work with hydration of server rendered HTML or single page apps - SPA).
26
+ * It's the solution for a predictable outcome: validate values before and never at the update runtime, explicit specification instead of a guessing game, no object lookup or any kind of overhead.
27
+ * While you can do quite complex animations with CSS3 alone, it's really poor DX to go back and forth and write complex animations to which you have little to no control.
28
+ * The package comes with a feature rich validation system, all to make sure your animations run smooth and never break your app. Values aren't valid? Animation won't start. Missed a configuration step? You will be provided with feedback.
29
+ * SVG math can be troublesome because of its own coordinates system, SVG transforms are also a thing, but with a little learning and our tweening engine you can do anything you really want.
13
30
 
14
31
 
15
- ### Features
16
- - Built in custom hooks/primitives for popular frameworks like React/SolidJS
17
- - Simple and powerful `Timeline` class for advanced sequencing/overlaps
18
- - Lightweight fork of tween.js (~half the size)
19
- - Chainable API for proper DX
20
- - Easy to extend via custom interpolators
21
- - Duration/delay in seconds
22
- - Automatic rAF loop (starts and stops automatically)
23
- - TypeScript-native, zero dependencies
24
- - Tested with Vitest 100% code coverage
32
+ ## Features
33
+ * The package includes `Tween` class for creating most simple tween objects, with most essential controls, callbacks and methods for sequencing.
34
+ * For more complex scheduling and easier sequencing this package has a simple to use `Timeline` class with similar controls, callbacks, but most importantly it allows setting **per-property** duration, start time / delay and easing.
35
+ * There are also a series of custom hooks/primitives for popular frameworks like React/SolidJS/Svelte/Preact/Vue (more to come), with proper documentation and detailed user guides.
36
+ * Yoyo and generally reverse playback works naturally with inverted easing function (without a `reverseEasing` option) and without re-assigning a `valuesStart` object on repeat iteration end like the original library.
37
+ * By default, only `number` values are supported, which is fine for most use cases, but both `Tween` and `Timeline` provide a way to extend beyond the original prototype. You can use the built-in [extensions](wiki/Extend.md) or creare your own to provide per property validation and interpolation with your design specification. The only limit is that objects are limited to a one level nesting and must be plain objects.
38
+ * All values are **always** validated on initialization from a given `initialValues` object, which, once validated, it becomes the source of truth to the type of values coming later from `to()` / `from()`.
39
+ * Automatic `requestAnimationFrame` loop (you won't need to handle it yourself), it starts when you call `tween.start()` or `timeline.play()` and stops when all tweens are complete.
40
+ * The package has some micro-optimizations for maximum performance:
41
+ - All loops are executed with `while`;
42
+ - No value validation of any kind during the update loop;
43
+ - Supported frameworks (via hooks/primitives/composables) use a `miniStore` designed to store tween values and trigger updates/effects in your UI efficiently, with zero GC pressure, no object lookup or re-assignment, just pure linear interpolation;
44
+ - The hot update runtime consists of tuples, to optimize GC presure and eliminate object lookup.
25
45
 
26
46
 
27
- ### Install
47
+ ### Take a minute to check a quick demo
48
+
49
+ [<img width="32" height="32" src="wiki/assets/react.svg" alt="React" />](https://stackblitz.com/fork/github/thednp/tween/tree/master/playground/react)
50
+ [<img width="32" height="32" src="wiki/assets/solid.svg" alt="SolidJS" />](https://stackblitz.com/fork/github/thednp/tween/tree/master/playground/solid)
51
+ [<img width="32" height="32" src="wiki/assets/preact.svg" alt="Preact" />](https://stackblitz.com/fork/github/thednp/tween/tree/master/playground/preact)
52
+ [<img width="32" height="32" src="wiki/assets/svelte.svg" alt="Svelte" />](https://stackblitz.com/fork/github/thednp/tween/tree/master/playground/svelte)
53
+ [<img width="32" height="32" src="wiki/assets/vue.svg" alt="Vue" />](https://stackblitz.com/fork/github/thednp/tween/tree/master/playground/vue)
54
+
55
+ Your favorite framework isn't listed? [Let us know](https://github.com/thednp/tween/issues/new)!
56
+
57
+
58
+ ### Documentation
59
+ * [Tween Guide](wiki/Tween.md) - the official `Tween` documentation
60
+ * [Timeline Guide](wiki/Timeline.md) - the official `Timeline` documentation
61
+ * [Easing Guide](wiki/Easing.md) - the easing functions documentation
62
+ * [Extend Guide](wiki/Extend.md) - the extensions documentation
63
+ * [React Guide](wiki/React.md) - the React documentation (custom hooks)
64
+ * [SolidJS Guide](wiki/Solid.md) - the SolidJS documentation (custom primitives, tips)
65
+ * [Vue Guide](wiki/Vue.md) - the Vue documentation (composables, tips)
66
+ * [Preact Guide](wiki/Preact.md) - the Preact documentation (custom hooks, tips)
67
+ * [Svelte Guide](wiki/Svelte.md) - the Svelte documentation (runes)
68
+ * [The original Tween.js User Guide](https://github.com/tweenjs/tween.js/blob/main/docs/user_guide.md) can also provide valuable tips.
69
+ * [Troubleshooting](wiki/Troubleshooting) - a quick check on issues and how to solve them.
70
+
71
+
72
+ ### Installation
28
73
  ```
29
74
  npm install @thednp/tween
30
75
  ```
@@ -44,6 +89,8 @@ bun add @thednp/tween
44
89
 
45
90
  ### Usage
46
91
 
92
+ To use `Tween` and `Timeline` with UI frameworks please check the dedicated sections: [React](wiki/React.md), [SolidJS](wiki/Solid.md), [Svelte](wiki/Svelte.md), [Preact](wiki/Preact.md) and [Vue](wiki/Vue.md).
93
+
47
94
  #### Using Tween
48
95
  ```ts
49
96
  import { Tween, Easing } from '@thednp/tween';
@@ -54,13 +101,11 @@ const target = document.getElementById('my-target');
54
101
  // define a tween
55
102
  const tween = new Tween({ x: 0 })
56
103
  .duration(1.5) // duration/delay accept seconds (e.g., 1.5 = 1.5s)
57
- .onUpdate((obj, elapsed, eased) => {
58
- // update App state
59
- // OR manipulate the DOM directly
60
- Object.assign(target.style, { translate: obj.x + "px"});
104
+ .onUpdate((obj, elapsed) => {
105
+ // manipulate the DOM directly
106
+ Object.assign(target.style, { translate: obj.x + "px" });
61
107
  // monitor progress of the tween
62
108
  console.log(`Tween progress: ${Math.floor(elapsed * 100)}%`)
63
- // do other stuff with the `eased` value
64
109
  });
65
110
 
66
111
  // override any value on the fly
@@ -72,6 +117,7 @@ const moveRight = () => tween
72
117
  .start(); // start the tween
73
118
 
74
119
  const moveLeft = () => tween
120
+ .from({ x: 150 }) // set a different from
75
121
  .to({ x: -150 }) // set a different to
76
122
  .easing(Easing.Elastic.Out) // override easing
77
123
  .duration(1.5) // override duration in seconds
@@ -86,6 +132,8 @@ button2.onclick = moveLeft;
86
132
 
87
133
  // The engine does requestAnimationFrame/cancelAnimationFrame for you
88
134
  ```
135
+ For an extended guide, check the [Tween Wiki](wiki/Tween.md).
136
+
89
137
 
90
138
  #### Using Timeline
91
139
  ```ts
@@ -99,145 +147,100 @@ const myTimeline = new Timeline({ x: 0, y: 0 })
99
147
  .to({ x: 150, duration: 2.5, easing: Easing.Elastic.Out })
100
148
  .to({ y: 150, duration: 1.5, easing: Easing.Elastic.Out }, "-=1")
101
149
  .onUpdate((obj, elapsed) => {
102
- // update App state
103
- // OR manipulate the DOM directly
150
+ // manipulate the DOM directly
104
151
  Object.assign(target.style, {
105
152
  translate: obj.x + "px " + obj.y + "px",
106
153
  });
107
- // monitor progress of the tween
154
+ // monitor progress of the timeline
108
155
  console.log(`Timeline progress: ${Math.floor(elapsed * 100)}%`)
109
156
  });
110
157
 
111
158
  // trigger any time
112
- const button = document.getElementById('my-button-1');
159
+ const button = document.getElementById('my-button');
113
160
 
114
161
  button.onclick = myTimeline.play();
115
162
 
116
163
  // The engine does requestAnimationFrame/cancelAnimationFrame for you
117
164
  ```
118
- To use `Tween` or `Timeline` with frameworks please check [React](wiki/React.md), [SolidJS](wiki/Solid.md) (more to come).
119
-
165
+ For an extended guide, check the [Timeline Wiki](wiki/Timeline.md).
120
166
 
121
- ### What is different from original?
122
167
 
123
- #### Back to Base
124
- This `Tween` version is very small, maybe half the size of the current original version. It was developed to create easy to use hooks for UI frameworks like Solid/React and enable customizable animations.
125
- In fact this is closer to the earlier versions of the original TWEEN.Tween.js.
168
+ ### What is Different from Original?
126
169
 
170
+ #### Looking Forward
171
+ On the surface, most changes seem superficial, but our `Tween` version is very different from the current source version. It was developed to create easy to use hooks for major popular UI frameworks and deliver a trully predictable outcome.
127
172
 
128
173
  #### New Features
129
- This package comes with `Timeline`, which works like a regular `Tween` under the hood, but it provides additional control methods like `pause()`, `resume()` `seek()`.
174
+ This package comes with `Timeline`, which works like a regular `Tween` under the hood, but it provides additional control methods like `seek()` or `label()` and allows per property easing, duration and start time / delay options.
130
175
 
131
- Both `Tween` and `Timeline` have a static method to add custom interpolators, which is a unique way to extend beyond the original design and very different from the original library.
132
-
133
- #### Other Notable Changes
134
- * Some features like `yoyo`, `repeat`, `repeatDelay`, `chain` and
135
- * callback options like `onRepeat` or `onEveryStart`, `onFirstStart` are **not** implemented;
136
- * `duration()` and `delay()` methods accept values in seconds and convert them to milliseconds;
137
- * The `pause()` and `resume()` methods have **not** been implemented in `Tween`, but they are implemented in `Timeline`;
138
- * The update loop which consists of `requestAnimationFrame` / `cancelAnimationFrame` calls is automatic and is integrated in the `Tween` methods;
139
- * The `onUpdate` callback also uses the value calculated by the easing function as the third parameter of your callback;
140
- * The original Tween.js array interpolation is **not** supported, however we have a static method to add custom interpolators.
176
+ Both `Tween` and `Timeline` have a method to add a custom **per property extension** that consists of a function to validate and another to interpolate, which is a unique way to extend beyond the original design and very different from the original library.
141
177
 
178
+ **Great DX**: Along with type safety, `Tween` and `Timeline` will validate your values on initialization or (re)configuration to enforce it. Calling `Tween.to()`, `Tween.from()` or `Timeline.to()` will use the **validation system** to provide feedback on what went wrong and how to fix, in most cases issues with invalid/incompatible values are mapped internally and only cleared once validated/re-validated. This is to make sure to **never crash** your app.
142
179
 
143
- ### Guides and Resources
144
- * The original Tween.js [User Guide](https://github.com/tweenjs/tween.js/blob/main/docs/user_guide.md)
145
- * [Tween.md](wiki/Tween.md) - our official `Tween` guide
146
- * [Timeline.md](wiki/Timeline.md) - our official `Timeline` guide
147
- * [Easing.md](wiki/Easing.md) - an extensive guide on easing functions.
148
- * [React.md](wiki/React.md) - use `Tween` / `Timeline` with React with custom hooks.
149
- * [Solid.md](wiki/Solid.md) - use `Tween` / `Timeline` with SolidJS with primitives.
180
+ #### Other Notable Changes
181
+ * The `chain` feature is **not** implemented;
182
+ * Callback options like `onEveryStart`, `onFirstStart` are **not** implemented;
183
+ * The `duration()`, `delay()`, `repeatDelay()` or `seek()` methods accept values in seconds and convert them to milliseconds;
184
+ * The update loop which consists of `requestAnimationFrame` is automatic and a queue system is integrated in the `Tween` and `Timeline` methods;
185
+ * The original [Tween.js](https://tweenjs.github.io/tween.js/examples/06_array_interpolation.html) array interpolation is **not** supported;
186
+ * Deeply nested objects are **not supported**, actively discouraged, objects in general are known to have very bad performance metrics;
187
+ * Dynamic end values like the original [Tween.js](https://tweenjs.github.io/tween.js/examples/07_dynamic_to.html) cannot be supported due to the intrinsic changes in the update runtime.
150
188
 
151
189
 
152
190
  ### Technical Notes & Design Choices
153
191
 
154
- **@thednp/tween** is intentionally designed as a **lightweight, state-first** tweening engine. Here's why certain choices were made and how the system works under the hood.
155
-
156
- #### Why declarative state-based animation?
192
+ **@thednp/tween** is intentionally designed as a **state-first** tweening engine. Here's why certain choices were made and how the system works under the hood.
157
193
 
158
- Most classic animation libraries (GSAP, KUTE.js, Velocity.js) are **imperative**: they read current DOM styles/attributes at runtime, parse them, compute differences, and write back updates.
194
+ #### Mini-Stores for Supported Frameworks
159
195
 
160
- **@thednp/tween** keeps with the original Tween.js, which is the opposite approach it's **purely state-driven**:
196
+ Each supported framework make use of a highly specialized `miniStore` to hold tween values and update your UI. This is to ensure great DX and eliminate GC pressure. Even React can work amazing. Check the [Ministore wiki](wiki/Ministore.md) for details.
161
197
 
162
- - You provide **target state values** (numbers, arrays, objects)
163
- - The engine interpolates **from current state** (captured at `.start()` / `.play()`)
164
- - No DOM reading/parsing ever happens
165
- - No per browser handling/processing
198
+ #### How the Global Update Loop Works
166
199
 
167
- **Advantages**:
168
- - Zero runtime style/attribute parsing → much faster startup & safer in concurrent React/Solid renders
169
- - Perfect for **state-based UI** (React, SolidJS, Vue, Svelte, etc.) — animate your app state, not the DOM directly
170
- - No surprises from inherited styles, CSS transitions, or computed values
171
- - Works equally well with **Canvas**, **SVG**, **Three.js**, **WebGL**, or **pure data** — no DOM dependency at all
172
- - Considerably smaller bundle size (no style parsing code)
173
- - More power to you due to the simplicity or the prototype
200
+ All tween objects and timelines share **one single `requestAnimationFrame` loop** managed by `Runtime.ts`.
174
201
 
175
- **Trade-offs**:
176
- - You must manage state yourself (which is the norm in modern frameworks anyway)
177
- - You must process complex values yourself (values for SVG path morph)
178
-
179
- This makes `@thednp/tween` feel more like **a reactive state interpolator** than a traditional DOM tweener.
180
-
181
- #### How the global update loop works
182
-
183
- All animations share **one single `requestAnimationFrame` loop** managed by `Runtime.ts`.
184
-
185
- - When you call `.start()` / `.play()`, the instance is added to a global `Queue`
202
+ - When you call `tween.start()` / `timeline.play()`, the instance is added to a global `Queue`
186
203
  - `Runtime()` runs every frame → calls `.update(time)` on every queued item
187
204
  - If `.update()` returns `false` (finished/stopped), the item is removed from the Queue
188
- - When `Queue` becomes empty → `cancelAnimationFrame` is called automatically → loop stops completely
205
+ - When `Queue` becomes empty → `cancelAnimationFrame` is called automatically → loop stops completely.
189
206
 
190
207
  **Benefits**:
191
- - Only **one** rAF subscription for the entire app — extremely efficient
208
+ - Only **one** `requestAnimationFrame` subscription for the entire app — extremely efficient
192
209
  - No manual start/stop of animation loop per tween/timeline
193
210
  - Zero overhead when nothing is animating
194
211
 
195
212
  This shared loop is why you never need to worry about starting/stopping individual `requestAnimationFrame` calls.
196
213
 
197
- #### Async nature of requestAnimationFrame
214
+
215
+ #### Async Nature of `requestAnimationFrame`
198
216
 
199
217
  All updates are **async** by nature:
200
218
 
201
- 1. You call `.start()` / `.play()` → instance queued
202
- 2. Next `rAF` tick → `Runtime()` calls `.update(time)` → interpolates → calls `onUpdate`
219
+ 1. You call `tween.start()` / `timeline.play()` → instance is queued (added to the main update loop)
220
+ 2. Next `requestAnimationFrame` tick → `Runtime()` calls `instance.update(time)` → interpolates values determines whether to call `cancelAnimationFrame` or continue
203
221
  3. DOM/state updates happen **on the next frame(s)** — never synchronous
204
222
 
205
223
  This means:
206
224
  - Visual changes are always **smooth** and **tied to the display refresh rate**
207
- - You can safely call `.to()`, `.duration()`, etc. **during** an animation changes apply on the next frame
208
- - No risk of partial/inconsistent frames (all calculations happen before paint)
225
+ - Very low risk of partial/inconsistent frames (most calculations happen before paint, depending on the stack size and GC queue)
209
226
 
210
227
  #### Server-Side Rendering (SSR) compatibility
211
228
 
212
- `@thednp/tween` is **SSR-safe** out of the box:
229
+ **@thednp/tween** is **SSR-safe** out of the box:
213
230
 
214
231
  - No DOM access anywhere in the core
215
232
  - `requestAnimationFrame` / `cancelAnimationFrame` are only called in browser (via `Runtime()`)
216
- - `now()` defaults to `performance.now()` or `Date.now()` safe fallbacks in Node
217
- - Hooks/primitives only start animation on client (via mount effects)
218
-
219
- Just make sure to **not call `tween.start()` / `timeline.play()`** during SSR (e.g. wrap in `if (typeof window !== 'undefined')` or use `useEffect`).
220
-
221
- #### Other important differences from classic tween libraries
233
+ - `now()` defaults to `performance.now()`, but you can switch to `Date.now()` to create safe fallbacks in Node
234
+ - All supported UI frameworks have consistent guards to prevent execution during server rendering, but also provide values required in the rendered HTML.
222
235
 
223
- | Feature/Aspect | Classic (GSAP/KUTE/Velocity) | @thednp/tween |
224
- |------------------------------------|-------------------------------------------|--------------------------------------------|
225
- | Animation target | DOM elements & CSS | Any JS object (state, data, Canvas, etc.) |
226
- | Value reading | Parses current style/attr at runtime | Captures current JS values at start |
227
- | Performance on startup | Slower (parsing + computation) | Fastest (no parsing) |
228
- | State-based UI compatibility | Requires extra glue code | Native — ideal for React/Solid/Vue/Svelte |
229
- | Global vs per-instance config | Plugins/global easing | Per-instance `.use()` (recommended) |
230
- | Bundle size | Larger (DOM utils, parsing, plugins) | Very small (~2–4 KB minzipped) |
231
- | Runtime loop | Per-tween or managed | Single shared global loop (most efficient) |
236
+ Just make sure to **not call `tween.start()` / `timeline.play()`** during SSR (if you're using custom hooks outside those included in this package).
232
237
 
233
- #### Additional notes
234
238
 
235
- - **No pause/resume on single Tween** — use `.stop()` + `.startFromLast()` for pause-like behavior (keeps it simple)
236
- - **Repeat & yoyo** — not built-in on `Tween` (use `Timeline` for sequencing/repeats)
237
- - **Custom interpolators** — register per instance with `.use('prop', fn)` (prevents conflicts in large apps)
238
- - **Testing** — `setNow()` allows perfect time control in tests (override `now()` to fake progression)
239
+ #### Working Around Limitations
239
240
 
240
- We believe this combination of **small size**, **state-first design**, **shared loop**, and **per-instance flexibility** makes **@thednp/tween** uniquely suitable for modern component-based UIs in 2026.
241
+ - **Chaining** use callbacks to start other `Tween` / `Timeline` (like the above, use `onComplete` callback to trigger the start of other tween/timeline instances)
242
+ - **Custom extensions** — register per instance per property with `.use('propName', extensionConfig)` eliminates the guessing game completely, your values are validated and interpolated exactly how you want it.
243
+ - The original Tween.js **array interpolation** - we have a way to add custom interpolators, might worth a try integrating them later.
241
244
 
242
245
 
243
246
  ### Contributing
@@ -256,4 +259,4 @@ This is a work in progress. For any issue or unclear guides, please [file an iss
256
259
 
257
260
 
258
261
  ### License
259
- **@thednp/tween** is released under [MIT License](LICENCE).
262
+ **@thednp/tween** is released under [MIT License](LICENSE).
@@ -0,0 +1,62 @@
1
+ /*!
2
+ * @thednp/tween hooks for Preact v0.0.2 (https://github.com/thednp/tween)
3
+ * Copyright 2026 © thednp
4
+ * Licensed under MIT (https://github.com/thednp/tween/blob/master/LICENSE)
5
+ */
6
+ "use strict";
7
+
8
+ import { Timeline, Tween, TweenProps } from "@thednp/tween";
9
+
10
+ //#region src/preact/miniStore.d.ts
11
+ declare function useMiniStore<T extends TweenProps>(initialValue: T): T;
12
+ //#endregion
13
+ //#region src/preact/index.d.ts
14
+ /**
15
+ * Hook for updating values with Tween.
16
+ *
17
+ * **NOTE**: - configuration must be wrapped in `useEffect` or `eventListener`.
18
+ * This has two important aspects: never configure or start update loop in SSR
19
+ * and only configure or start the loop when component is mounted in the client.
20
+ *
21
+ * @param initialValues - Initial tween values
22
+ * @returns [store, tween] Tuple of reactive store and Tween instance
23
+ * @example
24
+ * const App = () => {
25
+ * const [state, tween] = useTween({ x: 0, y: 0 })
26
+ *
27
+ * useEffect(() => {
28
+ * tween.to({ x: 100, y: 100 }, 1000).start()
29
+ * }, [])
30
+ *
31
+ * return (
32
+ * <div style={{ translate: `${state.x}px ${state.y}px` }} />
33
+ * );
34
+ * }
35
+ */
36
+ declare function useTween<T extends TweenProps>(initialValues: T): readonly [T, Tween<T>];
37
+ /**
38
+ * Hook for sequencing values update with Timeline.
39
+ *
40
+ * **NOTE**: - configuration must be wrapped in `useEffect` or `eventListener`.
41
+ * This has two important aspects: never configure or start update loop in SSR
42
+ * and only configure or start the loop when component is mounted in the client.
43
+ *
44
+ * @param initialValues - Initial tween values
45
+ * @returns [store, timeline] Tuple of reactive store and Timeline instance
46
+ * @example
47
+ * const App = () => {
48
+ * const [state, timeline] = useTimeline({ x: 0, y: 0 })
49
+ *
50
+ * useEffect(() => {
51
+ * timeline.to({ x: 100, y: 100 }).start()
52
+ * }, [])
53
+ *
54
+ * return (
55
+ * <div style={{ translate: `${state.x}px ${state.y}px` }} />
56
+ * );
57
+ * }
58
+ */
59
+ declare function useTimeline<T extends TweenProps>(initialValues: T): readonly [T, Timeline<T>];
60
+ //#endregion
61
+ export { Timeline, Tween, useMiniStore, useTimeline, useTween };
62
+ //# sourceMappingURL=preact.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preact.d.mts","names":[],"sources":["../../src/preact/miniStore.ts","../../src/preact/index.ts"],"mappings":";;;;;;;;;;iBA0JgB,YAAA,WAAuB,UAAA,CAAA,CAAY,YAAA,EAAc,CAAA,GAAC,CAAA;;;AAAlE;;;;;;;;;;;;;;;;;;ACxHA;;;;ADwHA,iBCxHgB,QAAA,WAAmB,UAAA,CAAA,CAAY,aAAA,EAAe,CAAA,aAAC,CAAA,EAAA,KAAA,CAAA,CAAA;;;;;;;;;;;;;;;AA2C/D;;;;;;;;iBAAgB,WAAA,WAAsB,UAAA,CAAA,CAAY,aAAA,EAAe,CAAA,aAAC,CAAA,EAAA,QAAA,CAAA,CAAA"}
@@ -0,0 +1,181 @@
1
+ /*!
2
+ * @thednp/tween hooks for Preact v0.0.2 (https://github.com/thednp/tween)
3
+ * Copyright 2026 © thednp
4
+ * Licensed under MIT (https://github.com/thednp/tween/blob/master/LICENSE)
5
+ */
6
+ "use strict";
7
+
8
+ import { useEffect, useRef, useState } from "preact/hooks";
9
+ import { Timeline, Tween, dummyInstance, isArray, isPlainObject, isServer, objectHasProp } from "@thednp/tween";
10
+
11
+ //#region src/preact/miniStore.ts
12
+ const STATE_PROXY = "_proxy";
13
+ const proxyProps = {
14
+ value: 1,
15
+ enumerable: false,
16
+ configurable: false,
17
+ writable: false
18
+ };
19
+ function defineArrayProxy(index, value, target, sourceLen, notifyListeners) {
20
+ const itemIsLast = index === sourceLen - 1;
21
+ if (isArray(value)) {
22
+ const subArray = [];
23
+ const valueLen = value.length;
24
+ value.forEach((itm, idx) => {
25
+ const subItemIsLast = itemIsLast && idx === valueLen - 1;
26
+ let currentItem = itm;
27
+ Object.defineProperty(subArray, idx, {
28
+ get: () => currentItem,
29
+ set: (newValue) => {
30
+ currentItem = newValue;
31
+ if (subItemIsLast) notifyListeners();
32
+ },
33
+ enumerable: true
34
+ });
35
+ });
36
+ target[index] = subArray;
37
+ } else {
38
+ let currentValue = value;
39
+ const getter = () => currentValue;
40
+ const setter = (newVal) => {
41
+ currentValue = newVal;
42
+ if (itemIsLast) notifyListeners();
43
+ };
44
+ Object.defineProperties(target, { [index]: {
45
+ get: getter,
46
+ set: setter,
47
+ enumerable: true
48
+ } });
49
+ }
50
+ }
51
+ function defineStateProxy(key, value, target, notifyListeners) {
52
+ const valueIsArray = isArray(value);
53
+ let currentValue = value;
54
+ const getter = () => currentValue;
55
+ let setter;
56
+ if (valueIsArray) {
57
+ const arrayProxy = [];
58
+ const valLength = value.length;
59
+ for (let i = 0; i < valLength; i++) defineArrayProxy(i, value[i], arrayProxy, valLength, notifyListeners);
60
+ currentValue = arrayProxy;
61
+ } else setter = (newValue) => {
62
+ if (currentValue !== newValue) {
63
+ currentValue = newValue;
64
+ notifyListeners();
65
+ }
66
+ };
67
+ Object.defineProperties(target, {
68
+ [STATE_PROXY]: proxyProps,
69
+ [key]: {
70
+ get: getter,
71
+ set: setter,
72
+ enumerable: true
73
+ }
74
+ });
75
+ }
76
+ function createMiniState(obj, parentReceiver, notifyListeners) {
77
+ if (objectHasProp(obj, STATE_PROXY)) return obj;
78
+ for (const [key, value] of Object.entries(obj)) if (isPlainObject(value)) parentReceiver[key] = createMiniState(value, {}, notifyListeners);
79
+ else defineStateProxy(key, value, parentReceiver, notifyListeners);
80
+ return parentReceiver;
81
+ }
82
+ function miniStore(init) {
83
+ const listeners = /* @__PURE__ */ new Set();
84
+ const notifyListeners = () => {
85
+ listeners.forEach((listener) => listener(store));
86
+ };
87
+ const store = createMiniState(init, {}, notifyListeners);
88
+ return {
89
+ get state() {
90
+ return store;
91
+ },
92
+ subscribe: (listener) => {
93
+ listeners.add(listener);
94
+ return () => {
95
+ listeners.delete(listener);
96
+ };
97
+ }
98
+ };
99
+ }
100
+ function useMiniStore(initialValue) {
101
+ const storeRef = useRef(null);
102
+ const [, setVersion] = useState(0);
103
+ if (!storeRef.current) storeRef.current = miniStore(initialValue);
104
+ useEffect(() => storeRef.current.subscribe(() => setVersion((v) => v === 2 ? 0 : v + 1)), []);
105
+ return storeRef.current.state;
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/preact/index.ts
110
+ /**
111
+ * Hook for updating values with Tween.
112
+ *
113
+ * **NOTE**: - configuration must be wrapped in `useEffect` or `eventListener`.
114
+ * This has two important aspects: never configure or start update loop in SSR
115
+ * and only configure or start the loop when component is mounted in the client.
116
+ *
117
+ * @param initialValues - Initial tween values
118
+ * @returns [store, tween] Tuple of reactive store and Tween instance
119
+ * @example
120
+ * const App = () => {
121
+ * const [state, tween] = useTween({ x: 0, y: 0 })
122
+ *
123
+ * useEffect(() => {
124
+ * tween.to({ x: 100, y: 100 }, 1000).start()
125
+ * }, [])
126
+ *
127
+ * return (
128
+ * <div style={{ translate: `${state.x}px ${state.y}px` }} />
129
+ * );
130
+ * }
131
+ */
132
+ function useTween(initialValues) {
133
+ if (isServer) return [initialValues, dummyInstance];
134
+ const store = useMiniStore(initialValues);
135
+ const tweenRef = useRef(null);
136
+ if (!tweenRef.current) tweenRef.current = new Tween(store);
137
+ const dispose = () => {
138
+ tweenRef.current.stop();
139
+ tweenRef.current.clear();
140
+ };
141
+ useEffect(() => dispose, []);
142
+ return [store, tweenRef.current];
143
+ }
144
+ /**
145
+ * Hook for sequencing values update with Timeline.
146
+ *
147
+ * **NOTE**: - configuration must be wrapped in `useEffect` or `eventListener`.
148
+ * This has two important aspects: never configure or start update loop in SSR
149
+ * and only configure or start the loop when component is mounted in the client.
150
+ *
151
+ * @param initialValues - Initial tween values
152
+ * @returns [store, timeline] Tuple of reactive store and Timeline instance
153
+ * @example
154
+ * const App = () => {
155
+ * const [state, timeline] = useTimeline({ x: 0, y: 0 })
156
+ *
157
+ * useEffect(() => {
158
+ * timeline.to({ x: 100, y: 100 }).start()
159
+ * }, [])
160
+ *
161
+ * return (
162
+ * <div style={{ translate: `${state.x}px ${state.y}px` }} />
163
+ * );
164
+ * }
165
+ */
166
+ function useTimeline(initialValues) {
167
+ if (isServer) return [initialValues, dummyInstance];
168
+ const store = useMiniStore(initialValues);
169
+ const timelineRef = useRef(null);
170
+ if (!timelineRef.current) timelineRef.current = new Timeline(store);
171
+ const dispose = () => {
172
+ timelineRef.current.stop();
173
+ timelineRef.current.clear();
174
+ };
175
+ useEffect(() => dispose, []);
176
+ return [store, timelineRef.current];
177
+ }
178
+
179
+ //#endregion
180
+ export { Timeline, Tween, useMiniStore, useTimeline, useTween };
181
+ //# sourceMappingURL=preact.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preact.mjs","names":[],"sources":["../../src/preact/miniStore.ts","../../src/preact/index.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"preact/hooks\";\nimport {\n type ArrayVal,\n isArray,\n isPlainObject,\n objectHasProp,\n type TweenProps,\n} from \"@thednp/tween\";\n\nconst STATE_PROXY = \"_proxy\";\nconst proxyProps = {\n value: 1,\n enumerable: false,\n configurable: false,\n writable: false,\n};\n\ntype Listener<T> = (state: T) => void;\n\nfunction defineArrayProxy<T extends ArrayVal>(\n index: number,\n value: T[number] | ArrayVal,\n target: T | ArrayVal | ArrayVal[],\n sourceLen: number,\n notifyListeners: () => void,\n) {\n const itemIsLast = index === sourceLen - 1;\n\n if (isArray(value)) {\n const subArray: typeof value = [];\n const valueLen = value.length;\n\n value.forEach((itm, idx) => {\n const subItemIsLast = itemIsLast && idx === valueLen - 1;\n\n let currentItem = itm;\n Object.defineProperty(subArray, idx, {\n get: () => currentItem,\n set: (newValue: typeof itm) => {\n currentItem = newValue;\n\n // Only notify on last element to batch updates\n if (subItemIsLast) {\n notifyListeners();\n }\n },\n enumerable: true,\n });\n });\n target[index] = subArray;\n } else {\n let currentValue = value;\n const getter = () => currentValue;\n const setter = (newVal: typeof value) => {\n currentValue = newVal;\n if (itemIsLast) {\n notifyListeners();\n }\n };\n Object.defineProperties(target, {\n [index]: {\n get: getter,\n set: setter,\n enumerable: true,\n },\n });\n }\n}\n\nfunction defineStateProxy<T extends Omit<TweenProps, \"_proxy\">>(\n key: number | keyof T,\n value: T[keyof T],\n target: T | ArrayVal,\n notifyListeners: () => void,\n) {\n const valueIsArray = isArray(value);\n let currentValue = value as ArrayVal | ArrayVal[];\n\n const getter = () => currentValue;\n let setter;\n\n if (valueIsArray) {\n // Build array proxy structure\n const arrayProxy: ArrayVal | ArrayVal[] = [];\n const valLength = value.length;\n\n for (let i = 0; i < valLength; i++) {\n defineArrayProxy(\n i,\n (value as ArrayVal)[i],\n arrayProxy as ArrayVal,\n valLength,\n notifyListeners,\n );\n }\n currentValue = arrayProxy;\n } else {\n setter = (newValue: typeof currentValue) => {\n if (currentValue !== newValue) {\n currentValue = newValue;\n notifyListeners();\n }\n };\n }\n\n Object.defineProperties(target, {\n [STATE_PROXY]: proxyProps,\n [key]: {\n get: getter,\n set: setter,\n enumerable: true,\n },\n });\n}\n\nfunction createMiniState<T extends TweenProps>(\n obj: T,\n parentReceiver: TweenProps,\n notifyListeners: () => void,\n) {\n if (objectHasProp(obj, STATE_PROXY)) return obj;\n\n for (const [key, value] of Object.entries(obj)) {\n if (isPlainObject(value)) {\n parentReceiver[key] = createMiniState(value, {}, notifyListeners);\n } else {\n defineStateProxy(key, value, parentReceiver, notifyListeners);\n }\n }\n\n return parentReceiver as T;\n}\n\nexport function miniStore<T extends TweenProps>(init: T) {\n const listeners = new Set<Listener<T>>();\n const notifyListeners = () => {\n listeners.forEach((listener) => listener(store));\n };\n\n const store = createMiniState(init, {}, notifyListeners) as T;\n\n return {\n get state() {\n return store;\n },\n subscribe: (listener: Listener<T>) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\nexport function useMiniStore<T extends TweenProps>(initialValue: T) {\n const storeRef = useRef<ReturnType<typeof miniStore<T>>>(null);\n const [, setVersion] = useState(0);\n\n // istanbul ignore else @preserve\n if (!storeRef.current) {\n storeRef.current = miniStore(initialValue);\n }\n\n useEffect(\n () =>\n storeRef.current!.subscribe(() => setVersion((v) => v === 2 ? 0 : v + 1)),\n [],\n );\n\n return storeRef.current!.state;\n}\n","import { useEffect, useRef } from \"preact/hooks\";\nimport {\n dummyInstance,\n isServer,\n Timeline,\n Tween,\n type TweenProps,\n} from \"@thednp/tween\";\nimport { useMiniStore } from \"./miniStore.ts\";\n\nexport { Timeline, Tween, useMiniStore };\n\n/**\n * Hook for updating values with Tween.\n *\n * **NOTE**: - configuration must be wrapped in `useEffect` or `eventListener`.\n * This has two important aspects: never configure or start update loop in SSR\n * and only configure or start the loop when component is mounted in the client.\n *\n * @param initialValues - Initial tween values\n * @returns [store, tween] Tuple of reactive store and Tween instance\n * @example\n * const App = () => {\n * const [state, tween] = useTween({ x: 0, y: 0 })\n *\n * useEffect(() => {\n * tween.to({ x: 100, y: 100 }, 1000).start()\n * }, [])\n *\n * return (\n * <div style={{ translate: `${state.x}px ${state.y}px` }} />\n * );\n * }\n */\nexport function useTween<T extends TweenProps>(initialValues: T) {\n if (isServer) {\n return [initialValues, dummyInstance as unknown as Tween<T>] as const;\n }\n const store = useMiniStore(initialValues);\n const tweenRef = useRef<Tween<T>>(null);\n\n // istanbul ignore else @preserve\n if (!tweenRef.current) {\n tweenRef.current = new Tween(store);\n }\n\n const dispose = () => {\n tweenRef.current!.stop();\n tweenRef.current!.clear();\n };\n useEffect(() => dispose, []);\n\n return [store, tweenRef.current] as [T, Tween<T>];\n}\n\n/**\n * Hook for sequencing values update with Timeline.\n *\n * **NOTE**: - configuration must be wrapped in `useEffect` or `eventListener`.\n * This has two important aspects: never configure or start update loop in SSR\n * and only configure or start the loop when component is mounted in the client.\n *\n * @param initialValues - Initial tween values\n * @returns [store, timeline] Tuple of reactive store and Timeline instance\n * @example\n * const App = () => {\n * const [state, timeline] = useTimeline({ x: 0, y: 0 })\n *\n * useEffect(() => {\n * timeline.to({ x: 100, y: 100 }).start()\n * }, [])\n *\n * return (\n * <div style={{ translate: `${state.x}px ${state.y}px` }} />\n * );\n * }\n */\nexport function useTimeline<T extends TweenProps>(initialValues: T) {\n if (isServer) {\n return [initialValues, dummyInstance as unknown as Timeline<T>] as const;\n }\n const store = useMiniStore(initialValues);\n const timelineRef = useRef<Timeline<T>>(null);\n\n // istanbul ignore else @preserve\n if (!timelineRef.current) {\n timelineRef.current = new Timeline(store);\n }\n\n const dispose = () => {\n timelineRef.current!.stop();\n timelineRef.current!.clear();\n };\n useEffect(() => dispose, []);\n\n return [store, timelineRef.current] as [T, Timeline<T>];\n}\n"],"mappings":"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"}