simple-calendar-js 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +310 -14
- package/dist/SimpleCalendarJs.min.css +2 -2
- package/dist/SimpleCalendarJs.min.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,12 @@ A pure JavaScript calendar component with no dependencies. Features responsive d
|
|
|
13
13
|
- **Smart Event Fetching** - Callback-based event loading with intelligent caching
|
|
14
14
|
- **Event Callbacks** - Day click, event click, and state change handlers
|
|
15
15
|
- **Grid Border Control** - Customizable grid borders (both, vertical, horizontal, none)
|
|
16
|
+
- **Internationalization** - Full localization support for weekdays, months, and UI labels
|
|
17
|
+
- **Month/Year Dropdowns** - Quick navigation with clickable month and year selectors
|
|
18
|
+
- **Color Customization** - Comprehensive theming system with light/dark mode support
|
|
19
|
+
- **Theme Integration** - Built-in dark theme with automatic color adaptation
|
|
20
|
+
- **Enhanced Navigation** - Transparent navigation buttons with theme-aware hover effects
|
|
21
|
+
- **Always-Visible Time Lines** - Hour and half-hour lines remain visible regardless of grid settings
|
|
16
22
|
- **CSS Prefix** - All classes prefixed with 'sc-' to avoid conflicts
|
|
17
23
|
- **Template Strings** - Clean HTML generation using template literals
|
|
18
24
|
- **Event Support** - Add, remove, and display events
|
|
@@ -68,6 +74,54 @@ const calendar = new SimpleCalendarJs('#calendar', {
|
|
|
68
74
|
showDayButton: true, // true/false - show day view button
|
|
69
75
|
showNavigation: true, // true/false - show navigation buttons (prev/next)
|
|
70
76
|
|
|
77
|
+
// Localization Options
|
|
78
|
+
weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
|
79
|
+
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
|
80
|
+
showWeekdayChars: null, // null = full weekday names, number = show first N characters
|
|
81
|
+
labels: { // UI labels for buttons and text
|
|
82
|
+
month: 'Month',
|
|
83
|
+
week: 'Week',
|
|
84
|
+
day: 'Day',
|
|
85
|
+
events: 'events', // Badge text (plural)
|
|
86
|
+
event: 'event', // Badge text (singular)
|
|
87
|
+
before: 'Before', // Time slot label for early times
|
|
88
|
+
after: 'After' // Time slot label for late times
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// Color Customization
|
|
92
|
+
colors: {
|
|
93
|
+
// Light theme colors (defaults)
|
|
94
|
+
background: '#ffffff',
|
|
95
|
+
backgroundSecondary: '#f8f9fa',
|
|
96
|
+
backgroundTertiary: '#ecf0f1',
|
|
97
|
+
text: '#212529',
|
|
98
|
+
textSecondary: '#6c757d',
|
|
99
|
+
textMuted: '#adb5bd',
|
|
100
|
+
border: '#e9ecef',
|
|
101
|
+
borderLight: '#dee2e6',
|
|
102
|
+
accent: '#4c6f94',
|
|
103
|
+
accentHover: '#0056b3',
|
|
104
|
+
todayBg: '#e3f2fd',
|
|
105
|
+
todayText: '#dc3545',
|
|
106
|
+
hoverBg: '#f8f9fa',
|
|
107
|
+
// Dark theme colors (auto-applied when data-theme="dark")
|
|
108
|
+
dark: {
|
|
109
|
+
background: '#2d2d2d',
|
|
110
|
+
backgroundSecondary: '#3a3a3a',
|
|
111
|
+
backgroundTertiary: '#4a4a4a',
|
|
112
|
+
text: '#ffffff',
|
|
113
|
+
textSecondary: '#cccccc',
|
|
114
|
+
textMuted: '#888888',
|
|
115
|
+
border: '#444444',
|
|
116
|
+
borderLight: '#555555',
|
|
117
|
+
accent: '#4a90e2',
|
|
118
|
+
accentHover: '#357abd',
|
|
119
|
+
todayBg: '#1a2f4a',
|
|
120
|
+
todayText: '#ff6b6b',
|
|
121
|
+
hoverBg: '#2d2d2d'
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
|
|
71
125
|
// Data Configuration
|
|
72
126
|
events: [], // Array of event objects OR callback function
|
|
73
127
|
|
|
@@ -78,6 +132,163 @@ const calendar = new SimpleCalendarJs('#calendar', {
|
|
|
78
132
|
});
|
|
79
133
|
```
|
|
80
134
|
|
|
135
|
+
## Localization
|
|
136
|
+
|
|
137
|
+
SimpleCalendarJs supports full internationalization with customizable text and date formats:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
141
|
+
// Portuguese example
|
|
142
|
+
weekdays: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'],
|
|
143
|
+
months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
|
|
144
|
+
showWeekdayChars: 3, // Show only first 3 characters: "Dom", "Seg", etc.
|
|
145
|
+
labels: {
|
|
146
|
+
month: 'Mês',
|
|
147
|
+
week: 'Semana',
|
|
148
|
+
day: 'Dia',
|
|
149
|
+
events: 'eventos',
|
|
150
|
+
event: 'evento',
|
|
151
|
+
before: 'Antes das',
|
|
152
|
+
after: 'Depois das'
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// French example
|
|
157
|
+
const frenchCalendar = new SimpleCalendarJs('#calendar', {
|
|
158
|
+
weekdays: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
|
|
159
|
+
months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
|
|
160
|
+
showWeekdayChars: 2, // Show first 2 characters: "Di", "Lu", etc.
|
|
161
|
+
labels: {
|
|
162
|
+
month: 'Mois',
|
|
163
|
+
week: 'Semaine',
|
|
164
|
+
day: 'Jour',
|
|
165
|
+
events: 'événements',
|
|
166
|
+
event: 'événement',
|
|
167
|
+
before: 'Avant',
|
|
168
|
+
after: 'Après'
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Weekday Display Options
|
|
174
|
+
- **`showWeekdayChars: null`** - Show full weekday names (default)
|
|
175
|
+
- **`showWeekdayChars: 3`** - Show first 3 characters ("Sun", "Mon", etc.)
|
|
176
|
+
- **`showWeekdayChars: 1`** - Show single character ("S", "M", etc.)
|
|
177
|
+
|
|
178
|
+
## Month/Year Navigation
|
|
179
|
+
|
|
180
|
+
In month view, clicking on the month or year title opens dropdown menus for quick navigation:
|
|
181
|
+
|
|
182
|
+
### Features
|
|
183
|
+
- **Month Dropdown** - Click month title to select any month
|
|
184
|
+
- **Year Dropdown** - Click year title to select from a range of years
|
|
185
|
+
- **Keyboard Support** - Navigate with arrow keys and Enter
|
|
186
|
+
- **Auto-scroll** - Current month/year automatically scrolls into view
|
|
187
|
+
- **Compact Design** - Minimal dropdown styling that adapts to themes
|
|
188
|
+
|
|
189
|
+
### Styling
|
|
190
|
+
The dropdowns automatically inherit the calendar's theme and can be customized via CSS:
|
|
191
|
+
|
|
192
|
+
```css
|
|
193
|
+
.sc-month-dropdown, .sc-year-dropdown {
|
|
194
|
+
/* Customize dropdown appearance */
|
|
195
|
+
max-height: 200px;
|
|
196
|
+
background: var(--sc-bg-secondary);
|
|
197
|
+
border: 1px solid var(--sc-border-color);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.sc-month-option:hover, .sc-year-option:hover {
|
|
201
|
+
background: var(--sc-hover-bg);
|
|
202
|
+
color: var(--sc-accent-color);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Color Customization
|
|
207
|
+
|
|
208
|
+
SimpleCalendarJs provides a comprehensive color system that supports partial customization and automatic theme switching:
|
|
209
|
+
|
|
210
|
+
### Basic Color Override
|
|
211
|
+
```javascript
|
|
212
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
213
|
+
colors: {
|
|
214
|
+
background: '#f0f8ff', // Only override background
|
|
215
|
+
accent: '#ff6b6b' // Only override accent color
|
|
216
|
+
// All other colors use defaults
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Full Theme Customization
|
|
222
|
+
```javascript
|
|
223
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
224
|
+
colors: {
|
|
225
|
+
// Light theme colors
|
|
226
|
+
background: '#ffffff',
|
|
227
|
+
backgroundSecondary: '#f8f9fa',
|
|
228
|
+
backgroundTertiary: '#ecf0f1',
|
|
229
|
+
text: '#212529',
|
|
230
|
+
textSecondary: '#6c757d',
|
|
231
|
+
textMuted: '#adb5bd',
|
|
232
|
+
border: '#e9ecef',
|
|
233
|
+
borderLight: '#dee2e6',
|
|
234
|
+
accent: '#4c6f94',
|
|
235
|
+
accentHover: '#0056b3',
|
|
236
|
+
todayBg: '#e3f2fd',
|
|
237
|
+
todayText: '#dc3545',
|
|
238
|
+
hoverBg: '#f8f9fa',
|
|
239
|
+
|
|
240
|
+
// Dark theme colors (optional)
|
|
241
|
+
dark: {
|
|
242
|
+
background: '#1a1a1a',
|
|
243
|
+
backgroundSecondary: '#2d2d2d',
|
|
244
|
+
backgroundTertiary: '#3a3a3a',
|
|
245
|
+
text: '#ffffff',
|
|
246
|
+
textSecondary: '#cccccc',
|
|
247
|
+
textMuted: '#888888',
|
|
248
|
+
border: '#444444',
|
|
249
|
+
borderLight: '#555555',
|
|
250
|
+
accent: '#4a90e2',
|
|
251
|
+
accentHover: '#357abd',
|
|
252
|
+
todayBg: '#1a2f4a',
|
|
253
|
+
todayText: '#ff6b6b',
|
|
254
|
+
hoverBg: '#2d2d2d'
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Transparent Values
|
|
261
|
+
Any color property can be set to `'transparent'` for seamless integration:
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
colors: {
|
|
265
|
+
background: 'transparent', // Calendar blends with page background
|
|
266
|
+
border: 'transparent', // Remove all borders
|
|
267
|
+
hoverBg: 'transparent' // Disable hover backgrounds
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Color Properties Reference
|
|
272
|
+
|
|
273
|
+
| Property | Description | Default (Light) | Default (Dark) |
|
|
274
|
+
|----------|-------------|-----------------|----------------|
|
|
275
|
+
| `background` | Main calendar background | `#ffffff` | `#2d2d2d` |
|
|
276
|
+
| `backgroundSecondary` | Secondary backgrounds | `#f8f9fa` | `#3a3a3a` |
|
|
277
|
+
| `backgroundTertiary` | Controls and headers | `#ecf0f1` | `#4a4a4a` |
|
|
278
|
+
| `text` | Primary text color | `#212529` | `#ffffff` |
|
|
279
|
+
| `textSecondary` | Secondary text | `#6c757d` | `#cccccc` |
|
|
280
|
+
| `textMuted` | Muted/disabled text | `#adb5bd` | `#888888` |
|
|
281
|
+
| `border` | Grid and element borders | `#e9ecef` | `#444444` |
|
|
282
|
+
| `borderLight` | Light accent borders | `#dee2e6` | `#555555` |
|
|
283
|
+
| `accent` | Active states and highlights | `#4c6f94` | `#4a90e2` |
|
|
284
|
+
| `accentHover` | Hover states | `#0056b3` | `#357abd` |
|
|
285
|
+
| `todayBg` | Today's date background | `#e3f2fd` | `#1a2f4a` |
|
|
286
|
+
| `todayText` | Today's date text | `#dc3545` | `#ff6b6b` |
|
|
287
|
+
| `hoverBg` | Hover backgrounds | `#f8f9fa` | `#2d2d2d` |
|
|
288
|
+
|
|
289
|
+
### Theme Integration
|
|
290
|
+
The calendar automatically detects `data-theme="dark"` on parent elements and applies dark colors accordingly. Perfect for integration with existing website theme systems.
|
|
291
|
+
|
|
81
292
|
## Event Format
|
|
82
293
|
|
|
83
294
|
```javascript
|
|
@@ -250,9 +461,17 @@ All CSS classes are prefixed with `sc-` to prevent conflicts:
|
|
|
250
461
|
### Headers & Navigation
|
|
251
462
|
- `.sc-nav` - Navigation buttons container
|
|
252
463
|
- `.sc-title` - Month/date title
|
|
464
|
+
- `.sc-title-month` - Clickable month title (in month view)
|
|
465
|
+
- `.sc-title-year` - Clickable year title (in month view)
|
|
253
466
|
- `.sc-view-switcher` - View mode buttons
|
|
254
467
|
- `.sc-btn` - Button styling
|
|
255
468
|
|
|
469
|
+
### Dropdown Navigation
|
|
470
|
+
- `.sc-month-dropdown` - Month selection dropdown
|
|
471
|
+
- `.sc-year-dropdown` - Year selection dropdown
|
|
472
|
+
- `.sc-month-option` - Individual month option
|
|
473
|
+
- `.sc-year-option` - Individual year option
|
|
474
|
+
|
|
256
475
|
### Border Modifiers
|
|
257
476
|
- `.sc-borders-both` - Show all grid borders
|
|
258
477
|
- `.sc-borders-vertical` - Vertical grid borders only
|
|
@@ -263,8 +482,9 @@ All CSS classes are prefixed with `sc-` to prevent conflicts:
|
|
|
263
482
|
|
|
264
483
|
## CSS Custom Properties (Variables)
|
|
265
484
|
|
|
266
|
-
The calendar uses CSS custom properties for
|
|
485
|
+
The calendar uses CSS custom properties for comprehensive theming. These are automatically managed by the color customization system, but can also be overridden directly:
|
|
267
486
|
|
|
487
|
+
### Event Colors
|
|
268
488
|
```css
|
|
269
489
|
.sc-calendar {
|
|
270
490
|
--sc-event-color: #4c6f94; /* Default event background color */
|
|
@@ -272,33 +492,109 @@ The calendar uses CSS custom properties for easy theming:
|
|
|
272
492
|
}
|
|
273
493
|
```
|
|
274
494
|
|
|
275
|
-
|
|
495
|
+
### Theme Colors (Auto-managed)
|
|
496
|
+
```css
|
|
497
|
+
.sc-calendar {
|
|
498
|
+
/* Main backgrounds */
|
|
499
|
+
--sc-bg-primary: #ffffff;
|
|
500
|
+
--sc-bg-secondary: #f8f9fa;
|
|
501
|
+
--sc-bg-tertiary: #ecf0f1;
|
|
502
|
+
|
|
503
|
+
/* Text colors */
|
|
504
|
+
--sc-text-primary: #212529;
|
|
505
|
+
--sc-text-secondary: #6c757d;
|
|
506
|
+
--sc-text-muted: #adb5bd;
|
|
507
|
+
|
|
508
|
+
/* Borders */
|
|
509
|
+
--sc-border-color: #e9ecef;
|
|
510
|
+
--sc-border-light: #dee2e6;
|
|
511
|
+
|
|
512
|
+
/* Interactive elements */
|
|
513
|
+
--sc-accent-color: #4c6f94;
|
|
514
|
+
--sc-accent-hover: #0056b3;
|
|
515
|
+
--sc-today-bg: #e3f2fd;
|
|
516
|
+
--sc-today-text: #dc3545;
|
|
517
|
+
--sc-hover-bg: #f8f9fa;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/* Dark theme variables (auto-applied with data-theme="dark") */
|
|
521
|
+
[data-theme="dark"] .sc-calendar {
|
|
522
|
+
--sc-bg-primary: #2d2d2d;
|
|
523
|
+
--sc-bg-secondary: #3a3a3a;
|
|
524
|
+
--sc-bg-tertiary: #4a4a4a;
|
|
525
|
+
--sc-text-primary: #ffffff;
|
|
526
|
+
--sc-text-secondary: #cccccc;
|
|
527
|
+
--sc-text-muted: #888888;
|
|
528
|
+
--sc-border-color: #444444;
|
|
529
|
+
--sc-border-light: #555555;
|
|
530
|
+
--sc-accent-color: #4a90e2;
|
|
531
|
+
--sc-accent-hover: #357abd;
|
|
532
|
+
--sc-today-bg: #1a2f4a;
|
|
533
|
+
--sc-today-text: #ff6b6b;
|
|
534
|
+
--sc-hover-bg: #2d2d2d;
|
|
535
|
+
}
|
|
536
|
+
```
|
|
276
537
|
|
|
538
|
+
### Manual Override Example
|
|
277
539
|
```css
|
|
278
|
-
/* Custom theme
|
|
540
|
+
/* Custom theme using CSS variables */
|
|
279
541
|
.sc-calendar {
|
|
280
|
-
--sc-
|
|
281
|
-
--sc-
|
|
542
|
+
--sc-bg-primary: #f0f8ff;
|
|
543
|
+
--sc-accent-color: #ff6b6b;
|
|
544
|
+
--sc-today-bg: #ffe4e1;
|
|
282
545
|
}
|
|
283
546
|
|
|
284
|
-
/* Dark theme
|
|
285
|
-
.sc-calendar
|
|
286
|
-
--sc-
|
|
287
|
-
|
|
288
|
-
color: #ecf0f1;
|
|
547
|
+
/* Dark theme integration */
|
|
548
|
+
[data-theme="dark"] .sc-calendar {
|
|
549
|
+
--sc-bg-primary: #1e1e1e;
|
|
550
|
+
--sc-accent-color: #ff8a80;
|
|
289
551
|
}
|
|
290
552
|
```
|
|
291
553
|
|
|
292
554
|
## Event Count Badges
|
|
293
555
|
|
|
294
|
-
In month view with `fulldayMode: false`, event counts are displayed as styled badges:
|
|
556
|
+
In month view with `fulldayMode: false`, event counts are displayed as styled badges with theme-aware coloring:
|
|
295
557
|
|
|
296
|
-
- **Regular days**:
|
|
297
|
-
- **Today**:
|
|
298
|
-
- **Other month days**:
|
|
558
|
+
- **Regular days**: Uses default event color with reduced opacity for background
|
|
559
|
+
- **Today**: Full default event color background with white text
|
|
560
|
+
- **Other month days**: Muted background with secondary text color
|
|
561
|
+
- **Theme adaptive**: Automatically adjusts colors for light/dark themes
|
|
299
562
|
- **Responsive**: Automatically adjusts size on mobile devices
|
|
300
563
|
- **Rounded corners**: Modern badge appearance with `border-radius: 10px`
|
|
301
564
|
|
|
565
|
+
### Badge Color Logic
|
|
566
|
+
- Background uses `color-mix()` function to blend event color with theme background
|
|
567
|
+
- Text color adapts to theme (darker in light mode, lighter in dark mode)
|
|
568
|
+
- Today's badge uses full event color saturation for emphasis
|
|
569
|
+
- Seamlessly integrates with custom color schemes
|
|
570
|
+
|
|
571
|
+
## Enhanced Styling Features
|
|
572
|
+
|
|
573
|
+
### Navigation Button Styling
|
|
574
|
+
- **Transparent Background**: Navigation arrows have transparent backgrounds in both themes
|
|
575
|
+
- **Theme-Aware Hover**: Hover effects use the default event color for consistency
|
|
576
|
+
- **Proper Contrast**: Arrow colors automatically adjust for light/dark themes
|
|
577
|
+
|
|
578
|
+
### View Button Styling
|
|
579
|
+
- **Active State**: Uses default event color when view button is active
|
|
580
|
+
- **Inactive State**: Transparent background with theme-appropriate text color
|
|
581
|
+
- **Consistent Theming**: Seamlessly adapts to color customization
|
|
582
|
+
|
|
583
|
+
### Time Lines Enhancement
|
|
584
|
+
- **Always Visible**: Hour and half-hour lines remain visible regardless of grid border settings
|
|
585
|
+
- **Proper Hierarchy**: Uses `!important` declarations to ensure visibility
|
|
586
|
+
- **Theme Integration**: Line colors adapt to current theme automatically
|
|
587
|
+
|
|
588
|
+
### Today Highlighting
|
|
589
|
+
- **Enhanced Contrast**: Today's background is darker in dark mode for better visibility
|
|
590
|
+
- **Cross-View Consistency**: Today highlighting works consistently across month, week, and day views
|
|
591
|
+
- **Customizable Colors**: Today colors can be overridden via color customization system
|
|
592
|
+
|
|
593
|
+
### Page Integration
|
|
594
|
+
- **Scrollable Layout**: Calendar no longer uses fixed positioning, allowing page scrolling
|
|
595
|
+
- **Flexible Container**: Adapts to parent container constraints
|
|
596
|
+
- **Better Integration**: Easier to embed in existing page layouts
|
|
597
|
+
|
|
302
598
|
## Keyboard Shortcuts
|
|
303
599
|
|
|
304
600
|
- **Arrow Left/Right** - Navigate to previous/next period
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v2.0.
|
|
2
|
+
* SimpleCalendarJs v2.0.2
|
|
3
3
|
* A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
|
|
4
4
|
*
|
|
5
5
|
* @author Pedro Lopes <simplecalendarjs@gmail.com>
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
* @license SEE LICENSE IN LICENSE
|
|
8
8
|
* @repository https://github.com/pclslopes/SimpleCalendarJs
|
|
9
9
|
*/
|
|
10
|
-
.sc-calendar{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1);overflow:hidden;width:100%;max-width:100%;display:flex;flex-direction:column}.sc-header{background:#f8f9fa;border-bottom:1px solid #e9ecef;padding:16px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px}.sc-nav{display:flex;align-items:center;gap:8px;justify-self:start}.sc-title{margin:0;font-size:1.25rem;font-weight:600;color:#212529;white-space:nowrap;text-align:center;justify-self:center}.sc-btn{background:#fff;border:1px solid #dee2e6;border-radius:4px;padding:8px 12px;cursor:pointer;font-size:.875rem;font-weight:500;transition:all .2s ease;min-width:36px;display:flex;align-items:center;justify-content:center}.sc-nav .sc-btn{background:0 0;border:none;font-size:1.2rem;padding:8px;min-width:32px;color:#212529;font-weight:700}.sc-btn:hover{background:#e9ecef;border-color:#adb5bd}.sc-nav .sc-btn:hover{background:rgba(108,117,125,.1);color:#495057}.sc-btn:active{background:#dee2e6}.sc-nav .sc-btn:active{background:rgba(108,117,125,.2)}.sc-view-switcher{display:flex;gap:4px;justify-self:end}.sc-view-btn.sc-active{background:#4c6f94;color:#fff;border-color:#4c6f94}.sc-view-btn.sc-active:hover{background:#0056b3;border-color:#0056b3}.sc-content{flex:1;min-height:520px;display:flex;flex-direction:column}.sc-view-container{flex:1;overflow:hidden}.sc-month-view{height:100%;display:flex;flex-direction:column}.sc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);background:#f8f9fa;border-bottom:1px solid #e9ecef}.sc-weekday{padding:12px 8px;text-align:center;font-weight:600;font-size:.875rem;color:#6c757d;border-right:1px solid #e9ecef}.sc-weekday:last-child{border-right:none}.sc-days-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:repeat(6,minmax(80px,1fr));flex:1;overflow:visible}.sc-month-view.sc-fullday .sc-days-grid{grid-template-rows:repeat(6,auto);min-height:auto}.sc-month-view.sc-fullday .sc-day{display:flex;flex-direction:column;min-height:60px;position:relative}.sc-day{display:flex;flex-direction:column;min-height:80px;cursor:pointer;transition:background-color .2s ease;overflow:visible}.sc-day:hover{background:#f8f9fa}.sc-day-number{text-align:center;font-weight:500;margin-bottom:4px;font-size:.875rem;line-height:1.2}.sc-day.sc-other-month .sc-day-number{color:#adb5bd}.sc-day.sc-today{background:#e3f2fd}.sc-day.sc-today .sc-day-number{color:#dc3545;font-weight:600}.sc-day-count-mode{display:flex;align-items:center;justify-content:center}.sc-day-content{text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center}.sc-day-count-mode .sc-day-number{font-size:1.125rem;font-weight:600;margin-bottom:4px}.sc-event-count{font-size:.7rem;font-weight:600;line-height:1.2;background:#e9ecef;color:#495057;padding:2px 6px;border-radius:10px;display:inline-block;margin-top:2px;min-width:16px;text-align:center;box-sizing:border-box}.sc-day-count-mode.sc-today .sc-event-count{background:#007bff;color:#fff}.sc-day-count-mode.sc-other-month .sc-event-count{background:#f8f9fa;color:#adb5bd}.sc-day-events{flex:1;display:flex;flex-direction:column;gap:2px;overflow:hidden;padding:4px}.sc-month-view.sc-fullday .sc-day-number{flex-shrink:0;margin-bottom:4px;padding:4px 6px 0 6px}.sc-event-day,.sc-event-month,.sc-event-time,.sc-event-week{background:var(--sc-event-color,#4c6f94);color:#fff;border-radius:3px;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;box-sizing:border-box}.sc-month-view .sc-event-month{padding:2px 6px;font-size:.75rem;height:18px;margin:1px 4px;min-width:0;display:block}.sc-month-view.sc-fullday .sc-event-month{position:relative;padding:2px 6px;border-radius:3px;min-height:18px;display:block;box-sizing:border-box;overflow:visible}.sc-month-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}.sc-month-view .sc-event-single{border-radius:3px}.sc-month-view .sc-event-start{border-radius:3px 0 0 3px}.sc-month-view .sc-event-middle{border-radius:0}.sc-month-view .sc-event-end{border-radius:0 3px 3px 0}.sc-month-view .sc-event-month.sc-event-start{border-radius:3px 0 0 3px}.sc-month-view .sc-event-month.sc-event-middle{border-radius:0}.sc-month-view .sc-event-month.sc-event-end{border-radius:0 3px 3px 0}.sc-month-view.sc-fullday .sc-event-month.sc-event-start{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-start::after{content:'';position:absolute;right:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::after,.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::before{content:'';position:absolute;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::before{left:-2px}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::after{right:-2px}.sc-month-view.sc-fullday .sc-event-month.sc-event-end{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-end::before{content:'';position:absolute;left:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-single{margin:1px 4px!important;border-radius:3px!important;z-index:1;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-start{margin:1px 0 1px 4px!important;border-radius:3px 0 0 3px!important;z-index:2;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle{margin:1px 0!important;border-radius:0!important;z-index:2;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-end{margin:1px 4px 1px 0!important;border-radius:0 3px 3px 0!important;z-index:2;position:relative}.sc-event-placeholder{opacity:0!important}.sc-month-view.sc-fullday .sc-event-placeholder{min-height:18px;padding:2px 6px;margin:1px 4px;display:block;box-sizing:border-box}.sc-week-view.sc-fullday .sc-event-placeholder{min-height:18px;padding:2px 6px;margin:1px 4px;display:block;box-sizing:border-box;line-height:.9em}.sc-week-view{height:100%;overflow-y:auto;overflow-x:hidden;max-height:calc(100vh - 200px);display:grid;grid-template-columns:80px repeat(7,1fr);align-content:start}.sc-day-header,.sc-time-column-header{position:sticky;top:0;z-index:20;display:flex;flex-direction:column;justify-content:center;text-align:center;padding:12px 8px;background:#f8f9fa;height:60px;box-sizing:border-box}.sc-day-header.sc-today{background:#e3f2fd}.sc-day-name{font-size:.75rem;color:#6c757d;font-weight:500;margin-bottom:2px}.sc-day-header .sc-day-number{font-size:1.25rem;font-weight:600;color:#212529}.sc-day-header.sc-today .sc-day-number{color:#dc3545;font-weight:600}.sc-day-column,.sc-time-slot{height:60px;position:relative;box-sizing:border-box}.sc-time-slot{background:#f8f9fa;padding:4px 8px 8px 8px;font-size:.75rem;color:#6c757d;text-align:center;display:flex;align-items:flex-start;justify-content:center}.sc-time-slot:first-child,.sc-time-slot:last-child{background:#e9ecef;color:#495057;font-weight:500;font-style:italic}.sc-day-column{cursor:pointer;background:#fff}.sc-day-column.sc-today{background:#f8fcff}.sc-day-column:hover{background:#f8f9fa}.sc-half-hour-line{position:absolute;top:50%;left:0;right:0;height:0;pointer-events:none;z-index:1}.sc-week-view .sc-event-time{position:absolute;top:4px;left:4px;right:4px;padding:2px 6px;font-size:.75rem;line-height:1.2;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:5;min-height:18px;display:block;box-sizing:border-box;padding-top:3px;padding-bottom:1px}.sc-week-view .sc-event-single{border-radius:3px}.sc-week-view .sc-event-start{border-radius:3px 0 0 3px}.sc-week-view .sc-event-middle{border-radius:0}.sc-week-view .sc-event-end{border-radius:0 3px 3px 0}.sc-week-view.sc-fullday .sc-event{width:100%;margin:2px 4px;padding:2px 6px;font-size:.75rem;height:18px;min-width:0}.sc-week-view.sc-fullday .sc-event-start{margin-right:0}.sc-week-view.sc-fullday .sc-event-middle{margin-left:0;margin-right:0}.sc-week-view.sc-fullday .sc-event-end{margin-left:0}.sc-week-view.sc-fullday{height:100%;display:flex;flex-direction:column}.sc-week-view.sc-fullday .sc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);background:#f8f9fa;border-bottom:1px solid #e9ecef}.sc-week-view.sc-fullday .sc-weekday{padding:12px 8px;text-align:center;font-weight:600;font-size:.875rem;color:#6c757d;border-right:1px solid #e9ecef}.sc-week-view.sc-fullday .sc-weekday:last-child{border-right:none}.sc-week-view.sc-fullday .sc-days-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:auto;flex:1;min-height:auto;overflow:visible}.sc-week-view.sc-fullday .sc-day{display:flex;flex-direction:column;min-height:120px;position:relative;cursor:pointer;transition:background-color .2s ease;overflow:visible}.sc-week-view.sc-fullday .sc-day:hover{background:#f8f9fa}.sc-week-view.sc-fullday .sc-day-number{text-align:center;font-weight:500;margin-bottom:4px;font-size:.875rem;line-height:1.2;flex-shrink:0;padding:4px 6px 0 6px}.sc-week-view.sc-fullday .sc-day.sc-today{background:#e3f2fd}.sc-week-view.sc-fullday .sc-day.sc-today .sc-day-number{color:#dc3545;font-weight:600}.sc-week-view.sc-fullday .sc-event-week{position:relative;border-radius:3px;min-height:18px;padding:2px 6px;display:block;box-sizing:border-box;overflow:visible}.sc-week-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}.sc-week-view.sc-fullday .sc-event-week.sc-event-single{margin:1px 4px!important;border-radius:3px!important;z-index:1;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-start{margin:1px 0 1px 4px!important;border-radius:3px 0 0 3px!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-start::after{content:'';position:absolute;right:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle{margin:1px 0!important;border-radius:0!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::after,.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::before{content:'';position:absolute;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::before{left:-2px}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::after{right:-2px}.sc-week-view.sc-fullday .sc-event-week.sc-event-end{margin:1px 4px 1px 0!important;border-radius:0 3px 3px 0!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-end::before{content:'';position:absolute;left:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-day-view{height:100%;overflow-y:auto;overflow-x:hidden;max-height:calc(100vh - 200px);display:grid;grid-template-columns:80px 1fr;align-content:start}.sc-day-view.sc-fullday{display:flex;flex-direction:column;padding:0;grid-template-columns:none}.sc-day-view.sc-fullday .sc-day-header{position:sticky;top:0;z-index:10;background:#f8f9fa;border-bottom:1px solid #e9ecef;padding:16px;text-align:center;flex-shrink:0}.sc-day-view.sc-fullday .sc-day-name{font-size:.875rem;color:#6c757d;font-weight:500;margin-bottom:4px}.sc-day-view.sc-fullday .sc-day-number{font-size:1.5rem;font-weight:600;color:#212529}.sc-day-view.sc-fullday .sc-day-header.sc-today .sc-day-number{color:#dc3545}.sc-day-view.sc-fullday .sc-day-events{flex:1;padding:16px;display:flex;flex-direction:column;gap:8px;overflow-y:auto;cursor:pointer}.sc-day-view .sc-event-time{position:absolute;top:4px;left:4px;right:4px;padding:2px 6px;font-size:.75rem;line-height:1.2;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:5;min-height:18px;display:block;box-sizing:border-box;padding-top:3px;padding-bottom:1px}.sc-day-view .sc-event{width:100%;margin:2px 4px;padding:2px 6px;font-size:.75rem;height:18px;min-width:0}.sc-day-view .sc-event-single{border-radius:3px}.sc-day-view .sc-event-start{border-radius:3px 0 0 3px}.sc-day-view .sc-event-middle{border-radius:0}.sc-day-view .sc-event-end{border-radius:0 3px 3px 0}.sc-day-view.sc-fullday .sc-event-start{margin-right:0}.sc-day-view.sc-fullday .sc-event-middle{margin-left:0;margin-right:0}.sc-day-view.sc-fullday .sc-event-end{margin-left:0}.sc-day-view.sc-fullday .sc-event-day{position:relative;padding:2px 6px;border-radius:3px;margin:1px 4px;min-height:18px;box-sizing:border-box;display:block}.sc-day-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}@media (max-width:768px){.sc-header{grid-template-columns:1fr;grid-template-rows:auto auto auto;gap:8px;justify-items:center}.sc-view-switcher{grid-row:1;justify-self:center}.sc-title{grid-row:2;font-size:1.125rem;justify-self:center}.sc-nav{grid-row:3;justify-self:center}.sc-days-grid{min-height:360px;grid-template-rows:repeat(6,minmax(60px,1fr))}.sc-day-number{font-size:.75rem}.sc-day-count-mode .sc-day-number{font-size:1rem}.sc-event-count{font-size:.625rem;padding:1px 4px;min-width:14px}.sc-event-month{font-size:.6rem;padding:1px 3px;height:14px}.sc-day-view .sc-event-time,.sc-week-view .sc-event-time{top:3px;left:3px;right:3px;padding:1px 4px;padding-top:2px;padding-bottom:1px;font-size:.65rem;min-height:16px}.sc-weekday{padding:8px 4px;font-size:.75rem}.sc-week-header{min-height:50px}.sc-week-view{grid-template-columns:60px repeat(7,1fr)}.sc-week-view.sc-fullday{grid-template-columns:repeat(7,1fr)}.sc-day-header{padding:8px 4px}.sc-day-name{font-size:.625rem}.sc-day-header .sc-day-number{font-size:1rem}.sc-day-header.sc-today .sc-day-number{color:#dc3545;font-weight:600}.sc-time-slot{height:40px;padding:4px;font-size:.625rem}.sc-hour-slot{height:40px}.sc-day-view{grid-template-columns:60px 1fr;max-height:calc(100vh - 160px)}.sc-week-view{max-height:calc(100vh - 160px)}}@media (max-width:480px){.sc-calendar{border-radius:0;box-shadow:none;border:1px solid #e9ecef}.sc-header{padding:12px}.sc-btn{padding:6px 8px;font-size:.75rem;min-width:30px}.sc-title{font-size:1rem}.sc-day{min-height:50px;padding:2px}.sc-days-grid{min-height:300px;grid-template-rows:repeat(6,minmax(50px,1fr))}.sc-day-count-mode .sc-day-number{font-size:.875rem}.sc-event-count{font-size:.5rem;padding:1px 3px;min-width:12px}.sc-weekday{padding:6px 2px;font-size:.625rem}.sc-week-header{min-height:40px}.sc-week-view{grid-template-columns:50px repeat(7,1fr);max-height:calc(100vh - 140px)}.sc-week-view.sc-fullday{grid-template-columns:repeat(7,1fr)}.sc-time-slot{height:30px;padding:2px;font-size:.5rem}.sc-hour-slot{height:30px}.sc-day-view{grid-template-columns:50px 1fr;max-height:calc(100vh - 140px)}.sc-day-view .sc-event-time,.sc-week-view .sc-event-time{top:2px;left:2px;right:2px;padding:1px 3px;padding-top:2px;padding-bottom:0;font-size:.5rem;min-height:14px}}.sc-borders-both .sc-day{border-right:1px solid #e9ecef;border-bottom:1px solid #e9ecef}.sc-borders-vertical .sc-day{border-right:1px solid #e9ecef;border-bottom:none}.sc-borders-horizontal .sc-day{border-right:none;border-bottom:1px solid #e9ecef}.sc-borders-none .sc-day{border-right:none;border-bottom:none}.sc-borders-both .sc-day-column,.sc-borders-both .sc-time-slot{border-right:1px solid #e9ecef;border-bottom:1px solid #e9ecef}.sc-borders-vertical .sc-day-column,.sc-borders-vertical .sc-time-slot{border-right:1px solid #e9ecef;border-bottom:none}.sc-borders-horizontal .sc-day-column,.sc-borders-horizontal .sc-time-slot{border-right:none;border-bottom:1px solid #e9ecef}.sc-borders-none .sc-day-column,.sc-borders-none .sc-time-slot{border-right:none;border-bottom:none}.sc-borders-both .sc-week-view.sc-fullday .sc-day{border-right:1px solid #e9ecef;border-bottom:1px solid #e9ecef}.sc-borders-vertical .sc-week-view.sc-fullday .sc-day{border-right:1px solid #e9ecef;border-bottom:none}.sc-borders-horizontal .sc-week-view.sc-fullday .sc-day{border-right:none;border-bottom:1px solid #e9ecef}.sc-borders-none .sc-week-view.sc-fullday .sc-day{border-right:none;border-bottom:none}.sc-borders-both .sc-day-header,.sc-borders-both .sc-time-column-header{border-right:1px solid #e9ecef;border-bottom:1px solid #e9ecef}.sc-borders-vertical .sc-day-header,.sc-borders-vertical .sc-time-column-header{border-right:1px solid #e9ecef;border-bottom:none}.sc-borders-horizontal .sc-day-header,.sc-borders-horizontal .sc-time-column-header{border-right:none;border-bottom:1px solid #e9ecef}.sc-borders-none .sc-day-header,.sc-borders-none .sc-time-column-header{border-right:none;border-bottom:none}.sc-borders-both .sc-half-hour-line,.sc-borders-horizontal .sc-half-hour-line{border-top:1px dotted #e9ecef}.sc-borders-none .sc-half-hour-line,.sc-borders-vertical .sc-half-hour-line{border-top:none}.sc-borders-both .sc-day-column:nth-child(8n),.sc-borders-both .sc-day-header:nth-child(8n),.sc-borders-both .sc-day:last-child,.sc-borders-both .sc-week-view.sc-fullday .sc-day:last-child,.sc-borders-vertical .sc-day-column:nth-child(8n),.sc-borders-vertical .sc-day-header:nth-child(8n),.sc-borders-vertical .sc-day:last-child,.sc-borders-vertical .sc-week-view.sc-fullday .sc-day:last-child{border-right:none}:root{--sc-event-border-color:#6c757d}.sc-event-borders .sc-event-day:not(.sc-event-placeholder),.sc-event-borders .sc-event-month:not(.sc-event-placeholder),.sc-event-borders .sc-event-time:not(.sc-event-placeholder),.sc-event-borders .sc-event-week:not(.sc-event-placeholder){border:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-single:not(.sc-event-placeholder){border:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-start:not(.sc-event-placeholder){border-left:1px solid var(--sc-event-border-color);border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-middle:not(.sc-event-placeholder){border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-end:not(.sc-event-placeholder){border-right:1px solid var(--sc-event-border-color);border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-day{border-right:none}.sc-week-view .sc-event-week{border-right:none}.sc-event-no-borders .sc-event-day,.sc-event-no-borders .sc-event-month,.sc-event-no-borders .sc-event-time,.sc-event-no-borders .sc-event-week{border:none!important}@media print{.sc-calendar{box-shadow:none;border:1px solid #000}.sc-header{background:#fff!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.sc-btn{display:none}.sc-view-switcher{display:none}}
|
|
10
|
+
.sc-calendar{--sc-bg-primary:#ffffff;--sc-bg-secondary:#f8f9fa;--sc-bg-tertiary:#e9ecef;--sc-text-primary:#212529;--sc-text-secondary:#6c757d;--sc-text-muted:#adb5bd;--sc-border-color:#e9ecef;--sc-border-light:#dee2e6;--sc-accent-color:#4c6f94;--sc-accent-hover:#0056b3;--sc-today-bg:#e3f2fd;--sc-today-text:#dc3545;--sc-hover-bg:#f8f9fa;--sc-shadow:none;--sc-shadow-enabled:0 2px 10px rgba(0, 0, 0, 0.1);font-family:inherit;background:var(--sc-bg-primary);color:var(--sc-text-primary);box-shadow:var(--sc-shadow);overflow:hidden;width:100%;max-width:100%;display:flex;flex-direction:column}.sc-calendar.sc-shadow{--sc-shadow:var(--sc-shadow-enabled)}@media (prefers-color-scheme:dark){.sc-calendar{--sc-bg-primary:#1a1a1a;--sc-bg-secondary:#2d2d2d;--sc-bg-tertiary:#3a3a3a;--sc-text-primary:#ffffff;--sc-text-secondary:#b0b0b0;--sc-text-muted:#888888;--sc-border-color:#444444;--sc-border-light:#555555;--sc-accent-color:#4a90e2;--sc-accent-hover:#357abd;--sc-today-bg:#1a2f4a;--sc-today-text:#ff6b6b;--sc-hover-bg:#2d2d2d;--sc-shadow-enabled:0 2px 10px rgba(0, 0, 0, 0.5)}}[data-theme=dark] .sc-calendar{--sc-bg-primary:#1a1a1a;--sc-bg-secondary:#2d2d2d;--sc-bg-tertiary:#3a3a3a;--sc-text-primary:#ffffff;--sc-text-secondary:#b0b0b0;--sc-text-muted:#888888;--sc-border-color:#444444;--sc-border-light:#555555;--sc-accent-color:#4a90e2;--sc-accent-hover:#357abd;--sc-today-bg:#1a2f4a;--sc-today-text:#ff6b6b;--sc-hover-bg:#2d2d2d;--sc-shadow-enabled:0 2px 10px rgba(0, 0, 0, 0.5)}[data-theme=light] .sc-calendar{--sc-bg-primary:#ffffff;--sc-bg-secondary:#f8f9fa;--sc-bg-tertiary:#e9ecef;--sc-text-primary:#212529;--sc-text-secondary:#6c757d;--sc-text-muted:#adb5bd;--sc-border-color:#e9ecef;--sc-border-light:#dee2e6;--sc-accent-color:#4c6f94;--sc-accent-hover:#0056b3;--sc-today-bg:#e3f2fd;--sc-today-text:#dc3545;--sc-hover-bg:#f8f9fa;--sc-shadow-enabled:0 2px 10px rgba(0, 0, 0, 0.1)}.sc-calendar{background:var(--calendar-bg,var(--sc-bg-primary));color:var(--calendar-text,var(--sc-text-primary));height:100%}.sc-header{background:0 0;padding:16px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px}.sc-borders-both .sc-header,.sc-borders-horizontal .sc-header{border-bottom:1px solid var(--sc-border-color)}.sc-nav{display:flex;align-items:center;gap:8px;justify-self:start}.sc-title-container{position:relative;justify-self:center}.sc-title{margin:0;font-size:1.25rem;font-weight:600;color:var(--sc-text-primary);white-space:nowrap;text-align:center;user-select:none}.sc-title-month,.sc-title-year{cursor:pointer;padding:2px 4px;border-radius:3px;transition:background-color .2s ease}.sc-title-month:hover,.sc-title-year:hover{color:var(--sc-event-color,#4c6f94)}.sc-month-dropdown{position:absolute;top:100%;left:50%;transform:translateX(-50%);background:var(--sc-bg-primary);border:1px solid var(--sc-border-color);border-radius:4px;z-index:1000;min-width:120px;box-shadow:var(--sc-shadow-enabled);max-height:240px;overflow-y:auto}.sc-month-option{padding:4px 8px;font-size:.75rem;color:var(--sc-text-primary);cursor:pointer;transition:background-color .2s ease}.sc-month-option:hover{background:var(--sc-bg-tertiary)}.sc-month-option.sc-current{background:var(--sc-event-color,#4c6f94);color:#fff}.sc-month-option.sc-current:hover{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 85%,#000)}.sc-year-dropdown{position:absolute;top:100%;right:-20px;background:var(--sc-bg-primary);border:1px solid var(--sc-border-color);border-radius:4px;z-index:1000;min-width:100px;box-shadow:var(--sc-shadow-enabled);max-height:240px;overflow-y:auto}.sc-year-option{padding:4px 8px;font-size:.75rem;color:var(--sc-text-primary);cursor:pointer;transition:background-color .2s ease;text-align:center}.sc-year-option:hover{background:var(--sc-bg-tertiary)}.sc-year-option.sc-current{background:var(--sc-event-color,#4c6f94);color:#fff}.sc-year-option.sc-current:hover{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 85%,#000)}.sc-btn{background:0 0;border:1px solid var(--sc-border-light);border-radius:4px;padding:8px 12px;cursor:pointer;color:var(--sc-text-primary);font-size:.875rem;font-weight:500;transition:all .2s ease;min-width:36px;display:flex;align-items:center;justify-content:center}.sc-nav .sc-btn{background:0 0;border:none;font-size:1.2rem;padding:8px;min-width:32px;color:var(--sc-text-primary);font-weight:700}.sc-btn:hover{background:var(--sc-bg-tertiary);border-color:#adb5bd}.sc-nav .sc-btn:hover{background:var(--sc-bg-tertiary);color:var(--sc-text-primary)}.sc-btn:active{background:#dee2e6}.sc-nav .sc-btn:active{background:var(--sc-bg-tertiary)}.sc-view-switcher{display:flex;gap:4px;justify-self:end}.sc-view-btn.sc-active{background:var(--sc-event-color,#4c6f94);color:#fff;border-color:var(--sc-event-color,#4c6f94)}.sc-view-btn.sc-active:hover{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 85%,#000);border-color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 85%,#000)}.sc-content{flex:1;height:100%;display:flex;flex-direction:column}.sc-view-container{flex:1;overflow:hidden;height:100%}.sc-month-view{height:100%;display:flex;flex-direction:column}.sc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);background:var(--sc-bg-secondary)}.sc-weekday{padding:12px 8px;text-align:center;font-weight:600;font-size:.875rem;color:var(--sc-text-secondary)}.sc-borders-both .sc-month-view .sc-weekdays,.sc-borders-vertical .sc-month-view .sc-weekdays{border-left:1px solid var(--sc-border-color);border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-month-view .sc-weekday,.sc-borders-vertical .sc-month-view .sc-weekday{border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-month-view .sc-weekday:nth-child(7n),.sc-borders-vertical .sc-month-view .sc-weekday:nth-child(7n){border-right:none}.sc-days-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:repeat(6,1fr);flex:1;overflow:visible}.sc-month-view.sc-fullday .sc-days-grid{grid-template-rows:repeat(6,auto);min-height:auto}.sc-month-view.sc-fullday .sc-day{display:flex;flex-direction:column;position:relative}.sc-day{display:flex;flex-direction:column;min-height:80px;cursor:pointer;transition:background-color .2s ease;overflow:visible}.sc-day:hover{background:var(--sc-hover-bg)}.sc-day-number{text-align:center;font-weight:500;margin-bottom:4px;font-size:.875rem;line-height:1.2}.sc-day.sc-other-month .sc-day-number{color:var(--sc-text-muted)}.sc-day.sc-today{background:var(--sc-today-bg)}.sc-day.sc-today .sc-day-number{color:var(--sc-today-text);font-weight:600}.sc-day-count-mode{display:flex;align-items:center;justify-content:center}.sc-day-content{text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center}.sc-day-count-mode .sc-day-number{font-size:1.125rem;font-weight:600;margin-bottom:4px}.sc-event-count{font-size:.7rem;font-weight:600;line-height:1.2;background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 15%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 80%,#000);padding:2px 6px;border-radius:10px;display:inline-block;margin-top:2px;text-align:center;box-sizing:border-box}@media (prefers-color-scheme:light){.sc-event-count{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 15%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 80%,#000)}}[data-theme=light] .sc-event-count{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 15%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 80%,#000)}@media (prefers-color-scheme:dark){.sc-event-count{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 20%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 30%,#fff)}}[data-theme=dark] .sc-event-count{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 20%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 30%,#fff)}.sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}@media (prefers-color-scheme:light){.sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}}[data-theme=light] .sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}@media (prefers-color-scheme:dark){.sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}}[data-theme=dark] .sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}.sc-day-count-mode.sc-other-month .sc-event-count{background:var(--sc-bg-secondary);color:var(--sc-text-muted)}.sc-day-events{flex:1;display:flex;flex-direction:column;gap:2px;overflow:hidden;padding:4px}.sc-month-view.sc-fullday .sc-day-number{flex-shrink:0;margin-bottom:4px;padding:4px 6px 0 6px}.sc-event-day,.sc-event-month,.sc-event-time,.sc-event-week{background:var(--sc-event-color,#4c6f94);color:#fff;border-radius:3px;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;box-sizing:border-box}.sc-month-view .sc-event-month{padding:2px 6px;font-size:.75rem;height:18px;margin:1px 4px;min-width:0;display:block}.sc-month-view.sc-fullday .sc-event-month{position:relative;padding:2px 6px;border-radius:3px;min-height:18px;display:block;box-sizing:border-box;overflow:visible}.sc-month-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}.sc-month-view .sc-event-single{border-radius:3px}.sc-month-view .sc-event-start{border-radius:3px 0 0 3px}.sc-month-view .sc-event-middle{border-radius:0}.sc-month-view .sc-event-end{border-radius:0 3px 3px 0}.sc-month-view .sc-event-month.sc-event-start{border-radius:3px 0 0 3px}.sc-month-view .sc-event-month.sc-event-middle{border-radius:0}.sc-month-view .sc-event-month.sc-event-end{border-radius:0 3px 3px 0}.sc-month-view.sc-fullday .sc-event-month.sc-event-start{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-start::after{content:'';position:absolute;right:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::after,.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::before{content:'';position:absolute;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::before{left:-2px}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::after{right:-2px}.sc-month-view.sc-fullday .sc-event-month.sc-event-end{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-end::before{content:'';position:absolute;left:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-single{margin:1px 4px!important;border-radius:3px!important;z-index:1;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-start{margin:1px 0 1px 4px!important;border-radius:3px 0 0 3px!important;z-index:2;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle{margin:1px 0!important;border-radius:0!important;z-index:2;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-end{margin:1px 4px 1px 0!important;border-radius:0 3px 3px 0!important;z-index:2;position:relative}.sc-event-placeholder{opacity:0!important}.sc-month-view.sc-fullday .sc-event-placeholder{min-height:18px;padding:2px 6px;margin:1px 4px;display:block;box-sizing:border-box}.sc-week-view.sc-fullday .sc-event-placeholder{min-height:18px;padding:2px 6px;margin:1px 4px;display:block;box-sizing:border-box;line-height:.9em}.sc-week-view{height:100%;overflow-y:auto;overflow-x:hidden;display:grid;grid-template-columns:80px repeat(7,1fr);align-content:start}.sc-day-header,.sc-time-column-header{position:sticky;top:0;z-index:20;display:flex;flex-direction:column;justify-content:center;text-align:center;padding:12px 8px;background:var(--sc-bg-secondary);height:60px;box-sizing:border-box}.sc-week-view .sc-day-header.sc-today{background:var(--sc-today-bg)}.sc-day-name{font-size:.75rem;color:var(--sc-text-secondary);font-weight:500;margin-bottom:2px}.sc-day-header .sc-day-number{font-size:1.25rem;font-weight:600;color:var(--sc-text-primary)}.sc-week-view .sc-day-header.sc-today .sc-day-number{color:var(--sc-today-text);font-weight:600}.sc-day-column,.sc-time-slot{height:60px;position:relative;box-sizing:border-box}.sc-time-slot{background:var(--sc-bg-secondary);padding:4px 8px 8px 8px;font-size:.75rem;color:var(--sc-text-secondary);text-align:center;display:flex;align-items:flex-start;justify-content:center}.sc-time-slot:first-child,.sc-time-slot:last-child{background:var(--sc-bg-tertiary);color:#495057;font-weight:500;font-style:italic}.sc-day-column{cursor:pointer;background:var(--sc-bg-primary)}.sc-week-view .sc-day-column.sc-today{background:var(--sc-today-bg)}.sc-day-column:hover{background:var(--sc-hover-bg)}.sc-half-hour-line{position:absolute;top:50%;left:0;right:0;height:0;pointer-events:none;z-index:1}.sc-week-view .sc-event-time{position:absolute;top:4px;left:4px;right:4px;padding:2px 6px;font-size:.75rem;line-height:1.2;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:5;min-height:18px;display:block;box-sizing:border-box;padding-top:3px;padding-bottom:1px}.sc-week-view .sc-event-single{border-radius:3px}.sc-week-view .sc-event-start{border-radius:3px 0 0 3px}.sc-week-view .sc-event-middle{border-radius:0}.sc-week-view .sc-event-end{border-radius:0 3px 3px 0}.sc-week-view.sc-fullday .sc-event{width:100%;margin:2px 4px;padding:2px 6px;font-size:.75rem;height:18px;min-width:0}.sc-week-view.sc-fullday .sc-event-start{margin-right:0}.sc-week-view.sc-fullday .sc-event-middle{margin-left:0;margin-right:0}.sc-week-view.sc-fullday .sc-event-end{margin-left:0}.sc-week-view.sc-fullday{height:auto;display:flex;flex-direction:column}.sc-week-view.sc-fullday .sc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);background:var(--sc-bg-secondary)}.sc-week-view.sc-fullday .sc-weekday{padding:12px 8px;text-align:center;font-weight:600;font-size:.875rem;color:var(--sc-text-secondary)}.sc-borders-horizontal .sc-week-view.sc-fullday .sc-weekdays{border-bottom:1px solid var(--sc-border-color)}.sc-borders-both .sc-week-view.sc-fullday .sc-weekday,.sc-borders-vertical .sc-week-view.sc-fullday .sc-weekday{border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-week-view.sc-fullday .sc-weekday:nth-child(7n),.sc-borders-vertical .sc-week-view.sc-fullday .sc-weekday:nth-child(7n){border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-week-view.sc-fullday .sc-weekday:first-child,.sc-borders-vertical .sc-week-view.sc-fullday .sc-weekday:first-child{border-left:1px solid var(--sc-border-color)}.sc-week-view.sc-fullday .sc-days-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:1fr;flex:1;min-height:auto;overflow:visible}.sc-week-view.sc-fullday .sc-day{display:flex;flex-direction:column;min-height:80px;position:relative;cursor:pointer;transition:background-color .2s ease;overflow:visible}.sc-week-view.sc-fullday .sc-day:hover{background:var(--sc-hover-bg)}.sc-week-view.sc-fullday .sc-day-number{text-align:center;font-weight:500;margin-bottom:4px;font-size:.875rem;line-height:1.2;flex-shrink:0;padding:4px 6px 0 6px}.sc-week-view.sc-fullday .sc-day.sc-today{background:var(--sc-today-bg)}.sc-week-view.sc-fullday .sc-day.sc-today .sc-day-number{color:var(--sc-today-text);font-weight:600}.sc-week-view.sc-fullday .sc-event-week{position:relative;border-radius:3px;min-height:18px;padding:2px 6px;display:block;box-sizing:border-box;overflow:visible}.sc-week-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}.sc-week-view.sc-fullday .sc-event-week.sc-event-single{margin:1px 4px!important;border-radius:3px!important;z-index:1;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-start{margin:1px 0 1px 4px!important;border-radius:3px 0 0 3px!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-start::after{content:'';position:absolute;right:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle{margin:1px 0!important;border-radius:0!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::after,.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::before{content:'';position:absolute;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::before{left:-2px}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::after{right:-2px}.sc-week-view.sc-fullday .sc-event-week.sc-event-end{margin:1px 4px 1px 0!important;border-radius:0 3px 3px 0!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-end::before{content:'';position:absolute;left:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-day-view{height:100%;overflow-y:auto;overflow-x:hidden;max-height:calc(100vh - 200px);display:grid;grid-template-columns:80px 1fr;align-content:start}.sc-day-view.sc-fullday{display:flex;flex-direction:column;padding:0;grid-template-columns:none}.sc-day-view.sc-fullday .sc-day-header{position:sticky;top:0;z-index:10;background:var(--sc-bg-secondary);border-bottom:1px solid var(--sc-border-color);padding:16px;text-align:center;flex-shrink:0}.sc-day-view.sc-fullday .sc-day-name{font-size:.875rem;color:var(--sc-text-secondary);font-weight:500;margin-bottom:4px}.sc-day-view.sc-fullday .sc-day-number{font-size:1.5rem;font-weight:600;color:var(--sc-text-primary)}.sc-day-view.sc-fullday .sc-day-header.sc-today .sc-day-number{color:var(--sc-today-text)}.sc-day-view.sc-fullday .sc-day-events{flex:1;padding:16px;display:flex;flex-direction:column;gap:8px;overflow-y:auto;cursor:pointer}.sc-day-view .sc-event-time{position:absolute;top:4px;left:4px;right:4px;padding:2px 6px;font-size:.75rem;line-height:1.2;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:5;min-height:18px;display:block;box-sizing:border-box;padding-top:3px;padding-bottom:1px}.sc-day-view .sc-event{width:100%;margin:2px 4px;padding:2px 6px;font-size:.75rem;height:18px;min-width:0}.sc-day-view .sc-event-single{border-radius:3px}.sc-day-view .sc-event-start{border-radius:3px 0 0 3px}.sc-day-view .sc-event-middle{border-radius:0}.sc-day-view .sc-event-end{border-radius:0 3px 3px 0}.sc-day-view.sc-fullday .sc-event-start{margin-right:0}.sc-day-view.sc-fullday .sc-event-middle{margin-left:0;margin-right:0}.sc-day-view.sc-fullday .sc-event-end{margin-left:0}.sc-day-view.sc-fullday .sc-event-day{position:relative;padding:2px 6px;border-radius:3px;margin:1px 4px;min-height:18px;box-sizing:border-box;display:block}.sc-day-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}@media (max-width:768px){.sc-header{grid-template-columns:1fr;grid-template-rows:auto auto auto;gap:8px;justify-items:center}.sc-view-switcher{grid-row:1;justify-self:center}.sc-title-container{grid-row:2;justify-self:center}.sc-title{font-size:1.125rem}.sc-nav{grid-row:3;justify-self:center}.sc-days-grid{min-height:360px;grid-template-rows:repeat(6,minmax(60px,1fr))}.sc-day-number{font-size:.75rem}.sc-day-count-mode .sc-day-number{font-size:1rem}.sc-event-count{font-size:.625rem;padding:1px 4px;min-width:14px}.sc-event-month{font-size:.6rem;padding:1px 3px;height:14px}.sc-day-view .sc-event-time,.sc-week-view .sc-event-time{top:3px;left:3px;right:3px;padding:1px 4px;padding-top:2px;padding-bottom:1px;font-size:.65rem;min-height:16px}.sc-weekday{padding:8px 4px;font-size:.75rem}.sc-week-header{min-height:50px}.sc-week-view{grid-template-columns:60px repeat(7,1fr)}.sc-week-view.sc-fullday{grid-template-columns:repeat(7,1fr)}.sc-day-header{padding:8px 4px}.sc-day-name{font-size:.625rem}.sc-day-header .sc-day-number{font-size:1rem}.sc-day-header.sc-today .sc-day-number{color:var(--sc-today-text);font-weight:600}.sc-time-slot{height:40px;padding:4px;font-size:.625rem}.sc-hour-slot{height:40px}.sc-day-view{grid-template-columns:60px 1fr;max-height:calc(100vh - 160px)}.sc-week-view{max-height:calc(100vh - 160px)}}@media (max-width:480px){.sc-calendar{border-radius:0;box-shadow:none;border:1px solid #e9ecef}.sc-header{padding:12px}.sc-btn{padding:6px 8px;font-size:.75rem;min-width:30px}.sc-title{font-size:1rem}.sc-day{min-height:50px;padding:2px}.sc-days-grid{min-height:300px;grid-template-rows:repeat(6,minmax(50px,1fr))}.sc-day-count-mode .sc-day-number{font-size:.875rem}.sc-event-count{font-size:.5rem;padding:1px 3px;min-width:12px}.sc-weekday{padding:6px 2px;font-size:.625rem}.sc-week-header{min-height:40px}.sc-week-view{grid-template-columns:50px repeat(7,1fr);max-height:calc(100vh - 140px)}.sc-week-view.sc-fullday{grid-template-columns:repeat(7,1fr)}.sc-time-slot{height:30px;padding:2px;font-size:.5rem}.sc-hour-slot{height:30px}.sc-day-view{grid-template-columns:50px 1fr;max-height:calc(100vh - 140px)}.sc-day-view .sc-event-time,.sc-week-view .sc-event-time{top:2px;left:2px;right:2px;padding:1px 3px;padding-top:2px;padding-bottom:0;font-size:.5rem;min-height:14px}}.sc-borders-both .sc-month-view .sc-days-grid{border-left:1px solid var(--sc-border-color);border-bottom:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-month-view .sc-days-grid{border-left:1px solid var(--sc-border-color);border-bottom:none}.sc-borders-horizontal .sc-month-view .sc-days-grid{border-left:none;border-bottom:1px solid var(--sc-border-color)}.sc-borders-none .sc-month-view .sc-days-grid{border-left:none;border-bottom:none}.sc-borders-both .sc-day{border-right:1px solid var(--sc-border-color);border-top:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-day{border-right:1px solid var(--sc-border-color);border-top:none}.sc-borders-horizontal .sc-day{border-right:none;border-top:1px solid var(--sc-border-color)}.sc-borders-none .sc-day{border-right:none;border-top:none}.sc-borders-both .sc-day-column,.sc-borders-both .sc-time-slot{border-right:1px solid var(--sc-border-color);border-bottom:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-day-column,.sc-borders-vertical .sc-time-slot{border-right:1px solid var(--sc-border-color);border-bottom:none}.sc-borders-horizontal .sc-day-column,.sc-borders-horizontal .sc-time-slot{border-right:none;border-bottom:1px solid var(--sc-border-color)}.sc-borders-none .sc-day-column,.sc-borders-none .sc-time-slot{border-right:none;border-bottom:none}.sc-week-view:not(.sc-fullday) .sc-time-slot{border-bottom:1px solid var(--sc-border-color)!important}.sc-week-view:not(.sc-fullday) .sc-day-column{border-bottom:1px solid var(--sc-border-color)!important}.sc-borders-both .sc-week-view.sc-fullday .sc-day{border-right:1px solid var(--sc-border-color);border-bottom:1px solid var(--sc-border-color)}.sc-borders-both .sc-week-view.sc-fullday .sc-day:first-child{border-left:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-week-view.sc-fullday .sc-day{border-right:1px solid var(--sc-border-color);border-bottom:none}.sc-borders-horizontal .sc-week-view.sc-fullday .sc-day{border-right:none;border-bottom:1px solid var(--sc-border-color)}.sc-borders-none .sc-week-view.sc-fullday .sc-day{border-right:none;border-bottom:none}.sc-borders-both .sc-day-header,.sc-borders-both .sc-time-column-header{border-right:1px solid var(--sc-border-color);border-bottom:1px solid var(--sc-border-color)}.sc-borders-both .sc-day-header:first-child,.sc-borders-both .sc-time-column-header:first-child,.sc-borders-vertical .sc-day-header:first-child,.sc-borders-vertical .sc-time-column-header:first-child{border-left:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-day-header,.sc-borders-vertical .sc-time-column-header{border-right:1px solid var(--sc-border-color);border-bottom:none}.sc-borders-horizontal .sc-day-header,.sc-borders-horizontal .sc-time-column-header{border-right:none;border-bottom:1px solid var(--sc-border-color)}.sc-borders-none .sc-day-header,.sc-borders-none .sc-time-column-header{border-right:none;border-bottom:none}.sc-borders-both .sc-week-view:not(.sc-fullday){border-left:1px solid var(--sc-border-color);border-right:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-week-view:not(.sc-fullday){border-left:1px solid var(--sc-border-color);border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-day-header:nth-child(8n),.sc-borders-vertical .sc-day-header:nth-child(8n){border-right:none}.sc-borders-both .sc-half-hour-line,.sc-borders-horizontal .sc-half-hour-line{border-top:1px dotted var(--sc-border-color)}.sc-borders-none .sc-half-hour-line,.sc-borders-vertical .sc-half-hour-line{border-top:none}.sc-week-view:not(.sc-fullday) .sc-half-hour-line{border-top:1px dotted var(--sc-border-color)!important}.sc-day-view:not(.sc-fullday) .sc-time-slot{border-bottom:1px solid var(--sc-border-color)!important}.sc-borders-both .sc-day-view:not(.sc-fullday) .sc-time-slot,.sc-borders-vertical .sc-day-view:not(.sc-fullday) .sc-time-slot{border-left:1px solid var(--sc-border-color)!important}.sc-day-view:not(.sc-fullday) .sc-day-column{border-bottom:1px solid var(--sc-border-color)!important}.sc-day-view:not(.sc-fullday) .sc-half-hour-line{border-top:1px dotted var(--sc-border-color)!important}.sc-borders-both .sc-month-view .sc-days-grid,.sc-borders-vertical .sc-month-view .sc-days-grid{border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-day:nth-child(7n),.sc-borders-vertical .sc-day:nth-child(7n){border-right:none}:root{--sc-event-border-color:#6c757d}.sc-event-borders .sc-event-day:not(.sc-event-placeholder),.sc-event-borders .sc-event-month:not(.sc-event-placeholder),.sc-event-borders .sc-event-time:not(.sc-event-placeholder),.sc-event-borders .sc-event-week:not(.sc-event-placeholder){border:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-single:not(.sc-event-placeholder){border:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-start:not(.sc-event-placeholder){border-left:1px solid var(--sc-event-border-color);border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-middle:not(.sc-event-placeholder){border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-end:not(.sc-event-placeholder){border-right:1px solid var(--sc-event-border-color);border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-week-view .sc-event-week{border-right:none}.sc-event-no-borders .sc-event-day,.sc-event-no-borders .sc-event-month,.sc-event-no-borders .sc-event-time,.sc-event-no-borders .sc-event-week{border:none!important}@media print{.sc-calendar{box-shadow:none;border:1px solid #000}.sc-header{background:#fff!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.sc-btn{display:none}.sc-view-switcher{display:none}}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v2.0.
|
|
2
|
+
* SimpleCalendarJs v2.0.2
|
|
3
3
|
* A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
|
|
4
4
|
*
|
|
5
5
|
* @author Pedro Lopes <simplecalendarjs@gmail.com>
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
* @license SEE LICENSE IN LICENSE
|
|
8
8
|
* @repository https://github.com/pclslopes/SimpleCalendarJs
|
|
9
9
|
*/
|
|
10
|
-
class t{constructor(t,e={}){this.container="string"==typeof t?document.querySelector(t):t,this.options={view:"month",date:new Date,events:[],fulldayMode:!1,startHour:6,endHour:22,timeSlotMinutes:30,dayClick:null,eventClick:null,changeState:null,gridBorders:"both",eventBorders:!1,eventBorderColor:"#6c757d",defaultEventColor:"#4c6f94",showMonthButton:!0,showWeekButton:!0,showDayButton:!0,showNavigation:!0,...e},this.currentDate=new Date(this.options.date),this.view=this.options.view,this.events=this.options.events,this.eventsCallback="function"==typeof this.options.events?this.options.events:null,this.fulldayMode=this.options.fulldayMode,this.fetchedRanges=[],this.allCachedEvents=[],this.weekdays=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],this.weekdaysShort=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],this.months=["January","February","March","April","May","June","July","August","September","October","November","December"],this.init()}init(){this.container.innerHTML=this.getCalendarTemplate(),this.applyGridBorders(),this.applyEventBorders(),this.applyEventColor(),this.applyViewButtonVisibility(),this.applyNavigationVisibility(),this.bindEvents(),this.render()}triggerChangeState(){if(this.options.changeState){const t={view:this.view,date:new Date(this.currentDate),fulldayMode:this.fulldayMode,startHour:this.options.startHour,endHour:this.options.endHour,timeSlotMinutes:this.options.timeSlotMinutes};this.options.changeState(t)}}applyGridBorders(){const t=this.container.querySelector(".sc-calendar");t&&(t.classList.remove("sc-borders-both","sc-borders-vertical","sc-borders-horizontal","sc-borders-none"),t.classList.add(`sc-borders-${this.options.gridBorders}`))}applyEventBorders(){const t=this.container.querySelector(".sc-calendar");t&&(t.classList.remove("sc-event-borders","sc-event-no-borders"),this.options.eventBorders?(t.classList.add("sc-event-borders"),t.style.setProperty("--sc-event-border-color",this.options.eventBorderColor)):t.classList.add("sc-event-no-borders"))}applyEventColor(){const t=this.container.querySelector(".sc-calendar");t&&t.style.setProperty("--sc-event-color",this.options.defaultEventColor)}applyViewButtonVisibility(){const t=this.container.querySelector('[data-view="month"]'),e=this.container.querySelector('[data-view="week"]'),s=this.container.querySelector('[data-view="day"]');t&&(t.style.display=this.options.showMonthButton?"flex":"none"),e&&(e.style.display=this.options.showWeekButton?"flex":"none"),s&&(s.style.display=this.options.showDayButton?"flex":"none");const n=this.container.querySelector(".sc-view-switcher");if(n){const t=this.options.showMonthButton||this.options.showWeekButton||this.options.showDayButton;n.style.display=t?"flex":"none"}}applyNavigationVisibility(){const t=this.container.querySelector(".sc-nav");t&&(t.style.visibility=this.options.showNavigation?"visible":"hidden")}getCalendarTemplate(){return`\n <div class="sc-calendar">\n <div class="sc-header">\n <div class="sc-nav">\n <button class="sc-btn sc-prev">←</button>\n <button class="sc-btn sc-next">→</button>\n </div>\n <h2 class="sc-title"></h2>\n <div class="sc-view-switcher">\n <button class="sc-btn sc-view-btn ${"month"===this.view?"sc-active":""}" data-view="month">Month</button>\n <button class="sc-btn sc-view-btn ${"week"===this.view?"sc-active":""}" data-view="week">Week</button>\n <button class="sc-btn sc-view-btn ${"day"===this.view?"sc-active":""}" data-view="day">Day</button>\n </div>\n </div>\n <div class="sc-content">\n <div class="sc-view-container"></div>\n </div>\n </div>\n `}bindEvents(){const t=this.container.querySelector(".sc-prev"),e=this.container.querySelector(".sc-next"),s=this.container.querySelectorAll(".sc-view-btn");t.addEventListener("click",()=>this.navigate("prev")),e.addEventListener("click",()=>this.navigate("next")),s.forEach(t=>{t.addEventListener("click",t=>{const e=t.target.dataset.view;this.setView(e)})}),this.container.addEventListener("click",t=>{this.handleCalendarClick(t)})}calculatePreciseTime(t,e){const s=t.getAttribute("data-time");if(!s)return null;const n=t.getBoundingClientRect(),i=e.clientY-n.top>n.height/2,a=this.parseTimeString(s);return a?(i&&a.setMinutes(a.getMinutes()+30),this.formatTime(a)):s}handleCalendarClick(t){const e=t.target.closest("[data-event-id]");if(e)return t.stopPropagation(),void this.handleEventClick(e,t);const s=t.target.closest("[data-date]");s&&this.handleDayClick(s,t)}handleEventClick(t,e){if(!this.options.eventClick)return;const s=t.getAttribute("data-event-id"),n=t.closest("[data-date]"),i=n?n.getAttribute("data-date"):null;if(s&&i){const t=new Date(i),a=this.getAllEventsForRange().find(t=>t.id==s);if(a){const s={date:t,time:this.calculatePreciseTime(n,e)};this.options.eventClick(a,s,e)}}}handleDayClick(t,e){if(!this.options.dayClick)return;const s=t.getAttribute("data-date");if(s){const n={date:new Date(s),time:this.calculatePreciseTime(t,e)};this.options.dayClick(n,e)}}navigate(t){const e="next"===t?1:-1;switch(this.view){case"month":this.currentDate.setMonth(this.currentDate.getMonth()+e);break;case"week":this.currentDate.setDate(this.currentDate.getDate()+7*e);break;case"day":this.currentDate.setDate(this.currentDate.getDate()+e)}this.render(),this.triggerChangeState()}setView(t){this.view=t,this.container.querySelectorAll(".sc-view-btn").forEach(t=>{t.classList.remove("sc-active")}),this.container.querySelector(`[data-view="${t}"]`).classList.add("sc-active"),this.render(),this.triggerChangeState()}render(){this.updateTitle(),this.renderView()}updateTitle(){const t=this.container.querySelector(".sc-title");switch(this.view){case"month":t.textContent=`${this.months[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}`;break;case"week":const e=this.getWeekStart(this.currentDate),s=new Date(e);s.setDate(s.getDate()+6),t.textContent=`${this.months[e.getMonth()]} ${e.getDate()} - ${this.months[s.getMonth()]} ${s.getDate()}, ${e.getFullYear()}`;break;case"day":t.textContent=`${this.weekdays[this.currentDate.getDay()]}, ${this.months[this.currentDate.getMonth()]} ${this.currentDate.getDate()}, ${this.currentDate.getFullYear()}`}}renderView(){const t=this.container.querySelector(".sc-view-container");switch(this.view){case"month":t.innerHTML=this.renderMonthView();break;case"week":t.innerHTML=this.renderWeekView();break;case"day":t.innerHTML=this.renderDayView()}}renderMonthView(){const t=this.currentDate.getFullYear(),e=this.currentDate.getMonth(),s=new Date(t,e,1),n=new Date(s);n.setDate(n.getDate()-s.getDay());let i=`\n <div class="sc-month-view ${this.fulldayMode?"sc-fullday":""}">\n <div class="sc-weekdays">\n ${this.weekdaysShort.map(t=>`<div class="sc-weekday">${t}</div>`).join("")}\n </div>\n <div class="sc-days-grid">\n `;for(let t=0;t<6;t++)for(let s=0;s<7;s++){const a=new Date(n);a.setDate(n.getDate()+7*t+s);const r=a.getMonth()===e,o=this.isToday(a),c=this.getEventsForDate(a);if(this.fulldayMode){const t=this.addEventPlaceholders(c,a);i+=`\n <div class="sc-day ${r?"sc-current-month":"sc-other-month"} ${o?"sc-today":""}" data-date="${a.toISOString()}">\n <div class="sc-day-number">${a.getDate()}</div>\n ${t.map(t=>{if(t.isPlaceholder)return'<div class="sc-event-month sc-event-single sc-event-placeholder"> </div>';const e=this.getEventPosition(t.event,a),s=e.isSingle?"sc-event-single":e.isStart?"sc-event-start":e.isEnd?"sc-event-end":"sc-event-middle",n=e.isSingle||e.isStart?t.event.title:"",i=this.applyEventStyles(t.event);return`<div class="sc-event-month ${s}" data-event-id="${t.event.id}" ${i}><span class="sc-event-text">${n}</span></div>`}).join("")}\n </div>\n `}else{const t=c.length,e=0===t?"":1===t?"1 event":`${t} events`;i+=`\n <div class="sc-day sc-day-count-mode ${r?"sc-current-month":"sc-other-month"} ${o?"sc-today":""}" data-date="${a.toISOString()}">\n <div class="sc-day-content">\n <div class="sc-day-number">${a.getDate()}</div>\n ${e?`<div class="sc-event-count">${e}</div>`:""}\n </div>\n </div>\n `}}return i+="\n </div>\n </div>\n ",i}renderWeekView(){const t=this.getWeekStart(this.currentDate),e=[];for(let s=0;s<7;s++){const n=new Date(t);n.setDate(n.getDate()+s),e.push(n)}if(this.fulldayMode)return this.renderWeekFullday(e);const s=this.generateTimeSlots();return`\n <div class="sc-week-view">\n <div class="sc-time-column-header"></div>\n ${e.map(t=>`\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.weekdaysShort[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n `).join("")}\n ${s.map(t=>`\n <div class="sc-time-slot">${t}</div>\n ${e.map(e=>{const s=this.getEventsForTimeSlot(e,t);return`\n <div class="sc-day-column ${this.isToday(e)?"sc-today":""}" data-date="${e.toISOString()}" data-time="${t}">\n <div class="sc-half-hour-line"></div>\n ${s.map(t=>{const e=this.applyEventStyles(t);return`<div class="sc-event-time" data-event-id="${t.id}" ${e}>${t.title}</div>`}).join("")}\n </div>\n `}).join("")}\n `).join("")}\n </div>\n `}renderWeekFullday(t){return`\n <div class="sc-week-view sc-fullday">\n <div class="sc-weekdays">\n ${t.map(t=>`<div class="sc-weekday">${this.weekdaysShort[t.getDay()]}</div>`).join("")}\n </div>\n <div class="sc-days-grid">\n ${t.map(t=>{const e=this.isToday(t),s=this.getEventsForDate(t),n=this.addEventPlaceholders(s,t);return`\n <div class="sc-day ${e?"sc-today":""}" data-date="${t.toISOString()}">\n <div class="sc-day-number">${t.getDate()}</div>\n ${n.map(e=>{if(e.isPlaceholder)return'<div class="sc-event-week sc-event-single sc-event-placeholder"> </div>';const s=this.getEventPosition(e.event,t),n=s.isSingle?"sc-event-single":s.isStart?"sc-event-start":s.isEnd?"sc-event-end":"sc-event-middle",i=s.isSingle||s.isStart?e.event.title:"",a=this.applyEventStyles(e.event);return`<div class="sc-event-week ${n}" data-event-id="${e.event.id}" ${a}><span class="sc-event-text">${i}</span></div>`}).join("")}\n </div>\n `}).join("")}\n </div>\n </div>\n `}renderDayView(){const t=new Date(this.currentDate);if(this.fulldayMode){const e=this.getEventsForDate(t);return`\n <div class="sc-day-view sc-fullday">\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.weekdays[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n <div class="sc-day-events" data-date="${t.toISOString()}">\n ${e.map(e=>{const s=this.getEventPosition(e,t),n=s.isSingle?"sc-event-single":s.isStart?"sc-event-start":s.isEnd?"sc-event-end":"sc-event-middle",i=e.title,a=this.applyEventStyles(e);return`<div class="sc-event-day ${n}" data-event-id="${e.id}" ${a}><span class="sc-event-text">${i}</span></div>`}).join("")}\n </div>\n </div>\n `}const e=this.generateTimeSlots();return`\n <div class="sc-day-view">\n <div class="sc-time-column-header"></div>\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.weekdays[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n ${e.map(e=>{const s=this.getEventsForTimeSlot(t,e);return`\n <div class="sc-time-slot">${e}</div>\n <div class="sc-day-column ${this.isToday(t)?"sc-today":""}" data-date="${t.toISOString()}" data-time="${e}">\n <div class="sc-half-hour-line"></div>\n ${s.map(t=>{const e=this.applyEventStyles(t);return`<div class="sc-event-time" data-event-id="${t.id}" ${e}>${t.title}</div>`}).join("")}\n </div>\n `}).join("")}\n </div>\n `}generateTimeSlots(){const t=[],e=this.options.startHour,s=this.options.endHour,n=new Date;n.setHours(e,0,0,0),t.push(`Before ${this.formatTime(n)}`);for(let n=e;n<=s;n++){const e=new Date;e.setHours(n,0,0,0),t.push(this.formatTime(e))}const i=new Date;return i.setHours(s,0,0,0),t.push(`After ${this.formatTime(i)}`),t}formatTime(t){return t.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}getWeekStart(t){const e=new Date(t);return e.setDate(e.getDate()-e.getDay()),e}getVisibleDateRange(){const t=this.currentDate.getFullYear(),e=this.currentDate.getMonth();switch(this.view){case"month":const s=new Date(t,e,1),n=new Date(s);n.setDate(n.getDate()-s.getDay());const i=new Date(n);return i.setDate(i.getDate()+41),{startDate:n,endDate:i};case"week":const a=this.getWeekStart(this.currentDate),r=new Date(a);return r.setDate(r.getDate()+6),{startDate:a,endDate:r};case"day":const o=new Date(this.currentDate);o.setHours(0,0,0,0);const c=new Date(o);return c.setHours(23,59,59,999),{startDate:o,endDate:c}}}isToday(t){const e=new Date;return t.toDateString()===e.toDateString()}isRangeCovered(t,e){for(const s of this.fetchedRanges)if(s.startDate<=t&&s.endDate>=e)return!0;return!1}getMissingRanges(t,e){const s=[];let n=new Date(t);const i=[...this.fetchedRanges].sort((t,e)=>t.startDate-e.startDate);for(const t of i){if(n<t.startDate&&n<=e){const i=new Date(Math.min(t.startDate.getTime()-864e5,e.getTime()));i>=n&&s.push({startDate:new Date(n),endDate:i})}t.endDate>=n&&(n=new Date(t.endDate.getTime()+864e5))}return n<=e&&s.push({startDate:new Date(n),endDate:new Date(e)}),s}addFetchedRange(t,e,s){const n={startDate:new Date(t),endDate:new Date(e),events:s};this.allCachedEvents=this.allCachedEvents.concat(s),this.fetchedRanges.push(n),this.mergeOverlappingRanges()}mergeOverlappingRanges(){this.fetchedRanges.sort((t,e)=>t.startDate-e.startDate);const t=[];for(const e of this.fetchedRanges)if(0===t.length||t[t.length-1].endDate<e.startDate)t.push(e);else{const s=t[t.length-1];s.endDate=new Date(Math.max(s.endDate.getTime(),e.endDate.getTime())),s.events=s.events.concat(e.events)}this.fetchedRanges=t,this.allCachedEvents=[];for(const t of this.fetchedRanges)this.allCachedEvents=this.allCachedEvents.concat(t.events)}getAllEventsForRange(){if(this.eventsCallback){const t=this.getVisibleDateRange();if(this.isRangeCovered(t.startDate,t.endDate))return this.allCachedEvents;const e=this.getMissingRanges(t.startDate,t.endDate);for(const t of e){const e=this.eventsCallback(t);this.addFetchedRange(t.startDate,t.endDate,e)}return this.allCachedEvents}return this.events||[]}getEventsForDate(t){return this.getAllEventsForRange().filter(e=>{const s=new Date(e.startDate||e.date),n=new Date(e.endDate||e.date),i=new Date(t);return i.setHours(0,0,0,0),s.setHours(0,0,0,0),n.setHours(0,0,0,0),i>=s&&i<=n}).sort((t,e)=>{const s=this.isMultiDayEvent(t),n=this.isMultiDayEvent(e);return s&&!n?-1:!s&&n?1:0})}getEventsForTimeSlot(t,e){if(this.fulldayMode)return this.getEventsForDate(t);return this.getEventsForDate(t).filter(t=>{if(!t.time)return!1;const s=this.parseTimeString(t.time);if(!s)return!1;const n=s.getHours(),i=this.options.startHour,a=this.options.endHour;if(e.startsWith("Before"))return n<i;if(e.startsWith("After"))return n>a;const r=this.parseTimeString(e);if(!r)return!1;return n===r.getHours()})}parseTimeString(t){try{const e=new Date,[s,n]=t.split(" "),[i,a]=s.split(":").map(Number);let r=i;return"PM"===n&&12!==i&&(r+=12),"AM"===n&&12===i&&(r=0),e.setHours(r,a||0,0,0),e}catch(t){return null}}isMultiDayEvent(t){const e=new Date(t.startDate||t.date),s=new Date(t.endDate||t.date);return e.setHours(0,0,0,0),s.setHours(0,0,0,0),e.getTime()!==s.getTime()}addEventPlaceholders(t,e){if(!this.fulldayMode)return t.map(t=>({event:t,isPlaceholder:!1}));const s=this.getWeekStart(e),n=[];for(let t=0;t<7;t++){const e=new Date(s);e.setDate(e.getDate()+t),n.push(e)}const i=new Map;n.forEach(t=>{this.getEventsForDate(t).filter(t=>this.isMultiDayEvent(t)).forEach(t=>{i.has(t.id)||i.set(t.id,t)})});const a=Array.from(i.values()).sort((t,e)=>new Date(t.startDate||t.date)-new Date(e.startDate||e.date)),r=[],o=t.filter(t=>this.isMultiDayEvent(t)),c=t.filter(t=>!this.isMultiDayEvent(t));return a.forEach(t=>{const n=o.find(e=>e.id===t.id);if(n)r.push({event:n,isPlaceholder:!1});else{const n=new Date(t.startDate||t.date),i=new Date(t.endDate||t.date);n.setHours(0,0,0,0),i.setHours(0,0,0,0);const a=new Date(e);a.setHours(0,0,0,0);const o=new Date(s);o.setHours(0,0,0,0);const c=new Date(s);c.setDate(c.getDate()+6),c.setHours(0,0,0,0);n<=c&&(i>=o&&i<=c)&&a>i&&r.push({isPlaceholder:!0,eventId:t.id})}}),c.forEach(t=>{r.push({event:t,isPlaceholder:!1})}),r}getEventPosition(t,e){const s=new Date(t.startDate||t.date),n=new Date(t.endDate||t.date),i=new Date(e);s.setHours(0,0,0,0),n.setHours(0,0,0,0),i.setHours(0,0,0,0);return{isStart:i.getTime()===s.getTime(),isEnd:i.getTime()===n.getTime(),isMiddle:i>s&&i<n,isSingle:s.getTime()===n.getTime()}}addEvent(t){this.eventsCallback||(this.events.push(t),this.render())}removeEvent(t){this.eventsCallback||(this.events=this.events.filter(e=>e.id!==t),this.render())}setEvents(t){"function"==typeof t?(this.eventsCallback=t,this.events=[]):(this.events=t||[],this.eventsCallback=null),this.fetchedRanges=[],this.allCachedEvents=[],this.render()}setFulldayMode(t){this.fulldayMode=t,this.render(),this.triggerChangeState()}goToDate(t){this.currentDate=new Date(t),this.render(),this.triggerChangeState()}goToToday(){this.currentDate=new Date,this.render(),this.triggerChangeState()}setGridBorders(t){this.options.gridBorders=t,this.applyGridBorders()}setEventBorders(t){this.options.eventBorders=t,this.applyEventBorders(),this.render()}setEventBorderColor(t){this.options.eventBorderColor=t,this.applyEventBorders(),this.render()}setShowMonthButton(t){this.options.showMonthButton=t,this.applyViewButtonVisibility()}setShowWeekButton(t){this.options.showWeekButton=t,this.applyViewButtonVisibility()}setShowDayButton(t){this.options.showDayButton=t,this.applyViewButtonVisibility()}setViewButtonsVisibility(t,e,s){this.options.showMonthButton=t,this.options.showWeekButton=e,this.options.showDayButton=s,this.applyViewButtonVisibility()}setShowNavigation(t){this.options.showNavigation=t,this.applyNavigationVisibility()}setDefaultEventColor(t){this.options.defaultEventColor=t,this.applyEventColor(),this.render()}getEventBaseCssStyle(t){const e=t.color,s=!0===t.colorIsGradient,n=e?t.color:this.options.defaultEventColor;if(!n)return{};const i=(" "+n).slice(1),a=this.hexToRgb(i);if(!a)return{};const r=this.getOptimalTextColor(a);if(s){const t={r:Math.min(255,a.r+40),g:Math.min(255,a.g+40),b:Math.min(255,a.b+40)};return{"background-image":`linear-gradient(to bottom, ${this.rgbToHex(t)} 0px, ${n} 100%)`,"background-repeat":"repeat-x",color:r,"background-color":n}}return{"background-color":n,color:r}}getOptimalTextColor(t){return this.getLuminance(t)>.5?"#000000":"#ffffff"}getLuminance(t){return.2126*this.getRelativeLuminanceComponent(t.r/255)+.7152*this.getRelativeLuminanceComponent(t.g/255)+.0722*this.getRelativeLuminanceComponent(t.b/255)}getRelativeLuminanceComponent(t){return t<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4)}getContrastRatio(t,e){const s=this.getLuminance(t),n=this.getLuminance(e);return(Math.max(s,n)+.05)/(Math.min(s,n)+.05)}hexToRgb(t){const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null}rgbToHex(t){return"#"+(1<<24|t.r<<16|t.g<<8|t.b).toString(16).slice(1)}applyEventStyles(t){const e=this.getEventBaseCssStyle(t),s=Object.entries(e).map(([t,e])=>`${t}: ${e}`).join("; ");return s?`style="${s}"`:""}}
|
|
10
|
+
class t{constructor(t,e={}){this.container="string"==typeof t?document.querySelector(t):t;const s=this.mergeColors({background:"#ffffff",backgroundSecondary:"#f8f9fa",backgroundTertiary:"#ecf0f1",text:"#212529",textSecondary:"#6c757d",textMuted:"#adb5bd",border:"#e9ecef",borderLight:"#dee2e6",accent:"#4c6f94",accentHover:"#0056b3",todayBg:"#e3f2fd",todayText:"#dc3545",hoverBg:"#f8f9fa",dark:{background:"#2d2d2d",backgroundSecondary:"#3a3a3a",backgroundTertiary:"#4a4a4a",text:"#ffffff",textSecondary:"#cccccc",textMuted:"#888888",border:"#444444",borderLight:"#555555",accent:"#4a90e2",accentHover:"#357abd",todayBg:"#1a2f4a",todayText:"#ff6b6b",hoverBg:"#2d2d2d"}},e.colors||{});this.options={view:"month",date:new Date,events:[],fulldayMode:!1,startHour:6,endHour:22,timeSlotMinutes:30,dayClick:null,eventClick:null,changeState:null,gridBorders:"both",eventBorders:!1,eventBorderColor:"#6c757d",defaultEventColor:"#4c6f94",showMonthButton:!0,showWeekButton:!0,showDayButton:!0,showNavigation:!0,weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],showWeekdayChars:null,labels:{month:"Month",week:"Week",day:"Day",events:"events",event:"event",before:"Before",after:"After"},colors:s,...e,colors:s},this.currentDate=new Date(this.options.date),this.view=this.options.view,this.events=this.options.events,this.eventsCallback="function"==typeof this.options.events?this.options.events:null,this.fulldayMode=this.options.fulldayMode,this.fetchedRanges=[],this.allCachedEvents=[],this.init()}mergeColors(t,e){const s={...t};return Object.keys(e).forEach(t=>{"dark"!==t&&void 0!==e[t]&&(s[t]=e[t])}),e.dark&&(s.dark={...t.dark},Object.keys(e.dark).forEach(t=>{void 0!==e.dark[t]&&(s.dark[t]=e.dark[t])})),s}init(){this.container.innerHTML=this.getCalendarTemplate(),this.applyGridBorders(),this.applyEventBorders(),this.applyEventColor(),this.applyViewButtonVisibility(),this.applyNavigationVisibility(),this.applyCustomColors(),this.bindEvents(),this.render()}getWeekdayDisplay(t){const e=this.options.weekdays[t];return null===this.options.showWeekdayChars?e:e.substring(0,this.options.showWeekdayChars)}triggerChangeState(){if(this.options.changeState){const t={view:this.view,date:new Date(this.currentDate),fulldayMode:this.fulldayMode,startHour:this.options.startHour,endHour:this.options.endHour,timeSlotMinutes:this.options.timeSlotMinutes};this.options.changeState(t)}}applyGridBorders(){const t=this.container.querySelector(".sc-calendar");t&&(t.classList.remove("sc-borders-both","sc-borders-vertical","sc-borders-horizontal","sc-borders-none"),t.classList.add(`sc-borders-${this.options.gridBorders}`))}applyEventBorders(){const t=this.container.querySelector(".sc-calendar");t&&(t.classList.remove("sc-event-borders","sc-event-no-borders"),this.options.eventBorders?(t.classList.add("sc-event-borders"),t.style.setProperty("--sc-event-border-color",this.options.eventBorderColor)):t.classList.add("sc-event-no-borders"))}applyEventColor(){const t=this.container.querySelector(".sc-calendar");t&&t.style.setProperty("--sc-event-color",this.options.defaultEventColor)}applyViewButtonVisibility(){const t=this.container.querySelector('[data-view="month"]'),e=this.container.querySelector('[data-view="week"]'),s=this.container.querySelector('[data-view="day"]');t&&(t.style.display=this.options.showMonthButton?"flex":"none"),e&&(e.style.display=this.options.showWeekButton?"flex":"none"),s&&(s.style.display=this.options.showDayButton?"flex":"none");const n=this.container.querySelector(".sc-view-switcher");if(n){const t=this.options.showMonthButton||this.options.showWeekButton||this.options.showDayButton;n.style.display=t?"flex":"none"}}applyNavigationVisibility(){const t=this.container.querySelector(".sc-nav");t&&(t.style.visibility=this.options.showNavigation?"visible":"hidden")}applyCustomColors(){if(!this.container.querySelector(".sc-calendar"))return;const t=this.options.colors,e=(t,e)=>void 0!==e?`--${t}: ${e} !important;`:"",s=`\n .sc-calendar {\n ${e("sc-bg-primary",t.background)}\n ${e("sc-bg-secondary",t.backgroundSecondary)}\n ${e("sc-bg-tertiary",t.backgroundTertiary)}\n ${e("sc-text-primary",t.text)}\n ${e("sc-text-secondary",t.textSecondary)}\n ${e("sc-text-muted",t.textMuted)}\n ${e("sc-border-color",t.border)}\n ${e("sc-border-light",t.borderLight)}\n ${e("sc-accent-color",t.accent)}\n ${e("sc-accent-hover",t.accentHover)}\n ${e("sc-today-bg",t.todayBg)}\n ${e("sc-today-text",t.todayText)}\n ${e("sc-hover-bg",t.hoverBg)}\n }\n\n ${t.dark?`\n [data-theme="dark"] .sc-calendar {\n ${e("sc-bg-primary",t.dark.background)}\n ${e("sc-bg-secondary",t.dark.backgroundSecondary)}\n ${e("sc-bg-tertiary",t.dark.backgroundTertiary)}\n ${e("sc-text-primary",t.dark.text)}\n ${e("sc-text-secondary",t.dark.textSecondary)}\n ${e("sc-text-muted",t.dark.textMuted)}\n ${e("sc-border-color",t.dark.border)}\n ${e("sc-border-light",t.dark.borderLight)}\n ${e("sc-accent-color",t.dark.accent)}\n ${e("sc-accent-hover",t.dark.accentHover)}\n ${e("sc-today-bg",t.dark.todayBg)}\n ${e("sc-today-text",t.dark.todayText)}\n ${e("sc-hover-bg",t.dark.hoverBg)}\n }\n `:""}\n `;let n=document.getElementById("sc-custom-colors");n||(n=document.createElement("style"),n.id="sc-custom-colors",document.head.appendChild(n)),n.textContent=s}getCalendarTemplate(){return`\n <div class="sc-calendar">\n <div class="sc-header">\n <div class="sc-nav">\n <button class="sc-btn sc-prev">←</button>\n <button class="sc-btn sc-next">→</button>\n </div>\n <div class="sc-title-container">\n <h2 class="sc-title">\n <span class="sc-title-month"></span>\n <span class="sc-title-year"></span>\n </h2>\n <div class="sc-month-dropdown" style="display: none;">\n ${this.options.months.map((t,e)=>`<div class="sc-month-option" data-month="${e}">${t}</div>`).join("")}\n </div>\n <div class="sc-year-dropdown" style="display: none;">\n </div>\n </div>\n <div class="sc-view-switcher">\n <button class="sc-btn sc-view-btn ${"month"===this.view?"sc-active":""}" data-view="month">${this.options.labels.month}</button>\n <button class="sc-btn sc-view-btn ${"week"===this.view?"sc-active":""}" data-view="week">${this.options.labels.week}</button>\n <button class="sc-btn sc-view-btn ${"day"===this.view?"sc-active":""}" data-view="day">${this.options.labels.day}</button>\n </div>\n </div>\n <div class="sc-content">\n <div class="sc-view-container"></div>\n </div>\n </div>\n `}bindEvents(){const t=this.container.querySelector(".sc-prev"),e=this.container.querySelector(".sc-next"),s=this.container.querySelectorAll(".sc-view-btn");t.addEventListener("click",()=>this.navigate("prev")),e.addEventListener("click",()=>this.navigate("next")),s.forEach(t=>{t.addEventListener("click",t=>{const e=t.target.dataset.view;this.setView(e)})}),this.container.addEventListener("click",t=>{this.handleCalendarClick(t)}),this.outsideClickBound||(document.addEventListener("click",t=>{t.target.closest(".sc-title-container")||(this.hideMonthDropdown(),this.hideYearDropdown())}),this.outsideClickBound=!0)}bindMonthViewDropdowns(){if("month"!==this.view)return;const t=this.container.querySelector(".sc-title-month"),e=this.container.querySelector(".sc-title-year");if(t){const e=t.cloneNode(!0);t.parentNode.replaceChild(e,t),e.addEventListener("click",t=>{t.stopPropagation(),this.hideYearDropdown(),this.toggleMonthDropdown()})}if(e){const t=e.cloneNode(!0);e.parentNode.replaceChild(t,e),t.addEventListener("click",t=>{t.stopPropagation(),this.hideMonthDropdown(),this.toggleYearDropdown()})}this.generateYearOptions(),this.dropdownOptionsHandler&&this.container.removeEventListener("click",this.dropdownOptionsHandler),this.dropdownOptionsHandler=t=>{if(t.target.classList.contains("sc-month-option")){const e=parseInt(t.target.getAttribute("data-month"));this.setMonth(e),this.hideMonthDropdown()}else if(t.target.classList.contains("sc-year-option")){const e=parseInt(t.target.getAttribute("data-year"));this.setYear(e),this.hideYearDropdown()}},this.container.addEventListener("click",this.dropdownOptionsHandler);this.container.querySelectorAll(".sc-month-option"),this.container.querySelectorAll(".sc-year-option")}calculatePreciseTime(t,e){const s=t.getAttribute("data-time");if(!s)return null;const n=t.getBoundingClientRect(),i=e.clientY-n.top>n.height/2,a=this.parseTimeString(s);return a?(i&&a.setMinutes(a.getMinutes()+30),this.formatTime(a)):s}handleCalendarClick(t){if(t.target.classList.contains("sc-title-month")||t.target.classList.contains("sc-title-year")||t.target.classList.contains("sc-month-option")||t.target.classList.contains("sc-year-option")||t.target.closest(".sc-month-dropdown")||t.target.closest(".sc-year-dropdown"))return;const e=t.target.closest("[data-event-id]");if(e)return t.stopPropagation(),void this.handleEventClick(e,t);const s=t.target.closest("[data-date]");s&&this.handleDayClick(s,t)}handleEventClick(t,e){if(!this.options.eventClick)return;const s=t.getAttribute("data-event-id"),n=t.closest("[data-date]"),i=n?n.getAttribute("data-date"):null;if(s&&i){const t=new Date(i),a=this.getAllEventsForRange().find(t=>t.id==s);if(a){const s={date:t,time:this.calculatePreciseTime(n,e)};this.options.eventClick(a,s,e)}}}handleDayClick(t,e){if(!this.options.dayClick)return;const s=t.getAttribute("data-date");if(s){const n={date:new Date(s),time:this.calculatePreciseTime(t,e)};this.options.dayClick(n,e)}}navigate(t){const e="next"===t?1:-1;switch(this.view){case"month":this.currentDate.setMonth(this.currentDate.getMonth()+e);break;case"week":this.currentDate.setDate(this.currentDate.getDate()+7*e);break;case"day":this.currentDate.setDate(this.currentDate.getDate()+e)}this.render(),this.triggerChangeState()}toggleMonthDropdown(){if("month"!==this.view)return;const t=this.container.querySelector(".sc-month-dropdown");if(t){"none"!==t.style.display?this.hideMonthDropdown():(this.updateMonthDropdownSelection(),t.style.display="block",this.scrollToActiveMonth())}}hideMonthDropdown(){const t=this.container.querySelector(".sc-month-dropdown");t&&(t.style.display="none")}updateMonthDropdownSelection(){const t=this.container.querySelectorAll(".sc-month-option"),e=this.currentDate.getMonth();t.forEach((t,s)=>{t.classList.remove("sc-current"),s===e&&t.classList.add("sc-current")})}toggleYearDropdown(){if("month"!==this.view)return;const t=this.container.querySelector(".sc-year-dropdown");if(t){"none"!==t.style.display?this.hideYearDropdown():(this.generateYearOptions(),this.updateYearDropdownSelection(),t.style.display="block",this.scrollToActiveYear())}}hideYearDropdown(){const t=this.container.querySelector(".sc-year-dropdown");t&&(t.style.display="none")}generateYearOptions(){const t=this.container.querySelector(".sc-year-dropdown"),e=this.currentDate.getFullYear(),s=e+10;if(t){let n="";for(let t=e-10;t<=s;t++)n+=`<div class="sc-year-option" data-year="${t}">${t}</div>`;t.innerHTML=n}}updateYearDropdownSelection(){const t=this.container.querySelectorAll(".sc-year-option"),e=this.currentDate.getFullYear();t.forEach(t=>{t.classList.remove("sc-current"),parseInt(t.getAttribute("data-year"))===e&&t.classList.add("sc-current")})}setYear(t){this.currentDate.setFullYear(t),this.render(),this.triggerChangeState()}scrollToActiveMonth(){const t=this.container.querySelector(".sc-month-dropdown"),e=t?.querySelector(".sc-month-option.sc-current");t&&e&&setTimeout(()=>{t.getBoundingClientRect(),e.getBoundingClientRect(),t.scrollTop;const s=e.offsetTop-t.clientHeight/2+e.offsetHeight/2;t.scrollTop=s},0)}scrollToActiveYear(){const t=this.container.querySelector(".sc-year-dropdown"),e=t?.querySelector(".sc-year-option.sc-current");t&&e&&setTimeout(()=>{t.getBoundingClientRect(),e.getBoundingClientRect(),t.scrollTop;const s=e.offsetTop-t.clientHeight/2+e.offsetHeight/2;t.scrollTop=s},0)}setMonth(t){this.currentDate.setMonth(t),this.render(),this.triggerChangeState()}setView(t){this.view=t,this.container.querySelectorAll(".sc-view-btn").forEach(t=>{t.classList.remove("sc-active")}),this.container.querySelector(`[data-view="${t}"]`).classList.add("sc-active"),this.hideMonthDropdown(),this.hideYearDropdown(),this.render(),this.triggerChangeState()}render(){this.updateTitle(),this.renderView(),"month"===this.view&&this.bindMonthViewDropdowns()}updateTitle(){const t=this.container.querySelector(".sc-title"),e=this.container.querySelector(".sc-title-month"),s=this.container.querySelector(".sc-title-year");switch(this.view){case"month":e&&s?(e.textContent=this.options.months[this.currentDate.getMonth()],s.textContent=this.currentDate.getFullYear()):t.innerHTML=`<span class="sc-title-month">${this.options.months[this.currentDate.getMonth()]}</span> <span class="sc-title-year">${this.currentDate.getFullYear()}</span>`;break;case"week":const n=this.getWeekStart(this.currentDate),i=new Date(n);i.setDate(i.getDate()+6),t.textContent=`${this.options.months[n.getMonth()]} ${n.getDate()} - ${this.options.months[i.getMonth()]} ${i.getDate()}, ${n.getFullYear()}`;break;case"day":t.textContent=`${this.options.weekdays[this.currentDate.getDay()]}, ${this.options.months[this.currentDate.getMonth()]} ${this.currentDate.getDate()}, ${this.currentDate.getFullYear()}`}}renderView(){const t=this.container.querySelector(".sc-view-container");switch(this.view){case"month":t.innerHTML=this.renderMonthView();break;case"week":t.innerHTML=this.renderWeekView();break;case"day":t.innerHTML=this.renderDayView()}}renderMonthView(){const t=this.currentDate.getFullYear(),e=this.currentDate.getMonth(),s=new Date(t,e,1),n=new Date(s);n.setDate(n.getDate()-s.getDay());let i=`\n <div class="sc-month-view ${this.fulldayMode?"sc-fullday":""}">\n <div class="sc-weekdays">\n ${this.options.weekdays.map((t,e)=>`<div class="sc-weekday">${this.getWeekdayDisplay(e)}</div>`).join("")}\n </div>\n <div class="sc-days-grid">\n `;for(let t=0;t<6;t++)for(let s=0;s<7;s++){const a=new Date(n);a.setDate(n.getDate()+7*t+s);const o=a.getMonth()===e,r=this.isToday(a),c=this.getEventsForDate(a);if(this.fulldayMode){const t=this.addEventPlaceholders(c,a);i+=`\n <div class="sc-day ${o?"sc-current-month":"sc-other-month"} ${r?"sc-today":""}" data-date="${a.toISOString()}">\n <div class="sc-day-number">${a.getDate()}</div>\n ${t.map(t=>{if(t.isPlaceholder)return'<div class="sc-event-month sc-event-single sc-event-placeholder"> </div>';const e=this.getEventPosition(t.event,a),s=e.isSingle?"sc-event-single":e.isStart?"sc-event-start":e.isEnd?"sc-event-end":"sc-event-middle",n=e.isSingle||e.isStart?t.event.title:"",i=this.applyEventStyles(t.event);return`<div class="sc-event-month ${s}" data-event-id="${t.event.id}" ${i}><span class="sc-event-text">${n}</span></div>`}).join("")}\n </div>\n `}else{const t=c.length,e=0===t?"":1===t?`1 ${this.options.labels.event}`:`${t} ${this.options.labels.events}`;i+=`\n <div class="sc-day sc-day-count-mode ${o?"sc-current-month":"sc-other-month"} ${r?"sc-today":""}" data-date="${a.toISOString()}">\n <div class="sc-day-content">\n <div class="sc-day-number">${a.getDate()}</div>\n ${e?`<div class="sc-event-count">${e}</div>`:""}\n </div>\n </div>\n `}}return i+="\n </div>\n </div>\n ",i}renderWeekView(){const t=this.getWeekStart(this.currentDate),e=[];for(let s=0;s<7;s++){const n=new Date(t);n.setDate(n.getDate()+s),e.push(n)}if(this.fulldayMode)return this.renderWeekFullday(e);const s=this.generateTimeSlots();return`\n <div class="sc-week-view">\n <div class="sc-time-column-header"></div>\n ${e.map(t=>`\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.getWeekdayDisplay(t.getDay())}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n `).join("")}\n ${s.map(t=>`\n <div class="sc-time-slot">${t}</div>\n ${e.map(e=>{const s=this.getEventsForTimeSlot(e,t);return`\n <div class="sc-day-column ${this.isToday(e)?"sc-today":""}" data-date="${e.toISOString()}" data-time="${t}">\n <div class="sc-half-hour-line"></div>\n ${s.map(t=>{const e=this.applyEventStyles(t);return`<div class="sc-event-time" data-event-id="${t.id}" ${e}>${t.title}</div>`}).join("")}\n </div>\n `}).join("")}\n `).join("")}\n </div>\n `}renderWeekFullday(t){return`\n <div class="sc-week-view sc-fullday">\n <div class="sc-weekdays">\n ${t.map(t=>`<div class="sc-weekday">${this.getWeekdayDisplay(t.getDay())}</div>`).join("")}\n </div>\n <div class="sc-days-grid">\n ${t.map(t=>{const e=this.isToday(t),s=this.getEventsForDate(t),n=this.addEventPlaceholders(s,t);return`\n <div class="sc-day ${e?"sc-today":""}" data-date="${t.toISOString()}">\n <div class="sc-day-number">${t.getDate()}</div>\n ${n.map(e=>{if(e.isPlaceholder)return'<div class="sc-event-week sc-event-single sc-event-placeholder"> </div>';const s=this.getEventPosition(e.event,t),n=s.isSingle?"sc-event-single":s.isStart?"sc-event-start":s.isEnd?"sc-event-end":"sc-event-middle",i=s.isSingle||s.isStart?e.event.title:"",a=this.applyEventStyles(e.event);return`<div class="sc-event-week ${n}" data-event-id="${e.event.id}" ${a}><span class="sc-event-text">${i}</span></div>`}).join("")}\n </div>\n `}).join("")}\n </div>\n </div>\n `}renderDayView(){const t=new Date(this.currentDate);if(this.fulldayMode){const e=this.getEventsForDate(t);return`\n <div class="sc-day-view sc-fullday">\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.options.weekdays[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n <div class="sc-day-events" data-date="${t.toISOString()}">\n ${e.map(e=>{const s=this.getEventPosition(e,t),n=s.isSingle?"sc-event-single":s.isStart?"sc-event-start":s.isEnd?"sc-event-end":"sc-event-middle",i=e.title,a=this.applyEventStyles(e);return`<div class="sc-event-day ${n}" data-event-id="${e.id}" ${a}><span class="sc-event-text">${i}</span></div>`}).join("")}\n </div>\n </div>\n `}const e=this.generateTimeSlots();return`\n <div class="sc-day-view">\n <div class="sc-time-column-header"></div>\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.options.weekdays[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n ${e.map(e=>{const s=this.getEventsForTimeSlot(t,e);return`\n <div class="sc-time-slot">${e}</div>\n <div class="sc-day-column ${this.isToday(t)?"sc-today":""}" data-date="${t.toISOString()}" data-time="${e}">\n <div class="sc-half-hour-line"></div>\n ${s.map(t=>{const e=this.applyEventStyles(t);return`<div class="sc-event-time" data-event-id="${t.id}" ${e}>${t.title}</div>`}).join("")}\n </div>\n `}).join("")}\n </div>\n `}generateTimeSlots(){const t=[],e=this.options.startHour,s=this.options.endHour,n=new Date;n.setHours(e,0,0,0),t.push(`${this.options.labels.before} ${this.formatTime(n)}`);for(let n=e;n<=s;n++){const e=new Date;e.setHours(n,0,0,0),t.push(this.formatTime(e))}const i=new Date;return i.setHours(s,0,0,0),t.push(`${this.options.labels.after} ${this.formatTime(i)}`),t}formatTime(t){return t.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}getWeekStart(t){const e=new Date(t);return e.setDate(e.getDate()-e.getDay()),e}getVisibleDateRange(){const t=this.currentDate.getFullYear(),e=this.currentDate.getMonth();switch(this.view){case"month":const s=new Date(t,e,1),n=new Date(s);n.setDate(n.getDate()-s.getDay());const i=new Date(n);return i.setDate(i.getDate()+41),{startDate:n,endDate:i};case"week":const a=this.getWeekStart(this.currentDate),o=new Date(a);return o.setDate(o.getDate()+6),{startDate:a,endDate:o};case"day":const r=new Date(this.currentDate);r.setHours(0,0,0,0);const c=new Date(r);return c.setHours(23,59,59,999),{startDate:r,endDate:c}}}isToday(t){const e=new Date;return t.toDateString()===e.toDateString()}isRangeCovered(t,e){for(const s of this.fetchedRanges)if(s.startDate<=t&&s.endDate>=e)return!0;return!1}getMissingRanges(t,e){const s=[];let n=new Date(t);const i=[...this.fetchedRanges].sort((t,e)=>t.startDate-e.startDate);for(const t of i){if(n<t.startDate&&n<=e){const i=new Date(Math.min(t.startDate.getTime()-864e5,e.getTime()));i>=n&&s.push({startDate:new Date(n),endDate:i})}t.endDate>=n&&(n=new Date(t.endDate.getTime()+864e5))}return n<=e&&s.push({startDate:new Date(n),endDate:new Date(e)}),s}addFetchedRange(t,e,s){const n={startDate:new Date(t),endDate:new Date(e),events:s};this.allCachedEvents=this.allCachedEvents.concat(s),this.fetchedRanges.push(n),this.mergeOverlappingRanges()}mergeOverlappingRanges(){this.fetchedRanges.sort((t,e)=>t.startDate-e.startDate);const t=[];for(const e of this.fetchedRanges)if(0===t.length||t[t.length-1].endDate<e.startDate)t.push(e);else{const s=t[t.length-1];s.endDate=new Date(Math.max(s.endDate.getTime(),e.endDate.getTime())),s.events=s.events.concat(e.events)}this.fetchedRanges=t,this.allCachedEvents=[];for(const t of this.fetchedRanges)this.allCachedEvents=this.allCachedEvents.concat(t.events)}getAllEventsForRange(){if(this.eventsCallback){const t=this.getVisibleDateRange();if(this.isRangeCovered(t.startDate,t.endDate))return this.allCachedEvents;const e=this.getMissingRanges(t.startDate,t.endDate);for(const t of e){const e=this.eventsCallback(t);this.addFetchedRange(t.startDate,t.endDate,e)}return this.allCachedEvents}return this.events||[]}getEventsForDate(t){return this.getAllEventsForRange().filter(e=>{const s=new Date(e.startDate||e.date),n=new Date(e.endDate||e.date),i=new Date(t);return i.setHours(0,0,0,0),s.setHours(0,0,0,0),n.setHours(0,0,0,0),i>=s&&i<=n}).sort((t,e)=>{const s=this.isMultiDayEvent(t),n=this.isMultiDayEvent(e);return s&&!n?-1:!s&&n?1:0})}getEventsForTimeSlot(t,e){if(this.fulldayMode)return this.getEventsForDate(t);return this.getEventsForDate(t).filter(t=>{if(!t.time)return!1;const s=this.parseTimeString(t.time);if(!s)return!1;const n=s.getHours(),i=this.options.startHour,a=this.options.endHour;if(e.startsWith(this.options.labels.before))return n<i;if(e.startsWith(this.options.labels.after))return n>a;const o=this.parseTimeString(e);if(!o)return!1;return n===o.getHours()})}parseTimeString(t){try{const e=new Date,[s,n]=t.split(" "),[i,a]=s.split(":").map(Number);let o=i;return"PM"===n&&12!==i&&(o+=12),"AM"===n&&12===i&&(o=0),e.setHours(o,a||0,0,0),e}catch(t){return null}}isMultiDayEvent(t){const e=new Date(t.startDate||t.date),s=new Date(t.endDate||t.date);return e.setHours(0,0,0,0),s.setHours(0,0,0,0),e.getTime()!==s.getTime()}addEventPlaceholders(t,e){if(!this.fulldayMode)return t.map(t=>({event:t,isPlaceholder:!1}));const s=this.getWeekStart(e),n=[];for(let t=0;t<7;t++){const e=new Date(s);e.setDate(e.getDate()+t),n.push(e)}const i=new Map;n.forEach(t=>{this.getEventsForDate(t).filter(t=>this.isMultiDayEvent(t)).forEach(t=>{i.has(t.id)||i.set(t.id,t)})});const a=Array.from(i.values()).sort((t,e)=>new Date(t.startDate||t.date)-new Date(e.startDate||e.date)),o=[],r=t.filter(t=>this.isMultiDayEvent(t)),c=t.filter(t=>!this.isMultiDayEvent(t));return a.forEach(t=>{const n=r.find(e=>e.id===t.id);if(n)o.push({event:n,isPlaceholder:!1});else{const n=new Date(t.startDate||t.date),i=new Date(t.endDate||t.date);n.setHours(0,0,0,0),i.setHours(0,0,0,0);const a=new Date(e);a.setHours(0,0,0,0);const r=new Date(s);r.setHours(0,0,0,0);const c=new Date(s);c.setDate(c.getDate()+6),c.setHours(0,0,0,0);n<=c&&(i>=r&&i<=c)&&a>i&&o.push({isPlaceholder:!0,eventId:t.id})}}),c.forEach(t=>{o.push({event:t,isPlaceholder:!1})}),o}getEventPosition(t,e){const s=new Date(t.startDate||t.date),n=new Date(t.endDate||t.date),i=new Date(e);s.setHours(0,0,0,0),n.setHours(0,0,0,0),i.setHours(0,0,0,0);return{isStart:i.getTime()===s.getTime(),isEnd:i.getTime()===n.getTime(),isMiddle:i>s&&i<n,isSingle:s.getTime()===n.getTime()}}addEvent(t){this.eventsCallback||(this.events.push(t),this.render())}removeEvent(t){this.eventsCallback||(this.events=this.events.filter(e=>e.id!==t),this.render())}setEvents(t){"function"==typeof t?(this.eventsCallback=t,this.events=[]):(this.events=t||[],this.eventsCallback=null),this.fetchedRanges=[],this.allCachedEvents=[],this.render()}setFulldayMode(t){this.fulldayMode=t,this.render(),this.triggerChangeState()}goToDate(t){this.currentDate=new Date(t),this.render(),this.triggerChangeState()}goToToday(){this.currentDate=new Date,this.render(),this.triggerChangeState()}setGridBorders(t){this.options.gridBorders=t,this.applyGridBorders()}setEventBorders(t){this.options.eventBorders=t,this.applyEventBorders(),this.render()}setEventBorderColor(t){this.options.eventBorderColor=t,this.applyEventBorders(),this.render()}setShowMonthButton(t){this.options.showMonthButton=t,this.applyViewButtonVisibility()}setShowWeekButton(t){this.options.showWeekButton=t,this.applyViewButtonVisibility()}setShowDayButton(t){this.options.showDayButton=t,this.applyViewButtonVisibility()}setViewButtonsVisibility(t,e,s){this.options.showMonthButton=t,this.options.showWeekButton=e,this.options.showDayButton=s,this.applyViewButtonVisibility()}setShowNavigation(t){this.options.showNavigation=t,this.applyNavigationVisibility()}setDefaultEventColor(t){this.options.defaultEventColor=t,this.applyEventColor(),this.render()}getEventBaseCssStyle(t){const e=t.color,s=!0===t.colorIsGradient,n=e?t.color:this.options.defaultEventColor;if(!n)return{};const i=(" "+n).slice(1),a=this.hexToRgb(i);if(!a)return{};const o=this.getOptimalTextColor(a);if(s){const t={r:Math.min(255,a.r+40),g:Math.min(255,a.g+40),b:Math.min(255,a.b+40)};return{"background-image":`linear-gradient(to bottom, ${this.rgbToHex(t)} 0px, ${n} 100%)`,"background-repeat":"repeat-x",color:o,"background-color":n}}return{"background-color":n,color:o}}getOptimalTextColor(t){return this.getLuminance(t)>.5?"#000000":"#ffffff"}getLuminance(t){return.2126*this.getRelativeLuminanceComponent(t.r/255)+.7152*this.getRelativeLuminanceComponent(t.g/255)+.0722*this.getRelativeLuminanceComponent(t.b/255)}getRelativeLuminanceComponent(t){return t<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4)}getContrastRatio(t,e){const s=this.getLuminance(t),n=this.getLuminance(e);return(Math.max(s,n)+.05)/(Math.min(s,n)+.05)}hexToRgb(t){const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null}rgbToHex(t){return"#"+(1<<24|t.r<<16|t.g<<8|t.b).toString(16).slice(1)}applyEventStyles(t){const e=this.getEventBaseCssStyle(t),s=Object.entries(e).map(([t,e])=>`${t}: ${e}`).join("; ");return s?`style="${s}"`:""}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-calendar-js",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A clean, modern, and feature-rich JavaScript calendar component with zero dependencies. Responsive design and intuitive navigation.",
|
|
5
5
|
"main": "dist/SimpleCalendarJs.min.js",
|
|
6
6
|
"style": "dist/SimpleCalendarJs.min.css",
|