visionary-image 1.0.4 → 1.1.0-pre.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 +34 -26
- package/dist/VisionaryImageElement-DvHh3Z7-.js +100 -0
- package/dist/VisionaryImageElement-Dy9g5-P8.cjs +17 -0
- package/dist/blurhash.cjs +1 -0
- package/dist/blurhash.d.cts +50 -0
- package/dist/blurhash.d.ts +50 -0
- package/dist/blurhash.js +2 -0
- package/dist/canvas-CnN_nKut.cjs +1 -0
- package/dist/canvas-D7cApRRm.js +245 -0
- package/dist/{visionary-image.d.ts → index.d.cts} +14 -9
- package/dist/index.d.ts +84 -0
- package/dist/loader.cjs +1 -0
- package/dist/loader.d.cts +18 -0
- package/dist/loader.d.ts +18 -0
- package/dist/loader.js +4 -0
- package/dist/visionary-image.cjs +2 -0
- package/dist/visionary-image.es.js +499 -512
- package/dist/web-component/register.cjs +1 -0
- package/dist/web-component/register.d.cts +18 -0
- package/dist/web-component/register.d.ts +18 -0
- package/dist/web-component/register.js +4 -0
- package/dist/web-component.cjs +1 -0
- package/dist/web-component.d.cts +71 -0
- package/dist/web-component.d.ts +71 -0
- package/dist/web-component.js +2 -0
- package/package.json +119 -46
- package/dist/visionary-image.umd.js +0 -6
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ React image component with built-in Blurhash placeholders for better UX and Core
|
|
|
9
9
|
- **Easy Blurhash:** Get started with Blurhash in 60 seconds.
|
|
10
10
|
- **Layout stability**: Eliminates Cumulative Layout Shift (CLS) with true-to-size, responsive placeholders.
|
|
11
11
|
- **Automatic lazy loading**: Off-screen images are deferred, reducing initial pageload size and optimizing Interaction to Next Paint (INP).
|
|
12
|
-
- **Lighning-fast previews**: Renders placeholders early (by initial DOM layout) using URL-embedded Blurhash data, powered by [`
|
|
12
|
+
- **Lighning-fast previews**: Renders placeholders early (by initial DOM layout) using URL-embedded Blurhash data, powered by [`blurhash-url`](https://github.com/visionary-ux/blurhash-url).
|
|
13
13
|
- **Framework tested**: Compatible with Remix, Next.js, and Vite and supports both client and server-side rendering (SSR, SSG).
|
|
14
14
|
- **Additional features**: Prevent image dragging; prevent user selecting image
|
|
15
15
|
- **Developer friendly**: Written in Typescript and [unit tested](./src/components/Image/__test__/).
|
|
@@ -32,45 +32,46 @@ npm install --save visionary-image
|
|
|
32
32
|
|
|
33
33
|
## Usage
|
|
34
34
|
|
|
35
|
-
Begin by creating a
|
|
35
|
+
Begin by creating a Blurhash URL. This is then passed to the `src` prop of the Image component.
|
|
36
36
|
|
|
37
|
-
### Creating a
|
|
37
|
+
### Creating a Blurhash URL
|
|
38
38
|
|
|
39
|
-
There are several ways to create a
|
|
39
|
+
There are several ways to create a Blurhash URL.
|
|
40
40
|
|
|
41
|
-
1. Use the [
|
|
41
|
+
1. Use the [Blurhash URL Maker](https://visionary.cloud/url-maker) for public image URLs
|
|
42
42
|
2. Use the [Drag & Drop Blurhash Generator](https://visionary.cloud/image-to-blurhash) for local image files
|
|
43
|
-
3. Use [
|
|
43
|
+
3. Use [blurhash-url](https://github.com/visionary-ux/blurhash-url) to programmatically generate a URL
|
|
44
44
|
|
|
45
45
|
### Render Image
|
|
46
46
|
|
|
47
47
|
```tsx
|
|
48
48
|
import { Image } from "visionary-image";
|
|
49
49
|
|
|
50
|
-
const ImageDetails = () => <Image alt="..." src="<
|
|
50
|
+
const ImageDetails = () => <Image alt="..." src="<Blurhash URL>" />;
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
## Component Props
|
|
54
54
|
|
|
55
|
-
| Name | Description
|
|
56
|
-
| --------------------------------- |
|
|
57
|
-
| `alt` <br/> string | Image `alt` tag. Adding alt text to images is highly recommended to accommodate accessible devices and improve discoverability.
|
|
58
|
-
| `bgColorAlpha` <br/> number | Base layer (background color) alpha channel.<br /> Default: `0.7`
|
|
59
|
-
| `className` <br/> string | Classname applied to the container `div` or the fallback `img` element.
|
|
60
|
-
| `debug` <br/> boolean | Prints handy debug info to the console (
|
|
61
|
-
| `disableBlurLayer` <br/> boolean | Disables rendering of the blur (canvas) layer.
|
|
62
|
-
| `disableImageLayer` <br/> boolean | Disables rendering of the image layer.
|
|
63
|
-
| `height` <br/>number, string | If set, will override internally computed image height. By default, Visionary renders optimally sized images, using the aspect-ratio and max-width placeholder data.
|
|
64
|
-
| `hideImageLayer` <br/> boolean | Hides the image layer, revealing the blur layer underneath.
|
|
65
|
-
| `lazy` <br/> boolean | Should image load lazily. <br/> Default: `true`
|
|
66
|
-
| `onClick` <br/> function | Callback function to invoke when the image is clicked. function.
|
|
67
|
-
| `onError` <br/> function | Error callback function.
|
|
68
|
-
| `onLoad` <br/> function | Image loaded callback function.
|
|
69
|
-
| `preventDrag` <br/>boolean | Prevents user from dragging the image element.
|
|
70
|
-
| `preventSelection` <br/>boolean | Prevents user from highlighting the image element.
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
55
|
+
| Name | Description |
|
|
56
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
57
|
+
| `alt` <br/> string | Image `alt` tag. Adding alt text to images is highly recommended to accommodate accessible devices and improve discoverability. |
|
|
58
|
+
| `bgColorAlpha` <br/> number | Base layer (background color) alpha channel.<br /> Default: `0.7` |
|
|
59
|
+
| `className` <br/> string | Classname applied to the container `div` or the fallback `img` element. |
|
|
60
|
+
| `debug` <br/> boolean | Prints handy debug info to the console (Blurhash URL data, render times). |
|
|
61
|
+
| `disableBlurLayer` <br/> boolean | Disables rendering of the blur (canvas) layer. |
|
|
62
|
+
| `disableImageLayer` <br/> boolean | Disables rendering of the image layer. |
|
|
63
|
+
| `height` <br/>number, string | If set, will override internally computed image height. By default, Visionary renders optimally sized images, using the aspect-ratio and max-width placeholder data. |
|
|
64
|
+
| `hideImageLayer` <br/> boolean | Hides the image layer, revealing the blur layer underneath. |
|
|
65
|
+
| `lazy` <br/> boolean | Should image load lazily. <br/> Default: `true` |
|
|
66
|
+
| `onClick` <br/> function | Callback function to invoke when the image is clicked. function. |
|
|
67
|
+
| `onError` <br/> function | Error callback function. |
|
|
68
|
+
| `onLoad` <br/> function | Image loaded callback function. |
|
|
69
|
+
| `preventDrag` <br/>boolean | Prevents user from dragging the image element. |
|
|
70
|
+
| `preventSelection` <br/>boolean | Prevents user from highlighting the image element. |
|
|
71
|
+
| `priority` <br/>boolean | Mark as priority image (above-the-fold). Sets `fetchpriority="high"` and `loading="eager"`. Use for LCP images.<br /> Default: `false` |
|
|
72
|
+
| `punch` <br/>number | Blurhash punch parameter.<br /> Default: `1` |
|
|
73
|
+
| `src` <br/>string | Blurhash URL, Visionary Code, or ordinary image URL.<br/> If `src` contains Blurhash URL data, placeholders will be rendered, otherwise falls back to an `img` element. <br/> **required** |
|
|
74
|
+
| `width` <br/>number, string | If set, will override internally computed image width. By default, Visionary renders optimally sized images, using the aspect-ratio and max-width placeholder data. |
|
|
74
75
|
|
|
75
76
|
## Relevant Questions
|
|
76
77
|
|
|
@@ -85,3 +86,10 @@ Canvas operations are highly efficient in modern browsers. Rendering the 24x24 p
|
|
|
85
86
|
### What is Blurhash and where can I learn more?
|
|
86
87
|
|
|
87
88
|
Blurhash uses Discrete Cosine Transforms to represent a color-accurate image placeholder as a compact string (in 20-30 characters). Check out the official [Blurhash docs](https://github.com/woltapp/blurhash) for more info.
|
|
89
|
+
|
|
90
|
+
## Related packages
|
|
91
|
+
|
|
92
|
+
| Package | Use for |
|
|
93
|
+
| -------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
94
|
+
| **`visionary-image`** (this package) | React apps (`<Image />`), or the `<visionary-image>` web component |
|
|
95
|
+
| [`visionary-image-js`](https://github.com/visionary-ux/visionary-image-js) | Zero-config `<script>` / CDN, or framework-agnostic SSR HTML |
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { c as e, n as t, o as n, s as r } from "./canvas-D7cApRRm.js";
|
|
2
|
+
//#region src/lib/util.ts
|
|
3
|
+
var i = (e, t) => `rgba(${e.r},${e.g},${e.b},${t})`, a = (e) => {
|
|
4
|
+
let t = e.replace(/^#/, "");
|
|
5
|
+
if (t.length !== 3 && t.length !== 6) return null;
|
|
6
|
+
t.length === 3 && (t = `${t[0]}${t[0]}${t[1]}${t[1]}${t[2]}${t[2]}`);
|
|
7
|
+
let n = parseInt(t.slice(0, 2), 16), r = parseInt(t.slice(2, 4), 16);
|
|
8
|
+
return {
|
|
9
|
+
b: parseInt(t.slice(4, 6), 16),
|
|
10
|
+
g: r,
|
|
11
|
+
r: n
|
|
12
|
+
};
|
|
13
|
+
}, o = null;
|
|
14
|
+
function s() {
|
|
15
|
+
return o ||= new IntersectionObserver((e) => {
|
|
16
|
+
e.forEach((e) => {
|
|
17
|
+
if (e.isIntersecting) {
|
|
18
|
+
let t = e.target;
|
|
19
|
+
t.renderBlurhashNow(), o?.unobserve(t);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}, { rootMargin: "200px" }), o;
|
|
23
|
+
}
|
|
24
|
+
var c = typeof HTMLElement < "u" ? HTMLElement : class {}, l = class extends c {
|
|
25
|
+
canvas = null;
|
|
26
|
+
img = null;
|
|
27
|
+
container = null;
|
|
28
|
+
blurhash = null;
|
|
29
|
+
hasRenderedBlurhash = !1;
|
|
30
|
+
static get observedAttributes() {
|
|
31
|
+
return [
|
|
32
|
+
"src",
|
|
33
|
+
"alt",
|
|
34
|
+
"lazy",
|
|
35
|
+
"priority"
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
connectedCallback() {
|
|
39
|
+
this.render();
|
|
40
|
+
}
|
|
41
|
+
disconnectedCallback() {
|
|
42
|
+
s().unobserve(this);
|
|
43
|
+
}
|
|
44
|
+
attributeChangedCallback() {
|
|
45
|
+
this.container && (this.hasRenderedBlurhash = !1, this.render());
|
|
46
|
+
}
|
|
47
|
+
renderBlurhashNow() {
|
|
48
|
+
this.hasRenderedBlurhash || !this.blurhash || !this.canvas || (this.renderBlurhashToCanvas(this.blurhash), this.hasRenderedBlurhash = !0);
|
|
49
|
+
}
|
|
50
|
+
render() {
|
|
51
|
+
let e = this.getAttribute("src");
|
|
52
|
+
if (!e) return;
|
|
53
|
+
let t = r(e);
|
|
54
|
+
if (!t) {
|
|
55
|
+
this.renderFallbackImage(e);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let { fields: o } = t, { blurhash: c, sourceWidth: l, sourceHeight: u, bcc: d } = o, f = this.getAttribute("priority") === "true", p = !f && this.getAttribute("lazy") !== "false";
|
|
59
|
+
this.blurhash = c || null;
|
|
60
|
+
let m = l / u;
|
|
61
|
+
if (this.container = document.createElement("div"), this.container.style.cssText = `
|
|
62
|
+
position: relative;
|
|
63
|
+
width: 100%;
|
|
64
|
+
aspect-ratio: ${m};
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
`, d) {
|
|
67
|
+
let e = a(d);
|
|
68
|
+
e && (this.container.style.backgroundColor = i(e, n));
|
|
69
|
+
}
|
|
70
|
+
c && (this.canvas = document.createElement("canvas"), this.canvas.width = 24, this.canvas.height = 24, this.canvas.style.cssText = "\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n ", this.container.appendChild(this.canvas), p ? s().observe(this) : (this.renderBlurhashToCanvas(c), this.hasRenderedBlurhash = !0)), this.img = document.createElement("img"), this.img.src = e, this.img.alt = this.getAttribute("alt") || "", this.img.loading = p ? "lazy" : "eager", f && (this.img.fetchPriority = "high"), this.img.style.cssText = "\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n ", this.img.onload = () => {
|
|
71
|
+
this.canvas && (this.canvas.style.opacity = "0", this.canvas.style.transition = "opacity 0.3s ease-out");
|
|
72
|
+
}, this.container.appendChild(this.img), this.innerHTML = "", this.appendChild(this.container);
|
|
73
|
+
}
|
|
74
|
+
renderBlurhashToCanvas(n) {
|
|
75
|
+
if (!this.canvas) return;
|
|
76
|
+
let r = this.canvas.getContext("2d");
|
|
77
|
+
if (!r) return;
|
|
78
|
+
let i = t(), a = `${n}:24:1`, o = i.get(a);
|
|
79
|
+
if (!o) try {
|
|
80
|
+
o = e(n, 24, 24, 1), i.set(a, o);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.warn("Blurhash decode error:", e);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
let s = r.createImageData(24, 24);
|
|
86
|
+
s.data.set(o), r.putImageData(s, 0, 0);
|
|
87
|
+
}
|
|
88
|
+
renderFallbackImage(e) {
|
|
89
|
+
this.innerHTML = "";
|
|
90
|
+
let t = document.createElement("img");
|
|
91
|
+
t.src = e, t.alt = this.getAttribute("alt") || "";
|
|
92
|
+
let n = this.getAttribute("priority") === "true";
|
|
93
|
+
t.loading = n || this.getAttribute("lazy") === "false" ? "eager" : "lazy", n && (t.fetchPriority = "high"), t.style.cssText = "width: 100%; height: auto;", this.appendChild(t);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
function u(e = "visionary-image") {
|
|
97
|
+
typeof HTMLElement > "u" || typeof customElements > "u" || customElements.get(e) || customElements.define(e, l);
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { u as n, l as t };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const e=require("./canvas-CnN_nKut.cjs");var t=(e,t)=>`rgba(${e.r},${e.g},${e.b},${t})`,n=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]}`);let n=parseInt(t.slice(0,2),16),r=parseInt(t.slice(2,4),16);return{b:parseInt(t.slice(4,6),16),g:r,r:n}},r=null;function i(){return r||=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){let t=e.target;t.renderBlurhashNow(),r?.unobserve(t)}})},{rootMargin:`200px`}),r}var a=typeof HTMLElement<`u`?HTMLElement:class{},o=class extends a{canvas=null;img=null;container=null;blurhash=null;hasRenderedBlurhash=!1;static get observedAttributes(){return[`src`,`alt`,`lazy`,`priority`]}connectedCallback(){this.render()}disconnectedCallback(){i().unobserve(this)}attributeChangedCallback(){this.container&&(this.hasRenderedBlurhash=!1,this.render())}renderBlurhashNow(){this.hasRenderedBlurhash||!this.blurhash||!this.canvas||(this.renderBlurhashToCanvas(this.blurhash),this.hasRenderedBlurhash=!0)}render(){let r=this.getAttribute(`src`);if(!r)return;let a=e.s(r);if(!a){this.renderFallbackImage(r);return}let{fields:o}=a,{blurhash:s,sourceWidth:c,sourceHeight:l,bcc:u}=o,d=this.getAttribute(`priority`)===`true`,f=!d&&this.getAttribute(`lazy`)!==`false`;this.blurhash=s||null;let p=c/l;if(this.container=document.createElement(`div`),this.container.style.cssText=`
|
|
2
|
+
position: relative;
|
|
3
|
+
width: 100%;
|
|
4
|
+
aspect-ratio: ${p};
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
`,u){let r=n(u);r&&(this.container.style.backgroundColor=t(r,e.o))}s&&(this.canvas=document.createElement(`canvas`),this.canvas.width=24,this.canvas.height=24,this.canvas.style.cssText=`
|
|
7
|
+
position: absolute;
|
|
8
|
+
inset: 0;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 100%;
|
|
11
|
+
`,this.container.appendChild(this.canvas),f?i().observe(this):(this.renderBlurhashToCanvas(s),this.hasRenderedBlurhash=!0)),this.img=document.createElement(`img`),this.img.src=r,this.img.alt=this.getAttribute(`alt`)||``,this.img.loading=f?`lazy`:`eager`,d&&(this.img.fetchPriority=`high`),this.img.style.cssText=`
|
|
12
|
+
position: absolute;
|
|
13
|
+
inset: 0;
|
|
14
|
+
width: 100%;
|
|
15
|
+
height: 100%;
|
|
16
|
+
object-fit: cover;
|
|
17
|
+
`,this.img.onload=()=>{this.canvas&&(this.canvas.style.opacity=`0`,this.canvas.style.transition=`opacity 0.3s ease-out`)},this.container.appendChild(this.img),this.innerHTML=``,this.appendChild(this.container)}renderBlurhashToCanvas(t){if(!this.canvas)return;let n=this.canvas.getContext(`2d`);if(!n)return;let r=e.n(),i=`${t}:24:1`,a=r.get(i);if(!a)try{a=e.c(t,24,24,1),r.set(i,a)}catch(e){console.warn(`Blurhash decode error:`,e);return}let o=n.createImageData(24,24);o.data.set(a),n.putImageData(o,0,0)}renderFallbackImage(e){this.innerHTML=``;let t=document.createElement(`img`);t.src=e,t.alt=this.getAttribute(`alt`)||``;let n=this.getAttribute(`priority`)===`true`;t.loading=n||this.getAttribute(`lazy`)===`false`?`eager`:`lazy`,n&&(t.fetchPriority=`high`),t.style.cssText=`width: 100%; height: auto;`,this.appendChild(t)}};function s(e=`visionary-image`){typeof HTMLElement>`u`||typeof customElements>`u`||customElements.get(e)||customElements.define(e,o)}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return o}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./canvas-CnN_nKut.cjs");exports.getOrDecodePixels=e.t,exports.getPixelCache=e.n,exports.getRenderedCanvasSet=e.r,exports.initBlurhashCanvases=e.i,exports.initOnDOMLoaded=e.a;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Get or decode blurhash pixels, using cache when available */
|
|
2
|
+
export declare function getOrDecodePixels(blurhash: string, size: number, punch: number): Uint8ClampedArray | null;
|
|
3
|
+
|
|
4
|
+
/** Returns the global pixel cache */
|
|
5
|
+
export declare function getPixelCache(): Map<string, Uint8ClampedArray>;
|
|
6
|
+
|
|
7
|
+
/** Returns the global set tracking rendered canvas keys */
|
|
8
|
+
export declare function getRenderedCanvasSet(): Set<string>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Finds server-rendered canvas elements and renders blurhash to them.
|
|
12
|
+
* Uses IntersectionObserver: above-fold renders immediately, below-fold defers.
|
|
13
|
+
*
|
|
14
|
+
* Exported via `visionary-image/blurhash` for manual control:
|
|
15
|
+
* ```js
|
|
16
|
+
* import { initBlurhashCanvases } from 'visionary-image/blurhash';
|
|
17
|
+
* document.addEventListener('DOMContentLoaded', () => initBlurhashCanvases());
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function initBlurhashCanvases(debug?: boolean): void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Runs initBlurhashCanvases() at DOMContentLoaded (or immediately if already loaded).
|
|
24
|
+
* Called by the loader entry point (`visionary-image/loader`).
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* ```js
|
|
28
|
+
* import 'visionary-image/loader';
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function initOnDOMLoaded(debug?: boolean): void;
|
|
32
|
+
|
|
33
|
+
export { }
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Blurhash rendering utilities shared across entry points:
|
|
37
|
+
*
|
|
38
|
+
* - `visionary-image` (React component) - uses getRenderedCanvasSet(), getOrDecodePixels()
|
|
39
|
+
* - `visionary-image/loader` (early loader) - calls initOnDOMLoaded()
|
|
40
|
+
* - `visionary-image/blurhash` (manual) - exports initBlurhashCanvases()
|
|
41
|
+
* - `visionary-image/web-component` - uses getPixelCache()
|
|
42
|
+
*/
|
|
43
|
+
declare global {
|
|
44
|
+
interface Window {
|
|
45
|
+
/** Set of canvas keys that have been rendered */
|
|
46
|
+
V7Y_CANVAS_RENDERED?: Set<string>;
|
|
47
|
+
/** Cache of decoded blurhash pixels, keyed by "blurhash:size:punch" */
|
|
48
|
+
V7Y_PIXEL_CACHE?: Map<string, Uint8ClampedArray>;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Get or decode blurhash pixels, using cache when available */
|
|
2
|
+
export declare function getOrDecodePixels(blurhash: string, size: number, punch: number): Uint8ClampedArray | null;
|
|
3
|
+
|
|
4
|
+
/** Returns the global pixel cache */
|
|
5
|
+
export declare function getPixelCache(): Map<string, Uint8ClampedArray>;
|
|
6
|
+
|
|
7
|
+
/** Returns the global set tracking rendered canvas keys */
|
|
8
|
+
export declare function getRenderedCanvasSet(): Set<string>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Finds server-rendered canvas elements and renders blurhash to them.
|
|
12
|
+
* Uses IntersectionObserver: above-fold renders immediately, below-fold defers.
|
|
13
|
+
*
|
|
14
|
+
* Exported via `visionary-image/blurhash` for manual control:
|
|
15
|
+
* ```js
|
|
16
|
+
* import { initBlurhashCanvases } from 'visionary-image/blurhash';
|
|
17
|
+
* document.addEventListener('DOMContentLoaded', () => initBlurhashCanvases());
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function initBlurhashCanvases(debug?: boolean): void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Runs initBlurhashCanvases() at DOMContentLoaded (or immediately if already loaded).
|
|
24
|
+
* Called by the loader entry point (`visionary-image/loader`).
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* ```js
|
|
28
|
+
* import 'visionary-image/loader';
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function initOnDOMLoaded(debug?: boolean): void;
|
|
32
|
+
|
|
33
|
+
export { }
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Blurhash rendering utilities shared across entry points:
|
|
37
|
+
*
|
|
38
|
+
* - `visionary-image` (React component) - uses getRenderedCanvasSet(), getOrDecodePixels()
|
|
39
|
+
* - `visionary-image/loader` (early loader) - calls initOnDOMLoaded()
|
|
40
|
+
* - `visionary-image/blurhash` (manual) - exports initBlurhashCanvases()
|
|
41
|
+
* - `visionary-image/web-component` - uses getPixelCache()
|
|
42
|
+
*/
|
|
43
|
+
declare global {
|
|
44
|
+
interface Window {
|
|
45
|
+
/** Set of canvas keys that have been rendered */
|
|
46
|
+
V7Y_CANVAS_RENDERED?: Set<string>;
|
|
47
|
+
/** Cache of decoded blurhash pixels, keyed by "blurhash:size:punch" */
|
|
48
|
+
V7Y_PIXEL_CACHE?: Map<string, Uint8ClampedArray>;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/blurhash.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=(e,t,n)=>{let r=0;for(;t<n;)r=r*83+`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~`.indexOf(e[t++]);return r},t=Math.pow,n=Math.PI,r=3294.6,i=269.025,a=e=>e>10.31475?t(e/i+.052132,2.4):e/r,o=e=>~~(e>1227e-8?i*t(e,.416666)-13.025:e*r+1),s=4096,c=new Uint8ClampedArray(4097);for(let e=0;e<=s;e++)c[e]=o(e/s);var l=e=>e<=0?0:e>=1?255:c[e*s+.5|0],u=e=>(e<0?-1:1)*e*e,d=e=>{for(e+=n/2;e>n;)e-=n*2;let t=1.27323954*e-.405284735*u(e);return .225*(u(t)-t)+t};function f(t){let n=e(t,2,6);return[n>>16,n>>8&255,n&255]}function p(t,r,i,o){let s=e(t,0,1),c=s%9+1,p=~~(s/9)+1,m=c*p,h,g,_,v,y,b,x,S,C,w,T,E,D=(e(t,1,2)+1)/13446*(o|1),O=new Float64Array(m*3),k=f(t);for(h=0;h<3;h++)O[h]=a(k[h]);for(h=1;h<m;h++)E=e(t,4+h*2,6+h*2),O[h*3]=u(~~(E/361)-9)*D,O[h*3+1]=u(~~(E/19)%19-9)*D,O[h*3+2]=u(E%19-9)*D;let A=new Float64Array(p*i),j=new Float64Array(c*r);for(g=0;g<p;g++)for(v=0;v<i;v++)A[g*i+v]=d(n*v*g/i);for(h=0;h<c;h++)for(_=0;_<r;_++)j[h*r+_]=d(n*_*h/r);let M=r*4,N=new Uint8ClampedArray(M*i),P=new Float64Array(c*3);for(v=0;v<i;v++){for(h=0;h<c;h++){for(y=b=x=0,g=0;g<p;g++)C=A[g*i+v],w=(h+g*c)*3,y+=O[w]*C,b+=O[w+1]*C,x+=O[w+2]*C;P[h*3]=y,P[h*3+1]=b,P[h*3+2]=x}for(_=0;_<r;_++){for(y=b=x=0,h=0;h<c;h++)S=j[h*r+_],w=h*3,y+=P[w]*S,b+=P[w+1]*S,x+=P[w+2]*S;T=4*_+v*M,N[T]=l(y),N[T+1]=l(b),N[T+2]=l(x),N[T+3]=255}}return N}var m=(e=>(e.AUTO=`auto`,e.AVIF=`avif`,e.JPEG=`jpeg`,e.WEBP=`webp`,e))(m||{}),h=(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))(h||{}),g=(e=>(e.DEBUG=`debug`,e.DOWNLOAD=`download`,e.FOLLOW=`follow`,e))(g||{});m.AUTO,h.lg;var _={[h.xs]:160,[h.sm]:320,[h.md]:640,[h.lg]:1280,[h.xl]:1920,[h.xxl]:2560,[h[`4k`]]:3840,[h[`5k`]]:5120,[h.full]:0};Object.keys(_).reduce((e,t)=>{let n=t,r=_[n];return r&&(e[r]=n),e},{});var v=null,y=()=>{if(typeof Buffer<`u`&&typeof Buffer.from==`function`)return`buffer`;if(typeof TextEncoder<`u`&&typeof TextDecoder<`u`&&typeof btoa==`function`&&typeof atob==`function`)return`text-encoding`;throw Error(`No UTF-8 base64 implementation available`)},b=()=>(v===null&&(v=y()),v),x=e=>{let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n);return t},S=e=>{let t=e.length%4;return t===0?e:e+`=`.repeat(4-t)},C=e=>Buffer.from(e,`base64`).toString(`utf8`),w=e=>new TextDecoder().decode(x(atob(S(e)))),T=e=>{switch(b()){case`buffer`:return C(e);case`text-encoding`:return w(e)}},E=e=>{if(typeof e!=`string`)throw Error(`decodeBase64Url: input must be a string`);return T(D(e))},D=e=>e.replace(/-/g,`+`).replace(/_/g,`/`),O=class extends Error{message=`invalid blurhash component dimensions`},k=e=>e===g.DEBUG,A=e=>e===g.DOWNLOAD,j=e=>e===g.FOLLOW,M=e=>Object.values(m).includes(e),N=e=>Object.values(h).includes(e),P=(e=[])=>{let t={};for(let n of e)N(n)?t.size=n:k(n)?t.debug=!0:A(n)?t.download=!0:j(n)?t.follow=!0:M(n)&&(t.format=n);return t},F=e=>e.filter(Boolean),I=(e=``)=>/^[A-Za-z0-9_-]*$/.test(e),L=e=>Number.isInteger(e)&&e>0,R=e=>{if(!e||e.length<6)throw new O;let t=z(e[0]);if(t<0)throw new O;let n=t%9+1,r=Math.floor(t/9)+1,i=4+2*n*r;if(e.length!==i)throw new O;return{xComponents:n,yComponents:r}},z=e=>`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~`.indexOf(e),B=e=>{if(typeof e!=`string`)return null;let t=e.trim();if(!t.length||!I(t))return null;let n=E(t);if(!n)return null;let r=n.split(`!`);if(r.length<3||r.length>6)return null;let[i,a,o,s,c,l]=r,u=i.trim();if(!u.length)return null;let d=Number(a.trim()),f=Number(o.trim());if(!L(d)||!L(f))return null;let p={sourceHeight:f,sourceWidth:d,url:u};if(s&&(p.bcc=s),c){p.blurhash=c;try{let e=R(c);p.blurhashX=e.xComponents,p.blurhashY=e.yComponents}catch{return null}}return l&&(p.altText=l),p},V=e=>{if(I(e)){let t=B(e);if(t)return{fields:t,options:{}}}return H(e)},H=e=>{let t=e?.trim();if(!t)return null;try{let e=U(t);if(!e)return null;let{code:n,optionTokens:r}=e,i=B(n);return i?{fields:i,options:P(r)}:null}catch{}return null},U=e=>{try{let t=F(new URL(e).pathname.split(`/`));if(t[0]!==`image`||![3,4].includes(t.length))throw Error(`Unrecognized URL`);let n=t[1].trim();if(!n.length||!I(n))throw Error(`URL is not formatted as base64url`);return t.length===4?{code:n,optionTokens:t[2].split(`,`)}:{code:n,optionTokens:[]}}catch{return null}return null},W=.7;h.lg,process.env.NODE_ENV;function G(){return typeof window>`u`?new Set:(window.V7Y_CANVAS_RENDERED||(window.V7Y_CANVAS_RENDERED=new Set),window.V7Y_CANVAS_RENDERED)}function K(){return typeof window>`u`?new Map:(window.V7Y_PIXEL_CACHE||(window.V7Y_PIXEL_CACHE=new Map),window.V7Y_PIXEL_CACHE)}function q(e,t,n){return`${e}:${t}:${n}`}function J(e,t,n){let r=q(e,t,n),i=K(),a=i.get(r);if(a)return a;try{let a=p(e,t,t,n);return i.set(r,a),a}catch(e){return console.warn(`Blurhash decode error:`,e),null}}var Y=null,X=!1;function Z(){return Y||=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){let t=e.target;Q(t,X),Y?.unobserve(t)}})},{rootMargin:`200px`}),Y}function Q(e,t){let n=e.dataset.v7yKey;if(!n)return;let r=G();if(r.has(n)){t&&console.log(`[visionary-loader] Canvas already rendered, skipping`);return}let i=e.parentElement?.querySelector(`img`);if(!i?.src){t&&console.log(`[visionary-loader] Canvas has no sibling img, skipping`);return}let a=V(i.src);if(!a?.fields?.blurhash){t&&console.log(`[visionary-loader] Sibling img is not a Blurhash URL, skipping`);return}let{blurhash:o}=a.fields;t&&console.log(`[visionary-loader] Rendering canvas:`,{blurhash:o.slice(0,20)+`...`,punch:1,size:24});let s=J(o,24,1);if(!s)return;let c=e.getContext(`2d`);if(!c)return;let l=c.createImageData(24,24);l.data.set(s),c.putImageData(l,0,0),r.add(n),t&&console.log(`[visionary-loader] Canvas rendered at ${performance.now().toFixed(1)}ms`)}function $(e=!1){X=e;let t=document.querySelectorAll(`canvas[data-v7y-key]`);e&&console.log(`[visionary-loader] Found ${t.length} canvas(es) with data-v7y-key`);let n=Z();t.forEach(e=>{n.observe(e)})}function ee(e=!1){if(typeof document>`u`)return;let t=()=>{e&&console.log(`[visionary-loader] initOnDOMLoaded running at ${performance.now().toFixed(1)}ms, readyState: ${document.readyState}`),$(e)};document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,t):t()}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return ee}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return $}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return G}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return J}});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
//#region node_modules/.pnpm/fast-blurhash@1.2.0/node_modules/fast-blurhash/index.js
|
|
2
|
+
var e = (e, t, n) => {
|
|
3
|
+
let r = 0;
|
|
4
|
+
for (; t < n;) r = r * 83 + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".indexOf(e[t++]);
|
|
5
|
+
return r;
|
|
6
|
+
}, t = Math.pow, n = Math.PI, r = 3294.6, i = 269.025, a = (e) => e > 10.31475 ? t(e / i + .052132, 2.4) : e / r, o = (e) => ~~(e > 1227e-8 ? i * t(e, .416666) - 13.025 : e * r + 1), s = 4096, c = /* @__PURE__ */ new Uint8ClampedArray(4097);
|
|
7
|
+
for (let e = 0; e <= s; e++) c[e] = o(e / s);
|
|
8
|
+
var l = (e) => e <= 0 ? 0 : e >= 1 ? 255 : c[e * s + .5 | 0], u = (e) => (e < 0 ? -1 : 1) * e * e, d = (e) => {
|
|
9
|
+
for (e += n / 2; e > n;) e -= n * 2;
|
|
10
|
+
let t = 1.27323954 * e - .405284735 * u(e);
|
|
11
|
+
return .225 * (u(t) - t) + t;
|
|
12
|
+
};
|
|
13
|
+
function f(t) {
|
|
14
|
+
let n = e(t, 2, 6);
|
|
15
|
+
return [
|
|
16
|
+
n >> 16,
|
|
17
|
+
n >> 8 & 255,
|
|
18
|
+
n & 255
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
function p(t, r, i, o) {
|
|
22
|
+
let s = e(t, 0, 1), c = s % 9 + 1, p = ~~(s / 9) + 1, m = c * p, h, g, _, v, y, b, x, S, C, w, T, E, D = (e(t, 1, 2) + 1) / 13446 * (o | 1), O = new Float64Array(m * 3), k = f(t);
|
|
23
|
+
for (h = 0; h < 3; h++) O[h] = a(k[h]);
|
|
24
|
+
for (h = 1; h < m; h++) E = e(t, 4 + h * 2, 6 + h * 2), O[h * 3] = u(~~(E / 361) - 9) * D, O[h * 3 + 1] = u(~~(E / 19) % 19 - 9) * D, O[h * 3 + 2] = u(E % 19 - 9) * D;
|
|
25
|
+
let A = new Float64Array(p * i), j = new Float64Array(c * r);
|
|
26
|
+
for (g = 0; g < p; g++) for (v = 0; v < i; v++) A[g * i + v] = d(n * v * g / i);
|
|
27
|
+
for (h = 0; h < c; h++) for (_ = 0; _ < r; _++) j[h * r + _] = d(n * _ * h / r);
|
|
28
|
+
let M = r * 4, N = new Uint8ClampedArray(M * i), P = new Float64Array(c * 3);
|
|
29
|
+
for (v = 0; v < i; v++) {
|
|
30
|
+
for (h = 0; h < c; h++) {
|
|
31
|
+
for (y = b = x = 0, g = 0; g < p; g++) C = A[g * i + v], w = (h + g * c) * 3, y += O[w] * C, b += O[w + 1] * C, x += O[w + 2] * C;
|
|
32
|
+
P[h * 3] = y, P[h * 3 + 1] = b, P[h * 3 + 2] = x;
|
|
33
|
+
}
|
|
34
|
+
for (_ = 0; _ < r; _++) {
|
|
35
|
+
for (y = b = x = 0, h = 0; h < c; h++) S = j[h * r + _], w = h * 3, y += P[w] * S, b += P[w + 1] * S, x += P[w + 2] * S;
|
|
36
|
+
T = 4 * _ + v * M, N[T] = l(y), N[T + 1] = l(b), N[T + 2] = l(x), N[T + 3] = 255;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return N;
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region node_modules/.pnpm/blurhash-url@1.4.0/node_modules/blurhash-url/dist/constants.js
|
|
43
|
+
var m = /* @__PURE__ */ ((e) => (e.AUTO = "auto", e.AVIF = "avif", e.JPEG = "jpeg", e.WEBP = "webp", e))(m || {}), h = /* @__PURE__ */ ((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))(h || {}), g = /* @__PURE__ */ ((e) => (e.DEBUG = "debug", e.DOWNLOAD = "download", e.FOLLOW = "follow", e))(g || {});
|
|
44
|
+
m.AUTO, h.lg;
|
|
45
|
+
var _ = {
|
|
46
|
+
[h.xs]: 160,
|
|
47
|
+
[h.sm]: 320,
|
|
48
|
+
[h.md]: 640,
|
|
49
|
+
[h.lg]: 1280,
|
|
50
|
+
[h.xl]: 1920,
|
|
51
|
+
[h.xxl]: 2560,
|
|
52
|
+
[h["4k"]]: 3840,
|
|
53
|
+
[h["5k"]]: 5120,
|
|
54
|
+
[h.full]: 0
|
|
55
|
+
};
|
|
56
|
+
Object.keys(_).reduce((e, t) => {
|
|
57
|
+
let n = t, r = _[n];
|
|
58
|
+
return r && (e[r] = n), e;
|
|
59
|
+
}, {});
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region node_modules/.pnpm/visionary-base64url@0.1.4/node_modules/visionary-base64url/dist/index.js
|
|
62
|
+
var v = null, y = () => {
|
|
63
|
+
if (typeof Buffer < "u" && typeof Buffer.from == "function") return "buffer";
|
|
64
|
+
if (typeof TextEncoder < "u" && typeof TextDecoder < "u" && typeof btoa == "function" && typeof atob == "function") return "text-encoding";
|
|
65
|
+
throw Error("No UTF-8 base64 implementation available");
|
|
66
|
+
}, b = () => (v === null && (v = y()), v), x = (e) => {
|
|
67
|
+
let t = new Uint8Array(e.length);
|
|
68
|
+
for (let n = 0; n < e.length; n++) t[n] = e.charCodeAt(n);
|
|
69
|
+
return t;
|
|
70
|
+
}, S = (e) => {
|
|
71
|
+
let t = e.length % 4;
|
|
72
|
+
return t === 0 ? e : e + "=".repeat(4 - t);
|
|
73
|
+
}, C = (e) => Buffer.from(e, "base64").toString("utf8"), w = (e) => new TextDecoder().decode(x(atob(S(e)))), T = (e) => {
|
|
74
|
+
switch (b()) {
|
|
75
|
+
case "buffer": return C(e);
|
|
76
|
+
case "text-encoding": return w(e);
|
|
77
|
+
}
|
|
78
|
+
}, E = (e) => {
|
|
79
|
+
if (typeof e != "string") throw Error("decodeBase64Url: input must be a string");
|
|
80
|
+
return T(D(e));
|
|
81
|
+
}, D = (e) => e.replace(/-/g, "+").replace(/_/g, "/"), O = class extends Error {
|
|
82
|
+
message = "invalid blurhash component dimensions";
|
|
83
|
+
}, k = (e) => e === g.DEBUG, A = (e) => e === g.DOWNLOAD, j = (e) => e === g.FOLLOW, M = (e) => Object.values(m).includes(e), N = (e) => Object.values(h).includes(e), P = (e = []) => {
|
|
84
|
+
let t = {};
|
|
85
|
+
for (let n of e) N(n) ? t.size = n : k(n) ? t.debug = !0 : A(n) ? t.download = !0 : j(n) ? t.follow = !0 : M(n) && (t.format = n);
|
|
86
|
+
return t;
|
|
87
|
+
}, F = (e) => e.filter(Boolean), I = (e = "") => /^[A-Za-z0-9_-]*$/.test(e), L = (e) => Number.isInteger(e) && e > 0, R = (e) => {
|
|
88
|
+
if (!e || e.length < 6) throw new O();
|
|
89
|
+
let t = z(e[0]);
|
|
90
|
+
if (t < 0) throw new O();
|
|
91
|
+
let n = t % 9 + 1, r = Math.floor(t / 9) + 1, i = 4 + 2 * n * r;
|
|
92
|
+
if (e.length !== i) throw new O();
|
|
93
|
+
return {
|
|
94
|
+
xComponents: n,
|
|
95
|
+
yComponents: r
|
|
96
|
+
};
|
|
97
|
+
}, z = (e) => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".indexOf(e), B = (e) => {
|
|
98
|
+
if (typeof e != "string") return null;
|
|
99
|
+
let t = e.trim();
|
|
100
|
+
if (!t.length || !I(t)) return null;
|
|
101
|
+
let n = E(t);
|
|
102
|
+
if (!n) return null;
|
|
103
|
+
let r = n.split("!");
|
|
104
|
+
if (r.length < 3 || r.length > 6) return null;
|
|
105
|
+
let [i, a, o, s, c, l] = r, u = i.trim();
|
|
106
|
+
if (!u.length) return null;
|
|
107
|
+
let d = Number(a.trim()), f = Number(o.trim());
|
|
108
|
+
if (!L(d) || !L(f)) return null;
|
|
109
|
+
let p = {
|
|
110
|
+
sourceHeight: f,
|
|
111
|
+
sourceWidth: d,
|
|
112
|
+
url: u
|
|
113
|
+
};
|
|
114
|
+
if (s && (p.bcc = s), c) {
|
|
115
|
+
p.blurhash = c;
|
|
116
|
+
try {
|
|
117
|
+
let e = R(c);
|
|
118
|
+
p.blurhashX = e.xComponents, p.blurhashY = e.yComponents;
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return l && (p.altText = l), p;
|
|
124
|
+
}, V = (e) => {
|
|
125
|
+
if (I(e)) {
|
|
126
|
+
let t = B(e);
|
|
127
|
+
if (t) return {
|
|
128
|
+
fields: t,
|
|
129
|
+
options: {}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return H(e);
|
|
133
|
+
}, H = (e) => {
|
|
134
|
+
let t = e?.trim();
|
|
135
|
+
if (!t) return null;
|
|
136
|
+
try {
|
|
137
|
+
let e = U(t);
|
|
138
|
+
if (!e) return null;
|
|
139
|
+
let { code: n, optionTokens: r } = e, i = B(n);
|
|
140
|
+
return i ? {
|
|
141
|
+
fields: i,
|
|
142
|
+
options: P(r)
|
|
143
|
+
} : null;
|
|
144
|
+
} catch {}
|
|
145
|
+
return null;
|
|
146
|
+
}, U = (e) => {
|
|
147
|
+
try {
|
|
148
|
+
let t = F(new URL(e).pathname.split("/"));
|
|
149
|
+
if (t[0] !== "image" || ![3, 4].includes(t.length)) throw Error("Unrecognized URL");
|
|
150
|
+
let n = t[1].trim();
|
|
151
|
+
if (!n.length || !I(n)) throw Error("URL is not formatted as base64url");
|
|
152
|
+
return t.length === 4 ? {
|
|
153
|
+
code: n,
|
|
154
|
+
optionTokens: t[2].split(",")
|
|
155
|
+
} : {
|
|
156
|
+
code: n,
|
|
157
|
+
optionTokens: []
|
|
158
|
+
};
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}, W = .7;
|
|
164
|
+
h.lg, process.env.NODE_ENV;
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/lib/canvas.ts
|
|
167
|
+
function G() {
|
|
168
|
+
return typeof window > "u" ? /* @__PURE__ */ new Set() : (window.V7Y_CANVAS_RENDERED || (window.V7Y_CANVAS_RENDERED = /* @__PURE__ */ new Set()), window.V7Y_CANVAS_RENDERED);
|
|
169
|
+
}
|
|
170
|
+
function K() {
|
|
171
|
+
return typeof window > "u" ? /* @__PURE__ */ new Map() : (window.V7Y_PIXEL_CACHE || (window.V7Y_PIXEL_CACHE = /* @__PURE__ */ new Map()), window.V7Y_PIXEL_CACHE);
|
|
172
|
+
}
|
|
173
|
+
function q(e, t, n) {
|
|
174
|
+
return `${e}:${t}:${n}`;
|
|
175
|
+
}
|
|
176
|
+
function J(e, t, n) {
|
|
177
|
+
let r = q(e, t, n), i = K(), a = i.get(r);
|
|
178
|
+
if (a) return a;
|
|
179
|
+
try {
|
|
180
|
+
let a = p(e, t, t, n);
|
|
181
|
+
return i.set(r, a), a;
|
|
182
|
+
} catch (e) {
|
|
183
|
+
return console.warn("Blurhash decode error:", e), null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
var Y = null, X = !1;
|
|
187
|
+
function Z() {
|
|
188
|
+
return Y ||= new IntersectionObserver((e) => {
|
|
189
|
+
e.forEach((e) => {
|
|
190
|
+
if (e.isIntersecting) {
|
|
191
|
+
let t = e.target;
|
|
192
|
+
Q(t, X), Y?.unobserve(t);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}, { rootMargin: "200px" }), Y;
|
|
196
|
+
}
|
|
197
|
+
function Q(e, t) {
|
|
198
|
+
let n = e.dataset.v7yKey;
|
|
199
|
+
if (!n) return;
|
|
200
|
+
let r = G();
|
|
201
|
+
if (r.has(n)) {
|
|
202
|
+
t && console.log("[visionary-loader] Canvas already rendered, skipping");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
let i = e.parentElement?.querySelector("img");
|
|
206
|
+
if (!i?.src) {
|
|
207
|
+
t && console.log("[visionary-loader] Canvas has no sibling img, skipping");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
let a = V(i.src);
|
|
211
|
+
if (!a?.fields?.blurhash) {
|
|
212
|
+
t && console.log("[visionary-loader] Sibling img is not a Blurhash URL, skipping");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
let { blurhash: o } = a.fields;
|
|
216
|
+
t && console.log("[visionary-loader] Rendering canvas:", {
|
|
217
|
+
blurhash: o.slice(0, 20) + "...",
|
|
218
|
+
punch: 1,
|
|
219
|
+
size: 24
|
|
220
|
+
});
|
|
221
|
+
let s = J(o, 24, 1);
|
|
222
|
+
if (!s) return;
|
|
223
|
+
let c = e.getContext("2d");
|
|
224
|
+
if (!c) return;
|
|
225
|
+
let l = c.createImageData(24, 24);
|
|
226
|
+
l.data.set(s), c.putImageData(l, 0, 0), r.add(n), t && console.log(`[visionary-loader] Canvas rendered at ${performance.now().toFixed(1)}ms`);
|
|
227
|
+
}
|
|
228
|
+
function $(e = !1) {
|
|
229
|
+
X = e;
|
|
230
|
+
let t = document.querySelectorAll("canvas[data-v7y-key]");
|
|
231
|
+
e && console.log(`[visionary-loader] Found ${t.length} canvas(es) with data-v7y-key`);
|
|
232
|
+
let n = Z();
|
|
233
|
+
t.forEach((e) => {
|
|
234
|
+
n.observe(e);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function ee(e = !1) {
|
|
238
|
+
if (typeof document > "u") return;
|
|
239
|
+
let t = () => {
|
|
240
|
+
e && console.log(`[visionary-loader] initOnDOMLoaded running at ${performance.now().toFixed(1)}ms, readyState: ${document.readyState}`), $(e);
|
|
241
|
+
};
|
|
242
|
+
document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", t) : t();
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
export { ee as a, p as c, $ as i, K as n, W as o, G as r, V as s, J as t };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { BlurhashUrlOptions } from 'blurhash-url';
|
|
1
2
|
import { CSSProperties } from 'react';
|
|
2
|
-
import { ImageSizeToken } from '
|
|
3
|
-
import { JSX
|
|
4
|
-
import { VisionaryImageOptions } from 'visionary-url';
|
|
3
|
+
import { ImageSizeToken } from 'blurhash-url/constants';
|
|
4
|
+
import { JSX } from 'react/jsx-runtime';
|
|
5
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) =>
|
|
6
|
+
declare const Image_2: ({ alt: altText, bgColorAlpha, className, debug, disableBlurLayer, disableImageLayer, endpoint, height: userHeight, hideImageLayer, lazy, priority, onClick, onError, onLoad, preventDrag, preventSelection, punch, size, src, style: userStyles, width: userWidth, }: VisionaryImageProps) => JSX.Element;
|
|
7
7
|
export { Image_2 as Image }
|
|
8
8
|
|
|
9
9
|
export declare interface VisionaryImageProps {
|
|
@@ -18,7 +18,7 @@ export declare interface VisionaryImageProps {
|
|
|
18
18
|
/** Classname applied to the container `div` or the fallback `img` element. */
|
|
19
19
|
className?: string;
|
|
20
20
|
/**
|
|
21
|
-
* Print debug info (
|
|
21
|
+
* Print debug info (Blurhash URL data, render times) to the console
|
|
22
22
|
*/
|
|
23
23
|
debug?: boolean;
|
|
24
24
|
/**
|
|
@@ -29,7 +29,7 @@ export declare interface VisionaryImageProps {
|
|
|
29
29
|
* Disable rendering of image layer
|
|
30
30
|
*/
|
|
31
31
|
disableImageLayer?: boolean;
|
|
32
|
-
/** Custom endpoint for image URLs (when using `
|
|
32
|
+
/** Custom endpoint for image URLs (when using `generateBlurhashUrl()`) */
|
|
33
33
|
endpoint?: string;
|
|
34
34
|
height?: number | string;
|
|
35
35
|
/**
|
|
@@ -52,7 +52,7 @@ export declare interface VisionaryImageProps {
|
|
|
52
52
|
* Callback function to run after image load
|
|
53
53
|
*/
|
|
54
54
|
onLoad?: () => void;
|
|
55
|
-
options?:
|
|
55
|
+
options?: BlurhashUrlOptions;
|
|
56
56
|
/**
|
|
57
57
|
* Prevents user from dragging the image element
|
|
58
58
|
*/
|
|
@@ -64,11 +64,16 @@ export declare interface VisionaryImageProps {
|
|
|
64
64
|
/**
|
|
65
65
|
* Blurhash punch parameter (default: 1)
|
|
66
66
|
*/
|
|
67
|
+
/**
|
|
68
|
+
* Mark as priority image (above-the-fold). Sets fetchpriority="high" and loading="eager".
|
|
69
|
+
* Use for LCP images to improve Core Web Vitals.
|
|
70
|
+
*/
|
|
71
|
+
priority?: boolean;
|
|
67
72
|
punch?: number;
|
|
68
|
-
/** If specified, overrides the size specified in a
|
|
73
|
+
/** If specified, overrides the size specified in a Blurhash URL */
|
|
69
74
|
size?: ImageSizeToken;
|
|
70
75
|
/**
|
|
71
|
-
* Image `src` prop; if `src` contains a
|
|
76
|
+
* Image `src` prop; if `src` contains a Blurhash URL, a Visionary image is rendered, otherwise falls back to a native `<img />` tag
|
|
72
77
|
*/
|
|
73
78
|
src: string;
|
|
74
79
|
/** Styles are applied to the Visionary container (or the fallback <img> if a Visionary Image is not rendered) */
|