visionary-image 1.0.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/README.md +79 -0
- package/dist/visionary-image.d.ts +79 -0
- package/dist/visionary-image.es.js +505 -0
- package/dist/visionary-image.umd.js +6 -0
- package/package.json +97 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# visionary-image
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
> Visionary Image rapidly renders blurhash placeholders and boosts Core Web Vitals.
|
|
6
|
+
|
|
7
|
+
**Features:**
|
|
8
|
+
|
|
9
|
+
- Blurhash data encoded in the image URL via [visionary-url](https://github.com/visionary-ux/visionary-url) means zero latency image placeholders
|
|
10
|
+
- Renders a true-to-size, responsive blurhash placeholder while the image loads, <u>eliminating Cumulative Layout Shift (CLS)</u>
|
|
11
|
+
- Off-screen images are lazily loaded, reducing initial pageload size and request count, <u>improving Interaction to Next Paint (INP)</u>
|
|
12
|
+
- Framework-tested (Remix, Next.js, Vite) with support for both client and server-side rendering (SSR, SSG) environments
|
|
13
|
+
- Additional features: Prevent dragging, user-selection of image
|
|
14
|
+
- Improves Core Web Vitals scores (CLS, and often INP and LCP) which are a primary ranking signal in Google Search
|
|
15
|
+
|
|
16
|
+
> "We highly recommend site owners achieve good Core Web Vitals for success with Search", [Google Search Central](https://developers.google.com/search/docs/appearance/core-web-vitals)
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Install via npm, yarn, or pnpm.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install --save visionary-image
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Getting Started
|
|
27
|
+
|
|
28
|
+
To render a Visionary Image, first create a Visionary URL.
|
|
29
|
+
|
|
30
|
+
### Creating a Visionary Image URL
|
|
31
|
+
|
|
32
|
+
To create a Visionary URL from a public image URL see [Visionary URL Generator](#).
|
|
33
|
+
|
|
34
|
+
To generate Visionary data from a local file, use the [Drag-and-Drop Blurhash Generator](#).
|
|
35
|
+
|
|
36
|
+
- Generate a Visionary URL at [https://visionary.cloud/url-generator](https://visionary.cloud/url-generator)
|
|
37
|
+
- Copy a URL from your [Visionary Dashboard](https://visionary.cloud/dashboard)
|
|
38
|
+
- Use this [sample URL]()
|
|
39
|
+
|
|
40
|
+
### Render image component
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { Image } from "visionary-image";
|
|
44
|
+
|
|
45
|
+
const imageUrl = "<visionary url or code>";
|
|
46
|
+
const altText = "Nighttime stroll in Tokyo, neon-lit with a purple glow";
|
|
47
|
+
|
|
48
|
+
const App = () => {
|
|
49
|
+
<div>
|
|
50
|
+
<Image alt={altText} src={imageUrl} />
|
|
51
|
+
</div>;
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Component Props
|
|
56
|
+
|
|
57
|
+
| Name | Description |
|
|
58
|
+
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `alt` <br/> string | Image `alt` tag. Adding alt text to images is highly recommended to accommodate accessible devices and improve discoverability. |
|
|
60
|
+
| `bgColorAlpha` <br/> number | Base layer (background color) alpha channel.<br /> Default: `0.7` |
|
|
61
|
+
| `className` <br/> string | Classname applied to the container `div` or the fallback `img` element. |
|
|
62
|
+
| `debug` <br/> boolean | Prints handy debug info to the console (Visionary URL data, render times). |
|
|
63
|
+
| `disableBlurLayer` <br/> boolean | Disables rendering of the blur (canvas) layer. |
|
|
64
|
+
| `disableImageLayer` <br/> boolean | Disables rendering of the image layer. |
|
|
65
|
+
| `hideImageLayer` <br/> boolean | Hides the image layer, revealing the blur layer underneath. |
|
|
66
|
+
| `lazy` <br/> boolean | Should image lazily load. <br/> Default: `true` |
|
|
67
|
+
| `onClick` <br/> function | Callback function to invoke when the image is clicked. function. |
|
|
68
|
+
| `onError` <br/> function | Error callback function. |
|
|
69
|
+
| `onLoad` <br/> function | Image loaded callback function. |
|
|
70
|
+
| `preventDrag` <br/>boolean | Prevents user from dragging the image element. |
|
|
71
|
+
| `preventSelection` <br/>boolean | Prevents user from highlighting the image element. |
|
|
72
|
+
| `punch` <br/>number | Blurhash punch parameter.<br /> Default: `1` |
|
|
73
|
+
| `src` <br/>string | Visionary URL, Visionary Code, or ordinary image URL.<br/> If `src` contains Visionary data, placeholders will be rendered, otherwise falls back to an `img` element. <br/> **required** |
|
|
74
|
+
|
|
75
|
+
### More Props
|
|
76
|
+
|
|
77
|
+
| | |
|
|
78
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
79
|
+
| `height`, `width` | If set, will override internally computed image dimensions. By default, Visionary renders optimally sized images, using the aspect-ratio and max-width placeholder data. |
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
import { ImageSizeToken } from 'visionary-url';
|
|
3
|
+
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
|
4
|
+
import { VisionaryImageOptions } from 'visionary-url';
|
|
5
|
+
|
|
6
|
+
declare const Image_2: ({ alt: altText, bgColorAlpha, className, debug, disableBlurLayer, disableImageLayer, endpoint, height: userHeight, hideImageLayer, lazy, onClick, onError, onLoad, preventDrag, preventSelection, punch, size, src, style: userStyles, width: userWidth, }: VisionaryImageProps) => JSX_2.Element;
|
|
7
|
+
export { Image_2 as Image }
|
|
8
|
+
|
|
9
|
+
export declare interface VisionaryImageProps {
|
|
10
|
+
/**
|
|
11
|
+
* Image `alt` tag; Adding alt text to images is *highly* recommended to accommodate accessible devices and improve discoverability.
|
|
12
|
+
*/
|
|
13
|
+
alt?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Base layer (background color) alpha channel (default: 0.7)
|
|
16
|
+
*/
|
|
17
|
+
bgColorAlpha?: number;
|
|
18
|
+
/** Classname applied to the container `div` or the fallback `img` element. */
|
|
19
|
+
className?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Print debug info (Visionary data, render times) to the console
|
|
22
|
+
*/
|
|
23
|
+
debug?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Disable rendering of blur layer
|
|
26
|
+
*/
|
|
27
|
+
disableBlurLayer?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Disable rendering of image layer
|
|
30
|
+
*/
|
|
31
|
+
disableImageLayer?: boolean;
|
|
32
|
+
/** Custom endpoint for image URLs (when using `generateVisionaryUrl()`) */
|
|
33
|
+
endpoint?: string;
|
|
34
|
+
height?: number | string;
|
|
35
|
+
/**
|
|
36
|
+
* Hides the image layer via CSS
|
|
37
|
+
*/
|
|
38
|
+
hideImageLayer?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Should image load lazily (default: true)
|
|
41
|
+
*/
|
|
42
|
+
lazy?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Callback function to run after click
|
|
45
|
+
*/
|
|
46
|
+
onClick?: () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Callback function to run if image resource failed to load
|
|
49
|
+
*/
|
|
50
|
+
onError?: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Callback function to run after image load
|
|
53
|
+
*/
|
|
54
|
+
onLoad?: () => void;
|
|
55
|
+
options?: VisionaryImageOptions;
|
|
56
|
+
/**
|
|
57
|
+
* Prevents user from dragging the image element
|
|
58
|
+
*/
|
|
59
|
+
preventDrag?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Prevents user from highlighting the image element
|
|
62
|
+
*/
|
|
63
|
+
preventSelection?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Blurhash punch parameter (default: 1)
|
|
66
|
+
*/
|
|
67
|
+
punch?: number;
|
|
68
|
+
/** If specified, overrides the size specified in a Visionary URL */
|
|
69
|
+
size?: ImageSizeToken;
|
|
70
|
+
/**
|
|
71
|
+
* Image `src` prop; if `src` contains a Visionary code, a Visionary image will be rendered, otherwise we fall back to a native `<img />` tag
|
|
72
|
+
*/
|
|
73
|
+
src: string;
|
|
74
|
+
/** Styles are applied to the Visionary container (or the fallback <img> if a Visionary Image is not rendered) */
|
|
75
|
+
style?: CSSProperties;
|
|
76
|
+
width?: number | string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { }
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
(function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.id="v7y-styles",e.appendChild(document.createTextNode('.v7y_7587:before{content:"";display:block;padding-top:var(--v-ar)}.v7y_f2b7{display:none}.v7y_b8c0{-webkit-user-drag:none;pointer-events:none}.v7y_e0dd{-webkit-user-select:none;user-select:none}')),document.head.appendChild(e)}}catch(n){console.error("vite-plugin-css-injected-by-js",n)}})();
|
|
2
|
+
import { jsx as W, jsxs as yt } from "react/jsx-runtime";
|
|
3
|
+
import { useRef as Y, useCallback as q, useState as wt, useEffect as xt, useLayoutEffect as Et, useMemo as It } from "react";
|
|
4
|
+
function Rt(t) {
|
|
5
|
+
return t && t.__esModule && Object.prototype.hasOwnProperty.call(t, "default") ? t.default : t;
|
|
6
|
+
}
|
|
7
|
+
var et = { exports: {} };
|
|
8
|
+
/*!
|
|
9
|
+
Copyright (c) 2018 Jed Watson.
|
|
10
|
+
Licensed under the MIT License (MIT), see
|
|
11
|
+
http://jedwatson.github.io/classnames
|
|
12
|
+
*/
|
|
13
|
+
(function(t) {
|
|
14
|
+
(function() {
|
|
15
|
+
var n = {}.hasOwnProperty;
|
|
16
|
+
function e() {
|
|
17
|
+
for (var r = "", s = 0; s < arguments.length; s++) {
|
|
18
|
+
var a = arguments[s];
|
|
19
|
+
a && (r = c(r, o(a)));
|
|
20
|
+
}
|
|
21
|
+
return r;
|
|
22
|
+
}
|
|
23
|
+
function o(r) {
|
|
24
|
+
if (typeof r == "string" || typeof r == "number")
|
|
25
|
+
return r;
|
|
26
|
+
if (typeof r != "object")
|
|
27
|
+
return "";
|
|
28
|
+
if (Array.isArray(r))
|
|
29
|
+
return e.apply(null, r);
|
|
30
|
+
if (r.toString !== Object.prototype.toString && !r.toString.toString().includes("[native code]"))
|
|
31
|
+
return r.toString();
|
|
32
|
+
var s = "";
|
|
33
|
+
for (var a in r)
|
|
34
|
+
n.call(r, a) && r[a] && (s = c(s, a));
|
|
35
|
+
return s;
|
|
36
|
+
}
|
|
37
|
+
function c(r, s) {
|
|
38
|
+
return s ? r ? r + " " + s : r + s : r;
|
|
39
|
+
}
|
|
40
|
+
t.exports ? (e.default = e, t.exports = e) : window.classNames = e;
|
|
41
|
+
})();
|
|
42
|
+
})(et);
|
|
43
|
+
var At = et.exports;
|
|
44
|
+
const K = /* @__PURE__ */ Rt(At), Nt = (t, { root: n, rootMargin: e, threshold: o } = {}, c = []) => {
|
|
45
|
+
const r = Y(null), s = Y(null);
|
|
46
|
+
return q((i) => {
|
|
47
|
+
r.current && s.current && (s.current.unobserve(r.current), s.current.disconnect(), s.current = null), i && (s.current = new IntersectionObserver(t, { root: n, rootMargin: e, threshold: o }), s.current.observe(i), r.current = i);
|
|
48
|
+
}, [r, n, e, JSON.stringify(o), ...c]);
|
|
49
|
+
}, Ot = ({ root: t, rootMargin: n, threshold: e, unobserveOnEnter: o, target: c, onEnter: r, onLeave: s, defaultInView: a } = {}, i = []) => {
|
|
50
|
+
const [d, h] = wt({
|
|
51
|
+
inView: a || !1,
|
|
52
|
+
entry: null,
|
|
53
|
+
observer: null
|
|
54
|
+
}), f = q(([p], l) => {
|
|
55
|
+
const b = l.thresholds.some((g) => p.intersectionRatio >= g) && p.isIntersecting;
|
|
56
|
+
h({
|
|
57
|
+
inView: b,
|
|
58
|
+
entry: p,
|
|
59
|
+
observer: l
|
|
60
|
+
}), b && o && (l.unobserve(p.target), l.disconnect()), b ? r == null || r(p, l) : s == null || s(p, l);
|
|
61
|
+
}, [r, s, o]), m = Nt(f, { root: t, rootMargin: n, threshold: e }, [o, ...i]);
|
|
62
|
+
return xt(() => {
|
|
63
|
+
c != null && c.current && m(c.current);
|
|
64
|
+
}, [c, m]), [m, d.inView, d.entry, d.observer];
|
|
65
|
+
};
|
|
66
|
+
var L = /* @__PURE__ */ ((t) => (t.AUTO = "auto", t.AVIF = "avif", t.JPEG = "jpeg", t.WEBP = "webp", t))(L || {}), y = /* @__PURE__ */ ((t) => (t.xs = "xs", t.sm = "sm", t.md = "md", t.lg = "lg", t.xl = "xl", t.xxl = "xxl", t["4k"] = "4k", t["5k"] = "5k", t.full = "full", t))(y || {}), C = /* @__PURE__ */ ((t) => (t.DEBUG = "debug", t.DOWNLOAD = "download", t))(C || {});
|
|
67
|
+
const Ut = "https://link.visionary.cloud";
|
|
68
|
+
L.AUTO, y.lg;
|
|
69
|
+
const Z = {
|
|
70
|
+
[y.xs]: 160,
|
|
71
|
+
[y.sm]: 320,
|
|
72
|
+
[y.md]: 640,
|
|
73
|
+
[y.lg]: 1280,
|
|
74
|
+
[y.xl]: 1920,
|
|
75
|
+
[y.xxl]: 2560,
|
|
76
|
+
[y["4k"]]: 3840,
|
|
77
|
+
[y["5k"]]: 5120,
|
|
78
|
+
[y.full]: 0
|
|
79
|
+
};
|
|
80
|
+
Object.keys(Z).reduce(
|
|
81
|
+
(t, n) => {
|
|
82
|
+
const e = n, o = Z[e];
|
|
83
|
+
return o && (t[o] = e), t;
|
|
84
|
+
},
|
|
85
|
+
{}
|
|
86
|
+
);
|
|
87
|
+
const rt = "!";
|
|
88
|
+
var St = Object.defineProperty, Tt = (t, n, e) => n in t ? St(t, n, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[n] = e, _t = (t, n, e) => Tt(t, typeof n != "symbol" ? n + "" : n, e), J = {}, j = {};
|
|
89
|
+
Object.defineProperty(j, "__esModule", { value: !0 });
|
|
90
|
+
function Ct(t) {
|
|
91
|
+
return String.fromCharCode(parseInt(t.slice(1), 16));
|
|
92
|
+
}
|
|
93
|
+
function Dt(t) {
|
|
94
|
+
return btoa(encodeURIComponent(t).replace(/%[0-9A-F]{2}/g, Ct));
|
|
95
|
+
}
|
|
96
|
+
j.encode = Dt;
|
|
97
|
+
function Pt(t) {
|
|
98
|
+
return `%${`00${t.charCodeAt(0).toString(16)}`.slice(-2)}`;
|
|
99
|
+
}
|
|
100
|
+
function Vt(t) {
|
|
101
|
+
return decodeURIComponent(Array.from(atob(t), Pt).join(""));
|
|
102
|
+
}
|
|
103
|
+
j.decode = Vt;
|
|
104
|
+
Object.defineProperty(J, "__esModule", { value: !0 });
|
|
105
|
+
const ot = j;
|
|
106
|
+
function $t(t) {
|
|
107
|
+
return ot.decode(t.replace(/\-/g, "+").replace(/_/g, "/"));
|
|
108
|
+
}
|
|
109
|
+
var Lt = J.decode = $t;
|
|
110
|
+
function jt(t) {
|
|
111
|
+
return ot.encode(t).replace(/\//g, "_").replace(/\+/g, "-").replace(/=+$/, "");
|
|
112
|
+
}
|
|
113
|
+
var kt = J.encode = jt;
|
|
114
|
+
const zt = (t) => t.filter(Boolean), Q = (t) => {
|
|
115
|
+
try {
|
|
116
|
+
return new URL(t);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}, k = (t = "") => /^[A-Za-z0-9_-]*$/.test(t), Bt = (t) => {
|
|
121
|
+
const { altText: n, bcc: e, blurhash: o, blurhashX: c, blurhashY: r, sourceHeight: s, sourceWidth: a, url: i } = t;
|
|
122
|
+
if (!i || !a || !s)
|
|
123
|
+
return new Error("Cannot construct visionary code: missing required url/width/height");
|
|
124
|
+
const d = [i, a, s];
|
|
125
|
+
return !e || (d.push(e), !o || !c || !r) || (d.push(o, c, r), n && n.length && d.push(n)), Mt(d);
|
|
126
|
+
}, Mt = (t) => kt(t.join(rt)), st = (t) => {
|
|
127
|
+
if (typeof t != "string")
|
|
128
|
+
return null;
|
|
129
|
+
const n = t.trim();
|
|
130
|
+
if (!n.length || !k(n))
|
|
131
|
+
return null;
|
|
132
|
+
const e = Lt(n);
|
|
133
|
+
if (!e)
|
|
134
|
+
return null;
|
|
135
|
+
const o = e.split(rt);
|
|
136
|
+
if (o.length < 3)
|
|
137
|
+
return null;
|
|
138
|
+
const [c, r, s, a, i, d, h, f] = o, m = c.trim();
|
|
139
|
+
if (!m.length)
|
|
140
|
+
return console.error("Cannot parse code, empty file id"), null;
|
|
141
|
+
const p = Number(r.trim()), l = Number(s.trim());
|
|
142
|
+
if (isNaN(p) || isNaN(l) || !p || !l)
|
|
143
|
+
return console.error("Cannot parse Visionary Code: invalid image dimensions", r, s), null;
|
|
144
|
+
const v = Number(d) ?? 0, b = Number(h) ?? 0;
|
|
145
|
+
return v < 1 || b < 1 ? (console.error(
|
|
146
|
+
"Cannot parse Visionary Code: invalid blurhash x, y component dimensions",
|
|
147
|
+
v,
|
|
148
|
+
b
|
|
149
|
+
), null) : {
|
|
150
|
+
altText: f,
|
|
151
|
+
bcc: a,
|
|
152
|
+
blurhash: i,
|
|
153
|
+
blurhashX: v,
|
|
154
|
+
blurhashY: b,
|
|
155
|
+
sourceHeight: l,
|
|
156
|
+
sourceWidth: p,
|
|
157
|
+
url: m
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
class Gt extends Error {
|
|
161
|
+
constructor() {
|
|
162
|
+
super(...arguments), _t(this, "message", "invalid endpoint URL (does it contain http/https?)");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const Wt = (t) => t === C.DEBUG, Ft = (t) => t === C.DOWNLOAD, Ht = (t) => Object.values(L).includes(t), ct = (t) => Object.values(y).includes(t), Xt = (t = []) => {
|
|
166
|
+
const n = {};
|
|
167
|
+
for (const e of t)
|
|
168
|
+
ct(e) ? n.size = y[e] : Wt(e) ? n.debug = !0 : Ft(e) ? n.download = !0 : Ht(e) && (n.format = e);
|
|
169
|
+
return n;
|
|
170
|
+
}, Yt = (t) => {
|
|
171
|
+
if (!t || typeof t != "object")
|
|
172
|
+
return null;
|
|
173
|
+
const n = [];
|
|
174
|
+
return t.debug && n.push(C.DEBUG), t.download && n.push(C.DOWNLOAD), t.format && t.format !== L.AUTO && n.push(t.format), t.size && ct(t.size) && n.push(t.size), n.length ? n.sort().join(",") : null;
|
|
175
|
+
}, Zt = (t) => {
|
|
176
|
+
if (k(t)) {
|
|
177
|
+
const n = st(t);
|
|
178
|
+
if (n)
|
|
179
|
+
return {
|
|
180
|
+
fields: n,
|
|
181
|
+
options: {}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return qt(t);
|
|
185
|
+
}, qt = (t) => {
|
|
186
|
+
if (!t)
|
|
187
|
+
return null;
|
|
188
|
+
const n = t.trim();
|
|
189
|
+
if (!n)
|
|
190
|
+
return null;
|
|
191
|
+
try {
|
|
192
|
+
const e = Kt(n);
|
|
193
|
+
if (!e)
|
|
194
|
+
return null;
|
|
195
|
+
const { code: o, optionTokens: c } = e, r = st(o);
|
|
196
|
+
if (!r)
|
|
197
|
+
return null;
|
|
198
|
+
const s = Xt(c);
|
|
199
|
+
return {
|
|
200
|
+
fields: r,
|
|
201
|
+
options: s
|
|
202
|
+
};
|
|
203
|
+
} catch (e) {
|
|
204
|
+
e instanceof Error ? console.error(`Error parsing URL: ${e.message}`, e) : console.error("uncaught error", e);
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}, Jt = (t, n) => {
|
|
208
|
+
const e = Bt(t);
|
|
209
|
+
if (e instanceof Error)
|
|
210
|
+
return null;
|
|
211
|
+
let o = null;
|
|
212
|
+
if (n != null && n.endpoint && (o = Q(n == null ? void 0 : n.endpoint), !o))
|
|
213
|
+
throw new Gt(
|
|
214
|
+
"Cannot construct URL: bad endpoint. Ensure endpoint starts with http:// or https://"
|
|
215
|
+
);
|
|
216
|
+
o || (o = Q(Ut));
|
|
217
|
+
const c = [o.origin, "image", e], r = n ? Yt(n) : null;
|
|
218
|
+
return r && c.push(r), n != null && n.filename ? c.push(n.filename) : c.push("image.jpg"), c.join("/");
|
|
219
|
+
}, Kt = (t) => {
|
|
220
|
+
try {
|
|
221
|
+
const n = new URL(t), e = zt(n.pathname.split("/"));
|
|
222
|
+
if (e[0] !== "image" || ![3, 4].includes(e.length))
|
|
223
|
+
throw new Error("Unrecognized URL");
|
|
224
|
+
const o = e[1].trim();
|
|
225
|
+
if (!o.length || !k(o))
|
|
226
|
+
throw new Error("URL is not formatted as base64url");
|
|
227
|
+
if (e.length === 4) {
|
|
228
|
+
const c = e[2].split(",");
|
|
229
|
+
return {
|
|
230
|
+
code: o,
|
|
231
|
+
optionTokens: c
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (e.length === 3)
|
|
235
|
+
return {
|
|
236
|
+
code: o,
|
|
237
|
+
optionTokens: []
|
|
238
|
+
};
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}, it = 0.7, at = 1, O = 24, Qt = y.lg, lt = process.env.NODE_ENV, tn = lt === "development", ut = typeof document > "u", nn = lt === "test", en = ut ? () => {
|
|
244
|
+
} : Et, rn = "<visionary>", N = (t, ...n) => {
|
|
245
|
+
!console || typeof console.log != "function" || console.log(`${rn} ${t}`, ...n);
|
|
246
|
+
}, on = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~", V = (t, n, e) => {
|
|
247
|
+
let o = 0;
|
|
248
|
+
for (; n < e; )
|
|
249
|
+
o *= 83, o += on.indexOf(t[n++]);
|
|
250
|
+
return o;
|
|
251
|
+
}, dt = Math.pow, D = Math.PI, sn = D * 2, ft = 3294.6, pt = 269.025, cn = (t) => t > 10.31475 ? dt(t / pt + 0.052132, 2.4) : t / ft, F = (t) => ~~(t > 1227e-8 ? pt * dt(t, 0.416666) - 13.025 : t * ft + 1), _ = (t) => (t < 0 ? -1 : 1) * t * t, tt = (t) => {
|
|
252
|
+
for (t += D / 2; t > D; )
|
|
253
|
+
t -= sn;
|
|
254
|
+
const n = 1.27323954 * t - 0.405284735 * _(t);
|
|
255
|
+
return 0.225 * (_(n) - n) + n;
|
|
256
|
+
};
|
|
257
|
+
function an(t) {
|
|
258
|
+
const n = V(t, 2, 6);
|
|
259
|
+
return [n >> 16, n >> 8 & 255, n & 255];
|
|
260
|
+
}
|
|
261
|
+
function ln(t, n, e, o) {
|
|
262
|
+
const c = V(t, 0, 1), r = c % 9 + 1, s = ~~(c / 9) + 1, a = r * s;
|
|
263
|
+
let i = 0, d = 0, h = 0, f = 0, m = 0, p = 0, l = 0, v = 0, b = 0, g = 0, w = 0, E = 0, u = 0, I = 0;
|
|
264
|
+
const U = (V(t, 1, 2) + 1) / 13446 * (o | 1), x = new Float64Array(a * 3), z = an(t);
|
|
265
|
+
for (i = 0; i < 3; i++)
|
|
266
|
+
x[i] = cn(z[i]);
|
|
267
|
+
for (i = 1; i < a; i++)
|
|
268
|
+
I = V(t, 4 + i * 2, 6 + i * 2), x[i * 3] = _(~~(I / (19 * 19)) - 9) * U, x[i * 3 + 1] = _(~~(I / 19) % 19 - 9) * U, x[i * 3 + 2] = _(I % 19 - 9) * U;
|
|
269
|
+
const S = n * 4, R = new Uint8ClampedArray(S * e);
|
|
270
|
+
for (f = 0; f < e; f++)
|
|
271
|
+
for (E = D * f / e, h = 0; h < n; h++) {
|
|
272
|
+
for (m = 0, p = 0, l = 0, u = D * h / n, d = 0; d < s; d++)
|
|
273
|
+
for (b = tt(E * d), i = 0; i < r; i++)
|
|
274
|
+
v = tt(u * i) * b, g = (i + d * r) * 3, m += x[g] * v, p += x[g + 1] * v, l += x[g + 2] * v;
|
|
275
|
+
w = 4 * h + f * S, R[w] = F(m), R[w + 1] = F(p), R[w + 2] = F(l), R[w + 3] = 255;
|
|
276
|
+
}
|
|
277
|
+
return R;
|
|
278
|
+
}
|
|
279
|
+
const $ = (t) => {
|
|
280
|
+
try {
|
|
281
|
+
return new URL(t);
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}, un = (t, n) => `rgba(${t.r},${t.g},${t.b},${n})`, H = (t) => tn || nn ? {
|
|
286
|
+
"data-testid": t
|
|
287
|
+
} : {}, dn = (t, n) => n ? {
|
|
288
|
+
"data-fileid": t
|
|
289
|
+
} : {}, nt = (t, n) => Math.min(t, n), fn = (t) => {
|
|
290
|
+
let n = t.replace(/^#/, "");
|
|
291
|
+
if (n.length !== 3 && n.length !== 6)
|
|
292
|
+
return null;
|
|
293
|
+
n.length === 3 && (n = `${n[0]}${n[0]}${n[1]}${n[1]}${n[2]}${n[2]}`);
|
|
294
|
+
const e = parseInt(n.slice(0, 2), 16), o = parseInt(n.slice(2, 4), 16);
|
|
295
|
+
return { b: parseInt(n.slice(4, 6), 16), g: o, r: e };
|
|
296
|
+
}, A = (t, n = 0) => {
|
|
297
|
+
const e = Math.pow(10, n);
|
|
298
|
+
return Math.round(t * e) / e;
|
|
299
|
+
}, pn = (t, n) => {
|
|
300
|
+
const e = $(t), o = $(n);
|
|
301
|
+
return !e || !o ? t : (o.pathname = e.pathname, o.search = e.search, o.toString());
|
|
302
|
+
}, hn = (t, n = {}, e = it, o = at) => {
|
|
303
|
+
if (!t)
|
|
304
|
+
return null;
|
|
305
|
+
try {
|
|
306
|
+
n.debug && N("input imageSrc:", t);
|
|
307
|
+
const c = Zt(t);
|
|
308
|
+
if (n.debug && N("Visionary URL data: ", c), !c)
|
|
309
|
+
throw new Error("Could not parse Visionary URL");
|
|
310
|
+
const { fields: r, options: s } = c;
|
|
311
|
+
if (r.sourceWidth < 1 || r.sourceHeight < 1)
|
|
312
|
+
throw new Error("Invalid image dimensions");
|
|
313
|
+
const a = r.sourceWidth / r.sourceHeight, i = (n == null ? void 0 : n.size) ?? s.size ?? Qt, d = Z[i];
|
|
314
|
+
let h = 0, f = 0, m = 0;
|
|
315
|
+
if (a >= 1) {
|
|
316
|
+
f = h = nt(d, r.sourceWidth);
|
|
317
|
+
const g = A(f / a);
|
|
318
|
+
m = A(f / g, 6);
|
|
319
|
+
} else
|
|
320
|
+
h = nt(d, r.sourceHeight), f = A(h * a), m = A(f / h, 6);
|
|
321
|
+
const p = A(100 / m, 6), l = {
|
|
322
|
+
...r,
|
|
323
|
+
arPaddingTop: `${p}%`,
|
|
324
|
+
aspectRatio: m,
|
|
325
|
+
maxWidth: f,
|
|
326
|
+
src: t
|
|
327
|
+
}, v = $(r.url);
|
|
328
|
+
if (v)
|
|
329
|
+
l.src = v.toString();
|
|
330
|
+
else if (!$(t) && k(r.url)) {
|
|
331
|
+
const g = Jt(r, {
|
|
332
|
+
endpoint: n.endpoint,
|
|
333
|
+
size: i
|
|
334
|
+
});
|
|
335
|
+
g && (l.src = g);
|
|
336
|
+
}
|
|
337
|
+
n.endpoint && (l.src = pn(l.src, n.endpoint));
|
|
338
|
+
const b = r.bcc ? fn(r.bcc) : null;
|
|
339
|
+
if (b && (l.backgroundColor = un(b, e)), !ut && r.blurhash && !n.disableBlurLayer) {
|
|
340
|
+
const g = performance.now(), w = ln(r.blurhash, O, O, o), E = performance.now() - g;
|
|
341
|
+
n.debug && N(`Blurhash decode time: ${A(E, 1)} ms`), w && (l.pixels = w);
|
|
342
|
+
}
|
|
343
|
+
return n.debug && N("Visionary Image state: ", l), l;
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}, ht = {
|
|
348
|
+
bottom: 0,
|
|
349
|
+
left: 0,
|
|
350
|
+
position: "absolute",
|
|
351
|
+
right: 0,
|
|
352
|
+
top: 0
|
|
353
|
+
}, mn = {
|
|
354
|
+
...ht,
|
|
355
|
+
maxHeight: "100%",
|
|
356
|
+
maxWidth: "100%"
|
|
357
|
+
}, gn = {
|
|
358
|
+
...ht,
|
|
359
|
+
height: "100%",
|
|
360
|
+
width: "100%"
|
|
361
|
+
}, X = {
|
|
362
|
+
CANVAS: "canvas",
|
|
363
|
+
CONTAINER: "container",
|
|
364
|
+
IMAGE: "image"
|
|
365
|
+
}, bn = "v7y_7587", vn = "v7y_f2b7", yn = "v7y_b8c0", wn = "v7y_e0dd", P = {
|
|
366
|
+
container: bn,
|
|
367
|
+
hidden: vn,
|
|
368
|
+
preventDrag: yn,
|
|
369
|
+
preventSelection: wn
|
|
370
|
+
}, An = ({
|
|
371
|
+
alt: t,
|
|
372
|
+
bgColorAlpha: n = it,
|
|
373
|
+
className: e,
|
|
374
|
+
debug: o = !1,
|
|
375
|
+
disableBlurLayer: c = !1,
|
|
376
|
+
disableImageLayer: r = !1,
|
|
377
|
+
endpoint: s,
|
|
378
|
+
height: a,
|
|
379
|
+
hideImageLayer: i,
|
|
380
|
+
lazy: d = !0,
|
|
381
|
+
onClick: h,
|
|
382
|
+
onError: f,
|
|
383
|
+
onLoad: m,
|
|
384
|
+
preventDrag: p = !1,
|
|
385
|
+
preventSelection: l = !1,
|
|
386
|
+
punch: v = at,
|
|
387
|
+
size: b,
|
|
388
|
+
src: g,
|
|
389
|
+
style: w,
|
|
390
|
+
width: E
|
|
391
|
+
}) => {
|
|
392
|
+
const u = It(() => hn(g, {
|
|
393
|
+
debug: o,
|
|
394
|
+
disableBlurLayer: c,
|
|
395
|
+
endpoint: s,
|
|
396
|
+
size: b
|
|
397
|
+
}, n, v), [n, o, c, s, v, b, g]), I = Y(null), [U, x] = Ot();
|
|
398
|
+
en(
|
|
399
|
+
function() {
|
|
400
|
+
if (!I.current || !(u != null && u.blurhash))
|
|
401
|
+
return;
|
|
402
|
+
if (!u.pixels) {
|
|
403
|
+
console.warn("No pixel data passed in, skipping");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const bt = performance.now(), M = I.current.getContext("2d");
|
|
407
|
+
if (!M) {
|
|
408
|
+
console.warn("Cannot access canvasRenderingContext, skipping");
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const G = M.createImageData(O, O);
|
|
412
|
+
if (!G) {
|
|
413
|
+
console.warn("Could not create ImageData on CanvasRenderingContext");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (G.data.set(u.pixels), M.putImageData(G, 0, 0), o) {
|
|
417
|
+
const vt = performance.now() - bt;
|
|
418
|
+
N(`Canvas render time: ${A(vt, 1)} ms`);
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
[u == null ? void 0 : u.blurhash, u == null ? void 0 : u.pixels, o]
|
|
422
|
+
);
|
|
423
|
+
const z = q(
|
|
424
|
+
(T) => {
|
|
425
|
+
o && N("Image load error ", T), f && f();
|
|
426
|
+
},
|
|
427
|
+
[o, f]
|
|
428
|
+
), S = {
|
|
429
|
+
alt: t,
|
|
430
|
+
loading: !d || x ? "eager" : "lazy",
|
|
431
|
+
onLoad: m
|
|
432
|
+
};
|
|
433
|
+
if (!u) {
|
|
434
|
+
o && N("Not a Visionary URL, rendering fallback <img />");
|
|
435
|
+
const T = {
|
|
436
|
+
...S,
|
|
437
|
+
className: e,
|
|
438
|
+
height: a,
|
|
439
|
+
onClick: h,
|
|
440
|
+
onError: f,
|
|
441
|
+
src: g,
|
|
442
|
+
style: w,
|
|
443
|
+
width: E
|
|
444
|
+
};
|
|
445
|
+
return /* @__PURE__ */ W("img", { ...T });
|
|
446
|
+
}
|
|
447
|
+
const R = K(P.container, e, {
|
|
448
|
+
[P.preventDrag]: !!p,
|
|
449
|
+
[P.preventSelection]: !!l
|
|
450
|
+
}), B = {
|
|
451
|
+
...{
|
|
452
|
+
"--v-ar": u.arPaddingTop
|
|
453
|
+
},
|
|
454
|
+
aspectRatio: u.aspectRatio,
|
|
455
|
+
backgroundColor: (u == null ? void 0 : u.backgroundColor) ?? void 0,
|
|
456
|
+
maxWidth: u.maxWidth,
|
|
457
|
+
position: "relative",
|
|
458
|
+
width: "100%",
|
|
459
|
+
...w
|
|
460
|
+
};
|
|
461
|
+
E && (B.width = E), a && (B.height = a);
|
|
462
|
+
const mt = {
|
|
463
|
+
onClick: h
|
|
464
|
+
}, gt = K({
|
|
465
|
+
[P.hidden]: !!i
|
|
466
|
+
});
|
|
467
|
+
return /* @__PURE__ */ yt(
|
|
468
|
+
"div",
|
|
469
|
+
{
|
|
470
|
+
className: R,
|
|
471
|
+
ref: U,
|
|
472
|
+
style: B,
|
|
473
|
+
...dn(u.url, o),
|
|
474
|
+
...H(X.CONTAINER),
|
|
475
|
+
...mt,
|
|
476
|
+
children: [
|
|
477
|
+
!c && /* @__PURE__ */ W(
|
|
478
|
+
"canvas",
|
|
479
|
+
{
|
|
480
|
+
height: O,
|
|
481
|
+
ref: I,
|
|
482
|
+
style: gn,
|
|
483
|
+
width: O,
|
|
484
|
+
...H(X.CANVAS)
|
|
485
|
+
}
|
|
486
|
+
),
|
|
487
|
+
!r && // eslint-disable-next-line jsx-a11y/alt-text -- `sharedImgProps` includes alt tag
|
|
488
|
+
/* @__PURE__ */ W(
|
|
489
|
+
"img",
|
|
490
|
+
{
|
|
491
|
+
...S,
|
|
492
|
+
...H(X.IMAGE),
|
|
493
|
+
className: gt,
|
|
494
|
+
onError: z,
|
|
495
|
+
src: u.src,
|
|
496
|
+
style: mn
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
]
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
};
|
|
503
|
+
export {
|
|
504
|
+
An as Image
|
|
505
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
(function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.id="v7y-styles",e.appendChild(document.createTextNode('.v7y_7587:before{content:"";display:block;padding-top:var(--v-ar)}.v7y_f2b7{display:none}.v7y_b8c0{-webkit-user-drag:none;pointer-events:none}.v7y_e0dd{-webkit-user-select:none;user-select:none}')),document.head.appendChild(e)}}catch(n){console.error("vite-plugin-css-injected-by-js",n)}})();
|
|
2
|
+
(function(E,R){typeof exports=="object"&&typeof module<"u"?R(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],R):(E=typeof globalThis<"u"?globalThis:E||self,R(E.VisionaryImage={},E.jsxRuntime,E.React))})(this,function(E,R,x){"use strict";function me(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var K={exports:{}};/*!
|
|
3
|
+
Copyright (c) 2018 Jed Watson.
|
|
4
|
+
Licensed under the MIT License (MIT), see
|
|
5
|
+
http://jedwatson.github.io/classnames
|
|
6
|
+
*/(function(e){(function(){var t={}.hasOwnProperty;function n(){for(var r="",s=0;s<arguments.length;s++){var a=arguments[s];a&&(r=i(r,o(a)))}return r}function o(r){if(typeof r=="string"||typeof r=="number")return r;if(typeof r!="object")return"";if(Array.isArray(r))return n.apply(null,r);if(r.toString!==Object.prototype.toString&&!r.toString.toString().includes("[native code]"))return r.toString();var s="";for(var a in r)t.call(r,a)&&r[a]&&(s=i(s,a));return s}function i(r,s){return s?r?r+" "+s:r+s:r}e.exports?(n.default=n,e.exports=n):window.classNames=n})()})(K);var ge=K.exports;const Q=me(ge),be=(e,{root:t,rootMargin:n,threshold:o}={},i=[])=>{const r=x.useRef(null),s=x.useRef(null);return x.useCallback(c=>{r.current&&s.current&&(s.current.unobserve(r.current),s.current.disconnect(),s.current=null),c&&(s.current=new IntersectionObserver(e,{root:t,rootMargin:n,threshold:o}),s.current.observe(c),r.current=c)},[r,t,n,JSON.stringify(o),...i])},ve=({root:e,rootMargin:t,threshold:n,unobserveOnEnter:o,target:i,onEnter:r,onLeave:s,defaultInView:a}={},c=[])=>{const[d,h]=x.useState({inView:a||!1,entry:null,observer:null}),f=x.useCallback(([p],l)=>{const b=l.thresholds.some(g=>p.intersectionRatio>=g)&&p.isIntersecting;h({inView:b,entry:p,observer:l}),b&&o&&(l.unobserve(p.target),l.disconnect()),b?r==null||r(p,l):s==null||s(p,l)},[r,s,o]),m=be(f,{root:e,rootMargin:t,threshold:n},[o,...c]);return x.useEffect(()=>{i!=null&&i.current&&m(i.current)},[i,m]),[m,d.inView,d.entry,d.observer]};var $=(e=>(e.AUTO="auto",e.AVIF="avif",e.JPEG="jpeg",e.WEBP="webp",e))($||{}),y=(e=>(e.xs="xs",e.sm="sm",e.md="md",e.lg="lg",e.xl="xl",e.xxl="xxl",e["4k"]="4k",e["5k"]="5k",e.full="full",e))(y||{}),C=(e=>(e.DEBUG="debug",e.DOWNLOAD="download",e))(C||{});const ye="https://link.visionary.cloud";$.AUTO,y.lg;const G={[y.xs]:160,[y.sm]:320,[y.md]:640,[y.lg]:1280,[y.xl]:1920,[y.xxl]:2560,[y["4k"]]:3840,[y["5k"]]:5120,[y.full]:0};Object.keys(G).reduce((e,t)=>{const n=t,o=G[n];return o&&(e[o]=n),e},{});const ee="!";var we=Object.defineProperty,xe=(e,t,n)=>t in e?we(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Ie=(e,t,n)=>xe(e,typeof t!="symbol"?t+"":t,n),W={},L={};Object.defineProperty(L,"__esModule",{value:!0});function Ee(e){return String.fromCharCode(parseInt(e.slice(1),16))}function Re(e){return btoa(encodeURIComponent(e).replace(/%[0-9A-F]{2}/g,Ee))}L.encode=Re;function Ae(e){return`%${`00${e.charCodeAt(0).toString(16)}`.slice(-2)}`}function Se(e){return decodeURIComponent(Array.from(atob(e),Ae).join(""))}L.decode=Se,Object.defineProperty(W,"__esModule",{value:!0});const te=L;function Te(e){return te.decode(e.replace(/\-/g,"+").replace(/_/g,"/"))}var Ne=W.decode=Te;function Oe(e){return te.encode(e).replace(/\//g,"_").replace(/\+/g,"-").replace(/=+$/,"")}var Ue=W.encode=Oe;const Ce=e=>e.filter(Boolean),ne=e=>{try{return new URL(e)}catch{return null}},k=(e="")=>/^[A-Za-z0-9_-]*$/.test(e),De=e=>{const{altText:t,bcc:n,blurhash:o,blurhashX:i,blurhashY:r,sourceHeight:s,sourceWidth:a,url:c}=e;if(!c||!a||!s)return new Error("Cannot construct visionary code: missing required url/width/height");const d=[c,a,s];return!n||(d.push(n),!o||!i||!r)||(d.push(o,i,r),t&&t.length&&d.push(t)),Pe(d)},Pe=e=>Ue(e.join(ee)),re=e=>{if(typeof e!="string")return null;const t=e.trim();if(!t.length||!k(t))return null;const n=Ne(t);if(!n)return null;const o=n.split(ee);if(o.length<3)return null;const[i,r,s,a,c,d,h,f]=o,m=i.trim();if(!m.length)return console.error("Cannot parse code, empty file id"),null;const p=Number(r.trim()),l=Number(s.trim());if(isNaN(p)||isNaN(l)||!p||!l)return console.error("Cannot parse Visionary Code: invalid image dimensions",r,s),null;const v=Number(d)??0,b=Number(h)??0;return v<1||b<1?(console.error("Cannot parse Visionary Code: invalid blurhash x, y component dimensions",v,b),null):{altText:f,bcc:a,blurhash:c,blurhashX:v,blurhashY:b,sourceHeight:l,sourceWidth:p,url:m}};class _e extends Error{constructor(){super(...arguments),Ie(this,"message","invalid endpoint URL (does it contain http/https?)")}}const je=e=>e===C.DEBUG,Ve=e=>e===C.DOWNLOAD,$e=e=>Object.values($).includes(e),oe=e=>Object.values(y).includes(e),Le=(e=[])=>{const t={};for(const n of e)oe(n)?t.size=y[n]:je(n)?t.debug=!0:Ve(n)?t.download=!0:$e(n)&&(t.format=n);return t},ke=e=>{if(!e||typeof e!="object")return null;const t=[];return e.debug&&t.push(C.DEBUG),e.download&&t.push(C.DOWNLOAD),e.format&&e.format!==$.AUTO&&t.push(e.format),e.size&&oe(e.size)&&t.push(e.size),t.length?t.sort().join(","):null},ze=e=>{if(k(e)){const t=re(e);if(t)return{fields:t,options:{}}}return Be(e)},Be=e=>{if(!e)return null;const t=e.trim();if(!t)return null;try{const n=Ge(t);if(!n)return null;const{code:o,optionTokens:i}=n,r=re(o);if(!r)return null;const s=Le(i);return{fields:r,options:s}}catch(n){n instanceof Error?console.error(`Error parsing URL: ${n.message}`,n):console.error("uncaught error",n)}return null},Me=(e,t)=>{const n=De(e);if(n instanceof Error)return null;let o=null;if(t!=null&&t.endpoint&&(o=ne(t==null?void 0:t.endpoint),!o))throw new _e("Cannot construct URL: bad endpoint. Ensure endpoint starts with http:// or https://");o||(o=ne(ye));const i=[o.origin,"image",n],r=t?ke(t):null;return r&&i.push(r),t!=null&&t.filename?i.push(t.filename):i.push("image.jpg"),i.join("/")},Ge=e=>{try{const t=new URL(e),n=Ce(t.pathname.split("/"));if(n[0]!=="image"||![3,4].includes(n.length))throw new Error("Unrecognized URL");const o=n[1].trim();if(!o.length||!k(o))throw new Error("URL is not formatted as base64url");if(n.length===4){const i=n[2].split(",");return{code:o,optionTokens:i}}if(n.length===3)return{code:o,optionTokens:[]}}catch{return null}return null},se=.7,ie=1,U=24,We=y.lg,ce=process.env.NODE_ENV,Fe=ce==="development",ae=typeof document>"u",He=ce==="test",qe=ae?()=>{}:x.useLayoutEffect,Xe="<visionary>",T=(e,...t)=>{!console||typeof console.log!="function"||console.log(`${Xe} ${e}`,...t)},Ye="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~",z=(e,t,n)=>{let o=0;for(;t<n;)o*=83,o+=Ye.indexOf(e[t++]);return o},le=Math.pow,D=Math.PI,Ze=D*2,ue=3294.6,de=269.025,Je=e=>e>10.31475?le(e/de+.052132,2.4):e/ue,F=e=>~~(e>1227e-8?de*le(e,.416666)-13.025:e*ue+1),P=e=>(e<0?-1:1)*e*e,fe=e=>{for(e+=D/2;e>D;)e-=Ze;const t=1.27323954*e-.405284735*P(e);return .225*(P(t)-t)+t};function Ke(e){const t=z(e,2,6);return[t>>16,t>>8&255,t&255]}function Qe(e,t,n,o){const i=z(e,0,1),r=i%9+1,s=~~(i/9)+1,a=r*s;let c=0,d=0,h=0,f=0,m=0,p=0,l=0,v=0,b=0,g=0,w=0,A=0,u=0,S=0;const _=(z(e,1,2)+1)/13446*(o|1),I=new Float64Array(a*3),X=Ke(e);for(c=0;c<3;c++)I[c]=Je(X[c]);for(c=1;c<a;c++)S=z(e,4+c*2,6+c*2),I[c*3]=P(~~(S/(19*19))-9)*_,I[c*3+1]=P(~~(S/19)%19-9)*_,I[c*3+2]=P(S%19-9)*_;const j=t*4,O=new Uint8ClampedArray(j*n);for(f=0;f<n;f++)for(A=D*f/n,h=0;h<t;h++){for(m=0,p=0,l=0,u=D*h/t,d=0;d<s;d++)for(b=fe(A*d),c=0;c<r;c++)v=fe(u*c)*b,g=(c+d*r)*3,m+=I[g]*v,p+=I[g+1]*v,l+=I[g+2]*v;w=4*h+f*j,O[w]=F(m),O[w+1]=F(p),O[w+2]=F(l),O[w+3]=255}return O}const B=e=>{try{return new URL(e)}catch{return null}},et=(e,t)=>`rgba(${e.r},${e.g},${e.b},${t})`,H=e=>Fe||He?{"data-testid":e}:{},tt=(e,t)=>t?{"data-fileid":e}:{},pe=(e,t)=>Math.min(e,t),nt=e=>{let t=e.replace(/^#/,"");if(t.length!==3&&t.length!==6)return null;t.length===3&&(t=`${t[0]}${t[0]}${t[1]}${t[1]}${t[2]}${t[2]}`);const n=parseInt(t.slice(0,2),16),o=parseInt(t.slice(2,4),16);return{b:parseInt(t.slice(4,6),16),g:o,r:n}},N=(e,t=0)=>{const n=Math.pow(10,t);return Math.round(e*n)/n},rt=(e,t)=>{const n=B(e),o=B(t);return!n||!o?e:(o.pathname=n.pathname,o.search=n.search,o.toString())},ot=(e,t={},n=se,o=ie)=>{if(!e)return null;try{t.debug&&T("input imageSrc:",e);const i=ze(e);if(t.debug&&T("Visionary URL data: ",i),!i)throw new Error("Could not parse Visionary URL");const{fields:r,options:s}=i;if(r.sourceWidth<1||r.sourceHeight<1)throw new Error("Invalid image dimensions");const a=r.sourceWidth/r.sourceHeight,c=(t==null?void 0:t.size)??s.size??We,d=G[c];let h=0,f=0,m=0;if(a>=1){f=h=pe(d,r.sourceWidth);const g=N(f/a);m=N(f/g,6)}else h=pe(d,r.sourceHeight),f=N(h*a),m=N(f/h,6);const p=N(100/m,6),l={...r,arPaddingTop:`${p}%`,aspectRatio:m,maxWidth:f,src:e},v=B(r.url);if(v)l.src=v.toString();else if(!B(e)&&k(r.url)){const g=Me(r,{endpoint:t.endpoint,size:c});g&&(l.src=g)}t.endpoint&&(l.src=rt(l.src,t.endpoint));const b=r.bcc?nt(r.bcc):null;if(b&&(l.backgroundColor=et(b,n)),!ae&&r.blurhash&&!t.disableBlurLayer){const g=performance.now(),w=Qe(r.blurhash,U,U,o),A=performance.now()-g;t.debug&&T(`Blurhash decode time: ${N(A,1)} ms`),w&&(l.pixels=w)}return t.debug&&T("Visionary Image state: ",l),l}catch{return null}},he={bottom:0,left:0,position:"absolute",right:0,top:0},st={...he,maxHeight:"100%",maxWidth:"100%"},it={...he,height:"100%",width:"100%"},q={CANVAS:"canvas",CONTAINER:"container",IMAGE:"image"},M={container:"v7y_7587",hidden:"v7y_f2b7",preventDrag:"v7y_b8c0",preventSelection:"v7y_e0dd"},ct=({alt:e,bgColorAlpha:t=se,className:n,debug:o=!1,disableBlurLayer:i=!1,disableImageLayer:r=!1,endpoint:s,height:a,hideImageLayer:c,lazy:d=!0,onClick:h,onError:f,onLoad:m,preventDrag:p=!1,preventSelection:l=!1,punch:v=ie,size:b,src:g,style:w,width:A})=>{const u=x.useMemo(()=>ot(g,{debug:o,disableBlurLayer:i,endpoint:s,size:b},t,v),[t,o,i,s,v,b,g]),S=x.useRef(null),[_,I]=ve();qe(function(){if(!S.current||!(u!=null&&u.blurhash))return;if(!u.pixels){console.warn("No pixel data passed in, skipping");return}const ut=performance.now(),Z=S.current.getContext("2d");if(!Z){console.warn("Cannot access canvasRenderingContext, skipping");return}const J=Z.createImageData(U,U);if(!J){console.warn("Could not create ImageData on CanvasRenderingContext");return}if(J.data.set(u.pixels),Z.putImageData(J,0,0),o){const dt=performance.now()-ut;T(`Canvas render time: ${N(dt,1)} ms`)}},[u==null?void 0:u.blurhash,u==null?void 0:u.pixels,o]);const X=x.useCallback(V=>{o&&T("Image load error ",V),f&&f()},[o,f]),j={alt:e,loading:!d||I?"eager":"lazy",onLoad:m};if(!u){o&&T("Not a Visionary URL, rendering fallback <img />");const V={...j,className:n,height:a,onClick:h,onError:f,src:g,style:w,width:A};return R.jsx("img",{...V})}const O=Q(M.container,n,{[M.preventDrag]:!!p,[M.preventSelection]:!!l}),Y={...{"--v-ar":u.arPaddingTop},aspectRatio:u.aspectRatio,backgroundColor:(u==null?void 0:u.backgroundColor)??void 0,maxWidth:u.maxWidth,position:"relative",width:"100%",...w};A&&(Y.width=A),a&&(Y.height=a);const at={onClick:h},lt=Q({[M.hidden]:!!c});return R.jsxs("div",{className:O,ref:_,style:Y,...tt(u.url,o),...H(q.CONTAINER),...at,children:[!i&&R.jsx("canvas",{height:U,ref:S,style:it,width:U,...H(q.CANVAS)}),!r&&R.jsx("img",{...j,...H(q.IMAGE),className:lt,onError:X,src:u.src,style:st})]})};E.Image=ct,Object.defineProperty(E,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "visionary-image",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/visionary-image.umd.js",
|
|
6
|
+
"module": "./dist/visionary-image.es.js",
|
|
7
|
+
"source": "./src/index.ts",
|
|
8
|
+
"types": "./dist/visionary-image.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/visionary-image.es.js",
|
|
12
|
+
"require": "./dist/visionary-image.umd.js",
|
|
13
|
+
"types": "./dist/visionary-image.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc && npm run clean && vite build",
|
|
19
|
+
"clean": "rimraf dist/",
|
|
20
|
+
"dev": "vite",
|
|
21
|
+
"lint": "tsc --noEmit; eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
22
|
+
"preview": "vite preview",
|
|
23
|
+
"test": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
"storybook": "storybook dev -p 6006 --no-open",
|
|
26
|
+
"build-storybook": "storybook build"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist/",
|
|
30
|
+
"package.json",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/visionary-ux/visionary-image.git"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"classnames": "2.5.1",
|
|
39
|
+
"fast-blurhash": "1.1.2",
|
|
40
|
+
"react-hook-inview": "4.5.1",
|
|
41
|
+
"visionary-url": "1.3.1"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": ">=16.9.0",
|
|
45
|
+
"react-dom": ">=16.9.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@storybook/addon-essentials": "8.2.7",
|
|
49
|
+
"@storybook/react": "8.2.7",
|
|
50
|
+
"@storybook/react-vite": "8.2.7",
|
|
51
|
+
"@testing-library/react": "14.3.1",
|
|
52
|
+
"@types/node": "18.19.1",
|
|
53
|
+
"@types/react": "18.3.1",
|
|
54
|
+
"@types/react-dom": "18.3.0",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "7.15.0",
|
|
56
|
+
"@typescript-eslint/parser": "7.15.0",
|
|
57
|
+
"@vitejs/plugin-legacy": "5.4.1",
|
|
58
|
+
"@vitejs/plugin-react-swc": "3.7.0",
|
|
59
|
+
"eslint": "8.57.0",
|
|
60
|
+
"eslint-plugin-jsx-a11y": "6.9.0",
|
|
61
|
+
"eslint-plugin-react": "7.35.0",
|
|
62
|
+
"eslint-plugin-react-hooks": "4.6.2",
|
|
63
|
+
"eslint-plugin-react-refresh": "0.4.9",
|
|
64
|
+
"eslint-plugin-sort-keys-fix": "1.1.2",
|
|
65
|
+
"eslint-plugin-storybook": "0.8.0",
|
|
66
|
+
"eslint-plugin-typescript-sort-keys": "3.2.0",
|
|
67
|
+
"jsdom": "24.1.1",
|
|
68
|
+
"react": "18.3.1",
|
|
69
|
+
"react-dom": "18.3.1",
|
|
70
|
+
"rimraf": "5.0.8",
|
|
71
|
+
"sass": "1.69.5",
|
|
72
|
+
"storybook": "8.2.7",
|
|
73
|
+
"storybook-dark-mode": "4.0.2",
|
|
74
|
+
"typescript": "5.5.4",
|
|
75
|
+
"vite": "5.3.5",
|
|
76
|
+
"vite-plugin-css-injected-by-js": "3.5.1",
|
|
77
|
+
"vite-plugin-dts": "3.9.1",
|
|
78
|
+
"vitest": "2.0.5",
|
|
79
|
+
"vitest-canvas-mock": "0.3.3"
|
|
80
|
+
},
|
|
81
|
+
"keywords": [
|
|
82
|
+
"blurhash",
|
|
83
|
+
"cls",
|
|
84
|
+
"component",
|
|
85
|
+
"core-web-vitals",
|
|
86
|
+
"fid",
|
|
87
|
+
"image",
|
|
88
|
+
"lcp",
|
|
89
|
+
"performance",
|
|
90
|
+
"placeholder",
|
|
91
|
+
"react",
|
|
92
|
+
"seo",
|
|
93
|
+
"speed",
|
|
94
|
+
"visionary",
|
|
95
|
+
"web-vitals"
|
|
96
|
+
]
|
|
97
|
+
}
|