@liquiddom/react 0.2.0-rc.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 ADDED
@@ -0,0 +1,23 @@
1
+ # @liquiddom/react
2
+
3
+ ## 0.2.0-rc.0
4
+
5
+ ### Minor Changes
6
+
7
+ Initial public release. React 18+ bindings for [`liquiddom`](https://www.npmjs.com/package/liquiddom).
8
+
9
+ **Exports:**
10
+ - `<LiquidProvider config?>` — owns a `LiquidDOMInstance` via React Context
11
+ - `useLiquid()` — read the instance (returns `null` before init / outside provider)
12
+ - `useLiquidRef<T>(opts?)` — callback ref that auto-observes/unobserves
13
+ - `<LiquidElement as? liquidType? ...rest>` — drop-in tag wrapping `useLiquidRef`
14
+
15
+ **Behavior:**
16
+ - Strict-mode safe (idempotent observe — React 18 double-mount in dev does not double-register)
17
+ - SSR-safe (provider effect gated on `typeof window`)
18
+ - `liquiddom` is a peer dependency (single WASM instance shared across the app)
19
+
20
+ ### Peer dependencies
21
+
22
+ - `liquiddom@^0.2.0-rc.0`
23
+ - `react@^18.0.0 || ^19.0.0`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dennis Schmock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @liquiddom/react
2
+
3
+ React 18+ bindings for [`liquiddom`](https://www.npmjs.com/package/liquiddom) — WASM-driven soft-body physics on real DOM elements.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install liquiddom @liquiddom/react react react-dom
9
+ ```
10
+
11
+ `liquiddom` is a peer dependency so a single WASM instance is shared across your app.
12
+
13
+ ## Quickstart
14
+
15
+ ```tsx
16
+ import { LiquidProvider, LiquidElement } from "@liquiddom/react";
17
+
18
+ export default function App() {
19
+ return (
20
+ <LiquidProvider config={{ capacity: 16, theme: { fusionRadius: 50 } }}>
21
+ <LiquidElement as="button">Click me</LiquidElement>
22
+ <LiquidElement as="a" href="#">Also liquid</LiquidElement>
23
+ </LiquidProvider>
24
+ );
25
+ }
26
+ ```
27
+
28
+ ## Three usage patterns
29
+
30
+ ### 1. `<LiquidElement>` — drop-in tag
31
+
32
+ Easiest. Wraps `useLiquidRef` and observes on mount.
33
+
34
+ ```tsx
35
+ <LiquidElement as="button" liquidType={4 /* Shake */}>
36
+ Shaky
37
+ </LiquidElement>
38
+ ```
39
+
40
+ ### 2. `useLiquidRef` — ref on your own element
41
+
42
+ When you need full control over the element (custom components, third-party libs).
43
+
44
+ ```tsx
45
+ import { useLiquidRef } from "@liquiddom/react";
46
+
47
+ function Card() {
48
+ const ref = useLiquidRef<HTMLDivElement>({ liquidType: 3 /* Dragged */ });
49
+ return <div ref={ref} className="my-card">Drag me</div>;
50
+ }
51
+ ```
52
+
53
+ ### 3. `useLiquid` — imperative access
54
+
55
+ For one-off actions like `impulse()`, `tween()`, `spawnDroplet()`.
56
+
57
+ ```tsx
58
+ import { useLiquid, useLiquidRef } from "@liquiddom/react";
59
+
60
+ function Button() {
61
+ const liquid = useLiquid();
62
+ const ref = useLiquidRef<HTMLButtonElement>();
63
+
64
+ return (
65
+ <button
66
+ ref={ref}
67
+ onClick={() => liquid?.impulse(ref.current!, { magnitude: 20 })}
68
+ >
69
+ Splash me
70
+ </button>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ## API
76
+
77
+ | Export | Description |
78
+ |--------|-------------|
79
+ | `<LiquidProvider config?>` | Owns a `LiquidDOMInstance` via React Context. `config` is captured once on mount — re-renders don't re-create the instance. |
80
+ | `useLiquid()` | Returns the active instance, or `null` before init / outside a provider. |
81
+ | `useLiquidRef<T>(opts?)` | Callback ref that auto-observes on mount, unobserves on unmount. `opts.liquidType` captured at first attach. |
82
+ | `<LiquidElement as? liquidType? ...rest>` | Convenience tag — internally uses `useLiquidRef`. Forwards all other props to the underlying element. |
83
+
84
+ ## Notes
85
+
86
+ - **Strict-mode safe** — `useLiquidRef` observes idempotently, so React 18's double-mount in dev doesn't double-register.
87
+ - **SSR-safe** — the provider effect is gated on `typeof window`, so server rendering is a no-op.
88
+ - **Single instance per provider** — nested providers each spin up their own WASM instance. For most apps, one provider at the root is correct.
89
+
90
+ ## Full options + imperative API
91
+
92
+ All options and methods exposed via `config` and `useLiquid()` come straight from [`liquiddom`'s core API](https://www.npmjs.com/package/liquiddom).
93
+
94
+ ## License
95
+
96
+ [MIT](./LICENSE) © Dennis Schmock
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @liquiddom/react — React 18+ bindings.
3
+ */
4
+ import { type ReactNode, type RefCallback, type HTMLAttributes } from "react";
5
+ import { type LiquidDOMInstance, type LiquidOptions } from "liquiddom";
6
+ /** Context value: the live instance, or null before init / after destroy. */
7
+ export declare const LiquidContext: import("react").Context<LiquidDOMInstance | null>;
8
+ /** Provider props. `config` is read ONCE on mount; subsequent changes are ignored. */
9
+ export interface LiquidProviderProps {
10
+ config?: LiquidOptions;
11
+ children: ReactNode;
12
+ }
13
+ export declare function LiquidProvider({ config, children }: LiquidProviderProps): JSX.Element;
14
+ /** Read the live instance from context. Null before init / outside a provider. */
15
+ export declare function useLiquid(): LiquidDOMInstance | null;
16
+ export interface UseLiquidRefOptions {
17
+ liquidType?: number;
18
+ }
19
+ /**
20
+ * Callback ref that auto-observes the attached element. Safe to use before
21
+ * the provider's instance is ready — observation deferred via state-trigger
22
+ * pattern (effect re-runs when `instance` becomes available).
23
+ *
24
+ * Strict-mode safe: cleanup unobserves, remount re-observes; `observe` is
25
+ * idempotent (W14 invariant).
26
+ *
27
+ * Closure-capture: the effect cleanup captures `el` and `instance` at the
28
+ * time the effect ran. A later `setEl(null)` does NOT change which element
29
+ * the captured cleanup unobserves.
30
+ */
31
+ export declare function useLiquidRef<T extends HTMLElement>(opts?: UseLiquidRefOptions): RefCallback<T>;
32
+ export interface LiquidElementProps {
33
+ as?: keyof JSX.IntrinsicElements;
34
+ liquidType?: number;
35
+ children?: ReactNode;
36
+ }
37
+ /**
38
+ * Convenience component: renders the chosen tag (default `div`) and auto-
39
+ * observes it via `useLiquidRef`. All HTML attributes are forwarded via spread.
40
+ *
41
+ * Polymorphic typing is intentionally loose for v1 — props are typed as
42
+ * `HTMLAttributes<HTMLElement>` so most common attributes typecheck across
43
+ * tags. Tag-specific attributes (`type` on input, `disabled` on button) may
44
+ * require a cast. Full polymorphic typing deferred to a follow-up ward.
45
+ */
46
+ export declare function LiquidElement(props: LiquidElementProps & HTMLAttributes<HTMLElement>): JSX.Element;
package/dist/index.js ADDED
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * @liquiddom/react — React 18+ bindings.
4
+ */
5
+ import { createContext, useContext, useEffect, useRef, useState, } from "react";
6
+ import { LiquidDOM, } from "liquiddom";
7
+ /** Context value: the live instance, or null before init / after destroy. */
8
+ export const LiquidContext = createContext(null);
9
+ export function LiquidProvider({ config, children }) {
10
+ const [instance, setInstance] = useState(null);
11
+ const configRef = useRef(config);
12
+ useEffect(() => {
13
+ if (typeof window === "undefined")
14
+ return; // SSR guard — never runs server-side
15
+ let cancelled = false;
16
+ let created = null;
17
+ LiquidDOM.create(configRef.current)
18
+ .then((inst) => {
19
+ if (cancelled) {
20
+ // Provider unmounted (or strict-mode cleanup) before create resolved.
21
+ inst.destroy();
22
+ return;
23
+ }
24
+ created = inst;
25
+ setInstance(inst);
26
+ })
27
+ .catch((err) => {
28
+ console.error("[liquiddom/react] LiquidProvider failed to create instance:", err);
29
+ });
30
+ return () => {
31
+ cancelled = true;
32
+ if (created)
33
+ created.destroy();
34
+ // No setInstance(null) on unmount — context is going away regardless,
35
+ // and post-unmount state updates generate noise in test output.
36
+ };
37
+ }, []); // empty deps — config captured by ref, ignored after mount
38
+ return _jsx(LiquidContext.Provider, { value: instance, children: children });
39
+ }
40
+ /** Read the live instance from context. Null before init / outside a provider. */
41
+ export function useLiquid() {
42
+ return useContext(LiquidContext);
43
+ }
44
+ /**
45
+ * Callback ref that auto-observes the attached element. Safe to use before
46
+ * the provider's instance is ready — observation deferred via state-trigger
47
+ * pattern (effect re-runs when `instance` becomes available).
48
+ *
49
+ * Strict-mode safe: cleanup unobserves, remount re-observes; `observe` is
50
+ * idempotent (W14 invariant).
51
+ *
52
+ * Closure-capture: the effect cleanup captures `el` and `instance` at the
53
+ * time the effect ran. A later `setEl(null)` does NOT change which element
54
+ * the captured cleanup unobserves.
55
+ */
56
+ export function useLiquidRef(opts) {
57
+ const instance = useLiquid();
58
+ const [el, setEl] = useState(null);
59
+ const liquidType = opts?.liquidType;
60
+ useEffect(() => {
61
+ if (!el || !instance)
62
+ return;
63
+ instance.observe(el, liquidType);
64
+ return () => {
65
+ instance.unobserve(el);
66
+ };
67
+ }, [el, instance, liquidType]);
68
+ return setEl;
69
+ }
70
+ /**
71
+ * Convenience component: renders the chosen tag (default `div`) and auto-
72
+ * observes it via `useLiquidRef`. All HTML attributes are forwarded via spread.
73
+ *
74
+ * Polymorphic typing is intentionally loose for v1 — props are typed as
75
+ * `HTMLAttributes<HTMLElement>` so most common attributes typecheck across
76
+ * tags. Tag-specific attributes (`type` on input, `disabled` on button) may
77
+ * require a cast. Full polymorphic typing deferred to a follow-up ward.
78
+ */
79
+ export function LiquidElement(props) {
80
+ const { as, liquidType, children, ...rest } = props;
81
+ const ref = useLiquidRef({ liquidType });
82
+ const Tag = (as ?? "div");
83
+ return (_jsx(Tag, { ref: ref, ...rest, children: children }));
84
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@liquiddom/react",
3
+ "version": "0.2.0-rc.0",
4
+ "description": "React bindings for liquiddom",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist/",
18
+ "README.md",
19
+ "CHANGELOG.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.build.json",
24
+ "test": "vitest run"
25
+ },
26
+ "peerDependencies": {
27
+ "liquiddom": "^0.2.0-rc.0",
28
+ "react": "^18.0.0 || ^19.0.0"
29
+ },
30
+ "author": "Dennis Schmock",
31
+ "license": "MIT",
32
+ "homepage": "https://github.com/boringreliability/liquiddom/tree/master/packages/react#readme",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/boringreliability/liquiddom.git",
36
+ "directory": "packages/react"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/boringreliability/liquiddom/issues"
40
+ },
41
+ "keywords": [
42
+ "liquiddom",
43
+ "react",
44
+ "wasm",
45
+ "physics",
46
+ "animation"
47
+ ],
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }