ink-motion 0.1.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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/index.cjs +445 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +262 -0
- package/dist/index.d.ts +262 -0
- package/dist/index.js +431 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { Text } from 'ink';
|
|
2
|
+
import React, { useRef, useEffect, useState, useMemo } from 'react';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
// src/components/Shimmer.tsx
|
|
6
|
+
var TARGET_FPS = 60;
|
|
7
|
+
var FRAME_INTERVAL_MS = 1e3 / TARGET_FPS;
|
|
8
|
+
function useAnimationFrame(callback, enabled = true) {
|
|
9
|
+
const intervalRef = useRef();
|
|
10
|
+
const previousTimeRef = useRef();
|
|
11
|
+
const callbackRef = useRef(callback);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
callbackRef.current = callback;
|
|
14
|
+
}, [callback]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!enabled) {
|
|
17
|
+
if (intervalRef.current) {
|
|
18
|
+
clearInterval(intervalRef.current);
|
|
19
|
+
}
|
|
20
|
+
previousTimeRef.current = void 0;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
intervalRef.current = setInterval(() => {
|
|
24
|
+
const currentTime = Date.now();
|
|
25
|
+
if (previousTimeRef.current !== void 0) {
|
|
26
|
+
const deltaTime = currentTime - previousTimeRef.current;
|
|
27
|
+
callbackRef.current(deltaTime);
|
|
28
|
+
}
|
|
29
|
+
previousTimeRef.current = currentTime;
|
|
30
|
+
}, FRAME_INTERVAL_MS);
|
|
31
|
+
return () => {
|
|
32
|
+
if (intervalRef.current) {
|
|
33
|
+
clearInterval(intervalRef.current);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}, [enabled]);
|
|
37
|
+
}
|
|
38
|
+
var HEX_BASE = 16;
|
|
39
|
+
var DECIMAL_BASE = 10;
|
|
40
|
+
var HEX_BYTE_LENGTH = 2;
|
|
41
|
+
var HEX_PAD_CHARACTER = "0";
|
|
42
|
+
var MIN_RGB_COMPONENTS = 3;
|
|
43
|
+
var INTERPOLATION_MIDPOINT = 0.5;
|
|
44
|
+
var HEX_COLOR_REGEX = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
|
|
45
|
+
var RGB_NUMBERS_REGEX = /\d+/g;
|
|
46
|
+
function rgbToHex(red, green, blue) {
|
|
47
|
+
const redHex = red.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER);
|
|
48
|
+
const greenHex = green.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER);
|
|
49
|
+
const blueHex = blue.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER);
|
|
50
|
+
return `#${redHex}${greenHex}${blueHex}`;
|
|
51
|
+
}
|
|
52
|
+
function hexToRgb(hex) {
|
|
53
|
+
const result = HEX_COLOR_REGEX.exec(hex);
|
|
54
|
+
if (!result)
|
|
55
|
+
return null;
|
|
56
|
+
const [, redHex, greenHex, blueHex] = result;
|
|
57
|
+
return [
|
|
58
|
+
Number.parseInt(redHex, HEX_BASE),
|
|
59
|
+
Number.parseInt(greenHex, HEX_BASE),
|
|
60
|
+
Number.parseInt(blueHex, HEX_BASE)
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
function colorize(text, color) {
|
|
64
|
+
try {
|
|
65
|
+
if (color.startsWith("#"))
|
|
66
|
+
return chalk.hex(color)(text);
|
|
67
|
+
if (color.startsWith("rgb")) {
|
|
68
|
+
const match = color.match(RGB_NUMBERS_REGEX);
|
|
69
|
+
if (!match || match.length < MIN_RGB_COMPONENTS)
|
|
70
|
+
return text;
|
|
71
|
+
const [redStr, greenStr, blueStr] = match;
|
|
72
|
+
const red = Number.parseInt(redStr, DECIMAL_BASE);
|
|
73
|
+
const green = Number.parseInt(greenStr, DECIMAL_BASE);
|
|
74
|
+
const blue = Number.parseInt(blueStr, DECIMAL_BASE);
|
|
75
|
+
if (Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue))
|
|
76
|
+
return text;
|
|
77
|
+
return chalk.rgb(red, green, blue)(text);
|
|
78
|
+
}
|
|
79
|
+
const chalkColor = chalk[color];
|
|
80
|
+
if (typeof chalkColor === "function")
|
|
81
|
+
return chalkColor(text);
|
|
82
|
+
return text;
|
|
83
|
+
} catch {
|
|
84
|
+
return text;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function applyOpacity(color, opacity) {
|
|
88
|
+
if (!color.startsWith("#"))
|
|
89
|
+
return color;
|
|
90
|
+
const rgb = hexToRgb(color);
|
|
91
|
+
if (!rgb)
|
|
92
|
+
return color;
|
|
93
|
+
const [red, green, blue] = rgb;
|
|
94
|
+
const adjustedRed = Math.round(red * opacity);
|
|
95
|
+
const adjustedGreen = Math.round(green * opacity);
|
|
96
|
+
const adjustedBlue = Math.round(blue * opacity);
|
|
97
|
+
return rgbToHex(adjustedRed, adjustedGreen, adjustedBlue);
|
|
98
|
+
}
|
|
99
|
+
function interpolateColor(color1, color2, progress) {
|
|
100
|
+
const isColor1Hex = color1.startsWith("#");
|
|
101
|
+
const isColor2Hex = color2.startsWith("#");
|
|
102
|
+
if (!isColor1Hex || !isColor2Hex)
|
|
103
|
+
return progress < INTERPOLATION_MIDPOINT ? color1 : color2;
|
|
104
|
+
const rgb1 = hexToRgb(color1);
|
|
105
|
+
const rgb2 = hexToRgb(color2);
|
|
106
|
+
if (!rgb1 || !rgb2)
|
|
107
|
+
return progress < INTERPOLATION_MIDPOINT ? color1 : color2;
|
|
108
|
+
const [red1, green1, blue1] = rgb1;
|
|
109
|
+
const [red2, green2, blue2] = rgb2;
|
|
110
|
+
const red = Math.round(red1 + (red2 - red1) * progress);
|
|
111
|
+
const green = Math.round(green1 + (green2 - green1) * progress);
|
|
112
|
+
const blue = Math.round(blue1 + (blue2 - blue1) * progress);
|
|
113
|
+
return rgbToHex(red, green, blue);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/utils/easing.ts
|
|
117
|
+
var linear = (time) => time;
|
|
118
|
+
var easeIn = (time) => time * time;
|
|
119
|
+
var easeOut = (time) => time * (2 - time);
|
|
120
|
+
var easeInOut = (time) => {
|
|
121
|
+
return time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time;
|
|
122
|
+
};
|
|
123
|
+
var easingFunctions = {
|
|
124
|
+
"linear": linear,
|
|
125
|
+
"ease-in": easeIn,
|
|
126
|
+
"ease-out": easeOut,
|
|
127
|
+
"ease-in-out": easeInOut
|
|
128
|
+
};
|
|
129
|
+
function getEasingFunction(name = "linear") {
|
|
130
|
+
return easingFunctions[name];
|
|
131
|
+
}
|
|
132
|
+
function clamp(value, min, max) {
|
|
133
|
+
return Math.min(Math.max(value, min), max);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/utils/text.ts
|
|
137
|
+
function splitChars(text) {
|
|
138
|
+
return [...text];
|
|
139
|
+
}
|
|
140
|
+
function getTextLength(text) {
|
|
141
|
+
return [...text].length;
|
|
142
|
+
}
|
|
143
|
+
function mapChars(text, fn) {
|
|
144
|
+
return splitChars(text).map(fn).join("");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/components/Shimmer.tsx
|
|
148
|
+
var DEFAULT_COLORS = ["#666666", "#ffffff", "#666666"];
|
|
149
|
+
var DEFAULT_WIDTH = 4;
|
|
150
|
+
var DEFAULT_INTENSITY = 1;
|
|
151
|
+
var DEFAULT_DIRECTION = "right";
|
|
152
|
+
var DEFAULT_SPEED = 1;
|
|
153
|
+
var ANIMATION_CYCLE_MS = 2e3;
|
|
154
|
+
var COLOUR_TRANSITION_MIDPOINT = 0.5;
|
|
155
|
+
var COLOUR_PROGRESS_MULTIPLIER = 2;
|
|
156
|
+
var MIN_INTENSITY = 0;
|
|
157
|
+
var MAX_INTENSITY = 1;
|
|
158
|
+
function Shimmer({
|
|
159
|
+
children,
|
|
160
|
+
colors = DEFAULT_COLORS,
|
|
161
|
+
width = DEFAULT_WIDTH,
|
|
162
|
+
intensity = DEFAULT_INTENSITY,
|
|
163
|
+
direction = DEFAULT_DIRECTION,
|
|
164
|
+
speed = DEFAULT_SPEED,
|
|
165
|
+
enabled = true,
|
|
166
|
+
onComplete
|
|
167
|
+
}) {
|
|
168
|
+
const [offset, setOffset] = useState(0);
|
|
169
|
+
const textLength = getTextLength(children);
|
|
170
|
+
const totalWidth = textLength + width;
|
|
171
|
+
useAnimationFrame((deltaTime) => {
|
|
172
|
+
setOffset((previousOffset) => {
|
|
173
|
+
const movement = deltaTime / ANIMATION_CYCLE_MS * speed * totalWidth;
|
|
174
|
+
const newOffset = direction === "right" ? previousOffset + movement : previousOffset - movement;
|
|
175
|
+
const hasCompletedCycle = Math.abs(newOffset) >= totalWidth;
|
|
176
|
+
if (hasCompletedCycle) {
|
|
177
|
+
onComplete?.();
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
return newOffset;
|
|
181
|
+
});
|
|
182
|
+
}, enabled);
|
|
183
|
+
const shimmerText = useMemo(() => {
|
|
184
|
+
return mapChars(children, (char, index) => {
|
|
185
|
+
const normalizedOffset = direction === "right" ? offset : totalWidth - offset;
|
|
186
|
+
const distance = Math.abs(index - normalizedOffset);
|
|
187
|
+
const isWithinShimmerWidth = distance < width;
|
|
188
|
+
const colorProgress = isWithinShimmerWidth ? distance / width : 1;
|
|
189
|
+
const [startColor, peakColor, endColor] = colors;
|
|
190
|
+
const adjustedIntensity = clamp(intensity, MIN_INTENSITY, MAX_INTENSITY);
|
|
191
|
+
const isFirstHalf = colorProgress < COLOUR_TRANSITION_MIDPOINT;
|
|
192
|
+
const currentColor = isFirstHalf ? interpolateColor(
|
|
193
|
+
startColor,
|
|
194
|
+
peakColor,
|
|
195
|
+
colorProgress * COLOUR_PROGRESS_MULTIPLIER * adjustedIntensity
|
|
196
|
+
) : interpolateColor(
|
|
197
|
+
peakColor,
|
|
198
|
+
endColor,
|
|
199
|
+
(colorProgress - COLOUR_TRANSITION_MIDPOINT) * COLOUR_PROGRESS_MULTIPLIER * adjustedIntensity
|
|
200
|
+
);
|
|
201
|
+
return colorize(char, currentColor);
|
|
202
|
+
});
|
|
203
|
+
}, [children, offset, colors, width, intensity, direction, totalWidth]);
|
|
204
|
+
return /* @__PURE__ */ React.createElement(Text, null, shimmerText);
|
|
205
|
+
}
|
|
206
|
+
var CURSOR_BLINK_INTERVAL_MS = 500;
|
|
207
|
+
function useCursorBlink({
|
|
208
|
+
enabled,
|
|
209
|
+
isComplete
|
|
210
|
+
}) {
|
|
211
|
+
const [showCursor, setShowCursor] = useState(true);
|
|
212
|
+
const intervalRef = useRef();
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (!enabled || isComplete) {
|
|
215
|
+
setShowCursor(false);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
intervalRef.current = setInterval(() => {
|
|
219
|
+
setShowCursor((previous) => !previous);
|
|
220
|
+
}, CURSOR_BLINK_INTERVAL_MS);
|
|
221
|
+
return () => {
|
|
222
|
+
if (intervalRef.current)
|
|
223
|
+
clearInterval(intervalRef.current);
|
|
224
|
+
};
|
|
225
|
+
}, [enabled, isComplete]);
|
|
226
|
+
return showCursor;
|
|
227
|
+
}
|
|
228
|
+
var BASE_CHARACTER_DELAY_MS = 80;
|
|
229
|
+
var MIN_VARIANCE = 0;
|
|
230
|
+
var MAX_VARIANCE = 1;
|
|
231
|
+
var FIRST_CHARACTER_INDEX = 0;
|
|
232
|
+
function calculateCharacterDelay(speed, variance) {
|
|
233
|
+
const clampedVariance = clamp(variance, MIN_VARIANCE, MAX_VARIANCE);
|
|
234
|
+
const randomVariance = (Math.random() - 0.5) * clampedVariance;
|
|
235
|
+
return BASE_CHARACTER_DELAY_MS / speed * (1 + randomVariance);
|
|
236
|
+
}
|
|
237
|
+
function useTypewriterProgress({
|
|
238
|
+
totalCharacters,
|
|
239
|
+
speed,
|
|
240
|
+
variance,
|
|
241
|
+
initialDelay,
|
|
242
|
+
enabled,
|
|
243
|
+
onComplete
|
|
244
|
+
}) {
|
|
245
|
+
const [visibleCharacters, setVisibleCharacters] = useState(0);
|
|
246
|
+
const timeoutRef = useRef();
|
|
247
|
+
const hasCompletedTyping = visibleCharacters >= totalCharacters;
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (!enabled)
|
|
250
|
+
return;
|
|
251
|
+
if (hasCompletedTyping) {
|
|
252
|
+
onComplete?.();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const characterDelay = calculateCharacterDelay(speed, variance);
|
|
256
|
+
const isFirstCharacter = visibleCharacters === FIRST_CHARACTER_INDEX;
|
|
257
|
+
const totalDelay = isFirstCharacter ? initialDelay + characterDelay : characterDelay;
|
|
258
|
+
timeoutRef.current = setTimeout(() => {
|
|
259
|
+
setVisibleCharacters((previous) => previous + 1);
|
|
260
|
+
}, totalDelay);
|
|
261
|
+
return () => {
|
|
262
|
+
if (timeoutRef.current)
|
|
263
|
+
clearTimeout(timeoutRef.current);
|
|
264
|
+
};
|
|
265
|
+
}, [visibleCharacters, enabled, speed, variance, initialDelay, hasCompletedTyping, onComplete]);
|
|
266
|
+
return visibleCharacters;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/components/Typewriter.tsx
|
|
270
|
+
var DEFAULT_CURSOR = "\u258B";
|
|
271
|
+
var DEFAULT_VARIANCE = 0.3;
|
|
272
|
+
var DEFAULT_DELAY = 0;
|
|
273
|
+
var DEFAULT_SPEED2 = 1;
|
|
274
|
+
var FIRST_CHARACTER_INDEX2 = 0;
|
|
275
|
+
function getCursorCharacter(cursor) {
|
|
276
|
+
return typeof cursor === "string" ? cursor : DEFAULT_CURSOR;
|
|
277
|
+
}
|
|
278
|
+
function formatCursor(cursorCharacter, cursorColor, textColor) {
|
|
279
|
+
if (cursorColor)
|
|
280
|
+
return colorize(cursorCharacter, cursorColor);
|
|
281
|
+
if (textColor)
|
|
282
|
+
return colorize(cursorCharacter, textColor);
|
|
283
|
+
return cursorCharacter;
|
|
284
|
+
}
|
|
285
|
+
function Typewriter({
|
|
286
|
+
children,
|
|
287
|
+
color,
|
|
288
|
+
cursor = DEFAULT_CURSOR,
|
|
289
|
+
cursorColor,
|
|
290
|
+
variance = DEFAULT_VARIANCE,
|
|
291
|
+
delay = DEFAULT_DELAY,
|
|
292
|
+
speed = DEFAULT_SPEED2,
|
|
293
|
+
enabled = true,
|
|
294
|
+
onComplete
|
|
295
|
+
}) {
|
|
296
|
+
const characters = splitChars(children);
|
|
297
|
+
const totalCharacters = getTextLength(children);
|
|
298
|
+
const visibleCharacters = useTypewriterProgress({
|
|
299
|
+
totalCharacters,
|
|
300
|
+
speed,
|
|
301
|
+
variance,
|
|
302
|
+
initialDelay: delay,
|
|
303
|
+
enabled,
|
|
304
|
+
onComplete
|
|
305
|
+
});
|
|
306
|
+
const hasCompletedTyping = visibleCharacters >= totalCharacters;
|
|
307
|
+
const isCursorEnabled = cursor !== false;
|
|
308
|
+
const showCursor = useCursorBlink({
|
|
309
|
+
enabled: enabled && isCursorEnabled,
|
|
310
|
+
isComplete: hasCompletedTyping
|
|
311
|
+
});
|
|
312
|
+
const displayText = useMemo(() => {
|
|
313
|
+
const visibleText = characters.slice(FIRST_CHARACTER_INDEX2, visibleCharacters).join("");
|
|
314
|
+
const coloredText = color ? colorize(visibleText, color) : visibleText;
|
|
315
|
+
const shouldShowCursor = isCursorEnabled && showCursor && !hasCompletedTyping;
|
|
316
|
+
if (!shouldShowCursor)
|
|
317
|
+
return coloredText;
|
|
318
|
+
const cursorCharacter = getCursorCharacter(cursor);
|
|
319
|
+
const cursorText = formatCursor(cursorCharacter, cursorColor, color);
|
|
320
|
+
return coloredText + cursorText;
|
|
321
|
+
}, [characters, visibleCharacters, color, cursor, cursorColor, showCursor, hasCompletedTyping, isCursorEnabled]);
|
|
322
|
+
return /* @__PURE__ */ React.createElement(Text, null, displayText);
|
|
323
|
+
}
|
|
324
|
+
function useElapsedTime(enabled = true, speed = 1) {
|
|
325
|
+
const [elapsedTime, setElapsedTime] = useState(0);
|
|
326
|
+
useAnimationFrame((deltaTime) => {
|
|
327
|
+
setElapsedTime((prev) => prev + deltaTime * speed);
|
|
328
|
+
}, enabled);
|
|
329
|
+
return elapsedTime;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/components/Fade.tsx
|
|
333
|
+
var DEFAULT_FROM = 0;
|
|
334
|
+
var DEFAULT_TO = 1;
|
|
335
|
+
var DEFAULT_DURATION_MS = 1e3;
|
|
336
|
+
var DEFAULT_EASING = "ease-out";
|
|
337
|
+
var DEFAULT_LOOP = false;
|
|
338
|
+
var DEFAULT_SPEED3 = 1;
|
|
339
|
+
function calculateOpacity(elapsedTime, duration, from, to, easing) {
|
|
340
|
+
const progress = Math.min(elapsedTime / duration, 1);
|
|
341
|
+
const easedProgress = getEasingFunction(easing)(progress);
|
|
342
|
+
return from + (to - from) * easedProgress;
|
|
343
|
+
}
|
|
344
|
+
function Fade({
|
|
345
|
+
children,
|
|
346
|
+
color,
|
|
347
|
+
from = DEFAULT_FROM,
|
|
348
|
+
to = DEFAULT_TO,
|
|
349
|
+
duration = DEFAULT_DURATION_MS,
|
|
350
|
+
easing = DEFAULT_EASING,
|
|
351
|
+
loop = DEFAULT_LOOP,
|
|
352
|
+
speed = DEFAULT_SPEED3,
|
|
353
|
+
enabled = true,
|
|
354
|
+
onComplete
|
|
355
|
+
}) {
|
|
356
|
+
const [hasCompleted, setHasCompleted] = useState(false);
|
|
357
|
+
const elapsedTime = useElapsedTime(enabled && !hasCompleted, speed);
|
|
358
|
+
useEffect(() => {
|
|
359
|
+
if (!enabled)
|
|
360
|
+
return;
|
|
361
|
+
const isComplete = elapsedTime >= duration;
|
|
362
|
+
if (isComplete && !loop && !hasCompleted) {
|
|
363
|
+
setHasCompleted(true);
|
|
364
|
+
onComplete?.();
|
|
365
|
+
}
|
|
366
|
+
}, [elapsedTime, duration, loop, hasCompleted, enabled, onComplete]);
|
|
367
|
+
const fadedText = useMemo(() => {
|
|
368
|
+
const effectiveTime = loop ? elapsedTime % duration : Math.min(elapsedTime, duration);
|
|
369
|
+
const opacity = calculateOpacity(effectiveTime, duration, from, to, easing);
|
|
370
|
+
const baseColor = color ?? "#ffffff";
|
|
371
|
+
const fadedColor = applyOpacity(baseColor, opacity);
|
|
372
|
+
return colorize(children, fadedColor);
|
|
373
|
+
}, [children, elapsedTime, duration, from, to, easing, color, loop]);
|
|
374
|
+
return /* @__PURE__ */ React.createElement(Text, null, fadedText);
|
|
375
|
+
}
|
|
376
|
+
var DEFAULT_COLORS2 = ["#888888", "#ffffff"];
|
|
377
|
+
var DEFAULT_AMPLITUDE = 0.5;
|
|
378
|
+
var DEFAULT_FREQUENCY = 2;
|
|
379
|
+
var DEFAULT_TYPE = "brightness";
|
|
380
|
+
var DEFAULT_SPEED4 = 1;
|
|
381
|
+
var FULL_CIRCLE_RADIANS = Math.PI * 2;
|
|
382
|
+
var WAVE_PERIOD_MS = 2e3;
|
|
383
|
+
var MIN_AMPLITUDE = 0;
|
|
384
|
+
var MAX_AMPLITUDE = 1;
|
|
385
|
+
var SINE_WAVE_OFFSET = 1;
|
|
386
|
+
var SINE_WAVE_NORMALIZE = 0.5;
|
|
387
|
+
var VERTICAL_SHIFT_THRESHOLD = 0.5;
|
|
388
|
+
function calculateWaveValue(characterIndex, time, frequency, amplitude) {
|
|
389
|
+
const clampedAmplitude = clamp(amplitude, MIN_AMPLITUDE, MAX_AMPLITUDE);
|
|
390
|
+
const safeFrequency = frequency || DEFAULT_FREQUENCY;
|
|
391
|
+
const waveLength = FULL_CIRCLE_RADIANS / safeFrequency;
|
|
392
|
+
const spatialComponent = characterIndex / waveLength * FULL_CIRCLE_RADIANS;
|
|
393
|
+
const temporalComponent = time * FULL_CIRCLE_RADIANS;
|
|
394
|
+
const phase = spatialComponent - temporalComponent;
|
|
395
|
+
const sineWave = Math.sin(phase);
|
|
396
|
+
const normalizedSine = (sineWave + SINE_WAVE_OFFSET) * SINE_WAVE_NORMALIZE;
|
|
397
|
+
const amplitudeComplement = MAX_AMPLITUDE - clampedAmplitude;
|
|
398
|
+
return normalizedSine * clampedAmplitude + amplitudeComplement;
|
|
399
|
+
}
|
|
400
|
+
function Wave({
|
|
401
|
+
children,
|
|
402
|
+
colors = DEFAULT_COLORS2,
|
|
403
|
+
amplitude = DEFAULT_AMPLITUDE,
|
|
404
|
+
frequency = DEFAULT_FREQUENCY,
|
|
405
|
+
type = DEFAULT_TYPE,
|
|
406
|
+
speed = DEFAULT_SPEED4,
|
|
407
|
+
enabled = true
|
|
408
|
+
}) {
|
|
409
|
+
const elapsedTime = useElapsedTime(enabled, speed);
|
|
410
|
+
const normalizedTime = elapsedTime / WAVE_PERIOD_MS;
|
|
411
|
+
const waveText = useMemo(() => {
|
|
412
|
+
if (type === "brightness") {
|
|
413
|
+
return mapChars(children, (character, index) => {
|
|
414
|
+
const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude);
|
|
415
|
+
const [darkColor, brightColor] = colors;
|
|
416
|
+
const characterColor = interpolateColor(darkColor, brightColor, waveValue);
|
|
417
|
+
return colorize(character, characterColor);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return mapChars(children, (character, index) => {
|
|
421
|
+
const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude);
|
|
422
|
+
const shouldShift = waveValue > VERTICAL_SHIFT_THRESHOLD;
|
|
423
|
+
return shouldShift ? ` ${character}` : character;
|
|
424
|
+
});
|
|
425
|
+
}, [children, normalizedTime, colors, amplitude, frequency, type]);
|
|
426
|
+
return /* @__PURE__ */ React.createElement(Text, null, waveText);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export { Fade, Shimmer, Typewriter, Wave, useAnimationFrame, useCursorBlink, useElapsedTime, useTypewriterProgress };
|
|
430
|
+
//# sourceMappingURL=index.js.map
|
|
431
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useAnimationFrame.ts","../src/utils/colors.ts","../src/utils/easing.ts","../src/utils/text.ts","../src/components/Shimmer.tsx","../src/hooks/useCursorBlink.ts","../src/hooks/useTypewriterProgress.ts","../src/components/Typewriter.tsx","../src/hooks/useElapsedTime.ts","../src/components/Fade.tsx","../src/components/Wave.tsx"],"names":["useState","useRef","useEffect","DEFAULT_SPEED","FIRST_CHARACTER_INDEX","useMemo","React","Text","DEFAULT_COLORS"],"mappings":"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","file":"index.js","sourcesContent":["import { useEffect, useRef } from 'react'\n\nconst TARGET_FPS = 60\nconst FRAME_INTERVAL_MS = 1000 / TARGET_FPS\n\n/**\n * Runs a callback on every animation frame using setInterval\n *\n * @param callback - Function called each frame with delta time in ms\n * @param enabled - Whether animation is active\n */\nexport function useAnimationFrame(\n callback: (deltaTime: number) => void,\n enabled: boolean = true,\n): void {\n const intervalRef = useRef<Timer>()\n const previousTimeRef = useRef<number>()\n const callbackRef = useRef(callback)\n\n useEffect(() => {\n callbackRef.current = callback\n }, [callback])\n\n useEffect(() => {\n if (!enabled) {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n }\n previousTimeRef.current = undefined\n return\n }\n\n intervalRef.current = setInterval(() => {\n const currentTime = Date.now()\n if (previousTimeRef.current !== undefined) {\n const deltaTime = currentTime - previousTimeRef.current\n callbackRef.current(deltaTime)\n }\n previousTimeRef.current = currentTime\n }, FRAME_INTERVAL_MS)\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n }\n }\n }, [enabled])\n}\n","import chalk from 'chalk'\nimport type { Colour } from '../types/index.js'\n\nconst HEX_BASE = 16\nconst DECIMAL_BASE = 10\nconst HEX_BYTE_LENGTH = 2\nconst HEX_PAD_CHARACTER = '0'\nconst MIN_RGB_COMPONENTS = 3\nconst INTERPOLATION_MIDPOINT = 0.5\n\nconst HEX_COLOR_REGEX = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i\nconst RGB_NUMBERS_REGEX = /\\d+/g\n\nfunction rgbToHex(red: number, green: number, blue: number): string {\n const redHex = red.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n const greenHex = green.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n const blueHex = blue.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n return `#${redHex}${greenHex}${blueHex}`\n}\n\n/**\n * Converts a hex color string to RGB values\n *\n * @param hex - Hex color string (with or without #)\n * @returns Tuple of [red, green, blue] values (0-255) or null if invalid\n */\nexport function hexToRgb(hex: string): [number, number, number] | null {\n const result = HEX_COLOR_REGEX.exec(hex)\n if (!result)\n return null\n\n const [, redHex, greenHex, blueHex] = result\n return [\n Number.parseInt(redHex, HEX_BASE),\n Number.parseInt(greenHex, HEX_BASE),\n Number.parseInt(blueHex, HEX_BASE),\n ]\n}\n\n/**\n * Applies color to text using chalk\n *\n * @param text - Text to colorize\n * @param color - Color as hex (#ff0000), rgb(255,0,0), or named color (red, blue, etc)\n * @returns Colorized text with ANSI escape codes\n */\nexport function colorize(text: string, color: Colour): string {\n try {\n if (color.startsWith('#'))\n return chalk.hex(color)(text)\n\n if (color.startsWith('rgb')) {\n const match = color.match(RGB_NUMBERS_REGEX)\n if (!match || match.length < MIN_RGB_COMPONENTS)\n return text\n\n const [redStr, greenStr, blueStr] = match\n const red = Number.parseInt(redStr, DECIMAL_BASE)\n const green = Number.parseInt(greenStr, DECIMAL_BASE)\n const blue = Number.parseInt(blueStr, DECIMAL_BASE)\n\n if (Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue))\n return text\n\n return chalk.rgb(red, green, blue)(text)\n }\n\n const chalkColor = chalk[color as keyof typeof chalk]\n if (typeof chalkColor === 'function')\n return chalkColor(text)\n\n return text\n }\n catch {\n return text\n }\n}\n\n/**\n * Applies opacity to a hex color by darkening it\n * Note: Terminal approximation - true opacity not possible in most terminals\n *\n * @param color - Hex color string\n * @param opacity - Opacity value from 0 (transparent/black) to 1 (fully opaque)\n * @returns Adjusted hex color or original color if not hex format\n */\nexport function applyOpacity(color: Colour, opacity: number): Colour {\n if (!color.startsWith('#'))\n return color\n\n const rgb = hexToRgb(color)\n if (!rgb)\n return color\n\n const [red, green, blue] = rgb\n const adjustedRed = Math.round(red * opacity)\n const adjustedGreen = Math.round(green * opacity)\n const adjustedBlue = Math.round(blue * opacity)\n\n return rgbToHex(adjustedRed, adjustedGreen, adjustedBlue)\n}\n\n/**\n * Interpolates between two colors\n *\n * @param color1 - Starting color\n * @param color2 - Ending color\n * @param progress - Interpolation progress from 0 to 1\n * @returns Interpolated color (hex if both inputs are hex, otherwise snaps to nearest)\n */\nexport function interpolateColor(\n color1: Colour,\n color2: Colour,\n progress: number,\n): Colour {\n const isColor1Hex = color1.startsWith('#')\n const isColor2Hex = color2.startsWith('#')\n\n if (!isColor1Hex || !isColor2Hex)\n return progress < INTERPOLATION_MIDPOINT ? color1 : color2\n\n const rgb1 = hexToRgb(color1)\n const rgb2 = hexToRgb(color2)\n\n if (!rgb1 || !rgb2)\n return progress < INTERPOLATION_MIDPOINT ? color1 : color2\n\n const [red1, green1, blue1] = rgb1\n const [red2, green2, blue2] = rgb2\n\n const red = Math.round(red1 + (red2 - red1) * progress)\n const green = Math.round(green1 + (green2 - green1) * progress)\n const blue = Math.round(blue1 + (blue2 - blue1) * progress)\n\n return rgbToHex(red, green, blue)\n}\n","import type { EasingFunction, EasingName } from '../types/index.js'\n\nexport const linear: EasingFunction = time => time\n\nexport const easeIn: EasingFunction = time => time * time\n\nexport const easeOut: EasingFunction = time => time * (2 - time)\n\nexport const easeInOut: EasingFunction = (time) => {\n return time < 0.5\n ? 2 * time * time\n : -1 + (4 - 2 * time) * time\n}\n\nexport const easingFunctions: Record<EasingName, EasingFunction> = {\n 'linear': linear,\n 'ease-in': easeIn,\n 'ease-out': easeOut,\n 'ease-in-out': easeInOut,\n}\n\nexport function getEasingFunction(name: EasingName = 'linear'): EasingFunction {\n return easingFunctions[name]\n}\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nexport function normalize(value: number, min: number, max: number): number {\n return clamp((value - min) / (max - min), 0, 1)\n}\n\nexport function lerp(start: number, end: number, progress: number): number {\n return start + (end - start) * progress\n}\n","export function splitChars(text: string): string[] {\n return [...text]\n}\n\nexport function getTextLength(text: string): number {\n return [...text].length\n}\n\nexport function mapChars(\n text: string,\n fn: (char: string, index: number) => string,\n): string {\n return splitChars(text).map(fn).join('')\n}\n","import { Text } from 'ink'\nimport React, { useMemo, useState } from 'react'\nimport type { BaseEffectProps, Colour } from '../types/index.js'\nimport { useAnimationFrame } from '../hooks/useAnimationFrame.js'\nimport { colorize, interpolateColor } from '../utils/colors.js'\nimport { clamp } from '../utils/easing.js'\nimport { getTextLength, mapChars } from '../utils/text.js'\n\ntype ShimmerDirection = 'left' | 'right'\n\ninterface ShimmerProps extends BaseEffectProps {\n /**\n * Gradient colours for the shimmer effect [start, peak, end]\n * @default ['#666666', '#ffffff', '#666666']\n * @example ['#60a5fa', '#3b82f6', '#60a5fa']\n */\n colors?: [Colour, Colour, Colour]\n\n /**\n * Width of the shimmer band in characters\n * @default 4\n */\n width?: number\n\n /**\n * Brightness multiplier (0-1)\n * @default 1\n */\n intensity?: number\n\n /**\n * Direction of shimmer movement\n * @default 'right'\n */\n direction?: ShimmerDirection\n}\n\nconst DEFAULT_COLORS: [Colour, Colour, Colour] = ['#666666', '#ffffff', '#666666']\nconst DEFAULT_WIDTH = 4\nconst DEFAULT_INTENSITY = 1\nconst DEFAULT_DIRECTION: ShimmerDirection = 'right'\nconst DEFAULT_SPEED = 1\nconst ANIMATION_CYCLE_MS = 2000\nconst COLOUR_TRANSITION_MIDPOINT = 0.5\nconst COLOUR_PROGRESS_MULTIPLIER = 2\nconst MIN_INTENSITY = 0\nconst MAX_INTENSITY = 1\n\n/**\n * Shimmer effect component that creates a moving highlight across text\n *\n * Creates a shimmering animation by interpolating through a gradient of colours\n * that sweeps across the text from left to right or right to left.\n *\n * @example\n * ```tsx\n * <Shimmer colors={['#60a5fa', '#3b82f6', '#60a5fa']} intensity={0.8}>\n * Loading...\n * </Shimmer>\n * ```\n */\nexport function Shimmer({\n children,\n colors = DEFAULT_COLORS,\n width = DEFAULT_WIDTH,\n intensity = DEFAULT_INTENSITY,\n direction = DEFAULT_DIRECTION,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: ShimmerProps) {\n const [offset, setOffset] = useState(0)\n const textLength = getTextLength(children)\n const totalWidth = textLength + width\n\n useAnimationFrame((deltaTime) => {\n setOffset((previousOffset) => {\n const movement = (deltaTime / ANIMATION_CYCLE_MS) * speed * totalWidth\n const newOffset = direction === 'right'\n ? previousOffset + movement\n : previousOffset - movement\n\n const hasCompletedCycle = Math.abs(newOffset) >= totalWidth\n if (hasCompletedCycle) {\n onComplete?.()\n return 0\n }\n\n return newOffset\n })\n }, enabled)\n\n const shimmerText = useMemo(() => {\n return mapChars(children, (char, index) => {\n const normalizedOffset = direction === 'right' ? offset : totalWidth - offset\n const distance = Math.abs(index - normalizedOffset)\n\n const isWithinShimmerWidth = distance < width\n const colorProgress = isWithinShimmerWidth\n ? distance / width\n : 1\n\n const [startColor, peakColor, endColor] = colors\n const adjustedIntensity = clamp(intensity, MIN_INTENSITY, MAX_INTENSITY)\n\n const isFirstHalf = colorProgress < COLOUR_TRANSITION_MIDPOINT\n const currentColor = isFirstHalf\n ? interpolateColor(\n startColor,\n peakColor,\n colorProgress * COLOUR_PROGRESS_MULTIPLIER * adjustedIntensity,\n )\n : interpolateColor(\n peakColor,\n endColor,\n (colorProgress - COLOUR_TRANSITION_MIDPOINT) * COLOUR_PROGRESS_MULTIPLIER * adjustedIntensity,\n )\n\n return colorize(char, currentColor)\n })\n }, [children, offset, colors, width, intensity, direction, totalWidth])\n\n return <Text>{shimmerText}</Text>\n}\n","import { useEffect, useRef, useState } from 'react'\n\nconst CURSOR_BLINK_INTERVAL_MS = 500\n\ninterface UseCursorBlinkOptions {\n enabled: boolean\n isComplete: boolean\n}\n\n/**\n * Hook that manages cursor blinking state\n *\n * @param options - Configuration for cursor blink\n * @returns Whether cursor should be visible\n */\nexport function useCursorBlink({\n enabled,\n isComplete,\n}: UseCursorBlinkOptions): boolean {\n const [showCursor, setShowCursor] = useState(true)\n const intervalRef = useRef<Timer>()\n\n useEffect(() => {\n if (!enabled || isComplete) {\n setShowCursor(false)\n return\n }\n\n intervalRef.current = setInterval(() => {\n setShowCursor(previous => !previous)\n }, CURSOR_BLINK_INTERVAL_MS)\n\n return () => {\n if (intervalRef.current)\n clearInterval(intervalRef.current)\n }\n }, [enabled, isComplete])\n\n return showCursor\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { clamp } from '../utils/easing.js'\n\nconst BASE_CHARACTER_DELAY_MS = 80\nconst MIN_VARIANCE = 0\nconst MAX_VARIANCE = 1\nconst FIRST_CHARACTER_INDEX = 0\n\nfunction calculateCharacterDelay(\n speed: number,\n variance: number,\n): number {\n const clampedVariance = clamp(variance, MIN_VARIANCE, MAX_VARIANCE)\n const randomVariance = (Math.random() - 0.5) * clampedVariance\n return (BASE_CHARACTER_DELAY_MS / speed) * (1 + randomVariance)\n}\n\ninterface UseTypewriterProgressOptions {\n totalCharacters: number\n speed: number\n variance: number\n initialDelay: number\n enabled: boolean\n onComplete?: () => void\n}\n\n/**\n * Hook that manages typewriter character reveal timing\n *\n * @param options - Configuration for typewriter progress\n * @returns Number of characters to display\n */\nexport function useTypewriterProgress({\n totalCharacters,\n speed,\n variance,\n initialDelay,\n enabled,\n onComplete,\n}: UseTypewriterProgressOptions): number {\n const [visibleCharacters, setVisibleCharacters] = useState(0)\n const timeoutRef = useRef<Timer>()\n const hasCompletedTyping = visibleCharacters >= totalCharacters\n\n useEffect(() => {\n if (!enabled)\n return\n\n if (hasCompletedTyping) {\n onComplete?.()\n return\n }\n\n const characterDelay = calculateCharacterDelay(speed, variance)\n const isFirstCharacter = visibleCharacters === FIRST_CHARACTER_INDEX\n const totalDelay = isFirstCharacter ? initialDelay + characterDelay : characterDelay\n\n timeoutRef.current = setTimeout(() => {\n setVisibleCharacters(previous => previous + 1)\n }, totalDelay)\n\n return () => {\n if (timeoutRef.current)\n clearTimeout(timeoutRef.current)\n }\n }, [visibleCharacters, enabled, speed, variance, initialDelay, hasCompletedTyping, onComplete])\n\n return visibleCharacters\n}\n","import { Text } from 'ink'\nimport React, { useMemo } from 'react'\nimport type { BaseEffectProps, Colour } from '../types/index.js'\nimport { useCursorBlink } from '../hooks/useCursorBlink.js'\nimport { useTypewriterProgress } from '../hooks/useTypewriterProgress.js'\nimport { colorize } from '../utils/colors.js'\nimport { getTextLength, splitChars } from '../utils/text.js'\n\ninterface TypewriterProps extends BaseEffectProps {\n /**\n * Text colour\n * @default undefined (inherits)\n * @example 'green'\n */\n color?: Colour\n\n /**\n * Cursor character or false to disable\n * @default '▋'\n * @example '█'\n */\n cursor?: string | boolean\n\n /**\n * Cursor colour (defaults to text colour)\n * @default undefined\n * @example 'cyan'\n */\n cursorColor?: Colour\n\n /**\n * Typing speed randomness (0-1) for more human-like typing\n * @default 0.3\n */\n variance?: number\n\n /**\n * Initial delay before typing starts (ms)\n * @default 0\n */\n delay?: number\n}\n\nconst DEFAULT_CURSOR = '▋'\nconst DEFAULT_VARIANCE = 0.3\nconst DEFAULT_DELAY = 0\nconst DEFAULT_SPEED = 1\nconst FIRST_CHARACTER_INDEX = 0\n\nfunction getCursorCharacter(cursor: string | boolean): string {\n return typeof cursor === 'string' ? cursor : DEFAULT_CURSOR\n}\n\nfunction formatCursor(\n cursorCharacter: string,\n cursorColor: Colour | undefined,\n textColor: Colour | undefined,\n): string {\n if (cursorColor)\n return colorize(cursorCharacter, cursorColor)\n\n if (textColor)\n return colorize(cursorCharacter, textColor)\n\n return cursorCharacter\n}\n\n/**\n * Typewriter effect component that reveals text character by character\n *\n * Simulates typing text with configurable speed, variance for realistic timing,\n * and an optional cursor. The cursor blinks while typing is in progress.\n *\n * @example\n * ```tsx\n * <Typewriter color=\"green\" cursor=\"█\" variance={0.5} speed={2}>\n * npm install ink-motion\n * </Typewriter>\n * ```\n */\nexport function Typewriter({\n children,\n color,\n cursor = DEFAULT_CURSOR,\n cursorColor,\n variance = DEFAULT_VARIANCE,\n delay = DEFAULT_DELAY,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: TypewriterProps) {\n const characters = splitChars(children)\n const totalCharacters = getTextLength(children)\n\n const visibleCharacters = useTypewriterProgress({\n totalCharacters,\n speed,\n variance,\n initialDelay: delay,\n enabled,\n onComplete,\n })\n\n const hasCompletedTyping = visibleCharacters >= totalCharacters\n const isCursorEnabled = cursor !== false\n\n const showCursor = useCursorBlink({\n enabled: enabled && isCursorEnabled,\n isComplete: hasCompletedTyping,\n })\n\n const displayText = useMemo(() => {\n const visibleText = characters.slice(FIRST_CHARACTER_INDEX, visibleCharacters).join('')\n const coloredText = color ? colorize(visibleText, color) : visibleText\n\n const shouldShowCursor = isCursorEnabled && showCursor && !hasCompletedTyping\n if (!shouldShowCursor)\n return coloredText\n\n const cursorCharacter = getCursorCharacter(cursor)\n const cursorText = formatCursor(cursorCharacter, cursorColor, color)\n\n return coloredText + cursorText\n }, [characters, visibleCharacters, color, cursor, cursorColor, showCursor, hasCompletedTyping, isCursorEnabled])\n\n return <Text>{displayText}</Text>\n}\n","import { useState } from 'react'\nimport { useAnimationFrame } from './useAnimationFrame.js'\n\n/**\n * Hook that tracks elapsed time since mount or last reset\n *\n * @param enabled - Whether to track time\n * @param speed - Speed multiplier (default: 1)\n * @returns Elapsed time in milliseconds\n */\nexport function useElapsedTime(\n enabled: boolean = true,\n speed: number = 1,\n): number {\n const [elapsedTime, setElapsedTime] = useState(0)\n\n useAnimationFrame((deltaTime) => {\n setElapsedTime(prev => prev + deltaTime * speed)\n }, enabled)\n\n return elapsedTime\n}\n","import { Text } from 'ink'\nimport React, { useEffect, useMemo, useState } from 'react'\nimport type { BaseEffectProps, Colour, EasingName } from '../types/index.js'\nimport { useElapsedTime } from '../hooks/useElapsedTime.js'\nimport { applyOpacity, colorize } from '../utils/colors.js'\nimport { getEasingFunction } from '../utils/easing.js'\n\ninterface FadeProps extends BaseEffectProps {\n /**\n * Text colour\n * @default '#ffffff'\n * @example 'yellow'\n */\n color?: Colour\n\n /**\n * Starting opacity (0-1)\n * @default 0\n */\n from?: number\n\n /**\n * Ending opacity (0-1)\n * @default 1\n */\n to?: number\n\n /**\n * Duration in milliseconds\n * @default 1000\n */\n duration?: number\n\n /**\n * Easing function\n * @default 'ease-out'\n */\n easing?: EasingName\n\n /**\n * Loop animation continuously\n * @default false\n */\n loop?: boolean\n}\n\nconst DEFAULT_FROM = 0\nconst DEFAULT_TO = 1\nconst DEFAULT_DURATION_MS = 1000\nconst DEFAULT_EASING: EasingName = 'ease-out'\nconst DEFAULT_LOOP = false\nconst DEFAULT_SPEED = 1\n\nfunction calculateOpacity(\n elapsedTime: number,\n duration: number,\n from: number,\n to: number,\n easing: EasingName,\n): number {\n const progress = Math.min(elapsedTime / duration, 1)\n const easedProgress = getEasingFunction(easing)(progress)\n return from + (to - from) * easedProgress\n}\n\n/**\n * Fade effect component that smoothly transitions text opacity\n *\n * Animates text from one opacity to another using configurable easing functions.\n * Can loop continuously or run once.\n *\n * @example\n * ```tsx\n * <Fade color=\"yellow\" from={0} to={1} duration={500} easing=\"ease-in\">\n * Success!\n * </Fade>\n * ```\n */\nexport function Fade({\n children,\n color,\n from = DEFAULT_FROM,\n to = DEFAULT_TO,\n duration = DEFAULT_DURATION_MS,\n easing = DEFAULT_EASING,\n loop = DEFAULT_LOOP,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: FadeProps) {\n const [hasCompleted, setHasCompleted] = useState(false)\n const elapsedTime = useElapsedTime(enabled && !hasCompleted, speed)\n\n useEffect(() => {\n if (!enabled)\n return\n\n const isComplete = elapsedTime >= duration\n\n if (isComplete && !loop && !hasCompleted) {\n setHasCompleted(true)\n onComplete?.()\n }\n }, [elapsedTime, duration, loop, hasCompleted, enabled, onComplete])\n\n const fadedText = useMemo(() => {\n const effectiveTime = loop ? elapsedTime % duration : Math.min(elapsedTime, duration)\n const opacity = calculateOpacity(effectiveTime, duration, from, to, easing)\n\n const baseColor = color ?? '#ffffff'\n const fadedColor = applyOpacity(baseColor, opacity)\n\n return colorize(children, fadedColor)\n }, [children, elapsedTime, duration, from, to, easing, color, loop])\n\n return <Text>{fadedText}</Text>\n}\n","import { Text } from 'ink'\nimport React, { useMemo } from 'react'\nimport type { BaseEffectProps, Colour } from '../types/index.js'\nimport { useElapsedTime } from '../hooks/useElapsedTime.js'\nimport { colorize, interpolateColor } from '../utils/colors.js'\nimport { clamp } from '../utils/easing.js'\nimport { mapChars } from '../utils/text.js'\n\ntype WaveType = 'brightness' | 'vertical'\n\ninterface WaveProps extends BaseEffectProps {\n /**\n * Gradient colours for the wave [dark, bright]\n * @default ['#888888', '#ffffff']\n * @example ['#ec4899', '#8b5cf6']\n */\n colors?: [Colour, Colour]\n\n /**\n * Wave height (0-1)\n * @default 0.5\n */\n amplitude?: number\n\n /**\n * Number of wave cycles across text\n * @default 2\n */\n frequency?: number\n\n /**\n * Wave effect type\n * @default 'brightness'\n */\n type?: WaveType\n}\n\nconst DEFAULT_COLORS: [Colour, Colour] = ['#888888', '#ffffff']\nconst DEFAULT_AMPLITUDE = 0.5\nconst DEFAULT_FREQUENCY = 2\nconst DEFAULT_TYPE: WaveType = 'brightness'\nconst DEFAULT_SPEED = 1\nconst FULL_CIRCLE_RADIANS = Math.PI * 2\nconst WAVE_PERIOD_MS = 2000\nconst MIN_AMPLITUDE = 0\nconst MAX_AMPLITUDE = 1\nconst SINE_WAVE_OFFSET = 1\nconst SINE_WAVE_NORMALIZE = 0.5\nconst VERTICAL_SHIFT_THRESHOLD = 0.5\n\nfunction calculateWaveValue(\n characterIndex: number,\n time: number,\n frequency: number,\n amplitude: number,\n): number {\n const clampedAmplitude = clamp(amplitude, MIN_AMPLITUDE, MAX_AMPLITUDE)\n const safeFrequency = frequency || DEFAULT_FREQUENCY\n const waveLength = FULL_CIRCLE_RADIANS / safeFrequency\n const spatialComponent = (characterIndex / waveLength) * FULL_CIRCLE_RADIANS\n const temporalComponent = time * FULL_CIRCLE_RADIANS\n const phase = spatialComponent - temporalComponent\n const sineWave = Math.sin(phase)\n const normalizedSine = (sineWave + SINE_WAVE_OFFSET) * SINE_WAVE_NORMALIZE\n const amplitudeComplement = MAX_AMPLITUDE - clampedAmplitude\n return normalizedSine * clampedAmplitude + amplitudeComplement\n}\n\n/**\n * Wave effect component that creates a wave motion through text\n *\n * Animates text with a sine wave pattern that can affect brightness or vertical position.\n * The wave continuously flows through the text.\n *\n * @example\n * ```tsx\n * <Wave colors={['#ec4899', '#8b5cf6']} amplitude={0.7} frequency={3}>\n * ~~ wavy text ~~\n * </Wave>\n * ```\n */\nexport function Wave({\n children,\n colors = DEFAULT_COLORS,\n amplitude = DEFAULT_AMPLITUDE,\n frequency = DEFAULT_FREQUENCY,\n type = DEFAULT_TYPE,\n speed = DEFAULT_SPEED,\n enabled = true,\n}: WaveProps) {\n const elapsedTime = useElapsedTime(enabled, speed)\n const normalizedTime = elapsedTime / WAVE_PERIOD_MS\n\n const waveText = useMemo(() => {\n if (type === 'brightness') {\n return mapChars(children, (character, index) => {\n const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude)\n const [darkColor, brightColor] = colors\n const characterColor = interpolateColor(darkColor, brightColor, waveValue)\n return colorize(character, characterColor)\n })\n }\n\n return mapChars(children, (character, index) => {\n const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude)\n const shouldShift = waveValue > VERTICAL_SHIFT_THRESHOLD\n return shouldShift ? ` ${character}` : character\n })\n }, [children, normalizedTime, colors, amplitude, frequency, type])\n\n return <Text>{waveText}</Text>\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ink-motion",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Motion and animation components for Ink - beautiful text effects for your CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"demo": "bun examples/demo.tsx",
|
|
28
|
+
"test": "bun test",
|
|
29
|
+
"type-check": "tsc --noEmit",
|
|
30
|
+
"lint": "eslint .",
|
|
31
|
+
"prepublishOnly": "bun run build && publint"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"ink",
|
|
35
|
+
"cli",
|
|
36
|
+
"terminal",
|
|
37
|
+
"animation",
|
|
38
|
+
"motion",
|
|
39
|
+
"text-effects",
|
|
40
|
+
"react",
|
|
41
|
+
"shimmer",
|
|
42
|
+
"typewriter",
|
|
43
|
+
"fade",
|
|
44
|
+
"wave"
|
|
45
|
+
],
|
|
46
|
+
"author": "James Glenn",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/JR-G/ink-motion.git"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/JR-G/ink-motion/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/JR-G/ink-motion#readme",
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"ink": "^5.0.0",
|
|
58
|
+
"react": "^18.0.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@antfu/eslint-config": "^3.8.0",
|
|
62
|
+
"@types/bun": "latest",
|
|
63
|
+
"@types/react": "^18.3.12",
|
|
64
|
+
"eslint": "^9.15.0",
|
|
65
|
+
"ink": "^5.0.1",
|
|
66
|
+
"ink-testing-library": "^4.0.0",
|
|
67
|
+
"publint": "^0.2.12",
|
|
68
|
+
"react": "^18.3.1",
|
|
69
|
+
"tsup": "^8.3.5",
|
|
70
|
+
"typescript": "^5.6.3"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"chalk": "^5.3.0"
|
|
74
|
+
}
|
|
75
|
+
}
|