@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 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
@@ -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
+ }