@dryui/ui 1.3.1 → 1.4.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/dist/accordion/accordion-content.svelte +1 -1
- package/dist/alert/alert.svelte +1 -1
- package/dist/app-frame/app-frame.svelte +125 -0
- package/dist/app-frame/app-frame.svelte.d.ts +10 -0
- package/dist/app-frame/index.d.ts +8 -0
- package/dist/app-frame/index.js +1 -0
- package/dist/aurora/aurora.svelte +22 -59
- package/dist/beam/beam.svelte +28 -9
- package/dist/carousel/carousel-button-dots.svelte +25 -8
- package/dist/carousel/carousel-button-thumbnails.svelte +25 -8
- package/dist/carousel/carousel-root.svelte +115 -4
- package/dist/carousel/carousel-slide.svelte +5 -1
- package/dist/carousel/carousel-viewport.svelte +2 -0
- package/dist/carousel/context.svelte.d.ts +5 -0
- package/dist/chart/chart-bars.svelte +25 -16
- package/dist/chart/chart-donut.svelte +25 -16
- package/dist/chart/chart-root.svelte +134 -30
- package/dist/chart/chart-root.svelte.d.ts +1 -0
- package/dist/chart/context.svelte.d.ts +3 -1
- package/dist/chart/context.svelte.js +1 -0
- package/dist/chart/index.d.ts +1 -0
- package/dist/chromatic-shift/chromatic-shift.svelte +36 -9
- package/dist/collapsible/collapsible-content.svelte +2 -1
- package/dist/combobox/combobox-content.svelte +26 -44
- package/dist/combobox/combobox-content.svelte.d.ts +1 -1
- package/dist/combobox/combobox-input-root.svelte +7 -1
- package/dist/combobox/combobox-input.svelte +21 -8
- package/dist/country-select/country-select-button-input.svelte +124 -260
- package/dist/date-picker/datepicker-content.svelte +18 -26
- package/dist/date-picker/datepicker-content.svelte.d.ts +2 -1
- package/dist/date-picker/datepicker-input-root.svelte +7 -1
- package/dist/date-range-picker/date-range-picker-content.svelte +18 -14
- package/dist/date-range-picker/date-range-picker-content.svelte.d.ts +2 -1
- package/dist/date-range-picker/date-range-picker-root.svelte +7 -1
- package/dist/displacement/displacement.svelte +16 -22
- package/dist/drag-and-drop/context.svelte.d.ts +2 -0
- package/dist/drag-and-drop/drag-and-drop-handle.svelte +34 -5
- package/dist/drag-and-drop/drag-and-drop-item.svelte +23 -14
- package/dist/drag-and-drop/drag-and-drop-root.svelte +60 -16
- package/dist/god-rays/god-rays.svelte +11 -0
- package/dist/gradient-mesh/gradient-mesh.svelte +27 -5
- package/dist/hover-card/context.svelte.d.ts +1 -10
- package/dist/hover-card/context.svelte.js +1 -2
- package/dist/hover-card/hover-card-content.svelte +41 -3
- package/dist/hover-card/hover-card-root.svelte +7 -55
- package/dist/hover-card/hover-card-trigger.svelte +79 -40
- package/dist/hover-card/hover-card-trigger.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/internal/motion.d.ts +1 -1
- package/dist/internal/motion.js +1 -1
- package/dist/marquee/marquee.svelte +42 -5
- package/dist/mega-menu/context.svelte.d.ts +2 -1
- package/dist/mega-menu/mega-menu-button-trigger.svelte +2 -14
- package/dist/mega-menu/mega-menu-item.svelte +3 -1
- package/dist/mega-menu/mega-menu-panel.svelte +35 -3
- package/dist/mega-menu/mega-menu-root.svelte +28 -13
- package/dist/menubar/context.svelte.d.ts +2 -2
- package/dist/menubar/menubar-button-trigger.svelte +5 -3
- package/dist/menubar/menubar-content.svelte +20 -12
- package/dist/menubar/menubar-root.svelte +4 -4
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte +18 -55
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte.d.ts +1 -1
- package/dist/noise/noise.svelte +38 -6
- package/dist/notification-center/context.svelte.d.ts +0 -1
- package/dist/notification-center/notification-center-panel.svelte +54 -35
- package/dist/notification-center/notification-center-root.svelte +0 -1
- package/dist/notification-center/notification-center-trigger-button.svelte +1 -8
- package/dist/option-picker/option-picker-description.svelte +2 -2
- package/dist/option-picker/option-picker-item.svelte +10 -3
- package/dist/option-picker/option-picker-label.svelte +2 -2
- package/dist/option-picker/option-picker-preview.svelte +18 -13
- package/dist/phone-input/phone-input-select.svelte +2 -152
- package/dist/phone-input/phone-input-select.svelte.d.ts +1 -7
- package/dist/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +84 -29
- package/dist/scroll-area/scroll-area.svelte +16 -4
- package/dist/select/select-content.svelte +21 -31
- package/dist/select/select-content.svelte.d.ts +1 -1
- package/dist/select/select-root-input.svelte +7 -1
- package/dist/shimmer/shimmer.svelte +22 -12
- package/dist/transfer/transfer-item.svelte +0 -3
- package/dist/transfer/transfer-list-input.svelte +1 -6
- package/dist/tree/context.svelte.d.ts +7 -1
- package/dist/tree/tree-item-children.svelte +12 -10
- package/dist/tree/tree-item-label.svelte +6 -17
- package/dist/tree/tree-item-label.svelte.d.ts +2 -2
- package/dist/tree/tree-item.svelte +28 -1
- package/dist/tree/tree-root.svelte +135 -59
- package/package.json +8 -2
- package/skills/dryui/SKILL.md +1 -0
- package/dist/hover-card/hover-card-root.svelte.d.ts +0 -9
package/dist/alert/alert.svelte
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
title?: string;
|
|
7
|
+
actions?: Snippet;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { title, actions, children, class: className, ...rest }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div data-app-frame class={className} {...rest}>
|
|
15
|
+
<div data-part="chrome" aria-hidden="true">
|
|
16
|
+
<div data-part="dots">
|
|
17
|
+
<span data-part="dot" data-tone="close"></span>
|
|
18
|
+
<span data-part="dot" data-tone="min"></span>
|
|
19
|
+
<span data-part="dot" data-tone="max"></span>
|
|
20
|
+
</div>
|
|
21
|
+
{#if title}
|
|
22
|
+
<span data-part="title">{title}</span>
|
|
23
|
+
{/if}
|
|
24
|
+
{#if actions}
|
|
25
|
+
<div data-part="actions">
|
|
26
|
+
{@render actions()}
|
|
27
|
+
</div>
|
|
28
|
+
{/if}
|
|
29
|
+
</div>
|
|
30
|
+
<div data-part="content">
|
|
31
|
+
{@render children()}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
[data-app-frame] {
|
|
37
|
+
--dry-app-frame-bg: var(--dry-color-bg-base);
|
|
38
|
+
--dry-app-frame-chrome-bg: var(--dry-color-bg-raised);
|
|
39
|
+
--dry-app-frame-border: var(--dry-color-stroke-weak);
|
|
40
|
+
--dry-app-frame-radius: var(--dry-radius-xl);
|
|
41
|
+
--dry-app-frame-dot-size: 0.75rem;
|
|
42
|
+
--dry-app-frame-dot-close: #ff5f56;
|
|
43
|
+
--dry-app-frame-dot-min: #ffbd2e;
|
|
44
|
+
--dry-app-frame-dot-max: #27c93f;
|
|
45
|
+
|
|
46
|
+
display: grid;
|
|
47
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
48
|
+
border: 1px solid var(--dry-app-frame-border);
|
|
49
|
+
border-radius: var(--dry-app-frame-radius);
|
|
50
|
+
background: var(--dry-app-frame-bg);
|
|
51
|
+
overflow: clip;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[data-part='chrome'] {
|
|
55
|
+
display: grid;
|
|
56
|
+
grid-template-columns: minmax(0, 1fr);
|
|
57
|
+
grid-template-rows: auto;
|
|
58
|
+
align-items: center;
|
|
59
|
+
padding: var(--dry-app-frame-chrome-padding, var(--dry-space-3) var(--dry-space-4));
|
|
60
|
+
border-block-end: 1px solid var(--dry-app-frame-border);
|
|
61
|
+
background: var(--dry-app-frame-chrome-bg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[data-part='chrome'] > * {
|
|
65
|
+
grid-row: 1;
|
|
66
|
+
grid-column: 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
[data-part='dots'] {
|
|
70
|
+
display: grid;
|
|
71
|
+
grid-auto-flow: column;
|
|
72
|
+
grid-auto-columns: auto;
|
|
73
|
+
gap: var(--dry-space-1_5);
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-self: start;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
[data-part='dot'] {
|
|
79
|
+
display: block;
|
|
80
|
+
block-size: var(--dry-app-frame-dot-size);
|
|
81
|
+
aspect-ratio: 1;
|
|
82
|
+
border-radius: var(--dry-radius-full);
|
|
83
|
+
background: var(--dry-color-stroke-weak);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
[data-part='dot'][data-tone='close'] {
|
|
87
|
+
background: var(--dry-app-frame-dot-close);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[data-part='dot'][data-tone='min'] {
|
|
91
|
+
background: var(--dry-app-frame-dot-min);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[data-part='dot'][data-tone='max'] {
|
|
95
|
+
background: var(--dry-app-frame-dot-max);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[data-part='title'] {
|
|
99
|
+
justify-self: center;
|
|
100
|
+
color: var(--dry-color-text-weak);
|
|
101
|
+
font-size: var(--dry-type-small-size, 0.875rem);
|
|
102
|
+
font-weight: 500;
|
|
103
|
+
letter-spacing: 0.01em;
|
|
104
|
+
text-align: center;
|
|
105
|
+
text-overflow: ellipsis;
|
|
106
|
+
overflow: hidden;
|
|
107
|
+
white-space: nowrap;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[data-part='actions'] {
|
|
112
|
+
display: grid;
|
|
113
|
+
grid-auto-flow: column;
|
|
114
|
+
grid-auto-columns: auto;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: var(--dry-space-2);
|
|
117
|
+
justify-self: end;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
[data-part='content'] {
|
|
121
|
+
display: grid;
|
|
122
|
+
padding: var(--dry-app-frame-content-padding, 0);
|
|
123
|
+
min-block-size: 0;
|
|
124
|
+
}
|
|
125
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
title?: string;
|
|
5
|
+
actions?: Snippet;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
declare const AppFrame: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type AppFrame = ReturnType<typeof AppFrame>;
|
|
10
|
+
export default AppFrame;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
export interface AppFrameProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
title?: string;
|
|
5
|
+
actions?: Snippet;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
export { default as AppFrame } from './app-frame.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as AppFrame } from './app-frame.svelte';
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
5
|
import type { BlendMode } from '@dryui/primitives/aurora';
|
|
6
|
+
import {
|
|
7
|
+
getReducedMotionPreference,
|
|
8
|
+
observeInViewport,
|
|
9
|
+
observePageVisibility,
|
|
10
|
+
observeReducedMotionPreference,
|
|
11
|
+
registerPropertyOnce,
|
|
12
|
+
supportsPropertyRegistration
|
|
13
|
+
} from '@dryui/primitives/internal/motion';
|
|
6
14
|
|
|
7
15
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
8
16
|
palette?: 'sunrise' | 'ocean' | 'forest' | 'cosmic' | readonly [string, string, string];
|
|
@@ -58,13 +66,6 @@
|
|
|
58
66
|
const customPalette = $derived(Array.isArray(palette) ? palette : null);
|
|
59
67
|
|
|
60
68
|
onMount(() => {
|
|
61
|
-
const {
|
|
62
|
-
registerPropertyOnce,
|
|
63
|
-
supportsPropertyRegistration,
|
|
64
|
-
observeReducedMotionPreference,
|
|
65
|
-
getReducedMotionPreference
|
|
66
|
-
} = await_motion();
|
|
67
|
-
|
|
68
69
|
supportsAnimation = supportsPropertyRegistration();
|
|
69
70
|
registerPropertyOnce({
|
|
70
71
|
name: '--dry-aurora-angle',
|
|
@@ -79,27 +80,21 @@
|
|
|
79
80
|
initialValue: '0%'
|
|
80
81
|
});
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
87
|
-
|
|
88
|
-
const updateAnimatedState = (matches: boolean) => {
|
|
83
|
+
const stopVisibility = observePageVisibility((visible) => {
|
|
84
|
+
documentVisible = visible;
|
|
85
|
+
});
|
|
86
|
+
const stopMotion = observeReducedMotionPreference((matches) => {
|
|
89
87
|
prefersReducedMotion = matches;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
inViewport = entry?.isIntersecting ?? true;
|
|
88
|
+
});
|
|
89
|
+
let stopViewport = () => {};
|
|
90
|
+
if (rootNode) {
|
|
91
|
+
stopViewport = observeInViewport(
|
|
92
|
+
rootNode,
|
|
93
|
+
(inView) => {
|
|
94
|
+
inViewport = inView;
|
|
98
95
|
},
|
|
99
96
|
{ threshold: 0.12 }
|
|
100
97
|
);
|
|
101
|
-
observer.observe(rootNode);
|
|
102
|
-
stopViewportObserver = () => observer.disconnect();
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
if (!supportsAnimation || getReducedMotionPreference()) {
|
|
@@ -107,44 +102,12 @@
|
|
|
107
102
|
}
|
|
108
103
|
|
|
109
104
|
return () => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
stopVisibility();
|
|
106
|
+
stopMotion();
|
|
107
|
+
stopViewport();
|
|
113
108
|
};
|
|
114
109
|
});
|
|
115
110
|
|
|
116
|
-
function await_motion() {
|
|
117
|
-
// Dynamic import would be needed for the motion utils from primitives
|
|
118
|
-
// Instead, inline the logic since we're rendering directly
|
|
119
|
-
return {
|
|
120
|
-
registerPropertyOnce(opts: {
|
|
121
|
-
name: string;
|
|
122
|
-
syntax: string;
|
|
123
|
-
inherits: boolean;
|
|
124
|
-
initialValue: string;
|
|
125
|
-
}) {
|
|
126
|
-
try {
|
|
127
|
-
CSS.registerProperty(opts);
|
|
128
|
-
} catch {
|
|
129
|
-
// already registered or not supported
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
supportsPropertyRegistration() {
|
|
133
|
-
return typeof CSS !== 'undefined' && 'registerProperty' in CSS;
|
|
134
|
-
},
|
|
135
|
-
observeReducedMotionPreference(cb: (matches: boolean) => void) {
|
|
136
|
-
const mql = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
137
|
-
cb(mql.matches);
|
|
138
|
-
const handler = (e: MediaQueryListEvent) => cb(e.matches);
|
|
139
|
-
mql.addEventListener('change', handler);
|
|
140
|
-
return () => mql.removeEventListener('change', handler);
|
|
141
|
-
},
|
|
142
|
-
getReducedMotionPreference() {
|
|
143
|
-
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
111
|
function applyRootStyles(node: HTMLElement) {
|
|
149
112
|
$effect(() => {
|
|
150
113
|
node.style.cssText = style || '';
|
package/dist/beam/beam.svelte
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import type { BlendMode } from '@dryui/primitives/beam';
|
|
5
|
+
import { observeOffscreenState } from '@dryui/primitives/internal/motion';
|
|
5
6
|
|
|
6
7
|
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
7
8
|
color?: string;
|
|
@@ -40,6 +41,8 @@
|
|
|
40
41
|
if (blendMode) node.style.setProperty('--dry-beam-blend', blendMode);
|
|
41
42
|
else node.style.removeProperty('--dry-beam-blend');
|
|
42
43
|
});
|
|
44
|
+
|
|
45
|
+
$effect(() => observeOffscreenState(node, { rootMargin: '200px' }));
|
|
43
46
|
}
|
|
44
47
|
</script>
|
|
45
48
|
|
|
@@ -61,12 +64,22 @@
|
|
|
61
64
|
position: relative;
|
|
62
65
|
overflow: hidden;
|
|
63
66
|
border-radius: inherit;
|
|
67
|
+
contain: content;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
[data-beam-layer] {
|
|
67
71
|
position: absolute;
|
|
68
72
|
inset: 0;
|
|
69
73
|
pointer-events: none;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
opacity: calc(var(--dry-beam-intensity, 70) / 100);
|
|
76
|
+
mix-blend-mode: var(--dry-beam-blend, var(--dry-beam-default-blend, multiply));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[data-beam-layer]::before {
|
|
80
|
+
content: '';
|
|
81
|
+
position: absolute;
|
|
82
|
+
inset: -100%;
|
|
70
83
|
background-image: linear-gradient(
|
|
71
84
|
var(--dry-beam-angle, 45deg),
|
|
72
85
|
transparent 0%,
|
|
@@ -75,26 +88,32 @@
|
|
|
75
88
|
transparent calc(50% + var(--dry-beam-width, 2px)),
|
|
76
89
|
transparent 100%
|
|
77
90
|
);
|
|
78
|
-
background-size: 300% 300%;
|
|
79
|
-
opacity: calc(var(--dry-beam-intensity, 70) / 100);
|
|
80
|
-
mix-blend-mode: var(--dry-beam-blend, var(--dry-beam-default-blend, multiply));
|
|
81
91
|
filter: blur(calc(var(--dry-beam-width, 2px) * 2));
|
|
82
92
|
animation: beam-sweep var(--dry-beam-speed, 3s) ease-in-out infinite;
|
|
93
|
+
backface-visibility: hidden;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[data-beam]:not([data-offscreen]) [data-beam-layer]::before {
|
|
97
|
+
will-change: transform, filter;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
[data-beam][data-offscreen] [data-beam-layer]::before {
|
|
101
|
+
animation-play-state: paused;
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
@keyframes beam-sweep {
|
|
86
|
-
|
|
87
|
-
|
|
105
|
+
from {
|
|
106
|
+
transform: translate(-33.333%, -33.333%);
|
|
88
107
|
}
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
to {
|
|
109
|
+
transform: translate(33.333%, 33.333%);
|
|
91
110
|
}
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
@media (prefers-reduced-motion: reduce) {
|
|
95
|
-
[data-beam-layer] {
|
|
114
|
+
[data-beam-layer]::before {
|
|
96
115
|
animation: none;
|
|
97
|
-
|
|
116
|
+
transform: translate(0, 0);
|
|
98
117
|
}
|
|
99
118
|
}
|
|
100
119
|
</style>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
3
|
import Button from '../button/button.svelte';
|
|
4
|
+
import { VisuallyHidden } from '../visually-hidden/index.js';
|
|
4
5
|
import { getCarouselCtx } from './context.svelte.js';
|
|
5
6
|
|
|
6
7
|
interface Props extends HTMLAttributes<HTMLDivElement> {}
|
|
@@ -9,19 +10,35 @@
|
|
|
9
10
|
const ctx = getCarouselCtx();
|
|
10
11
|
</script>
|
|
11
12
|
|
|
12
|
-
<div
|
|
13
|
+
<div
|
|
14
|
+
role="group"
|
|
15
|
+
aria-label="Choose slide to display"
|
|
16
|
+
data-carousel-dots
|
|
17
|
+
class={className}
|
|
18
|
+
{...rest}
|
|
19
|
+
>
|
|
13
20
|
{#each Array(ctx.totalSlides) as _, i (i)}
|
|
21
|
+
{@const isActive = ctx.activeIndex === i}
|
|
14
22
|
<Button
|
|
15
23
|
variant="toggle"
|
|
16
24
|
size="icon-sm"
|
|
17
25
|
type="button"
|
|
18
|
-
|
|
19
|
-
aria-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
aria-controls={ctx.getSlideId(i)}
|
|
27
|
+
aria-disabled={isActive ? true : undefined}
|
|
28
|
+
data-active={isActive ? '' : undefined}
|
|
29
|
+
onclick={() => {
|
|
30
|
+
if (!isActive) {
|
|
31
|
+
ctx.scrollTo(i);
|
|
32
|
+
}
|
|
33
|
+
}}
|
|
23
34
|
>
|
|
24
|
-
<
|
|
35
|
+
<VisuallyHidden>
|
|
36
|
+
Show slide {i + 1} of {ctx.totalSlides}
|
|
37
|
+
{#if isActive}
|
|
38
|
+
(current slide)
|
|
39
|
+
{/if}
|
|
40
|
+
</VisuallyHidden>
|
|
41
|
+
<span class="dot" aria-hidden="true"></span>
|
|
25
42
|
</Button>
|
|
26
43
|
{/each}
|
|
27
44
|
</div>
|
|
@@ -45,7 +62,7 @@
|
|
|
45
62
|
opacity: 0.4;
|
|
46
63
|
}
|
|
47
64
|
|
|
48
|
-
[
|
|
65
|
+
[data-active] .dot {
|
|
49
66
|
opacity: 1;
|
|
50
67
|
}
|
|
51
68
|
</style>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import Button from '../button/button.svelte';
|
|
5
|
+
import { VisuallyHidden } from '../visually-hidden/index.js';
|
|
5
6
|
import { getCarouselCtx } from './context.svelte.js';
|
|
6
7
|
|
|
7
8
|
interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
@@ -14,24 +15,36 @@
|
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
17
|
<div
|
|
17
|
-
role="
|
|
18
|
-
aria-label="
|
|
18
|
+
role="group"
|
|
19
|
+
aria-label="Choose slide to display"
|
|
19
20
|
data-carousel-thumbnails
|
|
20
21
|
data-position={position}
|
|
21
22
|
class={className}
|
|
22
23
|
{...rest}
|
|
23
24
|
>
|
|
24
25
|
{#each Array(ctx.totalSlides) as _, i (i)}
|
|
26
|
+
{@const isActive = ctx.activeIndex === i}
|
|
25
27
|
<Button
|
|
26
28
|
variant="bare"
|
|
27
29
|
type="button"
|
|
28
|
-
|
|
29
|
-
aria-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
aria-controls={ctx.getSlideId(i)}
|
|
31
|
+
aria-disabled={isActive ? true : undefined}
|
|
32
|
+
data-active={isActive ? '' : undefined}
|
|
33
|
+
onclick={() => {
|
|
34
|
+
if (!isActive) {
|
|
35
|
+
ctx.scrollTo(i);
|
|
36
|
+
}
|
|
37
|
+
}}
|
|
33
38
|
>
|
|
34
|
-
|
|
39
|
+
<VisuallyHidden>
|
|
40
|
+
Show slide {i + 1} of {ctx.totalSlides}
|
|
41
|
+
{#if isActive}
|
|
42
|
+
(current slide)
|
|
43
|
+
{/if}
|
|
44
|
+
</VisuallyHidden>
|
|
45
|
+
<span class="thumbnail-preview" aria-hidden="true">
|
|
46
|
+
{@render children({ index: i, isActive, scrollTo: ctx.scrollTo })}
|
|
47
|
+
</span>
|
|
35
48
|
</Button>
|
|
36
49
|
{/each}
|
|
37
50
|
</div>
|
|
@@ -62,4 +75,8 @@
|
|
|
62
75
|
overflow-y: auto;
|
|
63
76
|
overflow-x: hidden;
|
|
64
77
|
}
|
|
78
|
+
|
|
79
|
+
.thumbnail-preview {
|
|
80
|
+
display: inline-grid;
|
|
81
|
+
}
|
|
65
82
|
</style>
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
let carouselIds = 0;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<script lang="ts">
|
|
2
6
|
import type { Snippet } from 'svelte';
|
|
3
7
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
8
|
+
import Button from '../button/button.svelte';
|
|
9
|
+
import {
|
|
10
|
+
getReducedMotionPreference,
|
|
11
|
+
observeReducedMotionPreference
|
|
12
|
+
} from '../internal/motion.js';
|
|
4
13
|
import { setCarouselCtx } from './context.svelte.js';
|
|
5
14
|
|
|
6
15
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
@@ -23,9 +32,20 @@
|
|
|
23
32
|
let totalSlides = $state(0);
|
|
24
33
|
let slideCounter = $state(0);
|
|
25
34
|
let viewportEl = $state<HTMLElement | null>(null);
|
|
35
|
+
let rootEl = $state<HTMLDivElement | null>(null);
|
|
36
|
+
let prefersReducedMotion = $state(false);
|
|
37
|
+
let manualPaused = $state(false);
|
|
38
|
+
let pausedByHover = $state(false);
|
|
39
|
+
let pausedByFocus = $state(false);
|
|
40
|
+
const carouselId = `dry-carousel-${++carouselIds}`;
|
|
26
41
|
|
|
27
42
|
const canScrollPrev = $derived(loop || activeIndex > 0);
|
|
28
43
|
const canScrollNext = $derived(loop || activeIndex < totalSlides - 1);
|
|
44
|
+
const autoplayDelay = $derived(typeof autoplay === 'number' && autoplay > 0 ? autoplay : 0);
|
|
45
|
+
const autoplayConfigured = $derived(autoplayDelay > 0);
|
|
46
|
+
const autoplayEnabled = $derived(autoplayConfigured && totalSlides > 1);
|
|
47
|
+
const autoplayPaused = $derived(manualPaused || pausedByHover || pausedByFocus);
|
|
48
|
+
const autoplayRunning = $derived(autoplayEnabled && !autoplayPaused);
|
|
29
49
|
|
|
30
50
|
function resolveIndex(index: number) {
|
|
31
51
|
if (totalSlides === 0) return 0;
|
|
@@ -40,7 +60,7 @@
|
|
|
40
60
|
viewportEl.scrollTo({
|
|
41
61
|
left: orientation === 'horizontal' ? slide.offsetLeft : 0,
|
|
42
62
|
top: orientation === 'vertical' ? slide.offsetTop : 0,
|
|
43
|
-
behavior: 'smooth'
|
|
63
|
+
behavior: prefersReducedMotion ? 'auto' : 'smooth'
|
|
44
64
|
});
|
|
45
65
|
}
|
|
46
66
|
|
|
@@ -54,6 +74,19 @@
|
|
|
54
74
|
scrollViewportTo(target);
|
|
55
75
|
}
|
|
56
76
|
|
|
77
|
+
function toggleAutoplay() {
|
|
78
|
+
if (!autoplayEnabled) return;
|
|
79
|
+
|
|
80
|
+
if (autoplayRunning) {
|
|
81
|
+
manualPaused = true;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
manualPaused = false;
|
|
86
|
+
pausedByHover = false;
|
|
87
|
+
pausedByFocus = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
57
90
|
setCarouselCtx({
|
|
58
91
|
get activeIndex() {
|
|
59
92
|
return activeIndex;
|
|
@@ -70,6 +103,15 @@
|
|
|
70
103
|
get canScrollNext() {
|
|
71
104
|
return canScrollNext;
|
|
72
105
|
},
|
|
106
|
+
get autoplayEnabled() {
|
|
107
|
+
return autoplayEnabled;
|
|
108
|
+
},
|
|
109
|
+
get autoplayPaused() {
|
|
110
|
+
return autoplayPaused;
|
|
111
|
+
},
|
|
112
|
+
get autoplayRunning() {
|
|
113
|
+
return autoplayRunning;
|
|
114
|
+
},
|
|
73
115
|
scrollTo,
|
|
74
116
|
syncActiveIndex,
|
|
75
117
|
scrollPrev() {
|
|
@@ -78,6 +120,10 @@
|
|
|
78
120
|
scrollNext() {
|
|
79
121
|
scrollTo(activeIndex + 1);
|
|
80
122
|
},
|
|
123
|
+
toggleAutoplay,
|
|
124
|
+
getSlideId(index) {
|
|
125
|
+
return `${carouselId}-slide-${index + 1}`;
|
|
126
|
+
},
|
|
81
127
|
registerViewport(el) {
|
|
82
128
|
viewportEl = el;
|
|
83
129
|
},
|
|
@@ -97,21 +143,67 @@
|
|
|
97
143
|
});
|
|
98
144
|
|
|
99
145
|
$effect(() => {
|
|
100
|
-
if (!
|
|
146
|
+
if (!autoplayConfigured) {
|
|
147
|
+
prefersReducedMotion = false;
|
|
148
|
+
manualPaused = false;
|
|
149
|
+
pausedByHover = false;
|
|
150
|
+
pausedByFocus = false;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
prefersReducedMotion = getReducedMotionPreference();
|
|
155
|
+
if (prefersReducedMotion) {
|
|
156
|
+
manualPaused = true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return observeReducedMotionPreference((value) => {
|
|
160
|
+
prefersReducedMotion = value;
|
|
161
|
+
if (value) {
|
|
162
|
+
manualPaused = true;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
$effect(() => {
|
|
168
|
+
if (!autoplayRunning || !autoplayConfigured) return;
|
|
101
169
|
const interval = setInterval(() => {
|
|
102
170
|
scrollTo(activeIndex + 1);
|
|
103
|
-
},
|
|
171
|
+
}, autoplayDelay);
|
|
104
172
|
return () => clearInterval(interval);
|
|
105
173
|
});
|
|
106
174
|
</script>
|
|
107
175
|
|
|
108
176
|
<div
|
|
109
|
-
|
|
177
|
+
bind:this={rootEl}
|
|
178
|
+
role="group"
|
|
110
179
|
aria-roledescription="carousel"
|
|
111
180
|
data-carousel-root
|
|
112
181
|
data-orientation={orientation}
|
|
182
|
+
data-autoplay-enabled={autoplayEnabled ? '' : undefined}
|
|
183
|
+
data-autoplay-state={autoplayEnabled ? (autoplayRunning ? 'running' : 'paused') : undefined}
|
|
113
184
|
class={className}
|
|
114
185
|
{...rest}
|
|
186
|
+
onfocusin={() => {
|
|
187
|
+
if (autoplayEnabled) {
|
|
188
|
+
pausedByFocus = true;
|
|
189
|
+
}
|
|
190
|
+
}}
|
|
191
|
+
onfocusout={(event) => {
|
|
192
|
+
const nextTarget = event.relatedTarget;
|
|
193
|
+
if (rootEl && nextTarget instanceof Node && rootEl.contains(nextTarget)) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pausedByFocus = false;
|
|
198
|
+
}}
|
|
199
|
+
onpointerenter={() => {
|
|
200
|
+
if (autoplayEnabled) {
|
|
201
|
+
pausedByHover = true;
|
|
202
|
+
}
|
|
203
|
+
}}
|
|
204
|
+
onpointerleave={() => {
|
|
205
|
+
pausedByHover = false;
|
|
206
|
+
}}
|
|
115
207
|
onkeydown={(e) => {
|
|
116
208
|
const prev = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';
|
|
117
209
|
const next = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';
|
|
@@ -125,6 +217,18 @@
|
|
|
125
217
|
}
|
|
126
218
|
}}
|
|
127
219
|
>
|
|
220
|
+
{#if autoplayConfigured}
|
|
221
|
+
<Button
|
|
222
|
+
variant="outline"
|
|
223
|
+
size="sm"
|
|
224
|
+
type="button"
|
|
225
|
+
data-carousel-rotation-control
|
|
226
|
+
onclick={() => toggleAutoplay()}
|
|
227
|
+
>
|
|
228
|
+
{autoplayRunning ? 'Stop slide rotation' : 'Start slide rotation'}
|
|
229
|
+
</Button>
|
|
230
|
+
{/if}
|
|
231
|
+
|
|
128
232
|
{@render children()}
|
|
129
233
|
</div>
|
|
130
234
|
|
|
@@ -134,4 +238,11 @@
|
|
|
134
238
|
|
|
135
239
|
position: relative;
|
|
136
240
|
}
|
|
241
|
+
|
|
242
|
+
[data-carousel-rotation-control] {
|
|
243
|
+
position: absolute;
|
|
244
|
+
top: var(--dry-space-3);
|
|
245
|
+
left: var(--dry-space-3);
|
|
246
|
+
z-index: 1;
|
|
247
|
+
}
|
|
137
248
|
</style>
|