@lovo/matter-react 0.6.0 → 1.0.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 +4 -2
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @lovo/matter-react
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
3
5
|
## 0.6.0
|
|
4
6
|
|
|
5
7
|
## 0.5.0
|
|
@@ -53,9 +55,9 @@
|
|
|
53
55
|
|
|
54
56
|
**Registry-side ships (delivered via `@lovo/matter-cli` copy-paste):**
|
|
55
57
|
|
|
56
|
-
- `<
|
|
58
|
+
- `<Grain>` — additive or subtractive grain overlay.
|
|
57
59
|
- `<Vignette>` — radial edge darkening, aspect-corrected so the mask is a circle on widescreen.
|
|
58
|
-
- **Breaking:** `<MeshGradient>` no longer accepts `grain` / `grainSpeed` props. Stack `<
|
|
60
|
+
- **Breaking:** `<MeshGradient>` no longer accepts `grain` / `grainSpeed` props. Stack `<Grain />` as a sibling inside `<MatterScene>` instead. Existing copies pulled before this release keep working; new pulls / CLI refreshes pick up the new shape. The MeshGradient docs page has the new pattern.
|
|
59
61
|
|
|
60
62
|
### Patch Changes
|
|
61
63
|
|
package/dist/index.cjs
CHANGED
|
@@ -183,14 +183,16 @@ function ShaderScene(props) {
|
|
|
183
183
|
const camera = new import_three.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
|
|
184
184
|
camera.position.z = 1;
|
|
185
185
|
const postProcessing = new import_webgpu.PostProcessing(renderer.three);
|
|
186
|
+
postProcessing.outputColorTransform = false;
|
|
186
187
|
const scheduler = new import_matter.FrameScheduler();
|
|
187
188
|
const overlays = /* @__PURE__ */ new Map();
|
|
188
189
|
const basePassNode = (0, import_tsl.vec4)((0, import_tsl.pass)(scene, camera));
|
|
189
190
|
const rebuildOutputNode = () => {
|
|
190
|
-
|
|
191
|
+
const composed = Array.from(overlays.values()).reduce(
|
|
191
192
|
(currentPipeline, transform) => transform(currentPipeline),
|
|
192
193
|
basePassNode
|
|
193
194
|
);
|
|
195
|
+
postProcessing.outputNode = (0, import_matter.dither)((0, import_tsl.renderOutput)(composed));
|
|
194
196
|
postProcessing.needsUpdate = true;
|
|
195
197
|
};
|
|
196
198
|
rebuildOutputNode();
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/fallback-boundary/fallback-boundary.tsx","../src/components/shader-monitor/shader-monitor.tsx","../src/context/shader-context.ts","../src/components/shader-scene/shader-scene.tsx","../src/hooks/use-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"]}
|
|
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 dither,\n FrameScheduler,\n} from '@lovo/matter';\nimport { OrthographicCamera, Scene } from 'three';\nimport { pass, renderOutput, 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\n // Take ownership of the output color transform so we can dither in\n // display-encoded space (see rebuildOutputNode below).\n postProcessing.outputColorTransform = false;\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 // Overlays (Grain, Vignette, ...) compose in linear working space.\n const composed = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n\n // renderOutput applies tone mapping + the working->output color-space\n // transfer (it reads both from the context three sets because\n // outputColorTransform is false), so dither runs last, in\n // display-encoded space, right before 8-bit quantization. This breaks\n // up gradient banding uniformly across every component in the scene.\n postProcessing.outputNode = dither(renderOutput(composed));\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.js
CHANGED
|
@@ -81,10 +81,11 @@ import {
|
|
|
81
81
|
createIntersectionWatcher,
|
|
82
82
|
createRenderer,
|
|
83
83
|
createVisibilityWatcher,
|
|
84
|
+
dither,
|
|
84
85
|
FrameScheduler
|
|
85
86
|
} from "@lovo/matter";
|
|
86
87
|
import { OrthographicCamera, Scene } from "three";
|
|
87
|
-
import { pass, vec4 } from "three/tsl";
|
|
88
|
+
import { pass, renderOutput, vec4 } from "three/tsl";
|
|
88
89
|
import { PostProcessing } from "three/webgpu";
|
|
89
90
|
|
|
90
91
|
// src/hooks/use-display-gamut/use-display-gamut.ts
|
|
@@ -151,14 +152,16 @@ function ShaderScene(props) {
|
|
|
151
152
|
const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
|
|
152
153
|
camera.position.z = 1;
|
|
153
154
|
const postProcessing = new PostProcessing(renderer.three);
|
|
155
|
+
postProcessing.outputColorTransform = false;
|
|
154
156
|
const scheduler = new FrameScheduler();
|
|
155
157
|
const overlays = /* @__PURE__ */ new Map();
|
|
156
158
|
const basePassNode = vec4(pass(scene, camera));
|
|
157
159
|
const rebuildOutputNode = () => {
|
|
158
|
-
|
|
160
|
+
const composed = Array.from(overlays.values()).reduce(
|
|
159
161
|
(currentPipeline, transform) => transform(currentPipeline),
|
|
160
162
|
basePassNode
|
|
161
163
|
);
|
|
164
|
+
postProcessing.outputNode = dither(renderOutput(composed));
|
|
162
165
|
postProcessing.needsUpdate = true;
|
|
163
166
|
};
|
|
164
167
|
rebuildOutputNode();
|
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-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"]}
|
|
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 dither,\n FrameScheduler,\n} from '@lovo/matter';\nimport { OrthographicCamera, Scene } from 'three';\nimport { pass, renderOutput, 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\n // Take ownership of the output color transform so we can dither in\n // display-encoded space (see rebuildOutputNode below).\n postProcessing.outputColorTransform = false;\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 // Overlays (Grain, Vignette, ...) compose in linear working space.\n const composed = Array.from(overlays.values()).reduce(\n (currentPipeline, transform) => transform(currentPipeline),\n basePassNode,\n );\n\n // renderOutput applies tone mapping + the working->output color-space\n // transfer (it reads both from the context three sets because\n // outputColorTransform is false), so dither runs last, in\n // display-encoded space, right before 8-bit quantization. This breaks\n // up gradient banding uniformly across every component in the scene.\n postProcessing.outputNode = dither(renderOutput(composed));\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.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "React binding for Matter — MatterScene, useShaderMaterial, input hooks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"components",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"typescript": "^5.6.0",
|
|
58
58
|
"vite": "^8.0.14",
|
|
59
59
|
"vitest": "^4.1.7",
|
|
60
|
-
"@lovo/matter": "0.
|
|
60
|
+
"@lovo/matter": "1.0.0",
|
|
61
61
|
"@matter/tsconfig": "0.0.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|