liqvued 0.1.1 → 0.1.2
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 +15 -13
- package/dist/liqvued.css +1 -1
- package/dist/liqvued.mjs +159 -146
- package/dist/liqvued.umd.js +1 -1
- package/package.json +1 -1
- package/src/Liqvued.vue +65 -52
package/README.md
CHANGED
|
@@ -53,19 +53,21 @@ import { Liqvued } from 'liqvued'
|
|
|
53
53
|
|
|
54
54
|
## Props
|
|
55
55
|
|
|
56
|
-
| Prop | Type | Default | Description
|
|
57
|
-
|
|
58
|
-
| radius | number | 32 | Corner radius in pixels
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
56
|
+
| Prop | Type | Default | Description |
|
|
57
|
+
|-----------------|---------------------|------------|----------------------------------------------------------------------------------------|
|
|
58
|
+
| radius | number | 32 | Corner radius in pixels |
|
|
59
|
+
| borderRadius | string | — | Per-corner border-radius CSS value (e.g. `"10px 20px 10px 20px"`); overrides `radius` |
|
|
60
|
+
| bezel | number | 22 | Width of the displacement bezel in pixels |
|
|
61
|
+
| thickness | number | 42 | Maximum displacement magnitude in pixels |
|
|
62
|
+
| refraction | number | 1 | Refraction index multiplier |
|
|
63
|
+
| blur | number | 0.4 | CSS backdrop-filter blur amount |
|
|
64
|
+
| surface | string | `'convex'` | Glass surface profile shape |
|
|
65
|
+
| specularOpacity | number | 0.45 | Opacity of the specular highlight |
|
|
66
|
+
| glareAngle | number | -60 | Light source angle in degrees for specular highlight |
|
|
67
|
+
| glassBackground | string | — | Glass panel background color (auto-derived from `asProps.color` when available) |
|
|
68
|
+
| fallbackOnly | boolean | `false` | When `true`, disables the SVG displacement effect and uses only CSS blur |
|
|
69
|
+
| as | string \| Component | `'div'` | HTML tag or Vue component to render as (rendered as a child of the glass root) |
|
|
70
|
+
| asProps | object | `{}` | Props passed to the rendered element/component |
|
|
69
71
|
|
|
70
72
|
### Surface Types
|
|
71
73
|
|
package/dist/liqvued.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
.liquid-glass[data-v-
|
|
1
|
+
.liquid-glass[data-v-01d40041]{isolation:isolate;position:relative;overflow:hidden}.liquid-glass__svg[data-v-01d40041]{pointer-events:none;width:100%;height:100%;position:absolute;inset:0}@supports not ((-webkit-backdrop-filter:url("#x")) or (backdrop-filter:url("#x"))){.liquid-glass[data-v-01d40041]{-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}}
|
|
2
2
|
/*$vite$:1*/
|
package/dist/liqvued.mjs
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { computed as R, createBlock as
|
|
2
|
-
var
|
|
1
|
+
import { computed as R, createBlock as ie, createCommentVNode as ce, createElementBlock as Q, createElementVNode as v, defineComponent as de, guardReactiveProps as he, inject as fe, nextTick as X, normalizeProps as pe, normalizeStyle as ve, onBeforeUnmount as ge, onMounted as me, openBlock as L, provide as Me, ref as g, renderSlot as be, resolveDynamicComponent as ye, unref as _e, watch as xe, withCtx as ke } from "vue";
|
|
2
|
+
var we = {
|
|
3
3
|
key: 0,
|
|
4
4
|
class: "liquid-glass__svg",
|
|
5
5
|
"aria-hidden": "true"
|
|
6
|
-
},
|
|
6
|
+
}, Re = [
|
|
7
7
|
"href",
|
|
8
8
|
"width",
|
|
9
9
|
"height"
|
|
10
|
-
],
|
|
10
|
+
], Be = ["scale"], Ie = [
|
|
11
11
|
"href",
|
|
12
12
|
"width",
|
|
13
13
|
"height"
|
|
14
|
-
],
|
|
14
|
+
], Se = /* @__PURE__ */ de({
|
|
15
15
|
__name: "Liqvued",
|
|
16
16
|
props: {
|
|
17
17
|
as: { default: "div" },
|
|
18
18
|
asProps: { default: () => ({}) },
|
|
19
19
|
radius: { default: 32 },
|
|
20
|
+
borderRadius: {},
|
|
20
21
|
bezel: { default: 22 },
|
|
21
22
|
thickness: { default: 42 },
|
|
22
23
|
refraction: { default: 1 },
|
|
@@ -24,12 +25,16 @@ var ye = {
|
|
|
24
25
|
surface: { default: "convex" },
|
|
25
26
|
specularOpacity: { default: 0.45 },
|
|
26
27
|
glareAngle: { default: -60 },
|
|
27
|
-
glassBackground: { default: void 0 }
|
|
28
|
+
glassBackground: { default: void 0 },
|
|
29
|
+
fallbackOnly: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: !1
|
|
32
|
+
}
|
|
28
33
|
},
|
|
29
|
-
setup(
|
|
30
|
-
const a =
|
|
31
|
-
|
|
32
|
-
const
|
|
34
|
+
setup(d) {
|
|
35
|
+
const a = d, x = g(null), m = g(1), M = g(1), E = g(""), F = g(""), U = g(1), b = CSS.supports?.("backdrop-filter", "url(#x)") && /Chrome/.test(navigator.userAgent), T = `${`lg-${Math.random().toString(36).slice(2)}`}-filter`, A = fe("_liqvued", !1);
|
|
36
|
+
Me("_liqvued", !0);
|
|
37
|
+
const y = g(!1), G = {
|
|
33
38
|
0: 0,
|
|
34
39
|
sm: 4,
|
|
35
40
|
md: 8,
|
|
@@ -40,90 +45,91 @@ var ye = {
|
|
|
40
45
|
shaped: 16
|
|
41
46
|
};
|
|
42
47
|
function W() {
|
|
43
|
-
return
|
|
48
|
+
return x.value;
|
|
44
49
|
}
|
|
45
|
-
function
|
|
50
|
+
function k(e, t = 0, n = 1) {
|
|
46
51
|
return Math.max(t, Math.min(n, e));
|
|
47
52
|
}
|
|
48
|
-
function
|
|
49
|
-
return e =
|
|
53
|
+
function Y(e) {
|
|
54
|
+
return e = k(e), e * e * e * (e * (e * 6 - 15) + 10);
|
|
50
55
|
}
|
|
51
|
-
function
|
|
52
|
-
return Math.pow(1 - Math.pow(1 -
|
|
56
|
+
function B(e) {
|
|
57
|
+
return Math.pow(1 - Math.pow(1 - k(e), 4), 1 / 4);
|
|
53
58
|
}
|
|
54
|
-
function
|
|
55
|
-
if (a.surface === "concave") return 1 -
|
|
59
|
+
function N(e) {
|
|
60
|
+
if (a.surface === "concave") return 1 - B(e);
|
|
56
61
|
if (a.surface === "lip") {
|
|
57
|
-
const t =
|
|
58
|
-
return
|
|
62
|
+
const t = Y(e);
|
|
63
|
+
return B(e) * (1 - t) + (1 - B(e)) * t;
|
|
59
64
|
}
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
function Y(e) {
|
|
63
|
-
return (G(e + 1e-3) - G(e - 1e-3)) / (2 * 1e-3);
|
|
65
|
+
return B(e);
|
|
64
66
|
}
|
|
65
67
|
function Z(e) {
|
|
66
|
-
|
|
67
|
-
return Math.tan(s - g);
|
|
68
|
+
return (N(e + 1e-3) - N(e - 1e-3)) / (2 * 1e-3);
|
|
68
69
|
}
|
|
69
|
-
function ee(e
|
|
70
|
-
const
|
|
71
|
-
return
|
|
70
|
+
function ee(e) {
|
|
71
|
+
const r = Math.atan(e), c = 1 / 1.5 * Math.sin(r), l = Math.asin(k(c, -1, 1));
|
|
72
|
+
return Math.tan(r - l);
|
|
72
73
|
}
|
|
73
|
-
function te(e, t, n,
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
74
|
+
function te(e, t, n, r, c) {
|
|
75
|
+
const l = Math.min(c, Math.min(n, r) / 2), s = n / 2 - l, u = r / 2 - l, i = Math.abs(e - n / 2) - s, o = Math.abs(t - r / 2) - u;
|
|
76
|
+
return -(Math.min(Math.max(i, o), 0) + Math.hypot(Math.max(i, 0), Math.max(o, 0)) - l);
|
|
77
|
+
}
|
|
78
|
+
function ae(e, t, n, r) {
|
|
79
|
+
const c = Math.min(e, n - 1 - e), l = Math.min(t, r - 1 - t), s = 4, u = Math.pow(c, s) + Math.pow(l, s);
|
|
80
|
+
let i, o;
|
|
81
|
+
u > 1e-10 ? (i = (e > n / 2 ? 1 : -1) * (Math.pow(l, s) / u), o = (t > r / 2 ? 1 : -1) * (Math.pow(c, s) / u)) : (i = e > n / 2 ? 1 : -1, o = t > r / 2 ? 1 : -1);
|
|
82
|
+
const _ = Math.hypot(i, o);
|
|
83
|
+
return _ < 1e-10 ? {
|
|
84
|
+
x: 0,
|
|
85
|
+
y: -1
|
|
86
|
+
} : {
|
|
87
|
+
x: i / _,
|
|
88
|
+
y: o / _
|
|
84
89
|
};
|
|
85
90
|
}
|
|
86
|
-
function
|
|
91
|
+
function V(e) {
|
|
87
92
|
return e.toDataURL("image/png");
|
|
88
93
|
}
|
|
89
|
-
function
|
|
90
|
-
|
|
94
|
+
function C() {
|
|
95
|
+
if (a.fallbackOnly) return;
|
|
96
|
+
const e = Math.max(1, Math.round(m.value)), t = Math.max(1, Math.round(M.value)), n = document.createElement("canvas");
|
|
91
97
|
n.width = e, n.height = t;
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
let
|
|
96
|
-
const
|
|
98
|
+
const r = document.createElement("canvas");
|
|
99
|
+
r.width = e, r.height = t;
|
|
100
|
+
const c = n.getContext("2d"), l = r.getContext("2d"), s = c.createImageData(e, t), u = l.createImageData(e, t), i = [];
|
|
101
|
+
let o = 1;
|
|
102
|
+
const _ = {
|
|
97
103
|
x: Math.cos(a.glareAngle * Math.PI / 180),
|
|
98
104
|
y: Math.sin(a.glareAngle * Math.PI / 180)
|
|
99
105
|
};
|
|
100
|
-
for (let
|
|
101
|
-
const
|
|
102
|
-
let
|
|
103
|
-
if (
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
106
|
+
for (let h = 0; h < t; h++) for (let f = 0; f < e; f++) {
|
|
107
|
+
const w = te(f, h, e, t, q.value), p = k(w / a.bezel);
|
|
108
|
+
let z = 0, D = 0, J = 0;
|
|
109
|
+
if (w >= 0 && w < a.bezel) {
|
|
110
|
+
const $ = ae(f, h, e, t), K = ee(Z(p)) * a.thickness * (1 - p) * a.refraction;
|
|
111
|
+
z = -$.x * K, D = -$.y * K, o = Math.max(o, Math.abs(z), Math.abs(D));
|
|
112
|
+
const ue = k($.x * _.x + $.y * _.y, 0, 1);
|
|
113
|
+
J = Math.pow(ue, 18) * a.specularOpacity;
|
|
108
114
|
}
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
115
|
+
i.push([z, D]);
|
|
116
|
+
const S = (h * e + f) * 4, le = Math.round(J * 255);
|
|
117
|
+
u.data[S] = 255, u.data[S + 1] = 255, u.data[S + 2] = 255, u.data[S + 3] = le;
|
|
112
118
|
}
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
}),
|
|
119
|
+
i.forEach(([h, f], w) => {
|
|
120
|
+
const p = w * 4;
|
|
121
|
+
s.data[p] = Math.round(128 + h / o * 127), s.data[p + 1] = Math.round(128 + f / o * 127), s.data[p + 2] = 128, s.data[p + 3] = 255;
|
|
122
|
+
}), c.putImageData(s, 0, 0), l.putImageData(u, 0, 0), E.value = V(n), F.value = V(r), U.value = o;
|
|
117
123
|
}
|
|
118
|
-
function
|
|
124
|
+
function j() {
|
|
119
125
|
const e = W();
|
|
120
|
-
e && (
|
|
126
|
+
e && (m.value = Math.max(1, e.offsetWidth), M.value = Math.max(1, e.offsetHeight));
|
|
121
127
|
}
|
|
122
|
-
const
|
|
128
|
+
const H = b ? "rgba(255, 255, 255, 0.08)" : "rgba(255, 255, 255, 0.22)", q = R(() => {
|
|
123
129
|
if (a.radius !== 32) return a.radius;
|
|
124
130
|
const e = a.asProps;
|
|
125
|
-
return typeof e.rounded == "string" && e.rounded in
|
|
126
|
-
}),
|
|
131
|
+
return typeof e.rounded == "string" && e.rounded in G ? G[e.rounded] : a.radius;
|
|
132
|
+
}), ne = R(() => a.borderRadius || `${q.value}px`), re = /* @__PURE__ */ new Set([
|
|
127
133
|
"primary",
|
|
128
134
|
"secondary",
|
|
129
135
|
"accent",
|
|
@@ -134,107 +140,114 @@ var ye = {
|
|
|
134
140
|
"surface",
|
|
135
141
|
"background"
|
|
136
142
|
]);
|
|
137
|
-
function
|
|
138
|
-
if (
|
|
143
|
+
function oe(e, t) {
|
|
144
|
+
if (re.has(e)) return `rgba(var(--v-theme-${e}), ${t})`;
|
|
139
145
|
if (e.startsWith("#")) {
|
|
140
146
|
const n = e.replace("#", "");
|
|
141
147
|
return `rgba(${parseInt(n.slice(0, 2), 16)}, ${parseInt(n.slice(2, 4), 16)}, ${parseInt(n.slice(4, 6), 16)}, ${t})`;
|
|
142
148
|
}
|
|
143
|
-
return e.startsWith("rgb") ? e.replace("rgb(", "rgba(").replace(")", `, ${t})`) :
|
|
149
|
+
return e.startsWith("rgb") ? e.replace("rgb(", "rgba(").replace(")", `, ${t})`) : H;
|
|
144
150
|
}
|
|
145
|
-
const
|
|
151
|
+
const O = R(() => {
|
|
146
152
|
if (a.glassBackground !== void 0) return a.glassBackground;
|
|
147
153
|
const e = a.asProps;
|
|
148
|
-
return typeof e.color == "string" ?
|
|
149
|
-
}),
|
|
150
|
-
borderRadius: `${
|
|
151
|
-
...
|
|
152
|
-
...
|
|
153
|
-
...
|
|
154
|
-
backdropFilter:
|
|
155
|
-
WebkitBackdropFilter:
|
|
154
|
+
return typeof e.color == "string" ? oe(e.color, 0.6) : H;
|
|
155
|
+
}), I = R(() => a.fallbackOnly || !b ? "blur(12px)" : `url(#${T}) blur(${a.blur}px)`), se = R(() => ({
|
|
156
|
+
borderRadius: `${ne.value}`,
|
|
157
|
+
...O.value !== void 0 ? { backgroundColor: O.value } : {},
|
|
158
|
+
...O.value !== void 0 ? { boxShadow: ["inset 0 0 0 1px rgba(255, 255, 255, 0.28)", "0 18px 48px rgba(0, 0, 0, 0.18)"].join(", ") } : {},
|
|
159
|
+
...y.value && !A ? {
|
|
160
|
+
backdropFilter: I.value,
|
|
161
|
+
WebkitBackdropFilter: I.value
|
|
156
162
|
} : {},
|
|
157
|
-
...
|
|
158
|
-
backdropFilter:
|
|
159
|
-
WebkitBackdropFilter:
|
|
163
|
+
...y.value && A && !b ? {
|
|
164
|
+
backdropFilter: I.value,
|
|
165
|
+
WebkitBackdropFilter: I.value
|
|
160
166
|
} : {}
|
|
161
167
|
}));
|
|
162
|
-
let
|
|
163
|
-
return
|
|
164
|
-
if (await
|
|
165
|
-
|
|
166
|
-
|
|
168
|
+
let P = null;
|
|
169
|
+
return me(async () => {
|
|
170
|
+
if (await X(), b) {
|
|
171
|
+
j(), C(), await X(), y.value = !0, P = new ResizeObserver(() => {
|
|
172
|
+
j(), C(), y.value = !0;
|
|
167
173
|
});
|
|
168
174
|
const e = W();
|
|
169
|
-
e &&
|
|
170
|
-
} else
|
|
171
|
-
}),
|
|
172
|
-
|
|
175
|
+
e && P.observe(e);
|
|
176
|
+
} else y.value = !0;
|
|
177
|
+
}), xe(() => [
|
|
178
|
+
q.value,
|
|
173
179
|
a.bezel,
|
|
174
180
|
a.thickness,
|
|
175
181
|
a.refraction,
|
|
176
182
|
a.surface,
|
|
177
183
|
a.specularOpacity,
|
|
178
|
-
a.glareAngle
|
|
184
|
+
a.glareAngle,
|
|
185
|
+
a.fallbackOnly
|
|
179
186
|
], () => {
|
|
180
|
-
|
|
181
|
-
}),
|
|
182
|
-
|
|
183
|
-
}), (e, t) => (
|
|
187
|
+
b && C(), y.value = !0;
|
|
188
|
+
}), ge(() => {
|
|
189
|
+
P?.disconnect();
|
|
190
|
+
}), (e, t) => (L(), Q("div", {
|
|
184
191
|
ref_key: "root",
|
|
185
|
-
ref:
|
|
186
|
-
class: "liquid-glass"
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
ref: x,
|
|
193
|
+
class: "liquid-glass",
|
|
194
|
+
style: ve(se.value)
|
|
195
|
+
}, [_e(b) ? (L(), Q("svg", we, [v("defs", null, [v("filter", {
|
|
196
|
+
id: T,
|
|
197
|
+
"color-interpolation-filters": "sRGB",
|
|
198
|
+
filterUnits: "userSpaceOnUse",
|
|
199
|
+
x: "0",
|
|
200
|
+
y: "0",
|
|
201
|
+
width: "10000",
|
|
202
|
+
height: "10000"
|
|
203
|
+
}, [
|
|
204
|
+
v("feImage", {
|
|
205
|
+
href: E.value,
|
|
206
|
+
x: "0",
|
|
207
|
+
y: "0",
|
|
208
|
+
width: m.value,
|
|
209
|
+
height: M.value,
|
|
210
|
+
result: "displacement_map"
|
|
211
|
+
}, null, 8, Re),
|
|
212
|
+
t[0] || (t[0] = v("feGaussianBlur", {
|
|
213
|
+
in: "displacement_map",
|
|
214
|
+
stdDeviation: "2",
|
|
215
|
+
result: "smooth_disp"
|
|
216
|
+
}, null, -1)),
|
|
217
|
+
v("feDisplacementMap", {
|
|
218
|
+
in: "SourceGraphic",
|
|
219
|
+
in2: "smooth_disp",
|
|
220
|
+
scale: U.value,
|
|
221
|
+
xChannelSelector: "R",
|
|
222
|
+
yChannelSelector: "G",
|
|
223
|
+
result: "refracted"
|
|
224
|
+
}, null, 8, Be),
|
|
225
|
+
v("feImage", {
|
|
226
|
+
href: F.value,
|
|
192
227
|
x: "0",
|
|
193
228
|
y: "0",
|
|
194
|
-
width:
|
|
195
|
-
height:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
w("feDisplacementMap", {
|
|
206
|
-
in: "SourceGraphic",
|
|
207
|
-
in2: "displacement_map",
|
|
208
|
-
scale: z.value,
|
|
209
|
-
xChannelSelector: "R",
|
|
210
|
-
yChannelSelector: "G",
|
|
211
|
-
result: "refracted"
|
|
212
|
-
}, null, 8, ke),
|
|
213
|
-
w("feImage", {
|
|
214
|
-
href: U.value,
|
|
215
|
-
x: "0",
|
|
216
|
-
y: "0",
|
|
217
|
-
width: _.value,
|
|
218
|
-
height: y.value,
|
|
219
|
-
result: "specular"
|
|
220
|
-
}, null, 8, we),
|
|
221
|
-
t[0] || (t[0] = w("feBlend", {
|
|
222
|
-
in: "refracted",
|
|
223
|
-
in2: "specular",
|
|
224
|
-
mode: "screen"
|
|
225
|
-
}, null, -1))
|
|
226
|
-
])])])) : ue("", !0), ge(e.$slots, "default", {}, void 0, !0)]),
|
|
229
|
+
width: m.value,
|
|
230
|
+
height: M.value,
|
|
231
|
+
result: "specular"
|
|
232
|
+
}, null, 8, Ie),
|
|
233
|
+
t[1] || (t[1] = v("feBlend", {
|
|
234
|
+
in: "refracted",
|
|
235
|
+
in2: "specular",
|
|
236
|
+
mode: "screen"
|
|
237
|
+
}, null, -1))
|
|
238
|
+
])])])) : ce("", !0), (L(), ie(ye(d.as), pe(he(d.asProps)), {
|
|
239
|
+
default: ke(() => [be(e.$slots, "default", {}, void 0, !0)]),
|
|
227
240
|
_: 3
|
|
228
|
-
}, 16,
|
|
241
|
+
}, 16))], 4));
|
|
229
242
|
}
|
|
230
|
-
}),
|
|
231
|
-
const
|
|
232
|
-
for (const [
|
|
233
|
-
return
|
|
234
|
-
},
|
|
235
|
-
|
|
243
|
+
}), $e = (d, a) => {
|
|
244
|
+
const x = d.__vccOpts || d;
|
|
245
|
+
for (const [m, M] of a) x[m] = M;
|
|
246
|
+
return x;
|
|
247
|
+
}, Ce = /* @__PURE__ */ $e(Se, [["__scopeId", "data-v-01d40041"]]), Oe = { install(d) {
|
|
248
|
+
d.component("Liqvued", Ce);
|
|
236
249
|
} };
|
|
237
250
|
export {
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
Ce as Liqvued,
|
|
252
|
+
Oe as default
|
|
240
253
|
};
|
package/dist/liqvued.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(f,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],t):(f=typeof globalThis<"u"?globalThis:f||self,t(f.Liqvued={},f.Vue))})(this,function(f,t){Object.defineProperties(f,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var K={key:0,class:"liquid-glass__svg","aria-hidden":"true"},Q=["href","width","height"],X=["scale"],Y=["href","width","height"],Z=(0,t.defineComponent)({__name:"Liqvued",props:{as:{default:"div"},asProps:{default:()=>({})},radius:{default:32},borderRadius:{},bezel:{default:22},thickness:{default:42},refraction:{default:1},blur:{default:.4},surface:{default:"convex"},specularOpacity:{default:.45},glareAngle:{default:-60},glassBackground:{default:void 0},fallbackOnly:{type:Boolean,default:!1}},setup(h){const n=h,k=(0,t.ref)(null),v=(0,t.ref)(1),M=(0,t.ref)(1),V=(0,t.ref)(""),L=(0,t.ref)(""),N=(0,t.ref)(1),b=CSS.supports?.("backdrop-filter","url(#x)")&&/Chrome/.test(navigator.userAgent),z=`${`lg-${Math.random().toString(36).slice(2)}`}-filter`,D=(0,t.inject)("_liqvued",!1);(0,t.provide)("_liqvued",!0);const y=(0,t.ref)(!1),F={0:0,sm:4,md:8,lg:12,xl:16,pill:9999,circle:9999,shaped:16};function U(){return k.value}function x(e,a=0,r=1){return Math.max(a,Math.min(r,e))}function ae(e){return e=x(e),e*e*e*(e*(e*6-15)+10)}function B(e){return Math.pow(1-Math.pow(1-x(e),4),1/4)}function A(e){if(n.surface==="concave")return 1-B(e);if(n.surface==="lip"){const a=ae(e);return B(e)*(1-a)+(1-B(e))*a}return B(e)}function ne(e){return(A(e+.001)-A(e-.001))/(2*.001)}function re(e){const o=Math.atan(e),d=1/1.5*Math.sin(o),i=Math.asin(x(d,-1,1));return Math.tan(o-i)}function oe(e,a,r,o,d){const i=Math.min(d,Math.min(r,o)/2),s=r/2-i,c=o/2-i,u=Math.abs(e-r/2)-s,l=Math.abs(a-o/2)-c;return-(Math.min(Math.max(u,l),0)+Math.hypot(Math.max(u,0),Math.max(l,0))-i)}function le(e,a,r,o){const d=Math.min(e,r-1-e),i=Math.min(a,o-1-a),s=4,c=Math.pow(d,s)+Math.pow(i,s);let u,l;c>1e-10?(u=(e>r/2?1:-1)*(Math.pow(i,s)/c),l=(a>o/2?1:-1)*(Math.pow(d,s)/c)):(u=e>r/2?1:-1,l=a>o/2?1:-1);const _=Math.hypot(u,l);return _<1e-10?{x:0,y:-1}:{x:u/_,y:l/_}}function G(e){return e.toDataURL("image/png")}function E(){if(n.fallbackOnly)return;const e=Math.max(1,Math.round(v.value)),a=Math.max(1,Math.round(M.value)),r=document.createElement("canvas");r.width=e,r.height=a;const o=document.createElement("canvas");o.width=e,o.height=a;const d=r.getContext("2d"),i=o.getContext("2d"),s=d.createImageData(e,a),c=i.createImageData(e,a),u=[];let l=1;const _={x:Math.cos(n.glareAngle*Math.PI/180),y:Math.sin(n.glareAngle*Math.PI/180)};for(let p=0;p<a;p++)for(let m=0;m<e;m++){const w=oe(m,p,e,a,$.value),g=x(w/n.bezel);let O=0,P=0,H=0;if(w>=0&&w<n.bezel){const I=le(m,p,e,a),J=re(ne(g))*n.thickness*(1-g)*n.refraction;O=-I.x*J,P=-I.y*J,l=Math.max(l,Math.abs(O),Math.abs(P));const fe=x(I.x*_.x+I.y*_.y,0,1);H=Math.pow(fe,18)*n.specularOpacity}u.push([O,P]);const R=(p*e+m)*4,de=Math.round(H*255);c.data[R]=255,c.data[R+1]=255,c.data[R+2]=255,c.data[R+3]=de}u.forEach(([p,m],w)=>{const g=w*4;s.data[g]=Math.round(128+p/l*127),s.data[g+1]=Math.round(128+m/l*127),s.data[g+2]=128,s.data[g+3]=255}),d.putImageData(s,0,0),i.putImageData(c,0,0),V.value=G(r),L.value=G(o),N.value=l}function W(){const e=U();e&&(v.value=Math.max(1,e.offsetWidth),M.value=Math.max(1,e.offsetHeight))}const j=b?"rgba(255, 255, 255, 0.08)":"rgba(255, 255, 255, 0.22)",$=(0,t.computed)(()=>{if(n.radius!==32)return n.radius;const e=n.asProps;return typeof e.rounded=="string"&&e.rounded in F?F[e.rounded]:n.radius}),se=(0,t.computed)(()=>n.borderRadius||`${$.value}px`),ie=new Set(["primary","secondary","accent","info","warning","error","success","surface","background"]);function ce(e,a){if(ie.has(e))return`rgba(var(--v-theme-${e}), ${a})`;if(e.startsWith("#")){const r=e.replace("#","");return`rgba(${parseInt(r.slice(0,2),16)}, ${parseInt(r.slice(2,4),16)}, ${parseInt(r.slice(4,6),16)}, ${a})`}return e.startsWith("rgb")?e.replace("rgb(","rgba(").replace(")",`, ${a})`):j}const q=(0,t.computed)(()=>{if(n.glassBackground!==void 0)return n.glassBackground;const e=n.asProps;return typeof e.color=="string"?ce(e.color,.6):j}),S=(0,t.computed)(()=>n.fallbackOnly||!b?"blur(12px)":`url(#${z}) blur(${n.blur}px)`),ue=(0,t.computed)(()=>({borderRadius:`${se.value}`,...q.value!==void 0?{backgroundColor:q.value}:{},...q.value!==void 0?{boxShadow:["inset 0 0 0 1px rgba(255, 255, 255, 0.28)","0 18px 48px rgba(0, 0, 0, 0.18)"].join(", ")}:{},...y.value&&!D?{backdropFilter:S.value,WebkitBackdropFilter:S.value}:{},...y.value&&D&&!b?{backdropFilter:S.value,WebkitBackdropFilter:S.value}:{}}));let C=null;return(0,t.onMounted)(async()=>{if(await(0,t.nextTick)(),b){W(),E(),await(0,t.nextTick)(),y.value=!0,C=new ResizeObserver(()=>{W(),E(),y.value=!0});const e=U();e&&C.observe(e)}else y.value=!0}),(0,t.watch)(()=>[$.value,n.bezel,n.thickness,n.refraction,n.surface,n.specularOpacity,n.glareAngle,n.fallbackOnly],()=>{b&&E(),y.value=!0}),(0,t.onBeforeUnmount)(()=>{C?.disconnect()}),(e,a)=>((0,t.openBlock)(),(0,t.createElementBlock)("div",{ref_key:"root",ref:k,class:"liquid-glass",style:(0,t.normalizeStyle)(ue.value)},[(0,t.unref)(b)?((0,t.openBlock)(),(0,t.createElementBlock)("svg",K,[(0,t.createElementVNode)("defs",null,[(0,t.createElementVNode)("filter",{id:z,"color-interpolation-filters":"sRGB",filterUnits:"userSpaceOnUse",x:"0",y:"0",width:"10000",height:"10000"},[(0,t.createElementVNode)("feImage",{href:V.value,x:"0",y:"0",width:v.value,height:M.value,result:"displacement_map"},null,8,Q),a[0]||(a[0]=(0,t.createElementVNode)("feGaussianBlur",{in:"displacement_map",stdDeviation:"2",result:"smooth_disp"},null,-1)),(0,t.createElementVNode)("feDisplacementMap",{in:"SourceGraphic",in2:"smooth_disp",scale:N.value,xChannelSelector:"R",yChannelSelector:"G",result:"refracted"},null,8,X),(0,t.createElementVNode)("feImage",{href:L.value,x:"0",y:"0",width:v.value,height:M.value,result:"specular"},null,8,Y),a[1]||(a[1]=(0,t.createElementVNode)("feBlend",{in:"refracted",in2:"specular",mode:"screen"},null,-1))])])])):(0,t.createCommentVNode)("",!0),((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(h.as),(0,t.normalizeProps)((0,t.guardReactiveProps)(h.asProps)),{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,"default",{},void 0,!0)]),_:3},16))],4))}}),ee=(h,n)=>{const k=h.__vccOpts||h;for(const[v,M]of n)k[v]=M;return k},T=ee(Z,[["__scopeId","data-v-01d40041"]]),te={install(h){h.component("Liqvued",T)}};f.Liqvued=T,f.default=te});
|
package/package.json
CHANGED
package/src/Liqvued.vue
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {
|
|
3
3
|
type Component,
|
|
4
|
-
type ComponentPublicInstance,
|
|
5
4
|
computed,
|
|
6
5
|
inject,
|
|
7
6
|
nextTick,
|
|
@@ -17,6 +16,7 @@ const props = withDefaults(defineProps<{
|
|
|
17
16
|
as?: string | Component
|
|
18
17
|
asProps?: object
|
|
19
18
|
radius?: number
|
|
19
|
+
borderRadius?: string
|
|
20
20
|
bezel?: number
|
|
21
21
|
thickness?: number
|
|
22
22
|
refraction?: number
|
|
@@ -25,9 +25,11 @@ const props = withDefaults(defineProps<{
|
|
|
25
25
|
specularOpacity?: number
|
|
26
26
|
glareAngle?: number
|
|
27
27
|
glassBackground?: string
|
|
28
|
+
fallbackOnly?: boolean
|
|
28
29
|
}>(), {
|
|
29
30
|
as: 'div',
|
|
30
31
|
asProps: () => ({}),
|
|
32
|
+
fallbackOnly: false,
|
|
31
33
|
radius: 32,
|
|
32
34
|
bezel: 22,
|
|
33
35
|
thickness: 42,
|
|
@@ -39,7 +41,7 @@ const props = withDefaults(defineProps<{
|
|
|
39
41
|
glassBackground: undefined,
|
|
40
42
|
})
|
|
41
43
|
|
|
42
|
-
const root = ref<HTMLElement |
|
|
44
|
+
const root = ref<HTMLElement | null>(null)
|
|
43
45
|
|
|
44
46
|
const width = ref(1)
|
|
45
47
|
const height = ref(1)
|
|
@@ -54,6 +56,11 @@ const id = `lg-${Math.random().toString(36).slice(2)}`
|
|
|
54
56
|
const filterId = `${id}-filter`
|
|
55
57
|
const isNested = inject('_liqvued', false)
|
|
56
58
|
provide('_liqvued', true)
|
|
59
|
+
|
|
60
|
+
if (import.meta.env.DEV && typeof props.as !== 'string') {
|
|
61
|
+
console.warn('[Liqvued] `as` prop should be a string (HTML tag name). For Vue components, use Liqvued as a wrapper instead of `as`.')
|
|
62
|
+
}
|
|
63
|
+
|
|
57
64
|
const glassReady = ref(false)
|
|
58
65
|
|
|
59
66
|
const roundedMap: Record<string, number> = {
|
|
@@ -68,9 +75,7 @@ const roundedMap: Record<string, number> = {
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
function getRootEl(): HTMLElement | null {
|
|
71
|
-
|
|
72
|
-
if (root.value instanceof Element) return root.value as HTMLElement
|
|
73
|
-
return (root.value as ComponentPublicInstance).$el ?? null
|
|
78
|
+
return root.value
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
function clamp(v: number, min = 0, max = 1) {
|
|
@@ -120,20 +125,16 @@ function distanceToRoundedRectEdge(
|
|
|
120
125
|
h: number,
|
|
121
126
|
r: number,
|
|
122
127
|
) {
|
|
123
|
-
const
|
|
124
|
-
const
|
|
128
|
+
const hr = Math.min(r, Math.min(w, h) / 2)
|
|
129
|
+
const hw = w / 2 - hr
|
|
130
|
+
const hh = h / 2 - hr
|
|
131
|
+
const ax = Math.abs(x - w / 2) - hw
|
|
132
|
+
const ay = Math.abs(y - h / 2) - hh
|
|
125
133
|
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
const cornerDistance = Math.hypot(dx, dy)
|
|
130
|
-
const edgeDistance = Math.min(x, y, w - x, h - y)
|
|
131
|
-
|
|
132
|
-
if (dx !== 0 && dy !== 0) {
|
|
133
|
-
return r - cornerDistance
|
|
134
|
-
}
|
|
134
|
+
const outsidePart = Math.hypot(Math.max(ax, 0), Math.max(ay, 0))
|
|
135
|
+
const insidePart = Math.min(Math.max(ax, ay), 0)
|
|
135
136
|
|
|
136
|
-
return
|
|
137
|
+
return -(insidePart + outsidePart - hr)
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
function normalFromRoundedRect(
|
|
@@ -141,33 +142,28 @@ function normalFromRoundedRect(
|
|
|
141
142
|
y: number,
|
|
142
143
|
w: number,
|
|
143
144
|
h: number,
|
|
144
|
-
r: number,
|
|
145
145
|
) {
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (nx === 0 && ny === 0) {
|
|
153
|
-
const left = x
|
|
154
|
-
const right = w - x
|
|
155
|
-
const top = y
|
|
156
|
-
const bottom = h - y
|
|
157
|
-
const m = Math.min(left, right, top, bottom)
|
|
158
|
-
|
|
159
|
-
if (m === left) nx = -1
|
|
160
|
-
else if (m === right) nx = 1
|
|
161
|
-
else if (m === top) ny = -1
|
|
162
|
-
else ny = 1
|
|
163
|
-
}
|
|
146
|
+
const dx = Math.min(x, w - 1 - x)
|
|
147
|
+
const dy = Math.min(y, h - 1 - y)
|
|
148
|
+
|
|
149
|
+
const p = 4
|
|
150
|
+
const wsum = Math.pow(dx, p) + Math.pow(dy, p)
|
|
164
151
|
|
|
165
|
-
|
|
152
|
+
let nx: number
|
|
153
|
+
let ny: number
|
|
166
154
|
|
|
167
|
-
|
|
168
|
-
x:
|
|
169
|
-
y:
|
|
155
|
+
if (wsum > 1e-10) {
|
|
156
|
+
nx = (x > w / 2 ? 1 : -1) * (Math.pow(dy, p) / wsum)
|
|
157
|
+
ny = (y > h / 2 ? 1 : -1) * (Math.pow(dx, p) / wsum)
|
|
158
|
+
} else {
|
|
159
|
+
nx = x > w / 2 ? 1 : -1
|
|
160
|
+
ny = y > h / 2 ? 1 : -1
|
|
170
161
|
}
|
|
162
|
+
|
|
163
|
+
const len = Math.hypot(nx, ny)
|
|
164
|
+
if (len < 1e-10) return { x: 0, y: -1 }
|
|
165
|
+
|
|
166
|
+
return { x: nx / len, y: ny / len }
|
|
171
167
|
}
|
|
172
168
|
|
|
173
169
|
function canvasToUrl(canvas: HTMLCanvasElement) {
|
|
@@ -175,6 +171,10 @@ function canvasToUrl(canvas: HTMLCanvasElement) {
|
|
|
175
171
|
}
|
|
176
172
|
|
|
177
173
|
function buildMaps() {
|
|
174
|
+
if (props.fallbackOnly) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
178
|
const w = Math.max(1, Math.round(width.value))
|
|
179
179
|
const h = Math.max(1, Math.round(height.value))
|
|
180
180
|
|
|
@@ -210,7 +210,7 @@ function buildMaps() {
|
|
|
210
210
|
let spec = 0
|
|
211
211
|
|
|
212
212
|
if (dist >= 0 && dist < props.bezel) {
|
|
213
|
-
const n = normalFromRoundedRect(x, y, w, h
|
|
213
|
+
const n = normalFromRoundedRect(x, y, w, h)
|
|
214
214
|
const slope = derivative(t)
|
|
215
215
|
const bend = refractSlope(slope)
|
|
216
216
|
|
|
@@ -276,6 +276,10 @@ const resolvedRadius = computed(() => {
|
|
|
276
276
|
return props.radius
|
|
277
277
|
})
|
|
278
278
|
|
|
279
|
+
const borderRadios = computed(() => {
|
|
280
|
+
return props.borderRadius || `${resolvedRadius.value}px`
|
|
281
|
+
})
|
|
282
|
+
|
|
279
283
|
const vuetifyThemeColors = new Set([
|
|
280
284
|
'primary', 'secondary', 'accent', 'info', 'warning', 'error', 'success',
|
|
281
285
|
'surface', 'background',
|
|
@@ -308,14 +312,14 @@ const glassBgValue = computed(() => {
|
|
|
308
312
|
})
|
|
309
313
|
|
|
310
314
|
const backdropFilter = computed(() => {
|
|
311
|
-
if (supportsLiquidGlass) {
|
|
312
|
-
return
|
|
315
|
+
if (props.fallbackOnly || !supportsLiquidGlass) {
|
|
316
|
+
return 'blur(12px)'
|
|
313
317
|
}
|
|
314
|
-
return
|
|
318
|
+
return `url(#${filterId}) blur(${props.blur}px)`
|
|
315
319
|
})
|
|
316
320
|
|
|
317
321
|
const rootStyle = computed(() => ({
|
|
318
|
-
borderRadius: `${
|
|
322
|
+
borderRadius: `${borderRadios.value}`,
|
|
319
323
|
...(glassBgValue.value !== undefined
|
|
320
324
|
? { backgroundColor: glassBgValue.value }
|
|
321
325
|
: {}),
|
|
@@ -374,6 +378,7 @@ watch(
|
|
|
374
378
|
props.surface,
|
|
375
379
|
props.specularOpacity,
|
|
376
380
|
props.glareAngle,
|
|
381
|
+
props.fallbackOnly,
|
|
377
382
|
],
|
|
378
383
|
() => {
|
|
379
384
|
if (supportsLiquidGlass) {
|
|
@@ -386,15 +391,12 @@ watch(
|
|
|
386
391
|
onBeforeUnmount(() => {
|
|
387
392
|
ro?.disconnect()
|
|
388
393
|
})
|
|
389
|
-
|
|
390
394
|
</script>
|
|
391
395
|
|
|
392
396
|
<template>
|
|
393
|
-
<
|
|
394
|
-
:is="as"
|
|
397
|
+
<div
|
|
395
398
|
ref="root"
|
|
396
399
|
class="liquid-glass"
|
|
397
|
-
v-bind="asProps"
|
|
398
400
|
:style="rootStyle"
|
|
399
401
|
>
|
|
400
402
|
<svg
|
|
@@ -421,9 +423,15 @@ onBeforeUnmount(() => {
|
|
|
421
423
|
result="displacement_map"
|
|
422
424
|
/>
|
|
423
425
|
|
|
426
|
+
<feGaussianBlur
|
|
427
|
+
in="displacement_map"
|
|
428
|
+
stdDeviation="2"
|
|
429
|
+
result="smooth_disp"
|
|
430
|
+
/>
|
|
431
|
+
|
|
424
432
|
<feDisplacementMap
|
|
425
433
|
in="SourceGraphic"
|
|
426
|
-
in2="
|
|
434
|
+
in2="smooth_disp"
|
|
427
435
|
:scale="scale"
|
|
428
436
|
xChannelSelector="R"
|
|
429
437
|
yChannelSelector="G"
|
|
@@ -444,8 +452,13 @@ onBeforeUnmount(() => {
|
|
|
444
452
|
</defs>
|
|
445
453
|
</svg>
|
|
446
454
|
|
|
447
|
-
<
|
|
448
|
-
|
|
455
|
+
<component
|
|
456
|
+
:is="as"
|
|
457
|
+
v-bind="asProps"
|
|
458
|
+
>
|
|
459
|
+
<slot />
|
|
460
|
+
</component>
|
|
461
|
+
</div>
|
|
449
462
|
</template>
|
|
450
463
|
|
|
451
464
|
<style scoped>
|