simple-calendar-js 3.0.2 → 3.0.4

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 CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  A lightweight, zero-dependency JavaScript calendar component with internationalization support and framework wrappers for React, Vue, and Angular.
4
4
 
5
- ![Version](https://img.shields.io/badge/version-3.0.2-blue.svg)
5
+ ![Version](https://img.shields.io/badge/version-3.0.4-blue.svg)
6
6
  ![License](https://img.shields.io/badge/license-See%20LICENSE-green.svg)
7
7
 
8
8
  ## Features
9
9
 
10
10
  - **Zero Dependencies** - Pure vanilla JavaScript, no external libraries
11
- - **Three View Modes** - Month, week, and day views with smooth transitions
11
+ - **Four View Modes** - Month, week, day, and list views with smooth transitions
12
12
  - **Full Internationalization** - Built on Intl.DateTimeFormat API for native locale support
13
13
  - **Framework Wrappers** - Official React, Vue 3, and Angular components included
14
14
  - **Module System Support** - Works as UMD (CommonJS, AMD, ES modules, or browser global)
@@ -159,7 +159,7 @@ export class CalendarComponent {
159
159
 
160
160
  | Option | Type | Default | Description |
161
161
  |--------|------|---------|-------------|
162
- | `defaultView` | string | `'month'` | Initial view: `'month'`, `'week'`, or `'day'` |
162
+ | `defaultView` | string | `'month'` | Initial view: `'month'`, `'week'`, `'day'`, or `'list'` |
163
163
  | `defaultDate` | Date | `new Date()` | Initial date to display |
164
164
  | `weekStartsOn` | number | `0` | First day of week: `0` (Sunday) or `1` (Monday) |
165
165
  | `locale` | string | `'default'` | Locale code for Intl API (e.g., `'en-US'`, `'fr-FR'`, `'ja-JP'`) |
@@ -168,6 +168,7 @@ export class CalendarComponent {
168
168
  | `showTimeInItems` | boolean | `true` | Show time in event items |
169
169
  | `showGridLines` | boolean | `true` | Show calendar grid lines |
170
170
  | `showBorder` | boolean | `true` | Show calendar outer border |
171
+ | `maxEventsPerCell` | number | `3` | Maximum events per cell in month view before showing "+N more". Set to `0` for unlimited (show all events) |
171
172
  | `showToolbar` | boolean | `true` | Show the toolbar |
172
173
  | `showTodayButton` | boolean | `true` | Show "Today" button |
173
174
  | `showNavigation` | boolean | `true` | Show prev/next navigation arrows |
@@ -175,7 +176,8 @@ export class CalendarComponent {
175
176
  | `showYearPicker` | boolean | `true` | Enable year picker dropdown (month view) |
176
177
  | `showViewSwitcher` | boolean | `true` | Show view switcher buttons |
177
178
  | `showTooltips` | boolean | `true` | Show tooltips on hover for events |
178
- | `enabledViews` | string[] | `['month', 'week', 'day']` | Available view modes |
179
+ | `listDaysForward` | number | `30` | Number of days forward to show in list view |
180
+ | `enabledViews` | string[] | `['month', 'week', 'day']` | Available view modes. Add `'list'` to enable list view |
179
181
  | `fetchEvents` | function | `null` | Async function to fetch events: `async (start, end) => Event[]` |
180
182
  | `onEventClick` | function | `null` | Callback when event is clicked: `(event, mouseEvent) => void` |
181
183
  | `onSlotClick` | function | `null` | Callback when time slot is clicked: `(date, mouseEvent) => void` |
@@ -224,6 +226,91 @@ const events = [
224
226
  ];
225
227
  ```
226
228
 
229
+ ## Timezone Handling
230
+
231
+ SimpleCalendarJs relies on JavaScript's native Date object for timezone handling, which means events are automatically displayed in the **user's local timezone**.
232
+
233
+ ### How It Works
234
+
235
+ 1. **Automatic Conversion**: JavaScript Date objects automatically convert to the user's browser timezone
236
+ 2. **No Configuration Needed**: The calendar has no timezone settings - it uses the browser's timezone
237
+ 3. **Backend Responsibility**: Your backend should send timezone-aware date strings
238
+
239
+ ### Best Practices
240
+
241
+ **✓ Recommended - Send ISO 8601 strings with timezone:**
242
+
243
+ ```javascript
244
+ fetchEvents: async (start, end) => {
245
+ const response = await fetch(`/api/events?start=${start}&end=${end}`);
246
+ const events = await response.json();
247
+
248
+ // Backend returns ISO 8601 strings with timezone info
249
+ // Example: "2024-03-15T10:00:00Z" (UTC)
250
+ // or: "2024-03-15T10:00:00-05:00" (EST)
251
+
252
+ return events.map(event => ({
253
+ ...event,
254
+ start: new Date(event.start), // Automatically converts to local timezone
255
+ end: new Date(event.end)
256
+ }));
257
+ }
258
+ ```
259
+
260
+ **✗ Avoid - Sending dates without timezone info:**
261
+
262
+ ```javascript
263
+ // BAD: "2024-03-15T10:00:00" (no timezone)
264
+ // JavaScript interprets this as LOCAL time, not UTC
265
+ // This can cause issues for users in different timezones
266
+ ```
267
+
268
+ ### Example: Multi-Timezone Scenario
269
+
270
+ **Scenario**: Your server stores events in UTC, users are in different timezones
271
+
272
+ ```javascript
273
+ // Server returns (stored in UTC):
274
+ {
275
+ "title": "Team Meeting",
276
+ "start": "2024-03-15T14:00:00Z", // 2:00 PM UTC
277
+ "end": "2024-03-15T15:00:00Z" // 3:00 PM UTC
278
+ }
279
+
280
+ // User in New York (EST, UTC-5):
281
+ // Calendar displays: 9:00 AM - 10:00 AM
282
+
283
+ // User in London (GMT, UTC+0):
284
+ // Calendar displays: 2:00 PM - 3:00 PM
285
+
286
+ // User in Tokyo (JST, UTC+9):
287
+ // Calendar displays: 11:00 PM - 12:00 AM
288
+ ```
289
+
290
+ ### Important Notes
291
+
292
+ - **Storage**: Always store events in UTC in your database
293
+ - **API Format**: Send dates as ISO 8601 strings with timezone information
294
+ - **Display**: The calendar automatically shows events in the user's local timezone
295
+ - **No Timezone Selector**: The calendar doesn't provide UI to change timezone - it always uses the browser's timezone
296
+ - **Time Formatting**: Uses `Intl.DateTimeFormat` which respects the user's locale and timezone
297
+
298
+ ### Example Backend Response
299
+
300
+ ```json
301
+ {
302
+ "events": [
303
+ {
304
+ "id": 1,
305
+ "title": "Global Team Standup",
306
+ "start": "2024-03-15T14:00:00Z",
307
+ "end": "2024-03-15T14:30:00Z",
308
+ "description": "Daily standup - all timezones welcome"
309
+ }
310
+ ]
311
+ }
312
+ ```
313
+
227
314
  ## Tooltips
228
315
 
229
316
  SimpleCalendarJs includes built-in tooltip support for displaying additional event information on hover.
@@ -343,10 +430,56 @@ calendar.goToDate(new Date('2024-12-25'));
343
430
  // Jump to today
344
431
  calendar.goToToday();
345
432
 
433
+ // Refresh calendar (clear cache and re-fetch events)
434
+ calendar.refresh();
435
+
346
436
  // Cleanup (important for SPAs)
347
437
  calendar.destroy();
348
438
  ```
349
439
 
440
+ ### Event Caching
441
+
442
+ SimpleCalendarJs intelligently caches fetched events to minimize unnecessary network requests:
443
+
444
+ - **Smart fetch strategy**: Always fetches the **full month grid** (including leading/trailing days), regardless of current view
445
+ - **Automatic caching**: Events are cached by date range after each fetch
446
+ - **Efficient reuse**: View switches and navigation within the cached range use cached data (no spinner, instant display)
447
+ - **Fetch only when needed**: New data is fetched only when the required range extends beyond the cache
448
+ - **Manual refresh**: Call `calendar.refresh()` when events change externally (added, updated, or deleted)
449
+
450
+ **How it works:**
451
+ - When in **month/week/day views**: Fetches the full month grid for the current date context
452
+ - When in **list view**: Fetches the specific forward range (configurable with `listDaysForward`)
453
+ - Subsequent navigation within the same month uses cached data
454
+ - Navigation to a different month triggers a new fetch for that month's full grid
455
+
456
+ **Example behavior:**
457
+ ```javascript
458
+ // Month view Feb 2026: Fetches Jan 28 - Mar 1 (full month grid)
459
+ calendar.setView('month'); // ✓ Fetch: Jan 28 - Mar 1
460
+
461
+ // Switch to week view (Feb 20-26): Already in cache, no fetch
462
+ calendar.setView('week'); // ✗ No fetch (instant)
463
+
464
+ // Switch to day view (Feb 22): Already in cache, no fetch
465
+ calendar.setView('day'); // ✗ No fetch (instant)
466
+
467
+ // Navigate next week (Feb 27 - Mar 5): Extends beyond cache
468
+ // Fetches March's full month grid (Mar 1 - Apr 5)
469
+ calendar.navigate(1); // ✓ Fetch: Mar 1 - Apr 5
470
+
471
+ // Navigate next week (Mar 6-12): Already in March's cache
472
+ calendar.navigate(1); // ✗ No fetch (instant)
473
+
474
+ // Navigate previous week (Feb 27 - Mar 5): Partially outside cache
475
+ // Fetches February's full month grid (Jan 28 - Mar 1)
476
+ calendar.navigate(-1); // ✓ Fetch: Jan 28 - Mar 1
477
+
478
+ // After external event changes, refresh to clear cache
479
+ addEventToDatabase(newEvent);
480
+ calendar.refresh(); // ✓ Fetch (clears cache)
481
+ ```
482
+
350
483
  ## Internationalization
351
484
 
352
485
  SimpleCalendarJs uses the native Intl.DateTimeFormat API for full locale support. Simply pass a valid locale code:
@@ -411,6 +544,39 @@ new SimpleCalendarJs('#calendar', {
411
544
 
412
545
  **Note:** Different locales may format the same option differently. For example, `'short'` in `pt-PT` might display "Sábado" while `pt-BR` displays "Sáb." - this is controlled by the browser's Intl API based on locale conventions. Use `weekdayFormat` to control the length/style consistently.
413
546
 
547
+ ### List View
548
+
549
+ Display upcoming events in a chronological list format. Perfect for mobile devices and quick overview of upcoming events.
550
+
551
+ ```javascript
552
+ const calendar = new SimpleCalendarJs('#calendar', {
553
+ defaultView: 'list', // Start in list view
554
+ enabledViews: ['month', 'week', 'day', 'list'], // Include list in views
555
+ listDaysForward: 30 // Show next 30 days (default)
556
+ });
557
+ ```
558
+
559
+ **Features:**
560
+ - Shows events from current date/time forward
561
+ - Groups events by date with sticky headers
562
+ - Color-coded event indicators
563
+ - Mobile-friendly design
564
+ - Configurable range with `listDaysForward` option
565
+
566
+ **Example:**
567
+
568
+ ```javascript
569
+ // Show next 90 days in list view
570
+ new SimpleCalendarJs('#calendar', {
571
+ defaultView: 'list',
572
+ enabledViews: ['month', 'list'], // Only month and list views
573
+ listDaysForward: 90,
574
+ fetchEvents: async (start, end) => {
575
+ // Your event fetching logic
576
+ }
577
+ });
578
+ ```
579
+
414
580
  ## Dark Mode
415
581
 
416
582
  Dark mode is automatically detected from the user's system preferences. You can also manually toggle it:
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SimpleCalendarJs v3.0.2 — simple-calendar-js.css
2
+ * SimpleCalendarJs v3.0.4 — simple-calendar-js.css
3
3
  * A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
4
4
  *
5
5
  * @author Pedro Lopes <simplecalendarjs@gmail.com>
@@ -9,4 +9,4 @@
9
9
  * All styles scoped under .uc-calendar to prevent leaking.
10
10
  * Override any --cal-* variable in :root or on .uc-calendar.
11
11
  */
12
- :root{--cal-bg:#ffffff;--cal-text:#111827;--cal-text-subtle:#6b7280;--cal-text-muted:#9ca3af;--cal-border:#e5e7eb;--cal-border-strong:#d1d5db;--cal-primary:#4f46e5;--cal-primary-dark:#4338ca;--cal-primary-light:#eef2ff;--cal-event-bg:var(--cal-primary);--cal-event-text:#ffffff;--cal-event-border-radius:3px;--cal-today-bg:#eef2ff;--cal-today-text:var(--cal-primary);--cal-hover:#f9fafb;--cal-hover-strong:#f3f4f6;--cal-selected-bg:#ede9fe;--cal-font-family:inherit;--cal-font-size:13px;--cal-time-col-width:64px;--cal-hour-height:60px;--cal-cell-min-height:112px;--cal-event-height:22px;--cal-event-gap:2px;--cal-header-day-height:30px;--cal-day-name-height:36px;--cal-now-color:#ef4444;--cal-toolbar-bg:var(--cal-bg);--cal-radius:8px;--cal-shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.06);--cal-transition:150ms ease;--cal-tooltip-bg:#1f2937;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#374151;--cal-tooltip-shadow:0 4px 12px rgba(0, 0, 0, 0.15);--cal-tooltip-max-width:250px;--cal-tooltip-padding:8px 12px;--cal-tooltip-radius:6px;--cal-tooltip-font-size:12px;--cal-tooltip-offset:8px}.uc-calendar{font-family:var(--cal-font-family);font-size:var(--cal-font-size);color:var(--cal-text);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:var(--cal-radius);overflow:visible;display:flex;flex-direction:column;position:relative;min-height:500px;-webkit-font-smoothing:antialiased}.uc-calendar *,.uc-calendar ::after,.uc-calendar ::before{box-sizing:border-box;margin:0;padding:0}.uc-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;background:var(--cal-toolbar-bg);border-bottom:1px solid var(--cal-border);flex-shrink:0;flex-wrap:wrap}.uc-toolbar-section{display:flex;align-items:center;gap:4px}.uc-toolbar-center{flex:1;display:flex;justify-content:center;position:relative}.uc-title{font-size:15px;font-weight:600;color:var(--cal-text);white-space:nowrap;letter-spacing:-.01em;display:flex;align-items:baseline;gap:5px}.uc-title-main{text-transform:capitalize}.uc-year-btn{background:0 0;border:none;font:inherit;font-weight:600;font-size:15px;color:var(--cal-primary);cursor:pointer;padding:1px 4px;border-radius:4px;line-height:inherit;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px;transition:background var(--cal-transition),color var(--cal-transition)}.uc-year-btn.uc-open,.uc-year-btn:hover{background:var(--cal-primary-light);text-decoration:none}.uc-year-picker{position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:10px;box-shadow:0 4px 24px rgba(0,0,0,.12);padding:10px;z-index:200;min-width:210px}.uc-year-picker-nav{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.uc-year-range{font-size:12px;font-weight:600;color:var(--cal-text-subtle)}.uc-year-nav-btn{background:0 0;border:none;font-size:18px;line-height:1;color:var(--cal-text);cursor:pointer;padding:2px 7px;border-radius:5px;font-family:inherit;transition:background var(--cal-transition)}.uc-year-nav-btn:hover{background:var(--cal-hover-strong)}.uc-year-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:4px}.uc-year-item{background:0 0;border:none;border-radius:6px;font-size:13px;font-weight:500;font-family:inherit;color:var(--cal-text);cursor:pointer;padding:7px 4px;text-align:center;transition:background var(--cal-transition),color var(--cal-transition)}.uc-year-item:hover{background:var(--cal-hover-strong)}.uc-year-item.uc-today-year{color:var(--cal-primary);font-weight:700}.uc-year-item.uc-active{background:var(--cal-primary);color:#fff;font-weight:700}.uc-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:1px solid var(--cal-border);background:var(--cal-bg);color:var(--cal-text);border-radius:6px;padding:5px 10px;font-size:13px;font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;line-height:1.4;transition:background var(--cal-transition),border-color var(--cal-transition),color var(--cal-transition);user-select:none}.uc-btn:hover{background:var(--cal-hover-strong);border-color:var(--cal-border-strong)}.uc-btn:active{background:var(--cal-selected-bg)}.uc-btn:focus-visible{outline:2px solid var(--cal-primary);outline-offset:2px}.uc-nav-btn{font-size:18px;padding:4px 8px;line-height:1;border-color:transparent;background:0 0}.uc-nav-btn:hover{border-color:var(--cal-border)}.uc-today-btn{padding:5px 12px}.uc-today-btn.uc-active{background:var(--cal-primary-light);color:var(--cal-primary);font-weight:600;border-color:transparent}.uc-view-switcher{display:flex;border:1px solid var(--cal-border);border-radius:6px;overflow:hidden}.uc-view-btn{border:none;border-radius:0;border-right:1px solid var(--cal-border);background:0 0;color:var(--cal-text-subtle);font-weight:500;padding:5px 11px}.uc-view-btn:last-child{border-right:none}.uc-view-btn:hover{background:var(--cal-hover-strong);color:var(--cal-text);border-color:transparent}.uc-view-btn.uc-active{background:var(--cal-primary-light);color:var(--cal-primary);font-weight:600}.uc-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.7);z-index:100;pointer-events:none}.uc-spinner{width:28px;height:28px;border:3px solid var(--cal-border);border-top-color:var(--cal-primary);border-radius:50%;animation:uc-spin .6s linear infinite}@keyframes uc-spin{to{transform:rotate(360deg)}}.uc-view-container{flex:1;overflow:visible;display:flex;flex-direction:column}.uc-month-view{display:flex;flex-direction:column;flex:1;overflow:visible}.uc-month-header{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--cal-border);flex-shrink:0}.uc-month-day-name{height:var(--cal-day-name-height,36px);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--cal-text-subtle);user-select:none}.uc-month-body{flex:1;display:flex;flex-direction:column;overflow:visible}.uc-week-row{flex:1;position:relative;min-height:var(--cal-cell-min-height);border-bottom:1px solid var(--cal-border)}.uc-week-row:last-child{border-bottom:none}.uc-week-cells{position:absolute;inset:0;display:grid;grid-template-columns:repeat(7,1fr);z-index:1}.uc-day-cell{border-right:1px solid var(--cal-border);padding:4px 6px 4px 6px;cursor:pointer;transition:background var(--cal-transition);min-height:var(--cal-cell-min-height)}.uc-day-cell:last-child{border-right:none}.uc-day-cell:hover{background:var(--cal-hover)}.uc-day-cell.uc-today{background:var(--cal-today-bg)}.uc-day-cell.uc-other-month .uc-day-number{color:var(--cal-text-muted)}.uc-day-number{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;font-size:13px;font-weight:500;border-radius:50%;color:var(--cal-text);cursor:pointer;transition:background var(--cal-transition),color var(--cal-transition);line-height:1}.uc-day-number:hover{background:var(--cal-hover-strong)}.uc-today .uc-day-number{background:var(--cal-primary);color:#fff;font-weight:700}.uc-today .uc-day-number:hover{background:var(--cal-primary-dark)}.uc-week-events{position:absolute;inset:0;z-index:2;pointer-events:none;overflow:visible}.uc-event-bar{position:absolute;height:var(--cal-event-height);background:var(--cal-event-bg);color:var(--cal-event-text);border-radius:var(--cal-event-border-radius);display:flex;align-items:center;gap:4px;padding:0 6px;cursor:pointer;pointer-events:auto;white-space:nowrap;font-size:12px;font-weight:500;transition:filter var(--cal-transition),opacity var(--cal-transition);z-index:3}.uc-event-bar:hover{filter:brightness(.92);z-index:1000}.uc-event-bar:active{filter:brightness(.85)}.uc-event-bar.uc-continues-left{border-top-left-radius:0;border-bottom-left-radius:0}.uc-event-bar.uc-continues-right{border-top-right-radius:0;border-bottom-right-radius:0}.uc-event-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.uc-event-time{font-size:11px;opacity:.85;flex-shrink:0}.uc-more-link{position:absolute;height:16px;display:flex;align-items:center;padding:0 6px;font-size:10px;font-weight:600;color:var(--cal-text-subtle);cursor:pointer;pointer-events:auto;border-radius:var(--cal-event-border-radius);white-space:nowrap;transition:background var(--cal-transition),color var(--cal-transition);z-index:3}.uc-more-link:hover{background:var(--cal-hover-strong);color:var(--cal-text)}.uc-day-view,.uc-week-view{display:flex;flex-direction:column;flex:1;overflow:visible}.uc-week-header{display:flex;border-bottom:1px solid var(--cal-border);flex-shrink:0;background:var(--cal-bg)}.uc-time-gutter-spacer{width:var(--cal-time-col-width);flex-shrink:0;border-right:1px solid var(--cal-border)}.uc-all-day-label{display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--cal-text-muted);white-space:nowrap}.uc-week-day-headers{flex:1;display:grid;grid-template-columns:repeat(7,1fr)}.uc-week-day-headers.uc-day-header-single{grid-template-columns:1fr}.uc-week-day-header{padding:8px 4px;display:flex;flex-direction:column;align-items:center;gap:2px;border-right:1px solid var(--cal-border);cursor:default}.uc-week-day-header:last-child{border-right:none}.uc-week-day-name{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--cal-text-subtle);user-select:none}.uc-week-day-num{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;font-size:16px;font-weight:600;border-radius:50%;color:var(--cal-text);cursor:pointer;transition:background var(--cal-transition),color var(--cal-transition)}.uc-week-day-num:hover{background:var(--cal-hover-strong)}.uc-week-day-header.uc-today .uc-week-day-name{color:var(--cal-primary)}.uc-week-day-header.uc-today .uc-week-day-num{background:var(--cal-primary);color:#fff}.uc-week-day-header.uc-today .uc-week-day-num:hover{background:var(--cal-primary-dark)}.uc-all-day-section{display:flex;border-bottom:1px solid var(--cal-border);flex-shrink:0;background:var(--cal-bg)}.uc-all-day-events{flex:1;position:relative;min-height:calc(var(--cal-event-height) + 6px);padding:2px 0}.uc-time-body{flex:1;overflow-y:auto;overflow-x:visible;position:relative}.uc-time-body::-webkit-scrollbar{width:6px}.uc-time-body::-webkit-scrollbar-track{background:0 0}.uc-time-body::-webkit-scrollbar-thumb{background:var(--cal-border-strong);border-radius:3px}.uc-time-grid-inner{display:flex;flex-direction:row;height:calc(24 * var(--cal-hour-height));position:relative}.uc-time-gutter{width:var(--cal-time-col-width);flex-shrink:0;border-right:1px solid var(--cal-border);position:relative}.uc-hour-cell{height:var(--cal-hour-height);position:relative;display:flex;align-items:flex-start;justify-content:flex-end;padding-right:8px}.uc-hour-label{font-size:11px;font-weight:500;color:var(--cal-text-subtle);user-select:none;pointer-events:none;white-space:nowrap;transform:translateY(-50%);margin-top:1px}.uc-time-columns{flex:1;display:grid;grid-template-columns:repeat(var(--uc-col-count,7),1fr);position:relative}.uc-time-col{position:relative;border-right:1px solid var(--cal-border);height:calc(24 * var(--cal-hour-height));cursor:pointer}.uc-time-col:last-child{border-right:none}.uc-time-col.uc-today{background:var(--cal-today-bg)}.uc-hour-row{height:var(--cal-hour-height);border-bottom:1px solid var(--cal-border);position:relative;pointer-events:none}.uc-half-hour-line{position:absolute;top:50%;left:0;right:0;border-top:1px dashed var(--cal-border);pointer-events:none}.uc-timed-event{position:absolute;background:var(--cal-event-bg);color:var(--cal-event-text);border-radius:var(--cal-event-border-radius);padding:3px 6px;cursor:pointer;display:flex;flex-direction:column;gap:1px;font-size:12px;font-weight:500;border-left:3px solid rgba(0,0,0,.15);transition:filter var(--cal-transition),box-shadow var(--cal-transition);z-index:2;min-height:18px}.uc-timed-event:hover{filter:brightness(.92);box-shadow:0 2px 8px rgba(0,0,0,.15);z-index:1000}.uc-timed-event .uc-event-title{font-weight:600;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-timed-event .uc-event-time{font-size:11px;opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-timed-event--short{flex-direction:row;align-items:center;gap:4px}.uc-timed-event--short .uc-event-time{flex-shrink:0}.uc-timed-event--short .uc-event-title{flex:1;min-width:0}.uc-now-indicator{position:absolute;left:0;right:0;pointer-events:none;z-index:10;display:flex;align-items:center}.uc-now-dot{width:10px;height:10px;border-radius:50%;background:var(--cal-now-color);flex-shrink:0;margin-left:-5px}.uc-now-line{flex:1;height:2px;background:var(--cal-now-color)}.uc-time-col:hover{background:var(--cal-hover)}.uc-time-col.uc-today:hover{background:color-mix(in srgb,var(--cal-today-bg) 80%,var(--cal-hover-strong))}@supports not (color:color-mix(in srgb,red 50%,blue)){.uc-time-col.uc-today:hover{background:var(--cal-today-bg)}}.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::after,.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::before{opacity:1;visibility:visible;transition-delay:0.4s}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{content:attr(data-tooltip);position:absolute;bottom:calc(100% + var(--cal-tooltip-offset));left:50%;transform:translateX(-50%);background:var(--cal-tooltip-bg);color:var(--cal-tooltip-text);border:1px solid var(--cal-tooltip-border);box-shadow:var(--cal-tooltip-shadow);padding:var(--cal-tooltip-padding);border-radius:var(--cal-tooltip-radius);font-size:var(--cal-tooltip-font-size);font-weight:500;line-height:1.4;max-width:var(--cal-tooltip-max-width);width:max-content;white-space:pre-wrap;word-wrap:break-word;text-align:left;z-index:1000;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::after{content:'';position:absolute;bottom:calc(100% + var(--cal-tooltip-offset) - 5px);left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid var(--cal-tooltip-bg);z-index:1002;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{filter:drop-shadow(0 1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-left::before{left:auto;right:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-left::after{left:auto;right:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::before{left:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::after{left:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-bottom::before{bottom:auto;top:calc(100% + var(--cal-tooltip-offset));filter:drop-shadow(0 -1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-bottom::after{bottom:auto;top:calc(100% + var(--cal-tooltip-offset) - 5px);border-top:5px solid var(--cal-tooltip-bg);border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:none;transform:translateX(-50%) rotate(180deg)}@media (max-width:768px){.uc-toolbar{padding:8px 10px}.uc-title{font-size:14px}.uc-toolbar-center{order:-1;width:100%;flex:none}.uc-toolbar-section{justify-content:center}.uc-view-btn{padding:5px 8px;font-size:12px}:root{--cal-time-col-width:52px;--cal-hour-height:52px;--cal-cell-min-height:88px;--cal-event-height:20px}.uc-week-day-num{width:24px;height:24px;font-size:13px}.uc-week-day-name{font-size:10px}.uc-hour-cell:nth-child(odd) .uc-hour-label{visibility:hidden}}@media (max-width:480px){.uc-today-btn{display:none}.uc-toolbar-section.uc-toolbar-right{gap:2px}}.uc-calendar.uc-no-grid .uc-day-cell,.uc-calendar.uc-no-grid .uc-day-header-row,.uc-calendar.uc-no-grid .uc-day-name-row,.uc-calendar.uc-no-grid .uc-grid-line,.uc-calendar.uc-no-grid .uc-hour-cell,.uc-calendar.uc-no-grid .uc-time-col,.uc-calendar.uc-no-grid .uc-time-gutter,.uc-calendar.uc-no-grid .uc-week-day-header,.uc-calendar.uc-no-grid .uc-week-row{border:none!important}.uc-calendar.uc-no-grid .uc-all-day-section{border-top:none!important;border-left:none!important;border-right:none!important}.uc-calendar.uc-no-border{border:none!important}.uc-calendar.uc-dark,.uc-dark .uc-calendar{--cal-bg:#1f2937;--cal-text:#f9fafb;--cal-text-subtle:#9ca3af;--cal-text-muted:#6b7280;--cal-border:#374151;--cal-border-strong:#4b5563;--cal-hover:#374151;--cal-hover-strong:#4b5563;--cal-selected-bg:#78716c;--cal-today-bg:#57534e;--cal-primary-light:#57534e;--cal-toolbar-bg:#111827;--cal-tooltip-bg:#374151;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#4b5563}@media print{.uc-toolbar{display:none}.uc-time-body{overflow:visible}.uc-calendar{border:none}}
12
+ :root{--cal-bg:#ffffff;--cal-text:#111827;--cal-text-subtle:#6b7280;--cal-text-muted:#9ca3af;--cal-border:#e5e7eb;--cal-border-strong:#d1d5db;--cal-primary:#4f46e5;--cal-primary-dark:#4338ca;--cal-primary-light:#eef2ff;--cal-event-bg:var(--cal-primary);--cal-event-text:#ffffff;--cal-event-border-radius:3px;--cal-today-bg:#eef2ff;--cal-today-text:var(--cal-primary);--cal-hover:#f9fafb;--cal-hover-strong:#f3f4f6;--cal-selected-bg:#ede9fe;--cal-font-family:inherit;--cal-font-size:13px;--cal-time-col-width:64px;--cal-hour-height:60px;--cal-cell-min-height:112px;--cal-event-height:22px;--cal-event-gap:2px;--cal-header-day-height:30px;--cal-day-name-height:36px;--cal-now-color:#ef4444;--cal-toolbar-bg:var(--cal-bg);--cal-radius:8px;--cal-shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.06);--cal-transition:150ms ease;--cal-tooltip-bg:#1f2937;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#374151;--cal-tooltip-shadow:0 4px 12px rgba(0, 0, 0, 0.15);--cal-tooltip-max-width:250px;--cal-tooltip-padding:8px 12px;--cal-tooltip-radius:6px;--cal-tooltip-font-size:12px;--cal-tooltip-offset:8px}.uc-calendar{font-family:var(--cal-font-family);font-size:var(--cal-font-size);color:var(--cal-text);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:var(--cal-radius);overflow:visible;display:flex;flex-direction:column;position:relative;min-height:500px;-webkit-font-smoothing:antialiased}.uc-calendar *,.uc-calendar ::after,.uc-calendar ::before{box-sizing:border-box;margin:0;padding:0}.uc-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 14px;background:var(--cal-toolbar-bg);border-bottom:1px solid var(--cal-border);border-radius:var(--cal-radius) var(--cal-radius) 0 0;flex-shrink:0;flex-wrap:wrap}.uc-toolbar-section{display:flex;align-items:center;gap:4px}.uc-toolbar-center{flex:1;display:flex;justify-content:center;position:relative}.uc-title{font-size:15px;font-weight:600;color:var(--cal-text);white-space:nowrap;letter-spacing:-.01em;display:flex;align-items:baseline;gap:5px}.uc-title-main{text-transform:capitalize}.uc-year-btn{background:0 0;border:none;font:inherit;font-weight:600;font-size:15px;color:var(--cal-primary);cursor:pointer;padding:1px 4px;border-radius:4px;line-height:inherit;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:2px;transition:background var(--cal-transition),color var(--cal-transition)}.uc-year-btn.uc-open,.uc-year-btn:hover{background:var(--cal-primary-light);text-decoration:none}.uc-year-picker{position:absolute;top:calc(100% + 8px);left:50%;transform:translateX(-50%);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:10px;box-shadow:0 4px 24px rgba(0,0,0,.12);padding:10px;z-index:200;min-width:210px}.uc-year-picker-nav{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.uc-year-range{font-size:12px;font-weight:600;color:var(--cal-text-subtle)}.uc-year-nav-btn{background:0 0;border:none;font-size:18px;line-height:1;color:var(--cal-text);cursor:pointer;padding:2px 7px;border-radius:5px;font-family:inherit;transition:background var(--cal-transition)}.uc-year-nav-btn:hover{background:var(--cal-hover-strong)}.uc-year-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:4px}.uc-year-item{background:0 0;border:none;border-radius:6px;font-size:13px;font-weight:500;font-family:inherit;color:var(--cal-text);cursor:pointer;padding:7px 4px;text-align:center;transition:background var(--cal-transition),color var(--cal-transition)}.uc-year-item:hover{background:var(--cal-hover-strong)}.uc-year-item.uc-today-year{color:var(--cal-primary);font-weight:700}.uc-year-item.uc-active{background:var(--cal-primary);color:#fff;font-weight:700}.uc-btn{display:inline-flex;align-items:center;justify-content:center;gap:4px;border:1px solid var(--cal-border);background:var(--cal-bg);color:var(--cal-text);border-radius:6px;padding:5px 10px;font-size:13px;font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;line-height:1.4;transition:background var(--cal-transition),border-color var(--cal-transition),color var(--cal-transition);user-select:none}.uc-btn:hover{background:var(--cal-hover-strong);border-color:var(--cal-border-strong)}.uc-btn:active{background:var(--cal-selected-bg)}.uc-btn:focus-visible{outline:2px solid var(--cal-primary);outline-offset:2px}.uc-nav-btn{font-size:18px;padding:4px 8px;line-height:1;border-color:transparent;background:0 0}.uc-nav-btn:hover{border-color:var(--cal-border)}.uc-today-btn{padding:5px 12px}.uc-today-btn.uc-active{background:var(--cal-primary-light);color:var(--cal-primary);font-weight:600;border-color:transparent}.uc-view-switcher{display:flex;border:1px solid var(--cal-border);border-radius:6px;overflow:hidden}.uc-view-btn{border:none;border-radius:0;border-right:1px solid var(--cal-border);background:0 0;color:var(--cal-text-subtle);font-weight:500;padding:5px 11px}.uc-view-btn:last-child{border-right:none}.uc-view-btn:hover{background:var(--cal-hover-strong);color:var(--cal-text);border-color:transparent}.uc-view-btn.uc-active{background:var(--cal-primary-light);color:var(--cal-primary);font-weight:600}.uc-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.7);z-index:100;pointer-events:none}.uc-spinner{width:28px;height:28px;border:3px solid var(--cal-border);border-top-color:var(--cal-primary);border-radius:50%;animation:uc-spin .6s linear infinite}@keyframes uc-spin{to{transform:rotate(360deg)}}.uc-view-container{flex:1;overflow:visible;display:flex;flex-direction:column}.uc-view-container:first-child .uc-month-header,.uc-view-container:first-child .uc-week-header{border-radius:var(--cal-radius) var(--cal-radius) 0 0}.uc-month-view{display:flex;flex-direction:column;flex:1;overflow:visible;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-month-header{display:grid;grid-template-columns:repeat(7,1fr);border-bottom:1px solid var(--cal-border);flex-shrink:0}.uc-month-day-name{height:var(--cal-day-name-height,36px);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--cal-text-subtle);user-select:none}.uc-month-body{flex:1;display:flex;flex-direction:column;overflow:visible}.uc-week-row{flex:1;position:relative;min-height:var(--cal-cell-min-height);border-bottom:1px solid var(--cal-border)}.uc-week-row:last-child{border-bottom:none;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-week-cells{position:absolute;inset:0;display:grid;grid-template-columns:repeat(7,1fr);z-index:1}.uc-day-cell{border-right:1px solid var(--cal-border);padding:4px 6px 4px 6px;cursor:pointer;transition:background var(--cal-transition);min-height:var(--cal-cell-min-height)}.uc-day-cell:last-child{border-right:none}.uc-day-cell:hover{background:var(--cal-hover)}.uc-day-cell.uc-today{background:var(--cal-today-bg)}.uc-day-cell.uc-other-month .uc-day-number{color:var(--cal-text-muted)}.uc-day-number{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;font-size:13px;font-weight:500;border-radius:50%;color:var(--cal-text);cursor:pointer;transition:background var(--cal-transition),color var(--cal-transition);line-height:1}.uc-day-number:hover{background:var(--cal-hover-strong)}.uc-today .uc-day-number{background:var(--cal-primary);color:#fff;font-weight:700}.uc-today .uc-day-number:hover{background:var(--cal-primary-dark)}.uc-week-events{position:absolute;inset:0;z-index:2;pointer-events:none;overflow:visible}.uc-event-bar{position:absolute;height:var(--cal-event-height);background:var(--cal-event-bg);color:var(--cal-event-text);border-radius:var(--cal-event-border-radius);display:flex;align-items:center;gap:4px;padding:0 6px;cursor:pointer;pointer-events:auto;white-space:nowrap;font-size:12px;font-weight:500;transition:filter var(--cal-transition),opacity var(--cal-transition);z-index:3}.uc-event-bar:hover{filter:brightness(.92);z-index:1000}.uc-event-bar:active{filter:brightness(.85)}.uc-event-bar.uc-continues-left{border-top-left-radius:0;border-bottom-left-radius:0}.uc-event-bar.uc-continues-right{border-top-right-radius:0;border-bottom-right-radius:0}.uc-event-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.uc-event-time{font-size:11px;opacity:.85;flex-shrink:0}.uc-more-link{position:absolute;height:16px;display:flex;align-items:center;padding:0 6px;font-size:10px;font-weight:600;color:var(--cal-text-subtle);cursor:pointer;pointer-events:auto;border-radius:var(--cal-event-border-radius);white-space:nowrap;transition:background var(--cal-transition),color var(--cal-transition);z-index:3}.uc-more-link:hover{background:var(--cal-hover-strong);color:var(--cal-text)}.uc-day-view,.uc-week-view{display:flex;flex-direction:column;flex:1;overflow:visible;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-week-header{display:flex;border-bottom:1px solid var(--cal-border);flex-shrink:0;background:var(--cal-bg)}.uc-time-gutter-spacer{width:var(--cal-time-col-width);flex-shrink:0;border-right:1px solid var(--cal-border)}.uc-all-day-label{display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--cal-text-muted);white-space:nowrap}.uc-week-day-headers{flex:1;display:grid;grid-template-columns:repeat(7,1fr)}.uc-week-day-headers.uc-day-header-single{grid-template-columns:1fr}.uc-week-day-header{padding:8px 4px;display:flex;flex-direction:column;align-items:center;gap:2px;border-right:1px solid var(--cal-border);cursor:default}.uc-week-day-header:last-child{border-right:none}.uc-week-day-name{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--cal-text-subtle);user-select:none}.uc-week-day-num{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;font-size:16px;font-weight:600;border-radius:50%;color:var(--cal-text);cursor:pointer;transition:background var(--cal-transition),color var(--cal-transition)}.uc-week-day-num:hover{background:var(--cal-hover-strong)}.uc-week-day-header.uc-today .uc-week-day-name{color:var(--cal-primary)}.uc-week-day-header.uc-today .uc-week-day-num{background:var(--cal-primary);color:#fff}.uc-week-day-header.uc-today .uc-week-day-num:hover{background:var(--cal-primary-dark)}.uc-all-day-section{display:flex;border-bottom:1px solid var(--cal-border);flex-shrink:0;background:var(--cal-bg)}.uc-all-day-events{flex:1;position:relative;min-height:calc(var(--cal-event-height) + 6px);padding:2px 0}.uc-time-body{flex:1;overflow-y:auto;overflow-x:visible;position:relative;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-time-body::-webkit-scrollbar{width:6px}.uc-time-body::-webkit-scrollbar-track{background:0 0}.uc-time-body::-webkit-scrollbar-thumb{background:var(--cal-border-strong);border-radius:3px}.uc-time-grid-inner{display:flex;flex-direction:row;height:calc(24 * var(--cal-hour-height));position:relative}.uc-time-gutter{width:var(--cal-time-col-width);flex-shrink:0;border-right:1px solid var(--cal-border);position:relative}.uc-hour-cell{height:var(--cal-hour-height);position:relative;display:flex;align-items:flex-start;justify-content:flex-end;padding-right:8px}.uc-hour-label{font-size:11px;font-weight:500;color:var(--cal-text-subtle);user-select:none;pointer-events:none;white-space:nowrap;transform:translateY(-50%);margin-top:1px}.uc-time-columns{flex:1;display:grid;grid-template-columns:repeat(var(--uc-col-count,7),1fr);position:relative}.uc-time-col{position:relative;border-right:1px solid var(--cal-border);height:calc(24 * var(--cal-hour-height));cursor:pointer}.uc-time-col:last-child{border-right:none}.uc-time-col.uc-today{background:var(--cal-today-bg)}.uc-hour-row{height:var(--cal-hour-height);border-bottom:1px solid var(--cal-border);position:relative;pointer-events:none}.uc-half-hour-line{position:absolute;top:50%;left:0;right:0;border-top:1px dashed var(--cal-border);pointer-events:none}.uc-timed-event{position:absolute;background:var(--cal-event-bg);color:var(--cal-event-text);border-radius:var(--cal-event-border-radius);padding:3px 6px;cursor:pointer;display:flex;flex-direction:column;gap:1px;font-size:12px;font-weight:500;border-left:3px solid rgba(0,0,0,.15);transition:filter var(--cal-transition),box-shadow var(--cal-transition);z-index:2;min-height:18px}.uc-timed-event:hover{filter:brightness(.92);box-shadow:0 2px 8px rgba(0,0,0,.15);z-index:1000}.uc-timed-event .uc-event-title{font-weight:600;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-timed-event .uc-event-time{font-size:11px;opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-timed-event--short{flex-direction:row;align-items:center;gap:4px}.uc-timed-event--short .uc-event-time{flex-shrink:0}.uc-timed-event--short .uc-event-title{flex:1;min-width:0}.uc-now-indicator{position:absolute;left:0;right:0;pointer-events:none;z-index:10;display:flex;align-items:center}.uc-now-dot{width:10px;height:10px;border-radius:50%;background:var(--cal-now-color);flex-shrink:0;margin-left:-5px}.uc-now-line{flex:1;height:2px;background:var(--cal-now-color)}.uc-list-view{padding:0;overflow:visible;border-radius:0 0 var(--cal-radius) var(--cal-radius)}.uc-list-empty{display:flex;align-items:center;justify-content:center;min-height:300px;color:var(--cal-text-muted);font-size:14px}.uc-list-date-group{margin-bottom:24px;overflow:visible}.uc-list-date-group:last-child{margin-bottom:0}.uc-list-events{background:var(--cal-bg);overflow:visible}.uc-list-event{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-bottom:1px solid var(--cal-border);cursor:pointer;transition:background var(--cal-transition);position:relative;overflow:visible}.uc-list-date-header{font-size:14px;font-weight:600;color:var(--cal-text);padding:12px 16px;background:var(--cal-bg-secondary);border-bottom:1px solid var(--cal-border);position:sticky;top:0;z-index:1;overflow:visible}.uc-list-event:hover{background:var(--cal-hover)}.uc-list-event:last-child{border-bottom:none}.uc-list-event-indicator{width:8px;height:8px;border-radius:50%;margin-top:4px;flex-shrink:0}.uc-list-event-time{font-size:13px;font-weight:500;color:var(--cal-text-secondary);min-width:80px;flex-shrink:0}.uc-list-event-content{flex:1;min-width:0}.uc-list-event-title{font-size:14px;font-weight:500;color:var(--cal-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::after,.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::before{opacity:1;visibility:visible;transition-delay:0.4s}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{content:attr(data-tooltip);position:absolute;bottom:calc(100% + var(--cal-tooltip-offset));left:50%;transform:translateX(-50%);background:var(--cal-tooltip-bg);color:var(--cal-tooltip-text);border:1px solid var(--cal-tooltip-border);box-shadow:var(--cal-tooltip-shadow);padding:var(--cal-tooltip-padding);border-radius:var(--cal-tooltip-radius);font-size:var(--cal-tooltip-font-size);font-weight:500;line-height:1.4;max-width:var(--cal-tooltip-max-width);width:max-content;white-space:pre-wrap;word-wrap:break-word;text-align:left;z-index:1000;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::after{content:'';position:absolute;bottom:calc(100% + var(--cal-tooltip-offset) - 5px);left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid var(--cal-tooltip-bg);z-index:1002;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{filter:drop-shadow(0 1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-left::before{left:auto;right:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-left::after{left:auto;right:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::before{left:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::after{left:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-bottom::before{bottom:auto;top:calc(100% + var(--cal-tooltip-offset));filter:drop-shadow(0 -1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-bottom::after{bottom:auto;top:calc(100% + var(--cal-tooltip-offset) - 5px);border-top:5px solid var(--cal-tooltip-bg);border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:none;transform:translateX(-50%) rotate(180deg)}@media (max-width:768px){.uc-toolbar{padding:8px 10px}.uc-title{font-size:14px}.uc-toolbar-center{order:-1;width:100%;flex:none}.uc-toolbar-section{justify-content:center}.uc-view-btn{padding:5px 8px;font-size:12px}:root{--cal-time-col-width:52px;--cal-hour-height:52px;--cal-cell-min-height:88px;--cal-event-height:20px}.uc-week-day-num{width:24px;height:24px;font-size:13px}.uc-week-day-name{font-size:10px}.uc-hour-cell:nth-child(odd) .uc-hour-label{visibility:hidden}}@media (max-width:480px){.uc-today-btn{display:none}.uc-toolbar-section.uc-toolbar-right{gap:2px}}.uc-calendar.uc-no-grid .uc-day-cell,.uc-calendar.uc-no-grid .uc-day-header-row,.uc-calendar.uc-no-grid .uc-day-name-row,.uc-calendar.uc-no-grid .uc-grid-line,.uc-calendar.uc-no-grid .uc-hour-cell,.uc-calendar.uc-no-grid .uc-time-col,.uc-calendar.uc-no-grid .uc-time-gutter,.uc-calendar.uc-no-grid .uc-week-day-header,.uc-calendar.uc-no-grid .uc-week-row{border:none!important}.uc-calendar.uc-no-grid .uc-all-day-section{border-top:none!important;border-left:none!important;border-right:none!important}.uc-calendar.uc-no-border{border:none!important}.uc-calendar.uc-dark,.uc-dark .uc-calendar{--cal-bg:#1f2937;--cal-text:#f9fafb;--cal-text-subtle:#9ca3af;--cal-text-muted:#6b7280;--cal-border:#374151;--cal-border-strong:#4b5563;--cal-hover:#374151;--cal-hover-strong:#4b5563;--cal-selected-bg:#78716c;--cal-today-bg:#57534e;--cal-primary-light:#57534e;--cal-toolbar-bg:#111827;--cal-tooltip-bg:#374151;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#4b5563}@media print{.uc-toolbar{display:none}.uc-time-body{overflow:visible}.uc-calendar{border:none}}
@@ -1,14 +1,14 @@
1
1
  /**
2
- * SimpleCalendarJs v3.0.2
2
+ * SimpleCalendarJs v3.0.4
3
3
  * A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
4
4
  *
5
5
  * @author Pedro Lopes <simplecalendarjs@gmail.com>
6
6
  * @homepage https://www.simplecalendarjs.com
7
7
  * @license SEE LICENSE IN LICENSE
8
8
  */
9
- !function(t,e){"undefined"!=typeof module&&module.exports?module.exports=e():"function"==typeof define&&define.amd?define([],e):t.SimpleCalendarJs=e()}("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:this,function(){"use strict";function t(t){const e=new Date(t);return e.setHours(0,0,0,0),e}function e(t){const e=new Date(t);return e.setHours(23,59,59,999),e}function a(t,e){const a=new Date(t);return a.setDate(a.getDate()+e),a}function n(t,e){return t.getFullYear()===e.getFullYear()&&t.getMonth()===e.getMonth()&&t.getDate()===e.getDate()}function s(t){return n(t,new Date)}function i(e,a){return Math.floor((t(a)-t(e))/864e5)}function o(t){return t instanceof Date?t:new Date(t)}function r(e,n){const s=e.getDay(),i=t(a(e,-((s-n+7)%7)));return Array.from({length:7},(t,e)=>a(i,e))}function c(t,e,n){const s=new Date(t,e,1),i=new Date(t,e+1,0),o=(s.getDay()-n+7)%7,r=7*Math.ceil((o+i.getDate())/7),c=a(s,-o);return Array.from({length:r},(t,e)=>a(c,e))}function l(t,e,a){const n={hour:"numeric",hour12:!a};return 0!==t.getMinutes()&&(n.minute="2-digit"),new Intl.DateTimeFormat(e,n).format(t)}function d(t,e,a){return Array.from({length:7},(n,s)=>{const i=new Date(2025,0,5+(e+s)%7);return new Intl.DateTimeFormat(t,{weekday:a}).format(i)})}function h(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}const u={"en-US":{today:"Today",month:"Month",week:"Week",day:"Day"},"en-GB":{today:"Today",month:"Month",week:"Week",day:"Day"},"es-ES":{today:"Hoy",month:"Mes",week:"Semana",day:"Día"},"es-MX":{today:"Hoy",month:"Mes",week:"Semana",day:"Día"},"fr-FR":{today:"Aujourd'hui",month:"Mois",week:"Semaine",day:"Jour"},"fr-CA":{today:"Aujourd'hui",month:"Mois",week:"Semaine",day:"Jour"},"de-DE":{today:"Heute",month:"Monat",week:"Woche",day:"Tag"},"it-IT":{today:"Oggi",month:"Mese",week:"Settimana",day:"Giorno"},"pt-PT":{today:"Hoje",month:"Mês",week:"Semana",day:"Dia"},"pt-BR":{today:"Hoje",month:"Mês",week:"Semana",day:"Dia"},"nl-NL":{today:"Vandaag",month:"Maand",week:"Week",day:"Dag"},"pl-PL":{today:"Dzisiaj",month:"Miesiąc",week:"Tydzień",day:"Dzień"},"ru-RU":{today:"Сегодня",month:"Месяц",week:"Неделя",day:"День"},"tr-TR":{today:"Bugün",month:"Ay",week:"Hafta",day:"Gün"},"sv-SE":{today:"Idag",month:"Månad",week:"Vecka",day:"Dag"},"da-DK":{today:"I dag",month:"Måned",week:"Uge",day:"Dag"},"fi-FI":{today:"Tänään",month:"Kuukausi",week:"Viikko",day:"Päivä"},"no-NO":{today:"I dag",month:"Måned",week:"Uke",day:"Dag"},"cs-CZ":{today:"Dnes",month:"Měsíc",week:"Týden",day:"Den"},"hu-HU":{today:"Ma",month:"Hónap",week:"Hét",day:"Nap"},"ro-RO":{today:"Astăzi",month:"Lună",week:"Săptămână",day:"Zi"},"el-GR":{today:"Σήμερα",month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα"},"ja-JP":{today:"今日",month:"月",week:"週",day:"日"},"ko-KR":{today:"오늘",month:"월",week:"주",day:"일"},"zh-CN":{today:"今天",month:"月",week:"周",day:"日"},"zh-TW":{today:"今天",month:"月",week:"週",day:"日"},"ar-SA":{today:"اليوم",month:"شهر",week:"أسبوع",day:"يوم"},"he-IL":{today:"היום",month:"חודש",week:"שבוע",day:"יום"},"hi-IN":{today:"आज",month:"महीना",week:"सप्ताह",day:"दिन"},"th-TH":{today:"วันนี้",month:"เดือน",week:"สัปดาห์",day:"วัน"},"vi-VN":{today:"Hôm nay",month:"Tháng",week:"Tuần",day:"Ngày"},"id-ID":{today:"Hari ini",month:"Bulan",week:"Minggu",day:"Hari"},"ms-MY":{today:"Hari ini",month:"Bulan",week:"Minggu",day:"Hari"},"uk-UA":{today:"Сьогодні",month:"Місяць",week:"Тиждень",day:"День"}};function v(t,e){const a=t.split("-")[0];return(u[t]||u[a]||u["en-US"])[e]}function p(t){return!!t.allDay||!n(t.start,t.end)}function y(a,s){const o=t(s[0]),r=e(s[s.length-1]),c=a.filter(t=>t.start<=r&&t.end>=o).map(a=>({...a,_visStart:new Date(Math.max(a.start.getTime(),o.getTime())),_visEnd:new Date(Math.min(a.end.getTime(),r.getTime())),_isStart:t(a.start)>=o,_isEnd:e(a.end)<=r}));c.sort((t,e)=>{const a=t.start-e.start;if(0!==a)return a;const n=i(t._visStart,t._visEnd);return i(e._visStart,e._visEnd)-n||(t._origStart||t.start)-(e._origStart||e.start)});const l=[],d=[];for(const t of c){const e=s.findIndex(e=>n(e,t._visStart)),a=s.findIndex(e=>n(e,t._visEnd)),i=-1===e?0:e,o=-1===a?s.length-1:a;let r=l.findIndex(t=>t<=i);-1===r?(r=l.length,l.push(o+1)):l[r]=o+1,d.push({event:t,startCol:i,endCol:o,slot:r,isStart:t._isStart,isEnd:t._isEnd})}return d}
9
+ !function(t,e){"undefined"!=typeof module&&module.exports?module.exports=e():"function"==typeof define&&define.amd?define([],e):t.SimpleCalendarJs=e()}("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:this,function(){"use strict";function t(t){const e=new Date(t);return e.setHours(0,0,0,0),e}function e(t){const e=new Date(t);return e.setHours(23,59,59,999),e}function a(t,e){const a=new Date(t);return a.setDate(a.getDate()+e),a}function s(t,e){return t.getFullYear()===e.getFullYear()&&t.getMonth()===e.getMonth()&&t.getDate()===e.getDate()}function n(t){return s(t,new Date)}function i(e,a){return Math.floor((t(a)-t(e))/864e5)}function o(t){return t instanceof Date?t:new Date(t)}function l(e,s){const n=e.getDay(),i=t(a(e,-((n-s+7)%7)));return Array.from({length:7},(t,e)=>a(i,e))}function r(t,e,s){const n=new Date(t,e,1),i=new Date(t,e+1,0),o=(n.getDay()-s+7)%7,l=7*Math.ceil((o+i.getDate())/7),r=a(n,-o);return Array.from({length:l},(t,e)=>a(r,e))}function c(t,e,a){const s={hour:"numeric",hour12:!a};return 0!==t.getMinutes()&&(s.minute="2-digit"),new Intl.DateTimeFormat(e,s).format(t)}function d(t,e,a){return Array.from({length:7},(s,n)=>{const i=new Date(2025,0,5+(e+n)%7);return new Intl.DateTimeFormat(t,{weekday:a}).format(i)})}function h(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}const u={"en-US":{today:"Today",month:"Month",week:"Week",day:"Day",list:"List",allDay:"All-Day"},"en-GB":{today:"Today",month:"Month",week:"Week",day:"Day",list:"List",allDay:"All-Day"},"es-ES":{today:"Hoy",month:"Mes",week:"Semana",day:"Día",list:"Lista",allDay:"Todo el día"},"es-MX":{today:"Hoy",month:"Mes",week:"Semana",day:"Día",list:"Lista",allDay:"Todo el día"},"fr-FR":{today:"Aujourd'hui",month:"Mois",week:"Semaine",day:"Jour",list:"Liste",allDay:"Toute la journée"},"fr-CA":{today:"Aujourd'hui",month:"Mois",week:"Semaine",day:"Jour",list:"Liste",allDay:"Toute la journée"},"de-DE":{today:"Heute",month:"Monat",week:"Woche",day:"Tag",list:"Liste",allDay:"Ganztägig"},"it-IT":{today:"Oggi",month:"Mese",week:"Settimana",day:"Giorno",list:"Elenco",allDay:"Tutto il giorno"},"pt-PT":{today:"Hoje",month:"Mês",week:"Semana",day:"Dia",list:"Lista",allDay:"Dia todo"},"pt-BR":{today:"Hoje",month:"Mês",week:"Semana",day:"Dia",list:"Lista",allDay:"Dia todo"},"nl-NL":{today:"Vandaag",month:"Maand",week:"Week",day:"Dag",list:"Lijst",allDay:"Hele dag"},"pl-PL":{today:"Dzisiaj",month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Lista",allDay:"Cały dzień"},"ru-RU":{today:"Сегодня",month:"Месяц",week:"Неделя",day:"День",list:"Список",allDay:"Весь день"},"tr-TR":{today:"Bugün",month:"Ay",week:"Hafta",day:"Gün",list:"Liste",allDay:"Tüm gün"},"sv-SE":{today:"Idag",month:"Månad",week:"Vecka",day:"Dag",list:"Lista",allDay:"Heldag"},"da-DK":{today:"I dag",month:"Måned",week:"Uge",day:"Dag",list:"Liste",allDay:"Hele dagen"},"fi-FI":{today:"Tänään",month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Lista",allDay:"Koko päivä"},"no-NO":{today:"I dag",month:"Måned",week:"Uke",day:"Dag",list:"Liste",allDay:"Hele dagen"},"cs-CZ":{today:"Dnes",month:"Měsíc",week:"Týden",day:"Den",list:"Seznam",allDay:"Celý den"},"hu-HU":{today:"Ma",month:"Hónap",week:"Hét",day:"Nap",list:"Lista",allDay:"Egész nap"},"ro-RO":{today:"Astăzi",month:"Lună",week:"Săptămână",day:"Zi",list:"Listă",allDay:"Toată ziua"},"el-GR":{today:"Σήμερα",month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα",list:"Λίστα",allDay:"Ολοήμερο"},"ja-JP":{today:"今日",month:"月",week:"週",day:"日",list:"リスト",allDay:"終日"},"ko-KR":{today:"오늘",month:"월",week:"주",day:"일",list:"목록",allDay:"종일"},"zh-CN":{today:"今天",month:"月",week:"周",day:"日",list:"列表",allDay:"全天"},"zh-TW":{today:"今天",month:"月",week:"週",day:"日",list:"列表",allDay:"全天"},"ar-SA":{today:"اليوم",month:"شهر",week:"أسبوع",day:"يوم",list:"قائمة",allDay:"طوال اليوم"},"he-IL":{today:"היום",month:"חודש",week:"שבוע",day:"יום",list:"רשימה",allDay:"כל היום"},"hi-IN":{today:"आज",month:"महीना",week:"सप्ताह",day:"दिन",list:"सूची",allDay:"पूरे दिन"},"th-TH":{today:"วันนี้",month:"เดือน",week:"สัปดาห์",day:"วัน",list:"รายการ",allDay:"ตลอดวัน"},"vi-VN":{today:"Hôm nay",month:"Tháng",week:"Tuần",day:"Ngày",list:"Danh sách",allDay:"Cả ngày"},"id-ID":{today:"Hari ini",month:"Bulan",week:"Minggu",day:"Hari",list:"Daftar",allDay:"Sepanjang hari"},"ms-MY":{today:"Hari ini",month:"Bulan",week:"Minggu",day:"Hari",list:"Senarai",allDay:"Sepanjang hari"},"uk-UA":{today:"Сьогодні",month:"Місяць",week:"Тиждень",day:"День",list:"Список",allDay:"Весь день"}};function v(t,e){const a=t.split("-")[0];return(u[t]||u[a]||u["en-US"])[e]}function p(t){return!!t.allDay||!s(t.start,t.end)}function y(a,n){const o=t(n[0]),l=e(n[n.length-1]),r=a.filter(t=>t.start<=l&&t.end>=o).map(a=>({...a,_visStart:new Date(Math.max(a.start.getTime(),o.getTime())),_visEnd:new Date(Math.min(a.end.getTime(),l.getTime())),_isStart:t(a.start)>=o,_isEnd:e(a.end)<=l}));r.sort((t,e)=>{const a=t.start-e.start;if(0!==a)return a;const s=i(t._visStart,t._visEnd);return i(e._visStart,e._visEnd)-s||(t._origStart||t.start)-(e._origStart||e.start)});const c=[],d=[];for(const t of r){const e=n.findIndex(e=>s(e,t._visStart)),a=n.findIndex(e=>s(e,t._visEnd)),i=-1===e?0:e,o=-1===a?n.length-1:a;let l=c.findIndex(t=>t<=i);-1===l?(l=c.length,c.push(o+1)):c[l]=o+1,d.push({event:t,startCol:i,endCol:o,slot:l,isStart:t._isStart,isEnd:t._isEnd})}return d}
10
10
  /* ============================================================
11
11
  ES MODULE EXPORT
12
12
  Also available as window.SimpleCalendarJs via the IIFE wrapper.
13
13
  ============================================================ */
14
- return class{constructor(e,a={}){if("string"==typeof e){if(this._el=document.querySelector(e),!this._el)throw new Error(`SimpleCalendarJs: no element for "${e}"`)}else this._el=e;this._opts=Object.assign({defaultView:"month",defaultDate:null,weekStartsOn:0,locale:"default",weekdayFormat:"short",use24Hour:!1,showTimeInItems:!0,showGridLines:!0,showToolbar:!0,showTodayButton:!0,showNavigation:!0,showTitle:!0,showYearPicker:!0,showViewSwitcher:!0,showTooltips:!0,showBorder:!0,enabledViews:["month","week","day"],fetchEvents:null,onEventClick:null,onSlotClick:null,onViewChange:null,onNavigate:null},a),this._view=this._opts.defaultView,this._date=t(this._opts.defaultDate||new Date),this._events=[],this._nowInterval=null,this._yearPickerOpen=!1,this._yearPickerBase=0,this._yearOutsideHandler=null,this._root=document.createElement("div"),this._root.className="uc-calendar",this._opts.showGridLines||this._root.classList.add("uc-no-grid"),this._opts.showBorder||this._root.classList.add("uc-no-border"),this._el.appendChild(this._root),this._onClick=this._handleClick.bind(this),this._root.addEventListener("click",this._onClick),this._onMouseMove=this._handleTooltipPosition.bind(this),this._root.addEventListener("mouseover",this._onMouseMove),this._fetchAndRender(),this._startNowUpdater()}setView(t){t!==this._view&&(this._view=t,this._opts.onViewChange&&this._opts.onViewChange(t),this._fetchAndRender())}navigate(t){const e=new Date(this._date);"month"===this._view?(e.setMonth(e.getMonth()+t),e.setDate(1)):"week"===this._view?e.setDate(e.getDate()+7*t):e.setDate(e.getDate()+t),this._date=e;const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender()}goToToday(){this._date=t(new Date);const e=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(e.start,e.end),this._fetchAndRender()}goToDate(e){this._date=t(o(e)),this._fetchAndRender()}destroy(){this._nowInterval&&clearInterval(this._nowInterval),this._yearOutsideHandler&&document.removeEventListener("click",this._yearOutsideHandler),this._root.removeEventListener("click",this._onClick),this._root.removeEventListener("mouseover",this._onMouseMove),this._root.remove()}_getRange(){if("month"===this._view){const a=c(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}if("week"===this._view){const a=r(this._date,this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[6])}}return{start:t(this._date),end:e(this._date)}}async _fetchAndRender(){if(this._root.innerHTML=this._buildShell(),!this._opts.fetchEvents)return void this._renderView();const t=this._root.querySelector(".uc-loading");t&&(t.style.display="flex");const e=this._getRange();try{const t=await this._opts.fetchEvents(e.start,e.end);this._events=(t||[]).map(t=>({...t,start:o(t.start),end:o(t.end)}))}catch(t){this._events=[]}t&&(t.style.display="none"),this._renderView()}_renderView(){const t=this._root.querySelector(".uc-view-container");if(t)if("month"===this._view)t.innerHTML=this._buildMonthView();else if("week"===this._view){const e=r(this._date,this._opts.weekStartsOn);t.innerHTML=this._buildWeekOrDayView(e),this._scrollToBusinessHours(t)}else t.innerHTML=this._buildWeekOrDayView([this._date]),this._scrollToBusinessHours(t)}_scrollToBusinessHours(t){requestAnimationFrame(()=>{const e=t.querySelector(".uc-time-body");if(!e)return;const a=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60;e.scrollTop=7*a})}_renderToolbar(){const t=this._root.querySelector(".uc-toolbar");if(!t)return;const e=document.createElement("div");e.innerHTML=this._buildToolbar(),this._root.replaceChild(e.firstElementChild,t)}_buildShell(){return`\n ${this._buildToolbar()}\n <div class="uc-loading" style="display:none">\n <div class="uc-spinner"></div>\n </div>\n <div class="uc-view-container"></div>\n `}_buildToolbar(){if(!this._opts.showToolbar)return"";const t=this._date.getFullYear(),e=(new Date).getFullYear();let a;if("month"===this._view)a=new Intl.DateTimeFormat(this._opts.locale,{month:"long"}).format(this._date);else if("week"===this._view){const t=r(this._date,this._opts.weekStartsOn);a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}`;const n=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${n(t)} – ${n(e)}`}(t[0],t[6],this._opts.locale)}else a=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",month:"long",day:"numeric"}).format(this._date);let s="";if(this._opts.showYearPicker&&this._yearPickerOpen){const a=this._yearPickerBase,n=Array.from({length:12},(n,s)=>{const i=a+s,o=i===t;return`<button class="${"uc-year-item"+(o?" uc-active":"")+(i===e&&!o?" uc-today-year":"")}" data-action="select-year" data-year="${i}">${i}</button>`}).join("");s=`\n <div class="uc-year-picker">\n <div class="uc-year-picker-nav">\n <button class="uc-year-nav-btn" data-action="year-prev" aria-label="Previous years">&#8249;</button>\n <span class="uc-year-range">${a} – ${a+11}</span>\n <button class="uc-year-nav-btn" data-action="year-next" aria-label="Next years">&#8250;</button>\n </div>\n <div class="uc-year-grid">${n}</div>\n </div>`}const i=this._opts.locale,o=v(i,"today"),c=v(i,"month"),l=v(i,"week"),d=v(i,"day");let u="";if(this._opts.showNavigation||this._opts.showTodayButton){const t=this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="prev" aria-label="Previous">&#8249;</button>':"",e=new Date,a=n(this._date,e)?" uc-active":"";u=`\n <div class="uc-toolbar-section uc-toolbar-left">\n ${t}${this._opts.showTodayButton?`<button class="uc-btn uc-today-btn${a}" data-action="today">${h(o)}</button>`:""}${this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="next" aria-label="Next">&#8250;</button>':""}\n </div>`}let p="";if(this._opts.showTitle){const e=this._opts.showYearPicker?`<button class="uc-year-btn${this._yearPickerOpen?" uc-open":""}" data-action="year-pick" aria-label="Select year">${t}</button>`:t;p=`\n <div class="uc-toolbar-section uc-toolbar-center">\n <h2 class="uc-title">\n <span class="uc-title-main">${h(a)}</span>\n ${e}\n </h2>\n ${s}\n </div>`}let y="";if(this._opts.showViewSwitcher){const t=this._opts.enabledViews,e=[];t.includes("month")&&e.push(`<button class="uc-btn uc-view-btn${"month"===this._view?" uc-active":""}" data-view="month">${h(c)}</button>`),t.includes("week")&&e.push(`<button class="uc-btn uc-view-btn${"week"===this._view?" uc-active":""}" data-view="week">${h(l)}</button>`),t.includes("day")&&e.push(`<button class="uc-btn uc-view-btn${"day"===this._view?" uc-active":""}" data-view="day">${h(d)}</button>`),e.length>0&&(y=`\n <div class="uc-toolbar-section uc-toolbar-right">\n <div class="uc-view-switcher">\n ${e.join("")}\n </div>\n </div>`)}return`\n <div class="uc-toolbar">\n ${u}${p}${y}\n </div>\n `}_buildMonthView(){const{locale:a,weekStartsOn:n}=this._opts,s=c(this._date.getFullYear(),this._date.getMonth(),n),i=d(a,n,this._opts.weekdayFormat),o=this._events.map(a=>({...a,_origStart:a.start,start:p(a)?t(a.start):a.start,end:p(a)?e(a.end):a.end})),r=i.map(t=>`<div class="uc-month-day-name">${h(t)}</div>`).join(""),l=[];for(let t=0;t<s.length;t+=7)l.push(s.slice(t,t+7));return`\n <div class="uc-month-view">\n <div class="uc-month-header">${r}</div>\n <div class="uc-month-body">${l.map(t=>this._buildWeekRow(t,o)).join("")}</div>\n </div>\n `}_buildWeekRow(t,e){const a=this._date.getMonth(),i=e.filter(p),o=e.filter(t=>!p(t)),r=y(i,t),c=Array.from({length:7},()=>new Set);for(const{startCol:t,endCol:e,slot:a}of r)for(let n=t;n<=e;n++)c[n].add(a);const d=t.map(t=>o.filter(e=>n(e.start,t)).sort((t,e)=>t.start-e.start)),u=t.map((t,e)=>`\n <div class="uc-day-cell${s(t)?" uc-today":""}${t.getMonth()!==a?" uc-other-month":""}" data-date="${t.toISOString()}" data-action="day-click">\n <span class="uc-day-number" data-action="day-number" data-date="${t.toISOString()}">${t.getDate()}</span>\n </div>`).join("");let v="";for(const{event:t,startCol:e,endCol:a,slot:n,isStart:s,isEnd:i}of r){if(n>=3)continue;const o=100/7,r=e*o,c=(a-e+1)*o,l=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,d=t.color||"var(--cal-event-bg)",u=s?"var(--cal-event-border-radius)":"0",p=i?"var(--cal-event-border-radius)":"0",y=s?"":" uc-continues-left",_=i?"":" uc-continues-right",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";v+=`\n <div class="uc-event-bar${y}${_}"\n style="left:calc(${r}% + 2px);width:calc(${c}% - 4px);top:${l};background:${h(d)};border-radius:${u} ${p} ${p} ${u};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${s?`<span class="uc-event-title">${h(t.title)}</span>`:"&nbsp;"}\n </div>`}for(let e=0;e<7;e++){const a=100/7,n=e*a,s=t[e],i=([...c[e]].filter(t=>t<3).length,[...c[e]].filter(t=>t>=3).length),o=[...c[e]],r=o.length>0?Math.max(...o)+1:0,u=[];for(let t=r;t<3;t++)u.push(t);const p=d[e];let y=i;if(p.forEach((t,e)=>{if(e<u.length){const s=`calc(var(--cal-header-day-height) + ${u[e]} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,i=t.color||"var(--cal-event-bg)",o=l(t.start,this._opts.locale,this._opts.use24Hour),r=this._opts.showTimeInItems?`<span class="uc-event-time">${h(o)}</span>`:"",c=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";v+=`\n <div class="uc-event-bar"\n style="left:calc(${n}% + 2px);width:calc(${a}% - 4px);top:${s};background:${h(i)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${c}>\n ${r}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}else y++}),y>0){v+=`\n <div class="uc-more-link"\n style="left:calc(${n}% + 2px);width:calc(${a}% - 4px);top:${"calc(var(--cal-header-day-height) + 3 * (var(--cal-event-height) + var(--cal-event-gap)) + 2px)"};"\n data-date="${s.toISOString()}" data-action="more-click">\n +${y} more\n </div>`}}return`\n <div class="uc-week-row">\n <div class="uc-week-cells">${u}</div>\n <div class="uc-week-events">${v}</div>\n </div>`}_buildWeekOrDayView(a){const{locale:i,weekStartsOn:o,use24Hour:r}=this._opts,c=1===a.length,u=c?" uc-day-header-single":"",v=c?[new Intl.DateTimeFormat(i,{weekday:this._opts.weekdayFormat}).format(a[0])]:d(i,o,this._opts.weekdayFormat),_=t(a[0]),w=e(a[a.length-1]),g=this._events.filter(t=>p(t)&&t.start<=w&&e(t.end)>=_).map(a=>({...a,start:t(a.start),end:e(a.end)})),m=this._events.filter(t=>!p(t)&&t.start>=_&&t.start<=w),k=a.map((t,e)=>{const a=s(t)?" uc-today":"",n=t.getDate();return`\n <div class="uc-week-day-header${a}">\n <span class="uc-week-day-name">${h(v[e])}</span>\n <span class="uc-week-day-num" data-action="day-number" data-date="${t.toISOString()}">${n}</span>\n </div>`}).join(""),f=c?g.map((t,e)=>({event:t,startCol:0,endCol:0,slot:e,isStart:!0,isEnd:!0})):y(g,a),$=f.length?Math.max(...f.map(t=>t.slot))+1:0;let b="";for(const{event:t,startCol:e,endCol:n,slot:s,isStart:i,isEnd:o}of f){const r=100/a.length,c=e*r,l=(n-e+1)*r,d=`calc(${s} * (var(--cal-event-height) + 3px) + 2px)`,u=t.color||"var(--cal-event-bg)",v=i?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",y=i?"":" uc-continues-left",_=o?"":" uc-continues-right",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";b+=`\n <div class="uc-event-bar${y}${_}"\n style="left:calc(${c}% + 2px);width:calc(${l}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${i?`<span class="uc-event-title">${h(t.title)}</span>`:"&nbsp;"}\n </div>`}const D=`calc(${Math.max(1,$)} * (var(--cal-event-height) + 3px) + 6px)`,S=new Date,T=60*S.getHours()+S.getMinutes(),M=a.length;return`\n <div class="${c?"uc-day-view":"uc-week-view"}">\n <div class="uc-week-header">\n <div class="uc-time-gutter-spacer"></div>\n <div class="uc-week-day-headers${u}">${k}</div>\n </div>\n <div class="uc-all-day-section">\n <div class="uc-time-gutter-spacer uc-all-day-label">all-day</div>\n <div class="uc-all-day-events" style="min-height:${D}">${b}</div>\n </div>\n <div class="uc-time-body">\n <div class="uc-time-grid-inner">\n <div class="uc-time-gutter">${Array.from({length:24},(t,e)=>`<div class="uc-hour-cell"><span class="uc-hour-label">${h(0===e?"":l(new Date(2e3,0,1,e),"en-US",r))}</span></div>`).join("")}</div>\n <div class="uc-time-columns" style="--uc-col-count:${M}">${a.map(t=>{const e=s(t)?" uc-today":"",a=Array.from({length:24},()=>'<div class="uc-hour-row"><div class="uc-half-hour-line"></div></div>').join(""),o=function(t){if(!t.length)return[];const e=[...t].sort((t,e)=>t.start-e.start||e.end-t.end),a=[],n=[];for(const t of e){let e=a.findIndex(e=>e<=t.start);-1===e?(e=a.length,a.push(t.end)):a[e]=t.end,n.push({event:t,col:e})}return n.map(t=>{const e=n.filter(e=>e.event.start<t.event.end&&e.event.end>t.event.start),a=Math.max(...e.map(t=>t.col))+1;return{...t,totalCols:a}})}(m.filter(e=>n(e.start,t))).map(({event:t,col:e,totalCols:a})=>{const n=60*t.start.getHours()+t.start.getMinutes(),s=60*t.end.getHours()+t.end.getMinutes(),o=`calc(${n} / 60 * var(--cal-hour-height))`,c=`calc(${Math.max(s-n,30)} / 60 * var(--cal-hour-height))`,d=100/a,u=`calc(${e*d}% + 1px)`,v=`calc(${d}% - 2px)`,p=t.color||"var(--cal-event-bg)",y=l(t.start,i,r),_=s-n<=60?" uc-timed-event--short":"",w=this._opts.showTimeInItems?`<span class="uc-event-time">${h(y)}</span>`:"",g=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";return`\n <div class="uc-timed-event${_}"\n style="top:${o};height:${c};left:${u};width:${v};background:${h(p)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${g}>\n ${w}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}).join(""),c=s(t)?`<div class="uc-now-indicator" style="top:calc(${T} / 60 * var(--cal-hour-height));">\n <span class="uc-now-dot"></span>\n <span class="uc-now-line"></span>\n </div>`:"";return`<div class="uc-time-col${e}" data-date="${t.toISOString()}" data-action="slot-col">\n ${a}${o}${c}\n </div>`}).join("")}</div>\n </div>\n </div>\n </div>`}_closeYearPicker(){this._yearOutsideHandler&&(document.removeEventListener("click",this._yearOutsideHandler),this._yearOutsideHandler=null),this._yearPickerOpen=!1,this._renderToolbar()}_handleClick(e){const a=e.target.closest("[data-view]");if(a)return void this.setView(a.dataset.view);const n=e.target.closest("[data-action]");if(!n)return;switch(n.dataset.action){case"prev":this.navigate(-1);break;case"next":this.navigate(1);break;case"today":this.goToToday();break;case"year-pick":e.stopPropagation(),this._yearPickerOpen?this._closeYearPicker():(this._yearPickerOpen=!0,this._yearPickerBase=this._date.getFullYear()-4,this._renderToolbar(),this._yearOutsideHandler=t=>{t.target.closest(".uc-year-picker")||t.target.closest('[data-action="year-pick"]')||this._closeYearPicker()},setTimeout(()=>document.addEventListener("click",this._yearOutsideHandler),0));break;case"year-prev":e.stopPropagation(),this._yearPickerBase-=12,this._renderToolbar();break;case"year-next":e.stopPropagation(),this._yearPickerBase+=12,this._renderToolbar();break;case"select-year":{e.stopPropagation();const t=parseInt(n.dataset.year,10);this._date=new Date(this._date.getFullYear()!==t?new Date(this._date).setFullYear(t):this._date),this._closeYearPicker();const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender();break}case"event-click":{e.stopPropagation();const t=n.dataset.eventId,a=this._events.find(e=>String(e.id)===String(t));a&&this._opts.onEventClick&&this._opts.onEventClick(a,e);break}case"day-click":{if(e.target.closest('[data-action="day-number"]'))break;if(e.target.closest('[data-action="event-click"]'))break;const t=new Date(n.dataset.date);this._opts.onSlotClick&&this._opts.onSlotClick(t,e);break}case"day-number":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(n.dataset.date);this._date=t(a),this.setView("day");break}case"more-click":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(n.dataset.date);this._date=t(a),this.setView("day");break}case"slot-col":if(e.target.closest('[data-action="event-click"]'))break;if(this._opts.onSlotClick){const t=n,a=t.getBoundingClientRect(),s=e.clientY-a.top,i=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60,o=Math.max(0,Math.floor(s/i*60)),r=Math.floor(o/60)%24,c=15*Math.round(o%60/15),l=new Date(t.dataset.date);l.setHours(r,c,0,0),this._opts.onSlotClick(l,e)}}}_handleTooltipPosition(t){const e=t.target.closest("[data-tooltip]");if(!e||!e.dataset.tooltip)return;if(!e.dataset.tooltip.trim())return;const a=e.getBoundingClientRect(),n=window.innerWidth;e.classList.remove("uc-tooltip-left","uc-tooltip-right","uc-tooltip-bottom");const s=a.width/2-125;a.left+s+250>n-10?e.classList.add("uc-tooltip-left"):a.left+s<10&&e.classList.add("uc-tooltip-right"),a.top<50&&e.classList.add("uc-tooltip-bottom")}_startNowUpdater(){this._nowInterval=setInterval(()=>{const t=this._root.querySelectorAll(".uc-now-indicator");if(!t.length)return;const e=new Date,a=`calc(${60*e.getHours()+e.getMinutes()} / 60 * var(--cal-hour-height))`;t.forEach(t=>t.style.top=a)},6e4)}}});
14
+ return class{constructor(e,a={}){if("string"==typeof e){if(this._el=document.querySelector(e),!this._el)throw new Error(`SimpleCalendarJs: no element for "${e}"`)}else this._el=e;this._opts=Object.assign({defaultView:"month",defaultDate:null,weekStartsOn:0,locale:"default",weekdayFormat:"short",use24Hour:!1,showTimeInItems:!0,showGridLines:!0,showToolbar:!0,showTodayButton:!0,showNavigation:!0,showTitle:!0,showYearPicker:!0,showViewSwitcher:!0,showTooltips:!0,showBorder:!0,maxEventsPerCell:3,listDaysForward:30,enabledViews:["month","week","day"],fetchEvents:null,onEventClick:null,onSlotClick:null,onViewChange:null,onNavigate:null},a),this._view=this._opts.defaultView,this._date=t(this._opts.defaultDate||new Date),this._events=[],this._cachedRange=null,this._cachedEvents=[],this._nowInterval=null,this._yearPickerOpen=!1,this._yearPickerBase=0,this._yearOutsideHandler=null,this._root=document.createElement("div"),this._root.className="uc-calendar",this._opts.showGridLines||this._root.classList.add("uc-no-grid"),this._opts.showBorder||this._root.classList.add("uc-no-border"),this._el.appendChild(this._root),this._onClick=this._handleClick.bind(this),this._root.addEventListener("click",this._onClick),this._onMouseMove=this._handleTooltipPosition.bind(this),this._root.addEventListener("mouseover",this._onMouseMove),this._fetchAndRender(),this._startNowUpdater()}setView(t){t!==this._view&&(this._view=t,this._opts.onViewChange&&this._opts.onViewChange(t),this._fetchAndRender())}navigate(t){const e=new Date(this._date);"month"===this._view?(e.setMonth(e.getMonth()+t),e.setDate(1)):"week"===this._view?e.setDate(e.getDate()+7*t):e.setDate(e.getDate()+t),this._date=e;const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender()}goToToday(){this._date=t(new Date);const e=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(e.start,e.end),this._fetchAndRender()}goToDate(e){this._date=t(o(e)),this._fetchAndRender()}refresh(){this._cachedRange=null,this._cachedEvents=[],this._fetchAndRender()}destroy(){this._nowInterval&&clearInterval(this._nowInterval),this._yearOutsideHandler&&document.removeEventListener("click",this._yearOutsideHandler),this._root.removeEventListener("click",this._onClick),this._root.removeEventListener("mouseover",this._onMouseMove),this._root.remove()}_getRange(){if("month"===this._view){const a=r(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}if("week"===this._view){const a=l(this._date,this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[6])}}if("list"===this._view){const t=new Date,e=new Date(t);return e.setDate(e.getDate()+this._opts.listDaysForward),{start:t,end:e}}return{start:t(this._date),end:e(this._date)}}_getMonthGridRange(){const a=r(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}async _fetchAndRender(){if(this._root.innerHTML=this._buildShell(),!this._opts.fetchEvents)return void this._renderView();const t=this._getRange();if(this._cachedRange&&this._cachedRange.start<=t.start&&this._cachedRange.end>=t.end)return this._events=this._cachedEvents.filter(e=>e.end>=t.start&&e.start<=t.end),void this._renderView();const e=this._root.querySelector(".uc-loading");e&&(e.style.display="flex");try{let e;e="list"===this._view?t:this._getMonthGridRange();const a=(await this._opts.fetchEvents(e.start,e.end)||[]).map(t=>({...t,start:o(t.start),end:o(t.end)}));this._cachedRange={start:e.start,end:e.end},this._cachedEvents=a,this._events=a.filter(e=>e.end>=t.start&&e.start<=t.end)}catch(t){this._events=[]}e&&(e.style.display="none"),this._renderView()}_renderView(){const t=this._root.querySelector(".uc-view-container");if(t){if(0===this._opts.maxEventsPerCell)this._root.classList.add("uc-unlimited-events"),this._root.style.removeProperty("--cal-cell-min-height");else{this._root.classList.remove("uc-unlimited-events");const t=30+24*this._opts.maxEventsPerCell+28;this._root.style.setProperty("--cal-cell-min-height",`${t}px`)}if("month"===this._view)t.innerHTML=this._buildMonthView();else if("week"===this._view){const e=l(this._date,this._opts.weekStartsOn);t.innerHTML=this._buildWeekOrDayView(e),this._scrollToBusinessHours(t)}else"list"===this._view?t.innerHTML=this._buildListView():(t.innerHTML=this._buildWeekOrDayView([this._date]),this._scrollToBusinessHours(t))}}_scrollToBusinessHours(t){requestAnimationFrame(()=>{const e=t.querySelector(".uc-time-body");if(!e)return;const a=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60;e.scrollTop=7*a})}_renderToolbar(){const t=this._root.querySelector(".uc-toolbar");if(!t)return;const e=document.createElement("div");e.innerHTML=this._buildToolbar(),this._root.replaceChild(e.firstElementChild,t)}_buildShell(){return`\n ${this._buildToolbar()}\n <div class="uc-loading" style="display:none">\n <div class="uc-spinner"></div>\n </div>\n <div class="uc-view-container"></div>\n `}_buildToolbar(){if(!this._opts.showToolbar)return"";const t=this._date.getFullYear(),e=(new Date).getFullYear();let a,n=!0;if("month"===this._view)a=new Intl.DateTimeFormat(this._opts.locale,{month:"long"}).format(this._date);else if("week"===this._view){const t=l(this._date,this._opts.weekStartsOn);a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}`}(t[0],t[6],this._opts.locale)}else if("list"===this._view){const t=new Date,e=new Date(t);e.setDate(e.getDate()+this._opts.listDaysForward),a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}, ${t.getFullYear()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}, ${e.getFullYear()}`}(t,e,this._opts.locale),n=!1}else a=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",month:"long",day:"numeric"}).format(this._date);let i="";if(this._opts.showYearPicker&&this._yearPickerOpen){const a=this._yearPickerBase,s=Array.from({length:12},(s,n)=>{const i=a+n,o=i===t;return`<button class="${"uc-year-item"+(o?" uc-active":"")+(i===e&&!o?" uc-today-year":"")}" data-action="select-year" data-year="${i}">${i}</button>`}).join("");i=`\n <div class="uc-year-picker">\n <div class="uc-year-picker-nav">\n <button class="uc-year-nav-btn" data-action="year-prev" aria-label="Previous years">&#8249;</button>\n <span class="uc-year-range">${a} – ${a+11}</span>\n <button class="uc-year-nav-btn" data-action="year-next" aria-label="Next years">&#8250;</button>\n </div>\n <div class="uc-year-grid">${s}</div>\n </div>`}const o=this._opts.locale,r=v(o,"today"),c=v(o,"month"),d=v(o,"week"),u=v(o,"day"),p=v(o,"list");let y="";if((this._opts.showNavigation||this._opts.showTodayButton)&&"list"!==this._view){const t=this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="prev" aria-label="Previous">&#8249;</button>':"",e=new Date,a=s(this._date,e)?" uc-active":"";y=`\n <div class="uc-toolbar-section uc-toolbar-left">\n ${t}${this._opts.showTodayButton?`<button class="uc-btn uc-today-btn${a}" data-action="today">${h(r)}</button>`:""}${this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="next" aria-label="Next">&#8250;</button>':""}\n </div>`}let g="";if(this._opts.showTitle){const e=n?this._opts.showYearPicker?`<button class="uc-year-btn${this._yearPickerOpen?" uc-open":""}" data-action="year-pick" aria-label="Select year">${t}</button>`:t:"";g=`\n <div class="uc-toolbar-section uc-toolbar-center">\n <h2 class="uc-title">\n <span class="uc-title-main">${h(a)}</span>\n ${e}\n </h2>\n ${i}\n </div>`}let _="";if(this._opts.showViewSwitcher){const t=this._opts.enabledViews,e=[];t.includes("month")&&e.push(`<button class="uc-btn uc-view-btn${"month"===this._view?" uc-active":""}" data-view="month">${h(c)}</button>`),t.includes("week")&&e.push(`<button class="uc-btn uc-view-btn${"week"===this._view?" uc-active":""}" data-view="week">${h(d)}</button>`),t.includes("day")&&e.push(`<button class="uc-btn uc-view-btn${"day"===this._view?" uc-active":""}" data-view="day">${h(u)}</button>`),t.includes("list")&&e.push(`<button class="uc-btn uc-view-btn${"list"===this._view?" uc-active":""}" data-view="list">${h(p)}</button>`),e.length>0&&(_=`\n <div class="uc-toolbar-section uc-toolbar-right">\n <div class="uc-view-switcher">\n ${e.join("")}\n </div>\n </div>`)}return`\n <div class="uc-toolbar">\n ${y}${g}${_}\n </div>\n `}_buildMonthView(){const{locale:a,weekStartsOn:s}=this._opts,n=r(this._date.getFullYear(),this._date.getMonth(),s),i=d(a,s,this._opts.weekdayFormat),o=this._events.map(a=>({...a,_origStart:a.start,start:p(a)?t(a.start):a.start,end:p(a)?e(a.end):a.end})),l=i.map(t=>`<div class="uc-month-day-name">${h(t)}</div>`).join(""),c=[];for(let t=0;t<n.length;t+=7)c.push(n.slice(t,t+7));return`\n <div class="uc-month-view">\n <div class="uc-month-header">${l}</div>\n <div class="uc-month-body">${c.map(t=>this._buildWeekRow(t,o)).join("")}</div>\n </div>\n `}_buildWeekRow(t,e){const a=this._date.getMonth(),i=0===this._opts.maxEventsPerCell?1/0:this._opts.maxEventsPerCell,o=e.filter(p),l=e.filter(t=>!p(t)),r=y(o,t),d=Array.from({length:7},()=>new Set);for(const{startCol:t,endCol:e,slot:a}of r)for(let s=t;s<=e;s++)d[s].add(a);const u=t.map(t=>l.filter(e=>s(e.start,t)).sort((t,e)=>t.start-e.start)),v=t.map((t,e)=>`\n <div class="uc-day-cell${n(t)?" uc-today":""}${t.getMonth()!==a?" uc-other-month":""}" data-date="${t.toISOString()}" data-action="day-click">\n <span class="uc-day-number" data-action="day-number" data-date="${t.toISOString()}">${t.getDate()}</span>\n </div>`).join("");let g="";for(const{event:t,startCol:e,endCol:a,slot:s,isStart:n,isEnd:o}of r){if(i!==1/0&&s>=i)continue;const l=100/7,r=e*l,c=(a-e+1)*l,d=`calc(var(--cal-header-day-height) + ${s} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,u=t.color||"var(--cal-event-bg)",v=n?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",y=n?"":" uc-continues-left",_=o?"":" uc-continues-right",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar${y}${_}"\n style="left:calc(${r}% + 2px);width:calc(${c}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${n?`<span class="uc-event-title">${h(t.title)}</span>`:"&nbsp;"}\n </div>`}let _=-1;for(let e=0;e<7;e++){const a=100/7,s=e*a,n=t[e],o=[...d[e]],l=o.length>0?Math.max(...o)+1:0,r=u[e];if(i===1/0)r.forEach((t,e)=>{const n=l+e;_=Math.max(_,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,o=t.color||"var(--cal-event-bg)",r=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(r)}</span>`:"",u=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(o)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${u}>\n ${d}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`});else{const t=[...d[e]].filter(t=>t>=i).length,o=[];for(let t=l;t<i;t++)o.push(t);let u=t;if(r.forEach((t,e)=>{if(e<o.length){const n=o[e];_=Math.max(_,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,l=t.color||"var(--cal-event-bg)",r=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(r)}</span>`:"",u=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(l)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${u}>\n ${d}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}else u++}),u>0){g+=`\n <div class="uc-more-link"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${`calc(var(--cal-header-day-height) + ${i} * (var(--cal-event-height) + var(--cal-event-gap)) + 2px)`};"\n data-date="${n.toISOString()}" data-action="more-click">\n +${u} more\n </div>`}}}for(const{slot:t}of r)(i===1/0||t<i)&&(_=Math.max(_,t));let w="";return i===1/0&&(w=_>=0?` style="min-height: calc(var(--cal-header-day-height) + ${_+1} * (var(--cal-event-height) + var(--cal-event-gap)) + 8px);"`:' style="min-height: 80px;"'),`\n <div class="uc-week-row"${w}>\n <div class="uc-week-cells">${v}</div>\n <div class="uc-week-events">${g}</div>\n </div>`}_buildWeekOrDayView(a){const{locale:i,weekStartsOn:o,use24Hour:l}=this._opts,r=1===a.length,u=r?" uc-day-header-single":"",g=r?[new Intl.DateTimeFormat(i,{weekday:this._opts.weekdayFormat}).format(a[0])]:d(i,o,this._opts.weekdayFormat),_=t(a[0]),w=e(a[a.length-1]),m=this._events.filter(t=>p(t)&&t.start<=w&&e(t.end)>=_).map(a=>({...a,start:t(a.start),end:e(a.end)})),f=this._events.filter(t=>!p(t)&&t.start>=_&&t.start<=w),$=a.map((t,e)=>{const a=n(t)?" uc-today":"",s=t.getDate();return`\n <div class="uc-week-day-header${a}">\n <span class="uc-week-day-name">${h(g[e])}</span>\n <span class="uc-week-day-num" data-action="day-number" data-date="${t.toISOString()}">${s}</span>\n </div>`}).join(""),k=r?m.map((t,e)=>({event:t,startCol:0,endCol:0,slot:e,isStart:!0,isEnd:!0})):y(m,a),b=k.length?Math.max(...k.map(t=>t.slot))+1:0;let D="";for(const{event:t,startCol:e,endCol:s,slot:n,isStart:i,isEnd:o}of k){const l=100/a.length,r=e*l,c=(s-e+1)*l,d=`calc(${n} * (var(--cal-event-height) + 3px) + 2px)`,u=t.color||"var(--cal-event-bg)",v=i?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",y=i?"":" uc-continues-left",g=o?"":" uc-continues-right",_=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";D+=`\n <div class="uc-event-bar${y}${g}"\n style="left:calc(${r}% + 2px);width:calc(${c}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${_}>\n ${i?`<span class="uc-event-title">${h(t.title)}</span>`:"&nbsp;"}\n </div>`}const T=`calc(${Math.max(1,b)} * (var(--cal-event-height) + 3px) + 6px)`,S=new Date,M=60*S.getHours()+S.getMinutes(),C=a.length,x=Array.from({length:24},(t,e)=>`<div class="uc-hour-cell"><span class="uc-hour-label">${h(0===e?"":c(new Date(2e3,0,1,e),"en-US",l))}</span></div>`).join(""),H=a.map(t=>{const e=n(t)?" uc-today":"",a=Array.from({length:24},()=>'<div class="uc-hour-row"><div class="uc-half-hour-line"></div></div>').join(""),o=function(t){if(!t.length)return[];const e=[...t].sort((t,e)=>t.start-e.start||e.end-t.end),a=[],s=[];for(const t of e){let e=a.findIndex(e=>e<=t.start);-1===e?(e=a.length,a.push(t.end)):a[e]=t.end,s.push({event:t,col:e})}return s.map(t=>{const e=s.filter(e=>e.event.start<t.event.end&&e.event.end>t.event.start),a=Math.max(...e.map(t=>t.col))+1;return{...t,totalCols:a}})}(f.filter(e=>s(e.start,t))).map(({event:t,col:e,totalCols:a})=>{const s=60*t.start.getHours()+t.start.getMinutes(),n=60*t.end.getHours()+t.end.getMinutes(),o=`calc(${s} / 60 * var(--cal-hour-height))`,r=`calc(${Math.max(n-s,30)} / 60 * var(--cal-hour-height))`,d=100/a,u=`calc(${e*d}% + 1px)`,v=`calc(${d}% - 2px)`,p=t.color||"var(--cal-event-bg)",y=c(t.start,i,l),g=n-s<=60?" uc-timed-event--short":"",_=this._opts.showTimeInItems?`<span class="uc-event-time">${h(y)}</span>`:"",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";return`\n <div class="uc-timed-event${g}"\n style="top:${o};height:${r};left:${u};width:${v};background:${h(p)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${_}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}).join(""),r=n(t)?`<div class="uc-now-indicator" style="top:calc(${M} / 60 * var(--cal-hour-height));">\n <span class="uc-now-dot"></span>\n <span class="uc-now-line"></span>\n </div>`:"";return`<div class="uc-time-col${e}" data-date="${t.toISOString()}" data-action="slot-col">\n ${a}${o}${r}\n </div>`}).join("");return`\n <div class="${r?"uc-day-view":"uc-week-view"}">\n <div class="uc-week-header">\n <div class="uc-time-gutter-spacer"></div>\n <div class="uc-week-day-headers${u}">${$}</div>\n </div>\n <div class="uc-all-day-section">\n <div class="uc-time-gutter-spacer uc-all-day-label">${v(this._opts.locale,"allDay")}</div>\n <div class="uc-all-day-events" style="min-height:${T}">${D}</div>\n </div>\n <div class="uc-time-body">\n <div class="uc-time-grid-inner">\n <div class="uc-time-gutter">${x}</div>\n <div class="uc-time-columns" style="--uc-col-count:${C}">${H}</div>\n </div>\n </div>\n </div>`}_buildListView(){const e=new Date,a=t(e),s=new Date(a);s.setDate(s.getDate()+this._opts.listDaysForward);const n=this._events.filter(t=>t.end>=e&&t.start<=s);n.sort((e,s)=>{const n=(e.start<a?a:t(e.start))-(s.start<a?a:t(s.start));return 0!==n?n:e.start-s.start});const i=new Map;if(n.forEach(e=>{const s=e.start<a?a:t(e.start),n=`${(o=s).getFullYear()}-${String(o.getMonth()+1).padStart(2,"0")}-${String(o.getDate()).padStart(2,"0")}`;var o;i.has(n)||i.set(n,[]),i.get(n).push(e)}),0===i.size)return'\n <div class="uc-list-view">\n <div class="uc-list-empty">\n <p>No upcoming events</p>\n </div>\n </div>';let l="";return i.forEach((t,e)=>{const a=o(e),s=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",year:"numeric",month:"long",day:"numeric"}).format(a),n=t.map(t=>{const e=p(t)?"All day":c(t.start,this._opts.locale,this._opts.use24Hour),a=t.color||"var(--cal-event-bg)",s=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||"")}"`:"";return`\n <div class="uc-list-event" data-event-id="${h(t.id)}" data-action="event-click" ${s}>\n <div class="uc-list-event-indicator" style="background: ${h(a)};"></div>\n <div class="uc-list-event-time">${h(e)}</div>\n <div class="uc-list-event-content">\n <div class="uc-list-event-title">${h(t.title)}</div>\n </div>\n </div>`}).join("");l+=`\n <div class="uc-list-date-group">\n <div class="uc-list-date-header">${h(s)}</div>\n <div class="uc-list-events">${n}</div>\n </div>`}),`<div class="uc-list-view">${l}</div>`}_closeYearPicker(){this._yearOutsideHandler&&(document.removeEventListener("click",this._yearOutsideHandler),this._yearOutsideHandler=null),this._yearPickerOpen=!1,this._renderToolbar()}_handleClick(e){const a=e.target.closest("[data-view]");if(a)return void this.setView(a.dataset.view);const s=e.target.closest("[data-action]");if(!s)return;switch(s.dataset.action){case"prev":this.navigate(-1);break;case"next":this.navigate(1);break;case"today":this.goToToday();break;case"year-pick":e.stopPropagation(),this._yearPickerOpen?this._closeYearPicker():(this._yearPickerOpen=!0,this._yearPickerBase=this._date.getFullYear()-4,this._renderToolbar(),this._yearOutsideHandler=t=>{t.target.closest(".uc-year-picker")||t.target.closest('[data-action="year-pick"]')||this._closeYearPicker()},setTimeout(()=>document.addEventListener("click",this._yearOutsideHandler),0));break;case"year-prev":e.stopPropagation(),this._yearPickerBase-=12,this._renderToolbar();break;case"year-next":e.stopPropagation(),this._yearPickerBase+=12,this._renderToolbar();break;case"select-year":{e.stopPropagation();const t=parseInt(s.dataset.year,10);this._date=new Date(this._date.getFullYear()!==t?new Date(this._date).setFullYear(t):this._date),this._closeYearPicker();const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender();break}case"event-click":{e.stopPropagation();const t=s.dataset.eventId,a=this._events.find(e=>String(e.id)===String(t));a&&this._opts.onEventClick&&this._opts.onEventClick(a,e);break}case"day-click":{if(e.target.closest('[data-action="day-number"]'))break;if(e.target.closest('[data-action="event-click"]'))break;const t=new Date(s.dataset.date);this._opts.onSlotClick&&this._opts.onSlotClick(t,e);break}case"day-number":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"more-click":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"slot-col":if(e.target.closest('[data-action="event-click"]'))break;if(this._opts.onSlotClick){const t=s,a=t.getBoundingClientRect(),n=e.clientY-a.top,i=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60,o=Math.max(0,Math.floor(n/i*60)),l=Math.floor(o/60)%24,r=15*Math.round(o%60/15),c=new Date(t.dataset.date);c.setHours(l,r,0,0),this._opts.onSlotClick(c,e)}}}_handleTooltipPosition(t){const e=t.target.closest("[data-tooltip]");if(!e||!e.dataset.tooltip)return;if(!e.dataset.tooltip.trim())return;const a=e.getBoundingClientRect(),s=window.innerWidth;e.classList.remove("uc-tooltip-left","uc-tooltip-right","uc-tooltip-bottom");const n=a.width/2-125;a.left+n+250>s-10?e.classList.add("uc-tooltip-left"):a.left+n<10&&e.classList.add("uc-tooltip-right"),a.top<50&&e.classList.add("uc-tooltip-bottom")}_startNowUpdater(){this._nowInterval=setInterval(()=>{const t=this._root.querySelectorAll(".uc-now-indicator");if(!t.length)return;const e=new Date,a=`calc(${60*e.getHours()+e.getMinutes()} / 60 * var(--cal-hour-height))`;t.forEach(t=>t.style.top=a)},6e4)}}});
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SimpleCalendarJs v3.0.2 — Angular Wrapper
2
+ * SimpleCalendarJs v3.0.4 — Angular Wrapper
3
3
  * A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
4
4
  *
5
5
  * @author Pedro Lopes <simplecalendarjs@gmail.com>
@@ -50,6 +50,7 @@
50
50
  * this.calendar.navigate(1);
51
51
  * this.calendar.goToDate(new Date());
52
52
  * this.calendar.goToToday();
53
+ * this.calendar.refresh();
53
54
  * }
54
55
  *
55
56
  * Example with theme switching:
@@ -110,6 +111,7 @@ const INIT_PROPS = [
110
111
  'defaultDate',
111
112
  'weekStartsOn',
112
113
  'locale',
114
+ 'weekdayFormat',
113
115
  'use24Hour',
114
116
  'showTimeInItems',
115
117
  'showGridLines',
@@ -119,6 +121,10 @@ const INIT_PROPS = [
119
121
  'showTitle',
120
122
  'showYearPicker',
121
123
  'showViewSwitcher',
124
+ 'showTooltips',
125
+ 'showBorder',
126
+ 'maxEventsPerCell',
127
+ 'listDaysForward',
122
128
  'enabledViews',
123
129
  ];
124
130
 
@@ -142,6 +148,7 @@ export class SimpleCalendarJsComponent implements OnInit, OnDestroy, OnChanges,
142
148
  @Input() defaultDate?: Date;
143
149
  @Input() weekStartsOn: 0 | 1 = 0;
144
150
  @Input() locale: string = 'default';
151
+ @Input() weekdayFormat: string = 'short';
145
152
  @Input() use24Hour: boolean = false;
146
153
  @Input() showTimeInItems: boolean = true;
147
154
  @Input() showGridLines: boolean = true;
@@ -151,6 +158,10 @@ export class SimpleCalendarJsComponent implements OnInit, OnDestroy, OnChanges,
151
158
  @Input() showTitle: boolean = true;
152
159
  @Input() showYearPicker: boolean = true;
153
160
  @Input() showViewSwitcher: boolean = true;
161
+ @Input() showTooltips: boolean = true;
162
+ @Input() showBorder: boolean = true;
163
+ @Input() maxEventsPerCell: number = 3;
164
+ @Input() listDaysForward: number = 30;
154
165
  @Input() enabledViews: string[] = ['month', 'week', 'day'];
155
166
 
156
167
  // Callback inputs
@@ -286,6 +297,10 @@ export class SimpleCalendarJsComponent implements OnInit, OnDestroy, OnChanges,
286
297
  this.calendar?.goToToday();
287
298
  }
288
299
 
300
+ public refresh(): void {
301
+ this.calendar?.refresh();
302
+ }
303
+
289
304
  public getInstance(): any {
290
305
  return this.calendar;
291
306
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SimpleCalendarJs v3.0.2 — React Wrapper
2
+ * SimpleCalendarJs v3.0.4 — React Wrapper
3
3
  * A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
4
4
  *
5
5
  * @author Pedro Lopes <simplecalendarjs@gmail.com>
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Imperative handle (via ref):
11
11
  * const ref = useRef();
12
- * // ref.current exposes: { setView, navigate, goToDate, goToToday }
12
+ * // ref.current exposes: { setView, navigate, goToDate, goToToday, refresh }
13
13
  *
14
14
  * Example with theme switching:
15
15
  * function MyApp() {
@@ -38,6 +38,7 @@ const INIT_PROPS = [
38
38
  'defaultDate',
39
39
  'weekStartsOn',
40
40
  'locale',
41
+ 'weekdayFormat',
41
42
  'use24Hour',
42
43
  'showTimeInItems',
43
44
  'showGridLines',
@@ -47,6 +48,10 @@ const INIT_PROPS = [
47
48
  'showTitle',
48
49
  'showYearPicker',
49
50
  'showViewSwitcher',
51
+ 'showTooltips',
52
+ 'showBorder',
53
+ 'maxEventsPerCell',
54
+ 'listDaysForward',
50
55
  'enabledViews',
51
56
  ];
52
57
 
@@ -75,6 +80,7 @@ const SimpleCalendarJsReact = forwardRef(function SimpleCalendarJsReact(props, r
75
80
  navigate: (d) => instanceRef.current?.navigate(d),
76
81
  goToDate: (d) => instanceRef.current?.goToDate(d),
77
82
  goToToday: () => instanceRef.current?.goToToday(),
83
+ refresh: () => instanceRef.current?.refresh(),
78
84
  getInstance: () => instanceRef.current,
79
85
  }),
80
86
  []
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SimpleCalendarJs v3.0.2 — Vue 3 Wrapper
2
+ * SimpleCalendarJs v3.0.4 — Vue 3 Wrapper
3
3
  * A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
4
4
  *
5
5
  * @author Pedro Lopes <simplecalendarjs@gmail.com>
@@ -13,6 +13,7 @@
13
13
  * calRef.value.navigate(1);
14
14
  * calRef.value.goToDate(new Date());
15
15
  * calRef.value.goToToday();
16
+ * calRef.value.refresh();
16
17
  * calRef.value.getInstance(); // Get raw instance
17
18
  *
18
19
  * Example with theme switching:
@@ -47,6 +48,7 @@ const INIT_PROPS = [
47
48
  'defaultDate',
48
49
  'weekStartsOn',
49
50
  'locale',
51
+ 'weekdayFormat',
50
52
  'use24Hour',
51
53
  'showTimeInItems',
52
54
  'showGridLines',
@@ -56,6 +58,10 @@ const INIT_PROPS = [
56
58
  'showTitle',
57
59
  'showYearPicker',
58
60
  'showViewSwitcher',
61
+ 'showTooltips',
62
+ 'showBorder',
63
+ 'maxEventsPerCell',
64
+ 'listDaysForward',
59
65
  'enabledViews',
60
66
  ];
61
67
 
@@ -71,6 +77,7 @@ export default defineComponent({
71
77
  defaultDate: { type: Date, default: null },
72
78
  weekStartsOn: { type: Number, default: 0 },
73
79
  locale: { type: String, default: 'default' },
80
+ weekdayFormat: { type: String, default: 'short' },
74
81
  use24Hour: { type: Boolean, default: false },
75
82
  showTimeInItems: { type: Boolean, default: true },
76
83
  showGridLines: { type: Boolean, default: true },
@@ -80,6 +87,10 @@ export default defineComponent({
80
87
  showTitle: { type: Boolean, default: true },
81
88
  showYearPicker: { type: Boolean, default: true },
82
89
  showViewSwitcher: { type: Boolean, default: true },
90
+ showTooltips: { type: Boolean, default: true },
91
+ showBorder: { type: Boolean, default: true },
92
+ maxEventsPerCell: { type: Number, default: 3 },
93
+ listDaysForward: { type: Number, default: 30 },
83
94
  enabledViews: { type: Array, default: () => ['month', 'week', 'day'] },
84
95
 
85
96
  // Callback props
@@ -194,6 +205,7 @@ export default defineComponent({
194
205
  navigate: (direction) => instanceRef.value?.navigate(direction),
195
206
  goToDate: (date) => instanceRef.value?.goToDate(date),
196
207
  goToToday: () => instanceRef.value?.goToToday(),
208
+ refresh: () => instanceRef.value?.refresh(),
197
209
  getInstance: () => instanceRef.value,
198
210
  });
199
211
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-calendar-js",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "A clean, modern, and feature-rich JavaScript calendar component with zero dependencies. Responsive design and intuitive navigation.",
5
5
  "main": "dist/simple-calendar-js.min.js",
6
6
  "style": "dist/simple-calendar-js.min.css",