@spatika/react 1.1.0 → 1.3.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/AGENTS.md +71 -0
- package/README.md +4 -3
- package/dist/composites/Card.d.ts +7 -1
- package/dist/composites/Card.d.ts.map +1 -1
- package/dist/composites/Card.js +10 -0
- package/dist/composites/ChartDataGrid.d.ts +33 -0
- package/dist/composites/ChartDataGrid.d.ts.map +1 -0
- package/dist/composites/ChartDataGrid.js +77 -0
- package/dist/composites/CoverHero.d.ts +1 -1
- package/dist/composites/CoverHero.d.ts.map +1 -1
- package/dist/composites/CoverHero.js +2 -2
- package/dist/composites/EntityMediaCard.d.ts +1 -1
- package/dist/composites/EntityMediaCard.d.ts.map +1 -1
- package/dist/composites/EntityMediaCard.js +12 -4
- package/dist/composites/EventCalendar.d.ts +72 -0
- package/dist/composites/EventCalendar.d.ts.map +1 -0
- package/dist/composites/EventCalendar.js +371 -0
- package/dist/composites/EventEditor.d.ts +22 -0
- package/dist/composites/EventEditor.d.ts.map +1 -0
- package/dist/composites/EventEditor.js +86 -0
- package/dist/composites/EventTimeline.d.ts +29 -0
- package/dist/composites/EventTimeline.d.ts.map +1 -0
- package/dist/composites/EventTimeline.js +186 -0
- package/dist/composites/MapChart.d.ts +24 -0
- package/dist/composites/MapChart.d.ts.map +1 -0
- package/dist/composites/MapChart.js +58 -0
- package/dist/composites/ProfileHero.d.ts +6 -4
- package/dist/composites/ProfileHero.d.ts.map +1 -1
- package/dist/composites/ProfileHero.js +8 -8
- package/dist/composites/ScatterWebGL.d.ts +22 -0
- package/dist/composites/ScatterWebGL.d.ts.map +1 -0
- package/dist/composites/ScatterWebGL.js +45 -0
- package/dist/composites/SiteFooter.d.ts +1 -1
- package/dist/composites/SiteFooter.js +1 -1
- package/dist/composites/SiteNav.d.ts +7 -2
- package/dist/composites/SiteNav.d.ts.map +1 -1
- package/dist/composites/SiteNav.js +9 -5
- package/dist/composites/StatCard.d.ts +2 -1
- package/dist/composites/StatCard.d.ts.map +1 -1
- package/dist/composites/StatCard.js +3 -3
- package/dist/composites/StatusDot.d.ts +1 -1
- package/dist/composites/Wordmark.d.ts +2 -2
- package/dist/composites/Wordmark.d.ts.map +1 -1
- package/dist/composites/cartesian-charts.d.ts +138 -0
- package/dist/composites/cartesian-charts.d.ts.map +1 -0
- package/dist/composites/cartesian-charts.js +401 -0
- package/dist/composites/chart-composition.d.ts +139 -0
- package/dist/composites/chart-composition.d.ts.map +1 -0
- package/dist/composites/chart-composition.js +486 -0
- package/dist/composites/chart-ui.d.ts +83 -0
- package/dist/composites/chart-ui.d.ts.map +1 -0
- package/dist/composites/chart-ui.js +108 -0
- package/dist/composites/charts-3d.d.ts +37 -0
- package/dist/composites/charts-3d.d.ts.map +1 -0
- package/dist/composites/charts-3d.js +121 -0
- package/dist/composites/flow-charts.d.ts +89 -0
- package/dist/composites/flow-charts.d.ts.map +1 -0
- package/dist/composites/flow-charts.js +186 -0
- package/dist/composites/radial-charts.d.ts +109 -0
- package/dist/composites/radial-charts.d.ts.map +1 -0
- package/dist/composites/radial-charts.js +176 -0
- package/dist/composites/scheduler-ui.d.ts +44 -0
- package/dist/composites/scheduler-ui.d.ts.map +1 -0
- package/dist/composites/scheduler-ui.js +71 -0
- package/dist/index.d.ts +68 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -7
- package/dist/lib/chart-export.d.ts +10 -0
- package/dist/lib/chart-export.d.ts.map +1 -0
- package/dist/lib/chart-export.js +102 -0
- package/dist/lib/charts.d.ts +235 -0
- package/dist/lib/charts.d.ts.map +1 -0
- package/dist/lib/charts.js +744 -0
- package/dist/lib/geo.d.ts +39 -0
- package/dist/lib/geo.d.ts.map +1 -0
- package/dist/lib/geo.js +101 -0
- package/dist/lib/iso.d.ts +21 -0
- package/dist/lib/iso.d.ts.map +1 -0
- package/dist/lib/iso.js +56 -0
- package/dist/lib/rrule.d.ts +20 -0
- package/dist/lib/rrule.d.ts.map +1 -0
- package/dist/lib/rrule.js +236 -0
- package/dist/lib/scheduler.d.ts +156 -0
- package/dist/lib/scheduler.d.ts.map +1 -0
- package/dist/lib/scheduler.js +502 -0
- package/dist/lib/webgl-scatter.d.ts +44 -0
- package/dist/lib/webgl-scatter.d.ts.map +1 -0
- package/dist/lib/webgl-scatter.js +185 -0
- package/dist/primitives/Accordion.d.ts +44 -20
- package/dist/primitives/Accordion.d.ts.map +1 -1
- package/dist/primitives/Accordion.js +82 -22
- package/dist/primitives/AppBar.d.ts +13 -0
- package/dist/primitives/AppBar.d.ts.map +1 -0
- package/dist/primitives/AppBar.js +27 -0
- package/dist/primitives/Autocomplete.d.ts +28 -0
- package/dist/primitives/Autocomplete.d.ts.map +1 -0
- package/dist/primitives/Autocomplete.js +118 -0
- package/dist/primitives/Avatar.d.ts +13 -1
- package/dist/primitives/Avatar.d.ts.map +1 -1
- package/dist/primitives/Avatar.js +17 -2
- package/dist/primitives/Backdrop.d.ts +17 -0
- package/dist/primitives/Backdrop.d.ts.map +1 -0
- package/dist/primitives/Backdrop.js +17 -0
- package/dist/primitives/BottomNavigation.d.ts +23 -0
- package/dist/primitives/BottomNavigation.d.ts.map +1 -0
- package/dist/primitives/BottomNavigation.js +30 -0
- package/dist/primitives/ButtonBase.d.ts +14 -0
- package/dist/primitives/ButtonBase.d.ts.map +1 -0
- package/dist/primitives/ButtonBase.js +13 -0
- package/dist/primitives/ButtonGroup.d.ts +25 -0
- package/dist/primitives/ButtonGroup.d.ts.map +1 -0
- package/dist/primitives/ButtonGroup.js +44 -0
- package/dist/primitives/CircularProgress.d.ts +16 -0
- package/dist/primitives/CircularProgress.d.ts.map +1 -0
- package/dist/primitives/CircularProgress.js +17 -0
- package/dist/primitives/ClickAwayListener.d.ts +16 -0
- package/dist/primitives/ClickAwayListener.d.ts.map +1 -0
- package/dist/primitives/ClickAwayListener.js +33 -0
- package/dist/primitives/Collapse.d.ts +19 -0
- package/dist/primitives/Collapse.d.ts.map +1 -0
- package/dist/primitives/Collapse.js +14 -0
- package/dist/primitives/Container.d.ts +13 -0
- package/dist/primitives/Container.d.ts.map +1 -0
- package/dist/primitives/Container.js +26 -0
- package/dist/primitives/Fab.d.ts +22 -0
- package/dist/primitives/Fab.d.ts.map +1 -0
- package/dist/primitives/Fab.js +42 -0
- package/dist/primitives/FormControl.d.ts +27 -0
- package/dist/primitives/FormControl.d.ts.map +1 -0
- package/dist/primitives/FormControl.js +24 -0
- package/dist/primitives/FormControlLabel.d.ts +18 -0
- package/dist/primitives/FormControlLabel.d.ts.map +1 -0
- package/dist/primitives/FormControlLabel.js +19 -0
- package/dist/primitives/Grid.d.ts +32 -0
- package/dist/primitives/Grid.d.ts.map +1 -0
- package/dist/primitives/Grid.js +50 -0
- package/dist/primitives/IconButton.d.ts +22 -0
- package/dist/primitives/IconButton.d.ts.map +1 -0
- package/dist/primitives/IconButton.js +39 -0
- package/dist/primitives/ImageList.d.ts +31 -0
- package/dist/primitives/ImageList.d.ts.map +1 -0
- package/dist/primitives/ImageList.js +33 -0
- package/dist/primitives/InputAdornment.d.ts +28 -0
- package/dist/primitives/InputAdornment.d.ts.map +1 -0
- package/dist/primitives/InputAdornment.js +13 -0
- package/dist/primitives/Link.d.ts +17 -0
- package/dist/primitives/Link.d.ts.map +1 -0
- package/dist/primitives/Link.js +29 -0
- package/dist/primitives/List.d.ts +41 -0
- package/dist/primitives/List.d.ts.map +1 -0
- package/dist/primitives/List.js +22 -0
- package/dist/primitives/Masonry.d.ts +14 -0
- package/dist/primitives/Masonry.d.ts.map +1 -0
- package/dist/primitives/Masonry.js +15 -0
- package/dist/primitives/Menu.d.ts +28 -0
- package/dist/primitives/Menu.d.ts.map +1 -0
- package/dist/primitives/Menu.js +54 -0
- package/dist/primitives/MobileStepper.d.ts +15 -0
- package/dist/primitives/MobileStepper.d.ts.map +1 -0
- package/dist/primitives/MobileStepper.js +8 -0
- package/dist/primitives/Modal.d.ts +18 -0
- package/dist/primitives/Modal.d.ts.map +1 -0
- package/dist/primitives/Modal.js +29 -0
- package/dist/primitives/NoSsr.d.ts +15 -0
- package/dist/primitives/NoSsr.d.ts.map +1 -0
- package/dist/primitives/NoSsr.js +19 -0
- package/dist/primitives/Paper.d.ts +22 -0
- package/dist/primitives/Paper.d.ts.map +1 -0
- package/dist/primitives/Paper.js +33 -0
- package/dist/primitives/Progress.d.ts +6 -2
- package/dist/primitives/Progress.d.ts.map +1 -1
- package/dist/primitives/Progress.js +8 -3
- package/dist/primitives/Snackbar.d.ts +23 -0
- package/dist/primitives/Snackbar.d.ts.map +1 -0
- package/dist/primitives/Snackbar.js +33 -0
- package/dist/primitives/SpeedDial.d.ts +26 -0
- package/dist/primitives/SpeedDial.d.ts.map +1 -0
- package/dist/primitives/SpeedDial.js +26 -0
- package/dist/primitives/Stack.d.ts +15 -0
- package/dist/primitives/Stack.d.ts.map +1 -0
- package/dist/primitives/Stack.js +45 -0
- package/dist/primitives/StarRating.d.ts +19 -1
- package/dist/primitives/StarRating.d.ts.map +1 -1
- package/dist/primitives/StarRating.js +36 -6
- package/dist/primitives/Stepper.d.ts +36 -0
- package/dist/primitives/Stepper.d.ts.map +1 -0
- package/dist/primitives/Stepper.js +53 -0
- package/dist/primitives/TablePagination.d.ts +16 -0
- package/dist/primitives/TablePagination.d.ts.map +1 -0
- package/dist/primitives/TablePagination.js +12 -0
- package/dist/primitives/TextField.d.ts +24 -0
- package/dist/primitives/TextField.d.ts.map +1 -0
- package/dist/primitives/TextField.js +19 -0
- package/dist/primitives/TextareaAutosize.d.ts +11 -0
- package/dist/primitives/TextareaAutosize.d.ts.map +1 -0
- package/dist/primitives/TextareaAutosize.js +26 -0
- package/dist/primitives/ToggleButton.d.ts +28 -0
- package/dist/primitives/ToggleButton.d.ts.map +1 -0
- package/dist/primitives/ToggleButton.js +56 -0
- package/dist/primitives/TransferList.d.ts +21 -0
- package/dist/primitives/TransferList.d.ts.map +1 -0
- package/dist/primitives/TransferList.js +40 -0
- package/dist/primitives/Typography.d.ts +23 -0
- package/dist/primitives/Typography.d.ts.map +1 -0
- package/dist/primitives/Typography.js +69 -0
- package/dist/primitives/transitions.d.ts +29 -0
- package/dist/primitives/transitions.d.ts.map +1 -0
- package/dist/primitives/transitions.js +52 -0
- package/package.json +28 -4
- package/skills/spatika-ui/SKILL.md +96 -0
- package/src/composites/Card.tsx +51 -1
- package/src/composites/ChartDataGrid.test.tsx +57 -0
- package/src/composites/ChartDataGrid.tsx +196 -0
- package/src/composites/CoverHero.test.tsx +38 -0
- package/src/composites/CoverHero.tsx +29 -22
- package/src/composites/EntityMediaCard.test.tsx +58 -0
- package/src/composites/EntityMediaCard.tsx +31 -13
- package/src/composites/EventCalendar.tsx +1030 -0
- package/src/composites/EventEditor.tsx +268 -0
- package/src/composites/EventTimeline.tsx +366 -0
- package/src/composites/MapChart.test.tsx +39 -0
- package/src/composites/MapChart.tsx +130 -0
- package/src/composites/ProfileHero.test.tsx +33 -0
- package/src/composites/ProfileHero.tsx +96 -79
- package/src/composites/ScatterWebGL.test.tsx +25 -0
- package/src/composites/ScatterWebGL.tsx +79 -0
- package/src/composites/SiteFooter.test.tsx +37 -0
- package/src/composites/SiteFooter.tsx +1 -1
- package/src/composites/SiteNav.test.tsx +46 -0
- package/src/composites/SiteNav.tsx +14 -4
- package/src/composites/StatCard.test.tsx +30 -0
- package/src/composites/StatCard.tsx +4 -0
- package/src/composites/Wordmark.test.tsx +23 -0
- package/src/composites/Wordmark.tsx +2 -2
- package/src/composites/cartesian-charts.test.tsx +52 -0
- package/src/composites/cartesian-charts.tsx +1287 -0
- package/src/composites/chart-composition.test.tsx +53 -0
- package/src/composites/chart-composition.tsx +898 -0
- package/src/composites/chart-ui.tsx +328 -0
- package/src/composites/charts-3d.test.tsx +25 -0
- package/src/composites/charts-3d.tsx +318 -0
- package/src/composites/charts.test.tsx +254 -0
- package/src/composites/chrome.test.tsx +29 -0
- package/src/composites/flow-charts.test.tsx +79 -0
- package/src/composites/flow-charts.tsx +607 -0
- package/src/composites/marketing.test.tsx +38 -34
- package/src/composites/radial-charts.test.tsx +74 -0
- package/src/composites/radial-charts.tsx +617 -0
- package/src/composites/scheduler-ui.tsx +306 -0
- package/src/composites/scheduler.test.tsx +280 -0
- package/src/index.test.ts +26 -0
- package/src/index.ts +287 -5
- package/src/lib/chart-export.test.ts +20 -0
- package/src/lib/chart-export.ts +99 -0
- package/src/lib/charts-platform.test.ts +93 -0
- package/src/lib/charts.test.ts +132 -0
- package/src/lib/charts.ts +1021 -0
- package/src/lib/geo.test.ts +42 -0
- package/src/lib/geo.ts +138 -0
- package/src/lib/iso.test.ts +22 -0
- package/src/lib/iso.ts +102 -0
- package/src/lib/rrule.test.ts +53 -0
- package/src/lib/rrule.ts +281 -0
- package/src/lib/scheduler.test.ts +199 -0
- package/src/lib/scheduler.ts +693 -0
- package/src/lib/tokens.test.ts +2 -2
- package/src/lib/webgl-scatter.test.ts +36 -0
- package/src/lib/webgl-scatter.ts +230 -0
- package/src/primitives/Accordion.tsx +218 -63
- package/src/primitives/AppBar.tsx +39 -0
- package/src/primitives/Autocomplete.tsx +236 -0
- package/src/primitives/Avatar.tsx +52 -1
- package/src/primitives/Backdrop.tsx +48 -0
- package/src/primitives/BottomNavigation.tsx +102 -0
- package/src/primitives/ButtonBase.tsx +34 -0
- package/src/primitives/ButtonGroup.tsx +90 -0
- package/src/primitives/CircularProgress.tsx +104 -0
- package/src/primitives/ClickAwayListener.tsx +46 -0
- package/src/primitives/Collapse.tsx +39 -0
- package/src/primitives/Container.tsx +40 -0
- package/src/primitives/Fab.tsx +62 -0
- package/src/primitives/FormControl.tsx +90 -0
- package/src/primitives/FormControlLabel.tsx +51 -0
- package/src/primitives/Grid.tsx +101 -0
- package/src/primitives/IconButton.tsx +59 -0
- package/src/primitives/ImageList.test.tsx +48 -0
- package/src/primitives/ImageList.tsx +112 -0
- package/src/primitives/InputAdornment.tsx +86 -0
- package/src/primitives/Link.tsx +48 -0
- package/src/primitives/List.tsx +121 -0
- package/src/primitives/Masonry.tsx +32 -0
- package/src/primitives/Menu.tsx +112 -0
- package/src/primitives/MobileStepper.tsx +64 -0
- package/src/primitives/Modal.tsx +70 -0
- package/src/primitives/NoSsr.tsx +28 -0
- package/src/primitives/Paper.tsx +55 -0
- package/src/primitives/Progress.tsx +25 -7
- package/src/primitives/Snackbar.tsx +83 -0
- package/src/primitives/SpeedDial.tsx +110 -0
- package/src/primitives/Stack.tsx +88 -0
- package/src/primitives/StarRating.tsx +87 -28
- package/src/primitives/Stepper.tsx +184 -0
- package/src/primitives/TablePagination.tsx +58 -0
- package/src/primitives/TextField.tsx +105 -0
- package/src/primitives/TextareaAutosize.tsx +49 -0
- package/src/primitives/ToggleButton.tsx +148 -0
- package/src/primitives/TransferList.tsx +110 -0
- package/src/primitives/Typography.tsx +90 -0
- package/src/primitives/mui-more.test.tsx +76 -0
- package/src/primitives/mui.test.tsx +134 -0
- package/src/primitives/transitions.tsx +120 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useRef, useState, } from "react";
|
|
3
|
+
import { cn } from "../lib/cn";
|
|
4
|
+
import { expandVisibleEvents } from "../lib/rrule";
|
|
5
|
+
import { useControllableState } from "../lib/use-controllable-state";
|
|
6
|
+
import { addDays, addMonths, agendaDays, DEFAULT_SCHEDULER_PREFERENCES, eventCanDrag, eventCanResize, eventColorStyle, filterWeekendDays, formatDateSpan, formatDayHeading, formatHour, formatMonthYear, formatTime, formatWeekRange, getMonthGrid, getWeekDays, hoursInView, isMonthBar, isSameDay, isSameMonth, isoDay, layoutTimedEvents, normalizeEvents, occupiesDay, occurrenceKey, resizeEvent, shiftByDays, startOfDay, timedEventsOnDay, toDate, visibleEvents, weekNumber, weekSpanLanesForDays, } from "../lib/scheduler";
|
|
7
|
+
import { EventEditor } from "./EventEditor";
|
|
8
|
+
import { CALENDAR_VIEW_LABEL, EventResizeHandle, ResourceLegend, SchedulerPreferencesMenu, SchedulerShell, SchedulerToolbar, } from "./scheduler-ui";
|
|
9
|
+
import { DropdownMenuItem } from "../primitives/DropdownMenu";
|
|
10
|
+
const MAX_TIMED_IN_CELL = 3;
|
|
11
|
+
const AGENDA_LENGTH = 14;
|
|
12
|
+
function chunkWeeks(items) {
|
|
13
|
+
const weeks = [];
|
|
14
|
+
for (let i = 0; i < items.length; i += 7)
|
|
15
|
+
weeks.push(items.slice(i, i + 7));
|
|
16
|
+
return weeks;
|
|
17
|
+
}
|
|
18
|
+
function sourceEvent(events, id) {
|
|
19
|
+
return events.find((event) => event.id === id);
|
|
20
|
+
}
|
|
21
|
+
function startOfToday() {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
24
|
+
}
|
|
25
|
+
function visibleRange(view, date, weekStartsOn) {
|
|
26
|
+
if (view === "month") {
|
|
27
|
+
const grid = getMonthGrid(date, weekStartsOn);
|
|
28
|
+
return { start: grid[0], end: addDays(grid[grid.length - 1], 1) };
|
|
29
|
+
}
|
|
30
|
+
if (view === "week") {
|
|
31
|
+
const days = getWeekDays(date, weekStartsOn);
|
|
32
|
+
return { start: days[0], end: addDays(days[6], 1) };
|
|
33
|
+
}
|
|
34
|
+
if (view === "agenda") {
|
|
35
|
+
const days = agendaDays(date, AGENDA_LENGTH);
|
|
36
|
+
return { start: days[0], end: addDays(days[days.length - 1], 1) };
|
|
37
|
+
}
|
|
38
|
+
const start = startOfDay(date);
|
|
39
|
+
return { start, end: addDays(start, 1) };
|
|
40
|
+
}
|
|
41
|
+
function draftFromSource(source) {
|
|
42
|
+
return {
|
|
43
|
+
id: source.id,
|
|
44
|
+
title: source.title,
|
|
45
|
+
start: toDate(source.start),
|
|
46
|
+
end: toDate(source.end),
|
|
47
|
+
allDay: source.allDay,
|
|
48
|
+
resourceId: source.resourceId,
|
|
49
|
+
color: source.color,
|
|
50
|
+
description: source.description,
|
|
51
|
+
rrule: source.rrule,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Month / week / day / agenda calendar with editor, resize, recurrence, and preferences. */
|
|
55
|
+
export function EventCalendar({ events = [], resources = [], view: viewProp, defaultView = "month", onViewChange, date: dateProp, defaultDate, onDateChange, views = ["month", "week", "day", "agenda"], weekStartsOn = 0, hourStart = 7, hourEnd = 21, locale, readOnly = false, areEventsDraggable, areEventsResizable, preferences, defaultPreferences, onPreferencesChange, onEventClick, onEventChange, onEventCreate, onEventDelete, onSlotClick, showEventEditor = true, showPreferences = true, toolbarTrailing, renderEvent, renderToolbar, className, }) {
|
|
56
|
+
const [view, setView] = useControllableState({
|
|
57
|
+
prop: viewProp,
|
|
58
|
+
defaultProp: defaultView,
|
|
59
|
+
onChange: onViewChange,
|
|
60
|
+
});
|
|
61
|
+
const [date, setDate] = useControllableState({
|
|
62
|
+
prop: dateProp,
|
|
63
|
+
defaultProp: defaultDate ?? new Date(),
|
|
64
|
+
onChange: onDateChange,
|
|
65
|
+
});
|
|
66
|
+
const [prefs, setPrefs] = useControllableState({
|
|
67
|
+
prop: preferences
|
|
68
|
+
? { ...DEFAULT_SCHEDULER_PREFERENCES, weekStartsOn, ...defaultPreferences, ...preferences }
|
|
69
|
+
: undefined,
|
|
70
|
+
defaultProp: { ...DEFAULT_SCHEDULER_PREFERENCES, weekStartsOn, ...defaultPreferences },
|
|
71
|
+
onChange: onPreferencesChange,
|
|
72
|
+
});
|
|
73
|
+
const [hiddenIds, setHiddenIds] = useState(new Set());
|
|
74
|
+
const [draft, setDraft] = useState(null);
|
|
75
|
+
const dragRef = useRef(null);
|
|
76
|
+
const resolvedView = view ?? "month";
|
|
77
|
+
const resolvedDate = date ?? new Date();
|
|
78
|
+
const resolvedPrefs = prefs ?? { ...DEFAULT_SCHEDULER_PREFERENCES, weekStartsOn };
|
|
79
|
+
const startOn = resolvedPrefs.weekStartsOn;
|
|
80
|
+
const today = startOfToday();
|
|
81
|
+
const hour12 = resolvedPrefs.ampm;
|
|
82
|
+
const canDrag = !readOnly && (areEventsDraggable ?? Boolean(onEventChange));
|
|
83
|
+
const canResize = !readOnly && (areEventsResizable ?? Boolean(onEventChange));
|
|
84
|
+
const editorLocked = readOnly || Boolean(draft?.id && sourceEvent(events, draft.id)?.readOnly);
|
|
85
|
+
const range = useMemo(() => visibleRange(resolvedView, resolvedDate, startOn), [resolvedView, resolvedDate, startOn]);
|
|
86
|
+
const normalized = useMemo(() => {
|
|
87
|
+
const visible = visibleEvents(normalizeEvents(events, resources), hiddenIds);
|
|
88
|
+
return expandVisibleEvents(visible, range.start, range.end, events);
|
|
89
|
+
}, [events, resources, hiddenIds, range.start, range.end]);
|
|
90
|
+
const title = resolvedView === "month"
|
|
91
|
+
? formatMonthYear(resolvedDate, locale)
|
|
92
|
+
: resolvedView === "week"
|
|
93
|
+
? formatWeekRange(resolvedDate, startOn, locale)
|
|
94
|
+
: resolvedView === "agenda"
|
|
95
|
+
? formatDateSpan(range.start, addDays(range.end, -1), locale)
|
|
96
|
+
: formatDayHeading(resolvedDate, locale);
|
|
97
|
+
function go(direction) {
|
|
98
|
+
if (resolvedView === "month")
|
|
99
|
+
setDate(addMonths(resolvedDate, direction));
|
|
100
|
+
else if (resolvedView === "week")
|
|
101
|
+
setDate(addDays(resolvedDate, direction * 7));
|
|
102
|
+
else if (resolvedView === "agenda")
|
|
103
|
+
setDate(addDays(resolvedDate, direction * 7));
|
|
104
|
+
else
|
|
105
|
+
setDate(addDays(resolvedDate, direction));
|
|
106
|
+
}
|
|
107
|
+
function toggleResource(id) {
|
|
108
|
+
setHiddenIds((prev) => {
|
|
109
|
+
const next = new Set(prev);
|
|
110
|
+
if (next.has(id))
|
|
111
|
+
next.delete(id);
|
|
112
|
+
else
|
|
113
|
+
next.add(id);
|
|
114
|
+
return next;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function openEvent(event) {
|
|
118
|
+
const source = sourceEvent(events, event.id);
|
|
119
|
+
if (source)
|
|
120
|
+
onEventClick?.(source);
|
|
121
|
+
if (!showEventEditor)
|
|
122
|
+
return;
|
|
123
|
+
setDraft(source ? draftFromSource(source) : {
|
|
124
|
+
id: event.id,
|
|
125
|
+
title: event.title,
|
|
126
|
+
start: event.start,
|
|
127
|
+
end: event.end,
|
|
128
|
+
allDay: event.allDay,
|
|
129
|
+
resourceId: event.resourceId,
|
|
130
|
+
color: event.color,
|
|
131
|
+
description: event.description,
|
|
132
|
+
rrule: event.rrule,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function openSlot(slot) {
|
|
136
|
+
onSlotClick?.(slot);
|
|
137
|
+
if (readOnly || !showEventEditor)
|
|
138
|
+
return;
|
|
139
|
+
setDraft({ start: slot.start, end: slot.end, allDay: slot.allDay });
|
|
140
|
+
}
|
|
141
|
+
function handleEventPointerDown(event, pointer) {
|
|
142
|
+
if (!onEventChange || !eventCanDrag(event, { readOnly, enabled: canDrag }))
|
|
143
|
+
return;
|
|
144
|
+
pointer.currentTarget.setPointerCapture?.(pointer.pointerId);
|
|
145
|
+
dragRef.current = { id: event.id, x: pointer.clientX, y: pointer.clientY, active: false };
|
|
146
|
+
}
|
|
147
|
+
function handleEventPointerMove(pointer) {
|
|
148
|
+
const drag = dragRef.current;
|
|
149
|
+
if (!drag)
|
|
150
|
+
return;
|
|
151
|
+
if (Math.hypot(pointer.clientX - drag.x, pointer.clientY - drag.y) > 6) {
|
|
152
|
+
drag.active = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function handleEventPointerUp(event, pointer) {
|
|
156
|
+
const drag = dragRef.current;
|
|
157
|
+
dragRef.current = null;
|
|
158
|
+
if (!drag?.active || !onEventChange)
|
|
159
|
+
return;
|
|
160
|
+
const target = document
|
|
161
|
+
.elementFromPoint(pointer.clientX, pointer.clientY)
|
|
162
|
+
?.closest("[data-scheduler-day]");
|
|
163
|
+
const iso = target?.dataset.schedulerDay;
|
|
164
|
+
if (!iso)
|
|
165
|
+
return;
|
|
166
|
+
const [year, month, day] = iso.split("-").map(Number);
|
|
167
|
+
const targetDay = new Date(year, month - 1, day);
|
|
168
|
+
const currentDay = startOfDay(event.start);
|
|
169
|
+
const delta = Math.round((targetDay.getTime() - currentDay.getTime()) / 86_400_000);
|
|
170
|
+
if (delta === 0)
|
|
171
|
+
return;
|
|
172
|
+
const source = sourceEvent(events, event.id);
|
|
173
|
+
if (!source)
|
|
174
|
+
return;
|
|
175
|
+
const master = normalizeEvents([source], resources)[0];
|
|
176
|
+
onEventChange(source, shiftByDays(master, delta));
|
|
177
|
+
}
|
|
178
|
+
function commitResize(event, edge, instant) {
|
|
179
|
+
if (!onEventChange)
|
|
180
|
+
return;
|
|
181
|
+
const next = resizeEvent(event, edge, instant);
|
|
182
|
+
const source = sourceEvent(events, event.id);
|
|
183
|
+
if (!source)
|
|
184
|
+
return;
|
|
185
|
+
const master = normalizeEvents([source], resources)[0];
|
|
186
|
+
onEventChange(source, {
|
|
187
|
+
start: new Date(master.start.getTime() + (next.start.getTime() - event.start.getTime())),
|
|
188
|
+
end: new Date(master.end.getTime() + (next.end.getTime() - event.end.getTime())),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function handleKeyDown(keyboard) {
|
|
192
|
+
if (draft)
|
|
193
|
+
return;
|
|
194
|
+
const target = keyboard.target;
|
|
195
|
+
if (target.closest("input, textarea, select, [contenteditable='true']"))
|
|
196
|
+
return;
|
|
197
|
+
if (keyboard.key === "ArrowLeft") {
|
|
198
|
+
keyboard.preventDefault();
|
|
199
|
+
go(-1);
|
|
200
|
+
}
|
|
201
|
+
else if (keyboard.key === "ArrowRight") {
|
|
202
|
+
keyboard.preventDefault();
|
|
203
|
+
go(1);
|
|
204
|
+
}
|
|
205
|
+
else if (keyboard.key === "t" || keyboard.key === "T") {
|
|
206
|
+
keyboard.preventDefault();
|
|
207
|
+
setDate(new Date());
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function renderEventTrigger(event, className, defaultChildren, options) {
|
|
211
|
+
const source = sourceEvent(events, event.id);
|
|
212
|
+
const variant = options?.variant ?? "month-timed";
|
|
213
|
+
const children = renderEvent
|
|
214
|
+
? renderEvent(event, {
|
|
215
|
+
view: resolvedView,
|
|
216
|
+
variant,
|
|
217
|
+
locale,
|
|
218
|
+
hour12,
|
|
219
|
+
source,
|
|
220
|
+
defaultChildren,
|
|
221
|
+
})
|
|
222
|
+
: defaultChildren;
|
|
223
|
+
if (renderEvent && children == null)
|
|
224
|
+
return null;
|
|
225
|
+
const resizable = options?.resize === "ns" &&
|
|
226
|
+
eventCanResize(event, { readOnly, enabled: canResize }) &&
|
|
227
|
+
Boolean(onEventChange);
|
|
228
|
+
const painted = variant === "month-bar" || variant === "all-day" || variant === "time-grid";
|
|
229
|
+
const button = (_jsx("button", { type: "button", draggable: false, className: className, style: variant === "agenda"
|
|
230
|
+
? eventColorStyle(event.color, "wash")
|
|
231
|
+
: painted
|
|
232
|
+
? eventColorStyle(event.color, resolvedView === "month" ? "bar" : "block")
|
|
233
|
+
: undefined, onClick: () => {
|
|
234
|
+
if (dragRef.current?.active)
|
|
235
|
+
return;
|
|
236
|
+
openEvent(event);
|
|
237
|
+
}, onPointerDown: (pointer) => handleEventPointerDown(event, pointer), onPointerMove: handleEventPointerMove, onPointerUp: (pointer) => handleEventPointerUp(event, pointer), children: children }));
|
|
238
|
+
if (!resizable)
|
|
239
|
+
return button;
|
|
240
|
+
return (_jsxs("div", { className: "relative h-full min-h-0", children: [button, _jsx(EventResizeHandle, { edge: "start", axis: "ns", event: event, hourStart: hourStart, hourEnd: hourEnd, onCommit: commitResize }), _jsx(EventResizeHandle, { edge: "end", axis: "ns", event: event, hourStart: hourStart, hourEnd: hourEnd, onCommit: commitResize })] }));
|
|
241
|
+
}
|
|
242
|
+
return (_jsxs(SchedulerShell, { slot: "event-calendar", className: className, tabIndex: 0, "aria-label": "Event calendar", onKeyDown: handleKeyDown, children: [renderToolbar ? (renderToolbar({
|
|
243
|
+
view: resolvedView,
|
|
244
|
+
date: resolvedDate,
|
|
245
|
+
title,
|
|
246
|
+
views,
|
|
247
|
+
locale,
|
|
248
|
+
preferences: resolvedPrefs,
|
|
249
|
+
onViewChange: (next) => setView(next),
|
|
250
|
+
onPrev: () => go(-1),
|
|
251
|
+
onNext: () => go(1),
|
|
252
|
+
onToday: () => setDate(new Date()),
|
|
253
|
+
onDateChange: (next) => setDate(next),
|
|
254
|
+
onPreferencesChange: (next) => setPrefs(next),
|
|
255
|
+
})) : (_jsx(SchedulerToolbar, { title: title, onPrev: () => go(-1), onNext: () => go(1), onToday: () => setDate(new Date()), menuLabel: CALENDAR_VIEW_LABEL[resolvedView], menu: views.map((item) => (_jsx(DropdownMenuItem, { onClick: () => setView(item), children: CALENDAR_VIEW_LABEL[item] }, item))), trailing: _jsxs(_Fragment, { children: [toolbarTrailing, showPreferences ? (_jsx(SchedulerPreferencesMenu, { value: resolvedPrefs, onChange: (next) => setPrefs(next) })) : null] }) })), resolvedView === "month" ? (_jsx(MonthGrid, { date: resolvedDate, today: today, events: normalized, weekStartsOn: startOn, showWeekends: resolvedPrefs.showWeekends, showWeekNumber: resolvedPrefs.showWeekNumber, locale: locale, hour12: hour12, onSlotClick: openSlot, renderEventTrigger: renderEventTrigger })) : resolvedView === "agenda" ? (_jsx(AgendaList, { date: resolvedDate, events: normalized, showWeekends: resolvedPrefs.showWeekends, locale: locale, hour12: hour12, onSlotClick: openSlot, renderEventTrigger: renderEventTrigger })) : (_jsx(TimeGrid, { date: resolvedDate, today: today, events: normalized, view: resolvedView === "day" ? "day" : "week", weekStartsOn: startOn, showWeekends: resolvedPrefs.showWeekends, hourStart: hourStart, hourEnd: hourEnd, locale: locale, hour12: hour12, onSlotClick: openSlot, renderEventTrigger: renderEventTrigger })), _jsx(ResourceLegend, { resources: resources, hiddenIds: hiddenIds, onToggle: toggleResource }), showEventEditor ? (_jsx(EventEditor, { open: draft != null, onOpenChange: (open) => {
|
|
256
|
+
if (!open)
|
|
257
|
+
setDraft(null);
|
|
258
|
+
}, draft: draft, resources: resources, readOnly: editorLocked, onSave: (event) => {
|
|
259
|
+
if (event.id && sourceEvent(events, event.id)) {
|
|
260
|
+
const source = sourceEvent(events, event.id);
|
|
261
|
+
onEventChange?.(source, event);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
onEventCreate?.(event);
|
|
265
|
+
}
|
|
266
|
+
}, onDelete: onEventDelete
|
|
267
|
+
? (id) => {
|
|
268
|
+
const source = sourceEvent(events, id);
|
|
269
|
+
if (source)
|
|
270
|
+
onEventDelete(source);
|
|
271
|
+
}
|
|
272
|
+
: undefined })) : null] }));
|
|
273
|
+
}
|
|
274
|
+
function MonthGrid({ date, today, events, weekStartsOn, showWeekends, showWeekNumber, locale, hour12, onSlotClick, renderEventTrigger, }) {
|
|
275
|
+
const weeks = chunkWeeks(getMonthGrid(date, weekStartsOn));
|
|
276
|
+
const headerDays = filterWeekendDays(getWeekDays(date, weekStartsOn), showWeekends);
|
|
277
|
+
const columns = headerDays.length;
|
|
278
|
+
const gridStyle = { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` };
|
|
279
|
+
return (_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex border-b border-border/50", children: [showWeekNumber ? _jsx("div", { className: "w-8 shrink-0" }) : null, _jsx("div", { className: "grid min-w-0 flex-1", style: gridStyle, children: headerDays.map((day) => (_jsx("div", { className: "px-2 py-2 text-center text-[11px] font-bold uppercase tracking-wider text-muted-foreground", children: new Intl.DateTimeFormat(locale, { weekday: "short" }).format(day) }, day.getDay()))) })] }), _jsx("div", { children: weeks.map((week) => {
|
|
280
|
+
const days = filterWeekendDays(week, showWeekends);
|
|
281
|
+
const lanes = weekSpanLanesForDays(days, events);
|
|
282
|
+
return (_jsxs("div", { className: "flex border-b border-border/40 last:border-b-0", children: [showWeekNumber ? (_jsx("div", { className: "flex w-8 shrink-0 items-start justify-center pt-2 text-[10px] font-bold text-muted-foreground", children: weekNumber(week[0]) })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "grid", style: gridStyle, children: days.map((day) => {
|
|
283
|
+
const inMonth = isSameMonth(day, date);
|
|
284
|
+
const isToday = isSameDay(day, today);
|
|
285
|
+
return (_jsx("div", { "data-scheduler-day": isoDay(day), className: cn("flex items-start justify-end px-1.5 pt-1.5", !inMonth && "bg-muted/15"), children: _jsx("button", { type: "button", onClick: () => onSlotClick?.({
|
|
286
|
+
start: day,
|
|
287
|
+
end: addDays(day, 1),
|
|
288
|
+
allDay: true,
|
|
289
|
+
}), className: cn("flex size-7 items-center justify-center rounded-full text-xs font-bold", isToday && "bg-primary text-primary-foreground", !isToday && inMonth && "text-foreground hover:bg-muted/50", !isToday && !inMonth && "text-muted-foreground/70"), children: day.getDate() }) }, day.toISOString()));
|
|
290
|
+
}) }), lanes.map((lane, laneIndex) => (_jsx("div", { className: "grid gap-px px-0.5 pb-0.5", style: gridStyle, children: laneSlots(lane, columns).map((slot, slotIndex) => slot.kind === "empty" ? (_jsx("div", {}, slotIndex)) : (_jsx("div", { className: "min-w-0", style: { gridColumn: `span ${slot.span}` }, children: renderEventTrigger(slot.event, "flex h-5 w-full items-center truncate rounded-md px-1.5 text-[11px] font-semibold leading-none", slot.event.title, { variant: "month-bar" }) }, occurrenceKey(slot.event)))) }, laneIndex))), _jsx("div", { className: "grid min-h-[4.25rem]", style: gridStyle, children: days.map((day) => {
|
|
291
|
+
const timed = timedEventsOnDay(events, day);
|
|
292
|
+
const visibleTimed = timed.slice(0, MAX_TIMED_IN_CELL);
|
|
293
|
+
const extra = timed.length - visibleTimed.length;
|
|
294
|
+
return (_jsxs("div", { "data-scheduler-day": isoDay(day), className: cn("min-w-0 space-y-0.5 px-1 pb-2", !isSameMonth(day, date) && "bg-muted/15"), onClick: (click) => {
|
|
295
|
+
if (click.target.closest("button"))
|
|
296
|
+
return;
|
|
297
|
+
onSlotClick?.({
|
|
298
|
+
start: new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9),
|
|
299
|
+
end: new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10),
|
|
300
|
+
allDay: false,
|
|
301
|
+
});
|
|
302
|
+
}, children: [visibleTimed.map((event) => (_jsx("div", { className: "min-w-0", children: renderEventTrigger(event, "flex w-full min-w-0 items-center gap-1 rounded-sm px-0.5 py-0.5 text-left text-[11px] leading-tight text-foreground hover:bg-muted/40", _jsxs(_Fragment, { children: [_jsx("span", { className: "size-1.5 shrink-0 rounded-full", style: eventColorStyle(event.color, "dot") }), _jsx("span", { className: "shrink-0 text-muted-foreground", children: formatTime(event.start, locale, hour12) }), _jsx("span", { className: "min-w-0 truncate font-medium", children: event.title })] }), { variant: "month-timed" }) }, occurrenceKey(event)))), extra > 0 ? (_jsxs("p", { className: "px-0.5 text-[10px] font-bold text-muted-foreground", children: ["+", extra, " more"] })) : null] }, day.toISOString()));
|
|
303
|
+
}) })] })] }, week[0].toISOString()));
|
|
304
|
+
}) })] }));
|
|
305
|
+
}
|
|
306
|
+
function laneSlots(lane, columns) {
|
|
307
|
+
const slots = [];
|
|
308
|
+
let cursor = 0;
|
|
309
|
+
const ordered = [...lane].sort((a, b) => a.startCol - b.startCol);
|
|
310
|
+
for (const segment of ordered) {
|
|
311
|
+
while (cursor < segment.startCol) {
|
|
312
|
+
slots.push({ kind: "empty", span: 1 });
|
|
313
|
+
cursor += 1;
|
|
314
|
+
}
|
|
315
|
+
slots.push({ kind: "event", event: segment.event, span: segment.span });
|
|
316
|
+
cursor += segment.span;
|
|
317
|
+
}
|
|
318
|
+
while (cursor < columns) {
|
|
319
|
+
slots.push({ kind: "empty", span: 1 });
|
|
320
|
+
cursor += 1;
|
|
321
|
+
}
|
|
322
|
+
return slots;
|
|
323
|
+
}
|
|
324
|
+
function TimeGrid({ date, today, events, view, weekStartsOn, showWeekends, hourStart, hourEnd, locale, hour12, onSlotClick, renderEventTrigger, }) {
|
|
325
|
+
const days = view === "day"
|
|
326
|
+
? [date]
|
|
327
|
+
: filterWeekendDays(getWeekDays(date, weekStartsOn), showWeekends);
|
|
328
|
+
const hours = hoursInView(hourStart, hourEnd);
|
|
329
|
+
const hourHeight = 48;
|
|
330
|
+
const now = new Date();
|
|
331
|
+
const showNow = days.some((day) => isSameDay(day, now));
|
|
332
|
+
const nowTop = ((now.getHours() + now.getMinutes() / 60 - hourStart) / (hourEnd - hourStart)) * (hours.length * hourHeight);
|
|
333
|
+
return (_jsxs("div", { className: "overflow-x-auto", children: [_jsxs("div", { className: "grid min-w-[640px]", style: { gridTemplateColumns: `3.5rem repeat(${days.length}, minmax(0, 1fr))` }, children: [_jsx("div", { className: "border-b border-border/50" }), days.map((day) => (_jsxs("div", { className: cn("border-b border-l border-border/50 px-2 py-2 text-center", isSameDay(day, today) && "bg-primary/5"), children: [_jsx("p", { className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground", children: new Intl.DateTimeFormat(locale, { weekday: "short" }).format(day) }), _jsx("p", { className: cn("mx-auto mt-0.5 flex size-7 items-center justify-center rounded-full text-sm font-black", isSameDay(day, today) && "bg-primary text-primary-foreground"), children: day.getDate() })] }, day.toISOString()))), _jsx("div", { className: "border-b border-border/50" }), days.map((day) => {
|
|
334
|
+
const allDay = events.filter((event) => isMonthBar(event) && occupiesDay(event, day));
|
|
335
|
+
return (_jsx("div", { "data-scheduler-day": isoDay(day), className: "min-h-10 space-y-0.5 border-b border-l border-border/50 p-1", children: allDay.map((event) => (_jsx("div", { children: renderEventTrigger(event, "flex h-5 w-full items-center truncate rounded-md px-1.5 text-[11px] font-semibold", event.title, { variant: "all-day" }) }, occurrenceKey(event)))) }, `allday-${day.toISOString()}`));
|
|
336
|
+
})] }), _jsxs("div", { className: "relative min-w-[640px]", style: { height: hours.length * hourHeight }, children: [_jsxs("div", { className: "grid h-full", style: { gridTemplateColumns: `3.5rem repeat(${days.length}, minmax(0, 1fr))` }, children: [_jsx("div", { className: "relative", children: hours.map((hour) => (_jsx("div", { className: "relative border-b border-border/30 pr-2 text-right text-[10px] font-bold text-muted-foreground", style: { height: hourHeight }, children: _jsx("span", { className: "-translate-y-2 absolute right-2", children: formatHour(hour, locale, hour12) }) }, hour))) }), days.map((day) => {
|
|
337
|
+
const layout = layoutTimedEvents(events, day, hourStart, hourEnd);
|
|
338
|
+
return (_jsxs("div", { "data-scheduler-day": isoDay(day), className: "relative border-l border-border/40", onClick: (click) => {
|
|
339
|
+
if (click.target.closest("button"))
|
|
340
|
+
return;
|
|
341
|
+
const rect = click.currentTarget.getBoundingClientRect();
|
|
342
|
+
const ratio = (click.clientY - rect.top) / rect.height;
|
|
343
|
+
const hour = hourStart + ratio * (hourEnd - hourStart);
|
|
344
|
+
const startHour = Math.floor(hour);
|
|
345
|
+
const start = new Date(day.getFullYear(), day.getMonth(), day.getDate(), startHour);
|
|
346
|
+
onSlotClick?.({
|
|
347
|
+
start,
|
|
348
|
+
end: new Date(start.getTime() + 3_600_000),
|
|
349
|
+
allDay: false,
|
|
350
|
+
});
|
|
351
|
+
}, children: [hours.map((hour) => (_jsx("div", { className: "border-b border-border/30", style: { height: hourHeight } }, hour))), layout.map((item) => (_jsx("div", { className: "absolute px-0.5", style: {
|
|
352
|
+
top: `${item.top}%`,
|
|
353
|
+
height: `${item.height}%`,
|
|
354
|
+
left: `${(item.col / item.cols) * 100}%`,
|
|
355
|
+
width: `${100 / item.cols}%`,
|
|
356
|
+
}, children: renderEventTrigger(item.event, "flex h-full w-full flex-col overflow-hidden rounded-md px-1.5 py-1 text-left text-[11px] font-semibold leading-tight", _jsxs(_Fragment, { children: [_jsx("span", { className: "truncate", children: item.event.title }), _jsx("span", { className: "truncate text-[10px] font-medium opacity-80", children: formatTime(item.event.start, locale, hour12) })] }), { resize: "ns", variant: "time-grid" }) }, occurrenceKey(item.event))))] }, day.toISOString()));
|
|
357
|
+
})] }), showNow && nowTop > 0 && nowTop < hours.length * hourHeight ? (_jsx("div", { className: "pointer-events-none absolute right-0 left-14 z-10 h-px bg-destructive", style: { top: nowTop }, children: _jsx("span", { className: "absolute -top-1 -left-1 size-2 rounded-full bg-destructive" }) })) : null] })] }));
|
|
358
|
+
}
|
|
359
|
+
function AgendaList({ date, events, showWeekends, locale, hour12, onSlotClick, renderEventTrigger, }) {
|
|
360
|
+
const days = filterWeekendDays(agendaDays(date, AGENDA_LENGTH), showWeekends);
|
|
361
|
+
return (_jsx("div", { className: "divide-y divide-border/50", children: days.map((day) => {
|
|
362
|
+
const items = events.filter((event) => occupiesDay(event, day));
|
|
363
|
+
return (_jsxs("div", { className: "grid grid-cols-[7.5rem_minmax(0,1fr)] gap-3 px-3 py-3 sm:px-4", children: [_jsxs("button", { type: "button", "data-scheduler-day": isoDay(day), className: "text-left", onClick: () => onSlotClick?.({
|
|
364
|
+
start: new Date(day.getFullYear(), day.getMonth(), day.getDate(), 9),
|
|
365
|
+
end: new Date(day.getFullYear(), day.getMonth(), day.getDate(), 10),
|
|
366
|
+
allDay: false,
|
|
367
|
+
}), children: [_jsx("p", { className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground", children: new Intl.DateTimeFormat(locale, { weekday: "short" }).format(day) }), _jsx("p", { className: cn("text-lg font-black", isSameDay(day, startOfToday()) && "text-primary"), children: new Intl.DateTimeFormat(locale, { month: "short", day: "numeric" }).format(day) })] }), _jsx("div", { className: "min-w-0 space-y-1.5", children: items.length === 0 ? (_jsx("p", { className: "pt-2 text-xs text-muted-foreground", children: "No events" })) : (items.map((event) => (_jsx("div", { "data-scheduler-day": isoDay(day), children: renderEventTrigger(event, "flex w-full min-w-0 items-start gap-2 rounded-md px-2 py-1.5 text-left hover:bg-muted/40", _jsxs(_Fragment, { children: [_jsx("span", { className: "mt-1.5 size-2 shrink-0 rounded-full", style: eventColorStyle(event.color, "dot") }), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "block truncate text-sm font-semibold text-foreground", children: event.title }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: event.allDay || isMonthBar(event)
|
|
368
|
+
? "All day"
|
|
369
|
+
: `${formatTime(event.start, locale, hour12)} – ${formatTime(event.end, locale, hour12)}` })] })] }), { variant: "agenda" }) }, occurrenceKey(event))))) })] }, day.toISOString()));
|
|
370
|
+
}) }));
|
|
371
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type SchedulerColorTone, type SchedulerEvent, type SchedulerResource } from "../lib/scheduler";
|
|
2
|
+
export type EventEditorDraft = {
|
|
3
|
+
id?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
start: Date;
|
|
6
|
+
end: Date;
|
|
7
|
+
allDay?: boolean;
|
|
8
|
+
resourceId?: string;
|
|
9
|
+
color?: SchedulerColorTone;
|
|
10
|
+
description?: string;
|
|
11
|
+
rrule?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function EventEditor({ open, onOpenChange, draft, resources, readOnly, onSave, onDelete, }: {
|
|
14
|
+
open: boolean;
|
|
15
|
+
onOpenChange: (open: boolean) => void;
|
|
16
|
+
draft: EventEditorDraft | null;
|
|
17
|
+
resources?: SchedulerResource[];
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
onSave: (event: SchedulerEvent) => void;
|
|
20
|
+
onDelete?: (id: string) => void;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=EventEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventEditor.d.ts","sourceRoot":"","sources":["../../src/composites/EventEditor.tsx"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AAgB1B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAWF,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,SAAc,EACd,QAAgB,EAChB,MAAM,EACN,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,+BA4MA"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { createEventId, fromDatetimeLocalValue, SCHEDULER_COLOR_TONES, toDatetimeLocalValue, toDate, } from "../lib/scheduler";
|
|
4
|
+
import { Button } from "../primitives/Button";
|
|
5
|
+
import { Checkbox } from "../primitives/Checkbox";
|
|
6
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../primitives/Dialog";
|
|
7
|
+
import { Input } from "../primitives/Input";
|
|
8
|
+
import { NativeSelect } from "../primitives/NativeSelect";
|
|
9
|
+
import { Textarea } from "../primitives/Textarea";
|
|
10
|
+
import { FormField } from "./FormField";
|
|
11
|
+
const RRULE_PRESETS = [
|
|
12
|
+
{ value: "", label: "Does not repeat" },
|
|
13
|
+
{ value: "FREQ=DAILY", label: "Daily" },
|
|
14
|
+
{ value: "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR", label: "Every weekday" },
|
|
15
|
+
{ value: "FREQ=WEEKLY", label: "Weekly" },
|
|
16
|
+
{ value: "FREQ=MONTHLY", label: "Monthly" },
|
|
17
|
+
{ value: "FREQ=YEARLY", label: "Yearly" },
|
|
18
|
+
];
|
|
19
|
+
export function EventEditor({ open, onOpenChange, draft, resources = [], readOnly = false, onSave, onDelete, }) {
|
|
20
|
+
const [title, setTitle] = useState("");
|
|
21
|
+
const [start, setStart] = useState("");
|
|
22
|
+
const [end, setEnd] = useState("");
|
|
23
|
+
const [allDay, setAllDay] = useState(false);
|
|
24
|
+
const [resourceId, setResourceId] = useState("");
|
|
25
|
+
const [color, setColor] = useState("");
|
|
26
|
+
const [description, setDescription] = useState("");
|
|
27
|
+
const [rrule, setRrule] = useState("");
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!draft || !open)
|
|
30
|
+
return;
|
|
31
|
+
setTitle(draft.title ?? "");
|
|
32
|
+
setStart(draft.allDay
|
|
33
|
+
? toDatetimeLocalValue(toDate(draft.start)).slice(0, 10)
|
|
34
|
+
: toDatetimeLocalValue(toDate(draft.start)));
|
|
35
|
+
setEnd(draft.allDay
|
|
36
|
+
? toDatetimeLocalValue(toDate(draft.end)).slice(0, 10)
|
|
37
|
+
: toDatetimeLocalValue(toDate(draft.end)));
|
|
38
|
+
setAllDay(Boolean(draft.allDay));
|
|
39
|
+
setResourceId(draft.resourceId ?? "");
|
|
40
|
+
setColor(draft.color ?? "");
|
|
41
|
+
setDescription(draft.description ?? "");
|
|
42
|
+
setRrule(draft.rrule ?? "");
|
|
43
|
+
}, [draft, open]);
|
|
44
|
+
const locked = readOnly;
|
|
45
|
+
const isEdit = Boolean(draft?.id);
|
|
46
|
+
function submit() {
|
|
47
|
+
if (!draft || locked)
|
|
48
|
+
return;
|
|
49
|
+
const nextStart = allDay ? fromDatetimeLocalValue(`${start}T00:00`) : fromDatetimeLocalValue(start);
|
|
50
|
+
const nextEnd = allDay
|
|
51
|
+
? fromDatetimeLocalValue(`${end || start}T00:00`)
|
|
52
|
+
: fromDatetimeLocalValue(end || start);
|
|
53
|
+
onSave({
|
|
54
|
+
id: draft.id ?? createEventId(),
|
|
55
|
+
title: title.trim() || "Untitled",
|
|
56
|
+
start: nextStart,
|
|
57
|
+
end: nextEnd.getTime() <= nextStart.getTime()
|
|
58
|
+
? new Date(nextStart.getTime() + (allDay ? 86_400_000 : 3_600_000))
|
|
59
|
+
: nextEnd,
|
|
60
|
+
allDay,
|
|
61
|
+
resourceId: resourceId || undefined,
|
|
62
|
+
color: color || undefined,
|
|
63
|
+
description: description.trim() || undefined,
|
|
64
|
+
rrule: rrule || undefined,
|
|
65
|
+
});
|
|
66
|
+
onOpenChange(false);
|
|
67
|
+
}
|
|
68
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-md", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: locked ? "Event" : isEdit ? "Edit event" : "New event" }), _jsx(DialogDescription, { children: locked ? "This calendar is read-only." : "Title, time, resource, and recurrence." })] }), draft ? (_jsxs("form", { className: "grid gap-3", onSubmit: (event) => {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
submit();
|
|
71
|
+
}, children: [_jsx(FormField, { id: "spk-event-title", label: "Title", required: true, children: _jsx(Input, { id: "spk-event-title", value: title, onChange: (event) => setTitle(event.target.value), disabled: locked, autoFocus: !locked }) }), _jsxs("label", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx(Checkbox, { checked: allDay, onCheckedChange: (value) => {
|
|
72
|
+
const next = value === true;
|
|
73
|
+
setAllDay(next);
|
|
74
|
+
if (next) {
|
|
75
|
+
setStart(start.slice(0, 10));
|
|
76
|
+
setEnd((end || start).slice(0, 10));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
setStart(start.length <= 10 ? `${start}T09:00` : start);
|
|
80
|
+
setEnd((end || start).length <= 10 ? `${end || start}T10:00` : end);
|
|
81
|
+
}
|
|
82
|
+
}, disabled: locked }), "All day"] }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsx(FormField, { id: "spk-event-start", label: "Start", children: _jsx(Input, { id: "spk-event-start", type: allDay ? "date" : "datetime-local", value: start, onChange: (event) => setStart(event.target.value), disabled: locked }) }), _jsx(FormField, { id: "spk-event-end", label: "End", children: _jsx(Input, { id: "spk-event-end", type: allDay ? "date" : "datetime-local", value: end, onChange: (event) => setEnd(event.target.value), disabled: locked }) })] }), resources.length > 0 ? (_jsx(FormField, { id: "spk-event-resource", label: "Resource", children: _jsxs(NativeSelect, { id: "spk-event-resource", value: resourceId, onChange: (event) => setResourceId(event.target.value), disabled: locked, className: "h-9 min-h-9", children: [_jsx("option", { value: "", children: "None" }), resources.map((resource) => (_jsx("option", { value: resource.id, children: resource.title }, resource.id)))] }) })) : null, _jsx(FormField, { id: "spk-event-color", label: "Color", children: _jsxs(NativeSelect, { id: "spk-event-color", value: color, onChange: (event) => setColor(event.target.value), disabled: locked, className: "h-9 min-h-9", children: [_jsx("option", { value: "", children: "From resource" }), SCHEDULER_COLOR_TONES.map((tone) => (_jsx("option", { value: tone, children: tone }, tone)))] }) }), _jsx(FormField, { id: "spk-event-repeat", label: "Repeat", children: _jsxs(NativeSelect, { id: "spk-event-repeat", value: rrule, onChange: (event) => setRrule(event.target.value), disabled: locked, className: "h-9 min-h-9", children: [RRULE_PRESETS.map((preset) => (_jsx("option", { value: preset.value, children: preset.label }, preset.value || "none"))), rrule && !RRULE_PRESETS.some((preset) => preset.value === rrule) ? (_jsx("option", { value: rrule, children: "Custom" })) : null] }) }), _jsx(FormField, { id: "spk-event-notes", label: "Description", children: _jsx(Textarea, { id: "spk-event-notes", value: description, onChange: (event) => setDescription(event.target.value), disabled: locked, rows: 3 }) }), _jsxs(DialogFooter, { children: [isEdit && onDelete && !locked ? (_jsx(Button, { type: "button", variant: "destructive", className: "sm:mr-auto", onClick: () => {
|
|
83
|
+
onDelete(draft.id);
|
|
84
|
+
onOpenChange(false);
|
|
85
|
+
}, children: "Delete" })) : null, _jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: locked ? "Close" : "Cancel" }), !locked ? _jsx(Button, { type: "submit", children: "Save" }) : null] })] })) : null] }) }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type SchedulerEvent, type SchedulerResource, type TimelineScale } from "../lib/scheduler";
|
|
2
|
+
export type EventTimelineProps = {
|
|
3
|
+
events?: SchedulerEvent[];
|
|
4
|
+
resources?: SchedulerResource[];
|
|
5
|
+
scale?: TimelineScale;
|
|
6
|
+
defaultScale?: TimelineScale;
|
|
7
|
+
onScaleChange?: (scale: TimelineScale) => void;
|
|
8
|
+
date?: Date;
|
|
9
|
+
defaultDate?: Date;
|
|
10
|
+
onDateChange?: (date: Date) => void;
|
|
11
|
+
scales?: TimelineScale[];
|
|
12
|
+
locale?: string;
|
|
13
|
+
readOnly?: boolean;
|
|
14
|
+
areEventsDraggable?: boolean;
|
|
15
|
+
areEventsResizable?: boolean;
|
|
16
|
+
onEventClick?: (event: SchedulerEvent) => void;
|
|
17
|
+
onEventChange?: (event: SchedulerEvent, next: Partial<SchedulerEvent>) => void;
|
|
18
|
+
onEventCreate?: (event: SchedulerEvent) => void;
|
|
19
|
+
onEventDelete?: (event: SchedulerEvent) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Built-in create/edit dialog. Defaults to true.
|
|
22
|
+
* Set false when the host owns create/edit.
|
|
23
|
+
*/
|
|
24
|
+
showEventEditor?: boolean;
|
|
25
|
+
className?: string;
|
|
26
|
+
};
|
|
27
|
+
/** Horizontal resource timeline — rows are resources, bars are allocations across time. */
|
|
28
|
+
export declare function EventTimeline({ events, resources, scale: scaleProp, defaultScale, onScaleChange, date: dateProp, defaultDate, onDateChange, scales, locale, readOnly, areEventsDraggable, areEventsResizable, onEventClick, onEventChange, onEventCreate, onEventDelete, showEventEditor, className, }: EventTimelineProps): import("react").JSX.Element;
|
|
29
|
+
//# sourceMappingURL=EventTimeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventTimeline.d.ts","sourceRoot":"","sources":["../../src/composites/EventTimeline.tsx"],"names":[],"mappings":"AAGA,OAAO,EAeL,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAW1B,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAChC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACpC,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;IAC/E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAmCF,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,EAC5B,MAAW,EACX,SAAc,EACd,KAAK,EAAE,SAAS,EAChB,YAAuB,EACvB,aAAa,EACb,IAAI,EAAE,QAAQ,EACd,WAAW,EACX,YAAY,EACZ,MAAsD,EACtD,MAAM,EACN,QAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,eAAsB,EACtB,SAAS,GACV,EAAE,kBAAkB,+BA6PpB"}
|