@vsreact/core 0.0.2 → 0.0.4
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/animation.d.ts +19 -0
- package/dist/animation.js +43 -0
- package/dist/controls.d.ts +96 -2
- package/dist/controls.js +99 -12
- package/dist/cx.d.ts +6 -0
- package/dist/cx.js +26 -0
- package/dist/hooks.d.ts +16 -0
- package/dist/hooks.js +30 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.js +7 -4
- package/dist/meter.d.ts +37 -0
- package/dist/meter.js +48 -0
- package/dist/parameters.d.ts +14 -0
- package/dist/parameters.js +20 -0
- package/dist/theme.js +76 -1
- package/dist/tw.js +13 -1
- package/package.json +1 -1
- package/src/animation.test.tsx +89 -1
- package/src/animation.ts +71 -0
- package/src/controls.test.tsx +193 -0
- package/src/controls.tsx +392 -20
- package/src/cx.ts +33 -0
- package/src/hooks.ts +36 -0
- package/src/index.ts +54 -24
- package/src/meter.tsx +159 -0
- package/src/parameters.ts +30 -0
- package/src/theme.ts +76 -1
- package/src/tw.test.ts +38 -0
- package/src/tw.ts +10 -1
package/src/meter.tsx
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Meter — the level meter every plugin needs: painted bar with a hot zone
|
|
2
|
+
// and a peak-hold line that holds, then falls. Feed it any 0..1 value
|
|
3
|
+
// (typically pushed from C++ via useNativeEvent).
|
|
4
|
+
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { View, Text } from "./primitives";
|
|
7
|
+
|
|
8
|
+
const clamp01 = (v: number) => Math.min(1, Math.max(0, v));
|
|
9
|
+
|
|
10
|
+
export interface PeakHoldOptions {
|
|
11
|
+
/** How long a peak is held before it starts falling. Default 600ms. */
|
|
12
|
+
holdMs?: number;
|
|
13
|
+
/** Fall rate once the hold expires, in value/second. Default 1.5. */
|
|
14
|
+
decayPerSecond?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PeakHoldState {
|
|
18
|
+
peak: number;
|
|
19
|
+
heldForMs: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One peak-hold step (pure, testable): new peaks latch instantly and
|
|
23
|
+
reset the hold timer; after holdMs the peak decays toward the value. */
|
|
24
|
+
export function peakHoldStep(
|
|
25
|
+
state: PeakHoldState,
|
|
26
|
+
value: number,
|
|
27
|
+
dtMs: number,
|
|
28
|
+
{ holdMs = 600, decayPerSecond = 1.5 }: PeakHoldOptions = {},
|
|
29
|
+
): PeakHoldState {
|
|
30
|
+
if (value >= state.peak) return { peak: value, heldForMs: 0 };
|
|
31
|
+
|
|
32
|
+
const heldForMs = state.heldForMs + dtMs;
|
|
33
|
+
if (heldForMs < holdMs) return { peak: state.peak, heldForMs };
|
|
34
|
+
|
|
35
|
+
return { peak: Math.max(value, state.peak - decayPerSecond * (dtMs / 1000)), heldForMs };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const FRAME_MS = 33; // meters read fine at ~30fps
|
|
39
|
+
|
|
40
|
+
/** The held peak for a live value — drives the Meter's peak line. */
|
|
41
|
+
export function usePeakHold(value: number, options: PeakHoldOptions = {}): number {
|
|
42
|
+
const [peak, setPeak] = useState(value);
|
|
43
|
+
const state = useRef<PeakHoldState>({ peak: value, heldForMs: 0 });
|
|
44
|
+
const valueRef = useRef(value);
|
|
45
|
+
valueRef.current = value;
|
|
46
|
+
const optionsRef = useRef(options);
|
|
47
|
+
optionsRef.current = options;
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const id = setInterval(() => {
|
|
51
|
+
state.current = peakHoldStep(state.current, valueRef.current, FRAME_MS, optionsRef.current);
|
|
52
|
+
setPeak(state.current.peak);
|
|
53
|
+
}, FRAME_MS);
|
|
54
|
+
|
|
55
|
+
return () => clearInterval(id);
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
return Math.max(peak, value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MeterProps {
|
|
62
|
+
/** Level 0..1. */
|
|
63
|
+
value: number;
|
|
64
|
+
/** Long-axis length. Default 120. */
|
|
65
|
+
length?: number;
|
|
66
|
+
/** Short-axis thickness. Default 10. */
|
|
67
|
+
thickness?: number;
|
|
68
|
+
/** Horizontal bar instead of the vertical default. */
|
|
69
|
+
horizontal?: boolean;
|
|
70
|
+
/** Show the peak-hold line. Default true. */
|
|
71
|
+
peak?: boolean;
|
|
72
|
+
holdMs?: number;
|
|
73
|
+
decayPerSecond?: number;
|
|
74
|
+
/** Where the fill turns hot, 0..1. Default 0.85. */
|
|
75
|
+
hotFrom?: number;
|
|
76
|
+
trackColor?: string;
|
|
77
|
+
color?: string;
|
|
78
|
+
hotColor?: string;
|
|
79
|
+
label?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A natively painted level meter with hot zone + peak hold. */
|
|
83
|
+
export function Meter({
|
|
84
|
+
value,
|
|
85
|
+
length = 120,
|
|
86
|
+
thickness = 10,
|
|
87
|
+
horizontal,
|
|
88
|
+
peak = true,
|
|
89
|
+
holdMs,
|
|
90
|
+
decayPerSecond,
|
|
91
|
+
hotFrom = 0.85,
|
|
92
|
+
trackColor = "#141714",
|
|
93
|
+
color = "#C6F135",
|
|
94
|
+
hotColor = "#FF4545",
|
|
95
|
+
label,
|
|
96
|
+
}: MeterProps) {
|
|
97
|
+
const level = clamp01(value);
|
|
98
|
+
const held = usePeakHold(level, { holdMs, decayPerSecond });
|
|
99
|
+
const hot = clamp01(hotFrom);
|
|
100
|
+
|
|
101
|
+
const fill = Math.min(level, hot) * length;
|
|
102
|
+
const hotFill = level > hot ? (level - hot) * length : 0;
|
|
103
|
+
const peakAt = clamp01(held) * length;
|
|
104
|
+
|
|
105
|
+
const bar = horizontal ? (
|
|
106
|
+
<View
|
|
107
|
+
className="relative rounded overflow-hidden"
|
|
108
|
+
style={{ width: length, height: thickness, backgroundColor: trackColor }}
|
|
109
|
+
>
|
|
110
|
+
<View
|
|
111
|
+
className="absolute left-0 top-0 bottom-0"
|
|
112
|
+
style={{ width: fill, backgroundColor: color }}
|
|
113
|
+
/>
|
|
114
|
+
{hotFill > 0 ? (
|
|
115
|
+
<View
|
|
116
|
+
className="absolute top-0 bottom-0"
|
|
117
|
+
style={{ left: hot * length, width: hotFill, backgroundColor: hotColor }}
|
|
118
|
+
/>
|
|
119
|
+
) : null}
|
|
120
|
+
{peak && peakAt > 2 ? (
|
|
121
|
+
<View
|
|
122
|
+
className="absolute top-0 bottom-0 w-[2]"
|
|
123
|
+
style={{ left: peakAt - 2, backgroundColor: held >= hot ? hotColor : color }}
|
|
124
|
+
/>
|
|
125
|
+
) : null}
|
|
126
|
+
</View>
|
|
127
|
+
) : (
|
|
128
|
+
<View
|
|
129
|
+
className="relative rounded overflow-hidden"
|
|
130
|
+
style={{ width: thickness, height: length, backgroundColor: trackColor }}
|
|
131
|
+
>
|
|
132
|
+
<View
|
|
133
|
+
className="absolute bottom-0 left-0 right-0"
|
|
134
|
+
style={{ height: fill, backgroundColor: color }}
|
|
135
|
+
/>
|
|
136
|
+
{hotFill > 0 ? (
|
|
137
|
+
<View
|
|
138
|
+
className="absolute left-0 right-0"
|
|
139
|
+
style={{ bottom: hot * length, height: hotFill, backgroundColor: hotColor }}
|
|
140
|
+
/>
|
|
141
|
+
) : null}
|
|
142
|
+
{peak && peakAt > 2 ? (
|
|
143
|
+
<View
|
|
144
|
+
className="absolute left-0 right-0 h-[2]"
|
|
145
|
+
style={{ bottom: peakAt - 2, backgroundColor: held >= hot ? hotColor : color }}
|
|
146
|
+
/>
|
|
147
|
+
) : null}
|
|
148
|
+
</View>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (label === undefined) return bar;
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<View className="items-center gap-2">
|
|
155
|
+
{bar}
|
|
156
|
+
<Text className="text-faint text-[10] font-bold tracking-widest">{label}</Text>
|
|
157
|
+
</View>
|
|
158
|
+
);
|
|
159
|
+
}
|
package/src/parameters.ts
CHANGED
|
@@ -17,6 +17,36 @@ export interface ParameterHandle extends ParameterState {
|
|
|
17
17
|
end: () => void;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface ParameterInfo {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
label: string;
|
|
24
|
+
/** Normalized 0..1 snapshot at mount — use useParameter(id) for live values. */
|
|
25
|
+
value: number;
|
|
26
|
+
text: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Enumerates every APVTS parameter (via param:list) once at mount — the
|
|
31
|
+
* host's parameter set is fixed for the plugin's lifetime. Powers
|
|
32
|
+
* <GenericEditor/> and any auto-generated UI.
|
|
33
|
+
*/
|
|
34
|
+
export function useParameterList(): ParameterInfo[] {
|
|
35
|
+
const [list] = useState<ParameterInfo[]>(() => {
|
|
36
|
+
const result = native.call("param:list");
|
|
37
|
+
if (!Array.isArray(result)) return [];
|
|
38
|
+
return result.map((entry) => ({
|
|
39
|
+
id: String(entry?.id ?? ""),
|
|
40
|
+
name: String(entry?.name ?? entry?.id ?? ""),
|
|
41
|
+
label: String(entry?.label ?? ""),
|
|
42
|
+
value: Number(entry?.value ?? 0),
|
|
43
|
+
text: String(entry?.text ?? ""),
|
|
44
|
+
}));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return list;
|
|
48
|
+
}
|
|
49
|
+
|
|
20
50
|
export function useParameter(id: string): ParameterHandle {
|
|
21
51
|
const [state, setState] = useState<ParameterState>(() => {
|
|
22
52
|
const initial = native.call("param:get", { id });
|
package/src/theme.ts
CHANGED
|
@@ -1,7 +1,82 @@
|
|
|
1
1
|
export type ThemeColors = Record<string, string>;
|
|
2
2
|
|
|
3
|
-
// Tailwind v3
|
|
3
|
+
// The full Tailwind v3 color palette.
|
|
4
4
|
const palettes: Record<string, Record<string, string>> = {
|
|
5
|
+
slate: {
|
|
6
|
+
"50": "#f8fafc", "100": "#f1f5f9", "200": "#e2e8f0", "300": "#cbd5e1",
|
|
7
|
+
"400": "#94a3b8", "500": "#64748b", "600": "#475569", "700": "#334155",
|
|
8
|
+
"800": "#1e293b", "900": "#0f172a", "950": "#020617",
|
|
9
|
+
},
|
|
10
|
+
gray: {
|
|
11
|
+
"50": "#f9fafb", "100": "#f3f4f6", "200": "#e5e7eb", "300": "#d1d5db",
|
|
12
|
+
"400": "#9ca3af", "500": "#6b7280", "600": "#4b5563", "700": "#374151",
|
|
13
|
+
"800": "#1f2937", "900": "#111827", "950": "#030712",
|
|
14
|
+
},
|
|
15
|
+
stone: {
|
|
16
|
+
"50": "#fafaf9", "100": "#f5f5f4", "200": "#e7e5e4", "300": "#d6d3d1",
|
|
17
|
+
"400": "#a8a29e", "500": "#78716c", "600": "#57534e", "700": "#44403c",
|
|
18
|
+
"800": "#292524", "900": "#1c1917", "950": "#0c0a09",
|
|
19
|
+
},
|
|
20
|
+
orange: {
|
|
21
|
+
"50": "#fff7ed", "100": "#ffedd5", "200": "#fed7aa", "300": "#fdba74",
|
|
22
|
+
"400": "#fb923c", "500": "#f97316", "600": "#ea580c", "700": "#c2410c",
|
|
23
|
+
"800": "#9a3412", "900": "#7c2d12", "950": "#431407",
|
|
24
|
+
},
|
|
25
|
+
yellow: {
|
|
26
|
+
"50": "#fefce8", "100": "#fef9c3", "200": "#fef08a", "300": "#fde047",
|
|
27
|
+
"400": "#facc15", "500": "#eab308", "600": "#ca8a04", "700": "#a16207",
|
|
28
|
+
"800": "#854d0e", "900": "#713f12", "950": "#422006",
|
|
29
|
+
},
|
|
30
|
+
green: {
|
|
31
|
+
"50": "#f0fdf4", "100": "#dcfce7", "200": "#bbf7d0", "300": "#86efac",
|
|
32
|
+
"400": "#4ade80", "500": "#22c55e", "600": "#16a34a", "700": "#15803d",
|
|
33
|
+
"800": "#166534", "900": "#14532d", "950": "#052e16",
|
|
34
|
+
},
|
|
35
|
+
teal: {
|
|
36
|
+
"50": "#f0fdfa", "100": "#ccfbf1", "200": "#99f6e4", "300": "#5eead4",
|
|
37
|
+
"400": "#2dd4bf", "500": "#14b8a6", "600": "#0d9488", "700": "#0f766e",
|
|
38
|
+
"800": "#115e59", "900": "#134e4a", "950": "#042f2e",
|
|
39
|
+
},
|
|
40
|
+
cyan: {
|
|
41
|
+
"50": "#ecfeff", "100": "#cffafe", "200": "#a5f3fc", "300": "#67e8f9",
|
|
42
|
+
"400": "#22d3ee", "500": "#06b6d4", "600": "#0891b2", "700": "#0e7490",
|
|
43
|
+
"800": "#155e75", "900": "#164e63", "950": "#083344",
|
|
44
|
+
},
|
|
45
|
+
blue: {
|
|
46
|
+
"50": "#eff6ff", "100": "#dbeafe", "200": "#bfdbfe", "300": "#93c5fd",
|
|
47
|
+
"400": "#60a5fa", "500": "#3b82f6", "600": "#2563eb", "700": "#1d4ed8",
|
|
48
|
+
"800": "#1e40af", "900": "#1e3a8a", "950": "#172554",
|
|
49
|
+
},
|
|
50
|
+
indigo: {
|
|
51
|
+
"50": "#eef2ff", "100": "#e0e7ff", "200": "#c7d2fe", "300": "#a5b4fc",
|
|
52
|
+
"400": "#818cf8", "500": "#6366f1", "600": "#4f46e5", "700": "#4338ca",
|
|
53
|
+
"800": "#3730a3", "900": "#312e81", "950": "#1e1b4b",
|
|
54
|
+
},
|
|
55
|
+
violet: {
|
|
56
|
+
"50": "#f5f3ff", "100": "#ede9fe", "200": "#ddd6fe", "300": "#c4b5fd",
|
|
57
|
+
"400": "#a78bfa", "500": "#8b5cf6", "600": "#7c3aed", "700": "#6d28d9",
|
|
58
|
+
"800": "#5b21b6", "900": "#4c1d95", "950": "#2e1065",
|
|
59
|
+
},
|
|
60
|
+
purple: {
|
|
61
|
+
"50": "#faf5ff", "100": "#f3e8ff", "200": "#e9d5ff", "300": "#d8b4fe",
|
|
62
|
+
"400": "#c084fc", "500": "#a855f7", "600": "#9333ea", "700": "#7e22ce",
|
|
63
|
+
"800": "#6b21a8", "900": "#581c87", "950": "#3b0764",
|
|
64
|
+
},
|
|
65
|
+
fuchsia: {
|
|
66
|
+
"50": "#fdf4ff", "100": "#fae8ff", "200": "#f5d0fe", "300": "#f0abfc",
|
|
67
|
+
"400": "#e879f9", "500": "#d946ef", "600": "#c026d3", "700": "#a21caf",
|
|
68
|
+
"800": "#86198f", "900": "#701a75", "950": "#4a044e",
|
|
69
|
+
},
|
|
70
|
+
pink: {
|
|
71
|
+
"50": "#fdf2f8", "100": "#fce7f3", "200": "#fbcfe8", "300": "#f9a8d4",
|
|
72
|
+
"400": "#f472b6", "500": "#ec4899", "600": "#db2777", "700": "#be185d",
|
|
73
|
+
"800": "#9d174d", "900": "#831843", "950": "#500724",
|
|
74
|
+
},
|
|
75
|
+
rose: {
|
|
76
|
+
"50": "#fff1f2", "100": "#ffe4e6", "200": "#fecdd3", "300": "#fda4af",
|
|
77
|
+
"400": "#fb7185", "500": "#f43f5e", "600": "#e11d48", "700": "#be123c",
|
|
78
|
+
"800": "#9f1239", "900": "#881337", "950": "#4c0519",
|
|
79
|
+
},
|
|
5
80
|
zinc: {
|
|
6
81
|
"50": "#fafafa", "100": "#f4f4f5", "200": "#e4e4e7", "300": "#d4d4d8",
|
|
7
82
|
"400": "#a1a1aa", "500": "#71717a", "600": "#52525b", "700": "#3f3f46",
|
package/src/tw.test.ts
CHANGED
|
@@ -111,3 +111,41 @@ describe("tw resolver", () => {
|
|
|
111
111
|
expect(r.style.backgroundColor).toBe("#27272a");
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
describe("tw 0.0.3 additions", () => {
|
|
116
|
+
test("full Tailwind palette resolves", () => {
|
|
117
|
+
expect(tw("bg-blue-500").style.backgroundColor).toBe("#3b82f6");
|
|
118
|
+
expect(tw("text-rose-400").style.color).toBe("#fb7185");
|
|
119
|
+
expect(tw("border-slate-700").style.borderColor).toBe("#334155");
|
|
120
|
+
expect(tw("bg-violet-950").style.backgroundColor).toBe("#2e1065");
|
|
121
|
+
expect(tw("bg-teal-500/50").style.backgroundColor).toBe("#14b8a680");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("size-* sets width and height together", () => {
|
|
125
|
+
expect(tw("size-10").style).toEqual({ width: 40, height: 40 });
|
|
126
|
+
expect(tw("size-[13]").style).toEqual({ width: 13, height: 13 });
|
|
127
|
+
expect(tw("size-full").style).toEqual({ width: "100%", height: "100%" });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("inset-x / inset-y", () => {
|
|
131
|
+
expect(tw("inset-x-2").style).toEqual({ left: 8, right: 8 });
|
|
132
|
+
expect(tw("inset-y-0").style).toEqual({ top: 0, bottom: 0 });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("larger text sizes", () => {
|
|
136
|
+
expect(tw("text-5xl").style.fontSize).toBe(48);
|
|
137
|
+
expect(tw("text-6xl").style.fontSize).toBe(60);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("negative spacing", () => {
|
|
142
|
+
test("negative margins and offsets", () => {
|
|
143
|
+
expect(tw("-mt-2").style).toEqual({ marginTop: -8 });
|
|
144
|
+
expect(tw("-mx-[10]").style).toEqual({ marginLeft: -10, marginRight: -10 });
|
|
145
|
+
expect(tw("-top-4").style).toEqual({ top: -16 });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("negative percentages", () => {
|
|
149
|
+
expect(tw("-left-1/2").style).toEqual({ left: "-50%" });
|
|
150
|
+
});
|
|
151
|
+
});
|
package/src/tw.ts
CHANGED
|
@@ -83,6 +83,7 @@ const staticClasses: Record<string, Style> = {
|
|
|
83
83
|
|
|
84
84
|
const textSizes: Record<string, number> = {
|
|
85
85
|
xs: 12, sm: 14, base: 16, lg: 18, xl: 20, "2xl": 24, "3xl": 30, "4xl": 36,
|
|
86
|
+
"5xl": 48, "6xl": 60,
|
|
86
87
|
};
|
|
87
88
|
|
|
88
89
|
const radiusSizes: Record<string, number> = {
|
|
@@ -105,6 +106,7 @@ const radiusCorners: Record<string, string[]> = {
|
|
|
105
106
|
const lengthKeys: Record<string, string[]> = {
|
|
106
107
|
w: ["width"],
|
|
107
108
|
h: ["height"],
|
|
109
|
+
size: ["width", "height"],
|
|
108
110
|
"min-w": ["minWidth"],
|
|
109
111
|
"min-h": ["minHeight"],
|
|
110
112
|
"max-w": ["maxWidth"],
|
|
@@ -131,6 +133,8 @@ const lengthKeys: Record<string, string[]> = {
|
|
|
131
133
|
bottom: ["bottom"],
|
|
132
134
|
left: ["left"],
|
|
133
135
|
inset: ["left", "right", "top", "bottom"],
|
|
136
|
+
"inset-x": ["left", "right"],
|
|
137
|
+
"inset-y": ["top", "bottom"],
|
|
134
138
|
basis: ["flexBasis"],
|
|
135
139
|
};
|
|
136
140
|
|
|
@@ -165,7 +169,12 @@ function resolveClass(cls: string): Style | undefined {
|
|
|
165
169
|
const negative = cls.startsWith("-");
|
|
166
170
|
const body = negative ? cls.slice(1) : cls;
|
|
167
171
|
|
|
168
|
-
const negate = (v: StyleValue): StyleValue =>
|
|
172
|
+
const negate = (v: StyleValue): StyleValue => {
|
|
173
|
+
if (!negative) return v;
|
|
174
|
+
if (typeof v === "number") return -v;
|
|
175
|
+
if (typeof v === "string" && v.endsWith("%")) return `-${v}`;
|
|
176
|
+
return v;
|
|
177
|
+
};
|
|
169
178
|
|
|
170
179
|
const known = staticClasses[body];
|
|
171
180
|
if (known && !negative) return known;
|