simple-calendar-js 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +413 -1
- 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 +16 -1
- package/frameworks/simple-calendar-js-react.jsx +8 -2
- package/frameworks/simple-calendar-js-vue.js +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
## Features
|
|
@@ -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
|
|
|
@@ -226,6 +230,91 @@ const events = [
|
|
|
226
230
|
];
|
|
227
231
|
```
|
|
228
232
|
|
|
233
|
+
## Timezone Handling
|
|
234
|
+
|
|
235
|
+
SimpleCalendarJs relies on JavaScript's native Date object for timezone handling, which means events are automatically displayed in the **user's local timezone**.
|
|
236
|
+
|
|
237
|
+
### How It Works
|
|
238
|
+
|
|
239
|
+
1. **Automatic Conversion**: JavaScript Date objects automatically convert to the user's browser timezone
|
|
240
|
+
2. **No Configuration Needed**: The calendar has no timezone settings - it uses the browser's timezone
|
|
241
|
+
3. **Backend Responsibility**: Your backend should send timezone-aware date strings
|
|
242
|
+
|
|
243
|
+
### Best Practices
|
|
244
|
+
|
|
245
|
+
**✓ Recommended - Send ISO 8601 strings with timezone:**
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
fetchEvents: async (start, end) => {
|
|
249
|
+
const response = await fetch(`/api/events?start=${start}&end=${end}`);
|
|
250
|
+
const events = await response.json();
|
|
251
|
+
|
|
252
|
+
// Backend returns ISO 8601 strings with timezone info
|
|
253
|
+
// Example: "2024-03-15T10:00:00Z" (UTC)
|
|
254
|
+
// or: "2024-03-15T10:00:00-05:00" (EST)
|
|
255
|
+
|
|
256
|
+
return events.map(event => ({
|
|
257
|
+
...event,
|
|
258
|
+
start: new Date(event.start), // Automatically converts to local timezone
|
|
259
|
+
end: new Date(event.end)
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**✗ Avoid - Sending dates without timezone info:**
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
// BAD: "2024-03-15T10:00:00" (no timezone)
|
|
268
|
+
// JavaScript interprets this as LOCAL time, not UTC
|
|
269
|
+
// This can cause issues for users in different timezones
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Example: Multi-Timezone Scenario
|
|
273
|
+
|
|
274
|
+
**Scenario**: Your server stores events in UTC, users are in different timezones
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
// Server returns (stored in UTC):
|
|
278
|
+
{
|
|
279
|
+
"title": "Team Meeting",
|
|
280
|
+
"start": "2024-03-15T14:00:00Z", // 2:00 PM UTC
|
|
281
|
+
"end": "2024-03-15T15:00:00Z" // 3:00 PM UTC
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// User in New York (EST, UTC-5):
|
|
285
|
+
// Calendar displays: 9:00 AM - 10:00 AM
|
|
286
|
+
|
|
287
|
+
// User in London (GMT, UTC+0):
|
|
288
|
+
// Calendar displays: 2:00 PM - 3:00 PM
|
|
289
|
+
|
|
290
|
+
// User in Tokyo (JST, UTC+9):
|
|
291
|
+
// Calendar displays: 11:00 PM - 12:00 AM
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Important Notes
|
|
295
|
+
|
|
296
|
+
- **Storage**: Always store events in UTC in your database
|
|
297
|
+
- **API Format**: Send dates as ISO 8601 strings with timezone information
|
|
298
|
+
- **Display**: The calendar automatically shows events in the user's local timezone
|
|
299
|
+
- **No Timezone Selector**: The calendar doesn't provide UI to change timezone - it always uses the browser's timezone
|
|
300
|
+
- **Time Formatting**: Uses `Intl.DateTimeFormat` which respects the user's locale and timezone
|
|
301
|
+
|
|
302
|
+
### Example Backend Response
|
|
303
|
+
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"events": [
|
|
307
|
+
{
|
|
308
|
+
"id": 1,
|
|
309
|
+
"title": "Global Team Standup",
|
|
310
|
+
"start": "2024-03-15T14:00:00Z",
|
|
311
|
+
"end": "2024-03-15T14:30:00Z",
|
|
312
|
+
"description": "Daily standup - all timezones welcome"
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
229
318
|
## Tooltips
|
|
230
319
|
|
|
231
320
|
SimpleCalendarJs includes built-in tooltip support for displaying additional event information on hover.
|
|
@@ -329,6 +418,142 @@ Tooltips use CSS custom properties and can be customized:
|
|
|
329
418
|
- Special characters are automatically escaped for security
|
|
330
419
|
- Maximum width is 250px by default (can be customized via CSS variables)
|
|
331
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
|
+
|
|
332
557
|
## API Methods
|
|
333
558
|
|
|
334
559
|
```javascript
|
|
@@ -395,6 +620,190 @@ addEventToDatabase(newEvent);
|
|
|
395
620
|
calendar.refresh(); // ✓ Fetch (clears cache)
|
|
396
621
|
```
|
|
397
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
|
+
|
|
398
807
|
## Internationalization
|
|
399
808
|
|
|
400
809
|
SimpleCalendarJs uses the native Intl.DateTimeFormat API for full locale support. Simply pass a valid locale code:
|
|
@@ -601,6 +1010,9 @@ Example with a complete brand color scheme:
|
|
|
601
1010
|
- `--cal-tooltip-max-width`, `--cal-tooltip-padding`, `--cal-tooltip-radius` - Tooltip sizing
|
|
602
1011
|
- `--cal-tooltip-font-size`, `--cal-tooltip-offset` - Tooltip typography
|
|
603
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
|
+
|
|
604
1016
|
All styles are scoped under `.uc-calendar` to prevent conflicts with your existing CSS.
|
|
605
1017
|
|
|
606
1018
|
## Framework Wrapper APIs
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
2
|
+
* SimpleCalendarJs v3.0.5 — 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);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-month-view{display:flex;flex-direction:column;flex:1;overflow:visible}.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}.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}.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}.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}.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-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: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: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;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;--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.5
|
|
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 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
|
|
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 v(t,e){const a=t.split("-")[0];return(u[t]||u[a]||u["en-US"])[e]}function _(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=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._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=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 `}_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=v(o,"today"),c=v(o,"month"),d=v(o,"week"),u=v(o,"day"),p=v(o,"list");let _="";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":"";_=`\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 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 w="";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&&(w=`\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}${w}\n </div>\n `}_buildMonthView(){const{locale:a,weekStartsOn:s}=this._opts,n=l(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})),r=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">${r}</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),r=e.filter(t=>!p(t)),l=_(o,t),d=Array.from({length:7},()=>new Set);for(const{startCol:t,endCol:e,slot:a}of l)for(let s=t;s<=e;s++)d[s].add(a);const u=t.map(t=>r.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 l){if(i!==1/0&&s>=i)continue;const r=100/7,l=e*r,c=(a-e+1)*r,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",_=n?"":" uc-continues-left",w=o?"":" uc-continues-right",y=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar${_}${w}"\n style="left:calc(${l}% + 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 ${y}>\n ${n?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n </div>`}let w=-1;for(let e=0;e<7;e++){const a=100/7,s=e*a,n=t[e],o=[...d[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;w=Math.max(w,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=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(l)}</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=r;t<i;t++)o.push(t);let u=t;if(l.forEach((t,e)=>{if(e<o.length){const n=o[e];w=Math.max(w,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=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(l)}</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(r)};"\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 l)(i===1/0||t<i)&&(w=Math.max(w,t));let y="";return i===1/0&&(y=w>=0?` style="min-height: calc(var(--cal-header-day-height) + ${w+1} * (var(--cal-event-height) + var(--cal-event-gap)) + 8px);"`:' style="min-height: 80px;"'),`\n <div class="uc-week-row"${y}>\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:r}=this._opts,l=1===a.length,u=l?" uc-day-header-single":"",v=l?[new Intl.DateTimeFormat(i,{weekday:this._opts.weekdayFormat}).format(a[0])]:d(i,o,this._opts.weekdayFormat),g=t(a[0]),w=e(a[a.length-1]),y=this._events.filter(t=>p(t)&&t.start<=w&&e(t.end)>=g).map(a=>({...a,start:t(a.start),end:e(a.end)})),m=this._events.filter(t=>!p(t)&&t.start>=g&&t.start<=w),f=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(v[e])}</span>\n <span class="uc-week-day-num" data-action="day-number" data-date="${t.toISOString()}">${s}</span>\n </div>`}).join(""),$=l?y.map((t,e)=>({event:t,startCol:0,endCol:0,slot:e,isStart:!0,isEnd:!0})):_(y,a),k=$.length?Math.max(...$.map(t=>t.slot))+1:0;let b="";for(const{event:t,startCol:e,endCol:s,slot:n,isStart:i,isEnd:o}of $){const r=100/a.length,l=e*r,c=(s-e+1)*r,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",_=i?"":" uc-continues-left",g=o?"":" uc-continues-right",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";b+=`\n <div class="uc-event-bar${_}${g}"\n style="left:calc(${l}% + 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 ${i?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n </div>`}const D=`calc(${Math.max(1,k)} * (var(--cal-event-height) + 3px) + 6px)`,S=new Date,M=60*S.getHours()+S.getMinutes(),T=a.length;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}">${f}</div>\n </div>\n <div class="uc-all-day-section">\n <div class="uc-time-gutter-spacer uc-all-day-label">all-day</div>\n <div class="uc-all-day-events" style="min-height:${D}">${b}</div>\n </div>\n <div class="uc-time-body">\n <div class="uc-time-grid-inner">\n <div class="uc-time-gutter">${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",r))}</span></div>`).join("")}</div>\n <div class="uc-time-columns" style="--uc-col-count:${T}">${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}})}(m.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))`,d=100/a,u=`calc(${e*d}% + 1px)`,v=`calc(${d}% - 2px)`,p=t.color||"var(--cal-event-bg)",_=c(t.start,i,r),g=n-s<=60?" uc-timed-event--short":"",w=this._opts.showTimeInItems?`<span class="uc-event-time">${h(_)}</span>`:"",y=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:${l};left:${u};width:${v};background:${h(p)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${y}>\n ${w}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}).join(""),l=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}${l}\n </div>`}).join("")}</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=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("");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){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),c=new Date(t.dataset.date);c.setHours(r,l,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:3,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._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._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=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 `}_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=v(o,"today"),d=v(o,"month"),c=v(o,"week"),u=v(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(_)}</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:_(a)?t(a.start):a.start,end:_(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(),i=0===this._opts.maxEventsPerCell?1/0:this._opts.maxEventsPerCell,o=e.filter(_),r=e.filter(t=>!_(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)),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 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)",v=n?"var(--cal-event-border-radius)":"0",_=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">${v}</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=>_(t)&&t.start<=m&&e(t.end)>=y).map(a=>({...a,start:t(a.start),end:e(a.end)})),f=this._events.filter(t=>!_(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)",v=i?"var(--cal-event-border-radius)":"0",_=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 ${i?`<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)`,v=`calc(${c}% - 2px)`,_=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:${v};background:${h(_)};"\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">${v(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=_(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,v=15*Math.round(h%60/15),_=new Date(s.start);_.setHours(u,v,0,0),_<=s.start&&_.setTime(s.start.getTime()+9e5);const g=(_-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(_,this._opts.locale,this._opts.use24Hour);p.textContent=`${t} - ${e}`}this._dragState.newEndTime=_}_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),v=7*Math.floor(h/u)+r;v>=0&&v<c.length&&(d=e(c[v]))}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),v=7*h+c;return c<0||c>=7||v<0||v>=u.length?null:{date:u[v],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 v=this._cachedEvents.findIndex(t=>String(t.id)===String(this._dragState.eventId));v>=0&&(this._cachedEvents[v].start=l,this._cachedEvents[v].end=d,this._cachedEvents[v].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)}}});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
2
|
+
* SimpleCalendarJs v3.0.5 — Angular Wrapper
|
|
3
3
|
* A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
|
|
4
4
|
*
|
|
5
5
|
* @author Pedro Lopes <simplecalendarjs@gmail.com>
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* this.calendar.navigate(1);
|
|
51
51
|
* this.calendar.goToDate(new Date());
|
|
52
52
|
* this.calendar.goToToday();
|
|
53
|
+
* this.calendar.refresh();
|
|
53
54
|
* }
|
|
54
55
|
*
|
|
55
56
|
* Example with theme switching:
|
|
@@ -110,6 +111,7 @@ const INIT_PROPS = [
|
|
|
110
111
|
'defaultDate',
|
|
111
112
|
'weekStartsOn',
|
|
112
113
|
'locale',
|
|
114
|
+
'weekdayFormat',
|
|
113
115
|
'use24Hour',
|
|
114
116
|
'showTimeInItems',
|
|
115
117
|
'showGridLines',
|
|
@@ -119,6 +121,10 @@ const INIT_PROPS = [
|
|
|
119
121
|
'showTitle',
|
|
120
122
|
'showYearPicker',
|
|
121
123
|
'showViewSwitcher',
|
|
124
|
+
'showTooltips',
|
|
125
|
+
'showBorder',
|
|
126
|
+
'maxEventsPerCell',
|
|
127
|
+
'listDaysForward',
|
|
122
128
|
'enabledViews',
|
|
123
129
|
];
|
|
124
130
|
|
|
@@ -142,6 +148,7 @@ export class SimpleCalendarJsComponent implements OnInit, OnDestroy, OnChanges,
|
|
|
142
148
|
@Input() defaultDate?: Date;
|
|
143
149
|
@Input() weekStartsOn: 0 | 1 = 0;
|
|
144
150
|
@Input() locale: string = 'default';
|
|
151
|
+
@Input() weekdayFormat: string = 'short';
|
|
145
152
|
@Input() use24Hour: boolean = false;
|
|
146
153
|
@Input() showTimeInItems: boolean = true;
|
|
147
154
|
@Input() showGridLines: boolean = true;
|
|
@@ -151,6 +158,10 @@ export class SimpleCalendarJsComponent implements OnInit, OnDestroy, OnChanges,
|
|
|
151
158
|
@Input() showTitle: boolean = true;
|
|
152
159
|
@Input() showYearPicker: boolean = true;
|
|
153
160
|
@Input() showViewSwitcher: boolean = true;
|
|
161
|
+
@Input() showTooltips: boolean = true;
|
|
162
|
+
@Input() showBorder: boolean = true;
|
|
163
|
+
@Input() maxEventsPerCell: number = 3;
|
|
164
|
+
@Input() listDaysForward: number = 30;
|
|
154
165
|
@Input() enabledViews: string[] = ['month', 'week', 'day'];
|
|
155
166
|
|
|
156
167
|
// Callback inputs
|
|
@@ -286,6 +297,10 @@ export class SimpleCalendarJsComponent implements OnInit, OnDestroy, OnChanges,
|
|
|
286
297
|
this.calendar?.goToToday();
|
|
287
298
|
}
|
|
288
299
|
|
|
300
|
+
public refresh(): void {
|
|
301
|
+
this.calendar?.refresh();
|
|
302
|
+
}
|
|
303
|
+
|
|
289
304
|
public getInstance(): any {
|
|
290
305
|
return this.calendar;
|
|
291
306
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
2
|
+
* SimpleCalendarJs v3.0.5 — React Wrapper
|
|
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,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Imperative handle (via ref):
|
|
11
11
|
* const ref = useRef();
|
|
12
|
-
* // ref.current exposes: { setView, navigate, goToDate, goToToday }
|
|
12
|
+
* // ref.current exposes: { setView, navigate, goToDate, goToToday, refresh }
|
|
13
13
|
*
|
|
14
14
|
* Example with theme switching:
|
|
15
15
|
* function MyApp() {
|
|
@@ -38,6 +38,7 @@ const INIT_PROPS = [
|
|
|
38
38
|
'defaultDate',
|
|
39
39
|
'weekStartsOn',
|
|
40
40
|
'locale',
|
|
41
|
+
'weekdayFormat',
|
|
41
42
|
'use24Hour',
|
|
42
43
|
'showTimeInItems',
|
|
43
44
|
'showGridLines',
|
|
@@ -47,6 +48,10 @@ const INIT_PROPS = [
|
|
|
47
48
|
'showTitle',
|
|
48
49
|
'showYearPicker',
|
|
49
50
|
'showViewSwitcher',
|
|
51
|
+
'showTooltips',
|
|
52
|
+
'showBorder',
|
|
53
|
+
'maxEventsPerCell',
|
|
54
|
+
'listDaysForward',
|
|
50
55
|
'enabledViews',
|
|
51
56
|
];
|
|
52
57
|
|
|
@@ -75,6 +80,7 @@ const SimpleCalendarJsReact = forwardRef(function SimpleCalendarJsReact(props, r
|
|
|
75
80
|
navigate: (d) => instanceRef.current?.navigate(d),
|
|
76
81
|
goToDate: (d) => instanceRef.current?.goToDate(d),
|
|
77
82
|
goToToday: () => instanceRef.current?.goToToday(),
|
|
83
|
+
refresh: () => instanceRef.current?.refresh(),
|
|
78
84
|
getInstance: () => instanceRef.current,
|
|
79
85
|
}),
|
|
80
86
|
[]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
2
|
+
* SimpleCalendarJs v3.0.5 — Vue 3 Wrapper
|
|
3
3
|
* A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
|
|
4
4
|
*
|
|
5
5
|
* @author Pedro Lopes <simplecalendarjs@gmail.com>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* calRef.value.navigate(1);
|
|
14
14
|
* calRef.value.goToDate(new Date());
|
|
15
15
|
* calRef.value.goToToday();
|
|
16
|
+
* calRef.value.refresh();
|
|
16
17
|
* calRef.value.getInstance(); // Get raw instance
|
|
17
18
|
*
|
|
18
19
|
* Example with theme switching:
|
|
@@ -47,6 +48,7 @@ const INIT_PROPS = [
|
|
|
47
48
|
'defaultDate',
|
|
48
49
|
'weekStartsOn',
|
|
49
50
|
'locale',
|
|
51
|
+
'weekdayFormat',
|
|
50
52
|
'use24Hour',
|
|
51
53
|
'showTimeInItems',
|
|
52
54
|
'showGridLines',
|
|
@@ -56,6 +58,10 @@ const INIT_PROPS = [
|
|
|
56
58
|
'showTitle',
|
|
57
59
|
'showYearPicker',
|
|
58
60
|
'showViewSwitcher',
|
|
61
|
+
'showTooltips',
|
|
62
|
+
'showBorder',
|
|
63
|
+
'maxEventsPerCell',
|
|
64
|
+
'listDaysForward',
|
|
59
65
|
'enabledViews',
|
|
60
66
|
];
|
|
61
67
|
|
|
@@ -71,6 +77,7 @@ export default defineComponent({
|
|
|
71
77
|
defaultDate: { type: Date, default: null },
|
|
72
78
|
weekStartsOn: { type: Number, default: 0 },
|
|
73
79
|
locale: { type: String, default: 'default' },
|
|
80
|
+
weekdayFormat: { type: String, default: 'short' },
|
|
74
81
|
use24Hour: { type: Boolean, default: false },
|
|
75
82
|
showTimeInItems: { type: Boolean, default: true },
|
|
76
83
|
showGridLines: { type: Boolean, default: true },
|
|
@@ -80,6 +87,10 @@ export default defineComponent({
|
|
|
80
87
|
showTitle: { type: Boolean, default: true },
|
|
81
88
|
showYearPicker: { type: Boolean, default: true },
|
|
82
89
|
showViewSwitcher: { type: Boolean, default: true },
|
|
90
|
+
showTooltips: { type: Boolean, default: true },
|
|
91
|
+
showBorder: { type: Boolean, default: true },
|
|
92
|
+
maxEventsPerCell: { type: Number, default: 3 },
|
|
93
|
+
listDaysForward: { type: Number, default: 30 },
|
|
83
94
|
enabledViews: { type: Array, default: () => ['month', 'week', 'day'] },
|
|
84
95
|
|
|
85
96
|
// Callback props
|
|
@@ -194,6 +205,7 @@ export default defineComponent({
|
|
|
194
205
|
navigate: (direction) => instanceRef.value?.navigate(direction),
|
|
195
206
|
goToDate: (date) => instanceRef.value?.goToDate(date),
|
|
196
207
|
goToToday: () => instanceRef.value?.goToToday(),
|
|
208
|
+
refresh: () => instanceRef.value?.refresh(),
|
|
197
209
|
getInstance: () => instanceRef.value,
|
|
198
210
|
});
|
|
199
211
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-calendar-js",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5",
|
|
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",
|