@urbicon-ui/blocks 6.12.0 → 6.19.1
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 +5 -5
- package/dist/components/AreaChart/AreaChart.svelte +7 -3
- package/dist/components/BarChart/BarChart.svelte +7 -3
- package/dist/components/Calendar/Calendar.svelte +46 -29
- package/dist/components/Calendar/CalendarAgendaView.svelte +1 -1
- package/dist/components/Calendar/CalendarDayView.svelte +19 -39
- package/dist/components/Calendar/CalendarDayView.svelte.d.ts +1 -3
- package/dist/components/Calendar/CalendarEventItem.svelte +0 -1
- package/dist/components/Calendar/CalendarGrid.svelte +12 -8
- package/dist/components/Calendar/CalendarHeader.svelte +2 -2
- package/dist/components/Calendar/CalendarMiniMonth.svelte +0 -2
- package/dist/components/Calendar/CalendarTimeEvent.svelte +1 -1
- package/dist/components/Calendar/CalendarTimeGrid.svelte +0 -3
- package/dist/components/Calendar/CalendarWeekGrid.svelte +1 -1
- package/dist/components/Calendar/calendar.context.d.ts +1 -0
- package/dist/components/Calendar/calendar.types.d.ts +2 -0
- package/dist/components/Calendar/calendar.variants.d.ts +0 -12
- package/dist/components/Calendar/calendar.variants.js +0 -4
- package/dist/components/Calendar/index.d.ts +6 -2
- package/dist/components/CompositionBar/CompositionBar.svelte +1 -1
- package/dist/components/CompositionBar/composition-bar.variants.d.ts +15 -16
- package/dist/components/CompositionBar/composition-bar.variants.js +4 -2
- package/dist/components/CompositionBar/index.d.ts +1 -1
- package/dist/components/DatePicker/DatePicker.svelte +5 -3
- package/dist/components/DatePicker/DateRangePicker.svelte +3 -3
- package/dist/components/DatePicker/datepicker.engine.d.ts +6 -6
- package/dist/components/DatePicker/datepicker.engine.js +13 -13
- package/dist/components/DonutChart/DonutChart.svelte +5 -3
- package/dist/components/LineChart/LineChart.svelte +7 -3
- package/dist/components/Planner/Planner.svelte +7 -3
- package/dist/components/Planner/PlannerHeader.svelte +1 -1
- package/dist/components/Planner/planner.types.d.ts +3 -0
- package/dist/date/compare.d.ts +8 -0
- package/dist/date/compare.js +15 -0
- package/dist/date/index.d.ts +1 -1
- package/dist/date/index.js +1 -1
- package/dist/i18n/index.d.ts +22 -14
- package/dist/internal/date-grid/date-grid.svelte.d.ts +18 -3
- package/dist/internal/date-grid/date-grid.svelte.js +60 -19
- package/dist/internal/date-grid/index.d.ts +2 -2
- package/dist/internal/date-grid/index.js +2 -2
- package/dist/mint/README.md +12 -9
- package/dist/mint/registry.js +12 -0
- package/dist/primitives/Badge/badge.variants.d.ts +0 -1
- package/dist/primitives/Badge/badge.variants.js +1 -2
- package/dist/primitives/Breadcrumb/Breadcrumb.svelte +1 -2
- package/dist/primitives/ButtonGroup/ButtonGroup.svelte +1 -1
- package/dist/primitives/Combobox/Combobox.svelte +1 -0
- package/dist/primitives/Menu/Menu.svelte +2 -2
- package/dist/primitives/Pagination/Pagination.svelte +10 -2
- package/dist/primitives/Pagination/PaginationItem.svelte +0 -2
- package/dist/primitives/Stepper/StepperStep.svelte +3 -1
- package/dist/translations/de.d.ts +11 -7
- package/dist/translations/de.js +12 -8
- package/dist/translations/en.d.ts +11 -7
- package/dist/translations/en.js +12 -8
- package/dist/utils/date.d.ts +5 -0
- package/dist/utils/date.js +34 -24
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,13 +14,13 @@ Peer dependencies: `svelte` (^5), `@sveltejs/kit`, `@urbicon-ui/i18n`.
|
|
|
14
14
|
|
|
15
15
|
## Components
|
|
16
16
|
|
|
17
|
-
### Primitives (
|
|
17
|
+
### Primitives (36)
|
|
18
18
|
|
|
19
|
-
Accordion, Alert, Avatar, Badge, Breadcrumb, Button, ButtonGroup, Card, Checkbox, Collapsible, Combobox, ConfirmDialog, Dialog, Drawer, FormField, Input, Menu, Pagination, Popover, Progress, RadioGroup, SegmentGroup, Select, Separator, Sidebar, Skeleton, Slider, Spinner, Stepper, Tab, Textarea, Toast, Toggle, Toolbar, Tooltip
|
|
19
|
+
Accordion, Alert, Avatar, Badge, Breadcrumb, Button, ButtonGroup, Card, Checkbox, Collapsible, Combobox, ConfirmDialog, Dialog, Drawer, FormField, Input, JourneyTimeline, Menu, Pagination, Popover, Progress, RadioGroup, SegmentGroup, Select, Separator, Sidebar, Skeleton, Slider, Spinner, Stepper, Tab, Textarea, Toast, Toggle, Toolbar, Tooltip
|
|
20
20
|
|
|
21
|
-
### Components (
|
|
21
|
+
### Components (19)
|
|
22
22
|
|
|
23
|
-
Calendar, CommandPalette, CompositionBar, CurrencyInput, DatePicker, EmptyState, FileUpload, LocaleSwitcher, Sankey, ThemeSwitcher
|
|
23
|
+
AreaChart, BarChart, Calendar, ChartFrame, CommandPalette, CompositionBar, CurrencyInput, DatePicker, DonutChart, EmptyState, FileUpload, Guide, LineChart, LocaleSwitcher, Planner, Sankey, SidebarLayout, Sparkline, ThemeSwitcher
|
|
24
24
|
|
|
25
25
|
### System
|
|
26
26
|
|
|
@@ -73,7 +73,7 @@ Use `overrides` for **prop-conditional** rules an unconditional `slotClasses` ca
|
|
|
73
73
|
|
|
74
74
|
## Icons
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
315 hand-rolled SVG icons in `src/lib/icons/`, registered via `IconProvider`. Metadata (`ICON_METADATA`) enables search by name, keyword, or category. See the icon-design rules in [AGENTS.md → Icon Design Rules](../../AGENTS.md#icon-design-rules).
|
|
77
77
|
|
|
78
78
|
## i18n
|
|
79
79
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
} from '../../internal/charts/utils';
|
|
15
15
|
import type { ChartPoint } from '../../internal/charts/utils';
|
|
16
16
|
import ChartFrame from '../ChartFrame/ChartFrame.svelte';
|
|
17
|
+
import { useBlocksI18n } from '../..';
|
|
17
18
|
|
|
18
19
|
let {
|
|
19
20
|
data,
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
}: AreaChartProps = $props();
|
|
37
38
|
|
|
38
39
|
const blocksConfig = getBlocksConfig();
|
|
40
|
+
const bt = useBlocksI18n();
|
|
39
41
|
const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
|
|
40
42
|
const slotClasses = $derived(
|
|
41
43
|
resolveSlotClasses(blocksConfig, 'AreaChart', preset, {}, slotClassesProp)
|
|
@@ -48,7 +50,9 @@
|
|
|
48
50
|
const resolvedSeries = $derived<ChartSeries[]>(
|
|
49
51
|
seriesProp && seriesProp.length > 0
|
|
50
52
|
? seriesProp
|
|
51
|
-
: Array.from({ length: seriesCount }, (_, i) => ({
|
|
53
|
+
: Array.from({ length: seriesCount }, (_, i) => ({
|
|
54
|
+
label: bt('chart.series', { index: i + 1 })
|
|
55
|
+
}))
|
|
52
56
|
);
|
|
53
57
|
|
|
54
58
|
const fmt = $derived(formatValue ?? numberFormatter(locale));
|
|
@@ -213,9 +217,9 @@
|
|
|
213
217
|
<caption>{resolvedAriaLabel}</caption>
|
|
214
218
|
<thead>
|
|
215
219
|
<tr>
|
|
216
|
-
<th scope="col">
|
|
220
|
+
<th scope="col">{bt('chart.category')}</th>
|
|
217
221
|
{#each resolvedSeries as s, i (s.label + ' ' + i)}
|
|
218
|
-
<th scope="col">{s.label ||
|
|
222
|
+
<th scope="col">{s.label || bt('chart.series', { index: i + 1 })}</th>
|
|
219
223
|
{/each}
|
|
220
224
|
</tr>
|
|
221
225
|
</thead>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
extent
|
|
13
13
|
} from '../../internal/charts/utils';
|
|
14
14
|
import ChartFrame from '../ChartFrame/ChartFrame.svelte';
|
|
15
|
+
import { useBlocksI18n } from '../..';
|
|
15
16
|
|
|
16
17
|
let {
|
|
17
18
|
data,
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
}: BarChartProps = $props();
|
|
34
35
|
|
|
35
36
|
const blocksConfig = getBlocksConfig();
|
|
37
|
+
const bt = useBlocksI18n();
|
|
36
38
|
const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
|
|
37
39
|
const slotClasses = $derived(
|
|
38
40
|
resolveSlotClasses(blocksConfig, 'BarChart', preset, {}, slotClassesProp)
|
|
@@ -47,7 +49,9 @@
|
|
|
47
49
|
const resolvedSeries = $derived<ChartSeries[]>(
|
|
48
50
|
seriesProp && seriesProp.length > 0
|
|
49
51
|
? seriesProp
|
|
50
|
-
: Array.from({ length: seriesCount }, (_, i) => ({
|
|
52
|
+
: Array.from({ length: seriesCount }, (_, i) => ({
|
|
53
|
+
label: bt('chart.series', { index: i + 1 })
|
|
54
|
+
}))
|
|
51
55
|
);
|
|
52
56
|
|
|
53
57
|
const fmt = $derived(formatValue ?? numberFormatter(locale));
|
|
@@ -239,9 +243,9 @@
|
|
|
239
243
|
<caption>{resolvedAriaLabel}</caption>
|
|
240
244
|
<thead>
|
|
241
245
|
<tr>
|
|
242
|
-
<th scope="col">
|
|
246
|
+
<th scope="col">{bt('chart.category')}</th>
|
|
243
247
|
{#each resolvedSeries as s, i (s.label + ' ' + i)}
|
|
244
|
-
<th scope="col">{s.label ||
|
|
248
|
+
<th scope="col">{s.label || bt('chart.series', { index: i + 1 })}</th>
|
|
245
249
|
{/each}
|
|
246
250
|
</tr>
|
|
247
251
|
</thead>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
stripTime,
|
|
12
12
|
addDays,
|
|
13
13
|
clampMonth,
|
|
14
|
+
clampDate,
|
|
14
15
|
daysInMonth
|
|
15
16
|
} from '../../date';
|
|
16
17
|
import { DateGridController } from '../../internal/date-grid';
|
|
@@ -238,13 +239,17 @@
|
|
|
238
239
|
function handleSelect(selection: DateGridSelection, date: Date) {
|
|
239
240
|
// Date-picker behaviour: clicking an outside (spill) day in the month grid
|
|
240
241
|
// navigates to that month. Confined to month view — week/day anchors must
|
|
241
|
-
// not jump when a cell from an adjacent month is picked.
|
|
242
|
+
// not jump when a cell from an adjacent month is picked. The anchor is
|
|
243
|
+
// clamped to [minDate, maxDate] (a spill day at the edge of a partially
|
|
244
|
+
// out-of-range month must not push referenceDate past the bound), and
|
|
245
|
+
// onMonthChange reports the landed month so it stays consistent with it.
|
|
242
246
|
if (
|
|
243
247
|
view === 'month' &&
|
|
244
248
|
(date.getMonth() !== displayedMonth || date.getFullYear() !== displayedYear)
|
|
245
249
|
) {
|
|
246
|
-
|
|
247
|
-
|
|
250
|
+
const anchor = clampDate(new Date(date.getFullYear(), date.getMonth(), 1), minDate, maxDate);
|
|
251
|
+
referenceDate = anchor;
|
|
252
|
+
onMonthChange?.(anchor.getMonth(), anchor.getFullYear());
|
|
248
253
|
}
|
|
249
254
|
onDateClick?.(date);
|
|
250
255
|
const next = selection as CalendarProps['value'];
|
|
@@ -260,8 +265,8 @@
|
|
|
260
265
|
$effect(() => {
|
|
261
266
|
const _current = controller.today;
|
|
262
267
|
if (typeof window === 'undefined') return;
|
|
263
|
-
const now = new Date();
|
|
264
|
-
const midnight = new Date(now);
|
|
268
|
+
const now = new Date();
|
|
269
|
+
const midnight = new Date(now);
|
|
265
270
|
midnight.setHours(24, 0, 0, 0);
|
|
266
271
|
const msUntilMidnight = midnight.getTime() - now.getTime();
|
|
267
272
|
const timeout = setTimeout(() => controller.refreshToday(), msUntilMidnight);
|
|
@@ -286,12 +291,11 @@
|
|
|
286
291
|
// --- Derived: visible range for recurrence expansion ---
|
|
287
292
|
const visibleRange = $derived.by(() => {
|
|
288
293
|
if (view === 'year') {
|
|
289
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
290
294
|
return { start: new Date(displayedYear, 0, 1), end: new Date(displayedYear, 11, 31) };
|
|
291
295
|
}
|
|
292
296
|
if (view === 'agenda') {
|
|
293
|
-
const start = new Date(displayedYear, displayedMonth, 1);
|
|
294
|
-
const end = new Date(start);
|
|
297
|
+
const start = new Date(displayedYear, displayedMonth, 1);
|
|
298
|
+
const end = new Date(start);
|
|
295
299
|
end.setDate(end.getDate() + agendaDays);
|
|
296
300
|
return { start, end };
|
|
297
301
|
}
|
|
@@ -378,51 +382,60 @@
|
|
|
378
382
|
}
|
|
379
383
|
}
|
|
380
384
|
|
|
385
|
+
// Move `referenceDate` to a bounded month/year and return the clamped result.
|
|
386
|
+
// clampMonth keeps the month within [minDate, maxDate]; the day-of-month is
|
|
387
|
+
// preserved (so a later switch to week/day view anchors on a real in-month
|
|
388
|
+
// day, not the 1st's possibly-prior-month week) and then `clampDate` keeps a
|
|
389
|
+
// boundary month from landing before minDate / after maxDate. Shared by every
|
|
390
|
+
// programmatic month/year jump so none can escape the navigable range.
|
|
391
|
+
function setReferenceMonth(month: number, year: number): { month: number; year: number } {
|
|
392
|
+
const clamped = clampMonth(month, year, minDate, maxDate);
|
|
393
|
+
const day = Math.min(referenceDate.getDate(), daysInMonth(clamped.year, clamped.month));
|
|
394
|
+
referenceDate = clampDate(new Date(clamped.year, clamped.month, day), minDate, maxDate);
|
|
395
|
+
return clamped;
|
|
396
|
+
}
|
|
397
|
+
|
|
381
398
|
function navigateMonth(delta: number) {
|
|
382
399
|
const total = displayedYear * 12 + displayedMonth + delta;
|
|
383
400
|
const targetYear = Math.floor(total / 12);
|
|
384
401
|
const targetMonth = ((total % 12) + 12) % 12;
|
|
385
|
-
const clamped = clampMonth(targetMonth, targetYear, minDate, maxDate);
|
|
386
402
|
controller.navDirection = delta > 0 ? 'forward' : 'backward';
|
|
387
|
-
|
|
388
|
-
// anchors on a real in-month day, not the 1st's (possibly prior-month) week.
|
|
389
|
-
const day = Math.min(referenceDate.getDate(), daysInMonth(clamped.year, clamped.month));
|
|
390
|
-
let next = new Date(clamped.year, clamped.month, day);
|
|
391
|
-
// clampMonth bounds the month; clamp the day too so it never lands before
|
|
392
|
-
// minDate / after maxDate within that boundary month.
|
|
393
|
-
if (minDate && next < stripTime(minDate)) next = stripTime(minDate);
|
|
394
|
-
if (maxDate && next > stripTime(maxDate)) next = stripTime(maxDate);
|
|
395
|
-
referenceDate = next;
|
|
403
|
+
const clamped = setReferenceMonth(targetMonth, targetYear);
|
|
396
404
|
onMonthChange?.(clamped.month, clamped.year);
|
|
397
405
|
}
|
|
398
406
|
|
|
407
|
+
// These named navigators mutate referenceDate directly (the custom-header /
|
|
408
|
+
// mini-calendar surface), bypassing controller.navigate — so they clamp to
|
|
409
|
+
// [minDate, maxDate] themselves, mirroring the clamp the controller now applies
|
|
410
|
+
// to the main swipe / keyboard / header-arrow path.
|
|
399
411
|
function navigateWeek(delta: number) {
|
|
400
412
|
controller.navDirection = delta > 0 ? 'forward' : 'backward';
|
|
401
|
-
referenceDate = addDays(referenceDate, delta * 7);
|
|
413
|
+
referenceDate = clampDate(addDays(referenceDate, delta * 7), minDate, maxDate);
|
|
402
414
|
onWeekChange?.(referenceDate);
|
|
403
415
|
}
|
|
404
416
|
|
|
405
417
|
function navigateDay(delta: number) {
|
|
406
418
|
controller.navDirection = delta > 0 ? 'forward' : 'backward';
|
|
407
|
-
referenceDate = addDays(referenceDate, delta);
|
|
419
|
+
referenceDate = clampDate(addDays(referenceDate, delta), minDate, maxDate);
|
|
408
420
|
onDayChange?.(referenceDate);
|
|
409
421
|
}
|
|
410
422
|
|
|
411
423
|
function navigateYear(delta: number) {
|
|
412
424
|
controller.navDirection = delta > 0 ? 'forward' : 'backward';
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
referenceDate = new Date(targetYear, displayedMonth, day);
|
|
416
|
-
onMonthChange?.(displayedMonth, targetYear);
|
|
425
|
+
const clamped = setReferenceMonth(displayedMonth, displayedYear + delta);
|
|
426
|
+
onMonthChange?.(clamped.month, clamped.year);
|
|
417
427
|
}
|
|
418
428
|
|
|
419
429
|
function goToToday() {
|
|
420
430
|
controller.goToToday();
|
|
421
431
|
}
|
|
422
432
|
|
|
433
|
+
// A programmatic month jump (header month/year picker, year-grid month tap,
|
|
434
|
+
// mini-calendar) reports the landed month like the arrow navigators do — the
|
|
435
|
+
// clamped result, so a bounds-corrected pick still notifies the consumer.
|
|
423
436
|
function goToMonth(month: number, year: number) {
|
|
424
|
-
const
|
|
425
|
-
|
|
437
|
+
const clamped = setReferenceMonth(month, year);
|
|
438
|
+
onMonthChange?.(clamped.month, clamped.year);
|
|
426
439
|
}
|
|
427
440
|
|
|
428
441
|
function setView(v: CalendarViewMode) {
|
|
@@ -521,6 +534,9 @@
|
|
|
521
534
|
get canGoForward() {
|
|
522
535
|
return controller.canGoForward;
|
|
523
536
|
},
|
|
537
|
+
get canGoToToday() {
|
|
538
|
+
return controller.canGoToToday;
|
|
539
|
+
},
|
|
524
540
|
get grid() {
|
|
525
541
|
return controller.cells;
|
|
526
542
|
},
|
|
@@ -654,7 +670,8 @@
|
|
|
654
670
|
navigateMonth,
|
|
655
671
|
goToToday,
|
|
656
672
|
canGoBack: controller.canGoBack,
|
|
657
|
-
canGoForward: controller.canGoForward
|
|
673
|
+
canGoForward: controller.canGoForward,
|
|
674
|
+
canGoToToday: controller.canGoToToday
|
|
658
675
|
})}
|
|
659
676
|
{:else}
|
|
660
677
|
<CalendarHeader {showViewSwitcher} />
|
|
@@ -667,7 +684,7 @@
|
|
|
667
684
|
{#if view === 'week'}
|
|
668
685
|
<CalendarWeekGrid {onEventClick} />
|
|
669
686
|
{:else if view === 'day'}
|
|
670
|
-
<CalendarDayView {
|
|
687
|
+
<CalendarDayView {onEventClick} />
|
|
671
688
|
{:else if view === 'agenda'}
|
|
672
689
|
<CalendarAgendaView {eventItem} {onEventClick} {agendaDays} />
|
|
673
690
|
{/if}
|
|
@@ -680,7 +697,7 @@
|
|
|
680
697
|
{:else if view === 'week'}
|
|
681
698
|
<CalendarWeekGrid {onEventClick} />
|
|
682
699
|
{:else if view === 'day'}
|
|
683
|
-
<CalendarDayView {
|
|
700
|
+
<CalendarDayView {onEventClick} />
|
|
684
701
|
{:else if view === 'agenda'}
|
|
685
702
|
<CalendarAgendaView {eventItem} {onEventClick} {agendaDays} />
|
|
686
703
|
{/if}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
}> = [];
|
|
39
39
|
|
|
40
40
|
for (let i = 0; i < agendaDays; i++) {
|
|
41
|
-
const d = new Date(start);
|
|
41
|
+
const d = new Date(start);
|
|
42
42
|
d.setDate(start.getDate() + i);
|
|
43
43
|
const dayEvents = ctx.getEventsWithDayInfo(d);
|
|
44
44
|
if (dayEvents.length === 0) continue;
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useBlocksI18n } from '../..';
|
|
3
|
-
import type { Snippet } from 'svelte';
|
|
4
3
|
import { fly } from 'svelte/transition';
|
|
5
4
|
import { Badge } from '../../primitives/Badge';
|
|
6
5
|
import { getCalendarContext, createSlotHelper } from './calendar.context';
|
|
7
6
|
import { formatDateFull, toIso } from '../../date';
|
|
8
7
|
import { swipeable } from '../../utils/swipeable';
|
|
9
|
-
import type { CalendarEvent
|
|
8
|
+
import type { CalendarEvent } from './calendar.types';
|
|
10
9
|
import CalendarEventRenderer from './CalendarEventRenderer.svelte';
|
|
11
10
|
import CalendarTimeGrid from './CalendarTimeGrid.svelte';
|
|
12
11
|
|
|
13
12
|
const bt = useBlocksI18n();
|
|
14
13
|
|
|
15
14
|
interface CalendarDayViewInternalProps {
|
|
16
|
-
eventItem?: Snippet<[EventItemContext]>;
|
|
17
15
|
onEventClick?: (event: CalendarEvent) => void;
|
|
18
16
|
class?: string;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
let {
|
|
19
|
+
let { onEventClick, class: className = '' }: CalendarDayViewInternalProps = $props();
|
|
22
20
|
|
|
23
21
|
const ctx = getCalendarContext();
|
|
24
22
|
const slot = createSlotHelper(ctx);
|
|
@@ -28,13 +26,10 @@
|
|
|
28
26
|
const dateLabel = $derived(formatDateFull(displayedDate, ctx.locale));
|
|
29
27
|
const dayKey = $derived(toIso(displayedDate));
|
|
30
28
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
35
|
-
const timedEventCount = $derived(
|
|
36
|
-
ctx.showTimeGrid ? eventsWithInfo.filter((info) => info.event.allDay === false).length : 0
|
|
37
|
-
);
|
|
29
|
+
// The day view always renders as an hour grid (showTimeGrid is always true
|
|
30
|
+
// for day). All-day events show in a band above the grid; timed events flow
|
|
31
|
+
// through CalendarTimeGrid — no custom `eventItem` here, matching WeekGrid.
|
|
32
|
+
const allDayEvents = $derived(eventsWithInfo.filter((info) => info.event.allDay !== false));
|
|
38
33
|
const totalEventCount = $derived(eventsWithInfo.length);
|
|
39
34
|
|
|
40
35
|
function handleKeydown(e: KeyboardEvent) {
|
|
@@ -83,37 +78,22 @@
|
|
|
83
78
|
{/if}
|
|
84
79
|
</div>
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{#each allDayEvents as info (info.event.id)}
|
|
94
|
-
<CalendarEventRenderer {info} {eventItem} {onEventClick} />
|
|
95
|
-
{/each}
|
|
96
|
-
</div>
|
|
81
|
+
<!-- All-day events -->
|
|
82
|
+
{#if allDayEvents.length > 0}
|
|
83
|
+
<div class={slot('allDayArea')}>
|
|
84
|
+
<div class="flex flex-col gap-1">
|
|
85
|
+
{#each allDayEvents as info (info.event.id)}
|
|
86
|
+
<CalendarEventRenderer {info} {onEventClick} />
|
|
87
|
+
{/each}
|
|
97
88
|
</div>
|
|
98
|
-
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
<!-- Time grid -->
|
|
93
|
+
<CalendarTimeGrid dates={[displayedDate]} {onEventClick} />
|
|
102
94
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
{/if}
|
|
106
|
-
{:else}
|
|
107
|
-
<!-- Original list mode -->
|
|
108
|
-
<div class={slot('dayEventList')} aria-live="polite" aria-label={bt('calendar.events')}>
|
|
109
|
-
{#if eventsWithInfo.length === 0}
|
|
110
|
-
<div class={slot('empty')}>{bt('calendar.noEvents')}</div>
|
|
111
|
-
{:else}
|
|
112
|
-
{#each eventsWithInfo as info (info.event.id)}
|
|
113
|
-
<CalendarEventRenderer {info} {eventItem} {onEventClick} />
|
|
114
|
-
{/each}
|
|
115
|
-
{/if}
|
|
116
|
-
</div>
|
|
95
|
+
{#if totalEventCount === 0}
|
|
96
|
+
<div class={slot('empty')}>{bt('calendar.noEvents')}</div>
|
|
117
97
|
{/if}
|
|
118
98
|
</div>
|
|
119
99
|
{/key}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { CalendarEvent, EventItemContext } from './calendar.types.js';
|
|
1
|
+
import type { CalendarEvent } from './calendar.types.js';
|
|
3
2
|
interface CalendarDayViewInternalProps {
|
|
4
|
-
eventItem?: Snippet<[EventItemContext]>;
|
|
5
3
|
onEventClick?: (event: CalendarEvent) => void;
|
|
6
4
|
class?: string;
|
|
7
5
|
}
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
if (!targetDate || !ctx.onEventMove) return;
|
|
47
47
|
const eventDuration = event.end ? event.end.getTime() - event.start.getTime() : 0;
|
|
48
48
|
// Local computation — Date mutation is confined to this scope, not $state.
|
|
49
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
50
49
|
const newStart = new Date(targetDate);
|
|
51
50
|
newStart.setHours(event.start.getHours(), event.start.getMinutes(), event.start.getSeconds());
|
|
52
51
|
const newEnd =
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
// Map event IDs to events for quick lookup
|
|
33
33
|
const eventById = $derived.by(() => {
|
|
34
|
-
const map = new Map<string, CalendarEvent>();
|
|
34
|
+
const map = new Map<string, CalendarEvent>();
|
|
35
35
|
for (const e of ctx.events) {
|
|
36
36
|
map.set(e.id, e);
|
|
37
37
|
}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
const barGap = 2;
|
|
43
43
|
|
|
44
44
|
// Keyboard navigation handler
|
|
45
|
-
|
|
45
|
+
// Local Date copies for keyboard nav — not reactive state.
|
|
46
46
|
function handleKeydown(e: KeyboardEvent) {
|
|
47
47
|
const current = new Date(ctx.focusedDate);
|
|
48
48
|
let newDate: Date | null = null;
|
|
@@ -103,18 +103,22 @@
|
|
|
103
103
|
|
|
104
104
|
if (newDate) {
|
|
105
105
|
e.preventDefault();
|
|
106
|
+
// Capture the grid while the event is still dispatching: `e.currentTarget` is
|
|
107
|
+
// null inside the rAF callback a frame later (the browser clears it after
|
|
108
|
+
// dispatch). CalendarDay uses pure roving tabindex with no isFocused-driven
|
|
109
|
+
// .focus(), so this rAF is the only thing that moves real DOM focus onto the
|
|
110
|
+
// landed day — mirrors DateGridScaffold's bound-ref approach.
|
|
111
|
+
const grid = e.currentTarget as HTMLElement;
|
|
106
112
|
ctx.setFocusedDate(newDate);
|
|
107
113
|
|
|
108
114
|
requestAnimationFrame(() => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
)
|
|
113
|
-
button?.focus();
|
|
115
|
+
// setFocusedDate clamps to [minDate, maxDate], so focus the *landed* day (the
|
|
116
|
+
// one that took roving tabindex), not the raw requested newDate.
|
|
117
|
+
const dateStr = toIso(ctx.focusedDate);
|
|
118
|
+
grid.querySelector<HTMLElement>(`[data-date="${dateStr}"]`)?.focus();
|
|
114
119
|
});
|
|
115
120
|
}
|
|
116
121
|
}
|
|
117
|
-
/* eslint-enable svelte/prefer-svelte-reactivity */
|
|
118
122
|
</script>
|
|
119
123
|
|
|
120
124
|
<div
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
|
|
93
93
|
// Month/Year Quick-Pick
|
|
94
94
|
let monthPickerOpen = $state(false);
|
|
95
|
-
//
|
|
95
|
+
// Writable state (not $derived): needs both external sync and local mutation.
|
|
96
96
|
let pickerYear = $state(ctx.displayedYear);
|
|
97
97
|
|
|
98
98
|
// Keep pickerYear in sync when navigation changes
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
mint="none"
|
|
227
227
|
class="{slot('navButton')} ml-1"
|
|
228
228
|
onclick={() => ctx.goToToday()}
|
|
229
|
-
disabled={ctx.disabled}
|
|
229
|
+
disabled={!ctx.canGoToToday || ctx.disabled}
|
|
230
230
|
aria-label={bt('calendar.today')}
|
|
231
231
|
>
|
|
232
232
|
<CalendarIcon size={navIconSize} />
|
|
@@ -71,7 +71,6 @@
|
|
|
71
71
|
|
|
72
72
|
let newDate: Date | null = null;
|
|
73
73
|
|
|
74
|
-
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
75
74
|
switch (e.key) {
|
|
76
75
|
case 'ArrowRight':
|
|
77
76
|
newDate = new Date(focusedMiniDate);
|
|
@@ -97,7 +96,6 @@
|
|
|
97
96
|
default:
|
|
98
97
|
return;
|
|
99
98
|
}
|
|
100
|
-
/* eslint-enable svelte/prefer-svelte-reactivity */
|
|
101
99
|
|
|
102
100
|
if (newDate) {
|
|
103
101
|
e.preventDefault();
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
function handleDragEnd(targetDate: Date | null) {
|
|
91
91
|
if (!targetDate || !ctx.onEventMove) return;
|
|
92
92
|
const eventDuration = event.end ? event.end.getTime() - event.start.getTime() : 60 * 60 * 1000;
|
|
93
|
-
const newStart = new Date(targetDate);
|
|
93
|
+
const newStart = new Date(targetDate);
|
|
94
94
|
newStart.setHours(event.start.getHours(), event.start.getMinutes(), event.start.getSeconds());
|
|
95
95
|
const newEnd = new Date(newStart.getTime() + eventDuration);
|
|
96
96
|
ctx.onEventMove(event, newStart, newEnd);
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
const positionedByDate = $derived.by(() => {
|
|
29
29
|
// Local computation inside $derived — the returned Map is read-only
|
|
30
30
|
// downstream, so SvelteMap's reactive wrapping is unnecessary.
|
|
31
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
32
31
|
const map = new Map<string, PositionedEvent[]>();
|
|
33
32
|
for (const date of dates) {
|
|
34
33
|
const key = toIso(date);
|
|
@@ -72,12 +71,10 @@
|
|
|
72
71
|
function handleSlotClick(date: Date, slotHour: number, slotMinute: number) {
|
|
73
72
|
if (!ctx.onTimeSlotCreate) return;
|
|
74
73
|
// Local Date math handed straight to a callback — not reactive state.
|
|
75
|
-
/* eslint-disable svelte/prefer-svelte-reactivity */
|
|
76
74
|
const start = new Date(date);
|
|
77
75
|
start.setHours(slotHour, slotMinute, 0, 0);
|
|
78
76
|
const end = new Date(start);
|
|
79
77
|
end.setHours(start.getHours() + 1);
|
|
80
|
-
/* eslint-enable svelte/prefer-svelte-reactivity */
|
|
81
78
|
ctx.onTimeSlotCreate(start, end);
|
|
82
79
|
}
|
|
83
80
|
</script>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
// All-day events per date, drawn in the band above the hour grid.
|
|
29
29
|
const allDayByDate = $derived.by(() => {
|
|
30
|
-
const map = new Map<string, Array<{ event: CalendarEvent; color: string }>>();
|
|
30
|
+
const map = new Map<string, Array<{ event: CalendarEvent; color: string }>>();
|
|
31
31
|
for (const date of ctx.weekDates) {
|
|
32
32
|
const key = toIso(date);
|
|
33
33
|
const allDay = ctx
|
|
@@ -58,7 +58,6 @@ export declare const calendarVariants: (props?: import("../../utils/variants.js"
|
|
|
58
58
|
multiDayBar: string;
|
|
59
59
|
dayView: string;
|
|
60
60
|
dayViewHeader: string;
|
|
61
|
-
dayEventList: string;
|
|
62
61
|
agendaView: string;
|
|
63
62
|
agendaDayGroup: string;
|
|
64
63
|
agendaDayHeader: string;
|
|
@@ -112,7 +111,6 @@ export declare const calendarVariants: (props?: import("../../utils/variants.js"
|
|
|
112
111
|
multiDayBar: string;
|
|
113
112
|
dayView: string;
|
|
114
113
|
dayViewHeader: string;
|
|
115
|
-
dayEventList: string;
|
|
116
114
|
agendaView: string;
|
|
117
115
|
agendaDayGroup: string;
|
|
118
116
|
agendaDayHeader: string;
|
|
@@ -166,7 +164,6 @@ export declare const calendarVariants: (props?: import("../../utils/variants.js"
|
|
|
166
164
|
multiDayBar: string;
|
|
167
165
|
dayView: string;
|
|
168
166
|
dayViewHeader: string;
|
|
169
|
-
dayEventList: string;
|
|
170
167
|
agendaView: string;
|
|
171
168
|
agendaDayGroup: string;
|
|
172
169
|
agendaDayHeader: string;
|
|
@@ -613,15 +610,6 @@ export declare const calendarVariants: (props?: import("../../utils/variants.js"
|
|
|
613
610
|
class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
|
|
614
611
|
className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
|
|
615
612
|
}) | undefined) => string;
|
|
616
|
-
dayEventList: (props?: ({
|
|
617
|
-
variant?: "default" | "bordered" | "ghost" | undefined;
|
|
618
|
-
size?: "sm" | "md" | "lg" | undefined;
|
|
619
|
-
dayState?: "default" | "today" | "selected" | "todaySelected" | "rangeStart" | "rangeEnd" | "inRange" | "previewRange" | "previewRangeEnd" | "outsideMonth" | "disabled" | undefined;
|
|
620
|
-
hasEvents?: boolean | undefined;
|
|
621
|
-
} & {
|
|
622
|
-
class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
|
|
623
|
-
className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
|
|
624
|
-
}) | undefined) => string;
|
|
625
613
|
agendaView: (props?: ({
|
|
626
614
|
variant?: "default" | "bordered" | "ghost" | undefined;
|
|
627
615
|
size?: "sm" | "md" | "lg" | undefined;
|
|
@@ -92,7 +92,6 @@ export const calendarVariants = tv({
|
|
|
92
92
|
// Day view
|
|
93
93
|
dayView: 'flex flex-col',
|
|
94
94
|
dayViewHeader: 'font-semibold text-text-primary',
|
|
95
|
-
dayEventList: 'flex flex-col',
|
|
96
95
|
// Agenda view
|
|
97
96
|
agendaView: 'flex flex-col',
|
|
98
97
|
agendaDayGroup: 'flex flex-col',
|
|
@@ -204,7 +203,6 @@ export const calendarVariants = tv({
|
|
|
204
203
|
multiDayBar: 'text-2xs',
|
|
205
204
|
dayView: 'gap-2 mt-2',
|
|
206
205
|
dayViewHeader: 'text-sm mb-1',
|
|
207
|
-
dayEventList: 'gap-1.5',
|
|
208
206
|
agendaView: 'gap-2 mt-2',
|
|
209
207
|
agendaDayGroup: 'gap-1',
|
|
210
208
|
agendaDayHeader: 'text-xs py-1 px-2',
|
|
@@ -258,7 +256,6 @@ export const calendarVariants = tv({
|
|
|
258
256
|
multiDayBar: 'text-xs',
|
|
259
257
|
dayView: 'gap-3 mt-3',
|
|
260
258
|
dayViewHeader: 'text-base mb-1.5',
|
|
261
|
-
dayEventList: 'gap-2',
|
|
262
259
|
agendaView: 'gap-3 mt-3',
|
|
263
260
|
agendaDayGroup: 'gap-1.5',
|
|
264
261
|
agendaDayHeader: 'text-sm py-1.5 px-3',
|
|
@@ -312,7 +309,6 @@ export const calendarVariants = tv({
|
|
|
312
309
|
multiDayBar: 'text-sm',
|
|
313
310
|
dayView: 'gap-4 mt-4',
|
|
314
311
|
dayViewHeader: 'text-lg mb-2',
|
|
315
|
-
dayEventList: 'gap-3',
|
|
316
312
|
agendaView: 'gap-4 mt-4',
|
|
317
313
|
agendaDayGroup: 'gap-2',
|
|
318
314
|
agendaDayHeader: 'text-base py-2 px-4',
|
|
@@ -3,7 +3,7 @@ import type { HTMLAttributes } from 'svelte/elements';
|
|
|
3
3
|
import type { MintProp } from '../../mint/index.js';
|
|
4
4
|
import type { CalendarEvent, CalendarEventCategory, CalendarSelection, CalendarViewMode, DayCellContext, EventItemContext, HeaderContext } from './calendar.types.js';
|
|
5
5
|
import type { CalendarSlots, CalendarVariants } from './calendar.variants.js';
|
|
6
|
-
export type CalendarSlotName = 'base' | 'header' | 'title' | 'nav' | 'navButton' | 'grid' | 'weekdayHeader' | 'weekday' | 'weekRow' | 'weekNumber' | 'day' | 'dayNumber' | 'dotContainer' | 'dot' | 'list' | 'dateHeader' | 'empty' | 'item' | 'colorBar' | 'eventTitle' | 'eventDescription' | 'eventHelper' | 'legend' | 'legendItem' | 'legendDot' | 'legendLabel' | 'yearGrid' | 'yearMonth' | 'yearMonthTitle' | 'yearMiniDay' | 'yearMiniDot' | 'weekGrid' | 'weekColumn' | 'weekColumnHeader' | 'weekColumnDayName' | 'weekColumnDayNumber' | 'weekEventList' | 'weekAllDayEvent' | 'multiDayBar' | 'multiDayBarContainer' | 'agendaView' | 'agendaDayGroup' | 'agendaDayHeader' | 'agendaEventList' | 'dayView' | 'dayViewHeader' | '
|
|
6
|
+
export type CalendarSlotName = 'base' | 'header' | 'title' | 'nav' | 'navButton' | 'grid' | 'weekdayHeader' | 'weekday' | 'weekRow' | 'weekNumber' | 'day' | 'dayNumber' | 'dotContainer' | 'dot' | 'list' | 'dateHeader' | 'empty' | 'item' | 'colorBar' | 'eventTitle' | 'eventDescription' | 'eventHelper' | 'legend' | 'legendItem' | 'legendDot' | 'legendLabel' | 'yearGrid' | 'yearMonth' | 'yearMonthTitle' | 'yearMiniDay' | 'yearMiniDot' | 'weekGrid' | 'weekColumn' | 'weekColumnHeader' | 'weekColumnDayName' | 'weekColumnDayNumber' | 'weekEventList' | 'weekAllDayEvent' | 'multiDayBar' | 'multiDayBarContainer' | 'agendaView' | 'agendaDayGroup' | 'agendaDayHeader' | 'agendaEventList' | 'dayView' | 'dayViewHeader' | 'timeGrid' | 'timeLabel' | 'timeSlotRow' | 'timeDayColumn' | 'timeEvent' | 'allDayArea' | 'currentTimeLine' | 'weekTimeLayout' | 'eventPopover' | 'eventPopoverItem' | 'miniCalendar' | 'miniCalendarHeader' | 'miniCalendarTitle' | 'miniCalendarNavButton' | 'miniCalendarWeekday' | 'miniCalendarDay';
|
|
7
7
|
/**
|
|
8
8
|
* @description Flexible calendar component with month, year, week, and day views.
|
|
9
9
|
* Renders timed appointments, multi-day spans and recurrence on a time grid, with
|
|
@@ -118,7 +118,11 @@ export interface CalendarProps extends Omit<CalendarVariants, 'dayState' | 'hasE
|
|
|
118
118
|
onTimeSlotCreate?: (start: Date, end: Date) => void;
|
|
119
119
|
/** Custom snippet for rendering a day cell. */
|
|
120
120
|
dayCell?: Snippet<[DayCellContext]>;
|
|
121
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* Custom snippet for rendering an event item in the list-based views (agenda
|
|
123
|
+
* and the month event list). Time-grid views (week/day) render events through
|
|
124
|
+
* their hour grid and ignore this snippet.
|
|
125
|
+
*/
|
|
122
126
|
eventItem?: Snippet<[EventItemContext]>;
|
|
123
127
|
/** Custom snippet for the header area. */
|
|
124
128
|
header?: Snippet<[HeaderContext]>;
|