glasskit-js 1.0.1 → 1.1.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/LiquidGlass.d.ts +3 -1
- package/LiquidGlass.mjs +13 -4
- package/README.md +5 -1
- package/liquid-glass.d.ts +4 -2
- package/liquid-glass.js +34 -11
- package/package.json +1 -1
package/LiquidGlass.d.ts
CHANGED
|
@@ -9,5 +9,7 @@ export interface LiquidGlassProps
|
|
|
9
9
|
children?: React.ReactNode;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
declare const LiquidGlass: React.
|
|
12
|
+
declare const LiquidGlass: React.ForwardRefExoticComponent<
|
|
13
|
+
LiquidGlassProps & React.RefAttributes<HTMLElement>
|
|
14
|
+
>;
|
|
13
15
|
export default LiquidGlass;
|
package/LiquidGlass.mjs
CHANGED
|
@@ -19,10 +19,10 @@ import LG from "./liquid-glass.js";
|
|
|
19
19
|
const GLASS_KEYS = [
|
|
20
20
|
"mode", "frost", "refraction", "depth", "dispersion", "splay",
|
|
21
21
|
"lightAngle", "lightIntensity", "curvature", "convexity",
|
|
22
|
-
"tint", "tintOpacity", "sheen", "sheenColor", "saturate", "brightness", "radius", "background",
|
|
22
|
+
"tint", "tintOpacity", "sheen", "sheenColor", "saturate", "brightness", "shadow", "radius", "background",
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
const LiquidGlass = React.forwardRef(function LiquidGlass({ as: Tag = "div", children, ...props }, forwardedRef) {
|
|
26
26
|
const ref = useRef(null);
|
|
27
27
|
const instRef = useRef(null);
|
|
28
28
|
|
|
@@ -47,5 +47,14 @@ export default function LiquidGlass({ as: Tag = "div", children, ...props }) {
|
|
|
47
47
|
instRef.current?.update(JSON.parse(optsKey));
|
|
48
48
|
}, [optsKey]);
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
// keep our internal ref and forward the node to the caller's ref too
|
|
51
|
+
const setRef = (node) => {
|
|
52
|
+
ref.current = node;
|
|
53
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
54
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return React.createElement(Tag, { ref: setRef, ...rest }, children);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export default LiquidGlass;
|
package/README.md
CHANGED
|
@@ -73,7 +73,11 @@ It refracts DOM, not `<canvas>` pixels — use `webgl` for canvas/video backgrou
|
|
|
73
73
|
| Depth | `depth` | | tint | `tint`, `tintOpacity` |
|
|
74
74
|
| Dispersion | `dispersion` | | corner radius | `radius` |
|
|
75
75
|
| Splay | `splay` | | | |
|
|
76
|
-
| Light (angle / %) | `lightAngle` / `lightIntensity` | | | |
|
|
76
|
+
| Light (angle / %) | `lightAngle` / `lightIntensity` | | drop shadow | `shadow` |
|
|
77
|
+
|
|
78
|
+
- **`tint`** takes either `"r,g,b"` (paired with `tintOpacity`) **or a full CSS gradient** — `tint="linear-gradient(180deg, rgba(255,255,255,.2), rgba(19,19,19,.22))"`.
|
|
79
|
+
- **`radius`** sets `border-radius` on the element *and* the refraction map — `radius={999}` alone gives you a pill; no need to also set it in `style`.
|
|
80
|
+
- **`shadow`** is any CSS `box-shadow` (`"none"` removes it); the inner light border/bezel follow `lightIntensity`.
|
|
77
81
|
|
|
78
82
|
## Shapes
|
|
79
83
|
|
package/liquid-glass.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export interface LiquidGlassOptions {
|
|
|
31
31
|
curvature?: number;
|
|
32
32
|
/** +1 convex (magnify) .. 0 flat .. -1 concave (shrink). @default 1 */
|
|
33
33
|
convexity?: number;
|
|
34
|
-
/** Glass tint
|
|
34
|
+
/** Glass tint — either "r,g,b" (combined with `tintOpacity`) or a full CSS `gradient(...)` string (opacity baked into its color stops). @default "255,255,255" */
|
|
35
35
|
tint?: string;
|
|
36
36
|
/** Tint opacity 0..1. @default 0.08 */
|
|
37
37
|
tintOpacity?: number;
|
|
@@ -43,7 +43,9 @@ export interface LiquidGlassOptions {
|
|
|
43
43
|
saturate?: number;
|
|
44
44
|
/** Backdrop brightness (css mode). @default 1.04 */
|
|
45
45
|
brightness?: number;
|
|
46
|
-
/**
|
|
46
|
+
/** Outer drop shadow as any CSS box-shadow value; "none" or "" removes it. The inner light border/bezel are controlled by `lightIntensity`. @default "0 8px 30px rgba(0,0,0,0.18)" */
|
|
47
|
+
shadow?: string;
|
|
48
|
+
/** Corner radius in px. When set, it both feeds the refraction map AND applies `border-radius` to the element. null = read the element's existing border-radius. @default null */
|
|
47
49
|
radius?: number | null;
|
|
48
50
|
/** Element or selector to refract. Required for `svg-clone` and `webgl`. */
|
|
49
51
|
background?: Element | string | null;
|
package/liquid-glass.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
sheenColor: '255,255,255', // "r,g,b" — recolor the gloss
|
|
43
43
|
saturate: 1.4,
|
|
44
44
|
brightness: 1.04,
|
|
45
|
+
shadow: '0 8px 30px rgba(0,0,0,0.18)', // outer drop shadow; 'none'/'' removes it, or pass any CSS box-shadow
|
|
45
46
|
radius: null, // null = read element border-radius
|
|
46
47
|
background: null // Element|selector — required for 'svg-clone' & 'webgl'
|
|
47
48
|
};
|
|
@@ -58,7 +59,24 @@
|
|
|
58
59
|
function clamp8(v) { return v < 0 ? 0 : v > 255 ? 255 : Math.round(v); }
|
|
59
60
|
// only ever emit a clean "r,g,b" triplet into inline CSS — blocks url()/expression smuggling
|
|
60
61
|
function safeRGB(v) { return (typeof v === 'string' && /^\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*$/.test(v)) ? v.replace(/\s+/g, '') : '255,255,255'; }
|
|
62
|
+
// allow only box-shadow-shaped CSS (lengths/colors); reject url()/expression/extra declarations
|
|
63
|
+
function safeShadow(v) {
|
|
64
|
+
if (typeof v !== 'string' || !v || v === 'none') return '';
|
|
65
|
+
return /url\(|expression|javascript:|[;{}<>]/i.test(v) ? '' : v;
|
|
66
|
+
}
|
|
67
|
+
// allow CSS gradient values only; block url()/image-set()/extra declarations
|
|
68
|
+
function safeGradient(v) {
|
|
69
|
+
if (typeof v !== 'string') return '';
|
|
70
|
+
if (/url\(|image-set|element\(|expression|javascript:|[;{}<>]/i.test(v)) return '';
|
|
71
|
+
return /(^|\s)(repeating-)?(linear|radial|conic)-gradient\(/i.test(v) ? v : '';
|
|
72
|
+
}
|
|
61
73
|
function num(v, d) { var n = parseFloat(v); return isFinite(n) ? n : d; }
|
|
74
|
+
// tint background: a full CSS gradient string, else an rgba() built from "r,g,b" + opacity
|
|
75
|
+
function tintValue(o) {
|
|
76
|
+
var t = o.tint;
|
|
77
|
+
if (typeof t === 'string' && /gradient\(/i.test(t)) { var g = safeGradient(t); if (g) return g; }
|
|
78
|
+
return 'rgba(' + safeRGB(t) + ',' + num(o.tintOpacity, 0.08) + ')';
|
|
79
|
+
}
|
|
62
80
|
function resolveEl(x) { return typeof x === 'string' ? document.querySelector(x) : x; }
|
|
63
81
|
function readRadius(el) { return parseFloat(getComputedStyle(el).borderRadius) || 0; }
|
|
64
82
|
function ns(tag) { return document.createElementNS('http://www.w3.org/2000/svg', tag); }
|
|
@@ -111,7 +129,7 @@
|
|
|
111
129
|
if ((mode === 'svg-clone' || mode === 'webgl') && !bg) mode = isChromium() ? 'svg' : 'css';
|
|
112
130
|
|
|
113
131
|
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
|
114
|
-
var prevBg = el.style.
|
|
132
|
+
var prevBg = el.style.background, prevRadius = el.style.borderRadius;
|
|
115
133
|
|
|
116
134
|
var overlay = document.createElement('div');
|
|
117
135
|
overlay.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:2;';
|
|
@@ -245,17 +263,19 @@
|
|
|
245
263
|
/* ---- tint + specular overlay (all modes) ---- */
|
|
246
264
|
function applyOverlay() {
|
|
247
265
|
var li = num(o.lightIntensity, 0.8), a = (num(o.lightAngle, -45) + 90) * Math.PI / 180;
|
|
248
|
-
if (mode === 'css' || mode === 'svg') el.style.
|
|
266
|
+
if (mode === 'css' || mode === 'svg') el.style.background = tintValue(o);
|
|
249
267
|
// FACE gloss — controlled by `sheen`/`sheenColor`, independent of the border light below
|
|
250
268
|
var sc = safeRGB(o.sheenColor), sh = num(o.sheen, 0.7);
|
|
251
269
|
overlay.style.background = sh <= 0 ? 'none' :
|
|
252
270
|
'linear-gradient(' + (num(o.lightAngle, -45) + 90) + 'deg,rgba(' + sc + ',' + (0.6 * sh).toFixed(3) +
|
|
253
271
|
') 0%,rgba(' + sc + ',0) 30%,rgba(' + sc + ',0) 70%,rgba(' + sc + ',' + (0.14 * sh).toFixed(3) + ') 100%)';
|
|
254
|
-
|
|
272
|
+
// inset light border + bezel (scale with lightIntensity) and a separately controllable outer drop shadow
|
|
273
|
+
var insets =
|
|
255
274
|
'inset 0 0 0 1px rgba(255,255,255,' + (0.30 * li) + '),' +
|
|
256
275
|
'inset ' + (Math.cos(a) * 2).toFixed(1) + 'px ' + (Math.sin(a) * 2).toFixed(1) + 'px 2px rgba(255,255,255,' + (0.5 * li) + '),' +
|
|
257
|
-
'inset ' + (-Math.cos(a) * 2).toFixed(1) + 'px ' + (-Math.sin(a) * 2).toFixed(1) + 'px 6px rgba(0,0,0,.15)
|
|
258
|
-
|
|
276
|
+
'inset ' + (-Math.cos(a) * 2).toFixed(1) + 'px ' + (-Math.sin(a) * 2).toFixed(1) + 'px 6px rgba(0,0,0,.15)';
|
|
277
|
+
var drop = safeShadow(o.shadow);
|
|
278
|
+
overlay.style.boxShadow = drop ? insets + ',' + drop : insets;
|
|
259
279
|
}
|
|
260
280
|
|
|
261
281
|
/* ------------------------------ WebGL ------------------------------ */
|
|
@@ -363,14 +383,17 @@
|
|
|
363
383
|
var ro = new ResizeObserver(function () { refreshMap(); if (mode === 'svg-clone') syncClone(); });
|
|
364
384
|
ro.observe(el);
|
|
365
385
|
|
|
366
|
-
|
|
386
|
+
// when an explicit radius is given, round the element itself (not just the refraction map)
|
|
387
|
+
function applyRadius() { if (o.radius != null) el.style.borderRadius = num(o.radius, 0) + 'px'; }
|
|
388
|
+
|
|
389
|
+
refreshMap(); applyFilterParams(); applyOverlay(); applyRadius();
|
|
367
390
|
|
|
368
391
|
var instance = {
|
|
369
392
|
el: el, mode: mode,
|
|
370
393
|
update: function (patch) {
|
|
371
394
|
Object.assign(o, patch || {});
|
|
372
395
|
if (patch && patch.background != null && mode === 'webgl') loadGLBg(resolveEl(o.background));
|
|
373
|
-
refreshMap(); applyFilterParams(); applyOverlay();
|
|
396
|
+
refreshMap(); applyFilterParams(); applyOverlay(); applyRadius();
|
|
374
397
|
return instance;
|
|
375
398
|
},
|
|
376
399
|
refresh: function () { if (mode === 'svg-clone') reclone(); else if (mode === 'webgl') loadGLBg(resolveEl(o.background)); return instance; },
|
|
@@ -380,7 +403,7 @@
|
|
|
380
403
|
// free the WebGL context explicitly — browsers cap ~16 per page
|
|
381
404
|
if (gl) { var lc = gl.getExtension('WEBGL_lose_context'); if (lc) lc.loseContext(); gl = null; }
|
|
382
405
|
[svg, lensWrap, glcanvas, overlay].forEach(function (n) { if (n && n.parentNode) n.parentNode.removeChild(n); });
|
|
383
|
-
el.style.backdropFilter = ''; el.style.webkitBackdropFilter = ''; el.style.
|
|
406
|
+
el.style.backdropFilter = ''; el.style.webkitBackdropFilter = ''; el.style.background = prevBg; el.style.borderRadius = prevRadius;
|
|
384
407
|
}
|
|
385
408
|
};
|
|
386
409
|
return instance;
|
|
@@ -389,7 +412,7 @@
|
|
|
389
412
|
/* ===================== <glass-kit> web component ===================== */
|
|
390
413
|
var ATTRS = ['mode', 'frost', 'refraction', 'depth', 'dispersion', 'splay', 'light-angle',
|
|
391
414
|
'light-intensity', 'curvature', 'convexity', 'tint', 'tint-opacity', 'sheen', 'sheen-color',
|
|
392
|
-
'radius', 'background'];
|
|
415
|
+
'shadow', 'radius', 'background'];
|
|
393
416
|
function camel(s) { return s.replace(/-([a-z])/g, function (_, c) { return c.toUpperCase(); }); }
|
|
394
417
|
function defineElement() {
|
|
395
418
|
if (typeof customElements === 'undefined' || customElements.get('glass-kit')) return;
|
|
@@ -399,7 +422,7 @@
|
|
|
399
422
|
ATTRS.forEach(function (a) {
|
|
400
423
|
if (!node.hasAttribute(a)) return;
|
|
401
424
|
var v = node.getAttribute(a), key = camel(a);
|
|
402
|
-
opts[key] = (a === 'mode' || a === 'tint' || a === 'sheen-color' || a === 'background') ? v : parseFloat(v);
|
|
425
|
+
opts[key] = (a === 'mode' || a === 'tint' || a === 'sheen-color' || a === 'shadow' || a === 'background') ? v : parseFloat(v);
|
|
403
426
|
});
|
|
404
427
|
return opts;
|
|
405
428
|
}
|
|
@@ -416,5 +439,5 @@
|
|
|
416
439
|
}
|
|
417
440
|
if (typeof window !== 'undefined') { if (document.readyState !== 'loading') defineElement(); else document.addEventListener('DOMContentLoaded', defineElement); }
|
|
418
441
|
|
|
419
|
-
return { apply: apply, defineElement: defineElement, isChromium: isChromium, pickMode: pickMode, DEFAULTS: DEFAULTS, version: '1.0
|
|
442
|
+
return { apply: apply, defineElement: defineElement, isChromium: isChromium, pickMode: pickMode, DEFAULTS: DEFAULTS, version: '1.1.0' };
|
|
420
443
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glasskit-js",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Drop-in Apple/Figma 'Liquid Glass' for any element — switch between pure CSS, SVG displacement (live backdrop), cross-browser clone-mode, or WebGL. Real refraction, chromatic aberration, specular highlight. Zero dependencies. Class + <liquid-glass> web component.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"liquid-glass",
|