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
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
18
|
-
let
|
|
19
|
-
let
|
|
20
|
-
let
|
|
21
|
-
let
|
|
22
|
-
let
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}${widthUnits})`;
|
|
82
|
+
flushSync();
|
|
83
|
+
if (edge !== 'none' && !hasTransition()) {
|
|
84
|
+
snapFromClone();
|
|
65
85
|
}
|
|
66
86
|
}
|
|
67
87
|
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
88
|
+
function navigate(target: number) {
|
|
89
|
+
moveTo(target);
|
|
90
|
+
restarts += 1;
|
|
71
91
|
}
|
|
72
92
|
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
function handleTransitionEnd(event: TransitionEvent) {
|
|
94
|
+
if (event.target === slidesDiv && event.propertyName === 'transform' && edge !== 'none') {
|
|
95
|
+
snapFromClone();
|
|
96
|
+
}
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
function
|
|
79
|
-
if (event.
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
88
|
-
if (
|
|
89
|
-
|
|
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
|
|
104
|
-
if (
|
|
105
|
-
|
|
113
|
+
function previousSlide() {
|
|
114
|
+
if (activeSlideIndex !== 0 || wraps) {
|
|
115
|
+
navigate(activeSlideIndex - 1);
|
|
106
116
|
}
|
|
107
117
|
}
|
|
108
118
|
|
|
109
|
-
function
|
|
110
|
-
if (
|
|
111
|
-
|
|
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
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
151
|
-
{#if
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
232
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
233
|
+
{@html chevronLeftSvg}
|
|
160
234
|
{/if}
|
|
161
|
-
</
|
|
162
|
-
|
|
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
|
-
|
|
169
|
-
{#each views as _, index}
|
|
287
|
+
{#each { length: total } as _, index (index)}
|
|
170
288
|
<div
|
|
171
|
-
class={activeSlideIndex
|
|
172
|
-
onclick={() =>
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
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
|
-
}, "
|
|
4
|
+
}, "open" | "value" | "inputValue" | "highlightedIndex">;
|
|
5
5
|
type Combobox = ReturnType<typeof Combobox>;
|
|
6
6
|
export default Combobox;
|