@dryui/ui 1.1.1 → 1.1.2
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/calendar/calendar-button-grid.svelte +40 -140
- package/dist/date-picker/datepicker-button-calendar.svelte +46 -202
- package/dist/date-range-picker/date-range-picker-button-calendar.svelte +64 -239
- package/dist/image/image.svelte +4 -1
- package/dist/internal/calendar-grid-button.svelte +260 -0
- package/dist/internal/calendar-grid-button.svelte.d.ts +27 -0
- package/dist/internal/calendar-grid-utils.js +1 -1
- package/dist/number-input/number-input-button.svelte +2 -3
- package/dist/range-calendar/range-calendar-grid-button.svelte +41 -243
- package/dist/video-embed/video-embed-button.svelte +6 -0
- package/package.json +2 -2
|
@@ -1,271 +1,96 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
import
|
|
3
|
+
import { isSameDay } from '@dryui/primitives';
|
|
4
4
|
import { getDateRangePickerCtx } from './context.svelte.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
isToday,
|
|
9
|
-
isDateInRange,
|
|
10
|
-
formatDate
|
|
11
|
-
} from '@dryui/primitives';
|
|
12
|
-
import {
|
|
13
|
-
generateWeekdayLabels,
|
|
14
|
-
splitIntoWeeks,
|
|
15
|
-
getDayISOString,
|
|
16
|
-
handleCalendarKeydown,
|
|
17
|
-
focusCalendarDay
|
|
18
|
-
} from '../internal/calendar-grid-utils.js';
|
|
5
|
+
import CalendarGridButton, {
|
|
6
|
+
type CalendarGridAdapter
|
|
7
|
+
} from '../internal/calendar-grid-button.svelte';
|
|
19
8
|
|
|
20
9
|
interface Props extends HTMLAttributes<HTMLDivElement> {}
|
|
21
10
|
|
|
22
|
-
let
|
|
11
|
+
let props: Props = $props();
|
|
23
12
|
|
|
24
13
|
const ctx = getDateRangePickerCtx();
|
|
25
14
|
|
|
26
|
-
let containerEl = $state<HTMLDivElement>();
|
|
27
|
-
|
|
28
|
-
function bindContainer(node: HTMLDivElement) {
|
|
29
|
-
containerEl = node;
|
|
30
|
-
|
|
31
|
-
return () => {
|
|
32
|
-
if (containerEl === node) {
|
|
33
|
-
containerEl = undefined;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const weekdayLabels = $derived(generateWeekdayLabels(ctx.locale, ctx.weekStartDay));
|
|
39
|
-
|
|
40
|
-
const calendarDays = $derived(getCalendarDays(ctx.viewYear, ctx.viewMonth, ctx.weekStartDay));
|
|
41
|
-
|
|
42
|
-
const monthYearLabel = $derived(
|
|
43
|
-
formatDate(new Date(ctx.viewYear, ctx.viewMonth, 1), ctx.locale, {
|
|
44
|
-
month: 'long',
|
|
45
|
-
year: 'numeric'
|
|
46
|
-
})
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const weeks = $derived(splitIntoWeeks(calendarDays));
|
|
50
|
-
|
|
51
|
-
function isInSelectedRange(day: Date): boolean {
|
|
52
|
-
const start = ctx.startDate;
|
|
53
|
-
const end = ctx.endDate;
|
|
54
|
-
if (!start || !end) return false;
|
|
55
|
-
const time = day.getTime();
|
|
56
|
-
return time > start.getTime() && time < end.getTime();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
15
|
function isInPreviewRange(day: Date): boolean {
|
|
60
16
|
if (ctx.selecting !== 'end' || !ctx.startDate || !ctx.hoverDate) return false;
|
|
61
17
|
const time = day.getTime();
|
|
62
18
|
const startTime = ctx.startDate.getTime();
|
|
63
19
|
const hoverTime = ctx.hoverDate.getTime();
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
20
|
+
const lo = Math.min(startTime, hoverTime);
|
|
21
|
+
const hi = Math.max(startTime, hoverTime);
|
|
22
|
+
return time >= lo && time <= hi;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isInSelectedRange(day: Date): boolean {
|
|
26
|
+
if (!ctx.startDate || !ctx.endDate) return false;
|
|
27
|
+
const time = day.getTime();
|
|
28
|
+
return time > ctx.startDate.getTime() && time < ctx.endDate.getTime();
|
|
69
29
|
}
|
|
70
30
|
|
|
71
31
|
function isRangeStart(day: Date): boolean {
|
|
72
32
|
if (!ctx.startDate) return false;
|
|
73
|
-
if (ctx.endDate)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
33
|
+
if (ctx.endDate) return isSameDay(day, ctx.startDate);
|
|
34
|
+
if (
|
|
35
|
+
ctx.selecting === 'end' &&
|
|
36
|
+
ctx.hoverDate &&
|
|
37
|
+
ctx.hoverDate.getTime() < ctx.startDate.getTime()
|
|
38
|
+
) {
|
|
39
|
+
return isSameDay(day, ctx.hoverDate);
|
|
80
40
|
}
|
|
81
41
|
return isSameDay(day, ctx.startDate);
|
|
82
42
|
}
|
|
83
43
|
|
|
84
44
|
function isRangeEnd(day: Date): boolean {
|
|
85
|
-
if (ctx.endDate)
|
|
86
|
-
return isSameDay(day, ctx.endDate);
|
|
87
|
-
}
|
|
45
|
+
if (ctx.endDate) return isSameDay(day, ctx.endDate);
|
|
88
46
|
if (ctx.selecting === 'end' && ctx.startDate && ctx.hoverDate) {
|
|
89
|
-
if (ctx.hoverDate.getTime() < ctx.startDate.getTime())
|
|
90
|
-
return isSameDay(day, ctx.startDate);
|
|
91
|
-
}
|
|
47
|
+
if (ctx.hoverDate.getTime() < ctx.startDate.getTime()) return isSameDay(day, ctx.startDate);
|
|
92
48
|
return isSameDay(day, ctx.hoverDate);
|
|
93
49
|
}
|
|
94
50
|
return false;
|
|
95
51
|
}
|
|
96
52
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
53
|
+
const adapter: CalendarGridAdapter = {
|
|
54
|
+
get viewYear() {
|
|
55
|
+
return ctx.viewYear;
|
|
56
|
+
},
|
|
57
|
+
get viewMonth() {
|
|
58
|
+
return ctx.viewMonth;
|
|
59
|
+
},
|
|
60
|
+
get locale() {
|
|
61
|
+
return ctx.locale;
|
|
62
|
+
},
|
|
63
|
+
get weekStartDay() {
|
|
64
|
+
return ctx.weekStartDay;
|
|
65
|
+
},
|
|
66
|
+
get focusedDate() {
|
|
67
|
+
return ctx.focusedDate;
|
|
68
|
+
},
|
|
69
|
+
get min() {
|
|
70
|
+
return ctx.min;
|
|
71
|
+
},
|
|
72
|
+
get max() {
|
|
73
|
+
return ctx.max;
|
|
74
|
+
},
|
|
75
|
+
get disabled() {
|
|
76
|
+
return ctx.disabled;
|
|
77
|
+
},
|
|
78
|
+
isSelected: (day) => isRangeStart(day) || isRangeEnd(day),
|
|
79
|
+
isInRange: (day) => isInSelectedRange(day) || isInPreviewRange(day),
|
|
80
|
+
isRangeStart,
|
|
81
|
+
isRangeEnd,
|
|
82
|
+
selectDate: (day) => ctx.selectDate(day),
|
|
83
|
+
setFocusedDate: (day) => ctx.setFocusedDate(day),
|
|
84
|
+
prevMonth: () => ctx.prevMonth(),
|
|
85
|
+
nextMonth: () => ctx.nextMonth(),
|
|
86
|
+
onEscape: () => {
|
|
87
|
+
ctx.close();
|
|
88
|
+
ctx.triggerEl?.focus();
|
|
89
|
+
},
|
|
90
|
+
onHover: (day) => {
|
|
91
|
+
if (ctx.selecting === 'end') ctx.setHoverDate(day);
|
|
122
92
|
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function isDayDisabled(day: Date): boolean {
|
|
126
|
-
return !isDateInRange(day, ctx.min, ctx.max);
|
|
127
|
-
}
|
|
93
|
+
};
|
|
128
94
|
</script>
|
|
129
95
|
|
|
130
|
-
<
|
|
131
|
-
<div role="group" aria-label={monthYearLabel} data-calendar-panel>
|
|
132
|
-
<div data-calendar-header>
|
|
133
|
-
<Button
|
|
134
|
-
variant="trigger"
|
|
135
|
-
size="icon-sm"
|
|
136
|
-
type="button"
|
|
137
|
-
aria-label="Previous month"
|
|
138
|
-
disabled={ctx.disabled}
|
|
139
|
-
onclick={() => ctx.prevMonth()}
|
|
140
|
-
>
|
|
141
|
-
‹
|
|
142
|
-
</Button>
|
|
143
|
-
<span aria-live="polite" aria-atomic="true" data-calendar-heading>
|
|
144
|
-
{monthYearLabel}
|
|
145
|
-
</span>
|
|
146
|
-
<Button
|
|
147
|
-
variant="trigger"
|
|
148
|
-
size="icon-sm"
|
|
149
|
-
type="button"
|
|
150
|
-
aria-label="Next month"
|
|
151
|
-
disabled={ctx.disabled}
|
|
152
|
-
onclick={() => ctx.nextMonth()}
|
|
153
|
-
>
|
|
154
|
-
›
|
|
155
|
-
</Button>
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
<div role="grid" aria-label={monthYearLabel}>
|
|
159
|
-
<div role="row" data-calendar-row>
|
|
160
|
-
{#each weekdayLabels as label (label)}
|
|
161
|
-
<div role="columnheader" aria-label={label} data-calendar-columnheader>
|
|
162
|
-
<span aria-hidden="true">{label}</span>
|
|
163
|
-
</div>
|
|
164
|
-
{/each}
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
{#each weeks as week, weekIndex (weekIndex)}
|
|
168
|
-
<div role="row" data-calendar-row>
|
|
169
|
-
{#each week as day (getDayISOString(day))}
|
|
170
|
-
{@const isCurrent = day.getMonth() === ctx.viewMonth}
|
|
171
|
-
{@const selected = isSelected(day)}
|
|
172
|
-
{@const today = isToday(day)}
|
|
173
|
-
{@const disabled = isDayDisabled(day)}
|
|
174
|
-
{@const focused = isSameDay(day, ctx.focusedDate)}
|
|
175
|
-
{@const isoStr = getDayISOString(day)}
|
|
176
|
-
{@const inRange = isInSelectedRange(day) || isInPreviewRange(day)}
|
|
177
|
-
{@const rangeStart = isRangeStart(day)}
|
|
178
|
-
{@const rangeEnd = isRangeEnd(day)}
|
|
179
|
-
<div
|
|
180
|
-
role="gridcell"
|
|
181
|
-
data-calendar-cell
|
|
182
|
-
data-in-range={inRange ? '' : undefined}
|
|
183
|
-
data-range-start={rangeStart ? '' : undefined}
|
|
184
|
-
data-range-end={rangeEnd ? '' : undefined}
|
|
185
|
-
>
|
|
186
|
-
<Button
|
|
187
|
-
variant="trigger"
|
|
188
|
-
size="icon-sm"
|
|
189
|
-
type="button"
|
|
190
|
-
tabindex={focused ? 0 : -1}
|
|
191
|
-
aria-label={formatDate(day, ctx.locale, {
|
|
192
|
-
year: 'numeric',
|
|
193
|
-
month: 'long',
|
|
194
|
-
day: 'numeric'
|
|
195
|
-
})}
|
|
196
|
-
aria-pressed={selected}
|
|
197
|
-
aria-disabled={disabled}
|
|
198
|
-
data-calendar-day={isoStr}
|
|
199
|
-
data-today={today ? '' : undefined}
|
|
200
|
-
data-selected={selected ? '' : undefined}
|
|
201
|
-
data-in-range={inRange ? '' : undefined}
|
|
202
|
-
data-range-start={rangeStart ? '' : undefined}
|
|
203
|
-
data-range-end={rangeEnd ? '' : undefined}
|
|
204
|
-
data-outside-month={!isCurrent ? '' : undefined}
|
|
205
|
-
{disabled}
|
|
206
|
-
onclick={() => handleDayClick(day)}
|
|
207
|
-
onkeydown={(e) => handleDayKeydown(e, day)}
|
|
208
|
-
onmouseenter={() => {
|
|
209
|
-
if (ctx.selecting === 'end') {
|
|
210
|
-
ctx.setHoverDate(day);
|
|
211
|
-
}
|
|
212
|
-
}}
|
|
213
|
-
onmouseleave={() => {
|
|
214
|
-
if (ctx.selecting === 'end') {
|
|
215
|
-
ctx.setHoverDate(null);
|
|
216
|
-
}
|
|
217
|
-
}}
|
|
218
|
-
>
|
|
219
|
-
{day.getDate()}
|
|
220
|
-
</Button>
|
|
221
|
-
</div>
|
|
222
|
-
{/each}
|
|
223
|
-
</div>
|
|
224
|
-
{/each}
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
228
|
-
|
|
229
|
-
<style>
|
|
230
|
-
[data-drp-calendar] {
|
|
231
|
-
display: grid;
|
|
232
|
-
gap: var(--dry-space-2);
|
|
233
|
-
user-select: none;
|
|
234
|
-
color: var(--dry-color-text-strong);
|
|
235
|
-
font-family: var(--dry-font-sans);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
[data-drp-calendar] [data-calendar-panel] {
|
|
239
|
-
display: grid;
|
|
240
|
-
gap: var(--dry-space-2);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
[data-drp-calendar] [data-calendar-header] {
|
|
244
|
-
display: grid;
|
|
245
|
-
grid-template-columns: auto 1fr auto;
|
|
246
|
-
align-items: center;
|
|
247
|
-
gap: var(--dry-space-2);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
[data-drp-calendar] [data-calendar-heading] {
|
|
251
|
-
font-size: var(--dry-type-small-size);
|
|
252
|
-
font-weight: 600;
|
|
253
|
-
letter-spacing: -0.01em;
|
|
254
|
-
text-align: center;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
[data-drp-calendar] [role='grid'] {
|
|
258
|
-
display: grid;
|
|
259
|
-
gap: 1px;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
[data-drp-calendar] [data-calendar-row] {
|
|
263
|
-
display: grid;
|
|
264
|
-
grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
[data-drp-calendar] [data-calendar-cell] {
|
|
268
|
-
display: grid;
|
|
269
|
-
place-items: center;
|
|
270
|
-
}
|
|
271
|
-
</style>
|
|
96
|
+
<CalendarGridButton {adapter} {...props} />
|
package/dist/image/image.svelte
CHANGED
|
@@ -56,9 +56,12 @@
|
|
|
56
56
|
--dry-image-radius: var(--dry-radius-lg);
|
|
57
57
|
--dry-image-bg: var(--dry-color-bg-overlay);
|
|
58
58
|
--dry-image-object-fit: cover;
|
|
59
|
+
--dry-image-block-size: auto;
|
|
60
|
+
--dry-image-place-self: auto;
|
|
59
61
|
|
|
60
62
|
display: block;
|
|
61
|
-
|
|
63
|
+
block-size: var(--dry-image-block-size);
|
|
64
|
+
place-self: var(--dry-image-place-self);
|
|
62
65
|
border-radius: var(--dry-image-radius);
|
|
63
66
|
background: var(--dry-image-bg);
|
|
64
67
|
object-fit: var(--dry-image-object-fit);
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface CalendarGridAdapter {
|
|
3
|
+
readonly viewYear: number;
|
|
4
|
+
readonly viewMonth: number;
|
|
5
|
+
readonly locale: string;
|
|
6
|
+
readonly weekStartDay: number;
|
|
7
|
+
readonly focusedDate: Date;
|
|
8
|
+
readonly min: Date | null;
|
|
9
|
+
readonly max: Date | null;
|
|
10
|
+
readonly disabled: boolean;
|
|
11
|
+
isSelected: (day: Date) => boolean;
|
|
12
|
+
isInRange: (day: Date) => boolean;
|
|
13
|
+
isRangeStart: (day: Date) => boolean;
|
|
14
|
+
isRangeEnd: (day: Date) => boolean;
|
|
15
|
+
selectDate: (day: Date) => void;
|
|
16
|
+
setFocusedDate: (day: Date) => void;
|
|
17
|
+
prevMonth: () => void;
|
|
18
|
+
nextMonth: () => void;
|
|
19
|
+
onEscape?: () => void;
|
|
20
|
+
onHover?: (day: Date | null) => void;
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<script lang="ts">
|
|
25
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
26
|
+
import Button from '../button/button.svelte';
|
|
27
|
+
import {
|
|
28
|
+
getCalendarDays,
|
|
29
|
+
isSameDay,
|
|
30
|
+
isToday,
|
|
31
|
+
isDateInRange,
|
|
32
|
+
formatDate
|
|
33
|
+
} from '@dryui/primitives';
|
|
34
|
+
import {
|
|
35
|
+
generateWeekdayLabels,
|
|
36
|
+
splitIntoWeeks,
|
|
37
|
+
getDayISOString,
|
|
38
|
+
handleCalendarKeydown,
|
|
39
|
+
focusCalendarDay
|
|
40
|
+
} from './calendar-grid-utils.js';
|
|
41
|
+
|
|
42
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
43
|
+
adapter: CalendarGridAdapter;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let { adapter, class: className, ...rest }: Props = $props();
|
|
47
|
+
|
|
48
|
+
let containerEl = $state<HTMLDivElement>();
|
|
49
|
+
|
|
50
|
+
function bindContainer(node: HTMLDivElement) {
|
|
51
|
+
containerEl = node;
|
|
52
|
+
return () => {
|
|
53
|
+
if (containerEl === node) containerEl = undefined;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const weekdayLabels = $derived(generateWeekdayLabels(adapter.locale, adapter.weekStartDay));
|
|
58
|
+
const calendarDays = $derived(
|
|
59
|
+
getCalendarDays(adapter.viewYear, adapter.viewMonth, adapter.weekStartDay)
|
|
60
|
+
);
|
|
61
|
+
const monthYearLabel = $derived(
|
|
62
|
+
formatDate(new Date(adapter.viewYear, adapter.viewMonth, 1), adapter.locale, {
|
|
63
|
+
month: 'long',
|
|
64
|
+
year: 'numeric'
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
const weeks = $derived(splitIntoWeeks(calendarDays));
|
|
68
|
+
|
|
69
|
+
function handleDayClick(day: Date) {
|
|
70
|
+
if (!isDateInRange(day, adapter.min, adapter.max)) return;
|
|
71
|
+
adapter.selectDate(day);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleDayKeydown(e: KeyboardEvent, day: Date) {
|
|
75
|
+
const result = handleCalendarKeydown(e, day, {
|
|
76
|
+
onSelect: (d) => {
|
|
77
|
+
if (isDateInRange(d, adapter.min, adapter.max)) adapter.selectDate(d);
|
|
78
|
+
},
|
|
79
|
+
onEscape: () => adapter.onEscape?.(),
|
|
80
|
+
setFocusedDate: (d) => adapter.setFocusedDate(d),
|
|
81
|
+
weekStartDay: adapter.weekStartDay,
|
|
82
|
+
min: adapter.min,
|
|
83
|
+
max: adapter.max
|
|
84
|
+
});
|
|
85
|
+
if (result?.type === 'navigate') {
|
|
86
|
+
requestAnimationFrame(() => focusCalendarDay(containerEl, result.newDate));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<div {@attach bindContainer} class={className} {...rest} data-calendar-grid>
|
|
92
|
+
<div role="group" aria-label={monthYearLabel} data-calendar-panel>
|
|
93
|
+
<div data-calendar-header>
|
|
94
|
+
<Button
|
|
95
|
+
variant="trigger"
|
|
96
|
+
size="icon-sm"
|
|
97
|
+
type="button"
|
|
98
|
+
aria-label="Previous month"
|
|
99
|
+
disabled={adapter.disabled}
|
|
100
|
+
onclick={() => adapter.prevMonth()}
|
|
101
|
+
>
|
|
102
|
+
‹
|
|
103
|
+
</Button>
|
|
104
|
+
<span aria-live="polite" aria-atomic="true" data-calendar-heading>
|
|
105
|
+
{monthYearLabel}
|
|
106
|
+
</span>
|
|
107
|
+
<Button
|
|
108
|
+
variant="trigger"
|
|
109
|
+
size="icon-sm"
|
|
110
|
+
type="button"
|
|
111
|
+
aria-label="Next month"
|
|
112
|
+
disabled={adapter.disabled}
|
|
113
|
+
onclick={() => adapter.nextMonth()}
|
|
114
|
+
>
|
|
115
|
+
›
|
|
116
|
+
</Button>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div role="grid" aria-label={monthYearLabel}>
|
|
120
|
+
<div role="row" data-calendar-row>
|
|
121
|
+
{#each weekdayLabels as label, i (i)}
|
|
122
|
+
<div role="columnheader" aria-label={label} data-calendar-columnheader>
|
|
123
|
+
<span aria-hidden="true">{label}</span>
|
|
124
|
+
</div>
|
|
125
|
+
{/each}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{#each weeks as week, weekIndex (weekIndex)}
|
|
129
|
+
<div role="row" data-calendar-row>
|
|
130
|
+
{#each week as day (getDayISOString(day))}
|
|
131
|
+
{@const isCurrent = day.getMonth() === adapter.viewMonth}
|
|
132
|
+
{@const selected = adapter.isSelected(day)}
|
|
133
|
+
{@const today = isToday(day)}
|
|
134
|
+
{@const disabled = !isDateInRange(day, adapter.min, adapter.max)}
|
|
135
|
+
{@const focused = isSameDay(day, adapter.focusedDate)}
|
|
136
|
+
{@const isoStr = getDayISOString(day)}
|
|
137
|
+
{@const inRange = adapter.isInRange(day)}
|
|
138
|
+
{@const rangeStart = adapter.isRangeStart(day)}
|
|
139
|
+
{@const rangeEnd = adapter.isRangeEnd(day)}
|
|
140
|
+
<div
|
|
141
|
+
role="gridcell"
|
|
142
|
+
data-calendar-cell
|
|
143
|
+
data-selected={selected ? '' : undefined}
|
|
144
|
+
data-today={today ? '' : undefined}
|
|
145
|
+
data-outside-month={!isCurrent ? '' : undefined}
|
|
146
|
+
data-in-range={inRange ? '' : undefined}
|
|
147
|
+
data-range-start={rangeStart ? '' : undefined}
|
|
148
|
+
data-range-end={rangeEnd ? '' : undefined}
|
|
149
|
+
>
|
|
150
|
+
<Button
|
|
151
|
+
variant="trigger"
|
|
152
|
+
size="icon-sm"
|
|
153
|
+
type="button"
|
|
154
|
+
tabindex={focused ? 0 : -1}
|
|
155
|
+
aria-label={formatDate(day, adapter.locale, {
|
|
156
|
+
year: 'numeric',
|
|
157
|
+
month: 'long',
|
|
158
|
+
day: 'numeric'
|
|
159
|
+
})}
|
|
160
|
+
aria-pressed={selected}
|
|
161
|
+
aria-disabled={disabled}
|
|
162
|
+
data-calendar-day={isoStr}
|
|
163
|
+
{disabled}
|
|
164
|
+
onclick={() => handleDayClick(day)}
|
|
165
|
+
onkeydown={(e) => handleDayKeydown(e, day)}
|
|
166
|
+
onmouseenter={() => adapter.onHover?.(day)}
|
|
167
|
+
onmouseleave={() => adapter.onHover?.(null)}
|
|
168
|
+
>
|
|
169
|
+
{day.getDate()}
|
|
170
|
+
</Button>
|
|
171
|
+
</div>
|
|
172
|
+
{/each}
|
|
173
|
+
</div>
|
|
174
|
+
{/each}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<style>
|
|
180
|
+
[data-calendar-grid] {
|
|
181
|
+
display: grid;
|
|
182
|
+
gap: var(--dry-space-2);
|
|
183
|
+
user-select: none;
|
|
184
|
+
color: var(--dry-color-text-strong);
|
|
185
|
+
font-family: var(--dry-font-sans);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
[data-calendar-grid] [data-calendar-panel] {
|
|
189
|
+
display: grid;
|
|
190
|
+
gap: var(--dry-space-2);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
[data-calendar-grid] [data-calendar-header] {
|
|
194
|
+
display: grid;
|
|
195
|
+
grid-template-columns: auto 1fr auto;
|
|
196
|
+
align-items: center;
|
|
197
|
+
gap: var(--dry-space-2);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
[data-calendar-grid] [data-calendar-heading] {
|
|
201
|
+
font-size: var(--dry-type-small-size);
|
|
202
|
+
font-weight: 600;
|
|
203
|
+
letter-spacing: -0.01em;
|
|
204
|
+
text-align: center;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
[data-calendar-grid] [data-calendar-columnheader] {
|
|
208
|
+
display: grid;
|
|
209
|
+
place-items: center;
|
|
210
|
+
min-height: var(--dry-space-7);
|
|
211
|
+
font-size: var(--dry-type-tiny-size);
|
|
212
|
+
color: var(--dry-color-text-weak);
|
|
213
|
+
text-transform: uppercase;
|
|
214
|
+
letter-spacing: 0.04em;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
[data-calendar-grid] [role='grid'] {
|
|
218
|
+
display: grid;
|
|
219
|
+
gap: 1px;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
[data-calendar-grid] [data-calendar-row] {
|
|
223
|
+
display: grid;
|
|
224
|
+
grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
[data-calendar-grid] [data-calendar-cell] {
|
|
228
|
+
display: grid;
|
|
229
|
+
place-items: center;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
[data-calendar-grid] [data-calendar-cell][data-in-range] {
|
|
233
|
+
background: color-mix(in srgb, var(--dry-color-fill-brand) 14%, transparent);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
[data-calendar-grid] [data-calendar-cell][data-selected],
|
|
237
|
+
[data-calendar-grid] [data-calendar-cell][data-range-start],
|
|
238
|
+
[data-calendar-grid] [data-calendar-cell][data-range-end] {
|
|
239
|
+
--dry-btn-bg: var(--dry-color-fill-brand);
|
|
240
|
+
--dry-btn-color: var(--dry-color-on-brand);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
[data-calendar-grid] [data-calendar-cell][data-range-start][data-in-range] {
|
|
244
|
+
border-start-start-radius: var(--dry-radius-md);
|
|
245
|
+
border-end-start-radius: var(--dry-radius-md);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
[data-calendar-grid] [data-calendar-cell][data-range-end][data-in-range] {
|
|
249
|
+
border-start-end-radius: var(--dry-radius-md);
|
|
250
|
+
border-end-end-radius: var(--dry-radius-md);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
[data-calendar-grid] [data-calendar-cell][data-today]:not([data-selected]) {
|
|
254
|
+
--dry-btn-color: var(--dry-color-text-brand);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
[data-calendar-grid] [data-calendar-cell][data-outside-month] {
|
|
258
|
+
opacity: 0.4;
|
|
259
|
+
}
|
|
260
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface CalendarGridAdapter {
|
|
2
|
+
readonly viewYear: number;
|
|
3
|
+
readonly viewMonth: number;
|
|
4
|
+
readonly locale: string;
|
|
5
|
+
readonly weekStartDay: number;
|
|
6
|
+
readonly focusedDate: Date;
|
|
7
|
+
readonly min: Date | null;
|
|
8
|
+
readonly max: Date | null;
|
|
9
|
+
readonly disabled: boolean;
|
|
10
|
+
isSelected: (day: Date) => boolean;
|
|
11
|
+
isInRange: (day: Date) => boolean;
|
|
12
|
+
isRangeStart: (day: Date) => boolean;
|
|
13
|
+
isRangeEnd: (day: Date) => boolean;
|
|
14
|
+
selectDate: (day: Date) => void;
|
|
15
|
+
setFocusedDate: (day: Date) => void;
|
|
16
|
+
prevMonth: () => void;
|
|
17
|
+
nextMonth: () => void;
|
|
18
|
+
onEscape?: () => void;
|
|
19
|
+
onHover?: (day: Date | null) => void;
|
|
20
|
+
}
|
|
21
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
22
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
adapter: CalendarGridAdapter;
|
|
24
|
+
}
|
|
25
|
+
declare const CalendarGridButton: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
type CalendarGridButton = ReturnType<typeof CalendarGridButton>;
|
|
27
|
+
export default CalendarGridButton;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function generateWeekdayLabels(locale, weekStartDay) {
|
|
2
|
-
const formatter = new Intl.DateTimeFormat(locale, { weekday: '
|
|
2
|
+
const formatter = new Intl.DateTimeFormat(locale, { weekday: 'narrow' });
|
|
3
3
|
const labels = [];
|
|
4
4
|
for (let i = 0; i < 7; i++) {
|
|
5
5
|
const dayIndex = (weekStartDay + i) % 7;
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<div role="group" data-number-input-wrapper data-size={size} data-disabled={disabled || undefined}>
|
|
34
34
|
<Button
|
|
35
35
|
variant="outline"
|
|
36
|
-
size=
|
|
36
|
+
size={size === 'sm' ? 'icon-sm' : size === 'lg' ? 'icon-lg' : 'icon'}
|
|
37
37
|
type="button"
|
|
38
38
|
tabindex={-1}
|
|
39
39
|
aria-label="Decrease value"
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
/>
|
|
54
54
|
<Button
|
|
55
55
|
variant="outline"
|
|
56
|
-
size=
|
|
56
|
+
size={size === 'sm' ? 'icon-sm' : size === 'lg' ? 'icon-lg' : 'icon'}
|
|
57
57
|
type="button"
|
|
58
58
|
tabindex={-1}
|
|
59
59
|
aria-label="Increase value"
|
|
@@ -64,7 +64,6 @@
|
|
|
64
64
|
|
|
65
65
|
<style>
|
|
66
66
|
[data-number-input-wrapper] {
|
|
67
|
-
container-type: inline-size;
|
|
68
67
|
display: inline-grid;
|
|
69
68
|
grid-template-columns: auto minmax(3ch, max-content) auto;
|
|
70
69
|
align-items: stretch;
|