@fcannizzaro/streamdeck-react 0.1.9 → 0.1.11
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 +190 -21
- package/README.md +3 -1
- package/dist/action.d.ts +2 -2
- package/dist/action.js +2 -2
- package/dist/bundler-shared.d.ts +11 -0
- package/dist/bundler-shared.js +11 -0
- package/dist/context/event-bus.d.ts +1 -1
- package/dist/context/event-bus.js +1 -1
- package/dist/context/touchstrip-context.d.ts +2 -0
- package/dist/context/touchstrip-context.js +5 -0
- package/dist/devtools/bridge.d.ts +35 -7
- package/dist/devtools/bridge.js +153 -46
- package/dist/devtools/highlight.d.ts +6 -0
- package/dist/devtools/highlight.js +106 -57
- package/dist/devtools/index.js +6 -0
- package/dist/devtools/observers/lifecycle.d.ts +4 -4
- package/dist/devtools/server.d.ts +6 -1
- package/dist/devtools/server.js +6 -1
- package/dist/devtools/types.d.ts +50 -6
- package/dist/font-inline.d.ts +5 -1
- package/dist/font-inline.js +8 -3
- package/dist/hooks/animation.d.ts +154 -0
- package/dist/hooks/animation.js +381 -0
- package/dist/hooks/events.js +1 -5
- package/dist/hooks/touchstrip.d.ts +6 -0
- package/dist/hooks/touchstrip.js +37 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +3 -2
- package/dist/manifest-codegen.d.ts +38 -0
- package/dist/manifest-codegen.js +110 -0
- package/dist/node_modules/.bun/xxhash-wasm@1.1.0/node_modules/xxhash-wasm/esm/xxhash-wasm.js +3157 -0
- package/dist/plugin.js +20 -9
- package/dist/reconciler/host-config.js +19 -1
- package/dist/reconciler/vnode.d.ts +26 -0
- package/dist/reconciler/vnode.js +41 -10
- package/dist/render/buffer-pool.d.ts +19 -0
- package/dist/render/buffer-pool.js +51 -0
- package/dist/render/cache.d.ts +41 -0
- package/dist/render/cache.js +159 -5
- package/dist/render/image-cache.d.ts +53 -0
- package/dist/render/image-cache.js +128 -0
- package/dist/render/metrics.d.ts +58 -0
- package/dist/render/metrics.js +101 -0
- package/dist/render/pipeline.d.ts +46 -1
- package/dist/render/pipeline.js +370 -36
- package/dist/render/png.d.ts +10 -1
- package/dist/render/png.js +31 -13
- package/dist/render/render-pool.d.ts +26 -0
- package/dist/render/render-pool.js +141 -0
- package/dist/render/svg.d.ts +7 -0
- package/dist/render/svg.js +139 -0
- package/dist/render/worker.d.ts +1 -0
- package/dist/rollup.d.ts +23 -9
- package/dist/rollup.js +24 -9
- package/dist/roots/flush-coordinator.d.ts +18 -0
- package/dist/roots/flush-coordinator.js +38 -0
- package/dist/roots/registry.d.ts +6 -4
- package/dist/roots/registry.js +47 -33
- package/dist/roots/root.d.ts +32 -2
- package/dist/roots/root.js +104 -14
- package/dist/roots/settings-equality.d.ts +5 -0
- package/dist/roots/settings-equality.js +24 -0
- package/dist/roots/touchstrip-root.d.ts +93 -0
- package/dist/roots/touchstrip-root.js +383 -0
- package/dist/types.d.ts +62 -16
- package/dist/vite.d.ts +22 -8
- package/dist/vite.js +24 -8
- package/package.json +5 -4
- package/dist/context/touchbar-context.d.ts +0 -2
- package/dist/context/touchbar-context.js +0 -5
- package/dist/hooks/touchbar.d.ts +0 -6
- package/dist/hooks/touchbar.js +0 -37
- package/dist/roots/touchbar-root.d.ts +0 -45
- package/dist/roots/touchbar-root.js +0 -175
package/dist/devtools/types.d.ts
CHANGED
|
@@ -81,7 +81,7 @@ export interface SnapshotAction {
|
|
|
81
81
|
tree: SerializedVNode | null;
|
|
82
82
|
dataUri: string | null;
|
|
83
83
|
}
|
|
84
|
-
export interface
|
|
84
|
+
export interface SnapshotTouchStrip {
|
|
85
85
|
deviceId: string;
|
|
86
86
|
deviceName: string;
|
|
87
87
|
canvas: {
|
|
@@ -98,10 +98,11 @@ export interface SnapshotTouchBar {
|
|
|
98
98
|
export interface SnapshotMessage extends BaseMessage {
|
|
99
99
|
type: "snapshot";
|
|
100
100
|
actions: SnapshotAction[];
|
|
101
|
-
|
|
101
|
+
touchStrips: SnapshotTouchStrip[];
|
|
102
102
|
recentConsole: ConsoleMessage[];
|
|
103
103
|
recentNetwork: (NetworkRequestMessage | NetworkResponseMessage | NetworkErrorMessage)[];
|
|
104
104
|
recentEvents: EventBusMessage[];
|
|
105
|
+
metrics?: MetricsData;
|
|
105
106
|
}
|
|
106
107
|
export interface ConsoleMessage extends BaseMessage {
|
|
107
108
|
type: "console";
|
|
@@ -145,9 +146,11 @@ export interface RenderMessage extends BaseMessage {
|
|
|
145
146
|
tree: SerializedVNode;
|
|
146
147
|
dataUri: string;
|
|
147
148
|
renderMs: number;
|
|
149
|
+
/** Per-render pipeline timing profile, when available. */
|
|
150
|
+
profile?: ProfileData;
|
|
148
151
|
}
|
|
149
|
-
export interface
|
|
150
|
-
type: "render:
|
|
152
|
+
export interface TouchStripRenderMessage extends BaseMessage {
|
|
153
|
+
type: "render:touchstrip";
|
|
151
154
|
deviceId: string;
|
|
152
155
|
canvas: {
|
|
153
156
|
width: number;
|
|
@@ -160,6 +163,8 @@ export interface TouchBarRenderMessage extends BaseMessage {
|
|
|
160
163
|
dataUri: string;
|
|
161
164
|
}>;
|
|
162
165
|
renderMs: number;
|
|
166
|
+
/** Per-render pipeline timing profile, when available. */
|
|
167
|
+
profile?: ProfileData;
|
|
163
168
|
}
|
|
164
169
|
export interface EventBusMessage extends BaseMessage {
|
|
165
170
|
type: "event";
|
|
@@ -174,7 +179,7 @@ export interface LifecycleMessage extends BaseMessage {
|
|
|
174
179
|
event: "appear" | "disappear";
|
|
175
180
|
actionId: string;
|
|
176
181
|
actionUuid: string;
|
|
177
|
-
surface: "key" | "dial" | "touch" | "
|
|
182
|
+
surface: "key" | "dial" | "touch" | "touchstrip";
|
|
178
183
|
device: {
|
|
179
184
|
id: string;
|
|
180
185
|
type: number;
|
|
@@ -196,6 +201,45 @@ export interface HighlightRenderMessage extends BaseMessage {
|
|
|
196
201
|
/** Data URI of the rendered image with highlight overlay, or null to clear. */
|
|
197
202
|
dataUri: string | null;
|
|
198
203
|
}
|
|
204
|
+
/** Per-render pipeline timing data, embedded in RenderMessage. */
|
|
205
|
+
export interface ProfileData {
|
|
206
|
+
vnodeToElementMs: number;
|
|
207
|
+
fromJsxMs: number;
|
|
208
|
+
takumiRenderMs: number;
|
|
209
|
+
hashMs: number;
|
|
210
|
+
base64Ms: number;
|
|
211
|
+
totalMs: number;
|
|
212
|
+
skipped: boolean;
|
|
213
|
+
/** Whether this render was served from the image cache. */
|
|
214
|
+
cacheHit: boolean;
|
|
215
|
+
treeDepth: number;
|
|
216
|
+
nodeCount: number;
|
|
217
|
+
}
|
|
218
|
+
/** Aggregate render metrics snapshot. */
|
|
219
|
+
export interface MetricsData {
|
|
220
|
+
/** Total flush() calls (render attempts). */
|
|
221
|
+
flushCount: number;
|
|
222
|
+
/** Flushes that reached the Takumi renderer. */
|
|
223
|
+
renderCount: number;
|
|
224
|
+
/** Tree hash cache hits. */
|
|
225
|
+
cacheHitCount: number;
|
|
226
|
+
/** Skipped due to clean tree (dirty flag check). */
|
|
227
|
+
dirtySkipCount: number;
|
|
228
|
+
/** Skipped due to identical output (post-render FNV-1a dedup). */
|
|
229
|
+
hashDedupCount: number;
|
|
230
|
+
/** Average Takumi render time in milliseconds. */
|
|
231
|
+
avgRenderMs: number;
|
|
232
|
+
/** Peak (worst-case) render time in milliseconds. */
|
|
233
|
+
peakRenderMs: number;
|
|
234
|
+
/** Image cache memory usage in bytes. */
|
|
235
|
+
imageCacheBytes: number;
|
|
236
|
+
/** TouchStrip cache memory usage in bytes. */
|
|
237
|
+
touchstripCacheBytes: number;
|
|
238
|
+
}
|
|
239
|
+
export interface MetricsMessage extends BaseMessage {
|
|
240
|
+
type: "metrics";
|
|
241
|
+
metrics: MetricsData;
|
|
242
|
+
}
|
|
199
243
|
export interface RequestSnapshotMessage extends BaseMessage {
|
|
200
244
|
type: "request:snapshot";
|
|
201
245
|
}
|
|
@@ -207,6 +251,6 @@ export interface HighlightActionMessage extends BaseMessage {
|
|
|
207
251
|
nodeId: number | null;
|
|
208
252
|
}
|
|
209
253
|
/** Messages the plugin server sends to browser clients (via SSE). */
|
|
210
|
-
export type ServerMessage = ServerInfoMessage | SnapshotMessage | ConsoleMessage | NetworkRequestMessage | NetworkResponseMessage | NetworkErrorMessage | RenderMessage |
|
|
254
|
+
export type ServerMessage = ServerInfoMessage | SnapshotMessage | ConsoleMessage | NetworkRequestMessage | NetworkResponseMessage | NetworkErrorMessage | RenderMessage | TouchStripRenderMessage | EventBusMessage | LifecycleMessage | HighlightRenderMessage | MetricsMessage;
|
|
211
255
|
/** Messages browser clients send to the plugin server (via POST /message). */
|
|
212
256
|
export type ClientMessage = RequestSnapshotMessage | HighlightActionMessage;
|
package/dist/font-inline.d.ts
CHANGED
|
@@ -18,5 +18,9 @@ export declare function resolveFontId(source: string, importer: string | undefin
|
|
|
18
18
|
*
|
|
19
19
|
* Follows symlinks before reading so that bun's internal
|
|
20
20
|
* `node_modules/.bun/` layout resolves to the real file.
|
|
21
|
+
*
|
|
22
|
+
* Async: uses `fs.promises.readFile()` to avoid blocking the bundler
|
|
23
|
+
* event loop for large fonts (1-2 MB). Both Vite and Rollup `load`
|
|
24
|
+
* hooks support async return values.
|
|
21
25
|
*/
|
|
22
|
-
export declare function loadFont(id: string): string | null
|
|
26
|
+
export declare function loadFont(id: string): Promise<string | null>;
|
package/dist/font-inline.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { dirname, resolve } from "node:path";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import {
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
4
5
|
//#region src/font-inline.ts
|
|
5
6
|
var FONT_RE = /\.(ttf|otf|woff2?)$/;
|
|
6
7
|
/**
|
|
@@ -33,10 +34,14 @@ function resolveFontId(source, importer) {
|
|
|
33
34
|
*
|
|
34
35
|
* Follows symlinks before reading so that bun's internal
|
|
35
36
|
* `node_modules/.bun/` layout resolves to the real file.
|
|
37
|
+
*
|
|
38
|
+
* Async: uses `fs.promises.readFile()` to avoid blocking the bundler
|
|
39
|
+
* event loop for large fonts (1-2 MB). Both Vite and Rollup `load`
|
|
40
|
+
* hooks support async return values.
|
|
36
41
|
*/
|
|
37
|
-
function loadFont(id) {
|
|
42
|
+
async function loadFont(id) {
|
|
38
43
|
if (!isFontFile(id)) return null;
|
|
39
|
-
return `export default Buffer.from("${
|
|
44
|
+
return `export default Buffer.from("${(await readFile(safeRealpath(id) ?? id)).toString("base64")}", "base64");`;
|
|
40
45
|
}
|
|
41
46
|
function safeRealpath(p) {
|
|
42
47
|
try {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/** A single number or a flat object of named numbers. */
|
|
2
|
+
export type AnimationTarget = number | Record<string, number>;
|
|
3
|
+
/** Maps an AnimationTarget shape to its animated output shape. */
|
|
4
|
+
export type AnimatedValue<T extends AnimationTarget> = T extends number ? number : {
|
|
5
|
+
[K in keyof T]: number;
|
|
6
|
+
};
|
|
7
|
+
export interface SpringConfig {
|
|
8
|
+
/** Stiffness coefficient (force per unit displacement). @default 170 */
|
|
9
|
+
tension: number;
|
|
10
|
+
/** Damping coefficient (force per unit velocity). @default 26 */
|
|
11
|
+
friction: number;
|
|
12
|
+
/** Mass of the simulated object. @default 1 */
|
|
13
|
+
mass: number;
|
|
14
|
+
/** Absolute velocity threshold below which the spring settles. @default 0.01 */
|
|
15
|
+
velocityThreshold: number;
|
|
16
|
+
/** Absolute displacement threshold below which the spring settles. @default 0.005 */
|
|
17
|
+
displacementThreshold: number;
|
|
18
|
+
/** Clamp output to target (no overshoot). @default false */
|
|
19
|
+
clamp: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface SpringResult<T extends AnimationTarget> {
|
|
22
|
+
/** Current interpolated value(s). */
|
|
23
|
+
value: AnimatedValue<T>;
|
|
24
|
+
/** Whether the spring is still in motion. */
|
|
25
|
+
isAnimating: boolean;
|
|
26
|
+
/** Imperatively update the target. */
|
|
27
|
+
set: (target: T) => void;
|
|
28
|
+
/** Jump immediately to a value (no animation). */
|
|
29
|
+
jump: (target: T) => void;
|
|
30
|
+
}
|
|
31
|
+
export type EasingName = "linear" | "easeIn" | "easeOut" | "easeInOut" | "easeInCubic" | "easeOutCubic" | "easeInOutCubic" | "easeInBack" | "easeOutBack" | "easeOutBounce";
|
|
32
|
+
export type EasingFn = (t: number) => number;
|
|
33
|
+
export interface TweenConfig {
|
|
34
|
+
/** Duration in milliseconds. @default 300 */
|
|
35
|
+
duration: number;
|
|
36
|
+
/** Easing function name or custom (t: number) => number. @default "easeOut" */
|
|
37
|
+
easing: EasingName | EasingFn;
|
|
38
|
+
/** Target FPS for the animation tick loop. @default 60 */
|
|
39
|
+
fps: number;
|
|
40
|
+
}
|
|
41
|
+
export interface TweenResult<T extends AnimationTarget> {
|
|
42
|
+
/** Current interpolated value(s). */
|
|
43
|
+
value: AnimatedValue<T>;
|
|
44
|
+
/** 0..1 normalized progress of the current transition. */
|
|
45
|
+
progress: number;
|
|
46
|
+
/** Whether the tween is still running. */
|
|
47
|
+
isAnimating: boolean;
|
|
48
|
+
/** Imperatively update the target (starts a new tween from current value). */
|
|
49
|
+
set: (target: T) => void;
|
|
50
|
+
/** Jump immediately to a value (no animation). */
|
|
51
|
+
jump: (target: T) => void;
|
|
52
|
+
}
|
|
53
|
+
interface SpringState {
|
|
54
|
+
position: number;
|
|
55
|
+
velocity: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Semi-implicit Euler step for a damped harmonic oscillator.
|
|
59
|
+
* Updates velocity first, then position — stable at low frame rates.
|
|
60
|
+
*/
|
|
61
|
+
export declare function stepSpring(state: SpringState, target: number, config: SpringConfig, dtSeconds: number): SpringState;
|
|
62
|
+
/** Check if a spring channel has come to rest. */
|
|
63
|
+
export declare function isSettled(state: SpringState, target: number, config: SpringConfig): boolean;
|
|
64
|
+
export declare const SpringPresets: {
|
|
65
|
+
/** Default balanced spring. */
|
|
66
|
+
readonly default: {
|
|
67
|
+
readonly tension: 170;
|
|
68
|
+
readonly friction: 26;
|
|
69
|
+
readonly mass: 1;
|
|
70
|
+
};
|
|
71
|
+
/** Quick and responsive with slight overshoot. Good for press feedback. */
|
|
72
|
+
readonly stiff: {
|
|
73
|
+
readonly tension: 400;
|
|
74
|
+
readonly friction: 28;
|
|
75
|
+
readonly mass: 1;
|
|
76
|
+
};
|
|
77
|
+
/** Bouncy with visible oscillation. Good for playful UIs. */
|
|
78
|
+
readonly wobbly: {
|
|
79
|
+
readonly tension: 180;
|
|
80
|
+
readonly friction: 12;
|
|
81
|
+
readonly mass: 1;
|
|
82
|
+
};
|
|
83
|
+
/** Slow and smooth. Good for background transitions. */
|
|
84
|
+
readonly gentle: {
|
|
85
|
+
readonly tension: 120;
|
|
86
|
+
readonly friction: 14;
|
|
87
|
+
readonly mass: 1;
|
|
88
|
+
};
|
|
89
|
+
/** Very slow, molasses-like. Good for ambient drift. */
|
|
90
|
+
readonly molasses: {
|
|
91
|
+
readonly tension: 80;
|
|
92
|
+
readonly friction: 30;
|
|
93
|
+
readonly mass: 1;
|
|
94
|
+
};
|
|
95
|
+
/** Snappy with no overshoot. Good for precise UI elements. */
|
|
96
|
+
readonly snap: {
|
|
97
|
+
readonly tension: 300;
|
|
98
|
+
readonly friction: 36;
|
|
99
|
+
readonly mass: 1;
|
|
100
|
+
readonly clamp: true;
|
|
101
|
+
};
|
|
102
|
+
/** Heavy object feel. */
|
|
103
|
+
readonly heavy: {
|
|
104
|
+
readonly tension: 200;
|
|
105
|
+
readonly friction: 20;
|
|
106
|
+
readonly mass: 3;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
export declare const Easings: Record<EasingName, EasingFn>;
|
|
110
|
+
/**
|
|
111
|
+
* Spring physics-based animation hook.
|
|
112
|
+
*
|
|
113
|
+
* Returns animated value(s) that follow the target with natural spring dynamics
|
|
114
|
+
* (damped harmonic oscillator). Supports single numbers and objects of numbers.
|
|
115
|
+
*
|
|
116
|
+
* Automatically starts/stops the tick loop when the spring is in motion or settled.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```tsx
|
|
120
|
+
* const { value: scale } = useSpring(pressed ? 0.85 : 1, SpringPresets.wobbly);
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```tsx
|
|
125
|
+
* const { value } = useSpring({ x: targetX, opacity: show ? 1 : 0 }, SpringPresets.gentle);
|
|
126
|
+
* // value.x and value.opacity are plain numbers
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare function useSpring<T extends AnimationTarget>(target: T, config?: Partial<SpringConfig> & {
|
|
130
|
+
fps?: number;
|
|
131
|
+
}): SpringResult<T>;
|
|
132
|
+
/**
|
|
133
|
+
* Duration + easing-based animation hook.
|
|
134
|
+
*
|
|
135
|
+
* Returns animated value(s) that smoothly transition to the target over the
|
|
136
|
+
* specified duration using an easing curve. Supports single numbers and objects
|
|
137
|
+
* of numbers.
|
|
138
|
+
*
|
|
139
|
+
* When the target changes mid-tween, a new tween starts from the current
|
|
140
|
+
* interpolated position (no discontinuity).
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```tsx
|
|
144
|
+
* const { value: opacity } = useTween(visible ? 1 : 0, { duration: 500, easing: "easeInOut" });
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```tsx
|
|
149
|
+
* const { value } = useTween({ y: expanded ? 0 : -50, opacity: expanded ? 1 : 0 });
|
|
150
|
+
* // value.y and value.opacity are plain numbers
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export declare function useTween<T extends AnimationTarget>(target: T, config?: Partial<TweenConfig>): TweenResult<T>;
|
|
154
|
+
export {};
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { usePrevious, useTick } from "./utility.js";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
//#region src/hooks/animation.ts
|
|
4
|
+
var SPRING_DEFAULTS = {
|
|
5
|
+
tension: 170,
|
|
6
|
+
friction: 26,
|
|
7
|
+
mass: 1,
|
|
8
|
+
velocityThreshold: .01,
|
|
9
|
+
displacementThreshold: .005,
|
|
10
|
+
clamp: false
|
|
11
|
+
};
|
|
12
|
+
var TWEEN_DEFAULTS = {
|
|
13
|
+
duration: 300,
|
|
14
|
+
easing: "easeOut",
|
|
15
|
+
fps: 60
|
|
16
|
+
};
|
|
17
|
+
/** Max dt cap to prevent spring explosion after long pauses.
|
|
18
|
+
* If the process is suspended (debugger, GC pause), dt could be
|
|
19
|
+
* seconds-long, causing the spring to overshoot wildly. Capping
|
|
20
|
+
* at ~64ms (roughly one frame at 15fps) keeps the simulation stable. */
|
|
21
|
+
var MAX_DT_SEC = .064;
|
|
22
|
+
/**
|
|
23
|
+
* Semi-implicit Euler step for a damped harmonic oscillator.
|
|
24
|
+
* Updates velocity first, then position — stable at low frame rates.
|
|
25
|
+
*/
|
|
26
|
+
function stepSpring(state, target, config, dtSeconds) {
|
|
27
|
+
const { tension, friction, mass } = config;
|
|
28
|
+
const displacement = state.position - target;
|
|
29
|
+
const acceleration = (-tension * displacement + -friction * state.velocity) / mass;
|
|
30
|
+
let velocity = state.velocity + acceleration * dtSeconds;
|
|
31
|
+
let position = state.position + velocity * dtSeconds;
|
|
32
|
+
if (config.clamp) {
|
|
33
|
+
if (displacement > 0 && position < target || displacement < 0 && position > target) {
|
|
34
|
+
position = target;
|
|
35
|
+
velocity = 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
position,
|
|
40
|
+
velocity
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Check if a spring channel has come to rest. */
|
|
44
|
+
function isSettled(state, target, config) {
|
|
45
|
+
return Math.abs(state.velocity) < config.velocityThreshold && Math.abs(state.position - target) < config.displacementThreshold;
|
|
46
|
+
}
|
|
47
|
+
var SpringPresets = {
|
|
48
|
+
default: {
|
|
49
|
+
tension: 170,
|
|
50
|
+
friction: 26,
|
|
51
|
+
mass: 1
|
|
52
|
+
},
|
|
53
|
+
stiff: {
|
|
54
|
+
tension: 400,
|
|
55
|
+
friction: 28,
|
|
56
|
+
mass: 1
|
|
57
|
+
},
|
|
58
|
+
wobbly: {
|
|
59
|
+
tension: 180,
|
|
60
|
+
friction: 12,
|
|
61
|
+
mass: 1
|
|
62
|
+
},
|
|
63
|
+
gentle: {
|
|
64
|
+
tension: 120,
|
|
65
|
+
friction: 14,
|
|
66
|
+
mass: 1
|
|
67
|
+
},
|
|
68
|
+
molasses: {
|
|
69
|
+
tension: 80,
|
|
70
|
+
friction: 30,
|
|
71
|
+
mass: 1
|
|
72
|
+
},
|
|
73
|
+
snap: {
|
|
74
|
+
tension: 300,
|
|
75
|
+
friction: 36,
|
|
76
|
+
mass: 1,
|
|
77
|
+
clamp: true
|
|
78
|
+
},
|
|
79
|
+
heavy: {
|
|
80
|
+
tension: 200,
|
|
81
|
+
friction: 20,
|
|
82
|
+
mass: 3
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var Easings = {
|
|
86
|
+
linear: (t) => t,
|
|
87
|
+
easeIn: (t) => t * t,
|
|
88
|
+
easeOut: (t) => t * (2 - t),
|
|
89
|
+
easeInOut: (t) => t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
|
90
|
+
easeInCubic: (t) => t * t * t,
|
|
91
|
+
easeOutCubic: (t) => 1 - (1 - t) ** 3,
|
|
92
|
+
easeInOutCubic: (t) => t < .5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2,
|
|
93
|
+
easeInBack: (t) => {
|
|
94
|
+
const c = 1.70158;
|
|
95
|
+
return (c + 1) * t * t * t - c * t * t;
|
|
96
|
+
},
|
|
97
|
+
easeOutBack: (t) => {
|
|
98
|
+
const c = 1.70158;
|
|
99
|
+
return 1 + (c + 1) * (t - 1) ** 3 + c * (t - 1) ** 2;
|
|
100
|
+
},
|
|
101
|
+
easeOutBounce: (t) => {
|
|
102
|
+
const n1 = 7.5625;
|
|
103
|
+
const d1 = 2.75;
|
|
104
|
+
if (t < 1 / d1) return n1 * t * t;
|
|
105
|
+
if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + .75;
|
|
106
|
+
if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + .9375;
|
|
107
|
+
return n1 * (t -= 2.625 / d1) * t + .984375;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
function resolveEasing(easing) {
|
|
111
|
+
return typeof easing === "function" ? easing : Easings[easing];
|
|
112
|
+
}
|
|
113
|
+
var SCALAR_KEY = "_";
|
|
114
|
+
function toChannelMap(target) {
|
|
115
|
+
if (typeof target === "number") return new Map([[SCALAR_KEY, target]]);
|
|
116
|
+
return new Map(Object.entries(target));
|
|
117
|
+
}
|
|
118
|
+
function snapshotValue(target) {
|
|
119
|
+
if (typeof target === "number") return target;
|
|
120
|
+
const result = {};
|
|
121
|
+
for (const [key, val] of Object.entries(target)) result[key] = val;
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
function snapshotFromSpringState(state, isObject) {
|
|
125
|
+
if (!isObject) return state.get(SCALAR_KEY).position;
|
|
126
|
+
const result = {};
|
|
127
|
+
for (const [key, s] of state) result[key] = s.position;
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
function snapshotFromMap(map, isObject) {
|
|
131
|
+
if (!isObject) return map.get(SCALAR_KEY);
|
|
132
|
+
const result = {};
|
|
133
|
+
for (const [key, val] of map) result[key] = val;
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
function forEachChannel(target, fn) {
|
|
137
|
+
if (typeof target === "number") fn(SCALAR_KEY, target);
|
|
138
|
+
else for (const [key, val] of Object.entries(target)) fn(key, val);
|
|
139
|
+
}
|
|
140
|
+
function shallowEqual(a, b) {
|
|
141
|
+
if (typeof a === "number" && typeof b === "number") return a === b;
|
|
142
|
+
if (typeof a !== typeof b) return false;
|
|
143
|
+
const aObj = a;
|
|
144
|
+
const bObj = b;
|
|
145
|
+
const aKeys = Object.keys(aObj);
|
|
146
|
+
const bKeys = Object.keys(bObj);
|
|
147
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
148
|
+
return aKeys.every((k) => aObj[k] === bObj[k]);
|
|
149
|
+
}
|
|
150
|
+
function initializeSpringState(target) {
|
|
151
|
+
const map = /* @__PURE__ */ new Map();
|
|
152
|
+
forEachChannel(target, (key, value) => {
|
|
153
|
+
map.set(key, {
|
|
154
|
+
position: value,
|
|
155
|
+
velocity: 0
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
return map;
|
|
159
|
+
}
|
|
160
|
+
function snapSpringToTarget(state, target) {
|
|
161
|
+
forEachChannel(target, (key, value) => {
|
|
162
|
+
state.set(key, {
|
|
163
|
+
position: value,
|
|
164
|
+
velocity: 0
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Spring physics-based animation hook.
|
|
170
|
+
*
|
|
171
|
+
* Returns animated value(s) that follow the target with natural spring dynamics
|
|
172
|
+
* (damped harmonic oscillator). Supports single numbers and objects of numbers.
|
|
173
|
+
*
|
|
174
|
+
* Automatically starts/stops the tick loop when the spring is in motion or settled.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```tsx
|
|
178
|
+
* const { value: scale } = useSpring(pressed ? 0.85 : 1, SpringPresets.wobbly);
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```tsx
|
|
183
|
+
* const { value } = useSpring({ x: targetX, opacity: show ? 1 : 0 }, SpringPresets.gentle);
|
|
184
|
+
* // value.x and value.opacity are plain numbers
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
function useSpring(target, config) {
|
|
188
|
+
const resolvedConfig = useMemo(() => ({
|
|
189
|
+
...SPRING_DEFAULTS,
|
|
190
|
+
...config
|
|
191
|
+
}), [
|
|
192
|
+
config?.tension,
|
|
193
|
+
config?.friction,
|
|
194
|
+
config?.mass,
|
|
195
|
+
config?.velocityThreshold,
|
|
196
|
+
config?.displacementThreshold,
|
|
197
|
+
config?.clamp
|
|
198
|
+
]);
|
|
199
|
+
const fps = config?.fps ?? 60;
|
|
200
|
+
const isObject = typeof target === "object" && target !== null;
|
|
201
|
+
const stateRef = useRef(null);
|
|
202
|
+
if (stateRef.current === null) stateRef.current = initializeSpringState(target);
|
|
203
|
+
const targetRef = useRef(target);
|
|
204
|
+
targetRef.current = target;
|
|
205
|
+
const configRef = useRef(resolvedConfig);
|
|
206
|
+
configRef.current = resolvedConfig;
|
|
207
|
+
const [value, setValue] = useState(() => snapshotValue(target));
|
|
208
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
209
|
+
const prevTarget = usePrevious(target);
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
if (prevTarget !== void 0 && !shallowEqual(prevTarget, target)) {
|
|
212
|
+
const state = stateRef.current;
|
|
213
|
+
forEachChannel(target, (key, val) => {
|
|
214
|
+
if (!state.has(key)) state.set(key, {
|
|
215
|
+
position: val,
|
|
216
|
+
velocity: 0
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
setIsAnimating(true);
|
|
220
|
+
}
|
|
221
|
+
}, [prevTarget, target]);
|
|
222
|
+
useTick((deltaMs) => {
|
|
223
|
+
const dt = Math.min(deltaMs / 1e3, MAX_DT_SEC);
|
|
224
|
+
const state = stateRef.current;
|
|
225
|
+
const currentTarget = targetRef.current;
|
|
226
|
+
const cfg = configRef.current;
|
|
227
|
+
let allSettled = true;
|
|
228
|
+
forEachChannel(currentTarget, (key, targetVal) => {
|
|
229
|
+
const channelState = state.get(key);
|
|
230
|
+
if (!channelState) return;
|
|
231
|
+
const next = stepSpring(channelState, targetVal, cfg, dt);
|
|
232
|
+
state.set(key, next);
|
|
233
|
+
if (!isSettled(next, targetVal, cfg)) allSettled = false;
|
|
234
|
+
});
|
|
235
|
+
if (allSettled) {
|
|
236
|
+
snapSpringToTarget(state, currentTarget);
|
|
237
|
+
setValue(snapshotValue(currentTarget));
|
|
238
|
+
setIsAnimating(false);
|
|
239
|
+
} else setValue(snapshotFromSpringState(state, isObject));
|
|
240
|
+
}, isAnimating ? fps : false);
|
|
241
|
+
const set = useCallback((newTarget) => {
|
|
242
|
+
targetRef.current = newTarget;
|
|
243
|
+
const state = stateRef.current;
|
|
244
|
+
forEachChannel(newTarget, (key, val) => {
|
|
245
|
+
if (!state.has(key)) state.set(key, {
|
|
246
|
+
position: val,
|
|
247
|
+
velocity: 0
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
setIsAnimating(true);
|
|
251
|
+
}, []);
|
|
252
|
+
const jump = useCallback((newTarget) => {
|
|
253
|
+
targetRef.current = newTarget;
|
|
254
|
+
snapSpringToTarget(stateRef.current, newTarget);
|
|
255
|
+
setValue(snapshotValue(newTarget));
|
|
256
|
+
setIsAnimating(false);
|
|
257
|
+
}, []);
|
|
258
|
+
return useMemo(() => ({
|
|
259
|
+
value,
|
|
260
|
+
isAnimating,
|
|
261
|
+
set,
|
|
262
|
+
jump
|
|
263
|
+
}), [
|
|
264
|
+
value,
|
|
265
|
+
isAnimating,
|
|
266
|
+
set,
|
|
267
|
+
jump
|
|
268
|
+
]);
|
|
269
|
+
}
|
|
270
|
+
function interpolateTween(tween, easingFn) {
|
|
271
|
+
const easedT = easingFn(tween.duration > 0 ? Math.min(tween.elapsed / tween.duration, 1) : 1);
|
|
272
|
+
const result = /* @__PURE__ */ new Map();
|
|
273
|
+
for (const [key, fromVal] of tween.from) {
|
|
274
|
+
const toVal = tween.to.get(key) ?? fromVal;
|
|
275
|
+
result.set(key, fromVal + (toVal - fromVal) * easedT);
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Duration + easing-based animation hook.
|
|
281
|
+
*
|
|
282
|
+
* Returns animated value(s) that smoothly transition to the target over the
|
|
283
|
+
* specified duration using an easing curve. Supports single numbers and objects
|
|
284
|
+
* of numbers.
|
|
285
|
+
*
|
|
286
|
+
* When the target changes mid-tween, a new tween starts from the current
|
|
287
|
+
* interpolated position (no discontinuity).
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```tsx
|
|
291
|
+
* const { value: opacity } = useTween(visible ? 1 : 0, { duration: 500, easing: "easeInOut" });
|
|
292
|
+
* ```
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```tsx
|
|
296
|
+
* const { value } = useTween({ y: expanded ? 0 : -50, opacity: expanded ? 1 : 0 });
|
|
297
|
+
* // value.y and value.opacity are plain numbers
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
function useTween(target, config) {
|
|
301
|
+
const duration = config?.duration ?? TWEEN_DEFAULTS.duration;
|
|
302
|
+
const easingFn = resolveEasing(config?.easing ?? TWEEN_DEFAULTS.easing);
|
|
303
|
+
const fps = config?.fps ?? TWEEN_DEFAULTS.fps;
|
|
304
|
+
const isObject = typeof target === "object" && target !== null;
|
|
305
|
+
const tweenRef = useRef(null);
|
|
306
|
+
if (tweenRef.current === null) {
|
|
307
|
+
const initial = toChannelMap(target);
|
|
308
|
+
tweenRef.current = {
|
|
309
|
+
from: new Map(initial),
|
|
310
|
+
to: new Map(initial),
|
|
311
|
+
elapsed: duration,
|
|
312
|
+
duration
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const targetRef = useRef(target);
|
|
316
|
+
const easingRef = useRef(easingFn);
|
|
317
|
+
easingRef.current = easingFn;
|
|
318
|
+
const [value, setValue] = useState(() => snapshotValue(target));
|
|
319
|
+
const [progress, setProgress] = useState(1);
|
|
320
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
321
|
+
const prevTarget = usePrevious(target);
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
if (prevTarget !== void 0 && !shallowEqual(prevTarget, target)) {
|
|
324
|
+
const tween = tweenRef.current;
|
|
325
|
+
tween.from = interpolateTween(tween, easingRef.current);
|
|
326
|
+
tween.to = toChannelMap(target);
|
|
327
|
+
tween.elapsed = 0;
|
|
328
|
+
tween.duration = duration;
|
|
329
|
+
targetRef.current = target;
|
|
330
|
+
setIsAnimating(true);
|
|
331
|
+
}
|
|
332
|
+
}, [
|
|
333
|
+
prevTarget,
|
|
334
|
+
target,
|
|
335
|
+
duration
|
|
336
|
+
]);
|
|
337
|
+
useTick((deltaMs) => {
|
|
338
|
+
const tween = tweenRef.current;
|
|
339
|
+
tween.elapsed = Math.min(tween.elapsed + deltaMs, tween.duration);
|
|
340
|
+
const t = tween.duration > 0 ? tween.elapsed / tween.duration : 1;
|
|
341
|
+
const result = interpolateTween(tween, easingRef.current);
|
|
342
|
+
setProgress(t);
|
|
343
|
+
setValue(snapshotFromMap(result, isObject));
|
|
344
|
+
if (t >= 1) setIsAnimating(false);
|
|
345
|
+
}, isAnimating ? fps : false);
|
|
346
|
+
const set = useCallback((newTarget) => {
|
|
347
|
+
const tween = tweenRef.current;
|
|
348
|
+
tween.from = interpolateTween(tween, easingRef.current);
|
|
349
|
+
tween.to = toChannelMap(newTarget);
|
|
350
|
+
tween.elapsed = 0;
|
|
351
|
+
tween.duration = duration;
|
|
352
|
+
targetRef.current = newTarget;
|
|
353
|
+
setIsAnimating(true);
|
|
354
|
+
}, [duration]);
|
|
355
|
+
const jump = useCallback((newTarget) => {
|
|
356
|
+
const tween = tweenRef.current;
|
|
357
|
+
const map = toChannelMap(newTarget);
|
|
358
|
+
tween.from = new Map(map);
|
|
359
|
+
tween.to = new Map(map);
|
|
360
|
+
tween.elapsed = tween.duration;
|
|
361
|
+
targetRef.current = newTarget;
|
|
362
|
+
setValue(snapshotValue(newTarget));
|
|
363
|
+
setProgress(1);
|
|
364
|
+
setIsAnimating(false);
|
|
365
|
+
}, []);
|
|
366
|
+
return useMemo(() => ({
|
|
367
|
+
value,
|
|
368
|
+
progress,
|
|
369
|
+
isAnimating,
|
|
370
|
+
set,
|
|
371
|
+
jump
|
|
372
|
+
}), [
|
|
373
|
+
value,
|
|
374
|
+
progress,
|
|
375
|
+
isAnimating,
|
|
376
|
+
set,
|
|
377
|
+
jump
|
|
378
|
+
]);
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
381
|
+
export { Easings, SpringPresets, useSpring, useTween };
|
package/dist/hooks/events.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventBusContext, StreamDeckContext } from "../context/providers.js";
|
|
2
2
|
import { useCallbackRef } from "./internal/useCallbackRef.js";
|
|
3
|
-
import { useContext, useEffect
|
|
3
|
+
import { useContext, useEffect } from "react";
|
|
4
4
|
//#region src/hooks/events.ts
|
|
5
5
|
function useEvent(event, callback) {
|
|
6
6
|
const bus = useContext(EventBusContext);
|
|
@@ -37,11 +37,7 @@ function useTouchTap(callback) {
|
|
|
37
37
|
}
|
|
38
38
|
function useDialHint(hints) {
|
|
39
39
|
const { action } = useContext(StreamDeckContext);
|
|
40
|
-
const prevHints = useRef("");
|
|
41
40
|
useEffect(() => {
|
|
42
|
-
const serialized = JSON.stringify(hints);
|
|
43
|
-
if (serialized === prevHints.current) return;
|
|
44
|
-
prevHints.current = serialized;
|
|
45
41
|
if ("setTriggerDescription" in action) action.setTriggerDescription({
|
|
46
42
|
rotate: hints.rotate,
|
|
47
43
|
push: hints.press,
|