├── .gitignore
├── LICENSE
├── README.md
├── demo
├── .gitignore
├── README.md
├── index.html
├── package.json
├── pnpm-lock.yaml
├── src
│ ├── App.tsx
│ └── index.tsx
├── tsconfig.json
└── vite.config.ts
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── spring
├── .gitignore
├── package.json
├── rollup.config.js
├── src
│ ├── AnimatedArray.ts
│ ├── AnimatedObject.ts
│ ├── AnimatedString.ts
│ ├── AnimatedStyle.ts
│ ├── Animation.ts
│ ├── AnimationConfig.ts
│ ├── AnimationResult.ts
│ ├── Controller.ts
│ ├── FrameLoop.ts
│ ├── FrameValue.ts
│ ├── Interpolation.ts
│ ├── SpringPhase.ts
│ ├── SpringRef.ts
│ ├── SpringValue.ts
│ ├── animated.ts
│ ├── applyAnimatedValues.ts
│ ├── colors.ts
│ ├── context.ts
│ ├── createHost.ts
│ ├── createInterpolator.ts
│ ├── fluids.ts
│ ├── globals.ts
│ ├── index.ts
│ ├── normalizeColor.ts
│ ├── primitives.ts
│ ├── rafz.ts
│ ├── runAsync.ts
│ ├── scheduleProps.ts
│ ├── solid
│ │ ├── createSpring.ts
│ │ └── createSprings.ts
│ ├── utils.ts
│ └── withAnimated.tsx
└── tsconfig.json
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 M. Bagher Abiat
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
solid-spring
3 |
A port of react-spring, for SolidJS
4 |
5 | `solid-spring` is a spring-physics first animation library for SolidJS based on react-spring/core.
6 |
7 | > This an experimental project that was made to be submitted in hack.solidjs.com, feel free to create github ticket
8 |
9 | The API looks like this:
10 |
11 | ```jsx
12 | const styles = createSpring({
13 | from: {
14 | opacity: 0
15 | },
16 | to: {
17 | opacity: 1
18 | }
19 | })
20 |
21 |
22 | ```
23 |
24 | The API is similar to what we have in react-spring, with small differences to make the library compatible with SolidJS
25 |
26 | ## Preview
27 | Click on the below gifs for exploring the code of each preview (ported from Poimandres examples).
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ## Install
36 |
37 | ```shell
38 | npm install solid-spring
39 | ```
40 | ## Examples
41 |
42 | [Hello (opacity animation)](https://codesandbox.io/s/hello-qe3eq5?file=/index.tsx)
43 |
44 | [Svg (animating svg elements)](https://codesandbox.io/s/svg-omnp4c?file=/index.tsx)
45 |
46 | [Numbers (non style use case)](https://codesandbox.io/s/numbers-kbc57h?file=/index.tsx)
47 |
48 | ## API
49 |
50 | ### `createSpring`
51 | > Turns values into animated-values.
52 |
53 | ```jsx
54 | import { createSpring, animated } from "solid-spring";
55 |
56 | function ChainExample() {
57 | const styles = createSpring({
58 | loop: true,
59 | to: [
60 | { opacity: 1, color: '#ffaaee' },
61 | { opacity: 0, color: 'rgb(14,26,19)' },
62 | ],
63 | from: { opacity: 0, color: 'red' },
64 | })
65 |
66 | return I will fade in and out
67 | }
68 | ```
69 | `createSpring` also takes a function in case you want to pass a reactive value as a style!
70 | ```jsx
71 | const [disabled, setDisabled] = createSignal(false)
72 |
73 | const styles = createSpring(() => ({
74 | pause: disabled(),
75 | }))
76 | ```
77 | ### `createSprings`
78 | > Creates multiple springs, each with its own config. Use it for static lists, etc.
79 |
80 | Similar to `useSprings` in react-spring, It takes number or a function that returns a number (for reactivity) as the first argument, and a list of springs or a function that returns a spring as the second argument.
81 |
82 | ```jsx
83 | function createSprings(
84 | lengthFn: number | (() => number),
85 | props: Props[] & CreateSpringsProps>[]
86 | ): Accessor>[]> & {
87 | ref: SpringRefType>;
88 | };
89 |
90 | function createSprings(
91 | lengthFn: number | (() => number),
92 | props: (i: number, ctrl: Controller) => Props
93 | ): Accessor>[]> & {
94 | ref: SpringRefType>;
95 | };
96 | ```
97 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
4 |
5 | This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
6 |
7 | ```bash
8 | $ npm install # or pnpm install or yarn install
9 | ```
10 |
11 | ### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
12 |
13 | ## Available Scripts
14 |
15 | In the project directory, you can run:
16 |
17 | ### `npm dev` or `npm start`
18 |
19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
21 |
22 | The page will reload if you make edits.
23 |
24 | ### `npm run build`
25 |
26 | Builds the app for production to the `dist` folder.
27 | It correctly bundles Solid in production mode and optimizes the build for the best performance.
28 |
29 | The build is minified and the filenames include the hashes.
30 | Your app is ready to be deployed!
31 |
32 | ## Deployment
33 |
34 | You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
35 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Solid App
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-template-solid",
3 | "version": "0.0.0",
4 | "description": "",
5 | "scripts": {
6 | "start": "vite",
7 | "dev": "vite",
8 | "build": "vite build",
9 | "serve": "vite preview"
10 | },
11 | "license": "MIT",
12 | "devDependencies": {
13 | "typescript": "^4.6.3",
14 | "vite": "^2.8.6",
15 | "vite-plugin-solid": "^2.2.6"
16 | },
17 | "dependencies": {
18 | "@react-spring/animated": "^9.4.4",
19 | "@react-spring/core": "^9.4.4",
20 | "solid-js": "^1.3.13",
21 | "solid-spring": "workspace:^0.0.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/demo/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: 5.3
2 |
3 | specifiers:
4 | '@react-spring/animated': ^9.4.4
5 | '@react-spring/core': ^9.4.4
6 | solid-js: ^1.3.13
7 | typescript: ^4.6.3
8 | vite: ^2.8.6
9 | vite-plugin-solid: ^2.2.6
10 |
11 | dependencies:
12 | '@react-spring/animated': 9.4.4
13 | '@react-spring/core': 9.4.4
14 | solid-js: 1.3.13
15 |
16 | devDependencies:
17 | typescript: 4.6.3
18 | vite: 2.8.6
19 | vite-plugin-solid: 2.2.6
20 |
21 | packages:
22 |
23 | /@ampproject/remapping/2.1.2:
24 | resolution: {integrity: sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==}
25 | engines: {node: '>=6.0.0'}
26 | dependencies:
27 | '@jridgewell/trace-mapping': 0.3.4
28 | dev: true
29 |
30 | /@babel/code-frame/7.16.7:
31 | resolution: {integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==}
32 | engines: {node: '>=6.9.0'}
33 | dependencies:
34 | '@babel/highlight': 7.16.10
35 | dev: true
36 |
37 | /@babel/compat-data/7.17.7:
38 | resolution: {integrity: sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==}
39 | engines: {node: '>=6.9.0'}
40 | dev: true
41 |
42 | /@babel/core/7.17.8:
43 | resolution: {integrity: sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ==}
44 | engines: {node: '>=6.9.0'}
45 | dependencies:
46 | '@ampproject/remapping': 2.1.2
47 | '@babel/code-frame': 7.16.7
48 | '@babel/generator': 7.17.7
49 | '@babel/helper-compilation-targets': 7.17.7_@babel+core@7.17.8
50 | '@babel/helper-module-transforms': 7.17.7
51 | '@babel/helpers': 7.17.8
52 | '@babel/parser': 7.17.8
53 | '@babel/template': 7.16.7
54 | '@babel/traverse': 7.17.3
55 | '@babel/types': 7.17.0
56 | convert-source-map: 1.8.0
57 | debug: 4.3.4
58 | gensync: 1.0.0-beta.2
59 | json5: 2.2.1
60 | semver: 6.3.0
61 | transitivePeerDependencies:
62 | - supports-color
63 | dev: true
64 |
65 | /@babel/generator/7.17.7:
66 | resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==}
67 | engines: {node: '>=6.9.0'}
68 | dependencies:
69 | '@babel/types': 7.17.0
70 | jsesc: 2.5.2
71 | source-map: 0.5.7
72 | dev: true
73 |
74 | /@babel/helper-annotate-as-pure/7.16.7:
75 | resolution: {integrity: sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==}
76 | engines: {node: '>=6.9.0'}
77 | dependencies:
78 | '@babel/types': 7.17.0
79 | dev: true
80 |
81 | /@babel/helper-compilation-targets/7.17.7_@babel+core@7.17.8:
82 | resolution: {integrity: sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==}
83 | engines: {node: '>=6.9.0'}
84 | peerDependencies:
85 | '@babel/core': ^7.0.0
86 | dependencies:
87 | '@babel/compat-data': 7.17.7
88 | '@babel/core': 7.17.8
89 | '@babel/helper-validator-option': 7.16.7
90 | browserslist: 4.20.2
91 | semver: 6.3.0
92 | dev: true
93 |
94 | /@babel/helper-create-class-features-plugin/7.17.6_@babel+core@7.17.8:
95 | resolution: {integrity: sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==}
96 | engines: {node: '>=6.9.0'}
97 | peerDependencies:
98 | '@babel/core': ^7.0.0
99 | dependencies:
100 | '@babel/core': 7.17.8
101 | '@babel/helper-annotate-as-pure': 7.16.7
102 | '@babel/helper-environment-visitor': 7.16.7
103 | '@babel/helper-function-name': 7.16.7
104 | '@babel/helper-member-expression-to-functions': 7.17.7
105 | '@babel/helper-optimise-call-expression': 7.16.7
106 | '@babel/helper-replace-supers': 7.16.7
107 | '@babel/helper-split-export-declaration': 7.16.7
108 | transitivePeerDependencies:
109 | - supports-color
110 | dev: true
111 |
112 | /@babel/helper-environment-visitor/7.16.7:
113 | resolution: {integrity: sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==}
114 | engines: {node: '>=6.9.0'}
115 | dependencies:
116 | '@babel/types': 7.17.0
117 | dev: true
118 |
119 | /@babel/helper-function-name/7.16.7:
120 | resolution: {integrity: sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==}
121 | engines: {node: '>=6.9.0'}
122 | dependencies:
123 | '@babel/helper-get-function-arity': 7.16.7
124 | '@babel/template': 7.16.7
125 | '@babel/types': 7.17.0
126 | dev: true
127 |
128 | /@babel/helper-get-function-arity/7.16.7:
129 | resolution: {integrity: sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==}
130 | engines: {node: '>=6.9.0'}
131 | dependencies:
132 | '@babel/types': 7.17.0
133 | dev: true
134 |
135 | /@babel/helper-hoist-variables/7.16.7:
136 | resolution: {integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==}
137 | engines: {node: '>=6.9.0'}
138 | dependencies:
139 | '@babel/types': 7.17.0
140 | dev: true
141 |
142 | /@babel/helper-member-expression-to-functions/7.17.7:
143 | resolution: {integrity: sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==}
144 | engines: {node: '>=6.9.0'}
145 | dependencies:
146 | '@babel/types': 7.17.0
147 | dev: true
148 |
149 | /@babel/helper-module-imports/7.16.0:
150 | resolution: {integrity: sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==}
151 | engines: {node: '>=6.9.0'}
152 | dependencies:
153 | '@babel/types': 7.17.0
154 | dev: true
155 |
156 | /@babel/helper-module-imports/7.16.7:
157 | resolution: {integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==}
158 | engines: {node: '>=6.9.0'}
159 | dependencies:
160 | '@babel/types': 7.17.0
161 | dev: true
162 |
163 | /@babel/helper-module-transforms/7.17.7:
164 | resolution: {integrity: sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==}
165 | engines: {node: '>=6.9.0'}
166 | dependencies:
167 | '@babel/helper-environment-visitor': 7.16.7
168 | '@babel/helper-module-imports': 7.16.7
169 | '@babel/helper-simple-access': 7.17.7
170 | '@babel/helper-split-export-declaration': 7.16.7
171 | '@babel/helper-validator-identifier': 7.16.7
172 | '@babel/template': 7.16.7
173 | '@babel/traverse': 7.17.3
174 | '@babel/types': 7.17.0
175 | transitivePeerDependencies:
176 | - supports-color
177 | dev: true
178 |
179 | /@babel/helper-optimise-call-expression/7.16.7:
180 | resolution: {integrity: sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==}
181 | engines: {node: '>=6.9.0'}
182 | dependencies:
183 | '@babel/types': 7.17.0
184 | dev: true
185 |
186 | /@babel/helper-plugin-utils/7.16.7:
187 | resolution: {integrity: sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==}
188 | engines: {node: '>=6.9.0'}
189 | dev: true
190 |
191 | /@babel/helper-replace-supers/7.16.7:
192 | resolution: {integrity: sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==}
193 | engines: {node: '>=6.9.0'}
194 | dependencies:
195 | '@babel/helper-environment-visitor': 7.16.7
196 | '@babel/helper-member-expression-to-functions': 7.17.7
197 | '@babel/helper-optimise-call-expression': 7.16.7
198 | '@babel/traverse': 7.17.3
199 | '@babel/types': 7.17.0
200 | transitivePeerDependencies:
201 | - supports-color
202 | dev: true
203 |
204 | /@babel/helper-simple-access/7.17.7:
205 | resolution: {integrity: sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==}
206 | engines: {node: '>=6.9.0'}
207 | dependencies:
208 | '@babel/types': 7.17.0
209 | dev: true
210 |
211 | /@babel/helper-split-export-declaration/7.16.7:
212 | resolution: {integrity: sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==}
213 | engines: {node: '>=6.9.0'}
214 | dependencies:
215 | '@babel/types': 7.17.0
216 | dev: true
217 |
218 | /@babel/helper-validator-identifier/7.16.7:
219 | resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==}
220 | engines: {node: '>=6.9.0'}
221 | dev: true
222 |
223 | /@babel/helper-validator-option/7.16.7:
224 | resolution: {integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==}
225 | engines: {node: '>=6.9.0'}
226 | dev: true
227 |
228 | /@babel/helpers/7.17.8:
229 | resolution: {integrity: sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==}
230 | engines: {node: '>=6.9.0'}
231 | dependencies:
232 | '@babel/template': 7.16.7
233 | '@babel/traverse': 7.17.3
234 | '@babel/types': 7.17.0
235 | transitivePeerDependencies:
236 | - supports-color
237 | dev: true
238 |
239 | /@babel/highlight/7.16.10:
240 | resolution: {integrity: sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==}
241 | engines: {node: '>=6.9.0'}
242 | dependencies:
243 | '@babel/helper-validator-identifier': 7.16.7
244 | chalk: 2.4.2
245 | js-tokens: 4.0.0
246 | dev: true
247 |
248 | /@babel/parser/7.17.8:
249 | resolution: {integrity: sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==}
250 | engines: {node: '>=6.0.0'}
251 | hasBin: true
252 | dev: true
253 |
254 | /@babel/plugin-syntax-jsx/7.16.7_@babel+core@7.17.8:
255 | resolution: {integrity: sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==}
256 | engines: {node: '>=6.9.0'}
257 | peerDependencies:
258 | '@babel/core': ^7.0.0-0
259 | dependencies:
260 | '@babel/core': 7.17.8
261 | '@babel/helper-plugin-utils': 7.16.7
262 | dev: true
263 |
264 | /@babel/plugin-syntax-typescript/7.16.7_@babel+core@7.17.8:
265 | resolution: {integrity: sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==}
266 | engines: {node: '>=6.9.0'}
267 | peerDependencies:
268 | '@babel/core': ^7.0.0-0
269 | dependencies:
270 | '@babel/core': 7.17.8
271 | '@babel/helper-plugin-utils': 7.16.7
272 | dev: true
273 |
274 | /@babel/plugin-transform-typescript/7.16.8_@babel+core@7.17.8:
275 | resolution: {integrity: sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==}
276 | engines: {node: '>=6.9.0'}
277 | peerDependencies:
278 | '@babel/core': ^7.0.0-0
279 | dependencies:
280 | '@babel/core': 7.17.8
281 | '@babel/helper-create-class-features-plugin': 7.17.6_@babel+core@7.17.8
282 | '@babel/helper-plugin-utils': 7.16.7
283 | '@babel/plugin-syntax-typescript': 7.16.7_@babel+core@7.17.8
284 | transitivePeerDependencies:
285 | - supports-color
286 | dev: true
287 |
288 | /@babel/preset-typescript/7.16.7_@babel+core@7.17.8:
289 | resolution: {integrity: sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==}
290 | engines: {node: '>=6.9.0'}
291 | peerDependencies:
292 | '@babel/core': ^7.0.0-0
293 | dependencies:
294 | '@babel/core': 7.17.8
295 | '@babel/helper-plugin-utils': 7.16.7
296 | '@babel/helper-validator-option': 7.16.7
297 | '@babel/plugin-transform-typescript': 7.16.8_@babel+core@7.17.8
298 | transitivePeerDependencies:
299 | - supports-color
300 | dev: true
301 |
302 | /@babel/template/7.16.7:
303 | resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==}
304 | engines: {node: '>=6.9.0'}
305 | dependencies:
306 | '@babel/code-frame': 7.16.7
307 | '@babel/parser': 7.17.8
308 | '@babel/types': 7.17.0
309 | dev: true
310 |
311 | /@babel/traverse/7.17.3:
312 | resolution: {integrity: sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==}
313 | engines: {node: '>=6.9.0'}
314 | dependencies:
315 | '@babel/code-frame': 7.16.7
316 | '@babel/generator': 7.17.7
317 | '@babel/helper-environment-visitor': 7.16.7
318 | '@babel/helper-function-name': 7.16.7
319 | '@babel/helper-hoist-variables': 7.16.7
320 | '@babel/helper-split-export-declaration': 7.16.7
321 | '@babel/parser': 7.17.8
322 | '@babel/types': 7.17.0
323 | debug: 4.3.4
324 | globals: 11.12.0
325 | transitivePeerDependencies:
326 | - supports-color
327 | dev: true
328 |
329 | /@babel/types/7.17.0:
330 | resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==}
331 | engines: {node: '>=6.9.0'}
332 | dependencies:
333 | '@babel/helper-validator-identifier': 7.16.7
334 | to-fast-properties: 2.0.0
335 | dev: true
336 |
337 | /@jridgewell/resolve-uri/3.0.5:
338 | resolution: {integrity: sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==}
339 | engines: {node: '>=6.0.0'}
340 | dev: true
341 |
342 | /@jridgewell/sourcemap-codec/1.4.11:
343 | resolution: {integrity: sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==}
344 | dev: true
345 |
346 | /@jridgewell/trace-mapping/0.3.4:
347 | resolution: {integrity: sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==}
348 | dependencies:
349 | '@jridgewell/resolve-uri': 3.0.5
350 | '@jridgewell/sourcemap-codec': 1.4.11
351 | dev: true
352 |
353 | /@react-spring/animated/9.4.4:
354 | resolution: {integrity: sha512-e9xnuBaUTD+NolKikUmrGWjX8AVCPyj1GcEgjgq9E+0sXKv46UY7cm2EmB6mUDTxWIDVKebARY++xT4nGDraBQ==}
355 | peerDependencies:
356 | react: ^16.8.0 || ^17.0.0
357 | dependencies:
358 | '@react-spring/shared': 9.4.4
359 | '@react-spring/types': 9.4.4
360 | dev: false
361 |
362 | /@react-spring/core/9.4.4:
363 | resolution: {integrity: sha512-llgb0ljFyjMB0JhWsaFHOi9XFT8n1jBMVs1IFY2ipIBerWIRWrgUmIpakLPHTa4c4jwqTaDSwX90s2a0iN7dxQ==}
364 | peerDependencies:
365 | react: ^16.8.0 || ^17.0.0
366 | dependencies:
367 | '@react-spring/animated': 9.4.4
368 | '@react-spring/rafz': 9.4.4
369 | '@react-spring/shared': 9.4.4
370 | '@react-spring/types': 9.4.4
371 | dev: false
372 |
373 | /@react-spring/rafz/9.4.4:
374 | resolution: {integrity: sha512-5ki/sQ06Mdf8AuFstSt5zbNNicRT4LZogiJttDAww1ozhuvemafNWEHxhzcULgCPCDu2s7HsroaISV7+GQWrhw==}
375 | dev: false
376 |
377 | /@react-spring/shared/9.4.4:
378 | resolution: {integrity: sha512-ySVgScDZlhm/+Iy2smY9i/DDrShArY0j6zjTS/Re1lasKnhq8qigoGiAxe8xMPJNlCaj3uczCqHy3TY9bKRtfQ==}
379 | peerDependencies:
380 | react: ^16.8.0 || ^17.0.0
381 | dependencies:
382 | '@react-spring/rafz': 9.4.4
383 | '@react-spring/types': 9.4.4
384 | dev: false
385 |
386 | /@react-spring/types/9.4.4:
387 | resolution: {integrity: sha512-KpxKt/D//q/t/6FBcde/RE36LKp8PpWu7kFEMLwpzMGl9RpcexunmYOQJWwmJWtkQjgE1YRr7DzBMryz6La1cQ==}
388 | dev: false
389 |
390 | /ansi-styles/3.2.1:
391 | resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
392 | engines: {node: '>=4'}
393 | dependencies:
394 | color-convert: 1.9.3
395 | dev: true
396 |
397 | /babel-plugin-jsx-dom-expressions/0.32.11_@babel+core@7.17.8:
398 | resolution: {integrity: sha512-hytqY33SGW6B3obSLt8K5X510UwtNkTktCCWgwba+QOOV0CowDFiqeL+0ru895FLacFaYANHFTu1y76dg3GVtw==}
399 | dependencies:
400 | '@babel/helper-module-imports': 7.16.0
401 | '@babel/plugin-syntax-jsx': 7.16.7_@babel+core@7.17.8
402 | '@babel/types': 7.17.0
403 | html-entities: 2.3.2
404 | transitivePeerDependencies:
405 | - '@babel/core'
406 | dev: true
407 |
408 | /babel-preset-solid/1.3.13_@babel+core@7.17.8:
409 | resolution: {integrity: sha512-MZnmsceI9yiHlwwFCSALTJhadk2eea/+2UP4ec4jkPZFR+XRKTLoIwRkrBh7uLtvHF+3lHGyUaXtZukOmmUwhA==}
410 | dependencies:
411 | babel-plugin-jsx-dom-expressions: 0.32.11_@babel+core@7.17.8
412 | transitivePeerDependencies:
413 | - '@babel/core'
414 | dev: true
415 |
416 | /browserslist/4.20.2:
417 | resolution: {integrity: sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==}
418 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
419 | hasBin: true
420 | dependencies:
421 | caniuse-lite: 1.0.30001320
422 | electron-to-chromium: 1.4.93
423 | escalade: 3.1.1
424 | node-releases: 2.0.2
425 | picocolors: 1.0.0
426 | dev: true
427 |
428 | /caniuse-lite/1.0.30001320:
429 | resolution: {integrity: sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==}
430 | dev: true
431 |
432 | /chalk/2.4.2:
433 | resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
434 | engines: {node: '>=4'}
435 | dependencies:
436 | ansi-styles: 3.2.1
437 | escape-string-regexp: 1.0.5
438 | supports-color: 5.5.0
439 | dev: true
440 |
441 | /color-convert/1.9.3:
442 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
443 | dependencies:
444 | color-name: 1.1.3
445 | dev: true
446 |
447 | /color-name/1.1.3:
448 | resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=}
449 | dev: true
450 |
451 | /convert-source-map/1.8.0:
452 | resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
453 | dependencies:
454 | safe-buffer: 5.1.2
455 | dev: true
456 |
457 | /debug/4.3.4:
458 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
459 | engines: {node: '>=6.0'}
460 | peerDependencies:
461 | supports-color: '*'
462 | peerDependenciesMeta:
463 | supports-color:
464 | optional: true
465 | dependencies:
466 | ms: 2.1.2
467 | dev: true
468 |
469 | /electron-to-chromium/1.4.93:
470 | resolution: {integrity: sha512-ywq9Pc5Gwwpv7NG767CtoU8xF3aAUQJjH9//Wy3MBCg4w5JSLbJUq2L8IsCdzPMjvSgxuue9WcVaTOyyxCL0aQ==}
471 | dev: true
472 |
473 | /esbuild-android-64/0.14.27:
474 | resolution: {integrity: sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ==}
475 | engines: {node: '>=12'}
476 | cpu: [x64]
477 | os: [android]
478 | requiresBuild: true
479 | dev: true
480 | optional: true
481 |
482 | /esbuild-android-arm64/0.14.27:
483 | resolution: {integrity: sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ==}
484 | engines: {node: '>=12'}
485 | cpu: [arm64]
486 | os: [android]
487 | requiresBuild: true
488 | dev: true
489 | optional: true
490 |
491 | /esbuild-darwin-64/0.14.27:
492 | resolution: {integrity: sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g==}
493 | engines: {node: '>=12'}
494 | cpu: [x64]
495 | os: [darwin]
496 | requiresBuild: true
497 | dev: true
498 | optional: true
499 |
500 | /esbuild-darwin-arm64/0.14.27:
501 | resolution: {integrity: sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ==}
502 | engines: {node: '>=12'}
503 | cpu: [arm64]
504 | os: [darwin]
505 | requiresBuild: true
506 | dev: true
507 | optional: true
508 |
509 | /esbuild-freebsd-64/0.14.27:
510 | resolution: {integrity: sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA==}
511 | engines: {node: '>=12'}
512 | cpu: [x64]
513 | os: [freebsd]
514 | requiresBuild: true
515 | dev: true
516 | optional: true
517 |
518 | /esbuild-freebsd-arm64/0.14.27:
519 | resolution: {integrity: sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA==}
520 | engines: {node: '>=12'}
521 | cpu: [arm64]
522 | os: [freebsd]
523 | requiresBuild: true
524 | dev: true
525 | optional: true
526 |
527 | /esbuild-linux-32/0.14.27:
528 | resolution: {integrity: sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw==}
529 | engines: {node: '>=12'}
530 | cpu: [ia32]
531 | os: [linux]
532 | requiresBuild: true
533 | dev: true
534 | optional: true
535 |
536 | /esbuild-linux-64/0.14.27:
537 | resolution: {integrity: sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg==}
538 | engines: {node: '>=12'}
539 | cpu: [x64]
540 | os: [linux]
541 | requiresBuild: true
542 | dev: true
543 | optional: true
544 |
545 | /esbuild-linux-arm/0.14.27:
546 | resolution: {integrity: sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw==}
547 | engines: {node: '>=12'}
548 | cpu: [arm]
549 | os: [linux]
550 | requiresBuild: true
551 | dev: true
552 | optional: true
553 |
554 | /esbuild-linux-arm64/0.14.27:
555 | resolution: {integrity: sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ==}
556 | engines: {node: '>=12'}
557 | cpu: [arm64]
558 | os: [linux]
559 | requiresBuild: true
560 | dev: true
561 | optional: true
562 |
563 | /esbuild-linux-mips64le/0.14.27:
564 | resolution: {integrity: sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A==}
565 | engines: {node: '>=12'}
566 | cpu: [mips64el]
567 | os: [linux]
568 | requiresBuild: true
569 | dev: true
570 | optional: true
571 |
572 | /esbuild-linux-ppc64le/0.14.27:
573 | resolution: {integrity: sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA==}
574 | engines: {node: '>=12'}
575 | cpu: [ppc64]
576 | os: [linux]
577 | requiresBuild: true
578 | dev: true
579 | optional: true
580 |
581 | /esbuild-linux-riscv64/0.14.27:
582 | resolution: {integrity: sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg==}
583 | engines: {node: '>=12'}
584 | cpu: [riscv64]
585 | os: [linux]
586 | requiresBuild: true
587 | dev: true
588 | optional: true
589 |
590 | /esbuild-linux-s390x/0.14.27:
591 | resolution: {integrity: sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg==}
592 | engines: {node: '>=12'}
593 | cpu: [s390x]
594 | os: [linux]
595 | requiresBuild: true
596 | dev: true
597 | optional: true
598 |
599 | /esbuild-netbsd-64/0.14.27:
600 | resolution: {integrity: sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q==}
601 | engines: {node: '>=12'}
602 | cpu: [x64]
603 | os: [netbsd]
604 | requiresBuild: true
605 | dev: true
606 | optional: true
607 |
608 | /esbuild-openbsd-64/0.14.27:
609 | resolution: {integrity: sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw==}
610 | engines: {node: '>=12'}
611 | cpu: [x64]
612 | os: [openbsd]
613 | requiresBuild: true
614 | dev: true
615 | optional: true
616 |
617 | /esbuild-sunos-64/0.14.27:
618 | resolution: {integrity: sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg==}
619 | engines: {node: '>=12'}
620 | cpu: [x64]
621 | os: [sunos]
622 | requiresBuild: true
623 | dev: true
624 | optional: true
625 |
626 | /esbuild-windows-32/0.14.27:
627 | resolution: {integrity: sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw==}
628 | engines: {node: '>=12'}
629 | cpu: [ia32]
630 | os: [win32]
631 | requiresBuild: true
632 | dev: true
633 | optional: true
634 |
635 | /esbuild-windows-64/0.14.27:
636 | resolution: {integrity: sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg==}
637 | engines: {node: '>=12'}
638 | cpu: [x64]
639 | os: [win32]
640 | requiresBuild: true
641 | dev: true
642 | optional: true
643 |
644 | /esbuild-windows-arm64/0.14.27:
645 | resolution: {integrity: sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg==}
646 | engines: {node: '>=12'}
647 | cpu: [arm64]
648 | os: [win32]
649 | requiresBuild: true
650 | dev: true
651 | optional: true
652 |
653 | /esbuild/0.14.27:
654 | resolution: {integrity: sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q==}
655 | engines: {node: '>=12'}
656 | hasBin: true
657 | requiresBuild: true
658 | optionalDependencies:
659 | esbuild-android-64: 0.14.27
660 | esbuild-android-arm64: 0.14.27
661 | esbuild-darwin-64: 0.14.27
662 | esbuild-darwin-arm64: 0.14.27
663 | esbuild-freebsd-64: 0.14.27
664 | esbuild-freebsd-arm64: 0.14.27
665 | esbuild-linux-32: 0.14.27
666 | esbuild-linux-64: 0.14.27
667 | esbuild-linux-arm: 0.14.27
668 | esbuild-linux-arm64: 0.14.27
669 | esbuild-linux-mips64le: 0.14.27
670 | esbuild-linux-ppc64le: 0.14.27
671 | esbuild-linux-riscv64: 0.14.27
672 | esbuild-linux-s390x: 0.14.27
673 | esbuild-netbsd-64: 0.14.27
674 | esbuild-openbsd-64: 0.14.27
675 | esbuild-sunos-64: 0.14.27
676 | esbuild-windows-32: 0.14.27
677 | esbuild-windows-64: 0.14.27
678 | esbuild-windows-arm64: 0.14.27
679 | dev: true
680 |
681 | /escalade/3.1.1:
682 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
683 | engines: {node: '>=6'}
684 | dev: true
685 |
686 | /escape-string-regexp/1.0.5:
687 | resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}
688 | engines: {node: '>=0.8.0'}
689 | dev: true
690 |
691 | /fsevents/2.3.2:
692 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
693 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
694 | os: [darwin]
695 | requiresBuild: true
696 | dev: true
697 | optional: true
698 |
699 | /function-bind/1.1.1:
700 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
701 | dev: true
702 |
703 | /gensync/1.0.0-beta.2:
704 | resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
705 | engines: {node: '>=6.9.0'}
706 | dev: true
707 |
708 | /globals/11.12.0:
709 | resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
710 | engines: {node: '>=4'}
711 | dev: true
712 |
713 | /has-flag/3.0.0:
714 | resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=}
715 | engines: {node: '>=4'}
716 | dev: true
717 |
718 | /has/1.0.3:
719 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
720 | engines: {node: '>= 0.4.0'}
721 | dependencies:
722 | function-bind: 1.1.1
723 | dev: true
724 |
725 | /html-entities/2.3.2:
726 | resolution: {integrity: sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==}
727 | dev: true
728 |
729 | /is-core-module/2.8.1:
730 | resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==}
731 | dependencies:
732 | has: 1.0.3
733 | dev: true
734 |
735 | /is-what/4.1.7:
736 | resolution: {integrity: sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==}
737 | engines: {node: '>=12.13'}
738 | dev: true
739 |
740 | /js-tokens/4.0.0:
741 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
742 | dev: true
743 |
744 | /jsesc/2.5.2:
745 | resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
746 | engines: {node: '>=4'}
747 | hasBin: true
748 | dev: true
749 |
750 | /json5/2.2.1:
751 | resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==}
752 | engines: {node: '>=6'}
753 | hasBin: true
754 | dev: true
755 |
756 | /merge-anything/5.0.2:
757 | resolution: {integrity: sha512-POPQBWkBC0vxdgzRJ2Mkj4+2NTKbvkHo93ih+jGDhNMLzIw+rYKjO7949hOQM2X7DxMHH1uoUkwWFLIzImw7gA==}
758 | engines: {node: '>=12.13'}
759 | dependencies:
760 | is-what: 4.1.7
761 | ts-toolbelt: 9.6.0
762 | dev: true
763 |
764 | /ms/2.1.2:
765 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
766 | dev: true
767 |
768 | /nanoid/3.3.1:
769 | resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==}
770 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
771 | hasBin: true
772 | dev: true
773 |
774 | /node-releases/2.0.2:
775 | resolution: {integrity: sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==}
776 | dev: true
777 |
778 | /path-parse/1.0.7:
779 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
780 | dev: true
781 |
782 | /picocolors/1.0.0:
783 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
784 | dev: true
785 |
786 | /postcss/8.4.12:
787 | resolution: {integrity: sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==}
788 | engines: {node: ^10 || ^12 || >=14}
789 | dependencies:
790 | nanoid: 3.3.1
791 | picocolors: 1.0.0
792 | source-map-js: 1.0.2
793 | dev: true
794 |
795 | /resolve/1.22.0:
796 | resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==}
797 | hasBin: true
798 | dependencies:
799 | is-core-module: 2.8.1
800 | path-parse: 1.0.7
801 | supports-preserve-symlinks-flag: 1.0.0
802 | dev: true
803 |
804 | /rollup/2.70.1:
805 | resolution: {integrity: sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==}
806 | engines: {node: '>=10.0.0'}
807 | hasBin: true
808 | optionalDependencies:
809 | fsevents: 2.3.2
810 | dev: true
811 |
812 | /safe-buffer/5.1.2:
813 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
814 | dev: true
815 |
816 | /semver/6.3.0:
817 | resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
818 | hasBin: true
819 | dev: true
820 |
821 | /solid-js/1.3.13:
822 | resolution: {integrity: sha512-1EBEIW9u2yqT5QNjFdvz/tMAoKsDdaRA2Jbgykd2Dt13Ia0D4mV+BFvPkOaseSyu7DsMKS23+ZZofV8BVKmpuQ==}
823 |
824 | /solid-refresh/0.4.0_solid-js@1.3.13:
825 | resolution: {integrity: sha512-5XCUz845n/sHPzKK2i2G2EeV61tAmzv6SqzqhXcPaYhrgzVy7nKTQaBpKK8InKrriq9Z2JFF/mguIU00t/73xw==}
826 | peerDependencies:
827 | solid-js: ^1.3.0
828 | dependencies:
829 | '@babel/generator': 7.17.7
830 | '@babel/helper-module-imports': 7.16.7
831 | '@babel/types': 7.17.0
832 | solid-js: 1.3.13
833 | dev: true
834 |
835 | /source-map-js/1.0.2:
836 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
837 | engines: {node: '>=0.10.0'}
838 | dev: true
839 |
840 | /source-map/0.5.7:
841 | resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=}
842 | engines: {node: '>=0.10.0'}
843 | dev: true
844 |
845 | /supports-color/5.5.0:
846 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
847 | engines: {node: '>=4'}
848 | dependencies:
849 | has-flag: 3.0.0
850 | dev: true
851 |
852 | /supports-preserve-symlinks-flag/1.0.0:
853 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
854 | engines: {node: '>= 0.4'}
855 | dev: true
856 |
857 | /to-fast-properties/2.0.0:
858 | resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=}
859 | engines: {node: '>=4'}
860 | dev: true
861 |
862 | /ts-toolbelt/9.6.0:
863 | resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==}
864 | dev: true
865 |
866 | /typescript/4.6.3:
867 | resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==}
868 | engines: {node: '>=4.2.0'}
869 | hasBin: true
870 | dev: true
871 |
872 | /vite-plugin-solid/2.2.6:
873 | resolution: {integrity: sha512-J1RnmqkZZJSNYDW7vZj0giKKHLWGr9tS/gxR70WDSTYfhyXrgukbZdIfSEFbtrsg8ZiQ2t2zXcvkWoeefenqKw==}
874 | dependencies:
875 | '@babel/core': 7.17.8
876 | '@babel/preset-typescript': 7.16.7_@babel+core@7.17.8
877 | babel-preset-solid: 1.3.13_@babel+core@7.17.8
878 | merge-anything: 5.0.2
879 | solid-js: 1.3.13
880 | solid-refresh: 0.4.0_solid-js@1.3.13
881 | vite: 2.8.6
882 | transitivePeerDependencies:
883 | - less
884 | - sass
885 | - stylus
886 | - supports-color
887 | dev: true
888 |
889 | /vite/2.8.6:
890 | resolution: {integrity: sha512-e4H0QpludOVKkmOsRyqQ7LTcMUDF3mcgyNU4lmi0B5JUbe0ZxeBBl8VoZ8Y6Rfn9eFKYtdXNPcYK97ZwH+K2ug==}
891 | engines: {node: '>=12.2.0'}
892 | hasBin: true
893 | peerDependencies:
894 | less: '*'
895 | sass: '*'
896 | stylus: '*'
897 | peerDependenciesMeta:
898 | less:
899 | optional: true
900 | sass:
901 | optional: true
902 | stylus:
903 | optional: true
904 | dependencies:
905 | esbuild: 0.14.27
906 | postcss: 8.4.12
907 | resolve: 1.22.0
908 | rollup: 2.70.1
909 | optionalDependencies:
910 | fsevents: 2.3.2
911 | dev: true
912 |
--------------------------------------------------------------------------------
/demo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | createSignal,
4 | } from "solid-js";
5 | import { createSpring, animated, config } from "solid-spring";
6 |
7 | function ChainExample() {
8 | const [flip, set] = createSignal(false);
9 |
10 | const styles = createSpring(() => {
11 | return {
12 | to: { opacity: 1 },
13 | from: { opacity: 0 },
14 | reset: true,
15 | reverse: flip(),
16 | delay: 200,
17 | config: config.molasses,
18 | onRest: () => {
19 | set(!flip());
20 | },
21 | };
22 | });
23 |
24 | return hello;
25 | }
26 |
27 | const App: Component = () => {
28 | return ;
29 | };
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/demo/src/index.tsx:
--------------------------------------------------------------------------------
1 | /* @refresh reload */
2 | import { render } from 'solid-js/web';
3 |
4 | import App from './App';
5 |
6 | render(() => , document.getElementById('root') as HTMLElement);
7 |
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "allowSyntheticDefaultImports": true,
8 | "esModuleInterop": true,
9 | "jsx": "preserve",
10 | "jsxImportSource": "solid-js",
11 | "types": ["vite/client"],
12 | "noEmit": true,
13 | "isolatedModules": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/demo/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import solidPlugin from 'vite-plugin-solid';
3 |
4 | export default defineConfig({
5 | plugins: [solidPlugin()],
6 | build: {
7 | target: 'esnext',
8 | polyfillDynamicImport: false,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@monorepo/solid-spring",
3 | "version": "0.0.0",
4 | "repository": {
5 | "type": "git",
6 | "url": "git+https://github.com/aslemammad/solid-spring.git"
7 | },
8 | "license": "MIT",
9 | "bugs": {
10 | "url": "https://github.com/aslemammad/solid-spring/issues"
11 | },
12 | "homepage": "https://github.com/aslemammad/solid-spring#readme",
13 | "description": "Like react-spring, but for SolidJS",
14 | "info": "solid-spring is a spring-physics first animation library for SolidJS based on react-spring/core",
15 | "contributors": [],
16 | "scripts": {},
17 | "keywords": [
18 | "best_ecosystem",
19 | "solidhack",
20 | "react-spring",
21 | "solid"
22 | ],
23 | "author": "M. Bagher Abiat",
24 | "license": "MIT",
25 | "devDependencies": {
26 | "@rollup/plugin-alias": "^3.1.9",
27 | "@rollup/plugin-commonjs": "^21.0.2",
28 | "@rollup/plugin-json": "^4.1.0",
29 | "@rollup/plugin-node-resolve": "^13.1.3",
30 | "esbuild": "^0.14.27",
31 | "rollup-plugin-esbuild": "^4.8.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - ./*
3 |
--------------------------------------------------------------------------------
/spring/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist
3 |
--------------------------------------------------------------------------------
/spring/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "solid-spring",
3 | "version": "0.0.7",
4 | "type": "module",
5 | "description": "Like react-spring, but for SolidJS",
6 | "info": "solid-spring is a spring-physics first animation library for SolidJS based on react-spring/core",
7 | "contributors": [],
8 | "keywords": [
9 | "best_ecosystem",
10 | "solidhack",
11 | "react-spring",
12 | "solid"
13 | ],
14 | "scripts": {
15 | "build": "rimraf dist && rollup -c",
16 | "dev": "rollup -c --watch src",
17 | "typecheck": "tsc --noEmit",
18 | "test": "pnpm vitest"
19 | },
20 | "main": "./dist/index.js",
21 | "module": "./dist/index.js",
22 | "types": "./dist/index.d.ts",
23 | "exports": {
24 | ".": {
25 | "import": "./dist/index.js",
26 | "types": "./dist/index.d.ts"
27 | }
28 | },
29 | "files": [
30 | "dist",
31 | "bin",
32 | "*.d.ts"
33 | ],
34 | "repository": {
35 | "type": "git",
36 | "url": "git+https://github.com/aslemammad/solid-spring.git"
37 | },
38 | "license": "MIT",
39 | "bugs": {
40 | "url": "https://github.com/aslemammad/solid-spring/issues"
41 | },
42 | "homepage": "https://github.com/aslemammad/solid-spring#readme",
43 | "devDependencies": {
44 | "rollup-plugin-dts": "^4.2.0",
45 | "solid-js": "^1.3.13"
46 | },
47 | "peerDependencies": {
48 | "solid-js": "^1.3.13"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/spring/rollup.config.js:
--------------------------------------------------------------------------------
1 | import esbuild from "rollup-plugin-esbuild";
2 | import resolve from "@rollup/plugin-node-resolve";
3 | import commonjs from "@rollup/plugin-commonjs";
4 | import json from "@rollup/plugin-json";
5 | import alias from "@rollup/plugin-alias";
6 | import dts from 'rollup-plugin-dts'
7 | import pkg from "./package.json";
8 |
9 | const entry = ["src/index.ts"];
10 |
11 | const external = [
12 | ...Object.keys(pkg.dependencies || []),
13 | ...Object.keys(pkg.peerDependencies || []),
14 | "worker_threads",
15 | "esbuild",
16 | "solid-js",
17 | "solid-js/web",
18 | "fs/promises",
19 | ];
20 |
21 | export default [
22 | {
23 | input: entry,
24 | output: {
25 | dir: "dist",
26 | format: "esm",
27 | sourcemap: true,
28 | },
29 | external,
30 | plugins: [
31 | alias({
32 | entries: [{ find: /^node:(.+)$/, replacement: "$1" }],
33 | }),
34 | resolve({
35 | preferBuiltins: true,
36 | }),
37 | json(),
38 | commonjs(),
39 | esbuild({
40 | define: {
41 | "import.meta.vitest": 'false',
42 | },
43 | target: "node14",
44 | }),
45 | ],
46 | },
47 | {
48 | input: [
49 | 'src/index.ts',
50 | ],
51 | output: {
52 | file: 'dist/index.d.ts',
53 | format: 'esm',
54 | },
55 | external,
56 | plugins: [
57 | dts(),
58 | ],
59 | },
60 | ];
61 |
--------------------------------------------------------------------------------
/spring/src/AnimatedArray.ts:
--------------------------------------------------------------------------------
1 | import { AnimatedValue } from "./animated"
2 | import { AnimatedObject } from "./AnimatedObject"
3 | import { AnimatedString } from "./AnimatedString"
4 | import { isAnimatedString } from "./utils"
5 |
6 | type Value = number | string
7 | type Source = AnimatedValue[]
8 |
9 | /** An array of animated nodes */
10 | export class AnimatedArray<
11 | T extends ReadonlyArray = Value[]
12 | > extends AnimatedObject {
13 | protected declare source: Source
14 | constructor(source: T) {
15 | super(source)
16 | }
17 |
18 | /** @internal */
19 | static create>(source: T) {
20 | return new AnimatedArray(source)
21 | }
22 |
23 | getValue(): T {
24 | return this.source.map(node => node.getValue()) as any
25 | }
26 |
27 | setValue(source: T) {
28 | const payload = this.getPayload()
29 | // Reuse the payload when lengths are equal.
30 | if (source.length == payload.length) {
31 | return payload.map((node, i) => node.setValue(source[i])).some(Boolean)
32 | }
33 | // Remake the payload when length changes.
34 | super.setValue(source.map(makeAnimated))
35 | return true
36 | }
37 | }
38 |
39 | function makeAnimated(value: any) {
40 | const nodeType = isAnimatedString(value) ? AnimatedString : AnimatedValue
41 | return nodeType.create(value)
42 | }
43 |
--------------------------------------------------------------------------------
/spring/src/AnimatedObject.ts:
--------------------------------------------------------------------------------
1 | import { Animated, AnimatedValue, getPayload, isAnimated } from "./animated"
2 | import { TreeContext } from "./context"
3 | import { getFluidValue, hasFluidValue } from "./fluids"
4 | import { Lookup, eachProp, each } from "./utils"
5 |
6 |
7 | /** An object containing `Animated` nodes */
8 | export class AnimatedObject extends Animated {
9 | constructor(protected source: Lookup) {
10 | super()
11 | this.setValue(source)
12 | }
13 |
14 | getValue(animated?: boolean) {
15 | const values: Lookup = {}
16 | eachProp(this.source, (source, key) => {
17 | if (isAnimated(source)) {
18 | values[key] = source.getValue(animated)
19 | } else if (hasFluidValue(source)) {
20 | values[key] = getFluidValue(source)
21 | } else if (!animated) {
22 | values[key] = source
23 | }
24 | })
25 | return values
26 | }
27 |
28 | /** Replace the raw object data */
29 | setValue(source: Lookup) {
30 | this.source = source
31 | this.payload = this._makePayload(source)
32 | }
33 |
34 | reset() {
35 | if (this.payload) {
36 | each(this.payload, node => node.reset())
37 | }
38 | }
39 |
40 | /** Create a payload set. */
41 | protected _makePayload(source: Lookup) {
42 | if (source) {
43 | const payload = new Set()
44 | eachProp(source, this._addToPayload, payload)
45 | return Array.from(payload)
46 | }
47 | }
48 |
49 | /** Add to a payload set. */
50 | protected _addToPayload(this: Set, source: any) {
51 | if (TreeContext.dependencies && hasFluidValue(source)) {
52 | TreeContext.dependencies.add(source)
53 | }
54 | const payload = getPayload(source)
55 | if (payload) {
56 | each(payload, node => this.add(node))
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/spring/src/AnimatedString.ts:
--------------------------------------------------------------------------------
1 | import { AnimatedValue } from "./animated"
2 | import { createInterpolator } from "./createInterpolator"
3 | import { is } from "./utils"
4 |
5 | type Value = string | number
6 |
7 | export class AnimatedString extends AnimatedValue {
8 | protected declare _value: number
9 | protected _string: string | null = null
10 | protected _toString: (input: number) => string
11 |
12 | constructor(value: string) {
13 | super(0)
14 | this._toString = createInterpolator({
15 | output: [value, value],
16 | })
17 | }
18 |
19 | /** @internal */
20 | static create(value: string) {
21 | return new AnimatedString(value)
22 | }
23 |
24 | getValue() {
25 | let value = this._string
26 | return value == null ? (this._string = this._toString(this._value)) : value
27 | }
28 |
29 | setValue(value: Value) {
30 | if (is.str(value)) {
31 | if (value == this._string) {
32 | return false
33 | }
34 | this._string = value
35 | this._value = 1
36 | } else if (super.setValue(value)) {
37 | this._string = null
38 | } else {
39 | return false
40 | }
41 | return true
42 | }
43 |
44 | reset(goal?: string) {
45 | if (goal) {
46 | this._toString = createInterpolator({
47 | output: [this.getValue(), goal],
48 | })
49 | }
50 | this._value = 0
51 | super.reset()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/spring/src/AnimatedStyle.ts:
--------------------------------------------------------------------------------
1 | import { AnimatedObject } from "./AnimatedObject"
2 | import { addFluidObserver, callFluidObservers, FluidEvent, FluidValue, getFluidValue, hasFluidValue, removeFluidObserver } from "./fluids"
3 | import { is,each, Lookup, OneOrMore, eachProp, toArray } from "./utils"
4 |
5 | /** The transform-functions
6 | * (https://developer.mozilla.org/fr/docs/Web/CSS/transform-function)
7 | * that you can pass as keys to your animated component style and that will be
8 | * animated. Perspective has been left out as it would conflict with the
9 | * non-transform perspective style.
10 | */
11 | const domTransforms = /^(matrix|translate|scale|rotate|skew)/
12 |
13 | // These keys have "px" units by default
14 | const pxTransforms = /^(translate)/
15 |
16 | // These keys have "deg" units by default
17 | const degTransforms = /^(rotate|skew)/
18 |
19 | type Value = number | string
20 |
21 | /** Add a unit to the value when the value is unit-less (eg: a number) */
22 | const addUnit = (value: Value, unit: string): string | 0 =>
23 | is.num(value) && value !== 0 ? value + unit : value
24 |
25 | /**
26 | * Checks if the input value matches the identity value.
27 | *
28 | * isValueIdentity(0, 0) // => true
29 | * isValueIdentity('0px', 0) // => true
30 | * isValueIdentity([0, '0px', 0], 0) // => true
31 | */
32 | const isValueIdentity = (value: OneOrMore, id: number): boolean =>
33 | is.arr(value)
34 | ? value.every(v => isValueIdentity(v, id))
35 | : is.num(value)
36 | ? value === id
37 | : parseFloat(value) === id
38 |
39 | type Inputs = ReadonlyArray>[]
40 | type Transforms = ((value: any) => [string, boolean])[]
41 |
42 | /**
43 | * This AnimatedStyle will simplify animated components transforms by
44 | * interpolating all transform function passed as keys in the style object
45 | * including shortcuts such as x, y and z for translateX/Y/Z
46 | */
47 | export class AnimatedStyle extends AnimatedObject {
48 | constructor({ x, y, z, ...style }: Lookup) {
49 | /**
50 | * An array of arrays that contains the values (static or fluid)
51 | * used by each transform function.
52 | */
53 | const inputs: Inputs = []
54 | /**
55 | * An array of functions that take a list of values (static or fluid)
56 | * and returns (1) a CSS transform string and (2) a boolean that's true
57 | * when the transform has no effect (eg: an identity transform).
58 | */
59 | const transforms: Transforms = []
60 |
61 | // Combine x/y/z into translate3d
62 | if (x || y || z) {
63 | inputs.push([x || 0, y || 0, z || 0])
64 | transforms.push((xyz: Value[]) => [
65 | `translate3d(${xyz.map(v => addUnit(v, 'px')).join(',')})`, // prettier-ignore
66 | isValueIdentity(xyz, 0),
67 | ])
68 | }
69 |
70 | // Pluck any other transform-related props
71 | eachProp(style, (value, key) => {
72 | if (key === 'transform') {
73 | inputs.push([value || ''])
74 | transforms.push((transform: string) => [transform, transform === ''])
75 | } else if (domTransforms.test(key)) {
76 | delete style[key]
77 | if (is.und(value)) return
78 |
79 | const unit = pxTransforms.test(key)
80 | ? 'px'
81 | : degTransforms.test(key)
82 | ? 'deg'
83 | : ''
84 |
85 | inputs.push(toArray(value))
86 | transforms.push(
87 | key === 'rotate3d'
88 | ? ([x, y, z, deg]: [number, number, number, Value]) => [
89 | `rotate3d(${x},${y},${z},${addUnit(deg, unit)})`,
90 | isValueIdentity(deg, 0),
91 | ]
92 | : (input: Value[]) => [
93 | `${key}(${input.map(v => addUnit(v, unit)).join(',')})`,
94 | isValueIdentity(input, key.startsWith('scale') ? 1 : 0),
95 | ]
96 | )
97 | }
98 | })
99 |
100 | if (inputs.length) {
101 | style.transform = new FluidTransform(inputs, transforms)
102 | }
103 |
104 | super(style)
105 | }
106 | }
107 |
108 | /** @internal */
109 | class FluidTransform extends FluidValue {
110 | protected _value: string | null = null
111 |
112 | constructor(readonly inputs: Inputs, readonly transforms: Transforms) {
113 | super()
114 | }
115 |
116 | get() {
117 | return this._value || (this._value = this._get())
118 | }
119 |
120 | protected _get() {
121 | let transform = ''
122 | let identity = true
123 | each(this.inputs, (input, i) => {
124 | const arg1 = getFluidValue(input[0])
125 | const [t, id] = this.transforms[i](
126 | is.arr(arg1) ? arg1 : input.map(getFluidValue)
127 | )
128 | transform += ' ' + t
129 | identity = identity && id
130 | })
131 | return identity ? 'none' : transform
132 | }
133 |
134 | // Start observing our inputs once we have an observer.
135 | protected observerAdded(count: number) {
136 | if (count == 1)
137 | each(this.inputs, input =>
138 | each(
139 | input,
140 | value => hasFluidValue(value) && addFluidObserver(value, this)
141 | )
142 | )
143 | }
144 |
145 | // Stop observing our inputs once we have no observers.
146 | protected observerRemoved(count: number) {
147 | if (count == 0)
148 | each(this.inputs, input =>
149 | each(
150 | input,
151 | value => hasFluidValue(value) && removeFluidObserver(value, this)
152 | )
153 | )
154 | }
155 |
156 | eventObserved(event: FluidEvent) {
157 | if (event.type == 'change') {
158 | this._value = null
159 | }
160 | callFluidObservers(this, event)
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/spring/src/Animation.ts:
--------------------------------------------------------------------------------
1 | import { AnimatedValue } from "./animated"
2 | import { FluidValue } from "./fluids"
3 | import { AnimationConfig, PickEventFns, SpringProps } from "./utils"
4 |
5 | const emptyArray: readonly any[] = []
6 |
7 | /** An animation being executed by the frameloop */
8 | export class Animation {
9 | changed = false
10 | values: readonly AnimatedValue[] = emptyArray
11 | toValues: readonly number[] | null = null
12 | fromValues: readonly number[] = emptyArray
13 |
14 | to!: T | FluidValue
15 | from!: T | FluidValue
16 | config = new AnimationConfig()
17 | immediate = false
18 | }
19 |
20 | export interface Animation extends PickEventFns> {}
21 |
--------------------------------------------------------------------------------
/spring/src/AnimationConfig.ts:
--------------------------------------------------------------------------------
1 | import { config, EasingFunction, easings, is } from "./utils"
2 |
3 | const defaults: any = {
4 | ...config.default,
5 | mass: 1,
6 | damping: 1,
7 | easing: easings.linear,
8 | clamp: false,
9 | }
10 |
11 | export class AnimationConfig {
12 | /**
13 | * With higher tension, the spring will resist bouncing and try harder to stop at its end value.
14 | *
15 | * When tension is zero, no animation occurs.
16 | */
17 | tension!: number
18 |
19 | /**
20 | * The damping ratio coefficient, or just the damping ratio when `speed` is defined.
21 | *
22 | * When `speed` is defined, this value should be between 0 and 1.
23 | *
24 | * Higher friction means the spring will slow down faster.
25 | */
26 | friction!: number
27 |
28 | /**
29 | * The natural frequency (in seconds), which dictates the number of bounces
30 | * per second when no damping exists.
31 | *
32 | * When defined, `tension` is derived from this, and `friction` is derived
33 | * from `tension` and `damping`.
34 | */
35 | frequency?: number
36 |
37 | /**
38 | * The damping ratio, which dictates how the spring slows down.
39 | *
40 | * Set to `0` to never slow down. Set to `1` to slow down without bouncing.
41 | * Between `0` and `1` is for you to explore.
42 | *
43 | * Only works when `frequency` is defined.
44 | *
45 | * Defaults to 1
46 | */
47 | damping!: number
48 |
49 | /**
50 | * Higher mass means more friction is required to slow down.
51 | *
52 | * Defaults to 1, which works fine most of the time.
53 | */
54 | mass!: number
55 |
56 | /**
57 | * The initial velocity of one or more values.
58 | */
59 | velocity: number | number[] = 0
60 |
61 | /**
62 | * The smallest velocity before the animation is considered "not moving".
63 | *
64 | * When undefined, `precision` is used instead.
65 | */
66 | restVelocity?: number
67 |
68 | /**
69 | * The smallest distance from a value before that distance is essentially zero.
70 | *
71 | * This helps in deciding when a spring is "at rest". The spring must be within
72 | * this distance from its final value, and its velocity must be lower than this
73 | * value too (unless `restVelocity` is defined).
74 | */
75 | precision?: number
76 |
77 | /**
78 | * For `duration` animations only. Note: The `duration` is not affected
79 | * by this property.
80 | *
81 | * Defaults to `0`, which means "start from the beginning".
82 | *
83 | * Setting to `1+` makes an immediate animation.
84 | *
85 | * Setting to `0.5` means "start from the middle of the easing function".
86 | *
87 | * Any number `>= 0` and `<= 1` makes sense here.
88 | */
89 | progress?: number
90 |
91 | /**
92 | * Animation length in number of milliseconds.
93 | */
94 | duration?: number
95 |
96 | /**
97 | * The animation curve. Only used when `duration` is defined.
98 | *
99 | * Defaults to quadratic ease-in-out.
100 | */
101 | easing!: EasingFunction
102 |
103 | /**
104 | * Avoid overshooting by ending abruptly at the goal value.
105 | */
106 | clamp!: boolean
107 |
108 | /**
109 | * When above zero, the spring will bounce instead of overshooting when
110 | * exceeding its goal value. Its velocity is multiplied by `-1 + bounce`
111 | * whenever its current value equals or exceeds its goal. For example,
112 | * setting `bounce` to `0.5` chops the velocity in half on each bounce,
113 | * in addition to any friction.
114 | */
115 | bounce?: number
116 |
117 | /**
118 | * "Decay animations" decelerate without an explicit goal value.
119 | * Useful for scrolling animations.
120 | *
121 | * Use `true` for the default exponential decay factor (`0.998`).
122 | *
123 | * When a `number` between `0` and `1` is given, a lower number makes the
124 | * animation slow down faster. And setting to `1` would make an unending
125 | * animation.
126 | */
127 | decay?: boolean | number
128 |
129 | /**
130 | * While animating, round to the nearest multiple of this number.
131 | * The `from` and `to` values are never rounded, as well as any value
132 | * passed to the `set` method of an animated value.
133 | */
134 | round?: number
135 |
136 | constructor() {
137 | Object.assign(this, defaults)
138 | }
139 | }
140 |
141 | export function mergeConfig(
142 | config: AnimationConfig,
143 | newConfig: Partial,
144 | defaultConfig?: Partial
145 | ): typeof config
146 |
147 | export function mergeConfig(
148 | config: any,
149 | newConfig: object,
150 | defaultConfig?: object
151 | ) {
152 | if (defaultConfig) {
153 | defaultConfig = { ...defaultConfig }
154 | sanitizeConfig(defaultConfig, newConfig)
155 | newConfig = { ...defaultConfig, ...newConfig }
156 | }
157 |
158 | sanitizeConfig(config, newConfig)
159 | Object.assign(config, newConfig)
160 |
161 | for (const key in defaults) {
162 | if (config[key] == null) {
163 | config[key] = defaults[key]
164 | }
165 | }
166 |
167 | let { mass, frequency, damping } = config
168 | if (!is.und(frequency)) {
169 | if (frequency < 0.01) frequency = 0.01
170 | if (damping < 0) damping = 0
171 | config.tension = Math.pow((2 * Math.PI) / frequency, 2) * mass
172 | config.friction = (4 * Math.PI * damping * mass) / frequency
173 | }
174 |
175 | return config
176 | }
177 |
178 | // Prevent a config from accidentally overriding new props.
179 | // This depends on which "config" props take precedence when defined.
180 | function sanitizeConfig(
181 | config: Partial,
182 | props: Partial
183 | ) {
184 | if (!is.und(props.decay)) {
185 | config.duration = undefined
186 | } else {
187 | const isTensionConfig = !is.und(props.tension) || !is.und(props.friction)
188 | if (
189 | isTensionConfig ||
190 | !is.und(props.frequency) ||
191 | !is.und(props.damping) ||
192 | !is.und(props.mass)
193 | ) {
194 | config.duration = undefined
195 | config.decay = undefined
196 | }
197 | if (isTensionConfig) {
198 | config.frequency = undefined
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/spring/src/AnimationResult.ts:
--------------------------------------------------------------------------------
1 | import { AnimationResult, Readable } from "./utils"
2 |
3 | /** @internal */
4 | export const getCombinedResult = (
5 | target: T,
6 | results: AnimationResult[]
7 | ): AnimationResult =>
8 | results.length == 1
9 | ? results[0]
10 | : results.some(result => result.cancelled)
11 | ? getCancelledResult(target.get())
12 | : results.every(result => result.noop)
13 | ? getNoopResult(target.get())
14 | : getFinishedResult(
15 | target.get(),
16 | results.every(result => result.finished)
17 | )
18 |
19 | /** No-op results are for updates that never start an animation. */
20 | export const getNoopResult = (value: any) => ({
21 | value,
22 | noop: true,
23 | finished: true,
24 | cancelled: false,
25 | })
26 |
27 | export const getFinishedResult = (
28 | value: any,
29 | finished: boolean,
30 | cancelled: boolean = false
31 | ) => ({
32 | value,
33 | finished,
34 | cancelled,
35 | })
36 |
37 | export const getCancelledResult = (value: any) => ({
38 | value,
39 | cancelled: true,
40 | finished: false,
41 | })
42 |
--------------------------------------------------------------------------------
/spring/src/Controller.ts:
--------------------------------------------------------------------------------
1 | import { AnimationResult, each, eachProp, ControllerUpdate, Lookup, OnChange, OnRest, OnStart, SpringValues, toArray, UnknownProps, OneOrMore, is, flushCalls, AsyncResult, getDefaultProp, flush, Falsy, noop, ControllerFlushFn } from "./utils"
2 | import { createLoopUpdate, createUpdate, SpringValue, } from './SpringValue'
3 | import { SpringRef } from "./SpringRef"
4 | import { FrameValue } from "./FrameValue"
5 | import { addFluidObserver, FluidObserver } from "./fluids"
6 | import { runAsync, RunAsyncState, stopAsync } from "./runAsync"
7 | import { scheduleProps } from "./scheduleProps"
8 | import { getCancelledResult, getCombinedResult } from "./AnimationResult"
9 | import { raf } from "./rafz"
10 |
11 | /** Events batched by the `Controller` class */
12 | const BATCHED_EVENTS = ['onStart', 'onChange', 'onRest'] as const
13 |
14 | let nextId = 1
15 |
16 | /** Queue of pending updates for a `Controller` instance. */
17 | export interface ControllerQueue
18 | extends Array<
19 | ControllerUpdate & {
20 | /** The keys affected by this update. When null, all keys are affected. */
21 | keys: string[] | null
22 | }
23 | > {}
24 |
25 | export class Controller {
26 | readonly id = nextId++
27 |
28 | /** The animated values */
29 | springs: SpringValues = {} as any
30 |
31 | /** The queue of props passed to the `update` method. */
32 | queue: ControllerQueue = []
33 |
34 | /**
35 | * The injected ref. When defined, render-based updates are pushed
36 | * onto the `queue` instead of being auto-started.
37 | */
38 | ref?: SpringRef
39 |
40 | /** Custom handler for flushing update queues */
41 | protected _flush?: ControllerFlushFn
42 |
43 | /** These props are used by all future spring values */
44 | protected _initialProps?: Lookup
45 |
46 | /** The counter for tracking `scheduleProps` calls */
47 | protected _lastAsyncId = 0
48 |
49 | /** The values currently being animated */
50 | protected _active = new Set()
51 |
52 | /** The values that changed recently */
53 | protected _changed = new Set()
54 |
55 | /** Equals false when `onStart` listeners can be called */
56 | protected _started = false
57 |
58 | private _item?: any
59 |
60 | /** State used by the `runAsync` function */
61 | protected _state: RunAsyncState = {
62 | paused: false,
63 | pauseQueue: new Set(),
64 | resumeQueue: new Set(),
65 | timeouts: new Set(),
66 | }
67 |
68 | /** The event queues that are flushed once per frame maximum */
69 | protected _events = {
70 | onStart: new Map<
71 | OnStart, Controller, any>,
72 | AnimationResult
73 | >(),
74 | onChange: new Map<
75 | OnChange, Controller, any>,
76 | AnimationResult
77 | >(),
78 | onRest: new Map<
79 | OnRest, Controller, any>,
80 | AnimationResult
81 | >(),
82 | }
83 |
84 | constructor(
85 | props?: ControllerUpdate | null,
86 | flush?: ControllerFlushFn
87 | ) {
88 | this._onFrame = this._onFrame.bind(this)
89 | if (flush) {
90 | this._flush = flush
91 | }
92 | if (props) {
93 | this.start({ default: true, ...props })
94 | }
95 | }
96 |
97 | /**
98 | * Equals `true` when no spring values are in the frameloop, and
99 | * no async animation is currently active.
100 | */
101 | get idle() {
102 | return (
103 | !this._state.asyncTo &&
104 | Object.values(this.springs as Lookup).every(spring => {
105 | return spring.idle && !spring.isDelayed && !spring.isPaused
106 | })
107 | )
108 | }
109 |
110 | get item() {
111 | return this._item
112 | }
113 |
114 | set item(item) {
115 | this._item = item
116 | }
117 |
118 | /** Get the current values of our springs */
119 | get(): State & UnknownProps {
120 | const values: any = {}
121 | this.each((spring, key) => (values[key] = spring.get()))
122 | return values
123 | }
124 |
125 | /** Set the current values without animating. */
126 | set(values: Partial) {
127 | for (const key in values) {
128 | const value = values[key]
129 | if (!is.und(value)) {
130 | this.springs[key].set(value)
131 | }
132 | }
133 | }
134 |
135 | /** Push an update onto the queue of each value. */
136 | update(props: ControllerUpdate | Falsy) {
137 | if (props) {
138 | this.queue.push(createUpdate(props))
139 | }
140 | return this
141 | }
142 |
143 | /**
144 | * Start the queued animations for every spring, and resolve the returned
145 | * promise once all queued animations have finished or been cancelled.
146 | *
147 | * When you pass a queue (instead of nothing), that queue is used instead of
148 | * the queued animations added with the `update` method, which are left alone.
149 | */
150 | start(props?: OneOrMore> | null): AsyncResult {
151 | let { queue } = this as any
152 | if (props) {
153 | queue = toArray(props).map(createUpdate)
154 | } else {
155 | this.queue = []
156 | }
157 |
158 | if (this._flush) {
159 | return this._flush(this, queue)
160 | }
161 |
162 | prepareKeys(this, queue)
163 | return flushUpdateQueue(this, queue)
164 | }
165 |
166 | /** Stop all animations. */
167 | stop(): this
168 | /** Stop animations for the given keys. */
169 | stop(keys: OneOrMore): this
170 | /** Cancel all animations. */
171 | stop(cancel: boolean): this
172 | /** Cancel animations for the given keys. */
173 | stop(cancel: boolean, keys: OneOrMore): this
174 | /** Stop some or all animations. */
175 | stop(keys?: OneOrMore): this
176 | /** Cancel some or all animations. */
177 | stop(cancel: boolean, keys?: OneOrMore): this
178 | /** @internal */
179 | stop(arg?: boolean | OneOrMore, keys?: OneOrMore) {
180 | if (arg !== !!arg) {
181 | keys = arg as OneOrMore
182 | }
183 | if (keys) {
184 | const springs = this.springs as Lookup
185 | each(toArray(keys) as string[], key => springs[key].stop(!!arg))
186 | } else {
187 | stopAsync(this._state, this._lastAsyncId)
188 | this.each(spring => spring.stop(!!arg))
189 | }
190 | return this
191 | }
192 |
193 | /** Freeze the active animation in time */
194 | pause(keys?: OneOrMore) {
195 | if (is.und(keys)) {
196 | this.start({ pause: true })
197 | } else {
198 | const springs = this.springs as Lookup
199 | each(toArray(keys) as string[], key => springs[key].pause())
200 | }
201 | return this
202 | }
203 |
204 | /** Resume the animation if paused. */
205 | resume(keys?: OneOrMore) {
206 | if (is.und(keys)) {
207 | this.start({ pause: false })
208 | } else {
209 | const springs = this.springs as Lookup
210 | each(toArray(keys) as string[], key => springs[key].resume())
211 | }
212 | return this
213 | }
214 |
215 | /** Call a function once per spring value */
216 | each(iterator: (spring: SpringValue, key: string) => void) {
217 | eachProp(this.springs, iterator as any)
218 | }
219 |
220 | /** @internal Called at the end of every animation frame */
221 | protected _onFrame() {
222 | const { onStart, onChange, onRest } = this._events
223 |
224 | const active = this._active.size > 0
225 | const changed = this._changed.size > 0
226 |
227 | if ((active && !this._started) || (changed && !this._started)) {
228 | this._started = true
229 | flush(onStart, ([onStart, result]) => {
230 | result.value = this.get()
231 | onStart(result, this, this._item)
232 | })
233 | }
234 |
235 | const idle = !active && this._started
236 | const values = changed || (idle && onRest.size) ? this.get() : null
237 |
238 | if (changed && onChange.size) {
239 | flush(onChange, ([onChange, result]) => {
240 | result.value = values
241 | onChange(result, this, this._item)
242 | })
243 | }
244 |
245 | // The "onRest" queue is only flushed when all springs are idle.
246 | if (idle) {
247 | this._started = false
248 | flush(onRest, ([onRest, result]) => {
249 | result.value = values
250 | onRest(result, this, this._item)
251 | })
252 | }
253 | }
254 |
255 | /** @internal */
256 | eventObserved(event: FrameValue.Event) {
257 | if (event.type == 'change') {
258 | this._changed.add(event.parent)
259 | if (!event.idle) {
260 | this._active.add(event.parent)
261 | }
262 | } else if (event.type == 'idle') {
263 | this._active.delete(event.parent)
264 | }
265 | // The `onFrame` handler runs when a parent is changed or idle.
266 | else return
267 | raf.onFrame(this._onFrame)
268 | }
269 | }
270 |
271 | /**
272 | * Warning: Props might be mutated.
273 | */
274 | export function flushUpdateQueue(
275 | ctrl: Controller,
276 | queue: ControllerQueue
277 | ) {
278 | return Promise.all(queue.map(props => flushUpdate(ctrl, props))).then(
279 | results => getCombinedResult(ctrl, results)
280 | )
281 | }
282 |
283 | /**
284 | * Warning: Props might be mutated.
285 | *
286 | * Process a single set of props using the given controller.
287 | *
288 | * The returned promise resolves to `true` once the update is
289 | * applied and any animations it starts are finished without being
290 | * stopped or cancelled.
291 | */
292 | export async function flushUpdate(
293 | ctrl: Controller,
294 | props: ControllerQueue[number],
295 | isLoop?: boolean
296 | ): AsyncResult {
297 | const { keys, to, from, loop, onRest, onResolve } = props
298 | const defaults = is.obj(props.default) && props.default
299 |
300 | // Looping must be handled in this function, or else the values
301 | // would end up looping out-of-sync in many common cases.
302 | if (loop) {
303 | props.loop = false
304 | }
305 |
306 | // Treat false like null, which gets ignored.
307 | if (to === false) props.to = null
308 | if (from === false) props.from = null
309 |
310 | const asyncTo = is.arr(to) || is.fun(to) ? to : undefined
311 | if (asyncTo) {
312 | props.to = undefined
313 | props.onRest = undefined
314 | if (defaults) {
315 | defaults.onRest = undefined
316 | }
317 | }
318 | // For certain events, use batching to prevent multiple calls per frame.
319 | // However, batching is avoided when the `to` prop is async, because any
320 | // event props are used as default props instead.
321 | else {
322 | each(BATCHED_EVENTS, key => {
323 | const handler: any = props[key]
324 | if (is.fun(handler)) {
325 | const queue = ctrl['_events'][key]
326 | props[key] = (({ finished, cancelled }: AnimationResult) => {
327 | const result = queue.get(handler)
328 | if (result) {
329 | if (!finished) result.finished = false
330 | if (cancelled) result.cancelled = true
331 | } else {
332 | // The "value" is set before the "handler" is called.
333 | queue.set(handler, {
334 | value: null,
335 | finished: finished || false,
336 | cancelled: cancelled || false,
337 | })
338 | }
339 | }) as any
340 |
341 | // Avoid using a batched `handler` as a default prop.
342 | if (defaults) {
343 | defaults[key] = props[key] as any
344 | }
345 | }
346 | })
347 | }
348 |
349 | const state = ctrl['_state']
350 |
351 | // Pause/resume the `asyncTo` when `props.pause` is true/false.
352 | if (props.pause === !state.paused) {
353 | state.paused = props.pause
354 | flushCalls(props.pause ? state.pauseQueue : state.resumeQueue)
355 | }
356 | // When a controller is paused, its values are also paused.
357 | else if (state.paused) {
358 | props.pause = true
359 | }
360 |
361 | const promises: AsyncResult[] = (keys || Object.keys(ctrl.springs)).map(key =>
362 | ctrl.springs[key]!.start(props as any)
363 | )
364 |
365 | const cancel =
366 | props.cancel === true || getDefaultProp(props, 'cancel') === true
367 |
368 | if (asyncTo || (cancel && state.asyncId)) {
369 | promises.push(
370 | scheduleProps(++ctrl['_lastAsyncId'], {
371 | props,
372 | state,
373 | actions: {
374 | pause: noop,
375 | resume: noop,
376 | start(props, resolve) {
377 | if (cancel) {
378 | stopAsync(state, ctrl['_lastAsyncId'])
379 | resolve(getCancelledResult(ctrl))
380 | } else {
381 | props.onRest = onRest
382 | resolve(runAsync(asyncTo!, props, state, ctrl))
383 | }
384 | },
385 | },
386 | })
387 | )
388 | }
389 |
390 | // Pause after updating each spring, so they can be resumed separately
391 | // and so their default `pause` and `cancel` props are updated.
392 | if (state.paused) {
393 | // Ensure `this` must be resumed before the returned promise
394 | // is resolved and before starting the next `loop` repetition.
395 | await new Promise(resume => {
396 | state.resumeQueue.add(resume)
397 | })
398 | }
399 |
400 | const result = getCombinedResult(ctrl, await Promise.all(promises))
401 | if (loop && result.finished && !(isLoop && result.noop)) {
402 | const nextProps = createLoopUpdate(props, loop, to)
403 | if (nextProps) {
404 | prepareKeys(ctrl, [nextProps])
405 | return flushUpdate(ctrl, nextProps, true)
406 | }
407 | }
408 | if (onResolve) {
409 | raf.batchedUpdates(() => onResolve(result, ctrl, ctrl.item))
410 | }
411 | return result
412 | }
413 |
414 | /**
415 | * From an array of updates, get the map of `SpringValue` objects
416 | * by their keys. Springs are created when any update wants to
417 | * animate a new key.
418 | *
419 | * Springs created by `getSprings` are neither cached nor observed
420 | * until they're given to `setSprings`.
421 | */
422 | export function getSprings(
423 | ctrl: Controller>,
424 | props?: OneOrMore>
425 | ) {
426 | const springs = { ...ctrl.springs }
427 | if (props) {
428 | each(toArray(props), (props: any) => {
429 | if (is.und(props.keys)) {
430 | props = createUpdate(props)
431 | }
432 | if (!is.obj(props.to)) {
433 | // Avoid passing array/function to each spring.
434 | props = { ...props, to: undefined }
435 | }
436 | prepareSprings(springs as any, props, key => {
437 | return createSpring(key)
438 | })
439 | })
440 | }
441 | setSprings(ctrl, springs)
442 | return springs
443 | }
444 |
445 | /**
446 | * Tell a controller to manage the given `SpringValue` objects
447 | * whose key is not already in use.
448 | */
449 | export function setSprings(
450 | ctrl: Controller>,
451 | springs: SpringValues
452 | ) {
453 | eachProp(springs, (spring, key) => {
454 | if (!ctrl.springs[key]) {
455 | ctrl.springs[key] = spring
456 | addFluidObserver(spring, ctrl)
457 | }
458 | })
459 | }
460 |
461 | function createSpring(key: string, observer?: FluidObserver) {
462 | const spring = new SpringValue()
463 | spring.key = key
464 | if (observer) {
465 | addFluidObserver(spring, observer)
466 | }
467 | return spring
468 | }
469 |
470 | /**
471 | * Ensure spring objects exist for each defined key.
472 | *
473 | * Using the `props`, the `Animated` node of each `SpringValue` may
474 | * be created or updated.
475 | */
476 | function prepareSprings(
477 | springs: SpringValues,
478 | props: ControllerQueue[number],
479 | create: (key: string) => SpringValue
480 | ) {
481 | if (props.keys) {
482 | each(props.keys, key => {
483 | const spring = springs[key] || (springs[key] = create(key))
484 | spring['_prepareNode'](props)
485 | })
486 | }
487 | }
488 |
489 | /**
490 | * Ensure spring objects exist for each defined key, and attach the
491 | * `ctrl` to them for observation.
492 | *
493 | * The queue is expected to contain `createUpdate` results.
494 | */
495 | function prepareKeys(ctrl: Controller, queue: ControllerQueue[number][]) {
496 | each(queue, props => {
497 | prepareSprings(ctrl.springs, props, key => {
498 | return createSpring(key, ctrl)
499 | })
500 | })
501 | }
502 |
--------------------------------------------------------------------------------
/spring/src/FrameLoop.ts:
--------------------------------------------------------------------------------
1 | import * as G from './globals'
2 | import { raf } from './rafz'
3 |
4 | export interface OpaqueAnimation {
5 | idle: boolean
6 | priority: number
7 | advance(dt: number): void
8 | }
9 |
10 | // Animations starting on the next frame
11 | const startQueue = new Set()
12 |
13 | // The animations being updated in the current frame, sorted by lowest
14 | // priority first. These two arrays are swapped at the end of each frame.
15 | let currentFrame: OpaqueAnimation[] = []
16 | let prevFrame: OpaqueAnimation[] = []
17 |
18 | // The priority of the currently advancing animation.
19 | // To protect against a race condition whenever a frame is being processed,
20 | // where the filtering of `animations` is corrupted with a shifting index,
21 | // causing animations to potentially advance 2x faster than intended.
22 | let priority = 0
23 |
24 | /**
25 | * The frameloop executes its animations in order of lowest priority first.
26 | * Animations are retained until idle.
27 | */
28 | export const frameLoop = {
29 | get idle() {
30 | return !startQueue.size && !currentFrame.length
31 | },
32 |
33 | /** Advance the given animation on every frame until idle. */
34 | start(animation: OpaqueAnimation) {
35 | // An animation can be added while a frame is being processed,
36 | // unless its priority is lower than the animation last updated.
37 | if (priority > animation.priority) {
38 | startQueue.add(animation)
39 | raf.onStart(flushStartQueue)
40 | } else {
41 | startSafely(animation)
42 | raf(advance)
43 | }
44 | },
45 |
46 | /** Advance all animations by the given time. */
47 | advance,
48 |
49 | /** Call this when an animation's priority changes. */
50 | sort(animation: OpaqueAnimation) {
51 | if (priority) {
52 | raf.onFrame(() => frameLoop.sort(animation))
53 | } else {
54 | const prevIndex = currentFrame.indexOf(animation)
55 | if (~prevIndex) {
56 | currentFrame.splice(prevIndex, 1)
57 | startUnsafely(animation)
58 | }
59 | }
60 | },
61 |
62 | /**
63 | * Clear all animations. For testing purposes.
64 | *
65 | * ☠️ Never call this from within the frameloop.
66 | */
67 | clear() {
68 | currentFrame = []
69 | startQueue.clear()
70 | },
71 | }
72 |
73 | function flushStartQueue() {
74 | startQueue.forEach(startSafely)
75 | startQueue.clear()
76 | raf(advance)
77 | }
78 |
79 | function startSafely(animation: OpaqueAnimation) {
80 | if (!currentFrame.includes(animation)) startUnsafely(animation)
81 | }
82 |
83 | function startUnsafely(animation: OpaqueAnimation) {
84 | currentFrame.splice(
85 | findIndex(currentFrame, other => other.priority > animation.priority),
86 | 0,
87 | animation
88 | )
89 | }
90 |
91 | function advance(dt: number) {
92 | const nextFrame = prevFrame
93 |
94 | for (let i = 0; i < currentFrame.length; i++) {
95 | const animation = currentFrame[i]
96 | priority = animation.priority
97 |
98 | // Animations may go idle before advancing.
99 | if (!animation.idle) {
100 | G.willAdvance(animation)
101 | animation.advance(dt)
102 | if (!animation.idle) {
103 | nextFrame.push(animation)
104 | }
105 | }
106 | }
107 | priority = 0
108 |
109 | // Reuse the `currentFrame` array to avoid garbage collection.
110 | prevFrame = currentFrame
111 | prevFrame.length = 0
112 |
113 | // Set `currentFrame` for next frame, so the `start` function
114 | // adds new animations to the proper array.
115 | currentFrame = nextFrame
116 |
117 | return currentFrame.length > 0
118 | }
119 |
120 | /** Like `Array.prototype.findIndex` but returns `arr.length` instead of `-1` */
121 | function findIndex(arr: T[], test: (value: T) => boolean) {
122 | const index = arr.findIndex(test)
123 | return index < 0 ? arr.length : index
124 | }
125 |
--------------------------------------------------------------------------------
/spring/src/FrameValue.ts:
--------------------------------------------------------------------------------
1 | import * as G from "./globals";
2 | import { getAnimated } from "./animated";
3 | import { FluidValue, callFluidObservers } from "./fluids";
4 | import { Interpolation, InterpolatorArgs } from "./Interpolation";
5 | import { frameLoop } from "./FrameLoop";
6 |
7 | export const isFrameValue = (value: any): value is FrameValue =>
8 | value instanceof FrameValue;
9 |
10 | let nextId = 1;
11 |
12 | /**
13 | * A kind of `FluidValue` that manages an `AnimatedValue` node.
14 | *
15 | * Its underlying value can be accessed and even observed.
16 | */
17 | export abstract class FrameValue extends FluidValue<
18 | T,
19 | FrameValue.Event
20 | > {
21 | readonly id = nextId++;
22 |
23 | abstract key?: string;
24 | abstract get idle(): boolean;
25 |
26 | protected _priority = 0;
27 |
28 | get priority() {
29 | return this._priority;
30 | }
31 | set priority(priority: number) {
32 | if (this._priority != priority) {
33 | this._priority = priority;
34 | this._onPriorityChange(priority);
35 | }
36 | }
37 |
38 | /** Get the current value */
39 | get(): T {
40 | const node = getAnimated(this);
41 | return node && node.getValue();
42 | }
43 |
44 | /** Create a spring that maps our value to another value */
45 | to(...args: InterpolatorArgs) {
46 | return G.to(this, args) as Interpolation;
47 | }
48 |
49 | /** @deprecated Use the `to` method instead. */
50 | interpolate(...args: InterpolatorArgs) {
51 | return G.to(this, args) as Interpolation;
52 | }
53 |
54 | toJSON() {
55 | return this.get();
56 | }
57 |
58 | protected observerAdded(count: number) {
59 | if (count == 1) this._attach();
60 | }
61 |
62 | protected observerRemoved(count: number) {
63 | if (count == 0) this._detach();
64 | }
65 |
66 | /** @internal */
67 | abstract advance(dt: number): void;
68 |
69 | /** @internal */
70 | abstract eventObserved(_event: FrameValue.Event): void;
71 |
72 | /** Called when the first child is added. */
73 | protected _attach() {}
74 |
75 | /** Called when the last child is removed. */
76 | protected _detach() {}
77 |
78 | /** Tell our children about our new value */
79 | protected _onChange(value: T, idle = false) {
80 | callFluidObservers(this, {
81 | type: "change",
82 | parent: this,
83 | value,
84 | idle,
85 | });
86 | }
87 |
88 | /** Tell our children about our new priority */
89 | protected _onPriorityChange(priority: number) {
90 | if (!this.idle) {
91 | frameLoop.sort(this);
92 | }
93 | callFluidObservers(this, {
94 | type: "priority",
95 | parent: this,
96 | priority,
97 | });
98 | }
99 | }
100 |
101 | export declare namespace FrameValue {
102 | /** A parent changed its value */
103 | interface ChangeEvent {
104 | parent: FrameValue;
105 | type: "change";
106 | value: T;
107 | idle: boolean;
108 | }
109 |
110 | /** A parent changed its priority */
111 | interface PriorityEvent {
112 | parent: FrameValue;
113 | type: "priority";
114 | priority: number;
115 | }
116 |
117 | /** A parent is done animating */
118 | interface IdleEvent {
119 | parent: FrameValue;
120 | type: "idle";
121 | }
122 |
123 | /** Events sent to children of `FrameValue` objects */
124 | export type Event = ChangeEvent | PriorityEvent | IdleEvent;
125 | }
126 |
--------------------------------------------------------------------------------
/spring/src/Interpolation.ts:
--------------------------------------------------------------------------------
1 | import * as G from './globals'
2 | import { getAnimated, getPayload, setAnimated } from "./animated"
3 | import { createInterpolator } from "./createInterpolator"
4 | import { addFluidObserver, callFluidObservers, FluidValue, getFluidValue, hasFluidValue, removeFluidObserver } from "./fluids"
5 | import { frameLoop } from "./FrameLoop"
6 | import { FrameValue, isFrameValue } from "./FrameValue"
7 | import { raf } from "./rafz"
8 | import { Any, toArray, each, getAnimatedType, isEqual, is } from "./utils"
9 |
10 | export type Animatable = T extends number
11 | ? number
12 | : T extends string
13 | ? string
14 | : T extends ReadonlyArray
15 | ? Array extends T // When true, T is not a tuple
16 | ? ReadonlyArray
17 | : { [P in keyof T]: Animatable }
18 | : never
19 |
20 | /** Ensure each type of `T` is an array */
21 | export type Arrify = [T, T] extends [infer T, infer DT]
22 | ? DT extends ReadonlyArray
23 | ? Array
extends DT
24 | ? ReadonlyArray ? U : T>
25 | : DT
26 | : ReadonlyArray ? U : T>
27 | : never
28 |
29 | export type ExtrapolateType = 'identity' | 'clamp' | 'extend'
30 |
31 | /** Better type errors for overloads with generic types */
32 | export type Constrain = [T] extends [Any] ? U : [T] extends [U] ? T : U
33 |
34 | export type EasingFunction = (t: number) => number
35 |
36 | export type InterpolatorConfig = {
37 | /**
38 | * What happens when the spring goes below its target value.
39 | *
40 | * - `extend` continues the interpolation past the target value
41 | * - `clamp` limits the interpolation at the max value
42 | * - `identity` sets the value to the interpolation input as soon as it hits the boundary
43 | *
44 | * @default 'extend'
45 | */
46 | extrapolateLeft?: ExtrapolateType
47 |
48 | /**
49 | * What happens when the spring exceeds its target value.
50 | *
51 | * - `extend` continues the interpolation past the target value
52 | * - `clamp` limits the interpolation at the max value
53 | * - `identity` sets the value to the interpolation input as soon as it hits the boundary
54 | *
55 | * @default 'extend'
56 | */
57 | extrapolateRight?: ExtrapolateType
58 |
59 | /**
60 | * What happens when the spring exceeds its target value.
61 | * Shortcut to set `extrapolateLeft` and `extrapolateRight`.
62 | *
63 | * - `extend` continues the interpolation past the target value
64 | * - `clamp` limits the interpolation at the max value
65 | * - `identity` sets the value to the interpolation input as soon as it hits the boundary
66 | *
67 | * @default 'extend'
68 | */
69 | extrapolate?: ExtrapolateType
70 |
71 | /**
72 | * Input ranges mapping the interpolation to the output values.
73 | *
74 | * @example
75 | *
76 | * range: [0, 0.5, 1], output: ['yellow', 'orange', 'red']
77 | *
78 | * @default [0,1]
79 | */
80 | range?: readonly number[]
81 |
82 | /**
83 | * Output values from the interpolation function. Should match the length of the `range` array.
84 | */
85 | output: readonly Constrain[]
86 |
87 | /**
88 | * Transformation to apply to the value before interpolation.
89 | */
90 | map?: (value: number) => number
91 |
92 | /**
93 | * Custom easing to apply in interpolator.
94 | */
95 | easing?: EasingFunction
96 | }
97 |
98 | export type InterpolatorArgs =
99 | | [InterpolatorFn, Out>]
100 | | [InterpolatorConfig]
101 | | [
102 | readonly number[],
103 | readonly Constrain[],
104 | (ExtrapolateType | undefined)?
105 | ]
106 |
107 | export type InterpolatorFn = (...inputs: Arrify) => Out
108 | /**
109 | * An `Interpolation` is a memoized value that's computed whenever one of its
110 | * `FluidValue` dependencies has its value changed.
111 | *
112 | * Other `FrameValue` objects can depend on this. For example, passing an
113 | * `Interpolation` as the `to` prop of a `useSpring` call will trigger an
114 | * animation toward the memoized value.
115 | */
116 | export class Interpolation extends FrameValue {
117 | /** Useful for debugging. */
118 | key?: string
119 |
120 | /** Equals false when in the frameloop */
121 | idle = true
122 |
123 | /** The function that maps inputs values to output */
124 | readonly calc: InterpolatorFn
125 |
126 | /** The inputs which are currently animating */
127 | protected _active = new Set()
128 |
129 | constructor(
130 | /** The source of input values */
131 | readonly source: unknown,
132 | args: InterpolatorArgs
133 | ) {
134 | super()
135 | this.calc = createInterpolator(...args)
136 |
137 | const value = this._get()
138 | const nodeType = getAnimatedType(value)
139 |
140 | // Assume the computed value never changes type.
141 | setAnimated(this, nodeType.create(value))
142 | }
143 |
144 | advance(_dt?: number) {
145 | const value = this._get()
146 | const oldValue = this.get()
147 | if (!isEqual(value, oldValue)) {
148 | getAnimated(this)!.setValue(value)
149 | this._onChange(value, this.idle)
150 | }
151 | // Become idle when all parents are idle or paused.
152 | if (!this.idle && checkIdle(this._active)) {
153 | becomeIdle(this)
154 | }
155 | }
156 |
157 | protected _get() {
158 | const inputs: Arrify = is.arr(this.source)
159 | ? this.source.map(getFluidValue)
160 | : (toArray(getFluidValue(this.source)) as any)
161 |
162 | return this.calc(...inputs)
163 | }
164 |
165 | protected _start() {
166 | if (this.idle && !checkIdle(this._active)) {
167 | this.idle = false
168 |
169 | each(getPayload(this)!, node => {
170 | node.done = false
171 | })
172 |
173 | if (G.skipAnimation) {
174 | raf.batchedUpdates(() => this.advance())
175 | becomeIdle(this)
176 | } else {
177 | frameLoop.start(this)
178 | }
179 | }
180 | }
181 |
182 | // Observe our sources only when we're observed.
183 | protected _attach() {
184 | let priority = 1
185 | each(toArray(this.source), source => {
186 | if (hasFluidValue(source)) {
187 | addFluidObserver(source, this)
188 | }
189 | if (isFrameValue(source)) {
190 | if (!source.idle) {
191 | this._active.add(source)
192 | }
193 | priority = Math.max(priority, source.priority + 1)
194 | }
195 | })
196 | this.priority = priority
197 | this._start()
198 | }
199 |
200 | // Stop observing our sources once we have no observers.
201 | protected _detach() {
202 | each(toArray(this.source), source => {
203 | if (hasFluidValue(source)) {
204 | removeFluidObserver(source, this)
205 | }
206 | })
207 | this._active.clear()
208 | becomeIdle(this)
209 | }
210 |
211 | /** @internal */
212 | eventObserved(event: FrameValue.Event) {
213 | // Update our value when an idle parent is changed,
214 | // and enter the frameloop when a parent is resumed.
215 | if (event.type == 'change') {
216 | if (event.idle) {
217 | this.advance()
218 | } else {
219 | this._active.add(event.parent)
220 | this._start()
221 | }
222 | }
223 | // Once all parents are idle, the `advance` method runs one more time,
224 | // so we should avoid updating the `idle` status here.
225 | else if (event.type == 'idle') {
226 | this._active.delete(event.parent)
227 | }
228 | // Ensure our priority is greater than all parents, which means
229 | // our value won't be updated until our parents have updated.
230 | else if (event.type == 'priority') {
231 | this.priority = toArray(this.source).reduce(
232 | (highest: number, parent) =>
233 | Math.max(highest, (isFrameValue(parent) ? parent.priority : 0) + 1),
234 | 0
235 | )
236 | }
237 | }
238 | }
239 |
240 | export interface InterpolatorFactory {
241 | (interpolator: InterpolatorFn): typeof interpolator;
242 |
243 | (config: InterpolatorConfig): (input: number) => Animatable
244 |
245 | (
246 | range: readonly number[],
247 | output: readonly Constrain[],
248 | extrapolate?: ExtrapolateType
249 | ): (input: number) => Animatable
250 |
251 | (...args: InterpolatorArgs): InterpolatorFn
252 | }
253 |
254 |
255 | /** Returns true for an idle source. */
256 | function isIdle(source: any) {
257 | return source.idle !== false
258 | }
259 |
260 | /** Return true if all values in the given set are idle or paused. */
261 | function checkIdle(active: Set) {
262 | // Parents can be active even when paused, so the `.every` check
263 | // removes us from the frameloop if all active parents are paused.
264 | return !active.size || Array.from(active).every(isIdle)
265 | }
266 |
267 | /** Become idle if not already idle. */
268 | function becomeIdle(self: Interpolation) {
269 | if (!self.idle) {
270 | self.idle = true
271 |
272 | each(getPayload(self)!, node => {
273 | node.done = true
274 | })
275 |
276 | callFluidObservers(self, {
277 | type: 'idle',
278 | parent: self,
279 | })
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/spring/src/SpringPhase.ts:
--------------------------------------------------------------------------------
1 | /** The property symbol of the current animation phase. */
2 | const $P = Symbol.for('SpringPhase')
3 |
4 | const HAS_ANIMATED = 1
5 | const IS_ANIMATING = 2
6 | const IS_PAUSED = 4
7 |
8 | /** Returns true if the `target` has ever animated. */
9 | export const hasAnimated = (target: any) => (target[$P] & HAS_ANIMATED) > 0
10 |
11 | /** Returns true if the `target` is animating (even if paused). */
12 | export const isAnimating = (target: any) => (target[$P] & IS_ANIMATING) > 0
13 |
14 | /** Returns true if the `target` is paused (even if idle). */
15 | export const isPaused = (target: any) => (target[$P] & IS_PAUSED) > 0
16 |
17 | /** Set the active bit of the `target` phase. */
18 | export const setActiveBit = (target: any, active: boolean) =>
19 | active
20 | ? (target[$P] |= IS_ANIMATING | HAS_ANIMATED)
21 | : (target[$P] &= ~IS_ANIMATING)
22 |
23 | export const setPausedBit = (target: any, paused: boolean) =>
24 | paused ? (target[$P] |= IS_PAUSED) : (target[$P] &= ~IS_PAUSED)
25 |
--------------------------------------------------------------------------------
/spring/src/SpringRef.ts:
--------------------------------------------------------------------------------
1 | import { Controller } from "./Controller"
2 | import { AsyncResult, ControllerUpdate, Falsy, is, Lookup, OneOrMore, each } from "./utils"
3 |
4 | export interface ControllerUpdateFn {
5 | (i: number, ctrl: Controller): ControllerUpdate | Falsy
6 | }
7 |
8 | export interface SpringRef {
9 | (props?: ControllerUpdate | ControllerUpdateFn): AsyncResult<
10 | Controller
11 | >[]
12 | current: Controller[]
13 |
14 | /** Add a controller to this ref */
15 | add(ctrl: Controller): void
16 |
17 | /** Remove a controller from this ref */
18 | delete(ctrl: Controller): void
19 |
20 | /** Pause all animations. */
21 | pause(): this
22 | /** Pause animations for the given keys. */
23 | pause(keys: OneOrMore): this
24 | /** Pause some or all animations. */
25 | pause(keys?: OneOrMore): this
26 |
27 | /** Resume all animations. */
28 | resume(): this
29 | /** Resume animations for the given keys. */
30 | resume(keys: OneOrMore): this
31 | /** Resume some or all animations. */
32 | resume(keys?: OneOrMore): this
33 |
34 | /** Update the state of each controller without animating. */
35 | set(values: Partial): void
36 |
37 | /** Start the queued animations of each controller. */
38 | start(): AsyncResult>[]
39 | /** Update every controller with the same props. */
40 | start(props: ControllerUpdate): AsyncResult>[]
41 | /** Update controllers based on their state. */
42 | start(props: ControllerUpdateFn): AsyncResult>[]
43 | /** Start animating each controller. */
44 | start(
45 | props?: ControllerUpdate | ControllerUpdateFn
46 | ): AsyncResult>[]
47 |
48 | /** Stop all animations. */
49 | stop(): this
50 | /** Stop animations for the given keys. */
51 | stop(keys: OneOrMore): this
52 | /** Cancel all animations. */
53 | stop(cancel: boolean): this
54 | /** Cancel animations for the given keys. */
55 | stop(cancel: boolean, keys: OneOrMore): this
56 | /** Stop some or all animations. */
57 | stop(keys?: OneOrMore): this
58 | /** Cancel some or all animations. */
59 | stop(cancel: boolean, keys?: OneOrMore): this
60 |
61 | /** Add the same props to each controller's update queue. */
62 | update(props: ControllerUpdate): this
63 | /** Generate separate props for each controller's update queue. */
64 | update(props: ControllerUpdateFn): this
65 | /** Add props to each controller's update queue. */
66 | update(props: ControllerUpdate | ControllerUpdateFn): this
67 |
68 | _getProps(
69 | arg: ControllerUpdate | ControllerUpdateFn,
70 | ctrl: Controller,
71 | index: number
72 | ): ControllerUpdate | Falsy
73 | }
74 |
75 | export const SpringRef = <
76 | State extends Lookup = Lookup
77 | >(): SpringRef => {
78 | const current: Controller[] = []
79 |
80 | const SpringRef: SpringRef = function (props) {
81 | const results: AsyncResult[] = []
82 |
83 | each(current, (ctrl, i) => {
84 | if (is.und(props)) {
85 | results.push(ctrl.start())
86 | } else {
87 | const update = _getProps(props, ctrl, i)
88 | if (update) {
89 | results.push(ctrl.start(update))
90 | }
91 | }
92 | })
93 |
94 | return results
95 | }
96 |
97 | SpringRef.current = current
98 |
99 | /** Add a controller to this ref */
100 | SpringRef.add = function (ctrl: Controller) {
101 | if (!current.includes(ctrl)) {
102 | current.push(ctrl)
103 | }
104 | }
105 |
106 | /** Remove a controller from this ref */
107 | SpringRef.delete = function (ctrl: Controller) {
108 | const i = current.indexOf(ctrl)
109 | if (~i) current.splice(i, 1)
110 | }
111 |
112 | /** Pause all animations. */
113 | SpringRef.pause = function () {
114 | each(current, ctrl => ctrl.pause(...arguments))
115 | return this
116 | }
117 |
118 | /** Resume all animations. */
119 | SpringRef.resume = function () {
120 | each(current, ctrl => ctrl.resume(...arguments))
121 | return this
122 | }
123 |
124 | /** Update the state of each controller without animating. */
125 | SpringRef.set = function (values: Partial) {
126 | each(current, ctrl => ctrl.set(values))
127 | }
128 |
129 | SpringRef.start = function (props?: object | ControllerUpdateFn) {
130 | const results: AsyncResult[] = []
131 |
132 | each(current, (ctrl, i) => {
133 | if (is.und(props)) {
134 | results.push(ctrl.start())
135 | } else {
136 | const update = this._getProps(props, ctrl, i)
137 | if (update) {
138 | results.push(ctrl.start(update))
139 | }
140 | }
141 | })
142 |
143 | return results
144 | }
145 |
146 | /** Stop all animations. */
147 | SpringRef.stop = function () {
148 | each(current, ctrl => ctrl.stop(...arguments))
149 | return this
150 | }
151 |
152 | SpringRef.update = function (props: object | ControllerUpdateFn) {
153 | each(current, (ctrl, i) => ctrl.update(this._getProps(props, ctrl, i)))
154 | return this
155 | }
156 |
157 | /** Overridden by `useTrail` to manipulate props */
158 | const _getProps = function (
159 | arg: ControllerUpdate | ControllerUpdateFn,
160 | ctrl: Controller,
161 | index: number
162 | ): ControllerUpdate | Falsy {
163 | return is.fun(arg) ? arg(index, ctrl) : arg
164 | }
165 |
166 | SpringRef._getProps = _getProps
167 |
168 | return SpringRef
169 | }
170 |
--------------------------------------------------------------------------------
/spring/src/SpringValue.ts:
--------------------------------------------------------------------------------
1 | import * as G from './globals'
2 | import {scheduleProps} from './scheduleProps'
3 | import { Animated, AnimatedValue, getAnimated, getPayload, setAnimated } from "./animated";
4 | import { AnimatedString } from "./AnimatedString";
5 | import { Animation } from "./Animation";
6 | import { getCancelledResult, getCombinedResult, getFinishedResult, getNoopResult } from "./AnimationResult";
7 | import { addFluidObserver, callFluidObservers, FluidValue, getFluidObservers, getFluidValue, hasFluidValue, removeFluidObserver } from "./fluids";
8 | import { FrameValue, isFrameValue } from "./FrameValue";
9 | import { raf } from "./rafz";
10 | import { runAsync, RunAsyncProps, RunAsyncState, stopAsync } from "./runAsync";
11 | import { hasAnimated, isAnimating, isPaused, setActiveBit, setPausedBit } from "./SpringPhase";
12 | import {
13 | EventKey,
14 | Lookup,
15 | SpringProps,
16 | SpringUpdate,
17 | eachProp,
18 | getDefaultProp,
19 | resolveProp,
20 | computeGoal,
21 | isEqual,
22 | callProp,
23 | inferTo,
24 | getDefaultProps,
25 | is,
26 | isAsyncTo,
27 | PickEventFns,
28 | AnimationResolver,
29 | VelocityProp,
30 | toArray,
31 | AsyncResult,
32 | flushCalls,
33 | each,
34 | matchProp,
35 | isAnimatedString,
36 | AnimationRange,
37 | getAnimatedType
38 | } from "./utils";
39 | import { mergeConfig } from './AnimationConfig';
40 | import { frameLoop } from './FrameLoop';
41 |
42 | declare const console: any
43 |
44 | interface DefaultSpringProps
45 | extends Pick, 'pause' | 'cancel' | 'immediate' | 'config'>,
46 | PickEventFns> {}
47 |
48 | /**
49 | * Only numbers, strings, and arrays of numbers/strings are supported.
50 | * Non-animatable strings are also supported.
51 | */
52 | export class SpringValue extends FrameValue {
53 | /** The property name used when `to` or `from` is an object. Useful when debugging too. */
54 | key?: string;
55 |
56 | /** The animation state */
57 | animation = new Animation();
58 |
59 | /** The queue of pending props */
60 | queue?: SpringUpdate[];
61 |
62 | /** Some props have customizable default values */
63 | defaultProps: DefaultSpringProps = {};
64 |
65 | /** The state for `runAsync` calls */
66 | protected _state: RunAsyncState> = {
67 | paused: false,
68 | delayed: false,
69 | pauseQueue: new Set(),
70 | resumeQueue: new Set(),
71 | timeouts: new Set(),
72 | };
73 |
74 | /** The promise resolvers of pending `start` calls */
75 | protected _pendingCalls = new Set>();
76 |
77 | /** The counter for tracking `scheduleProps` calls */
78 | protected _lastCallId = 0;
79 |
80 | /** The last `scheduleProps` call that changed the `to` prop */
81 | protected _lastToId = 0;
82 |
83 | protected _memoizedDuration = 0;
84 |
85 | constructor(from: Exclude, props?: SpringUpdate);
86 | constructor(props?: SpringUpdate);
87 | constructor(arg1?: any, arg2?: any) {
88 | super();
89 | if (!is.und(arg1) || !is.und(arg2)) {
90 | const props = is.obj(arg1) ? { ...arg1 } : { ...arg2, from: arg1 };
91 | if (is.und(props.default)) {
92 | props.default = true;
93 | }
94 | this.start(props);
95 | }
96 | }
97 |
98 | /** Equals true when not advancing on each frame. */
99 | get idle() {
100 | return !(isAnimating(this) || this._state.asyncTo) || isPaused(this);
101 | }
102 |
103 | get goal() {
104 | return getFluidValue(this.animation.to) as T;
105 | }
106 |
107 | get velocity(): VelocityProp {
108 | const node = getAnimated(this)!;
109 | return (
110 | node instanceof AnimatedValue
111 | ? node.lastVelocity || 0
112 | : node.getPayload().map((node) => node.lastVelocity || 0)
113 | ) as any;
114 | }
115 |
116 | /**
117 | * When true, this value has been animated at least once.
118 | */
119 | get hasAnimated() {
120 | return hasAnimated(this);
121 | }
122 |
123 | /**
124 | * When true, this value has an unfinished animation,
125 | * which is either active or paused.
126 | */
127 | get isAnimating() {
128 | return isAnimating(this);
129 | }
130 |
131 | /**
132 | * When true, all current and future animations are paused.
133 | */
134 | get isPaused() {
135 | return isPaused(this);
136 | }
137 |
138 | /**
139 | *
140 | *
141 | */
142 | get isDelayed() {
143 | return this._state.delayed;
144 | }
145 |
146 | /** Advance the current animation by a number of milliseconds */
147 | advance(dt: number) {
148 | let idle = true;
149 | let changed = false;
150 |
151 | const anim = this.animation;
152 | let { config, toValues } = anim;
153 |
154 | const payload = getPayload(anim.to);
155 | if (!payload && hasFluidValue(anim.to)) {
156 | toValues = toArray(getFluidValue(anim.to)) as any;
157 | }
158 |
159 | anim.values.forEach((node, i) => {
160 | if (node.done) return;
161 |
162 | const to =
163 | // Animated strings always go from 0 to 1.
164 | node.constructor == AnimatedString
165 | ? 1
166 | : payload
167 | ? payload[i].lastPosition
168 | : toValues![i];
169 |
170 | let finished = anim.immediate;
171 | let position = to;
172 |
173 | if (!finished) {
174 | position = node.lastPosition;
175 |
176 | // Loose springs never move.
177 | if (config.tension <= 0) {
178 | node.done = true;
179 | return;
180 | }
181 |
182 | let elapsed = (node.elapsedTime += dt);
183 | const from = anim.fromValues[i];
184 |
185 | const v0 =
186 | node.v0 != null
187 | ? node.v0
188 | : (node.v0 = is.arr(config.velocity)
189 | ? config.velocity[i]
190 | : config.velocity);
191 |
192 | let velocity: number;
193 |
194 | // Duration easing
195 | if (!is.und(config.duration)) {
196 | let p = 1;
197 | if (config.duration > 0) {
198 | /**
199 | * Here we check if the duration has changed in the config
200 | * and if so update the elapsed time to the percentage
201 | * of completition so there is no jank in the animation
202 | * https://github.com/pmndrs/react-spring/issues/1163
203 | */
204 | if (this._memoizedDuration !== config.duration) {
205 | // update the memoized version to the new duration
206 | this._memoizedDuration = config.duration;
207 |
208 | // if the value has started animating we need to update it
209 | if (node.durationProgress > 0) {
210 | // set elapsed time to be the same percentage of progress as the previous duration
211 | node.elapsedTime = config.duration * node.durationProgress;
212 | // add the delta so the below updates work as expected
213 | elapsed = node.elapsedTime += dt;
214 | }
215 | }
216 |
217 | // calculate the new progress
218 | p = (config.progress || 0) + elapsed / this._memoizedDuration;
219 | // p is clamped between 0-1
220 | p = p > 1 ? 1 : p < 0 ? 0 : p;
221 | // store our new progress
222 | node.durationProgress = p;
223 | }
224 |
225 | position = from + config.easing(p) * (to - from);
226 | velocity = (position - node.lastPosition) / dt;
227 |
228 | finished = p == 1;
229 | }
230 |
231 | // Decay easing
232 | else if (config.decay) {
233 | const decay = config.decay === true ? 0.998 : config.decay;
234 | const e = Math.exp(-(1 - decay) * elapsed);
235 |
236 | position = from + (v0 / (1 - decay)) * (1 - e);
237 | finished = Math.abs(node.lastPosition - position) < 0.1;
238 |
239 | // derivative of position
240 | velocity = v0 * e;
241 | }
242 |
243 | // Spring easing
244 | else {
245 | velocity = node.lastVelocity == null ? v0 : node.lastVelocity;
246 |
247 | /** The smallest distance from a value before being treated like said value. */
248 | /**
249 | * TODO: make this value ~0.0001 by default in next breaking change
250 | * for more info see – https://github.com/pmndrs/react-spring/issues/1389
251 | */
252 | const precision =
253 | config.precision ||
254 | (from == to ? 0.005 : Math.min(1, Math.abs(to - from) * 0.001));
255 |
256 | /** The velocity at which movement is essentially none */
257 | const restVelocity = config.restVelocity || precision / 10;
258 |
259 | // Bouncing is opt-in (not to be confused with overshooting)
260 | const bounceFactor = config.clamp ? 0 : config.bounce!;
261 | const canBounce = !is.und(bounceFactor);
262 |
263 | /** When `true`, the value is increasing over time */
264 | const isGrowing = from == to ? node.v0 > 0 : from < to;
265 |
266 | /** When `true`, the velocity is considered moving */
267 | let isMoving!: boolean;
268 |
269 | /** When `true`, the velocity is being deflected or clamped */
270 | let isBouncing = false;
271 |
272 | const step = 1; // 1ms
273 | const numSteps = Math.ceil(dt / step);
274 | for (let n = 0; n < numSteps; ++n) {
275 | isMoving = Math.abs(velocity) > restVelocity;
276 |
277 | if (!isMoving) {
278 | finished = Math.abs(to - position) <= precision;
279 | if (finished) {
280 | break;
281 | }
282 | }
283 |
284 | if (canBounce) {
285 | isBouncing = position == to || position > to == isGrowing;
286 |
287 | // Invert the velocity with a magnitude, or clamp it.
288 | if (isBouncing) {
289 | velocity = -velocity * bounceFactor;
290 | position = to;
291 | }
292 | }
293 |
294 | const springForce = -config.tension * 0.000001 * (position - to);
295 | const dampingForce = -config.friction * 0.001 * velocity;
296 | const acceleration = (springForce + dampingForce) / config.mass; // pt/ms^2
297 |
298 | velocity = velocity + acceleration * step; // pt/ms
299 | position = position + velocity * step;
300 | }
301 | }
302 |
303 | node.lastVelocity = velocity;
304 |
305 | if (Number.isNaN(position)) {
306 | console.warn(`Got NaN while animating:`, this);
307 | finished = true;
308 | }
309 | }
310 |
311 | // Parent springs must finish before their children can.
312 | if (payload && !payload[i].done) {
313 | finished = false;
314 | }
315 |
316 | if (finished) {
317 | node.done = true;
318 | } else {
319 | idle = false;
320 | }
321 |
322 | if (node.setValue(position, config.round)) {
323 | changed = true;
324 | }
325 | });
326 |
327 | const node = getAnimated(this)!;
328 | /**
329 | * Get the node's current value, this will be different
330 | * to anim.to when config.decay is true
331 | */
332 | const currVal = node.getValue();
333 | if (idle) {
334 | // get our final fluid val from the anim.to
335 | const finalVal = getFluidValue(anim.to);
336 | /**
337 | * check if they're not equal, or if they're
338 | * change and if there's no config.decay set
339 | */
340 | if ((currVal !== finalVal || changed) && !config.decay) {
341 | // set the value to anim.to
342 | node.setValue(finalVal);
343 | this._onChange(finalVal);
344 | } else if (changed && config.decay) {
345 | /**
346 | * if it's changed but there is a config.decay,
347 | * just call _onChange with currrent value
348 | */
349 | this._onChange(currVal);
350 | }
351 | // call stop because the spring has stopped.
352 | this._stop();
353 | } else if (changed) {
354 | /**
355 | * if the spring has changed, but is not idle,
356 | * just call the _onChange handler
357 | */
358 | this._onChange(currVal);
359 | }
360 | }
361 |
362 | /** Set the current value, while stopping the current animation */
363 | set(value: T | FluidValue) {
364 | raf.batchedUpdates(() => {
365 | this._stop();
366 |
367 | // These override the current value and goal value that may have
368 | // been updated by `onRest` handlers in the `_stop` call above.
369 | this._focus(value);
370 | this._set(value);
371 | });
372 | return this;
373 | }
374 |
375 | /**
376 | * Freeze the active animation in time, as well as any updates merged
377 | * before `resume` is called.
378 | */
379 | pause() {
380 | this._update({ pause: true });
381 | }
382 |
383 | /** Resume the animation if paused. */
384 | resume() {
385 | this._update({ pause: false });
386 | }
387 |
388 | /** Skip to the end of the current animation. */
389 | finish() {
390 | if (isAnimating(this)) {
391 | const { to, config } = this.animation;
392 | raf.batchedUpdates(() => {
393 | // Ensure the "onStart" and "onRest" props are called.
394 | this._onStart();
395 |
396 | // Jump to the goal value, except for decay animations
397 | // which have an undefined goal value.
398 | if (!config.decay) {
399 | this._set(to, false);
400 | }
401 |
402 | this._stop();
403 | });
404 | }
405 | return this;
406 | }
407 |
408 | /** Push props into the pending queue. */
409 | update(props: SpringUpdate) {
410 | const queue = this.queue || (this.queue = []);
411 | queue.push(props);
412 | return this;
413 | }
414 |
415 | /**
416 | * Update this value's animation using the queue of pending props,
417 | * and unpause the current animation (if one is frozen).
418 | *
419 | * When arguments are passed, a new animation is created, and the
420 | * queued animations are left alone.
421 | */
422 | start(): AsyncResult;
423 |
424 | start(props: SpringUpdate): AsyncResult;
425 |
426 | start(to: T, props?: SpringProps): AsyncResult;
427 |
428 | start(to?: T | SpringUpdate, arg2?: SpringProps) {
429 | let queue: SpringUpdate[];
430 | if (!is.und(to)) {
431 | queue = [is.obj(to) ? to : { ...arg2, to }];
432 | } else {
433 | queue = this.queue || [];
434 | this.queue = [];
435 | }
436 |
437 | return Promise.all(
438 | queue.map((props) => {
439 | const up = this._update(props);
440 | return up;
441 | })
442 | ).then((results) => getCombinedResult(this, results));
443 | }
444 |
445 | /**
446 | * Stop the current animation, and cancel any delayed updates.
447 | *
448 | * Pass `true` to call `onRest` with `cancelled: true`.
449 | */
450 | stop(cancel?: boolean) {
451 | const { to } = this.animation;
452 |
453 | // The current value becomes the goal value.
454 | this._focus(this.get());
455 |
456 | stopAsync(this._state, cancel && this._lastCallId);
457 | raf.batchedUpdates(() => this._stop(to, cancel));
458 |
459 | return this;
460 | }
461 |
462 | /** Restart the animation. */
463 | reset() {
464 | this._update({ reset: true });
465 | }
466 |
467 | /** @internal */
468 | eventObserved(event: FrameValue.Event) {
469 | if (event.type == "change") {
470 | this._start();
471 | } else if (event.type == "priority") {
472 | this.priority = event.priority + 1;
473 | }
474 | }
475 |
476 | /**
477 | * Parse the `to` and `from` range from the given `props` object.
478 | *
479 | * This also ensures the initial value is available to animated components
480 | * during the render phase.
481 | */
482 | protected _prepareNode(props: {
483 | to?: any;
484 | from?: any;
485 | reverse?: boolean;
486 | default?: any;
487 | }) {
488 | const key = this.key || "";
489 |
490 | let { to, from } = props;
491 |
492 | to = is.obj(to) ? to[key] : to;
493 | if (to == null || isAsyncTo(to)) {
494 | to = undefined;
495 | }
496 |
497 | from = is.obj(from) ? from[key] : from;
498 | if (from == null) {
499 | from = undefined;
500 | }
501 |
502 | // Create the range now to avoid "reverse" logic.
503 | const range = { to, from };
504 |
505 | // Before ever animating, this method ensures an `Animated` node
506 | // exists and keeps its value in sync with the "from" prop.
507 | if (!hasAnimated(this)) {
508 | if (props.reverse) [to, from] = [from, to];
509 |
510 | from = getFluidValue(from);
511 | if (!is.und(from)) {
512 | this._set(from);
513 | }
514 | // Use the "to" value if our node is undefined.
515 | else if (!getAnimated(this)) {
516 | this._set(to);
517 | }
518 | }
519 |
520 | return range;
521 | }
522 |
523 | /** Every update is processed by this method before merging. */
524 | protected _update(
525 | { ...props }: SpringProps,
526 | isLoop?: boolean
527 | ): AsyncResult> {
528 | const { key, defaultProps } = this;
529 |
530 | // Update the default props immediately.
531 | if (props.default)
532 | Object.assign(
533 | defaultProps,
534 | getDefaultProps(props, (value, prop) =>
535 | /^on/.test(prop) ? resolveProp(value, key) : value
536 | )
537 | );
538 |
539 | mergeActiveFn(this, props, "onProps");
540 | sendEvent(this, "onProps", props, this);
541 |
542 | // Ensure the initial value can be accessed by animated components.
543 | const range = this._prepareNode(props);
544 |
545 | if (Object.isFrozen(this)) {
546 | throw Error(
547 | "Cannot animate a `SpringValue` object that is frozen. " +
548 | "Did you forget to pass your component to `animated(...)` before animating its props?"
549 | );
550 | }
551 |
552 | const state = this._state;
553 |
554 | return scheduleProps(++this._lastCallId, {
555 | key,
556 | props,
557 | defaultProps,
558 | state,
559 | actions: {
560 | pause: () => {
561 | if (!isPaused(this)) {
562 | setPausedBit(this, true);
563 | flushCalls(state.pauseQueue);
564 | sendEvent(
565 | this,
566 | "onPause",
567 | getFinishedResult(this, checkFinished(this, this.animation.to)),
568 | this
569 | );
570 | }
571 | },
572 | resume: () => {
573 | if (isPaused(this)) {
574 | setPausedBit(this, false);
575 | if (isAnimating(this)) {
576 | this._resume();
577 | }
578 | flushCalls(state.resumeQueue);
579 | sendEvent(
580 | this,
581 | "onResume",
582 | getFinishedResult(this, checkFinished(this, this.animation.to)),
583 | this
584 | );
585 | }
586 | },
587 | start: this._merge.bind(this, range),
588 | },
589 | }).then((result) => {
590 | if (props.loop && result.finished && !(isLoop && result.noop)) {
591 | const nextProps = createLoopUpdate(props);
592 | if (nextProps) {
593 | return this._update(nextProps, true);
594 | }
595 | }
596 | return result;
597 | });
598 | }
599 |
600 | /** Merge props into the current animation */
601 | protected _merge(
602 | range: AnimationRange,
603 | props: RunAsyncProps>,
604 | resolve: AnimationResolver>
605 | ): void {
606 | // The "cancel" prop cancels all pending delays and it forces the
607 | // active animation to stop where it is.
608 | if (props.cancel) {
609 | this.stop(true);
610 | return resolve(getCancelledResult(this));
611 | }
612 |
613 | /** The "to" prop is defined. */
614 | const hasToProp = !is.und(range.to);
615 |
616 | /** The "from" prop is defined. */
617 | const hasFromProp = !is.und(range.from);
618 |
619 | // Avoid merging other props if implicitly prevented, except
620 | // when both the "to" and "from" props are undefined.
621 | if (hasToProp || hasFromProp) {
622 | if (props.callId > this._lastToId) {
623 | this._lastToId = props.callId;
624 | } else {
625 | return resolve(getCancelledResult(this));
626 | }
627 | }
628 |
629 | const { key, defaultProps, animation: anim } = this;
630 | const { to: prevTo, from: prevFrom } = anim;
631 | let { to = prevTo, from = prevFrom } = range;
632 |
633 | // Focus the "from" value if changing without a "to" value.
634 | // For default updates, do this only if no "to" value exists.
635 | if (hasFromProp && !hasToProp && (!props.default || is.und(to))) {
636 | to = from;
637 | }
638 |
639 | // Flip the current range if "reverse" is true.
640 | if (props.reverse) [to, from] = [from, to];
641 |
642 | /** The "from" value is changing. */
643 | const hasFromChanged = !isEqual(from, prevFrom);
644 |
645 | if (hasFromChanged) {
646 | anim.from = from;
647 | }
648 |
649 | // Coerce "from" into a static value.
650 | from = getFluidValue(from);
651 |
652 | /** The "to" value is changing. */
653 | const hasToChanged = !isEqual(to, prevTo);
654 |
655 | if (hasToChanged) {
656 | this._focus(to);
657 | }
658 |
659 | /** The "to" prop is async. */
660 | const hasAsyncTo = isAsyncTo(props.to);
661 |
662 | const { config } = anim;
663 | const { decay, velocity } = config;
664 |
665 | // Reset to default velocity when goal values are defined.
666 | if (hasToProp || hasFromProp) {
667 | config.velocity = 0;
668 | }
669 |
670 | // The "runAsync" function treats the "config" prop as a default,
671 | // so we must avoid merging it when the "to" prop is async.
672 | if (props.config && !hasAsyncTo) {
673 | mergeConfig(
674 | config,
675 | callProp(props.config, key!),
676 | // Avoid calling the same "config" prop twice.
677 | props.config !== defaultProps.config
678 | ? callProp(defaultProps.config, key!)
679 | : void 0
680 | );
681 | }
682 |
683 | // This instance might not have its Animated node yet. For example,
684 | // the constructor can be given props without a "to" or "from" value.
685 | let node = getAnimated(this);
686 | if (!node || is.und(to)) {
687 | return resolve(getFinishedResult(this, true));
688 | }
689 |
690 | /** When true, start at the "from" value. */
691 | const reset =
692 | // When `reset` is undefined, the `from` prop implies `reset: true`,
693 | // except for declarative updates. When `reset` is defined, there
694 | // must exist a value to animate from.
695 | is.und(props.reset)
696 | ? hasFromProp && !props.default
697 | : !is.und(from) && matchProp(props.reset, key);
698 |
699 | // The current value, where the animation starts from.
700 | const value = reset ? (from as T) : this.get();
701 |
702 | // The animation ends at this value, unless "to" is fluid.
703 | const goal = computeGoal(to);
704 |
705 | // Only specific types can be animated to/from.
706 | const isAnimatable = is.num(goal) || is.arr(goal) || isAnimatedString(goal);
707 |
708 | // When true, the value changes instantly on the next frame.
709 | const immediate =
710 | !hasAsyncTo &&
711 | (!isAnimatable ||
712 | matchProp(defaultProps.immediate || props.immediate, key));
713 |
714 | if (hasToChanged) {
715 | const nodeType = getAnimatedType(to);
716 | if (nodeType !== node.constructor) {
717 | if (immediate) {
718 | node = this._set(goal)!;
719 | } else
720 | throw Error(
721 | `Cannot animate between ${node.constructor.name} and ${nodeType.name}, as the "to" prop suggests`
722 | );
723 | }
724 | }
725 |
726 | // The type of Animated node for the goal value.
727 | const goalType = node.constructor;
728 |
729 | // When the goal value is fluid, we don't know if its value
730 | // will change before the next animation frame, so it always
731 | // starts the animation to be safe.
732 | let started = hasFluidValue(to);
733 | let finished = false;
734 |
735 | if (!started) {
736 | // When true, the current value has probably changed.
737 | const hasValueChanged = reset || (!hasAnimated(this) && hasFromChanged);
738 |
739 | // When the "to" value or current value are changed,
740 | // start animating if not already finished.
741 | if (hasToChanged || hasValueChanged) {
742 | finished = isEqual(computeGoal(value), goal);
743 | started = !finished;
744 | }
745 |
746 | // Changing "decay" or "velocity" starts the animation.
747 | if (
748 | (!isEqual(anim.immediate, immediate) && !immediate) ||
749 | !isEqual(config.decay, decay) ||
750 | !isEqual(config.velocity, velocity)
751 | ) {
752 | started = true;
753 | }
754 | }
755 |
756 | // Was the goal value set to the current value while animating?
757 | if (finished && isAnimating(this)) {
758 | // If the first frame has passed, allow the animation to
759 | // overshoot instead of stopping abruptly.
760 | if (anim.changed && !reset) {
761 | started = true;
762 | }
763 | // Stop the animation before its first frame.
764 | else if (!started) {
765 | this._stop(prevTo);
766 | }
767 | }
768 |
769 | if (!hasAsyncTo) {
770 | // Make sure our "toValues" are updated even if our previous
771 | // "to" prop is a fluid value whose current value is also ours.
772 | if (started || hasFluidValue(prevTo)) {
773 | anim.values = node.getPayload();
774 | anim.toValues = hasFluidValue(to)
775 | ? null
776 | : goalType == AnimatedString
777 | ? [1]
778 | : toArray(goal);
779 | }
780 |
781 | if (anim.immediate != immediate) {
782 | anim.immediate = immediate;
783 |
784 | // Ensure the immediate goal is used as from value.
785 | if (!immediate && !reset) {
786 | this._set(prevTo);
787 | }
788 | }
789 |
790 | if (started) {
791 | const { onRest } = anim;
792 |
793 | // Set the active handlers when an animation starts.
794 | each(ACTIVE_EVENTS, (type) => mergeActiveFn(this, props, type));
795 |
796 | const result = getFinishedResult(this, checkFinished(this, prevTo));
797 | flushCalls(this._pendingCalls, result);
798 | this._pendingCalls.add(resolve);
799 |
800 | if (anim.changed)
801 | raf.batchedUpdates(() => {
802 | // Ensure `onStart` can be called after a reset.
803 | anim.changed = !reset;
804 |
805 | // Call the active `onRest` handler from the interrupted animation.
806 | onRest?.(result, this);
807 |
808 | // Notify the default `onRest` of the reset, but wait for the
809 | // first frame to pass before sending an `onStart` event.
810 | if (reset) {
811 | callProp(defaultProps.onRest, result);
812 | }
813 | // Call the active `onStart` handler here since the first frame
814 | // has already passed, which means this is a goal update and not
815 | // an entirely new animation.
816 | else {
817 | anim.onStart?.(result, this);
818 | }
819 | });
820 | }
821 | }
822 |
823 | if (reset) {
824 | this._set(value);
825 | }
826 |
827 | if (hasAsyncTo) {
828 | resolve(runAsync(props.to, props, this._state, this));
829 | }
830 |
831 | // Start an animation
832 | else if (started) {
833 | this._start();
834 | }
835 |
836 | // Postpone promise resolution until the animation is finished,
837 | // so that no-op updates still resolve at the expected time.
838 | else if (isAnimating(this) && !hasToChanged) {
839 | this._pendingCalls.add(resolve);
840 | }
841 |
842 | // Resolve our promise immediately.
843 | else {
844 | resolve(getNoopResult(value));
845 | }
846 | }
847 |
848 | /** Update the `animation.to` value, which might be a `FluidValue` */
849 | protected _focus(value: T | FluidValue) {
850 | const anim = this.animation;
851 | if (value !== anim.to) {
852 | if (getFluidObservers(this)) {
853 | this._detach();
854 | }
855 | anim.to = value;
856 | if (getFluidObservers(this)) {
857 | this._attach();
858 | }
859 | }
860 | }
861 |
862 | protected _attach() {
863 | let priority = 0;
864 |
865 | const { to } = this.animation;
866 | if (hasFluidValue(to)) {
867 | addFluidObserver(to, this);
868 | if (isFrameValue(to)) {
869 | priority = to.priority + 1;
870 | }
871 | }
872 |
873 | this.priority = priority;
874 | }
875 |
876 | protected _detach() {
877 | const { to } = this.animation;
878 | if (hasFluidValue(to)) {
879 | removeFluidObserver(to, this);
880 | }
881 | }
882 |
883 | /**
884 | * Update the current value from outside the frameloop,
885 | * and return the `Animated` node.
886 | */
887 | protected _set(arg: T | FluidValue, idle = true): Animated | undefined {
888 | const value = getFluidValue(arg);
889 | if (!is.und(value)) {
890 | const oldNode = getAnimated(this);
891 | if (!oldNode || !isEqual(value, oldNode.getValue())) {
892 | // Create a new node or update the existing node.
893 | const nodeType = getAnimatedType(value);
894 | if (!oldNode || oldNode.constructor != nodeType) {
895 | setAnimated(this, nodeType.create(value));
896 | } else {
897 | oldNode.setValue(value);
898 | }
899 | // Never emit a "change" event for the initial value.
900 | if (oldNode) {
901 | raf.batchedUpdates(() => {
902 | this._onChange(value, idle);
903 | });
904 | }
905 | }
906 | }
907 | return getAnimated(this);
908 | }
909 |
910 | protected _onStart() {
911 | const anim = this.animation;
912 | if (!anim.changed) {
913 | anim.changed = true;
914 | sendEvent(
915 | this,
916 | "onStart",
917 | getFinishedResult(this, checkFinished(this, anim.to)),
918 | this
919 | );
920 | }
921 | }
922 |
923 | protected _onChange(value: T, idle?: boolean) {
924 | if (!idle) {
925 | this._onStart();
926 | callProp(this.animation.onChange, value, this);
927 | }
928 | callProp(this.defaultProps.onChange, value, this);
929 | super._onChange(value, idle);
930 | }
931 |
932 | // This method resets the animation state (even if already animating) to
933 | // ensure the latest from/to range is used, and it also ensures this spring
934 | // is added to the frameloop.
935 | protected _start() {
936 | const anim = this.animation;
937 |
938 | // Reset the state of each Animated node.
939 | getAnimated(this)!.reset(getFluidValue(anim.to));
940 |
941 | // Use the current values as the from values.
942 | if (!anim.immediate) {
943 | anim.fromValues = anim.values.map((node) => node.lastPosition);
944 | }
945 |
946 | if (!isAnimating(this)) {
947 | setActiveBit(this, true);
948 | if (!isPaused(this)) {
949 | this._resume();
950 | }
951 | }
952 | }
953 |
954 | protected _resume() {
955 | // The "skipAnimation" global avoids the frameloop.
956 | if (G.skipAnimation) {
957 | this.finish();
958 | } else {
959 | frameLoop.start(this);
960 | }
961 | }
962 |
963 | /**
964 | * Exit the frameloop and notify `onRest` listeners.
965 | *
966 | * Always wrap `_stop` calls with `batchedUpdates`.
967 | */
968 | protected _stop(goal?: any, cancel?: boolean) {
969 | if (isAnimating(this)) {
970 | setActiveBit(this, false);
971 |
972 | const anim = this.animation;
973 | each(anim.values, (node) => {
974 | node.done = true;
975 | });
976 |
977 | // These active handlers must be reset to undefined or else
978 | // they could be called while idle. But keep them defined
979 | // when the goal value is dynamic.
980 | if (anim.toValues) {
981 | anim.onChange = anim.onPause = anim.onResume = undefined;
982 | }
983 |
984 | callFluidObservers(this, {
985 | type: "idle",
986 | parent: this,
987 | });
988 |
989 | const result = cancel
990 | ? getCancelledResult(this.get())
991 | : getFinishedResult(this.get(), checkFinished(this, goal ?? anim.to));
992 |
993 | flushCalls(this._pendingCalls, result);
994 | if (anim.changed) {
995 | anim.changed = false;
996 | sendEvent(this, "onRest", result, this);
997 | }
998 | }
999 | }
1000 | }
1001 |
1002 | /** Returns true when the current value and goal value are equal. */
1003 | function checkFinished(target: SpringValue, to: T | FluidValue) {
1004 | const goal = computeGoal(to);
1005 | const value = computeGoal(target.get());
1006 | return isEqual(value, goal);
1007 | }
1008 |
1009 | export function createLoopUpdate(
1010 | props: T & { loop?: any; to?: any; from?: any; reverse?: any },
1011 | loop = props.loop,
1012 | to = props.to
1013 | ): T | undefined {
1014 | let loopRet = callProp(loop);
1015 | if (loopRet) {
1016 | const overrides = loopRet !== true && inferTo(loopRet);
1017 | const reverse = (overrides || props).reverse;
1018 | const reset = !overrides || overrides.reset;
1019 | return createUpdate({
1020 | ...props,
1021 | loop,
1022 |
1023 | // Avoid updating default props when looping.
1024 | default: false,
1025 |
1026 | // Never loop the `pause` prop.
1027 | pause: undefined,
1028 |
1029 | // For the "reverse" prop to loop as expected, the "to" prop
1030 | // must be undefined. The "reverse" prop is ignored when the
1031 | // "to" prop is an array or function.
1032 | to: !reverse || isAsyncTo(to) ? to : undefined,
1033 |
1034 | // Ignore the "from" prop except on reset.
1035 | from: reset ? props.from : undefined,
1036 | reset,
1037 |
1038 | // The "loop" prop can return a "useSpring" props object to
1039 | // override any of the original props.
1040 | ...overrides,
1041 | });
1042 | }
1043 | }
1044 |
1045 | /**
1046 | * Return a new object based on the given `props`.
1047 | *
1048 | * - All non-reserved props are moved into the `to` prop object.
1049 | * - The `keys` prop is set to an array of affected keys,
1050 | * or `null` if all keys are affected.
1051 | */
1052 | export function createUpdate(props: any) {
1053 | const { to, from } = (props = inferTo(props));
1054 |
1055 | // Collect the keys affected by this update.
1056 | const keys = new Set();
1057 |
1058 | if (is.obj(to)) findDefined(to, keys);
1059 | if (is.obj(from)) findDefined(from, keys);
1060 |
1061 | // The "keys" prop helps in applying updates to affected keys only.
1062 | props.keys = keys.size ? Array.from(keys) : null;
1063 |
1064 | return props;
1065 | }
1066 |
1067 | /**
1068 | * A modified version of `createUpdate` meant for declarative APIs.
1069 | */
1070 | export function declareUpdate(props: any) {
1071 | const update = createUpdate(props);
1072 | if (is.und(update.default)) {
1073 | update.default = getDefaultProps(update);
1074 | }
1075 | return update;
1076 | }
1077 |
1078 | /** Find keys with defined values */
1079 | function findDefined(values: Lookup, keys: Set) {
1080 | eachProp(values, (value, key) => value != null && keys.add(key as any));
1081 | }
1082 |
1083 | /** Event props with "active handler" support */
1084 | const ACTIVE_EVENTS = [
1085 | "onStart",
1086 | "onRest",
1087 | "onChange",
1088 | "onPause",
1089 | "onResume",
1090 | ] as const;
1091 |
1092 | function mergeActiveFn(
1093 | target: SpringValue,
1094 | props: SpringProps,
1095 | type: P
1096 | ) {
1097 | target.animation[type] =
1098 | props[type] !== getDefaultProp(props, type)
1099 | ? resolveProp(props[type], target.key)
1100 | : undefined;
1101 | }
1102 |
1103 | type EventArgs = Parameters<
1104 | Extract[P], Function>
1105 | >;
1106 |
1107 | /** Call the active handler first, then the default handler. */
1108 | function sendEvent(
1109 | target: SpringValue,
1110 | type: P,
1111 | ...args: EventArgs
1112 | ) {
1113 | target.animation[type]?.(...(args as [any, any]));
1114 | target.defaultProps[type]?.(...(args as [any, any]));
1115 | }
1116 |
--------------------------------------------------------------------------------
/spring/src/animated.ts:
--------------------------------------------------------------------------------
1 | import { defineHidden, is } from "./utils";
2 |
3 | const $node: any = Symbol.for("Animated:node");
4 |
5 | export const isAnimated = (value: any): value is Animated =>
6 | !!value && value[$node] === value;
7 |
8 | /** Get the owner's `Animated` node. */
9 | export const getAnimated = (owner: any): Animated | undefined =>
10 | owner && owner[$node];
11 |
12 | /** Set the owner's `Animated` node. */
13 | export const setAnimated = (owner: any, node: Animated) =>
14 | defineHidden(owner, $node, node);
15 |
16 | /** Get every `AnimatedValue` in the owner's `Animated` node. */
17 | export const getPayload = (owner: any): AnimatedValue[] | undefined =>
18 | owner && owner[$node] && owner[$node].getPayload();
19 |
20 | export abstract class Animated {
21 | /** The cache of animated values */
22 | protected payload?: Payload;
23 |
24 | constructor() {
25 | // This makes "isAnimated" return true.
26 | setAnimated(this, this);
27 | }
28 |
29 | /** Get the current value. Pass `true` for only animated values. */
30 | abstract getValue(animated?: boolean): T;
31 |
32 | /** Set the current value. Returns `true` if the value changed. */
33 | abstract setValue(value: T): boolean | void;
34 |
35 | /** Reset any animation state. */
36 | abstract reset(goal?: T): void;
37 |
38 | /** Get every `AnimatedValue` used by this node. */
39 | getPayload(): Payload {
40 | return this.payload || [];
41 | }
42 | }
43 |
44 | export type Payload = readonly AnimatedValue[];
45 | /** An animated number or a native attribute value */
46 | export class AnimatedValue extends Animated {
47 | done = true;
48 | elapsedTime!: number;
49 | lastPosition!: number;
50 | lastVelocity?: number | null;
51 | v0?: number | null;
52 | durationProgress = 0;
53 |
54 | constructor(protected _value: T) {
55 | super();
56 | if (is.num(this._value)) {
57 | this.lastPosition = this._value;
58 | }
59 | }
60 |
61 | /** @internal */
62 | static create(value: any) {
63 | return new AnimatedValue(value);
64 | }
65 |
66 | getPayload(): Payload {
67 | return [this];
68 | }
69 |
70 | getValue() {
71 | return this._value;
72 | }
73 |
74 | setValue(value: T, step?: number) {
75 | if (is.num(value)) {
76 | this.lastPosition = value;
77 | if (step) {
78 | value = (Math.round(value / step) * step) as any;
79 | if (this.done) {
80 | this.lastPosition = value as any;
81 | }
82 | }
83 | }
84 | if (this._value === value) {
85 | return false;
86 | }
87 | this._value = value;
88 | return true;
89 | }
90 |
91 | reset() {
92 | const { done } = this;
93 | this.done = false;
94 | if (is.num(this._value)) {
95 | this.elapsedTime = 0;
96 | this.durationProgress = 0;
97 | this.lastPosition = this._value;
98 | if (done) this.lastVelocity = null;
99 | this.v0 = null;
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/spring/src/applyAnimatedValues.ts:
--------------------------------------------------------------------------------
1 | import { Lookup } from "./utils";
2 |
3 | const isCustomPropRE = /^--/;
4 |
5 | type Value = string | number | boolean | null;
6 |
7 | function dangerousStyleValue(name: string, value: Value) {
8 | if (value == null || typeof value === "boolean" || value === "") return "";
9 | if (
10 | typeof value === "number" &&
11 | value !== 0 &&
12 | !isCustomPropRE.test(name) &&
13 | !(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name])
14 | )
15 | return value + "px";
16 | // Presumes implicit 'px' suffix for unitless numbers
17 | return ("" + value).trim();
18 | }
19 |
20 | const attributeCache: Lookup = {};
21 |
22 | type Instance = HTMLDivElement & { style?: Lookup };
23 |
24 | export function applyAnimatedValues(instance: Instance, props: Lookup) {
25 | if (!instance.nodeType || !instance.setAttribute) {
26 | return false;
27 | }
28 |
29 | const isFilterElement =
30 | instance.nodeName === "filter" ||
31 | (instance.parentNode && instance.parentNode.nodeName === "filter");
32 |
33 | const { style, children, scrollTop, scrollLeft, ...attributes } = props!;
34 |
35 | const values = Object.values(attributes);
36 | const names = Object.keys(attributes).map((name) =>
37 | isFilterElement || instance.hasAttribute(name)
38 | ? name
39 | : attributeCache[name] ||
40 | (attributeCache[name] = name.replace(
41 | /([A-Z])/g,
42 | // Attributes are written in dash case
43 | (n) => "-" + n.toLowerCase()
44 | ))
45 | );
46 |
47 | if (children !== void 0) {
48 | instance.textContent = children;
49 | }
50 |
51 | // Apply CSS styles
52 | for (let name in style) {
53 | if (style.hasOwnProperty(name)) {
54 | const value = dangerousStyleValue(name, style[name]);
55 | if (isCustomPropRE.test(name)) {
56 | instance.style.setProperty(name, value);
57 | } else {
58 | instance.style[name] = value;
59 | }
60 | }
61 | }
62 |
63 | // Apply DOM attributes
64 | names.forEach((name, i) => {
65 | instance.setAttribute(name, values[i]);
66 | });
67 |
68 | if (scrollTop !== void 0) {
69 | instance.scrollTop = scrollTop;
70 | }
71 | if (scrollLeft !== void 0) {
72 | instance.scrollLeft = scrollLeft;
73 | }
74 | }
75 |
76 | let isUnitlessNumber: { [key: string]: true } = {
77 | animationIterationCount: true,
78 | borderImageOutset: true,
79 | borderImageSlice: true,
80 | borderImageWidth: true,
81 | boxFlex: true,
82 | boxFlexGroup: true,
83 | boxOrdinalGroup: true,
84 | columnCount: true,
85 | columns: true,
86 | flex: true,
87 | flexGrow: true,
88 | flexPositive: true,
89 | flexShrink: true,
90 | flexNegative: true,
91 | flexOrder: true,
92 | gridRow: true,
93 | gridRowEnd: true,
94 | gridRowSpan: true,
95 | gridRowStart: true,
96 | gridColumn: true,
97 | gridColumnEnd: true,
98 | gridColumnSpan: true,
99 | gridColumnStart: true,
100 | fontWeight: true,
101 | lineClamp: true,
102 | lineHeight: true,
103 | opacity: true,
104 | order: true,
105 | orphans: true,
106 | tabSize: true,
107 | widows: true,
108 | zIndex: true,
109 | zoom: true,
110 | // SVG-related properties
111 | fillOpacity: true,
112 | floodOpacity: true,
113 | stopOpacity: true,
114 | strokeDasharray: true,
115 | strokeDashoffset: true,
116 | strokeMiterlimit: true,
117 | strokeOpacity: true,
118 | strokeWidth: true,
119 | };
120 |
121 | const prefixKey = (prefix: string, key: string) =>
122 | prefix + key.charAt(0).toUpperCase() + key.substring(1);
123 | const prefixes = ["Webkit", "Ms", "Moz", "O"];
124 |
125 | isUnitlessNumber = Object.keys(isUnitlessNumber).reduce((acc, prop) => {
126 | prefixes.forEach((prefix) => (acc[prefixKey(prefix, prop)] = acc[prop]));
127 | return acc;
128 | }, isUnitlessNumber);
129 |
--------------------------------------------------------------------------------
/spring/src/colors.ts:
--------------------------------------------------------------------------------
1 | export type ColorName = keyof typeof colors
2 |
3 | // http://www.w3.org/TR/css3-color/#svg-color
4 | export const colors = {
5 | transparent: 0x00000000,
6 | aliceblue: 0xf0f8ffff,
7 | antiquewhite: 0xfaebd7ff,
8 | aqua: 0x00ffffff,
9 | aquamarine: 0x7fffd4ff,
10 | azure: 0xf0ffffff,
11 | beige: 0xf5f5dcff,
12 | bisque: 0xffe4c4ff,
13 | black: 0x000000ff,
14 | blanchedalmond: 0xffebcdff,
15 | blue: 0x0000ffff,
16 | blueviolet: 0x8a2be2ff,
17 | brown: 0xa52a2aff,
18 | burlywood: 0xdeb887ff,
19 | burntsienna: 0xea7e5dff,
20 | cadetblue: 0x5f9ea0ff,
21 | chartreuse: 0x7fff00ff,
22 | chocolate: 0xd2691eff,
23 | coral: 0xff7f50ff,
24 | cornflowerblue: 0x6495edff,
25 | cornsilk: 0xfff8dcff,
26 | crimson: 0xdc143cff,
27 | cyan: 0x00ffffff,
28 | darkblue: 0x00008bff,
29 | darkcyan: 0x008b8bff,
30 | darkgoldenrod: 0xb8860bff,
31 | darkgray: 0xa9a9a9ff,
32 | darkgreen: 0x006400ff,
33 | darkgrey: 0xa9a9a9ff,
34 | darkkhaki: 0xbdb76bff,
35 | darkmagenta: 0x8b008bff,
36 | darkolivegreen: 0x556b2fff,
37 | darkorange: 0xff8c00ff,
38 | darkorchid: 0x9932ccff,
39 | darkred: 0x8b0000ff,
40 | darksalmon: 0xe9967aff,
41 | darkseagreen: 0x8fbc8fff,
42 | darkslateblue: 0x483d8bff,
43 | darkslategray: 0x2f4f4fff,
44 | darkslategrey: 0x2f4f4fff,
45 | darkturquoise: 0x00ced1ff,
46 | darkviolet: 0x9400d3ff,
47 | deeppink: 0xff1493ff,
48 | deepskyblue: 0x00bfffff,
49 | dimgray: 0x696969ff,
50 | dimgrey: 0x696969ff,
51 | dodgerblue: 0x1e90ffff,
52 | firebrick: 0xb22222ff,
53 | floralwhite: 0xfffaf0ff,
54 | forestgreen: 0x228b22ff,
55 | fuchsia: 0xff00ffff,
56 | gainsboro: 0xdcdcdcff,
57 | ghostwhite: 0xf8f8ffff,
58 | gold: 0xffd700ff,
59 | goldenrod: 0xdaa520ff,
60 | gray: 0x808080ff,
61 | green: 0x008000ff,
62 | greenyellow: 0xadff2fff,
63 | grey: 0x808080ff,
64 | honeydew: 0xf0fff0ff,
65 | hotpink: 0xff69b4ff,
66 | indianred: 0xcd5c5cff,
67 | indigo: 0x4b0082ff,
68 | ivory: 0xfffff0ff,
69 | khaki: 0xf0e68cff,
70 | lavender: 0xe6e6faff,
71 | lavenderblush: 0xfff0f5ff,
72 | lawngreen: 0x7cfc00ff,
73 | lemonchiffon: 0xfffacdff,
74 | lightblue: 0xadd8e6ff,
75 | lightcoral: 0xf08080ff,
76 | lightcyan: 0xe0ffffff,
77 | lightgoldenrodyellow: 0xfafad2ff,
78 | lightgray: 0xd3d3d3ff,
79 | lightgreen: 0x90ee90ff,
80 | lightgrey: 0xd3d3d3ff,
81 | lightpink: 0xffb6c1ff,
82 | lightsalmon: 0xffa07aff,
83 | lightseagreen: 0x20b2aaff,
84 | lightskyblue: 0x87cefaff,
85 | lightslategray: 0x778899ff,
86 | lightslategrey: 0x778899ff,
87 | lightsteelblue: 0xb0c4deff,
88 | lightyellow: 0xffffe0ff,
89 | lime: 0x00ff00ff,
90 | limegreen: 0x32cd32ff,
91 | linen: 0xfaf0e6ff,
92 | magenta: 0xff00ffff,
93 | maroon: 0x800000ff,
94 | mediumaquamarine: 0x66cdaaff,
95 | mediumblue: 0x0000cdff,
96 | mediumorchid: 0xba55d3ff,
97 | mediumpurple: 0x9370dbff,
98 | mediumseagreen: 0x3cb371ff,
99 | mediumslateblue: 0x7b68eeff,
100 | mediumspringgreen: 0x00fa9aff,
101 | mediumturquoise: 0x48d1ccff,
102 | mediumvioletred: 0xc71585ff,
103 | midnightblue: 0x191970ff,
104 | mintcream: 0xf5fffaff,
105 | mistyrose: 0xffe4e1ff,
106 | moccasin: 0xffe4b5ff,
107 | navajowhite: 0xffdeadff,
108 | navy: 0x000080ff,
109 | oldlace: 0xfdf5e6ff,
110 | olive: 0x808000ff,
111 | olivedrab: 0x6b8e23ff,
112 | orange: 0xffa500ff,
113 | orangered: 0xff4500ff,
114 | orchid: 0xda70d6ff,
115 | palegoldenrod: 0xeee8aaff,
116 | palegreen: 0x98fb98ff,
117 | paleturquoise: 0xafeeeeff,
118 | palevioletred: 0xdb7093ff,
119 | papayawhip: 0xffefd5ff,
120 | peachpuff: 0xffdab9ff,
121 | peru: 0xcd853fff,
122 | pink: 0xffc0cbff,
123 | plum: 0xdda0ddff,
124 | powderblue: 0xb0e0e6ff,
125 | purple: 0x800080ff,
126 | rebeccapurple: 0x663399ff,
127 | red: 0xff0000ff,
128 | rosybrown: 0xbc8f8fff,
129 | royalblue: 0x4169e1ff,
130 | saddlebrown: 0x8b4513ff,
131 | salmon: 0xfa8072ff,
132 | sandybrown: 0xf4a460ff,
133 | seagreen: 0x2e8b57ff,
134 | seashell: 0xfff5eeff,
135 | sienna: 0xa0522dff,
136 | silver: 0xc0c0c0ff,
137 | skyblue: 0x87ceebff,
138 | slateblue: 0x6a5acdff,
139 | slategray: 0x708090ff,
140 | slategrey: 0x708090ff,
141 | snow: 0xfffafaff,
142 | springgreen: 0x00ff7fff,
143 | steelblue: 0x4682b4ff,
144 | tan: 0xd2b48cff,
145 | teal: 0x008080ff,
146 | thistle: 0xd8bfd8ff,
147 | tomato: 0xff6347ff,
148 | turquoise: 0x40e0d0ff,
149 | violet: 0xee82eeff,
150 | wheat: 0xf5deb3ff,
151 | white: 0xffffffff,
152 | whitesmoke: 0xf5f5f5ff,
153 | yellow: 0xffff00ff,
154 | yellowgreen: 0x9acd32ff,
155 | }
156 |
--------------------------------------------------------------------------------
/spring/src/context.ts:
--------------------------------------------------------------------------------
1 | import { FluidValue } from "./fluids"
2 |
3 | export type TreeContext = {
4 | /**
5 | * Any animated values found when updating the payload of an `AnimatedObject`
6 | * are also added to this `Set` to be observed by an animated component.
7 | */
8 | dependencies: Set | null
9 | }
10 |
11 | export const TreeContext: TreeContext = { dependencies: null }
12 |
--------------------------------------------------------------------------------
/spring/src/createHost.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 |
3 | import type { JSX } from "solid-js";
4 | import { Animated } from "./animated";
5 | import { AnimatedObject as AnimatedObjectClass } from "./AnimatedObject";
6 | import { FluidProps, FluidValue } from "./fluids";
7 | import { is, Lookup, eachProp, Merge, NonObject } from "./utils";
8 | import { AnimatableComponent, withAnimated } from "./withAnimated";
9 |
10 | export interface HostConfig {
11 | /** Provide custom logic for native updates */
12 | applyAnimatedValues: (node: any, props: Lookup) => boolean | void;
13 | /** Wrap the `style` prop with an animated node */
14 | createAnimatedStyle: (style: Lookup) => Animated;
15 | /** Intercept props before they're passed to an animated component */
16 | getComponentProps: (props: Lookup) => typeof props;
17 | }
18 | type Angle = number | string;
19 | type Length = number | string;
20 |
21 | type TransformProps = {
22 | transform?: string;
23 | x?: Length;
24 | y?: Length;
25 | z?: Length;
26 | translate?: Length | readonly [Length, Length];
27 | translateX?: Length;
28 | translateY?: Length;
29 | translateZ?: Length;
30 | translate3d?: readonly [Length, Length, Length];
31 | rotate?: Angle;
32 | rotateX?: Angle;
33 | rotateY?: Angle;
34 | rotateZ?: Angle;
35 | rotate3d?: readonly [number, number, number, Angle];
36 | // Note: "string" is not really supported by "scale", but this lets us
37 | // spread React.CSSProperties into an animated style object.
38 | scale?: number | readonly [number, number] | string;
39 | scaleX?: number;
40 | scaleY?: number;
41 | scaleZ?: number;
42 | scale3d?: readonly [number, number, number];
43 | skew?: Angle | readonly [Angle, Angle];
44 | skewX?: Angle;
45 | skewY?: Angle;
46 | matrix?: readonly [number, number, number, number, number, number];
47 | matrix3d?: readonly [
48 | number, // a1
49 | number,
50 | number,
51 | number,
52 | number, // a2
53 | number,
54 | number,
55 | number,
56 | number, // a3
57 | number,
58 | number,
59 | number,
60 | number, // a4
61 | number,
62 | number,
63 | number
64 | ];
65 | };
66 |
67 | type CSSProperties = JSX.IntrinsicElements["a"]["style"];
68 | type StyleProps = Merge;
69 |
70 | // A stub type that gets replaced by @react-spring/web and others.
71 | export type WithAnimated = ((Component: any) => any) &
72 | {
73 | // (Component: AnimatableComponent): any;
74 | [K in keyof JSX.IntrinsicElements]: (
75 | props: AnimatedProps<
76 | Merge
77 | > &
78 | FluidProps<{
79 | scrollTop?: number;
80 | scrollLeft?: number;
81 | }>
82 | ) => JSX.Element;
83 | };
84 | /** The props of an `animated()` component */
85 | export type AnimatedProps = {
86 | [P in keyof Props]: P extends "ref" | "key"
87 | ? Props[P]
88 | : AnimatedProp;
89 | };
90 | // The animated prop value of a React element
91 | type AnimatedProp = [T, T] extends [infer T, infer DT]
92 | ? [DT] extends [never]
93 | ? never
94 | : DT extends void
95 | ? undefined
96 | : DT extends string | number
97 | ? DT | AnimatedLeaf
98 | : DT extends object
99 | ? [ValidStyleProps