@wave3d/react 0.2.1 → 0.3.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/README.md CHANGED
@@ -49,6 +49,7 @@ Server-render your own poster by passing it as a child. The shell adopts it:
49
49
  | `preset` | `string \| () => Partial<StudioConfig>` | a name (lazy-loads the presets chunk) or a function preset (tree-shakeable) |
50
50
  | `config` | `Partial<StudioConfig>` | escape hatch, applied last |
51
51
  | `poster` | `string` | poster image shown before or instead of WebGL |
52
+ | `posterFit` | `"fill" \| "cover" \| "contain"` | poster `object-fit`; `"fill"` (default) matches the canvas |
52
53
  | `lazy` | `boolean` | defer the upgrade until visible |
53
54
  | `webgl` | `"auto" \| "force" \| "off"` | force or disable the WebGL upgrade |
54
55
  | `respectReducedMotion` | `boolean` | freeze on the poster for reduced-motion users |
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CSSProperties, ReactElement, ReactNode } from "react";
2
- import { BlendMode, ColorStop, FallbackReason, FallbackReason as FallbackReason$1, SnapshotOptions, StudioConfig, StudioConfig as StudioConfig$1, WaveHandle, WaveRenderer, WaveRenderer as WaveRenderer$1 } from "@wave3d/core";
2
+ import { BlendMode, ColorStop, FallbackReason, FallbackReason as FallbackReason$1, PosterFit, SnapshotOptions, StudioConfig, StudioConfig as StudioConfig$1, WaveHandle, WaveInteractionConfig, WaveRenderer, WaveRenderer as WaveRenderer$1 } from "@wave3d/core";
3
3
 
4
4
  //#region src/index.d.ts
5
5
  /** Flat props mapped onto the first wave. */
@@ -14,6 +14,9 @@ interface FlatWaveProps {
14
14
  opacity?: number;
15
15
  blendMode?: BlendMode;
16
16
  theme?: "solid" | "wireframe";
17
+ /** Per-wave interactivity for the first wave (hover field / click / bindings). Off when omitted.
18
+ * Scene-level shared inputs + scene bindings go through the `config` prop. */
19
+ interaction?: WaveInteractionConfig;
17
20
  }
18
21
  /** Flat props mapped onto the scene. */
19
22
  interface FlatSceneProps {
@@ -31,6 +34,8 @@ interface Wave3DProps extends FlatWaveProps, FlatSceneProps {
31
34
  /** Escape hatch: a full/partial config, applied last. Precedence: default ← preset ← flat props ← config. */
32
35
  config?: Partial<StudioConfig$1>;
33
36
  poster?: string;
37
+ /** Poster `object-fit`. Default `"fill"` (matches the canvas → seamless handoff); `"cover"` crops. */
38
+ posterFit?: PosterFit;
34
39
  lazy?: boolean;
35
40
  webgl?: "auto" | "force" | "off";
36
41
  respectReducedMotion?: boolean;
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ function buildConfig(base, props) {
39
39
  if (props.opacity !== void 0) w.opacity = props.opacity;
40
40
  if (props.blendMode !== void 0) w.blendMode = props.blendMode;
41
41
  if (props.theme !== void 0) w.theme = props.theme;
42
+ if (props.interaction !== void 0) w.interaction = props.interaction;
42
43
  if (props.background !== void 0) base.background = props.background;
43
44
  if (props.transparentBackground !== void 0) base.transparentBackground = props.transparentBackground;
44
45
  if (props.quality !== void 0) base.quality = props.quality;
@@ -71,7 +72,8 @@ function configKey(props) {
71
72
  "dprMax",
72
73
  "loopSeconds",
73
74
  "introRamp",
74
- "paused"
75
+ "paused",
76
+ "interaction"
75
77
  ]) if (props[k] !== void 0) flat[k] = props[k];
76
78
  return JSON.stringify({
77
79
  preset: typeof props.preset === "function" ? "fn" : props.preset,
@@ -97,6 +99,7 @@ function Wave3D(props) {
97
99
  let cancelled = false;
98
100
  const options = {
99
101
  poster: props.poster,
102
+ posterFit: props.posterFit,
100
103
  lazy: props.lazy,
101
104
  webgl: props.webgl,
102
105
  respectReducedMotion: props.respectReducedMotion,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport type { CSSProperties, ReactNode, ReactElement } from \"react\";\nimport { createWave, createDefaultConfig, makeStops } from \"@wave3d/core\";\nimport type {\n StudioConfig,\n WaveConfig,\n ColorStop,\n BlendMode,\n WaveHandle,\n WaveOptions,\n WaveRenderer,\n FallbackReason,\n} from \"@wave3d/core\";\n\n/** Flat props mapped onto the first wave. */\ninterface FlatWaveProps {\n palette?: string[] | ColorStop[];\n fiberCount?: number;\n fiberStrength?: number;\n sheen?: number;\n iridescence?: number;\n displaceAmount?: number;\n speed?: number;\n opacity?: number;\n blendMode?: BlendMode;\n theme?: \"solid\" | \"wireframe\";\n}\n\n/** Flat props mapped onto the scene. */\ninterface FlatSceneProps {\n background?: string;\n transparentBackground?: boolean;\n quality?: number;\n dprMax?: number;\n loopSeconds?: number;\n introRamp?: boolean;\n paused?: boolean;\n}\n\nexport interface Wave3DProps extends FlatWaveProps, FlatSceneProps {\n /** A preset: a function (tree-shakeable) or a name string (lazy-imports the presets chunk). */\n preset?: string | (() => Partial<StudioConfig>);\n /** Escape hatch: a full/partial config, applied last. Precedence: default ← preset ← flat props ← config. */\n config?: Partial<StudioConfig>;\n poster?: string;\n lazy?: boolean;\n webgl?: \"auto\" | \"force\" | \"off\";\n respectReducedMotion?: boolean;\n className?: string;\n style?: CSSProperties;\n /** Custom SSR poster markup, e.g. `<img data-wave3d-poster src=\"…\" />` — the shell adopts it. */\n children?: ReactNode;\n onReady?: (renderer: WaveRenderer) => void;\n onFallback?: (reason: FallbackReason) => void;\n}\n\nfunction normalizePalette(p: string[] | ColorStop[]): ColorStop[] {\n return p.length > 0 && typeof p[0] === \"string\" ? makeStops(p as string[]) : (p as ColorStop[]);\n}\n\n/** Resolve the base config: function preset (sync) → its config; string preset → lazy-load presets. */\nasync function resolveBase(preset: Wave3DProps[\"preset\"]): Promise<StudioConfig> {\n if (typeof preset === \"function\") return { ...createDefaultConfig(), ...preset() };\n if (typeof preset === \"string\") {\n const { PRESETS } = await import(\"@wave3d/core/presets\");\n const make = PRESETS[preset];\n if (make) return make();\n }\n return createDefaultConfig();\n}\n\n/** Base config we can build synchronously (function preset / default) — a string preset resolves later. */\nfunction syncBase(preset: Wave3DProps[\"preset\"]): StudioConfig {\n return typeof preset === \"function\"\n ? { ...createDefaultConfig(), ...preset() }\n : createDefaultConfig();\n}\n\n/** Apply the flat props (and the config escape hatch) onto a full base config. */\nfunction buildConfig(base: StudioConfig, props: Wave3DProps): Partial<StudioConfig> {\n const w: WaveConfig = base.waves[0];\n if (props.palette !== undefined) w.palette = normalizePalette(props.palette);\n if (props.fiberCount !== undefined) w.fiberCount = props.fiberCount;\n if (props.fiberStrength !== undefined) w.fiberStrength = props.fiberStrength;\n if (props.sheen !== undefined) w.sheen = props.sheen;\n if (props.iridescence !== undefined) w.iridescence = props.iridescence;\n if (props.displaceAmount !== undefined) w.displaceAmount = props.displaceAmount;\n if (props.speed !== undefined) w.speed = props.speed;\n if (props.opacity !== undefined) w.opacity = props.opacity;\n if (props.blendMode !== undefined) w.blendMode = props.blendMode;\n if (props.theme !== undefined) w.theme = props.theme;\n if (props.background !== undefined) base.background = props.background;\n if (props.transparentBackground !== undefined)\n base.transparentBackground = props.transparentBackground;\n if (props.quality !== undefined) base.quality = props.quality;\n if (props.dprMax !== undefined) base.dprMax = props.dprMax;\n if (props.loopSeconds !== undefined) base.loopSeconds = props.loopSeconds;\n if (props.introRamp !== undefined) base.introRamp = props.introRamp;\n if (props.paused !== undefined) base.paused = props.paused;\n return { ...base, ...props.config };\n}\n\n/** A stable string that changes whenever the resolved config would — keys the update effect. */\nfunction configKey(props: Wave3DProps): string {\n const flat: Record<string, unknown> = {};\n const keys = [\n \"palette\",\n \"fiberCount\",\n \"fiberStrength\",\n \"sheen\",\n \"iridescence\",\n \"displaceAmount\",\n \"speed\",\n \"opacity\",\n \"blendMode\",\n \"theme\",\n \"background\",\n \"transparentBackground\",\n \"quality\",\n \"dprMax\",\n \"loopSeconds\",\n \"introRamp\",\n \"paused\",\n ] as const;\n for (const k of keys) if (props[k] !== undefined) flat[k] = props[k];\n return JSON.stringify({\n preset: typeof props.preset === \"function\" ? \"fn\" : props.preset,\n flat,\n config: props.config,\n });\n}\n\n/**\n * A drop-in, self-optimizing gradient wave. Renders a `<div>` (SSR-safe; pass an\n * `<img data-wave3d-poster>` child for a server-rendered poster) and, on the client, mounts the\n * shell — poster-first, lazy, WebGL/reduced-motion/save-data aware, with the engine code-split out.\n */\nexport function Wave3D(props: Wave3DProps): ReactElement {\n const { className, style, children } = props;\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<WaveHandle | null>(null);\n // Keep the latest callbacks in a ref so they never force a remount.\n const cbRef = useRef<Pick<Wave3DProps, \"onReady\" | \"onFallback\">>({});\n cbRef.current.onReady = props.onReady;\n cbRef.current.onFallback = props.onFallback;\n\n // Mount once. StrictMode double-mount is safe: destroy() aborts a pending upgrade, and the\n // pre-upgrade create/destroy is DOM-only (poster + IntersectionObserver).\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n let cancelled = false;\n const options: WaveOptions = {\n poster: props.poster,\n lazy: props.lazy,\n webgl: props.webgl,\n respectReducedMotion: props.respectReducedMotion,\n onReady: (r) => cbRef.current.onReady?.(r),\n onFallback: (reason) => cbRef.current.onFallback?.(reason),\n };\n const handle = createWave(container, buildConfig(syncBase(props.preset), props), options);\n handleRef.current = handle;\n // A string preset resolves asynchronously; stage/set the real config once it loads.\n if (typeof props.preset === \"string\") {\n void resolveBase(props.preset).then((base) => {\n if (!cancelled && handleRef.current === handle) handle.set(buildConfig(base, props));\n });\n }\n return () => {\n cancelled = true;\n handle.destroy();\n handleRef.current = null;\n };\n // Mount-time only (options are captured once; config changes flow through the effect below).\n }, []);\n\n // Push config changes (flat props / config / preset) to the live handle.\n const key = configKey(props);\n useEffect(() => {\n const handle = handleRef.current;\n if (!handle) return;\n const apply = (base: StudioConfig): void => {\n if (handleRef.current === handle) handle.set(buildConfig(base, props));\n };\n if (typeof props.preset === \"string\") void resolveBase(props.preset).then(apply);\n else apply(syncBase(props.preset));\n // Re-runs only when the serialized config (`key`) changes; `props` is read fresh inside.\n }, [key]);\n\n return (\n <div ref={containerRef} className={className} style={style}>\n {children}\n </div>\n );\n}\n\nexport default Wave3D;\nexport type {\n StudioConfig,\n WaveHandle,\n WaveRenderer,\n FallbackReason,\n SnapshotOptions,\n} from \"@wave3d/core\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport type { CSSProperties, ReactNode, ReactElement } from \"react\";\nimport { createWave, createDefaultConfig, makeStops } from \"@wave3d/core\";\nimport type {\n StudioConfig,\n WaveConfig,\n ColorStop,\n BlendMode,\n WaveInteractionConfig,\n WaveHandle,\n WaveOptions,\n WaveRenderer,\n FallbackReason,\n PosterFit,\n} from \"@wave3d/core\";\n\n/** Flat props mapped onto the first wave. */\ninterface FlatWaveProps {\n palette?: string[] | ColorStop[];\n fiberCount?: number;\n fiberStrength?: number;\n sheen?: number;\n iridescence?: number;\n displaceAmount?: number;\n speed?: number;\n opacity?: number;\n blendMode?: BlendMode;\n theme?: \"solid\" | \"wireframe\";\n /** Per-wave interactivity for the first wave (hover field / click / bindings). Off when omitted.\n * Scene-level shared inputs + scene bindings go through the `config` prop. */\n interaction?: WaveInteractionConfig;\n}\n\n/** Flat props mapped onto the scene. */\ninterface FlatSceneProps {\n background?: string;\n transparentBackground?: boolean;\n quality?: number;\n dprMax?: number;\n loopSeconds?: number;\n introRamp?: boolean;\n paused?: boolean;\n}\n\nexport interface Wave3DProps extends FlatWaveProps, FlatSceneProps {\n /** A preset: a function (tree-shakeable) or a name string (lazy-imports the presets chunk). */\n preset?: string | (() => Partial<StudioConfig>);\n /** Escape hatch: a full/partial config, applied last. Precedence: default ← preset ← flat props ← config. */\n config?: Partial<StudioConfig>;\n poster?: string;\n /** Poster `object-fit`. Default `\"fill\"` (matches the canvas → seamless handoff); `\"cover\"` crops. */\n posterFit?: PosterFit;\n lazy?: boolean;\n webgl?: \"auto\" | \"force\" | \"off\";\n respectReducedMotion?: boolean;\n className?: string;\n style?: CSSProperties;\n /** Custom SSR poster markup, e.g. `<img data-wave3d-poster src=\"…\" />` — the shell adopts it. */\n children?: ReactNode;\n onReady?: (renderer: WaveRenderer) => void;\n onFallback?: (reason: FallbackReason) => void;\n}\n\nfunction normalizePalette(p: string[] | ColorStop[]): ColorStop[] {\n return p.length > 0 && typeof p[0] === \"string\" ? makeStops(p as string[]) : (p as ColorStop[]);\n}\n\n/** Resolve the base config: function preset (sync) → its config; string preset → lazy-load presets. */\nasync function resolveBase(preset: Wave3DProps[\"preset\"]): Promise<StudioConfig> {\n if (typeof preset === \"function\") return { ...createDefaultConfig(), ...preset() };\n if (typeof preset === \"string\") {\n const { PRESETS } = await import(\"@wave3d/core/presets\");\n const make = PRESETS[preset];\n if (make) return make();\n }\n return createDefaultConfig();\n}\n\n/** Base config we can build synchronously (function preset / default) — a string preset resolves later. */\nfunction syncBase(preset: Wave3DProps[\"preset\"]): StudioConfig {\n return typeof preset === \"function\"\n ? { ...createDefaultConfig(), ...preset() }\n : createDefaultConfig();\n}\n\n/** Apply the flat props (and the config escape hatch) onto a full base config. */\nfunction buildConfig(base: StudioConfig, props: Wave3DProps): Partial<StudioConfig> {\n const w: WaveConfig = base.waves[0];\n if (props.palette !== undefined) w.palette = normalizePalette(props.palette);\n if (props.fiberCount !== undefined) w.fiberCount = props.fiberCount;\n if (props.fiberStrength !== undefined) w.fiberStrength = props.fiberStrength;\n if (props.sheen !== undefined) w.sheen = props.sheen;\n if (props.iridescence !== undefined) w.iridescence = props.iridescence;\n if (props.displaceAmount !== undefined) w.displaceAmount = props.displaceAmount;\n if (props.speed !== undefined) w.speed = props.speed;\n if (props.opacity !== undefined) w.opacity = props.opacity;\n if (props.blendMode !== undefined) w.blendMode = props.blendMode;\n if (props.theme !== undefined) w.theme = props.theme;\n if (props.interaction !== undefined) w.interaction = props.interaction;\n if (props.background !== undefined) base.background = props.background;\n if (props.transparentBackground !== undefined)\n base.transparentBackground = props.transparentBackground;\n if (props.quality !== undefined) base.quality = props.quality;\n if (props.dprMax !== undefined) base.dprMax = props.dprMax;\n if (props.loopSeconds !== undefined) base.loopSeconds = props.loopSeconds;\n if (props.introRamp !== undefined) base.introRamp = props.introRamp;\n if (props.paused !== undefined) base.paused = props.paused;\n return { ...base, ...props.config };\n}\n\n/** A stable string that changes whenever the resolved config would — keys the update effect. */\nfunction configKey(props: Wave3DProps): string {\n const flat: Record<string, unknown> = {};\n const keys = [\n \"palette\",\n \"fiberCount\",\n \"fiberStrength\",\n \"sheen\",\n \"iridescence\",\n \"displaceAmount\",\n \"speed\",\n \"opacity\",\n \"blendMode\",\n \"theme\",\n \"background\",\n \"transparentBackground\",\n \"quality\",\n \"dprMax\",\n \"loopSeconds\",\n \"introRamp\",\n \"paused\",\n \"interaction\",\n ] as const;\n for (const k of keys) if (props[k] !== undefined) flat[k] = props[k];\n return JSON.stringify({\n preset: typeof props.preset === \"function\" ? \"fn\" : props.preset,\n flat,\n config: props.config,\n });\n}\n\n/**\n * A drop-in, self-optimizing gradient wave. Renders a `<div>` (SSR-safe; pass an\n * `<img data-wave3d-poster>` child for a server-rendered poster) and, on the client, mounts the\n * shell — poster-first, lazy, WebGL/reduced-motion/save-data aware, with the engine code-split out.\n */\nexport function Wave3D(props: Wave3DProps): ReactElement {\n const { className, style, children } = props;\n const containerRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<WaveHandle | null>(null);\n // Keep the latest callbacks in a ref so they never force a remount.\n const cbRef = useRef<Pick<Wave3DProps, \"onReady\" | \"onFallback\">>({});\n cbRef.current.onReady = props.onReady;\n cbRef.current.onFallback = props.onFallback;\n\n // Mount once. StrictMode double-mount is safe: destroy() aborts a pending upgrade, and the\n // pre-upgrade create/destroy is DOM-only (poster + IntersectionObserver).\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n let cancelled = false;\n const options: WaveOptions = {\n poster: props.poster,\n posterFit: props.posterFit,\n lazy: props.lazy,\n webgl: props.webgl,\n respectReducedMotion: props.respectReducedMotion,\n onReady: (r) => cbRef.current.onReady?.(r),\n onFallback: (reason) => cbRef.current.onFallback?.(reason),\n };\n const handle = createWave(container, buildConfig(syncBase(props.preset), props), options);\n handleRef.current = handle;\n // A string preset resolves asynchronously; stage/set the real config once it loads.\n if (typeof props.preset === \"string\") {\n void resolveBase(props.preset).then((base) => {\n if (!cancelled && handleRef.current === handle) handle.set(buildConfig(base, props));\n });\n }\n return () => {\n cancelled = true;\n handle.destroy();\n handleRef.current = null;\n };\n // Mount-time only (options are captured once; config changes flow through the effect below).\n }, []);\n\n // Push config changes (flat props / config / preset) to the live handle.\n const key = configKey(props);\n useEffect(() => {\n const handle = handleRef.current;\n if (!handle) return;\n const apply = (base: StudioConfig): void => {\n if (handleRef.current === handle) handle.set(buildConfig(base, props));\n };\n if (typeof props.preset === \"string\") void resolveBase(props.preset).then(apply);\n else apply(syncBase(props.preset));\n // Re-runs only when the serialized config (`key`) changes; `props` is read fresh inside.\n }, [key]);\n\n return (\n <div ref={containerRef} className={className} style={style}>\n {children}\n </div>\n );\n}\n\nexport default Wave3D;\nexport type {\n StudioConfig,\n WaveHandle,\n WaveRenderer,\n FallbackReason,\n SnapshotOptions,\n} from \"@wave3d/core\";\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wave3d/react",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Drop-in animated 3D gradient wave for React: the <Wave3D> component",
5
5
  "keywords": [
6
6
  "animation",
@@ -37,7 +37,7 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@wave3d/core": "^0.2.1"
40
+ "@wave3d/core": "^0.3.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/react": "^19.0.0",