@liquiddom/vue 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 +25 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +112 -0
- package/package.json +52 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @liquiddom/vue
|
|
2
|
+
|
|
3
|
+
## 0.2.0-rc.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
Initial public release. Vue 3.4+ bindings for [`liquiddom`](https://www.npmjs.com/package/liquiddom).
|
|
8
|
+
|
|
9
|
+
**Exports:**
|
|
10
|
+
- `<LiquidProvider :config>` — owns a `LiquidDOMInstance` via provide/inject
|
|
11
|
+
- `LiquidPlugin` — alternative install via `app.use(LiquidPlugin, config?)`
|
|
12
|
+
- `useLiquid()` — returns `Ref<LiquidDOMInstance | null>` (reactive)
|
|
13
|
+
- `useLiquidRef<T>(opts?)` — template ref that auto-observes/unobserves
|
|
14
|
+
- `<LiquidElement :as :liquid-type ...attrs>` — drop-in tag wrapping `useLiquidRef`
|
|
15
|
+
|
|
16
|
+
**Behavior:**
|
|
17
|
+
- SSR-safe (provider effect gated on `typeof window`)
|
|
18
|
+
- Pure TypeScript with `h()` render functions — no `.vue` SFC dependency in the published package
|
|
19
|
+
- `liquidType` is captured once on first attach (not reactive)
|
|
20
|
+
- `liquiddom` is a peer dependency (single WASM instance shared across the app)
|
|
21
|
+
|
|
22
|
+
### Peer dependencies
|
|
23
|
+
|
|
24
|
+
- `liquiddom@^0.2.0-rc.0`
|
|
25
|
+
- `vue@^3.4.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,115 @@
|
|
|
1
|
+
# @liquiddom/vue
|
|
2
|
+
|
|
3
|
+
Vue 3.4+ 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/vue vue
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`liquiddom` is a peer dependency so a single WASM instance is shared across your app.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```vue
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { LiquidProvider, LiquidElement } from "@liquiddom/vue";
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<LiquidProvider :config="{ capacity: 16, theme: { fusionRadius: 50 } }">
|
|
22
|
+
<LiquidElement as="button">Click me</LiquidElement>
|
|
23
|
+
<LiquidElement as="a" href="#">Also liquid</LiquidElement>
|
|
24
|
+
</LiquidProvider>
|
|
25
|
+
</template>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Three usage patterns
|
|
29
|
+
|
|
30
|
+
### 1. `<LiquidElement>` — drop-in tag
|
|
31
|
+
|
|
32
|
+
Easiest. Wraps `useLiquidRef` and observes on mount.
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<LiquidElement as="button" :liquid-type="4 /* Shake */">
|
|
36
|
+
Shaky
|
|
37
|
+
</LiquidElement>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. `useLiquidRef` — template ref on your own element
|
|
41
|
+
|
|
42
|
+
When you need full control over the element.
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { useLiquidRef } from "@liquiddom/vue";
|
|
47
|
+
|
|
48
|
+
const cardRef = useLiquidRef<HTMLDivElement>({ liquidType: 3 /* Dragged */ });
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div ref="cardRef" class="my-card">Drag me</div>
|
|
53
|
+
</template>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. `useLiquid` — imperative access
|
|
57
|
+
|
|
58
|
+
For one-off actions like `impulse()`, `tween()`, `spawnDroplet()`.
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<script setup lang="ts">
|
|
62
|
+
import { useLiquid, useLiquidRef } from "@liquiddom/vue";
|
|
63
|
+
|
|
64
|
+
const liquid = useLiquid();
|
|
65
|
+
const buttonRef = useLiquidRef<HTMLButtonElement>();
|
|
66
|
+
|
|
67
|
+
function splash() {
|
|
68
|
+
if (liquid.value && buttonRef.value) {
|
|
69
|
+
liquid.value.impulse(buttonRef.value, { magnitude: 20 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<button ref="buttonRef" @click="splash">Splash me</button>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Plugin install (alternative to `<LiquidProvider>`)
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { createApp } from "vue";
|
|
83
|
+
import { LiquidPlugin } from "@liquiddom/vue";
|
|
84
|
+
import App from "./App.vue";
|
|
85
|
+
|
|
86
|
+
createApp(App)
|
|
87
|
+
.use(LiquidPlugin, { capacity: 16 })
|
|
88
|
+
.mount("#app");
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The plugin attaches the instance app-wide; `useLiquid()` works without a surrounding `<LiquidProvider>`. The plugin monkey-patches `app.unmount` for cleanup.
|
|
92
|
+
|
|
93
|
+
## API
|
|
94
|
+
|
|
95
|
+
| Export | Description |
|
|
96
|
+
|--------|-------------|
|
|
97
|
+
| `<LiquidProvider :config>` | Owns a `LiquidDOMInstance` via provide/inject. `config` captured once on mount. |
|
|
98
|
+
| `LiquidPlugin` | Alternative install via `app.use(LiquidPlugin, config?)`. |
|
|
99
|
+
| `useLiquid()` | Returns `Ref<LiquidDOMInstance \| null>`. Reactive — updates when async create resolves. |
|
|
100
|
+
| `useLiquidRef<T>(opts?)` | Template ref that auto-observes / unobserves across lifecycle. Internal watch uses `flush: "post"` so the element is mounted before observe fires. |
|
|
101
|
+
| `<LiquidElement :as :liquid-type ...attrs>` | Convenience tag. Uses `inheritAttrs: false` + manual spread so class/style/events/data-* forward correctly. |
|
|
102
|
+
|
|
103
|
+
## Notes
|
|
104
|
+
|
|
105
|
+
- **SSR-safe** — provider effect gated on `typeof window`; `useLiquidRef`'s watch doesn't fire on the server.
|
|
106
|
+
- **No `.vue` SFC dependency in the package** — adapter is pure TypeScript using `h()` render functions, so it can be consumed without `vite-plugin-vue` configured for `node_modules`.
|
|
107
|
+
- **`liquidType` is captured at first attach** — `:liquid-type="reactive"` looks reactive but is read once. Change requires re-mount.
|
|
108
|
+
|
|
109
|
+
## Full options + imperative API
|
|
110
|
+
|
|
111
|
+
All options and methods exposed via `:config` and `useLiquid()` come straight from [`liquiddom`'s core API](https://www.npmjs.com/package/liquiddom).
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
[MIT](./LICENSE) © Dennis Schmock
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @liquiddom/vue — Vue 3.4+ bindings.
|
|
3
|
+
*/
|
|
4
|
+
import { type InjectionKey, type Plugin, type PropType, type Ref } from "vue";
|
|
5
|
+
import { type LiquidDOMInstance, type LiquidOptions } from "liquiddom";
|
|
6
|
+
/** Injection key used by both `<LiquidProvider>` and `LiquidPlugin`. */
|
|
7
|
+
export declare const LiquidKey: InjectionKey<Ref<LiquidDOMInstance | null>>;
|
|
8
|
+
export declare const LiquidProvider: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
9
|
+
config: {
|
|
10
|
+
type: PropType<LiquidOptions>;
|
|
11
|
+
};
|
|
12
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}>[] | undefined, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
15
|
+
config: {
|
|
16
|
+
type: PropType<LiquidOptions>;
|
|
17
|
+
};
|
|
18
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
19
|
+
export declare const LiquidPlugin: Plugin<[LiquidOptions?]>;
|
|
20
|
+
/** Read the live instance from injection. Null ref outside a provider/plugin. */
|
|
21
|
+
export declare function useLiquid(): Ref<LiquidDOMInstance | null>;
|
|
22
|
+
export interface UseLiquidRefOptions {
|
|
23
|
+
liquidType?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Template-ref composable: auto-observes when both element and instance are ready.
|
|
27
|
+
*
|
|
28
|
+
* `flush: "post"` is required so the watch fires AFTER Vue's DOM commit — observing
|
|
29
|
+
* earlier would miss the mounted node. `liquidType` is captured once at setup;
|
|
30
|
+
* changes to a `:liquidType="..."` binding after mount are NOT reactive (v1 limitation).
|
|
31
|
+
*/
|
|
32
|
+
export declare function useLiquidRef<T extends HTMLElement = HTMLElement>(opts?: UseLiquidRefOptions): Ref<T | null>;
|
|
33
|
+
export declare const LiquidElement: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
34
|
+
as: {
|
|
35
|
+
type: StringConstructor;
|
|
36
|
+
default: string;
|
|
37
|
+
};
|
|
38
|
+
liquidType: {
|
|
39
|
+
type: NumberConstructor;
|
|
40
|
+
default: undefined;
|
|
41
|
+
};
|
|
42
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
43
|
+
[key: string]: any;
|
|
44
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
45
|
+
as: {
|
|
46
|
+
type: StringConstructor;
|
|
47
|
+
default: string;
|
|
48
|
+
};
|
|
49
|
+
liquidType: {
|
|
50
|
+
type: NumberConstructor;
|
|
51
|
+
default: undefined;
|
|
52
|
+
};
|
|
53
|
+
}>> & Readonly<{}>, {
|
|
54
|
+
as: string;
|
|
55
|
+
liquidType: number;
|
|
56
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @liquiddom/vue — Vue 3.4+ bindings.
|
|
3
|
+
*/
|
|
4
|
+
import { defineComponent, h, inject, onBeforeUnmount, onMounted, provide, ref, watch, } from "vue";
|
|
5
|
+
import { LiquidDOM } from "liquiddom";
|
|
6
|
+
/** Injection key used by both `<LiquidProvider>` and `LiquidPlugin`. */
|
|
7
|
+
export const LiquidKey = Symbol("LiquidDOM");
|
|
8
|
+
export const LiquidProvider = defineComponent({
|
|
9
|
+
name: "LiquidProvider",
|
|
10
|
+
props: { config: { type: Object } },
|
|
11
|
+
setup(props, { slots }) {
|
|
12
|
+
const instance = ref(null);
|
|
13
|
+
provide(LiquidKey, instance);
|
|
14
|
+
let cancelled = false;
|
|
15
|
+
onMounted(async () => {
|
|
16
|
+
if (typeof window === "undefined")
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
const inst = await LiquidDOM.create(props.config);
|
|
20
|
+
if (cancelled) {
|
|
21
|
+
// Race path: unmount fired before create resolved — instance.value
|
|
22
|
+
// was never assigned, so this branch owns the destroy.
|
|
23
|
+
inst.destroy();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
instance.value = inst;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error("[liquiddom/vue] LiquidProvider failed to create instance:", err);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
onBeforeUnmount(() => {
|
|
33
|
+
cancelled = true;
|
|
34
|
+
instance.value?.destroy();
|
|
35
|
+
instance.value = null;
|
|
36
|
+
});
|
|
37
|
+
return () => slots.default?.();
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
export const LiquidPlugin = {
|
|
41
|
+
install(app, config) {
|
|
42
|
+
const instance = ref(null);
|
|
43
|
+
app.provide(LiquidKey, instance);
|
|
44
|
+
if (typeof window === "undefined")
|
|
45
|
+
return;
|
|
46
|
+
let unmounted = false;
|
|
47
|
+
LiquidDOM.create(config)
|
|
48
|
+
.then((inst) => {
|
|
49
|
+
if (unmounted) {
|
|
50
|
+
// Race path: app.unmount() fired before create() resolved.
|
|
51
|
+
// The unmount wrapper below could not destroy because instance.value was still null,
|
|
52
|
+
// so this branch owns the destroy. Do NOT delete without restoring the symmetric guard.
|
|
53
|
+
inst.destroy();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
instance.value = inst;
|
|
57
|
+
})
|
|
58
|
+
.catch((err) => {
|
|
59
|
+
console.error("[liquiddom/vue] LiquidPlugin failed to create instance:", err);
|
|
60
|
+
});
|
|
61
|
+
const origUnmount = app.unmount.bind(app);
|
|
62
|
+
app.unmount = () => {
|
|
63
|
+
unmounted = true;
|
|
64
|
+
// Race-symmetric destroy: handles "app.unmount() AFTER create() resolved".
|
|
65
|
+
// The "BEFORE create() resolved" path is handled in the .then() above. Both
|
|
66
|
+
// branches are required for full cleanup coverage.
|
|
67
|
+
instance.value?.destroy();
|
|
68
|
+
instance.value = null;
|
|
69
|
+
origUnmount();
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
/** Read the live instance from injection. Null ref outside a provider/plugin. */
|
|
74
|
+
export function useLiquid() {
|
|
75
|
+
return inject(LiquidKey, () => ref(null), true);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Template-ref composable: auto-observes when both element and instance are ready.
|
|
79
|
+
*
|
|
80
|
+
* `flush: "post"` is required so the watch fires AFTER Vue's DOM commit — observing
|
|
81
|
+
* earlier would miss the mounted node. `liquidType` is captured once at setup;
|
|
82
|
+
* changes to a `:liquidType="..."` binding after mount are NOT reactive (v1 limitation).
|
|
83
|
+
*/
|
|
84
|
+
export function useLiquidRef(opts) {
|
|
85
|
+
const instance = useLiquid();
|
|
86
|
+
// Vue's generic Ref<T> erases to Ref<UnwrapRef<T> | null> through `ref()`,
|
|
87
|
+
// which doesn't structurally match the function signature `Ref<T | null>`.
|
|
88
|
+
// Casting at the type-construction site keeps the public API contract clean.
|
|
89
|
+
const elRef = ref(null);
|
|
90
|
+
const liquidType = opts?.liquidType;
|
|
91
|
+
watch([elRef, instance], ([el, inst], _prev, onCleanup) => {
|
|
92
|
+
if (!el || !inst)
|
|
93
|
+
return;
|
|
94
|
+
inst.observe(el, liquidType);
|
|
95
|
+
onCleanup(() => {
|
|
96
|
+
inst.unobserve(el);
|
|
97
|
+
});
|
|
98
|
+
}, { flush: "post" });
|
|
99
|
+
return elRef;
|
|
100
|
+
}
|
|
101
|
+
export const LiquidElement = defineComponent({
|
|
102
|
+
name: "LiquidElement",
|
|
103
|
+
inheritAttrs: false,
|
|
104
|
+
props: {
|
|
105
|
+
as: { type: String, default: "div" },
|
|
106
|
+
liquidType: { type: Number, default: undefined },
|
|
107
|
+
},
|
|
108
|
+
setup(props, { slots, attrs }) {
|
|
109
|
+
const elRef = useLiquidRef({ liquidType: props.liquidType });
|
|
110
|
+
return () => h(props.as, { ...attrs, ref: elRef }, slots.default?.());
|
|
111
|
+
},
|
|
112
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liquiddom/vue",
|
|
3
|
+
"version": "0.2.0-rc.0",
|
|
4
|
+
"description": "Vue 3 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
|
+
"vue": "^3.4.0"
|
|
29
|
+
},
|
|
30
|
+
"author": "Dennis Schmock",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"homepage": "https://github.com/boringreliability/liquiddom/tree/master/packages/vue#readme",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/boringreliability/liquiddom.git",
|
|
36
|
+
"directory": "packages/vue"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/boringreliability/liquiddom/issues"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"liquiddom",
|
|
43
|
+
"vue",
|
|
44
|
+
"vue3",
|
|
45
|
+
"wasm",
|
|
46
|
+
"physics",
|
|
47
|
+
"animation"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|