polymorph-ui-components 0.6.2 → 0.8.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 +30 -5
- package/dist/Button/Button.svelte +5 -5
- package/dist/Carousel/Carousel.svelte +266 -145
- package/dist/Carousel/properties.d.ts +14 -2
- package/dist/Combobox/Combobox.svelte.d.ts +1 -1
- package/dist/CommandMenu/CommandMenu.svelte +3 -3
- package/dist/Gallery/Gallery.svelte +65 -22
- package/dist/Gallery/Gallery.svelte.d.ts +1 -1
- package/dist/Gallery/properties.d.ts +1 -0
- package/dist/Input/Input.svelte +12 -3
- package/dist/Input/properties.d.ts +2 -0
- package/dist/InputButton/InputButton.svelte +13 -5
- package/dist/Loader/Loader.svelte +20 -4
- package/dist/Loader/properties.d.ts +1 -0
- package/dist/MediaUpload/MediaUpload.svelte +2 -2
- package/dist/Modal/Modal.svelte +5 -5
- package/dist/NumberStepper/NumberStepper.svelte +154 -0
- package/dist/NumberStepper/NumberStepper.svelte.d.ts +4 -0
- package/dist/NumberStepper/properties.d.ts +22 -0
- package/dist/NumberStepper/properties.js +1 -0
- package/dist/Pill/Pill.svelte +2 -1
- package/dist/Pill/properties.d.ts +1 -0
- package/dist/Progress/Progress.svelte +146 -8
- package/dist/Progress/properties.d.ts +9 -0
- package/dist/Sheet/Sheet.svelte +96 -40
- package/dist/Sheet/properties.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/utils.d.ts +20 -0
- package/dist/utils.js +107 -0
- package/dist-wc/index.js +18861 -0
- package/package.json +7 -3
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { SvelteMap } from 'svelte/reactivity';
|
|
5
5
|
import Img from '../Img/Img.svelte';
|
|
6
6
|
import searchSvg from '../assets/search.svg?raw';
|
|
7
|
+
import { lockDocumentScroll } from '../utils';
|
|
7
8
|
|
|
8
9
|
let {
|
|
9
10
|
items,
|
|
@@ -162,7 +163,7 @@
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
function scrollLockAction(_node: HTMLElement) {
|
|
165
|
-
|
|
166
|
+
const unlockScroll = lockDocumentScroll();
|
|
166
167
|
tick().then(() => {
|
|
167
168
|
if (inputElement !== null) {
|
|
168
169
|
inputElement.focus();
|
|
@@ -170,7 +171,7 @@
|
|
|
170
171
|
});
|
|
171
172
|
return {
|
|
172
173
|
destroy() {
|
|
173
|
-
|
|
174
|
+
unlockScroll();
|
|
174
175
|
}
|
|
175
176
|
};
|
|
176
177
|
}
|
|
@@ -181,7 +182,6 @@
|
|
|
181
182
|
|
|
182
183
|
onDestroy(() => {
|
|
183
184
|
if (typeof window !== 'undefined') {
|
|
184
|
-
document.body.style.overflow = '';
|
|
185
185
|
window.removeEventListener('keydown', handleGlobalKeyDown);
|
|
186
186
|
}
|
|
187
187
|
});
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import chevronRightSvg from '../assets/chevron-right-lg.svg?raw';
|
|
12
12
|
import editSvg from '../assets/edit.svg?raw';
|
|
13
13
|
import deleteSvg from '../assets/delete.svg?raw';
|
|
14
|
+
import { activeElementOf, deepActiveElement, lockDocumentScroll } from '../utils';
|
|
14
15
|
|
|
15
16
|
let {
|
|
16
17
|
images,
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
closeIcon,
|
|
27
28
|
editIcon,
|
|
28
29
|
deleteIcon,
|
|
30
|
+
itemFooter,
|
|
29
31
|
testId,
|
|
30
32
|
onimageclick,
|
|
31
33
|
oneditclick,
|
|
@@ -47,21 +49,23 @@
|
|
|
47
49
|
let hasPrevious = $derived(loop ? images.length > 1 : activeIndex > 0);
|
|
48
50
|
let hasNext = $derived(loop ? images.length > 1 : activeIndex < images.length - 1);
|
|
49
51
|
let itemsAreInteractive = $derived(enableLightbox || typeof onimageclick === 'function');
|
|
52
|
+
let hasItemFooter = $derived(view === 'grid' && typeof itemFooter === 'function');
|
|
50
53
|
let showEditButton = $derived(typeof oneditclick === 'function');
|
|
51
54
|
let showDeleteButton = $derived(typeof ondeleteclick === 'function');
|
|
52
55
|
let showItemActions = $derived(showEditButton || showDeleteButton);
|
|
53
56
|
|
|
54
57
|
function lightboxAction(node: HTMLElement) {
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
const active = deepActiveElement();
|
|
59
|
+
if (openerElement === null && active instanceof HTMLElement) {
|
|
60
|
+
openerElement = active;
|
|
57
61
|
}
|
|
58
|
-
|
|
62
|
+
const unlockScroll = lockDocumentScroll();
|
|
59
63
|
tick().then(() => {
|
|
60
64
|
node.focus();
|
|
61
65
|
});
|
|
62
66
|
return {
|
|
63
67
|
destroy() {
|
|
64
|
-
|
|
68
|
+
unlockScroll();
|
|
65
69
|
if (openerElement !== null) {
|
|
66
70
|
openerElement.focus();
|
|
67
71
|
openerElement = null;
|
|
@@ -91,7 +95,7 @@
|
|
|
91
95
|
activeIndex = index;
|
|
92
96
|
onchange?.(activeIndex);
|
|
93
97
|
await tick();
|
|
94
|
-
if (lightboxDiv !== null && !lightboxDiv.contains(
|
|
98
|
+
if (lightboxDiv !== null && !lightboxDiv.contains(activeElementOf(lightboxDiv))) {
|
|
95
99
|
lightboxDiv.focus();
|
|
96
100
|
}
|
|
97
101
|
}
|
|
@@ -133,13 +137,11 @@
|
|
|
133
137
|
if (first === null || last === null) {
|
|
134
138
|
return;
|
|
135
139
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
(document.activeElement === first || document.activeElement === lightboxDiv)
|
|
139
|
-
) {
|
|
140
|
+
const active = activeElementOf(first);
|
|
141
|
+
if (event.shiftKey && (active === first || active === lightboxDiv)) {
|
|
140
142
|
event.preventDefault();
|
|
141
143
|
last.focus();
|
|
142
|
-
} else if (!event.shiftKey &&
|
|
144
|
+
} else if (!event.shiftKey && active === last) {
|
|
143
145
|
event.preventDefault();
|
|
144
146
|
first.focus();
|
|
145
147
|
}
|
|
@@ -181,19 +183,32 @@
|
|
|
181
183
|
{/if}
|
|
182
184
|
{/snippet}
|
|
183
185
|
|
|
184
|
-
{#snippet itemContent(image: GalleryImage)}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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}
|
|
186
|
+
{#snippet itemContent(image: GalleryImage, index: number)}
|
|
187
|
+
{#if hasItemFooter}
|
|
188
|
+
<span class="grid-image-wrap">
|
|
189
|
+
<Img src={image.thumbnail ?? image.src} alt={image.alt} fallback={image.fallback} />
|
|
192
190
|
</span>
|
|
191
|
+
{#if typeof itemFooter === 'function'}
|
|
192
|
+
{@render itemFooter(image, index)}
|
|
193
|
+
{/if}
|
|
194
|
+
{:else}
|
|
195
|
+
<Img src={image.thumbnail ?? image.src} alt={image.alt} fallback={image.fallback} />
|
|
196
|
+
{#if view === 'list'}
|
|
197
|
+
<span class="list-text">
|
|
198
|
+
<span class="list-title">{image.alt}</span>
|
|
199
|
+
{#if typeof image.caption === 'string' && image.caption.length > 0}
|
|
200
|
+
<span class="list-caption">{image.caption}</span>
|
|
201
|
+
{/if}
|
|
202
|
+
</span>
|
|
203
|
+
{/if}
|
|
193
204
|
{/if}
|
|
194
205
|
{/snippet}
|
|
195
206
|
|
|
196
|
-
<div
|
|
207
|
+
<div
|
|
208
|
+
class="gallery {view} {hasItemFooter ? 'has-item-footer' : ''} {classes ?? ''}"
|
|
209
|
+
data-pw={testId}
|
|
210
|
+
role="list"
|
|
211
|
+
>
|
|
197
212
|
{#each images as image, index (index)}
|
|
198
213
|
<div class="gallery-item" role="listitem">
|
|
199
214
|
{#if itemsAreInteractive}
|
|
@@ -204,11 +219,11 @@
|
|
|
204
219
|
? `View image ${index + 1} of ${images.length}: ${image.alt}`
|
|
205
220
|
: image.alt}
|
|
206
221
|
>
|
|
207
|
-
{@render itemContent(image)}
|
|
222
|
+
{@render itemContent(image, index)}
|
|
208
223
|
</button>
|
|
209
224
|
{:else}
|
|
210
225
|
<div class="gallery-item-content">
|
|
211
|
-
{@render itemContent(image)}
|
|
226
|
+
{@render itemContent(image, index)}
|
|
212
227
|
</div>
|
|
213
228
|
{/if}
|
|
214
229
|
{#if showItemActions}
|
|
@@ -297,7 +312,10 @@
|
|
|
297
312
|
|
|
298
313
|
.gallery.grid {
|
|
299
314
|
display: grid;
|
|
300
|
-
grid-template-columns:
|
|
315
|
+
grid-template-columns: var(
|
|
316
|
+
--gallery-grid-template-columns,
|
|
317
|
+
repeat(var(--gallery-columns, 3), 1fr)
|
|
318
|
+
);
|
|
301
319
|
gap: var(--gallery-gap, 8px);
|
|
302
320
|
}
|
|
303
321
|
|
|
@@ -322,6 +340,12 @@
|
|
|
322
340
|
--image-transition: var(--gallery-item-image-transition);
|
|
323
341
|
}
|
|
324
342
|
|
|
343
|
+
.grid.has-item-footer .gallery-item {
|
|
344
|
+
aspect-ratio: auto;
|
|
345
|
+
overflow: visible;
|
|
346
|
+
border-radius: 0px;
|
|
347
|
+
}
|
|
348
|
+
|
|
325
349
|
.list .gallery-item {
|
|
326
350
|
display: flex;
|
|
327
351
|
align-items: center;
|
|
@@ -355,6 +379,25 @@
|
|
|
355
379
|
border-radius: var(--gallery-item-border-radius, 0px);
|
|
356
380
|
}
|
|
357
381
|
|
|
382
|
+
.grid.has-item-footer .gallery-item-content {
|
|
383
|
+
display: flex;
|
|
384
|
+
flex-direction: column;
|
|
385
|
+
height: auto;
|
|
386
|
+
border-radius: var(--gallery-item-border-radius, 0px);
|
|
387
|
+
overflow: hidden;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.grid-image-wrap {
|
|
391
|
+
display: block;
|
|
392
|
+
aspect-ratio: var(--gallery-item-aspect-ratio, 1);
|
|
393
|
+
overflow: hidden;
|
|
394
|
+
--image-width: 100%;
|
|
395
|
+
--image-height: 100%;
|
|
396
|
+
--image-object-fit: var(--gallery-item-image-fit, cover);
|
|
397
|
+
--image-border-radius: 0px;
|
|
398
|
+
--image-transition: var(--gallery-item-image-transition);
|
|
399
|
+
}
|
|
400
|
+
|
|
358
401
|
.list .gallery-item-content {
|
|
359
402
|
display: flex;
|
|
360
403
|
align-items: center;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { GalleryProperties } from './properties';
|
|
2
|
-
declare const Gallery: import("svelte").Component<GalleryProperties, {}, "
|
|
2
|
+
declare const Gallery: import("svelte").Component<GalleryProperties, {}, "open" | "activeIndex">;
|
|
3
3
|
type Gallery = ReturnType<typeof Gallery>;
|
|
4
4
|
export default Gallery;
|
package/dist/Input/Input.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { validateInput } from '../utils';
|
|
2
|
+
import { activeElementOf, validateInput } from '../utils';
|
|
3
3
|
import type { InputProperties } from './properties';
|
|
4
4
|
import type { ValidationState } from '../types';
|
|
5
5
|
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
useTextArea = false,
|
|
24
24
|
autoComplete = 'on',
|
|
25
25
|
name = '',
|
|
26
|
+
id,
|
|
27
|
+
ariaLabel,
|
|
26
28
|
testId = '',
|
|
27
29
|
textTransformers = [],
|
|
28
30
|
textViewPresentation = [],
|
|
@@ -42,6 +44,9 @@
|
|
|
42
44
|
ariaActivedescendant
|
|
43
45
|
}: InputProperties = $props();
|
|
44
46
|
|
|
47
|
+
const generatedId = $props.id();
|
|
48
|
+
const inputId = $derived(id ?? generatedId);
|
|
49
|
+
|
|
45
50
|
export function focus() {
|
|
46
51
|
try {
|
|
47
52
|
inputElement?.focus();
|
|
@@ -77,7 +82,7 @@
|
|
|
77
82
|
valueValidation === 'InProgress' &&
|
|
78
83
|
value.length > 0 &&
|
|
79
84
|
inputElement !== null &&
|
|
80
|
-
inputElement !==
|
|
85
|
+
inputElement !== activeElementOf(inputElement)
|
|
81
86
|
) {
|
|
82
87
|
return 'Invalid';
|
|
83
88
|
}
|
|
@@ -194,7 +199,7 @@
|
|
|
194
199
|
|
|
195
200
|
<div class="input-container {classes ?? ''}" class:input-error={showErrorMessage && !actionInput}>
|
|
196
201
|
{#if typeof label === 'string' && label !== '' && !actionInput}
|
|
197
|
-
<label class="label" for={
|
|
202
|
+
<label class="label" for={inputId}>
|
|
198
203
|
{label}
|
|
199
204
|
</label>
|
|
200
205
|
{/if}
|
|
@@ -206,7 +211,9 @@
|
|
|
206
211
|
{placeholder}
|
|
207
212
|
autocomplete={autoComplete}
|
|
208
213
|
{name}
|
|
214
|
+
id={inputId}
|
|
209
215
|
{role}
|
|
216
|
+
aria-label={ariaLabel}
|
|
210
217
|
aria-expanded={ariaExpanded}
|
|
211
218
|
aria-autocomplete={ariaAutocomplete}
|
|
212
219
|
aria-controls={ariaControls}
|
|
@@ -231,7 +238,9 @@
|
|
|
231
238
|
{placeholder}
|
|
232
239
|
autocomplete={autoComplete}
|
|
233
240
|
{name}
|
|
241
|
+
id={inputId}
|
|
234
242
|
{role}
|
|
243
|
+
aria-label={ariaLabel}
|
|
235
244
|
aria-expanded={ariaExpanded}
|
|
236
245
|
aria-autocomplete={ariaAutocomplete}
|
|
237
246
|
aria-controls={ariaControls}
|
|
@@ -23,6 +23,8 @@ export type OptionalInputProperties = {
|
|
|
23
23
|
useTextArea?: boolean;
|
|
24
24
|
autoComplete?: HTMLInputAttributes['autocomplete'];
|
|
25
25
|
name?: string;
|
|
26
|
+
id?: string;
|
|
27
|
+
ariaLabel?: string;
|
|
26
28
|
textTransformers?: TextTransformer[];
|
|
27
29
|
textViewPresentation?: TextTransformer[];
|
|
28
30
|
testId?: string;
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
|
|
24
24
|
let validationState = $state<ValidationState>('InProgress');
|
|
25
25
|
|
|
26
|
+
const generatedId = $props.id();
|
|
27
|
+
const inputId = $derived(inputProperties.id ?? generatedId);
|
|
28
|
+
|
|
26
29
|
let inputRef: SvelteComponent | null = $state(null);
|
|
27
30
|
|
|
28
31
|
// Derive enable state for right button
|
|
@@ -61,8 +64,8 @@
|
|
|
61
64
|
</script>
|
|
62
65
|
|
|
63
66
|
<div class="container {classes ?? ''}" data-pw={testId}>
|
|
64
|
-
{#if inputProperties.label && inputProperties.label
|
|
65
|
-
<label class="label" for={
|
|
67
|
+
{#if typeof inputProperties.label === 'string' && inputProperties.label.length > 0}
|
|
68
|
+
<label class="label" for={inputId}>
|
|
66
69
|
{inputProperties.label}
|
|
67
70
|
</label>
|
|
68
71
|
{/if}
|
|
@@ -79,6 +82,7 @@
|
|
|
79
82
|
<Input
|
|
80
83
|
{...inputProperties}
|
|
81
84
|
{...inputEventProperties}
|
|
85
|
+
id={inputId}
|
|
82
86
|
bind:value
|
|
83
87
|
bind:this={inputRef}
|
|
84
88
|
onstatechange={handleStateChange}
|
|
@@ -102,12 +106,12 @@
|
|
|
102
106
|
</div>
|
|
103
107
|
{/if}
|
|
104
108
|
</div>
|
|
105
|
-
{#if inputProperties.onErrorMessage
|
|
109
|
+
{#if typeof inputProperties.onErrorMessage === 'string' && inputProperties.onErrorMessage.length > 0 && validationState === 'Invalid'}
|
|
106
110
|
<div class="error-message">
|
|
107
111
|
{inputProperties.onErrorMessage}
|
|
108
112
|
</div>
|
|
109
113
|
{/if}
|
|
110
|
-
{#if inputProperties.infoMessage
|
|
114
|
+
{#if typeof inputProperties.infoMessage === 'string' && inputProperties.infoMessage.length > 0}
|
|
111
115
|
<div class="info-message">
|
|
112
116
|
{inputProperties.infoMessage}
|
|
113
117
|
</div>
|
|
@@ -127,7 +131,6 @@
|
|
|
127
131
|
--input-focus-border: none;
|
|
128
132
|
--input-box-shadow: none;
|
|
129
133
|
--input-margin: none;
|
|
130
|
-
--input-width: fit-content;
|
|
131
134
|
height: var(--input-height, fit-content);
|
|
132
135
|
font-size: var(--input-font-size, 16px) !important;
|
|
133
136
|
font-weight: 500;
|
|
@@ -141,6 +144,7 @@
|
|
|
141
144
|
.input-button {
|
|
142
145
|
display: flex;
|
|
143
146
|
align-items: stretch;
|
|
147
|
+
height: 100%;
|
|
144
148
|
border-radius: var(--input-button-radius, 6px);
|
|
145
149
|
border: var(--input-button-border, 1px solid currentColor);
|
|
146
150
|
box-shadow: var(--input-button-box-shadow, none);
|
|
@@ -151,7 +155,11 @@
|
|
|
151
155
|
border: var(--input-button-focus-border);
|
|
152
156
|
}
|
|
153
157
|
.input {
|
|
158
|
+
--input-container-width: 100%;
|
|
159
|
+
--input-width: 100%;
|
|
160
|
+
--input-height: 100%;
|
|
154
161
|
flex: 2;
|
|
162
|
+
display: flex;
|
|
155
163
|
min-width: 0px;
|
|
156
164
|
}
|
|
157
165
|
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { LoaderProperties } from './properties';
|
|
3
3
|
|
|
4
|
-
let { testId, classes }: LoaderProperties = $props();
|
|
4
|
+
let { label, testId, classes }: LoaderProperties = $props();
|
|
5
|
+
|
|
6
|
+
let announced = $derived(typeof label === 'string' && label.length > 0);
|
|
5
7
|
</script>
|
|
6
8
|
|
|
7
|
-
<div class="loader {classes ?? ''}" data-pw={testId}
|
|
9
|
+
<div class="loader {classes ?? ''}" role={announced ? 'status' : null} data-pw={testId}>
|
|
10
|
+
{#if announced}
|
|
11
|
+
<span class="loader-label">{label}</span>
|
|
12
|
+
{/if}
|
|
13
|
+
</div>
|
|
8
14
|
|
|
9
15
|
<style>
|
|
10
16
|
.loader {
|
|
@@ -40,8 +46,8 @@
|
|
|
40
46
|
var(--loader-foreground-end, transparent) 42%
|
|
41
47
|
);
|
|
42
48
|
position: relative;
|
|
43
|
-
-webkit-animation: load3 1.4s infinite linear;
|
|
44
|
-
animation: load3 1.4s infinite linear;
|
|
49
|
+
-webkit-animation: load3 var(--loader-duration, 1.4s) infinite linear;
|
|
50
|
+
animation: load3 var(--loader-duration, 1.4s) infinite linear;
|
|
45
51
|
-webkit-transform: translateZ(0);
|
|
46
52
|
-ms-transform: translateZ(0);
|
|
47
53
|
transform: translateZ(0);
|
|
@@ -67,6 +73,16 @@
|
|
|
67
73
|
left: var(--loader-after-left, 50%);
|
|
68
74
|
transform: translate(-50%, -50%);
|
|
69
75
|
}
|
|
76
|
+
|
|
77
|
+
.loader-label {
|
|
78
|
+
position: absolute;
|
|
79
|
+
width: 1px;
|
|
80
|
+
height: 1px;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
clip-path: inset(50%);
|
|
83
|
+
white-space: nowrap;
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
@-webkit-keyframes load3 {
|
|
71
87
|
0% {
|
|
72
88
|
-webkit-transform: rotate(0deg);
|
|
@@ -88,13 +88,13 @@
|
|
|
88
88
|
isImage
|
|
89
89
|
};
|
|
90
90
|
items = [...items, item];
|
|
91
|
+
const updatedItem = items[items.length - 1];
|
|
91
92
|
|
|
92
93
|
if (isImage) {
|
|
93
94
|
const reader = new FileReader();
|
|
94
95
|
reader.onload = (event) => {
|
|
95
96
|
if (typeof event.target?.result === 'string') {
|
|
96
|
-
|
|
97
|
-
items = [...items];
|
|
97
|
+
updatedItem.src = event.target.result;
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
reader.readAsDataURL(file);
|
package/dist/Modal/Modal.svelte
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
import { onMount, onDestroy, untrack } from 'svelte';
|
|
4
4
|
import ModalAnimation from '../Animations/ModalAnimation.svelte';
|
|
5
5
|
import OverlayAnimation from '../Animations/OverlayAnimation.svelte';
|
|
6
|
-
import { createDebouncer } from '../utils';
|
|
6
|
+
import { createDebouncer, lockDocumentScroll } from '../utils';
|
|
7
7
|
import Button from '../Button/Button.svelte';
|
|
8
8
|
|
|
9
9
|
let overlayDiv: HTMLDivElement | null = $state(null);
|
|
10
10
|
let backPressed = false;
|
|
11
11
|
let dismissTimer: ReturnType<typeof setTimeout> | null = null;
|
|
12
|
+
let unlockScroll: (() => void) | null = null;
|
|
12
13
|
|
|
13
14
|
let {
|
|
14
15
|
size = 'fit-content',
|
|
@@ -79,7 +80,7 @@
|
|
|
79
80
|
|
|
80
81
|
onMount(() => {
|
|
81
82
|
if (lockScroll) {
|
|
82
|
-
|
|
83
|
+
unlockScroll = lockDocumentScroll();
|
|
83
84
|
}
|
|
84
85
|
if (typeof autoDismissAfter === 'number') {
|
|
85
86
|
dismissTimer = setTimeout(() => onclose?.(), autoDismissAfter);
|
|
@@ -95,9 +96,7 @@
|
|
|
95
96
|
clearTimeout(dismissTimer);
|
|
96
97
|
}
|
|
97
98
|
if (typeof window !== 'undefined') {
|
|
98
|
-
|
|
99
|
-
document.body.style.overflow = '';
|
|
100
|
-
}
|
|
99
|
+
unlockScroll?.();
|
|
101
100
|
if (supportHardwareBackPress) {
|
|
102
101
|
if (!backPressed) {
|
|
103
102
|
history.back();
|
|
@@ -221,6 +220,7 @@
|
|
|
221
220
|
|
|
222
221
|
.slot-content {
|
|
223
222
|
display: var(--modal-display, flex);
|
|
223
|
+
flex-direction: var(--modal-flex-direction, row);
|
|
224
224
|
overflow-y: var(--modal-overflow-y, scroll);
|
|
225
225
|
scrollbar-width: var(--modal-scrollbar-width, none);
|
|
226
226
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import type { NumberStepperProperties } from './properties';
|
|
4
|
+
import Button from '../Button/Button.svelte';
|
|
5
|
+
import Loader from '../Loader/Loader.svelte';
|
|
6
|
+
import minusSvg from '../assets/minus.svg?raw';
|
|
7
|
+
import addSvg from '../assets/add.svg?raw';
|
|
8
|
+
import { deepActiveElement } from '../utils';
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
value,
|
|
12
|
+
min = 0,
|
|
13
|
+
max = Number.POSITIVE_INFINITY,
|
|
14
|
+
step = 1,
|
|
15
|
+
disabled = false,
|
|
16
|
+
ariaLabel = 'Quantity',
|
|
17
|
+
decrementLabel = 'Decrease',
|
|
18
|
+
incrementLabel = 'Increase',
|
|
19
|
+
loadingLabel = 'Updating',
|
|
20
|
+
decrementIcon,
|
|
21
|
+
incrementIcon,
|
|
22
|
+
testId,
|
|
23
|
+
onchange,
|
|
24
|
+
classes
|
|
25
|
+
}: NumberStepperProperties = $props();
|
|
26
|
+
|
|
27
|
+
let pending = $state(false);
|
|
28
|
+
let decrementButton: ReturnType<typeof Button> | null = $state(null);
|
|
29
|
+
let incrementButton: ReturnType<typeof Button> | null = $state(null);
|
|
30
|
+
let canDecrement = $derived(!disabled && value - step >= min);
|
|
31
|
+
let canIncrement = $derived(!disabled && value + step <= max);
|
|
32
|
+
|
|
33
|
+
async function change(next: number, pressed: ReturnType<typeof Button> | null): Promise<void> {
|
|
34
|
+
if (pending) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const pressedElement = pressed?.getButtonRef() ?? null;
|
|
38
|
+
const hadFocus = pressedElement !== null && deepActiveElement() === pressedElement;
|
|
39
|
+
|
|
40
|
+
const result = onchange?.(next);
|
|
41
|
+
if (result instanceof Promise) {
|
|
42
|
+
pending = true;
|
|
43
|
+
try {
|
|
44
|
+
await result;
|
|
45
|
+
} finally {
|
|
46
|
+
pending = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await tick();
|
|
51
|
+
const other = (pressed === incrementButton ? decrementButton : incrementButton)?.getButtonRef();
|
|
52
|
+
if (hadFocus && pressedElement.disabled && other instanceof HTMLElement && !other.disabled) {
|
|
53
|
+
other.focus();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
class="number-stepper {classes ?? ''}"
|
|
60
|
+
class:pending
|
|
61
|
+
role="group"
|
|
62
|
+
aria-label={ariaLabel}
|
|
63
|
+
aria-busy={pending}
|
|
64
|
+
data-pw={testId}
|
|
65
|
+
>
|
|
66
|
+
<div class="number-stepper-button">
|
|
67
|
+
<Button
|
|
68
|
+
bind:this={decrementButton}
|
|
69
|
+
ariaLabel={decrementLabel}
|
|
70
|
+
disabled={!canDecrement}
|
|
71
|
+
onclick={() => change(value - step, decrementButton)}
|
|
72
|
+
{...typeof testId === 'string' ? { testId: `${testId}-decrement` } : {}}
|
|
73
|
+
>
|
|
74
|
+
{#if typeof decrementIcon === 'function'}
|
|
75
|
+
{@render decrementIcon(value)}
|
|
76
|
+
{:else}
|
|
77
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
78
|
+
{@html minusSvg}
|
|
79
|
+
{/if}
|
|
80
|
+
</Button>
|
|
81
|
+
</div>
|
|
82
|
+
<span class="number-stepper-value" aria-live="polite">
|
|
83
|
+
{#if pending}
|
|
84
|
+
<Loader label={loadingLabel} />
|
|
85
|
+
{:else}
|
|
86
|
+
{value}
|
|
87
|
+
{/if}
|
|
88
|
+
</span>
|
|
89
|
+
<div class="number-stepper-button">
|
|
90
|
+
<Button
|
|
91
|
+
bind:this={incrementButton}
|
|
92
|
+
ariaLabel={incrementLabel}
|
|
93
|
+
disabled={!canIncrement}
|
|
94
|
+
onclick={() => change(value + step, incrementButton)}
|
|
95
|
+
{...typeof testId === 'string' ? { testId: `${testId}-increment` } : {}}
|
|
96
|
+
>
|
|
97
|
+
{#if typeof incrementIcon === 'function'}
|
|
98
|
+
{@render incrementIcon(value)}
|
|
99
|
+
{:else}
|
|
100
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
101
|
+
{@html addSvg}
|
|
102
|
+
{/if}
|
|
103
|
+
</Button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<style>
|
|
108
|
+
.number-stepper {
|
|
109
|
+
display: inline-flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
gap: var(--number-stepper-gap, 0);
|
|
112
|
+
padding: var(--number-stepper-padding, 0);
|
|
113
|
+
border: var(--number-stepper-border, 1px solid currentColor);
|
|
114
|
+
border-radius: var(--number-stepper-border-radius, 6px);
|
|
115
|
+
background: var(--number-stepper-background, transparent);
|
|
116
|
+
color: var(--number-stepper-color, currentColor);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.number-stepper-button {
|
|
120
|
+
--button-color: var(--number-stepper-button-background, transparent);
|
|
121
|
+
--button-text-color: var(--number-stepper-button-color, currentColor);
|
|
122
|
+
--button-hover-color: var(--number-stepper-button-hover-background, transparent);
|
|
123
|
+
--button-padding: var(--number-stepper-button-padding, 6px);
|
|
124
|
+
--button-border: none;
|
|
125
|
+
--button-border-radius: var(--number-stepper-button-border-radius, 6px);
|
|
126
|
+
--button-disabled-opacity: var(--number-stepper-button-disabled-opacity, 0.4);
|
|
127
|
+
display: flex;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.pending .number-stepper-button {
|
|
131
|
+
--button-cursor: var(--number-stepper-pending-cursor, progress);
|
|
132
|
+
opacity: var(--number-stepper-button-disabled-opacity, 0.4);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.number-stepper-button :global(svg) {
|
|
136
|
+
width: var(--number-stepper-icon-size, 14px);
|
|
137
|
+
height: var(--number-stepper-icon-size, 14px);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.number-stepper-value {
|
|
141
|
+
--loader-width: var(--number-stepper-loader-size, 14px);
|
|
142
|
+
--loader-height: var(--number-stepper-loader-size, 14px);
|
|
143
|
+
--loader-before-width: calc(var(--number-stepper-loader-size, 14px) / 2);
|
|
144
|
+
--loader-before-height: calc(var(--number-stepper-loader-size, 14px) / 2);
|
|
145
|
+
--loader-after-width: calc(var(--number-stepper-loader-size, 14px) * 0.75);
|
|
146
|
+
--loader-after-height: calc(var(--number-stepper-loader-size, 14px) * 0.75);
|
|
147
|
+
display: flex;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
min-width: var(--number-stepper-value-min-width, 24px);
|
|
150
|
+
font-size: var(--number-stepper-value-font-size, 14px);
|
|
151
|
+
font-weight: var(--number-stepper-value-font-weight, 500);
|
|
152
|
+
font-variant-numeric: var(--number-stepper-value-font-variant-numeric, tabular-nums);
|
|
153
|
+
}
|
|
154
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type NumberStepperProperties = MandatoryNumberStepperProperties & OptionalNumberStepperProperties & NumberStepperEventProperties;
|
|
3
|
+
export type MandatoryNumberStepperProperties = {
|
|
4
|
+
value: number;
|
|
5
|
+
};
|
|
6
|
+
export type OptionalNumberStepperProperties = {
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
step?: number;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
ariaLabel?: string;
|
|
12
|
+
decrementLabel?: string;
|
|
13
|
+
incrementLabel?: string;
|
|
14
|
+
loadingLabel?: string;
|
|
15
|
+
decrementIcon?: Snippet<[number]>;
|
|
16
|
+
incrementIcon?: Snippet<[number]>;
|
|
17
|
+
testId?: string;
|
|
18
|
+
classes?: string;
|
|
19
|
+
};
|
|
20
|
+
export type NumberStepperEventProperties = {
|
|
21
|
+
onchange?: ((value: number) => void) | ((value: number) => Promise<void>);
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/Pill/Pill.svelte
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
let {
|
|
7
7
|
text,
|
|
8
8
|
dismissible = false,
|
|
9
|
+
dismissLabel = 'Dismiss',
|
|
9
10
|
disabled = false,
|
|
10
11
|
testId,
|
|
11
12
|
dismissIcon,
|
|
@@ -58,7 +59,7 @@
|
|
|
58
59
|
<Button
|
|
59
60
|
{disabled}
|
|
60
61
|
onclick={handleDismiss}
|
|
61
|
-
ariaLabel=
|
|
62
|
+
ariaLabel={dismissLabel}
|
|
62
63
|
{...typeof testId === 'string' ? { testId: `${testId}-dismiss` } : {}}
|
|
63
64
|
>
|
|
64
65
|
{#if typeof dismissIcon === 'function'}
|