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 CHANGED
@@ -138,8 +138,15 @@ Each component documents its **complete** variable surface in [`docs/`](docs/).
138
138
 
139
139
  The same components compile to **framework-agnostic custom elements** — theming works identically because it's pure CSS.
140
140
 
141
+ ### From a CDN — no build step, no install
142
+
143
+ The bundle is self-contained (the Svelte runtime is compiled in) and registers every `<pui-*>` element on import, so a single `<script>` tag in a plain `.html` file is enough:
144
+
141
145
  ```html
142
- <script type="module" src="polymorph-ui-components/wc"></script>
146
+ <script
147
+ type="module"
148
+ src="https://cdn.jsdelivr.net/npm/polymorph-ui-components@latest/dist-wc/index.js"
149
+ ></script>
143
150
 
144
151
  <pui-button text="Save"></pui-button>
145
152
  <pui-input placeholder="Search…"></pui-input>
@@ -152,7 +159,24 @@ The same components compile to **framework-agnostic custom elements** — themin
152
159
  </style>
153
160
  ```
154
161
 
155
- Drop them into React, Vue, Angular, Astro, or a plain `.html` file — no build step required.
162
+ Pin an exact version for production — `…/polymorph-ui-components@<version>/dist-wc/index.js` — so a release can never change your page underneath you. [unpkg](https://unpkg.com) serves the same path if you prefer it:
163
+
164
+ ```
165
+ https://cdn.jsdelivr.net/npm/polymorph-ui-components@<version>/dist-wc/index.js
166
+ https://unpkg.com/polymorph-ui-components@<version>/dist-wc/index.js
167
+ ```
168
+
169
+ ### From npm, through a bundler
170
+
171
+ ```bash
172
+ npm install polymorph-ui-components
173
+ ```
174
+
175
+ ```js
176
+ import 'polymorph-ui-components/wc';
177
+ ```
178
+
179
+ Either way, drop them into React, Vue, Angular, Astro, or a plain `.html` file.
156
180
 
157
181
  ---
158
182
 
@@ -200,6 +224,7 @@ Now you can ask: _"Using the polymorph-ui MCP, theme the Select to match my bran
200
224
  | **SplitInput** | Segmented input (OTP / PIN) with paste distribution and auto-advance. | [docs](docs/SplitInput.md) |
201
225
  | **ColorPicker** | HSV color picker with pointer-drag saturation panel and hue slider. | [docs](docs/ColorPicker.md) |
202
226
  | **Calendar** | Date / range picker with roving-tabindex grid and `Intl` formatting. | [docs](docs/Calendar.md) |
227
+ | **NumberStepper** | − value + control for quantities; shows a loader while an async `onchange` settles. | [docs](docs/NumberStepper.md) |
203
228
 
204
229
  </details>
205
230
 
@@ -209,7 +234,7 @@ Now you can ask: _"Using the polymorph-ui MCP, theme the Select to match my bran
209
234
  | Component | Description | |
210
235
  | ----------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------ |
211
236
  | **Modal** | Dialog overlay with size/alignment, header, footer, transitions, scroll-lock, and back-press support. | [docs](docs/Modal.md) |
212
- | **Sheet** | Slide-in panel from any edge with header, scrollable body, footer, and focus trap. | [docs](docs/Sheet.md) |
237
+ | **Sheet** | Slide-in panel from any edge with header, scrollable body, footer, focus trap and focus return. | [docs](docs/Sheet.md) |
213
238
  | **Menu** | Dropdown action menu with keyboard navigation, typeahead, disabled/danger items, and separators. | [docs](docs/Menu.md) |
214
239
  | **ContextMenu** | Right-click menu with separators, disabled/danger items, and keyboard navigation. | [docs](docs/ContextMenu.md) |
215
240
  | **CommandMenu** | Command palette (Ctrl/Cmd+K) with search, grouped commands, and keyboard navigation. | [docs](docs/CommandMenu.md) |
@@ -244,7 +269,7 @@ Now you can ask: _"Using the polymorph-ui MCP, theme the Select to match my bran
244
269
  | **Pagination** | Windowed page navigation with ellipsis truncation. | [docs](docs/Pagination.md) |
245
270
  | **Stepper** / **Step** | Multi-step progress indicator with completed/active/pending states. | [docs](docs/Stepper.md) |
246
271
  | **Accordion** | Collapsible container with CSS grid animation. | [docs](docs/Accordion.md) |
247
- | **Carousel** | Swipeable content slider with autoplay and pagination dots. | [docs](docs/Carousel.md) |
272
+ | **Carousel** | Swipeable slider with snippet or component slides, arrows, dots, seamless looping and autoplay that pauses on hover/focus. | [docs](docs/Carousel.md) |
248
273
  | **Scroller** | Overflowing list with arrow nav, gradient edges, and drag-to-scroll. | [docs](docs/Scroller.md) |
249
274
  | **CheckListItem** | Checklist row with checkbox, label, and checked state. | [docs](docs/CheckListItem.md) |
250
275
  | **Toolbar** | Header bar with back button, title, and customizable content areas. | [docs](docs/Toolbar.md) |
@@ -259,7 +284,7 @@ Now you can ask: _"Using the polymorph-ui MCP, theme the Select to match my bran
259
284
  | ------------------------------------------ | ----------------------------------------------------------------------- | ------------------------ |
260
285
  | **Toast** | Animated slide-in notification with auto-dismiss and per-direction fly. | [docs](docs/Toast.md) |
261
286
  | **Banner** | Notification banner with icon snippet, link text, and dismiss. | [docs](docs/Banner.md) |
262
- | **Progress** | Animated horizontal progress bar (determinate / indeterminate). | [docs](docs/Progress.md) |
287
+ | **Progress** | Animated progress bar (determinate / indeterminate) with optional milestones. | [docs](docs/Progress.md) |
263
288
  | **Gauge** | Full-circle ring gauge (0–100) with animated SVG arc. | [docs](docs/Gauge.md) |
264
289
  | **Loader** / **LoadingDots** / **Shimmer** | Spinner, animated dots, and skeleton shimmer — all CSS-variable themed. | [docs](docs/Loader.md) |
265
290
 
@@ -110,9 +110,9 @@
110
110
  cursor: var(--button-disabled-cursor, not-allowed);
111
111
  opacity: var(--button-disabled-opacity, 0.4);
112
112
  color: var(--button-disabled-text-color, var(--button-text-color, #ffffff));
113
- font-size: var(--button-disabled-font-size);
114
- font-weight: var(--button-disabled-font-weight);
115
- border: var(--button-disabled-border);
113
+ font-size: var(--button-disabled-font-size, var(--button-font-size, 14px));
114
+ font-weight: var(--button-disabled-font-weight, var(--button-font-weight, 500));
115
+ border: var(--button-disabled-border, var(--button-border, none));
116
116
  background: var(--button-disabled-background-color, var(--button-color, #18181b));
117
117
  text-decoration: var(--button-disabled-text-decoration, var(--button-text-decoration, none));
118
118
  }
@@ -131,7 +131,7 @@
131
131
  display: var(--button-text-display);
132
132
  }
133
133
 
134
- button:hover {
134
+ button:hover:not(:disabled) {
135
135
  background: var(--button-hover-color, var(--button-color, #18181b));
136
136
  color: var(--button-hover-text-color, var(--button-text-color, #ffffff));
137
137
  border: var(--button-hover-border, var(--button-border, none));
@@ -139,7 +139,7 @@
139
139
  opacity: var(--button-hover-opacity, var(--button-opacity, 1));
140
140
  }
141
141
 
142
- button:active {
142
+ button:active:not(:disabled) {
143
143
  transform: var(--button-active-transform);
144
144
  }
145
145
 
@@ -1,180 +1,298 @@
1
1
  <script lang="ts">
2
- import { onMount, onDestroy } from 'svelte';
2
+ import { flushSync } from 'svelte';
3
3
  import type { CarouselProperties } from './properties';
4
+ import Button from '../Button/Button.svelte';
5
+ import chevronLeftSvg from '../assets/chevron-left.svg?raw';
6
+ import chevronRightSvg from '../assets/chevron-right.svg?raw';
7
+
8
+ type Rotation = { enabled: boolean; interval: number; restarts: number };
4
9
 
5
10
  let {
6
- views,
11
+ views = [],
12
+ count = 0,
13
+ slide,
7
14
  autoplay = false,
8
15
  autoplayInterval = 1000,
9
16
  showDots = false,
17
+ showArrows = false,
10
18
  isScrollableLast = false,
19
+ loop = false,
20
+ announce = true,
21
+ ariaLabel = 'Carousel',
22
+ previousLabel = 'Previous slide',
23
+ nextLabel = 'Next slide',
24
+ previousIcon,
25
+ nextIcon,
11
26
  testId,
12
27
  onkeydown,
13
28
  classes
14
29
  }: CarouselProperties = $props();
15
30
 
16
31
  let slidesDiv: HTMLDivElement | null = $state(null);
17
- let intervalId: number;
18
- let endTouch: number;
19
- let startTouch: number;
20
- let startMouse: number;
21
- let endMouse: number;
22
- let carouselWidth: string;
23
- let carouselDiv: HTMLDivElement | null = $state(null);
24
- let activeSlideIndex = $state(0);
25
- let widthUnits: string;
32
+ let requestedIndex = $state(0);
33
+ let edge = $state<'none' | 'before' | 'after'>('none');
34
+ let hovered = $state(false);
35
+ let focused = $state(false);
36
+ let animate = $state(true);
37
+ let restarts = $state(0);
26
38
 
27
- function handleDotKeydown(event: KeyboardEvent, index: number) {
28
- if (event.key === 'Enter' || event.key === ' ') {
29
- event.preventDefault();
30
- moveSlideToIndex(index);
39
+ let total = $derived(typeof slide === 'function' ? count : views.length);
40
+ let activeSlideIndex = $derived(Math.min(requestedIndex, Math.max(total - 1, 0)));
41
+ let wraps = $derived(isScrollableLast || loop);
42
+ let seamless = $derived(loop && total > 1);
43
+ let trackPosition = $derived.by(() => {
44
+ if (!seamless) {
45
+ return activeSlideIndex;
31
46
  }
32
- onkeydown?.(event);
33
- }
34
-
35
- function nextSlide() {
36
- if (activeSlideIndex != views.length - 1 || isScrollableLast) {
37
- activeSlideIndex++;
38
- changeCurrentSlide();
39
- if (autoplay) {
40
- resetInterval();
41
- }
47
+ if (edge === 'before') {
48
+ return 0;
42
49
  }
50
+ if (edge === 'after') {
51
+ return total + 1;
52
+ }
53
+ return activeSlideIndex + 1;
54
+ });
55
+ let rotating = $derived(autoplay && total > 1 && !hovered && !focused);
56
+
57
+ function hasTransition(): boolean {
58
+ return slidesDiv !== null && parseFloat(getComputedStyle(slidesDiv).transitionDuration) > 0;
43
59
  }
44
60
 
45
- function previousSlide() {
46
- if (activeSlideIndex != 0 || isScrollableLast) {
47
- activeSlideIndex--;
48
- changeCurrentSlide();
49
- if (autoplay) {
50
- resetInterval();
51
- }
61
+ function snapFromClone() {
62
+ animate = false;
63
+ edge = 'none';
64
+ flushSync();
65
+ // Reading layout commits the jump before the transition is switched back on.
66
+ if (slidesDiv !== null) {
67
+ void slidesDiv.offsetWidth;
52
68
  }
69
+ animate = true;
53
70
  }
54
71
 
55
- function changeCurrentSlide() {
56
- if (activeSlideIndex > views.length - 1) {
57
- activeSlideIndex = 0;
58
- } else if (activeSlideIndex < 0) {
59
- activeSlideIndex = views.length - 1;
72
+ function moveTo(target: number) {
73
+ if (edge !== 'none') {
74
+ snapFromClone();
75
+ }
76
+ if (wraps) {
77
+ requestedIndex = ((target % total) + total) % total;
78
+ edge = seamless && target < 0 ? 'before' : seamless && target >= total ? 'after' : 'none';
79
+ } else {
80
+ requestedIndex = Math.min(total - 1, Math.max(0, target));
60
81
  }
61
- if (slidesDiv) {
62
- slidesDiv.style.transform = `translateX(${
63
- -activeSlideIndex * parseInt(carouselWidth)
64
- }${widthUnits})`;
82
+ flushSync();
83
+ if (edge !== 'none' && !hasTransition()) {
84
+ snapFromClone();
65
85
  }
66
86
  }
67
87
 
68
- function moveSlideToIndex(index: number) {
69
- activeSlideIndex = index;
70
- changeCurrentSlide();
88
+ function navigate(target: number) {
89
+ moveTo(target);
90
+ restarts += 1;
71
91
  }
72
92
 
73
- function resetInterval() {
74
- clearInterval(intervalId);
75
- intervalId = window.setInterval(nextSlide, autoplayInterval);
93
+ function handleTransitionEnd(event: TransitionEvent) {
94
+ if (event.target === slidesDiv && event.propertyName === 'transform' && edge !== 'none') {
95
+ snapFromClone();
96
+ }
76
97
  }
77
98
 
78
- function handleTouchStart(event: TouchEvent) {
79
- if (event.touches.length > 0) {
80
- const touch = event.touches.item(0);
81
- if (touch !== null) {
82
- startTouch = touch.clientX;
83
- }
99
+ function handleDotKeydown(event: KeyboardEvent, index: number) {
100
+ if (event.key === 'Enter' || event.key === ' ') {
101
+ event.preventDefault();
102
+ navigate(index);
84
103
  }
104
+ onkeydown?.(event);
85
105
  }
86
106
 
87
- function handleTouchEnd(event: TouchEvent) {
88
- if (event.changedTouches.length > 0) {
89
- const changedTouch = event.changedTouches.item(0);
90
- if (changedTouch !== null) {
91
- endTouch = changedTouch.clientX;
92
- if (startTouch - endTouch > 20) {
93
- nextSlide();
94
- } else {
95
- if (endTouch - startTouch > 20) {
96
- previousSlide();
97
- }
98
- }
99
- }
107
+ function nextSlide() {
108
+ if (activeSlideIndex !== total - 1 || wraps) {
109
+ navigate(activeSlideIndex + 1);
100
110
  }
101
111
  }
102
112
 
103
- function handleMouseDown(event: MouseEvent) {
104
- if (typeof event.clientX !== 'undefined') {
105
- startMouse = event.clientX;
113
+ function previousSlide() {
114
+ if (activeSlideIndex !== 0 || wraps) {
115
+ navigate(activeSlideIndex - 1);
106
116
  }
107
117
  }
108
118
 
109
- function handeMouseUp(event: MouseEvent) {
110
- if (typeof event.clientX !== 'undefined') {
111
- endMouse = event.clientX;
112
- if (startMouse - endMouse > 20) {
113
- nextSlide();
114
- } else {
115
- if (endMouse - startMouse > 20) {
116
- previousSlide();
117
- }
118
- }
119
+ function advance() {
120
+ if (rotating) {
121
+ moveTo(activeSlideIndex + 1);
119
122
  }
120
123
  }
121
124
 
122
- function setWidthUnit(carouselWidth: string) {
123
- widthUnits = carouselWidth.slice(-3);
124
- if (/^-?\d+$/.test(widthUnits.at(0) ?? '')) {
125
- widthUnits = widthUnits.slice(-2);
126
- }
125
+ function rotation(_node: HTMLElement, initial: Rotation) {
126
+ let timer: number | null = null;
127
+ const stop = () => {
128
+ if (timer !== null) {
129
+ clearInterval(timer);
130
+ timer = null;
131
+ }
132
+ };
133
+ const start = (settings: Rotation) => {
134
+ stop();
135
+ if (settings.enabled) {
136
+ timer = window.setInterval(advance, settings.interval);
137
+ }
138
+ };
139
+ start(initial);
140
+ return { update: start, destroy: stop };
127
141
  }
128
142
 
129
- onMount(() => {
130
- if (carouselDiv) {
131
- carouselWidth = getComputedStyle(carouselDiv).getPropertyValue('--carousel-width');
132
- setWidthUnit(carouselWidth);
133
- carouselDiv.addEventListener('touchstart', handleTouchStart);
134
- carouselDiv.addEventListener('touchend', handleTouchEnd);
135
- carouselDiv.addEventListener('mousedown', handleMouseDown);
136
- carouselDiv.addEventListener('mouseup', handeMouseUp);
137
- }
138
- if (autoplay) {
139
- intervalId = window.setInterval(nextSlide, autoplayInterval);
140
- }
141
- });
143
+ function swipe(node: HTMLElement) {
144
+ let startX = 0;
145
+ const finish = (endX: number) => {
146
+ if (startX - endX > 20) {
147
+ nextSlide();
148
+ } else if (endX - startX > 20) {
149
+ previousSlide();
150
+ }
151
+ };
152
+ const touchStart = (event: TouchEvent) => {
153
+ const touch = event.touches.item(0);
154
+ if (touch !== null) {
155
+ startX = touch.clientX;
156
+ }
157
+ };
158
+ const touchEnd = (event: TouchEvent) => {
159
+ const touch = event.changedTouches.item(0);
160
+ if (touch !== null) {
161
+ finish(touch.clientX);
162
+ }
163
+ };
164
+ const mouseDown = (event: MouseEvent) => {
165
+ startX = event.clientX;
166
+ };
167
+ const mouseUp = (event: MouseEvent) => {
168
+ finish(event.clientX);
169
+ };
170
+ node.addEventListener('touchstart', touchStart);
171
+ node.addEventListener('touchend', touchEnd);
172
+ node.addEventListener('mousedown', mouseDown);
173
+ node.addEventListener('mouseup', mouseUp);
174
+ return {
175
+ destroy() {
176
+ node.removeEventListener('touchstart', touchStart);
177
+ node.removeEventListener('touchend', touchEnd);
178
+ node.removeEventListener('mousedown', mouseDown);
179
+ node.removeEventListener('mouseup', mouseUp);
180
+ }
181
+ };
182
+ }
142
183
 
143
- onDestroy(() => {
144
- if (autoplay) {
145
- clearInterval(intervalId);
184
+ function handleFocusOut(event: FocusEvent) {
185
+ const container = event.currentTarget;
186
+ if (
187
+ container instanceof Node &&
188
+ event.relatedTarget instanceof Node &&
189
+ container.contains(event.relatedTarget)
190
+ ) {
191
+ return;
146
192
  }
147
- });
193
+ focused = false;
194
+ }
148
195
  </script>
149
196
 
150
- <div class="carousel-container {classes ?? ''}" data-pw={testId}>
151
- {#if views.length > 0}
152
- <div class="carousel" bind:this={carouselDiv}>
153
- <div class="slidesDiv" bind:this={slidesDiv}>
154
- {#each views as view, index (index)}
155
- <div class="current-slide">
156
- {#if typeof view.properties === 'object'}
157
- <view.component properties={view.properties} />
197
+ {#snippet renderSlide(index: number)}
198
+ {#if typeof slide === 'function'}
199
+ {@render slide(index)}
200
+ {:else}
201
+ {@const view = views.at(index)}
202
+ {#if typeof view === 'object'}
203
+ {#if typeof view.properties === 'object'}
204
+ <view.component properties={view.properties} />
205
+ {:else}
206
+ <view.component />
207
+ {/if}
208
+ {/if}
209
+ {/if}
210
+ {/snippet}
211
+
212
+ <div
213
+ class="carousel-container {classes ?? ''}"
214
+ role="region"
215
+ aria-roledescription="carousel"
216
+ aria-label={ariaLabel}
217
+ data-pw={testId}
218
+ use:rotation={{ enabled: autoplay && total > 1, interval: autoplayInterval, restarts }}
219
+ onmouseenter={() => (hovered = true)}
220
+ onmouseleave={() => (hovered = false)}
221
+ onfocusin={() => (focused = true)}
222
+ onfocusout={handleFocusOut}
223
+ >
224
+ {#if total > 0}
225
+ <div class="carousel-row">
226
+ {#if showArrows && total > 1}
227
+ <div class="carousel-arrow">
228
+ <Button ariaLabel={previousLabel} onclick={previousSlide}>
229
+ {#if typeof previousIcon === 'function'}
230
+ {@render previousIcon()}
158
231
  {:else}
159
- <view.component />
232
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
233
+ {@html chevronLeftSvg}
160
234
  {/if}
161
- </div>
162
- {/each}
235
+ </Button>
236
+ </div>
237
+ {/if}
238
+ <div class="carousel" use:swipe>
239
+ <div
240
+ class="slidesDiv"
241
+ class:animate
242
+ bind:this={slidesDiv}
243
+ style:transform="translateX({-trackPosition * 100}%)"
244
+ aria-live={announce && !rotating ? 'polite' : 'off'}
245
+ ontransitionend={handleTransitionEnd}
246
+ >
247
+ {#if seamless}
248
+ <div class="current-slide" aria-hidden="true" inert>
249
+ {@render renderSlide(total - 1)}
250
+ </div>
251
+ {/if}
252
+ {#each { length: total } as _, index (index)}
253
+ <div
254
+ class="current-slide"
255
+ role="group"
256
+ aria-roledescription="slide"
257
+ aria-label="{index + 1} of {total}"
258
+ aria-hidden={index !== activeSlideIndex}
259
+ inert={index !== activeSlideIndex}
260
+ >
261
+ {@render renderSlide(index)}
262
+ </div>
263
+ {/each}
264
+ {#if seamless}
265
+ <div class="current-slide" aria-hidden="true" inert>
266
+ {@render renderSlide(0)}
267
+ </div>
268
+ {/if}
269
+ </div>
163
270
  </div>
271
+ {#if showArrows && total > 1}
272
+ <div class="carousel-arrow">
273
+ <Button ariaLabel={nextLabel} onclick={nextSlide}>
274
+ {#if typeof nextIcon === 'function'}
275
+ {@render nextIcon()}
276
+ {:else}
277
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
278
+ {@html chevronRightSvg}
279
+ {/if}
280
+ </Button>
281
+ </div>
282
+ {/if}
164
283
  </div>
165
284
  {/if}
166
285
  {#if showDots}
167
286
  <div class="dots-wrapper">
168
- <!-- eslint-disable-next-line -->
169
- {#each views as _, index}
287
+ {#each { length: total } as _, index (index)}
170
288
  <div
171
- class={activeSlideIndex == index ? 'active-dot' : 'dot'}
172
- onclick={() => moveSlideToIndex(index)}
289
+ class={activeSlideIndex === index ? 'active-dot' : 'dot'}
290
+ onclick={() => navigate(index)}
173
291
  onkeydown={(event) => handleDotKeydown(event, index)}
174
292
  role="button"
175
293
  tabindex="0"
176
294
  aria-label={`Go to slide ${index + 1}`}
177
- aria-current={activeSlideIndex == index}
295
+ aria-current={activeSlideIndex === index}
178
296
  ></div>
179
297
  {/each}
180
298
  </div>
@@ -185,15 +303,21 @@
185
303
  .carousel-container {
186
304
  width: var(--carousel-width);
187
305
  }
306
+ .carousel-row {
307
+ display: flex;
308
+ align-items: center;
309
+ gap: var(--carousel-arrow-gap, 8px);
310
+ }
188
311
  .current-slide {
189
- width: var(--carousel-width, 300px);
312
+ width: 100%;
190
313
  height: var(--carousel-height, 100px);
191
314
  flex-shrink: 0;
192
315
  }
193
316
  .carousel {
194
317
  box-shadow: var(--carousel-shadow);
195
318
  height: var(--carousel-height, 100px);
196
- width: var(--carousel-width, 300px);
319
+ flex: 0 1 var(--carousel-width, 300px);
320
+ min-width: 0;
197
321
  overflow: hidden;
198
322
  border-radius: var(--carousel-border-radius, 0%);
199
323
  }
@@ -202,8 +326,24 @@
202
326
  }
203
327
  .slidesDiv {
204
328
  display: flex;
205
- transform: translateX(0);
206
- transition: transform 0.5s ease-in-out;
329
+ }
330
+ .slidesDiv.animate {
331
+ transition: transform var(--carousel-transition-duration, 0.5s)
332
+ var(--carousel-transition-easing, ease-in-out);
333
+ }
334
+ .carousel-arrow {
335
+ --button-color: var(--carousel-arrow-background, transparent);
336
+ --button-text-color: var(--carousel-arrow-color, currentColor);
337
+ --button-hover-color: var(--carousel-arrow-hover-background, transparent);
338
+ --button-padding: var(--carousel-arrow-padding, 4px);
339
+ --button-border: var(--carousel-arrow-border, none);
340
+ --button-border-radius: var(--carousel-arrow-border-radius, 999px);
341
+ display: flex;
342
+ flex-shrink: 0;
343
+ }
344
+ .carousel-arrow :global(svg) {
345
+ width: var(--carousel-arrow-size, 16px);
346
+ height: var(--carousel-arrow-size, 16px);
207
347
  }
208
348
  .dots-wrapper {
209
349
  gap: var(--carousel-dot-gap, 10px);
@@ -228,31 +368,12 @@
228
368
  background: var(--carousel-active-dot-color, currentColor);
229
369
  transition: 0.3s ease;
230
370
  }
231
- /*
232
- @media only screen and (max-width: 324px) {
233
371
 
234
- .carousel-container {
235
- width:270px;
236
-
237
- }
238
- .current-slide {
239
- width: 270px;
240
- }
241
- .carousel {
242
- width:270px;
372
+ @media (prefers-reduced-motion: reduce) {
373
+ .slidesDiv.animate,
374
+ .dot,
375
+ .active-dot {
376
+ transition-duration: 0s;
243
377
  }
244
378
  }
245
-
246
- @media only screen and (max-width: 269px) {
247
- .carousel-container {
248
- width:240px;
249
- }
250
- .current-slide {
251
- width: 240px;
252
- }
253
- .carousel {
254
- width:240px;
255
- }
256
-
257
- } */
258
379
  </style>
@@ -1,17 +1,29 @@
1
- import type { Component } from 'svelte';
1
+ import type { Component, Snippet } from 'svelte';
2
2
  export type CarouselView = {
3
3
  properties?: Record<string, unknown>;
4
4
  component: Component<Record<string, unknown>>;
5
5
  };
6
- export type CarouselProperties = CarouselEventProperties & OptionalCarouselProperties & MandatoryCarouselProperties;
6
+ export type CarouselProperties = CarouselEventProperties & OptionalCarouselProperties;
7
+ /** @deprecated `views` is optional now that slides can also come from the `slide` snippet. */
7
8
  export type MandatoryCarouselProperties = {
8
9
  views: CarouselView[];
9
10
  };
10
11
  export type OptionalCarouselProperties = {
12
+ views?: CarouselView[];
13
+ count?: number;
14
+ slide?: Snippet<[number]>;
11
15
  autoplay?: boolean;
12
16
  autoplayInterval?: number;
13
17
  showDots?: boolean;
18
+ showArrows?: boolean;
14
19
  isScrollableLast?: boolean;
20
+ loop?: boolean;
21
+ announce?: boolean;
22
+ ariaLabel?: string;
23
+ previousLabel?: string;
24
+ nextLabel?: string;
25
+ previousIcon?: Snippet;
26
+ nextIcon?: Snippet;
15
27
  testId?: string;
16
28
  classes?: string;
17
29
  };
@@ -1,6 +1,6 @@
1
1
  import type { ComboboxProperties } from './properties';
2
2
  declare const Combobox: import("svelte").Component<ComboboxProperties, {
3
3
  getInputRef: () => HTMLInputElement | HTMLTextAreaElement | null;
4
- }, "value" | "open" | "inputValue" | "highlightedIndex">;
4
+ }, "open" | "value" | "inputValue" | "highlightedIndex">;
5
5
  type Combobox = ReturnType<typeof Combobox>;
6
6
  export default Combobox;