Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x 1x 1x 1x 1x 5x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x | /** @import { Task } from '#client' */ /** @import { SpringOpts, SpringUpdateOpts, TickContext } from './private.js' */ /** @import { Spring } from './public.js' */ import { writable } from '../store/shared/index.js'; import { loop } from '../internal/client/loop.js'; import { raf } from '../internal/client/timing.js'; import { is_date } from './utils.js'; /** * @template T * @param {TickContext<T>} ctx * @param {T} last_value * @param {T} current_value * @param {T} target_value * @returns {T} */ function tick_spring(ctx, last_value, current_value, target_value) { if (typeof current_value === 'number' || is_date(current_value)) { // @ts-ignore const delta = target_value - current_value; // @ts-ignore const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0 const spring = ctx.opts.stiffness * delta; const damper = ctx.opts.damping * velocity; const acceleration = (spring - damper) * ctx.inv_mass; const d = (velocity + acceleration) * ctx.dt; if (Math.abs(d) < ctx.opts.precision && Math.abs(delta) < ctx.opts.precision) { return target_value; // settled } else { ctx.settled = false; // signal loop to keep ticking // @ts-ignore return is_date(current_value) ? new Date(current_value.getTime() + d) : current_value + d; } } else if (Array.isArray(current_value)) { // @ts-ignore return current_value.map((_, i) => // @ts-ignore tick_spring(ctx, last_value[i], current_value[i], target_value[i]) ); } else if (typeof current_value === 'object') { const next_value = {}; for (const k in current_value) { // @ts-ignore next_value[k] = tick_spring(ctx, last_value[k], current_value[k], target_value[k]); } // @ts-ignore return next_value; } else { throw new Error(`Cannot spring ${typeof current_value} values`); } } /** * The spring function in Svelte creates a store whose value is animated, with a motion that simulates the behavior of a spring. This means when the value changes, instead of transitioning at a steady rate, it "bounces" like a spring would, depending on the physics parameters provided. This adds a level of realism to the transitions and can enhance the user experience. * * @template [T=any] * @param {T} [value] * @param {SpringOpts} [opts] * @returns {Spring<T>} */ export function spring(value, opts = {}) { const store = writable(value); const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; /** @type {number} */ let last_time; /** @type {Task | null} */ let task; /** @type {object} */ let current_token; let last_value = /** @type {T} */ (value); let target_value = /** @type {T | undefined} */ (value); let inv_mass = 1; let inv_mass_recovery_rate = 0; let cancel_task = false; /** * @param {T} new_value * @param {SpringUpdateOpts} opts * @returns {Promise<void>} */ function set(new_value, opts = {}) { target_value = new_value; const token = (current_token = {}); if (value == null || opts.hard || (spring.stiffness >= 1 && spring.damping >= 1)) { cancel_task = true; // cancel any running animation last_time = raf.now(); last_value = new_value; store.set((value = target_value)); return Promise.resolve(); } else if (opts.soft) { const rate = opts.soft === true ? 0.5 : +opts.soft; inv_mass_recovery_rate = 1 / (rate * 60); inv_mass = 0; // infinite mass, unaffected by spring forces } if (!task) { last_time = raf.now(); cancel_task = false; task = loop((now) => { if (cancel_task) { cancel_task = false; task = null; return false; } inv_mass = Math.min(inv_mass + inv_mass_recovery_rate, 1); /** @type {TickContext<T>} */ const ctx = { inv_mass, opts: spring, settled: true, dt: ((now - last_time) * 60) / 1000 }; // @ts-ignore const next_value = tick_spring(ctx, last_value, value, target_value); last_time = now; last_value = /** @type {T} */ (value); store.set((value = /** @type {T} */ (next_value))); if (ctx.settled) { task = null; } return !ctx.settled; }); } return new Promise((fulfil) => { /** @type {Task} */ (task).promise.then(() => { if (token === current_token) fulfil(); }); }); } /** @type {Spring<T>} */ const spring = { set, update: (fn, opts) => set(fn(/** @type {T} */ (target_value), /** @type {T} */ (value)), opts), subscribe: store.subscribe, stiffness, damping, precision }; return spring; } |