@classic-homes/theme-svelte 1.2.2 → 1.3.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 +43 -0
- package/dist/__tests__/a11y/Media.a11y.test.js +145 -0
- package/dist/__tests__/media-components.test.js +206 -0
- package/dist/__tests__/media-hls.test.js +273 -0
- package/dist/__tests__/media-matterport.test.js +296 -0
- package/dist/__tests__/media-players.test.js +244 -0
- package/dist/__tests__/media-scrubber.test.js +44 -0
- package/dist/__tests__/media-utils.test.js +427 -0
- package/dist/__tests__/setup.js +50 -0
- package/dist/lib/components/DataPanel.svelte +8 -0
- package/dist/lib/components/DataPanel.svelte.d.ts +6 -0
- package/dist/lib/components/DataPanelTab.svelte +38 -8
- package/dist/lib/components/DataPanelTab.svelte.d.ts +7 -0
- package/dist/lib/components/FileUpload.svelte +221 -4
- package/dist/lib/components/FileUpload.svelte.d.ts +13 -1
- package/dist/lib/components/media/AspectRatio.svelte +47 -0
- package/dist/lib/components/media/AspectRatio.svelte.d.ts +20 -0
- package/dist/lib/components/media/Audio.svelte +247 -0
- package/dist/lib/components/media/Audio.svelte.d.ts +34 -0
- package/dist/lib/components/media/Image.svelte +209 -0
- package/dist/lib/components/media/Image.svelte.d.ts +52 -0
- package/dist/lib/components/media/Lightbox.svelte +449 -0
- package/dist/lib/components/media/Lightbox.svelte.d.ts +33 -0
- package/dist/lib/components/media/MediaCarousel.svelte +359 -0
- package/dist/lib/components/media/MediaCarousel.svelte.d.ts +30 -0
- package/dist/lib/components/media/MediaEmbed.svelte +314 -0
- package/dist/lib/components/media/MediaEmbed.svelte.d.ts +99 -0
- package/dist/lib/components/media/MediaGrid.svelte +327 -0
- package/dist/lib/components/media/MediaGrid.svelte.d.ts +57 -0
- package/dist/lib/components/media/Video.svelte +455 -0
- package/dist/lib/components/media/Video.svelte.d.ts +81 -0
- package/dist/lib/components/media/index.d.ts +14 -0
- package/dist/lib/components/media/index.js +14 -0
- package/dist/lib/components/media/internal/MediaControls.svelte +283 -0
- package/dist/lib/components/media/internal/MediaControls.svelte.d.ts +32 -0
- package/dist/lib/components/media/internal/MediaScrubber.svelte +211 -0
- package/dist/lib/components/media/internal/MediaScrubber.svelte.d.ts +27 -0
- package/dist/lib/components/media/internal/MediaTile.svelte +129 -0
- package/dist/lib/components/media/internal/MediaTile.svelte.d.ts +22 -0
- package/dist/lib/composables/index.d.ts +3 -0
- package/dist/lib/composables/index.js +4 -0
- package/dist/lib/composables/useHlsSource.svelte.d.ts +70 -0
- package/dist/lib/composables/useHlsSource.svelte.js +119 -0
- package/dist/lib/composables/useMediaPlayer.svelte.d.ts +114 -0
- package/dist/lib/composables/useMediaPlayer.svelte.js +422 -0
- package/dist/lib/composables/useMediaZoom.svelte.d.ts +59 -0
- package/dist/lib/composables/useMediaZoom.svelte.js +199 -0
- package/dist/lib/index.d.ts +8 -2
- package/dist/lib/index.js +14 -1
- package/dist/lib/types/components.d.ts +172 -0
- package/dist/lib/utils/hls.d.ts +76 -0
- package/dist/lib/utils/hls.js +83 -0
- package/dist/lib/utils/media.d.ts +379 -0
- package/dist/lib/utils/media.js +715 -0
- package/package.json +6 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Image - Responsive image with loading, placeholder, and error handling
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Lazy loading and async decoding by default
|
|
7
|
+
* - Skeleton or blurred low-resolution placeholder while loading
|
|
8
|
+
* - Graceful fallback when the source fails to load
|
|
9
|
+
* - Optional aspect-ratio box that reserves layout space (no layout shift)
|
|
10
|
+
* - `alt` is required at the type level; pass an empty string for decorative images
|
|
11
|
+
*/
|
|
12
|
+
import type { Snippet } from 'svelte';
|
|
13
|
+
import type { HTMLImgAttributes } from 'svelte/elements';
|
|
14
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
15
|
+
import type { MediaAspectRatio } from '../../types/components.js';
|
|
16
|
+
import { cn } from '../../utils.js';
|
|
17
|
+
import Icon from '../Icon.svelte';
|
|
18
|
+
import Skeleton from '../Skeleton.svelte';
|
|
19
|
+
import AspectRatio from './AspectRatio.svelte';
|
|
20
|
+
|
|
21
|
+
const imageVariants = tv({
|
|
22
|
+
slots: {
|
|
23
|
+
wrapper: 'relative isolate overflow-hidden bg-muted',
|
|
24
|
+
img: 'block h-full w-full',
|
|
25
|
+
},
|
|
26
|
+
variants: {
|
|
27
|
+
fit: {
|
|
28
|
+
cover: { img: 'object-cover' },
|
|
29
|
+
contain: { img: 'object-contain' },
|
|
30
|
+
fill: { img: 'object-fill' },
|
|
31
|
+
none: { img: 'object-none' },
|
|
32
|
+
'scale-down': { img: 'object-scale-down' },
|
|
33
|
+
},
|
|
34
|
+
rounded: {
|
|
35
|
+
none: { wrapper: 'rounded-none' },
|
|
36
|
+
sm: { wrapper: 'rounded-sm' },
|
|
37
|
+
md: { wrapper: 'rounded-md' },
|
|
38
|
+
lg: { wrapper: 'rounded-lg' },
|
|
39
|
+
xl: { wrapper: 'rounded-xl' },
|
|
40
|
+
full: { wrapper: 'rounded-full' },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
defaultVariants: {
|
|
44
|
+
fit: 'cover',
|
|
45
|
+
rounded: 'none',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
type ImageVariants = VariantProps<typeof imageVariants>;
|
|
50
|
+
|
|
51
|
+
interface Props extends Omit<HTMLImgAttributes, 'alt' | 'class' | 'onload' | 'onerror'> {
|
|
52
|
+
/** Image URL */
|
|
53
|
+
src: string;
|
|
54
|
+
/**
|
|
55
|
+
* Alternative text. Required — pass an empty string only when the image is
|
|
56
|
+
* purely decorative and the surrounding content already conveys its meaning.
|
|
57
|
+
*/
|
|
58
|
+
alt: string;
|
|
59
|
+
/**
|
|
60
|
+
* Constrain the image to a ratio box. Omit to let the image size itself
|
|
61
|
+
* from its intrinsic dimensions.
|
|
62
|
+
*/
|
|
63
|
+
ratio?: MediaAspectRatio | number;
|
|
64
|
+
/** How the image fills its box */
|
|
65
|
+
fit?: ImageVariants['fit'];
|
|
66
|
+
/** Corner rounding */
|
|
67
|
+
rounded?: ImageVariants['rounded'];
|
|
68
|
+
/**
|
|
69
|
+
* Low-resolution placeholder (typically a tiny data URL) blurred in behind
|
|
70
|
+
* the image and cross-faded out once the real image decodes.
|
|
71
|
+
*/
|
|
72
|
+
placeholder?: string;
|
|
73
|
+
/** Show a pulsing skeleton while loading (ignored when `placeholder` is set) */
|
|
74
|
+
showSkeleton?: boolean;
|
|
75
|
+
/** Called once the image has loaded */
|
|
76
|
+
onload?: (event: Event) => void;
|
|
77
|
+
/** Called when the image fails to load */
|
|
78
|
+
onerror?: (event: Event) => void;
|
|
79
|
+
/** Custom content rendered in place of a failed image */
|
|
80
|
+
fallback?: Snippet;
|
|
81
|
+
/** Content overlaid on the image, such as a caption or badge */
|
|
82
|
+
overlay?: Snippet;
|
|
83
|
+
/** Additional classes applied to the wrapper */
|
|
84
|
+
class?: string;
|
|
85
|
+
/** Additional classes applied to the `<img>` element itself */
|
|
86
|
+
imgClass?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let {
|
|
90
|
+
src,
|
|
91
|
+
alt,
|
|
92
|
+
ratio,
|
|
93
|
+
fit = 'cover',
|
|
94
|
+
rounded = 'none',
|
|
95
|
+
placeholder,
|
|
96
|
+
showSkeleton = true,
|
|
97
|
+
onload,
|
|
98
|
+
onerror,
|
|
99
|
+
fallback,
|
|
100
|
+
overlay,
|
|
101
|
+
class: className,
|
|
102
|
+
imgClass,
|
|
103
|
+
loading = 'lazy',
|
|
104
|
+
decoding = 'async',
|
|
105
|
+
...restProps
|
|
106
|
+
}: Props = $props();
|
|
107
|
+
|
|
108
|
+
// Track load state per-URL rather than as a mutable flag, so swapping `src`
|
|
109
|
+
// automatically returns to the loading state without a reset effect.
|
|
110
|
+
let loadedSrc = $state<string | null>(null);
|
|
111
|
+
let erroredSrc = $state<string | null>(null);
|
|
112
|
+
|
|
113
|
+
const status = $derived<'loading' | 'loaded' | 'error'>(
|
|
114
|
+
erroredSrc === src ? 'error' : loadedSrc === src ? 'loaded' : 'loading'
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const styles = $derived(imageVariants({ fit, rounded }));
|
|
118
|
+
|
|
119
|
+
function handleLoad(event: Event) {
|
|
120
|
+
loadedSrc = src;
|
|
121
|
+
onload?.(event);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function handleError(event: Event) {
|
|
125
|
+
erroredSrc = src;
|
|
126
|
+
onerror?.(event);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* An image already in the memory cache can finish decoding before the `load`
|
|
131
|
+
* listener is attached, and then never fires it. `complete` covers that case.
|
|
132
|
+
*/
|
|
133
|
+
function detectCached(node: HTMLImageElement) {
|
|
134
|
+
if (node.complete && node.currentSrc) {
|
|
135
|
+
if (node.naturalWidth > 0) loadedSrc = src;
|
|
136
|
+
else erroredSrc = src;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
|
|
141
|
+
$effect(() => {
|
|
142
|
+
if (alt === undefined) {
|
|
143
|
+
console.warn(
|
|
144
|
+
'Image: Missing "alt" prop. Provide descriptive alternative text, or an empty string ("") if the image is decorative.'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
{#snippet layers()}
|
|
152
|
+
{#if status === 'error'}
|
|
153
|
+
<div
|
|
154
|
+
class="flex h-full w-full min-h-[3rem] items-center justify-center bg-muted text-muted-foreground"
|
|
155
|
+
>
|
|
156
|
+
{#if fallback}
|
|
157
|
+
{@render fallback()}
|
|
158
|
+
{:else}
|
|
159
|
+
<Icon name="image" size={32} label={alt ? `Failed to load: ${alt}` : 'Image unavailable'} />
|
|
160
|
+
{/if}
|
|
161
|
+
</div>
|
|
162
|
+
{:else}
|
|
163
|
+
{#if placeholder}
|
|
164
|
+
<img
|
|
165
|
+
src={placeholder}
|
|
166
|
+
alt=""
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
class={cn(
|
|
169
|
+
styles.img(),
|
|
170
|
+
'absolute inset-0 scale-105 blur-lg transition-opacity duration-300',
|
|
171
|
+
status === 'loaded' ? 'opacity-0' : 'opacity-100'
|
|
172
|
+
)}
|
|
173
|
+
/>
|
|
174
|
+
{:else if showSkeleton && status === 'loading'}
|
|
175
|
+
<Skeleton class="absolute inset-0 h-full w-full rounded-none" />
|
|
176
|
+
{/if}
|
|
177
|
+
|
|
178
|
+
<img
|
|
179
|
+
{src}
|
|
180
|
+
{alt}
|
|
181
|
+
{loading}
|
|
182
|
+
{decoding}
|
|
183
|
+
class={cn(
|
|
184
|
+
styles.img(),
|
|
185
|
+
'relative transition-opacity duration-300',
|
|
186
|
+
status === 'loaded' ? 'opacity-100' : 'opacity-0',
|
|
187
|
+
imgClass
|
|
188
|
+
)}
|
|
189
|
+
onload={handleLoad}
|
|
190
|
+
onerror={handleError}
|
|
191
|
+
use:detectCached
|
|
192
|
+
{...restProps}
|
|
193
|
+
/>
|
|
194
|
+
{/if}
|
|
195
|
+
|
|
196
|
+
{#if overlay}
|
|
197
|
+
{@render overlay()}
|
|
198
|
+
{/if}
|
|
199
|
+
{/snippet}
|
|
200
|
+
|
|
201
|
+
{#if ratio}
|
|
202
|
+
<AspectRatio {ratio} class={cn(styles.wrapper(), className)}>
|
|
203
|
+
{@render layers()}
|
|
204
|
+
</AspectRatio>
|
|
205
|
+
{:else}
|
|
206
|
+
<div class={cn(styles.wrapper(), 'inline-block', className)}>
|
|
207
|
+
{@render layers()}
|
|
208
|
+
</div>
|
|
209
|
+
{/if}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image - Responsive image with loading, placeholder, and error handling
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Lazy loading and async decoding by default
|
|
6
|
+
* - Skeleton or blurred low-resolution placeholder while loading
|
|
7
|
+
* - Graceful fallback when the source fails to load
|
|
8
|
+
* - Optional aspect-ratio box that reserves layout space (no layout shift)
|
|
9
|
+
* - `alt` is required at the type level; pass an empty string for decorative images
|
|
10
|
+
*/
|
|
11
|
+
import type { Snippet } from 'svelte';
|
|
12
|
+
import type { HTMLImgAttributes } from 'svelte/elements';
|
|
13
|
+
import type { MediaAspectRatio } from '../../types/components.js';
|
|
14
|
+
declare const Image: import("svelte").Component<Omit<HTMLImgAttributes, "class" | "onload" | "onerror" | "alt"> & {
|
|
15
|
+
/** Image URL */
|
|
16
|
+
src: string;
|
|
17
|
+
/**
|
|
18
|
+
* Alternative text. Required — pass an empty string only when the image is
|
|
19
|
+
* purely decorative and the surrounding content already conveys its meaning.
|
|
20
|
+
*/
|
|
21
|
+
alt: string;
|
|
22
|
+
/**
|
|
23
|
+
* Constrain the image to a ratio box. Omit to let the image size itself
|
|
24
|
+
* from its intrinsic dimensions.
|
|
25
|
+
*/
|
|
26
|
+
ratio?: MediaAspectRatio | number;
|
|
27
|
+
/** How the image fills its box */
|
|
28
|
+
fit?: "none" | "cover" | "contain" | "fill" | "scale-down" | undefined;
|
|
29
|
+
/** Corner rounding */
|
|
30
|
+
rounded?: "sm" | "md" | "lg" | "xl" | "none" | "full" | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Low-resolution placeholder (typically a tiny data URL) blurred in behind
|
|
33
|
+
* the image and cross-faded out once the real image decodes.
|
|
34
|
+
*/
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
/** Show a pulsing skeleton while loading (ignored when `placeholder` is set) */
|
|
37
|
+
showSkeleton?: boolean;
|
|
38
|
+
/** Called once the image has loaded */
|
|
39
|
+
onload?: (event: Event) => void;
|
|
40
|
+
/** Called when the image fails to load */
|
|
41
|
+
onerror?: (event: Event) => void;
|
|
42
|
+
/** Custom content rendered in place of a failed image */
|
|
43
|
+
fallback?: Snippet;
|
|
44
|
+
/** Content overlaid on the image, such as a caption or badge */
|
|
45
|
+
overlay?: Snippet;
|
|
46
|
+
/** Additional classes applied to the wrapper */
|
|
47
|
+
class?: string;
|
|
48
|
+
/** Additional classes applied to the `<img>` element itself */
|
|
49
|
+
imgClass?: string;
|
|
50
|
+
}, {}, "">;
|
|
51
|
+
type Image = ReturnType<typeof Image>;
|
|
52
|
+
export default Image;
|