@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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # @lovo/matter-react
2
2
 
3
+ ## 0.6.0
4
+
3
5
  ## 0.5.0
4
6
 
5
7
  ### Minor Changes
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 import_react4 = require("react");
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 canvasRef = (0, import_react4.useRef)(null);
131
- const [shaderContext, setShaderContext] = (0, import_react4.useState)(null);
132
- const [error, setError] = (0, import_react4.useState)(null);
133
- (0, import_react4.useEffect)(() => {
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 basePass = (0, import_tsl.pass)(scene, camera);
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
- scheduler.add(() => postProcessing.render());
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 onResize = () => renderer.resize();
183
- window.addEventListener("resize", onResize);
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
- window.removeEventListener("resize", onResize);
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 = fallback ?? null;
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 import_react5 = require("react");
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, import_react5.useMemo)(() => {
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, import_react5.useEffect)(() => {
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 import_react7 = require("react");
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 import_react6 = require("react");
328
+ var import_react7 = require("react");
276
329
  function useShaderContext() {
277
- return (0, import_react6.useContext)(ShaderContext);
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, import_react7.useState)(null);
288
- (0, import_react7.useEffect)(() => {
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 import_react8 = require("react");
375
+ var import_react9 = require("react");
323
376
  function usePostProcessPass(transform, deps) {
324
377
  const shaderContext = useShaderContext();
325
- (0, import_react8.useEffect)(() => {
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 import_react9 = require("react");
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, import_react9.useState)(null);
360
- (0, import_react9.useEffect)(() => {
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 import_react10 = require("react");
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, import_react10.useState)(null);
421
- (0, import_react10.useEffect)(() => {
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 import_react11 = require("react");
508
+ var import_react12 = require("react");
456
509
  var import_webgpu2 = require("three/webgpu");
457
510
  function useShaderMaterial(build) {
458
- const material = (0, import_react11.useMemo)(() => {
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, import_react11.useEffect)(() => {
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 import_react12 = require("react");
523
+ var import_react13 = require("react");
471
524
  function useStaticSceneHint(isStatic) {
472
525
  const shaderContext = useShaderContext();
473
- (0, import_react12.useEffect)(() => {
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,
@@ -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 { ShaderNodeObject } from 'three/tsl';
4
- import { Node, MeshBasicNodeMaterial } from 'three/webgpu';
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>): ShaderNodeObject<Node> & {
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 { ShaderNodeObject } from 'three/tsl';
4
- import { Node, MeshBasicNodeMaterial } from 'three/webgpu';
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>): ShaderNodeObject<Node> & {
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 useEffect3, useRef as useRef2, useState as useState3 } from "react";
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
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
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] = useState3(null);
101
- const [error, setError] = useState3(null);
102
- useEffect3(() => {
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 basePass = pass(scene, camera);
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
- scheduler.add(() => postProcessing.render());
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 onResize = () => renderer.resize();
152
- window.addEventListener("resize", onResize);
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
- window.removeEventListener("resize", onResize);
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 = fallback ?? null;
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 useEffect4, useMemo } from "react";
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
- useEffect4(() => {
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 useEffect5, useState as useState4 } from "react";
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] = useState4(null);
257
- useEffect5(() => {
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 useEffect6 } from "react";
343
+ import { useEffect as useEffect7 } from "react";
292
344
  function usePostProcessPass(transform, deps) {
293
345
  const shaderContext = useShaderContext();
294
- useEffect6(() => {
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 useEffect7, useState as useState5 } from "react";
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] = useState5(null);
329
- useEffect7(() => {
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 useEffect8, useState as useState6 } from "react";
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] = useState6(null);
390
- useEffect8(() => {
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 useEffect9, useMemo as useMemo2 } from "react";
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
- useEffect9(() => {
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 useEffect10 } from "react";
491
+ import { useEffect as useEffect11 } from "react";
440
492
  function useStaticSceneHint(isStatic) {
441
493
  const shaderContext = useShaderContext();
442
- useEffect10(() => {
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":"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","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.5.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.5.0",
60
+ "@lovo/matter": "0.6.0",
61
61
  "@matter/tsconfig": "0.0.0"
62
62
  },
63
63
  "peerDependencies": {