@vsreact/core 0.0.23 → 0.0.25

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/hooks.d.ts CHANGED
@@ -22,6 +22,16 @@ export declare function useNativeEvent(name: string, handler: (payload: any) =>
22
22
  * <Meter value={meter.level} />
23
23
  */
24
24
  export declare function useNativeValue<T = any>(name: string, initial: T): T;
25
+ export interface RootSize {
26
+ width: number;
27
+ height: number;
28
+ }
29
+ /**
30
+ * The editor's current size. The native side sends a "resize" event at
31
+ * mount (so this resolves on the first committed frame) and on every
32
+ * host resize — the foundation for resizable editors.
33
+ */
34
+ export declare function useRootSize(): RootSize;
25
35
  /**
26
36
  * The value, but only after it has stopped changing for `delayMs` —
27
37
  * classic input debouncing for expensive native calls:
package/dist/hooks.js CHANGED
@@ -35,6 +35,21 @@ export function useNativeValue(name, initial) {
35
35
  useNativeEvent(name, setValue);
36
36
  return value;
37
37
  }
38
+ /**
39
+ * The editor's current size. The native side sends a "resize" event at
40
+ * mount (so this resolves on the first committed frame) and on every
41
+ * host resize — the foundation for resizable editors.
42
+ */
43
+ export function useRootSize() {
44
+ const [size, setSize] = useState({ width: 0, height: 0 });
45
+ useNativeEvent("resize", (payload) => {
46
+ const width = Number(payload?.width);
47
+ const height = Number(payload?.height);
48
+ if (Number.isFinite(width) && Number.isFinite(height))
49
+ setSize((s) => (s.width === width && s.height === height ? s : { width, height }));
50
+ });
51
+ return size;
52
+ }
38
53
  /**
39
54
  * The value, but only after it has stopped changing for `delayMs` —
40
55
  * classic input debouncing for expensive native calls:
@@ -10,6 +10,8 @@ const hostTypes = {
10
10
  "vs-image": "image",
11
11
  "vs-textinput": "textinput",
12
12
  "vs-native": "native",
13
+ "vs-svg": "svg",
14
+ "vs-svgpath": "svgpath",
13
15
  };
14
16
  const eventPropNames = {
15
17
  onClick: "click",
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
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.23";
5
- export { View, Text, Image, TextInput, NativeView } from "./primitives";
6
- export type { CommonProps, TextProps, ImageProps, TextInputProps, NativeViewProps, } from "./primitives";
4
+ export declare const VERSION = "0.0.25";
5
+ export { View, Text, Image, TextInput, NativeView, Svg, SvgPath } from "./primitives";
6
+ export type { CommonProps, TextProps, ImageProps, TextInputProps, NativeViewProps, SvgProps, SvgPathProps, } from "./primitives";
7
7
  export { render, unmount } from "./render";
8
8
  export { native } from "./native";
9
- export { useNativeEvent, useNativeValue, useDebounced, useThrottled, usePrevious, useToggle, useInterval, useHover, useLayoutRect, } from "./hooks";
9
+ export { useNativeEvent, useNativeValue, useRootSize, useDebounced, useThrottled, usePrevious, useToggle, useInterval, useHover, useLayoutRect, } from "./hooks";
10
+ export type { RootSize } from "./hooks";
10
11
  export { useOverlay, OverlayLayer } from "./overlay";
11
12
  export { Tooltip, Modal } from "./popover";
12
13
  export type { TooltipProps, ModalProps } from "./popover";
package/dist/index.js CHANGED
@@ -3,11 +3,11 @@
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.23";
7
- export { View, Text, Image, TextInput, NativeView } from "./primitives";
6
+ export const VERSION = "0.0.25";
7
+ export { View, Text, Image, TextInput, NativeView, Svg, SvgPath } from "./primitives";
8
8
  export { render, unmount } from "./render";
9
9
  export { native } from "./native";
10
- export { useNativeEvent, useNativeValue, useDebounced, useThrottled, usePrevious, useToggle, useInterval, useHover, useLayoutRect, } from "./hooks";
10
+ export { useNativeEvent, useNativeValue, useRootSize, useDebounced, useThrottled, usePrevious, useToggle, useInterval, useHover, useLayoutRect, } from "./hooks";
11
11
  export { useOverlay, OverlayLayer } from "./overlay";
12
12
  export { Tooltip, Modal } from "./popover";
13
13
  export { Button } from "./button";
@@ -73,6 +73,30 @@ export interface ImageProps extends CommonProps {
73
73
  src: string;
74
74
  }
75
75
  export declare function Image(props: ImageProps): import("react").ReactElement<ImageProps, string | import("react").JSXElementConstructor<any>>;
76
+ export interface SvgProps extends CommonProps {
77
+ /** "minX minY width height" — path coordinates map from this box onto the
78
+ node's layout frame (like the web's preserveAspectRatio="none"). */
79
+ viewBox: string;
80
+ }
81
+ /** Vector container: lays out like a View, paints its <SvgPath> children
82
+ scaled from viewBox space. Icon sets port directly — rename attributes
83
+ and drop each path's `d` in. */
84
+ export declare function Svg(props: SvgProps): import("react").ReactElement<SvgProps, string | import("react").JSXElementConstructor<any>>;
85
+ export interface SvgPathProps {
86
+ /** SVG path data ("M12 2 L22 22 …") — parsed once and cached. */
87
+ d: string;
88
+ /** Fill color; SVG's default black. "none" for stroke-only paths. */
89
+ fill?: string;
90
+ stroke?: string;
91
+ /** Stroke width in viewBox units (scales with the frame, like SVG). */
92
+ strokeWidth?: number;
93
+ strokeCap?: "butt" | "round" | "square";
94
+ strokeJoin?: "miter" | "round" | "bevel";
95
+ /** Dash pattern in viewBox units, e.g. "4 2". */
96
+ strokeDash?: string;
97
+ fillRule?: "nonzero" | "evenodd";
98
+ }
99
+ export declare function SvgPath(props: SvgPathProps): import("react").ReactElement<SvgPathProps, string | import("react").JSXElementConstructor<any>>;
76
100
  export interface TextInputProps extends Omit<CommonProps, "children" | "onChange" | "onSubmit"> {
77
101
  value?: string;
78
102
  defaultValue?: string;
@@ -8,6 +8,15 @@ export function Text(props) {
8
8
  export function Image(props) {
9
9
  return createElement("vs-image", props);
10
10
  }
11
+ /** Vector container: lays out like a View, paints its <SvgPath> children
12
+ scaled from viewBox space. Icon sets port directly — rename attributes
13
+ and drop each path's `d` in. */
14
+ export function Svg(props) {
15
+ return createElement("vs-svg", props);
16
+ }
17
+ export function SvgPath(props) {
18
+ return createElement("vs-svgpath", props);
19
+ }
11
20
  export function TextInput({ onChange, onSubmit, ...rest }) {
12
21
  return createElement("vs-textinput", {
13
22
  ...rest,
package/dist/tw.js CHANGED
@@ -80,6 +80,18 @@ const staticClasses = {
80
80
  "cursor-not-allowed": { cursor: "not-allowed" },
81
81
  "pointer-events-none": { pointerEvents: "none" },
82
82
  "pointer-events-auto": { pointerEvents: "auto" },
83
+ hidden: { display: "none" },
84
+ invisible: { visibility: "hidden" },
85
+ visible: { visibility: "visible" },
86
+ "origin-center": { transformOriginX: 50, transformOriginY: 50 },
87
+ "origin-top": { transformOriginX: 50, transformOriginY: 0 },
88
+ "origin-bottom": { transformOriginX: 50, transformOriginY: 100 },
89
+ "origin-left": { transformOriginX: 0, transformOriginY: 50 },
90
+ "origin-right": { transformOriginX: 100, transformOriginY: 50 },
91
+ "origin-top-left": { transformOriginX: 0, transformOriginY: 0 },
92
+ "origin-top-right": { transformOriginX: 100, transformOriginY: 0 },
93
+ "origin-bottom-left": { transformOriginX: 0, transformOriginY: 100 },
94
+ "origin-bottom-right": { transformOriginX: 100, transformOriginY: 100 },
83
95
  "border-solid": { borderStyle: "solid" },
84
96
  "border-dashed": { borderStyle: "dashed" },
85
97
  "border-dotted": { borderStyle: "dotted" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vsreact/core",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "description": "Write React. Ship native VST. A React renderer for JUCE audio plugins — QuickJS + Yoga + juce::Graphics, no webview.",
5
5
  "keywords": [
6
6
  "react",
package/src/hooks.ts CHANGED
@@ -42,6 +42,30 @@ export function useNativeValue<T = any>(name: string, initial: T): T {
42
42
  return value;
43
43
  }
44
44
 
45
+ export interface RootSize {
46
+ width: number;
47
+ height: number;
48
+ }
49
+
50
+ /**
51
+ * The editor's current size. The native side sends a "resize" event at
52
+ * mount (so this resolves on the first committed frame) and on every
53
+ * host resize — the foundation for resizable editors.
54
+ */
55
+ export function useRootSize(): RootSize {
56
+ const [size, setSize] = useState<RootSize>({ width: 0, height: 0 });
57
+
58
+ useNativeEvent("resize", (payload) => {
59
+ const width = Number(payload?.width);
60
+ const height = Number(payload?.height);
61
+
62
+ if (Number.isFinite(width) && Number.isFinite(height))
63
+ setSize((s) => (s.width === width && s.height === height ? s : { width, height }));
64
+ });
65
+
66
+ return size;
67
+ }
68
+
45
69
  /**
46
70
  * The value, but only after it has stopped changing for `delayMs` —
47
71
  * classic input debouncing for expensive native calls:
package/src/hostConfig.ts CHANGED
@@ -20,6 +20,8 @@ const hostTypes: Record<string, string> = {
20
20
  "vs-image": "image",
21
21
  "vs-textinput": "textinput",
22
22
  "vs-native": "native",
23
+ "vs-svg": "svg",
24
+ "vs-svgpath": "svgpath",
23
25
  };
24
26
 
25
27
  const eventPropNames: Record<string, string> = {
package/src/index.ts CHANGED
@@ -4,21 +4,24 @@ 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.23";
7
+ export const VERSION = "0.0.25";
8
8
 
9
- export { View, Text, Image, TextInput, NativeView } from "./primitives";
9
+ export { View, Text, Image, TextInput, NativeView, Svg, SvgPath } from "./primitives";
10
10
  export type {
11
11
  CommonProps,
12
12
  TextProps,
13
13
  ImageProps,
14
14
  TextInputProps,
15
15
  NativeViewProps,
16
+ SvgProps,
17
+ SvgPathProps,
16
18
  } from "./primitives";
17
19
  export { render, unmount } from "./render";
18
20
  export { native } from "./native";
19
21
  export {
20
22
  useNativeEvent,
21
23
  useNativeValue,
24
+ useRootSize,
22
25
  useDebounced,
23
26
  useThrottled,
24
27
  usePrevious,
@@ -27,6 +30,7 @@ export {
27
30
  useHover,
28
31
  useLayoutRect,
29
32
  } from "./hooks";
33
+ export type { RootSize } from "./hooks";
30
34
  export { useOverlay, OverlayLayer } from "./overlay";
31
35
  export { Tooltip, Modal } from "./popover";
32
36
  export type { TooltipProps, ModalProps } from "./popover";
@@ -125,3 +125,45 @@ describe("useNativeValue", () => {
125
125
  expect(seen.at(-1)).toBe(0.8);
126
126
  });
127
127
  });
128
+
129
+ describe("Svg primitives (0.0.24)", () => {
130
+ test("Svg + SvgPath create their node types with data props", async () => {
131
+ const { Svg, SvgPath } = require("./index");
132
+ render(
133
+ <Svg viewBox="0 0 24 24" style={{ width: 24, height: 24 }}>
134
+ <SvgPath d="M12 2 L22 22 L2 22 Z" fill="#ff0000" />
135
+ <SvgPath d="M2 12 H22" fill="none" stroke="#00ff00" strokeWidth={2} strokeDash="4 2" />
136
+ </Svg>,
137
+ );
138
+
139
+ const creates = allOps().filter((op: any) => op[0] === "create");
140
+ expect(creates.some((op: any) => op[2] === "svg")).toBe(true);
141
+ expect(creates.filter((op: any) => op[2] === "svgpath")).toHaveLength(2);
142
+
143
+ const propOps = allOps().filter((op: any) => op[0] === "setProps");
144
+ const svgProps: any = propOps.find((op: any) => op[2]?.viewBox);
145
+ expect(svgProps[2].viewBox).toBe("0 0 24 24");
146
+
147
+ const pathProps: any = propOps.find((op: any) => op[2]?.strokeDash);
148
+ expect(pathProps[2]).toMatchObject({ d: "M2 12 H22", fill: "none", stroke: "#00ff00", strokeWidth: 2 });
149
+ });
150
+ });
151
+
152
+ describe("useRootSize (0.0.25)", () => {
153
+ test("tracks resize events, ignores junk", async () => {
154
+ const { useRootSize } = require("./index");
155
+ let seen: any = null;
156
+ function Probe() {
157
+ seen = useRootSize();
158
+ return <View />;
159
+ }
160
+ render(<Probe />);
161
+ await new Promise((r) => setTimeout(r, 0));
162
+
163
+ expect(seen).toEqual({ width: 0, height: 0 });
164
+ dispatch({ kind: "native", name: "resize", payload: { width: 793, height: 496 } });
165
+ expect(seen).toEqual({ width: 793, height: 496 });
166
+ dispatch({ kind: "native", name: "resize", payload: { width: "nope" } });
167
+ expect(seen).toEqual({ width: 793, height: 496 });
168
+ });
169
+ });
@@ -93,6 +93,38 @@ export function Image(props: ImageProps) {
93
93
  return createElement("vs-image", props);
94
94
  }
95
95
 
96
+ export interface SvgProps extends CommonProps {
97
+ /** "minX minY width height" — path coordinates map from this box onto the
98
+ node's layout frame (like the web's preserveAspectRatio="none"). */
99
+ viewBox: string;
100
+ }
101
+
102
+ /** Vector container: lays out like a View, paints its <SvgPath> children
103
+ scaled from viewBox space. Icon sets port directly — rename attributes
104
+ and drop each path's `d` in. */
105
+ export function Svg(props: SvgProps) {
106
+ return createElement("vs-svg", props);
107
+ }
108
+
109
+ export interface SvgPathProps {
110
+ /** SVG path data ("M12 2 L22 22 …") — parsed once and cached. */
111
+ d: string;
112
+ /** Fill color; SVG's default black. "none" for stroke-only paths. */
113
+ fill?: string;
114
+ stroke?: string;
115
+ /** Stroke width in viewBox units (scales with the frame, like SVG). */
116
+ strokeWidth?: number;
117
+ strokeCap?: "butt" | "round" | "square";
118
+ strokeJoin?: "miter" | "round" | "bevel";
119
+ /** Dash pattern in viewBox units, e.g. "4 2". */
120
+ strokeDash?: string;
121
+ fillRule?: "nonzero" | "evenodd";
122
+ }
123
+
124
+ export function SvgPath(props: SvgPathProps) {
125
+ return createElement("vs-svgpath", props);
126
+ }
127
+
96
128
  export interface TextInputProps extends Omit<CommonProps, "children" | "onChange" | "onSubmit"> {
97
129
  value?: string;
98
130
  defaultValue?: string;
package/src/tw.test.ts CHANGED
@@ -262,3 +262,17 @@ describe("input & media classes (0.0.23)", () => {
262
262
  expect(tw("cursor-grab").style).toEqual({ cursor: "grab" });
263
263
  });
264
264
  });
265
+
266
+ describe("layout & robustness classes (0.0.25)", () => {
267
+ test("hidden vs invisible", () => {
268
+ expect(tw("hidden").style).toEqual({ display: "none" });
269
+ expect(tw("invisible").style).toEqual({ visibility: "hidden" });
270
+ expect(tw("visible").style).toEqual({ visibility: "visible" });
271
+ });
272
+
273
+ test("percent translate and transform origin", () => {
274
+ expect(tw("translate-x-1/2").style).toEqual({ translateX: "50%" });
275
+ expect(tw("-translate-y-1/2").style).toEqual({ translateY: "-50%" });
276
+ expect(tw("origin-top-left").style).toEqual({ transformOriginX: 0, transformOriginY: 0 });
277
+ });
278
+ });
package/src/tw.ts CHANGED
@@ -91,6 +91,18 @@ const staticClasses: Record<string, Style> = {
91
91
  "cursor-not-allowed": { cursor: "not-allowed" },
92
92
  "pointer-events-none": { pointerEvents: "none" },
93
93
  "pointer-events-auto": { pointerEvents: "auto" },
94
+ hidden: { display: "none" },
95
+ invisible: { visibility: "hidden" },
96
+ visible: { visibility: "visible" },
97
+ "origin-center": { transformOriginX: 50, transformOriginY: 50 },
98
+ "origin-top": { transformOriginX: 50, transformOriginY: 0 },
99
+ "origin-bottom": { transformOriginX: 50, transformOriginY: 100 },
100
+ "origin-left": { transformOriginX: 0, transformOriginY: 50 },
101
+ "origin-right": { transformOriginX: 100, transformOriginY: 50 },
102
+ "origin-top-left": { transformOriginX: 0, transformOriginY: 0 },
103
+ "origin-top-right": { transformOriginX: 100, transformOriginY: 0 },
104
+ "origin-bottom-left": { transformOriginX: 0, transformOriginY: 100 },
105
+ "origin-bottom-right": { transformOriginX: 100, transformOriginY: 100 },
94
106
  "border-solid": { borderStyle: "solid" },
95
107
  "border-dashed": { borderStyle: "dashed" },
96
108
  "border-dotted": { borderStyle: "dotted" },