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.
@@ -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
- document.body.style.overflow = 'hidden';
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
- document.body.style.overflow = '';
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
- if (openerElement === null && document.activeElement instanceof HTMLElement) {
56
- openerElement = document.activeElement;
58
+ const active = deepActiveElement();
59
+ if (openerElement === null && active instanceof HTMLElement) {
60
+ openerElement = active;
57
61
  }
58
- document.body.style.overflow = 'hidden';
62
+ const unlockScroll = lockDocumentScroll();
59
63
  tick().then(() => {
60
64
  node.focus();
61
65
  });
62
66
  return {
63
67
  destroy() {
64
- document.body.style.overflow = '';
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(document.activeElement)) {
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
- if (
137
- event.shiftKey &&
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 && document.activeElement === last) {
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
- <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}
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 class="gallery {view} {classes ?? ''}" data-pw={testId} role="list">
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: repeat(var(--gallery-columns, 3), 1fr);
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, {}, "activeIndex" | "open">;
2
+ declare const Gallery: import("svelte").Component<GalleryProperties, {}, "open" | "activeIndex">;
3
3
  type Gallery = ReturnType<typeof Gallery>;
4
4
  export default Gallery;
@@ -24,6 +24,7 @@ export type OptionalGalleryProperties = {
24
24
  closeIcon?: Snippet;
25
25
  editIcon?: Snippet;
26
26
  deleteIcon?: Snippet;
27
+ itemFooter?: Snippet<[GalleryImage, number]>;
27
28
  testId?: string;
28
29
  classes?: string;
29
30
  };
@@ -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 !== document.activeElement
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={name}>
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={inputProperties.name}>
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 !== '' && validationState === 'Invalid'}
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}></div>
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);
@@ -1,4 +1,5 @@
1
1
  export type LoaderProperties = {
2
+ label?: string;
2
3
  testId?: string;
3
4
  classes?: string;
4
5
  };
@@ -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
- item.src = event.target.result;
97
- items = [...items];
97
+ updatedItem.src = event.target.result;
98
98
  }
99
99
  };
100
100
  reader.readAsDataURL(file);
@@ -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
- document.body.style.overflow = 'hidden';
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
- if (lockScroll) {
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,4 @@
1
+ import type { NumberStepperProperties } from './properties';
2
+ declare const NumberStepper: import("svelte").Component<NumberStepperProperties, {}, "">;
3
+ type NumberStepper = ReturnType<typeof NumberStepper>;
4
+ export default NumberStepper;
@@ -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 {};
@@ -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="Dismiss"
62
+ ariaLabel={dismissLabel}
62
63
  {...typeof testId === 'string' ? { testId: `${testId}-dismiss` } : {}}
63
64
  >
64
65
  {#if typeof dismissIcon === 'function'}