simple-calendar-js 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -25
- package/dist/simple-calendar-js.min.css +2 -3
- package/dist/simple-calendar-js.min.js +3 -4
- package/frameworks/simple-calendar-js-angular.component.ts +1 -1
- package/frameworks/simple-calendar-js-react.jsx +1 -1
- package/frameworks/simple-calendar-js-vue.js +1 -1
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, zero-dependency JavaScript calendar component with internationalization support and framework wrappers for React, Vue, and Angular.
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
## Features
|
|
@@ -163,17 +163,19 @@ export class CalendarComponent {
|
|
|
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'`) |
|
|
166
|
+
| `weekdayFormat` | string | `'short'` | Weekday name format: `'narrow'` (1-2 letters), `'short'` (abbreviated), or `'long'` (full name) |
|
|
166
167
|
| `use24Hour` | boolean | `false` | Use 24-hour time format |
|
|
167
168
|
| `showTimeInItems` | boolean | `true` | Show time in event items |
|
|
168
169
|
| `showGridLines` | boolean | `true` | Show calendar grid lines |
|
|
170
|
+
| `showBorder` | boolean | `true` | Show calendar outer border |
|
|
169
171
|
| `showToolbar` | boolean | `true` | Show the toolbar |
|
|
170
172
|
| `showTodayButton` | boolean | `true` | Show "Today" button |
|
|
171
173
|
| `showNavigation` | boolean | `true` | Show prev/next navigation arrows |
|
|
172
174
|
| `showTitle` | boolean | `true` | Show month/year title |
|
|
173
175
|
| `showYearPicker` | boolean | `true` | Enable year picker dropdown (month view) |
|
|
174
176
|
| `showViewSwitcher` | boolean | `true` | Show view switcher buttons |
|
|
177
|
+
| `showTooltips` | boolean | `true` | Show tooltips on hover for events |
|
|
175
178
|
| `enabledViews` | string[] | `['month', 'week', 'day']` | Available view modes |
|
|
176
|
-
| `primaryColor` | string | `'#4f46e5'` | Primary theme color (hex) |
|
|
177
179
|
| `fetchEvents` | function | `null` | Async function to fetch events: `async (start, end) => Event[]` |
|
|
178
180
|
| `onEventClick` | function | `null` | Callback when event is clicked: `(event, mouseEvent) => void` |
|
|
179
181
|
| `onSlotClick` | function | `null` | Callback when time slot is clicked: `(date, mouseEvent) => void` |
|
|
@@ -192,6 +194,8 @@ interface CalendarEvent {
|
|
|
192
194
|
end?: Date; // End date/time (optional, defaults to start)
|
|
193
195
|
allDay?: boolean; // All-day event flag (optional)
|
|
194
196
|
color?: string; // Custom color (hex, optional)
|
|
197
|
+
description?: string; // Event description (also used for tooltip if tooltip not provided)
|
|
198
|
+
tooltip?: string; // Custom tooltip text shown on hover (overrides description)
|
|
195
199
|
[key: string]: any; // Any additional custom properties
|
|
196
200
|
}
|
|
197
201
|
```
|
|
@@ -205,7 +209,8 @@ const events = [
|
|
|
205
209
|
title: 'Team Meeting',
|
|
206
210
|
start: new Date('2024-03-15T10:00:00'),
|
|
207
211
|
end: new Date('2024-03-15T11:00:00'),
|
|
208
|
-
color: '#3b82f6'
|
|
212
|
+
color: '#3b82f6',
|
|
213
|
+
tooltip: 'Weekly team sync\nDiscuss Q1 roadmap and sprint planning'
|
|
209
214
|
},
|
|
210
215
|
{
|
|
211
216
|
id: 2,
|
|
@@ -213,11 +218,115 @@ const events = [
|
|
|
213
218
|
start: new Date('2024-03-20T00:00:00'),
|
|
214
219
|
end: new Date('2024-03-22T23:59:59'),
|
|
215
220
|
allDay: true,
|
|
216
|
-
color: '#10b981'
|
|
221
|
+
color: '#10b981',
|
|
222
|
+
description: 'Tech Conference 2024\nDowntown Convention Center'
|
|
217
223
|
}
|
|
218
224
|
];
|
|
219
225
|
```
|
|
220
226
|
|
|
227
|
+
## Tooltips
|
|
228
|
+
|
|
229
|
+
SimpleCalendarJs includes built-in tooltip support for displaying additional event information on hover.
|
|
230
|
+
|
|
231
|
+
### How Tooltips Work
|
|
232
|
+
|
|
233
|
+
Tooltips appear when you hover over an event for 400ms. They display content based on the following priority:
|
|
234
|
+
|
|
235
|
+
1. **`tooltip` property** (highest priority) - Custom tooltip text
|
|
236
|
+
2. **`description` property** - Falls back if no tooltip is provided
|
|
237
|
+
3. **`title` property** - Falls back if neither tooltip nor description is provided
|
|
238
|
+
|
|
239
|
+
### Basic Usage
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
const events = [
|
|
243
|
+
{
|
|
244
|
+
id: 1,
|
|
245
|
+
title: 'Team Meeting',
|
|
246
|
+
start: new Date('2024-03-15T10:00:00'),
|
|
247
|
+
end: new Date('2024-03-15T11:00:00'),
|
|
248
|
+
tooltip: 'Weekly team sync\nDiscuss Q1 roadmap' // Custom tooltip
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: 2,
|
|
252
|
+
title: 'Client Call',
|
|
253
|
+
start: new Date('2024-03-15T14:00:00'),
|
|
254
|
+
end: new Date('2024-03-15T15:00:00'),
|
|
255
|
+
description: 'Requirements gathering\nPrepare demo' // Used as tooltip
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: 3,
|
|
259
|
+
title: 'Code Review',
|
|
260
|
+
start: new Date('2024-03-15T16:00:00'),
|
|
261
|
+
end: new Date('2024-03-15T17:00:00')
|
|
262
|
+
// No tooltip/description - will show title
|
|
263
|
+
}
|
|
264
|
+
];
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Multiline Tooltips
|
|
268
|
+
|
|
269
|
+
Use `\n` (newline character) in your tooltip or description text to create multiple lines:
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
{
|
|
273
|
+
id: 1,
|
|
274
|
+
title: 'Project Kickoff',
|
|
275
|
+
start: new Date('2024-03-20T09:00:00'),
|
|
276
|
+
end: new Date('2024-03-20T10:30:00'),
|
|
277
|
+
tooltip: 'Project Kickoff Meeting\n\nAgenda:\n- Introductions\n- Timeline review\n- Q&A session\n\nLocation: Conference Room A'
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Tooltip Features
|
|
282
|
+
|
|
283
|
+
- **Smart Positioning**: Tooltips automatically adjust their position to stay visible:
|
|
284
|
+
- Events near the **top** of the viewport: tooltip shows below
|
|
285
|
+
- Events near the **right edge**: tooltip aligns to the right
|
|
286
|
+
- Events near the **left edge**: tooltip aligns to the left
|
|
287
|
+
|
|
288
|
+
- **Visual Design**:
|
|
289
|
+
- Dark background with rounded corners
|
|
290
|
+
- Small arrow pointing to the event
|
|
291
|
+
- Smooth fade-in animation (400ms delay)
|
|
292
|
+
- Supports both light and dark themes
|
|
293
|
+
|
|
294
|
+
- **Responsive**: Tooltips overflow outside calendar boundaries to ensure full visibility
|
|
295
|
+
|
|
296
|
+
### Disabling Tooltips
|
|
297
|
+
|
|
298
|
+
You can disable tooltips globally using the `showTooltips` option:
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
302
|
+
showTooltips: false, // Disable all tooltips
|
|
303
|
+
fetchEvents: async (start, end) => { ... }
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Styling Tooltips
|
|
308
|
+
|
|
309
|
+
Tooltips use CSS custom properties and can be customized:
|
|
310
|
+
|
|
311
|
+
```css
|
|
312
|
+
:root {
|
|
313
|
+
--cal-tooltip-bg: #1f2937; /* Background color */
|
|
314
|
+
--cal-tooltip-text: #f9fafb; /* Text color */
|
|
315
|
+
--cal-tooltip-border: #374151; /* Border color */
|
|
316
|
+
--cal-tooltip-max-width: 250px; /* Maximum width */
|
|
317
|
+
--cal-tooltip-padding: 8px 12px; /* Inner padding */
|
|
318
|
+
--cal-tooltip-radius: 6px; /* Border radius */
|
|
319
|
+
--cal-tooltip-font-size: 12px; /* Font size */
|
|
320
|
+
--cal-tooltip-offset: 8px; /* Distance from event */
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Limitations
|
|
325
|
+
|
|
326
|
+
- Tooltips display **plain text only** (no HTML rendering)
|
|
327
|
+
- Special characters are automatically escaped for security
|
|
328
|
+
- Maximum width is 250px by default (can be customized via CSS variables)
|
|
329
|
+
|
|
221
330
|
## API Methods
|
|
222
331
|
|
|
223
332
|
```javascript
|
|
@@ -262,6 +371,46 @@ const calendar = new SimpleCalendarJs('#calendar', {
|
|
|
262
371
|
|
|
263
372
|
Supported locales include: `en-US`, `en-GB`, `fr-FR`, `de-DE`, `es-ES`, `it-IT`, `pt-PT`, `pt-BR`, `ja-JP`, `zh-CN`, `ko-KR`, `ar-SA`, `ru-RU`, `tr-TR`, and [many more](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#locales).
|
|
264
373
|
|
|
374
|
+
### Weekday Name Format
|
|
375
|
+
|
|
376
|
+
Control how day names are displayed using the `weekdayFormat` option:
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
const calendar = new SimpleCalendarJs('#calendar', {
|
|
380
|
+
locale: 'pt-PT',
|
|
381
|
+
weekdayFormat: 'long' // Full day names
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Available formats:**
|
|
386
|
+
- `'narrow'` - 1-2 letter abbreviation (S, M, T, W, T, F, S)
|
|
387
|
+
- `'short'` - Short abbreviation (Sun, Mon, Tue... or Dom, Seg, Ter...)
|
|
388
|
+
- `'long'` - Full day name (Sunday, Monday... or Domingo, Segunda-feira...)
|
|
389
|
+
|
|
390
|
+
**Examples:**
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
// English with narrow format
|
|
394
|
+
new SimpleCalendarJs('#calendar', {
|
|
395
|
+
locale: 'en-US',
|
|
396
|
+
weekdayFormat: 'narrow' // S M T W T F S
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Portuguese (Portugal) with long format
|
|
400
|
+
new SimpleCalendarJs('#calendar', {
|
|
401
|
+
locale: 'pt-PT',
|
|
402
|
+
weekdayFormat: 'long' // Domingo Segunda-feira Terça-feira...
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Portuguese (Brazil) with short format (default)
|
|
406
|
+
new SimpleCalendarJs('#calendar', {
|
|
407
|
+
locale: 'pt-BR',
|
|
408
|
+
weekdayFormat: 'short' // Dom Seg Ter Qua...
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**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
|
+
|
|
265
414
|
## Dark Mode
|
|
266
415
|
|
|
267
416
|
Dark mode is automatically detected from the user's system preferences. You can also manually toggle it:
|
|
@@ -289,40 +438,88 @@ For framework wrappers, use the `darkMode` prop:
|
|
|
289
438
|
|
|
290
439
|
## Styling & Theming
|
|
291
440
|
|
|
292
|
-
Customize the calendar appearance using CSS custom properties
|
|
441
|
+
Customize the calendar appearance using CSS custom properties.
|
|
442
|
+
|
|
443
|
+
### Override Light Mode Colors
|
|
444
|
+
|
|
445
|
+
Target the `.uc-calendar` class to customize light mode:
|
|
293
446
|
|
|
294
447
|
```css
|
|
295
448
|
.uc-calendar {
|
|
296
449
|
/* Primary color */
|
|
297
|
-
--cal-primary: #
|
|
298
|
-
--cal-primary-dark: #
|
|
299
|
-
--cal-primary-light: #
|
|
450
|
+
--cal-primary: #10b981; /* Green theme */
|
|
451
|
+
--cal-primary-dark: #059669;
|
|
452
|
+
--cal-primary-light: #d1fae5;
|
|
453
|
+
|
|
454
|
+
/* Today indicator */
|
|
455
|
+
--cal-today-bg: #d1fae5;
|
|
456
|
+
--cal-today-text: #059669;
|
|
457
|
+
}
|
|
458
|
+
```
|
|
300
459
|
|
|
301
|
-
|
|
302
|
-
--cal-bg: #ffffff;
|
|
303
|
-
--cal-bg-secondary: #f9fafb;
|
|
460
|
+
### Override Dark Mode Colors
|
|
304
461
|
|
|
305
|
-
|
|
306
|
-
--cal-text: #111827;
|
|
307
|
-
--cal-text-subtle: #6b7280;
|
|
462
|
+
Target `.uc-calendar.uc-dark` to customize dark mode:
|
|
308
463
|
|
|
309
|
-
|
|
310
|
-
|
|
464
|
+
```css
|
|
465
|
+
.uc-calendar.uc-dark {
|
|
466
|
+
/* Dark mode colors */
|
|
467
|
+
--cal-bg: #1f2937;
|
|
468
|
+
--cal-bg-secondary: #111827;
|
|
469
|
+
--cal-text: #f9fafb;
|
|
470
|
+
--cal-border: #374151;
|
|
471
|
+
|
|
472
|
+
/* Dark mode today/selected indicators */
|
|
473
|
+
--cal-today-bg: #065f46; /* Custom dark green */
|
|
474
|
+
--cal-selected-bg: #047857;
|
|
475
|
+
}
|
|
476
|
+
```
|
|
311
477
|
|
|
312
|
-
|
|
313
|
-
--cal-today-bg: #eef2ff;
|
|
314
|
-
--cal-today-text: var(--cal-primary);
|
|
478
|
+
### Customize Both Light and Dark Modes
|
|
315
479
|
|
|
316
|
-
|
|
317
|
-
--cal-event-bg: var(--cal-primary);
|
|
318
|
-
--cal-event-text: #ffffff;
|
|
480
|
+
Example with a complete brand color scheme:
|
|
319
481
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
482
|
+
```css
|
|
483
|
+
/* Light mode - Yellow theme */
|
|
484
|
+
.uc-calendar {
|
|
485
|
+
--cal-primary: #eab308; /* yellow-500 */
|
|
486
|
+
--cal-primary-dark: #ca8a04; /* yellow-600 */
|
|
487
|
+
--cal-primary-light: #fef9c3; /* yellow-100 */
|
|
488
|
+
--cal-today-bg: #fef9c3;
|
|
489
|
+
--cal-today-text: #854d0e;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/* Dark mode - Yellow theme */
|
|
493
|
+
.uc-calendar.uc-dark {
|
|
494
|
+
--cal-primary: #eab308;
|
|
495
|
+
--cal-primary-dark: #facc15;
|
|
496
|
+
--cal-primary-light: #713f12;
|
|
497
|
+
--cal-today-bg: #713f12; /* yellow-900 */
|
|
498
|
+
--cal-selected-bg: #854d0e; /* yellow-800 */
|
|
323
499
|
}
|
|
324
500
|
```
|
|
325
501
|
|
|
502
|
+
### Available CSS Variables
|
|
503
|
+
|
|
504
|
+
**Colors:**
|
|
505
|
+
- `--cal-primary`, `--cal-primary-dark`, `--cal-primary-light` - Primary theme colors
|
|
506
|
+
- `--cal-bg`, `--cal-bg-secondary` - Background colors
|
|
507
|
+
- `--cal-text`, `--cal-text-subtle`, `--cal-text-muted` - Text colors
|
|
508
|
+
- `--cal-border`, `--cal-border-strong` - Border colors
|
|
509
|
+
- `--cal-today-bg`, `--cal-today-text` - Today indicator
|
|
510
|
+
- `--cal-selected-bg` - Selected date background
|
|
511
|
+
- `--cal-hover`, `--cal-hover-strong` - Hover states
|
|
512
|
+
|
|
513
|
+
**Sizing:**
|
|
514
|
+
- `--cal-font-size` - Base font size (default: 13px)
|
|
515
|
+
- `--cal-hour-height` - Hour row height in week/day views (default: 60px)
|
|
516
|
+
- `--cal-event-height` - Event bar height (default: 22px)
|
|
517
|
+
|
|
518
|
+
**Tooltips:**
|
|
519
|
+
- `--cal-tooltip-bg`, `--cal-tooltip-text`, `--cal-tooltip-border` - Tooltip colors
|
|
520
|
+
- `--cal-tooltip-max-width`, `--cal-tooltip-padding`, `--cal-tooltip-radius` - Tooltip sizing
|
|
521
|
+
- `--cal-tooltip-font-size`, `--cal-tooltip-offset` - Tooltip typography
|
|
522
|
+
|
|
326
523
|
All styles are scoped under `.uc-calendar` to prevent conflicts with your existing CSS.
|
|
327
524
|
|
|
328
525
|
## Framework Wrapper APIs
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
2
|
+
* SimpleCalendarJs v3.0.2 — 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>
|
|
6
6
|
* @homepage https://www.simplecalendarjs.com
|
|
7
7
|
* @license SEE LICENSE IN LICENSE
|
|
8
|
-
* @repository https://github.com/pclslopes/SimpleCalendarJs
|
|
9
8
|
*
|
|
10
9
|
* All styles scoped under .uc-calendar to prevent leaking.
|
|
11
10
|
* Override any --cal-* variable in :root or on .uc-calendar.
|
|
12
11
|
*/
|
|
13
|
-
: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}.uc-calendar{font-family:var(--cal-font-family);font-size:var(--cal-font-size);color:var(--cal-text);background:var(--cal-bg);border:1px solid var(--cal-border);border-radius:var(--cal-radius);overflow:hidden;display:flex;flex-direction:column;position:relative;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:hidden;display:flex;flex-direction:column}.uc-month-view{display:flex;flex-direction:column;flex:1;overflow:hidden}.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:hidden}.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:hidden}.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;overflow:hidden;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)}.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:var(--cal-event-height);display:flex;align-items:center;padding:0 6px;font-size:11px;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:hidden}.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:hidden;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;overflow:hidden;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:4}.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)}}@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-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:#312e81;--cal-today-bg:#1e1b4b;--cal-primary-light:#1e1b4b;--cal-toolbar-bg:#111827}@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);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}}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
2
|
+
* SimpleCalendarJs v3.0.2
|
|
3
3
|
* A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
|
|
4
4
|
*
|
|
5
5
|
* @author Pedro Lopes <simplecalendarjs@gmail.com>
|
|
6
6
|
* @homepage https://www.simplecalendarjs.com
|
|
7
7
|
* @license SEE LICENSE IN LICENSE
|
|
8
|
-
* @repository https://github.com/pclslopes/SimpleCalendarJs
|
|
9
8
|
*/
|
|
10
|
-
!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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}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}
|
|
11
10
|
/* ============================================================
|
|
12
11
|
ES MODULE EXPORT
|
|
13
12
|
Also available as window.SimpleCalendarJs via the IIFE wrapper.
|
|
14
13
|
============================================================ */
|
|
15
|
-
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",use24Hour:!1,showTimeInItems:!0,showGridLines:!0,showToolbar:!0,showTodayButton:!0,showNavigation:!0,showTitle:!0,showYearPicker:!0,showViewSwitcher:!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._el.appendChild(this._root),this._onClick=this._handleClick.bind(this),this._root.addEventListener("click",this._onClick),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.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">‹</button>\n <span class="uc-year-range">${a} – ${a+11}</span>\n <button class="uc-year-nav-btn" data-action="year-next" aria-label="Next years">›</button>\n </div>\n <div class="uc-year-grid">${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">‹</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">›</button>':""}\n </div>`}let y="";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;y=`\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 g="";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&&(g=`\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}${y}${g}\n </div>\n `}_buildMonthView(){const{locale:a,weekStartsOn:n}=this._opts,s=c(this._date.getFullYear(),this._date.getMonth(),n),i=d(a,n,"short"),o=this._events.map(a=>({...a,_origStart:a.start,start:y(a)?t(a.start):a.start,end:y(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(y),o=e.filter(t=>!y(t)),r=g(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",y=i?"var(--cal-event-border-radius)":"0";v+=`\n <div class="uc-event-bar${s?"":" uc-continues-left"}${i?"":" uc-continues-right"}"\n style="left:calc(${r}% + 2px);width:calc(${c}% - 4px);top:${l};background:${h(d)};border-radius:${u} ${y} ${y} ${u};"\n data-event-id="${h(t.id)}" data-action="event-click" title="${h(t.title)}">\n ${s?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\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 y=d[e];let g=i;if(y.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>`:"";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" title="${h(t.title)}">\n ${r}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}else g++}),g>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)) + 4px)"};"\n data-date="${s.toISOString()}" data-action="more-click">\n +${g} 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:"short"}).format(a[0])]:d(i,o,"short"),p=t(a[0]),_=e(a[a.length-1]),w=this._events.filter(t=>y(t)&&t.start<=_&&e(t.end)>=p).map(a=>({...a,start:t(a.start),end:e(a.end)})),m=this._events.filter(t=>!y(t)&&t.start>=p&&t.start<=_),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?w.map((t,e)=>({event:t,startCol:0,endCol:0,slot:e,isStart:!0,isEnd:!0})):g(w,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=i?"var(--cal-event-border-radius)":"0",l=o?"var(--cal-event-border-radius)":"0";b+=`\n <div class="uc-event-bar${i?"":" uc-continues-left"}${o?"":" uc-continues-right"}"\n style="left:calc(${e*r}% + 2px);width:calc(${(n-e+1)*r}% - 4px);top:${`calc(${s} * (var(--cal-event-height) + 3px) + 2px)`};background:${h(t.color||"var(--cal-event-bg)")};border-radius:${c} ${l} ${l} ${c};"\n data-event-id="${h(t.id)}" data-action="event-click" title="${h(t.title)}">\n ${i?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\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)`,y=t.color||"var(--cal-event-bg)",g=l(t.start,i,r),p=s-n<=60?" uc-timed-event--short":"",_=this._opts.showTimeInItems?`<span class="uc-event-time">${h(g)}</span>`:"";return`\n <div class="uc-timed-event${p}"\n style="top:${o};height:${c};left:${u};width:${v};background:${h(y)};"\n data-event-id="${h(t.id)}" data-action="event-click" title="${h(t.title)}">\n ${_}\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)}}}_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,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">‹</button>\n <span class="uc-year-range">${a} – ${a+11}</span>\n <button class="uc-year-nav-btn" data-action="year-next" aria-label="Next years">›</button>\n </div>\n <div class="uc-year-grid">${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">‹</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">›</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>`:" "}\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>`:" "}\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)}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-calendar-js",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "A clean, modern, and feature-rich JavaScript calendar component with zero dependencies. Responsive design and intuitive navigation.",
|
|
5
5
|
"main": "dist/simple-calendar-js.min.js",
|
|
6
6
|
"style": "dist/simple-calendar-js.min.css",
|
|
@@ -15,10 +15,6 @@
|
|
|
15
15
|
"build": "node build.js",
|
|
16
16
|
"prepublishOnly": "npm run build"
|
|
17
17
|
},
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/pclslopes/SimpleCalendarJs.git"
|
|
21
|
-
},
|
|
22
18
|
"keywords": [
|
|
23
19
|
"calendar",
|
|
24
20
|
"javascript",
|
|
@@ -42,7 +38,7 @@
|
|
|
42
38
|
"author": "Pedro Lopes <simplecalendarjs@gmail.com> (https://www.simplecalendarjs.com)",
|
|
43
39
|
"license": "SEE LICENSE IN LICENSE",
|
|
44
40
|
"bugs": {
|
|
45
|
-
"
|
|
41
|
+
"email": "simplecalendarjs@gmail.com"
|
|
46
42
|
},
|
|
47
43
|
"homepage": "https://www.simplecalendarjs.com",
|
|
48
44
|
"devDependencies": {
|