@juspay/svelte-ui-components 2.23.1 → 2.24.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/dist/Img/Img.svelte +109 -4
- package/dist/Img/properties.d.ts +16 -0
- package/package.json +1 -1
package/dist/Img/Img.svelte
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ImgProperties } from './properties';
|
|
3
3
|
|
|
4
|
-
let { src, alt, fallback, onerror, classes }: ImgProperties = $props();
|
|
4
|
+
let { src, alt, fallback, onerror, classes, inlineSvg, transformSvg }: ImgProperties = $props();
|
|
5
5
|
|
|
6
6
|
let currentSrc = $derived(src);
|
|
7
|
+
// Per-source failure marker: when inlining a given URL fails we fall back to
|
|
8
|
+
// the plain <img> for that URL (which drives the regular fallback/onerror
|
|
9
|
+
// chain), while a new src still gets a fresh inlining attempt.
|
|
10
|
+
let failedInlineSrc: string | null = $state(null);
|
|
11
|
+
|
|
12
|
+
function isSvgSource(source: string): boolean {
|
|
13
|
+
if (source.startsWith('data:image/svg+xml')) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
const path = source.split('?')[0] ?? '';
|
|
17
|
+
return path.endsWith('.svg');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let shouldInline = $derived(
|
|
21
|
+
(inlineSvg === true || typeof transformSvg === 'function') &&
|
|
22
|
+
isSvgSource(currentSrc) &&
|
|
23
|
+
failedInlineSrc !== currentSrc
|
|
24
|
+
);
|
|
7
25
|
|
|
8
26
|
function handleFallback(): void {
|
|
9
27
|
if (typeof fallback === 'string' && fallback.length > 0 && currentSrc !== fallback) {
|
|
@@ -12,12 +30,98 @@
|
|
|
12
30
|
onerror?.();
|
|
13
31
|
}
|
|
14
32
|
}
|
|
33
|
+
|
|
34
|
+
type InlineParams = {
|
|
35
|
+
url: string;
|
|
36
|
+
transform: ((svg: string) => string) | null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
async function loadInlineSvg(
|
|
40
|
+
host: SVGSVGElement,
|
|
41
|
+
url: string,
|
|
42
|
+
transform: ((svg: string) => string) | null,
|
|
43
|
+
signal: AbortSignal
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch(url, { signal });
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
failedInlineSrc = url;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const rawSvg = await response.text();
|
|
52
|
+
const markup = typeof transform === 'function' ? transform(rawSvg) : rawSvg;
|
|
53
|
+
const parsed = new DOMParser().parseFromString(markup, 'image/svg+xml');
|
|
54
|
+
if (parsed.querySelector('parsererror') !== null) {
|
|
55
|
+
failedInlineSrc = url;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const sourceSvg = parsed.querySelector('svg');
|
|
59
|
+
if (sourceSvg === null) {
|
|
60
|
+
failedInlineSrc = url;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// The action may have been torn down (or re-run) while awaiting the
|
|
64
|
+
// body — bail before mutating so a stale fetch never paints over the
|
|
65
|
+
// latest one.
|
|
66
|
+
if (signal.aborted) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
while (host.firstChild !== null) {
|
|
70
|
+
host.removeChild(host.firstChild);
|
|
71
|
+
}
|
|
72
|
+
for (const attribute of Array.from(sourceSvg.attributes)) {
|
|
73
|
+
if (attribute.name === 'class') {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
host.setAttribute(attribute.name, attribute.value);
|
|
77
|
+
}
|
|
78
|
+
while (sourceSvg.firstChild !== null) {
|
|
79
|
+
host.appendChild(sourceSvg.firstChild);
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
if (!signal.aborted) {
|
|
83
|
+
failedInlineSrc = url;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function inlineSvgInto(host: SVGSVGElement, params: InlineParams) {
|
|
89
|
+
let controller = new AbortController();
|
|
90
|
+
|
|
91
|
+
function load(current: InlineParams): void {
|
|
92
|
+
controller.abort();
|
|
93
|
+
controller = new AbortController();
|
|
94
|
+
void loadInlineSvg(host, current.url, current.transform, controller.signal);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
load(params);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
update(next: InlineParams): void {
|
|
101
|
+
load(next);
|
|
102
|
+
},
|
|
103
|
+
destroy(): void {
|
|
104
|
+
controller.abort();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
15
108
|
</script>
|
|
16
109
|
|
|
17
|
-
|
|
110
|
+
{#if shouldInline}
|
|
111
|
+
<svg
|
|
112
|
+
use:inlineSvgInto={{ url: currentSrc, transform: transformSvg ?? null }}
|
|
113
|
+
class={classes ?? ''}
|
|
114
|
+
role={alt.length > 0 ? 'img' : null}
|
|
115
|
+
aria-label={alt.length > 0 ? alt : null}
|
|
116
|
+
aria-hidden={alt.length === 0 ? 'true' : null}
|
|
117
|
+
></svg>
|
|
118
|
+
{:else}
|
|
119
|
+
<img class={classes ?? ''} src={currentSrc} {alt} onerror={handleFallback} />
|
|
120
|
+
{/if}
|
|
18
121
|
|
|
19
122
|
<style>
|
|
20
|
-
img
|
|
123
|
+
img,
|
|
124
|
+
svg {
|
|
21
125
|
object-fit: var(--image-object-fit);
|
|
22
126
|
height: var(--image-height, 24px);
|
|
23
127
|
width: var(--image-width, 24px);
|
|
@@ -30,7 +134,8 @@
|
|
|
30
134
|
transition: var(--image-transition);
|
|
31
135
|
}
|
|
32
136
|
|
|
33
|
-
img:hover
|
|
137
|
+
img:hover,
|
|
138
|
+
svg:hover {
|
|
34
139
|
background: var(--image-hover-background, var(--image-background));
|
|
35
140
|
border: var(--image-hover-border, var(--image-border));
|
|
36
141
|
}
|
package/dist/Img/properties.d.ts
CHANGED
|
@@ -6,6 +6,22 @@ export type MandatoryImgProperties = {
|
|
|
6
6
|
export type OptionalImgProperties = {
|
|
7
7
|
fallback?: string | null;
|
|
8
8
|
classes?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Fetch `.svg` / `data:image/svg+xml` sources and inline their markup into an
|
|
11
|
+
* `<svg>` host element so page CSS (currentColor, fill/stroke overrides) applies
|
|
12
|
+
* to the icon. Non-SVG sources always render the plain `<img>`. If fetching or
|
|
13
|
+
* parsing fails, the component falls back to the plain `<img>` (and from there
|
|
14
|
+
* to the regular `fallback`/`onerror` chain).
|
|
15
|
+
*/
|
|
16
|
+
inlineSvg?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Hook to rewrite the fetched SVG markup before it is inlined (e.g. recolor
|
|
19
|
+
* hardcoded fills). Receives the raw SVG text and returns the transformed text.
|
|
20
|
+
* Providing a transform implies `inlineSvg`. The inlining effect re-runs when
|
|
21
|
+
* the prop identity changes, so consumers can pass a closure over reactive
|
|
22
|
+
* state (e.g. a theme colour) to get live re-renders on theme change.
|
|
23
|
+
*/
|
|
24
|
+
transformSvg?: (svg: string) => string;
|
|
9
25
|
};
|
|
10
26
|
export type ImgEventProperties = {
|
|
11
27
|
onerror?: () => void;
|