@vsreact/core 0.0.19 → 0.0.20
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/controls.d.ts +5 -0
- package/dist/controls.js +58 -5
- package/dist/hostConfig.js +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/primitives.d.ts +23 -0
- package/dist/tw.js +4 -0
- package/package.json +1 -1
- package/src/controls.test.tsx +48 -1
- package/src/controls.tsx +62 -0
- package/src/hostConfig.ts +2 -0
- package/src/index.ts +1 -1
- package/src/primitives.tsx +23 -0
- package/src/tw.test.ts +8 -0
- package/src/tw.ts +4 -0
package/dist/controls.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import type { KeyEventPayload } from "./primitives";
|
|
1
2
|
/** Vertical-drag-to-value mapping shared by Knob (and tested in isolation). */
|
|
2
3
|
export declare function dragToValue(startValue: number, dy: number, sensitivity?: number): number;
|
|
4
|
+
/** Web <input type="range"> keyboard model for a normalized control:
|
|
5
|
+
arrows ±0.01 (shift = ±0.001 fine), PageUp/Down ±0.1, Home/End 0/1.
|
|
6
|
+
Returns the new value, or null for keys the control doesn't own. */
|
|
7
|
+
export declare function sliderKeyTarget(value: number, e: KeyEventPayload): number | null;
|
|
3
8
|
export interface KnobProps {
|
|
4
9
|
value: number;
|
|
5
10
|
text?: string;
|
package/dist/controls.js
CHANGED
|
@@ -13,6 +13,30 @@ const clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
|
13
13
|
export function dragToValue(startValue, dy, sensitivity = 0.005) {
|
|
14
14
|
return clamp01(startValue - dy * sensitivity);
|
|
15
15
|
}
|
|
16
|
+
/** Web <input type="range"> keyboard model for a normalized control:
|
|
17
|
+
arrows ±0.01 (shift = ±0.001 fine), PageUp/Down ±0.1, Home/End 0/1.
|
|
18
|
+
Returns the new value, or null for keys the control doesn't own. */
|
|
19
|
+
export function sliderKeyTarget(value, e) {
|
|
20
|
+
const step = e.shift ? 0.001 : 0.01;
|
|
21
|
+
switch (e.key) {
|
|
22
|
+
case "ArrowUp":
|
|
23
|
+
case "ArrowRight":
|
|
24
|
+
return clamp01(value + step);
|
|
25
|
+
case "ArrowDown":
|
|
26
|
+
case "ArrowLeft":
|
|
27
|
+
return clamp01(value - step);
|
|
28
|
+
case "PageUp":
|
|
29
|
+
return clamp01(value + 0.1);
|
|
30
|
+
case "PageDown":
|
|
31
|
+
return clamp01(value - 0.1);
|
|
32
|
+
case "Home":
|
|
33
|
+
return 0;
|
|
34
|
+
case "End":
|
|
35
|
+
return 1;
|
|
36
|
+
default:
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
16
40
|
const ARC_START = -135;
|
|
17
41
|
const ARC_END = 135;
|
|
18
42
|
export function Knob({ value, text, label, size = 64, disabled, defaultValue, bipolar, wheelSensitivity = 0.4, trackColor = "#2A2F27", valueColor = "#C6F135", onChange, onBegin, onEnd, }) {
|
|
@@ -42,7 +66,13 @@ export function Knob({ value, text, label, size = 64, disabled, defaultValue, bi
|
|
|
42
66
|
onBegin?.();
|
|
43
67
|
}, onDrag: disabled ? undefined : (e) => onChange(dragToValue(startValue.current, e.dy)), onDragEnd: disabled ? undefined : () => onEnd?.(), onDoubleClick: disabled || defaultValue === undefined ? undefined : () => nudge(defaultValue), onWheel: disabled || wheelSensitivity === 0
|
|
44
68
|
? undefined
|
|
45
|
-
: (e) => nudge(clamped + e.dy * wheelSensitivity),
|
|
69
|
+
: (e) => nudge(clamped + e.dy * wheelSensitivity), onKeyDown: disabled
|
|
70
|
+
? undefined
|
|
71
|
+
: (e) => {
|
|
72
|
+
const target = sliderKeyTarget(clamped, e);
|
|
73
|
+
if (target !== null)
|
|
74
|
+
nudge(target);
|
|
75
|
+
}, children: text !== undefined ? (_jsx(Text, { className: "text-text font-bold text-center", style: { fontSize: Math.max(10, size * 0.16) }, children: text })) : null }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
|
|
46
76
|
}
|
|
47
77
|
/** A Knob bound to an APVTS parameter — double-click resets to the
|
|
48
78
|
host's default, the wheel nudges, gestures stay automation-safe. */
|
|
@@ -69,10 +99,17 @@ export function Slider({ value, width = 160, height = 160, vertical, barThumb, l
|
|
|
69
99
|
const onWheel = disabled || wheelSensitivity === 0
|
|
70
100
|
? undefined
|
|
71
101
|
: (e) => nudge(clamped + e.dy * wheelSensitivity);
|
|
102
|
+
const onKeyDown = disabled
|
|
103
|
+
? undefined
|
|
104
|
+
: (e) => {
|
|
105
|
+
const target = sliderKeyTarget(clamped, e);
|
|
106
|
+
if (target !== null)
|
|
107
|
+
nudge(target);
|
|
108
|
+
};
|
|
72
109
|
if (vertical) {
|
|
73
|
-
return (_jsxs(View, { className: "items-center gap-2", children: [_jsxs(View, { className: `w-[18] relative ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { height }, onDragStart: onDragStart, onDrag: disabled ? undefined : (e) => onChange(clamp01(startValue.current - e.dy / height)), onDragEnd: onDragEnd, onDoubleClick: onDoubleClick, onWheel: onWheel, children: [_jsx(View, { className: "absolute w-[4] rounded-full left-[7] top-0 bottom-0", style: { backgroundColor: trackColor } }), _jsx(View, { className: "absolute w-[4] rounded-full left-[7] bottom-0", style: { height: clamped * height, backgroundColor: valueColor } }), barThumb ? (_jsx(View, { className: "absolute w-[18] h-[5] rounded-[2] left-0", style: { top: (1 - clamped) * (height - 5), backgroundColor: valueColor } })) : (_jsx(View, { className: "absolute w-[12] h-[12] rounded-full left-[3]", style: { top: (1 - clamped) * (height - 12), backgroundColor: valueColor } }))] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
|
|
110
|
+
return (_jsxs(View, { className: "items-center gap-2", children: [_jsxs(View, { className: `w-[18] relative ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { height }, onDragStart: onDragStart, onDrag: disabled ? undefined : (e) => onChange(clamp01(startValue.current - e.dy / height)), onDragEnd: onDragEnd, onDoubleClick: onDoubleClick, onWheel: onWheel, onKeyDown: onKeyDown, children: [_jsx(View, { className: "absolute w-[4] rounded-full left-[7] top-0 bottom-0", style: { backgroundColor: trackColor } }), _jsx(View, { className: "absolute w-[4] rounded-full left-[7] bottom-0", style: { height: clamped * height, backgroundColor: valueColor } }), barThumb ? (_jsx(View, { className: "absolute w-[18] h-[5] rounded-[2] left-0", style: { top: (1 - clamped) * (height - 5), backgroundColor: valueColor } })) : (_jsx(View, { className: "absolute w-[12] h-[12] rounded-full left-[3]", style: { top: (1 - clamped) * (height - 12), backgroundColor: valueColor } }))] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
|
|
74
111
|
}
|
|
75
|
-
return (_jsxs(View, { className: "gap-2", children: [_jsxs(View, { className: `h-[18] justify-center relative ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width }, onDragStart: onDragStart, onDrag: disabled ? undefined : (e) => onChange(clamp01(startValue.current + e.dx / width)), onDragEnd: onDragEnd, onDoubleClick: onDoubleClick, onWheel: onWheel, children: [_jsx(View, { className: "h-[4] rounded-full", style: { backgroundColor: trackColor } }), _jsx(View, { className: "absolute h-[4] rounded-full top-[7]", style: { width: clamped * width, backgroundColor: valueColor } }), barThumb ? (_jsx(View, { className: "absolute w-[5] h-[18] rounded-[2] top-0", style: { left: clamped * (width - 5), backgroundColor: valueColor } })) : (_jsx(View, { className: "absolute w-[12] h-[12] rounded-full top-[3]", style: { left: clamped * (width - 12), backgroundColor: valueColor } }))] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
|
|
112
|
+
return (_jsxs(View, { className: "gap-2", children: [_jsxs(View, { className: `h-[18] justify-center relative ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width }, onDragStart: onDragStart, onDrag: disabled ? undefined : (e) => onChange(clamp01(startValue.current + e.dx / width)), onDragEnd: onDragEnd, onDoubleClick: onDoubleClick, onWheel: onWheel, onKeyDown: onKeyDown, children: [_jsx(View, { className: "h-[4] rounded-full", style: { backgroundColor: trackColor } }), _jsx(View, { className: "absolute h-[4] rounded-full top-[7]", style: { width: clamped * width, backgroundColor: valueColor } }), barThumb ? (_jsx(View, { className: "absolute w-[5] h-[18] rounded-[2] top-0", style: { left: clamped * (width - 5), backgroundColor: valueColor } })) : (_jsx(View, { className: "absolute w-[12] h-[12] rounded-full top-[3]", style: { left: clamped * (width - 12), backgroundColor: valueColor } }))] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
|
|
76
113
|
}
|
|
77
114
|
/** A Slider bound to an APVTS parameter — double-click resets to the
|
|
78
115
|
host's default, the wheel nudges, gestures stay automation-safe. */
|
|
@@ -86,7 +123,12 @@ export function Toggle({ on, label, offLabel, onLabel, size = 22, disabled, trac
|
|
|
86
123
|
const trackWidth = Math.round(size * 1.8);
|
|
87
124
|
const thumb = size - 6;
|
|
88
125
|
const travel = trackWidth - thumb - 6;
|
|
89
|
-
return (_jsxs(View, { className: "items-center gap-2", children: [_jsxs(View, { className: "flex-row items-center gap-2", children: [offLabel !== undefined ? (_jsx(Text, { className: "text-[9] font-bold tracking-widest", style: { color: on ? "#6f6e66" : "#ECF2E8" }, children: offLabel })) : null, _jsx(View, { className: `relative rounded-full ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width: trackWidth, height: size, backgroundColor: on ? onColor : trackColor }, onClick: disabled ? undefined : () => onChange(!on),
|
|
126
|
+
return (_jsxs(View, { className: "items-center gap-2", children: [_jsxs(View, { className: "flex-row items-center gap-2", children: [offLabel !== undefined ? (_jsx(Text, { className: "text-[9] font-bold tracking-widest", style: { color: on ? "#6f6e66" : "#ECF2E8" }, children: offLabel })) : null, _jsx(View, { className: `relative rounded-full ${disabled ? "opacity-40" : "cursor-pointer"}`, style: { width: trackWidth, height: size, backgroundColor: on ? onColor : trackColor }, onClick: disabled ? undefined : () => onChange(!on), onKeyDown: disabled
|
|
127
|
+
? undefined
|
|
128
|
+
: (e) => {
|
|
129
|
+
if (e.key === "Enter" || e.key === " ")
|
|
130
|
+
onChange(!on);
|
|
131
|
+
}, children: _jsx(View, { className: "absolute rounded-full", style: {
|
|
90
132
|
width: thumb,
|
|
91
133
|
height: thumb,
|
|
92
134
|
top: 3,
|
|
@@ -118,7 +160,18 @@ export function XYPad({ x, y, width = 160, height = 160, label, disabled, trackC
|
|
|
118
160
|
onBegin?.();
|
|
119
161
|
}, onDrag: disabled
|
|
120
162
|
? undefined
|
|
121
|
-
: (e) => onChange(clamp01(start.current.x + e.dx / width), clamp01(start.current.y - e.dy / height)), onDragEnd: disabled ? undefined : () => onEnd?.(),
|
|
163
|
+
: (e) => onChange(clamp01(start.current.x + e.dx / width), clamp01(start.current.y - e.dy / height)), onDragEnd: disabled ? undefined : () => onEnd?.(), onKeyDown: disabled
|
|
164
|
+
? undefined
|
|
165
|
+
: (e) => {
|
|
166
|
+
const step = e.shift ? 0.001 : 0.01;
|
|
167
|
+
const dx = e.key === "ArrowRight" ? step : e.key === "ArrowLeft" ? -step : 0;
|
|
168
|
+
const dy = e.key === "ArrowUp" ? step : e.key === "ArrowDown" ? -step : 0;
|
|
169
|
+
if (dx === 0 && dy === 0)
|
|
170
|
+
return;
|
|
171
|
+
onBegin?.();
|
|
172
|
+
onChange(clamp01(cxv + dx), clamp01(cyv + dy));
|
|
173
|
+
onEnd?.();
|
|
174
|
+
}, children: [_jsx(View, { className: "absolute left-0 right-0 h-[1]", style: { top: ty + thumb / 2, backgroundColor: valueColor, opacity: 0.35 } }), _jsx(View, { className: "absolute top-0 bottom-0 w-[1]", style: { left: tx + thumb / 2, backgroundColor: valueColor, opacity: 0.35 } }), _jsx(View, { className: "absolute rounded-full", style: { width: thumb, height: thumb, left: tx, top: ty, backgroundColor: valueColor } })] }), label !== undefined ? (_jsx(Text, { className: "text-faint text-[10] font-bold tracking-widest", children: label })) : null] }));
|
|
122
175
|
}
|
|
123
176
|
/** An XYPad driving two APVTS parameters at once. */
|
|
124
177
|
export function ParamXYPad({ paramX, paramY, label, ...rest }) {
|
package/dist/hostConfig.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./runtime";
|
|
2
2
|
import "./bridge";
|
|
3
3
|
/** The SDK version — stamp it on support dumps and analytics. */
|
|
4
|
-
export declare const VERSION = "0.0.
|
|
4
|
+
export declare const VERSION = "0.0.20";
|
|
5
5
|
export { View, Text, Image, TextInput, NativeView } from "./primitives";
|
|
6
6
|
export type { CommonProps, TextProps, ImageProps, TextInputProps, NativeViewProps, } from "./primitives";
|
|
7
7
|
export { render, unmount } from "./render";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import "./runtime";
|
|
4
4
|
import "./bridge";
|
|
5
5
|
/** The SDK version — stamp it on support dumps and analytics. */
|
|
6
|
-
export const VERSION = "0.0.
|
|
6
|
+
export const VERSION = "0.0.20";
|
|
7
7
|
export { View, Text, Image, TextInput, NativeView } from "./primitives";
|
|
8
8
|
export { render, unmount } from "./render";
|
|
9
9
|
export { native } from "./native";
|
package/dist/primitives.d.ts
CHANGED
|
@@ -20,6 +20,20 @@ export interface LayoutRect {
|
|
|
20
20
|
export interface WheelEventPayload {
|
|
21
21
|
dy: number;
|
|
22
22
|
}
|
|
23
|
+
/** Web-style key event: key names match KeyboardEvent.key ("ArrowUp",
|
|
24
|
+
"Enter", "Escape", " ", "a"). */
|
|
25
|
+
export interface KeyEventPayload {
|
|
26
|
+
key: string;
|
|
27
|
+
shift: boolean;
|
|
28
|
+
ctrl: boolean;
|
|
29
|
+
alt: boolean;
|
|
30
|
+
meta: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Root-space pointer position. */
|
|
33
|
+
export interface MouseMovePayload {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
}
|
|
23
37
|
export interface CommonProps {
|
|
24
38
|
className?: string;
|
|
25
39
|
style?: Style;
|
|
@@ -37,6 +51,13 @@ export interface CommonProps {
|
|
|
37
51
|
onDragStart?: (e: DragEventPayload) => void;
|
|
38
52
|
onDrag?: (e: DragEventPayload) => void;
|
|
39
53
|
onDragEnd?: (e: DragEventPayload) => void;
|
|
54
|
+
onMouseMove?: (e: MouseMovePayload) => void;
|
|
55
|
+
/** Declaring onKeyDown (or onFocus/onBlur, or a focus: style variant)
|
|
56
|
+
makes the node focusable: click focuses it, Tab cycles, keys arrive
|
|
57
|
+
here with web KeyboardEvent.key names. */
|
|
58
|
+
onKeyDown?: (e: KeyEventPayload) => void;
|
|
59
|
+
onFocus?: () => void;
|
|
60
|
+
onBlur?: () => void;
|
|
40
61
|
/** Fires after layout whenever this node's root-space rect changes —
|
|
41
62
|
the foundation for popovers, menus, and tooltips. */
|
|
42
63
|
onLayout?: (rect: LayoutRect) => void;
|
|
@@ -82,6 +103,8 @@ export declare function TextInput({ onChange, onSubmit, ...rest }: TextInputProp
|
|
|
82
103
|
onDragStart?: ((e: DragEventPayload) => void) | undefined;
|
|
83
104
|
onDrag?: ((e: DragEventPayload) => void) | undefined;
|
|
84
105
|
onDragEnd?: ((e: DragEventPayload) => void) | undefined;
|
|
106
|
+
onMouseMove?: ((e: MouseMovePayload) => void) | undefined;
|
|
107
|
+
onKeyDown?: ((e: KeyEventPayload) => void) | undefined;
|
|
85
108
|
onLayout?: ((rect: LayoutRect) => void) | undefined;
|
|
86
109
|
}, string | import("react").JSXElementConstructor<any>>;
|
|
87
110
|
export interface NativeViewProps extends CommonProps {
|
package/dist/tw.js
CHANGED
|
@@ -303,6 +303,10 @@ function resolveClass(cls) {
|
|
|
303
303
|
const n = Number(rest);
|
|
304
304
|
return Number.isFinite(n) ? { flex: n } : undefined;
|
|
305
305
|
}
|
|
306
|
+
case "z": {
|
|
307
|
+
const z = rest.startsWith("[") ? parseLength(rest) : Number(rest);
|
|
308
|
+
return typeof z === "number" && Number.isFinite(z) ? { zIndex: negate(z) } : undefined;
|
|
309
|
+
}
|
|
306
310
|
default:
|
|
307
311
|
return undefined;
|
|
308
312
|
}
|
package/package.json
CHANGED
package/src/controls.test.tsx
CHANGED
|
@@ -15,7 +15,7 @@ const defaultNativeCall = (name: string, argsJson: string) => {
|
|
|
15
15
|
};
|
|
16
16
|
(globalThis as Record<string, any>).__vsreact_nativeCall = defaultNativeCall;
|
|
17
17
|
|
|
18
|
-
import { render, unmount, Slider, Toggle, XYPad, Segmented, ParamSegmented, ParamToggle } from "./index";
|
|
18
|
+
import { render, unmount, Knob, Slider, Toggle, XYPad, Segmented, ParamSegmented, ParamToggle } from "./index";
|
|
19
19
|
|
|
20
20
|
const allOps = () => batches.flat();
|
|
21
21
|
const opsNamed = (name: string) => allOps().filter((op: any) => op[0] === name);
|
|
@@ -773,3 +773,50 @@ describe("0.0.11 — flagship visuals", () => {
|
|
|
773
773
|
expect(bar[2].style.top).toBeCloseTo(0.5 * 95);
|
|
774
774
|
});
|
|
775
775
|
});
|
|
776
|
+
|
|
777
|
+
describe("keyboard control model (0.0.20)", () => {
|
|
778
|
+
test("Knob responds to arrows / paging / Home-End via keydown", () => {
|
|
779
|
+
const seen: number[] = [];
|
|
780
|
+
render(<Knob value={0.5} onChange={(v) => seen.push(v)} />);
|
|
781
|
+
|
|
782
|
+
const id = nodeWithListener("keydown");
|
|
783
|
+
const press = (key: string, shift = false) =>
|
|
784
|
+
dispatch({ kind: "event", nodeId: id, type: "keydown", payload: { key, shift, ctrl: false, alt: false, meta: false } });
|
|
785
|
+
|
|
786
|
+
press("ArrowUp");
|
|
787
|
+
expect(seen.at(-1)).toBeCloseTo(0.51);
|
|
788
|
+
press("ArrowDown", true);
|
|
789
|
+
expect(seen.at(-1)).toBeCloseTo(0.499);
|
|
790
|
+
press("PageUp");
|
|
791
|
+
expect(seen.at(-1)).toBeCloseTo(0.6);
|
|
792
|
+
press("Home");
|
|
793
|
+
expect(seen.at(-1)).toBe(0);
|
|
794
|
+
press("End");
|
|
795
|
+
expect(seen.at(-1)).toBe(1);
|
|
796
|
+
const count = seen.length;
|
|
797
|
+
press("Escape");
|
|
798
|
+
expect(seen.length).toBe(count); // unowned keys ignored
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test("Toggle flips on Enter and Space", () => {
|
|
802
|
+
const seen: boolean[] = [];
|
|
803
|
+
render(<Toggle on={false} onChange={(v) => seen.push(v)} />);
|
|
804
|
+
|
|
805
|
+
const id = nodeWithListener("keydown");
|
|
806
|
+
dispatch({ kind: "event", nodeId: id, type: "keydown", payload: { key: "Enter", shift: false, ctrl: false, alt: false, meta: false } });
|
|
807
|
+
expect(seen.at(-1)).toBe(true);
|
|
808
|
+
dispatch({ kind: "event", nodeId: id, type: "keydown", payload: { key: " ", shift: false, ctrl: false, alt: false, meta: false } });
|
|
809
|
+
expect(seen.at(-1)).toBe(true); // controlled: still !on
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test("XYPad arrows move both axes", () => {
|
|
813
|
+
const seen: Array<[number, number]> = [];
|
|
814
|
+
render(<XYPad x={0.5} y={0.5} onChange={(x, y) => seen.push([x, y])} />);
|
|
815
|
+
|
|
816
|
+
const id = nodeWithListener("keydown");
|
|
817
|
+
dispatch({ kind: "event", nodeId: id, type: "keydown", payload: { key: "ArrowRight", shift: false, ctrl: false, alt: false, meta: false } });
|
|
818
|
+
expect(seen.at(-1)?.[0]).toBeCloseTo(0.51);
|
|
819
|
+
dispatch({ kind: "event", nodeId: id, type: "keydown", payload: { key: "ArrowDown", shift: false, ctrl: false, alt: false, meta: false } });
|
|
820
|
+
expect(seen.at(-1)?.[1]).toBeCloseTo(0.49);
|
|
821
|
+
});
|
|
822
|
+
});
|
package/src/controls.tsx
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
6
|
import { View, Text } from "./primitives";
|
|
7
|
+
import type { KeyEventPayload } from "./primitives";
|
|
7
8
|
import { useParameter, useParameterList } from "./parameters";
|
|
8
9
|
import { useSpring } from "./animation";
|
|
9
10
|
import { useLayoutRect } from "./hooks";
|
|
@@ -16,6 +17,31 @@ export function dragToValue(startValue: number, dy: number, sensitivity = 0.005)
|
|
|
16
17
|
return clamp01(startValue - dy * sensitivity);
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
/** Web <input type="range"> keyboard model for a normalized control:
|
|
21
|
+
arrows ±0.01 (shift = ±0.001 fine), PageUp/Down ±0.1, Home/End 0/1.
|
|
22
|
+
Returns the new value, or null for keys the control doesn't own. */
|
|
23
|
+
export function sliderKeyTarget(value: number, e: KeyEventPayload): number | null {
|
|
24
|
+
const step = e.shift ? 0.001 : 0.01;
|
|
25
|
+
switch (e.key) {
|
|
26
|
+
case "ArrowUp":
|
|
27
|
+
case "ArrowRight":
|
|
28
|
+
return clamp01(value + step);
|
|
29
|
+
case "ArrowDown":
|
|
30
|
+
case "ArrowLeft":
|
|
31
|
+
return clamp01(value - step);
|
|
32
|
+
case "PageUp":
|
|
33
|
+
return clamp01(value + 0.1);
|
|
34
|
+
case "PageDown":
|
|
35
|
+
return clamp01(value - 0.1);
|
|
36
|
+
case "Home":
|
|
37
|
+
return 0;
|
|
38
|
+
case "End":
|
|
39
|
+
return 1;
|
|
40
|
+
default:
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
19
45
|
const ARC_START = -135;
|
|
20
46
|
const ARC_END = 135;
|
|
21
47
|
|
|
@@ -98,6 +124,14 @@ export function Knob({
|
|
|
98
124
|
? undefined
|
|
99
125
|
: (e) => nudge(clamped + e.dy * wheelSensitivity)
|
|
100
126
|
}
|
|
127
|
+
onKeyDown={
|
|
128
|
+
disabled
|
|
129
|
+
? undefined
|
|
130
|
+
: (e) => {
|
|
131
|
+
const target = sliderKeyTarget(clamped, e);
|
|
132
|
+
if (target !== null) nudge(target);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
101
135
|
>
|
|
102
136
|
{text !== undefined ? (
|
|
103
137
|
<Text
|
|
@@ -205,6 +239,12 @@ export function Slider({
|
|
|
205
239
|
disabled || wheelSensitivity === 0
|
|
206
240
|
? undefined
|
|
207
241
|
: (e: { dy: number }) => nudge(clamped + e.dy * wheelSensitivity);
|
|
242
|
+
const onKeyDown = disabled
|
|
243
|
+
? undefined
|
|
244
|
+
: (e: KeyEventPayload) => {
|
|
245
|
+
const target = sliderKeyTarget(clamped, e);
|
|
246
|
+
if (target !== null) nudge(target);
|
|
247
|
+
};
|
|
208
248
|
|
|
209
249
|
if (vertical) {
|
|
210
250
|
return (
|
|
@@ -217,6 +257,7 @@ export function Slider({
|
|
|
217
257
|
onDragEnd={onDragEnd}
|
|
218
258
|
onDoubleClick={onDoubleClick}
|
|
219
259
|
onWheel={onWheel}
|
|
260
|
+
onKeyDown={onKeyDown}
|
|
220
261
|
>
|
|
221
262
|
<View
|
|
222
263
|
className="absolute w-[4] rounded-full left-[7] top-0 bottom-0"
|
|
@@ -255,6 +296,7 @@ export function Slider({
|
|
|
255
296
|
onDragEnd={onDragEnd}
|
|
256
297
|
onDoubleClick={onDoubleClick}
|
|
257
298
|
onWheel={onWheel}
|
|
299
|
+
onKeyDown={onKeyDown}
|
|
258
300
|
>
|
|
259
301
|
<View className="h-[4] rounded-full" style={{ backgroundColor: trackColor }} />
|
|
260
302
|
<View
|
|
@@ -357,6 +399,13 @@ export function Toggle({
|
|
|
357
399
|
className={`relative rounded-full ${disabled ? "opacity-40" : "cursor-pointer"}`}
|
|
358
400
|
style={{ width: trackWidth, height: size, backgroundColor: on ? onColor : trackColor }}
|
|
359
401
|
onClick={disabled ? undefined : () => onChange(!on)}
|
|
402
|
+
onKeyDown={
|
|
403
|
+
disabled
|
|
404
|
+
? undefined
|
|
405
|
+
: (e) => {
|
|
406
|
+
if (e.key === "Enter" || e.key === " ") onChange(!on);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
360
409
|
>
|
|
361
410
|
<View
|
|
362
411
|
className="absolute rounded-full"
|
|
@@ -477,6 +526,19 @@ export function XYPad({
|
|
|
477
526
|
)
|
|
478
527
|
}
|
|
479
528
|
onDragEnd={disabled ? undefined : () => onEnd?.()}
|
|
529
|
+
onKeyDown={
|
|
530
|
+
disabled
|
|
531
|
+
? undefined
|
|
532
|
+
: (e) => {
|
|
533
|
+
const step = e.shift ? 0.001 : 0.01;
|
|
534
|
+
const dx = e.key === "ArrowRight" ? step : e.key === "ArrowLeft" ? -step : 0;
|
|
535
|
+
const dy = e.key === "ArrowUp" ? step : e.key === "ArrowDown" ? -step : 0;
|
|
536
|
+
if (dx === 0 && dy === 0) return;
|
|
537
|
+
onBegin?.();
|
|
538
|
+
onChange(clamp01(cxv + dx), clamp01(cyv + dy));
|
|
539
|
+
onEnd?.();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
480
542
|
>
|
|
481
543
|
<View
|
|
482
544
|
className="absolute left-0 right-0 h-[1]"
|
package/src/hostConfig.ts
CHANGED
|
@@ -30,6 +30,8 @@ const eventPropNames: Record<string, string> = {
|
|
|
30
30
|
onMouseLeave: "mouseleave",
|
|
31
31
|
onMouseDown: "mousedown",
|
|
32
32
|
onMouseUp: "mouseup",
|
|
33
|
+
onMouseMove: "mousemove",
|
|
34
|
+
onKeyDown: "keydown",
|
|
33
35
|
onDragStart: "dragstart",
|
|
34
36
|
onDrag: "drag",
|
|
35
37
|
onDragEnd: "dragend",
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import "./runtime";
|
|
|
4
4
|
import "./bridge";
|
|
5
5
|
|
|
6
6
|
/** The SDK version — stamp it on support dumps and analytics. */
|
|
7
|
-
export const VERSION = "0.0.
|
|
7
|
+
export const VERSION = "0.0.20";
|
|
8
8
|
|
|
9
9
|
export { View, Text, Image, TextInput, NativeView } from "./primitives";
|
|
10
10
|
export type {
|
package/src/primitives.tsx
CHANGED
|
@@ -24,6 +24,22 @@ export interface WheelEventPayload {
|
|
|
24
24
|
dy: number;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Web-style key event: key names match KeyboardEvent.key ("ArrowUp",
|
|
28
|
+
"Enter", "Escape", " ", "a"). */
|
|
29
|
+
export interface KeyEventPayload {
|
|
30
|
+
key: string;
|
|
31
|
+
shift: boolean;
|
|
32
|
+
ctrl: boolean;
|
|
33
|
+
alt: boolean;
|
|
34
|
+
meta: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Root-space pointer position. */
|
|
38
|
+
export interface MouseMovePayload {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
export interface CommonProps {
|
|
28
44
|
className?: string;
|
|
29
45
|
style?: Style;
|
|
@@ -41,6 +57,13 @@ export interface CommonProps {
|
|
|
41
57
|
onDragStart?: (e: DragEventPayload) => void;
|
|
42
58
|
onDrag?: (e: DragEventPayload) => void;
|
|
43
59
|
onDragEnd?: (e: DragEventPayload) => void;
|
|
60
|
+
onMouseMove?: (e: MouseMovePayload) => void;
|
|
61
|
+
/** Declaring onKeyDown (or onFocus/onBlur, or a focus: style variant)
|
|
62
|
+
makes the node focusable: click focuses it, Tab cycles, keys arrive
|
|
63
|
+
here with web KeyboardEvent.key names. */
|
|
64
|
+
onKeyDown?: (e: KeyEventPayload) => void;
|
|
65
|
+
onFocus?: () => void;
|
|
66
|
+
onBlur?: () => void;
|
|
44
67
|
/** Fires after layout whenever this node's root-space rect changes —
|
|
45
68
|
the foundation for popovers, menus, and tooltips. */
|
|
46
69
|
onLayout?: (rect: LayoutRect) => void;
|
package/src/tw.test.ts
CHANGED
|
@@ -226,3 +226,11 @@ describe("per-side borders + inner shadow (0.0.19)", () => {
|
|
|
226
226
|
});
|
|
227
227
|
});
|
|
228
228
|
});
|
|
229
|
+
|
|
230
|
+
describe("zIndex (0.0.20)", () => {
|
|
231
|
+
test("z classes", () => {
|
|
232
|
+
expect(tw("z-10").style).toEqual({ zIndex: 10 });
|
|
233
|
+
expect(tw("z-[3]").style).toEqual({ zIndex: 3 });
|
|
234
|
+
expect(tw("-z-1").style).toEqual({ zIndex: -1 });
|
|
235
|
+
});
|
|
236
|
+
});
|
package/src/tw.ts
CHANGED
|
@@ -310,6 +310,10 @@ function resolveClass(cls: string): Style | undefined {
|
|
|
310
310
|
const n = Number(rest);
|
|
311
311
|
return Number.isFinite(n) ? { flex: n } : undefined;
|
|
312
312
|
}
|
|
313
|
+
case "z": {
|
|
314
|
+
const z = rest.startsWith("[") ? parseLength(rest) : Number(rest);
|
|
315
|
+
return typeof z === "number" && Number.isFinite(z) ? { zIndex: negate(z) } : undefined;
|
|
316
|
+
}
|
|
313
317
|
default:
|
|
314
318
|
return undefined;
|
|
315
319
|
}
|