react-shadertoy 0.1.0
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 +21 -0
- package/README.md +85 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +326 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +323 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wrennly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# react-shadertoy
|
|
2
|
+
|
|
3
|
+
Run [Shadertoy](https://www.shadertoy.com/) GLSL shaders in React. Copy-paste and it works.
|
|
4
|
+
|
|
5
|
+
- Zero dependencies (just React)
|
|
6
|
+
- All Shadertoy uniforms supported (`iTime`, `iResolution`, `iMouse`, `iDate`, etc.)
|
|
7
|
+
- Mouse & touch interaction built-in
|
|
8
|
+
- TypeScript-first
|
|
9
|
+
- < 4KB gzipped
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install react-shadertoy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { Shadertoy } from 'react-shadertoy'
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
return (
|
|
24
|
+
<Shadertoy
|
|
25
|
+
style={{ width: '100%', height: '400px' }}
|
|
26
|
+
fragmentShader={`
|
|
27
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
28
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
29
|
+
fragColor = vec4(uv, 0.5 + 0.5 * sin(iTime), 1.0);
|
|
30
|
+
}
|
|
31
|
+
`}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Find a shader on [Shadertoy](https://www.shadertoy.com/), copy the GLSL code, paste it into `fragmentShader`. Done.
|
|
38
|
+
|
|
39
|
+
## Hooks API
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { useShadertoy } from 'react-shadertoy'
|
|
43
|
+
|
|
44
|
+
function MyShader() {
|
|
45
|
+
const { canvasRef, isReady, error, pause, resume } = useShadertoy({
|
|
46
|
+
fragmentShader: `
|
|
47
|
+
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
48
|
+
vec2 uv = fragCoord / iResolution.xy;
|
|
49
|
+
fragColor = vec4(uv, 0.5 + 0.5 * sin(iTime), 1.0);
|
|
50
|
+
}
|
|
51
|
+
`,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return <canvas ref={canvasRef} style={{ width: '100%', height: '400px' }} />
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Props
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Default | Description |
|
|
61
|
+
|------|------|---------|-------------|
|
|
62
|
+
| `fragmentShader` | `string` | required | Shadertoy GLSL code |
|
|
63
|
+
| `style` | `CSSProperties` | — | Container style |
|
|
64
|
+
| `className` | `string` | — | Container className |
|
|
65
|
+
| `paused` | `boolean` | `false` | Pause rendering |
|
|
66
|
+
| `speed` | `number` | `1.0` | `iTime` speed multiplier |
|
|
67
|
+
| `pixelRatio` | `number` | `devicePixelRatio` | Canvas pixel ratio |
|
|
68
|
+
| `mouse` | `boolean` | `true` | Enable mouse/touch tracking |
|
|
69
|
+
| `onError` | `(error: string) => void` | — | GLSL compile error callback |
|
|
70
|
+
| `onLoad` | `() => void` | — | WebGL ready callback |
|
|
71
|
+
|
|
72
|
+
## Supported Uniforms
|
|
73
|
+
|
|
74
|
+
| Uniform | Type | Description |
|
|
75
|
+
|---------|------|-------------|
|
|
76
|
+
| `iResolution` | `vec3` | Viewport size in pixels |
|
|
77
|
+
| `iTime` | `float` | Elapsed time in seconds |
|
|
78
|
+
| `iTimeDelta` | `float` | Time since last frame |
|
|
79
|
+
| `iFrame` | `int` | Frame counter |
|
|
80
|
+
| `iMouse` | `vec4` | Mouse position & click state |
|
|
81
|
+
| `iDate` | `vec4` | Year, month, day, seconds |
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { CSSProperties, RefObject } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ShadertoyProps {
|
|
5
|
+
/** Shadertoy-compatible GLSL fragment shader (must contain mainImage) */
|
|
6
|
+
fragmentShader: string;
|
|
7
|
+
/** Container style */
|
|
8
|
+
style?: CSSProperties;
|
|
9
|
+
/** Container className */
|
|
10
|
+
className?: string;
|
|
11
|
+
/** Pause rendering (default: false) */
|
|
12
|
+
paused?: boolean;
|
|
13
|
+
/** iTime speed multiplier (default: 1.0) */
|
|
14
|
+
speed?: number;
|
|
15
|
+
/** Device pixel ratio (default: window.devicePixelRatio) */
|
|
16
|
+
pixelRatio?: number;
|
|
17
|
+
/** Enable mouse/touch tracking (default: true) */
|
|
18
|
+
mouse?: boolean;
|
|
19
|
+
/** Called when GLSL compilation fails */
|
|
20
|
+
onError?: (error: string) => void;
|
|
21
|
+
/** Called when WebGL is ready */
|
|
22
|
+
onLoad?: () => void;
|
|
23
|
+
}
|
|
24
|
+
interface UseShadertoyOptions {
|
|
25
|
+
fragmentShader: string;
|
|
26
|
+
paused?: boolean;
|
|
27
|
+
speed?: number;
|
|
28
|
+
pixelRatio?: number;
|
|
29
|
+
mouse?: boolean;
|
|
30
|
+
onError?: (error: string) => void;
|
|
31
|
+
onLoad?: () => void;
|
|
32
|
+
}
|
|
33
|
+
interface UseShadertoyReturn {
|
|
34
|
+
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
35
|
+
isReady: boolean;
|
|
36
|
+
error: string | null;
|
|
37
|
+
pause: () => void;
|
|
38
|
+
resume: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare function Shadertoy({ fragmentShader, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
|
|
42
|
+
|
|
43
|
+
declare function useShadertoy({ fragmentShader, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn;
|
|
44
|
+
|
|
45
|
+
export { Shadertoy, type ShadertoyProps, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { CSSProperties, RefObject } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ShadertoyProps {
|
|
5
|
+
/** Shadertoy-compatible GLSL fragment shader (must contain mainImage) */
|
|
6
|
+
fragmentShader: string;
|
|
7
|
+
/** Container style */
|
|
8
|
+
style?: CSSProperties;
|
|
9
|
+
/** Container className */
|
|
10
|
+
className?: string;
|
|
11
|
+
/** Pause rendering (default: false) */
|
|
12
|
+
paused?: boolean;
|
|
13
|
+
/** iTime speed multiplier (default: 1.0) */
|
|
14
|
+
speed?: number;
|
|
15
|
+
/** Device pixel ratio (default: window.devicePixelRatio) */
|
|
16
|
+
pixelRatio?: number;
|
|
17
|
+
/** Enable mouse/touch tracking (default: true) */
|
|
18
|
+
mouse?: boolean;
|
|
19
|
+
/** Called when GLSL compilation fails */
|
|
20
|
+
onError?: (error: string) => void;
|
|
21
|
+
/** Called when WebGL is ready */
|
|
22
|
+
onLoad?: () => void;
|
|
23
|
+
}
|
|
24
|
+
interface UseShadertoyOptions {
|
|
25
|
+
fragmentShader: string;
|
|
26
|
+
paused?: boolean;
|
|
27
|
+
speed?: number;
|
|
28
|
+
pixelRatio?: number;
|
|
29
|
+
mouse?: boolean;
|
|
30
|
+
onError?: (error: string) => void;
|
|
31
|
+
onLoad?: () => void;
|
|
32
|
+
}
|
|
33
|
+
interface UseShadertoyReturn {
|
|
34
|
+
canvasRef: RefObject<HTMLCanvasElement | null>;
|
|
35
|
+
isReady: boolean;
|
|
36
|
+
error: string | null;
|
|
37
|
+
pause: () => void;
|
|
38
|
+
resume: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare function Shadertoy({ fragmentShader, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
|
|
42
|
+
|
|
43
|
+
declare function useShadertoy({ fragmentShader, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn;
|
|
44
|
+
|
|
45
|
+
export { Shadertoy, type ShadertoyProps, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/useShadertoy.ts
|
|
7
|
+
|
|
8
|
+
// src/renderer.ts
|
|
9
|
+
var QUAD_VERTICES = new Float32Array([
|
|
10
|
+
-1,
|
|
11
|
+
-1,
|
|
12
|
+
1,
|
|
13
|
+
-1,
|
|
14
|
+
-1,
|
|
15
|
+
1,
|
|
16
|
+
-1,
|
|
17
|
+
1,
|
|
18
|
+
1,
|
|
19
|
+
-1,
|
|
20
|
+
1,
|
|
21
|
+
1
|
|
22
|
+
]);
|
|
23
|
+
var VERTEX_SHADER = `
|
|
24
|
+
attribute vec2 position;
|
|
25
|
+
void main() {
|
|
26
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
function wrapFragmentShader(shader) {
|
|
30
|
+
return `precision highp float;
|
|
31
|
+
|
|
32
|
+
uniform vec3 iResolution;
|
|
33
|
+
uniform float iTime;
|
|
34
|
+
uniform float iTimeDelta;
|
|
35
|
+
uniform int iFrame;
|
|
36
|
+
uniform vec4 iMouse;
|
|
37
|
+
uniform vec4 iDate;
|
|
38
|
+
|
|
39
|
+
${shader}
|
|
40
|
+
|
|
41
|
+
void main() {
|
|
42
|
+
mainImage(gl_FragColor, gl_FragCoord.xy);
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
function compileShader(gl, type, source) {
|
|
47
|
+
const shader = gl.createShader(type);
|
|
48
|
+
if (!shader) return "Failed to create shader";
|
|
49
|
+
gl.shaderSource(shader, source);
|
|
50
|
+
gl.compileShader(shader);
|
|
51
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
52
|
+
const log = gl.getShaderInfoLog(shader) || "Unknown compile error";
|
|
53
|
+
gl.deleteShader(shader);
|
|
54
|
+
return log;
|
|
55
|
+
}
|
|
56
|
+
return shader;
|
|
57
|
+
}
|
|
58
|
+
function createRenderer(canvas, fragmentShader) {
|
|
59
|
+
const gl = canvas.getContext("webgl", {
|
|
60
|
+
antialias: false,
|
|
61
|
+
alpha: true,
|
|
62
|
+
premultipliedAlpha: false
|
|
63
|
+
});
|
|
64
|
+
if (!gl) return "WebGL not supported";
|
|
65
|
+
const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
66
|
+
if (typeof vert === "string") return vert;
|
|
67
|
+
const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader));
|
|
68
|
+
if (typeof frag === "string") return frag;
|
|
69
|
+
const program = gl.createProgram();
|
|
70
|
+
if (!program) return "Failed to create program";
|
|
71
|
+
gl.attachShader(program, vert);
|
|
72
|
+
gl.attachShader(program, frag);
|
|
73
|
+
gl.linkProgram(program);
|
|
74
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
75
|
+
const log = gl.getProgramInfoLog(program) || "Unknown link error";
|
|
76
|
+
gl.deleteProgram(program);
|
|
77
|
+
return log;
|
|
78
|
+
}
|
|
79
|
+
gl.deleteShader(vert);
|
|
80
|
+
gl.deleteShader(frag);
|
|
81
|
+
const buffer = gl.createBuffer();
|
|
82
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
83
|
+
gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW);
|
|
84
|
+
const positionLoc = gl.getAttribLocation(program, "position");
|
|
85
|
+
gl.enableVertexAttribArray(positionLoc);
|
|
86
|
+
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
|
|
87
|
+
const locations = {
|
|
88
|
+
iResolution: gl.getUniformLocation(program, "iResolution"),
|
|
89
|
+
iTime: gl.getUniformLocation(program, "iTime"),
|
|
90
|
+
iTimeDelta: gl.getUniformLocation(program, "iTimeDelta"),
|
|
91
|
+
iFrame: gl.getUniformLocation(program, "iFrame"),
|
|
92
|
+
iMouse: gl.getUniformLocation(program, "iMouse"),
|
|
93
|
+
iDate: gl.getUniformLocation(program, "iDate")
|
|
94
|
+
};
|
|
95
|
+
gl.useProgram(program);
|
|
96
|
+
return {
|
|
97
|
+
gl,
|
|
98
|
+
program,
|
|
99
|
+
locations,
|
|
100
|
+
time: 0,
|
|
101
|
+
frame: 0,
|
|
102
|
+
lastTime: 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function render(state) {
|
|
106
|
+
const { gl } = state;
|
|
107
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
108
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
109
|
+
}
|
|
110
|
+
function dispose(state) {
|
|
111
|
+
const { gl, program } = state;
|
|
112
|
+
gl.deleteProgram(program);
|
|
113
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/uniforms.ts
|
|
117
|
+
function updateUniforms(state, delta, speed, mouse) {
|
|
118
|
+
const { gl, locations } = state;
|
|
119
|
+
state.time += delta * speed;
|
|
120
|
+
if (locations.iTime) {
|
|
121
|
+
gl.uniform1f(locations.iTime, state.time);
|
|
122
|
+
}
|
|
123
|
+
if (locations.iTimeDelta) {
|
|
124
|
+
gl.uniform1f(locations.iTimeDelta, delta);
|
|
125
|
+
}
|
|
126
|
+
state.frame++;
|
|
127
|
+
if (locations.iFrame) {
|
|
128
|
+
gl.uniform1i(locations.iFrame, state.frame);
|
|
129
|
+
}
|
|
130
|
+
if (locations.iResolution) {
|
|
131
|
+
gl.uniform3f(
|
|
132
|
+
locations.iResolution,
|
|
133
|
+
gl.drawingBufferWidth,
|
|
134
|
+
gl.drawingBufferHeight,
|
|
135
|
+
1
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (locations.iMouse) {
|
|
139
|
+
const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX);
|
|
140
|
+
const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY);
|
|
141
|
+
gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw);
|
|
142
|
+
}
|
|
143
|
+
if (locations.iDate) {
|
|
144
|
+
const now = /* @__PURE__ */ new Date();
|
|
145
|
+
const seconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1e3;
|
|
146
|
+
gl.uniform4f(
|
|
147
|
+
locations.iDate,
|
|
148
|
+
now.getFullYear(),
|
|
149
|
+
now.getMonth(),
|
|
150
|
+
// 0-based, matches Shadertoy
|
|
151
|
+
now.getDate(),
|
|
152
|
+
seconds
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/useShadertoy.ts
|
|
158
|
+
function useShadertoy({
|
|
159
|
+
fragmentShader,
|
|
160
|
+
paused = false,
|
|
161
|
+
speed = 1,
|
|
162
|
+
pixelRatio,
|
|
163
|
+
mouse: mouseEnabled = true,
|
|
164
|
+
onError,
|
|
165
|
+
onLoad
|
|
166
|
+
}) {
|
|
167
|
+
const canvasRef = react.useRef(null);
|
|
168
|
+
const rendererRef = react.useRef(null);
|
|
169
|
+
const rafRef = react.useRef(0);
|
|
170
|
+
const pausedRef = react.useRef(paused);
|
|
171
|
+
const speedRef = react.useRef(speed);
|
|
172
|
+
const [isReady, setIsReady] = react.useState(false);
|
|
173
|
+
const [error, setError] = react.useState(null);
|
|
174
|
+
const mouseState = react.useRef({
|
|
175
|
+
x: 0,
|
|
176
|
+
y: 0,
|
|
177
|
+
clickX: 0,
|
|
178
|
+
clickY: 0,
|
|
179
|
+
pressed: false
|
|
180
|
+
});
|
|
181
|
+
pausedRef.current = paused;
|
|
182
|
+
speedRef.current = speed;
|
|
183
|
+
react.useEffect(() => {
|
|
184
|
+
const canvas = canvasRef.current;
|
|
185
|
+
if (!canvas) return;
|
|
186
|
+
const result = createRenderer(canvas, fragmentShader);
|
|
187
|
+
if (typeof result === "string") {
|
|
188
|
+
setError(result);
|
|
189
|
+
onError?.(result);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
rendererRef.current = result;
|
|
193
|
+
setIsReady(true);
|
|
194
|
+
setError(null);
|
|
195
|
+
onLoad?.();
|
|
196
|
+
let lastTimestamp = 0;
|
|
197
|
+
const loop = (timestamp) => {
|
|
198
|
+
const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1e3 : 0;
|
|
199
|
+
lastTimestamp = timestamp;
|
|
200
|
+
if (!pausedRef.current && rendererRef.current) {
|
|
201
|
+
updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current);
|
|
202
|
+
render(rendererRef.current);
|
|
203
|
+
}
|
|
204
|
+
rafRef.current = requestAnimationFrame(loop);
|
|
205
|
+
};
|
|
206
|
+
rafRef.current = requestAnimationFrame(loop);
|
|
207
|
+
return () => {
|
|
208
|
+
cancelAnimationFrame(rafRef.current);
|
|
209
|
+
if (rendererRef.current) {
|
|
210
|
+
dispose(rendererRef.current);
|
|
211
|
+
rendererRef.current = null;
|
|
212
|
+
}
|
|
213
|
+
setIsReady(false);
|
|
214
|
+
};
|
|
215
|
+
}, [fragmentShader, onError, onLoad]);
|
|
216
|
+
react.useEffect(() => {
|
|
217
|
+
const canvas = canvasRef.current;
|
|
218
|
+
if (!canvas) return;
|
|
219
|
+
const dpr = pixelRatio ?? (typeof window !== "undefined" ? window.devicePixelRatio : 1);
|
|
220
|
+
const observer = new ResizeObserver((entries) => {
|
|
221
|
+
for (const entry of entries) {
|
|
222
|
+
const { width, height } = entry.contentRect;
|
|
223
|
+
canvas.width = Math.round(width * dpr);
|
|
224
|
+
canvas.height = Math.round(height * dpr);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
observer.observe(canvas);
|
|
228
|
+
return () => observer.disconnect();
|
|
229
|
+
}, [pixelRatio]);
|
|
230
|
+
react.useEffect(() => {
|
|
231
|
+
if (!mouseEnabled) return;
|
|
232
|
+
const canvas = canvasRef.current;
|
|
233
|
+
if (!canvas) return;
|
|
234
|
+
const toPixel = (cx, cy) => {
|
|
235
|
+
const r = canvas.getBoundingClientRect();
|
|
236
|
+
const dpr = pixelRatio ?? window.devicePixelRatio;
|
|
237
|
+
return {
|
|
238
|
+
x: (cx - r.left) * dpr,
|
|
239
|
+
y: (r.height - (cy - r.top)) * dpr
|
|
240
|
+
// flip Y
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
const onMove = (cx, cy) => {
|
|
244
|
+
if (!mouseState.current.pressed) return;
|
|
245
|
+
const { x, y } = toPixel(cx, cy);
|
|
246
|
+
mouseState.current.x = x;
|
|
247
|
+
mouseState.current.y = y;
|
|
248
|
+
};
|
|
249
|
+
const onDown = (cx, cy) => {
|
|
250
|
+
const { x, y } = toPixel(cx, cy);
|
|
251
|
+
mouseState.current.x = x;
|
|
252
|
+
mouseState.current.y = y;
|
|
253
|
+
mouseState.current.clickX = x;
|
|
254
|
+
mouseState.current.clickY = y;
|
|
255
|
+
mouseState.current.pressed = true;
|
|
256
|
+
};
|
|
257
|
+
const onUp = () => {
|
|
258
|
+
mouseState.current.pressed = false;
|
|
259
|
+
};
|
|
260
|
+
const mm = (e) => onMove(e.clientX, e.clientY);
|
|
261
|
+
const md = (e) => onDown(e.clientX, e.clientY);
|
|
262
|
+
const mu = () => onUp();
|
|
263
|
+
const tm = (e) => {
|
|
264
|
+
if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY);
|
|
265
|
+
};
|
|
266
|
+
const ts = (e) => {
|
|
267
|
+
if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY);
|
|
268
|
+
};
|
|
269
|
+
const te = () => onUp();
|
|
270
|
+
window.addEventListener("mousemove", mm);
|
|
271
|
+
canvas.addEventListener("mousedown", md);
|
|
272
|
+
window.addEventListener("mouseup", mu);
|
|
273
|
+
window.addEventListener("touchmove", tm, { passive: true });
|
|
274
|
+
canvas.addEventListener("touchstart", ts, { passive: true });
|
|
275
|
+
window.addEventListener("touchend", te);
|
|
276
|
+
return () => {
|
|
277
|
+
window.removeEventListener("mousemove", mm);
|
|
278
|
+
canvas.removeEventListener("mousedown", md);
|
|
279
|
+
window.removeEventListener("mouseup", mu);
|
|
280
|
+
window.removeEventListener("touchmove", tm);
|
|
281
|
+
canvas.removeEventListener("touchstart", ts);
|
|
282
|
+
window.removeEventListener("touchend", te);
|
|
283
|
+
};
|
|
284
|
+
}, [mouseEnabled, pixelRatio]);
|
|
285
|
+
const pause = react.useCallback(() => {
|
|
286
|
+
pausedRef.current = true;
|
|
287
|
+
}, []);
|
|
288
|
+
const resume = react.useCallback(() => {
|
|
289
|
+
pausedRef.current = false;
|
|
290
|
+
}, []);
|
|
291
|
+
return { canvasRef, isReady, error, pause, resume };
|
|
292
|
+
}
|
|
293
|
+
function Shadertoy({
|
|
294
|
+
fragmentShader,
|
|
295
|
+
style,
|
|
296
|
+
className,
|
|
297
|
+
paused,
|
|
298
|
+
speed,
|
|
299
|
+
pixelRatio,
|
|
300
|
+
mouse,
|
|
301
|
+
onError,
|
|
302
|
+
onLoad
|
|
303
|
+
}) {
|
|
304
|
+
const { canvasRef } = useShadertoy({
|
|
305
|
+
fragmentShader,
|
|
306
|
+
paused,
|
|
307
|
+
speed,
|
|
308
|
+
pixelRatio,
|
|
309
|
+
mouse,
|
|
310
|
+
onError,
|
|
311
|
+
onLoad
|
|
312
|
+
});
|
|
313
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
314
|
+
"canvas",
|
|
315
|
+
{
|
|
316
|
+
ref: canvasRef,
|
|
317
|
+
className,
|
|
318
|
+
style: { width: "100%", height: "100%", display: "block", ...style }
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
exports.Shadertoy = Shadertoy;
|
|
324
|
+
exports.useShadertoy = useShadertoy;
|
|
325
|
+
//# sourceMappingURL=index.js.map
|
|
326
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/renderer.ts","../src/uniforms.ts","../src/useShadertoy.ts","../src/Shadertoy.tsx"],"names":["useRef","useState","useEffect","useCallback","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { RendererState, UniformLocations } from './types'\n\n// Full-screen quad: two triangles covering clip space\nconst QUAD_VERTICES = new Float32Array([\n -1, -1,\n 1, -1,\n -1, 1,\n -1, 1,\n 1, -1,\n 1, 1,\n])\n\nconst VERTEX_SHADER = `\nattribute vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`\n\n/**\n * Wrap Shadertoy GLSL: prepend uniform declarations + main() bridge.\n */\nfunction wrapFragmentShader(shader: string): string {\n return `precision highp float;\n\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform int iFrame;\nuniform vec4 iMouse;\nuniform vec4 iDate;\n\n${shader}\n\nvoid main() {\n mainImage(gl_FragColor, gl_FragCoord.xy);\n}\n`\n}\n\nfunction compileShader(\n gl: WebGLRenderingContext,\n type: number,\n source: string,\n): WebGLShader | string {\n const shader = gl.createShader(type)\n if (!shader) return 'Failed to create shader'\n\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) || 'Unknown compile error'\n gl.deleteShader(shader)\n return log\n }\n\n return shader\n}\n\n/**\n * Initialize WebGL: compile shaders, link program, setup quad.\n * Returns RendererState on success or error string on failure.\n */\nexport function createRenderer(\n canvas: HTMLCanvasElement,\n fragmentShader: string,\n): RendererState | string {\n const gl = canvas.getContext('webgl', {\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n })\n if (!gl) return 'WebGL not supported'\n\n // Compile vertex shader\n const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER)\n if (typeof vert === 'string') return vert\n\n // Compile fragment shader (wrapped)\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader))\n if (typeof frag === 'string') return frag\n\n // Link program\n const program = gl.createProgram()\n if (!program) return 'Failed to create program'\n\n gl.attachShader(program, vert)\n gl.attachShader(program, frag)\n gl.linkProgram(program)\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program) || 'Unknown link error'\n gl.deleteProgram(program)\n return log\n }\n\n // Clean up individual shaders (attached to program)\n gl.deleteShader(vert)\n gl.deleteShader(frag)\n\n // Setup quad geometry\n const buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW)\n\n const positionLoc = gl.getAttribLocation(program, 'position')\n gl.enableVertexAttribArray(positionLoc)\n gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)\n\n // Get uniform locations\n const locations: UniformLocations = {\n iResolution: gl.getUniformLocation(program, 'iResolution'),\n iTime: gl.getUniformLocation(program, 'iTime'),\n iTimeDelta: gl.getUniformLocation(program, 'iTimeDelta'),\n iFrame: gl.getUniformLocation(program, 'iFrame'),\n iMouse: gl.getUniformLocation(program, 'iMouse'),\n iDate: gl.getUniformLocation(program, 'iDate'),\n }\n\n gl.useProgram(program)\n\n return {\n gl,\n program,\n locations,\n time: 0,\n frame: 0,\n lastTime: 0,\n }\n}\n\n/**\n * Render one frame.\n */\nexport function render(state: RendererState): void {\n const { gl } = state\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)\n gl.drawArrays(gl.TRIANGLES, 0, 6)\n}\n\n/**\n * Clean up WebGL resources.\n */\nexport function dispose(state: RendererState): void {\n const { gl, program } = state\n gl.deleteProgram(program)\n gl.getExtension('WEBGL_lose_context')?.loseContext()\n}\n","import type { MouseState, RendererState } from './types'\n\n/**\n * Update all Shadertoy standard uniforms for one frame.\n */\nexport function updateUniforms(\n state: RendererState,\n delta: number,\n speed: number,\n mouse: MouseState,\n): void {\n const { gl, locations } = state\n\n // iTime\n state.time += delta * speed\n if (locations.iTime) {\n gl.uniform1f(locations.iTime, state.time)\n }\n\n // iTimeDelta\n if (locations.iTimeDelta) {\n gl.uniform1f(locations.iTimeDelta, delta)\n }\n\n // iFrame\n state.frame++\n if (locations.iFrame) {\n gl.uniform1i(locations.iFrame, state.frame)\n }\n\n // iResolution\n if (locations.iResolution) {\n gl.uniform3f(\n locations.iResolution,\n gl.drawingBufferWidth,\n gl.drawingBufferHeight,\n 1.0,\n )\n }\n\n // iMouse — Shadertoy convention:\n // xy: current position (if pressed)\n // zw: click position (positive when pressed, negative when released)\n if (locations.iMouse) {\n const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX)\n const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY)\n gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw)\n }\n\n // iDate — vec4(year, month, day, seconds_since_midnight)\n if (locations.iDate) {\n const now = new Date()\n const seconds =\n now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1000\n gl.uniform4f(\n locations.iDate,\n now.getFullYear(),\n now.getMonth(), // 0-based, matches Shadertoy\n now.getDate(),\n seconds,\n )\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { createRenderer, dispose, render } from './renderer'\nimport type { MouseState, RendererState, UseShadertoyOptions, UseShadertoyReturn } from './types'\nimport { updateUniforms } from './uniforms'\n\nexport function useShadertoy({\n fragmentShader,\n paused = false,\n speed = 1.0,\n pixelRatio,\n mouse: mouseEnabled = true,\n onError,\n onLoad,\n}: UseShadertoyOptions): UseShadertoyReturn {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const rendererRef = useRef<RendererState | null>(null)\n const rafRef = useRef<number>(0)\n const pausedRef = useRef(paused)\n const speedRef = useRef(speed)\n\n const [isReady, setIsReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const mouseState = useRef<MouseState>({\n x: 0, y: 0,\n clickX: 0, clickY: 0,\n pressed: false,\n })\n\n // Keep refs in sync\n pausedRef.current = paused\n speedRef.current = speed\n\n // Initialize WebGL\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const result = createRenderer(canvas, fragmentShader)\n if (typeof result === 'string') {\n setError(result)\n onError?.(result)\n return\n }\n\n rendererRef.current = result\n setIsReady(true)\n setError(null)\n onLoad?.()\n\n // Render loop\n let lastTimestamp = 0\n\n const loop = (timestamp: number) => {\n const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1000 : 0\n lastTimestamp = timestamp\n\n if (!pausedRef.current && rendererRef.current) {\n updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current)\n render(rendererRef.current)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n\n return () => {\n cancelAnimationFrame(rafRef.current)\n if (rendererRef.current) {\n dispose(rendererRef.current)\n rendererRef.current = null\n }\n setIsReady(false)\n }\n }, [fragmentShader, onError, onLoad])\n\n // Canvas resize\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const dpr = pixelRatio ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1)\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n canvas.width = Math.round(width * dpr)\n canvas.height = Math.round(height * dpr)\n }\n })\n\n observer.observe(canvas)\n return () => observer.disconnect()\n }, [pixelRatio])\n\n // Mouse / touch events\n useEffect(() => {\n if (!mouseEnabled) return\n const canvas = canvasRef.current\n if (!canvas) return\n\n const toPixel = (cx: number, cy: number) => {\n const r = canvas.getBoundingClientRect()\n const dpr = pixelRatio ?? window.devicePixelRatio\n return {\n x: (cx - r.left) * dpr,\n y: (r.height - (cy - r.top)) * dpr, // flip Y\n }\n }\n\n const onMove = (cx: number, cy: number) => {\n if (!mouseState.current.pressed) return\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n }\n\n const onDown = (cx: number, cy: number) => {\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n mouseState.current.clickX = x\n mouseState.current.clickY = y\n mouseState.current.pressed = true\n }\n\n const onUp = () => {\n mouseState.current.pressed = false\n }\n\n const mm = (e: MouseEvent) => onMove(e.clientX, e.clientY)\n const md = (e: MouseEvent) => onDown(e.clientX, e.clientY)\n const mu = () => onUp()\n const tm = (e: TouchEvent) => {\n if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY)\n }\n const ts = (e: TouchEvent) => {\n if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY)\n }\n const te = () => onUp()\n\n window.addEventListener('mousemove', mm)\n canvas.addEventListener('mousedown', md)\n window.addEventListener('mouseup', mu)\n window.addEventListener('touchmove', tm, { passive: true })\n canvas.addEventListener('touchstart', ts, { passive: true })\n window.addEventListener('touchend', te)\n\n return () => {\n window.removeEventListener('mousemove', mm)\n canvas.removeEventListener('mousedown', md)\n window.removeEventListener('mouseup', mu)\n window.removeEventListener('touchmove', tm)\n canvas.removeEventListener('touchstart', ts)\n window.removeEventListener('touchend', te)\n }\n }, [mouseEnabled, pixelRatio])\n\n const pause = useCallback(() => { pausedRef.current = true }, [])\n const resume = useCallback(() => { pausedRef.current = false }, [])\n\n return { canvasRef, isReady, error, pause, resume }\n}\n","import type { ShadertoyProps } from './types'\nimport { useShadertoy } from './useShadertoy'\n\nexport function Shadertoy({\n fragmentShader,\n style,\n className,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n}: ShadertoyProps) {\n const { canvasRef } = useShadertoy({\n fragmentShader,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n })\n\n return (\n <canvas\n ref={canvasRef}\n className={className}\n style={{ width: '100%', height: '100%', display: 'block', ...style }}\n />\n )\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { useRef, useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/useShadertoy.ts
|
|
5
|
+
|
|
6
|
+
// src/renderer.ts
|
|
7
|
+
var QUAD_VERTICES = new Float32Array([
|
|
8
|
+
-1,
|
|
9
|
+
-1,
|
|
10
|
+
1,
|
|
11
|
+
-1,
|
|
12
|
+
-1,
|
|
13
|
+
1,
|
|
14
|
+
-1,
|
|
15
|
+
1,
|
|
16
|
+
1,
|
|
17
|
+
-1,
|
|
18
|
+
1,
|
|
19
|
+
1
|
|
20
|
+
]);
|
|
21
|
+
var VERTEX_SHADER = `
|
|
22
|
+
attribute vec2 position;
|
|
23
|
+
void main() {
|
|
24
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
function wrapFragmentShader(shader) {
|
|
28
|
+
return `precision highp float;
|
|
29
|
+
|
|
30
|
+
uniform vec3 iResolution;
|
|
31
|
+
uniform float iTime;
|
|
32
|
+
uniform float iTimeDelta;
|
|
33
|
+
uniform int iFrame;
|
|
34
|
+
uniform vec4 iMouse;
|
|
35
|
+
uniform vec4 iDate;
|
|
36
|
+
|
|
37
|
+
${shader}
|
|
38
|
+
|
|
39
|
+
void main() {
|
|
40
|
+
mainImage(gl_FragColor, gl_FragCoord.xy);
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
function compileShader(gl, type, source) {
|
|
45
|
+
const shader = gl.createShader(type);
|
|
46
|
+
if (!shader) return "Failed to create shader";
|
|
47
|
+
gl.shaderSource(shader, source);
|
|
48
|
+
gl.compileShader(shader);
|
|
49
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
50
|
+
const log = gl.getShaderInfoLog(shader) || "Unknown compile error";
|
|
51
|
+
gl.deleteShader(shader);
|
|
52
|
+
return log;
|
|
53
|
+
}
|
|
54
|
+
return shader;
|
|
55
|
+
}
|
|
56
|
+
function createRenderer(canvas, fragmentShader) {
|
|
57
|
+
const gl = canvas.getContext("webgl", {
|
|
58
|
+
antialias: false,
|
|
59
|
+
alpha: true,
|
|
60
|
+
premultipliedAlpha: false
|
|
61
|
+
});
|
|
62
|
+
if (!gl) return "WebGL not supported";
|
|
63
|
+
const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
64
|
+
if (typeof vert === "string") return vert;
|
|
65
|
+
const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader));
|
|
66
|
+
if (typeof frag === "string") return frag;
|
|
67
|
+
const program = gl.createProgram();
|
|
68
|
+
if (!program) return "Failed to create program";
|
|
69
|
+
gl.attachShader(program, vert);
|
|
70
|
+
gl.attachShader(program, frag);
|
|
71
|
+
gl.linkProgram(program);
|
|
72
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
73
|
+
const log = gl.getProgramInfoLog(program) || "Unknown link error";
|
|
74
|
+
gl.deleteProgram(program);
|
|
75
|
+
return log;
|
|
76
|
+
}
|
|
77
|
+
gl.deleteShader(vert);
|
|
78
|
+
gl.deleteShader(frag);
|
|
79
|
+
const buffer = gl.createBuffer();
|
|
80
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
81
|
+
gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW);
|
|
82
|
+
const positionLoc = gl.getAttribLocation(program, "position");
|
|
83
|
+
gl.enableVertexAttribArray(positionLoc);
|
|
84
|
+
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
|
|
85
|
+
const locations = {
|
|
86
|
+
iResolution: gl.getUniformLocation(program, "iResolution"),
|
|
87
|
+
iTime: gl.getUniformLocation(program, "iTime"),
|
|
88
|
+
iTimeDelta: gl.getUniformLocation(program, "iTimeDelta"),
|
|
89
|
+
iFrame: gl.getUniformLocation(program, "iFrame"),
|
|
90
|
+
iMouse: gl.getUniformLocation(program, "iMouse"),
|
|
91
|
+
iDate: gl.getUniformLocation(program, "iDate")
|
|
92
|
+
};
|
|
93
|
+
gl.useProgram(program);
|
|
94
|
+
return {
|
|
95
|
+
gl,
|
|
96
|
+
program,
|
|
97
|
+
locations,
|
|
98
|
+
time: 0,
|
|
99
|
+
frame: 0,
|
|
100
|
+
lastTime: 0
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function render(state) {
|
|
104
|
+
const { gl } = state;
|
|
105
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
106
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
107
|
+
}
|
|
108
|
+
function dispose(state) {
|
|
109
|
+
const { gl, program } = state;
|
|
110
|
+
gl.deleteProgram(program);
|
|
111
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/uniforms.ts
|
|
115
|
+
function updateUniforms(state, delta, speed, mouse) {
|
|
116
|
+
const { gl, locations } = state;
|
|
117
|
+
state.time += delta * speed;
|
|
118
|
+
if (locations.iTime) {
|
|
119
|
+
gl.uniform1f(locations.iTime, state.time);
|
|
120
|
+
}
|
|
121
|
+
if (locations.iTimeDelta) {
|
|
122
|
+
gl.uniform1f(locations.iTimeDelta, delta);
|
|
123
|
+
}
|
|
124
|
+
state.frame++;
|
|
125
|
+
if (locations.iFrame) {
|
|
126
|
+
gl.uniform1i(locations.iFrame, state.frame);
|
|
127
|
+
}
|
|
128
|
+
if (locations.iResolution) {
|
|
129
|
+
gl.uniform3f(
|
|
130
|
+
locations.iResolution,
|
|
131
|
+
gl.drawingBufferWidth,
|
|
132
|
+
gl.drawingBufferHeight,
|
|
133
|
+
1
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (locations.iMouse) {
|
|
137
|
+
const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX);
|
|
138
|
+
const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY);
|
|
139
|
+
gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw);
|
|
140
|
+
}
|
|
141
|
+
if (locations.iDate) {
|
|
142
|
+
const now = /* @__PURE__ */ new Date();
|
|
143
|
+
const seconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1e3;
|
|
144
|
+
gl.uniform4f(
|
|
145
|
+
locations.iDate,
|
|
146
|
+
now.getFullYear(),
|
|
147
|
+
now.getMonth(),
|
|
148
|
+
// 0-based, matches Shadertoy
|
|
149
|
+
now.getDate(),
|
|
150
|
+
seconds
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/useShadertoy.ts
|
|
156
|
+
function useShadertoy({
|
|
157
|
+
fragmentShader,
|
|
158
|
+
paused = false,
|
|
159
|
+
speed = 1,
|
|
160
|
+
pixelRatio,
|
|
161
|
+
mouse: mouseEnabled = true,
|
|
162
|
+
onError,
|
|
163
|
+
onLoad
|
|
164
|
+
}) {
|
|
165
|
+
const canvasRef = useRef(null);
|
|
166
|
+
const rendererRef = useRef(null);
|
|
167
|
+
const rafRef = useRef(0);
|
|
168
|
+
const pausedRef = useRef(paused);
|
|
169
|
+
const speedRef = useRef(speed);
|
|
170
|
+
const [isReady, setIsReady] = useState(false);
|
|
171
|
+
const [error, setError] = useState(null);
|
|
172
|
+
const mouseState = useRef({
|
|
173
|
+
x: 0,
|
|
174
|
+
y: 0,
|
|
175
|
+
clickX: 0,
|
|
176
|
+
clickY: 0,
|
|
177
|
+
pressed: false
|
|
178
|
+
});
|
|
179
|
+
pausedRef.current = paused;
|
|
180
|
+
speedRef.current = speed;
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const canvas = canvasRef.current;
|
|
183
|
+
if (!canvas) return;
|
|
184
|
+
const result = createRenderer(canvas, fragmentShader);
|
|
185
|
+
if (typeof result === "string") {
|
|
186
|
+
setError(result);
|
|
187
|
+
onError?.(result);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
rendererRef.current = result;
|
|
191
|
+
setIsReady(true);
|
|
192
|
+
setError(null);
|
|
193
|
+
onLoad?.();
|
|
194
|
+
let lastTimestamp = 0;
|
|
195
|
+
const loop = (timestamp) => {
|
|
196
|
+
const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1e3 : 0;
|
|
197
|
+
lastTimestamp = timestamp;
|
|
198
|
+
if (!pausedRef.current && rendererRef.current) {
|
|
199
|
+
updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current);
|
|
200
|
+
render(rendererRef.current);
|
|
201
|
+
}
|
|
202
|
+
rafRef.current = requestAnimationFrame(loop);
|
|
203
|
+
};
|
|
204
|
+
rafRef.current = requestAnimationFrame(loop);
|
|
205
|
+
return () => {
|
|
206
|
+
cancelAnimationFrame(rafRef.current);
|
|
207
|
+
if (rendererRef.current) {
|
|
208
|
+
dispose(rendererRef.current);
|
|
209
|
+
rendererRef.current = null;
|
|
210
|
+
}
|
|
211
|
+
setIsReady(false);
|
|
212
|
+
};
|
|
213
|
+
}, [fragmentShader, onError, onLoad]);
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
const canvas = canvasRef.current;
|
|
216
|
+
if (!canvas) return;
|
|
217
|
+
const dpr = pixelRatio ?? (typeof window !== "undefined" ? window.devicePixelRatio : 1);
|
|
218
|
+
const observer = new ResizeObserver((entries) => {
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
const { width, height } = entry.contentRect;
|
|
221
|
+
canvas.width = Math.round(width * dpr);
|
|
222
|
+
canvas.height = Math.round(height * dpr);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
observer.observe(canvas);
|
|
226
|
+
return () => observer.disconnect();
|
|
227
|
+
}, [pixelRatio]);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!mouseEnabled) return;
|
|
230
|
+
const canvas = canvasRef.current;
|
|
231
|
+
if (!canvas) return;
|
|
232
|
+
const toPixel = (cx, cy) => {
|
|
233
|
+
const r = canvas.getBoundingClientRect();
|
|
234
|
+
const dpr = pixelRatio ?? window.devicePixelRatio;
|
|
235
|
+
return {
|
|
236
|
+
x: (cx - r.left) * dpr,
|
|
237
|
+
y: (r.height - (cy - r.top)) * dpr
|
|
238
|
+
// flip Y
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
const onMove = (cx, cy) => {
|
|
242
|
+
if (!mouseState.current.pressed) return;
|
|
243
|
+
const { x, y } = toPixel(cx, cy);
|
|
244
|
+
mouseState.current.x = x;
|
|
245
|
+
mouseState.current.y = y;
|
|
246
|
+
};
|
|
247
|
+
const onDown = (cx, cy) => {
|
|
248
|
+
const { x, y } = toPixel(cx, cy);
|
|
249
|
+
mouseState.current.x = x;
|
|
250
|
+
mouseState.current.y = y;
|
|
251
|
+
mouseState.current.clickX = x;
|
|
252
|
+
mouseState.current.clickY = y;
|
|
253
|
+
mouseState.current.pressed = true;
|
|
254
|
+
};
|
|
255
|
+
const onUp = () => {
|
|
256
|
+
mouseState.current.pressed = false;
|
|
257
|
+
};
|
|
258
|
+
const mm = (e) => onMove(e.clientX, e.clientY);
|
|
259
|
+
const md = (e) => onDown(e.clientX, e.clientY);
|
|
260
|
+
const mu = () => onUp();
|
|
261
|
+
const tm = (e) => {
|
|
262
|
+
if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY);
|
|
263
|
+
};
|
|
264
|
+
const ts = (e) => {
|
|
265
|
+
if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY);
|
|
266
|
+
};
|
|
267
|
+
const te = () => onUp();
|
|
268
|
+
window.addEventListener("mousemove", mm);
|
|
269
|
+
canvas.addEventListener("mousedown", md);
|
|
270
|
+
window.addEventListener("mouseup", mu);
|
|
271
|
+
window.addEventListener("touchmove", tm, { passive: true });
|
|
272
|
+
canvas.addEventListener("touchstart", ts, { passive: true });
|
|
273
|
+
window.addEventListener("touchend", te);
|
|
274
|
+
return () => {
|
|
275
|
+
window.removeEventListener("mousemove", mm);
|
|
276
|
+
canvas.removeEventListener("mousedown", md);
|
|
277
|
+
window.removeEventListener("mouseup", mu);
|
|
278
|
+
window.removeEventListener("touchmove", tm);
|
|
279
|
+
canvas.removeEventListener("touchstart", ts);
|
|
280
|
+
window.removeEventListener("touchend", te);
|
|
281
|
+
};
|
|
282
|
+
}, [mouseEnabled, pixelRatio]);
|
|
283
|
+
const pause = useCallback(() => {
|
|
284
|
+
pausedRef.current = true;
|
|
285
|
+
}, []);
|
|
286
|
+
const resume = useCallback(() => {
|
|
287
|
+
pausedRef.current = false;
|
|
288
|
+
}, []);
|
|
289
|
+
return { canvasRef, isReady, error, pause, resume };
|
|
290
|
+
}
|
|
291
|
+
function Shadertoy({
|
|
292
|
+
fragmentShader,
|
|
293
|
+
style,
|
|
294
|
+
className,
|
|
295
|
+
paused,
|
|
296
|
+
speed,
|
|
297
|
+
pixelRatio,
|
|
298
|
+
mouse,
|
|
299
|
+
onError,
|
|
300
|
+
onLoad
|
|
301
|
+
}) {
|
|
302
|
+
const { canvasRef } = useShadertoy({
|
|
303
|
+
fragmentShader,
|
|
304
|
+
paused,
|
|
305
|
+
speed,
|
|
306
|
+
pixelRatio,
|
|
307
|
+
mouse,
|
|
308
|
+
onError,
|
|
309
|
+
onLoad
|
|
310
|
+
});
|
|
311
|
+
return /* @__PURE__ */ jsx(
|
|
312
|
+
"canvas",
|
|
313
|
+
{
|
|
314
|
+
ref: canvasRef,
|
|
315
|
+
className,
|
|
316
|
+
style: { width: "100%", height: "100%", display: "block", ...style }
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export { Shadertoy, useShadertoy };
|
|
322
|
+
//# sourceMappingURL=index.mjs.map
|
|
323
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/renderer.ts","../src/uniforms.ts","../src/useShadertoy.ts","../src/Shadertoy.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { RendererState, UniformLocations } from './types'\n\n// Full-screen quad: two triangles covering clip space\nconst QUAD_VERTICES = new Float32Array([\n -1, -1,\n 1, -1,\n -1, 1,\n -1, 1,\n 1, -1,\n 1, 1,\n])\n\nconst VERTEX_SHADER = `\nattribute vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`\n\n/**\n * Wrap Shadertoy GLSL: prepend uniform declarations + main() bridge.\n */\nfunction wrapFragmentShader(shader: string): string {\n return `precision highp float;\n\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform int iFrame;\nuniform vec4 iMouse;\nuniform vec4 iDate;\n\n${shader}\n\nvoid main() {\n mainImage(gl_FragColor, gl_FragCoord.xy);\n}\n`\n}\n\nfunction compileShader(\n gl: WebGLRenderingContext,\n type: number,\n source: string,\n): WebGLShader | string {\n const shader = gl.createShader(type)\n if (!shader) return 'Failed to create shader'\n\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) || 'Unknown compile error'\n gl.deleteShader(shader)\n return log\n }\n\n return shader\n}\n\n/**\n * Initialize WebGL: compile shaders, link program, setup quad.\n * Returns RendererState on success or error string on failure.\n */\nexport function createRenderer(\n canvas: HTMLCanvasElement,\n fragmentShader: string,\n): RendererState | string {\n const gl = canvas.getContext('webgl', {\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n })\n if (!gl) return 'WebGL not supported'\n\n // Compile vertex shader\n const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER)\n if (typeof vert === 'string') return vert\n\n // Compile fragment shader (wrapped)\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader))\n if (typeof frag === 'string') return frag\n\n // Link program\n const program = gl.createProgram()\n if (!program) return 'Failed to create program'\n\n gl.attachShader(program, vert)\n gl.attachShader(program, frag)\n gl.linkProgram(program)\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program) || 'Unknown link error'\n gl.deleteProgram(program)\n return log\n }\n\n // Clean up individual shaders (attached to program)\n gl.deleteShader(vert)\n gl.deleteShader(frag)\n\n // Setup quad geometry\n const buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW)\n\n const positionLoc = gl.getAttribLocation(program, 'position')\n gl.enableVertexAttribArray(positionLoc)\n gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)\n\n // Get uniform locations\n const locations: UniformLocations = {\n iResolution: gl.getUniformLocation(program, 'iResolution'),\n iTime: gl.getUniformLocation(program, 'iTime'),\n iTimeDelta: gl.getUniformLocation(program, 'iTimeDelta'),\n iFrame: gl.getUniformLocation(program, 'iFrame'),\n iMouse: gl.getUniformLocation(program, 'iMouse'),\n iDate: gl.getUniformLocation(program, 'iDate'),\n }\n\n gl.useProgram(program)\n\n return {\n gl,\n program,\n locations,\n time: 0,\n frame: 0,\n lastTime: 0,\n }\n}\n\n/**\n * Render one frame.\n */\nexport function render(state: RendererState): void {\n const { gl } = state\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)\n gl.drawArrays(gl.TRIANGLES, 0, 6)\n}\n\n/**\n * Clean up WebGL resources.\n */\nexport function dispose(state: RendererState): void {\n const { gl, program } = state\n gl.deleteProgram(program)\n gl.getExtension('WEBGL_lose_context')?.loseContext()\n}\n","import type { MouseState, RendererState } from './types'\n\n/**\n * Update all Shadertoy standard uniforms for one frame.\n */\nexport function updateUniforms(\n state: RendererState,\n delta: number,\n speed: number,\n mouse: MouseState,\n): void {\n const { gl, locations } = state\n\n // iTime\n state.time += delta * speed\n if (locations.iTime) {\n gl.uniform1f(locations.iTime, state.time)\n }\n\n // iTimeDelta\n if (locations.iTimeDelta) {\n gl.uniform1f(locations.iTimeDelta, delta)\n }\n\n // iFrame\n state.frame++\n if (locations.iFrame) {\n gl.uniform1i(locations.iFrame, state.frame)\n }\n\n // iResolution\n if (locations.iResolution) {\n gl.uniform3f(\n locations.iResolution,\n gl.drawingBufferWidth,\n gl.drawingBufferHeight,\n 1.0,\n )\n }\n\n // iMouse — Shadertoy convention:\n // xy: current position (if pressed)\n // zw: click position (positive when pressed, negative when released)\n if (locations.iMouse) {\n const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX)\n const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY)\n gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw)\n }\n\n // iDate — vec4(year, month, day, seconds_since_midnight)\n if (locations.iDate) {\n const now = new Date()\n const seconds =\n now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1000\n gl.uniform4f(\n locations.iDate,\n now.getFullYear(),\n now.getMonth(), // 0-based, matches Shadertoy\n now.getDate(),\n seconds,\n )\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { createRenderer, dispose, render } from './renderer'\nimport type { MouseState, RendererState, UseShadertoyOptions, UseShadertoyReturn } from './types'\nimport { updateUniforms } from './uniforms'\n\nexport function useShadertoy({\n fragmentShader,\n paused = false,\n speed = 1.0,\n pixelRatio,\n mouse: mouseEnabled = true,\n onError,\n onLoad,\n}: UseShadertoyOptions): UseShadertoyReturn {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const rendererRef = useRef<RendererState | null>(null)\n const rafRef = useRef<number>(0)\n const pausedRef = useRef(paused)\n const speedRef = useRef(speed)\n\n const [isReady, setIsReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const mouseState = useRef<MouseState>({\n x: 0, y: 0,\n clickX: 0, clickY: 0,\n pressed: false,\n })\n\n // Keep refs in sync\n pausedRef.current = paused\n speedRef.current = speed\n\n // Initialize WebGL\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const result = createRenderer(canvas, fragmentShader)\n if (typeof result === 'string') {\n setError(result)\n onError?.(result)\n return\n }\n\n rendererRef.current = result\n setIsReady(true)\n setError(null)\n onLoad?.()\n\n // Render loop\n let lastTimestamp = 0\n\n const loop = (timestamp: number) => {\n const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1000 : 0\n lastTimestamp = timestamp\n\n if (!pausedRef.current && rendererRef.current) {\n updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current)\n render(rendererRef.current)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n\n return () => {\n cancelAnimationFrame(rafRef.current)\n if (rendererRef.current) {\n dispose(rendererRef.current)\n rendererRef.current = null\n }\n setIsReady(false)\n }\n }, [fragmentShader, onError, onLoad])\n\n // Canvas resize\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const dpr = pixelRatio ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1)\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n canvas.width = Math.round(width * dpr)\n canvas.height = Math.round(height * dpr)\n }\n })\n\n observer.observe(canvas)\n return () => observer.disconnect()\n }, [pixelRatio])\n\n // Mouse / touch events\n useEffect(() => {\n if (!mouseEnabled) return\n const canvas = canvasRef.current\n if (!canvas) return\n\n const toPixel = (cx: number, cy: number) => {\n const r = canvas.getBoundingClientRect()\n const dpr = pixelRatio ?? window.devicePixelRatio\n return {\n x: (cx - r.left) * dpr,\n y: (r.height - (cy - r.top)) * dpr, // flip Y\n }\n }\n\n const onMove = (cx: number, cy: number) => {\n if (!mouseState.current.pressed) return\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n }\n\n const onDown = (cx: number, cy: number) => {\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n mouseState.current.clickX = x\n mouseState.current.clickY = y\n mouseState.current.pressed = true\n }\n\n const onUp = () => {\n mouseState.current.pressed = false\n }\n\n const mm = (e: MouseEvent) => onMove(e.clientX, e.clientY)\n const md = (e: MouseEvent) => onDown(e.clientX, e.clientY)\n const mu = () => onUp()\n const tm = (e: TouchEvent) => {\n if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY)\n }\n const ts = (e: TouchEvent) => {\n if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY)\n }\n const te = () => onUp()\n\n window.addEventListener('mousemove', mm)\n canvas.addEventListener('mousedown', md)\n window.addEventListener('mouseup', mu)\n window.addEventListener('touchmove', tm, { passive: true })\n canvas.addEventListener('touchstart', ts, { passive: true })\n window.addEventListener('touchend', te)\n\n return () => {\n window.removeEventListener('mousemove', mm)\n canvas.removeEventListener('mousedown', md)\n window.removeEventListener('mouseup', mu)\n window.removeEventListener('touchmove', tm)\n canvas.removeEventListener('touchstart', ts)\n window.removeEventListener('touchend', te)\n }\n }, [mouseEnabled, pixelRatio])\n\n const pause = useCallback(() => { pausedRef.current = true }, [])\n const resume = useCallback(() => { pausedRef.current = false }, [])\n\n return { canvasRef, isReady, error, pause, resume }\n}\n","import type { ShadertoyProps } from './types'\nimport { useShadertoy } from './useShadertoy'\n\nexport function Shadertoy({\n fragmentShader,\n style,\n className,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n}: ShadertoyProps) {\n const { canvasRef } = useShadertoy({\n fragmentShader,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n })\n\n return (\n <canvas\n ref={canvasRef}\n className={className}\n style={{ width: '100%', height: '100%', display: 'block', ...style }}\n />\n )\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-shadertoy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Run Shadertoy GLSL shaders in React. Copy-paste and it works.",
|
|
5
|
+
"author": "Wrennly (https://github.com/wrennly)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/wrennly/react-shadertoy",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/wrennly/react-shadertoy"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"react",
|
|
14
|
+
"shadertoy",
|
|
15
|
+
"glsl",
|
|
16
|
+
"webgl",
|
|
17
|
+
"shader",
|
|
18
|
+
"fragment-shader",
|
|
19
|
+
"creative-coding",
|
|
20
|
+
"generative-art",
|
|
21
|
+
"webgpu",
|
|
22
|
+
"three.js"
|
|
23
|
+
],
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.mjs",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.mjs",
|
|
31
|
+
"require": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"dev": "tsup --watch"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"react": ">=18.0.0",
|
|
44
|
+
"react-dom": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/react": "^19.2.14",
|
|
48
|
+
"react": "^19.2.4",
|
|
49
|
+
"react-dom": "^19.2.4",
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
|
+
}
|
|
53
|
+
}
|