polymorph-ui-components 0.3.0 → 0.5.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/Button/Button.svelte +7 -0
- package/dist/Button/Button.svelte.d.ts +3 -1
- package/dist/ChatBubble/ChatBubble.svelte +30 -2
- package/dist/ChatBubble/ChatBubble.svelte.d.ts +1 -1
- package/dist/ChatBubble/properties.d.ts +3 -0
- package/dist/Gallery/Gallery.svelte +549 -0
- package/dist/Gallery/Gallery.svelte.d.ts +4 -0
- package/dist/Gallery/properties.d.ts +38 -0
- package/dist/Gallery/properties.js +1 -0
- package/dist/Icon/Icon.svelte +23 -5
- package/dist/Img/Img.svelte +1 -0
- package/dist/Resizable/Resizable.svelte +8 -0
- package/dist/assets/delete.svg +7 -0
- package/dist/assets/edit.svg +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
classes
|
|
23
23
|
}: ButtonProperties = $props();
|
|
24
24
|
|
|
25
|
+
export function getButtonRef(): HTMLButtonElement | null {
|
|
26
|
+
return buttonElement;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let buttonElement: HTMLButtonElement | null = $state(null);
|
|
30
|
+
|
|
25
31
|
let isDisabled = $derived(!enable || disabled || showLoader);
|
|
26
32
|
|
|
27
33
|
function handleButtonClick(event: MouseEvent): void {
|
|
@@ -40,6 +46,7 @@
|
|
|
40
46
|
<div class="button-progress-bar"></div>
|
|
41
47
|
{/if}
|
|
42
48
|
<button
|
|
49
|
+
bind:this={buttonElement}
|
|
43
50
|
class:disabled={isDisabled}
|
|
44
51
|
onclick={handleButtonClick}
|
|
45
52
|
{onkeyup}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ButtonProperties } from './properties';
|
|
2
|
-
declare const Button: import("svelte").Component<ButtonProperties, {
|
|
2
|
+
declare const Button: import("svelte").Component<ButtonProperties, {
|
|
3
|
+
getButtonRef: () => HTMLButtonElement | null;
|
|
4
|
+
}, "showProgressBar">;
|
|
3
5
|
type Button = ReturnType<typeof Button>;
|
|
4
6
|
export default Button;
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
panelHeight = $bindable(600),
|
|
25
25
|
minPanelWidth = 280,
|
|
26
26
|
minPanelHeight = 360,
|
|
27
|
+
expanded = $bindable(false),
|
|
28
|
+
expandedPanelWidth = $bindable(600),
|
|
29
|
+
expandedPanelHeight = $bindable(760),
|
|
27
30
|
onopen,
|
|
28
31
|
onclose,
|
|
29
32
|
ontoggle,
|
|
@@ -61,6 +64,25 @@
|
|
|
61
64
|
);
|
|
62
65
|
let resizeHandles = $derived(handlesFor(verticalSide, effectiveSide));
|
|
63
66
|
|
|
67
|
+
let liveWidth = $derived(expanded ? expandedPanelWidth : panelWidth);
|
|
68
|
+
let liveHeight = $derived(expanded ? expandedPanelHeight : panelHeight);
|
|
69
|
+
|
|
70
|
+
function setLiveWidth(value: number): void {
|
|
71
|
+
if (expanded) {
|
|
72
|
+
expandedPanelWidth = value;
|
|
73
|
+
} else {
|
|
74
|
+
panelWidth = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function setLiveHeight(value: number): void {
|
|
79
|
+
if (expanded) {
|
|
80
|
+
expandedPanelHeight = value;
|
|
81
|
+
} else {
|
|
82
|
+
panelHeight = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
function snapMargin(): number {
|
|
65
87
|
if (root === null) {
|
|
66
88
|
return 24;
|
|
@@ -235,6 +257,7 @@
|
|
|
235
257
|
data-position={position}
|
|
236
258
|
data-effective-side={effectiveSide}
|
|
237
259
|
data-effective-vertical={verticalSide}
|
|
260
|
+
data-expanded={expanded}
|
|
238
261
|
data-pw={testId}
|
|
239
262
|
bind:this={root}
|
|
240
263
|
style:transform={draggable ? `translate(${dragX}px, ${dragY}px)` : null}
|
|
@@ -247,8 +270,8 @@
|
|
|
247
270
|
>
|
|
248
271
|
<Resizable
|
|
249
272
|
disabled={!resizable}
|
|
250
|
-
bind:width={
|
|
251
|
-
bind:height={
|
|
273
|
+
bind:width={() => liveWidth, setLiveWidth}
|
|
274
|
+
bind:height={() => liveHeight, setLiveHeight}
|
|
252
275
|
minWidth={minPanelWidth}
|
|
253
276
|
minHeight={minPanelHeight}
|
|
254
277
|
maxWidth={availWidth ?? Number.POSITIVE_INFINITY}
|
|
@@ -325,6 +348,11 @@
|
|
|
325
348
|
.panel-anchor {
|
|
326
349
|
position: absolute;
|
|
327
350
|
--resizable-handle-color: var(--chat-bubble-resize-handle-color, transparent);
|
|
351
|
+
--resizable-transition: var(
|
|
352
|
+
--chat-bubble-expand-transition,
|
|
353
|
+
width 0.32s cubic-bezier(0.22, 1, 0.36, 1),
|
|
354
|
+
height 0.32s cubic-bezier(0.22, 1, 0.36, 1)
|
|
355
|
+
);
|
|
328
356
|
}
|
|
329
357
|
|
|
330
358
|
.chat-bubble[data-effective-vertical='bottom'] .panel-anchor {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ChatBubbleProperties } from './properties';
|
|
2
|
-
declare const ChatBubble: import("svelte").Component<ChatBubbleProperties, {}, "open" | "dragX" | "dragY" | "panelWidth" | "panelHeight">;
|
|
2
|
+
declare const ChatBubble: import("svelte").Component<ChatBubbleProperties, {}, "open" | "dragX" | "dragY" | "panelWidth" | "panelHeight" | "expanded" | "expandedPanelWidth" | "expandedPanelHeight">;
|
|
3
3
|
type ChatBubble = ReturnType<typeof ChatBubble>;
|
|
4
4
|
export default ChatBubble;
|
|
@@ -30,6 +30,9 @@ export type OptionalChatBubbleProperties = {
|
|
|
30
30
|
panelHeight?: number;
|
|
31
31
|
minPanelWidth?: number;
|
|
32
32
|
minPanelHeight?: number;
|
|
33
|
+
expanded?: boolean;
|
|
34
|
+
expandedPanelWidth?: number;
|
|
35
|
+
expandedPanelHeight?: number;
|
|
33
36
|
testId?: string;
|
|
34
37
|
classes?: string;
|
|
35
38
|
};
|
|
@@ -0,0 +1,549 @@
|
|
|
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
|
+
testId,
|
|
30
|
+
onimageclick,
|
|
31
|
+
oneditclick,
|
|
32
|
+
ondeleteclick,
|
|
33
|
+
onopen,
|
|
34
|
+
onclose,
|
|
35
|
+
onchange,
|
|
36
|
+
onkeydown,
|
|
37
|
+
classes
|
|
38
|
+
}: GalleryProperties = $props();
|
|
39
|
+
|
|
40
|
+
let lightboxDiv: HTMLDivElement | null = $state(null);
|
|
41
|
+
let closeButtonRef: ReturnType<typeof Button> | null = $state(null);
|
|
42
|
+
let previousButtonRef: ReturnType<typeof Button> | null = $state(null);
|
|
43
|
+
let nextButtonRef: ReturnType<typeof Button> | null = $state(null);
|
|
44
|
+
let openerElement: HTMLElement | null = null;
|
|
45
|
+
|
|
46
|
+
let activeImage = $derived(images.at(activeIndex));
|
|
47
|
+
let hasPrevious = $derived(loop ? images.length > 1 : activeIndex > 0);
|
|
48
|
+
let hasNext = $derived(loop ? images.length > 1 : activeIndex < images.length - 1);
|
|
49
|
+
let itemsAreInteractive = $derived(enableLightbox || typeof onimageclick === 'function');
|
|
50
|
+
let showEditButton = $derived(typeof oneditclick === 'function');
|
|
51
|
+
let showDeleteButton = $derived(typeof ondeleteclick === 'function');
|
|
52
|
+
let showItemActions = $derived(showEditButton || showDeleteButton);
|
|
53
|
+
|
|
54
|
+
function lightboxAction(node: HTMLElement) {
|
|
55
|
+
if (openerElement === null && document.activeElement instanceof HTMLElement) {
|
|
56
|
+
openerElement = document.activeElement;
|
|
57
|
+
}
|
|
58
|
+
document.body.style.overflow = 'hidden';
|
|
59
|
+
tick().then(() => {
|
|
60
|
+
node.focus();
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
destroy() {
|
|
64
|
+
document.body.style.overflow = '';
|
|
65
|
+
if (openerElement !== null) {
|
|
66
|
+
openerElement.focus();
|
|
67
|
+
openerElement = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function openLightbox(index: number, opener: EventTarget | null): void {
|
|
74
|
+
if (opener instanceof HTMLElement) {
|
|
75
|
+
openerElement = opener;
|
|
76
|
+
}
|
|
77
|
+
activeIndex = index;
|
|
78
|
+
open = true;
|
|
79
|
+
onopen?.(index);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function closeLightbox(): void {
|
|
83
|
+
open = false;
|
|
84
|
+
onclose?.();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function goToIndex(index: number): Promise<void> {
|
|
88
|
+
if (index < 0 || index >= images.length) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
activeIndex = index;
|
|
92
|
+
onchange?.(activeIndex);
|
|
93
|
+
await tick();
|
|
94
|
+
if (lightboxDiv !== null && !lightboxDiv.contains(document.activeElement)) {
|
|
95
|
+
lightboxDiv.focus();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function showPrevious(): void {
|
|
100
|
+
if (activeIndex > 0) {
|
|
101
|
+
goToIndex(activeIndex - 1);
|
|
102
|
+
} else if (loop && images.length > 1) {
|
|
103
|
+
goToIndex(images.length - 1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function showNext(): void {
|
|
108
|
+
if (activeIndex < images.length - 1) {
|
|
109
|
+
goToIndex(activeIndex + 1);
|
|
110
|
+
} else if (loop && images.length > 1) {
|
|
111
|
+
goToIndex(0);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function handleImageClick(index: number, event: MouseEvent): void {
|
|
116
|
+
onimageclick?.(index, event);
|
|
117
|
+
if (enableLightbox) {
|
|
118
|
+
openLightbox(index, event.currentTarget);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function handleEditClick(index: number, event: MouseEvent): void {
|
|
123
|
+
oneditclick?.(index, event);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function handleDeleteClick(index: number, event: MouseEvent): void {
|
|
127
|
+
ondeleteclick?.(index, event);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function trapFocus(event: KeyboardEvent): void {
|
|
131
|
+
const first = closeButtonRef?.getButtonRef() ?? null;
|
|
132
|
+
const last = (nextButtonRef ?? previousButtonRef ?? closeButtonRef)?.getButtonRef() ?? null;
|
|
133
|
+
if (first === null || last === null) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
event.shiftKey &&
|
|
138
|
+
(document.activeElement === first || document.activeElement === lightboxDiv)
|
|
139
|
+
) {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
last.focus();
|
|
142
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
first.focus();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function handleLightboxKeydown(event: KeyboardEvent): void {
|
|
149
|
+
onkeydown?.(event);
|
|
150
|
+
if (event.key === 'Escape') {
|
|
151
|
+
closeLightbox();
|
|
152
|
+
} else if (event.key === 'ArrowLeft') {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
showPrevious();
|
|
155
|
+
} else if (event.key === 'ArrowRight') {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
showNext();
|
|
158
|
+
} else if (event.key === 'Home') {
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
goToIndex(0);
|
|
161
|
+
} else if (event.key === 'End') {
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
goToIndex(images.length - 1);
|
|
164
|
+
} else if (event.key === 'Tab') {
|
|
165
|
+
trapFocus(event);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function handleBackdropClick(event: MouseEvent): void {
|
|
170
|
+
if (event.target === lightboxDiv) {
|
|
171
|
+
closeLightbox();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
{#snippet controlIcon(fallbackMarkup: string, custom?: Snippet)}
|
|
177
|
+
{#if typeof custom === 'function'}
|
|
178
|
+
{@render custom()}
|
|
179
|
+
{:else}
|
|
180
|
+
<Icon svg={fallbackMarkup} />
|
|
181
|
+
{/if}
|
|
182
|
+
{/snippet}
|
|
183
|
+
|
|
184
|
+
{#snippet itemContent(image: GalleryImage)}
|
|
185
|
+
<Img src={image.thumbnail ?? image.src} alt={image.alt} fallback={image.fallback} />
|
|
186
|
+
{#if view === 'list'}
|
|
187
|
+
<span class="list-text">
|
|
188
|
+
<span class="list-title">{image.alt}</span>
|
|
189
|
+
{#if typeof image.caption === 'string' && image.caption.length > 0}
|
|
190
|
+
<span class="list-caption">{image.caption}</span>
|
|
191
|
+
{/if}
|
|
192
|
+
</span>
|
|
193
|
+
{/if}
|
|
194
|
+
{/snippet}
|
|
195
|
+
|
|
196
|
+
<div class="gallery {view} {classes ?? ''}" data-pw={testId} role="list">
|
|
197
|
+
{#each images as image, index (index)}
|
|
198
|
+
<div class="gallery-item" role="listitem">
|
|
199
|
+
{#if itemsAreInteractive}
|
|
200
|
+
<button
|
|
201
|
+
class="gallery-item-content"
|
|
202
|
+
onclick={(event) => handleImageClick(index, event)}
|
|
203
|
+
aria-label={enableLightbox
|
|
204
|
+
? `View image ${index + 1} of ${images.length}: ${image.alt}`
|
|
205
|
+
: image.alt}
|
|
206
|
+
>
|
|
207
|
+
{@render itemContent(image)}
|
|
208
|
+
</button>
|
|
209
|
+
{:else}
|
|
210
|
+
<div class="gallery-item-content">
|
|
211
|
+
{@render itemContent(image)}
|
|
212
|
+
</div>
|
|
213
|
+
{/if}
|
|
214
|
+
{#if showItemActions}
|
|
215
|
+
<div class="gallery-item-actions">
|
|
216
|
+
{#if showEditButton}
|
|
217
|
+
<div class="gallery-item-action">
|
|
218
|
+
<Button
|
|
219
|
+
ariaLabel={`Edit image ${index + 1}: ${image.alt}`}
|
|
220
|
+
onclick={(event) => handleEditClick(index, event)}
|
|
221
|
+
>
|
|
222
|
+
{@render controlIcon(editSvg, editIcon)}
|
|
223
|
+
</Button>
|
|
224
|
+
</div>
|
|
225
|
+
{/if}
|
|
226
|
+
{#if showDeleteButton}
|
|
227
|
+
<div class="gallery-item-action">
|
|
228
|
+
<Button
|
|
229
|
+
ariaLabel={`Delete image ${index + 1}: ${image.alt}`}
|
|
230
|
+
onclick={(event) => handleDeleteClick(index, event)}
|
|
231
|
+
>
|
|
232
|
+
{@render controlIcon(deleteSvg, deleteIcon)}
|
|
233
|
+
</Button>
|
|
234
|
+
</div>
|
|
235
|
+
{/if}
|
|
236
|
+
</div>
|
|
237
|
+
{/if}
|
|
238
|
+
</div>
|
|
239
|
+
{/each}
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{#if open}
|
|
243
|
+
<div
|
|
244
|
+
class="lightbox {classes ?? ''}"
|
|
245
|
+
role="dialog"
|
|
246
|
+
aria-modal="true"
|
|
247
|
+
aria-label={typeof activeImage === 'object' ? activeImage.alt : 'Image gallery'}
|
|
248
|
+
tabindex="-1"
|
|
249
|
+
bind:this={lightboxDiv}
|
|
250
|
+
use:lightboxAction
|
|
251
|
+
onclick={handleBackdropClick}
|
|
252
|
+
onkeydown={handleLightboxKeydown}
|
|
253
|
+
transition:fade={{ duration: 200 }}
|
|
254
|
+
>
|
|
255
|
+
<div class="lightbox-close">
|
|
256
|
+
<Button ariaLabel="Close gallery" onclick={closeLightbox} bind:this={closeButtonRef}>
|
|
257
|
+
{@render controlIcon(closeSvg, closeIcon)}
|
|
258
|
+
</Button>
|
|
259
|
+
</div>
|
|
260
|
+
{#if hasPrevious}
|
|
261
|
+
<div class="lightbox-previous">
|
|
262
|
+
<Button ariaLabel="Previous image" onclick={showPrevious} bind:this={previousButtonRef}>
|
|
263
|
+
{@render controlIcon(chevronLeftSvg, previousIcon)}
|
|
264
|
+
</Button>
|
|
265
|
+
</div>
|
|
266
|
+
{/if}
|
|
267
|
+
{#if typeof activeImage === 'object'}
|
|
268
|
+
<figure class="lightbox-figure">
|
|
269
|
+
<Img src={activeImage.src} alt={activeImage.alt} fallback={activeImage.fallback} />
|
|
270
|
+
{#if showCaption && typeof activeImage.caption === 'string' && activeImage.caption.length > 0}
|
|
271
|
+
<figcaption class="lightbox-caption">{activeImage.caption}</figcaption>
|
|
272
|
+
{/if}
|
|
273
|
+
</figure>
|
|
274
|
+
{/if}
|
|
275
|
+
{#if hasNext}
|
|
276
|
+
<div class="lightbox-next">
|
|
277
|
+
<Button ariaLabel="Next image" onclick={showNext} bind:this={nextButtonRef}>
|
|
278
|
+
{@render controlIcon(chevronRightSvg, nextIcon)}
|
|
279
|
+
</Button>
|
|
280
|
+
</div>
|
|
281
|
+
{/if}
|
|
282
|
+
{#if showCounter && typeof activeImage === 'object'}
|
|
283
|
+
<div class="lightbox-counter" aria-live="polite">
|
|
284
|
+
{activeIndex + 1} / {images.length}
|
|
285
|
+
</div>
|
|
286
|
+
{/if}
|
|
287
|
+
</div>
|
|
288
|
+
{/if}
|
|
289
|
+
|
|
290
|
+
<style>
|
|
291
|
+
.gallery {
|
|
292
|
+
width: var(--gallery-width, 100%);
|
|
293
|
+
padding: var(--gallery-padding, 0px);
|
|
294
|
+
margin: var(--gallery-margin, 0px);
|
|
295
|
+
background: var(--gallery-background, transparent);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.gallery.grid {
|
|
299
|
+
display: grid;
|
|
300
|
+
grid-template-columns: repeat(var(--gallery-columns, 3), 1fr);
|
|
301
|
+
gap: var(--gallery-gap, 8px);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.gallery.list {
|
|
305
|
+
display: flex;
|
|
306
|
+
flex-direction: column;
|
|
307
|
+
gap: var(--gallery-gap, 8px);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.gallery-item {
|
|
311
|
+
position: relative;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.grid .gallery-item {
|
|
315
|
+
aspect-ratio: var(--gallery-item-aspect-ratio, 1);
|
|
316
|
+
border-radius: var(--gallery-item-border-radius, 0px);
|
|
317
|
+
overflow: hidden;
|
|
318
|
+
--image-width: 100%;
|
|
319
|
+
--image-height: 100%;
|
|
320
|
+
--image-object-fit: var(--gallery-item-image-fit, cover);
|
|
321
|
+
--image-border-radius: var(--gallery-item-border-radius, 0px);
|
|
322
|
+
--image-transition: var(--gallery-item-image-transition);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.list .gallery-item {
|
|
326
|
+
display: flex;
|
|
327
|
+
align-items: center;
|
|
328
|
+
border-radius: var(--gallery-list-item-border-radius, 0px);
|
|
329
|
+
background: var(--gallery-list-item-background, transparent);
|
|
330
|
+
--image-width: var(--gallery-list-thumbnail-width, 56px);
|
|
331
|
+
--image-height: var(--gallery-list-thumbnail-height, 56px);
|
|
332
|
+
--image-object-fit: var(--gallery-list-thumbnail-fit, cover);
|
|
333
|
+
--image-border-radius: var(--gallery-list-thumbnail-border-radius, 0px);
|
|
334
|
+
--image-transition: var(--gallery-item-image-transition);
|
|
335
|
+
--image-flex-shrink: 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.gallery-item-content {
|
|
339
|
+
font: inherit;
|
|
340
|
+
color: inherit;
|
|
341
|
+
border: var(--gallery-item-border, none);
|
|
342
|
+
background: transparent;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
button.gallery-item-content {
|
|
346
|
+
cursor: var(--gallery-item-cursor, pointer);
|
|
347
|
+
transition: var(--gallery-item-transition);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.grid .gallery-item-content {
|
|
351
|
+
display: block;
|
|
352
|
+
width: 100%;
|
|
353
|
+
height: 100%;
|
|
354
|
+
padding: 0;
|
|
355
|
+
border-radius: var(--gallery-item-border-radius, 0px);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.list .gallery-item-content {
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
text-align: left;
|
|
362
|
+
flex: 1;
|
|
363
|
+
min-width: 0;
|
|
364
|
+
gap: var(--gallery-list-item-gap, 12px);
|
|
365
|
+
padding: var(--gallery-list-item-padding, 8px);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.grid button.gallery-item-content:hover {
|
|
369
|
+
opacity: var(--gallery-item-hover-opacity, 1);
|
|
370
|
+
transform: var(--gallery-item-hover-transform);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.grid button.gallery-item-content:focus-visible {
|
|
374
|
+
outline: var(--gallery-item-focus-outline, 2px solid currentColor);
|
|
375
|
+
outline-offset: var(--gallery-item-focus-outline-offset, 2px);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.list button.gallery-item-content:focus-visible {
|
|
379
|
+
outline: none;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.list .gallery-item:has(button.gallery-item-content):hover {
|
|
383
|
+
background: var(--gallery-list-item-hover-background, transparent);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.list .gallery-item:has(button.gallery-item-content:focus-visible) {
|
|
387
|
+
outline: var(--gallery-item-focus-outline, 2px solid currentColor);
|
|
388
|
+
outline-offset: var(--gallery-item-focus-outline-offset, 2px);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.list-text {
|
|
392
|
+
display: flex;
|
|
393
|
+
flex-direction: column;
|
|
394
|
+
gap: var(--gallery-list-text-gap, 2px);
|
|
395
|
+
min-width: 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.list-title,
|
|
399
|
+
.list-caption {
|
|
400
|
+
overflow: hidden;
|
|
401
|
+
text-overflow: ellipsis;
|
|
402
|
+
white-space: nowrap;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.list-title {
|
|
406
|
+
color: var(--gallery-list-title-color, inherit);
|
|
407
|
+
font-size: var(--gallery-list-title-font-size, 14px);
|
|
408
|
+
font-weight: var(--gallery-list-title-font-weight, 500);
|
|
409
|
+
font-family: var(--gallery-list-title-font-family);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.list-caption {
|
|
413
|
+
color: var(--gallery-list-caption-color, inherit);
|
|
414
|
+
font-size: var(--gallery-list-caption-font-size, 12px);
|
|
415
|
+
font-family: var(--gallery-list-caption-font-family);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.gallery-item-actions {
|
|
419
|
+
display: flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
gap: var(--gallery-item-actions-gap, 4px);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.grid .gallery-item-actions {
|
|
425
|
+
position: absolute;
|
|
426
|
+
top: var(--gallery-item-actions-top, 8px);
|
|
427
|
+
right: var(--gallery-item-actions-right, 8px);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.list .gallery-item-actions {
|
|
431
|
+
flex-shrink: 0;
|
|
432
|
+
padding-right: var(--gallery-item-actions-right, 8px);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.gallery-item-action {
|
|
436
|
+
border-radius: var(--gallery-item-action-border-radius, 8px);
|
|
437
|
+
--button-color: transparent;
|
|
438
|
+
--button-hover-color: transparent;
|
|
439
|
+
--button-padding: var(--gallery-item-action-padding, 6px);
|
|
440
|
+
--button-border-radius: var(--gallery-item-action-border-radius, 8px);
|
|
441
|
+
--icon-container-padding: 0px;
|
|
442
|
+
--icon-padding: 0px;
|
|
443
|
+
--icon-width: var(--gallery-item-action-icon-size, 16px);
|
|
444
|
+
--icon-height: var(--gallery-item-action-icon-size, 16px);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.grid .gallery-item-action {
|
|
448
|
+
background: var(--gallery-item-action-background, #00000066);
|
|
449
|
+
-webkit-backdrop-filter: var(--gallery-item-action-backdrop-filter, blur(8px));
|
|
450
|
+
backdrop-filter: var(--gallery-item-action-backdrop-filter, blur(8px));
|
|
451
|
+
--button-text-color: var(--gallery-item-action-color, #ffffff);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.grid .gallery-item-action:hover {
|
|
455
|
+
background: var(--gallery-item-action-hover-background, #00000099);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.list .gallery-item-action {
|
|
459
|
+
background: var(--gallery-item-action-background, transparent);
|
|
460
|
+
--button-text-color: var(--gallery-item-action-color, currentColor);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.list .gallery-item-action:hover {
|
|
464
|
+
background: var(--gallery-item-action-hover-background, #80808026);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.lightbox {
|
|
468
|
+
position: fixed;
|
|
469
|
+
top: 0;
|
|
470
|
+
bottom: 0;
|
|
471
|
+
left: 0;
|
|
472
|
+
right: 0;
|
|
473
|
+
z-index: var(--gallery-lightbox-z-index, 15);
|
|
474
|
+
display: flex;
|
|
475
|
+
justify-content: center;
|
|
476
|
+
align-items: center;
|
|
477
|
+
background: var(--gallery-lightbox-background, #000000e6);
|
|
478
|
+
-webkit-tap-highlight-color: transparent;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.lightbox:focus {
|
|
482
|
+
outline: none;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.lightbox-figure {
|
|
486
|
+
display: flex;
|
|
487
|
+
flex-direction: column;
|
|
488
|
+
align-items: center;
|
|
489
|
+
gap: var(--gallery-lightbox-caption-gap, 12px);
|
|
490
|
+
margin: 0;
|
|
491
|
+
pointer-events: none;
|
|
492
|
+
--image-width: var(--gallery-lightbox-image-width, 85vw);
|
|
493
|
+
--image-height: var(--gallery-lightbox-image-height, 75vh);
|
|
494
|
+
--image-object-fit: var(--gallery-lightbox-image-fit, contain);
|
|
495
|
+
--image-border-radius: var(--gallery-lightbox-image-border-radius, 0px);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.lightbox-caption {
|
|
499
|
+
color: var(--gallery-lightbox-caption-color, #ffffff);
|
|
500
|
+
font-size: var(--gallery-lightbox-caption-font-size, 14px);
|
|
501
|
+
font-family: var(--gallery-lightbox-caption-font-family);
|
|
502
|
+
text-align: center;
|
|
503
|
+
max-width: var(--gallery-lightbox-image-width, 85vw);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.lightbox-counter {
|
|
507
|
+
position: absolute;
|
|
508
|
+
bottom: var(--gallery-lightbox-counter-bottom, 16px);
|
|
509
|
+
left: 50%;
|
|
510
|
+
transform: translateX(-50%);
|
|
511
|
+
color: var(--gallery-lightbox-counter-color, #ffffff);
|
|
512
|
+
font-size: var(--gallery-lightbox-counter-font-size, 13px);
|
|
513
|
+
font-family: var(--gallery-lightbox-counter-font-family);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.lightbox-close,
|
|
517
|
+
.lightbox-previous,
|
|
518
|
+
.lightbox-next {
|
|
519
|
+
position: absolute;
|
|
520
|
+
--button-color: var(--gallery-lightbox-control-background, transparent);
|
|
521
|
+
--button-text-color: var(--gallery-lightbox-control-color, #ffffff);
|
|
522
|
+
--button-hover-color: var(--gallery-lightbox-control-hover-background, #ffffff1f);
|
|
523
|
+
--button-padding: var(--gallery-lightbox-control-padding, 8px);
|
|
524
|
+
--button-border-radius: var(--gallery-lightbox-control-border-radius, 50%);
|
|
525
|
+
--icon-container-padding: 0px;
|
|
526
|
+
--icon-padding: 0px;
|
|
527
|
+
--icon-width: var(--gallery-lightbox-control-icon-size, 24px);
|
|
528
|
+
--icon-height: var(--gallery-lightbox-control-icon-size, 24px);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.lightbox-close {
|
|
532
|
+
top: var(--gallery-lightbox-close-top, 16px);
|
|
533
|
+
right: var(--gallery-lightbox-close-right, 16px);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.lightbox-previous,
|
|
537
|
+
.lightbox-next {
|
|
538
|
+
top: 50%;
|
|
539
|
+
transform: translateY(-50%);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.lightbox-previous {
|
|
543
|
+
left: var(--gallery-lightbox-nav-inset, 16px);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.lightbox-next {
|
|
547
|
+
right: var(--gallery-lightbox-nav-inset, 16px);
|
|
548
|
+
}
|
|
549
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
testId?: string;
|
|
28
|
+
classes?: string;
|
|
29
|
+
};
|
|
30
|
+
export type GalleryEventProperties = {
|
|
31
|
+
onimageclick?: (index: number, event: MouseEvent) => void;
|
|
32
|
+
oneditclick?: (index: number, event: MouseEvent) => void;
|
|
33
|
+
ondeleteclick?: (index: number, event: MouseEvent) => void;
|
|
34
|
+
onopen?: (index: number) => void;
|
|
35
|
+
onclose?: () => void;
|
|
36
|
+
onchange?: (index: number) => void;
|
|
37
|
+
onkeydown?: (event: KeyboardEvent) => void;
|
|
38
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/Icon/Icon.svelte
CHANGED
|
@@ -2,14 +2,28 @@
|
|
|
2
2
|
import type { IconProperties } from './properties';
|
|
3
3
|
|
|
4
4
|
let { icon, svg, text, onclick, onkeydown, testId, classes }: IconProperties = $props();
|
|
5
|
+
|
|
6
|
+
let interactive = $derived(typeof onclick === 'function');
|
|
7
|
+
|
|
8
|
+
function handleKeydown(event: KeyboardEvent): void {
|
|
9
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
10
|
+
event.preventDefault();
|
|
11
|
+
if (event.currentTarget instanceof HTMLElement) {
|
|
12
|
+
event.currentTarget.click();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
onkeydown?.(event);
|
|
16
|
+
}
|
|
5
17
|
</script>
|
|
6
18
|
|
|
19
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
7
20
|
<div
|
|
8
21
|
class="icon-container {classes ?? ''}"
|
|
9
|
-
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
22
|
+
class:interactive
|
|
23
|
+
onclick={interactive ? onclick : null}
|
|
24
|
+
onkeydown={interactive ? handleKeydown : onkeydown}
|
|
25
|
+
role={interactive ? 'button' : null}
|
|
26
|
+
tabindex={interactive ? 0 : null}
|
|
13
27
|
data-pw={testId}
|
|
14
28
|
>
|
|
15
29
|
{#if typeof svg === 'string' && svg.length > 0}
|
|
@@ -29,8 +43,12 @@
|
|
|
29
43
|
padding: var(--icon-container-padding, 4px);
|
|
30
44
|
flex-direction: var(--icon-container-direction, column);
|
|
31
45
|
align-items: center;
|
|
32
|
-
cursor: pointer;
|
|
33
46
|
}
|
|
47
|
+
|
|
48
|
+
.icon-container.interactive {
|
|
49
|
+
cursor: var(--icon-cursor, pointer);
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
.icon-container img {
|
|
35
53
|
height: var(--icon-height, 20px);
|
|
36
54
|
width: var(--icon-width, 20px);
|
package/dist/Img/Img.svelte
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
border-radius: var(--image-border-radius, 0px);
|
|
26
26
|
margin: var(--image-margin, 0px);
|
|
27
27
|
filter: var(--image-filter, none);
|
|
28
|
+
flex-shrink: var(--image-flex-shrink);
|
|
28
29
|
background: var(--image-background);
|
|
29
30
|
border: var(--image-border);
|
|
30
31
|
transition: var(--image-transition);
|
|
@@ -210,10 +210,18 @@
|
|
|
210
210
|
box-sizing: border-box;
|
|
211
211
|
max-width: var(--resizable-max-width, none);
|
|
212
212
|
max-height: var(--resizable-max-height, none);
|
|
213
|
+
transition: var(--resizable-transition, none);
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
.resizable.resizing {
|
|
216
217
|
user-select: none;
|
|
218
|
+
transition: none;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@media (prefers-reduced-motion: reduce) {
|
|
222
|
+
.resizable {
|
|
223
|
+
transition: none;
|
|
224
|
+
}
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
.handle {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M3 6h18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
<line x1="10" y1="11" x2="10" y2="17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
+
<line x1="14" y1="11" x2="14" y2="17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
|
+
<path d="m15 5 4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
</svg>
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ export { default as ChatSuggestions } from './ChatSuggestions/ChatSuggestions.sv
|
|
|
64
64
|
export { default as Resizable } from './Resizable/Resizable.svelte';
|
|
65
65
|
export { default as Draggable } from './Draggable/Draggable.svelte';
|
|
66
66
|
export { default as ChatBubble } from './ChatBubble/ChatBubble.svelte';
|
|
67
|
+
export { default as Gallery } from './Gallery/Gallery.svelte';
|
|
67
68
|
export { ChatController } from './Chat/controller.svelte';
|
|
68
69
|
export { partyOf } from './Chat/roles';
|
|
69
70
|
export type * from './Button/properties';
|
|
@@ -127,4 +128,5 @@ export type * from './ChatSuggestions/properties';
|
|
|
127
128
|
export type * from './Resizable/properties';
|
|
128
129
|
export type * from './Draggable/properties';
|
|
129
130
|
export type * from './ChatBubble/properties';
|
|
131
|
+
export type * from './Gallery/properties';
|
|
130
132
|
export { validateInput } from './utils';
|
package/dist/index.js
CHANGED
|
@@ -64,6 +64,7 @@ export { default as ChatSuggestions } from './ChatSuggestions/ChatSuggestions.sv
|
|
|
64
64
|
export { default as Resizable } from './Resizable/Resizable.svelte';
|
|
65
65
|
export { default as Draggable } from './Draggable/Draggable.svelte';
|
|
66
66
|
export { default as ChatBubble } from './ChatBubble/ChatBubble.svelte';
|
|
67
|
+
export { default as Gallery } from './Gallery/Gallery.svelte';
|
|
67
68
|
export { ChatController } from './Chat/controller.svelte';
|
|
68
69
|
export { partyOf } from './Chat/roles';
|
|
69
70
|
export { validateInput } from './utils';
|