@effect-tui/react 0.9.3 → 0.10.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/src/dev/Toast.d.ts.map +1 -1
- package/dist/src/dev/Toast.js +24 -15
- package/dist/src/dev/Toast.js.map +1 -1
- package/dist/src/dev.d.ts +1 -1
- package/dist/src/dev.d.ts.map +1 -1
- package/dist/src/dev.js.map +1 -1
- package/dist/src/hooks/index.d.ts +2 -0
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/index.js +1 -0
- package/dist/src/hooks/index.js.map +1 -1
- package/dist/src/hooks/use-scroll.d.ts.map +1 -1
- package/dist/src/hooks/use-scroll.js +0 -8
- package/dist/src/hooks/use-scroll.js.map +1 -1
- package/dist/src/hooks/use-timer.d.ts +57 -0
- package/dist/src/hooks/use-timer.d.ts.map +1 -0
- package/dist/src/hooks/use-timer.js +94 -0
- package/dist/src/hooks/use-timer.js.map +1 -0
- package/dist/src/hosts/base.d.ts +20 -0
- package/dist/src/hosts/base.d.ts.map +1 -1
- package/dist/src/hosts/base.js +104 -0
- package/dist/src/hosts/base.js.map +1 -1
- package/dist/src/hosts/box.d.ts +6 -3
- package/dist/src/hosts/box.d.ts.map +1 -1
- package/dist/src/hosts/box.js +10 -3
- package/dist/src/hosts/box.js.map +1 -1
- package/dist/src/hosts/scroll.d.ts +0 -2
- package/dist/src/hosts/scroll.d.ts.map +1 -1
- package/dist/src/hosts/scroll.js +3 -7
- package/dist/src/hosts/scroll.js.map +1 -1
- package/dist/src/hosts/text.d.ts +5 -2
- package/dist/src/hosts/text.d.ts.map +1 -1
- package/dist/src/hosts/text.js +7 -3
- package/dist/src/hosts/text.js.map +1 -1
- package/dist/src/motion/brands.d.ts +9 -0
- package/dist/src/motion/brands.d.ts.map +1 -0
- package/dist/src/motion/brands.js +9 -0
- package/dist/src/motion/brands.js.map +1 -0
- package/dist/src/motion/color-motion-value.d.ts +4 -0
- package/dist/src/motion/color-motion-value.d.ts.map +1 -1
- package/dist/src/motion/color-motion-value.js +4 -0
- package/dist/src/motion/color-motion-value.js.map +1 -1
- package/dist/src/motion/hooks.d.ts +29 -3
- package/dist/src/motion/hooks.d.ts.map +1 -1
- package/dist/src/motion/hooks.js +40 -4
- package/dist/src/motion/hooks.js.map +1 -1
- package/dist/src/motion/index.d.ts +1 -1
- package/dist/src/motion/index.d.ts.map +1 -1
- package/dist/src/motion/index.js +1 -1
- package/dist/src/motion/index.js.map +1 -1
- package/dist/src/motion/motion-value.d.ts +6 -2
- package/dist/src/motion/motion-value.d.ts.map +1 -1
- package/dist/src/motion/motion-value.js +6 -2
- package/dist/src/motion/motion-value.js.map +1 -1
- package/dist/src/motion/use-sequence.d.ts +10 -2
- package/dist/src/motion/use-sequence.d.ts.map +1 -1
- package/dist/src/motion/use-sequence.js +101 -11
- package/dist/src/motion/use-sequence.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/dev/Toast.tsx +27 -17
- package/src/dev.tsx +4 -4
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-scroll.ts +0 -8
- package/src/hooks/use-timer.ts +155 -0
- package/src/hosts/base.ts +120 -0
- package/src/hosts/box.ts +17 -6
- package/src/hosts/scroll.ts +3 -7
- package/src/hosts/text.ts +13 -5
- package/src/motion/brands.ts +10 -0
- package/src/motion/color-motion-value.ts +6 -0
- package/src/motion/hooks.ts +50 -4
- package/src/motion/index.ts +3 -0
- package/src/motion/motion-value.ts +8 -1
- package/src/motion/use-sequence.ts +113 -13
package/src/motion/index.ts
CHANGED
|
@@ -5,10 +5,13 @@ export { useAnimationFrame } from "./frame.js"
|
|
|
5
5
|
export type { ColorInput, MotionValue, RGBA, SpringOptions } from "./motion-value.js"
|
|
6
6
|
export {
|
|
7
7
|
// Color springs
|
|
8
|
+
COLOR_MOTION_VALUE_BRAND,
|
|
8
9
|
ColorMotionValue,
|
|
10
|
+
MOTION_VALUE_BRAND,
|
|
9
11
|
motionValue,
|
|
10
12
|
useColorMotionValue,
|
|
11
13
|
useColorSpring,
|
|
14
|
+
useColorSpringState,
|
|
12
15
|
useMotionValue,
|
|
13
16
|
useMotionValueEvent,
|
|
14
17
|
useSpring,
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* Inspired by framer-motion's MotionValue.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { MOTION_VALUE_BRAND } from "./brands.js"
|
|
6
7
|
import { EventEmitter } from "./event-emitter.js"
|
|
7
8
|
import { subscribeFrame } from "./frame.js"
|
|
8
9
|
import { createSpringResolver, springFromVisualDuration } from "./spring-math.js"
|
|
9
10
|
import { DEFAULT_SPRING_OPTIONS, type SpringOptions } from "./types.js"
|
|
10
11
|
|
|
12
|
+
export { MOTION_VALUE_BRAND } from "./brands.js"
|
|
13
|
+
|
|
11
14
|
/** Create a MotionValue. Factory function like Motion's motionValue(). */
|
|
12
15
|
export function motionValue<T>(initial: T): MotionValue<T> {
|
|
13
16
|
return new MotionValue(initial)
|
|
@@ -17,6 +20,9 @@ export function motionValue<T>(initial: T): MotionValue<T> {
|
|
|
17
20
|
* MotionValue tracks a value and its velocity, supporting spring animations.
|
|
18
21
|
*/
|
|
19
22
|
export class MotionValue<T = number> extends EventEmitter<T> {
|
|
23
|
+
/** @internal Brand for safe type detection */
|
|
24
|
+
readonly [MOTION_VALUE_BRAND] = true
|
|
25
|
+
|
|
20
26
|
private current: T
|
|
21
27
|
private target: T
|
|
22
28
|
private velocity = 0
|
|
@@ -124,11 +130,12 @@ export class MotionValue<T = number> extends EventEmitter<T> {
|
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
export type { ColorInput, RGBA } from "./color.js"
|
|
127
|
-
export { ColorMotionValue } from "./color-motion-value.js"
|
|
133
|
+
export { COLOR_MOTION_VALUE_BRAND, ColorMotionValue } from "./color-motion-value.js"
|
|
128
134
|
export type { EventName, Subscriber } from "./event-emitter.js"
|
|
129
135
|
export {
|
|
130
136
|
useColorMotionValue,
|
|
131
137
|
useColorSpring,
|
|
138
|
+
useColorSpringState,
|
|
132
139
|
useMotionValue,
|
|
133
140
|
useMotionValueEvent,
|
|
134
141
|
useSpring,
|
|
@@ -1,17 +1,51 @@
|
|
|
1
1
|
// useSequence - Timed discrete state sequences (motion.dev-style keyframes)
|
|
2
2
|
// For animating through discrete values like emojis, strings, or any non-numeric state
|
|
3
3
|
|
|
4
|
-
import { useEffect, useState } from "react"
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
5
5
|
|
|
6
6
|
export interface SequenceOptions<T> {
|
|
7
7
|
/** Array of values to animate through */
|
|
8
8
|
keyframes: T[]
|
|
9
|
-
/** Position of each keyframe as 0-1 fraction (
|
|
10
|
-
times
|
|
9
|
+
/** Position of each keyframe as 0-1 fraction (defaults to evenly spaced) */
|
|
10
|
+
times?: number[]
|
|
11
11
|
/** Total duration in milliseconds */
|
|
12
12
|
duration: number
|
|
13
|
+
/** Initial delay before starting (ms) */
|
|
14
|
+
delay?: number
|
|
13
15
|
/** Start automatically (default: true) */
|
|
14
16
|
autoPlay?: boolean
|
|
17
|
+
/** Change this value to replay the sequence */
|
|
18
|
+
playKey?: unknown
|
|
19
|
+
/** Optional callback when the sequence finishes */
|
|
20
|
+
onComplete?: () => void
|
|
21
|
+
/** Optional callback for each keyframe change */
|
|
22
|
+
onUpdate?: (value: T, index: number) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function clamp01(value: number) {
|
|
26
|
+
if (!Number.isFinite(value)) return 0
|
|
27
|
+
if (value < 0) return 0
|
|
28
|
+
if (value > 1) return 1
|
|
29
|
+
return value
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveTimes(count: number, times?: number[]): number[] {
|
|
33
|
+
if (count <= 0) return []
|
|
34
|
+
if (!times || times.length !== count) {
|
|
35
|
+
if (count === 1) return [0]
|
|
36
|
+
const step = 1 / (count - 1)
|
|
37
|
+
return Array.from({ length: count }, (_, i) => i * step)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const resolved = new Array<number>(count)
|
|
41
|
+
let prev = 0
|
|
42
|
+
resolved[0] = 0
|
|
43
|
+
for (let i = 1; i < count; i++) {
|
|
44
|
+
const next = Math.max(prev, clamp01(times[i] ?? 0))
|
|
45
|
+
resolved[i] = next
|
|
46
|
+
prev = next
|
|
47
|
+
}
|
|
48
|
+
return resolved
|
|
15
49
|
}
|
|
16
50
|
|
|
17
51
|
/**
|
|
@@ -30,22 +64,88 @@ export interface SequenceOptions<T> {
|
|
|
30
64
|
* ```
|
|
31
65
|
*/
|
|
32
66
|
export function useSequence<T>(options: SequenceOptions<T>): T {
|
|
33
|
-
const { keyframes,
|
|
67
|
+
const { keyframes, autoPlay = true, playKey } = options
|
|
34
68
|
const [index, setIndex] = useState(0)
|
|
69
|
+
const optionsRef = useRef(options)
|
|
70
|
+
optionsRef.current = options
|
|
71
|
+
const timersRef = useRef<NodeJS.Timeout[]>([])
|
|
72
|
+
const runIdRef = useRef(0)
|
|
73
|
+
const prevPlayKeyRef = useRef<unknown>(playKey)
|
|
74
|
+
const hasInitializedRef = useRef(false)
|
|
35
75
|
|
|
36
|
-
|
|
37
|
-
|
|
76
|
+
const clearTimers = useCallback(() => {
|
|
77
|
+
for (const timer of timersRef.current) {
|
|
78
|
+
clearTimeout(timer)
|
|
79
|
+
}
|
|
80
|
+
timersRef.current = []
|
|
81
|
+
}, [])
|
|
82
|
+
|
|
83
|
+
const play = useCallback(() => {
|
|
84
|
+
const { keyframes, times, duration, delay = 0, onComplete, onUpdate } = optionsRef.current
|
|
85
|
+
clearTimers()
|
|
86
|
+
runIdRef.current += 1
|
|
87
|
+
const runId = runIdRef.current
|
|
88
|
+
|
|
89
|
+
if (keyframes.length === 0) return
|
|
90
|
+
|
|
91
|
+
setIndex((prev) => (prev === 0 ? prev : 0))
|
|
92
|
+
onUpdate?.(keyframes[0], 0)
|
|
93
|
+
|
|
94
|
+
if (keyframes.length === 1) {
|
|
95
|
+
onComplete?.()
|
|
96
|
+
return
|
|
97
|
+
}
|
|
38
98
|
|
|
39
|
-
const
|
|
99
|
+
const resolvedTimes = resolveTimes(keyframes.length, times)
|
|
100
|
+
const durationMs = Number.isFinite(duration) ? Math.max(0, duration) : 0
|
|
101
|
+
const delayMs = Number.isFinite(delay) ? Math.max(0, delay) : 0
|
|
40
102
|
|
|
41
|
-
// Schedule each keyframe transition
|
|
42
103
|
for (let i = 1; i < keyframes.length; i++) {
|
|
43
|
-
const
|
|
44
|
-
|
|
104
|
+
const targetDelay = delayMs + resolvedTimes[i]! * durationMs
|
|
105
|
+
const timer = setTimeout(() => {
|
|
106
|
+
if (runId !== runIdRef.current) return
|
|
107
|
+
setIndex(i)
|
|
108
|
+
onUpdate?.(keyframes[i]!, i)
|
|
109
|
+
if (i === keyframes.length - 1) {
|
|
110
|
+
onComplete?.()
|
|
111
|
+
}
|
|
112
|
+
}, targetDelay)
|
|
113
|
+
timersRef.current.push(timer)
|
|
114
|
+
}
|
|
115
|
+
}, [clearTimers])
|
|
116
|
+
|
|
117
|
+
// Handle autoPlay on initial mount
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (hasInitializedRef.current) return
|
|
120
|
+
hasInitializedRef.current = true
|
|
121
|
+
if (autoPlay) {
|
|
122
|
+
play()
|
|
45
123
|
}
|
|
124
|
+
return () => clearTimers()
|
|
125
|
+
}, [autoPlay, play, clearTimers])
|
|
46
126
|
|
|
47
|
-
|
|
48
|
-
|
|
127
|
+
// Handle playKey changes - ALWAYS replay regardless of autoPlay
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (playKey !== prevPlayKeyRef.current) {
|
|
130
|
+
prevPlayKeyRef.current = playKey
|
|
131
|
+
play()
|
|
132
|
+
}
|
|
133
|
+
}, [playKey, play])
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
return () => clearTimers()
|
|
137
|
+
}, [clearTimers])
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (keyframes.length === 0) return
|
|
141
|
+
if (index >= keyframes.length) {
|
|
142
|
+
setIndex(keyframes.length - 1)
|
|
143
|
+
}
|
|
144
|
+
}, [index, keyframes.length])
|
|
49
145
|
|
|
50
|
-
|
|
146
|
+
if (keyframes.length === 0) {
|
|
147
|
+
return undefined as T
|
|
148
|
+
}
|
|
149
|
+
const safeIndex = Math.min(index, keyframes.length - 1)
|
|
150
|
+
return keyframes[safeIndex]!
|
|
51
151
|
}
|