polymorph-ui-components 0.7.0 → 0.9.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 +4 -3
- 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 +10 -10
- package/dist/Gallery/Gallery.svelte.d.ts +1 -1
- 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/Modal/Modal.svelte +4 -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/Select/Select.svelte +11 -1
- package/dist/Select/properties.d.ts +1 -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 +4085 -3323
- package/package.json +1 -1
|
@@ -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,
|
|
@@ -54,16 +55,17 @@
|
|
|
54
55
|
let showItemActions = $derived(showEditButton || showDeleteButton);
|
|
55
56
|
|
|
56
57
|
function lightboxAction(node: HTMLElement) {
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const active = deepActiveElement();
|
|
59
|
+
if (openerElement === null && active instanceof HTMLElement) {
|
|
60
|
+
openerElement = active;
|
|
59
61
|
}
|
|
60
|
-
|
|
62
|
+
const unlockScroll = lockDocumentScroll();
|
|
61
63
|
tick().then(() => {
|
|
62
64
|
node.focus();
|
|
63
65
|
});
|
|
64
66
|
return {
|
|
65
67
|
destroy() {
|
|
66
|
-
|
|
68
|
+
unlockScroll();
|
|
67
69
|
if (openerElement !== null) {
|
|
68
70
|
openerElement.focus();
|
|
69
71
|
openerElement = null;
|
|
@@ -93,7 +95,7 @@
|
|
|
93
95
|
activeIndex = index;
|
|
94
96
|
onchange?.(activeIndex);
|
|
95
97
|
await tick();
|
|
96
|
-
if (lightboxDiv !== null && !lightboxDiv.contains(
|
|
98
|
+
if (lightboxDiv !== null && !lightboxDiv.contains(activeElementOf(lightboxDiv))) {
|
|
97
99
|
lightboxDiv.focus();
|
|
98
100
|
}
|
|
99
101
|
}
|
|
@@ -135,13 +137,11 @@
|
|
|
135
137
|
if (first === null || last === null) {
|
|
136
138
|
return;
|
|
137
139
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
(document.activeElement === first || document.activeElement === lightboxDiv)
|
|
141
|
-
) {
|
|
140
|
+
const active = activeElementOf(first);
|
|
141
|
+
if (event.shiftKey && (active === first || active === lightboxDiv)) {
|
|
142
142
|
event.preventDefault();
|
|
143
143
|
last.focus();
|
|
144
|
-
} else if (!event.shiftKey &&
|
|
144
|
+
} else if (!event.shiftKey && active === last) {
|
|
145
145
|
event.preventDefault();
|
|
146
146
|
first.focus();
|
|
147
147
|
}
|
|
@@ -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);
|
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();
|
|
@@ -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'}
|
|
@@ -1,19 +1,88 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ProgressProperties } from './properties';
|
|
3
3
|
|
|
4
|
-
let {
|
|
4
|
+
let {
|
|
5
|
+
value,
|
|
6
|
+
max = 100,
|
|
7
|
+
showLabel = false,
|
|
8
|
+
ariaLabel,
|
|
9
|
+
valueText,
|
|
10
|
+
milestones = [],
|
|
11
|
+
milestone,
|
|
12
|
+
testId,
|
|
13
|
+
classes
|
|
14
|
+
}: ProgressProperties = $props();
|
|
5
15
|
|
|
6
|
-
let
|
|
16
|
+
let clampedValue = $derived(Math.min(max, Math.max(0, value)));
|
|
17
|
+
let percentage = $derived(max > 0 ? (clampedValue / max) * 100 : 0);
|
|
7
18
|
let isIndeterminate = $derived(value < 0);
|
|
19
|
+
|
|
20
|
+
let stops = $derived.by(() => {
|
|
21
|
+
let previous = 0;
|
|
22
|
+
return [...milestones]
|
|
23
|
+
.sort((a, b) => a.value - b.value)
|
|
24
|
+
.map((stop) => {
|
|
25
|
+
const position = Math.min(max, Math.max(0, stop.value));
|
|
26
|
+
const span = position - previous;
|
|
27
|
+
previous = position;
|
|
28
|
+
return { stop, span, reached: !isIndeterminate && value >= stop.value };
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
let trailingSpan = $derived(max - Math.min(max, Math.max(0, stops.at(-1)?.stop.value ?? max)));
|
|
32
|
+
let hasMilestoneLabels = $derived(
|
|
33
|
+
stops.some(({ stop }) => typeof stop.label === 'string' && stop.label.length > 0)
|
|
34
|
+
);
|
|
8
35
|
</script>
|
|
9
36
|
|
|
10
37
|
<div class="container {classes ?? ''}" data-pw={testId}>
|
|
11
|
-
<div class="
|
|
12
|
-
<div
|
|
13
|
-
class="
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
38
|
+
<div class="meter">
|
|
39
|
+
<div class="rail" class:with-milestones={stops.length > 0}>
|
|
40
|
+
<div class="track-layer">
|
|
41
|
+
<div
|
|
42
|
+
class="track"
|
|
43
|
+
role="progressbar"
|
|
44
|
+
aria-label={ariaLabel ?? 'Progress'}
|
|
45
|
+
aria-valuemin={0}
|
|
46
|
+
aria-valuemax={max}
|
|
47
|
+
aria-valuenow={isIndeterminate ? null : clampedValue}
|
|
48
|
+
aria-valuetext={valueText}
|
|
49
|
+
>
|
|
50
|
+
<div
|
|
51
|
+
class="bar"
|
|
52
|
+
class:indeterminate={isIndeterminate}
|
|
53
|
+
style:width={isIndeterminate ? null : `${percentage}%`}
|
|
54
|
+
></div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
{#if stops.length > 0}
|
|
58
|
+
<div class="milestones">
|
|
59
|
+
{#each stops as { stop, span, reached }, index (index)}
|
|
60
|
+
<div class="segment" style:flex-grow={span}>
|
|
61
|
+
<div class="milestone" class:reached>
|
|
62
|
+
{#if typeof milestone === 'function'}
|
|
63
|
+
{@render milestone(stop, reached)}
|
|
64
|
+
{:else}
|
|
65
|
+
<span class="milestone-marker"></span>
|
|
66
|
+
{/if}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
{/each}
|
|
70
|
+
{#if trailingSpan > 0}
|
|
71
|
+
<div class="segment" style:flex-grow={trailingSpan}></div>
|
|
72
|
+
{/if}
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
75
|
+
</div>
|
|
76
|
+
{#if hasMilestoneLabels}
|
|
77
|
+
<div class="milestone-labels">
|
|
78
|
+
{#each stops as { stop, span }, index (index)}
|
|
79
|
+
<span class="segment milestone-label" style:flex-grow={span}>{stop.label ?? ''}</span>
|
|
80
|
+
{/each}
|
|
81
|
+
{#if trailingSpan > 0}
|
|
82
|
+
<span class="segment" style:flex-grow={trailingSpan}></span>
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
|
85
|
+
{/if}
|
|
17
86
|
</div>
|
|
18
87
|
{#if showLabel && !isIndeterminate}
|
|
19
88
|
<div class="label">{Math.round(percentage)}%</div>
|
|
@@ -29,6 +98,32 @@
|
|
|
29
98
|
gap: var(--progress-container-gap, 8px);
|
|
30
99
|
}
|
|
31
100
|
|
|
101
|
+
.meter {
|
|
102
|
+
flex: 1;
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
gap: var(--progress-milestone-label-gap, 8px);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.rail {
|
|
109
|
+
position: relative;
|
|
110
|
+
display: flex;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.track-layer {
|
|
114
|
+
flex: 1;
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.with-milestones .track-layer {
|
|
120
|
+
position: absolute;
|
|
121
|
+
top: 0;
|
|
122
|
+
right: 0;
|
|
123
|
+
bottom: 0;
|
|
124
|
+
left: 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
32
127
|
.track {
|
|
33
128
|
flex: 1;
|
|
34
129
|
height: var(--progress-track-height, 8px);
|
|
@@ -49,6 +144,49 @@
|
|
|
49
144
|
animation: indeterminate var(--progress-indeterminate-duration, 1.5s) ease-in-out infinite;
|
|
50
145
|
}
|
|
51
146
|
|
|
147
|
+
.milestones {
|
|
148
|
+
flex: 1;
|
|
149
|
+
display: flex;
|
|
150
|
+
position: relative;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.segment {
|
|
154
|
+
flex-basis: 0;
|
|
155
|
+
flex-shrink: 1;
|
|
156
|
+
min-width: 0;
|
|
157
|
+
display: flex;
|
|
158
|
+
justify-content: flex-end;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.milestone {
|
|
162
|
+
display: flex;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.milestone-marker {
|
|
166
|
+
width: var(--progress-milestone-size, 12px);
|
|
167
|
+
height: var(--progress-milestone-size, 12px);
|
|
168
|
+
border: var(--progress-milestone-border, 2px solid #ffffff);
|
|
169
|
+
border-radius: var(--progress-milestone-border-radius, 50%);
|
|
170
|
+
background: var(--progress-milestone-background, #e4e4e7);
|
|
171
|
+
box-sizing: border-box;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.reached .milestone-marker {
|
|
175
|
+
background: var(--progress-milestone-reached-background, currentColor);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.milestone-labels {
|
|
179
|
+
display: flex;
|
|
180
|
+
font-size: var(--progress-milestone-label-font-size, 12px);
|
|
181
|
+
font-weight: var(--progress-milestone-label-font-weight, 400);
|
|
182
|
+
color: var(--progress-milestone-label-color, currentColor);
|
|
183
|
+
font-family: var(--progress-milestone-label-font-family, inherit);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.milestone-label {
|
|
187
|
+
text-align: var(--progress-milestone-label-text-align, end);
|
|
188
|
+
}
|
|
189
|
+
|
|
52
190
|
.label {
|
|
53
191
|
font-size: var(--progress-label-font-size, 14px);
|
|
54
192
|
font-weight: var(--progress-label-font-weight, 500);
|