glasskit-js 1.0.2 → 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 CHANGED
@@ -9,5 +9,7 @@ export interface LiquidGlassProps
9
9
  children?: React.ReactNode;
10
10
  }
11
11
 
12
- declare const LiquidGlass: React.FC<LiquidGlassProps>;
12
+ declare const LiquidGlass: React.ForwardRefExoticComponent<
13
+ LiquidGlassProps & React.RefAttributes<HTMLElement>
14
+ >;
13
15
  export default LiquidGlass;
package/LiquidGlass.mjs CHANGED
@@ -22,7 +22,7 @@ const GLASS_KEYS = [
22
22
  "tint", "tintOpacity", "sheen", "sheenColor", "saturate", "brightness", "shadow", "radius", "background",
23
23
  ];
24
24
 
25
- export default function LiquidGlass({ as: Tag = "div", children, ...props }) {
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
- return React.createElement(Tag, { ref, ...rest }, children);
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 as "r,g,b". @default "255,255,255" */
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;
@@ -45,7 +45,7 @@ export interface LiquidGlassOptions {
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
47
  shadow?: string;
48
- /** Override corner radius (px). null = read element's border-radius. @default null */
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 */
49
49
  radius?: number | null;
50
50
  /** Element or selector to refract. Required for `svg-clone` and `webgl`. */
51
51
  background?: Element | string | null;
package/liquid-glass.js CHANGED
@@ -64,7 +64,19 @@
64
64
  if (typeof v !== 'string' || !v || v === 'none') return '';
65
65
  return /url\(|expression|javascript:|[;{}<>]/i.test(v) ? '' : v;
66
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
+ }
67
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
+ }
68
80
  function resolveEl(x) { return typeof x === 'string' ? document.querySelector(x) : x; }
69
81
  function readRadius(el) { return parseFloat(getComputedStyle(el).borderRadius) || 0; }
70
82
  function ns(tag) { return document.createElementNS('http://www.w3.org/2000/svg', tag); }
@@ -117,7 +129,7 @@
117
129
  if ((mode === 'svg-clone' || mode === 'webgl') && !bg) mode = isChromium() ? 'svg' : 'css';
118
130
 
119
131
  if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
120
- var prevBg = el.style.backgroundColor;
132
+ var prevBg = el.style.background, prevRadius = el.style.borderRadius;
121
133
 
122
134
  var overlay = document.createElement('div');
123
135
  overlay.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:2;';
@@ -251,7 +263,7 @@
251
263
  /* ---- tint + specular overlay (all modes) ---- */
252
264
  function applyOverlay() {
253
265
  var li = num(o.lightIntensity, 0.8), a = (num(o.lightAngle, -45) + 90) * Math.PI / 180;
254
- if (mode === 'css' || mode === 'svg') el.style.backgroundColor = 'rgba(' + safeRGB(o.tint) + ',' + num(o.tintOpacity, 0.08) + ')';
266
+ if (mode === 'css' || mode === 'svg') el.style.background = tintValue(o);
255
267
  // FACE gloss — controlled by `sheen`/`sheenColor`, independent of the border light below
256
268
  var sc = safeRGB(o.sheenColor), sh = num(o.sheen, 0.7);
257
269
  overlay.style.background = sh <= 0 ? 'none' :
@@ -371,14 +383,17 @@
371
383
  var ro = new ResizeObserver(function () { refreshMap(); if (mode === 'svg-clone') syncClone(); });
372
384
  ro.observe(el);
373
385
 
374
- refreshMap(); applyFilterParams(); applyOverlay();
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();
375
390
 
376
391
  var instance = {
377
392
  el: el, mode: mode,
378
393
  update: function (patch) {
379
394
  Object.assign(o, patch || {});
380
395
  if (patch && patch.background != null && mode === 'webgl') loadGLBg(resolveEl(o.background));
381
- refreshMap(); applyFilterParams(); applyOverlay();
396
+ refreshMap(); applyFilterParams(); applyOverlay(); applyRadius();
382
397
  return instance;
383
398
  },
384
399
  refresh: function () { if (mode === 'svg-clone') reclone(); else if (mode === 'webgl') loadGLBg(resolveEl(o.background)); return instance; },
@@ -388,7 +403,7 @@
388
403
  // free the WebGL context explicitly — browsers cap ~16 per page
389
404
  if (gl) { var lc = gl.getExtension('WEBGL_lose_context'); if (lc) lc.loseContext(); gl = null; }
390
405
  [svg, lensWrap, glcanvas, overlay].forEach(function (n) { if (n && n.parentNode) n.parentNode.removeChild(n); });
391
- el.style.backdropFilter = ''; el.style.webkitBackdropFilter = ''; el.style.backgroundColor = prevBg;
406
+ el.style.backdropFilter = ''; el.style.webkitBackdropFilter = ''; el.style.background = prevBg; el.style.borderRadius = prevRadius;
392
407
  }
393
408
  };
394
409
  return instance;
@@ -424,5 +439,5 @@
424
439
  }
425
440
  if (typeof window !== 'undefined') { if (document.readyState !== 'loading') defineElement(); else document.addEventListener('DOMContentLoaded', defineElement); }
426
441
 
427
- return { apply: apply, defineElement: defineElement, isChromium: isChromium, pickMode: pickMode, DEFAULTS: DEFAULTS, version: '1.0.2' };
442
+ return { apply: apply, defineElement: defineElement, isChromium: isChromium, pickMode: pickMode, DEFAULTS: DEFAULTS, version: '1.1.0' };
428
443
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glasskit-js",
3
- "version": "1.0.2",
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",