@lovo/matter-react 0.5.0 → 0.6.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/CHANGELOG.md +2 -0
- package/dist/index.cjs +92 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -7
- package/dist/index.d.ts +16 -7
- package/dist/index.js +88 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
ShaderScene: () => ShaderScene,
|
|
26
26
|
useAnimatableUniform: () => useAnimatableUniform,
|
|
27
27
|
useCursor: () => useCursor,
|
|
28
|
+
useDisplayGamut: () => useDisplayGamut,
|
|
28
29
|
usePostProcessPass: () => usePostProcessPass,
|
|
29
30
|
useResize: () => useResize,
|
|
30
31
|
useScroll: () => useScroll,
|
|
@@ -112,11 +113,44 @@ function ShaderMonitor({ anchor = "top-right" }) {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
// src/components/shader-scene/shader-scene.tsx
|
|
115
|
-
var
|
|
116
|
+
var import_react5 = require("react");
|
|
116
117
|
var import_matter = require("@lovo/matter");
|
|
117
118
|
var import_three = require("three");
|
|
118
119
|
var import_tsl = require("three/tsl");
|
|
119
120
|
var import_webgpu = require("three/webgpu");
|
|
121
|
+
|
|
122
|
+
// src/hooks/use-display-gamut/use-display-gamut.ts
|
|
123
|
+
var import_react4 = require("react");
|
|
124
|
+
var P3_QUERY = "(color-gamut: p3)";
|
|
125
|
+
function detectGamut() {
|
|
126
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
127
|
+
return "srgb";
|
|
128
|
+
}
|
|
129
|
+
return window.matchMedia(P3_QUERY).matches ? "p3" : "srgb";
|
|
130
|
+
}
|
|
131
|
+
function useDisplayGamut(preference) {
|
|
132
|
+
const [resolved, setResolved] = (0, import_react4.useState)(
|
|
133
|
+
() => preference === "auto" ? detectGamut() : preference
|
|
134
|
+
);
|
|
135
|
+
(0, import_react4.useEffect)(() => {
|
|
136
|
+
if (preference !== "auto") {
|
|
137
|
+
setResolved(preference);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
141
|
+
setResolved("srgb");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const mediaQuery = window.matchMedia(P3_QUERY);
|
|
145
|
+
const update = () => setResolved(mediaQuery.matches ? "p3" : "srgb");
|
|
146
|
+
update();
|
|
147
|
+
mediaQuery.addEventListener("change", update);
|
|
148
|
+
return () => mediaQuery.removeEventListener("change", update);
|
|
149
|
+
}, [preference]);
|
|
150
|
+
return resolved;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/components/shader-scene/shader-scene.tsx
|
|
120
154
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
121
155
|
var defaultStyle = {
|
|
122
156
|
position: "absolute",
|
|
@@ -126,18 +160,21 @@ var defaultStyle = {
|
|
|
126
160
|
height: "100%"
|
|
127
161
|
};
|
|
128
162
|
function ShaderScene(props) {
|
|
129
|
-
const { children, fallback, className, style, maxDPR } = props;
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const [
|
|
133
|
-
(0,
|
|
163
|
+
const { children, fallback, className, style, maxDPR, gamut = "auto" } = props;
|
|
164
|
+
const resolvedGamut = useDisplayGamut(gamut);
|
|
165
|
+
const canvasRef = (0, import_react5.useRef)(null);
|
|
166
|
+
const [shaderContext, setShaderContext] = (0, import_react5.useState)(null);
|
|
167
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
168
|
+
const [firstFramePainted, setFirstFramePainted] = (0, import_react5.useState)(false);
|
|
169
|
+
(0, import_react5.useEffect)(() => {
|
|
134
170
|
const canvas = canvasRef.current;
|
|
135
171
|
if (!canvas) return;
|
|
136
172
|
let cancelled = false;
|
|
137
173
|
let cleanup = null;
|
|
174
|
+
let firstPaintRaf = null;
|
|
138
175
|
const setup = async () => {
|
|
139
176
|
try {
|
|
140
|
-
const renderer = await (0, import_matter.createRenderer)(canvas, { maxDPR });
|
|
177
|
+
const renderer = await (0, import_matter.createRenderer)(canvas, { maxDPR, gamut: resolvedGamut });
|
|
141
178
|
if (cancelled) {
|
|
142
179
|
renderer.dispose();
|
|
143
180
|
return;
|
|
@@ -148,9 +185,8 @@ function ShaderScene(props) {
|
|
|
148
185
|
const postProcessing = new import_webgpu.PostProcessing(renderer.three);
|
|
149
186
|
const scheduler = new import_matter.FrameScheduler();
|
|
150
187
|
const overlays = /* @__PURE__ */ new Map();
|
|
151
|
-
const
|
|
188
|
+
const basePassNode = (0, import_tsl.vec4)((0, import_tsl.pass)(scene, camera));
|
|
152
189
|
const rebuildOutputNode = () => {
|
|
153
|
-
const basePassNode = basePass;
|
|
154
190
|
postProcessing.outputNode = Array.from(overlays.values()).reduce(
|
|
155
191
|
(currentPipeline, transform) => transform(currentPipeline),
|
|
156
192
|
basePassNode
|
|
@@ -167,7 +203,18 @@ function ShaderScene(props) {
|
|
|
167
203
|
rebuildOutputNode();
|
|
168
204
|
};
|
|
169
205
|
};
|
|
170
|
-
|
|
206
|
+
let firstPaintSignaled = false;
|
|
207
|
+
const renderFrame = () => {
|
|
208
|
+
postProcessing.render();
|
|
209
|
+
if (!firstPaintSignaled && (scene.children.length > 0 || overlays.size > 0)) {
|
|
210
|
+
firstPaintSignaled = true;
|
|
211
|
+
firstPaintRaf = requestAnimationFrame(() => {
|
|
212
|
+
firstPaintRaf = null;
|
|
213
|
+
if (!cancelled) setFirstFramePainted(true);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
scheduler.add(renderFrame);
|
|
171
218
|
scheduler.start();
|
|
172
219
|
const visibility = (0, import_matter.createVisibilityWatcher)();
|
|
173
220
|
const intersection = (0, import_matter.createIntersectionWatcher)(canvas);
|
|
@@ -179,14 +226,14 @@ function ShaderScene(props) {
|
|
|
179
226
|
updatePauseState();
|
|
180
227
|
const unsubVisibility = visibility.subscribe(updatePauseState);
|
|
181
228
|
const unsubIntersection = intersection.subscribe(updatePauseState);
|
|
182
|
-
const
|
|
183
|
-
|
|
229
|
+
const resizeObserver = new ResizeObserver(() => renderer.resize());
|
|
230
|
+
resizeObserver.observe(canvas);
|
|
184
231
|
cleanup = () => {
|
|
185
232
|
unsubVisibility();
|
|
186
233
|
unsubIntersection();
|
|
187
234
|
visibility.dispose();
|
|
188
235
|
intersection.dispose();
|
|
189
|
-
|
|
236
|
+
resizeObserver.disconnect();
|
|
190
237
|
scheduler.dispose();
|
|
191
238
|
renderer.dispose();
|
|
192
239
|
};
|
|
@@ -201,11 +248,16 @@ function ShaderScene(props) {
|
|
|
201
248
|
void setup();
|
|
202
249
|
return () => {
|
|
203
250
|
cancelled = true;
|
|
251
|
+
if (firstPaintRaf !== null) {
|
|
252
|
+
cancelAnimationFrame(firstPaintRaf);
|
|
253
|
+
firstPaintRaf = null;
|
|
254
|
+
}
|
|
204
255
|
cleanup?.();
|
|
205
256
|
cleanup = null;
|
|
206
257
|
setShaderContext(null);
|
|
258
|
+
setFirstFramePainted(false);
|
|
207
259
|
};
|
|
208
|
-
}, [maxDPR]);
|
|
260
|
+
}, [maxDPR, resolvedGamut]);
|
|
209
261
|
let content;
|
|
210
262
|
if (error) {
|
|
211
263
|
content = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
@@ -231,10 +283,11 @@ function ShaderScene(props) {
|
|
|
231
283
|
]
|
|
232
284
|
}
|
|
233
285
|
);
|
|
234
|
-
} else if (shaderContext) {
|
|
235
|
-
content = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShaderContext.Provider, { value: shaderContext, children });
|
|
236
286
|
} else {
|
|
237
|
-
content =
|
|
287
|
+
content = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
288
|
+
shaderContext && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShaderContext.Provider, { value: shaderContext, children }),
|
|
289
|
+
!firstFramePainted && (fallback ?? null)
|
|
290
|
+
] });
|
|
238
291
|
}
|
|
239
292
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: { ...defaultStyle, ...style }, children: [
|
|
240
293
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block" } }),
|
|
@@ -243,18 +296,18 @@ function ShaderScene(props) {
|
|
|
243
296
|
}
|
|
244
297
|
|
|
245
298
|
// src/hooks/use-animatable-uniform/use-animatable-uniform.ts
|
|
246
|
-
var
|
|
299
|
+
var import_react6 = require("react");
|
|
247
300
|
var import_tsl2 = require("three/tsl");
|
|
248
301
|
var isSignal = (value) => {
|
|
249
302
|
if (typeof value !== "object" || value === null) return false;
|
|
250
303
|
return "get" in value && typeof value.get === "function" && "on" in value && typeof value.on === "function";
|
|
251
304
|
};
|
|
252
305
|
function useAnimatableUniform(value) {
|
|
253
|
-
const uniformNode = (0,
|
|
306
|
+
const uniformNode = (0, import_react6.useMemo)(() => {
|
|
254
307
|
const initial = isSignal(value) ? value.get() : value;
|
|
255
308
|
return (0, import_tsl2.uniform)(initial);
|
|
256
309
|
}, []);
|
|
257
|
-
(0,
|
|
310
|
+
(0, import_react6.useEffect)(() => {
|
|
258
311
|
if (isSignal(value)) {
|
|
259
312
|
const unsub = value.on("change", (next) => {
|
|
260
313
|
uniformNode.value = next;
|
|
@@ -268,13 +321,13 @@ function useAnimatableUniform(value) {
|
|
|
268
321
|
}
|
|
269
322
|
|
|
270
323
|
// src/hooks/use-cursor/use-cursor.ts
|
|
271
|
-
var
|
|
324
|
+
var import_react8 = require("react");
|
|
272
325
|
var import_matter2 = require("@lovo/matter");
|
|
273
326
|
|
|
274
327
|
// src/hooks/use-shader-context/use-shader-context.ts
|
|
275
|
-
var
|
|
328
|
+
var import_react7 = require("react");
|
|
276
329
|
function useShaderContext() {
|
|
277
|
-
return (0,
|
|
330
|
+
return (0, import_react7.useContext)(ShaderContext);
|
|
278
331
|
}
|
|
279
332
|
|
|
280
333
|
// src/hooks/use-cursor/use-cursor.ts
|
|
@@ -284,8 +337,8 @@ var STUB_SIGNAL = {
|
|
|
284
337
|
};
|
|
285
338
|
function useCursor(opts = {}) {
|
|
286
339
|
const shaderContext = useShaderContext();
|
|
287
|
-
const [input, setInput] = (0,
|
|
288
|
-
(0,
|
|
340
|
+
const [input, setInput] = (0, import_react8.useState)(null);
|
|
341
|
+
(0, import_react8.useEffect)(() => {
|
|
289
342
|
const canvas = shaderContext?.renderer.three.domElement;
|
|
290
343
|
const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : void 0);
|
|
291
344
|
const newCursorInput = new import_matter2.CursorInput({ ...opts, element: resolvedElement });
|
|
@@ -319,10 +372,10 @@ function useCursor(opts = {}) {
|
|
|
319
372
|
}
|
|
320
373
|
|
|
321
374
|
// src/hooks/use-overlay-pass/use-overlay-pass.ts
|
|
322
|
-
var
|
|
375
|
+
var import_react9 = require("react");
|
|
323
376
|
function usePostProcessPass(transform, deps) {
|
|
324
377
|
const shaderContext = useShaderContext();
|
|
325
|
-
(0,
|
|
378
|
+
(0, import_react9.useEffect)(() => {
|
|
326
379
|
if (!shaderContext) return;
|
|
327
380
|
const unregister = shaderContext.registerOverlay(transform);
|
|
328
381
|
return unregister;
|
|
@@ -330,7 +383,7 @@ function usePostProcessPass(transform, deps) {
|
|
|
330
383
|
}
|
|
331
384
|
|
|
332
385
|
// src/hooks/use-resize/use-resize.ts
|
|
333
|
-
var
|
|
386
|
+
var import_react10 = require("react");
|
|
334
387
|
|
|
335
388
|
// src/internal/create-signal.ts
|
|
336
389
|
function createSignal(getValue) {
|
|
@@ -356,8 +409,8 @@ var STUB_SIGNAL2 = {
|
|
|
356
409
|
};
|
|
357
410
|
function useResize() {
|
|
358
411
|
const shaderContext = useShaderContext();
|
|
359
|
-
const [signal, setSignal] = (0,
|
|
360
|
-
(0,
|
|
412
|
+
const [signal, setSignal] = (0, import_react10.useState)(null);
|
|
413
|
+
(0, import_react10.useEffect)(() => {
|
|
361
414
|
if (!shaderContext) return void 0;
|
|
362
415
|
const canvas = shaderContext.renderer.three.domElement;
|
|
363
416
|
if (!(canvas instanceof HTMLCanvasElement)) return void 0;
|
|
@@ -411,14 +464,14 @@ function useResize() {
|
|
|
411
464
|
}
|
|
412
465
|
|
|
413
466
|
// src/hooks/use-scroll/use-scroll.ts
|
|
414
|
-
var
|
|
467
|
+
var import_react11 = require("react");
|
|
415
468
|
var STUB_SIGNAL3 = {
|
|
416
469
|
get: () => [0, 0],
|
|
417
470
|
on: () => () => void 0
|
|
418
471
|
};
|
|
419
472
|
function useScroll() {
|
|
420
|
-
const [signal, setSignal] = (0,
|
|
421
|
-
(0,
|
|
473
|
+
const [signal, setSignal] = (0, import_react11.useState)(null);
|
|
474
|
+
(0, import_react11.useEffect)(() => {
|
|
422
475
|
if (typeof window === "undefined") return void 0;
|
|
423
476
|
const compute = () => {
|
|
424
477
|
const scrollYPosition = window.scrollY;
|
|
@@ -452,25 +505,25 @@ function useScroll() {
|
|
|
452
505
|
}
|
|
453
506
|
|
|
454
507
|
// src/hooks/use-shader-material/use-shader-material.ts
|
|
455
|
-
var
|
|
508
|
+
var import_react12 = require("react");
|
|
456
509
|
var import_webgpu2 = require("three/webgpu");
|
|
457
510
|
function useShaderMaterial(build) {
|
|
458
|
-
const material = (0,
|
|
511
|
+
const material = (0, import_react12.useMemo)(() => {
|
|
459
512
|
const nodeMaterial = new import_webgpu2.MeshBasicNodeMaterial();
|
|
460
513
|
nodeMaterial.colorNode = build();
|
|
461
514
|
return nodeMaterial;
|
|
462
515
|
}, [build]);
|
|
463
|
-
(0,
|
|
516
|
+
(0, import_react12.useEffect)(() => {
|
|
464
517
|
return () => material.dispose();
|
|
465
518
|
}, [material]);
|
|
466
519
|
return material;
|
|
467
520
|
}
|
|
468
521
|
|
|
469
522
|
// src/hooks/use-static-hint/use-static-hint.ts
|
|
470
|
-
var
|
|
523
|
+
var import_react13 = require("react");
|
|
471
524
|
function useStaticSceneHint(isStatic) {
|
|
472
525
|
const shaderContext = useShaderContext();
|
|
473
|
-
(0,
|
|
526
|
+
(0, import_react13.useEffect)(() => {
|
|
474
527
|
if (!shaderContext) return;
|
|
475
528
|
return shaderContext.scheduler.setIdle(isStatic);
|
|
476
529
|
}, [shaderContext, isStatic]);
|
|
@@ -482,6 +535,7 @@ function useStaticSceneHint(isStatic) {
|
|
|
482
535
|
ShaderScene,
|
|
483
536
|
useAnimatableUniform,
|
|
484
537
|
useCursor,
|
|
538
|
+
useDisplayGamut,
|
|
485
539
|
usePostProcessPass,
|
|
486
540
|
useResize,
|
|
487
541
|
useScroll,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/fallback-boundary/fallback-boundary.tsx","../src/components/shader-monitor/shader-monitor.tsx","../src/context/shader-context.ts","../src/components/shader-scene/shader-scene.tsx","../src/hooks/use-animatable-uniform/use-animatable-uniform.ts","../src/hooks/use-cursor/use-cursor.ts","../src/hooks/use-shader-context/use-shader-context.ts","../src/hooks/use-overlay-pass/use-overlay-pass.ts","../src/hooks/use-resize/use-resize.ts","../src/internal/create-signal.ts","../src/hooks/use-scroll/use-scroll.ts","../src/hooks/use-shader-material/use-shader-material.ts","../src/hooks/use-static-hint/use-static-hint.ts"],"sourcesContent":["// @lovo/matter-react — React binding for Matter.\n\nexport * from './components/index.js';\nexport * from './hooks/index.js';\n\nexport type { ShaderContextValue, PostProcessTransform } from './context/shader-context.js';\n","'use client';\n\nimport { type ReactNode, useEffect, useState } from 'react';\n\nexport interface FallbackBoundaryProps {\n fallback?: ReactNode;\n children: ReactNode;\n}\n\nexport function FallbackBoundary({ fallback, children }: FallbackBoundaryProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n return <>{mounted ? children : (fallback ?? null)}</>;\n}\n","'use client';\n\nimport { type CSSProperties, useContext, useEffect, useRef, useState } from 'react';\n\nimport { ShaderContext } from '../../context/shader-context.js';\n\nexport type ShaderMonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nconst anchorStyle: Record<ShaderMonitorAnchor, CSSProperties> = {\n 'top-left': { top: 8, left: 8 },\n 'top-right': { top: 8, right: 8 },\n 'bottom-left': { bottom: 8, left: 8 },\n 'bottom-right': { bottom: 8, right: 8 },\n};\n\nconst baseStyle: CSSProperties = {\n position: 'absolute',\n zIndex: 10,\n padding: '6px 8px',\n borderRadius: 6,\n background: 'rgba(0, 0, 0, 0.6)',\n color: '#fff',\n font: '11px ui-monospace, monospace',\n lineHeight: 1.4,\n pointerEvents: 'none',\n whiteSpace: 'pre',\n};\n\nexport interface ShaderMonitorProps {\n anchor?: ShaderMonitorAnchor;\n}\n\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const shaderContext = useContext(ShaderContext);\n const [stats, setStats] = useState({ fps: 0, ticks: 0, frames: 0 });\n const ticksRef = useRef(0);\n const fpsAccumRef = useRef({ frames: 0, lastSampleAt: 0, fps: 0 });\n\n useEffect(() => {\n if (!shaderContext) return;\n const schedulerTickHandler = (tick: { now: number }) => {\n ticksRef.current += 1;\n const fpsAccumulator = fpsAccumRef.current;\n\n fpsAccumulator.frames += 1;\n if (fpsAccumulator.lastSampleAt === 0) fpsAccumulator.lastSampleAt = tick.now;\n const deltaTimeSinceLastSample = tick.now - fpsAccumulator.lastSampleAt;\n\n if (deltaTimeSinceLastSample >= 500) {\n fpsAccumulator.fps = Math.round((fpsAccumulator.frames * 1000) / deltaTimeSinceLastSample);\n fpsAccumulator.frames = 0;\n fpsAccumulator.lastSampleAt = tick.now;\n }\n setStats({ fps: fpsAccumulator.fps, ticks: ticksRef.current, frames: fpsAccumulator.frames });\n };\n\n shaderContext.scheduler.add(schedulerTickHandler);\n\n return () => shaderContext.scheduler.remove(schedulerTickHandler);\n }, [shaderContext]);\n\n if (!shaderContext) {\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n no scene\n </div>\n );\n }\n\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n <span data-testid=\"matter-monitor-fps\">fps: {stats.fps || '—'}</span>\n {'\\n'}\n <span data-testid=\"matter-monitor-ticks\">ticks: {stats.ticks}</span>\n </div>\n );\n}\n","import { createContext } from 'react';\n\nimport type { FrameScheduler, GpuRenderer } from '@lovo/matter';\nimport type { Camera, Scene } from 'three';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport type PostProcessTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>;\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer;\n scene: Scene;\n camera: Camera;\n scheduler: FrameScheduler;\n registerOverlay: (transform: PostProcessTransform) => () => void;\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null);\n","'use client';\n\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n FrameScheduler,\n} from '@lovo/matter';\nimport { OrthographicCamera, Scene } from 'three';\nimport { pass } from 'three/tsl';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { PostProcessing } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nimport {\n type PostProcessTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js';\n\nexport interface ShaderSceneProps {\n children?: ReactNode;\n fallback?: ReactNode;\n className?: string;\n style?: CSSProperties;\n maxDPR?: number;\n}\n\nconst defaultStyle: CSSProperties = {\n position: 'absolute',\n inset: 0,\n display: 'block',\n width: '100%',\n height: '100%',\n};\n\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR } = props;\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [shaderContext, setShaderContext] = useState<ShaderContextValue | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n\n if (!canvas) return;\n\n let cancelled = false;\n let cleanup: (() => void) | null = null;\n\n const setup = async () => {\n try {\n const renderer = await createRenderer(canvas, { maxDPR });\n\n if (cancelled) {\n renderer.dispose();\n\n return;\n }\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);\n\n camera.position.z = 1;\n const postProcessing = new PostProcessing(renderer.three);\n const scheduler = new FrameScheduler();\n\n const overlays = new Map<symbol, PostProcessTransform>();\n\n const basePass = pass(scene, camera);\n\n const rebuildOutputNode = () => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n const basePassNode = basePass as unknown as ShaderNodeObject<Node>;\n\n postProcessing.outputNode = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n postProcessing.needsUpdate = true;\n };\n\n rebuildOutputNode(); // initial: just basePass, no overlays\n\n const registerOverlay = (transform: PostProcessTransform): (() => void) => {\n const key = Symbol('overlay');\n\n overlays.set(key, transform);\n rebuildOutputNode();\n\n return () => {\n overlays.delete(key);\n rebuildOutputNode();\n };\n };\n\n scheduler.add(() => postProcessing.render());\n scheduler.start();\n\n const visibility = createVisibilityWatcher();\n const intersection = createIntersectionWatcher(canvas);\n\n const updatePauseState = () => {\n const shouldRun = visibility.isVisible() && intersection.isInView();\n\n if (shouldRun) scheduler.resume();\n else scheduler.pause();\n };\n\n updatePauseState();\n\n const unsubVisibility = visibility.subscribe(updatePauseState);\n const unsubIntersection = intersection.subscribe(updatePauseState);\n\n const onResize = () => renderer.resize();\n\n window.addEventListener('resize', onResize);\n\n cleanup = () => {\n unsubVisibility();\n unsubIntersection();\n visibility.dispose();\n intersection.dispose();\n window.removeEventListener('resize', onResize);\n scheduler.dispose();\n renderer.dispose();\n };\n\n setShaderContext({ renderer, scene, camera, scheduler, registerOverlay });\n } catch (caughtError) {\n if (cancelled) return;\n const normalizedError =\n caughtError instanceof Error ? caughtError : new Error(String(caughtError));\n\n console.error('[ShaderScene] renderer init failed:', normalizedError);\n setError(normalizedError);\n }\n };\n\n void setup();\n\n return () => {\n cancelled = true;\n cleanup?.();\n cleanup = null;\n setShaderContext(null);\n };\n }, [maxDPR]);\n\n let content: ReactNode;\n\n if (error) {\n content = (\n <div\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '1rem',\n color: '#fff',\n background: 'rgba(120, 30, 30, 0.85)',\n font: '0.85rem ui-monospace, monospace',\n whiteSpace: 'pre-wrap',\n textAlign: 'center',\n }}\n >\n ShaderScene init failed:\n {'\\n'}\n {error.message}\n </div>\n );\n } else if (shaderContext) {\n content = <ShaderContext.Provider value={shaderContext}>{children}</ShaderContext.Provider>;\n } else {\n content = fallback ?? null;\n }\n\n return (\n <div className={className} style={{ ...defaultStyle, ...style }}>\n <canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />\n {content}\n </div>\n );\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport { uniform } from 'three/tsl';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport interface AnimatableSignal<T> {\n get(): T;\n on(event: 'change', cb: (value: T) => void): () => void;\n}\n\nexport type AnimatableProp<T> = T | AnimatableSignal<T>;\n\nconst isSignal = <T>(value: AnimatableProp<T>): value is AnimatableSignal<T> => {\n if (typeof value !== 'object' || value === null) return false;\n\n return (\n 'get' in value &&\n typeof value.get === 'function' &&\n 'on' in value &&\n typeof value.on === 'function'\n );\n};\n\nexport function useAnimatableUniform<T>(\n value: AnimatableProp<T>,\n): ShaderNodeObject<Node> & { value: T } {\n const uniformNode = useMemo(() => {\n const initial = isSignal(value) ? value.get() : value;\n\n return uniform(initial);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (isSignal(value)) {\n const unsub = value.on('change', (next) => {\n uniformNode.value = next;\n });\n\n return unsub;\n }\n uniformNode.value = value;\n\n return undefined;\n }, [value, uniformNode]);\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n return uniformNode as unknown as ShaderNodeObject<Node> & { value: T };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { CursorInput, type CursorInputOptions, type Vector2 } from '@lovo/matter';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport interface CursorSignal {\n get(): Vector2;\n on(event: 'change', cb: (value: Vector2) => void): () => void;\n}\n\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n};\n\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const shaderContext = useShaderContext();\n const [input, setInput] = useState<CursorInput | null>(null);\n\n useEffect(() => {\n const canvas = shaderContext?.renderer.three.domElement;\n const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined);\n const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });\n\n setInput(newCursorInput);\n\n let detach: (() => void) | null = null;\n\n if (shaderContext?.scheduler) {\n const schedulerTickHandler = ({ delta }: { delta: number }) => newCursorInput.tick(delta);\n\n shaderContext.scheduler.add(schedulerTickHandler);\n detach = () => shaderContext.scheduler.remove(schedulerTickHandler);\n } else {\n let animationFrameId: number | null = null;\n let lastNow = performance.now();\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000;\n\n lastNow = now;\n newCursorInput.tick(delta);\n animationFrameId = requestAnimationFrame(loop);\n };\n\n animationFrameId = requestAnimationFrame(loop);\n detach = () => {\n if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);\n };\n }\n\n return () => {\n detach();\n newCursorInput.dispose();\n setInput(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext]);\n\n return input ?? STUB_SIGNAL;\n}\n","import { useContext } from 'react';\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js';\n\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext);\n}\n","'use client';\n\nimport { type DependencyList, useEffect } from 'react';\n\nimport type { PostProcessTransform } from '../../context/shader-context.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function usePostProcessPass(transform: PostProcessTransform, deps: DependencyList): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n const unregister = shaderContext.registerOverlay(transform);\n\n return unregister;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext, ...deps]);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport type ResizeValue = readonly [width: number, height: number, dpr: number];\n\nexport interface ResizeSignal {\n get(): ResizeValue;\n on(event: 'change', cb: (value: ResizeValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n};\n\nexport function useResize(): ResizeSignal {\n const shaderContext = useShaderContext();\n const [signal, setSignal] = useState<ResizeSignal | null>(null);\n\n useEffect(() => {\n if (!shaderContext) return undefined;\n\n const canvas = shaderContext.renderer.three.domElement;\n\n if (!(canvas instanceof HTMLCanvasElement)) return undefined;\n\n let value: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ];\n const { signal: newSignal, listeners } = createSignal<ResizeValue>(() => value);\n\n setSignal(newSignal);\n\n const emit = () => {\n const next: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ];\n\n if (next[0] === value[0] && next[1] === value[1] && next[2] === value[2]) return;\n value = next;\n for (const listener of listeners) listener(next);\n };\n\n const observer = new ResizeObserver(emit);\n\n observer.observe(canvas);\n\n let mediaQueryList: MediaQueryList | null = null;\n let mediaQueryListener: (() => void) | null = null;\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return;\n const dpr = window.devicePixelRatio;\n const nextMediaQueryList = window.matchMedia(`(resolution: ${dpr}dppx)`);\n const nextMediaQueryListener = () => {\n emit();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n setupDprWatch();\n };\n\n nextMediaQueryList.addEventListener('change', nextMediaQueryListener);\n mediaQueryList = nextMediaQueryList;\n mediaQueryListener = nextMediaQueryListener;\n };\n\n setupDprWatch();\n\n return () => {\n observer.disconnect();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n mediaQueryList = null;\n mediaQueryListener = null;\n listeners.clear();\n setSignal(null);\n };\n }, [shaderContext]);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nexport function createSignal<T>(getValue: () => T): {\n signal: { get(): T; on(event: string, listener: (v: T) => void): () => void };\n listeners: Set<(v: T) => void>;\n} {\n const listeners = new Set<(v: T) => void>();\n\n return {\n listeners,\n signal: {\n get: getValue,\n on: (_event, listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n },\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\n\nexport type ScrollValue = readonly [scrollY: number, progress: number];\n\nexport interface ScrollSignal {\n get(): ScrollValue;\n on(event: 'change', cb: (value: ScrollValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n};\n\nexport function useScroll(): ScrollSignal {\n const [signal, setSignal] = useState<ScrollSignal | null>(null);\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined;\n\n const compute = (): ScrollValue => {\n const scrollYPosition = window.scrollY;\n\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1);\n const progress = Math.max(0, Math.min(1, scrollYPosition / max));\n\n return [scrollYPosition, progress];\n };\n\n let value: ScrollValue = compute();\n const { signal: newSignal, listeners } = createSignal<ScrollValue>(() => value);\n\n setSignal(newSignal);\n\n let rafPending = false;\n const onScroll = () => {\n if (rafPending) return;\n rafPending = true;\n requestAnimationFrame(() => {\n rafPending = false;\n const next = compute();\n\n if (next[0] === value[0] && next[1] === value[1]) return;\n value = next;\n for (const listener of listeners) listener(next);\n });\n };\n\n window.addEventListener('scroll', onScroll, { passive: true });\n\n return () => {\n window.removeEventListener('scroll', onScroll);\n listeners.clear();\n setSignal(null);\n };\n }, []);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { MeshBasicNodeMaterial } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nexport type ColorTSL = Node | ShaderNodeObject<Node>;\n\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const nodeMaterial = new MeshBasicNodeMaterial();\n\n nodeMaterial.colorNode = build();\n\n return nodeMaterial;\n }, [build]);\n\n useEffect(() => {\n return () => material.dispose();\n }, [material]);\n\n return material;\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function useStaticSceneHint(isStatic: boolean): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n\n return shaderContext.scheduler.setIdle(isStatic);\n }, [shaderContext, isStatic]);\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_tsl","import_react","import_matter","import_react","import_react","import_react","STUB_SIGNAL","import_react","STUB_SIGNAL","import_react","import_webgpu","import_react"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/fallback-boundary/fallback-boundary.tsx","../src/components/shader-monitor/shader-monitor.tsx","../src/context/shader-context.ts","../src/components/shader-scene/shader-scene.tsx","../src/hooks/use-display-gamut/use-display-gamut.ts","../src/hooks/use-animatable-uniform/use-animatable-uniform.ts","../src/hooks/use-cursor/use-cursor.ts","../src/hooks/use-shader-context/use-shader-context.ts","../src/hooks/use-overlay-pass/use-overlay-pass.ts","../src/hooks/use-resize/use-resize.ts","../src/internal/create-signal.ts","../src/hooks/use-scroll/use-scroll.ts","../src/hooks/use-shader-material/use-shader-material.ts","../src/hooks/use-static-hint/use-static-hint.ts"],"sourcesContent":["// @lovo/matter-react — React binding for Matter.\n\nexport * from './components/index.js';\nexport * from './hooks/index.js';\n\nexport type { ShaderContextValue, PostProcessTransform } from './context/shader-context.js';\n","'use client';\n\nimport { type ReactNode, useEffect, useState } from 'react';\n\nexport interface FallbackBoundaryProps {\n fallback?: ReactNode;\n children: ReactNode;\n}\n\nexport function FallbackBoundary({ fallback, children }: FallbackBoundaryProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n return <>{mounted ? children : (fallback ?? null)}</>;\n}\n","'use client';\n\nimport { type CSSProperties, useContext, useEffect, useRef, useState } from 'react';\n\nimport { ShaderContext } from '../../context/shader-context.js';\n\nexport type ShaderMonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nconst anchorStyle: Record<ShaderMonitorAnchor, CSSProperties> = {\n 'top-left': { top: 8, left: 8 },\n 'top-right': { top: 8, right: 8 },\n 'bottom-left': { bottom: 8, left: 8 },\n 'bottom-right': { bottom: 8, right: 8 },\n};\n\nconst baseStyle: CSSProperties = {\n position: 'absolute',\n zIndex: 10,\n padding: '6px 8px',\n borderRadius: 6,\n background: 'rgba(0, 0, 0, 0.6)',\n color: '#fff',\n font: '11px ui-monospace, monospace',\n lineHeight: 1.4,\n pointerEvents: 'none',\n whiteSpace: 'pre',\n};\n\nexport interface ShaderMonitorProps {\n anchor?: ShaderMonitorAnchor;\n}\n\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const shaderContext = useContext(ShaderContext);\n const [stats, setStats] = useState({ fps: 0, ticks: 0, frames: 0 });\n const ticksRef = useRef(0);\n const fpsAccumRef = useRef({ frames: 0, lastSampleAt: 0, fps: 0 });\n\n useEffect(() => {\n if (!shaderContext) return;\n const schedulerTickHandler = (tick: { now: number }) => {\n ticksRef.current += 1;\n const fpsAccumulator = fpsAccumRef.current;\n\n fpsAccumulator.frames += 1;\n if (fpsAccumulator.lastSampleAt === 0) fpsAccumulator.lastSampleAt = tick.now;\n const deltaTimeSinceLastSample = tick.now - fpsAccumulator.lastSampleAt;\n\n if (deltaTimeSinceLastSample >= 500) {\n fpsAccumulator.fps = Math.round((fpsAccumulator.frames * 1000) / deltaTimeSinceLastSample);\n fpsAccumulator.frames = 0;\n fpsAccumulator.lastSampleAt = tick.now;\n }\n setStats({ fps: fpsAccumulator.fps, ticks: ticksRef.current, frames: fpsAccumulator.frames });\n };\n\n shaderContext.scheduler.add(schedulerTickHandler);\n\n return () => shaderContext.scheduler.remove(schedulerTickHandler);\n }, [shaderContext]);\n\n if (!shaderContext) {\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n no scene\n </div>\n );\n }\n\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n <span data-testid=\"matter-monitor-fps\">fps: {stats.fps || '—'}</span>\n {'\\n'}\n <span data-testid=\"matter-monitor-ticks\">ticks: {stats.ticks}</span>\n </div>\n );\n}\n","import { createContext } from 'react';\n\nimport type { FrameScheduler, GpuRenderer } from '@lovo/matter';\nimport type { Camera, Scene } from 'three';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport type PostProcessTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>;\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer;\n scene: Scene;\n camera: Camera;\n scheduler: FrameScheduler;\n registerOverlay: (transform: PostProcessTransform) => () => void;\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null);\n","'use client';\n\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n FrameScheduler,\n} from '@lovo/matter';\nimport { OrthographicCamera, Scene } from 'three';\nimport { pass, vec4 } from 'three/tsl';\nimport { PostProcessing } from 'three/webgpu';\n\nimport {\n type PostProcessTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js';\nimport {\n type GamutPreference,\n useDisplayGamut,\n} from '../../hooks/use-display-gamut/use-display-gamut.js';\n\nexport interface ShaderSceneProps {\n children?: ReactNode;\n fallback?: ReactNode;\n className?: string;\n style?: CSSProperties;\n maxDPR?: number;\n /** Output color gamut. 'auto' (default) uses the widest the display supports. */\n gamut?: GamutPreference;\n}\n\nconst defaultStyle: CSSProperties = {\n position: 'absolute',\n inset: 0,\n display: 'block',\n width: '100%',\n height: '100%',\n};\n\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR, gamut = 'auto' } = props;\n const resolvedGamut = useDisplayGamut(gamut);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [shaderContext, setShaderContext] = useState<ShaderContextValue | null>(null);\n const [error, setError] = useState<Error | null>(null);\n // Stays false until the renderer has actually painted a frame containing the\n // shader. The fallback is held until then so there's no gap between dropping\n // the fallback and the first shader frame (which would otherwise flash the\n // canvas's clear state).\n const [firstFramePainted, setFirstFramePainted] = useState(false);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n\n if (!canvas) return;\n\n let cancelled = false;\n let cleanup: (() => void) | null = null;\n let firstPaintRaf: number | null = null;\n\n const setup = async () => {\n try {\n const renderer = await createRenderer(canvas, { maxDPR, gamut: resolvedGamut });\n\n if (cancelled) {\n renderer.dispose();\n\n return;\n }\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);\n\n camera.position.z = 1;\n const postProcessing = new PostProcessing(renderer.three);\n const scheduler = new FrameScheduler();\n\n const overlays = new Map<symbol, PostProcessTransform>();\n\n const basePassNode = vec4(pass(scene, camera));\n\n const rebuildOutputNode = () => {\n postProcessing.outputNode = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n postProcessing.needsUpdate = true;\n };\n\n rebuildOutputNode(); // initial: just basePass, no overlays\n\n const registerOverlay = (transform: PostProcessTransform): (() => void) => {\n const key = Symbol('overlay');\n\n overlays.set(key, transform);\n rebuildOutputNode();\n\n return () => {\n overlays.delete(key);\n rebuildOutputNode();\n };\n };\n\n // Signal \"first paint\" only once the scene actually has something to\n // draw (a base shader mesh, or at least an overlay pass) — the scheduler\n // renders empty frames before the child shader mounts its mesh, and we\n // don't want to drop the fallback over an empty canvas. Defer the state\n // flip by one rAF so the just-submitted frame composites before the\n // fallback is removed.\n let firstPaintSignaled = false;\n const renderFrame = () => {\n postProcessing.render();\n\n if (!firstPaintSignaled && (scene.children.length > 0 || overlays.size > 0)) {\n firstPaintSignaled = true;\n firstPaintRaf = requestAnimationFrame(() => {\n firstPaintRaf = null;\n if (!cancelled) setFirstFramePainted(true);\n });\n }\n };\n\n scheduler.add(renderFrame);\n scheduler.start();\n\n const visibility = createVisibilityWatcher();\n const intersection = createIntersectionWatcher(canvas);\n\n const updatePauseState = () => {\n const shouldRun = visibility.isVisible() && intersection.isInView();\n\n if (shouldRun) scheduler.resume();\n else scheduler.pause();\n };\n\n updatePauseState();\n\n const unsubVisibility = visibility.subscribe(updatePauseState);\n const unsubIntersection = intersection.subscribe(updatePauseState);\n\n // Track the canvas's actual box size, not just window 'resize'. The\n // canvas commonly gets its real size from layout AFTER renderer init\n // (with no window resize firing), which would otherwise leave the\n // renderer stuck at the default 300x150 and render the scene into an\n // undersized target — compressing every shader's output. ResizeObserver\n // fires once on observe() and on every subsequent box change.\n const resizeObserver = new ResizeObserver(() => renderer.resize());\n\n resizeObserver.observe(canvas);\n\n cleanup = () => {\n unsubVisibility();\n unsubIntersection();\n visibility.dispose();\n intersection.dispose();\n resizeObserver.disconnect();\n scheduler.dispose();\n renderer.dispose();\n };\n\n setShaderContext({ renderer, scene, camera, scheduler, registerOverlay });\n } catch (caughtError) {\n if (cancelled) return;\n const normalizedError =\n caughtError instanceof Error ? caughtError : new Error(String(caughtError));\n\n console.error('[ShaderScene] renderer init failed:', normalizedError);\n setError(normalizedError);\n }\n };\n\n void setup();\n\n return () => {\n cancelled = true;\n if (firstPaintRaf !== null) {\n cancelAnimationFrame(firstPaintRaf);\n firstPaintRaf = null;\n }\n cleanup?.();\n cleanup = null;\n setShaderContext(null);\n // A fresh renderer (e.g. on gamut change) must re-prove its first paint,\n // so show the fallback again until it does.\n setFirstFramePainted(false);\n };\n }, [maxDPR, resolvedGamut]);\n\n let content: ReactNode;\n\n if (error) {\n content = (\n <div\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '1rem',\n color: '#fff',\n background: 'rgba(120, 30, 30, 0.85)',\n font: '0.85rem ui-monospace, monospace',\n whiteSpace: 'pre-wrap',\n textAlign: 'center',\n }}\n >\n ShaderScene init failed:\n {'\\n'}\n {error.message}\n </div>\n );\n } else {\n // Mount the children as soon as the context exists so the shader can build\n // and paint, but keep the fallback overlaid on top until that first frame\n // lands. The children render no visible DOM of their own (they drive the\n // canvas), so the fallback sits above the canvas and is removed only once\n // the shader is actually on screen.\n content = (\n <>\n {shaderContext && (\n <ShaderContext.Provider value={shaderContext}>{children}</ShaderContext.Provider>\n )}\n {!firstFramePainted && (fallback ?? null)}\n </>\n );\n }\n\n return (\n <div className={className} style={{ ...defaultStyle, ...style }}>\n <canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />\n {content}\n </div>\n );\n}\n","import { useEffect, useState } from 'react';\n\nimport type { OutputGamut } from '@lovo/matter';\n\n/** What the consumer asks for: a fixed gamut, or 'auto' to detect the display. */\nexport type GamutPreference = 'auto' | OutputGamut;\n\nconst P3_QUERY = '(color-gamut: p3)';\n\nfunction detectGamut(): OutputGamut {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return 'srgb';\n }\n\n return window.matchMedia(P3_QUERY).matches ? 'p3' : 'srgb';\n}\n\n/**\n * Resolve a gamut preference to a concrete output gamut. Explicit 'srgb'/'p3'\n * pass through untouched; 'auto' queries `(color-gamut: p3)` and re-resolves\n * when the display capability changes (e.g. window dragged to another monitor).\n */\nexport function useDisplayGamut(preference: GamutPreference): OutputGamut {\n const [resolved, setResolved] = useState<OutputGamut>(() =>\n preference === 'auto' ? detectGamut() : preference,\n );\n\n useEffect(() => {\n if (preference !== 'auto') {\n setResolved(preference);\n\n return;\n }\n\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n setResolved('srgb');\n\n return;\n }\n\n const mediaQuery = window.matchMedia(P3_QUERY);\n const update = () => setResolved(mediaQuery.matches ? 'p3' : 'srgb');\n\n update();\n mediaQuery.addEventListener('change', update);\n\n return () => mediaQuery.removeEventListener('change', update);\n }, [preference]);\n\n return resolved;\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport { uniform } from 'three/tsl';\n\nexport interface AnimatableSignal<T> {\n get(): T;\n on(event: 'change', cb: (value: T) => void): () => void;\n}\n\nexport type AnimatableProp<T> = T | AnimatableSignal<T>;\n\nconst isSignal = <T>(value: AnimatableProp<T>): value is AnimatableSignal<T> => {\n if (typeof value !== 'object' || value === null) return false;\n\n return (\n 'get' in value &&\n typeof value.get === 'function' &&\n 'on' in value &&\n typeof value.on === 'function'\n );\n};\n\nexport function useAnimatableUniform<T>(value: AnimatableProp<T>): ReturnType<typeof uniform<T>> {\n const uniformNode = useMemo(() => {\n const initial = isSignal(value) ? value.get() : value;\n\n return uniform(initial);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (isSignal(value)) {\n const unsub = value.on('change', (next) => {\n uniformNode.value = next;\n });\n\n return unsub;\n }\n uniformNode.value = value;\n\n return undefined;\n }, [value, uniformNode]);\n\n return uniformNode;\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { CursorInput, type CursorInputOptions, type Vector2 } from '@lovo/matter';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport interface CursorSignal {\n get(): Vector2;\n on(event: 'change', cb: (value: Vector2) => void): () => void;\n}\n\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n};\n\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const shaderContext = useShaderContext();\n const [input, setInput] = useState<CursorInput | null>(null);\n\n useEffect(() => {\n const canvas = shaderContext?.renderer.three.domElement;\n const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined);\n const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });\n\n setInput(newCursorInput);\n\n let detach: (() => void) | null = null;\n\n if (shaderContext?.scheduler) {\n const schedulerTickHandler = ({ delta }: { delta: number }) => newCursorInput.tick(delta);\n\n shaderContext.scheduler.add(schedulerTickHandler);\n detach = () => shaderContext.scheduler.remove(schedulerTickHandler);\n } else {\n let animationFrameId: number | null = null;\n let lastNow = performance.now();\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000;\n\n lastNow = now;\n newCursorInput.tick(delta);\n animationFrameId = requestAnimationFrame(loop);\n };\n\n animationFrameId = requestAnimationFrame(loop);\n detach = () => {\n if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);\n };\n }\n\n return () => {\n detach();\n newCursorInput.dispose();\n setInput(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext]);\n\n return input ?? STUB_SIGNAL;\n}\n","import { useContext } from 'react';\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js';\n\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext);\n}\n","'use client';\n\nimport { type DependencyList, useEffect } from 'react';\n\nimport type { PostProcessTransform } from '../../context/shader-context.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function usePostProcessPass(transform: PostProcessTransform, deps: DependencyList): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n const unregister = shaderContext.registerOverlay(transform);\n\n return unregister;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext, ...deps]);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport type ResizeValue = readonly [width: number, height: number, dpr: number];\n\nexport interface ResizeSignal {\n get(): ResizeValue;\n on(event: 'change', cb: (value: ResizeValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n};\n\nexport function useResize(): ResizeSignal {\n const shaderContext = useShaderContext();\n const [signal, setSignal] = useState<ResizeSignal | null>(null);\n\n useEffect(() => {\n if (!shaderContext) return undefined;\n\n const canvas = shaderContext.renderer.three.domElement;\n\n if (!(canvas instanceof HTMLCanvasElement)) return undefined;\n\n let value: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ];\n const { signal: newSignal, listeners } = createSignal<ResizeValue>(() => value);\n\n setSignal(newSignal);\n\n const emit = () => {\n const next: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ];\n\n if (next[0] === value[0] && next[1] === value[1] && next[2] === value[2]) return;\n value = next;\n for (const listener of listeners) listener(next);\n };\n\n const observer = new ResizeObserver(emit);\n\n observer.observe(canvas);\n\n let mediaQueryList: MediaQueryList | null = null;\n let mediaQueryListener: (() => void) | null = null;\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return;\n const dpr = window.devicePixelRatio;\n const nextMediaQueryList = window.matchMedia(`(resolution: ${dpr}dppx)`);\n const nextMediaQueryListener = () => {\n emit();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n setupDprWatch();\n };\n\n nextMediaQueryList.addEventListener('change', nextMediaQueryListener);\n mediaQueryList = nextMediaQueryList;\n mediaQueryListener = nextMediaQueryListener;\n };\n\n setupDprWatch();\n\n return () => {\n observer.disconnect();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n mediaQueryList = null;\n mediaQueryListener = null;\n listeners.clear();\n setSignal(null);\n };\n }, [shaderContext]);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nexport function createSignal<T>(getValue: () => T): {\n signal: { get(): T; on(event: string, listener: (v: T) => void): () => void };\n listeners: Set<(v: T) => void>;\n} {\n const listeners = new Set<(v: T) => void>();\n\n return {\n listeners,\n signal: {\n get: getValue,\n on: (_event, listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n },\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\n\nexport type ScrollValue = readonly [scrollY: number, progress: number];\n\nexport interface ScrollSignal {\n get(): ScrollValue;\n on(event: 'change', cb: (value: ScrollValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n};\n\nexport function useScroll(): ScrollSignal {\n const [signal, setSignal] = useState<ScrollSignal | null>(null);\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined;\n\n const compute = (): ScrollValue => {\n const scrollYPosition = window.scrollY;\n\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1);\n const progress = Math.max(0, Math.min(1, scrollYPosition / max));\n\n return [scrollYPosition, progress];\n };\n\n let value: ScrollValue = compute();\n const { signal: newSignal, listeners } = createSignal<ScrollValue>(() => value);\n\n setSignal(newSignal);\n\n let rafPending = false;\n const onScroll = () => {\n if (rafPending) return;\n rafPending = true;\n requestAnimationFrame(() => {\n rafPending = false;\n const next = compute();\n\n if (next[0] === value[0] && next[1] === value[1]) return;\n value = next;\n for (const listener of listeners) listener(next);\n });\n };\n\n window.addEventListener('scroll', onScroll, { passive: true });\n\n return () => {\n window.removeEventListener('scroll', onScroll);\n listeners.clear();\n setSignal(null);\n };\n }, []);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { MeshBasicNodeMaterial } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nexport type ColorTSL = Node | ShaderNodeObject<Node>;\n\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const nodeMaterial = new MeshBasicNodeMaterial();\n\n nodeMaterial.colorNode = build();\n\n return nodeMaterial;\n }, [build]);\n\n useEffect(() => {\n return () => material.dispose();\n }, [material]);\n\n return material;\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function useStaticSceneHint(isStatic: boolean): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n\n return shaderContext.scheduler.setIdle(isStatic);\n }, [shaderContext, isStatic]);\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","import_react","import_react","import_jsx_runtime","import_react","import_tsl","import_react","import_matter","import_react","import_react","import_react","STUB_SIGNAL","import_react","STUB_SIGNAL","import_react","import_webgpu","import_react"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode, CSSProperties, DependencyList } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { Vector2, CursorInputOptions, GpuRenderer, FrameScheduler } from '@lovo/matter';
|
|
3
|
+
import { OutputGamut, Vector2, CursorInputOptions, GpuRenderer, FrameScheduler } from '@lovo/matter';
|
|
4
|
+
import { uniform, ShaderNodeObject } from 'three/tsl';
|
|
6
5
|
import { Scene, Camera } from 'three';
|
|
6
|
+
import { Node, MeshBasicNodeMaterial } from 'three/webgpu';
|
|
7
7
|
|
|
8
8
|
interface FallbackBoundaryProps {
|
|
9
9
|
fallback?: ReactNode;
|
|
@@ -17,12 +17,23 @@ interface ShaderMonitorProps {
|
|
|
17
17
|
}
|
|
18
18
|
declare function ShaderMonitor({ anchor }: ShaderMonitorProps): react_jsx_runtime.JSX.Element;
|
|
19
19
|
|
|
20
|
+
/** What the consumer asks for: a fixed gamut, or 'auto' to detect the display. */
|
|
21
|
+
type GamutPreference = 'auto' | OutputGamut;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a gamut preference to a concrete output gamut. Explicit 'srgb'/'p3'
|
|
24
|
+
* pass through untouched; 'auto' queries `(color-gamut: p3)` and re-resolves
|
|
25
|
+
* when the display capability changes (e.g. window dragged to another monitor).
|
|
26
|
+
*/
|
|
27
|
+
declare function useDisplayGamut(preference: GamutPreference): OutputGamut;
|
|
28
|
+
|
|
20
29
|
interface ShaderSceneProps {
|
|
21
30
|
children?: ReactNode;
|
|
22
31
|
fallback?: ReactNode;
|
|
23
32
|
className?: string;
|
|
24
33
|
style?: CSSProperties;
|
|
25
34
|
maxDPR?: number;
|
|
35
|
+
/** Output color gamut. 'auto' (default) uses the widest the display supports. */
|
|
36
|
+
gamut?: GamutPreference;
|
|
26
37
|
}
|
|
27
38
|
declare function ShaderScene(props: ShaderSceneProps): react_jsx_runtime.JSX.Element;
|
|
28
39
|
|
|
@@ -31,9 +42,7 @@ interface AnimatableSignal<T> {
|
|
|
31
42
|
on(event: 'change', cb: (value: T) => void): () => void;
|
|
32
43
|
}
|
|
33
44
|
type AnimatableProp<T> = T | AnimatableSignal<T>;
|
|
34
|
-
declare function useAnimatableUniform<T>(value: AnimatableProp<T>):
|
|
35
|
-
value: T;
|
|
36
|
-
};
|
|
45
|
+
declare function useAnimatableUniform<T>(value: AnimatableProp<T>): ReturnType<typeof uniform<T>>;
|
|
37
46
|
|
|
38
47
|
interface CursorSignal {
|
|
39
48
|
get(): Vector2;
|
|
@@ -73,4 +82,4 @@ declare function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial
|
|
|
73
82
|
|
|
74
83
|
declare function useStaticSceneHint(isStatic: boolean): void;
|
|
75
84
|
|
|
76
|
-
export { type AnimatableProp, type AnimatableSignal, type CursorSignal, FallbackBoundary, type FallbackBoundaryProps, type PostProcessTransform, type ResizeSignal, type ResizeValue, type ScrollSignal, type ScrollValue, type ShaderContextValue, ShaderMonitor, type ShaderMonitorAnchor, type ShaderMonitorProps, ShaderScene, type ShaderSceneProps, useAnimatableUniform, useCursor, usePostProcessPass, useResize, useScroll, useShaderContext, useShaderMaterial, useStaticSceneHint };
|
|
85
|
+
export { type AnimatableProp, type AnimatableSignal, type CursorSignal, FallbackBoundary, type FallbackBoundaryProps, type GamutPreference, type PostProcessTransform, type ResizeSignal, type ResizeValue, type ScrollSignal, type ScrollValue, type ShaderContextValue, ShaderMonitor, type ShaderMonitorAnchor, type ShaderMonitorProps, ShaderScene, type ShaderSceneProps, useAnimatableUniform, useCursor, useDisplayGamut, usePostProcessPass, useResize, useScroll, useShaderContext, useShaderMaterial, useStaticSceneHint };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode, CSSProperties, DependencyList } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { Vector2, CursorInputOptions, GpuRenderer, FrameScheduler } from '@lovo/matter';
|
|
3
|
+
import { OutputGamut, Vector2, CursorInputOptions, GpuRenderer, FrameScheduler } from '@lovo/matter';
|
|
4
|
+
import { uniform, ShaderNodeObject } from 'three/tsl';
|
|
6
5
|
import { Scene, Camera } from 'three';
|
|
6
|
+
import { Node, MeshBasicNodeMaterial } from 'three/webgpu';
|
|
7
7
|
|
|
8
8
|
interface FallbackBoundaryProps {
|
|
9
9
|
fallback?: ReactNode;
|
|
@@ -17,12 +17,23 @@ interface ShaderMonitorProps {
|
|
|
17
17
|
}
|
|
18
18
|
declare function ShaderMonitor({ anchor }: ShaderMonitorProps): react_jsx_runtime.JSX.Element;
|
|
19
19
|
|
|
20
|
+
/** What the consumer asks for: a fixed gamut, or 'auto' to detect the display. */
|
|
21
|
+
type GamutPreference = 'auto' | OutputGamut;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a gamut preference to a concrete output gamut. Explicit 'srgb'/'p3'
|
|
24
|
+
* pass through untouched; 'auto' queries `(color-gamut: p3)` and re-resolves
|
|
25
|
+
* when the display capability changes (e.g. window dragged to another monitor).
|
|
26
|
+
*/
|
|
27
|
+
declare function useDisplayGamut(preference: GamutPreference): OutputGamut;
|
|
28
|
+
|
|
20
29
|
interface ShaderSceneProps {
|
|
21
30
|
children?: ReactNode;
|
|
22
31
|
fallback?: ReactNode;
|
|
23
32
|
className?: string;
|
|
24
33
|
style?: CSSProperties;
|
|
25
34
|
maxDPR?: number;
|
|
35
|
+
/** Output color gamut. 'auto' (default) uses the widest the display supports. */
|
|
36
|
+
gamut?: GamutPreference;
|
|
26
37
|
}
|
|
27
38
|
declare function ShaderScene(props: ShaderSceneProps): react_jsx_runtime.JSX.Element;
|
|
28
39
|
|
|
@@ -31,9 +42,7 @@ interface AnimatableSignal<T> {
|
|
|
31
42
|
on(event: 'change', cb: (value: T) => void): () => void;
|
|
32
43
|
}
|
|
33
44
|
type AnimatableProp<T> = T | AnimatableSignal<T>;
|
|
34
|
-
declare function useAnimatableUniform<T>(value: AnimatableProp<T>):
|
|
35
|
-
value: T;
|
|
36
|
-
};
|
|
45
|
+
declare function useAnimatableUniform<T>(value: AnimatableProp<T>): ReturnType<typeof uniform<T>>;
|
|
37
46
|
|
|
38
47
|
interface CursorSignal {
|
|
39
48
|
get(): Vector2;
|
|
@@ -73,4 +82,4 @@ declare function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial
|
|
|
73
82
|
|
|
74
83
|
declare function useStaticSceneHint(isStatic: boolean): void;
|
|
75
84
|
|
|
76
|
-
export { type AnimatableProp, type AnimatableSignal, type CursorSignal, FallbackBoundary, type FallbackBoundaryProps, type PostProcessTransform, type ResizeSignal, type ResizeValue, type ScrollSignal, type ScrollValue, type ShaderContextValue, ShaderMonitor, type ShaderMonitorAnchor, type ShaderMonitorProps, ShaderScene, type ShaderSceneProps, useAnimatableUniform, useCursor, usePostProcessPass, useResize, useScroll, useShaderContext, useShaderMaterial, useStaticSceneHint };
|
|
85
|
+
export { type AnimatableProp, type AnimatableSignal, type CursorSignal, FallbackBoundary, type FallbackBoundaryProps, type GamutPreference, type PostProcessTransform, type ResizeSignal, type ResizeValue, type ScrollSignal, type ScrollValue, type ShaderContextValue, ShaderMonitor, type ShaderMonitorAnchor, type ShaderMonitorProps, ShaderScene, type ShaderSceneProps, useAnimatableUniform, useCursor, useDisplayGamut, usePostProcessPass, useResize, useScroll, useShaderContext, useShaderMaterial, useStaticSceneHint };
|
package/dist/index.js
CHANGED
|
@@ -76,7 +76,7 @@ function ShaderMonitor({ anchor = "top-right" }) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// src/components/shader-scene/shader-scene.tsx
|
|
79
|
-
import { useEffect as
|
|
79
|
+
import { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
|
|
80
80
|
import {
|
|
81
81
|
createIntersectionWatcher,
|
|
82
82
|
createRenderer,
|
|
@@ -84,9 +84,42 @@ import {
|
|
|
84
84
|
FrameScheduler
|
|
85
85
|
} from "@lovo/matter";
|
|
86
86
|
import { OrthographicCamera, Scene } from "three";
|
|
87
|
-
import { pass } from "three/tsl";
|
|
87
|
+
import { pass, vec4 } from "three/tsl";
|
|
88
88
|
import { PostProcessing } from "three/webgpu";
|
|
89
|
-
|
|
89
|
+
|
|
90
|
+
// src/hooks/use-display-gamut/use-display-gamut.ts
|
|
91
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
92
|
+
var P3_QUERY = "(color-gamut: p3)";
|
|
93
|
+
function detectGamut() {
|
|
94
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
95
|
+
return "srgb";
|
|
96
|
+
}
|
|
97
|
+
return window.matchMedia(P3_QUERY).matches ? "p3" : "srgb";
|
|
98
|
+
}
|
|
99
|
+
function useDisplayGamut(preference) {
|
|
100
|
+
const [resolved, setResolved] = useState3(
|
|
101
|
+
() => preference === "auto" ? detectGamut() : preference
|
|
102
|
+
);
|
|
103
|
+
useEffect3(() => {
|
|
104
|
+
if (preference !== "auto") {
|
|
105
|
+
setResolved(preference);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
109
|
+
setResolved("srgb");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const mediaQuery = window.matchMedia(P3_QUERY);
|
|
113
|
+
const update = () => setResolved(mediaQuery.matches ? "p3" : "srgb");
|
|
114
|
+
update();
|
|
115
|
+
mediaQuery.addEventListener("change", update);
|
|
116
|
+
return () => mediaQuery.removeEventListener("change", update);
|
|
117
|
+
}, [preference]);
|
|
118
|
+
return resolved;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/components/shader-scene/shader-scene.tsx
|
|
122
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
90
123
|
var defaultStyle = {
|
|
91
124
|
position: "absolute",
|
|
92
125
|
inset: 0,
|
|
@@ -95,18 +128,21 @@ var defaultStyle = {
|
|
|
95
128
|
height: "100%"
|
|
96
129
|
};
|
|
97
130
|
function ShaderScene(props) {
|
|
98
|
-
const { children, fallback, className, style, maxDPR } = props;
|
|
131
|
+
const { children, fallback, className, style, maxDPR, gamut = "auto" } = props;
|
|
132
|
+
const resolvedGamut = useDisplayGamut(gamut);
|
|
99
133
|
const canvasRef = useRef2(null);
|
|
100
|
-
const [shaderContext, setShaderContext] =
|
|
101
|
-
const [error, setError] =
|
|
102
|
-
|
|
134
|
+
const [shaderContext, setShaderContext] = useState4(null);
|
|
135
|
+
const [error, setError] = useState4(null);
|
|
136
|
+
const [firstFramePainted, setFirstFramePainted] = useState4(false);
|
|
137
|
+
useEffect4(() => {
|
|
103
138
|
const canvas = canvasRef.current;
|
|
104
139
|
if (!canvas) return;
|
|
105
140
|
let cancelled = false;
|
|
106
141
|
let cleanup = null;
|
|
142
|
+
let firstPaintRaf = null;
|
|
107
143
|
const setup = async () => {
|
|
108
144
|
try {
|
|
109
|
-
const renderer = await createRenderer(canvas, { maxDPR });
|
|
145
|
+
const renderer = await createRenderer(canvas, { maxDPR, gamut: resolvedGamut });
|
|
110
146
|
if (cancelled) {
|
|
111
147
|
renderer.dispose();
|
|
112
148
|
return;
|
|
@@ -117,9 +153,8 @@ function ShaderScene(props) {
|
|
|
117
153
|
const postProcessing = new PostProcessing(renderer.three);
|
|
118
154
|
const scheduler = new FrameScheduler();
|
|
119
155
|
const overlays = /* @__PURE__ */ new Map();
|
|
120
|
-
const
|
|
156
|
+
const basePassNode = vec4(pass(scene, camera));
|
|
121
157
|
const rebuildOutputNode = () => {
|
|
122
|
-
const basePassNode = basePass;
|
|
123
158
|
postProcessing.outputNode = Array.from(overlays.values()).reduce(
|
|
124
159
|
(currentPipeline, transform) => transform(currentPipeline),
|
|
125
160
|
basePassNode
|
|
@@ -136,7 +171,18 @@ function ShaderScene(props) {
|
|
|
136
171
|
rebuildOutputNode();
|
|
137
172
|
};
|
|
138
173
|
};
|
|
139
|
-
|
|
174
|
+
let firstPaintSignaled = false;
|
|
175
|
+
const renderFrame = () => {
|
|
176
|
+
postProcessing.render();
|
|
177
|
+
if (!firstPaintSignaled && (scene.children.length > 0 || overlays.size > 0)) {
|
|
178
|
+
firstPaintSignaled = true;
|
|
179
|
+
firstPaintRaf = requestAnimationFrame(() => {
|
|
180
|
+
firstPaintRaf = null;
|
|
181
|
+
if (!cancelled) setFirstFramePainted(true);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
scheduler.add(renderFrame);
|
|
140
186
|
scheduler.start();
|
|
141
187
|
const visibility = createVisibilityWatcher();
|
|
142
188
|
const intersection = createIntersectionWatcher(canvas);
|
|
@@ -148,14 +194,14 @@ function ShaderScene(props) {
|
|
|
148
194
|
updatePauseState();
|
|
149
195
|
const unsubVisibility = visibility.subscribe(updatePauseState);
|
|
150
196
|
const unsubIntersection = intersection.subscribe(updatePauseState);
|
|
151
|
-
const
|
|
152
|
-
|
|
197
|
+
const resizeObserver = new ResizeObserver(() => renderer.resize());
|
|
198
|
+
resizeObserver.observe(canvas);
|
|
153
199
|
cleanup = () => {
|
|
154
200
|
unsubVisibility();
|
|
155
201
|
unsubIntersection();
|
|
156
202
|
visibility.dispose();
|
|
157
203
|
intersection.dispose();
|
|
158
|
-
|
|
204
|
+
resizeObserver.disconnect();
|
|
159
205
|
scheduler.dispose();
|
|
160
206
|
renderer.dispose();
|
|
161
207
|
};
|
|
@@ -170,11 +216,16 @@ function ShaderScene(props) {
|
|
|
170
216
|
void setup();
|
|
171
217
|
return () => {
|
|
172
218
|
cancelled = true;
|
|
219
|
+
if (firstPaintRaf !== null) {
|
|
220
|
+
cancelAnimationFrame(firstPaintRaf);
|
|
221
|
+
firstPaintRaf = null;
|
|
222
|
+
}
|
|
173
223
|
cleanup?.();
|
|
174
224
|
cleanup = null;
|
|
175
225
|
setShaderContext(null);
|
|
226
|
+
setFirstFramePainted(false);
|
|
176
227
|
};
|
|
177
|
-
}, [maxDPR]);
|
|
228
|
+
}, [maxDPR, resolvedGamut]);
|
|
178
229
|
let content;
|
|
179
230
|
if (error) {
|
|
180
231
|
content = /* @__PURE__ */ jsxs2(
|
|
@@ -200,10 +251,11 @@ function ShaderScene(props) {
|
|
|
200
251
|
]
|
|
201
252
|
}
|
|
202
253
|
);
|
|
203
|
-
} else if (shaderContext) {
|
|
204
|
-
content = /* @__PURE__ */ jsx3(ShaderContext.Provider, { value: shaderContext, children });
|
|
205
254
|
} else {
|
|
206
|
-
content =
|
|
255
|
+
content = /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
256
|
+
shaderContext && /* @__PURE__ */ jsx3(ShaderContext.Provider, { value: shaderContext, children }),
|
|
257
|
+
!firstFramePainted && (fallback ?? null)
|
|
258
|
+
] });
|
|
207
259
|
}
|
|
208
260
|
return /* @__PURE__ */ jsxs2("div", { className, style: { ...defaultStyle, ...style }, children: [
|
|
209
261
|
/* @__PURE__ */ jsx3("canvas", { ref: canvasRef, style: { width: "100%", height: "100%", display: "block" } }),
|
|
@@ -212,7 +264,7 @@ function ShaderScene(props) {
|
|
|
212
264
|
}
|
|
213
265
|
|
|
214
266
|
// src/hooks/use-animatable-uniform/use-animatable-uniform.ts
|
|
215
|
-
import { useEffect as
|
|
267
|
+
import { useEffect as useEffect5, useMemo } from "react";
|
|
216
268
|
import { uniform } from "three/tsl";
|
|
217
269
|
var isSignal = (value) => {
|
|
218
270
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -223,7 +275,7 @@ function useAnimatableUniform(value) {
|
|
|
223
275
|
const initial = isSignal(value) ? value.get() : value;
|
|
224
276
|
return uniform(initial);
|
|
225
277
|
}, []);
|
|
226
|
-
|
|
278
|
+
useEffect5(() => {
|
|
227
279
|
if (isSignal(value)) {
|
|
228
280
|
const unsub = value.on("change", (next) => {
|
|
229
281
|
uniformNode.value = next;
|
|
@@ -237,7 +289,7 @@ function useAnimatableUniform(value) {
|
|
|
237
289
|
}
|
|
238
290
|
|
|
239
291
|
// src/hooks/use-cursor/use-cursor.ts
|
|
240
|
-
import { useEffect as
|
|
292
|
+
import { useEffect as useEffect6, useState as useState5 } from "react";
|
|
241
293
|
import { CursorInput } from "@lovo/matter";
|
|
242
294
|
|
|
243
295
|
// src/hooks/use-shader-context/use-shader-context.ts
|
|
@@ -253,8 +305,8 @@ var STUB_SIGNAL = {
|
|
|
253
305
|
};
|
|
254
306
|
function useCursor(opts = {}) {
|
|
255
307
|
const shaderContext = useShaderContext();
|
|
256
|
-
const [input, setInput] =
|
|
257
|
-
|
|
308
|
+
const [input, setInput] = useState5(null);
|
|
309
|
+
useEffect6(() => {
|
|
258
310
|
const canvas = shaderContext?.renderer.three.domElement;
|
|
259
311
|
const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : void 0);
|
|
260
312
|
const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });
|
|
@@ -288,10 +340,10 @@ function useCursor(opts = {}) {
|
|
|
288
340
|
}
|
|
289
341
|
|
|
290
342
|
// src/hooks/use-overlay-pass/use-overlay-pass.ts
|
|
291
|
-
import { useEffect as
|
|
343
|
+
import { useEffect as useEffect7 } from "react";
|
|
292
344
|
function usePostProcessPass(transform, deps) {
|
|
293
345
|
const shaderContext = useShaderContext();
|
|
294
|
-
|
|
346
|
+
useEffect7(() => {
|
|
295
347
|
if (!shaderContext) return;
|
|
296
348
|
const unregister = shaderContext.registerOverlay(transform);
|
|
297
349
|
return unregister;
|
|
@@ -299,7 +351,7 @@ function usePostProcessPass(transform, deps) {
|
|
|
299
351
|
}
|
|
300
352
|
|
|
301
353
|
// src/hooks/use-resize/use-resize.ts
|
|
302
|
-
import { useEffect as
|
|
354
|
+
import { useEffect as useEffect8, useState as useState6 } from "react";
|
|
303
355
|
|
|
304
356
|
// src/internal/create-signal.ts
|
|
305
357
|
function createSignal(getValue) {
|
|
@@ -325,8 +377,8 @@ var STUB_SIGNAL2 = {
|
|
|
325
377
|
};
|
|
326
378
|
function useResize() {
|
|
327
379
|
const shaderContext = useShaderContext();
|
|
328
|
-
const [signal, setSignal] =
|
|
329
|
-
|
|
380
|
+
const [signal, setSignal] = useState6(null);
|
|
381
|
+
useEffect8(() => {
|
|
330
382
|
if (!shaderContext) return void 0;
|
|
331
383
|
const canvas = shaderContext.renderer.three.domElement;
|
|
332
384
|
if (!(canvas instanceof HTMLCanvasElement)) return void 0;
|
|
@@ -380,14 +432,14 @@ function useResize() {
|
|
|
380
432
|
}
|
|
381
433
|
|
|
382
434
|
// src/hooks/use-scroll/use-scroll.ts
|
|
383
|
-
import { useEffect as
|
|
435
|
+
import { useEffect as useEffect9, useState as useState7 } from "react";
|
|
384
436
|
var STUB_SIGNAL3 = {
|
|
385
437
|
get: () => [0, 0],
|
|
386
438
|
on: () => () => void 0
|
|
387
439
|
};
|
|
388
440
|
function useScroll() {
|
|
389
|
-
const [signal, setSignal] =
|
|
390
|
-
|
|
441
|
+
const [signal, setSignal] = useState7(null);
|
|
442
|
+
useEffect9(() => {
|
|
391
443
|
if (typeof window === "undefined") return void 0;
|
|
392
444
|
const compute = () => {
|
|
393
445
|
const scrollYPosition = window.scrollY;
|
|
@@ -421,7 +473,7 @@ function useScroll() {
|
|
|
421
473
|
}
|
|
422
474
|
|
|
423
475
|
// src/hooks/use-shader-material/use-shader-material.ts
|
|
424
|
-
import { useEffect as
|
|
476
|
+
import { useEffect as useEffect10, useMemo as useMemo2 } from "react";
|
|
425
477
|
import { MeshBasicNodeMaterial } from "three/webgpu";
|
|
426
478
|
function useShaderMaterial(build) {
|
|
427
479
|
const material = useMemo2(() => {
|
|
@@ -429,17 +481,17 @@ function useShaderMaterial(build) {
|
|
|
429
481
|
nodeMaterial.colorNode = build();
|
|
430
482
|
return nodeMaterial;
|
|
431
483
|
}, [build]);
|
|
432
|
-
|
|
484
|
+
useEffect10(() => {
|
|
433
485
|
return () => material.dispose();
|
|
434
486
|
}, [material]);
|
|
435
487
|
return material;
|
|
436
488
|
}
|
|
437
489
|
|
|
438
490
|
// src/hooks/use-static-hint/use-static-hint.ts
|
|
439
|
-
import { useEffect as
|
|
491
|
+
import { useEffect as useEffect11 } from "react";
|
|
440
492
|
function useStaticSceneHint(isStatic) {
|
|
441
493
|
const shaderContext = useShaderContext();
|
|
442
|
-
|
|
494
|
+
useEffect11(() => {
|
|
443
495
|
if (!shaderContext) return;
|
|
444
496
|
return shaderContext.scheduler.setIdle(isStatic);
|
|
445
497
|
}, [shaderContext, isStatic]);
|
|
@@ -450,6 +502,7 @@ export {
|
|
|
450
502
|
ShaderScene,
|
|
451
503
|
useAnimatableUniform,
|
|
452
504
|
useCursor,
|
|
505
|
+
useDisplayGamut,
|
|
453
506
|
usePostProcessPass,
|
|
454
507
|
useResize,
|
|
455
508
|
useScroll,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/fallback-boundary/fallback-boundary.tsx","../src/components/shader-monitor/shader-monitor.tsx","../src/context/shader-context.ts","../src/components/shader-scene/shader-scene.tsx","../src/hooks/use-animatable-uniform/use-animatable-uniform.ts","../src/hooks/use-cursor/use-cursor.ts","../src/hooks/use-shader-context/use-shader-context.ts","../src/hooks/use-overlay-pass/use-overlay-pass.ts","../src/hooks/use-resize/use-resize.ts","../src/internal/create-signal.ts","../src/hooks/use-scroll/use-scroll.ts","../src/hooks/use-shader-material/use-shader-material.ts","../src/hooks/use-static-hint/use-static-hint.ts"],"sourcesContent":["'use client';\n\nimport { type ReactNode, useEffect, useState } from 'react';\n\nexport interface FallbackBoundaryProps {\n fallback?: ReactNode;\n children: ReactNode;\n}\n\nexport function FallbackBoundary({ fallback, children }: FallbackBoundaryProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n return <>{mounted ? children : (fallback ?? null)}</>;\n}\n","'use client';\n\nimport { type CSSProperties, useContext, useEffect, useRef, useState } from 'react';\n\nimport { ShaderContext } from '../../context/shader-context.js';\n\nexport type ShaderMonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nconst anchorStyle: Record<ShaderMonitorAnchor, CSSProperties> = {\n 'top-left': { top: 8, left: 8 },\n 'top-right': { top: 8, right: 8 },\n 'bottom-left': { bottom: 8, left: 8 },\n 'bottom-right': { bottom: 8, right: 8 },\n};\n\nconst baseStyle: CSSProperties = {\n position: 'absolute',\n zIndex: 10,\n padding: '6px 8px',\n borderRadius: 6,\n background: 'rgba(0, 0, 0, 0.6)',\n color: '#fff',\n font: '11px ui-monospace, monospace',\n lineHeight: 1.4,\n pointerEvents: 'none',\n whiteSpace: 'pre',\n};\n\nexport interface ShaderMonitorProps {\n anchor?: ShaderMonitorAnchor;\n}\n\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const shaderContext = useContext(ShaderContext);\n const [stats, setStats] = useState({ fps: 0, ticks: 0, frames: 0 });\n const ticksRef = useRef(0);\n const fpsAccumRef = useRef({ frames: 0, lastSampleAt: 0, fps: 0 });\n\n useEffect(() => {\n if (!shaderContext) return;\n const schedulerTickHandler = (tick: { now: number }) => {\n ticksRef.current += 1;\n const fpsAccumulator = fpsAccumRef.current;\n\n fpsAccumulator.frames += 1;\n if (fpsAccumulator.lastSampleAt === 0) fpsAccumulator.lastSampleAt = tick.now;\n const deltaTimeSinceLastSample = tick.now - fpsAccumulator.lastSampleAt;\n\n if (deltaTimeSinceLastSample >= 500) {\n fpsAccumulator.fps = Math.round((fpsAccumulator.frames * 1000) / deltaTimeSinceLastSample);\n fpsAccumulator.frames = 0;\n fpsAccumulator.lastSampleAt = tick.now;\n }\n setStats({ fps: fpsAccumulator.fps, ticks: ticksRef.current, frames: fpsAccumulator.frames });\n };\n\n shaderContext.scheduler.add(schedulerTickHandler);\n\n return () => shaderContext.scheduler.remove(schedulerTickHandler);\n }, [shaderContext]);\n\n if (!shaderContext) {\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n no scene\n </div>\n );\n }\n\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n <span data-testid=\"matter-monitor-fps\">fps: {stats.fps || '—'}</span>\n {'\\n'}\n <span data-testid=\"matter-monitor-ticks\">ticks: {stats.ticks}</span>\n </div>\n );\n}\n","import { createContext } from 'react';\n\nimport type { FrameScheduler, GpuRenderer } from '@lovo/matter';\nimport type { Camera, Scene } from 'three';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport type PostProcessTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>;\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer;\n scene: Scene;\n camera: Camera;\n scheduler: FrameScheduler;\n registerOverlay: (transform: PostProcessTransform) => () => void;\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null);\n","'use client';\n\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n FrameScheduler,\n} from '@lovo/matter';\nimport { OrthographicCamera, Scene } from 'three';\nimport { pass } from 'three/tsl';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { PostProcessing } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nimport {\n type PostProcessTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js';\n\nexport interface ShaderSceneProps {\n children?: ReactNode;\n fallback?: ReactNode;\n className?: string;\n style?: CSSProperties;\n maxDPR?: number;\n}\n\nconst defaultStyle: CSSProperties = {\n position: 'absolute',\n inset: 0,\n display: 'block',\n width: '100%',\n height: '100%',\n};\n\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR } = props;\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [shaderContext, setShaderContext] = useState<ShaderContextValue | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n\n if (!canvas) return;\n\n let cancelled = false;\n let cleanup: (() => void) | null = null;\n\n const setup = async () => {\n try {\n const renderer = await createRenderer(canvas, { maxDPR });\n\n if (cancelled) {\n renderer.dispose();\n\n return;\n }\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);\n\n camera.position.z = 1;\n const postProcessing = new PostProcessing(renderer.three);\n const scheduler = new FrameScheduler();\n\n const overlays = new Map<symbol, PostProcessTransform>();\n\n const basePass = pass(scene, camera);\n\n const rebuildOutputNode = () => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n const basePassNode = basePass as unknown as ShaderNodeObject<Node>;\n\n postProcessing.outputNode = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n postProcessing.needsUpdate = true;\n };\n\n rebuildOutputNode(); // initial: just basePass, no overlays\n\n const registerOverlay = (transform: PostProcessTransform): (() => void) => {\n const key = Symbol('overlay');\n\n overlays.set(key, transform);\n rebuildOutputNode();\n\n return () => {\n overlays.delete(key);\n rebuildOutputNode();\n };\n };\n\n scheduler.add(() => postProcessing.render());\n scheduler.start();\n\n const visibility = createVisibilityWatcher();\n const intersection = createIntersectionWatcher(canvas);\n\n const updatePauseState = () => {\n const shouldRun = visibility.isVisible() && intersection.isInView();\n\n if (shouldRun) scheduler.resume();\n else scheduler.pause();\n };\n\n updatePauseState();\n\n const unsubVisibility = visibility.subscribe(updatePauseState);\n const unsubIntersection = intersection.subscribe(updatePauseState);\n\n const onResize = () => renderer.resize();\n\n window.addEventListener('resize', onResize);\n\n cleanup = () => {\n unsubVisibility();\n unsubIntersection();\n visibility.dispose();\n intersection.dispose();\n window.removeEventListener('resize', onResize);\n scheduler.dispose();\n renderer.dispose();\n };\n\n setShaderContext({ renderer, scene, camera, scheduler, registerOverlay });\n } catch (caughtError) {\n if (cancelled) return;\n const normalizedError =\n caughtError instanceof Error ? caughtError : new Error(String(caughtError));\n\n console.error('[ShaderScene] renderer init failed:', normalizedError);\n setError(normalizedError);\n }\n };\n\n void setup();\n\n return () => {\n cancelled = true;\n cleanup?.();\n cleanup = null;\n setShaderContext(null);\n };\n }, [maxDPR]);\n\n let content: ReactNode;\n\n if (error) {\n content = (\n <div\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '1rem',\n color: '#fff',\n background: 'rgba(120, 30, 30, 0.85)',\n font: '0.85rem ui-monospace, monospace',\n whiteSpace: 'pre-wrap',\n textAlign: 'center',\n }}\n >\n ShaderScene init failed:\n {'\\n'}\n {error.message}\n </div>\n );\n } else if (shaderContext) {\n content = <ShaderContext.Provider value={shaderContext}>{children}</ShaderContext.Provider>;\n } else {\n content = fallback ?? null;\n }\n\n return (\n <div className={className} style={{ ...defaultStyle, ...style }}>\n <canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />\n {content}\n </div>\n );\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport { uniform } from 'three/tsl';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport interface AnimatableSignal<T> {\n get(): T;\n on(event: 'change', cb: (value: T) => void): () => void;\n}\n\nexport type AnimatableProp<T> = T | AnimatableSignal<T>;\n\nconst isSignal = <T>(value: AnimatableProp<T>): value is AnimatableSignal<T> => {\n if (typeof value !== 'object' || value === null) return false;\n\n return (\n 'get' in value &&\n typeof value.get === 'function' &&\n 'on' in value &&\n typeof value.on === 'function'\n );\n};\n\nexport function useAnimatableUniform<T>(\n value: AnimatableProp<T>,\n): ShaderNodeObject<Node> & { value: T } {\n const uniformNode = useMemo(() => {\n const initial = isSignal(value) ? value.get() : value;\n\n return uniform(initial);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (isSignal(value)) {\n const unsub = value.on('change', (next) => {\n uniformNode.value = next;\n });\n\n return unsub;\n }\n uniformNode.value = value;\n\n return undefined;\n }, [value, uniformNode]);\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n return uniformNode as unknown as ShaderNodeObject<Node> & { value: T };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { CursorInput, type CursorInputOptions, type Vector2 } from '@lovo/matter';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport interface CursorSignal {\n get(): Vector2;\n on(event: 'change', cb: (value: Vector2) => void): () => void;\n}\n\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n};\n\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const shaderContext = useShaderContext();\n const [input, setInput] = useState<CursorInput | null>(null);\n\n useEffect(() => {\n const canvas = shaderContext?.renderer.three.domElement;\n const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined);\n const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });\n\n setInput(newCursorInput);\n\n let detach: (() => void) | null = null;\n\n if (shaderContext?.scheduler) {\n const schedulerTickHandler = ({ delta }: { delta: number }) => newCursorInput.tick(delta);\n\n shaderContext.scheduler.add(schedulerTickHandler);\n detach = () => shaderContext.scheduler.remove(schedulerTickHandler);\n } else {\n let animationFrameId: number | null = null;\n let lastNow = performance.now();\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000;\n\n lastNow = now;\n newCursorInput.tick(delta);\n animationFrameId = requestAnimationFrame(loop);\n };\n\n animationFrameId = requestAnimationFrame(loop);\n detach = () => {\n if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);\n };\n }\n\n return () => {\n detach();\n newCursorInput.dispose();\n setInput(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext]);\n\n return input ?? STUB_SIGNAL;\n}\n","import { useContext } from 'react';\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js';\n\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext);\n}\n","'use client';\n\nimport { type DependencyList, useEffect } from 'react';\n\nimport type { PostProcessTransform } from '../../context/shader-context.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function usePostProcessPass(transform: PostProcessTransform, deps: DependencyList): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n const unregister = shaderContext.registerOverlay(transform);\n\n return unregister;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext, ...deps]);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport type ResizeValue = readonly [width: number, height: number, dpr: number];\n\nexport interface ResizeSignal {\n get(): ResizeValue;\n on(event: 'change', cb: (value: ResizeValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n};\n\nexport function useResize(): ResizeSignal {\n const shaderContext = useShaderContext();\n const [signal, setSignal] = useState<ResizeSignal | null>(null);\n\n useEffect(() => {\n if (!shaderContext) return undefined;\n\n const canvas = shaderContext.renderer.three.domElement;\n\n if (!(canvas instanceof HTMLCanvasElement)) return undefined;\n\n let value: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ];\n const { signal: newSignal, listeners } = createSignal<ResizeValue>(() => value);\n\n setSignal(newSignal);\n\n const emit = () => {\n const next: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ];\n\n if (next[0] === value[0] && next[1] === value[1] && next[2] === value[2]) return;\n value = next;\n for (const listener of listeners) listener(next);\n };\n\n const observer = new ResizeObserver(emit);\n\n observer.observe(canvas);\n\n let mediaQueryList: MediaQueryList | null = null;\n let mediaQueryListener: (() => void) | null = null;\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return;\n const dpr = window.devicePixelRatio;\n const nextMediaQueryList = window.matchMedia(`(resolution: ${dpr}dppx)`);\n const nextMediaQueryListener = () => {\n emit();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n setupDprWatch();\n };\n\n nextMediaQueryList.addEventListener('change', nextMediaQueryListener);\n mediaQueryList = nextMediaQueryList;\n mediaQueryListener = nextMediaQueryListener;\n };\n\n setupDprWatch();\n\n return () => {\n observer.disconnect();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n mediaQueryList = null;\n mediaQueryListener = null;\n listeners.clear();\n setSignal(null);\n };\n }, [shaderContext]);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nexport function createSignal<T>(getValue: () => T): {\n signal: { get(): T; on(event: string, listener: (v: T) => void): () => void };\n listeners: Set<(v: T) => void>;\n} {\n const listeners = new Set<(v: T) => void>();\n\n return {\n listeners,\n signal: {\n get: getValue,\n on: (_event, listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n },\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\n\nexport type ScrollValue = readonly [scrollY: number, progress: number];\n\nexport interface ScrollSignal {\n get(): ScrollValue;\n on(event: 'change', cb: (value: ScrollValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n};\n\nexport function useScroll(): ScrollSignal {\n const [signal, setSignal] = useState<ScrollSignal | null>(null);\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined;\n\n const compute = (): ScrollValue => {\n const scrollYPosition = window.scrollY;\n\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1);\n const progress = Math.max(0, Math.min(1, scrollYPosition / max));\n\n return [scrollYPosition, progress];\n };\n\n let value: ScrollValue = compute();\n const { signal: newSignal, listeners } = createSignal<ScrollValue>(() => value);\n\n setSignal(newSignal);\n\n let rafPending = false;\n const onScroll = () => {\n if (rafPending) return;\n rafPending = true;\n requestAnimationFrame(() => {\n rafPending = false;\n const next = compute();\n\n if (next[0] === value[0] && next[1] === value[1]) return;\n value = next;\n for (const listener of listeners) listener(next);\n });\n };\n\n window.addEventListener('scroll', onScroll, { passive: true });\n\n return () => {\n window.removeEventListener('scroll', onScroll);\n listeners.clear();\n setSignal(null);\n };\n }, []);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { MeshBasicNodeMaterial } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nexport type ColorTSL = Node | ShaderNodeObject<Node>;\n\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const nodeMaterial = new MeshBasicNodeMaterial();\n\n nodeMaterial.colorNode = build();\n\n return nodeMaterial;\n }, [build]);\n\n useEffect(() => {\n return () => material.dispose();\n }, [material]);\n\n return material;\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function useStaticSceneHint(isStatic: boolean): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n\n return shaderContext.scheduler.setIdle(isStatic);\n }, [shaderContext, isStatic]);\n}\n"],"mappings":"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","names":["useEffect","useState","jsx","useState","useEffect","useEffect","useRef","useState","jsx","jsxs","useRef","useState","useEffect","useEffect","useEffect","useState","useContext","useContext","useState","useEffect","useEffect","useEffect","useEffect","useState","STUB_SIGNAL","useState","useEffect","useEffect","useState","STUB_SIGNAL","useState","useEffect","useEffect","useMemo","useEffect","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/fallback-boundary/fallback-boundary.tsx","../src/components/shader-monitor/shader-monitor.tsx","../src/context/shader-context.ts","../src/components/shader-scene/shader-scene.tsx","../src/hooks/use-display-gamut/use-display-gamut.ts","../src/hooks/use-animatable-uniform/use-animatable-uniform.ts","../src/hooks/use-cursor/use-cursor.ts","../src/hooks/use-shader-context/use-shader-context.ts","../src/hooks/use-overlay-pass/use-overlay-pass.ts","../src/hooks/use-resize/use-resize.ts","../src/internal/create-signal.ts","../src/hooks/use-scroll/use-scroll.ts","../src/hooks/use-shader-material/use-shader-material.ts","../src/hooks/use-static-hint/use-static-hint.ts"],"sourcesContent":["'use client';\n\nimport { type ReactNode, useEffect, useState } from 'react';\n\nexport interface FallbackBoundaryProps {\n fallback?: ReactNode;\n children: ReactNode;\n}\n\nexport function FallbackBoundary({ fallback, children }: FallbackBoundaryProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n return <>{mounted ? children : (fallback ?? null)}</>;\n}\n","'use client';\n\nimport { type CSSProperties, useContext, useEffect, useRef, useState } from 'react';\n\nimport { ShaderContext } from '../../context/shader-context.js';\n\nexport type ShaderMonitorAnchor = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nconst anchorStyle: Record<ShaderMonitorAnchor, CSSProperties> = {\n 'top-left': { top: 8, left: 8 },\n 'top-right': { top: 8, right: 8 },\n 'bottom-left': { bottom: 8, left: 8 },\n 'bottom-right': { bottom: 8, right: 8 },\n};\n\nconst baseStyle: CSSProperties = {\n position: 'absolute',\n zIndex: 10,\n padding: '6px 8px',\n borderRadius: 6,\n background: 'rgba(0, 0, 0, 0.6)',\n color: '#fff',\n font: '11px ui-monospace, monospace',\n lineHeight: 1.4,\n pointerEvents: 'none',\n whiteSpace: 'pre',\n};\n\nexport interface ShaderMonitorProps {\n anchor?: ShaderMonitorAnchor;\n}\n\nexport function ShaderMonitor({ anchor = 'top-right' }: ShaderMonitorProps) {\n const shaderContext = useContext(ShaderContext);\n const [stats, setStats] = useState({ fps: 0, ticks: 0, frames: 0 });\n const ticksRef = useRef(0);\n const fpsAccumRef = useRef({ frames: 0, lastSampleAt: 0, fps: 0 });\n\n useEffect(() => {\n if (!shaderContext) return;\n const schedulerTickHandler = (tick: { now: number }) => {\n ticksRef.current += 1;\n const fpsAccumulator = fpsAccumRef.current;\n\n fpsAccumulator.frames += 1;\n if (fpsAccumulator.lastSampleAt === 0) fpsAccumulator.lastSampleAt = tick.now;\n const deltaTimeSinceLastSample = tick.now - fpsAccumulator.lastSampleAt;\n\n if (deltaTimeSinceLastSample >= 500) {\n fpsAccumulator.fps = Math.round((fpsAccumulator.frames * 1000) / deltaTimeSinceLastSample);\n fpsAccumulator.frames = 0;\n fpsAccumulator.lastSampleAt = tick.now;\n }\n setStats({ fps: fpsAccumulator.fps, ticks: ticksRef.current, frames: fpsAccumulator.frames });\n };\n\n shaderContext.scheduler.add(schedulerTickHandler);\n\n return () => shaderContext.scheduler.remove(schedulerTickHandler);\n }, [shaderContext]);\n\n if (!shaderContext) {\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n no scene\n </div>\n );\n }\n\n return (\n <div data-testid=\"matter-monitor\" style={{ ...baseStyle, ...anchorStyle[anchor] }}>\n <span data-testid=\"matter-monitor-fps\">fps: {stats.fps || '—'}</span>\n {'\\n'}\n <span data-testid=\"matter-monitor-ticks\">ticks: {stats.ticks}</span>\n </div>\n );\n}\n","import { createContext } from 'react';\n\nimport type { FrameScheduler, GpuRenderer } from '@lovo/matter';\nimport type { Camera, Scene } from 'three';\nimport type { ShaderNodeObject } from 'three/tsl';\nimport type { Node } from 'three/webgpu';\n\nexport type PostProcessTransform = (input: ShaderNodeObject<Node>) => ShaderNodeObject<Node>;\n\nexport interface ShaderContextValue {\n renderer: GpuRenderer;\n scene: Scene;\n camera: Camera;\n scheduler: FrameScheduler;\n registerOverlay: (transform: PostProcessTransform) => () => void;\n}\n\nexport const ShaderContext = createContext<ShaderContextValue | null>(null);\n","'use client';\n\nimport { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';\n\nimport {\n createIntersectionWatcher,\n createRenderer,\n createVisibilityWatcher,\n FrameScheduler,\n} from '@lovo/matter';\nimport { OrthographicCamera, Scene } from 'three';\nimport { pass, vec4 } from 'three/tsl';\nimport { PostProcessing } from 'three/webgpu';\n\nimport {\n type PostProcessTransform,\n ShaderContext,\n type ShaderContextValue,\n} from '../../context/shader-context.js';\nimport {\n type GamutPreference,\n useDisplayGamut,\n} from '../../hooks/use-display-gamut/use-display-gamut.js';\n\nexport interface ShaderSceneProps {\n children?: ReactNode;\n fallback?: ReactNode;\n className?: string;\n style?: CSSProperties;\n maxDPR?: number;\n /** Output color gamut. 'auto' (default) uses the widest the display supports. */\n gamut?: GamutPreference;\n}\n\nconst defaultStyle: CSSProperties = {\n position: 'absolute',\n inset: 0,\n display: 'block',\n width: '100%',\n height: '100%',\n};\n\nexport function ShaderScene(props: ShaderSceneProps) {\n const { children, fallback, className, style, maxDPR, gamut = 'auto' } = props;\n const resolvedGamut = useDisplayGamut(gamut);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const [shaderContext, setShaderContext] = useState<ShaderContextValue | null>(null);\n const [error, setError] = useState<Error | null>(null);\n // Stays false until the renderer has actually painted a frame containing the\n // shader. The fallback is held until then so there's no gap between dropping\n // the fallback and the first shader frame (which would otherwise flash the\n // canvas's clear state).\n const [firstFramePainted, setFirstFramePainted] = useState(false);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n\n if (!canvas) return;\n\n let cancelled = false;\n let cleanup: (() => void) | null = null;\n let firstPaintRaf: number | null = null;\n\n const setup = async () => {\n try {\n const renderer = await createRenderer(canvas, { maxDPR, gamut: resolvedGamut });\n\n if (cancelled) {\n renderer.dispose();\n\n return;\n }\n const scene = new Scene();\n const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);\n\n camera.position.z = 1;\n const postProcessing = new PostProcessing(renderer.three);\n const scheduler = new FrameScheduler();\n\n const overlays = new Map<symbol, PostProcessTransform>();\n\n const basePassNode = vec4(pass(scene, camera));\n\n const rebuildOutputNode = () => {\n postProcessing.outputNode = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n postProcessing.needsUpdate = true;\n };\n\n rebuildOutputNode(); // initial: just basePass, no overlays\n\n const registerOverlay = (transform: PostProcessTransform): (() => void) => {\n const key = Symbol('overlay');\n\n overlays.set(key, transform);\n rebuildOutputNode();\n\n return () => {\n overlays.delete(key);\n rebuildOutputNode();\n };\n };\n\n // Signal \"first paint\" only once the scene actually has something to\n // draw (a base shader mesh, or at least an overlay pass) — the scheduler\n // renders empty frames before the child shader mounts its mesh, and we\n // don't want to drop the fallback over an empty canvas. Defer the state\n // flip by one rAF so the just-submitted frame composites before the\n // fallback is removed.\n let firstPaintSignaled = false;\n const renderFrame = () => {\n postProcessing.render();\n\n if (!firstPaintSignaled && (scene.children.length > 0 || overlays.size > 0)) {\n firstPaintSignaled = true;\n firstPaintRaf = requestAnimationFrame(() => {\n firstPaintRaf = null;\n if (!cancelled) setFirstFramePainted(true);\n });\n }\n };\n\n scheduler.add(renderFrame);\n scheduler.start();\n\n const visibility = createVisibilityWatcher();\n const intersection = createIntersectionWatcher(canvas);\n\n const updatePauseState = () => {\n const shouldRun = visibility.isVisible() && intersection.isInView();\n\n if (shouldRun) scheduler.resume();\n else scheduler.pause();\n };\n\n updatePauseState();\n\n const unsubVisibility = visibility.subscribe(updatePauseState);\n const unsubIntersection = intersection.subscribe(updatePauseState);\n\n // Track the canvas's actual box size, not just window 'resize'. The\n // canvas commonly gets its real size from layout AFTER renderer init\n // (with no window resize firing), which would otherwise leave the\n // renderer stuck at the default 300x150 and render the scene into an\n // undersized target — compressing every shader's output. ResizeObserver\n // fires once on observe() and on every subsequent box change.\n const resizeObserver = new ResizeObserver(() => renderer.resize());\n\n resizeObserver.observe(canvas);\n\n cleanup = () => {\n unsubVisibility();\n unsubIntersection();\n visibility.dispose();\n intersection.dispose();\n resizeObserver.disconnect();\n scheduler.dispose();\n renderer.dispose();\n };\n\n setShaderContext({ renderer, scene, camera, scheduler, registerOverlay });\n } catch (caughtError) {\n if (cancelled) return;\n const normalizedError =\n caughtError instanceof Error ? caughtError : new Error(String(caughtError));\n\n console.error('[ShaderScene] renderer init failed:', normalizedError);\n setError(normalizedError);\n }\n };\n\n void setup();\n\n return () => {\n cancelled = true;\n if (firstPaintRaf !== null) {\n cancelAnimationFrame(firstPaintRaf);\n firstPaintRaf = null;\n }\n cleanup?.();\n cleanup = null;\n setShaderContext(null);\n // A fresh renderer (e.g. on gamut change) must re-prove its first paint,\n // so show the fallback again until it does.\n setFirstFramePainted(false);\n };\n }, [maxDPR, resolvedGamut]);\n\n let content: ReactNode;\n\n if (error) {\n content = (\n <div\n style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '1rem',\n color: '#fff',\n background: 'rgba(120, 30, 30, 0.85)',\n font: '0.85rem ui-monospace, monospace',\n whiteSpace: 'pre-wrap',\n textAlign: 'center',\n }}\n >\n ShaderScene init failed:\n {'\\n'}\n {error.message}\n </div>\n );\n } else {\n // Mount the children as soon as the context exists so the shader can build\n // and paint, but keep the fallback overlaid on top until that first frame\n // lands. The children render no visible DOM of their own (they drive the\n // canvas), so the fallback sits above the canvas and is removed only once\n // the shader is actually on screen.\n content = (\n <>\n {shaderContext && (\n <ShaderContext.Provider value={shaderContext}>{children}</ShaderContext.Provider>\n )}\n {!firstFramePainted && (fallback ?? null)}\n </>\n );\n }\n\n return (\n <div className={className} style={{ ...defaultStyle, ...style }}>\n <canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />\n {content}\n </div>\n );\n}\n","import { useEffect, useState } from 'react';\n\nimport type { OutputGamut } from '@lovo/matter';\n\n/** What the consumer asks for: a fixed gamut, or 'auto' to detect the display. */\nexport type GamutPreference = 'auto' | OutputGamut;\n\nconst P3_QUERY = '(color-gamut: p3)';\n\nfunction detectGamut(): OutputGamut {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return 'srgb';\n }\n\n return window.matchMedia(P3_QUERY).matches ? 'p3' : 'srgb';\n}\n\n/**\n * Resolve a gamut preference to a concrete output gamut. Explicit 'srgb'/'p3'\n * pass through untouched; 'auto' queries `(color-gamut: p3)` and re-resolves\n * when the display capability changes (e.g. window dragged to another monitor).\n */\nexport function useDisplayGamut(preference: GamutPreference): OutputGamut {\n const [resolved, setResolved] = useState<OutputGamut>(() =>\n preference === 'auto' ? detectGamut() : preference,\n );\n\n useEffect(() => {\n if (preference !== 'auto') {\n setResolved(preference);\n\n return;\n }\n\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n setResolved('srgb');\n\n return;\n }\n\n const mediaQuery = window.matchMedia(P3_QUERY);\n const update = () => setResolved(mediaQuery.matches ? 'p3' : 'srgb');\n\n update();\n mediaQuery.addEventListener('change', update);\n\n return () => mediaQuery.removeEventListener('change', update);\n }, [preference]);\n\n return resolved;\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport { uniform } from 'three/tsl';\n\nexport interface AnimatableSignal<T> {\n get(): T;\n on(event: 'change', cb: (value: T) => void): () => void;\n}\n\nexport type AnimatableProp<T> = T | AnimatableSignal<T>;\n\nconst isSignal = <T>(value: AnimatableProp<T>): value is AnimatableSignal<T> => {\n if (typeof value !== 'object' || value === null) return false;\n\n return (\n 'get' in value &&\n typeof value.get === 'function' &&\n 'on' in value &&\n typeof value.on === 'function'\n );\n};\n\nexport function useAnimatableUniform<T>(value: AnimatableProp<T>): ReturnType<typeof uniform<T>> {\n const uniformNode = useMemo(() => {\n const initial = isSignal(value) ? value.get() : value;\n\n return uniform(initial);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n if (isSignal(value)) {\n const unsub = value.on('change', (next) => {\n uniformNode.value = next;\n });\n\n return unsub;\n }\n uniformNode.value = value;\n\n return undefined;\n }, [value, uniformNode]);\n\n return uniformNode;\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { CursorInput, type CursorInputOptions, type Vector2 } from '@lovo/matter';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport interface CursorSignal {\n get(): Vector2;\n on(event: 'change', cb: (value: Vector2) => void): () => void;\n}\n\nconst STUB_SIGNAL: CursorSignal = {\n get: () => [0.5, 0.5] as const,\n on: () => () => undefined,\n};\n\nexport function useCursor(opts: CursorInputOptions = {}): CursorSignal {\n const shaderContext = useShaderContext();\n const [input, setInput] = useState<CursorInput | null>(null);\n\n useEffect(() => {\n const canvas = shaderContext?.renderer.three.domElement;\n const resolvedElement = opts.element ?? (canvas instanceof HTMLElement ? canvas : undefined);\n const newCursorInput = new CursorInput({ ...opts, element: resolvedElement });\n\n setInput(newCursorInput);\n\n let detach: (() => void) | null = null;\n\n if (shaderContext?.scheduler) {\n const schedulerTickHandler = ({ delta }: { delta: number }) => newCursorInput.tick(delta);\n\n shaderContext.scheduler.add(schedulerTickHandler);\n detach = () => shaderContext.scheduler.remove(schedulerTickHandler);\n } else {\n let animationFrameId: number | null = null;\n let lastNow = performance.now();\n const loop = (now: number) => {\n const delta = (now - lastNow) / 1000;\n\n lastNow = now;\n newCursorInput.tick(delta);\n animationFrameId = requestAnimationFrame(loop);\n };\n\n animationFrameId = requestAnimationFrame(loop);\n detach = () => {\n if (animationFrameId !== null) cancelAnimationFrame(animationFrameId);\n };\n }\n\n return () => {\n detach();\n newCursorInput.dispose();\n setInput(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext]);\n\n return input ?? STUB_SIGNAL;\n}\n","import { useContext } from 'react';\n\nimport { ShaderContext, type ShaderContextValue } from '../../context/shader-context.js';\n\nexport function useShaderContext(): ShaderContextValue | null {\n return useContext(ShaderContext);\n}\n","'use client';\n\nimport { type DependencyList, useEffect } from 'react';\n\nimport type { PostProcessTransform } from '../../context/shader-context.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function usePostProcessPass(transform: PostProcessTransform, deps: DependencyList): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n const unregister = shaderContext.registerOverlay(transform);\n\n return unregister;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shaderContext, ...deps]);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport type ResizeValue = readonly [width: number, height: number, dpr: number];\n\nexport interface ResizeSignal {\n get(): ResizeValue;\n on(event: 'change', cb: (value: ResizeValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ResizeSignal = {\n get: () => [0, 0, 1] as const,\n on: () => () => undefined,\n};\n\nexport function useResize(): ResizeSignal {\n const shaderContext = useShaderContext();\n const [signal, setSignal] = useState<ResizeSignal | null>(null);\n\n useEffect(() => {\n if (!shaderContext) return undefined;\n\n const canvas = shaderContext.renderer.three.domElement;\n\n if (!(canvas instanceof HTMLCanvasElement)) return undefined;\n\n let value: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ];\n const { signal: newSignal, listeners } = createSignal<ResizeValue>(() => value);\n\n setSignal(newSignal);\n\n const emit = () => {\n const next: ResizeValue = [\n canvas.clientWidth,\n canvas.clientHeight,\n typeof window !== 'undefined' ? window.devicePixelRatio : 1,\n ];\n\n if (next[0] === value[0] && next[1] === value[1] && next[2] === value[2]) return;\n value = next;\n for (const listener of listeners) listener(next);\n };\n\n const observer = new ResizeObserver(emit);\n\n observer.observe(canvas);\n\n let mediaQueryList: MediaQueryList | null = null;\n let mediaQueryListener: (() => void) | null = null;\n const setupDprWatch = () => {\n if (typeof window === 'undefined') return;\n const dpr = window.devicePixelRatio;\n const nextMediaQueryList = window.matchMedia(`(resolution: ${dpr}dppx)`);\n const nextMediaQueryListener = () => {\n emit();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n setupDprWatch();\n };\n\n nextMediaQueryList.addEventListener('change', nextMediaQueryListener);\n mediaQueryList = nextMediaQueryList;\n mediaQueryListener = nextMediaQueryListener;\n };\n\n setupDprWatch();\n\n return () => {\n observer.disconnect();\n if (mediaQueryList && mediaQueryListener)\n mediaQueryList.removeEventListener('change', mediaQueryListener);\n mediaQueryList = null;\n mediaQueryListener = null;\n listeners.clear();\n setSignal(null);\n };\n }, [shaderContext]);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nexport function createSignal<T>(getValue: () => T): {\n signal: { get(): T; on(event: string, listener: (v: T) => void): () => void };\n listeners: Set<(v: T) => void>;\n} {\n const listeners = new Set<(v: T) => void>();\n\n return {\n listeners,\n signal: {\n get: getValue,\n on: (_event, listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n },\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { createSignal } from '../../internal/create-signal.js';\n\nexport type ScrollValue = readonly [scrollY: number, progress: number];\n\nexport interface ScrollSignal {\n get(): ScrollValue;\n on(event: 'change', cb: (value: ScrollValue) => void): () => void;\n}\n\nconst STUB_SIGNAL: ScrollSignal = {\n get: () => [0, 0] as const,\n on: () => () => undefined,\n};\n\nexport function useScroll(): ScrollSignal {\n const [signal, setSignal] = useState<ScrollSignal | null>(null);\n\n useEffect(() => {\n if (typeof window === 'undefined') return undefined;\n\n const compute = (): ScrollValue => {\n const scrollYPosition = window.scrollY;\n\n const max = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1);\n const progress = Math.max(0, Math.min(1, scrollYPosition / max));\n\n return [scrollYPosition, progress];\n };\n\n let value: ScrollValue = compute();\n const { signal: newSignal, listeners } = createSignal<ScrollValue>(() => value);\n\n setSignal(newSignal);\n\n let rafPending = false;\n const onScroll = () => {\n if (rafPending) return;\n rafPending = true;\n requestAnimationFrame(() => {\n rafPending = false;\n const next = compute();\n\n if (next[0] === value[0] && next[1] === value[1]) return;\n value = next;\n for (const listener of listeners) listener(next);\n });\n };\n\n window.addEventListener('scroll', onScroll, { passive: true });\n\n return () => {\n window.removeEventListener('scroll', onScroll);\n listeners.clear();\n setSignal(null);\n };\n }, []);\n\n return signal ?? STUB_SIGNAL;\n}\n","'use client';\n\nimport { useEffect, useMemo } from 'react';\n\nimport type { ShaderNodeObject } from 'three/tsl';\nimport { MeshBasicNodeMaterial } from 'three/webgpu';\nimport type { Node } from 'three/webgpu';\n\nexport type ColorTSL = Node | ShaderNodeObject<Node>;\n\nexport function useShaderMaterial(build: () => ColorTSL): MeshBasicNodeMaterial {\n const material = useMemo(() => {\n const nodeMaterial = new MeshBasicNodeMaterial();\n\n nodeMaterial.colorNode = build();\n\n return nodeMaterial;\n }, [build]);\n\n useEffect(() => {\n return () => material.dispose();\n }, [material]);\n\n return material;\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\nimport { useShaderContext } from '../use-shader-context/use-shader-context.js';\n\nexport function useStaticSceneHint(isStatic: boolean): void {\n const shaderContext = useShaderContext();\n\n useEffect(() => {\n if (!shaderContext) return;\n\n return shaderContext.scheduler.setIdle(isStatic);\n }, [shaderContext, isStatic]);\n}\n"],"mappings":";AAEA,SAAyB,WAAW,gBAAgB;AAc3C;AAPF,SAAS,iBAAiB,EAAE,UAAU,SAAS,GAA0B;AAC9E,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,CAAC;AAEL,SAAO,gCAAG,oBAAU,WAAY,YAAY,MAAM;AACpD;;;ACfA,SAA6B,YAAY,aAAAA,YAAW,QAAQ,YAAAC,iBAAgB;;;ACF5E,SAAS,qBAAqB;AAiBvB,IAAM,gBAAgB,cAAyC,IAAI;;;AD8CpE,gBAAAC,MAQA,YARA;AAvDN,IAAM,cAA0D;AAAA,EAC9D,YAAY,EAAE,KAAK,GAAG,MAAM,EAAE;AAAA,EAC9B,aAAa,EAAE,KAAK,GAAG,OAAO,EAAE;AAAA,EAChC,eAAe,EAAE,QAAQ,GAAG,MAAM,EAAE;AAAA,EACpC,gBAAgB,EAAE,QAAQ,GAAG,OAAO,EAAE;AACxC;AAEA,IAAM,YAA2B;AAAA,EAC/B,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,YAAY;AACd;AAMO,SAAS,cAAc,EAAE,SAAS,YAAY,GAAuB;AAC1E,QAAM,gBAAgB,WAAW,aAAa;AAC9C,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAAS,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,EAAE,CAAC;AAClE,QAAM,WAAW,OAAO,CAAC;AACzB,QAAM,cAAc,OAAO,EAAE,QAAQ,GAAG,cAAc,GAAG,KAAK,EAAE,CAAC;AAEjE,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,cAAe;AACpB,UAAM,uBAAuB,CAAC,SAA0B;AACtD,eAAS,WAAW;AACpB,YAAM,iBAAiB,YAAY;AAEnC,qBAAe,UAAU;AACzB,UAAI,eAAe,iBAAiB,EAAG,gBAAe,eAAe,KAAK;AAC1E,YAAM,2BAA2B,KAAK,MAAM,eAAe;AAE3D,UAAI,4BAA4B,KAAK;AACnC,uBAAe,MAAM,KAAK,MAAO,eAAe,SAAS,MAAQ,wBAAwB;AACzF,uBAAe,SAAS;AACxB,uBAAe,eAAe,KAAK;AAAA,MACrC;AACA,eAAS,EAAE,KAAK,eAAe,KAAK,OAAO,SAAS,SAAS,QAAQ,eAAe,OAAO,CAAC;AAAA,IAC9F;AAEA,kBAAc,UAAU,IAAI,oBAAoB;AAEhD,WAAO,MAAM,cAAc,UAAU,OAAO,oBAAoB;AAAA,EAClE,GAAG,CAAC,aAAa,CAAC;AAElB,MAAI,CAAC,eAAe;AAClB,WACE,gBAAAF,KAAC,SAAI,eAAY,kBAAiB,OAAO,EAAE,GAAG,WAAW,GAAG,YAAY,MAAM,EAAE,GAAG,sBAEnF;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,eAAY,kBAAiB,OAAO,EAAE,GAAG,WAAW,GAAG,YAAY,MAAM,EAAE,GAC9E;AAAA,yBAAC,UAAK,eAAY,sBAAqB;AAAA;AAAA,MAAM,MAAM,OAAO;AAAA,OAAI;AAAA,IAC7D;AAAA,IACD,qBAAC,UAAK,eAAY,wBAAuB;AAAA;AAAA,MAAQ,MAAM;AAAA,OAAM;AAAA,KAC/D;AAEJ;;;AE1EA,SAA6C,aAAAG,YAAW,UAAAC,SAAQ,YAAAC,iBAAgB;AAEhF;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB,aAAa;AAC1C,SAAS,MAAM,YAAY;AAC3B,SAAS,sBAAsB;;;ACZ/B,SAAS,aAAAC,YAAW,YAAAC,iBAAgB;AAOpC,IAAM,WAAW;AAEjB,SAAS,cAA2B;AAClC,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC5E,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,WAAW,QAAQ,EAAE,UAAU,OAAO;AACtD;AAOO,SAAS,gBAAgB,YAA0C;AACxE,QAAM,CAAC,UAAU,WAAW,IAAIA;AAAA,IAAsB,MACpD,eAAe,SAAS,YAAY,IAAI;AAAA,EAC1C;AAEA,EAAAD,WAAU,MAAM;AACd,QAAI,eAAe,QAAQ;AACzB,kBAAY,UAAU;AAEtB;AAAA,IACF;AAEA,QAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC5E,kBAAY,MAAM;AAElB;AAAA,IACF;AAEA,UAAM,aAAa,OAAO,WAAW,QAAQ;AAC7C,UAAM,SAAS,MAAM,YAAY,WAAW,UAAU,OAAO,MAAM;AAEnE,WAAO;AACP,eAAW,iBAAiB,UAAU,MAAM;AAE5C,WAAO,MAAM,WAAW,oBAAoB,UAAU,MAAM;AAAA,EAC9D,GAAG,CAAC,UAAU,CAAC;AAEf,SAAO;AACT;;;ADgJM,SA2BA,YAAAE,WAEI,OAAAC,MA7BJ,QAAAC,aAAA;AAhKN,IAAM,eAA8B;AAAA,EAClC,UAAU;AAAA,EACV,OAAO;AAAA,EACP,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AACV;AAEO,SAAS,YAAY,OAAyB;AACnD,QAAM,EAAE,UAAU,UAAU,WAAW,OAAO,QAAQ,QAAQ,OAAO,IAAI;AACzE,QAAM,gBAAgB,gBAAgB,KAAK;AAC3C,QAAM,YAAYC,QAA0B,IAAI;AAChD,QAAM,CAAC,eAAe,gBAAgB,IAAIC,UAAoC,IAAI;AAClF,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAuB,IAAI;AAKrD,QAAM,CAAC,mBAAmB,oBAAoB,IAAIA,UAAS,KAAK;AAEhE,EAAAC,WAAU,MAAM;AACd,UAAM,SAAS,UAAU;AAEzB,QAAI,CAAC,OAAQ;AAEb,QAAI,YAAY;AAChB,QAAI,UAA+B;AACnC,QAAI,gBAA+B;AAEnC,UAAM,QAAQ,YAAY;AACxB,UAAI;AACF,cAAM,WAAW,MAAM,eAAe,QAAQ,EAAE,QAAQ,OAAO,cAAc,CAAC;AAE9E,YAAI,WAAW;AACb,mBAAS,QAAQ;AAEjB;AAAA,QACF;AACA,cAAM,QAAQ,IAAI,MAAM;AACxB,cAAM,SAAS,IAAI,mBAAmB,IAAI,GAAG,GAAG,IAAI,KAAK,EAAE;AAE3D,eAAO,SAAS,IAAI;AACpB,cAAM,iBAAiB,IAAI,eAAe,SAAS,KAAK;AACxD,cAAM,YAAY,IAAI,eAAe;AAErC,cAAM,WAAW,oBAAI,IAAkC;AAEvD,cAAM,eAAe,KAAK,KAAK,OAAO,MAAM,CAAC;AAE7C,cAAM,oBAAoB,MAAM;AAC9B,yBAAe,aAAa,MAAM,KAAK,SAAS,OAAO,CAAC,EAAE;AAAA,YACxD,CAAC,iBAAiB,cAAc,UAAU,eAAe;AAAA,YACzD;AAAA,UACF;AACA,yBAAe,cAAc;AAAA,QAC/B;AAEA,0BAAkB;AAElB,cAAM,kBAAkB,CAAC,cAAkD;AACzE,gBAAM,MAAM,uBAAO,SAAS;AAE5B,mBAAS,IAAI,KAAK,SAAS;AAC3B,4BAAkB;AAElB,iBAAO,MAAM;AACX,qBAAS,OAAO,GAAG;AACnB,8BAAkB;AAAA,UACpB;AAAA,QACF;AAQA,YAAI,qBAAqB;AACzB,cAAM,cAAc,MAAM;AACxB,yBAAe,OAAO;AAEtB,cAAI,CAAC,uBAAuB,MAAM,SAAS,SAAS,KAAK,SAAS,OAAO,IAAI;AAC3E,iCAAqB;AACrB,4BAAgB,sBAAsB,MAAM;AAC1C,8BAAgB;AAChB,kBAAI,CAAC,UAAW,sBAAqB,IAAI;AAAA,YAC3C,CAAC;AAAA,UACH;AAAA,QACF;AAEA,kBAAU,IAAI,WAAW;AACzB,kBAAU,MAAM;AAEhB,cAAM,aAAa,wBAAwB;AAC3C,cAAM,eAAe,0BAA0B,MAAM;AAErD,cAAM,mBAAmB,MAAM;AAC7B,gBAAM,YAAY,WAAW,UAAU,KAAK,aAAa,SAAS;AAElE,cAAI,UAAW,WAAU,OAAO;AAAA,cAC3B,WAAU,MAAM;AAAA,QACvB;AAEA,yBAAiB;AAEjB,cAAM,kBAAkB,WAAW,UAAU,gBAAgB;AAC7D,cAAM,oBAAoB,aAAa,UAAU,gBAAgB;AAQjE,cAAM,iBAAiB,IAAI,eAAe,MAAM,SAAS,OAAO,CAAC;AAEjE,uBAAe,QAAQ,MAAM;AAE7B,kBAAU,MAAM;AACd,0BAAgB;AAChB,4BAAkB;AAClB,qBAAW,QAAQ;AACnB,uBAAa,QAAQ;AACrB,yBAAe,WAAW;AAC1B,oBAAU,QAAQ;AAClB,mBAAS,QAAQ;AAAA,QACnB;AAEA,yBAAiB,EAAE,UAAU,OAAO,QAAQ,WAAW,gBAAgB,CAAC;AAAA,MAC1E,SAAS,aAAa;AACpB,YAAI,UAAW;AACf,cAAM,kBACJ,uBAAuB,QAAQ,cAAc,IAAI,MAAM,OAAO,WAAW,CAAC;AAE5E,gBAAQ,MAAM,uCAAuC,eAAe;AACpE,iBAAS,eAAe;AAAA,MAC1B;AAAA,IACF;AAEA,SAAK,MAAM;AAEX,WAAO,MAAM;AACX,kBAAY;AACZ,UAAI,kBAAkB,MAAM;AAC1B,6BAAqB,aAAa;AAClC,wBAAgB;AAAA,MAClB;AACA,gBAAU;AACV,gBAAU;AACV,uBAAiB,IAAI;AAGrB,2BAAqB,KAAK;AAAA,IAC5B;AAAA,EACF,GAAG,CAAC,QAAQ,aAAa,CAAC;AAE1B,MAAI;AAEJ,MAAI,OAAO;AACT,cACE,gBAAAH;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,UACL,UAAU;AAAA,UACV,OAAO;AAAA,UACP,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,gBAAgB;AAAA,UAChB,SAAS;AAAA,UACT,OAAO;AAAA,UACP,YAAY;AAAA,UACZ,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,WAAW;AAAA,QACb;AAAA,QACD;AAAA;AAAA,UAEE;AAAA,UACA,MAAM;AAAA;AAAA;AAAA,IACT;AAAA,EAEJ,OAAO;AAML,cACE,gBAAAA,MAAAF,WAAA,EACG;AAAA,uBACC,gBAAAC,KAAC,cAAc,UAAd,EAAuB,OAAO,eAAgB,UAAS;AAAA,MAEzD,CAAC,sBAAsB,YAAY;AAAA,OACtC;AAAA,EAEJ;AAEA,SACE,gBAAAC,MAAC,SAAI,WAAsB,OAAO,EAAE,GAAG,cAAc,GAAG,MAAM,GAC5D;AAAA,oBAAAD,KAAC,YAAO,KAAK,WAAW,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,GAAG;AAAA,IACnF;AAAA,KACH;AAEJ;;;AE1OA,SAAS,aAAAK,YAAW,eAAe;AAEnC,SAAS,eAAe;AASxB,IAAM,WAAW,CAAI,UAA2D;AAC9E,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AAExD,SACE,SAAS,SACT,OAAO,MAAM,QAAQ,cACrB,QAAQ,SACR,OAAO,MAAM,OAAO;AAExB;AAEO,SAAS,qBAAwB,OAAyD;AAC/F,QAAM,cAAc,QAAQ,MAAM;AAChC,UAAM,UAAU,SAAS,KAAK,IAAI,MAAM,IAAI,IAAI;AAEhD,WAAO,QAAQ,OAAO;AAAA,EAExB,GAAG,CAAC,CAAC;AAEL,EAAAA,WAAU,MAAM;AACd,QAAI,SAAS,KAAK,GAAG;AACnB,YAAM,QAAQ,MAAM,GAAG,UAAU,CAAC,SAAS;AACzC,oBAAY,QAAQ;AAAA,MACtB,CAAC;AAED,aAAO;AAAA,IACT;AACA,gBAAY,QAAQ;AAEpB,WAAO;AAAA,EACT,GAAG,CAAC,OAAO,WAAW,CAAC;AAEvB,SAAO;AACT;;;AC5CA,SAAS,aAAAC,YAAW,YAAAC,iBAAgB;AAEpC,SAAS,mBAA0D;;;ACJnE,SAAS,cAAAC,mBAAkB;AAIpB,SAAS,mBAA8C;AAC5D,SAAOC,YAAW,aAAa;AACjC;;;ADOA,IAAM,cAA4B;AAAA,EAChC,KAAK,MAAM,CAAC,KAAK,GAAG;AAAA,EACpB,IAAI,MAAM,MAAM;AAClB;AAEO,SAAS,UAAU,OAA2B,CAAC,GAAiB;AACrE,QAAM,gBAAgB,iBAAiB;AACvC,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAA6B,IAAI;AAE3D,EAAAC,WAAU,MAAM;AACd,UAAM,SAAS,eAAe,SAAS,MAAM;AAC7C,UAAM,kBAAkB,KAAK,YAAY,kBAAkB,cAAc,SAAS;AAClF,UAAM,iBAAiB,IAAI,YAAY,EAAE,GAAG,MAAM,SAAS,gBAAgB,CAAC;AAE5E,aAAS,cAAc;AAEvB,QAAI,SAA8B;AAElC,QAAI,eAAe,WAAW;AAC5B,YAAM,uBAAuB,CAAC,EAAE,MAAM,MAAyB,eAAe,KAAK,KAAK;AAExF,oBAAc,UAAU,IAAI,oBAAoB;AAChD,eAAS,MAAM,cAAc,UAAU,OAAO,oBAAoB;AAAA,IACpE,OAAO;AACL,UAAI,mBAAkC;AACtC,UAAI,UAAU,YAAY,IAAI;AAC9B,YAAM,OAAO,CAAC,QAAgB;AAC5B,cAAM,SAAS,MAAM,WAAW;AAEhC,kBAAU;AACV,uBAAe,KAAK,KAAK;AACzB,2BAAmB,sBAAsB,IAAI;AAAA,MAC/C;AAEA,yBAAmB,sBAAsB,IAAI;AAC7C,eAAS,MAAM;AACb,YAAI,qBAAqB,KAAM,sBAAqB,gBAAgB;AAAA,MACtE;AAAA,IACF;AAEA,WAAO,MAAM;AACX,aAAO;AACP,qBAAe,QAAQ;AACvB,eAAS,IAAI;AAAA,IACf;AAAA,EAEF,GAAG,CAAC,aAAa,CAAC;AAElB,SAAO,SAAS;AAClB;;;AE5DA,SAA8B,aAAAC,kBAAiB;AAKxC,SAAS,mBAAmB,WAAiC,MAA4B;AAC9F,QAAM,gBAAgB,iBAAiB;AAEvC,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,cAAe;AACpB,UAAM,aAAa,cAAc,gBAAgB,SAAS;AAE1D,WAAO;AAAA,EAET,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC;AAC7B;;;ACfA,SAAS,aAAAC,YAAW,YAAAC,iBAAgB;;;ACA7B,SAAS,aAAgB,UAG9B;AACA,QAAM,YAAY,oBAAI,IAAoB;AAE1C,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,MACN,KAAK;AAAA,MACL,IAAI,CAAC,QAAQ,aAAa;AACxB,kBAAU,IAAI,QAAQ;AAEtB,eAAO,MAAM;AACX,oBAAU,OAAO,QAAQ;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ADPA,IAAMC,eAA4B;AAAA,EAChC,KAAK,MAAM,CAAC,GAAG,GAAG,CAAC;AAAA,EACnB,IAAI,MAAM,MAAM;AAClB;AAEO,SAAS,YAA0B;AACxC,QAAM,gBAAgB,iBAAiB;AACvC,QAAM,CAAC,QAAQ,SAAS,IAAIC,UAA8B,IAAI;AAE9D,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,cAAe,QAAO;AAE3B,UAAM,SAAS,cAAc,SAAS,MAAM;AAE5C,QAAI,EAAE,kBAAkB,mBAAoB,QAAO;AAEnD,QAAI,QAAqB;AAAA,MACvB,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO,WAAW,cAAc,OAAO,mBAAmB;AAAA,IAC5D;AACA,UAAM,EAAE,QAAQ,WAAW,UAAU,IAAI,aAA0B,MAAM,KAAK;AAE9E,cAAU,SAAS;AAEnB,UAAM,OAAO,MAAM;AACjB,YAAM,OAAoB;AAAA,QACxB,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO,WAAW,cAAc,OAAO,mBAAmB;AAAA,MAC5D;AAEA,UAAI,KAAK,CAAC,MAAM,MAAM,CAAC,KAAK,KAAK,CAAC,MAAM,MAAM,CAAC,KAAK,KAAK,CAAC,MAAM,MAAM,CAAC,EAAG;AAC1E,cAAQ;AACR,iBAAW,YAAY,UAAW,UAAS,IAAI;AAAA,IACjD;AAEA,UAAM,WAAW,IAAI,eAAe,IAAI;AAExC,aAAS,QAAQ,MAAM;AAEvB,QAAI,iBAAwC;AAC5C,QAAI,qBAA0C;AAC9C,UAAM,gBAAgB,MAAM;AAC1B,UAAI,OAAO,WAAW,YAAa;AACnC,YAAM,MAAM,OAAO;AACnB,YAAM,qBAAqB,OAAO,WAAW,gBAAgB,GAAG,OAAO;AACvE,YAAM,yBAAyB,MAAM;AACnC,aAAK;AACL,YAAI,kBAAkB;AACpB,yBAAe,oBAAoB,UAAU,kBAAkB;AACjE,sBAAc;AAAA,MAChB;AAEA,yBAAmB,iBAAiB,UAAU,sBAAsB;AACpE,uBAAiB;AACjB,2BAAqB;AAAA,IACvB;AAEA,kBAAc;AAEd,WAAO,MAAM;AACX,eAAS,WAAW;AACpB,UAAI,kBAAkB;AACpB,uBAAe,oBAAoB,UAAU,kBAAkB;AACjE,uBAAiB;AACjB,2BAAqB;AACrB,gBAAU,MAAM;AAChB,gBAAU,IAAI;AAAA,IAChB;AAAA,EACF,GAAG,CAAC,aAAa,CAAC;AAElB,SAAO,UAAUF;AACnB;;;AErFA,SAAS,aAAAG,YAAW,YAAAC,iBAAgB;AAWpC,IAAMC,eAA4B;AAAA,EAChC,KAAK,MAAM,CAAC,GAAG,CAAC;AAAA,EAChB,IAAI,MAAM,MAAM;AAClB;AAEO,SAAS,YAA0B;AACxC,QAAM,CAAC,QAAQ,SAAS,IAAIC,UAA8B,IAAI;AAE9D,EAAAC,WAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa,QAAO;AAE1C,UAAM,UAAU,MAAmB;AACjC,YAAM,kBAAkB,OAAO;AAE/B,YAAM,MAAM,KAAK,IAAI,SAAS,gBAAgB,eAAe,OAAO,aAAa,CAAC;AAClF,YAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,kBAAkB,GAAG,CAAC;AAE/D,aAAO,CAAC,iBAAiB,QAAQ;AAAA,IACnC;AAEA,QAAI,QAAqB,QAAQ;AACjC,UAAM,EAAE,QAAQ,WAAW,UAAU,IAAI,aAA0B,MAAM,KAAK;AAE9E,cAAU,SAAS;AAEnB,QAAI,aAAa;AACjB,UAAM,WAAW,MAAM;AACrB,UAAI,WAAY;AAChB,mBAAa;AACb,4BAAsB,MAAM;AAC1B,qBAAa;AACb,cAAM,OAAO,QAAQ;AAErB,YAAI,KAAK,CAAC,MAAM,MAAM,CAAC,KAAK,KAAK,CAAC,MAAM,MAAM,CAAC,EAAG;AAClD,gBAAQ;AACR,mBAAW,YAAY,UAAW,UAAS,IAAI;AAAA,MACjD,CAAC;AAAA,IACH;AAEA,WAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;AAE7D,WAAO,MAAM;AACX,aAAO,oBAAoB,UAAU,QAAQ;AAC7C,gBAAU,MAAM;AAChB,gBAAU,IAAI;AAAA,IAChB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,UAAUF;AACnB;;;AC5DA,SAAS,aAAAG,aAAW,WAAAC,gBAAe;AAGnC,SAAS,6BAA6B;AAK/B,SAAS,kBAAkB,OAA8C;AAC9E,QAAM,WAAWA,SAAQ,MAAM;AAC7B,UAAM,eAAe,IAAI,sBAAsB;AAE/C,iBAAa,YAAY,MAAM;AAE/B,WAAO;AAAA,EACT,GAAG,CAAC,KAAK,CAAC;AAEV,EAAAD,YAAU,MAAM;AACd,WAAO,MAAM,SAAS,QAAQ;AAAA,EAChC,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAO;AACT;;;ACtBA,SAAS,aAAAE,mBAAiB;AAInB,SAAS,mBAAmB,UAAyB;AAC1D,QAAM,gBAAgB,iBAAiB;AAEvC,EAAAC,YAAU,MAAM;AACd,QAAI,CAAC,cAAe;AAEpB,WAAO,cAAc,UAAU,QAAQ,QAAQ;AAAA,EACjD,GAAG,CAAC,eAAe,QAAQ,CAAC;AAC9B;","names":["useEffect","useState","jsx","useState","useEffect","useEffect","useRef","useState","useEffect","useState","Fragment","jsx","jsxs","useRef","useState","useEffect","useEffect","useEffect","useState","useContext","useContext","useState","useEffect","useEffect","useEffect","useEffect","useState","STUB_SIGNAL","useState","useEffect","useEffect","useState","STUB_SIGNAL","useState","useEffect","useEffect","useMemo","useEffect","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovo/matter-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "React binding for Matter — MatterScene, useShaderMaterial, input hooks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"components",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"typescript": "^5.6.0",
|
|
58
58
|
"vite": "^8.0.14",
|
|
59
59
|
"vitest": "^4.1.7",
|
|
60
|
-
"@lovo/matter": "0.
|
|
60
|
+
"@lovo/matter": "0.6.0",
|
|
61
61
|
"@matter/tsconfig": "0.0.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|