@datocms/svelte 2.0.0 → 2.0.1
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 +19 -5
- package/package/components/Head/Head.svelte.d.ts +1 -1
- package/package/components/Head/README.md +24 -19
- package/package/components/Image/Image.svelte +105 -144
- package/package/components/Image/Image.svelte.d.ts +5 -29
- package/package/components/Image/README.md +99 -49
- package/package/components/Image/utils.d.ts +13 -0
- package/package/components/Image/utils.js +36 -0
- package/package/components/NakedImage/NakedImage.svelte +71 -0
- package/package/components/NakedImage/NakedImage.svelte.d.ts +39 -0
- package/package/components/NakedImage/utils.d.ts +31 -0
- package/package/components/NakedImage/utils.js +48 -0
- package/package/components/StructuredText/README.md +9 -2
- package/package/components/StructuredText/StructuredText.svelte +3 -1
- package/package/components/StructuredText/nodes/ThematicBreak.svelte +1 -1
- package/package/components/VideoPlayer/README.md +16 -4
- package/package/components/VideoPlayer/VideoPlayer.svelte.d.ts +2 -2
- package/package/components/VideoPlayer/__tests__/VideoPlayer.svelte.test.js +2 -2
- package/package/index.d.ts +4 -2
- package/package/index.js +3 -2
- package/package.json +6 -5
- package/package/components/Image/Placeholder.svelte +0 -30
- package/package/components/Image/Placeholder.svelte.d.ts +0 -22
- package/package/components/Image/Sizer.svelte +0 -17
- package/package/components/Image/Sizer.svelte.d.ts +0 -19
- package/package/components/Image/Source.svelte +0 -6
- package/package/components/Image/Source.svelte.d.ts +0 -18
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<!--datocms-autoinclude-header start-->
|
|
2
|
+
|
|
2
3
|
<a href="https://www.datocms.com/"><img src="https://www.datocms.com/images/full_logo.svg" height="60"></a>
|
|
3
4
|
|
|
4
5
|
👉 [Visit the DatoCMS homepage](https://www.datocms.com) or see [What is DatoCMS?](#what-is-datocms)
|
|
6
|
+
|
|
5
7
|
<!--datocms-autoinclude-header end-->
|
|
6
8
|
|
|
7
9
|
# @datocms/svelte
|
|
@@ -14,11 +16,18 @@ A set of components to work faster with [DatoCMS](https://www.datocms.com/) in S
|
|
|
14
16
|
- Written in TypeScript;
|
|
15
17
|
- Usable both client and server side;
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
### Table of Contents
|
|
20
|
+
|
|
21
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
22
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
18
23
|
|
|
19
|
-
- [Features](#features)
|
|
20
|
-
- [Installation](#installation)
|
|
21
|
-
- [Development](#development)
|
|
24
|
+
- [Features](#features)
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [Development](#development)
|
|
27
|
+
- [Building](#building)
|
|
28
|
+
- [What is DatoCMS?](#what-is-datocms)
|
|
29
|
+
|
|
30
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
22
31
|
|
|
23
32
|
## Features
|
|
24
33
|
|
|
@@ -36,6 +45,7 @@ Components:
|
|
|
36
45
|
```
|
|
37
46
|
npm install @datocms/svelte
|
|
38
47
|
```
|
|
48
|
+
|
|
39
49
|
## Development
|
|
40
50
|
|
|
41
51
|
This repository contains some examples in the `app/routes` folder. You can use them to locally test your changes to the package:
|
|
@@ -43,6 +53,7 @@ This repository contains some examples in the `app/routes` folder. You can use t
|
|
|
43
53
|
```bash
|
|
44
54
|
npm run dev
|
|
45
55
|
```
|
|
56
|
+
|
|
46
57
|
## Building
|
|
47
58
|
|
|
48
59
|
To create a production version of this library:
|
|
@@ -52,8 +63,9 @@ npm run build
|
|
|
52
63
|
```
|
|
53
64
|
|
|
54
65
|
<!--datocms-autoinclude-footer start-->
|
|
55
|
-
|
|
66
|
+
|
|
56
67
|
# What is DatoCMS?
|
|
68
|
+
|
|
57
69
|
<a href="https://www.datocms.com/"><img src="https://www.datocms.com/images/full_logo.svg" height="60"></a>
|
|
58
70
|
|
|
59
71
|
[DatoCMS](https://www.datocms.com/) is the REST & GraphQL Headless CMS for the modern web.
|
|
@@ -68,6 +80,7 @@ Trusted by over 25,000 enterprise businesses, agency partners, and individuals a
|
|
|
68
80
|
- 🆕 Stay up to date on new features and fixes on the [changelog](https://www.datocms.com/product-updates)
|
|
69
81
|
|
|
70
82
|
**Our featured repos:**
|
|
83
|
+
|
|
71
84
|
- [datocms/react-datocms](https://github.com/datocms/react-datocms): React helper components for images, Structured Text rendering, and more
|
|
72
85
|
- [datocms/js-rest-api-clients](https://github.com/datocms/js-rest-api-clients): Node and browser JavaScript clients for updating and administering your content. For frontend fetches, we recommend using our [GraphQL Content Delivery API](https://www.datocms.com/docs/content-delivery-api) instead.
|
|
73
86
|
- [datocms/cli](https://github.com/datocms/cli): Command-line interface that includes our [Contentful importer](https://github.com/datocms/cli/tree/main/packages/cli-plugin-contentful) and [Wordpress importer](https://github.com/datocms/cli/tree/main/packages/cli-plugin-wordpress)
|
|
@@ -76,4 +89,5 @@ Trusted by over 25,000 enterprise businesses, agency partners, and individuals a
|
|
|
76
89
|
- Frontend examples in different frameworks: [Next.js](https://github.com/datocms/nextjs-demo), [Vue](https://github.com/datocms/vue-datocms) and [Nuxt](https://github.com/datocms/nuxtjs-demo), [Svelte](https://github.com/datocms/datocms-svelte) and [SvelteKit](https://github.com/datocms/sveltekit-demo), [Astro](https://github.com/datocms/datocms-astro-blog-demo), [Remix](https://github.com/datocms/remix-example). See [all our starter templates](https://www.datocms.com/marketplace/starters).
|
|
77
90
|
|
|
78
91
|
Or see [all our public repos](https://github.com/orgs/datocms/repositories?q=&type=public&language=&sort=stargazers)
|
|
92
|
+
|
|
79
93
|
<!--datocms-autoinclude-footer end-->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
2
|
export interface TitleMetaLinkTag {
|
|
3
3
|
/** the tag for the meta information */
|
|
4
|
-
tag:
|
|
4
|
+
tag: 'title' | 'meta' | 'link';
|
|
5
5
|
/** the inner content of the meta tag */
|
|
6
6
|
content?: string | null | undefined;
|
|
7
7
|
/** the HTML attributes to attach to the meta tag */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# Social share, SEO and Favicon meta tags
|
|
2
2
|
|
|
3
3
|
Just like the image component, `<Head />` is a component specially designed to work seamlessly with DatoCMS’s [`_seoMetaTags` and `faviconMetaTags` GraphQL queries](https://www.datocms.com/docs/content-delivery-api/seo) so that you can handle proper SEO in your pages.
|
|
4
4
|
|
|
@@ -6,27 +6,32 @@ You can use `<Head />` your components, and it will inject title, meta and link
|
|
|
6
6
|
|
|
7
7
|
### Table of contents
|
|
8
8
|
|
|
9
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
10
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
11
|
+
|
|
9
12
|
- [Usage](#usage)
|
|
10
13
|
- [Example](#example)
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
13
18
|
|
|
14
19
|
`<Head />`'s `data` prop takes an array of `Tag`s in the exact form they're returned by the following [DatoCMS GraphQL API](https://www.datocms.com/docs/content-delivery-api/seo) queries:
|
|
15
20
|
|
|
16
21
|
- `_seoMetaTags` query on any record, or
|
|
17
22
|
- `faviconMetaTags` on the global `_site` object.
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
## Example
|
|
20
25
|
|
|
21
26
|
Here is an example:
|
|
22
27
|
|
|
23
28
|
```svelte
|
|
24
29
|
<script>
|
|
25
|
-
|
|
30
|
+
import { onMount } from 'svelte';
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
import { Head } from '@datocms/svelte';
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
const query = `
|
|
30
35
|
query {
|
|
31
36
|
page: homepage {
|
|
32
37
|
title
|
|
@@ -46,22 +51,22 @@ Here is an example:
|
|
|
46
51
|
}
|
|
47
52
|
`;
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
export let data = null;
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
onMount(async () => {
|
|
57
|
+
const response = await fetch('https://graphql.datocms.com/', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
Authorization: 'Bearer faeb9172e232a75339242faafb9e56de8c8f13b735f7090964'
|
|
62
|
+
},
|
|
63
|
+
body: JSON.stringify({ query })
|
|
64
|
+
});
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
const json = await response.json();
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
data = [...json.data.page.seo, ...json.data.site.favicon];
|
|
69
|
+
});
|
|
65
70
|
</script>
|
|
66
71
|
|
|
67
72
|
<Head {data} />
|
|
@@ -1,89 +1,23 @@
|
|
|
1
|
-
<script context="module">import IntersectionObserver from "svelte-intersection-observer";
|
|
2
|
-
const isWindowDefined = typeof window !== "undefined";
|
|
3
|
-
const parseStyleAttributes = (styleString) => {
|
|
4
|
-
const styleRules = {};
|
|
5
|
-
styleString.split(";").forEach((rule) => {
|
|
6
|
-
const [key, value] = rule.split(":").map((part) => part.trim());
|
|
7
|
-
if (key && value) {
|
|
8
|
-
styleRules[key] = value;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
return styleRules;
|
|
12
|
-
};
|
|
13
|
-
const noTypeCheck = (x) => x;
|
|
14
|
-
const imageAddStrategy = ({ lazyLoad, intersecting, loaded }) => {
|
|
15
|
-
const isIntersectionObserverAvailable = isWindowDefined ? !!window.IntersectionObserver : false;
|
|
16
|
-
if (!lazyLoad) {
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
if (!isWindowDefined) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
if (isIntersectionObserverAvailable) {
|
|
23
|
-
return intersecting || loaded;
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
26
|
-
};
|
|
27
|
-
const imageShowStrategy = ({ lazyLoad, loaded }) => {
|
|
28
|
-
const isIntersectionObserverAvailable = isWindowDefined ? !!window.IntersectionObserver : false;
|
|
29
|
-
if (!lazyLoad) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
if (!isWindowDefined) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
if (isIntersectionObserverAvailable) {
|
|
36
|
-
return loaded;
|
|
37
|
-
}
|
|
38
|
-
return true;
|
|
39
|
-
};
|
|
40
|
-
const bogusBaseUrl = "https://example.com/";
|
|
41
|
-
const buildSrcSet = (src, width, candidateMultipliers) => {
|
|
42
|
-
if (!(src && width)) {
|
|
43
|
-
return void 0;
|
|
44
|
-
}
|
|
45
|
-
return candidateMultipliers.map((multiplier) => {
|
|
46
|
-
const url = new URL(src, bogusBaseUrl);
|
|
47
|
-
if (multiplier !== 1) {
|
|
48
|
-
url.searchParams.set("dpr", `${multiplier}`);
|
|
49
|
-
const maxH = url.searchParams.get("max-h");
|
|
50
|
-
const maxW = url.searchParams.get("max-w");
|
|
51
|
-
if (maxH) {
|
|
52
|
-
url.searchParams.set("max-h", `${Math.floor(Number.parseInt(maxH) * multiplier)}`);
|
|
53
|
-
}
|
|
54
|
-
if (maxW) {
|
|
55
|
-
url.searchParams.set("max-w", `${Math.floor(Number.parseInt(maxW) * multiplier)}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const finalWidth = Math.floor(width * multiplier);
|
|
59
|
-
if (finalWidth < 50) {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
return `${url.toString().replace(bogusBaseUrl, "/")} ${finalWidth}w`;
|
|
63
|
-
}).filter(Boolean).join(",");
|
|
64
|
-
};
|
|
65
|
-
</script>
|
|
66
|
-
|
|
67
1
|
<script>import { createEventDispatcher } from "svelte";
|
|
68
|
-
import
|
|
69
|
-
import
|
|
70
|
-
import
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
2
|
+
import IntersectionObserver from "svelte-intersection-observer";
|
|
3
|
+
import { imageAddStrategy, imageShowStrategy, absolutePositioning, universalBtoa } from "./utils";
|
|
4
|
+
import {
|
|
5
|
+
buildSrcSet,
|
|
6
|
+
dumpStyleAttributes,
|
|
7
|
+
noTypeCheck,
|
|
8
|
+
parseStyleAttributes
|
|
9
|
+
} from "../NakedImage/utils";
|
|
76
10
|
export let data;
|
|
77
|
-
let
|
|
78
|
-
export {
|
|
11
|
+
let rootClass = null;
|
|
12
|
+
export { rootClass as class };
|
|
79
13
|
export let pictureClass = null;
|
|
14
|
+
export let placeholderClass = null;
|
|
80
15
|
export let fadeInDuration = 500;
|
|
81
16
|
export let intersectionThreshold = 0;
|
|
82
17
|
export let intersectionMargin = "0px";
|
|
83
|
-
let rawLazyLoad = true;
|
|
84
|
-
export { rawLazyLoad as lazyLoad };
|
|
85
18
|
export let style = null;
|
|
86
19
|
export let pictureStyle = null;
|
|
20
|
+
export let placeholderStyle = null;
|
|
87
21
|
export let layout = "intrinsic";
|
|
88
22
|
export let objectFit = void 0;
|
|
89
23
|
export let objectPosition = void 0;
|
|
@@ -91,105 +25,132 @@ export let usePlaceholder = true;
|
|
|
91
25
|
export let sizes = null;
|
|
92
26
|
export let priority = false;
|
|
93
27
|
export let srcSetCandidates = [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4];
|
|
28
|
+
const dispatch = createEventDispatcher();
|
|
29
|
+
let rootEl;
|
|
30
|
+
let inView = false;
|
|
31
|
+
let loaded = false;
|
|
94
32
|
$:
|
|
95
|
-
({
|
|
96
|
-
$:
|
|
97
|
-
lazyLoad = priority ? false : rawLazyLoad;
|
|
33
|
+
addImage = imageAddStrategy({ priority, inView, loaded });
|
|
98
34
|
$:
|
|
99
|
-
|
|
100
|
-
lazyLoad,
|
|
101
|
-
intersecting,
|
|
102
|
-
loaded
|
|
103
|
-
});
|
|
35
|
+
showImage = imageShowStrategy({ priority, inView, loaded });
|
|
104
36
|
$:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
intersecting,
|
|
108
|
-
loaded
|
|
109
|
-
});
|
|
110
|
-
const absolutePositioning = {
|
|
111
|
-
position: "absolute",
|
|
112
|
-
left: 0,
|
|
113
|
-
top: 0,
|
|
114
|
-
width: "100%",
|
|
115
|
-
height: "100%",
|
|
116
|
-
"max-width": "none",
|
|
117
|
-
"max-height": "none"
|
|
118
|
-
};
|
|
37
|
+
transition = fadeInDuration > 0 ? `opacity ${fadeInDuration}ms` : void 0;
|
|
38
|
+
let basePlaceholderStyle;
|
|
119
39
|
$:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
40
|
+
basePlaceholderStyle = {
|
|
41
|
+
transition,
|
|
42
|
+
opacity: showImage ? 0 : 1,
|
|
43
|
+
// During the opacity transition of the placeholder to the definitive version,
|
|
44
|
+
// hardware acceleration is triggered. This results in the browser trying to render the
|
|
45
|
+
// placeholder with your GPU, causing blurred edges. Solution: style the placeholder
|
|
46
|
+
// so the edges overflow the container
|
|
47
|
+
position: "absolute",
|
|
48
|
+
left: "-5%",
|
|
49
|
+
top: "-5%",
|
|
50
|
+
width: "110%",
|
|
51
|
+
height: "110%",
|
|
52
|
+
"max-width": "none",
|
|
53
|
+
"max-height": "none",
|
|
54
|
+
...parseStyleAttributes(placeholderStyle)
|
|
124
55
|
};
|
|
125
|
-
|
|
56
|
+
$:
|
|
57
|
+
({ width, aspectRatio } = data);
|
|
58
|
+
$:
|
|
59
|
+
height = data.height ?? (aspectRatio ? width / aspectRatio : 0);
|
|
60
|
+
$:
|
|
61
|
+
svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"></svg>`;
|
|
126
62
|
</script>
|
|
127
63
|
|
|
128
64
|
<IntersectionObserver
|
|
129
|
-
{
|
|
130
|
-
bind:intersecting
|
|
65
|
+
element={rootEl}
|
|
66
|
+
bind:intersecting={inView}
|
|
131
67
|
threshold={intersectionThreshold}
|
|
132
68
|
rootMargin={intersectionMargin}
|
|
133
69
|
>
|
|
134
70
|
<div
|
|
135
|
-
bind:this={
|
|
136
|
-
class={
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
71
|
+
bind:this={rootEl}
|
|
72
|
+
class={rootClass}
|
|
73
|
+
style={dumpStyleAttributes({
|
|
74
|
+
overflow: 'hidden',
|
|
75
|
+
...(layout === 'fill'
|
|
76
|
+
? absolutePositioning
|
|
77
|
+
: layout === 'intrinsic'
|
|
78
|
+
? { position: 'relative', width: '100%', 'max-width': `${width}px` }
|
|
79
|
+
: layout === 'fixed'
|
|
80
|
+
? { position: 'relative', width: `${width}px` }
|
|
81
|
+
: { position: 'relative', width: '100%' }),
|
|
82
|
+
...parseStyleAttributes(style)
|
|
83
|
+
})}
|
|
144
84
|
data-testid="image"
|
|
145
85
|
>
|
|
146
|
-
{#if layout !== 'fill'
|
|
147
|
-
<
|
|
86
|
+
{#if layout !== 'fill'}
|
|
87
|
+
<img
|
|
88
|
+
class={pictureClass}
|
|
89
|
+
style={dumpStyleAttributes({
|
|
90
|
+
display: 'block',
|
|
91
|
+
width: '100%',
|
|
92
|
+
...parseStyleAttributes(pictureStyle)
|
|
93
|
+
})}
|
|
94
|
+
src={`data:image/svg+xml;base64,${universalBtoa(svg)}`}
|
|
95
|
+
aria-hidden="true"
|
|
96
|
+
alt=""
|
|
97
|
+
/>
|
|
148
98
|
{/if}
|
|
149
99
|
|
|
150
|
-
{#if usePlaceholder
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
100
|
+
{#if usePlaceholder}
|
|
101
|
+
{#if data.base64}
|
|
102
|
+
<img
|
|
103
|
+
aria-hidden="true"
|
|
104
|
+
alt=""
|
|
105
|
+
src={data.base64}
|
|
106
|
+
class={placeholderClass}
|
|
107
|
+
style={dumpStyleAttributes({
|
|
108
|
+
'object-fit': objectFit,
|
|
109
|
+
'object-position': objectPosition,
|
|
110
|
+
...basePlaceholderStyle
|
|
111
|
+
})}
|
|
112
|
+
/>
|
|
113
|
+
{:else if data.bgColor}
|
|
114
|
+
<div
|
|
115
|
+
class={placeholderClass}
|
|
116
|
+
style={dumpStyleAttributes({
|
|
117
|
+
'background-color': data.bgColor,
|
|
118
|
+
...basePlaceholderStyle
|
|
119
|
+
})}
|
|
120
|
+
/>
|
|
121
|
+
{/if}
|
|
159
122
|
{/if}
|
|
160
123
|
|
|
161
124
|
{#if addImage}
|
|
162
|
-
<picture
|
|
125
|
+
<picture data-testid="picture">
|
|
163
126
|
{#if data.webpSrcSet}
|
|
164
|
-
<
|
|
127
|
+
<source srcset={data.webpSrcSet} sizes={sizes ?? data.sizes ?? null} type="image/webp" />
|
|
165
128
|
{/if}
|
|
166
|
-
<
|
|
129
|
+
<source
|
|
167
130
|
srcset={data.srcSet ?? buildSrcSet(data.src, data.width, srcSetCandidates) ?? null}
|
|
168
131
|
sizes={sizes ?? data.sizes ?? null}
|
|
169
132
|
/>
|
|
170
133
|
{#if data.src}
|
|
171
134
|
<img
|
|
172
|
-
{...noTypeCheck({
|
|
173
|
-
// See: https://github.com/sveltejs/language-tools/issues/1026#issuecomment-1002839154
|
|
174
|
-
fetchpriority: priority ? 'high' : undefined
|
|
175
|
-
})}
|
|
176
135
|
src={data.src}
|
|
177
|
-
alt={
|
|
136
|
+
alt={data.alt ?? ''}
|
|
178
137
|
title={data.title ?? null}
|
|
179
138
|
on:load={() => {
|
|
180
139
|
dispatch('load');
|
|
181
140
|
loaded = true;
|
|
182
141
|
}}
|
|
142
|
+
{...noTypeCheck({
|
|
143
|
+
fetchpriority: priority ? 'high' : undefined
|
|
144
|
+
})}
|
|
183
145
|
class={pictureClass}
|
|
184
|
-
style
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
style:object-position={objectPosition}
|
|
146
|
+
style={dumpStyleAttributes({
|
|
147
|
+
opacity: showImage ? 1 : 0,
|
|
148
|
+
transition,
|
|
149
|
+
...absolutePositioning,
|
|
150
|
+
'object-fit': objectFit,
|
|
151
|
+
'object-position': objectPosition,
|
|
152
|
+
...parseStyleAttributes(pictureStyle)
|
|
153
|
+
})}
|
|
193
154
|
data-testid="img"
|
|
194
155
|
/>
|
|
195
156
|
{/if}
|
|
@@ -1,42 +1,18 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
export type Maybe<T> = T | null;
|
|
3
|
-
export type ResponsiveImageType = {
|
|
4
|
-
/** A base64-encoded thumbnail to offer during image loading */
|
|
5
|
-
base64?: Maybe<string>;
|
|
6
|
-
/** The height of the image */
|
|
7
|
-
height?: Maybe<number>;
|
|
8
|
-
/** The width of the image */
|
|
9
|
-
width: number;
|
|
10
|
-
/** The aspect ratio (width/height) of the image */
|
|
11
|
-
aspectRatio?: number;
|
|
12
|
-
/** The HTML5 `sizes` attribute for the image */
|
|
13
|
-
sizes?: Maybe<string>;
|
|
14
|
-
/** The fallback `src` attribute for the image */
|
|
15
|
-
src?: Maybe<string>;
|
|
16
|
-
/** The HTML5 `srcSet` attribute for the image */
|
|
17
|
-
srcSet?: Maybe<string>;
|
|
18
|
-
/** The HTML5 `srcSet` attribute for the image in WebP format, for browsers that support the format */
|
|
19
|
-
webpSrcSet?: Maybe<string>;
|
|
20
|
-
/** The background color for the image placeholder */
|
|
21
|
-
bgColor?: Maybe<string>;
|
|
22
|
-
/** Alternate text (`alt`) for the image */
|
|
23
|
-
alt?: Maybe<string>;
|
|
24
|
-
/** Title attribute (`title`) for the image */
|
|
25
|
-
title?: Maybe<string>;
|
|
26
|
-
};
|
|
27
2
|
import type * as CSS from 'csstype';
|
|
3
|
+
import { type ResponsiveImageType } from '../NakedImage/utils';
|
|
28
4
|
declare const __propDef: {
|
|
29
5
|
props: {
|
|
30
|
-
alt?: string | null | undefined;
|
|
31
6
|
/** The actual response you get from a DatoCMS `responsiveImage` GraphQL query */ data: ResponsiveImageType;
|
|
32
7
|
/** Additional CSS className for root node */ class?: string | null | undefined;
|
|
33
8
|
/** Additional CSS class for the image inside the `<picture />` tag */ pictureClass?: string | null | undefined;
|
|
9
|
+
/** Additional CSS class for the placeholder element */ placeholderClass?: string | null | undefined;
|
|
34
10
|
/** Duration (in ms) of the fade-in transition effect upoad image loading */ fadeInDuration?: number | undefined;
|
|
35
11
|
/** Indicate at what percentage of the placeholder visibility the loading of the image should be triggered. A value of 0 means that as soon as even one pixel is visible, the callback will be run. A value of 1.0 means that the threshold isn't considered passed until every pixel is visible */ intersectionThreshold?: number | undefined;
|
|
36
12
|
/** Margin around the placeholder. Can have values similar to the CSS margin property (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the placeholder element's bounding box before computing intersections */ intersectionMargin?: string | undefined;
|
|
37
|
-
/** Whether enable lazy loading or not */ lazyLoad?: boolean | undefined;
|
|
38
13
|
/** Additional CSS rules to add to the root node */ style?: string | null | undefined;
|
|
39
14
|
/** Additional CSS rules to add to the image inside the `<picture />` tag */ pictureStyle?: string | null | undefined;
|
|
15
|
+
/** Additional CSS rules to add to the placeholder element */ placeholderStyle?: string | null | undefined;
|
|
40
16
|
/**
|
|
41
17
|
* The layout behavior of the image as the viewport changes size
|
|
42
18
|
*
|
|
@@ -47,8 +23,8 @@ declare const __propDef: {
|
|
|
47
23
|
* * `responsive`: the image will scale the dimensions down for smaller viewports and scale up for larger viewports
|
|
48
24
|
* * `fill`: image will stretch both width and height to the dimensions of the parent element, provided the parent element is `relative`
|
|
49
25
|
**/ layout?: "fill" | "intrinsic" | "fixed" | "responsive" | undefined;
|
|
50
|
-
/** Defines how the image will fit into its parent container when using layout="fill" */ objectFit?: CSS.
|
|
51
|
-
/** Defines how the image is positioned within its parent element when using layout="fill". */ objectPosition?: CSS.
|
|
26
|
+
/** Defines how the image will fit into its parent container when using layout="fill" */ objectFit?: CSS.PropertiesHyphen['object-fit'];
|
|
27
|
+
/** Defines how the image is positioned within its parent element when using layout="fill". */ objectPosition?: CSS.PropertiesHyphen['object-position'];
|
|
52
28
|
/** Whether the component should use a blurred image placeholder */ usePlaceholder?: boolean | undefined;
|
|
53
29
|
/**
|
|
54
30
|
* The HTML5 `sizes` attribute for the image
|
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
# Progressive responsive image
|
|
2
2
|
|
|
3
|
-
`<Image>`
|
|
3
|
+
`<Image>` and `<NakedImage />` are Svelte component specially designed to work seamlessly with DatoCMS’s [`responsiveImage` GraphQL query](https://www.datocms.com/docs/content-delivery-api/uploads#responsive-images) which optimizes image loading for your websites.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
- [Table of contents](#table-of-contents)
|
|
9
|
-
- [Out-of-the-box features](#out-of-the-box-features)
|
|
10
|
-
- [Intersection Observer](#intersection-observer)
|
|
11
|
-
- [Setup](#setup)
|
|
12
|
-
- [Usage](#usage)
|
|
13
|
-
- [Example](#example)
|
|
14
|
-
- [Props](#props)
|
|
15
|
-
- [Layout mode](#layout-mode)
|
|
16
|
-
- [The `ResponsiveImage` object](#the-responsiveimage-object)
|
|
5
|
+
- TypeScript ready;
|
|
6
|
+
- Usable both client and server side;
|
|
7
|
+
- Compatible with vanilla Svelte and Sveltekit;
|
|
17
8
|
|
|
18
9
|
### Out-of-the-box features
|
|
19
10
|
|
|
@@ -23,28 +14,50 @@
|
|
|
23
14
|
- Holds the image position so your page doesn’t jump while images load
|
|
24
15
|
- Uses either blur-up or background color techniques to show a preview of the image while it loads
|
|
25
16
|
|
|
26
|
-
|
|
17
|
+
### Table of contents
|
|
27
18
|
|
|
28
|
-
|
|
19
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
20
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
- [Setup](#setup)
|
|
23
|
+
- [Usage](#usage)
|
|
24
|
+
- [`<Image />` vs `<NakedImage />`](#image--vs-nakedimage-)
|
|
25
|
+
- [Example](#example)
|
|
26
|
+
- [The `ResponsiveImage` object](#the-responsiveimage-object)
|
|
27
|
+
- [`<NakedImage />`](#nakedimage-)
|
|
28
|
+
- [Props](#props)
|
|
29
|
+
- [Events](#events)
|
|
30
|
+
- [`<Image />`](#image-)
|
|
31
|
+
- [Props](#props-1)
|
|
32
|
+
- [Events](#events-1)
|
|
33
|
+
- [Layout mode](#layout-mode)
|
|
34
|
+
- [Intersection Observer](#intersection-observer)
|
|
35
|
+
|
|
36
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
31
37
|
|
|
32
38
|
### Setup
|
|
33
39
|
|
|
34
40
|
You can import the components like this:
|
|
35
41
|
|
|
36
42
|
```js
|
|
37
|
-
import { Image } from '@datocms/svelte';
|
|
43
|
+
import { Image, NakedImage } from '@datocms/svelte';
|
|
38
44
|
```
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
## Usage
|
|
41
47
|
|
|
42
|
-
1. Use `<Image>`
|
|
48
|
+
1. Use `<Image>` or `<NakedImage />` in place of the regular `<img />` tag
|
|
43
49
|
2. Write a GraphQL query to your DatoCMS project using the [`responsiveImage` query](https://www.datocms.com/docs/content-delivery-api/images-and-videos#responsive-images)
|
|
44
50
|
|
|
45
|
-
The GraphQL query returns multiple thumbnails with optimized compression. The
|
|
51
|
+
The GraphQL query returns multiple thumbnails with optimized compression. The components automatically set up the "blur-up" effect as well as lazy loading of images further down the screen.
|
|
52
|
+
|
|
53
|
+
## `<Image />` vs `<NakedImage />`
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
Even though their purpose is the same, there are some significant differences between these two components. Depending on your specific needs, you can choose to use one or the other:
|
|
56
|
+
|
|
57
|
+
- `<NakedImage />` generates minimum JS footprint, outputs a single `<picture />` element and implements lazy-loading using the native [`loading="lazy"` attribute](https://web.dev/articles/browser-level-image-lazy-loading). The placeholder is set as the background to the image itself.
|
|
58
|
+
- `<Image />` has the ability to set a cross-fade effect between the placeholder and the original image, but at the cost of generating more complex HTML output composed of multiple elements around the main `<picture />` element. It also implements lazy-loading through `IntersectionObserver`, which allows customization of the thresholds at which lazy loading occurs.
|
|
59
|
+
|
|
60
|
+
## Example
|
|
48
61
|
|
|
49
62
|
For a fully working example take a look at [`routes` directory](https://github.com/datocms/datocms-svelte/tree/main/src/routes/image/+page.svelte).
|
|
50
63
|
|
|
@@ -55,7 +68,7 @@ Here is a minimal starting point:
|
|
|
55
68
|
|
|
56
69
|
import { onMount } from 'svelte';
|
|
57
70
|
|
|
58
|
-
import { Image } from '@datocms/svelte';
|
|
71
|
+
import { Image, NakedImage } from '@datocms/svelte';
|
|
59
72
|
|
|
60
73
|
const query = gql`
|
|
61
74
|
query {
|
|
@@ -104,10 +117,60 @@ onMount(async () => {
|
|
|
104
117
|
</script>
|
|
105
118
|
|
|
106
119
|
{#if data}
|
|
107
|
-
|
|
120
|
+
<Image data={data.blogPost.cover.responsiveImage} />
|
|
121
|
+
<NakedImage data={data.blogPost.cover.responsiveImage} />
|
|
108
122
|
{/if}
|
|
109
123
|
```
|
|
110
124
|
|
|
125
|
+
## The `ResponsiveImage` object
|
|
126
|
+
|
|
127
|
+
The `data` prop of both components expects an object with the same shape as the one returned by `responsiveImage` GraphQL call. It's up to you to make a GraphQL query that will return the properties you need for a specific use of the `<datocms-image>` component.
|
|
128
|
+
|
|
129
|
+
- The minimum required properties for `data` are: `src`, `width` and `height`;
|
|
130
|
+
- `alt` and `title`, while not mandatory, are all highly suggested, so remember to use them!
|
|
131
|
+
- If you don't request `srcSet`, the component will auto-generate an `srcset` based on `src` + the `srcSetCandidates` prop (it can help reducing the GraphQL response size drammatically when many images are returned);
|
|
132
|
+
- We strongly to suggest to always specify [`{ auto: format }`](https://docs.imgix.com/apis/rendering/auto/auto#format) in your `imgixParams`, instead of requesting `webpSrcSet`, so that you can also take advantage of more performant optimizations (AVIF), without increasing GraphQL response size;
|
|
133
|
+
- If you request both the `bgColor` and `base64` property, the latter will take precedence, so just avoid querying both fields at the same time, as it will only make the GraphQL response bigger :wink:;
|
|
134
|
+
- You can avoid requesting `sizes` and directly pass a `sizes` prop to the component to reduce the GraphQL response size;
|
|
135
|
+
|
|
136
|
+
Here's a complete recap of what `responsiveImage` offers:
|
|
137
|
+
|
|
138
|
+
| property | type | required | description |
|
|
139
|
+
| ----------- | ------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| src | string | :white_check_mark: | The `src` attribute for the image |
|
|
141
|
+
| width | integer | :white_check_mark: | The width of the image |
|
|
142
|
+
| height | integer | :white_check_mark: | The height of the image |
|
|
143
|
+
| alt | string | :x: | Alternate text (`alt`) for the image (not required, but strongly suggested!) |
|
|
144
|
+
| title | string | :x: | Title attribute (`title`) for the image (not required, but strongly suggested!) |
|
|
145
|
+
| sizes | string | :x: | The HTML5 `sizes` attribute for the image (omit it if you're already passing a `sizes` prop to the Image component) |
|
|
146
|
+
| base64 | string | :x: | A base64-encoded thumbnail to offer during image loading |
|
|
147
|
+
| bgColor | string | :x: | The background color for the image placeholder (omit it if you're already requesting `base64`) |
|
|
148
|
+
| srcSet | string | :x: | The HTML5 `srcSet` attribute for the image (can be omitted, the Image component knows how to build it based on `src`) |
|
|
149
|
+
| webpSrcSet | string | :x: | The HTML5 `srcSet` attribute for the image in WebP format (deprecated, it's better to use the [`auto=format`](https://docs.imgix.com/apis/rendering/auto/auto#format) Imgix transform instead) |
|
|
150
|
+
| aspectRatio | float | :x: | The aspect ratio (width/height) of the image |
|
|
151
|
+
|
|
152
|
+
## `<NakedImage />`
|
|
153
|
+
|
|
154
|
+
### Props
|
|
155
|
+
|
|
156
|
+
| prop | type | default | required | description |
|
|
157
|
+
| ---------------- | ------------------------ | ---------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
158
|
+
| data | `ResponsiveImage` object | | :white_check_mark: | The actual response you get from a DatoCMS `responsiveImage` GraphQL query \*\*\*\* |
|
|
159
|
+
| class | string | null | :x: | Additional CSS class for image |
|
|
160
|
+
| style | CSS properties | null | :x: | Additional CSS rules to add to the image |
|
|
161
|
+
| priority | Boolean | false | :x: | Disables lazy loading, and sets the image [fetchPriority](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority) to "high" |
|
|
162
|
+
| sizes | string | undefined | :x: | The HTML5 [`sizes`](https://web.dev/learn/design/responsive-images/#sizes) attribute for the image (will be used `data.sizes` as a fallback) |
|
|
163
|
+
| usePlaceholder | Boolean | true | :x: | Whether the image should use a blurred image placeholder |
|
|
164
|
+
| srcSetCandidates | Array<number> | [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4] | :x: | If `data` does not contain `srcSet`, the candidates for the `srcset` attribute of the image will be auto-generated based on these width multipliers |
|
|
165
|
+
|
|
166
|
+
### Events
|
|
167
|
+
|
|
168
|
+
| prop | description |
|
|
169
|
+
| ----- | ------------------------------------------- |
|
|
170
|
+
| @load | Emitted when the image has finished loading |
|
|
171
|
+
|
|
172
|
+
## `<Image />`
|
|
173
|
+
|
|
111
174
|
### Props
|
|
112
175
|
|
|
113
176
|
| prop | type | default | required | description |
|
|
@@ -131,7 +194,15 @@ onMount(async () => {
|
|
|
131
194
|
| onLoad | () => void | undefined | :x: | Function triggered when the image has finished loading |
|
|
132
195
|
| usePlaceholder | Boolean | true | :x: | Whether the component should use a blurred image placeholder |
|
|
133
196
|
|
|
134
|
-
|
|
197
|
+
### Events
|
|
198
|
+
|
|
199
|
+
| prop | description |
|
|
200
|
+
| ----- | ------------------------------------------- |
|
|
201
|
+
| @load | Emitted when the image has finished loading |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### Layout mode
|
|
135
206
|
|
|
136
207
|
With the `layout` property, you can configure the behavior of the image as the viewport changes size:
|
|
137
208
|
|
|
@@ -142,29 +213,8 @@ With the `layout` property, you can configure the behavior of the image as the v
|
|
|
142
213
|
- This is usually paired with the `objectFit` and `objectPosition` properties.
|
|
143
214
|
- Ensure the parent element has `position: relative` in their stylesheet.
|
|
144
215
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
The `data` prop expects an object with the same shape as the one returned by `responsiveImage` GraphQL call. It's up to you to make a GraphQL query that will return the properties you need for a specific use of the `<datocms-image>` component.
|
|
148
|
-
|
|
149
|
-
- The minimum required properties for `data` are: `src`, `width` and `height`;
|
|
150
|
-
- `alt` and `title`, while not mandatory, are all highly suggested, so remember to use them!
|
|
151
|
-
- If you don't request `srcSet`, the component will auto-generate an `srcset` based on `src` + the `srcSetCandidates` prop (it can help reducing the GraphQL response size drammatically when many images are returned);
|
|
152
|
-
- We strongly to suggest to always specify [`{ auto: format }`](https://docs.imgix.com/apis/rendering/auto/auto#format) in your `imgixParams`, instead of requesting `webpSrcSet`, so that you can also take advantage of more performant optimizations (AVIF), without increasing GraphQL response size;
|
|
153
|
-
- If you request both the `bgColor` and `base64` property, the latter will take precedence, so just avoid querying both fields at the same time, as it will only make the GraphQL response bigger :wink:;
|
|
154
|
-
- You can avoid requesting `sizes` and directly pass a `sizes` prop to the component to reduce the GraphQL response size;
|
|
216
|
+
### Intersection Observer
|
|
155
217
|
|
|
156
|
-
|
|
218
|
+
`IntersectionObserver` is the API used to determine if the image is inside the viewport or not. [Browser support is really good](https://caniuse.com/intersectionobserver): with Safari adding support in 12.1, all major browsers now support `IntersectionObserver` natively.
|
|
157
219
|
|
|
158
|
-
|
|
159
|
-
| ----------- | ------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
160
|
-
| src | string | :white_check_mark: | The `src` attribute for the image |
|
|
161
|
-
| width | integer | :white_check_mark: | The width of the image |
|
|
162
|
-
| height | integer | :white_check_mark: | The height of the image |
|
|
163
|
-
| alt | string | :x: | Alternate text (`alt`) for the image (not required, but strongly suggested!) |
|
|
164
|
-
| title | string | :x: | Title attribute (`title`) for the image (not required, but strongly suggested!) |
|
|
165
|
-
| sizes | string | :x: | The HTML5 `sizes` attribute for the image (omit it if you're already passing a `sizes` prop to the Image component) |
|
|
166
|
-
| base64 | string | :x: | A base64-encoded thumbnail to offer during image loading |
|
|
167
|
-
| bgColor | string | :x: | The background color for the image placeholder (omit it if you're already requesting `base64`) |
|
|
168
|
-
| srcSet | string | :x: | The HTML5 `srcSet` attribute for the image (can be omitted, the Image component knows how to build it based on `src`) |
|
|
169
|
-
| webpSrcSet | string | :x: | The HTML5 `srcSet` attribute for the image in WebP format (deprecated, it's better to use the [`auto=format`](https://docs.imgix.com/apis/rendering/auto/auto#format) Imgix transform instead) |
|
|
170
|
-
| aspectRatio | float | :x: | The aspect ratio (width/height) of the image |
|
|
220
|
+
If `IntersectionObserver` object is not available, the component treats the image as it's always visible in the viewport. Feel free to add a [polyfill](https://www.npmjs.com/package/intersection-observer) so that it will also 100% work on older versions of iOS and IE11.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type * as CSS from 'csstype';
|
|
2
|
+
export declare const isSsr: () => boolean;
|
|
3
|
+
export declare const isIntersectionObserverAvailable: () => boolean;
|
|
4
|
+
export declare const universalBtoa: (str: string) => string;
|
|
5
|
+
type State = {
|
|
6
|
+
priority: boolean;
|
|
7
|
+
inView: boolean;
|
|
8
|
+
loaded: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const imageAddStrategy: ({ priority, inView, loaded }: State) => boolean;
|
|
11
|
+
export declare const imageShowStrategy: ({ priority, loaded }: State) => boolean;
|
|
12
|
+
export declare const absolutePositioning: CSS.PropertiesHyphen;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const isSsr = () => typeof window === 'undefined';
|
|
2
|
+
export const isIntersectionObserverAvailable = () => isSsr() ? false : !!window.IntersectionObserver;
|
|
3
|
+
export const universalBtoa = (str) => isSsr() ? Buffer.from(str.toString(), 'binary').toString('base64') : window.btoa(str);
|
|
4
|
+
export const imageAddStrategy = ({ priority, inView, loaded }) => {
|
|
5
|
+
if (priority) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
if (isSsr()) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (isIntersectionObserverAvailable()) {
|
|
12
|
+
return inView || loaded;
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
};
|
|
16
|
+
export const imageShowStrategy = ({ priority, loaded }) => {
|
|
17
|
+
if (priority) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (isSsr()) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (isIntersectionObserverAvailable()) {
|
|
24
|
+
return loaded;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
export const absolutePositioning = {
|
|
29
|
+
position: 'absolute',
|
|
30
|
+
left: '0px',
|
|
31
|
+
top: '0px',
|
|
32
|
+
width: '100%',
|
|
33
|
+
height: '100%',
|
|
34
|
+
'max-width': 'none',
|
|
35
|
+
'max-height': 'none'
|
|
36
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script>import { createEventDispatcher } from "svelte";
|
|
2
|
+
import {
|
|
3
|
+
buildSrcSet,
|
|
4
|
+
dumpStyleAttributes,
|
|
5
|
+
noTypeCheck,
|
|
6
|
+
parseStyleAttributes
|
|
7
|
+
} from "./utils";
|
|
8
|
+
export let data;
|
|
9
|
+
let rootClass = null;
|
|
10
|
+
export { rootClass as class };
|
|
11
|
+
export let style = null;
|
|
12
|
+
export let usePlaceholder = true;
|
|
13
|
+
export let sizes = null;
|
|
14
|
+
export let priority = false;
|
|
15
|
+
export let srcSetCandidates = [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4];
|
|
16
|
+
const dispatch = createEventDispatcher();
|
|
17
|
+
let loaded = false;
|
|
18
|
+
let placeholderStyle;
|
|
19
|
+
$:
|
|
20
|
+
placeholderStyle = usePlaceholder && !loaded && data.base64 ? {
|
|
21
|
+
"background-image": `url("${data.base64}")`,
|
|
22
|
+
"background-size": "cover",
|
|
23
|
+
"background-repeat": "no-repeat",
|
|
24
|
+
"background-position": "50% 50%",
|
|
25
|
+
color: "transparent"
|
|
26
|
+
} : usePlaceholder && !loaded && data.bgColor ? { "background-color": data.bgColor ?? void 0, color: "transparent" } : void 0;
|
|
27
|
+
$:
|
|
28
|
+
({ width } = data);
|
|
29
|
+
$:
|
|
30
|
+
height = data.height ?? Math.round(data.aspectRatio ? width / data.aspectRatio : 0);
|
|
31
|
+
let sizingStyle;
|
|
32
|
+
$:
|
|
33
|
+
sizingStyle = {
|
|
34
|
+
"aspect-ratio": `${width} / ${height}`,
|
|
35
|
+
width: "100%",
|
|
36
|
+
"max-width": `${width}px`,
|
|
37
|
+
height: "auto"
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<picture data-testid="picture">
|
|
42
|
+
{#if data.webpSrcSet}
|
|
43
|
+
<source srcset={data.webpSrcSet} sizes={sizes ?? data.sizes ?? null} type="image/webp" />
|
|
44
|
+
{/if}
|
|
45
|
+
<source
|
|
46
|
+
srcset={data.srcSet ?? buildSrcSet(data.src, data.width, srcSetCandidates) ?? null}
|
|
47
|
+
sizes={sizes ?? data.sizes ?? null}
|
|
48
|
+
/>
|
|
49
|
+
{#if data.src}
|
|
50
|
+
<img
|
|
51
|
+
src={data.src}
|
|
52
|
+
alt={data.alt ?? ''}
|
|
53
|
+
title={data.title ?? null}
|
|
54
|
+
on:load={() => {
|
|
55
|
+
dispatch('load');
|
|
56
|
+
loaded = true;
|
|
57
|
+
}}
|
|
58
|
+
{...noTypeCheck({
|
|
59
|
+
fetchpriority: priority ? 'high' : undefined
|
|
60
|
+
})}
|
|
61
|
+
loading={priority ? undefined : 'lazy'}
|
|
62
|
+
class={rootClass}
|
|
63
|
+
style={dumpStyleAttributes({
|
|
64
|
+
...placeholderStyle,
|
|
65
|
+
...sizingStyle,
|
|
66
|
+
...parseStyleAttributes(style)
|
|
67
|
+
})}
|
|
68
|
+
data-testid="img"
|
|
69
|
+
/>
|
|
70
|
+
{/if}
|
|
71
|
+
</picture>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
import { type ResponsiveImageType } from './utils';
|
|
3
|
+
declare const __propDef: {
|
|
4
|
+
props: {
|
|
5
|
+
/** The actual response you get from a DatoCMS `responsiveImage` GraphQL query */ data: ResponsiveImageType;
|
|
6
|
+
/** Additional CSS className for root node */ class?: string | null | undefined;
|
|
7
|
+
/** Additional CSS rules to add to the root node */ style?: string | null | undefined;
|
|
8
|
+
/** Whether the component should use a blurred image placeholder */ usePlaceholder?: boolean | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* The HTML5 `sizes` attribute for the image
|
|
11
|
+
*
|
|
12
|
+
* Learn more about srcset and sizes:
|
|
13
|
+
* -> https://web.dev/learn/design/responsive-images/#sizes
|
|
14
|
+
* -> https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes
|
|
15
|
+
**/ sizes?: string | null | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* When true, the image will be considered high priority. Lazy loading is automatically disabled, and fetchpriority="high" is added to the image.
|
|
18
|
+
* You should use the priority property on any image detected as the Largest Contentful Paint (LCP) element. It may be appropriate to have multiple priority images, as different images may be the LCP element for different viewport sizes.
|
|
19
|
+
* Should only be used when the image is visible above the fold.
|
|
20
|
+
**/ priority?: boolean | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* If `data` does not contain `srcSet`, the candidates for the `srcset` of the image will be auto-generated based on these width multipliers
|
|
23
|
+
*
|
|
24
|
+
* Default candidate multipliers are [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4]
|
|
25
|
+
**/ srcSetCandidates?: number[] | undefined;
|
|
26
|
+
};
|
|
27
|
+
events: {
|
|
28
|
+
load: CustomEvent<any>;
|
|
29
|
+
} & {
|
|
30
|
+
[evt: string]: CustomEvent<any>;
|
|
31
|
+
};
|
|
32
|
+
slots: {};
|
|
33
|
+
};
|
|
34
|
+
export type NakedImageProps = typeof __propDef.props;
|
|
35
|
+
export type NakedImageEvents = typeof __propDef.events;
|
|
36
|
+
export type NakedImageSlots = typeof __propDef.slots;
|
|
37
|
+
export default class NakedImage extends SvelteComponentTyped<NakedImageProps, NakedImageEvents, NakedImageSlots> {
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type * as CSS from 'csstype';
|
|
2
|
+
type Maybe<T> = T | null;
|
|
3
|
+
export type ResponsiveImageType = {
|
|
4
|
+
/** A base64-encoded thumbnail to offer during image loading */
|
|
5
|
+
base64?: Maybe<string>;
|
|
6
|
+
/** The height of the image */
|
|
7
|
+
height?: Maybe<number>;
|
|
8
|
+
/** The width of the image */
|
|
9
|
+
width: number;
|
|
10
|
+
/** The aspect ratio (width/height) of the image */
|
|
11
|
+
aspectRatio?: number;
|
|
12
|
+
/** The HTML5 `sizes` attribute for the image */
|
|
13
|
+
sizes?: Maybe<string>;
|
|
14
|
+
/** The fallback `src` attribute for the image */
|
|
15
|
+
src?: Maybe<string>;
|
|
16
|
+
/** The HTML5 `srcSet` attribute for the image */
|
|
17
|
+
srcSet?: Maybe<string>;
|
|
18
|
+
/** The HTML5 `srcSet` attribute for the image in WebP format, for browsers that support the format */
|
|
19
|
+
webpSrcSet?: Maybe<string>;
|
|
20
|
+
/** The background color for the image placeholder */
|
|
21
|
+
bgColor?: Maybe<string>;
|
|
22
|
+
/** Alternate text (`alt`) for the image */
|
|
23
|
+
alt?: Maybe<string>;
|
|
24
|
+
/** Title attribute (`title`) for the image */
|
|
25
|
+
title?: Maybe<string>;
|
|
26
|
+
};
|
|
27
|
+
export declare const parseStyleAttributes: (styleString: string | null) => CSS.PropertiesHyphen;
|
|
28
|
+
export declare const dumpStyleAttributes: (style: CSS.PropertiesHyphen) => string;
|
|
29
|
+
export declare const noTypeCheck: (x: object) => object;
|
|
30
|
+
export declare const buildSrcSet: (src: string | null | undefined, width: number | undefined, candidateMultipliers: number[]) => string | undefined;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const parseStyleAttributes = (styleString) => {
|
|
2
|
+
const styleRules = {};
|
|
3
|
+
if (styleString) {
|
|
4
|
+
for (const rule of styleString.split(';')) {
|
|
5
|
+
const [key, value] = rule.split(':').map((part) => part.trim());
|
|
6
|
+
if (key && value) {
|
|
7
|
+
styleRules[key] = value;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return styleRules;
|
|
12
|
+
};
|
|
13
|
+
export const dumpStyleAttributes = (style) => {
|
|
14
|
+
return Object.entries(style)
|
|
15
|
+
.map(([key, value]) => typeof value !== 'undefined' && value !== 'null' ? `${key}: ${value};` : undefined)
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.join(' ');
|
|
18
|
+
};
|
|
19
|
+
// See: https://github.com/sveltejs/language-tools/issues/1026#issuecomment-1002839154
|
|
20
|
+
export const noTypeCheck = (x) => x;
|
|
21
|
+
const bogusBaseUrl = 'https://example.com/';
|
|
22
|
+
export const buildSrcSet = (src, width, candidateMultipliers) => {
|
|
23
|
+
if (!(src && width)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return candidateMultipliers
|
|
27
|
+
.map((multiplier) => {
|
|
28
|
+
const url = new URL(src, bogusBaseUrl);
|
|
29
|
+
if (multiplier !== 1) {
|
|
30
|
+
url.searchParams.set('dpr', `${multiplier}`);
|
|
31
|
+
const maxH = url.searchParams.get('max-h');
|
|
32
|
+
const maxW = url.searchParams.get('max-w');
|
|
33
|
+
if (maxH) {
|
|
34
|
+
url.searchParams.set('max-h', `${Math.floor(Number.parseInt(maxH) * multiplier)}`);
|
|
35
|
+
}
|
|
36
|
+
if (maxW) {
|
|
37
|
+
url.searchParams.set('max-w', `${Math.floor(Number.parseInt(maxW) * multiplier)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const finalWidth = Math.floor(width * multiplier);
|
|
41
|
+
if (finalWidth < 50) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return `${url.toString().replace(bogusBaseUrl, '/')} ${finalWidth}w`;
|
|
45
|
+
})
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join(',');
|
|
48
|
+
};
|
|
@@ -4,11 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
### Table of contents
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
8
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
9
|
+
|
|
10
|
+
- [Setup](#setup)
|
|
8
11
|
- [Basic usage](#basic-usage)
|
|
9
|
-
- [
|
|
12
|
+
- [Customization](#customization)
|
|
13
|
+
- [Custom components for blocks](#custom-components-for-blocks)
|
|
14
|
+
- [Override default rendering of nodes](#override-default-rendering-of-nodes)
|
|
10
15
|
- [Props](#props)
|
|
11
16
|
|
|
17
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
18
|
+
|
|
12
19
|
### Setup
|
|
13
20
|
|
|
14
21
|
Import the component like this:
|
|
@@ -17,10 +17,22 @@ optimal videos to your users.
|
|
|
17
17
|
|
|
18
18
|
- Offers optimized streaming so smartphones and tablets don’t request desktop-sized videos
|
|
19
19
|
- Lazy loads the underlying video player web component and the video to be
|
|
20
|
-
|
|
20
|
+
played to speed initial page load and save bandwidth
|
|
21
21
|
- Holds the video position so your page doesn’t jump while the player loads
|
|
22
22
|
- Uses blur-up technique to show a placeholder of the video while it loads
|
|
23
23
|
|
|
24
|
+
### Table of contents
|
|
25
|
+
|
|
26
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
27
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
28
|
+
|
|
29
|
+
- [Installation](#installation)
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [Example](#example)
|
|
32
|
+
- [Props](#props)
|
|
33
|
+
|
|
34
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
35
|
+
|
|
24
36
|
## Installation
|
|
25
37
|
|
|
26
38
|
```sh {"id":"01HP46D8MDP5Y76HY788MWNDMX"}
|
|
@@ -118,10 +130,10 @@ inner `<mux-player />`.
|
|
|
118
130
|
`<VideoPlayer />` generate some default attributes:
|
|
119
131
|
|
|
120
132
|
- when not declared, the `disableCookies` prop is true, unless you explicitly
|
|
121
|
-
|
|
133
|
+
set the prop to `false` (therefore it generates a `disable-cookies` attribute)
|
|
122
134
|
- `preload` defaults to `metadata`, for an optimal UX experience together with saved bandwidth
|
|
123
135
|
- the video height and width, when available in the `data` props, are used to
|
|
124
|
-
|
|
125
|
-
|
|
136
|
+
set a default `aspect-ratio: [width] / [height];` for the `<mux-player />`'s
|
|
137
|
+
`style` attribute
|
|
126
138
|
|
|
127
139
|
All the other props are forwarded to the `<mux-player />` web component that is used internally.
|
|
@@ -119,8 +119,8 @@ declare const __propDef: {
|
|
|
119
119
|
suspend: Event;
|
|
120
120
|
ended: Event;
|
|
121
121
|
error: ErrorEvent;
|
|
122
|
-
cuepointchange: Event | UIEvent | ProgressEvent<EventTarget> | ErrorEvent |
|
|
123
|
-
cuepointschange: Event | UIEvent | ProgressEvent<EventTarget> | ErrorEvent |
|
|
122
|
+
cuepointchange: Event | UIEvent | ProgressEvent<EventTarget> | ErrorEvent | ClipboardEvent | DragEvent | MouseEvent | FocusEvent | AnimationEvent | InputEvent | CompositionEvent | FormDataEvent | PointerEvent | KeyboardEvent | SecurityPolicyViolationEvent | SubmitEvent | TouchEvent | TransitionEvent | WheelEvent;
|
|
123
|
+
cuepointschange: Event | UIEvent | ProgressEvent<EventTarget> | ErrorEvent | ClipboardEvent | DragEvent | MouseEvent | FocusEvent | AnimationEvent | InputEvent | CompositionEvent | FormDataEvent | PointerEvent | KeyboardEvent | SecurityPolicyViolationEvent | SubmitEvent | TouchEvent | TransitionEvent | WheelEvent;
|
|
124
124
|
} & {
|
|
125
125
|
[evt: string]: CustomEvent<any>;
|
|
126
126
|
};
|
|
@@ -54,13 +54,13 @@ describe('VideoPlayer', () => {
|
|
|
54
54
|
});
|
|
55
55
|
describe('and `preload` is passed', () => {
|
|
56
56
|
it('uses it for the <mux-player /> element', () => {
|
|
57
|
-
const props = { data, preload:
|
|
57
|
+
const props = { data, preload: 'auto' };
|
|
58
58
|
const { container } = render(VideoPlayer, { props });
|
|
59
59
|
expect(container).toMatchSnapshot();
|
|
60
60
|
});
|
|
61
61
|
describe('with value `none`', () => {
|
|
62
62
|
it("doesn't use it for the <mux-player /> element", () => {
|
|
63
|
-
const props = { data, preload:
|
|
63
|
+
const props = { data, preload: 'none' };
|
|
64
64
|
const { container } = render(VideoPlayer, { props });
|
|
65
65
|
expect(container).toMatchSnapshot();
|
|
66
66
|
});
|
package/package/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Node } from 'datocms-structured-text-utils';
|
|
2
2
|
import type { SvelteComponent } from 'svelte';
|
|
3
|
-
export { default as
|
|
4
|
-
export {
|
|
3
|
+
export { default as NakedImage } from './components/NakedImage/NakedImage.svelte';
|
|
4
|
+
export type { ResponsiveImageType } from './components/NakedImage/utils';
|
|
5
5
|
export { default as Head } from './components/Head/Head.svelte';
|
|
6
|
+
export { default as Image } from './components/Image/Image.svelte';
|
|
7
|
+
export { default as StructuredText } from './components/StructuredText/StructuredText.svelte';
|
|
6
8
|
export { default as VideoPlayer } from './components/VideoPlayer/VideoPlayer.svelte';
|
|
7
9
|
export type PredicateComponentTuple = [
|
|
8
10
|
(n: Node) => boolean,
|
package/package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as Image } from './components/Image/Image.svelte';
|
|
1
|
+
export { default as NakedImage } from './components/NakedImage/NakedImage.svelte';
|
|
3
2
|
export { default as Head } from './components/Head/Head.svelte';
|
|
3
|
+
export { default as Image } from './components/Image/Image.svelte';
|
|
4
|
+
export { default as StructuredText } from './components/StructuredText/StructuredText.svelte';
|
|
4
5
|
export { default as VideoPlayer } from './components/VideoPlayer/VideoPlayer.svelte';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datocms/svelte",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "A set of components and utilities to work faster with DatoCMS in Svelte",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"test:unit": "vitest",
|
|
23
23
|
"test": "npm run test:unit",
|
|
24
24
|
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
|
25
|
-
"format": "prettier --plugin-search-dir . --write ."
|
|
25
|
+
"format": "npm run toc && prettier --plugin-search-dir . --write .",
|
|
26
|
+
"toc": "doctoc --github src README.md"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|
|
28
29
|
"@mux/mux-player": "*",
|
|
@@ -59,13 +60,13 @@
|
|
|
59
60
|
"tslib": "^2.6.2",
|
|
60
61
|
"typescript": "^5.0.0",
|
|
61
62
|
"vite": "^5.0.0",
|
|
62
|
-
"vitest": "^1.0.0"
|
|
63
|
+
"vitest": "^1.0.0",
|
|
64
|
+
"doctoc": "^2.0.0"
|
|
63
65
|
},
|
|
64
66
|
"type": "module",
|
|
65
67
|
"dependencies": {
|
|
66
68
|
"datocms-structured-text-utils": "^2.0.4",
|
|
67
|
-
"svelte-intersection-observer": "^1.0.0"
|
|
68
|
-
"universal-base64": "^2.1.0"
|
|
69
|
+
"svelte-intersection-observer": "^1.0.0"
|
|
69
70
|
},
|
|
70
71
|
"exports": {
|
|
71
72
|
"./package.json": "./package.json",
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
<script>export let base64 = null;
|
|
2
|
-
export let backgroundColor = null;
|
|
3
|
-
export let objectFit;
|
|
4
|
-
export let objectPosition;
|
|
5
|
-
export let showImage;
|
|
6
|
-
export let fadeInDuration = 0;
|
|
7
|
-
let transition = fadeInDuration > 0 ? `opacity ${fadeInDuration}ms` : void 0;
|
|
8
|
-
let opacity = showImage ? 0 : 1;
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<img
|
|
12
|
-
class="placeholder"
|
|
13
|
-
aria-hidden="true"
|
|
14
|
-
alt=""
|
|
15
|
-
src={base64}
|
|
16
|
-
style:background-color={backgroundColor}
|
|
17
|
-
style:object-fit={objectFit}
|
|
18
|
-
style:object-position={objectPosition}
|
|
19
|
-
style:transition
|
|
20
|
-
style:opacity
|
|
21
|
-
/>
|
|
22
|
-
|
|
23
|
-
<style>
|
|
24
|
-
.placeholder {
|
|
25
|
-
position: absolute;
|
|
26
|
-
width: 100%;
|
|
27
|
-
top: 0;
|
|
28
|
-
scale: 110%;
|
|
29
|
-
}
|
|
30
|
-
</style>
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
import type * as CSS from 'csstype';
|
|
3
|
-
declare const __propDef: {
|
|
4
|
-
props: {
|
|
5
|
-
base64?: string | null | undefined;
|
|
6
|
-
backgroundColor?: string | null | undefined;
|
|
7
|
-
objectFit: CSS.Properties['objectFit'];
|
|
8
|
-
objectPosition: CSS.Properties['objectPosition'];
|
|
9
|
-
showImage: boolean;
|
|
10
|
-
fadeInDuration?: number | undefined;
|
|
11
|
-
};
|
|
12
|
-
events: {
|
|
13
|
-
[evt: string]: CustomEvent<any>;
|
|
14
|
-
};
|
|
15
|
-
slots: {};
|
|
16
|
-
};
|
|
17
|
-
export type PlaceholderProps = typeof __propDef.props;
|
|
18
|
-
export type PlaceholderEvents = typeof __propDef.events;
|
|
19
|
-
export type PlaceholderSlots = typeof __propDef.slots;
|
|
20
|
-
export default class Placeholder extends SvelteComponentTyped<PlaceholderProps, PlaceholderEvents, PlaceholderSlots> {
|
|
21
|
-
}
|
|
22
|
-
export {};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<script>import { encode } from "universal-base64";
|
|
2
|
-
let klass = null;
|
|
3
|
-
export { klass as class };
|
|
4
|
-
export let width;
|
|
5
|
-
export let height;
|
|
6
|
-
export let aspectRatio;
|
|
7
|
-
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height ?? (aspectRatio ? width / aspectRatio : 0)}"></svg>`;
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<img
|
|
11
|
-
class={klass}
|
|
12
|
-
style:display="block"
|
|
13
|
-
style:width="100%"
|
|
14
|
-
src="data:image/svg+xml;base64,{encode(svg)}"
|
|
15
|
-
aria-hidden="true"
|
|
16
|
-
alt=""
|
|
17
|
-
/>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
class?: string | null | undefined;
|
|
5
|
-
width: number;
|
|
6
|
-
height: number | null | undefined;
|
|
7
|
-
aspectRatio: number | undefined;
|
|
8
|
-
};
|
|
9
|
-
events: {
|
|
10
|
-
[evt: string]: CustomEvent<any>;
|
|
11
|
-
};
|
|
12
|
-
slots: {};
|
|
13
|
-
};
|
|
14
|
-
export type SizerProps = typeof __propDef.props;
|
|
15
|
-
export type SizerEvents = typeof __propDef.events;
|
|
16
|
-
export type SizerSlots = typeof __propDef.slots;
|
|
17
|
-
export default class Sizer extends SvelteComponentTyped<SizerProps, SizerEvents, SizerSlots> {
|
|
18
|
-
}
|
|
19
|
-
export {};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
srcset: string | null;
|
|
5
|
-
sizes: string | null;
|
|
6
|
-
type?: string | null | undefined;
|
|
7
|
-
};
|
|
8
|
-
events: {
|
|
9
|
-
[evt: string]: CustomEvent<any>;
|
|
10
|
-
};
|
|
11
|
-
slots: {};
|
|
12
|
-
};
|
|
13
|
-
export type SourceProps = typeof __propDef.props;
|
|
14
|
-
export type SourceEvents = typeof __propDef.events;
|
|
15
|
-
export type SourceSlots = typeof __propDef.slots;
|
|
16
|
-
export default class Source extends SvelteComponentTyped<SourceProps, SourceEvents, SourceSlots> {
|
|
17
|
-
}
|
|
18
|
-
export {};
|