@vsreact/core 0.0.3 → 0.0.5
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 +46 -0
- package/dist/controls.js +54 -2
- package/dist/hooks.d.ts +16 -0
- package/dist/hooks.js +26 -1
- package/dist/hostConfig.js +1 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +5 -3
- package/dist/meter.d.ts +37 -0
- package/dist/meter.js +48 -0
- package/dist/overlay.d.ts +9 -0
- package/dist/overlay.js +47 -0
- package/dist/parameters.d.ts +14 -0
- package/dist/parameters.js +20 -0
- package/dist/primitives.d.ts +11 -0
- package/dist/render.d.ts +2 -1
- package/dist/render.js +5 -2
- package/dist/tw.js +9 -1
- package/package.json +2 -2
- package/src/animation.test.tsx +33 -0
- package/src/controls.test.tsx +174 -1
- package/src/controls.tsx +191 -2
- package/src/hooks.ts +31 -1
- package/src/hostConfig.ts +1 -0
- package/src/index.ts +13 -4
- package/src/meter.tsx +159 -0
- package/src/overlay.tsx +70 -0
- package/src/parameters.ts +30 -0
- package/src/primitives.tsx +11 -0
- package/src/render.tsx +12 -2
- package/src/tw.test.ts +12 -0
- package/src/tw.ts +6 -1
package/src/overlay.tsx
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// The overlay layer — content that paints above everything else (menus,
|
|
2
|
+
// tooltips, modals). render() mounts <OverlayLayer/> after your app
|
|
3
|
+
// automatically, so painting order puts overlays on top and hit-testing
|
|
4
|
+
// reaches them first. Position entries absolutely using rects from
|
|
5
|
+
// onLayout / useLayoutRect.
|
|
6
|
+
|
|
7
|
+
import { Fragment, useCallback, useEffect, useRef, useSyncExternalStore, type ReactNode } from "react";
|
|
8
|
+
|
|
9
|
+
interface OverlayEntry {
|
|
10
|
+
key: number;
|
|
11
|
+
node: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let entries: OverlayEntry[] = [];
|
|
15
|
+
const subscribers = new Set<() => void>();
|
|
16
|
+
let nextOverlayKey = 1;
|
|
17
|
+
|
|
18
|
+
function emit(): void {
|
|
19
|
+
for (const subscriber of subscribers) subscriber();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function setEntry(key: number, node: ReactNode): void {
|
|
23
|
+
entries = [...entries.filter((e) => e.key !== key), { key, node }];
|
|
24
|
+
emit();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function clearEntry(key: number): void {
|
|
28
|
+
if (!entries.some((e) => e.key === key)) return;
|
|
29
|
+
entries = entries.filter((e) => e.key !== key);
|
|
30
|
+
emit();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A per-component slot in the overlay layer. show() replaces the slot's
|
|
34
|
+
content; hide() removes it; unmounting cleans up automatically. */
|
|
35
|
+
export function useOverlay(): {
|
|
36
|
+
show: (node: ReactNode) => void;
|
|
37
|
+
hide: () => void;
|
|
38
|
+
} {
|
|
39
|
+
const key = useRef(0);
|
|
40
|
+
if (key.current === 0) key.current = nextOverlayKey++;
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const k = key.current;
|
|
44
|
+
return () => clearEntry(k);
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
show: useCallback((node: ReactNode) => setEntry(key.current, node), []),
|
|
49
|
+
hide: useCallback(() => clearEntry(key.current), []),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Mounted automatically by render() as the last sibling of your app. */
|
|
54
|
+
export function OverlayLayer() {
|
|
55
|
+
const list = useSyncExternalStore(
|
|
56
|
+
(onStoreChange) => {
|
|
57
|
+
subscribers.add(onStoreChange);
|
|
58
|
+
return () => subscribers.delete(onStoreChange);
|
|
59
|
+
},
|
|
60
|
+
() => entries,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
{list.map((entry) => (
|
|
66
|
+
<Fragment key={entry.key}>{entry.node}</Fragment>
|
|
67
|
+
))}
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
}
|
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/primitives.tsx
CHANGED
|
@@ -10,6 +10,14 @@ export interface DragEventPayload {
|
|
|
10
10
|
y: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** A node's laid-out rect in root coordinates (scroll-adjusted). */
|
|
14
|
+
export interface LayoutRect {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
export interface CommonProps {
|
|
14
22
|
className?: string;
|
|
15
23
|
style?: Style;
|
|
@@ -24,6 +32,9 @@ export interface CommonProps {
|
|
|
24
32
|
onDragStart?: (e: DragEventPayload) => void;
|
|
25
33
|
onDrag?: (e: DragEventPayload) => void;
|
|
26
34
|
onDragEnd?: (e: DragEventPayload) => void;
|
|
35
|
+
/** Fires after layout whenever this node's root-space rect changes —
|
|
36
|
+
the foundation for popovers, menus, and tooltips. */
|
|
37
|
+
onLayout?: (rect: LayoutRect) => void;
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
export function View(props: CommonProps) {
|
package/src/render.tsx
CHANGED
|
@@ -3,12 +3,14 @@ import { LegacyRoot } from "react-reconciler/constants";
|
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
4
|
import { hostConfig } from "./hostConfig";
|
|
5
5
|
import { flushOps } from "./bridge";
|
|
6
|
+
import { OverlayLayer } from "./overlay";
|
|
6
7
|
|
|
7
8
|
const reconciler = Reconciler(hostConfig as any);
|
|
8
9
|
|
|
9
10
|
let container: ReturnType<typeof reconciler.createContainer> | null = null;
|
|
10
11
|
|
|
11
|
-
/** Mounts (or re-renders) the app into the plugin window.
|
|
12
|
+
/** Mounts (or re-renders) the app into the plugin window. The overlay
|
|
13
|
+
layer (menus, tooltips) mounts after the app so it paints on top. */
|
|
12
14
|
export function render(element: ReactNode): void {
|
|
13
15
|
if (!container) {
|
|
14
16
|
container = reconciler.createContainer(
|
|
@@ -23,7 +25,15 @@ export function render(element: ReactNode): void {
|
|
|
23
25
|
);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
reconciler.updateContainer(
|
|
28
|
+
reconciler.updateContainer(
|
|
29
|
+
<>
|
|
30
|
+
{element}
|
|
31
|
+
<OverlayLayer />
|
|
32
|
+
</>,
|
|
33
|
+
container,
|
|
34
|
+
null,
|
|
35
|
+
null,
|
|
36
|
+
);
|
|
27
37
|
flushOps();
|
|
28
38
|
}
|
|
29
39
|
|
package/src/tw.test.ts
CHANGED
|
@@ -137,3 +137,15 @@ describe("tw 0.0.3 additions", () => {
|
|
|
137
137
|
expect(tw("text-6xl").style.fontSize).toBe(60);
|
|
138
138
|
});
|
|
139
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
|
@@ -169,7 +169,12 @@ function resolveClass(cls: string): Style | undefined {
|
|
|
169
169
|
const negative = cls.startsWith("-");
|
|
170
170
|
const body = negative ? cls.slice(1) : cls;
|
|
171
171
|
|
|
172
|
-
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
|
+
};
|
|
173
178
|
|
|
174
179
|
const known = staticClasses[body];
|
|
175
180
|
if (known && !negative) return known;
|