solid-drift 0.9.0 → 0.11.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/README.md +54 -0
- package/dist/flip.d.ts +73 -0
- package/dist/flip.js +157 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/typography.d.ts +39 -0
- package/dist/typography.js +45 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -467,6 +467,41 @@ const shuffle = () =>
|
|
|
467
467
|
|
|
468
468
|
Returns `{ stop, finished }`: `stop()` halts mid-flight and restores the original transform. SSR-safe and reduced-motion safe: the mutation runs with no animation. If the element does not move, no animation runs either.
|
|
469
469
|
|
|
470
|
+
### `createSharedLayout(ref, options)`
|
|
471
|
+
|
|
472
|
+
Shared-element transition across mounts, the `layoutId` magic. Give the same `id` to elements that represent the same thing in different places: a tab pill, a card that opens into a detail view, a thumbnail that becomes a hero image. When the old element unmounts its rect is remembered; when the new one mounts it starts at the donor's rect and springs home, sliding and morphing into place. This is cross-mount FLIP: where `animateFlip` inverts the delta around a mutation on one element, `createSharedLayout` inverts the delta between two elements that share an identity. One active carrier per id.
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
import { createSharedLayout } from "solid-drift"
|
|
476
|
+
|
|
477
|
+
function TabPill(props: { id: string }) {
|
|
478
|
+
let pill!: HTMLDivElement
|
|
479
|
+
const { x, y, scaleX, scaleY } = createSharedLayout(() => pill, {
|
|
480
|
+
id: props.id,
|
|
481
|
+
})
|
|
482
|
+
return (
|
|
483
|
+
<div
|
|
484
|
+
ref={pill}
|
|
485
|
+
style={{
|
|
486
|
+
transform: `translate(${x()}px, ${y()}px) scale(${scaleX()}, ${scaleY()})`,
|
|
487
|
+
}}
|
|
488
|
+
/>
|
|
489
|
+
)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Only one pill is mounted at a time; it glides between tabs.
|
|
493
|
+
<Show when={tab() === "a"}><TabPill id="pill" /></Show>
|
|
494
|
+
<Show when={tab() === "b"}><TabPill id="pill" /></Show>
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
| Option | Default | Description |
|
|
498
|
+
| -------- | -------------------------------- | --------------------------------------------------------------- |
|
|
499
|
+
| `id` | required | Shared identity; elements with the same id hand off to each other |
|
|
500
|
+
| `spring` | `{ stiffness: 260, damping: 30 }` | Spring physics for the handoff flight |
|
|
501
|
+
| `scale` | `true` | Also morph the size delta as scale |
|
|
502
|
+
|
|
503
|
+
Returns `{ x, y, scaleX, scaleY, flying }`: the corrective transform and `flying`, true while the handoff runs. SSR-safe and reduced-motion safe: the element simply appears, with no flight.
|
|
504
|
+
|
|
470
505
|
### `springPresets`
|
|
471
506
|
|
|
472
507
|
Named spring configurations for common feels. Spread into `createSpring`, `createMagnetic`, or `createTilt` options.
|
|
@@ -741,6 +776,25 @@ createTextWave(() => headline, {
|
|
|
741
776
|
|
|
742
777
|
Options: `amplitude`, `wavelength` (characters per wave), `period`, `tilt` (default true), `progress`. Returns `{ stop }`. Under reduced motion the text sits still.
|
|
743
778
|
|
|
779
|
+
### `createCountUp(source, options?)`
|
|
780
|
+
|
|
781
|
+
A signal that counts toward a source number with an eased tween, rendering as a formatted string: dashboard stats, prices, scores. When the source changes mid-count the tween retargets from the current displayed value, with no snapping.
|
|
782
|
+
|
|
783
|
+
```tsx
|
|
784
|
+
import { createCountUp } from "solid-drift"
|
|
785
|
+
|
|
786
|
+
const [revenue, setRevenue] = createSignal(0)
|
|
787
|
+
const display = createCountUp(revenue, {
|
|
788
|
+
decimals: 2,
|
|
789
|
+
prefix: "$",
|
|
790
|
+
separator: ",",
|
|
791
|
+
})
|
|
792
|
+
<div>{display()}</div> // "$1,234.50" gliding up from "$0.00"
|
|
793
|
+
setRevenue(1234.5)
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
Options: `decimals` (default 0), `duration` (ms, default 1000), `easing` (default `"easeOutExpo"`), `prefix` (default `""`), `suffix` (default `""`), `separator` (thousands separator, default `""`). Returns an accessor of the formatted string. SSR-safe: renders the formatted source value. Under reduced motion the text jumps straight to each new value.
|
|
797
|
+
|
|
744
798
|
### `createKineticType(ref, options?)`
|
|
745
799
|
|
|
746
800
|
Kinetic typography: each character (or word) flies in with position, blur, scale, and opacity, staggered for that showreel title feel. One master clock drives every unit, so a 40-character headline costs a single rAF task.
|
package/dist/flip.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type Accessor } from "solid-js";
|
|
1
2
|
import { type AnimateOptions, type AnimationControls } from "./animate.js";
|
|
3
|
+
import type { SpringOptions } from "./spring.js";
|
|
2
4
|
export interface FlipOptions {
|
|
3
5
|
/** Duration in milliseconds. Default 400. */
|
|
4
6
|
duration?: number;
|
|
@@ -42,3 +44,74 @@ export interface FlipOptions {
|
|
|
42
44
|
* ```
|
|
43
45
|
*/
|
|
44
46
|
export declare function animateFlip(ref: () => Element | null | undefined, mutate: () => void, options?: FlipOptions): AnimationControls;
|
|
47
|
+
export interface SharedLayoutOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Shared identity. Elements with the same id hand off to each other
|
|
50
|
+
* across mounts: the newcomer flies in from the donor's last rect.
|
|
51
|
+
*/
|
|
52
|
+
id: string;
|
|
53
|
+
/**
|
|
54
|
+
* Spring physics for the handoff flight. Default
|
|
55
|
+
* `{ stiffness: 260, damping: 30 }`.
|
|
56
|
+
*/
|
|
57
|
+
spring?: SpringOptions;
|
|
58
|
+
/**
|
|
59
|
+
* Also morph the size delta as scale, so growing or shrinking elements
|
|
60
|
+
* morph instead of just sliding. Default true.
|
|
61
|
+
*/
|
|
62
|
+
scale?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface SharedLayoutResult {
|
|
65
|
+
/** Horizontal corrective offset in pixels. */
|
|
66
|
+
x: Accessor<number>;
|
|
67
|
+
/** Vertical corrective offset in pixels. */
|
|
68
|
+
y: Accessor<number>;
|
|
69
|
+
/** Horizontal corrective scale. */
|
|
70
|
+
scaleX: Accessor<number>;
|
|
71
|
+
/** Vertical corrective scale. */
|
|
72
|
+
scaleY: Accessor<number>;
|
|
73
|
+
/** True while the handoff flight is running. */
|
|
74
|
+
flying: Accessor<boolean>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Shared-element transition across mounts, the `layoutId` magic.
|
|
78
|
+
*
|
|
79
|
+
* Give the same `id` to elements that represent the same thing in
|
|
80
|
+
* different places: a tab pill, a card that opens into a detail view,
|
|
81
|
+
* a thumbnail that becomes a hero image. When the old element unmounts
|
|
82
|
+
* its rect is remembered; when the new one mounts it starts at the
|
|
83
|
+
* donor's rect and springs home, sliding and morphing into place.
|
|
84
|
+
*
|
|
85
|
+
* This is cross-mount FLIP: where `animateFlip` inverts the delta
|
|
86
|
+
* around a mutation on one element, `createSharedLayout` inverts the
|
|
87
|
+
* delta between two elements that share an identity. Bind the returned
|
|
88
|
+
* values as a transform on the mounted element.
|
|
89
|
+
*
|
|
90
|
+
* SSR-safe and reduced-motion safe: the element simply appears, with
|
|
91
|
+
* no flight. One active carrier per id: the first mount consumes the
|
|
92
|
+
* parked donor rect.
|
|
93
|
+
*
|
|
94
|
+
* ```tsx
|
|
95
|
+
* import { createSharedLayout } from "solid-drift"
|
|
96
|
+
*
|
|
97
|
+
* function TabPill(props: { id: string }) {
|
|
98
|
+
* let pill!: HTMLDivElement
|
|
99
|
+
* const { x, y, scaleX, scaleY } = createSharedLayout(() => pill, {
|
|
100
|
+
* id: props.id,
|
|
101
|
+
* })
|
|
102
|
+
* return (
|
|
103
|
+
* <div
|
|
104
|
+
* ref={pill}
|
|
105
|
+
* style={{
|
|
106
|
+
* transform: `translate(${x()}px, ${y()}px) scale(${scaleX()}, ${scaleY()})`,
|
|
107
|
+
* }}
|
|
108
|
+
* />
|
|
109
|
+
* )
|
|
110
|
+
* }
|
|
111
|
+
*
|
|
112
|
+
* // Only one pill is mounted at a time; it glides between tabs.
|
|
113
|
+
* <Show when={tab() === "a"}><TabPill id="pill" /></Show>
|
|
114
|
+
* <Show when={tab() === "b"}><TabPill id="pill" /></Show>
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function createSharedLayout(ref: () => Element | null | undefined, options: SharedLayoutOptions): SharedLayoutResult;
|
package/dist/flip.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { createEffect, createSignal, onCleanup, } from "solid-js";
|
|
1
2
|
import { animate, } from "./animate.js";
|
|
3
|
+
import { now, schedule } from "./engine.js";
|
|
2
4
|
import { prefersReducedMotion } from "./reduced-motion.js";
|
|
3
5
|
/**
|
|
4
6
|
* FLIP layout animation around a DOM mutation.
|
|
@@ -106,3 +108,158 @@ export function animateFlip(ref, mutate, options = {}) {
|
|
|
106
108
|
}));
|
|
107
109
|
return controls;
|
|
108
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Donor registry for shared-element handoffs. When an element carrying a
|
|
113
|
+
* shared id unmounts, its last rect is parked here; the next element that
|
|
114
|
+
* mounts with the same id consumes it and flies in from that rect.
|
|
115
|
+
* One active carrier per id: consumption deletes the entry.
|
|
116
|
+
*/
|
|
117
|
+
const donors = new Map();
|
|
118
|
+
function rectOf(el) {
|
|
119
|
+
const r = el.getBoundingClientRect();
|
|
120
|
+
return { left: r.left, top: r.top, width: r.width, height: r.height };
|
|
121
|
+
}
|
|
122
|
+
const zero = () => 0;
|
|
123
|
+
const one = () => 1;
|
|
124
|
+
const no = () => false;
|
|
125
|
+
/**
|
|
126
|
+
* Shared-element transition across mounts, the `layoutId` magic.
|
|
127
|
+
*
|
|
128
|
+
* Give the same `id` to elements that represent the same thing in
|
|
129
|
+
* different places: a tab pill, a card that opens into a detail view,
|
|
130
|
+
* a thumbnail that becomes a hero image. When the old element unmounts
|
|
131
|
+
* its rect is remembered; when the new one mounts it starts at the
|
|
132
|
+
* donor's rect and springs home, sliding and morphing into place.
|
|
133
|
+
*
|
|
134
|
+
* This is cross-mount FLIP: where `animateFlip` inverts the delta
|
|
135
|
+
* around a mutation on one element, `createSharedLayout` inverts the
|
|
136
|
+
* delta between two elements that share an identity. Bind the returned
|
|
137
|
+
* values as a transform on the mounted element.
|
|
138
|
+
*
|
|
139
|
+
* SSR-safe and reduced-motion safe: the element simply appears, with
|
|
140
|
+
* no flight. One active carrier per id: the first mount consumes the
|
|
141
|
+
* parked donor rect.
|
|
142
|
+
*
|
|
143
|
+
* ```tsx
|
|
144
|
+
* import { createSharedLayout } from "solid-drift"
|
|
145
|
+
*
|
|
146
|
+
* function TabPill(props: { id: string }) {
|
|
147
|
+
* let pill!: HTMLDivElement
|
|
148
|
+
* const { x, y, scaleX, scaleY } = createSharedLayout(() => pill, {
|
|
149
|
+
* id: props.id,
|
|
150
|
+
* })
|
|
151
|
+
* return (
|
|
152
|
+
* <div
|
|
153
|
+
* ref={pill}
|
|
154
|
+
* style={{
|
|
155
|
+
* transform: `translate(${x()}px, ${y()}px) scale(${scaleX()}, ${scaleY()})`,
|
|
156
|
+
* }}
|
|
157
|
+
* />
|
|
158
|
+
* )
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* // Only one pill is mounted at a time; it glides between tabs.
|
|
162
|
+
* <Show when={tab() === "a"}><TabPill id="pill" /></Show>
|
|
163
|
+
* <Show when={tab() === "b"}><TabPill id="pill" /></Show>
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export function createSharedLayout(ref, options) {
|
|
167
|
+
const { id, spring = {}, scale = true } = options;
|
|
168
|
+
if (typeof window === "undefined") {
|
|
169
|
+
return { x: zero, y: zero, scaleX: one, scaleY: one, flying: no };
|
|
170
|
+
}
|
|
171
|
+
const [x, setX] = createSignal(0);
|
|
172
|
+
const [y, setY] = createSignal(0);
|
|
173
|
+
const [scaleX, setScaleX] = createSignal(1);
|
|
174
|
+
const [scaleY, setScaleY] = createSignal(1);
|
|
175
|
+
const [flying, setFlying] = createSignal(false);
|
|
176
|
+
let cancelFlight = null;
|
|
177
|
+
const stopFlight = () => {
|
|
178
|
+
cancelFlight?.();
|
|
179
|
+
cancelFlight = null;
|
|
180
|
+
};
|
|
181
|
+
/** Spring flight from the inverted delta back to identity. */
|
|
182
|
+
const flyFrom = (dx, dy, sx, sy) => {
|
|
183
|
+
const { stiffness = 260, damping = 30 } = spring;
|
|
184
|
+
let cx = dx;
|
|
185
|
+
let cy = dy;
|
|
186
|
+
let csx = sx;
|
|
187
|
+
let csy = sy;
|
|
188
|
+
let vx = 0;
|
|
189
|
+
let vy = 0;
|
|
190
|
+
let vsx = 0;
|
|
191
|
+
let vsy = 0;
|
|
192
|
+
let last = now();
|
|
193
|
+
setX(cx);
|
|
194
|
+
setY(cy);
|
|
195
|
+
setScaleX(csx);
|
|
196
|
+
setScaleY(csy);
|
|
197
|
+
setFlying(true);
|
|
198
|
+
stopFlight();
|
|
199
|
+
const task = (t) => {
|
|
200
|
+
const dt = Math.min(Math.max((t - last) / 1000, 0), 0.064);
|
|
201
|
+
last = t;
|
|
202
|
+
// Semi-implicit Euler per channel, same integrator as createSpring.
|
|
203
|
+
vx += (-stiffness * cx - damping * vx) * dt;
|
|
204
|
+
vy += (-stiffness * cy - damping * vy) * dt;
|
|
205
|
+
vsx += (-stiffness * (csx - 1) - damping * vsx) * dt;
|
|
206
|
+
vsy += (-stiffness * (csy - 1) - damping * vsy) * dt;
|
|
207
|
+
cx += vx * dt;
|
|
208
|
+
cy += vy * dt;
|
|
209
|
+
csx += vsx * dt;
|
|
210
|
+
csy += vsy * dt;
|
|
211
|
+
setX(cx);
|
|
212
|
+
setY(cy);
|
|
213
|
+
setScaleX(csx);
|
|
214
|
+
setScaleY(csy);
|
|
215
|
+
const settled = Math.hypot(cx, cy) < 0.5 &&
|
|
216
|
+
Math.abs(csx - 1) < 0.003 &&
|
|
217
|
+
Math.abs(csy - 1) < 0.003 &&
|
|
218
|
+
Math.hypot(vx, vy) < 20 &&
|
|
219
|
+
Math.hypot(vsx, vsy) < 0.05;
|
|
220
|
+
if (settled) {
|
|
221
|
+
setX(0);
|
|
222
|
+
setY(0);
|
|
223
|
+
setScaleX(1);
|
|
224
|
+
setScaleY(1);
|
|
225
|
+
setFlying(false);
|
|
226
|
+
cancelFlight = null;
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
};
|
|
231
|
+
cancelFlight = schedule(task);
|
|
232
|
+
};
|
|
233
|
+
// Late-bound refs (Solid assigns `ref` after mount) still hand off.
|
|
234
|
+
createEffect(() => {
|
|
235
|
+
const el = ref();
|
|
236
|
+
if (!el)
|
|
237
|
+
return;
|
|
238
|
+
const mountRect = rectOf(el);
|
|
239
|
+
const donor = donors.get(id);
|
|
240
|
+
donors.delete(id);
|
|
241
|
+
if (donor && !prefersReducedMotion()) {
|
|
242
|
+
const own = mountRect;
|
|
243
|
+
const dx = donor.left - own.left;
|
|
244
|
+
const dy = donor.top - own.top;
|
|
245
|
+
const sx = scale && own.width > 0 ? donor.width / own.width : 1;
|
|
246
|
+
const sy = scale && own.height > 0 ? donor.height / own.height : 1;
|
|
247
|
+
const moved = Math.abs(dx) >= 0.5 ||
|
|
248
|
+
Math.abs(dy) >= 0.5 ||
|
|
249
|
+
Math.abs(sx - 1) >= 0.002 ||
|
|
250
|
+
Math.abs(sy - 1) >= 0.002;
|
|
251
|
+
if (moved)
|
|
252
|
+
flyFrom(dx, dy, sx, sy);
|
|
253
|
+
}
|
|
254
|
+
onCleanup(() => {
|
|
255
|
+
// In <Show>/<Switch>/&& branches the cleanup runs while the node
|
|
256
|
+
// is still attached, so this is the live rect. If the node was
|
|
257
|
+
// already detached (all-zero rect), keep the mount snapshot.
|
|
258
|
+
const r = rectOf(el);
|
|
259
|
+
const valid = r.width > 0 || r.height > 0 || r.left !== 0 || r.top !== 0;
|
|
260
|
+
donors.set(id, valid ? r : mountRect);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
onCleanup(() => stopFlight());
|
|
264
|
+
return { x, y, scaleX, scaleY, flying };
|
|
265
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,10 +19,10 @@ export { createVelocity, type VelocityOptions } from "./velocity.js";
|
|
|
19
19
|
export { createMagnetic, type MagneticOptions, type MagneticResult, createTilt, type TiltOptions, type TiltResult, } from "./pointer.js";
|
|
20
20
|
export { createTrail, type TrailOptions } from "./trail.js";
|
|
21
21
|
export { createTimeline, type TimelineStep, type TimelineStatus, type TimelineControls, } from "./timeline.js";
|
|
22
|
-
export { animateFlip, type FlipOptions } from "./flip.js";
|
|
22
|
+
export { animateFlip, type FlipOptions, createSharedLayout, type SharedLayoutOptions, type SharedLayoutResult, } from "./flip.js";
|
|
23
23
|
export { createSquashStretch, type SquashStretchOptions, type SquashStretchResult, createFollowThrough, type FollowThroughOptions, createAnticipation, type AnticipationOptions, createWobble, type WobbleOptions, type WobbleResult, } from "./cartoon.js";
|
|
24
24
|
export { createGravity, type GravityOptions, type GravityResult, createPendulum, type PendulumOptions, type PendulumResult, createFling, type FlingOptions, type FlingResult, } from "./physics.js";
|
|
25
|
-
export { createFontSwap, type FontSwapOptions, type FontSwapResult, createTyping, type TypingOptions, type TypingResult, createTextPhysics, type TextPhysicsOptions, type TextPhysicsResult, createTextTunnel, type TextTunnelOptions, type TextTunnelResult, createTextCutout, type TextCutoutOptions, createTextGradient, type TextGradientOptions, createTextScramble, type TextScrambleOptions, type TextScrambleResult, createTextWave, type TextWaveOptions, } from "./typography.js";
|
|
25
|
+
export { createFontSwap, type FontSwapOptions, type FontSwapResult, createTyping, type TypingOptions, type TypingResult, createTextPhysics, type TextPhysicsOptions, type TextPhysicsResult, createTextTunnel, type TextTunnelOptions, type TextTunnelResult, createTextCutout, type TextCutoutOptions, createTextGradient, type TextGradientOptions, createTextScramble, type TextScrambleOptions, type TextScrambleResult, createTextWave, type TextWaveOptions, createCountUp, type CountUpOptions, } from "./typography.js";
|
|
26
26
|
export { easings, cubicBezier, linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeOutExpo, easeOutBack, easeInBack, easeInOutBack, easeOutElastic, easeOutBounce, resolveEasing, type Easing, type EasingName, } from "./easing.js";
|
|
27
27
|
export { createKineticType, createScenePlayer, createShowreel, createCamera, createColorShift, createTransition, createBeat, createBeatCuts, } from "./motion.js";
|
|
28
28
|
export type { KineticTypeFrom, KineticTypeOptions, KineticTypeStatus, KineticTypeControls, MotionScene, ScenePlayerStatus, ScenePlayerControls, ShowreelScene, ShowreelSceneKind, CameraKeyframe, CameraOptions, ColorShiftOptions, ColorShiftStatus, ColorShiftControls, TransitionType, TransitionDirection, TransitionOptions, TransitionLayerStyle, TransitionStatus, TransitionControls, BeatOptions, BeatStatus, BeatControls, BeatCutOptions, } from "./motion.js";
|
package/dist/index.js
CHANGED
|
@@ -19,10 +19,10 @@ export { createVelocity } from "./velocity.js";
|
|
|
19
19
|
export { createMagnetic, createTilt, } from "./pointer.js";
|
|
20
20
|
export { createTrail } from "./trail.js";
|
|
21
21
|
export { createTimeline, } from "./timeline.js";
|
|
22
|
-
export { animateFlip } from "./flip.js";
|
|
22
|
+
export { animateFlip, createSharedLayout, } from "./flip.js";
|
|
23
23
|
export { createSquashStretch, createFollowThrough, createAnticipation, createWobble, } from "./cartoon.js";
|
|
24
24
|
export { createGravity, createPendulum, createFling, } from "./physics.js";
|
|
25
|
-
export { createFontSwap, createTyping, createTextPhysics, createTextTunnel, createTextCutout, createTextGradient, createTextScramble, createTextWave, } from "./typography.js";
|
|
25
|
+
export { createFontSwap, createTyping, createTextPhysics, createTextTunnel, createTextCutout, createTextGradient, createTextScramble, createTextWave, createCountUp, } from "./typography.js";
|
|
26
26
|
export { easings, cubicBezier, linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeOutExpo, easeOutBack, easeInBack, easeInOutBack, easeOutElastic, easeOutBounce, resolveEasing, } from "./easing.js";
|
|
27
27
|
export { createKineticType, createScenePlayer, createShowreel, createCamera, createColorShift, createTransition, createBeat, createBeatCuts, } from "./motion.js";
|
|
28
28
|
export { createDrag, } from "./gesture.js";
|
package/dist/typography.d.ts
CHANGED
|
@@ -238,4 +238,43 @@ export interface TextWaveOptions {
|
|
|
238
238
|
export declare function createTextWave(ref: MaybeElement, options?: TextWaveOptions): {
|
|
239
239
|
stop: () => void;
|
|
240
240
|
};
|
|
241
|
+
export interface CountUpOptions {
|
|
242
|
+
/** Decimal places in the output. Default 0. */
|
|
243
|
+
decimals?: number;
|
|
244
|
+
/** Tween duration in milliseconds when the source changes. Default 1000. */
|
|
245
|
+
duration?: number;
|
|
246
|
+
/** Easing for the count. Default "easeOutExpo". */
|
|
247
|
+
easing?: Easing | EasingName;
|
|
248
|
+
/** String placed before the number, e.g. "$". Default "". */
|
|
249
|
+
prefix?: string;
|
|
250
|
+
/** String placed after the number, e.g. "%". Default "". */
|
|
251
|
+
suffix?: string;
|
|
252
|
+
/** Thousands separator, e.g. ",". Default "" (no grouping). */
|
|
253
|
+
separator?: string;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* A signal that counts toward a source number with an eased tween,
|
|
257
|
+
* rendering as a formatted string: dashboard stats, prices, scores.
|
|
258
|
+
*
|
|
259
|
+
* When the source changes mid-count the tween retargets from the
|
|
260
|
+
* current displayed value, with no snapping. Formatting (decimals,
|
|
261
|
+
* prefix, suffix, thousands separator) applies to every frame.
|
|
262
|
+
*
|
|
263
|
+
* SSR-safe: renders the formatted source value. Under reduced motion
|
|
264
|
+
* the text jumps straight to each new value with no tween.
|
|
265
|
+
*
|
|
266
|
+
* ```tsx
|
|
267
|
+
* import { createCountUp } from "solid-drift"
|
|
268
|
+
*
|
|
269
|
+
* const [revenue, setRevenue] = createSignal(0)
|
|
270
|
+
* const display = createCountUp(revenue, {
|
|
271
|
+
* decimals: 2,
|
|
272
|
+
* prefix: "$",
|
|
273
|
+
* separator: ",",
|
|
274
|
+
* })
|
|
275
|
+
* <div>{display()}</div> // "$1,234.50" gliding up from "$0.00"
|
|
276
|
+
* setRevenue(1234.5)
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export declare function createCountUp(source: Accessor<number>, options?: CountUpOptions): Accessor<string>;
|
|
241
280
|
export {};
|
package/dist/typography.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createEffect, createSignal, onCleanup, untrack, } from "solid-js";
|
|
2
2
|
import { animate } from "./animate.js";
|
|
3
|
+
import { createTween } from "./tween.js";
|
|
3
4
|
import { resolveEasing } from "./easing.js";
|
|
4
5
|
import { now, schedule } from "./engine.js";
|
|
5
6
|
import { prefersReducedMotion } from "./reduced-motion.js";
|
|
@@ -810,3 +811,47 @@ export function createTextWave(ref, options = {}) {
|
|
|
810
811
|
},
|
|
811
812
|
};
|
|
812
813
|
}
|
|
814
|
+
function groupInteger(int, separator) {
|
|
815
|
+
if (!separator)
|
|
816
|
+
return int;
|
|
817
|
+
const negative = int.startsWith("-");
|
|
818
|
+
const digits = negative ? int.slice(1) : int;
|
|
819
|
+
const grouped = digits.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
820
|
+
return (negative ? "-" : "") + grouped;
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* A signal that counts toward a source number with an eased tween,
|
|
824
|
+
* rendering as a formatted string: dashboard stats, prices, scores.
|
|
825
|
+
*
|
|
826
|
+
* When the source changes mid-count the tween retargets from the
|
|
827
|
+
* current displayed value, with no snapping. Formatting (decimals,
|
|
828
|
+
* prefix, suffix, thousands separator) applies to every frame.
|
|
829
|
+
*
|
|
830
|
+
* SSR-safe: renders the formatted source value. Under reduced motion
|
|
831
|
+
* the text jumps straight to each new value with no tween.
|
|
832
|
+
*
|
|
833
|
+
* ```tsx
|
|
834
|
+
* import { createCountUp } from "solid-drift"
|
|
835
|
+
*
|
|
836
|
+
* const [revenue, setRevenue] = createSignal(0)
|
|
837
|
+
* const display = createCountUp(revenue, {
|
|
838
|
+
* decimals: 2,
|
|
839
|
+
* prefix: "$",
|
|
840
|
+
* separator: ",",
|
|
841
|
+
* })
|
|
842
|
+
* <div>{display()}</div> // "$1,234.50" gliding up from "$0.00"
|
|
843
|
+
* setRevenue(1234.5)
|
|
844
|
+
* ```
|
|
845
|
+
*/
|
|
846
|
+
export function createCountUp(source, options = {}) {
|
|
847
|
+
const { decimals = 0, duration = 1000, easing = "easeOutExpo", prefix = "", suffix = "", separator = "", } = options;
|
|
848
|
+
const tweened = createTween(source, { duration, easing });
|
|
849
|
+
const format = (value) => {
|
|
850
|
+
const fixed = value.toFixed(decimals);
|
|
851
|
+
const dot = fixed.indexOf(".");
|
|
852
|
+
const head = dot === -1 ? fixed : fixed.slice(0, dot);
|
|
853
|
+
const tail = dot === -1 ? "" : fixed.slice(dot);
|
|
854
|
+
return prefix + groupInteger(head, separator) + tail + suffix;
|
|
855
|
+
};
|
|
856
|
+
return () => format(tweened());
|
|
857
|
+
}
|
package/package.json
CHANGED