simple-calendar-js 3.0.4 → 3.0.6
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/CHANGELOG.md +118 -0
- package/README.md +332 -4
- package/dist/simple-calendar-js.min.css +2 -2
- package/dist/simple-calendar-js.min.js +3 -3
- package/frameworks/simple-calendar-js-angular.component.ts +1 -1
- package/frameworks/simple-calendar-js-react.jsx +1 -1
- package/frameworks/simple-calendar-js-vue.js +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to SimpleCalendarJs will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
No unreleased changes yet.
|
|
11
|
+
|
|
12
|
+
## [3.0.6] - 2026-02-24
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Drag and drop support for moving events between dates and times (`enableDragDrop` option)
|
|
16
|
+
- Event resize functionality to change event duration (`enableResize` option)
|
|
17
|
+
- Month view timed event display style option (`monthTimedEventStyle`: `'list'` or `'block'`)
|
|
18
|
+
- `onEventDrop` callback for handling both move and resize operations
|
|
19
|
+
- Touch support for drag and drop on mobile/tablet devices
|
|
20
|
+
- Visual feedback during drag operations with 15-minute snap-to-grid in week/day views
|
|
21
|
+
- Cross-boundary conversion (timed ↔ all-day events when dragging between sections)
|
|
22
|
+
- Auto-calculate feature for `maxEventsPerCell`: set to `-1` to automatically calculate how many events fit based on cell height
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- **`maxEventsPerCell` default changed from `3` to `-1`** (auto-calculate mode)
|
|
26
|
+
- `-1` = Auto-calculate based on cell height (responsive, adapts to calendar size)
|
|
27
|
+
- `0` = Show all events, cells expand infinitely
|
|
28
|
+
- `1+` = Show exactly that number, rest in "+N more"
|
|
29
|
+
- Week, day, and list views now have a max-height of 650px and scroll content while keeping headers and toolbar fixed in place
|
|
30
|
+
- Month view maintains its natural expanding height based on number of weeks and events per cell
|
|
31
|
+
- All scrollable views use consistent custom scrollbar styling
|
|
32
|
+
- Calendar adapts to container height: if you set a custom height on the container div, week/day/list views will respect it
|
|
33
|
+
- Week/day view all-day events now always display titles on continuation segments (matches day view behavior)
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- List view sticky date headers now have opaque background to prevent events showing through when scrolling
|
|
37
|
+
- Calendar no longer rebuilds entire shell during event fetching, preventing layout shift and page content jumping
|
|
38
|
+
|
|
39
|
+
## [3.0.5] - 2026-02-23
|
|
40
|
+
|
|
41
|
+
No core calendar changes in this version.
|
|
42
|
+
|
|
43
|
+
## [3.0.4] - 2026-02-23
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- List view mode for chronological event display
|
|
47
|
+
- Intelligent event caching system to minimize network requests
|
|
48
|
+
- Localized "All-day" text display using Intl API
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- Border-radius consistency across all event types
|
|
52
|
+
- CSS styling improvements for event rendering
|
|
53
|
+
|
|
54
|
+
## [3.0.3] - 2026-02-22
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
- `maxEventsPerCell` option to limit events displayed per cell in month view (with "+N more" indicator)
|
|
58
|
+
- Set to `0` for unlimited events display
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- Date navigation edge cases when switching between views
|
|
62
|
+
|
|
63
|
+
## [3.0.2] - 2026-02-20
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- `showBorder` option to control calendar outer border visibility
|
|
67
|
+
- `weekdayFormat` option with three formats: `'narrow'` (1-2 letters), `'short'` (abbreviated), `'long'` (full name)
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
- Improved theming system with better CSS custom property organization
|
|
71
|
+
|
|
72
|
+
## [3.0.1] - 2026-02-19
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
- Event tooltips with smart positioning system
|
|
76
|
+
- Tooltip configuration with `showTooltips` option
|
|
77
|
+
- Multi-line tooltip support using `\n` newline characters
|
|
78
|
+
- Customizable tooltip styling via CSS custom properties
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
- Optimized overflow behavior for better event visibility
|
|
82
|
+
|
|
83
|
+
## [3.0.0] - 2026-02-19
|
|
84
|
+
|
|
85
|
+
### Breaking Changes
|
|
86
|
+
**Version 3.0.0 represents a major rewrite with significant breaking changes from version 2.x**
|
|
87
|
+
|
|
88
|
+
This version is not backward compatible with 2.x. Please review all changes before upgrading.
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
- **Framework Wrappers**: Official React, Vue 3, and Angular component wrappers
|
|
92
|
+
- **Four View Modes**: Month, week, day, and list views with smooth transitions
|
|
93
|
+
- **Full Internationalization**: Built on `Intl.DateTimeFormat` API for native locale support
|
|
94
|
+
- **Dark Mode Support**: Automatic dark theme detection and manual toggle support
|
|
95
|
+
- **Month and Year Dropdowns**: Interactive dropdowns for quick date navigation
|
|
96
|
+
- **Year Picker**: Quick year selection in month view
|
|
97
|
+
- **Color System**: Comprehensive theming with CSS custom properties
|
|
98
|
+
- **Global Export**: Proper UMD module support (CommonJS, AMD, ES modules, browser global)
|
|
99
|
+
- **Accessibility**: Semantic HTML with proper ARIA attributes
|
|
100
|
+
|
|
101
|
+
### Changed
|
|
102
|
+
- **Standardized file naming**: Renamed and reorganized source files for consistency
|
|
103
|
+
- **Improved alignment**: Better container and element alignment across all views
|
|
104
|
+
- **Font inheritance**: Calendar now inherits font from parent container
|
|
105
|
+
- **Updated license and headers**: Clarified licensing for commercial vs non-commercial use
|
|
106
|
+
- **Complete UI overhaul**: Redesigned interface with modern styling
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
- Hour and half-hour grid lines now properly visible in day/week views
|
|
110
|
+
- GitHub Actions workflow configuration
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Version 2.x and Earlier
|
|
115
|
+
|
|
116
|
+
Changes prior to version 3.0.0 are not documented in this changelog as version 3.0.0 represents a complete rewrite of the library.
|
|
117
|
+
|
|
118
|
+
For reference, version 2.0.1 was the last release in the 2.x series.
|
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, zero-dependency JavaScript calendar component with internationalization support and framework wrappers for React, Vue, and Angular.
|
|
4
4
|
|
|
5
|
-
](https://www.npmjs.com/package/simple-calendar-js)
|
|
6
|
+
[](LICENSE)
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
@@ -17,7 +17,7 @@ A lightweight, zero-dependency JavaScript calendar component with internationali
|
|
|
17
17
|
- **Responsive Design** - Adapts to any screen size
|
|
18
18
|
- **Customizable Styling** - CSS custom properties for easy theming
|
|
19
19
|
- **Accessible** - Semantic HTML with proper ARIA attributes
|
|
20
|
-
- **Small Bundle Size** - ~
|
|
20
|
+
- **Small Bundle Size** - ~42KB minified JS + ~20KB CSS (~15KB total gzipped)
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -168,7 +168,7 @@ export class CalendarComponent {
|
|
|
168
168
|
| `showTimeInItems` | boolean | `true` | Show time in event items |
|
|
169
169
|
| `showGridLines` | boolean | `true` | Show calendar grid lines |
|
|
170
170
|
| `showBorder` | boolean | `true` | Show calendar outer border |
|
|
171
|
-
| `maxEventsPerCell` | number | `
|
|
171
|
+
| `maxEventsPerCell` | number | `-1` | Maximum events per cell in month view. `-1` = auto-calculate based on cell height (responsive), `0` = unlimited (show all events), `1+` = show exactly that number |
|
|
172
172
|
| `showToolbar` | boolean | `true` | Show the toolbar |
|
|
173
173
|
| `showTodayButton` | boolean | `true` | Show "Today" button |
|
|
174
174
|
| `showNavigation` | boolean | `true` | Show prev/next navigation arrows |
|
|
@@ -178,11 +178,15 @@ export class CalendarComponent {
|
|
|
178
178
|
| `showTooltips` | boolean | `true` | Show tooltips on hover for events |
|
|
179
179
|
| `listDaysForward` | number | `30` | Number of days forward to show in list view |
|
|
180
180
|
| `enabledViews` | string[] | `['month', 'week', 'day']` | Available view modes. Add `'list'` to enable list view |
|
|
181
|
+
| `enableDragDrop` | boolean | `false` | Enable drag and drop to move events |
|
|
182
|
+
| `enableResize` | boolean | `false` | Enable resizing events to change duration |
|
|
183
|
+
| `monthTimedEventStyle` | string | `'list'` | Display style for timed events in month view: `'list'` (schedule format) or `'block'` (traditional blocks) |
|
|
181
184
|
| `fetchEvents` | function | `null` | Async function to fetch events: `async (start, end) => Event[]` |
|
|
182
185
|
| `onEventClick` | function | `null` | Callback when event is clicked: `(event, mouseEvent) => void` |
|
|
183
186
|
| `onSlotClick` | function | `null` | Callback when time slot is clicked: `(date, mouseEvent) => void` |
|
|
184
187
|
| `onViewChange` | function | `null` | Callback when view changes: `(view) => void` |
|
|
185
188
|
| `onNavigate` | function | `null` | Callback when date range changes: `(startDate, endDate) => void` |
|
|
189
|
+
| `onEventDrop` | function | `null` | Callback when event is dropped: `(event, oldStart, oldEnd, newStart, newEnd) => void` |
|
|
186
190
|
|
|
187
191
|
## Event Object Format
|
|
188
192
|
|
|
@@ -414,6 +418,142 @@ Tooltips use CSS custom properties and can be customized:
|
|
|
414
418
|
- Special characters are automatically escaped for security
|
|
415
419
|
- Maximum width is 250px by default (can be customized via CSS variables)
|
|
416
420
|
|
|
421
|
+
## Drag and Drop
|
|
422
|
+
|
|
423
|
+
SimpleCalendarJs supports drag and drop for moving events and resizing them to change their duration.
|
|
424
|
+
|
|
425
|
+
### Configuration
|
|
426
|
+
|
|
427
|
+
```javascript
|
|
428
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
429
|
+
enableDragDrop: true, // Enable moving events
|
|
430
|
+
enableResize: true, // Enable resizing events
|
|
431
|
+
|
|
432
|
+
onEventDrop: (event, oldStart, oldEnd, newStart, newEnd) => {
|
|
433
|
+
// Detect if this is a move or resize
|
|
434
|
+
const isMoved = oldStart.getTime() !== newStart.getTime();
|
|
435
|
+
const isResized = oldEnd.getTime() !== newEnd.getTime() && !isMoved;
|
|
436
|
+
|
|
437
|
+
if (isMoved) {
|
|
438
|
+
console.log(`Event "${event.title}" moved to ${newStart}`);
|
|
439
|
+
} else if (isResized) {
|
|
440
|
+
console.log(`Event "${event.title}" resized to end at ${newEnd}`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Update your backend
|
|
444
|
+
await fetch(`/api/events/${event.id}`, {
|
|
445
|
+
method: 'PATCH',
|
|
446
|
+
headers: { 'Content-Type': 'application/json' },
|
|
447
|
+
body: JSON.stringify({
|
|
448
|
+
start: newStart.toISOString(),
|
|
449
|
+
end: newEnd.toISOString(),
|
|
450
|
+
allDay: event.allDay
|
|
451
|
+
})
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Moving Events (`enableDragDrop`)
|
|
458
|
+
|
|
459
|
+
**How It Works:**
|
|
460
|
+
- **Drag Initiation**: Click and hold on an event, then move at least 5px or wait 150ms
|
|
461
|
+
- **Visual Feedback**: The event follows your cursor while dragging
|
|
462
|
+
- **Snap to Grid**: Events snap to 15-minute intervals in week/day views
|
|
463
|
+
- **Drop**: Release to drop the event at the new date/time
|
|
464
|
+
- **Cancel**: Press ESC to cancel the drag operation
|
|
465
|
+
- **Duration Preservation**: Events maintain their duration when moved
|
|
466
|
+
- **Touch Support**: Full support for mobile/tablet touch gestures
|
|
467
|
+
|
|
468
|
+
**Cross-Boundary Conversion:**
|
|
469
|
+
|
|
470
|
+
When dragging events between different sections:
|
|
471
|
+
|
|
472
|
+
**Timed Event → All-Day Section (Week/Day Views):**
|
|
473
|
+
- Converts to an all-day event
|
|
474
|
+
- Preserves the day span
|
|
475
|
+
|
|
476
|
+
**All-Day Event → Timed Section (Week/Day Views):**
|
|
477
|
+
- Converts to a timed event
|
|
478
|
+
- Default duration: 1 hour
|
|
479
|
+
- Snaps to the time slot where dropped
|
|
480
|
+
|
|
481
|
+
**Month View:**
|
|
482
|
+
- Events maintain their original type (all-day stays all-day, timed stays timed)
|
|
483
|
+
- Timed events preserve their original time of day
|
|
484
|
+
|
|
485
|
+
### Resizing Events (`enableResize`)
|
|
486
|
+
|
|
487
|
+
**Horizontal Resize (All-Day Events):**
|
|
488
|
+
- **Visual Indicator**: Small vertical line appears on the right edge when hovering
|
|
489
|
+
- **How It Works**: Drag the right edge to change the number of days the event spans
|
|
490
|
+
- **Available In**: Month view, week/day all-day sections
|
|
491
|
+
- **Minimum**: 1 day
|
|
492
|
+
|
|
493
|
+
**Vertical Resize (Timed Events):**
|
|
494
|
+
- **Visual Indicator**: Small horizontal line appears at the bottom when hovering
|
|
495
|
+
- **How It Works**: Drag the bottom edge to change the end time
|
|
496
|
+
- **Available In**: Week/day timed sections
|
|
497
|
+
- **Snap to Grid**: 15-minute intervals
|
|
498
|
+
- **Minimum**: 15 minutes
|
|
499
|
+
- **Live Feedback**: Time display updates to show start and end times as you drag
|
|
500
|
+
|
|
501
|
+
### Callback Parameters
|
|
502
|
+
|
|
503
|
+
The `onEventDrop` callback receives the same parameters for both move and resize operations:
|
|
504
|
+
|
|
505
|
+
| Parameter | Type | Description |
|
|
506
|
+
|-----------|------|-------------|
|
|
507
|
+
| `event` | Object | The updated event object (with new start/end) |
|
|
508
|
+
| `oldStart` | Date | Original start date/time |
|
|
509
|
+
| `oldEnd` | Date | Original end date/time |
|
|
510
|
+
| `newStart` | Date | New start date/time |
|
|
511
|
+
| `newEnd` | Date | New end date/time |
|
|
512
|
+
|
|
513
|
+
**Detecting Operation Type:**
|
|
514
|
+
- **Move**: `oldStart !== newStart`
|
|
515
|
+
- **Resize**: `oldStart === newStart` and `oldEnd !== newEnd`
|
|
516
|
+
|
|
517
|
+
### Important Notes
|
|
518
|
+
|
|
519
|
+
- The calendar updates the event internally before firing the callback
|
|
520
|
+
- You **must** update your backend in the `onEventDrop` callback
|
|
521
|
+
- If the backend update fails, call `calendar.refresh()` to revert to the previous state
|
|
522
|
+
- Both features are disabled in list view (read-only)
|
|
523
|
+
- You can enable one, both, or neither feature independently
|
|
524
|
+
|
|
525
|
+
## Month View Timed Event Display Style
|
|
526
|
+
|
|
527
|
+
The `monthTimedEventStyle` option controls how timed events are displayed in month view:
|
|
528
|
+
|
|
529
|
+
### List Style (Default: `'list'`)
|
|
530
|
+
Schedule-style display with horizontal layout:
|
|
531
|
+
- **Colored dot**: Shows event color as a small circle
|
|
532
|
+
- **Time**: Displays start time (if `showTimeInItems` is enabled)
|
|
533
|
+
- **Title**: Event title truncated with ellipsis if too long
|
|
534
|
+
- **Compact**: Clean, minimal appearance similar to schedule apps
|
|
535
|
+
|
|
536
|
+
```javascript
|
|
537
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
538
|
+
monthTimedEventStyle: 'list', // Default
|
|
539
|
+
showTimeInItems: true // Shows time next to dot
|
|
540
|
+
});
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Block Style (`'block'`)
|
|
544
|
+
Traditional calendar block display:
|
|
545
|
+
- **Colored Background**: Full event background in event color
|
|
546
|
+
- **Time Display**: Start time shown inside block (if enabled)
|
|
547
|
+
- **Classic Look**: Traditional calendar appearance
|
|
548
|
+
|
|
549
|
+
```javascript
|
|
550
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
551
|
+
monthTimedEventStyle: 'block'
|
|
552
|
+
});
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**Note**: This option only affects timed events in month view. All-day events always display as blocks, and week/day views always use block style with duration-based heights.
|
|
556
|
+
|
|
417
557
|
## API Methods
|
|
418
558
|
|
|
419
559
|
```javascript
|
|
@@ -480,6 +620,190 @@ addEventToDatabase(newEvent);
|
|
|
480
620
|
calendar.refresh(); // ✓ Fetch (clears cache)
|
|
481
621
|
```
|
|
482
622
|
|
|
623
|
+
## Persisting User Preferences
|
|
624
|
+
|
|
625
|
+
The calendar doesn't include built-in persistence - this is intentionally left to your application so you have full control over how and where preferences are stored. Here are common patterns:
|
|
626
|
+
|
|
627
|
+
### Using LocalStorage (Persistent Across Sessions)
|
|
628
|
+
|
|
629
|
+
```javascript
|
|
630
|
+
// Restore saved preferences or use defaults
|
|
631
|
+
const savedView = localStorage.getItem('calendarView') || 'month';
|
|
632
|
+
const savedDate = localStorage.getItem('calendarDate');
|
|
633
|
+
|
|
634
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
635
|
+
defaultView: savedView,
|
|
636
|
+
defaultDate: savedDate ? new Date(savedDate) : null,
|
|
637
|
+
|
|
638
|
+
// Save view changes
|
|
639
|
+
onViewChange: (view) => {
|
|
640
|
+
localStorage.setItem('calendarView', view);
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
// Save navigation (current date)
|
|
644
|
+
onNavigate: (startDate) => {
|
|
645
|
+
localStorage.setItem('calendarDate', startDate.toISOString());
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Using SessionStorage (Per-Tab)
|
|
651
|
+
|
|
652
|
+
```javascript
|
|
653
|
+
// Same as localStorage but survives only for the current tab/session
|
|
654
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
655
|
+
defaultView: sessionStorage.getItem('calendarView') || 'month',
|
|
656
|
+
onViewChange: (view) => sessionStorage.setItem('calendarView', view),
|
|
657
|
+
onNavigate: (start) => sessionStorage.setItem('calendarDate', start.toISOString())
|
|
658
|
+
});
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Using URL Query Parameters (Shareable State)
|
|
662
|
+
|
|
663
|
+
```javascript
|
|
664
|
+
// Read from URL
|
|
665
|
+
const params = new URLSearchParams(window.location.search);
|
|
666
|
+
const view = params.get('view') || 'month';
|
|
667
|
+
const date = params.get('date') ? new Date(params.get('date')) : null;
|
|
668
|
+
|
|
669
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
670
|
+
defaultView: view,
|
|
671
|
+
defaultDate: date,
|
|
672
|
+
|
|
673
|
+
onViewChange: (newView) => {
|
|
674
|
+
const url = new URL(window.location);
|
|
675
|
+
url.searchParams.set('view', newView);
|
|
676
|
+
window.history.pushState({}, '', url);
|
|
677
|
+
},
|
|
678
|
+
|
|
679
|
+
onNavigate: (startDate) => {
|
|
680
|
+
const url = new URL(window.location);
|
|
681
|
+
url.searchParams.set('date', startDate.toISOString().split('T')[0]);
|
|
682
|
+
window.history.pushState({}, '', url);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### React Example with State Hook
|
|
688
|
+
|
|
689
|
+
```javascript
|
|
690
|
+
function MyCalendar() {
|
|
691
|
+
const [view, setView] = useState(
|
|
692
|
+
() => localStorage.getItem('calendarView') || 'month'
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
const [currentDate, setCurrentDate] = useState(
|
|
696
|
+
() => {
|
|
697
|
+
const saved = localStorage.getItem('calendarDate');
|
|
698
|
+
return saved ? new Date(saved) : new Date();
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
const handleViewChange = (newView) => {
|
|
703
|
+
setView(newView);
|
|
704
|
+
localStorage.setItem('calendarView', newView);
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
const handleNavigate = (startDate) => {
|
|
708
|
+
setCurrentDate(startDate);
|
|
709
|
+
localStorage.setItem('calendarDate', startDate.toISOString());
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
return (
|
|
713
|
+
<SimpleCalendarJsReact
|
|
714
|
+
defaultView={view}
|
|
715
|
+
defaultDate={currentDate}
|
|
716
|
+
onViewChange={handleViewChange}
|
|
717
|
+
onNavigate={handleNavigate}
|
|
718
|
+
fetchEvents={fetchEvents}
|
|
719
|
+
/>
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Vue Example with Composable
|
|
725
|
+
|
|
726
|
+
```javascript
|
|
727
|
+
// composables/useCalendarPreferences.js
|
|
728
|
+
import { ref, watch } from 'vue';
|
|
729
|
+
|
|
730
|
+
export function useCalendarPreferences() {
|
|
731
|
+
const view = ref(localStorage.getItem('calendarView') || 'month');
|
|
732
|
+
const currentDate = ref(
|
|
733
|
+
localStorage.getItem('calendarDate')
|
|
734
|
+
? new Date(localStorage.getItem('calendarDate'))
|
|
735
|
+
: new Date()
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
watch(view, (newView) => {
|
|
739
|
+
localStorage.setItem('calendarView', newView);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
watch(currentDate, (newDate) => {
|
|
743
|
+
localStorage.setItem('calendarDate', newDate.toISOString());
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
return { view, currentDate };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// In component
|
|
750
|
+
<script setup>
|
|
751
|
+
import { useCalendarPreferences } from './composables/useCalendarPreferences';
|
|
752
|
+
const { view, currentDate } = useCalendarPreferences();
|
|
753
|
+
|
|
754
|
+
const handleViewChange = (newView) => {
|
|
755
|
+
view.value = newView;
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const handleNavigate = (startDate) => {
|
|
759
|
+
currentDate.value = startDate;
|
|
760
|
+
};
|
|
761
|
+
</script>
|
|
762
|
+
|
|
763
|
+
<template>
|
|
764
|
+
<SimpleCalendarJsVue
|
|
765
|
+
:defaultView="view"
|
|
766
|
+
:defaultDate="currentDate"
|
|
767
|
+
@viewChange="handleViewChange"
|
|
768
|
+
@navigate="handleNavigate"
|
|
769
|
+
/>
|
|
770
|
+
</template>
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### Server-Side Preferences (Synced Across Devices)
|
|
774
|
+
|
|
775
|
+
```javascript
|
|
776
|
+
// Load preferences from your API
|
|
777
|
+
const preferences = await fetch('/api/user/calendar-preferences').then(r => r.json());
|
|
778
|
+
|
|
779
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
780
|
+
defaultView: preferences.view || 'month',
|
|
781
|
+
defaultDate: preferences.lastViewedDate ? new Date(preferences.lastViewedDate) : null,
|
|
782
|
+
|
|
783
|
+
onViewChange: async (view) => {
|
|
784
|
+
// Debounce to avoid too many API calls
|
|
785
|
+
clearTimeout(window.savePreferencesTimeout);
|
|
786
|
+
window.savePreferencesTimeout = setTimeout(() => {
|
|
787
|
+
fetch('/api/user/calendar-preferences', {
|
|
788
|
+
method: 'PATCH',
|
|
789
|
+
headers: { 'Content-Type': 'application/json' },
|
|
790
|
+
body: JSON.stringify({ view })
|
|
791
|
+
});
|
|
792
|
+
}, 1000);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
### What to Persist
|
|
798
|
+
|
|
799
|
+
Common preferences to save:
|
|
800
|
+
- **View mode**: `'month'`, `'week'`, `'day'`, or `'list'`
|
|
801
|
+
- **Current date**: Last viewed date for returning to the same position
|
|
802
|
+
- **Enabled views**: Which view modes the user prefers to have available
|
|
803
|
+
- **UI preferences**: Dark mode, show/hide options
|
|
804
|
+
|
|
805
|
+
**Note**: The calendar is designed to be stateless - all state management is your responsibility, giving you full flexibility over storage method, persistence duration, and privacy controls.
|
|
806
|
+
|
|
483
807
|
## Internationalization
|
|
484
808
|
|
|
485
809
|
SimpleCalendarJs uses the native Intl.DateTimeFormat API for full locale support. Simply pass a valid locale code:
|
|
@@ -686,6 +1010,9 @@ Example with a complete brand color scheme:
|
|
|
686
1010
|
- `--cal-tooltip-max-width`, `--cal-tooltip-padding`, `--cal-tooltip-radius` - Tooltip sizing
|
|
687
1011
|
- `--cal-tooltip-font-size`, `--cal-tooltip-offset` - Tooltip typography
|
|
688
1012
|
|
|
1013
|
+
**Loading Overlay:**
|
|
1014
|
+
- `--cal-loading-bg` - Loading spinner overlay background (default: `rgba(255, 255, 255, 0.7)` in light mode, `rgba(31, 41, 55, 0.7)` in dark mode)
|
|
1015
|
+
|
|
689
1016
|
All styles are scoped under `.uc-calendar` to prevent conflicts with your existing CSS.
|
|
690
1017
|
|
|
691
1018
|
## Framework Wrapper APIs
|
|
@@ -803,6 +1130,7 @@ For commercial licensing inquiries: simplecalendarjs@gmail.com
|
|
|
803
1130
|
|
|
804
1131
|
- **Documentation**: [docs.html](docs.html)
|
|
805
1132
|
- **Demo**: [index.html](index.html)
|
|
1133
|
+
- **Changelog**: [CHANGELOG.md](CHANGELOG.md)
|
|
806
1134
|
- **Repository**: [github.com/pclslopes/SimpleCalendarJs](https://github.com/pclslopes/SimpleCalendarJs)
|
|
807
1135
|
- **NPM**: [npmjs.com/package/simple-calendar-js](https://www.npmjs.com/package/simple-calendar-js)
|
|
808
1136
|
- **Website**: [simplecalendarjs.com](https://www.simplecalendarjs.com)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
2
|
+
* SimpleCalendarJs v3.0.6 — simple-calendar-js.css
|
|
3
3
|
* A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
|
|
4
4
|
*
|
|
5
5
|
* @author Pedro Lopes <simplecalendarjs@gmail.com>
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
* All styles scoped under .uc-calendar to prevent leaking.
|
|
10
10
|
* Override any --cal-* variable in :root or on .uc-calendar.
|
|
11
11
|
*/
|
|
12
|
-
:root{--cal-bg:#ffffff;--cal-text:#111827;--cal-text-subtle:#6b7280;--cal-text-muted:#9ca3af;--cal-border:#e5e7eb;--cal-border-strong:#d1d5db;--cal-primary:#4f46e5;--cal-primary-dark:#4338ca;--cal-primary-light:#eef2ff;--cal-event-bg:var(--cal-primary);--cal-event-text:#ffffff;--cal-event-border-radius:3px;--cal-today-bg:#eef2ff;--cal-today-text:var(--cal-primary);--cal-hover:#f9fafb;--cal-hover-strong:#f3f4f6;--cal-selected-bg:#ede9fe;--cal-font-family:inherit;--cal-font-size:13px;--cal-time-col-width:64px;--cal-hour-height:60px;--cal-cell-min-height:112px;--cal-event-height:22px;--cal-event-gap:2px;--cal-header-day-height:30px;--cal-day-name-height:36px;--cal-now-color:#ef4444;--cal-toolbar-bg:var(--cal-bg);--cal-radius:8px;--cal-shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.06);--cal-transition:150ms ease;--cal-tooltip-bg:#1f2937;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#374151;--cal-tooltip-shadow:0 4px 12px rgba(0, 0, 0, 0.15);--cal-tooltip-max-width:250px;--cal-tooltip-padding:8px 12px;--cal-tooltip-radius:6px;--cal-tooltip-font-size:12px;--cal-tooltip-offset:8px}.uc-calendar{font-family:var(--cal-font-family);font-size:var(--cal-font-size);color:var(--cal-text);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:var(--cal-radius);overflow:visible;display:flex;flex-direction:column;position:relative;min-height:500px;-webkit-font-smoothing:antialiased}.uc-calendar *,.uc-calendar ::after,.uc-calendar ::before{box-sizing:border-box;margin:0;padding:0}.uc-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;background:var(--cal-toolbar-bg);border-bottom:1px solid var(--cal-border);border-radius:var(--cal-radius) var(--cal-radius) 0 0;flex-shrink:0;flex-wrap:wrap}.uc-toolbar-section{display:flex;align-items:center;gap:4px}.uc-toolbar-center{flex:1;display:flex;justify-content:center;position:relative}.uc-title{font-size:15px;font-weight:600;color:var(--cal-text);white-space:nowrap;letter-spacing:-.01em;display:flex;align-items:baseline;gap:5px}.uc-title-main{text-transform:capitalize}.uc-year-btn{background:0 0;border:none;font:inherit;font-weight:600;font-size:15px;color:var(--cal-primary);cursor:pointer;padding:1px 4px;border-radius:4px;line-height:inherit;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px;transition:background var(--cal-transition),color var(--cal-transition)}.uc-year-btn.uc-open,.uc-year-btn:hover{background:var(--cal-primary-light);text-decoration:none}.uc-year-picker{position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:10px;box-shadow:0 4px 24px rgba(0,0,0,.12);padding:10px;z-index:200;min-width:210px}.uc-year-picker-nav{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.uc-year-range{font-size:12px;font-weight:600;color:var(--cal-text-subtle)}.uc-year-nav-btn{background:0 0;border:none;font-size:18px;line-height:1;color:var(--cal-text);cursor:pointer;padding:2px 7px;border-radius:5px;font-family:inherit;transition:background var(--cal-transition)}.uc-year-nav-btn:hover{background:var(--cal-hover-strong)}.uc-year-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:4px}.uc-year-item{background:0 0;border:none;border-radius:6px;font-size:13px;font-weight:500;font-family:inherit;color:var(--cal-text);cursor:pointer;padding:7px 4px;text-align:center;transition:background var(--cal-transition),color var(--cal-transition)}.uc-year-item:hover{background:var(--cal-hover-strong)}.uc-year-item.uc-today-year{color:var(--cal-primary);font-weight:700}.uc-year-item.uc-active{background:var(--cal-primary);color:#fff;font-weight:700}.uc-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:1px solid var(--cal-border);background:var(--cal-bg);color:var(--cal-text);border-radius:6px;padding:5px 10px;font-size:13px;font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;line-height:1.4;transition:background var(--cal-transition),border-color var(--cal-transition),color var(--cal-transition);user-select:none}.uc-btn:hover{background:var(--cal-hover-strong);border-color:var(--cal-border-strong)}.uc-btn:active{background:var(--cal-selected-bg)}.uc-btn:focus-visible{outline:2px solid var(--cal-primary);outline-offset:2px}.uc-nav-btn{font-size:18px;padding:4px 8px;line-height:1;border-color:transparent;background:0 0}.uc-nav-btn:hover{border-color:var(--cal-border)}.uc-today-btn{padding:5px 12px}.uc-today-btn.uc-active{background:var(--cal-primary-light);color:var(--cal-primary);font-weight:600;border-color:transparent}.uc-view-switcher{display:flex;border:1px solid var(--cal-border);border-radius:6px;overflow:hidden}.uc-view-btn{border:none;border-radius:0;border-right:1px solid var(--cal-border);background:0 0;color:var(--cal-text-subtle);font-weight:500;padding:5px 11px}.uc-view-btn:last-child{border-right:none}.uc-view-btn:hover{background:var(--cal-hover-strong);color:var(--cal-text);border-color:transparent}.uc-view-btn.uc-active{background:var(--cal-primary-light);color:var(--cal-primary);font-weight:600}.uc-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.7);z-index:100;pointer-events:none}.uc-spinner{width:28px;height:28px;border:3px solid var(--cal-border);border-top-color:var(--cal-primary);border-radius:50%;animation:uc-spin .6s linear infinite}@keyframes uc-spin{to{transform:rotate(360deg)}}.uc-view-container{flex:1;overflow:visible;display:flex;flex-direction:column}.uc-view-container:first-child .uc-month-header,.uc-view-container:first-child .uc-week-header{border-radius:var(--cal-radius) var(--cal-radius) 0 0}.uc-month-view{display:flex;flex-direction:column;flex:1;overflow:visible;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-month-header{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--cal-border);flex-shrink:0}.uc-month-day-name{height:var(--cal-day-name-height,36px);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--cal-text-subtle);user-select:none}.uc-month-body{flex:1;display:flex;flex-direction:column;overflow:visible}.uc-week-row{flex:1;position:relative;min-height:var(--cal-cell-min-height);border-bottom:1px solid var(--cal-border)}.uc-week-row:last-child{border-bottom:none;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-week-cells{position:absolute;inset:0;display:grid;grid-template-columns:repeat(7,1fr);z-index:1}.uc-day-cell{border-right:1px solid var(--cal-border);padding:4px 6px 4px 6px;cursor:pointer;transition:background var(--cal-transition);min-height:var(--cal-cell-min-height)}.uc-day-cell:last-child{border-right:none}.uc-day-cell:hover{background:var(--cal-hover)}.uc-day-cell.uc-today{background:var(--cal-today-bg)}.uc-day-cell.uc-other-month .uc-day-number{color:var(--cal-text-muted)}.uc-day-number{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;font-size:13px;font-weight:500;border-radius:50%;color:var(--cal-text);cursor:pointer;transition:background var(--cal-transition),color var(--cal-transition);line-height:1}.uc-day-number:hover{background:var(--cal-hover-strong)}.uc-today .uc-day-number{background:var(--cal-primary);color:#fff;font-weight:700}.uc-today .uc-day-number:hover{background:var(--cal-primary-dark)}.uc-week-events{position:absolute;inset:0;z-index:2;pointer-events:none;overflow:visible}.uc-event-bar{position:absolute;height:var(--cal-event-height);background:var(--cal-event-bg);color:var(--cal-event-text);border-radius:var(--cal-event-border-radius);display:flex;align-items:center;gap:4px;padding:0 6px;cursor:pointer;pointer-events:auto;white-space:nowrap;font-size:12px;font-weight:500;transition:filter var(--cal-transition),opacity var(--cal-transition);z-index:3}.uc-event-bar:hover{filter:brightness(.92);z-index:1000}.uc-event-bar:active{filter:brightness(.85)}.uc-event-bar.uc-continues-left{border-top-left-radius:0;border-bottom-left-radius:0}.uc-event-bar.uc-continues-right{border-top-right-radius:0;border-bottom-right-radius:0}.uc-event-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.uc-event-time{font-size:11px;opacity:.85;flex-shrink:0}.uc-more-link{position:absolute;height:16px;display:flex;align-items:center;padding:0 6px;font-size:10px;font-weight:600;color:var(--cal-text-subtle);cursor:pointer;pointer-events:auto;border-radius:var(--cal-event-border-radius);white-space:nowrap;transition:background var(--cal-transition),color var(--cal-transition);z-index:3}.uc-more-link:hover{background:var(--cal-hover-strong);color:var(--cal-text)}.uc-day-view,.uc-week-view{display:flex;flex-direction:column;flex:1;overflow:visible;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-week-header{display:flex;border-bottom:1px solid var(--cal-border);flex-shrink:0;background:var(--cal-bg)}.uc-time-gutter-spacer{width:var(--cal-time-col-width);flex-shrink:0;border-right:1px solid var(--cal-border)}.uc-all-day-label{display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--cal-text-muted);white-space:nowrap}.uc-week-day-headers{flex:1;display:grid;grid-template-columns:repeat(7,1fr)}.uc-week-day-headers.uc-day-header-single{grid-template-columns:1fr}.uc-week-day-header{padding:8px 4px;display:flex;flex-direction:column;align-items:center;gap:2px;border-right:1px solid var(--cal-border);cursor:default}.uc-week-day-header:last-child{border-right:none}.uc-week-day-name{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--cal-text-subtle);user-select:none}.uc-week-day-num{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;font-size:16px;font-weight:600;border-radius:50%;color:var(--cal-text);cursor:pointer;transition:background var(--cal-transition),color var(--cal-transition)}.uc-week-day-num:hover{background:var(--cal-hover-strong)}.uc-week-day-header.uc-today .uc-week-day-name{color:var(--cal-primary)}.uc-week-day-header.uc-today .uc-week-day-num{background:var(--cal-primary);color:#fff}.uc-week-day-header.uc-today .uc-week-day-num:hover{background:var(--cal-primary-dark)}.uc-all-day-section{display:flex;border-bottom:1px solid var(--cal-border);flex-shrink:0;background:var(--cal-bg)}.uc-all-day-events{flex:1;position:relative;min-height:calc(var(--cal-event-height) + 6px);padding:2px 0}.uc-time-body{flex:1;overflow-y:auto;overflow-x:visible;position:relative;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-time-body::-webkit-scrollbar{width:6px}.uc-time-body::-webkit-scrollbar-track{background:0 0}.uc-time-body::-webkit-scrollbar-thumb{background:var(--cal-border-strong);border-radius:3px}.uc-time-grid-inner{display:flex;flex-direction:row;height:calc(24 * var(--cal-hour-height));position:relative}.uc-time-gutter{width:var(--cal-time-col-width);flex-shrink:0;border-right:1px solid var(--cal-border);position:relative}.uc-hour-cell{height:var(--cal-hour-height);position:relative;display:flex;align-items:flex-start;justify-content:flex-end;padding-right:8px}.uc-hour-label{font-size:11px;font-weight:500;color:var(--cal-text-subtle);user-select:none;pointer-events:none;white-space:nowrap;transform:translateY(-50%);margin-top:1px}.uc-time-columns{flex:1;display:grid;grid-template-columns:repeat(var(--uc-col-count,7),1fr);position:relative}.uc-time-col{position:relative;border-right:1px solid var(--cal-border);height:calc(24 * var(--cal-hour-height));cursor:pointer}.uc-time-col:last-child{border-right:none}.uc-time-col.uc-today{background:var(--cal-today-bg)}.uc-hour-row{height:var(--cal-hour-height);border-bottom:1px solid var(--cal-border);position:relative;pointer-events:none}.uc-half-hour-line{position:absolute;top:50%;left:0;right:0;border-top:1px dashed var(--cal-border);pointer-events:none}.uc-timed-event{position:absolute;background:var(--cal-event-bg);color:var(--cal-event-text);border-radius:var(--cal-event-border-radius);padding:3px 6px;cursor:pointer;display:flex;flex-direction:column;gap:1px;font-size:12px;font-weight:500;border-left:3px solid rgba(0,0,0,.15);transition:filter var(--cal-transition),box-shadow var(--cal-transition);z-index:2;min-height:18px}.uc-timed-event:hover{filter:brightness(.92);box-shadow:0 2px 8px rgba(0,0,0,.15);z-index:1000}.uc-timed-event .uc-event-title{font-weight:600;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-timed-event .uc-event-time{font-size:11px;opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-timed-event--short{flex-direction:row;align-items:center;gap:4px}.uc-timed-event--short .uc-event-time{flex-shrink:0}.uc-timed-event--short .uc-event-title{flex:1;min-width:0}.uc-now-indicator{position:absolute;left:0;right:0;pointer-events:none;z-index:10;display:flex;align-items:center}.uc-now-dot{width:10px;height:10px;border-radius:50%;background:var(--cal-now-color);flex-shrink:0;margin-left:-5px}.uc-now-line{flex:1;height:2px;background:var(--cal-now-color)}.uc-list-view{padding:0;overflow:visible;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-list-empty{display:flex;align-items:center;justify-content:center;min-height:300px;color:var(--cal-text-muted);font-size:14px}.uc-list-date-group{margin-bottom:24px;overflow:visible}.uc-list-date-group:last-child{margin-bottom:0}.uc-list-events{background:var(--cal-bg);overflow:visible}.uc-list-event{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-bottom:1px solid var(--cal-border);cursor:pointer;transition:background var(--cal-transition);position:relative;overflow:visible}.uc-list-date-header{font-size:14px;font-weight:600;color:var(--cal-text);padding:12px 16px;background:var(--cal-bg-secondary);border-bottom:1px solid var(--cal-border);position:sticky;top:0;z-index:1;overflow:visible}.uc-list-event:hover{background:var(--cal-hover)}.uc-list-event:last-child{border-bottom:none}.uc-list-event-indicator{width:8px;height:8px;border-radius:50%;margin-top:4px;flex-shrink:0}.uc-list-event-time{font-size:13px;font-weight:500;color:var(--cal-text-secondary);min-width:80px;flex-shrink:0}.uc-list-event-content{flex:1;min-width:0}.uc-list-event-title{font-size:14px;font-weight:500;color:var(--cal-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::after,.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::before{opacity:1;visibility:visible;transition-delay:0.4s}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{content:attr(data-tooltip);position:absolute;bottom:calc(100% + var(--cal-tooltip-offset));left:50%;transform:translateX(-50%);background:var(--cal-tooltip-bg);color:var(--cal-tooltip-text);border:1px solid var(--cal-tooltip-border);box-shadow:var(--cal-tooltip-shadow);padding:var(--cal-tooltip-padding);border-radius:var(--cal-tooltip-radius);font-size:var(--cal-tooltip-font-size);font-weight:500;line-height:1.4;max-width:var(--cal-tooltip-max-width);width:max-content;white-space:pre-wrap;word-wrap:break-word;text-align:left;z-index:1000;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::after{content:'';position:absolute;bottom:calc(100% + var(--cal-tooltip-offset) - 5px);left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid var(--cal-tooltip-bg);z-index:1002;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{filter:drop-shadow(0 1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-left::before{left:auto;right:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-left::after{left:auto;right:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::before{left:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::after{left:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-bottom::before{bottom:auto;top:calc(100% + var(--cal-tooltip-offset));filter:drop-shadow(0 -1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-bottom::after{bottom:auto;top:calc(100% + var(--cal-tooltip-offset) - 5px);border-top:5px solid var(--cal-tooltip-bg);border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:none;transform:translateX(-50%) rotate(180deg)}@media (max-width:768px){.uc-toolbar{padding:8px 10px}.uc-title{font-size:14px}.uc-toolbar-center{order:-1;width:100%;flex:none}.uc-toolbar-section{justify-content:center}.uc-view-btn{padding:5px 8px;font-size:12px}:root{--cal-time-col-width:52px;--cal-hour-height:52px;--cal-cell-min-height:88px;--cal-event-height:20px}.uc-week-day-num{width:24px;height:24px;font-size:13px}.uc-week-day-name{font-size:10px}.uc-hour-cell:nth-child(odd) .uc-hour-label{visibility:hidden}}@media (max-width:480px){.uc-today-btn{display:none}.uc-toolbar-section.uc-toolbar-right{gap:2px}}.uc-calendar.uc-no-grid .uc-day-cell,.uc-calendar.uc-no-grid .uc-day-header-row,.uc-calendar.uc-no-grid .uc-day-name-row,.uc-calendar.uc-no-grid .uc-grid-line,.uc-calendar.uc-no-grid .uc-hour-cell,.uc-calendar.uc-no-grid .uc-time-col,.uc-calendar.uc-no-grid .uc-time-gutter,.uc-calendar.uc-no-grid .uc-week-day-header,.uc-calendar.uc-no-grid .uc-week-row{border:none!important}.uc-calendar.uc-no-grid .uc-all-day-section{border-top:none!important;border-left:none!important;border-right:none!important}.uc-calendar.uc-no-border{border:none!important}.uc-calendar.uc-dark,.uc-dark .uc-calendar{--cal-bg:#1f2937;--cal-text:#f9fafb;--cal-text-subtle:#9ca3af;--cal-text-muted:#6b7280;--cal-border:#374151;--cal-border-strong:#4b5563;--cal-hover:#374151;--cal-hover-strong:#4b5563;--cal-selected-bg:#78716c;--cal-today-bg:#57534e;--cal-primary-light:#57534e;--cal-toolbar-bg:#111827;--cal-tooltip-bg:#374151;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#4b5563}@media print{.uc-toolbar{display:none}.uc-time-body{overflow:visible}.uc-calendar{border:none}}
|
|
12
|
+
:root{--cal-bg:#ffffff;--cal-bg-secondary:#f9fafb;--cal-text:#111827;--cal-text-subtle:#6b7280;--cal-text-muted:#9ca3af;--cal-border:#e5e7eb;--cal-border-strong:#d1d5db;--cal-primary:#4f46e5;--cal-primary-dark:#4338ca;--cal-primary-light:#eef2ff;--cal-event-bg:var(--cal-primary);--cal-event-text:#ffffff;--cal-event-border-radius:3px;--cal-today-bg:#eef2ff;--cal-today-text:var(--cal-primary);--cal-hover:#f9fafb;--cal-hover-strong:#f3f4f6;--cal-selected-bg:#ede9fe;--cal-font-family:inherit;--cal-font-size:13px;--cal-time-col-width:64px;--cal-hour-height:60px;--cal-cell-min-height:112px;--cal-event-height:22px;--cal-event-gap:2px;--cal-header-day-height:30px;--cal-day-name-height:36px;--cal-now-color:#ef4444;--cal-toolbar-bg:var(--cal-bg);--cal-radius:8px;--cal-shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.06);--cal-transition:150ms ease;--cal-tooltip-bg:#1f2937;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#374151;--cal-tooltip-shadow:0 4px 12px rgba(0, 0, 0, 0.15);--cal-tooltip-max-width:250px;--cal-tooltip-padding:8px 12px;--cal-tooltip-radius:6px;--cal-tooltip-font-size:12px;--cal-tooltip-offset:8px;--cal-loading-bg:rgba(255, 255, 255, 0.7)}.uc-calendar{font-family:var(--cal-font-family);font-size:var(--cal-font-size);color:var(--cal-text);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:var(--cal-radius);overflow:hidden;display:flex;flex-direction:column;position:relative;-webkit-font-smoothing:antialiased}.uc-calendar *,.uc-calendar ::after,.uc-calendar ::before{box-sizing:border-box;margin:0;padding:0}.uc-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;background:var(--cal-toolbar-bg);border-bottom:1px solid var(--cal-border);border-radius:var(--cal-radius) var(--cal-radius) 0 0;flex-shrink:0;flex-wrap:wrap}.uc-toolbar-section{display:flex;align-items:center;gap:4px}.uc-toolbar-center{flex:1;display:flex;justify-content:center;position:relative}.uc-title{font-size:15px;font-weight:600;color:var(--cal-text);white-space:nowrap;letter-spacing:-.01em;display:flex;align-items:baseline;gap:5px}.uc-title-main{text-transform:capitalize}.uc-year-btn{background:0 0;border:none;font:inherit;font-weight:600;font-size:15px;color:var(--cal-primary);cursor:pointer;padding:1px 4px;border-radius:4px;line-height:inherit;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px;transition:background var(--cal-transition),color var(--cal-transition)}.uc-year-btn.uc-open,.uc-year-btn:hover{background:var(--cal-primary-light);text-decoration:none}.uc-year-picker{position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:10px;box-shadow:0 4px 24px rgba(0,0,0,.12);padding:10px;z-index:200;min-width:210px}.uc-year-picker-nav{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.uc-year-range{font-size:12px;font-weight:600;color:var(--cal-text-subtle)}.uc-year-nav-btn{background:0 0;border:none;font-size:18px;line-height:1;color:var(--cal-text);cursor:pointer;padding:2px 7px;border-radius:5px;font-family:inherit;transition:background var(--cal-transition)}.uc-year-nav-btn:hover{background:var(--cal-hover-strong)}.uc-year-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:4px}.uc-year-item{background:0 0;border:none;border-radius:6px;font-size:13px;font-weight:500;font-family:inherit;color:var(--cal-text);cursor:pointer;padding:7px 4px;text-align:center;transition:background var(--cal-transition),color var(--cal-transition)}.uc-year-item:hover{background:var(--cal-hover-strong)}.uc-year-item.uc-today-year{color:var(--cal-primary);font-weight:700}.uc-year-item.uc-active{background:var(--cal-primary);color:#fff;font-weight:700}.uc-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:1px solid var(--cal-border);background:var(--cal-bg);color:var(--cal-text);border-radius:6px;padding:5px 10px;font-size:13px;font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;line-height:1.4;transition:background var(--cal-transition),border-color var(--cal-transition),color var(--cal-transition);user-select:none}.uc-btn:hover{background:var(--cal-hover-strong);border-color:var(--cal-border-strong)}.uc-btn:active{background:var(--cal-selected-bg)}.uc-btn:focus-visible{outline:2px solid var(--cal-primary);outline-offset:2px}.uc-nav-btn{font-size:18px;padding:4px 8px;line-height:1;border-color:transparent;background:0 0}.uc-nav-btn:hover{border-color:var(--cal-border)}.uc-today-btn{padding:5px 12px}.uc-today-btn.uc-active{background:var(--cal-primary-light);color:var(--cal-primary);font-weight:600;border-color:transparent}.uc-view-switcher{display:flex;border:1px solid var(--cal-border);border-radius:6px;overflow:hidden}.uc-view-btn{border:none;border-radius:0;border-right:1px solid var(--cal-border);background:0 0;color:var(--cal-text-subtle);font-weight:500;padding:5px 11px}.uc-view-btn:last-child{border-right:none}.uc-view-btn:hover{background:var(--cal-hover-strong);color:var(--cal-text);border-color:transparent}.uc-view-btn.uc-active{background:var(--cal-primary-light);color:var(--cal-primary);font-weight:600}.uc-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--cal-loading-bg);z-index:100;pointer-events:none}.uc-spinner{width:28px;height:28px;border:3px solid var(--cal-border);border-top-color:var(--cal-primary);border-radius:50%;animation:uc-spin .6s linear infinite}@keyframes uc-spin{to{transform:rotate(360deg)}}.uc-view-container{flex:1;overflow:visible;display:flex;flex-direction:column}.uc-view-container:first-child .uc-month-header,.uc-view-container:first-child .uc-week-header{border-radius:var(--cal-radius) var(--cal-radius) 0 0}.uc-month-view{display:flex;flex-direction:column;flex:1;overflow:visible;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-month-header{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--cal-border);flex-shrink:0}.uc-month-day-name{height:var(--cal-day-name-height,36px);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--cal-text-subtle);user-select:none}.uc-month-body{flex:1;display:flex;flex-direction:column;overflow:visible}.uc-week-row{flex:1;position:relative;min-height:var(--cal-cell-min-height);border-bottom:1px solid var(--cal-border)}.uc-week-row:last-child{border-bottom:none;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-week-cells{position:absolute;inset:0;display:grid;grid-template-columns:repeat(7,1fr);z-index:1}.uc-day-cell{border-right:1px solid var(--cal-border);padding:4px 6px 4px 6px;cursor:pointer;transition:background var(--cal-transition);min-height:var(--cal-cell-min-height)}.uc-day-cell:last-child{border-right:none}.uc-day-cell:hover{background:var(--cal-hover)}.uc-day-cell.uc-today{background:var(--cal-today-bg)}.uc-day-cell.uc-other-month .uc-day-number{color:var(--cal-text-muted)}.uc-day-number{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;font-size:13px;font-weight:500;border-radius:50%;color:var(--cal-text);cursor:pointer;transition:background var(--cal-transition),color var(--cal-transition);line-height:1}.uc-day-number:hover{background:var(--cal-hover-strong)}.uc-today .uc-day-number{background:var(--cal-primary);color:#fff;font-weight:700}.uc-today .uc-day-number:hover{background:var(--cal-primary-dark)}.uc-week-events{position:absolute;inset:0;z-index:2;pointer-events:none;overflow:visible}.uc-event-bar{position:absolute;height:var(--cal-event-height);background:var(--cal-event-bg);color:var(--cal-event-text);border-radius:var(--cal-event-border-radius);display:flex;align-items:center;gap:4px;padding:0 6px;cursor:pointer;pointer-events:auto;white-space:nowrap;font-size:12px;font-weight:500;transition:filter var(--cal-transition),opacity var(--cal-transition);z-index:3}.uc-event-bar:hover{filter:brightness(.92);z-index:1000}.uc-event-bar:active{filter:brightness(.85)}.uc-event-bar.uc-continues-left{border-top-left-radius:0;border-bottom-left-radius:0}.uc-event-bar.uc-continues-right{border-top-right-radius:0;border-bottom-right-radius:0}.uc-event-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.uc-event-time{font-size:11px;opacity:.85;flex-shrink:0}.uc-more-link{position:absolute;height:16px;display:flex;align-items:center;padding:0 6px;font-size:10px;font-weight:600;color:var(--cal-text-subtle);cursor:pointer;pointer-events:auto;border-radius:var(--cal-event-border-radius);white-space:nowrap;transition:background var(--cal-transition),color var(--cal-transition);z-index:3}.uc-more-link:hover{background:var(--cal-hover-strong);color:var(--cal-text)}.uc-day-view,.uc-week-view{display:flex;flex-direction:column;max-height:650px;overflow:hidden;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-week-header{display:flex;border-bottom:1px solid var(--cal-border);flex-shrink:0;background:var(--cal-bg)}.uc-time-gutter-spacer{width:var(--cal-time-col-width);flex-shrink:0;border-right:1px solid var(--cal-border)}.uc-all-day-label{display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--cal-text-muted);white-space:nowrap}.uc-week-day-headers{flex:1;display:grid;grid-template-columns:repeat(7,1fr)}.uc-week-day-headers.uc-day-header-single{grid-template-columns:1fr}.uc-week-day-header{padding:8px 4px;display:flex;flex-direction:column;align-items:center;gap:2px;border-right:1px solid var(--cal-border);cursor:default}.uc-week-day-header:last-child{border-right:none}.uc-week-day-name{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--cal-text-subtle);user-select:none}.uc-week-day-num{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;font-size:16px;font-weight:600;border-radius:50%;color:var(--cal-text);cursor:pointer;transition:background var(--cal-transition),color var(--cal-transition)}.uc-week-day-num:hover{background:var(--cal-hover-strong)}.uc-week-day-header.uc-today .uc-week-day-name{color:var(--cal-primary)}.uc-week-day-header.uc-today .uc-week-day-num{background:var(--cal-primary);color:#fff}.uc-week-day-header.uc-today .uc-week-day-num:hover{background:var(--cal-primary-dark)}.uc-all-day-section{display:flex;border-bottom:1px solid var(--cal-border);flex-shrink:0;background:var(--cal-bg)}.uc-all-day-events{flex:1;position:relative;min-height:calc(var(--cal-event-height) + 6px);padding:2px 0}.uc-time-body{flex:1;overflow-y:auto;overflow-x:visible;position:relative;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-time-body::-webkit-scrollbar{width:6px}.uc-time-body::-webkit-scrollbar-track{background:0 0}.uc-time-body::-webkit-scrollbar-thumb{background:var(--cal-border-strong);border-radius:3px}.uc-time-grid-inner{display:flex;flex-direction:row;height:calc(24 * var(--cal-hour-height));position:relative}.uc-time-gutter{width:var(--cal-time-col-width);flex-shrink:0;border-right:1px solid var(--cal-border);position:relative}.uc-hour-cell{height:var(--cal-hour-height);position:relative;display:flex;align-items:flex-start;justify-content:flex-end;padding-right:8px}.uc-hour-label{font-size:11px;font-weight:500;color:var(--cal-text-subtle);user-select:none;pointer-events:none;white-space:nowrap;transform:translateY(-50%);margin-top:1px}.uc-time-columns{flex:1;display:grid;grid-template-columns:repeat(var(--uc-col-count,7),1fr);position:relative}.uc-time-col{position:relative;border-right:1px solid var(--cal-border);height:calc(24 * var(--cal-hour-height));cursor:pointer}.uc-time-col:last-child{border-right:none}.uc-time-col.uc-today{background:var(--cal-today-bg)}.uc-hour-row{height:var(--cal-hour-height);border-bottom:1px solid var(--cal-border);position:relative;pointer-events:none}.uc-half-hour-line{position:absolute;top:50%;left:0;right:0;border-top:1px dashed var(--cal-border);pointer-events:none}.uc-timed-event{position:absolute;background:var(--cal-event-bg);color:var(--cal-event-text);border-radius:var(--cal-event-border-radius);padding:3px 6px;cursor:pointer;display:flex;flex-direction:column;gap:1px;font-size:12px;font-weight:500;border-left:3px solid rgba(0,0,0,.15);transition:filter var(--cal-transition),box-shadow var(--cal-transition);z-index:2;min-height:18px}.uc-timed-event:hover{filter:brightness(.92);box-shadow:0 2px 8px rgba(0,0,0,.15);z-index:1000}.uc-timed-event .uc-event-title{font-weight:600;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-timed-event .uc-event-time{font-size:11px;opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-timed-event--short{flex-direction:row;align-items:center;gap:4px}.uc-timed-event--short .uc-event-time{flex-shrink:0}.uc-timed-event--short .uc-event-title{flex:1;min-width:0}.uc-now-indicator{position:absolute;left:0;right:0;pointer-events:none;z-index:10;display:flex;align-items:center}.uc-now-dot{width:10px;height:10px;border-radius:50%;background:var(--cal-now-color);flex-shrink:0;margin-left:-5px}.uc-now-line{flex:1;height:2px;background:var(--cal-now-color)}.uc-list-view{padding:0;max-height:650px;overflow-y:auto;overflow-x:hidden;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-list-view::-webkit-scrollbar{width:6px}.uc-list-view::-webkit-scrollbar-track{background:0 0}.uc-list-view::-webkit-scrollbar-thumb{background:var(--cal-border-strong);border-radius:3px}.uc-list-empty{display:flex;align-items:center;justify-content:center;min-height:300px;color:var(--cal-text-muted);font-size:14px}.uc-list-date-group{margin-bottom:24px;overflow:visible}.uc-list-date-group:last-child{margin-bottom:0}.uc-list-events{background:var(--cal-bg);overflow:visible}.uc-list-event{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-bottom:1px solid var(--cal-border);cursor:pointer;transition:background var(--cal-transition);position:relative;overflow:visible}.uc-list-date-header{font-size:14px;font-weight:600;color:var(--cal-text);padding:12px 16px;background:var(--cal-bg-secondary);border-bottom:1px solid var(--cal-border);position:sticky;top:0;z-index:1;overflow:visible}.uc-list-event:hover{background:var(--cal-hover)}.uc-list-event:last-child{border-bottom:none}.uc-list-event-indicator{width:8px;height:8px;border-radius:50%;margin-top:4px;flex-shrink:0}.uc-list-event-time{font-size:13px;font-weight:500;color:var(--cal-text-secondary);min-width:80px;flex-shrink:0}.uc-list-event-content{flex:1;min-width:0}.uc-list-event-title{font-size:14px;font-weight:500;color:var(--cal-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::after,.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::before{opacity:1;visibility:visible;transition-delay:0.4s}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{content:attr(data-tooltip);position:absolute;bottom:calc(100% + var(--cal-tooltip-offset));left:50%;transform:translateX(-50%);background:var(--cal-tooltip-bg);color:var(--cal-tooltip-text);border:1px solid var(--cal-tooltip-border);box-shadow:var(--cal-tooltip-shadow);padding:var(--cal-tooltip-padding);border-radius:var(--cal-tooltip-radius);font-size:var(--cal-tooltip-font-size);font-weight:500;line-height:1.4;max-width:var(--cal-tooltip-max-width);width:max-content;white-space:pre-wrap;word-wrap:break-word;text-align:left;z-index:1000;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::after{content:'';position:absolute;bottom:calc(100% + var(--cal-tooltip-offset) - 5px);left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid var(--cal-tooltip-bg);z-index:1002;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{filter:drop-shadow(0 1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-left::before{left:auto;right:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-left::after{left:auto;right:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::before{left:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::after{left:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-bottom::before{bottom:auto;top:calc(100% + var(--cal-tooltip-offset));filter:drop-shadow(0 -1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-bottom::after{bottom:auto;top:calc(100% + var(--cal-tooltip-offset) - 5px);border-top:5px solid var(--cal-tooltip-bg);border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:none;transform:translateX(-50%) rotate(180deg)}@media (max-width:768px){.uc-toolbar{padding:8px 10px}.uc-title{font-size:14px}.uc-toolbar-center{order:-1;width:100%;flex:none}.uc-toolbar-section{justify-content:center}.uc-view-btn{padding:5px 8px;font-size:12px}:root{--cal-time-col-width:52px;--cal-hour-height:52px;--cal-cell-min-height:88px;--cal-event-height:20px}.uc-week-day-num{width:24px;height:24px;font-size:13px}.uc-week-day-name{font-size:10px}.uc-hour-cell:nth-child(odd) .uc-hour-label{visibility:hidden}}@media (max-width:480px){.uc-today-btn{display:none}.uc-toolbar-section.uc-toolbar-right{gap:2px}}.uc-calendar.uc-no-grid .uc-day-cell,.uc-calendar.uc-no-grid .uc-day-header-row,.uc-calendar.uc-no-grid .uc-day-name-row,.uc-calendar.uc-no-grid .uc-grid-line,.uc-calendar.uc-no-grid .uc-hour-cell,.uc-calendar.uc-no-grid .uc-time-col,.uc-calendar.uc-no-grid .uc-time-gutter,.uc-calendar.uc-no-grid .uc-week-day-header,.uc-calendar.uc-no-grid .uc-week-row{border:none!important}.uc-calendar.uc-no-grid .uc-all-day-section{border-top:none!important;border-left:none!important;border-right:none!important}.uc-calendar.uc-no-border{border:none!important}.uc-calendar.uc-dark,.uc-dark .uc-calendar{--cal-bg:#1f2937;--cal-bg-secondary:#111827;--cal-text:#f9fafb;--cal-text-subtle:#9ca3af;--cal-text-muted:#6b7280;--cal-border:#374151;--cal-border-strong:#4b5563;--cal-hover:#374151;--cal-hover-strong:#4b5563;--cal-selected-bg:#78716c;--cal-today-bg:#57534e;--cal-primary-light:#57534e;--cal-toolbar-bg:#111827;--cal-tooltip-bg:#374151;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#4b5563;--cal-loading-bg:rgba(31, 41, 55, 0.7)}.uc-calendar.uc-dragging{cursor:grabbing!important;user-select:none;-webkit-user-select:none}.uc-calendar.uc-dragging *{cursor:grabbing!important}.uc-dragging-element{opacity:.8;box-shadow:0 4px 12px rgba(0,0,0,.3);transition:none!important}.uc-calendar[data-drag-enabled] .uc-event-bar,.uc-calendar[data-drag-enabled] .uc-timed-event{cursor:grab}.uc-calendar[data-drag-enabled] .uc-event-bar:active,.uc-calendar[data-drag-enabled] .uc-timed-event:active{cursor:grabbing}.uc-calendar.uc-dark .uc-dragging-element{box-shadow:0 4px 12px rgba(0,0,0,.6)}.uc-resize-handle{position:absolute;bottom:0;left:0;right:0;height:8px;cursor:ns-resize;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s}.uc-resize-handle::after{content:'';width:24px;height:3px;background:rgba(255,255,255,.6);border-radius:2px}.uc-timed-event:hover .uc-resize-handle{opacity:1}.uc-calendar.uc-dragging .uc-resize-handle{cursor:ns-resize!important}.uc-resize-handle-right{position:absolute;top:2px;bottom:2px;right:2px;width:6px;cursor:ew-resize;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s}.uc-resize-handle-right::after{content:'';width:2px;height:12px;background:rgba(255,255,255,.6);border-radius:1px}.uc-event-bar:hover .uc-resize-handle-right{opacity:1}.uc-calendar.uc-dragging .uc-resize-handle-right{cursor:ew-resize!important}.uc-event-bar--list{display:flex;align-items:center;gap:6px;padding:2px 6px;background:0 0!important;overflow:hidden}.uc-event-dot{flex-shrink:0;width:8px;height:8px;border-radius:50%}.uc-event-bar--list .uc-event-time{flex-shrink:0;font-size:11px;font-weight:500;color:var(--cal-text);opacity:.8}.uc-event-bar--list .uc-event-title{flex:1;font-size:12px;color:var(--cal-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-calendar[data-drag-enabled] .uc-event-bar--list{cursor:grab}.uc-calendar[data-drag-enabled] .uc-event-bar--list:active{cursor:grabbing}@media print{.uc-toolbar{display:none}.uc-time-body{overflow:visible}.uc-calendar{border:none}}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
2
|
+
* SimpleCalendarJs v3.0.6
|
|
3
3
|
* A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
|
|
4
4
|
*
|
|
5
5
|
* @author Pedro Lopes <simplecalendarjs@gmail.com>
|
|
6
6
|
* @homepage https://www.simplecalendarjs.com
|
|
7
7
|
* @license SEE LICENSE IN LICENSE
|
|
8
8
|
*/
|
|
9
|
-
!function(t,e){"undefined"!=typeof module&&module.exports?module.exports=e():"function"==typeof define&&define.amd?define([],e):t.SimpleCalendarJs=e()}("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:this,function(){"use strict";function t(t){const e=new Date(t);return e.setHours(0,0,0,0),e}function e(t){const e=new Date(t);return e.setHours(23,59,59,999),e}function a(t,e){const a=new Date(t);return a.setDate(a.getDate()+e),a}function s(t,e){return t.getFullYear()===e.getFullYear()&&t.getMonth()===e.getMonth()&&t.getDate()===e.getDate()}function n(t){return s(t,new Date)}function i(e,a){return Math.floor((t(a)-t(e))/864e5)}function o(t){return t instanceof Date?t:new Date(t)}function
|
|
9
|
+
!function(t,e){"undefined"!=typeof module&&module.exports?module.exports=e():"function"==typeof define&&define.amd?define([],e):t.SimpleCalendarJs=e()}("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:this,function(){"use strict";function t(t){const e=new Date(t);return e.setHours(0,0,0,0),e}function e(t){const e=new Date(t);return e.setHours(23,59,59,999),e}function a(t,e){const a=new Date(t);return a.setDate(a.getDate()+e),a}function s(t,e){return t.getFullYear()===e.getFullYear()&&t.getMonth()===e.getMonth()&&t.getDate()===e.getDate()}function n(t){return s(t,new Date)}function i(e,a){return Math.floor((t(a)-t(e))/864e5)}function o(t){return t instanceof Date?t:new Date(t)}function r(e,s){const n=e.getDay(),i=t(a(e,-((n-s+7)%7)));return Array.from({length:7},(t,e)=>a(i,e))}function l(t,e,s){const n=new Date(t,e,1),i=new Date(t,e+1,0),o=(n.getDay()-s+7)%7,r=7*Math.ceil((o+i.getDate())/7),l=a(n,-o);return Array.from({length:r},(t,e)=>a(l,e))}function d(t,e,a){const s={hour:"numeric",hour12:!a};return 0!==t.getMinutes()&&(s.minute="2-digit"),new Intl.DateTimeFormat(e,s).format(t)}function c(t,e,a){return Array.from({length:7},(s,n)=>{const i=new Date(2025,0,5+(e+n)%7);return new Intl.DateTimeFormat(t,{weekday:a}).format(i)})}function h(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}const u={"en-US":{today:"Today",month:"Month",week:"Week",day:"Day",list:"List",allDay:"All-Day"},"en-GB":{today:"Today",month:"Month",week:"Week",day:"Day",list:"List",allDay:"All-Day"},"es-ES":{today:"Hoy",month:"Mes",week:"Semana",day:"Día",list:"Lista",allDay:"Todo el día"},"es-MX":{today:"Hoy",month:"Mes",week:"Semana",day:"Día",list:"Lista",allDay:"Todo el día"},"fr-FR":{today:"Aujourd'hui",month:"Mois",week:"Semaine",day:"Jour",list:"Liste",allDay:"Toute la journée"},"fr-CA":{today:"Aujourd'hui",month:"Mois",week:"Semaine",day:"Jour",list:"Liste",allDay:"Toute la journée"},"de-DE":{today:"Heute",month:"Monat",week:"Woche",day:"Tag",list:"Liste",allDay:"Ganztägig"},"it-IT":{today:"Oggi",month:"Mese",week:"Settimana",day:"Giorno",list:"Elenco",allDay:"Tutto il giorno"},"pt-PT":{today:"Hoje",month:"Mês",week:"Semana",day:"Dia",list:"Lista",allDay:"Dia todo"},"pt-BR":{today:"Hoje",month:"Mês",week:"Semana",day:"Dia",list:"Lista",allDay:"Dia todo"},"nl-NL":{today:"Vandaag",month:"Maand",week:"Week",day:"Dag",list:"Lijst",allDay:"Hele dag"},"pl-PL":{today:"Dzisiaj",month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Lista",allDay:"Cały dzień"},"ru-RU":{today:"Сегодня",month:"Месяц",week:"Неделя",day:"День",list:"Список",allDay:"Весь день"},"tr-TR":{today:"Bugün",month:"Ay",week:"Hafta",day:"Gün",list:"Liste",allDay:"Tüm gün"},"sv-SE":{today:"Idag",month:"Månad",week:"Vecka",day:"Dag",list:"Lista",allDay:"Heldag"},"da-DK":{today:"I dag",month:"Måned",week:"Uge",day:"Dag",list:"Liste",allDay:"Hele dagen"},"fi-FI":{today:"Tänään",month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Lista",allDay:"Koko päivä"},"no-NO":{today:"I dag",month:"Måned",week:"Uke",day:"Dag",list:"Liste",allDay:"Hele dagen"},"cs-CZ":{today:"Dnes",month:"Měsíc",week:"Týden",day:"Den",list:"Seznam",allDay:"Celý den"},"hu-HU":{today:"Ma",month:"Hónap",week:"Hét",day:"Nap",list:"Lista",allDay:"Egész nap"},"ro-RO":{today:"Astăzi",month:"Lună",week:"Săptămână",day:"Zi",list:"Listă",allDay:"Toată ziua"},"el-GR":{today:"Σήμερα",month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα",list:"Λίστα",allDay:"Ολοήμερο"},"ja-JP":{today:"今日",month:"月",week:"週",day:"日",list:"リスト",allDay:"終日"},"ko-KR":{today:"오늘",month:"월",week:"주",day:"일",list:"목록",allDay:"종일"},"zh-CN":{today:"今天",month:"月",week:"周",day:"日",list:"列表",allDay:"全天"},"zh-TW":{today:"今天",month:"月",week:"週",day:"日",list:"列表",allDay:"全天"},"ar-SA":{today:"اليوم",month:"شهر",week:"أسبوع",day:"يوم",list:"قائمة",allDay:"طوال اليوم"},"he-IL":{today:"היום",month:"חודש",week:"שבוע",day:"יום",list:"רשימה",allDay:"כל היום"},"hi-IN":{today:"आज",month:"महीना",week:"सप्ताह",day:"दिन",list:"सूची",allDay:"पूरे दिन"},"th-TH":{today:"วันนี้",month:"เดือน",week:"สัปดาห์",day:"วัน",list:"รายการ",allDay:"ตลอดวัน"},"vi-VN":{today:"Hôm nay",month:"Tháng",week:"Tuần",day:"Ngày",list:"Danh sách",allDay:"Cả ngày"},"id-ID":{today:"Hari ini",month:"Bulan",week:"Minggu",day:"Hari",list:"Daftar",allDay:"Sepanjang hari"},"ms-MY":{today:"Hari ini",month:"Bulan",week:"Minggu",day:"Hari",list:"Senarai",allDay:"Sepanjang hari"},"uk-UA":{today:"Сьогодні",month:"Місяць",week:"Тиждень",day:"День",list:"Список",allDay:"Весь день"}};function _(t,e){const a=t.split("-")[0];return(u[t]||u[a]||u["en-US"])[e]}function v(t){return!!t.allDay||!s(t.start,t.end)}function g(a,n){const o=t(n[0]),r=e(n[n.length-1]),l=a.filter(t=>t.start<=r&&t.end>=o).map(a=>({...a,_visStart:new Date(Math.max(a.start.getTime(),o.getTime())),_visEnd:new Date(Math.min(a.end.getTime(),r.getTime())),_isStart:t(a.start)>=o,_isEnd:e(a.end)<=r}));l.sort((t,e)=>{const a=t.start-e.start;if(0!==a)return a;const s=i(t._visStart,t._visEnd);return i(e._visStart,e._visEnd)-s||(t._origStart||t.start)-(e._origStart||e.start)});const d=[],c=[];for(const t of l){const e=n.findIndex(e=>s(e,t._visStart)),a=n.findIndex(e=>s(e,t._visEnd)),i=-1===e?0:e,o=-1===a?n.length-1:a;let r=d.findIndex(t=>t<=i);-1===r?(r=d.length,d.push(o+1)):d[r]=o+1,c.push({event:t,startCol:i,endCol:o,slot:r,isStart:t._isStart,isEnd:t._isEnd})}return c}
|
|
10
10
|
/* ============================================================
|
|
11
11
|
ES MODULE EXPORT
|
|
12
12
|
Also available as window.SimpleCalendarJs via the IIFE wrapper.
|
|
13
13
|
============================================================ */
|
|
14
|
-
return class{constructor(e,a={}){if("string"==typeof e){if(this._el=document.querySelector(e),!this._el)throw new Error(`SimpleCalendarJs: no element for "${e}"`)}else this._el=e;this._opts=Object.assign({defaultView:"month",defaultDate:null,weekStartsOn:0,locale:"default",weekdayFormat:"short",use24Hour:!1,showTimeInItems:!0,showGridLines:!0,showToolbar:!0,showTodayButton:!0,showNavigation:!0,showTitle:!0,showYearPicker:!0,showViewSwitcher:!0,showTooltips:!0,showBorder:!0,maxEventsPerCell:3,listDaysForward:30,enabledViews:["month","week","day"],fetchEvents:null,onEventClick:null,onSlotClick:null,onViewChange:null,onNavigate:null},a),this._view=this._opts.defaultView,this._date=t(this._opts.defaultDate||new Date),this._events=[],this._cachedRange=null,this._cachedEvents=[],this._nowInterval=null,this._yearPickerOpen=!1,this._yearPickerBase=0,this._yearOutsideHandler=null,this._root=document.createElement("div"),this._root.className="uc-calendar",this._opts.showGridLines||this._root.classList.add("uc-no-grid"),this._opts.showBorder||this._root.classList.add("uc-no-border"),this._el.appendChild(this._root),this._onClick=this._handleClick.bind(this),this._root.addEventListener("click",this._onClick),this._onMouseMove=this._handleTooltipPosition.bind(this),this._root.addEventListener("mouseover",this._onMouseMove),this._fetchAndRender(),this._startNowUpdater()}setView(t){t!==this._view&&(this._view=t,this._opts.onViewChange&&this._opts.onViewChange(t),this._fetchAndRender())}navigate(t){const e=new Date(this._date);"month"===this._view?(e.setMonth(e.getMonth()+t),e.setDate(1)):"week"===this._view?e.setDate(e.getDate()+7*t):e.setDate(e.getDate()+t),this._date=e;const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender()}goToToday(){this._date=t(new Date);const e=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(e.start,e.end),this._fetchAndRender()}goToDate(e){this._date=t(o(e)),this._fetchAndRender()}refresh(){this._cachedRange=null,this._cachedEvents=[],this._fetchAndRender()}destroy(){this._nowInterval&&clearInterval(this._nowInterval),this._yearOutsideHandler&&document.removeEventListener("click",this._yearOutsideHandler),this._root.removeEventListener("click",this._onClick),this._root.removeEventListener("mouseover",this._onMouseMove),this._root.remove()}_getRange(){if("month"===this._view){const a=r(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}if("week"===this._view){const a=l(this._date,this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[6])}}if("list"===this._view){const t=new Date,e=new Date(t);return e.setDate(e.getDate()+this._opts.listDaysForward),{start:t,end:e}}return{start:t(this._date),end:e(this._date)}}_getMonthGridRange(){const a=r(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}async _fetchAndRender(){if(this._root.innerHTML=this._buildShell(),!this._opts.fetchEvents)return void this._renderView();const t=this._getRange();if(this._cachedRange&&this._cachedRange.start<=t.start&&this._cachedRange.end>=t.end)return this._events=this._cachedEvents.filter(e=>e.end>=t.start&&e.start<=t.end),void this._renderView();const e=this._root.querySelector(".uc-loading");e&&(e.style.display="flex");try{let e;e="list"===this._view?t:this._getMonthGridRange();const a=(await this._opts.fetchEvents(e.start,e.end)||[]).map(t=>({...t,start:o(t.start),end:o(t.end)}));this._cachedRange={start:e.start,end:e.end},this._cachedEvents=a,this._events=a.filter(e=>e.end>=t.start&&e.start<=t.end)}catch(t){this._events=[]}e&&(e.style.display="none"),this._renderView()}_renderView(){const t=this._root.querySelector(".uc-view-container");if(t){if(0===this._opts.maxEventsPerCell)this._root.classList.add("uc-unlimited-events"),this._root.style.removeProperty("--cal-cell-min-height");else{this._root.classList.remove("uc-unlimited-events");const t=30+24*this._opts.maxEventsPerCell+28;this._root.style.setProperty("--cal-cell-min-height",`${t}px`)}if("month"===this._view)t.innerHTML=this._buildMonthView();else if("week"===this._view){const e=l(this._date,this._opts.weekStartsOn);t.innerHTML=this._buildWeekOrDayView(e),this._scrollToBusinessHours(t)}else"list"===this._view?t.innerHTML=this._buildListView():(t.innerHTML=this._buildWeekOrDayView([this._date]),this._scrollToBusinessHours(t))}}_scrollToBusinessHours(t){requestAnimationFrame(()=>{const e=t.querySelector(".uc-time-body");if(!e)return;const a=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60;e.scrollTop=7*a})}_renderToolbar(){const t=this._root.querySelector(".uc-toolbar");if(!t)return;const e=document.createElement("div");e.innerHTML=this._buildToolbar(),this._root.replaceChild(e.firstElementChild,t)}_buildShell(){return`\n ${this._buildToolbar()}\n <div class="uc-loading" style="display:none">\n <div class="uc-spinner"></div>\n </div>\n <div class="uc-view-container"></div>\n `}_buildToolbar(){if(!this._opts.showToolbar)return"";const t=this._date.getFullYear(),e=(new Date).getFullYear();let a,n=!0;if("month"===this._view)a=new Intl.DateTimeFormat(this._opts.locale,{month:"long"}).format(this._date);else if("week"===this._view){const t=l(this._date,this._opts.weekStartsOn);a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}`}(t[0],t[6],this._opts.locale)}else if("list"===this._view){const t=new Date,e=new Date(t);e.setDate(e.getDate()+this._opts.listDaysForward),a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}, ${t.getFullYear()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}, ${e.getFullYear()}`}(t,e,this._opts.locale),n=!1}else a=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",month:"long",day:"numeric"}).format(this._date);let i="";if(this._opts.showYearPicker&&this._yearPickerOpen){const a=this._yearPickerBase,s=Array.from({length:12},(s,n)=>{const i=a+n,o=i===t;return`<button class="${"uc-year-item"+(o?" uc-active":"")+(i===e&&!o?" uc-today-year":"")}" data-action="select-year" data-year="${i}">${i}</button>`}).join("");i=`\n <div class="uc-year-picker">\n <div class="uc-year-picker-nav">\n <button class="uc-year-nav-btn" data-action="year-prev" aria-label="Previous years">‹</button>\n <span class="uc-year-range">${a} – ${a+11}</span>\n <button class="uc-year-nav-btn" data-action="year-next" aria-label="Next years">›</button>\n </div>\n <div class="uc-year-grid">${s}</div>\n </div>`}const o=this._opts.locale,r=v(o,"today"),c=v(o,"month"),d=v(o,"week"),u=v(o,"day"),p=v(o,"list");let y="";if((this._opts.showNavigation||this._opts.showTodayButton)&&"list"!==this._view){const t=this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="prev" aria-label="Previous">‹</button>':"",e=new Date,a=s(this._date,e)?" uc-active":"";y=`\n <div class="uc-toolbar-section uc-toolbar-left">\n ${t}${this._opts.showTodayButton?`<button class="uc-btn uc-today-btn${a}" data-action="today">${h(r)}</button>`:""}${this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="next" aria-label="Next">›</button>':""}\n </div>`}let g="";if(this._opts.showTitle){const e=n?this._opts.showYearPicker?`<button class="uc-year-btn${this._yearPickerOpen?" uc-open":""}" data-action="year-pick" aria-label="Select year">${t}</button>`:t:"";g=`\n <div class="uc-toolbar-section uc-toolbar-center">\n <h2 class="uc-title">\n <span class="uc-title-main">${h(a)}</span>\n ${e}\n </h2>\n ${i}\n </div>`}let _="";if(this._opts.showViewSwitcher){const t=this._opts.enabledViews,e=[];t.includes("month")&&e.push(`<button class="uc-btn uc-view-btn${"month"===this._view?" uc-active":""}" data-view="month">${h(c)}</button>`),t.includes("week")&&e.push(`<button class="uc-btn uc-view-btn${"week"===this._view?" uc-active":""}" data-view="week">${h(d)}</button>`),t.includes("day")&&e.push(`<button class="uc-btn uc-view-btn${"day"===this._view?" uc-active":""}" data-view="day">${h(u)}</button>`),t.includes("list")&&e.push(`<button class="uc-btn uc-view-btn${"list"===this._view?" uc-active":""}" data-view="list">${h(p)}</button>`),e.length>0&&(_=`\n <div class="uc-toolbar-section uc-toolbar-right">\n <div class="uc-view-switcher">\n ${e.join("")}\n </div>\n </div>`)}return`\n <div class="uc-toolbar">\n ${y}${g}${_}\n </div>\n `}_buildMonthView(){const{locale:a,weekStartsOn:s}=this._opts,n=r(this._date.getFullYear(),this._date.getMonth(),s),i=d(a,s,this._opts.weekdayFormat),o=this._events.map(a=>({...a,_origStart:a.start,start:p(a)?t(a.start):a.start,end:p(a)?e(a.end):a.end})),l=i.map(t=>`<div class="uc-month-day-name">${h(t)}</div>`).join(""),c=[];for(let t=0;t<n.length;t+=7)c.push(n.slice(t,t+7));return`\n <div class="uc-month-view">\n <div class="uc-month-header">${l}</div>\n <div class="uc-month-body">${c.map(t=>this._buildWeekRow(t,o)).join("")}</div>\n </div>\n `}_buildWeekRow(t,e){const a=this._date.getMonth(),i=0===this._opts.maxEventsPerCell?1/0:this._opts.maxEventsPerCell,o=e.filter(p),l=e.filter(t=>!p(t)),r=y(o,t),d=Array.from({length:7},()=>new Set);for(const{startCol:t,endCol:e,slot:a}of r)for(let s=t;s<=e;s++)d[s].add(a);const u=t.map(t=>l.filter(e=>s(e.start,t)).sort((t,e)=>t.start-e.start)),v=t.map((t,e)=>`\n <div class="uc-day-cell${n(t)?" uc-today":""}${t.getMonth()!==a?" uc-other-month":""}" data-date="${t.toISOString()}" data-action="day-click">\n <span class="uc-day-number" data-action="day-number" data-date="${t.toISOString()}">${t.getDate()}</span>\n </div>`).join("");let g="";for(const{event:t,startCol:e,endCol:a,slot:s,isStart:n,isEnd:o}of r){if(i!==1/0&&s>=i)continue;const l=100/7,r=e*l,c=(a-e+1)*l,d=`calc(var(--cal-header-day-height) + ${s} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,u=t.color||"var(--cal-event-bg)",v=n?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",y=n?"":" uc-continues-left",_=o?"":" uc-continues-right",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar${y}${_}"\n style="left:calc(${r}% + 2px);width:calc(${c}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${n?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n </div>`}let _=-1;for(let e=0;e<7;e++){const a=100/7,s=e*a,n=t[e],o=[...d[e]],l=o.length>0?Math.max(...o)+1:0,r=u[e];if(i===1/0)r.forEach((t,e)=>{const n=l+e;_=Math.max(_,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,o=t.color||"var(--cal-event-bg)",r=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(r)}</span>`:"",u=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(o)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${u}>\n ${d}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`});else{const t=[...d[e]].filter(t=>t>=i).length,o=[];for(let t=l;t<i;t++)o.push(t);let u=t;if(r.forEach((t,e)=>{if(e<o.length){const n=o[e];_=Math.max(_,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,l=t.color||"var(--cal-event-bg)",r=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(r)}</span>`:"",u=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(l)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${u}>\n ${d}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}else u++}),u>0){g+=`\n <div class="uc-more-link"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${`calc(var(--cal-header-day-height) + ${i} * (var(--cal-event-height) + var(--cal-event-gap)) + 2px)`};"\n data-date="${n.toISOString()}" data-action="more-click">\n +${u} more\n </div>`}}}for(const{slot:t}of r)(i===1/0||t<i)&&(_=Math.max(_,t));let w="";return i===1/0&&(w=_>=0?` style="min-height: calc(var(--cal-header-day-height) + ${_+1} * (var(--cal-event-height) + var(--cal-event-gap)) + 8px);"`:' style="min-height: 80px;"'),`\n <div class="uc-week-row"${w}>\n <div class="uc-week-cells">${v}</div>\n <div class="uc-week-events">${g}</div>\n </div>`}_buildWeekOrDayView(a){const{locale:i,weekStartsOn:o,use24Hour:l}=this._opts,r=1===a.length,u=r?" uc-day-header-single":"",g=r?[new Intl.DateTimeFormat(i,{weekday:this._opts.weekdayFormat}).format(a[0])]:d(i,o,this._opts.weekdayFormat),_=t(a[0]),w=e(a[a.length-1]),m=this._events.filter(t=>p(t)&&t.start<=w&&e(t.end)>=_).map(a=>({...a,start:t(a.start),end:e(a.end)})),f=this._events.filter(t=>!p(t)&&t.start>=_&&t.start<=w),$=a.map((t,e)=>{const a=n(t)?" uc-today":"",s=t.getDate();return`\n <div class="uc-week-day-header${a}">\n <span class="uc-week-day-name">${h(g[e])}</span>\n <span class="uc-week-day-num" data-action="day-number" data-date="${t.toISOString()}">${s}</span>\n </div>`}).join(""),k=r?m.map((t,e)=>({event:t,startCol:0,endCol:0,slot:e,isStart:!0,isEnd:!0})):y(m,a),b=k.length?Math.max(...k.map(t=>t.slot))+1:0;let D="";for(const{event:t,startCol:e,endCol:s,slot:n,isStart:i,isEnd:o}of k){const l=100/a.length,r=e*l,c=(s-e+1)*l,d=`calc(${n} * (var(--cal-event-height) + 3px) + 2px)`,u=t.color||"var(--cal-event-bg)",v=i?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",y=i?"":" uc-continues-left",g=o?"":" uc-continues-right",_=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";D+=`\n <div class="uc-event-bar${y}${g}"\n style="left:calc(${r}% + 2px);width:calc(${c}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${_}>\n ${i?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n </div>`}const T=`calc(${Math.max(1,b)} * (var(--cal-event-height) + 3px) + 6px)`,S=new Date,M=60*S.getHours()+S.getMinutes(),C=a.length,x=Array.from({length:24},(t,e)=>`<div class="uc-hour-cell"><span class="uc-hour-label">${h(0===e?"":c(new Date(2e3,0,1,e),"en-US",l))}</span></div>`).join(""),H=a.map(t=>{const e=n(t)?" uc-today":"",a=Array.from({length:24},()=>'<div class="uc-hour-row"><div class="uc-half-hour-line"></div></div>').join(""),o=function(t){if(!t.length)return[];const e=[...t].sort((t,e)=>t.start-e.start||e.end-t.end),a=[],s=[];for(const t of e){let e=a.findIndex(e=>e<=t.start);-1===e?(e=a.length,a.push(t.end)):a[e]=t.end,s.push({event:t,col:e})}return s.map(t=>{const e=s.filter(e=>e.event.start<t.event.end&&e.event.end>t.event.start),a=Math.max(...e.map(t=>t.col))+1;return{...t,totalCols:a}})}(f.filter(e=>s(e.start,t))).map(({event:t,col:e,totalCols:a})=>{const s=60*t.start.getHours()+t.start.getMinutes(),n=60*t.end.getHours()+t.end.getMinutes(),o=`calc(${s} / 60 * var(--cal-hour-height))`,r=`calc(${Math.max(n-s,30)} / 60 * var(--cal-hour-height))`,d=100/a,u=`calc(${e*d}% + 1px)`,v=`calc(${d}% - 2px)`,p=t.color||"var(--cal-event-bg)",y=c(t.start,i,l),g=n-s<=60?" uc-timed-event--short":"",_=this._opts.showTimeInItems?`<span class="uc-event-time">${h(y)}</span>`:"",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";return`\n <div class="uc-timed-event${g}"\n style="top:${o};height:${r};left:${u};width:${v};background:${h(p)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${_}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}).join(""),r=n(t)?`<div class="uc-now-indicator" style="top:calc(${M} / 60 * var(--cal-hour-height));">\n <span class="uc-now-dot"></span>\n <span class="uc-now-line"></span>\n </div>`:"";return`<div class="uc-time-col${e}" data-date="${t.toISOString()}" data-action="slot-col">\n ${a}${o}${r}\n </div>`}).join("");return`\n <div class="${r?"uc-day-view":"uc-week-view"}">\n <div class="uc-week-header">\n <div class="uc-time-gutter-spacer"></div>\n <div class="uc-week-day-headers${u}">${$}</div>\n </div>\n <div class="uc-all-day-section">\n <div class="uc-time-gutter-spacer uc-all-day-label">${v(this._opts.locale,"allDay")}</div>\n <div class="uc-all-day-events" style="min-height:${T}">${D}</div>\n </div>\n <div class="uc-time-body">\n <div class="uc-time-grid-inner">\n <div class="uc-time-gutter">${x}</div>\n <div class="uc-time-columns" style="--uc-col-count:${C}">${H}</div>\n </div>\n </div>\n </div>`}_buildListView(){const e=new Date,a=t(e),s=new Date(a);s.setDate(s.getDate()+this._opts.listDaysForward);const n=this._events.filter(t=>t.end>=e&&t.start<=s);n.sort((e,s)=>{const n=(e.start<a?a:t(e.start))-(s.start<a?a:t(s.start));return 0!==n?n:e.start-s.start});const i=new Map;if(n.forEach(e=>{const s=e.start<a?a:t(e.start),n=`${(o=s).getFullYear()}-${String(o.getMonth()+1).padStart(2,"0")}-${String(o.getDate()).padStart(2,"0")}`;var o;i.has(n)||i.set(n,[]),i.get(n).push(e)}),0===i.size)return'\n <div class="uc-list-view">\n <div class="uc-list-empty">\n <p>No upcoming events</p>\n </div>\n </div>';let l="";return i.forEach((t,e)=>{const a=o(e),s=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",year:"numeric",month:"long",day:"numeric"}).format(a),n=t.map(t=>{const e=p(t)?"All day":c(t.start,this._opts.locale,this._opts.use24Hour),a=t.color||"var(--cal-event-bg)",s=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||"")}"`:"";return`\n <div class="uc-list-event" data-event-id="${h(t.id)}" data-action="event-click" ${s}>\n <div class="uc-list-event-indicator" style="background: ${h(a)};"></div>\n <div class="uc-list-event-time">${h(e)}</div>\n <div class="uc-list-event-content">\n <div class="uc-list-event-title">${h(t.title)}</div>\n </div>\n </div>`}).join("");l+=`\n <div class="uc-list-date-group">\n <div class="uc-list-date-header">${h(s)}</div>\n <div class="uc-list-events">${n}</div>\n </div>`}),`<div class="uc-list-view">${l}</div>`}_closeYearPicker(){this._yearOutsideHandler&&(document.removeEventListener("click",this._yearOutsideHandler),this._yearOutsideHandler=null),this._yearPickerOpen=!1,this._renderToolbar()}_handleClick(e){const a=e.target.closest("[data-view]");if(a)return void this.setView(a.dataset.view);const s=e.target.closest("[data-action]");if(!s)return;switch(s.dataset.action){case"prev":this.navigate(-1);break;case"next":this.navigate(1);break;case"today":this.goToToday();break;case"year-pick":e.stopPropagation(),this._yearPickerOpen?this._closeYearPicker():(this._yearPickerOpen=!0,this._yearPickerBase=this._date.getFullYear()-4,this._renderToolbar(),this._yearOutsideHandler=t=>{t.target.closest(".uc-year-picker")||t.target.closest('[data-action="year-pick"]')||this._closeYearPicker()},setTimeout(()=>document.addEventListener("click",this._yearOutsideHandler),0));break;case"year-prev":e.stopPropagation(),this._yearPickerBase-=12,this._renderToolbar();break;case"year-next":e.stopPropagation(),this._yearPickerBase+=12,this._renderToolbar();break;case"select-year":{e.stopPropagation();const t=parseInt(s.dataset.year,10);this._date=new Date(this._date.getFullYear()!==t?new Date(this._date).setFullYear(t):this._date),this._closeYearPicker();const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender();break}case"event-click":{e.stopPropagation();const t=s.dataset.eventId,a=this._events.find(e=>String(e.id)===String(t));a&&this._opts.onEventClick&&this._opts.onEventClick(a,e);break}case"day-click":{if(e.target.closest('[data-action="day-number"]'))break;if(e.target.closest('[data-action="event-click"]'))break;const t=new Date(s.dataset.date);this._opts.onSlotClick&&this._opts.onSlotClick(t,e);break}case"day-number":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"more-click":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"slot-col":if(e.target.closest('[data-action="event-click"]'))break;if(this._opts.onSlotClick){const t=s,a=t.getBoundingClientRect(),n=e.clientY-a.top,i=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60,o=Math.max(0,Math.floor(n/i*60)),l=Math.floor(o/60)%24,r=15*Math.round(o%60/15),c=new Date(t.dataset.date);c.setHours(l,r,0,0),this._opts.onSlotClick(c,e)}}}_handleTooltipPosition(t){const e=t.target.closest("[data-tooltip]");if(!e||!e.dataset.tooltip)return;if(!e.dataset.tooltip.trim())return;const a=e.getBoundingClientRect(),s=window.innerWidth;e.classList.remove("uc-tooltip-left","uc-tooltip-right","uc-tooltip-bottom");const n=a.width/2-125;a.left+n+250>s-10?e.classList.add("uc-tooltip-left"):a.left+n<10&&e.classList.add("uc-tooltip-right"),a.top<50&&e.classList.add("uc-tooltip-bottom")}_startNowUpdater(){this._nowInterval=setInterval(()=>{const t=this._root.querySelectorAll(".uc-now-indicator");if(!t.length)return;const e=new Date,a=`calc(${60*e.getHours()+e.getMinutes()} / 60 * var(--cal-hour-height))`;t.forEach(t=>t.style.top=a)},6e4)}}});
|
|
14
|
+
return class{constructor(e,a={}){if("string"==typeof e){if(this._el=document.querySelector(e),!this._el)throw new Error(`SimpleCalendarJs: no element for "${e}"`)}else this._el=e;this._opts=Object.assign({defaultView:"month",defaultDate:null,weekStartsOn:0,locale:"default",weekdayFormat:"short",use24Hour:!1,showTimeInItems:!0,showGridLines:!0,monthTimedEventStyle:"list",showToolbar:!0,showTodayButton:!0,showNavigation:!0,showTitle:!0,showYearPicker:!0,showViewSwitcher:!0,showTooltips:!0,showBorder:!0,maxEventsPerCell:-1,listDaysForward:30,enabledViews:["month","week","day"],enableDragDrop:!1,enableResize:!1,fetchEvents:null,onEventClick:null,onSlotClick:null,onViewChange:null,onNavigate:null,onEventDrop:null},a),this._view=this._opts.defaultView,this._date=t(this._opts.defaultDate||new Date),this._events=[],this._cachedRange=null,this._cachedEvents=[],this._nowInterval=null,this._yearPickerOpen=!1,this._yearPickerBase=0,this._yearOutsideHandler=null,this._dragState=null,this._clickSuppressed=!1,this._root=document.createElement("div"),this._root.className="uc-calendar",this._opts.showGridLines||this._root.classList.add("uc-no-grid"),this._opts.showBorder||this._root.classList.add("uc-no-border"),this._el.appendChild(this._root),this._onClick=this._handleClick.bind(this),this._root.addEventListener("click",this._onClick),this._onMouseMove=this._handleTooltipPosition.bind(this),this._root.addEventListener("mouseover",this._onMouseMove),(this._opts.enableDragDrop||this._opts.enableResize)&&(this._onMouseDown=this._handleDragStart.bind(this),this._onMouseMoveGlobal=this._handleDragMove.bind(this),this._onMouseUp=this._handleDragEnd.bind(this),this._onTouchStart=this._handleDragStart.bind(this),this._onTouchMove=this._handleDragMove.bind(this),this._onTouchEnd=this._handleDragEnd.bind(this),this._onKeyDown=this._handleDragKeyDown.bind(this),this._root.addEventListener("mousedown",this._onMouseDown),document.addEventListener("mousemove",this._onMouseMoveGlobal),document.addEventListener("mouseup",this._onMouseUp),this._root.addEventListener("touchstart",this._onTouchStart,{passive:!0}),document.addEventListener("touchmove",this._onTouchMove,{passive:!1}),document.addEventListener("touchend",this._onTouchEnd),document.addEventListener("keydown",this._onKeyDown),this._opts.enableDragDrop&&this._root.setAttribute("data-drag-enabled","true"),this._opts.enableResize&&this._root.setAttribute("data-resize-enabled","true")),this._root.innerHTML=this._buildShell(),this._fetchAndRender(),this._startNowUpdater()}setView(t){t!==this._view&&(this._view=t,this._opts.onViewChange&&this._opts.onViewChange(t),this._fetchAndRender())}navigate(t){const e=new Date(this._date);"month"===this._view?(e.setMonth(e.getMonth()+t),e.setDate(1)):"week"===this._view?e.setDate(e.getDate()+7*t):e.setDate(e.getDate()+t),this._date=e;const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender()}goToToday(){this._date=t(new Date);const e=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(e.start,e.end),this._fetchAndRender()}goToDate(e){this._date=t(o(e)),this._fetchAndRender()}refresh(){this._cachedRange=null,this._cachedEvents=[],this._fetchAndRender()}destroy(){this._nowInterval&&clearInterval(this._nowInterval),this._yearOutsideHandler&&document.removeEventListener("click",this._yearOutsideHandler),this._root.removeEventListener("click",this._onClick),this._root.removeEventListener("mouseover",this._onMouseMove),(this._opts.enableDragDrop||this._opts.enableResize)&&(this._root.removeEventListener("mousedown",this._onMouseDown),document.removeEventListener("mousemove",this._onMouseMoveGlobal),document.removeEventListener("mouseup",this._onMouseUp),this._root.removeEventListener("touchstart",this._onTouchStart),document.removeEventListener("touchmove",this._onTouchMove),document.removeEventListener("touchend",this._onTouchEnd),document.removeEventListener("keydown",this._onKeyDown)),this._root.remove()}_getRange(){if("month"===this._view){const a=l(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}if("week"===this._view){const a=r(this._date,this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[6])}}if("list"===this._view){const t=new Date,e=new Date(t);return e.setDate(e.getDate()+this._opts.listDaysForward),{start:t,end:e}}return{start:t(this._date),end:e(this._date)}}_getMonthGridRange(){const a=l(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}async _fetchAndRender(){if(this._updateToolbar(),!this._opts.fetchEvents)return void this._renderView();const t=this._getRange();if(this._cachedRange&&this._cachedRange.start<=t.start&&this._cachedRange.end>=t.end)return this._events=this._cachedEvents.filter(e=>e.end>=t.start&&e.start<=t.end),void this._renderView();const e=this._root.querySelector(".uc-loading");e&&(e.style.display="flex");try{let e;e="list"===this._view?t:this._getMonthGridRange();const a=(await this._opts.fetchEvents(e.start,e.end)||[]).map(t=>({...t,start:o(t.start),end:o(t.end)}));this._cachedRange={start:e.start,end:e.end},this._cachedEvents=a,this._events=a.filter(e=>e.end>=t.start&&e.start<=t.end)}catch(t){this._events=[]}e&&(e.style.display="none"),this._renderView()}_renderView(){const t=this._root.querySelector(".uc-view-container");if(t){if(0===this._opts.maxEventsPerCell)this._root.classList.add("uc-unlimited-events"),this._root.style.removeProperty("--cal-cell-min-height");else if(-1===this._opts.maxEventsPerCell)this._root.classList.remove("uc-unlimited-events"),this._root.style.removeProperty("--cal-cell-min-height");else{this._root.classList.remove("uc-unlimited-events");const t=30+24*this._opts.maxEventsPerCell+28;this._root.style.setProperty("--cal-cell-min-height",`${t}px`)}if("month"===this._view)t.innerHTML=this._buildMonthView();else if("week"===this._view){const e=r(this._date,this._opts.weekStartsOn);t.innerHTML=this._buildWeekOrDayView(e),this._scrollToBusinessHours(t)}else"list"===this._view?t.innerHTML=this._buildListView():(t.innerHTML=this._buildWeekOrDayView([this._date]),this._scrollToBusinessHours(t))}}_scrollToBusinessHours(t){requestAnimationFrame(()=>{const e=t.querySelector(".uc-time-body");if(!e)return;const a=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60;e.scrollTop=7*a})}_renderToolbar(){const t=this._root.querySelector(".uc-toolbar");if(!t)return;const e=document.createElement("div");e.innerHTML=this._buildToolbar(),this._root.replaceChild(e.firstElementChild,t)}_buildShell(){return`\n ${this._buildToolbar()}\n <div class="uc-loading" style="display:none">\n <div class="uc-spinner"></div>\n </div>\n <div class="uc-view-container"></div>\n `}_updateToolbar(){const t=this._root.querySelector(".uc-toolbar");if(t){const e=document.createElement("div");e.innerHTML=this._buildToolbar();const a=e.firstElementChild;a&&t.replaceWith(a)}}_buildToolbar(){if(!this._opts.showToolbar)return"";const t=this._date.getFullYear(),e=(new Date).getFullYear();let a,n=!0;if("month"===this._view)a=new Intl.DateTimeFormat(this._opts.locale,{month:"long"}).format(this._date);else if("week"===this._view){const t=r(this._date,this._opts.weekStartsOn);a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}`}(t[0],t[6],this._opts.locale)}else if("list"===this._view){const t=new Date,e=new Date(t);e.setDate(e.getDate()+this._opts.listDaysForward),a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}, ${t.getFullYear()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}, ${e.getFullYear()}`}(t,e,this._opts.locale),n=!1}else a=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",month:"long",day:"numeric"}).format(this._date);let i="";if(this._opts.showYearPicker&&this._yearPickerOpen){const a=this._yearPickerBase,s=Array.from({length:12},(s,n)=>{const i=a+n,o=i===t;return`<button class="${"uc-year-item"+(o?" uc-active":"")+(i===e&&!o?" uc-today-year":"")}" data-action="select-year" data-year="${i}">${i}</button>`}).join("");i=`\n <div class="uc-year-picker">\n <div class="uc-year-picker-nav">\n <button class="uc-year-nav-btn" data-action="year-prev" aria-label="Previous years">‹</button>\n <span class="uc-year-range">${a} – ${a+11}</span>\n <button class="uc-year-nav-btn" data-action="year-next" aria-label="Next years">›</button>\n </div>\n <div class="uc-year-grid">${s}</div>\n </div>`}const o=this._opts.locale,l=_(o,"today"),d=_(o,"month"),c=_(o,"week"),u=_(o,"day"),v=_(o,"list");let g="";if((this._opts.showNavigation||this._opts.showTodayButton)&&"list"!==this._view){const t=this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="prev" aria-label="Previous">‹</button>':"",e=new Date,a=s(this._date,e)?" uc-active":"";g=`\n <div class="uc-toolbar-section uc-toolbar-left">\n ${t}${this._opts.showTodayButton?`<button class="uc-btn uc-today-btn${a}" data-action="today">${h(l)}</button>`:""}${this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="next" aria-label="Next">›</button>':""}\n </div>`}let p="";if(this._opts.showTitle){const e=n?this._opts.showYearPicker?`<button class="uc-year-btn${this._yearPickerOpen?" uc-open":""}" data-action="year-pick" aria-label="Select year">${t}</button>`:t:"";p=`\n <div class="uc-toolbar-section uc-toolbar-center">\n <h2 class="uc-title">\n <span class="uc-title-main">${h(a)}</span>\n ${e}\n </h2>\n ${i}\n </div>`}let y="";if(this._opts.showViewSwitcher){const t=this._opts.enabledViews,e=[];t.includes("month")&&e.push(`<button class="uc-btn uc-view-btn${"month"===this._view?" uc-active":""}" data-view="month">${h(d)}</button>`),t.includes("week")&&e.push(`<button class="uc-btn uc-view-btn${"week"===this._view?" uc-active":""}" data-view="week">${h(c)}</button>`),t.includes("day")&&e.push(`<button class="uc-btn uc-view-btn${"day"===this._view?" uc-active":""}" data-view="day">${h(u)}</button>`),t.includes("list")&&e.push(`<button class="uc-btn uc-view-btn${"list"===this._view?" uc-active":""}" data-view="list">${h(v)}</button>`),e.length>0&&(y=`\n <div class="uc-toolbar-section uc-toolbar-right">\n <div class="uc-view-switcher">\n ${e.join("")}\n </div>\n </div>`)}return`\n <div class="uc-toolbar">\n ${g}${p}${y}\n </div>\n `}_buildMonthView(){const{locale:a,weekStartsOn:s}=this._opts,n=l(this._date.getFullYear(),this._date.getMonth(),s),i=c(a,s,this._opts.weekdayFormat),o=this._events.map(a=>({...a,_origStart:a.start,start:v(a)?t(a.start):a.start,end:v(a)?e(a.end):a.end})),r=i.map(t=>`<div class="uc-month-day-name">${h(t)}</div>`).join(""),d=[];for(let t=0;t<n.length;t+=7)d.push(n.slice(t,t+7));return`\n <div class="uc-month-view">\n <div class="uc-month-header">${r}</div>\n <div class="uc-month-body">${d.map(t=>this._buildWeekRow(t,o)).join("")}</div>\n </div>\n `}_buildWeekRow(t,e){const a=this._date.getMonth();let i;if(0===this._opts.maxEventsPerCell)i=1/0;else if(-1===this._opts.maxEventsPerCell){const t=getComputedStyle(this._root),e=parseInt(t.getPropertyValue("--cal-cell-min-height")||"112");i=Math.floor((e-30-28)/24),i=Math.max(1,i)}else i=this._opts.maxEventsPerCell;const o=e.filter(v),r=e.filter(t=>!v(t)),l=g(o,t),c=Array.from({length:7},()=>new Set);for(const{startCol:t,endCol:e,slot:a}of l)for(let s=t;s<=e;s++)c[s].add(a);const u=t.map(t=>r.filter(e=>s(e.start,t)).sort((t,e)=>t.start-e.start)),_=t.map((t,e)=>`\n <div class="uc-day-cell${n(t)?" uc-today":""}${t.getMonth()!==a?" uc-other-month":""}" data-date="${t.toISOString()}" data-action="day-click">\n <span class="uc-day-number" data-action="day-number" data-date="${t.toISOString()}">${t.getDate()}</span>\n </div>`).join("");let p="";for(const{event:t,startCol:e,endCol:a,slot:s,isStart:n,isEnd:o}of l){if(i!==1/0&&s>=i)continue;const r=100/7,l=e*r,d=(a-e+1)*r,c=`calc(var(--cal-header-day-height) + ${s} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,u=t.color||"var(--cal-event-bg)",_=n?"var(--cal-event-border-radius)":"0",v=o?"var(--cal-event-border-radius)":"0",g=n?"":" uc-continues-left",y=o?"":" uc-continues-right",m=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"",w=this._opts.enableResize&&o?'<div class="uc-resize-handle-right" data-action="resize-handle"></div>':"";p+=`\n <div class="uc-event-bar${g}${y}"\n style="left:calc(${l}% + 2px);width:calc(${d}% - 4px);top:${c};background:${h(u)};border-radius:${_} ${v} ${v} ${_};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${m}>\n ${n?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n ${w}\n </div>`}let y=-1;for(let e=0;e<7;e++){const a=100/7,s=e*a,n=t[e],o=[...c[e]],r=o.length>0?Math.max(...o)+1:0,l=u[e];if(i===1/0)l.forEach((t,e)=>{const n=r+e;y=Math.max(y,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,o=t.color||"var(--cal-event-bg)",l=d(t.start,this._opts.locale,this._opts.use24Hour),c=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";if("list"===this._opts.monthTimedEventStyle){const e=this._opts.showTimeInItems?`<span class="uc-event-time">${h(l)}</span>`:"";p+=`\n <div class="uc-event-bar uc-event-bar--list"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${c}>\n <span class="uc-event-dot" style="background:${h(o)};"></span>\n ${e}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}else{const e=this._opts.showTimeInItems?`<span class="uc-event-time">${h(l)}</span>`:"";p+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(o)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${c}>\n ${e}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}});else{const t=[...c[e]].filter(t=>t>=i).length,o=[];for(let t=r;t<i;t++)o.push(t);let u=t;if(l.forEach((t,e)=>{if(e<o.length){const n=o[e];y=Math.max(y,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,r=t.color||"var(--cal-event-bg)",l=d(t.start,this._opts.locale,this._opts.use24Hour),c=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";if("list"===this._opts.monthTimedEventStyle){const e=this._opts.showTimeInItems?`<span class="uc-event-time">${h(l)}</span>`:"";p+=`\n <div class="uc-event-bar uc-event-bar--list"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${c}>\n <span class="uc-event-dot" style="background:${h(r)};"></span>\n ${e}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}else{const e=this._opts.showTimeInItems?`<span class="uc-event-time">${h(l)}</span>`:"";p+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(r)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${c}>\n ${e}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}}else u++}),u>0){p+=`\n <div class="uc-more-link"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${`calc(var(--cal-header-day-height) + ${i} * (var(--cal-event-height) + var(--cal-event-gap)) + 2px)`};"\n data-date="${n.toISOString()}" data-action="more-click">\n +${u} more\n </div>`}}}for(const{slot:t}of l)(i===1/0||t<i)&&(y=Math.max(y,t));let m="";return i===1/0&&(m=y>=0?` style="min-height: calc(var(--cal-header-day-height) + ${y+1} * (var(--cal-event-height) + var(--cal-event-gap)) + 8px);"`:' style="min-height: 80px;"'),`\n <div class="uc-week-row"${m}>\n <div class="uc-week-cells">${_}</div>\n <div class="uc-week-events">${p}</div>\n </div>`}_buildWeekOrDayView(a){const{locale:i,weekStartsOn:o,use24Hour:r}=this._opts,l=1===a.length,u=l?" uc-day-header-single":"",p=l?[new Intl.DateTimeFormat(i,{weekday:this._opts.weekdayFormat}).format(a[0])]:c(i,o,this._opts.weekdayFormat),y=t(a[0]),m=e(a[a.length-1]),w=this._events.filter(t=>v(t)&&t.start<=m&&e(t.end)>=y).map(a=>({...a,start:t(a.start),end:e(a.end)})),f=this._events.filter(t=>!v(t)&&t.start>=y&&t.start<=m),D=a.map((t,e)=>{const a=n(t)?" uc-today":"",s=t.getDate();return`\n <div class="uc-week-day-header${a}">\n <span class="uc-week-day-name">${h(p[e])}</span>\n <span class="uc-week-day-num" data-action="day-number" data-date="${t.toISOString()}">${s}</span>\n </div>`}).join(""),S=l?w.map((t,e)=>({event:t,startCol:0,endCol:0,slot:e,isStart:!0,isEnd:!0})):g(w,a),k=S.length?Math.max(...S.map(t=>t.slot))+1:0;let $="";for(const{event:t,startCol:e,endCol:s,slot:n,isStart:i,isEnd:o}of S){const r=100/a.length,l=e*r,d=(s-e+1)*r,c=`calc(${n} * (var(--cal-event-height) + 3px) + 2px)`,u=t.color||"var(--cal-event-bg)",_=i?"var(--cal-event-border-radius)":"0",v=o?"var(--cal-event-border-radius)":"0",g=i?"":" uc-continues-left",p=o?"":" uc-continues-right",y=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"",m=this._opts.enableResize&&o?'<div class="uc-resize-handle-right" data-action="resize-handle"></div>':"";$+=`\n <div class="uc-event-bar${g}${p}"\n style="left:calc(${l}% + 2px);width:calc(${d}% - 4px);top:${c};background:${h(u)};border-radius:${_} ${v} ${v} ${_};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${y}>\n <span class="uc-event-title">${h(t.title)}</span>\n ${m}\n </div>`}const b=`calc(${Math.max(1,k)} * (var(--cal-event-height) + 3px) + 6px)`,M=new Date,E=60*M.getHours()+M.getMinutes(),T=a.length,x=Array.from({length:24},(t,e)=>`<div class="uc-hour-cell"><span class="uc-hour-label">${h(0===e?"":d(new Date(2e3,0,1,e),"en-US",r))}</span></div>`).join(""),C=a.map(t=>{const e=n(t)?" uc-today":"",a=Array.from({length:24},()=>'<div class="uc-hour-row"><div class="uc-half-hour-line"></div></div>').join(""),o=function(t){if(!t.length)return[];const e=[...t].sort((t,e)=>t.start-e.start||e.end-t.end),a=[],s=[];for(const t of e){let e=a.findIndex(e=>e<=t.start);-1===e?(e=a.length,a.push(t.end)):a[e]=t.end,s.push({event:t,col:e})}return s.map(t=>{const e=s.filter(e=>e.event.start<t.event.end&&e.event.end>t.event.start),a=Math.max(...e.map(t=>t.col))+1;return{...t,totalCols:a}})}(f.filter(e=>s(e.start,t))).map(({event:t,col:e,totalCols:a})=>{const s=60*t.start.getHours()+t.start.getMinutes(),n=60*t.end.getHours()+t.end.getMinutes(),o=`calc(${s} / 60 * var(--cal-hour-height))`,l=`calc(${Math.max(n-s,30)} / 60 * var(--cal-hour-height))`,c=100/a,u=`calc(${e*c}% + 1px)`,_=`calc(${c}% - 2px)`,v=t.color||"var(--cal-event-bg)",g=d(t.start,i,r),p=n-s<=60?" uc-timed-event--short":"",y=this._opts.showTimeInItems?`<span class="uc-event-time">${h(g)}</span>`:"",m=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"",w=this._opts.enableResize?'<div class="uc-resize-handle" data-action="resize-handle"></div>':"";return`\n <div class="uc-timed-event${p}"\n style="top:${o};height:${l};left:${u};width:${_};background:${h(v)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${m}>\n ${y}\n <span class="uc-event-title">${h(t.title)}</span>\n ${w}\n </div>`}).join(""),l=n(t)?`<div class="uc-now-indicator" style="top:calc(${E} / 60 * var(--cal-hour-height));">\n <span class="uc-now-dot"></span>\n <span class="uc-now-line"></span>\n </div>`:"";return`<div class="uc-time-col${e}" data-date="${t.toISOString()}" data-action="slot-col">\n ${a}${o}${l}\n </div>`}).join("");return`\n <div class="${l?"uc-day-view":"uc-week-view"}">\n <div class="uc-week-header">\n <div class="uc-time-gutter-spacer"></div>\n <div class="uc-week-day-headers${u}">${D}</div>\n </div>\n <div class="uc-all-day-section">\n <div class="uc-time-gutter-spacer uc-all-day-label">${_(this._opts.locale,"allDay")}</div>\n <div class="uc-all-day-events" style="min-height:${b}">${$}</div>\n </div>\n <div class="uc-time-body">\n <div class="uc-time-grid-inner">\n <div class="uc-time-gutter">${x}</div>\n <div class="uc-time-columns" style="--uc-col-count:${T}">${C}</div>\n </div>\n </div>\n </div>`}_buildListView(){const e=new Date,a=t(e),s=new Date(a);s.setDate(s.getDate()+this._opts.listDaysForward);const n=this._events.filter(t=>t.end>=e&&t.start<=s);n.sort((e,s)=>{const n=(e.start<a?a:t(e.start))-(s.start<a?a:t(s.start));return 0!==n?n:e.start-s.start});const i=new Map;if(n.forEach(e=>{const s=e.start<a?a:t(e.start),n=`${(o=s).getFullYear()}-${String(o.getMonth()+1).padStart(2,"0")}-${String(o.getDate()).padStart(2,"0")}`;var o;i.has(n)||i.set(n,[]),i.get(n).push(e)}),0===i.size)return'\n <div class="uc-list-view">\n <div class="uc-list-empty">\n <p>No upcoming events</p>\n </div>\n </div>';let r="";return i.forEach((t,e)=>{const a=o(e),s=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",year:"numeric",month:"long",day:"numeric"}).format(a),n=t.map(t=>{const e=v(t)?"All day":d(t.start,this._opts.locale,this._opts.use24Hour),a=t.color||"var(--cal-event-bg)",s=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||"")}"`:"";return`\n <div class="uc-list-event" data-event-id="${h(t.id)}" data-action="event-click" ${s}>\n <div class="uc-list-event-indicator" style="background: ${h(a)};"></div>\n <div class="uc-list-event-time">${h(e)}</div>\n <div class="uc-list-event-content">\n <div class="uc-list-event-title">${h(t.title)}</div>\n </div>\n </div>`}).join("");r+=`\n <div class="uc-list-date-group">\n <div class="uc-list-date-header">${h(s)}</div>\n <div class="uc-list-events">${n}</div>\n </div>`}),`<div class="uc-list-view">${r}</div>`}_closeYearPicker(){this._yearOutsideHandler&&(document.removeEventListener("click",this._yearOutsideHandler),this._yearOutsideHandler=null),this._yearPickerOpen=!1,this._renderToolbar()}_handleClick(e){if(this._clickSuppressed)return void(this._clickSuppressed=!1);const a=e.target.closest("[data-view]");if(a)return void this.setView(a.dataset.view);const s=e.target.closest("[data-action]");if(!s)return;switch(s.dataset.action){case"prev":this.navigate(-1);break;case"next":this.navigate(1);break;case"today":this.goToToday();break;case"year-pick":e.stopPropagation(),this._yearPickerOpen?this._closeYearPicker():(this._yearPickerOpen=!0,this._yearPickerBase=this._date.getFullYear()-4,this._renderToolbar(),this._yearOutsideHandler=t=>{t.target.closest(".uc-year-picker")||t.target.closest('[data-action="year-pick"]')||this._closeYearPicker()},setTimeout(()=>document.addEventListener("click",this._yearOutsideHandler),0));break;case"year-prev":e.stopPropagation(),this._yearPickerBase-=12,this._renderToolbar();break;case"year-next":e.stopPropagation(),this._yearPickerBase+=12,this._renderToolbar();break;case"select-year":{e.stopPropagation();const t=parseInt(s.dataset.year,10);this._date=new Date(this._date.getFullYear()!==t?new Date(this._date).setFullYear(t):this._date),this._closeYearPicker();const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender();break}case"event-click":{e.stopPropagation();const t=s.dataset.eventId,a=this._events.find(e=>String(e.id)===String(t));a&&this._opts.onEventClick&&this._opts.onEventClick(a,e);break}case"day-click":{if(e.target.closest('[data-action="day-number"]'))break;if(e.target.closest('[data-action="event-click"]'))break;const t=new Date(s.dataset.date);this._opts.onSlotClick&&this._opts.onSlotClick(t,e);break}case"day-number":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"more-click":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"slot-col":if(e.target.closest('[data-action="event-click"]'))break;if(this._opts.onSlotClick){const t=s,a=t.getBoundingClientRect(),n=e.clientY-a.top,i=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60,o=Math.max(0,Math.floor(n/i*60)),r=Math.floor(o/60)%24,l=15*Math.round(o%60/15),d=new Date(t.dataset.date);d.setHours(r,l,0,0),this._opts.onSlotClick(d,e)}}}_handleTooltipPosition(t){const e=t.target.closest("[data-tooltip]");if(!e||!e.dataset.tooltip)return;if(!e.dataset.tooltip.trim())return;const a=e.getBoundingClientRect(),s=window.innerWidth;e.classList.remove("uc-tooltip-left","uc-tooltip-right","uc-tooltip-bottom");const n=a.width/2-125;a.left+n+250>s-10?e.classList.add("uc-tooltip-left"):a.left+n<10&&e.classList.add("uc-tooltip-right"),a.top<50&&e.classList.add("uc-tooltip-bottom")}_handleDragStart(t){if("list"===this._view)return;const e=t.target.closest('[data-action="resize-handle"]'),a=!!e,s=e&&e.classList.contains("uc-resize-handle-right"),n=e&&e.classList.contains("uc-resize-handle"),i=t.target.closest('[data-event-id][data-action="event-click"]');if(!i)return;if(t.target.closest('.uc-year-picker, [data-action="year-pick"], .uc-toolbar'))return;const o=i.dataset.eventId,r=this._events.find(t=>String(t.id)===String(o));if(!r)return;const l=r.allDay||!1;if(a&&!this._opts.enableResize)return;if(!a&&!this._opts.enableDragDrop)return;if(n&&(l||"week"!==this._view&&"day"!==this._view))return;if(s&&!l)return;const d=this._getEventPosition(t),c=i.getBoundingClientRect(),h=d.x-c.left,u=d.y-c.top;this._dragState={mode:a?"resize":"move",resizeDirection:s?"horizontal":n?"vertical":null,eventId:o,originalEvent:{...r,start:new Date(r.start),end:new Date(r.end)},dragElement:null,startX:d.x,startY:d.y,currentX:d.x,currentY:d.y,offsetX:h,offsetY:u,originalView:this._view,isAllDay:l,hasMoved:!1,startTime:Date.now(),originalElement:i}}_handleDragMove(t){if(!this._dragState)return;const e=this._getEventPosition(t);this._dragState.currentX=e.x,this._dragState.currentY=e.y;const a=e.x-this._dragState.startX,s=e.y-this._dragState.startY,n=Math.sqrt(a*a+s*s);if(!this._dragState.hasMoved){const t=Date.now()-this._dragState.startTime;if(!(n>5||t>150))return;this._dragState.hasMoved=!0,this._createDragElement(),this._root.classList.add("uc-dragging")}if(t.type.startsWith("touch")&&t.preventDefault(),this._dragState.dragElement)if("resize"===this._dragState.mode&&"vertical"===this._dragState.resizeDirection)this._updateResizePreview(e.x,e.y);else if("resize"===this._dragState.mode&&"horizontal"===this._dragState.resizeDirection)this._updateHorizontalResizePreview(e.x);else{const t=this._getSnappedPosition(e.x,e.y);t?(this._dragState.dragElement.style.left=t.x+"px",this._dragState.dragElement.style.top=t.y+"px"):(this._dragState.dragElement.style.left=e.x-this._dragState.offsetX+"px",this._dragState.dragElement.style.top=e.y-this._dragState.offsetY+"px")}}_getSnappedPosition(t,e){if("week"!==this._view&&"day"!==this._view)return null;const a=this._calculateDropPosition(t,e);if(!a)return null;const s=this._dragState.dragElement;if(!s)return null;if(!a.isAllDay){const e=this._root.querySelector(".uc-time-columns");if(!e)return null;const n=e.querySelectorAll(".uc-time-col");let i=null;for(const e of n){const a=e.getBoundingClientRect();if(t>=a.left&&t<a.right){i=e;break}}if(!i)return null;const o=i.getBoundingClientRect(),r=(60*a.date.getHours()+a.date.getMinutes())/60*(parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60),l=s.querySelector(".uc-event-time");if(l){const t=d(a.date,this._opts.locale,this._opts.use24Hour);l.textContent=t}return{x:o.left+4,y:o.top+r}}if(a.isAllDay){const e=this._root.querySelector(".uc-all-day-events");if(!e)return null;const a=e.getBoundingClientRect(),s="day"===this._view?[this._date]:r(this._date,this._opts.weekStartsOn),n=a.width/s.length,i=t-a.left,o=Math.floor(i/n);return o<0||o>=s.length?null:{x:a.left+o*n+4,y:a.top+4}}return null}_updateResizePreview(t,e){const a=this._dragState.dragElement,s=this._dragState.originalEvent,n=this._root.querySelector(".uc-time-columns");if(!n)return;const i=n.querySelectorAll(".uc-time-col");let o=null;for(const e of i){const a=e.getBoundingClientRect();if(t>=a.left&&t<a.right){o=e;break}}if(!o)return;const r=o.getBoundingClientRect(),l=Math.max(0,e-r.top),c=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60,h=Math.floor(l/c*60),u=Math.floor(h/60)%24,_=15*Math.round(h%60/15),v=new Date(s.start);v.setHours(u,_,0,0),v<=s.start&&v.setTime(s.start.getTime()+9e5);const g=(v-s.start)/6e4/60*c;a.style.height=g+"px";const p=a.querySelector(".uc-event-time");if(p){const t=d(s.start,this._opts.locale,this._opts.use24Hour),e=d(v,this._opts.locale,this._opts.use24Hour);p.textContent=`${t} - ${e}`}this._dragState.newEndTime=v}_updateHorizontalResizePreview(t){const a=this._dragState.dragElement,s=this._dragState.originalEvent,n=this._dragState.originalElement;if(!n)return;const i=n.getBoundingClientRect(),o=t-i.left;let d;if("month"===this._view){const a=this._root.querySelector(".uc-month-body");if(!a)return;const s=a.getBoundingClientRect(),n=s.width/7,o=t-s.left,r=Math.floor(o/n),c=l(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn),h=i.top-s.top,u=s.height/Math.ceil(c.length/7),_=7*Math.floor(h/u)+r;_>=0&&_<c.length&&(d=e(c[_]))}else{const a="day"===this._view?[this._date]:r(this._date,this._opts.weekStartsOn),s=this._root.querySelector(".uc-all-day-events");if(!s)return;const n=s.getBoundingClientRect(),i=n.width/a.length,o=t-n.left,l=Math.floor(o/i);l>=0&&l<a.length&&(d=e(a[l]))}if(!d)return;d<s.start&&(d=e(s.start));const c=o+20;a.style.width=Math.max(c,60)+"px",this._dragState.newEndDate=d}_handleDragEnd(t){if(!this._dragState)return;if(this._dragState.hasMoved){if(t.preventDefault(),t.stopPropagation(),this._clickSuppressed=!0,setTimeout(()=>{this._clickSuppressed=!1},10),"resize"===this._dragState.mode&&"vertical"===this._dragState.resizeDirection)this._dragState.newEndTime&&this._performResize(this._dragState.newEndTime);else if("resize"===this._dragState.mode&&"horizontal"===this._dragState.resizeDirection)this._dragState.newEndDate&&this._performHorizontalResize(this._dragState.newEndDate);else{const e=this._getEventPosition(t),a=this._calculateDropPosition(e.x,e.y);a&&this._performDrop(a)}this._cleanupDrag()}else this._dragState=null}_handleDragKeyDown(t){this._dragState&&this._dragState.hasMoved&&"Escape"===t.key&&this._cleanupDrag()}_getEventPosition(t){if(t.type.startsWith("touch")){const e=t.touches[0]||t.changedTouches[0];return{x:e.clientX,y:e.clientY}}return{x:t.clientX,y:t.clientY}}_createDragElement(){const t=this._root.querySelector(`[data-event-id="${this._dragState.eventId}"]`);if(!t)return;const e=t.cloneNode(!0);e.classList.add("uc-dragging-element");const a=t.getBoundingClientRect();e.style.position="fixed",e.style.pointerEvents="none",e.style.zIndex="10000",e.style.opacity="0.8",e.style.width=t.offsetWidth+"px","resize"===this._dragState.mode?(e.style.left=a.left+"px",e.style.top=a.top+"px",e.style.height=t.offsetHeight+"px"):(e.style.left=this._dragState.currentX-this._dragState.offsetX+"px",e.style.top=this._dragState.currentY-this._dragState.offsetY+"px"),document.body.appendChild(e),this._dragState.dragElement=e}_cleanupDrag(){this._dragState&&this._dragState.dragElement&&this._dragState.dragElement.remove(),this._root.classList.remove("uc-dragging"),this._dragState=null}_calculateDropPosition(t,e){if("week"===this._view||"day"===this._view){const a=this._calculateAllDayDropPosition(t,e);if(a)return a;const s=this._calculateTimedDropPosition(t,e);if(s)return s}return"month"===this._view?this._calculateMonthDropPosition(t,e):null}_calculateMonthDropPosition(t,e){const a=this._root.querySelector(".uc-month-body");if(!a)return null;const s=a.getBoundingClientRect(),n=t-s.left,i=e-s.top,o=s.width/7,r=a.querySelectorAll(".uc-week-row").length,d=s.height/r,c=Math.floor(n/o),h=Math.floor(i/d),u=l(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn),_=7*h+c;return c<0||c>=7||_<0||_>=u.length?null:{date:u[_],isMonthView:!0}}_calculateTimedDropPosition(t,e){const a=this._root.querySelector(".uc-time-columns");if(!a)return null;const s=a.querySelectorAll(".uc-time-col");let n=null;for(const e of s){const a=e.getBoundingClientRect();if(t>=a.left&&t<a.right){n=e;break}}if(!n)return null;const i=n.getBoundingClientRect(),o=Math.max(0,e-i.top),r=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60,l=Math.floor(o/r*60),d=Math.floor(l/60)%24,c=15*Math.round(l%60/15),h=n.dataset.date;if(!h)return null;const u=new Date(h);return u.setHours(d,c,0,0),{date:u,isAllDay:!1}}_calculateAllDayDropPosition(e,a){const s=this._root.querySelector(".uc-all-day-events");if(!s)return null;const n=s.getBoundingClientRect();if(a<n.top||a>n.bottom)return null;const i="day"===this._view?[this._date]:r(this._date,this._opts.weekStartsOn),o=n.width/i.length,l=e-n.left,d=Math.floor(l/o);return d<0||d>=i.length?null:{date:t(i[d]),isAllDay:!0}}_performDrop(s){const n=this._dragState.originalEvent,o=new Date(n.start),r=new Date(n.end);let l,d,c;const h=n.allDay||this._dragState.isAllDay;if(s.isMonthView)if(h){const n=i(o,r);l=t(s.date),d=e(a(s.date,Math.max(0,n))),c=!0}else{const t=r-o;l=new Date(s.date),l.setHours(o.getHours(),o.getMinutes(),o.getSeconds(),o.getMilliseconds()),d=new Date(l.getTime()+t),c=!1}else if(s.isAllDay){const n=Math.max(0,Math.ceil((r-o)/864e5));l=t(s.date),d=e(a(s.date,n)),c=!0}else{if(l=s.date,h)d=new Date(l.getTime()+36e5);else{const t=r-o;d=new Date(l.getTime()+t)}c=!1}const u=this._events.findIndex(t=>String(t.id)===String(this._dragState.eventId));u>=0&&(this._events[u].start=l,this._events[u].end=d,this._events[u].allDay=c);const _=this._cachedEvents.findIndex(t=>String(t.id)===String(this._dragState.eventId));_>=0&&(this._cachedEvents[_].start=l,this._cachedEvents[_].end=d,this._cachedEvents[_].allDay=c),this._opts.onEventDrop&&this._opts.onEventDrop(u>=0?this._events[u]:null,o,r,l,d),this._renderView()}_performResize(t){const e=this._dragState.originalEvent,a=new Date(e.start),s=new Date(e.end),n=a,i=this._events.findIndex(t=>String(t.id)===String(this._dragState.eventId));i>=0&&(this._events[i].end=t);const o=this._cachedEvents.findIndex(t=>String(t.id)===String(this._dragState.eventId));o>=0&&(this._cachedEvents[o].end=t),this._opts.onEventDrop&&this._opts.onEventDrop(i>=0?this._events[i]:null,a,s,n,t),this._renderView()}_performHorizontalResize(t){const e=this._dragState.originalEvent,a=new Date(e.start),s=new Date(e.end),n=a,i=this._events.findIndex(t=>String(t.id)===String(this._dragState.eventId));i>=0&&(this._events[i].end=t);const o=this._cachedEvents.findIndex(t=>String(t.id)===String(this._dragState.eventId));o>=0&&(this._cachedEvents[o].end=t),this._opts.onEventDrop&&this._opts.onEventDrop(i>=0?this._events[i]:null,a,s,n,t),this._renderView()}_startNowUpdater(){this._nowInterval=setInterval(()=>{const t=this._root.querySelectorAll(".uc-now-indicator");if(!t.length)return;const e=new Date,a=`calc(${60*e.getHours()+e.getMinutes()} / 60 * var(--cal-hour-height))`;t.forEach(t=>t.style.top=a)},6e4)}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-calendar-js",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6",
|
|
4
4
|
"description": "A clean, modern, and feature-rich JavaScript calendar component with zero dependencies. Responsive design and intuitive navigation.",
|
|
5
5
|
"main": "dist/simple-calendar-js.min.js",
|
|
6
6
|
"style": "dist/simple-calendar-js.min.css",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"dist/",
|
|
9
9
|
"frameworks/",
|
|
10
10
|
"LICENSE",
|
|
11
|
-
"README.md"
|
|
11
|
+
"README.md",
|
|
12
|
+
"CHANGELOG.md"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
15
|
"test": "echo \"Error: no test specified\" && exit 1",
|