coily 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vue.js CHANGED
@@ -1,4 +1,5 @@
1
- import { a as SpringDefinition, n as SpringSourceSymbol, r as isSpringSource, t as createSpringSystem } from "./system-BXxAHM2a.js";
1
+ import { a as SpringSourceSymbol, n as CompositeSpring, o as isSpringSource, r as SpringDefinition, t as createSpringSystem } from "./system-WejqGkfv.js";
2
+ import { o as invariant } from "./solver-CAXuxdKj.js";
2
3
  import { computed, customRef, defineComponent, getCurrentInstance, getCurrentScope, inject, onBeforeUnmount, onMounted, onScopeDispose, provide, toRefs, toValue, watchSyncEffect } from "vue";
3
4
  //#region src/vue/local.ts
4
5
  const localProvides = /* @__PURE__ */ new WeakMap();
@@ -26,25 +27,24 @@ function injectLocal(key) {
26
27
  //#region src/vue/system.ts
27
28
  const SpringSystemKey = Symbol.for("coily/spring-system");
28
29
  /**
29
- * Supplies a spring system you created and manage yourself — coily never
30
- * starts or stops it. With `app`, the system is installed app-wide
31
- * (what the Nuxt module does); without, it is provided to the current
32
- * component and its descendants, including this component's own
33
- * `useSpring` calls. Mostly useful in tests.
30
+ * Provides a spring system you create and manage yourself. Coily never
31
+ * starts or stops it. With `app`, the whole app gets it, as with the
32
+ * Nuxt module. Without `app`, call it in `setup()`, and it reaches the
33
+ * current component, including its own `useSpring` calls, and its
34
+ * descendants. Mostly useful in tests.
34
35
  */
35
36
  function provideSpringSystem(system, app) {
36
37
  if (app) app.provide(SpringSystemKey, system);
37
38
  else provideLocal(SpringSystemKey, system);
38
39
  }
39
40
  /**
40
- * Returns the spring system provided by this component or an ancestor,
41
- * creating one when none exists. A created system is provided to this
42
- * component and its descendants and started/stopped with the component's
43
- * mount lifecycle.
41
+ * Returns the spring system this component or an ancestor provides, or
42
+ * creates one. A created system is provided to this component and its
43
+ * descendants, starts when the component mounts, and is disposed before
44
+ * it unmounts.
44
45
  *
45
- * Idempotent: call it near the root and again in any descendant.
46
- * `options` only applies when the call actually creates a system.
47
- * Throws outside `setup()`.
46
+ * A call below a provider returns the provided system, and `options`
47
+ * applies only when the call creates one. Throws outside `setup()`.
48
48
  */
49
49
  function useSpringSystem(options) {
50
50
  if (!getCurrentInstance()) throw new Error("useSpringSystem must be called inside setup()");
@@ -53,7 +53,7 @@ function useSpringSystem(options) {
53
53
  const system = createSpringSystem(options);
54
54
  provideLocal(SpringSystemKey, system);
55
55
  onMounted(() => system.start());
56
- onBeforeUnmount(() => system.stop());
56
+ onBeforeUnmount(() => system.dispose());
57
57
  return system;
58
58
  }
59
59
  //#endregion
@@ -83,6 +83,9 @@ function createReactiveSpringRef(spring, config, instanceKey) {
83
83
  triggerVelocity?.();
84
84
  triggerTimeRemaining?.();
85
85
  });
86
+ const triggerRemaining = () => triggerTimeRemaining?.();
87
+ spring.onStart(triggerRemaining);
88
+ spring.onStop(triggerRemaining);
86
89
  const value = divergentRef((track, trigger) => ({
87
90
  get() {
88
91
  triggerValue ??= trigger;
@@ -142,27 +145,38 @@ const CompositeSpringInstanceKey = Symbol("composite-spring");
142
145
  function hasCompositeSpringInstance(value) {
143
146
  return typeof value === "object" && value !== null && CompositeSpringInstanceKey in value;
144
147
  }
148
+ /** The composite that `value` leads with, or `undefined` when it is not a `CompositeLeader`. */
149
+ function compositeLeaderOf(value) {
150
+ if (value instanceof CompositeSpring) return value;
151
+ if (hasCompositeSpringInstance(value)) return value[CompositeSpringInstanceKey];
152
+ }
153
+ const SHAPED_SOURCE = "useSpring follows a scalar SpringSource or a composite spring, not a source of another shape. Derive a scalar source with mapSpring";
145
154
  function resolveConfigShape(config) {
146
155
  return computed(() => toValue(config));
147
156
  }
148
- function createCompositeSpringRef(value, config, options) {
157
+ /**
158
+ * Creates the composite behind an object `useSpring`. `target` resolves
159
+ * to a shape to animate toward or a `CompositeLeader` to follow, and it
160
+ * can switch between them.
161
+ */
162
+ function createCompositeSpringRef(target, config, options) {
149
163
  const system = injectSpringSystem();
150
164
  const resolvedConfig = resolveConfigShape(config);
151
- const spring = system.createSpring(toValue(value), resolvedConfig.value, options);
165
+ const resolve = () => {
166
+ const next = toValue(target);
167
+ const leader = compositeLeaderOf(next);
168
+ if (leader !== void 0) return leader;
169
+ invariant(!isSpringSource(next), SHAPED_SOURCE);
170
+ return next;
171
+ };
172
+ const initial = resolve();
173
+ const spring = system.createSpring(initial instanceof CompositeSpring ? initial.value : initial, resolvedConfig.value, options);
152
174
  const ref = createReactiveSpringRef(spring, resolvedConfig, CompositeSpringInstanceKey);
153
175
  watchSyncEffect(() => {
154
- spring.target = toValue(value);
176
+ spring.target = resolve();
155
177
  });
156
178
  return ref;
157
179
  }
158
- function createLinkedCompositeSpringRef(leaderRef, config, options) {
159
- const system = injectSpringSystem();
160
- const leader = leaderRef[CompositeSpringInstanceKey];
161
- const resolvedConfig = resolveConfigShape(config);
162
- const spring = system.createSpring(leader.value, resolvedConfig.value, options);
163
- spring.target = leader;
164
- return createReactiveSpringRef(spring, resolvedConfig, CompositeSpringInstanceKey);
165
- }
166
180
  //#endregion
167
181
  //#region src/vue/spring.ts
168
182
  const SpringInstanceKey = Symbol("spring");
@@ -171,10 +185,10 @@ function hasSpringInstance(value) {
171
185
  }
172
186
  function useSpring(target, config, options) {
173
187
  if (hasSpringInstance(target)) return createLinkedSpringRef(target, config, options);
174
- if (hasCompositeSpringInstance(target)) return createLinkedCompositeSpringRef(target, config, options);
175
- const resolved = toValue(target);
176
- if (typeof resolved === "number" || isSpringSource(resolved)) return createSpringRef(target, config, options);
177
- return createCompositeSpringRef(target, config, options);
188
+ const follow = isSpringSource(target) ? () => target : target;
189
+ const resolved = toValue(follow);
190
+ if (typeof resolved === "number" || isSpringSource(resolved) && typeof resolved[SpringSourceSymbol].value === "number") return createSpringRef(follow, config, options);
191
+ return createCompositeSpringRef(follow, config, options);
178
192
  }
179
193
  function createSpringRef(target, config, options) {
180
194
  const system = injectSpringSystem();
@@ -194,16 +208,121 @@ function createLinkedSpringRef(leaderRef, config, options) {
194
208
  return createReactiveSpringRef(system.createSpring(leader, resolvedConfig.value, options), resolvedConfig, SpringInstanceKey);
195
209
  }
196
210
  //#endregion
211
+ //#region src/vue/ramp.ts
212
+ /**
213
+ * Creates a ramp and returns it as a `RampRef`. A ramp moves toward
214
+ * `target` in a straight line at `rate` value units per second, and
215
+ * stops there. A target of `Infinity` or `-Infinity` makes it endless.
216
+ * A spring following the ref with the default `arrival` tracks its
217
+ * steady motion exactly, with no frame-to-frame steps. On an endless
218
+ * ramp, that spring lags behind like a heavy spinning object.
219
+ *
220
+ * A new `target` sends the ramp there from its current value. A new
221
+ * `rate` applies from the current value on.
222
+ *
223
+ * Call it during `setup()` below a provided spring system (the coily/nuxt
224
+ * module, or `useSpringSystem()` in an ancestor). It throws without one.
225
+ * The ramp is disposed with the component's scope.
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * const angle = useRamp(Infinity, 270, { from: 0 })
230
+ * const ring = useSpring(angle, { duration: 300, dampingRatio: 1 })
231
+ * // template: <div :style="{ rotate: `${ring}deg` }" />
232
+ *
233
+ * const heading = ref(0)
234
+ * const slew = useRamp(heading, 400) // turns toward heading at 400 deg/s
235
+ * ```
236
+ */
237
+ function useRamp(target, rate, options) {
238
+ const system = injectSpringSystem();
239
+ const initial = toValue(target);
240
+ const from = options?.from ?? initial;
241
+ invariant(Number.isFinite(from), "useRamp needs a finite `from` when its initial target is infinite");
242
+ const ramp = system.createRamp(from, {
243
+ rate: toValue(rate),
244
+ target: initial,
245
+ purpose: options?.purpose
246
+ });
247
+ watchSyncEffect(() => {
248
+ ramp.target = toValue(target);
249
+ });
250
+ watchSyncEffect(() => {
251
+ ramp.rate = toValue(rate);
252
+ });
253
+ const ref = createRampRef(ramp);
254
+ if (getCurrentScope()) onScopeDispose(() => ramp.dispose());
255
+ return ref;
256
+ }
257
+ function createRampRef(ramp) {
258
+ const triggers = /* @__PURE__ */ new Set();
259
+ let unread = false;
260
+ let unsubscribe = null;
261
+ const triggerAll = () => {
262
+ for (const trigger of triggers) trigger();
263
+ };
264
+ const onUpdate = () => {
265
+ if (unread) {
266
+ unsubscribe();
267
+ unsubscribe = null;
268
+ return;
269
+ }
270
+ unread = true;
271
+ triggerAll();
272
+ };
273
+ const read = (trigger) => {
274
+ triggers.add(trigger);
275
+ unread = false;
276
+ if (unsubscribe === null) {
277
+ const offUpdate = ramp.onUpdate(onUpdate);
278
+ const offStart = ramp.onStart(triggerAll);
279
+ const offStop = ramp.onStop(triggerAll);
280
+ unsubscribe = () => {
281
+ offUpdate();
282
+ offStart();
283
+ offStop();
284
+ };
285
+ }
286
+ };
287
+ const value = customRef((track, trigger) => ({
288
+ get() {
289
+ track();
290
+ read(trigger);
291
+ return ramp.value;
292
+ },
293
+ set(next) {
294
+ ramp.value = next;
295
+ trigger();
296
+ }
297
+ }));
298
+ const readOnly = (get) => customRef((track, trigger) => ({
299
+ get() {
300
+ track();
301
+ read(trigger);
302
+ return get();
303
+ },
304
+ set() {}
305
+ }));
306
+ const ref = Object.assign(value, {
307
+ velocity: readOnly(() => ramp.velocity),
308
+ timeRemaining: readOnly(() => ramp.timeRemaining),
309
+ isResting: readOnly(() => ramp.isResting),
310
+ jumpTo: (next) => ramp.jumpTo(next)
311
+ });
312
+ Object.defineProperty(ref, "settled", { get: () => ramp.settled });
313
+ Object.defineProperty(ref, SpringSourceSymbol, { value: ramp[SpringSourceSymbol] });
314
+ return ref;
315
+ }
316
+ //#endregion
197
317
  //#region src/vue/pool.ts
198
318
  /**
199
- * Returns spring factories bound to the provided spring system, with
200
- * lifetimes tied to the current effect scope: every pooled spring still
201
- * alive at scope teardown is disposed, so leaked motions are
202
- * structurally impossible.
319
+ * Returns spring factories bound to the provided spring system. When
320
+ * the current effect scope stops, the pool disposes every pooled spring
321
+ * still alive.
203
322
  *
204
- * Call it during `setup()` — or inside an effect scope — below a
205
- * provided spring system. Outside a scope, pooled springs are only
206
- * disposed manually.
323
+ * Call it during `setup()` or inside an effect scope, below a provided
324
+ * spring system. It throws without one. Outside a scope, you must
325
+ * dispose each pooled spring yourself.
207
326
  */
208
327
  function useSpringPool() {
209
328
  const system = injectSpringSystem();
@@ -225,13 +344,14 @@ function useSpringPool() {
225
344
  //#endregion
226
345
  //#region src/vue/component.ts
227
346
  /**
228
- * Renderless component exposing one springed number through its default
229
- * slot — the template-only sibling of `useSpring`. Must sit below a
230
- * provided spring system.
347
+ * A renderless component that passes one spring's state to its default
348
+ * slot: the template-only form of `useSpring`. It must render below a
349
+ * provided spring system, and throws otherwise. A template ref to it
350
+ * reads the same fields as the slot scope.
231
351
  *
232
352
  * @example
233
353
  * ```vue
234
- * <SpringValue :target="expanded ? 1 : 0" purpose="appearance" v-slot="{ value }">
354
+ * <SpringValue :target="expanded ? 1 : 0" purpose="nonmotion" v-slot="{ value }">
235
355
  * <div :style="{ opacity: value }" />
236
356
  * </SpringValue>
237
357
  * ```
@@ -262,4 +382,4 @@ const SpringValue = defineComponent((props, { slots, expose }) => {
262
382
  ]
263
383
  });
264
384
  //#endregion
265
- export { SpringValue, provideSpringSystem, useSpring, useSpringPool, useSpringSystem };
385
+ export { SpringValue, provideSpringSystem, useRamp, useSpring, useSpringPool, useSpringSystem };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coily",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Simulate values using spring physics",
5
5
  "homepage": "https://github.com/tkofh/coily#readme",
6
6
  "bugs": {
@@ -19,6 +19,7 @@
19
19
  "sideEffects": false,
20
20
  "exports": {
21
21
  ".": "./dist/index.js",
22
+ "./css": "./dist/css.js",
22
23
  "./nuxt": "./dist/nuxt.js",
23
24
  "./vue": "./dist/vue.js",
24
25
  "./package.json": "./package.json"