@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 +23 -0
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +84 -0
- package/package.json +51 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|