@juspay/svelte-ui-components 2.133.1 → 2.134.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 +30 -0
- package/dist/ChipInput/ChipInput.svelte +2 -0
- package/dist/ChipInput/properties.d.ts +6 -0
- package/dist/Draggable/Draggable.svelte +175 -0
- package/dist/Draggable/Draggable.svelte.d.ts +4 -0
- package/dist/Draggable/properties.d.ts +26 -0
- package/dist/Draggable/properties.js +1 -0
- package/dist/Gallery/Gallery.svelte +593 -0
- package/dist/Gallery/Gallery.svelte.d.ts +4 -0
- package/dist/Gallery/properties.d.ts +39 -0
- package/dist/Gallery/properties.js +1 -0
- package/dist/Input/Input.svelte +29 -3
- package/dist/MediaPlayer/MediaPlayer.svelte +277 -0
- package/dist/MediaPlayer/MediaPlayer.svelte.d.ts +4 -0
- package/dist/MediaPlayer/properties.d.ts +34 -0
- package/dist/MediaPlayer/properties.js +1 -0
- package/dist/MediaUpload/MediaUpload.svelte +528 -0
- package/dist/MediaUpload/MediaUpload.svelte.d.ts +4 -0
- package/dist/MediaUpload/properties.d.ts +46 -0
- package/dist/MediaUpload/properties.js +1 -0
- package/dist/Modal/Modal.svelte +17 -3
- package/dist/Modal/properties.d.ts +4 -0
- package/dist/assets/add.svg +4 -0
- package/dist/assets/delete.svg +7 -0
- package/dist/assets/edit.svg +4 -0
- package/dist/assets/file.svg +4 -0
- package/dist/assets/mute.svg +5 -0
- package/dist/assets/pause.svg +4 -0
- package/dist/assets/play.svg +3 -0
- package/dist/assets/volume.svg +5 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist-wc/index.d.ts +1 -0
- package/dist-wc/index.js +33020 -0
- package/package.json +16 -9
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import { fade } from 'svelte/transition';
|
|
5
|
+
import type { GalleryImage, GalleryProperties } from './properties';
|
|
6
|
+
import Img from '../Img/Img.svelte';
|
|
7
|
+
import Button from '../Button/Button.svelte';
|
|
8
|
+
import Icon from '../Icon/Icon.svelte';
|
|
9
|
+
import closeSvg from '../assets/close.svg?raw';
|
|
10
|
+
import chevronLeftSvg from '../assets/chevron-left-lg.svg?raw';
|
|
11
|
+
import chevronRightSvg from '../assets/chevron-right-lg.svg?raw';
|
|
12
|
+
import editSvg from '../assets/edit.svg?raw';
|
|
13
|
+
import deleteSvg from '../assets/delete.svg?raw';
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
images,
|
|
17
|
+
view = 'grid',
|
|
18
|
+
open = $bindable(false),
|
|
19
|
+
activeIndex = $bindable(0),
|
|
20
|
+
enableLightbox = true,
|
|
21
|
+
loop = false,
|
|
22
|
+
showCounter = true,
|
|
23
|
+
showCaption = true,
|
|
24
|
+
previousIcon,
|
|
25
|
+
nextIcon,
|
|
26
|
+
closeIcon,
|
|
27
|
+
editIcon,
|
|
28
|
+
deleteIcon,
|
|
29
|
+
itemFooter,
|
|
30
|
+
testId,
|
|
31
|
+
onImageClick,
|
|
32
|
+
onEditClick,
|
|
33
|
+
onDeleteClick,
|
|
34
|
+
onOpen,
|
|
35
|
+
onDismiss,
|
|
36
|
+
onIndexChange,
|
|
37
|
+
onkeydown,
|
|
38
|
+
classes
|
|
39
|
+
}: GalleryProperties = $props();
|
|
40
|
+
|
|
41
|
+
let lightboxDiv: HTMLDivElement | null = $state(null);
|
|
42
|
+
let closeButtonWrap: HTMLDivElement | null = $state(null);
|
|
43
|
+
let previousButtonWrap: HTMLDivElement | null = $state(null);
|
|
44
|
+
let nextButtonWrap: HTMLDivElement | null = $state(null);
|
|
45
|
+
let openerElement: HTMLElement | null = null;
|
|
46
|
+
|
|
47
|
+
let activeImage = $derived(images.at(activeIndex));
|
|
48
|
+
let hasPrevious = $derived(loop ? images.length > 1 : activeIndex > 0);
|
|
49
|
+
let hasNext = $derived(loop ? images.length > 1 : activeIndex < images.length - 1);
|
|
50
|
+
let itemsAreInteractive = $derived(enableLightbox || typeof onImageClick === 'function');
|
|
51
|
+
let hasItemFooter = $derived(view === 'grid' && typeof itemFooter === 'function');
|
|
52
|
+
let showEditButton = $derived(typeof onEditClick === 'function');
|
|
53
|
+
let showDeleteButton = $derived(typeof onDeleteClick === 'function');
|
|
54
|
+
let showItemActions = $derived(showEditButton || showDeleteButton);
|
|
55
|
+
|
|
56
|
+
function lightboxAction(node: HTMLElement) {
|
|
57
|
+
if (openerElement === null && document.activeElement instanceof HTMLElement) {
|
|
58
|
+
openerElement = document.activeElement;
|
|
59
|
+
}
|
|
60
|
+
document.body.style.overflow = 'hidden';
|
|
61
|
+
tick().then(() => {
|
|
62
|
+
node.focus();
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
destroy() {
|
|
66
|
+
document.body.style.overflow = '';
|
|
67
|
+
if (openerElement !== null) {
|
|
68
|
+
openerElement.focus();
|
|
69
|
+
openerElement = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function openLightbox(index: number, opener: EventTarget | null): void {
|
|
76
|
+
if (opener instanceof HTMLElement) {
|
|
77
|
+
openerElement = opener;
|
|
78
|
+
}
|
|
79
|
+
activeIndex = index;
|
|
80
|
+
open = true;
|
|
81
|
+
onOpen?.(index);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function closeLightbox(): void {
|
|
85
|
+
open = false;
|
|
86
|
+
onDismiss?.();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function goToIndex(index: number): Promise<void> {
|
|
90
|
+
if (index < 0 || index >= images.length) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
activeIndex = index;
|
|
94
|
+
onIndexChange?.(activeIndex);
|
|
95
|
+
await tick();
|
|
96
|
+
if (lightboxDiv !== null && !lightboxDiv.contains(document.activeElement)) {
|
|
97
|
+
lightboxDiv.focus();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function showPrevious(): void {
|
|
102
|
+
if (activeIndex > 0) {
|
|
103
|
+
goToIndex(activeIndex - 1);
|
|
104
|
+
} else if (loop && images.length > 1) {
|
|
105
|
+
goToIndex(images.length - 1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function showNext(): void {
|
|
110
|
+
if (activeIndex < images.length - 1) {
|
|
111
|
+
goToIndex(activeIndex + 1);
|
|
112
|
+
} else if (loop && images.length > 1) {
|
|
113
|
+
goToIndex(0);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function handleImageClick(index: number, event: MouseEvent): void {
|
|
118
|
+
onImageClick?.(index, event);
|
|
119
|
+
if (enableLightbox) {
|
|
120
|
+
openLightbox(index, event.currentTarget);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function handleEditClick(index: number, event: MouseEvent): void {
|
|
125
|
+
onEditClick?.(index, event);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function handleDeleteClick(index: number, event: MouseEvent): void {
|
|
129
|
+
onDeleteClick?.(index, event);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function trapFocus(event: KeyboardEvent): void {
|
|
133
|
+
const first = closeButtonWrap?.querySelector('button') ?? null;
|
|
134
|
+
const last =
|
|
135
|
+
(nextButtonWrap ?? previousButtonWrap ?? closeButtonWrap)?.querySelector('button') ?? null;
|
|
136
|
+
if (first === null || last === null) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (
|
|
140
|
+
event.shiftKey &&
|
|
141
|
+
(document.activeElement === first || document.activeElement === lightboxDiv)
|
|
142
|
+
) {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
last.focus();
|
|
145
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
first.focus();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function handleLightboxKeydown(event: KeyboardEvent): void {
|
|
152
|
+
onkeydown?.(event);
|
|
153
|
+
if (event.key === 'Escape') {
|
|
154
|
+
closeLightbox();
|
|
155
|
+
} else if (event.key === 'ArrowLeft') {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
showPrevious();
|
|
158
|
+
} else if (event.key === 'ArrowRight') {
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
showNext();
|
|
161
|
+
} else if (event.key === 'Home') {
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
goToIndex(0);
|
|
164
|
+
} else if (event.key === 'End') {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
goToIndex(images.length - 1);
|
|
167
|
+
} else if (event.key === 'Tab') {
|
|
168
|
+
trapFocus(event);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleBackdropClick(event: MouseEvent): void {
|
|
173
|
+
if (event.target === lightboxDiv) {
|
|
174
|
+
closeLightbox();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
{#snippet controlIcon(fallbackMarkup: string, custom?: Snippet)}
|
|
180
|
+
{#if typeof custom === 'function'}
|
|
181
|
+
{@render custom()}
|
|
182
|
+
{:else}
|
|
183
|
+
<Icon svg={fallbackMarkup} />
|
|
184
|
+
{/if}
|
|
185
|
+
{/snippet}
|
|
186
|
+
|
|
187
|
+
{#snippet itemContent(image: GalleryImage, index: number)}
|
|
188
|
+
{#if hasItemFooter}
|
|
189
|
+
<span class="grid-image-wrap">
|
|
190
|
+
<Img src={image.thumbnail ?? image.src} alt={image.alt} fallback={image.fallback} />
|
|
191
|
+
</span>
|
|
192
|
+
{#if typeof itemFooter === 'function'}
|
|
193
|
+
{@render itemFooter(image, index)}
|
|
194
|
+
{/if}
|
|
195
|
+
{:else}
|
|
196
|
+
<Img src={image.thumbnail ?? image.src} alt={image.alt} fallback={image.fallback} />
|
|
197
|
+
{#if view === 'list'}
|
|
198
|
+
<span class="list-text">
|
|
199
|
+
<span class="list-title">{image.alt}</span>
|
|
200
|
+
{#if typeof image.caption === 'string' && image.caption.length > 0}
|
|
201
|
+
<span class="list-caption">{image.caption}</span>
|
|
202
|
+
{/if}
|
|
203
|
+
</span>
|
|
204
|
+
{/if}
|
|
205
|
+
{/if}
|
|
206
|
+
{/snippet}
|
|
207
|
+
|
|
208
|
+
<div
|
|
209
|
+
class="gallery {view} {hasItemFooter ? 'has-item-footer' : ''} {classes ?? ''}"
|
|
210
|
+
data-pw={typeof testId === 'string' ? testId : null}
|
|
211
|
+
role="list"
|
|
212
|
+
>
|
|
213
|
+
{#each images as image, index (index)}
|
|
214
|
+
<div class="gallery-item" role="listitem">
|
|
215
|
+
{#if itemsAreInteractive}
|
|
216
|
+
<button
|
|
217
|
+
class="gallery-item-content"
|
|
218
|
+
onclick={(event) => handleImageClick(index, event)}
|
|
219
|
+
aria-label={enableLightbox
|
|
220
|
+
? `View image ${index + 1} of ${images.length}: ${image.alt}`
|
|
221
|
+
: image.alt}
|
|
222
|
+
>
|
|
223
|
+
{@render itemContent(image, index)}
|
|
224
|
+
</button>
|
|
225
|
+
{:else}
|
|
226
|
+
<div class="gallery-item-content">
|
|
227
|
+
{@render itemContent(image, index)}
|
|
228
|
+
</div>
|
|
229
|
+
{/if}
|
|
230
|
+
{#if showItemActions}
|
|
231
|
+
<div class="gallery-item-actions">
|
|
232
|
+
{#if showEditButton}
|
|
233
|
+
<div class="gallery-item-action">
|
|
234
|
+
<Button
|
|
235
|
+
ariaLabel={`Edit image ${index + 1}: ${image.alt}`}
|
|
236
|
+
onclick={(event) => handleEditClick(index, event)}
|
|
237
|
+
>
|
|
238
|
+
{@render controlIcon(editSvg, editIcon)}
|
|
239
|
+
</Button>
|
|
240
|
+
</div>
|
|
241
|
+
{/if}
|
|
242
|
+
{#if showDeleteButton}
|
|
243
|
+
<div class="gallery-item-action">
|
|
244
|
+
<Button
|
|
245
|
+
ariaLabel={`Delete image ${index + 1}: ${image.alt}`}
|
|
246
|
+
onclick={(event) => handleDeleteClick(index, event)}
|
|
247
|
+
>
|
|
248
|
+
{@render controlIcon(deleteSvg, deleteIcon)}
|
|
249
|
+
</Button>
|
|
250
|
+
</div>
|
|
251
|
+
{/if}
|
|
252
|
+
</div>
|
|
253
|
+
{/if}
|
|
254
|
+
</div>
|
|
255
|
+
{/each}
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
{#if open}
|
|
259
|
+
<div
|
|
260
|
+
class="lightbox {classes ?? ''}"
|
|
261
|
+
role="dialog"
|
|
262
|
+
aria-modal="true"
|
|
263
|
+
aria-label={typeof activeImage === 'object' ? activeImage.alt : 'Image gallery'}
|
|
264
|
+
tabindex="-1"
|
|
265
|
+
bind:this={lightboxDiv}
|
|
266
|
+
use:lightboxAction
|
|
267
|
+
onclick={handleBackdropClick}
|
|
268
|
+
onkeydown={handleLightboxKeydown}
|
|
269
|
+
transition:fade={{ duration: 200 }}
|
|
270
|
+
>
|
|
271
|
+
<div class="lightbox-close" bind:this={closeButtonWrap}>
|
|
272
|
+
<Button ariaLabel="Close gallery" onclick={closeLightbox}>
|
|
273
|
+
{@render controlIcon(closeSvg, closeIcon)}
|
|
274
|
+
</Button>
|
|
275
|
+
</div>
|
|
276
|
+
{#if hasPrevious}
|
|
277
|
+
<div class="lightbox-previous" bind:this={previousButtonWrap}>
|
|
278
|
+
<Button ariaLabel="Previous image" onclick={showPrevious}>
|
|
279
|
+
{@render controlIcon(chevronLeftSvg, previousIcon)}
|
|
280
|
+
</Button>
|
|
281
|
+
</div>
|
|
282
|
+
{/if}
|
|
283
|
+
{#if typeof activeImage === 'object'}
|
|
284
|
+
<figure class="lightbox-figure">
|
|
285
|
+
<Img src={activeImage.src} alt={activeImage.alt} fallback={activeImage.fallback} />
|
|
286
|
+
{#if showCaption && typeof activeImage.caption === 'string' && activeImage.caption.length > 0}
|
|
287
|
+
<figcaption class="lightbox-caption">{activeImage.caption}</figcaption>
|
|
288
|
+
{/if}
|
|
289
|
+
</figure>
|
|
290
|
+
{/if}
|
|
291
|
+
{#if hasNext}
|
|
292
|
+
<div class="lightbox-next" bind:this={nextButtonWrap}>
|
|
293
|
+
<Button ariaLabel="Next image" onclick={showNext}>
|
|
294
|
+
{@render controlIcon(chevronRightSvg, nextIcon)}
|
|
295
|
+
</Button>
|
|
296
|
+
</div>
|
|
297
|
+
{/if}
|
|
298
|
+
{#if showCounter && typeof activeImage === 'object'}
|
|
299
|
+
<div class="lightbox-counter" aria-live="polite">
|
|
300
|
+
{activeIndex + 1} / {images.length}
|
|
301
|
+
</div>
|
|
302
|
+
{/if}
|
|
303
|
+
</div>
|
|
304
|
+
{/if}
|
|
305
|
+
|
|
306
|
+
<style>
|
|
307
|
+
.gallery {
|
|
308
|
+
width: var(--gallery-width, 100%);
|
|
309
|
+
padding: var(--gallery-padding, 0px);
|
|
310
|
+
margin: var(--gallery-margin, 0px);
|
|
311
|
+
background: var(--gallery-background, transparent);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.gallery.grid {
|
|
315
|
+
display: grid;
|
|
316
|
+
grid-template-columns: var(
|
|
317
|
+
--gallery-grid-template-columns,
|
|
318
|
+
repeat(var(--gallery-columns, 3), 1fr)
|
|
319
|
+
);
|
|
320
|
+
gap: var(--gallery-gap, 8px);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.gallery.list {
|
|
324
|
+
display: flex;
|
|
325
|
+
flex-direction: column;
|
|
326
|
+
gap: var(--gallery-gap, 8px);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.gallery-item {
|
|
330
|
+
position: relative;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.grid .gallery-item {
|
|
334
|
+
aspect-ratio: var(--gallery-item-aspect-ratio, 1);
|
|
335
|
+
border-radius: var(--gallery-item-border-radius, 0px);
|
|
336
|
+
overflow: hidden;
|
|
337
|
+
--image-width: 100%;
|
|
338
|
+
--image-height: 100%;
|
|
339
|
+
--image-object-fit: var(--gallery-item-image-fit, cover);
|
|
340
|
+
--image-border-radius: var(--gallery-item-border-radius, 0px);
|
|
341
|
+
--image-transition: var(--gallery-item-image-transition);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.grid.has-item-footer .gallery-item {
|
|
345
|
+
aspect-ratio: auto;
|
|
346
|
+
overflow: visible;
|
|
347
|
+
border-radius: 0px;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.list .gallery-item {
|
|
351
|
+
display: flex;
|
|
352
|
+
align-items: center;
|
|
353
|
+
border-radius: var(--gallery-list-item-border-radius, 0px);
|
|
354
|
+
background: var(--gallery-list-item-background, transparent);
|
|
355
|
+
--image-width: var(--gallery-list-thumbnail-width, 56px);
|
|
356
|
+
--image-height: var(--gallery-list-thumbnail-height, 56px);
|
|
357
|
+
--image-object-fit: var(--gallery-list-thumbnail-fit, cover);
|
|
358
|
+
--image-border-radius: var(--gallery-list-thumbnail-border-radius, 0px);
|
|
359
|
+
--image-transition: var(--gallery-item-image-transition);
|
|
360
|
+
--image-flex-shrink: 0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.gallery-item-content {
|
|
364
|
+
font: inherit;
|
|
365
|
+
color: inherit;
|
|
366
|
+
border: var(--gallery-item-border, none);
|
|
367
|
+
background: transparent;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
button.gallery-item-content {
|
|
371
|
+
cursor: var(--gallery-item-cursor, pointer);
|
|
372
|
+
transition: var(--gallery-item-transition);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.grid .gallery-item-content {
|
|
376
|
+
display: block;
|
|
377
|
+
width: 100%;
|
|
378
|
+
height: 100%;
|
|
379
|
+
padding: 0;
|
|
380
|
+
border-radius: var(--gallery-item-border-radius, 0px);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.grid.has-item-footer .gallery-item-content {
|
|
384
|
+
display: flex;
|
|
385
|
+
flex-direction: column;
|
|
386
|
+
height: auto;
|
|
387
|
+
border-radius: var(--gallery-item-border-radius, 0px);
|
|
388
|
+
overflow: hidden;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.grid-image-wrap {
|
|
392
|
+
display: block;
|
|
393
|
+
aspect-ratio: var(--gallery-item-aspect-ratio, 1);
|
|
394
|
+
overflow: hidden;
|
|
395
|
+
--image-width: 100%;
|
|
396
|
+
--image-height: 100%;
|
|
397
|
+
--image-object-fit: var(--gallery-item-image-fit, cover);
|
|
398
|
+
--image-border-radius: 0px;
|
|
399
|
+
--image-transition: var(--gallery-item-image-transition);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.list .gallery-item-content {
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
text-align: left;
|
|
406
|
+
flex: 1;
|
|
407
|
+
min-width: 0;
|
|
408
|
+
gap: var(--gallery-list-item-gap, 12px);
|
|
409
|
+
padding: var(--gallery-list-item-padding, 8px);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.grid button.gallery-item-content:hover {
|
|
413
|
+
opacity: var(--gallery-item-hover-opacity, 1);
|
|
414
|
+
transform: var(--gallery-item-hover-transform);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.grid button.gallery-item-content:focus-visible {
|
|
418
|
+
outline: var(--gallery-item-focus-outline, 2px solid currentColor);
|
|
419
|
+
outline-offset: var(--gallery-item-focus-outline-offset, 2px);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.list button.gallery-item-content:focus-visible {
|
|
423
|
+
outline: none;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.list .gallery-item:has(button.gallery-item-content):hover {
|
|
427
|
+
background: var(--gallery-list-item-hover-background, transparent);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.list .gallery-item:has(button.gallery-item-content:focus-visible) {
|
|
431
|
+
outline: var(--gallery-item-focus-outline, 2px solid currentColor);
|
|
432
|
+
outline-offset: var(--gallery-item-focus-outline-offset, 2px);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.list-text {
|
|
436
|
+
display: flex;
|
|
437
|
+
flex-direction: column;
|
|
438
|
+
gap: var(--gallery-list-text-gap, 2px);
|
|
439
|
+
min-width: 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.list-title,
|
|
443
|
+
.list-caption {
|
|
444
|
+
overflow: hidden;
|
|
445
|
+
text-overflow: ellipsis;
|
|
446
|
+
white-space: nowrap;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.list-title {
|
|
450
|
+
color: var(--gallery-list-title-color, inherit);
|
|
451
|
+
font-size: var(--gallery-list-title-font-size, 14px);
|
|
452
|
+
font-weight: var(--gallery-list-title-font-weight, 500);
|
|
453
|
+
font-family: var(--gallery-list-title-font-family);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.list-caption {
|
|
457
|
+
color: var(--gallery-list-caption-color, inherit);
|
|
458
|
+
font-size: var(--gallery-list-caption-font-size, 12px);
|
|
459
|
+
font-family: var(--gallery-list-caption-font-family);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.gallery-item-actions {
|
|
463
|
+
display: flex;
|
|
464
|
+
align-items: center;
|
|
465
|
+
gap: var(--gallery-item-actions-gap, 4px);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.grid .gallery-item-actions {
|
|
469
|
+
position: absolute;
|
|
470
|
+
top: var(--gallery-item-actions-top, 8px);
|
|
471
|
+
right: var(--gallery-item-actions-right, 8px);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.list .gallery-item-actions {
|
|
475
|
+
flex-shrink: 0;
|
|
476
|
+
padding-right: var(--gallery-item-actions-right, 8px);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.gallery-item-action {
|
|
480
|
+
border-radius: var(--gallery-item-action-border-radius, 8px);
|
|
481
|
+
--button-color: transparent;
|
|
482
|
+
--button-hover-color: transparent;
|
|
483
|
+
--button-padding: var(--gallery-item-action-padding, 6px);
|
|
484
|
+
--button-border-radius: var(--gallery-item-action-border-radius, 8px);
|
|
485
|
+
--icon-container-padding: 0px;
|
|
486
|
+
--icon-padding: 0px;
|
|
487
|
+
--icon-width: var(--gallery-item-action-icon-size, 16px);
|
|
488
|
+
--icon-height: var(--gallery-item-action-icon-size, 16px);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.grid .gallery-item-action {
|
|
492
|
+
background: var(--gallery-item-action-background, #00000066);
|
|
493
|
+
-webkit-backdrop-filter: var(--gallery-item-action-backdrop-filter, blur(8px));
|
|
494
|
+
backdrop-filter: var(--gallery-item-action-backdrop-filter, blur(8px));
|
|
495
|
+
--button-text-color: var(--gallery-item-action-color, #ffffff);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.grid .gallery-item-action:hover {
|
|
499
|
+
background: var(--gallery-item-action-hover-background, #00000099);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.list .gallery-item-action {
|
|
503
|
+
background: var(--gallery-item-action-background, transparent);
|
|
504
|
+
--button-text-color: var(--gallery-item-action-color, currentColor);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.list .gallery-item-action:hover {
|
|
508
|
+
background: var(--gallery-item-action-hover-background, #80808026);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.lightbox {
|
|
512
|
+
position: fixed;
|
|
513
|
+
top: 0;
|
|
514
|
+
bottom: 0;
|
|
515
|
+
left: 0;
|
|
516
|
+
right: 0;
|
|
517
|
+
z-index: var(--gallery-lightbox-z-index, 15);
|
|
518
|
+
display: flex;
|
|
519
|
+
justify-content: center;
|
|
520
|
+
align-items: center;
|
|
521
|
+
background: var(--gallery-lightbox-background, #000000e6);
|
|
522
|
+
-webkit-tap-highlight-color: transparent;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.lightbox:focus {
|
|
526
|
+
outline: none;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.lightbox-figure {
|
|
530
|
+
display: flex;
|
|
531
|
+
flex-direction: column;
|
|
532
|
+
align-items: center;
|
|
533
|
+
gap: var(--gallery-lightbox-caption-gap, 12px);
|
|
534
|
+
margin: 0;
|
|
535
|
+
pointer-events: none;
|
|
536
|
+
--image-width: var(--gallery-lightbox-image-width, 85vw);
|
|
537
|
+
--image-height: var(--gallery-lightbox-image-height, 75vh);
|
|
538
|
+
--image-object-fit: var(--gallery-lightbox-image-fit, contain);
|
|
539
|
+
--image-border-radius: var(--gallery-lightbox-image-border-radius, 0px);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.lightbox-caption {
|
|
543
|
+
color: var(--gallery-lightbox-caption-color, #ffffff);
|
|
544
|
+
font-size: var(--gallery-lightbox-caption-font-size, 14px);
|
|
545
|
+
font-family: var(--gallery-lightbox-caption-font-family);
|
|
546
|
+
text-align: center;
|
|
547
|
+
max-width: var(--gallery-lightbox-image-width, 85vw);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.lightbox-counter {
|
|
551
|
+
position: absolute;
|
|
552
|
+
bottom: var(--gallery-lightbox-counter-bottom, 16px);
|
|
553
|
+
left: 50%;
|
|
554
|
+
transform: translateX(-50%);
|
|
555
|
+
color: var(--gallery-lightbox-counter-color, #ffffff);
|
|
556
|
+
font-size: var(--gallery-lightbox-counter-font-size, 13px);
|
|
557
|
+
font-family: var(--gallery-lightbox-counter-font-family);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.lightbox-close,
|
|
561
|
+
.lightbox-previous,
|
|
562
|
+
.lightbox-next {
|
|
563
|
+
position: absolute;
|
|
564
|
+
--button-color: var(--gallery-lightbox-control-background, transparent);
|
|
565
|
+
--button-text-color: var(--gallery-lightbox-control-color, #ffffff);
|
|
566
|
+
--button-hover-color: var(--gallery-lightbox-control-hover-background, #ffffff1f);
|
|
567
|
+
--button-padding: var(--gallery-lightbox-control-padding, 8px);
|
|
568
|
+
--button-border-radius: var(--gallery-lightbox-control-border-radius, 50%);
|
|
569
|
+
--icon-container-padding: 0px;
|
|
570
|
+
--icon-padding: 0px;
|
|
571
|
+
--icon-width: var(--gallery-lightbox-control-icon-size, 24px);
|
|
572
|
+
--icon-height: var(--gallery-lightbox-control-icon-size, 24px);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.lightbox-close {
|
|
576
|
+
top: var(--gallery-lightbox-close-top, 16px);
|
|
577
|
+
right: var(--gallery-lightbox-close-right, 16px);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.lightbox-previous,
|
|
581
|
+
.lightbox-next {
|
|
582
|
+
top: 50%;
|
|
583
|
+
transform: translateY(-50%);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.lightbox-previous {
|
|
587
|
+
left: var(--gallery-lightbox-nav-inset, 16px);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.lightbox-next {
|
|
591
|
+
right: var(--gallery-lightbox-nav-inset, 16px);
|
|
592
|
+
}
|
|
593
|
+
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type GalleryView = 'grid' | 'list';
|
|
3
|
+
export type GalleryImage = {
|
|
4
|
+
src: string;
|
|
5
|
+
alt: string;
|
|
6
|
+
thumbnail?: string;
|
|
7
|
+
fallback?: string;
|
|
8
|
+
caption?: string;
|
|
9
|
+
};
|
|
10
|
+
export type GalleryProperties = MandatoryGalleryProperties & OptionalGalleryProperties & GalleryEventProperties;
|
|
11
|
+
export type MandatoryGalleryProperties = {
|
|
12
|
+
images: GalleryImage[];
|
|
13
|
+
};
|
|
14
|
+
export type OptionalGalleryProperties = {
|
|
15
|
+
view?: GalleryView;
|
|
16
|
+
open?: boolean;
|
|
17
|
+
activeIndex?: number;
|
|
18
|
+
enableLightbox?: boolean;
|
|
19
|
+
loop?: boolean;
|
|
20
|
+
showCounter?: boolean;
|
|
21
|
+
showCaption?: boolean;
|
|
22
|
+
previousIcon?: Snippet;
|
|
23
|
+
nextIcon?: Snippet;
|
|
24
|
+
closeIcon?: Snippet;
|
|
25
|
+
editIcon?: Snippet;
|
|
26
|
+
deleteIcon?: Snippet;
|
|
27
|
+
itemFooter?: Snippet<[GalleryImage, number]>;
|
|
28
|
+
testId?: string;
|
|
29
|
+
classes?: string;
|
|
30
|
+
};
|
|
31
|
+
export type GalleryEventProperties = {
|
|
32
|
+
onImageClick?: (index: number, event: MouseEvent) => void;
|
|
33
|
+
onEditClick?: (index: number, event: MouseEvent) => void;
|
|
34
|
+
onDeleteClick?: (index: number, event: MouseEvent) => void;
|
|
35
|
+
onOpen?: (index: number) => void;
|
|
36
|
+
onDismiss?: () => void;
|
|
37
|
+
onIndexChange?: (index: number) => void;
|
|
38
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/Input/Input.svelte
CHANGED
|
@@ -122,6 +122,26 @@
|
|
|
122
122
|
// forceError lets consumers drive the error border from server/runtime validation,
|
|
123
123
|
// independent of validationPattern.
|
|
124
124
|
const showError = $derived(showErrorMessage || forceError);
|
|
125
|
+
// The error text has to be reachable FROM the field it describes and announced when it
|
|
126
|
+
// appears. Without both, a screen-reader user submits, hears nothing, and is left on a form
|
|
127
|
+
// that did not move -- the message is on screen and absent from the accessibility tree.
|
|
128
|
+
const errorMessageId = $derived(`${effectiveId}-error`);
|
|
129
|
+
// `onErrorMessage` is `string | null`, so null has to be excluded explicitly: `null !== ''`
|
|
130
|
+
// is true, which would describe the field by an alert element carrying no message.
|
|
131
|
+
const isShowingError = $derived(
|
|
132
|
+
onErrorMessage != null && onErrorMessage !== '' && showError && !actionInput
|
|
133
|
+
);
|
|
134
|
+
const infoMessageId = $derived(`${effectiveId}-info`);
|
|
135
|
+
const isShowingInfo = $derived(infoMessage !== '' && !actionInput);
|
|
136
|
+
// Helper text is part of the field's description, not decoration: a consumer's `infoMessage`
|
|
137
|
+
// ("Enter a percentage between 1 and 100") is exactly the guidance a screen-reader user needs
|
|
138
|
+
// BEFORE they trip an error. Reference both, in reading order, so the field is described by
|
|
139
|
+
// everything visibly attached to it rather than only by its failure.
|
|
140
|
+
const describedBy = $derived(
|
|
141
|
+
[isShowingError ? errorMessageId : null, isShowingInfo ? infoMessageId : null]
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.join(' ')
|
|
144
|
+
);
|
|
125
145
|
const hasLeftIcon = $derived(typeof leftIcon === 'function');
|
|
126
146
|
const hasRightIcon = $derived(typeof rightIcon === 'function');
|
|
127
147
|
|
|
@@ -301,6 +321,8 @@
|
|
|
301
321
|
aria-controls={ariaControls}
|
|
302
322
|
aria-activedescendant={ariaActivedescendant}
|
|
303
323
|
aria-required={mandatory || null}
|
|
324
|
+
aria-invalid={showError ? 'true' : null}
|
|
325
|
+
aria-describedby={describedBy || null}
|
|
304
326
|
required={mandatory || null}
|
|
305
327
|
onfocus={onFocus}
|
|
306
328
|
onfocusout={_onFocusOut}
|
|
@@ -337,6 +359,8 @@
|
|
|
337
359
|
aria-controls={ariaControls}
|
|
338
360
|
aria-activedescendant={ariaActivedescendant}
|
|
339
361
|
aria-required={mandatory || null}
|
|
362
|
+
aria-invalid={showError ? 'true' : null}
|
|
363
|
+
aria-describedby={describedBy || null}
|
|
340
364
|
required={mandatory || null}
|
|
341
365
|
onfocus={onFocus}
|
|
342
366
|
onfocusout={_onFocusOut}
|
|
@@ -399,8 +423,10 @@
|
|
|
399
423
|
{@render fieldElement()}
|
|
400
424
|
{/if}
|
|
401
425
|
|
|
402
|
-
{#if
|
|
426
|
+
{#if isShowingError}
|
|
403
427
|
<div
|
|
428
|
+
id={errorMessageId}
|
|
429
|
+
role="alert"
|
|
404
430
|
class="error-message"
|
|
405
431
|
data-pw={typeof testId === 'string' && testId.length > 0 ? `${testId}-error-message` : null}
|
|
406
432
|
testID={typeof testId === 'string' && testId.length > 0 ? `${testId}-error-message` : null}
|
|
@@ -408,8 +434,8 @@
|
|
|
408
434
|
{onErrorMessage}
|
|
409
435
|
</div>
|
|
410
436
|
{/if}
|
|
411
|
-
{#if
|
|
412
|
-
<div class="info-message">
|
|
437
|
+
{#if isShowingInfo}
|
|
438
|
+
<div id={infoMessageId} class="info-message">
|
|
413
439
|
{infoMessage}
|
|
414
440
|
</div>
|
|
415
441
|
{/if}
|