simple-calendar-js 3.0.3 → 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 +86 -1
- package/dist/simple-calendar-js.min.css +2 -2
- package/dist/simple-calendar-js.min.js +3 -3
- package/frameworks/simple-calendar-js-angular.component.ts +16 -1
- package/frameworks/simple-calendar-js-react.jsx +8 -2
- package/frameworks/simple-calendar-js-vue.js +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, zero-dependency JavaScript calendar component with internationalization support and framework wrappers for React, Vue, and Angular.
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
## Features
|
|
@@ -226,6 +226,91 @@ const events = [
|
|
|
226
226
|
];
|
|
227
227
|
```
|
|
228
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
|
+
|
|
229
314
|
## Tooltips
|
|
230
315
|
|
|
231
316
|
SimpleCalendarJs includes built-in tooltip support for displaying additional event information on hover.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
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-list-view{padding:0;overflow:visible}.uc-list-empty{display:flex;align-items:center;justify-content:center;min-height:300px;color:var(--cal-text-muted);font-size:14px}.uc-list-date-group{margin-bottom:24px;overflow:visible}.uc-list-date-group:last-child{margin-bottom:0}.uc-list-events{background:var(--cal-bg);overflow:visible}.uc-list-event{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-bottom:1px solid var(--cal-border);cursor:pointer;transition:background var(--cal-transition);position:relative;overflow:visible}.uc-list-date-header{font-size:14px;font-weight:600;color:var(--cal-text);padding:12px 16px;background:var(--cal-bg-secondary);border-bottom:1px solid var(--cal-border);position:sticky;top:0;z-index:1;overflow:visible}.uc-list-event:hover{background:var(--cal-hover)}.uc-list-event:last-child{border-bottom:none}.uc-list-event-indicator{width:8px;height:8px;border-radius:50%;margin-top:4px;flex-shrink:0}.uc-list-event-time{font-size:13px;font-weight:500;color:var(--cal-text-secondary);min-width:80px;flex-shrink:0}.uc-list-event-content{flex:1;min-width:0}.uc-list-event-title{font-size:14px;font-weight:500;color:var(--cal-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::after,.uc-calendar [data-tooltip]:not([data-tooltip=""]):hover::before{opacity:1;visibility:visible;transition-delay:0.4s}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{content:attr(data-tooltip);position:absolute;bottom:calc(100% + var(--cal-tooltip-offset));left:50%;transform:translateX(-50%);background:var(--cal-tooltip-bg);color:var(--cal-tooltip-text);border:1px solid var(--cal-tooltip-border);box-shadow:var(--cal-tooltip-shadow);padding:var(--cal-tooltip-padding);border-radius:var(--cal-tooltip-radius);font-size:var(--cal-tooltip-font-size);font-weight:500;line-height:1.4;max-width:var(--cal-tooltip-max-width);width:max-content;white-space:pre-wrap;word-wrap:break-word;text-align:left;z-index:1000;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::after{content:'';position:absolute;bottom:calc(100% + var(--cal-tooltip-offset) - 5px);left:50%;transform:translateX(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid var(--cal-tooltip-bg);z-index:1002;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .2s ease,visibility .2s ease}.uc-calendar [data-tooltip]:not([data-tooltip=""])::before{filter:drop-shadow(0 1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-left::before{left:auto;right:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-left::after{left:auto;right:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::before{left:0;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-right::after{left:12px;transform:translateX(0)}.uc-calendar [data-tooltip].uc-tooltip-bottom::before{bottom:auto;top:calc(100% + var(--cal-tooltip-offset));filter:drop-shadow(0 -1px 0 var(--cal-tooltip-border))}.uc-calendar [data-tooltip].uc-tooltip-bottom::after{bottom:auto;top:calc(100% + var(--cal-tooltip-offset) - 5px);border-top:5px solid var(--cal-tooltip-bg);border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:none;transform:translateX(-50%) rotate(180deg)}@media (max-width:768px){.uc-toolbar{padding:8px 10px}.uc-title{font-size:14px}.uc-toolbar-center{order:-1;width:100%;flex:none}.uc-toolbar-section{justify-content:center}.uc-view-btn{padding:5px 8px;font-size:12px}:root{--cal-time-col-width:52px;--cal-hour-height:52px;--cal-cell-min-height:88px;--cal-event-height:20px}.uc-week-day-num{width:24px;height:24px;font-size:13px}.uc-week-day-name{font-size:10px}.uc-hour-cell:nth-child(odd) .uc-hour-label{visibility:hidden}}@media (max-width:480px){.uc-today-btn{display:none}.uc-toolbar-section.uc-toolbar-right{gap:2px}}.uc-calendar.uc-no-grid .uc-day-cell,.uc-calendar.uc-no-grid .uc-day-header-row,.uc-calendar.uc-no-grid .uc-day-name-row,.uc-calendar.uc-no-grid .uc-grid-line,.uc-calendar.uc-no-grid .uc-hour-cell,.uc-calendar.uc-no-grid .uc-time-col,.uc-calendar.uc-no-grid .uc-time-gutter,.uc-calendar.uc-no-grid .uc-week-day-header,.uc-calendar.uc-no-grid .uc-week-row{border:none!important}.uc-calendar.uc-no-grid .uc-all-day-section{border-top:none!important;border-left:none!important;border-right:none!important}.uc-calendar.uc-no-border{border:none!important}.uc-calendar.uc-dark,.uc-dark .uc-calendar{--cal-bg:#1f2937;--cal-text:#f9fafb;--cal-text-subtle:#9ca3af;--cal-text-muted:#6b7280;--cal-border:#374151;--cal-border-strong:#4b5563;--cal-hover:#374151;--cal-hover-strong:#4b5563;--cal-selected-bg:#78716c;--cal-today-bg:#57534e;--cal-primary-light:#57534e;--cal-toolbar-bg:#111827;--cal-tooltip-bg:#374151;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#4b5563}@media print{.uc-toolbar{display:none}.uc-time-body{overflow:visible}.uc-calendar{border:none}}
|
|
12
|
+
:root{--cal-bg:#ffffff;--cal-text:#111827;--cal-text-subtle:#6b7280;--cal-text-muted:#9ca3af;--cal-border:#e5e7eb;--cal-border-strong:#d1d5db;--cal-primary:#4f46e5;--cal-primary-dark:#4338ca;--cal-primary-light:#eef2ff;--cal-event-bg:var(--cal-primary);--cal-event-text:#ffffff;--cal-event-border-radius:3px;--cal-today-bg:#eef2ff;--cal-today-text:var(--cal-primary);--cal-hover:#f9fafb;--cal-hover-strong:#f3f4f6;--cal-selected-bg:#ede9fe;--cal-font-family:inherit;--cal-font-size:13px;--cal-time-col-width:64px;--cal-hour-height:60px;--cal-cell-min-height:112px;--cal-event-height:22px;--cal-event-gap:2px;--cal-header-day-height:30px;--cal-day-name-height:36px;--cal-now-color:#ef4444;--cal-toolbar-bg:var(--cal-bg);--cal-radius:8px;--cal-shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.06);--cal-transition:150ms ease;--cal-tooltip-bg:#1f2937;--cal-tooltip-text:#f9fafb;--cal-tooltip-border:#374151;--cal-tooltip-shadow:0 4px 12px rgba(0, 0, 0, 0.15);--cal-tooltip-max-width:250px;--cal-tooltip-padding:8px 12px;--cal-tooltip-radius:6px;--cal-tooltip-font-size:12px;--cal-tooltip-offset:8px}.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
|
+
* 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 s(t,e){return t.getFullYear()===e.getFullYear()&&t.getMonth()===e.getMonth()&&t.getDate()===e.getDate()}function n(t){return s(t,new Date)}function i(e,a){return Math.floor((t(a)-t(e))/864e5)}function o(t){return t instanceof Date?t:new Date(t)}function
|
|
9
|
+
!function(t,e){"undefined"!=typeof module&&module.exports?module.exports=e():"function"==typeof define&&define.amd?define([],e):t.SimpleCalendarJs=e()}("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:this,function(){"use strict";function t(t){const e=new Date(t);return e.setHours(0,0,0,0),e}function e(t){const e=new Date(t);return e.setHours(23,59,59,999),e}function a(t,e){const a=new Date(t);return a.setDate(a.getDate()+e),a}function s(t,e){return t.getFullYear()===e.getFullYear()&&t.getMonth()===e.getMonth()&&t.getDate()===e.getDate()}function n(t){return s(t,new Date)}function i(e,a){return Math.floor((t(a)-t(e))/864e5)}function o(t){return t instanceof Date?t:new Date(t)}function 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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}const u={"en-US":{today:"Today",month:"Month",week:"Week",day:"Day",list:"List",allDay:"All-Day"},"en-GB":{today:"Today",month:"Month",week:"Week",day:"Day",list:"List",allDay:"All-Day"},"es-ES":{today:"Hoy",month:"Mes",week:"Semana",day:"Día",list:"Lista",allDay:"Todo el día"},"es-MX":{today:"Hoy",month:"Mes",week:"Semana",day:"Día",list:"Lista",allDay:"Todo el día"},"fr-FR":{today:"Aujourd'hui",month:"Mois",week:"Semaine",day:"Jour",list:"Liste",allDay:"Toute la journée"},"fr-CA":{today:"Aujourd'hui",month:"Mois",week:"Semaine",day:"Jour",list:"Liste",allDay:"Toute la journée"},"de-DE":{today:"Heute",month:"Monat",week:"Woche",day:"Tag",list:"Liste",allDay:"Ganztägig"},"it-IT":{today:"Oggi",month:"Mese",week:"Settimana",day:"Giorno",list:"Elenco",allDay:"Tutto il giorno"},"pt-PT":{today:"Hoje",month:"Mês",week:"Semana",day:"Dia",list:"Lista",allDay:"Dia todo"},"pt-BR":{today:"Hoje",month:"Mês",week:"Semana",day:"Dia",list:"Lista",allDay:"Dia todo"},"nl-NL":{today:"Vandaag",month:"Maand",week:"Week",day:"Dag",list:"Lijst",allDay:"Hele dag"},"pl-PL":{today:"Dzisiaj",month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Lista",allDay:"Cały dzień"},"ru-RU":{today:"Сегодня",month:"Месяц",week:"Неделя",day:"День",list:"Список",allDay:"Весь день"},"tr-TR":{today:"Bugün",month:"Ay",week:"Hafta",day:"Gün",list:"Liste",allDay:"Tüm gün"},"sv-SE":{today:"Idag",month:"Månad",week:"Vecka",day:"Dag",list:"Lista",allDay:"Heldag"},"da-DK":{today:"I dag",month:"Måned",week:"Uge",day:"Dag",list:"Liste",allDay:"Hele dagen"},"fi-FI":{today:"Tänään",month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Lista",allDay:"Koko päivä"},"no-NO":{today:"I dag",month:"Måned",week:"Uke",day:"Dag",list:"Liste",allDay:"Hele dagen"},"cs-CZ":{today:"Dnes",month:"Měsíc",week:"Týden",day:"Den",list:"Seznam",allDay:"Celý den"},"hu-HU":{today:"Ma",month:"Hónap",week:"Hét",day:"Nap",list:"Lista",allDay:"Egész nap"},"ro-RO":{today:"Astăzi",month:"Lună",week:"Săptămână",day:"Zi",list:"Listă",allDay:"Toată ziua"},"el-GR":{today:"Σήμερα",month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα",list:"Λίστα",allDay:"Ολοήμερο"},"ja-JP":{today:"今日",month:"月",week:"週",day:"日",list:"リスト",allDay:"終日"},"ko-KR":{today:"오늘",month:"월",week:"주",day:"일",list:"목록",allDay:"종일"},"zh-CN":{today:"今天",month:"月",week:"周",day:"日",list:"列表",allDay:"全天"},"zh-TW":{today:"今天",month:"月",week:"週",day:"日",list:"列表",allDay:"全天"},"ar-SA":{today:"اليوم",month:"شهر",week:"أسبوع",day:"يوم",list:"قائمة",allDay:"طوال اليوم"},"he-IL":{today:"היום",month:"חודש",week:"שבוע",day:"יום",list:"רשימה",allDay:"כל היום"},"hi-IN":{today:"आज",month:"महीना",week:"सप्ताह",day:"दिन",list:"सूची",allDay:"पूरे दिन"},"th-TH":{today:"วันนี้",month:"เดือน",week:"สัปดาห์",day:"วัน",list:"รายการ",allDay:"ตลอดวัน"},"vi-VN":{today:"Hôm nay",month:"Tháng",week:"Tuần",day:"Ngày",list:"Danh sách",allDay:"Cả ngày"},"id-ID":{today:"Hari ini",month:"Bulan",week:"Minggu",day:"Hari",list:"Daftar",allDay:"Sepanjang hari"},"ms-MY":{today:"Hari ini",month:"Bulan",week:"Minggu",day:"Hari",list:"Senarai",allDay:"Sepanjang hari"},"uk-UA":{today:"Сьогодні",month:"Місяць",week:"Тиждень",day:"День",list:"Список",allDay:"Весь день"}};function v(t,e){const a=t.split("-")[0];return(u[t]||u[a]||u["en-US"])[e]}function 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,maxEventsPerCell:3,listDaysForward:30,enabledViews:["month","week","day"],fetchEvents:null,onEventClick:null,onSlotClick:null,onViewChange:null,onNavigate:null},a),this._view=this._opts.defaultView,this._date=t(this._opts.defaultDate||new Date),this._events=[],this._cachedRange=null,this._cachedEvents=[],this._nowInterval=null,this._yearPickerOpen=!1,this._yearPickerBase=0,this._yearOutsideHandler=null,this._root=document.createElement("div"),this._root.className="uc-calendar",this._opts.showGridLines||this._root.classList.add("uc-no-grid"),this._opts.showBorder||this._root.classList.add("uc-no-border"),this._el.appendChild(this._root),this._onClick=this._handleClick.bind(this),this._root.addEventListener("click",this._onClick),this._onMouseMove=this._handleTooltipPosition.bind(this),this._root.addEventListener("mouseover",this._onMouseMove),this._fetchAndRender(),this._startNowUpdater()}setView(t){t!==this._view&&(this._view=t,this._opts.onViewChange&&this._opts.onViewChange(t),this._fetchAndRender())}navigate(t){const e=new Date(this._date);"month"===this._view?(e.setMonth(e.getMonth()+t),e.setDate(1)):"week"===this._view?e.setDate(e.getDate()+7*t):e.setDate(e.getDate()+t),this._date=e;const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender()}goToToday(){this._date=t(new Date);const e=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(e.start,e.end),this._fetchAndRender()}goToDate(e){this._date=t(o(e)),this._fetchAndRender()}refresh(){this._cachedRange=null,this._cachedEvents=[],this._fetchAndRender()}destroy(){this._nowInterval&&clearInterval(this._nowInterval),this._yearOutsideHandler&&document.removeEventListener("click",this._yearOutsideHandler),this._root.removeEventListener("click",this._onClick),this._root.removeEventListener("mouseover",this._onMouseMove),this._root.remove()}_getRange(){if("month"===this._view){const a=l(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}if("week"===this._view){const a=r(this._date,this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[6])}}if("list"===this._view){const t=new Date,e=new Date(t);return e.setDate(e.getDate()+this._opts.listDaysForward),{start:t,end:e}}return{start:t(this._date),end:e(this._date)}}_getMonthGridRange(){const a=l(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}async _fetchAndRender(){if(this._root.innerHTML=this._buildShell(),!this._opts.fetchEvents)return void this._renderView();const t=this._getRange();if(this._cachedRange&&this._cachedRange.start<=t.start&&this._cachedRange.end>=t.end)return this._events=this._cachedEvents.filter(e=>e.end>=t.start&&e.start<=t.end),void this._renderView();const e=this._root.querySelector(".uc-loading");e&&(e.style.display="flex");try{let e;e="list"===this._view?t:this._getMonthGridRange();const a=(await this._opts.fetchEvents(e.start,e.end)||[]).map(t=>({...t,start:o(t.start),end:o(t.end)}));this._cachedRange={start:e.start,end:e.end},this._cachedEvents=a,this._events=a.filter(e=>e.end>=t.start&&e.start<=t.end)}catch(t){this._events=[]}e&&(e.style.display="none"),this._renderView()}_renderView(){const t=this._root.querySelector(".uc-view-container");if(t){if(0===this._opts.maxEventsPerCell)this._root.classList.add("uc-unlimited-events"),this._root.style.removeProperty("--cal-cell-min-height");else{this._root.classList.remove("uc-unlimited-events");const t=30+24*this._opts.maxEventsPerCell+28;this._root.style.setProperty("--cal-cell-min-height",`${t}px`)}if("month"===this._view)t.innerHTML=this._buildMonthView();else if("week"===this._view){const e=r(this._date,this._opts.weekStartsOn);t.innerHTML=this._buildWeekOrDayView(e),this._scrollToBusinessHours(t)}else"list"===this._view?t.innerHTML=this._buildListView():(t.innerHTML=this._buildWeekOrDayView([this._date]),this._scrollToBusinessHours(t))}}_scrollToBusinessHours(t){requestAnimationFrame(()=>{const e=t.querySelector(".uc-time-body");if(!e)return;const a=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60;e.scrollTop=7*a})}_renderToolbar(){const t=this._root.querySelector(".uc-toolbar");if(!t)return;const e=document.createElement("div");e.innerHTML=this._buildToolbar(),this._root.replaceChild(e.firstElementChild,t)}_buildShell(){return`\n ${this._buildToolbar()}\n <div class="uc-loading" style="display:none">\n <div class="uc-spinner"></div>\n </div>\n <div class="uc-view-container"></div>\n `}_buildToolbar(){if(!this._opts.showToolbar)return"";const t=this._date.getFullYear(),e=(new Date).getFullYear();let a,n=!0;if("month"===this._view)a=new Intl.DateTimeFormat(this._opts.locale,{month:"long"}).format(this._date);else if("week"===this._view){const t=r(this._date,this._opts.weekStartsOn);a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}`}(t[0],t[6],this._opts.locale)}else if("list"===this._view){const t=new Date,e=new Date(t);e.setDate(e.getDate()+this._opts.listDaysForward),a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}, ${t.getFullYear()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}, ${e.getFullYear()}`}(t,e,this._opts.locale),n=!1}else a=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",month:"long",day:"numeric"}).format(this._date);let i="";if(this._opts.showYearPicker&&this._yearPickerOpen){const a=this._yearPickerBase,s=Array.from({length:12},(s,n)=>{const i=a+n,o=i===t;return`<button class="${"uc-year-item"+(o?" uc-active":"")+(i===e&&!o?" uc-today-year":"")}" data-action="select-year" data-year="${i}">${i}</button>`}).join("");i=`\n <div class="uc-year-picker">\n <div class="uc-year-picker-nav">\n <button class="uc-year-nav-btn" data-action="year-prev" aria-label="Previous years">‹</button>\n <span class="uc-year-range">${a} – ${a+11}</span>\n <button class="uc-year-nav-btn" data-action="year-next" aria-label="Next years">›</button>\n </div>\n <div class="uc-year-grid">${s}</div>\n </div>`}const o=this._opts.locale,l=v(o,"today"),c=v(o,"month"),d=v(o,"week"),u=v(o,"day"),p=v(o,"list");let _="";if((this._opts.showNavigation||this._opts.showTodayButton)&&"list"!==this._view){const t=this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="prev" aria-label="Previous">‹</button>':"",e=new Date,a=s(this._date,e)?" uc-active":"";_=`\n <div class="uc-toolbar-section uc-toolbar-left">\n ${t}${this._opts.showTodayButton?`<button class="uc-btn uc-today-btn${a}" data-action="today">${h(l)}</button>`:""}${this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="next" aria-label="Next">›</button>':""}\n </div>`}let g="";if(this._opts.showTitle){const e=n?this._opts.showYearPicker?`<button class="uc-year-btn${this._yearPickerOpen?" uc-open":""}" data-action="year-pick" aria-label="Select year">${t}</button>`:t:"";g=`\n <div class="uc-toolbar-section uc-toolbar-center">\n <h2 class="uc-title">\n <span class="uc-title-main">${h(a)}</span>\n ${e}\n </h2>\n ${i}\n </div>`}let w="";if(this._opts.showViewSwitcher){const t=this._opts.enabledViews,e=[];t.includes("month")&&e.push(`<button class="uc-btn uc-view-btn${"month"===this._view?" uc-active":""}" data-view="month">${h(c)}</button>`),t.includes("week")&&e.push(`<button class="uc-btn uc-view-btn${"week"===this._view?" uc-active":""}" data-view="week">${h(d)}</button>`),t.includes("day")&&e.push(`<button class="uc-btn uc-view-btn${"day"===this._view?" uc-active":""}" data-view="day">${h(u)}</button>`),t.includes("list")&&e.push(`<button class="uc-btn uc-view-btn${"list"===this._view?" uc-active":""}" data-view="list">${h(p)}</button>`),e.length>0&&(w=`\n <div class="uc-toolbar-section uc-toolbar-right">\n <div class="uc-view-switcher">\n ${e.join("")}\n </div>\n </div>`)}return`\n <div class="uc-toolbar">\n ${_}${g}${w}\n </div>\n `}_buildMonthView(){const{locale:a,weekStartsOn:s}=this._opts,n=l(this._date.getFullYear(),this._date.getMonth(),s),i=d(a,s,this._opts.weekdayFormat),o=this._events.map(a=>({...a,_origStart:a.start,start:p(a)?t(a.start):a.start,end:p(a)?e(a.end):a.end})),r=i.map(t=>`<div class="uc-month-day-name">${h(t)}</div>`).join(""),c=[];for(let t=0;t<n.length;t+=7)c.push(n.slice(t,t+7));return`\n <div class="uc-month-view">\n <div class="uc-month-header">${r}</div>\n <div class="uc-month-body">${c.map(t=>this._buildWeekRow(t,o)).join("")}</div>\n </div>\n `}_buildWeekRow(t,e){const a=this._date.getMonth(),i=0===this._opts.maxEventsPerCell?1/0:this._opts.maxEventsPerCell,o=e.filter(p),r=e.filter(t=>!p(t)),l=_(o,t),d=Array.from({length:7},()=>new Set);for(const{startCol:t,endCol:e,slot:a}of l)for(let s=t;s<=e;s++)d[s].add(a);const u=t.map(t=>r.filter(e=>s(e.start,t)).sort((t,e)=>t.start-e.start)),v=t.map((t,e)=>`\n <div class="uc-day-cell${n(t)?" uc-today":""}${t.getMonth()!==a?" uc-other-month":""}" data-date="${t.toISOString()}" data-action="day-click">\n <span class="uc-day-number" data-action="day-number" data-date="${t.toISOString()}">${t.getDate()}</span>\n </div>`).join("");let g="";for(const{event:t,startCol:e,endCol:a,slot:s,isStart:n,isEnd:o}of l){if(i!==1/0&&s>=i)continue;const r=100/7,l=e*r,c=(a-e+1)*r,d=`calc(var(--cal-header-day-height) + ${s} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,u=t.color||"var(--cal-event-bg)",v=n?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",_=n?"":" uc-continues-left",w=o?"":" uc-continues-right",y=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar${_}${w}"\n style="left:calc(${l}% + 2px);width:calc(${c}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${y}>\n ${n?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n </div>`}let w=-1;for(let e=0;e<7;e++){const a=100/7,s=e*a,n=t[e],o=[...d[e]],r=o.length>0?Math.max(...o)+1:0,l=u[e];if(i===1/0)l.forEach((t,e)=>{const n=r+e;w=Math.max(w,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,o=t.color||"var(--cal-event-bg)",l=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(l)}</span>`:"",u=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(o)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${u}>\n ${d}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`});else{const t=[...d[e]].filter(t=>t>=i).length,o=[];for(let t=r;t<i;t++)o.push(t);let u=t;if(l.forEach((t,e)=>{if(e<o.length){const n=o[e];w=Math.max(w,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,r=t.color||"var(--cal-event-bg)",l=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(l)}</span>`:"",u=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(r)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${u}>\n ${d}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}else u++}),u>0){g+=`\n <div class="uc-more-link"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${`calc(var(--cal-header-day-height) + ${i} * (var(--cal-event-height) + var(--cal-event-gap)) + 2px)`};"\n data-date="${n.toISOString()}" data-action="more-click">\n +${u} more\n </div>`}}}for(const{slot:t}of l)(i===1/0||t<i)&&(w=Math.max(w,t));let y="";return i===1/0&&(y=w>=0?` style="min-height: calc(var(--cal-header-day-height) + ${w+1} * (var(--cal-event-height) + var(--cal-event-gap)) + 8px);"`:' style="min-height: 80px;"'),`\n <div class="uc-week-row"${y}>\n <div class="uc-week-cells">${v}</div>\n <div class="uc-week-events">${g}</div>\n </div>`}_buildWeekOrDayView(a){const{locale:i,weekStartsOn:o,use24Hour:r}=this._opts,l=1===a.length,u=l?" uc-day-header-single":"",v=l?[new Intl.DateTimeFormat(i,{weekday:this._opts.weekdayFormat}).format(a[0])]:d(i,o,this._opts.weekdayFormat),g=t(a[0]),w=e(a[a.length-1]),y=this._events.filter(t=>p(t)&&t.start<=w&&e(t.end)>=g).map(a=>({...a,start:t(a.start),end:e(a.end)})),m=this._events.filter(t=>!p(t)&&t.start>=g&&t.start<=w),f=a.map((t,e)=>{const a=n(t)?" uc-today":"",s=t.getDate();return`\n <div class="uc-week-day-header${a}">\n <span class="uc-week-day-name">${h(v[e])}</span>\n <span class="uc-week-day-num" data-action="day-number" data-date="${t.toISOString()}">${s}</span>\n </div>`}).join(""),$=l?y.map((t,e)=>({event:t,startCol:0,endCol:0,slot:e,isStart:!0,isEnd:!0})):_(y,a),k=$.length?Math.max(...$.map(t=>t.slot))+1:0;let b="";for(const{event:t,startCol:e,endCol:s,slot:n,isStart:i,isEnd:o}of $){const r=100/a.length,l=e*r,c=(s-e+1)*r,d=`calc(${n} * (var(--cal-event-height) + 3px) + 2px)`,u=t.color||"var(--cal-event-bg)",v=i?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",_=i?"":" uc-continues-left",g=o?"":" uc-continues-right",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";b+=`\n <div class="uc-event-bar${_}${g}"\n style="left:calc(${l}% + 2px);width:calc(${c}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${i?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n </div>`}const D=`calc(${Math.max(1,k)} * (var(--cal-event-height) + 3px) + 6px)`,S=new Date,M=60*S.getHours()+S.getMinutes(),T=a.length;return`\n <div class="${l?"uc-day-view":"uc-week-view"}">\n <div class="uc-week-header">\n <div class="uc-time-gutter-spacer"></div>\n <div class="uc-week-day-headers${u}">${f}</div>\n </div>\n <div class="uc-all-day-section">\n <div class="uc-time-gutter-spacer uc-all-day-label">all-day</div>\n <div class="uc-all-day-events" style="min-height:${D}">${b}</div>\n </div>\n <div class="uc-time-body">\n <div class="uc-time-grid-inner">\n <div class="uc-time-gutter">${Array.from({length:24},(t,e)=>`<div class="uc-hour-cell"><span class="uc-hour-label">${h(0===e?"":c(new Date(2e3,0,1,e),"en-US",r))}</span></div>`).join("")}</div>\n <div class="uc-time-columns" style="--uc-col-count:${T}">${a.map(t=>{const e=n(t)?" uc-today":"",a=Array.from({length:24},()=>'<div class="uc-hour-row"><div class="uc-half-hour-line"></div></div>').join(""),o=function(t){if(!t.length)return[];const e=[...t].sort((t,e)=>t.start-e.start||e.end-t.end),a=[],s=[];for(const t of e){let e=a.findIndex(e=>e<=t.start);-1===e?(e=a.length,a.push(t.end)):a[e]=t.end,s.push({event:t,col:e})}return s.map(t=>{const e=s.filter(e=>e.event.start<t.event.end&&e.event.end>t.event.start),a=Math.max(...e.map(t=>t.col))+1;return{...t,totalCols:a}})}(m.filter(e=>s(e.start,t))).map(({event:t,col:e,totalCols:a})=>{const s=60*t.start.getHours()+t.start.getMinutes(),n=60*t.end.getHours()+t.end.getMinutes(),o=`calc(${s} / 60 * var(--cal-hour-height))`,l=`calc(${Math.max(n-s,30)} / 60 * var(--cal-hour-height))`,d=100/a,u=`calc(${e*d}% + 1px)`,v=`calc(${d}% - 2px)`,p=t.color||"var(--cal-event-bg)",_=c(t.start,i,r),g=n-s<=60?" uc-timed-event--short":"",w=this._opts.showTimeInItems?`<span class="uc-event-time">${h(_)}</span>`:"",y=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";return`\n <div class="uc-timed-event${g}"\n style="top:${o};height:${l};left:${u};width:${v};background:${h(p)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${y}>\n ${w}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}).join(""),l=n(t)?`<div class="uc-now-indicator" style="top:calc(${M} / 60 * var(--cal-hour-height));">\n <span class="uc-now-dot"></span>\n <span class="uc-now-line"></span>\n </div>`:"";return`<div class="uc-time-col${e}" data-date="${t.toISOString()}" data-action="slot-col">\n ${a}${o}${l}\n </div>`}).join("")}</div>\n </div>\n </div>\n </div>`}_buildListView(){const e=new Date,a=t(e),s=new Date(a);s.setDate(s.getDate()+this._opts.listDaysForward);const n=this._events.filter(t=>t.end>=e&&t.start<=s);n.sort((e,s)=>{const n=(e.start<a?a:t(e.start))-(s.start<a?a:t(s.start));return 0!==n?n:e.start-s.start});const i=new Map;if(n.forEach(e=>{const s=e.start<a?a:t(e.start),n=`${(o=s).getFullYear()}-${String(o.getMonth()+1).padStart(2,"0")}-${String(o.getDate()).padStart(2,"0")}`;var o;i.has(n)||i.set(n,[]),i.get(n).push(e)}),0===i.size)return'\n <div class="uc-list-view">\n <div class="uc-list-empty">\n <p>No upcoming events</p>\n </div>\n </div>';let r="";return i.forEach((t,e)=>{const a=o(e),s=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",year:"numeric",month:"long",day:"numeric"}).format(a),n=t.map(t=>{const e=p(t)?"All day":c(t.start,this._opts.locale,this._opts.use24Hour),a=t.color||"var(--cal-event-bg)",s=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||"")}"`:"";return`\n <div class="uc-list-event" data-event-id="${h(t.id)}" data-action="event-click" ${s}>\n <div class="uc-list-event-indicator" style="background: ${h(a)};"></div>\n <div class="uc-list-event-time">${h(e)}</div>\n <div class="uc-list-event-content">\n <div class="uc-list-event-title">${h(t.title)}</div>\n </div>\n </div>`}).join("");r+=`\n <div class="uc-list-date-group">\n <div class="uc-list-date-header">${h(s)}</div>\n <div class="uc-list-events">${n}</div>\n </div>`}),`<div class="uc-list-view">${r}</div>`}_closeYearPicker(){this._yearOutsideHandler&&(document.removeEventListener("click",this._yearOutsideHandler),this._yearOutsideHandler=null),this._yearPickerOpen=!1,this._renderToolbar()}_handleClick(e){const a=e.target.closest("[data-view]");if(a)return void this.setView(a.dataset.view);const s=e.target.closest("[data-action]");if(!s)return;switch(s.dataset.action){case"prev":this.navigate(-1);break;case"next":this.navigate(1);break;case"today":this.goToToday();break;case"year-pick":e.stopPropagation(),this._yearPickerOpen?this._closeYearPicker():(this._yearPickerOpen=!0,this._yearPickerBase=this._date.getFullYear()-4,this._renderToolbar(),this._yearOutsideHandler=t=>{t.target.closest(".uc-year-picker")||t.target.closest('[data-action="year-pick"]')||this._closeYearPicker()},setTimeout(()=>document.addEventListener("click",this._yearOutsideHandler),0));break;case"year-prev":e.stopPropagation(),this._yearPickerBase-=12,this._renderToolbar();break;case"year-next":e.stopPropagation(),this._yearPickerBase+=12,this._renderToolbar();break;case"select-year":{e.stopPropagation();const t=parseInt(s.dataset.year,10);this._date=new Date(this._date.getFullYear()!==t?new Date(this._date).setFullYear(t):this._date),this._closeYearPicker();const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender();break}case"event-click":{e.stopPropagation();const t=s.dataset.eventId,a=this._events.find(e=>String(e.id)===String(t));a&&this._opts.onEventClick&&this._opts.onEventClick(a,e);break}case"day-click":{if(e.target.closest('[data-action="day-number"]'))break;if(e.target.closest('[data-action="event-click"]'))break;const t=new Date(s.dataset.date);this._opts.onSlotClick&&this._opts.onSlotClick(t,e);break}case"day-number":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"more-click":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"slot-col":if(e.target.closest('[data-action="event-click"]'))break;if(this._opts.onSlotClick){const t=s,a=t.getBoundingClientRect(),n=e.clientY-a.top,i=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60,o=Math.max(0,Math.floor(n/i*60)),r=Math.floor(o/60)%24,l=15*Math.round(o%60/15),c=new Date(t.dataset.date);c.setHours(r,l,0,0),this._opts.onSlotClick(c,e)}}}_handleTooltipPosition(t){const e=t.target.closest("[data-tooltip]");if(!e||!e.dataset.tooltip)return;if(!e.dataset.tooltip.trim())return;const a=e.getBoundingClientRect(),s=window.innerWidth;e.classList.remove("uc-tooltip-left","uc-tooltip-right","uc-tooltip-bottom");const n=a.width/2-125;a.left+n+250>s-10?e.classList.add("uc-tooltip-left"):a.left+n<10&&e.classList.add("uc-tooltip-right"),a.top<50&&e.classList.add("uc-tooltip-bottom")}_startNowUpdater(){this._nowInterval=setInterval(()=>{const t=this._root.querySelectorAll(".uc-now-indicator");if(!t.length)return;const e=new Date,a=`calc(${60*e.getHours()+e.getMinutes()} / 60 * var(--cal-hour-height))`;t.forEach(t=>t.style.top=a)},6e4)}}});
|
|
14
|
+
return class{constructor(e,a={}){if("string"==typeof e){if(this._el=document.querySelector(e),!this._el)throw new Error(`SimpleCalendarJs: no element for "${e}"`)}else this._el=e;this._opts=Object.assign({defaultView:"month",defaultDate:null,weekStartsOn:0,locale:"default",weekdayFormat:"short",use24Hour:!1,showTimeInItems:!0,showGridLines:!0,showToolbar:!0,showTodayButton:!0,showNavigation:!0,showTitle:!0,showYearPicker:!0,showViewSwitcher:!0,showTooltips:!0,showBorder:!0,maxEventsPerCell:3,listDaysForward:30,enabledViews:["month","week","day"],fetchEvents:null,onEventClick:null,onSlotClick:null,onViewChange:null,onNavigate:null},a),this._view=this._opts.defaultView,this._date=t(this._opts.defaultDate||new Date),this._events=[],this._cachedRange=null,this._cachedEvents=[],this._nowInterval=null,this._yearPickerOpen=!1,this._yearPickerBase=0,this._yearOutsideHandler=null,this._root=document.createElement("div"),this._root.className="uc-calendar",this._opts.showGridLines||this._root.classList.add("uc-no-grid"),this._opts.showBorder||this._root.classList.add("uc-no-border"),this._el.appendChild(this._root),this._onClick=this._handleClick.bind(this),this._root.addEventListener("click",this._onClick),this._onMouseMove=this._handleTooltipPosition.bind(this),this._root.addEventListener("mouseover",this._onMouseMove),this._fetchAndRender(),this._startNowUpdater()}setView(t){t!==this._view&&(this._view=t,this._opts.onViewChange&&this._opts.onViewChange(t),this._fetchAndRender())}navigate(t){const e=new Date(this._date);"month"===this._view?(e.setMonth(e.getMonth()+t),e.setDate(1)):"week"===this._view?e.setDate(e.getDate()+7*t):e.setDate(e.getDate()+t),this._date=e;const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender()}goToToday(){this._date=t(new Date);const e=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(e.start,e.end),this._fetchAndRender()}goToDate(e){this._date=t(o(e)),this._fetchAndRender()}refresh(){this._cachedRange=null,this._cachedEvents=[],this._fetchAndRender()}destroy(){this._nowInterval&&clearInterval(this._nowInterval),this._yearOutsideHandler&&document.removeEventListener("click",this._yearOutsideHandler),this._root.removeEventListener("click",this._onClick),this._root.removeEventListener("mouseover",this._onMouseMove),this._root.remove()}_getRange(){if("month"===this._view){const a=r(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}if("week"===this._view){const a=l(this._date,this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[6])}}if("list"===this._view){const t=new Date,e=new Date(t);return e.setDate(e.getDate()+this._opts.listDaysForward),{start:t,end:e}}return{start:t(this._date),end:e(this._date)}}_getMonthGridRange(){const a=r(this._date.getFullYear(),this._date.getMonth(),this._opts.weekStartsOn);return{start:t(a[0]),end:e(a[a.length-1])}}async _fetchAndRender(){if(this._root.innerHTML=this._buildShell(),!this._opts.fetchEvents)return void this._renderView();const t=this._getRange();if(this._cachedRange&&this._cachedRange.start<=t.start&&this._cachedRange.end>=t.end)return this._events=this._cachedEvents.filter(e=>e.end>=t.start&&e.start<=t.end),void this._renderView();const e=this._root.querySelector(".uc-loading");e&&(e.style.display="flex");try{let e;e="list"===this._view?t:this._getMonthGridRange();const a=(await this._opts.fetchEvents(e.start,e.end)||[]).map(t=>({...t,start:o(t.start),end:o(t.end)}));this._cachedRange={start:e.start,end:e.end},this._cachedEvents=a,this._events=a.filter(e=>e.end>=t.start&&e.start<=t.end)}catch(t){this._events=[]}e&&(e.style.display="none"),this._renderView()}_renderView(){const t=this._root.querySelector(".uc-view-container");if(t){if(0===this._opts.maxEventsPerCell)this._root.classList.add("uc-unlimited-events"),this._root.style.removeProperty("--cal-cell-min-height");else{this._root.classList.remove("uc-unlimited-events");const t=30+24*this._opts.maxEventsPerCell+28;this._root.style.setProperty("--cal-cell-min-height",`${t}px`)}if("month"===this._view)t.innerHTML=this._buildMonthView();else if("week"===this._view){const e=l(this._date,this._opts.weekStartsOn);t.innerHTML=this._buildWeekOrDayView(e),this._scrollToBusinessHours(t)}else"list"===this._view?t.innerHTML=this._buildListView():(t.innerHTML=this._buildWeekOrDayView([this._date]),this._scrollToBusinessHours(t))}}_scrollToBusinessHours(t){requestAnimationFrame(()=>{const e=t.querySelector(".uc-time-body");if(!e)return;const a=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60;e.scrollTop=7*a})}_renderToolbar(){const t=this._root.querySelector(".uc-toolbar");if(!t)return;const e=document.createElement("div");e.innerHTML=this._buildToolbar(),this._root.replaceChild(e.firstElementChild,t)}_buildShell(){return`\n ${this._buildToolbar()}\n <div class="uc-loading" style="display:none">\n <div class="uc-spinner"></div>\n </div>\n <div class="uc-view-container"></div>\n `}_buildToolbar(){if(!this._opts.showToolbar)return"";const t=this._date.getFullYear(),e=(new Date).getFullYear();let a,n=!0;if("month"===this._view)a=new Intl.DateTimeFormat(this._opts.locale,{month:"long"}).format(this._date);else if("week"===this._view){const t=l(this._date,this._opts.weekStartsOn);a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}`}(t[0],t[6],this._opts.locale)}else if("list"===this._view){const t=new Date,e=new Date(t);e.setDate(e.getDate()+this._opts.listDaysForward),a=function(t,e,a){if(t.getMonth()===e.getMonth()&&t.getFullYear()===e.getFullYear())return`${new Intl.DateTimeFormat(a,{month:"long"}).format(t)} ${t.getDate()}–${e.getDate()}, ${t.getFullYear()}`;const s=t=>new Intl.DateTimeFormat(a,{month:"short",day:"numeric"}).format(t);return`${s(t)} – ${s(e)}, ${e.getFullYear()}`}(t,e,this._opts.locale),n=!1}else a=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",month:"long",day:"numeric"}).format(this._date);let i="";if(this._opts.showYearPicker&&this._yearPickerOpen){const a=this._yearPickerBase,s=Array.from({length:12},(s,n)=>{const i=a+n,o=i===t;return`<button class="${"uc-year-item"+(o?" uc-active":"")+(i===e&&!o?" uc-today-year":"")}" data-action="select-year" data-year="${i}">${i}</button>`}).join("");i=`\n <div class="uc-year-picker">\n <div class="uc-year-picker-nav">\n <button class="uc-year-nav-btn" data-action="year-prev" aria-label="Previous years">‹</button>\n <span class="uc-year-range">${a} – ${a+11}</span>\n <button class="uc-year-nav-btn" data-action="year-next" aria-label="Next years">›</button>\n </div>\n <div class="uc-year-grid">${s}</div>\n </div>`}const o=this._opts.locale,r=v(o,"today"),c=v(o,"month"),d=v(o,"week"),u=v(o,"day"),p=v(o,"list");let y="";if((this._opts.showNavigation||this._opts.showTodayButton)&&"list"!==this._view){const t=this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="prev" aria-label="Previous">‹</button>':"",e=new Date,a=s(this._date,e)?" uc-active":"";y=`\n <div class="uc-toolbar-section uc-toolbar-left">\n ${t}${this._opts.showTodayButton?`<button class="uc-btn uc-today-btn${a}" data-action="today">${h(r)}</button>`:""}${this._opts.showNavigation?'<button class="uc-btn uc-nav-btn" data-action="next" aria-label="Next">›</button>':""}\n </div>`}let g="";if(this._opts.showTitle){const e=n?this._opts.showYearPicker?`<button class="uc-year-btn${this._yearPickerOpen?" uc-open":""}" data-action="year-pick" aria-label="Select year">${t}</button>`:t:"";g=`\n <div class="uc-toolbar-section uc-toolbar-center">\n <h2 class="uc-title">\n <span class="uc-title-main">${h(a)}</span>\n ${e}\n </h2>\n ${i}\n </div>`}let _="";if(this._opts.showViewSwitcher){const t=this._opts.enabledViews,e=[];t.includes("month")&&e.push(`<button class="uc-btn uc-view-btn${"month"===this._view?" uc-active":""}" data-view="month">${h(c)}</button>`),t.includes("week")&&e.push(`<button class="uc-btn uc-view-btn${"week"===this._view?" uc-active":""}" data-view="week">${h(d)}</button>`),t.includes("day")&&e.push(`<button class="uc-btn uc-view-btn${"day"===this._view?" uc-active":""}" data-view="day">${h(u)}</button>`),t.includes("list")&&e.push(`<button class="uc-btn uc-view-btn${"list"===this._view?" uc-active":""}" data-view="list">${h(p)}</button>`),e.length>0&&(_=`\n <div class="uc-toolbar-section uc-toolbar-right">\n <div class="uc-view-switcher">\n ${e.join("")}\n </div>\n </div>`)}return`\n <div class="uc-toolbar">\n ${y}${g}${_}\n </div>\n `}_buildMonthView(){const{locale:a,weekStartsOn:s}=this._opts,n=r(this._date.getFullYear(),this._date.getMonth(),s),i=d(a,s,this._opts.weekdayFormat),o=this._events.map(a=>({...a,_origStart:a.start,start:p(a)?t(a.start):a.start,end:p(a)?e(a.end):a.end})),l=i.map(t=>`<div class="uc-month-day-name">${h(t)}</div>`).join(""),c=[];for(let t=0;t<n.length;t+=7)c.push(n.slice(t,t+7));return`\n <div class="uc-month-view">\n <div class="uc-month-header">${l}</div>\n <div class="uc-month-body">${c.map(t=>this._buildWeekRow(t,o)).join("")}</div>\n </div>\n `}_buildWeekRow(t,e){const a=this._date.getMonth(),i=0===this._opts.maxEventsPerCell?1/0:this._opts.maxEventsPerCell,o=e.filter(p),l=e.filter(t=>!p(t)),r=y(o,t),d=Array.from({length:7},()=>new Set);for(const{startCol:t,endCol:e,slot:a}of r)for(let s=t;s<=e;s++)d[s].add(a);const u=t.map(t=>l.filter(e=>s(e.start,t)).sort((t,e)=>t.start-e.start)),v=t.map((t,e)=>`\n <div class="uc-day-cell${n(t)?" uc-today":""}${t.getMonth()!==a?" uc-other-month":""}" data-date="${t.toISOString()}" data-action="day-click">\n <span class="uc-day-number" data-action="day-number" data-date="${t.toISOString()}">${t.getDate()}</span>\n </div>`).join("");let g="";for(const{event:t,startCol:e,endCol:a,slot:s,isStart:n,isEnd:o}of r){if(i!==1/0&&s>=i)continue;const l=100/7,r=e*l,c=(a-e+1)*l,d=`calc(var(--cal-header-day-height) + ${s} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,u=t.color||"var(--cal-event-bg)",v=n?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",y=n?"":" uc-continues-left",_=o?"":" uc-continues-right",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar${y}${_}"\n style="left:calc(${r}% + 2px);width:calc(${c}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${n?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n </div>`}let _=-1;for(let e=0;e<7;e++){const a=100/7,s=e*a,n=t[e],o=[...d[e]],l=o.length>0?Math.max(...o)+1:0,r=u[e];if(i===1/0)r.forEach((t,e)=>{const n=l+e;_=Math.max(_,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,o=t.color||"var(--cal-event-bg)",r=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(r)}</span>`:"",u=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(o)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${u}>\n ${d}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`});else{const t=[...d[e]].filter(t=>t>=i).length,o=[];for(let t=l;t<i;t++)o.push(t);let u=t;if(r.forEach((t,e)=>{if(e<o.length){const n=o[e];_=Math.max(_,n);const i=`calc(var(--cal-header-day-height) + ${n} * (var(--cal-event-height) + var(--cal-event-gap)) + 4px)`,l=t.color||"var(--cal-event-bg)",r=c(t.start,this._opts.locale,this._opts.use24Hour),d=this._opts.showTimeInItems?`<span class="uc-event-time">${h(r)}</span>`:"",u=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";g+=`\n <div class="uc-event-bar"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${i};background:${h(l)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${u}>\n ${d}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}else u++}),u>0){g+=`\n <div class="uc-more-link"\n style="left:calc(${s}% + 2px);width:calc(${a}% - 4px);top:${`calc(var(--cal-header-day-height) + ${i} * (var(--cal-event-height) + var(--cal-event-gap)) + 2px)`};"\n data-date="${n.toISOString()}" data-action="more-click">\n +${u} more\n </div>`}}}for(const{slot:t}of r)(i===1/0||t<i)&&(_=Math.max(_,t));let w="";return i===1/0&&(w=_>=0?` style="min-height: calc(var(--cal-header-day-height) + ${_+1} * (var(--cal-event-height) + var(--cal-event-gap)) + 8px);"`:' style="min-height: 80px;"'),`\n <div class="uc-week-row"${w}>\n <div class="uc-week-cells">${v}</div>\n <div class="uc-week-events">${g}</div>\n </div>`}_buildWeekOrDayView(a){const{locale:i,weekStartsOn:o,use24Hour:l}=this._opts,r=1===a.length,u=r?" uc-day-header-single":"",g=r?[new Intl.DateTimeFormat(i,{weekday:this._opts.weekdayFormat}).format(a[0])]:d(i,o,this._opts.weekdayFormat),_=t(a[0]),w=e(a[a.length-1]),m=this._events.filter(t=>p(t)&&t.start<=w&&e(t.end)>=_).map(a=>({...a,start:t(a.start),end:e(a.end)})),f=this._events.filter(t=>!p(t)&&t.start>=_&&t.start<=w),$=a.map((t,e)=>{const a=n(t)?" uc-today":"",s=t.getDate();return`\n <div class="uc-week-day-header${a}">\n <span class="uc-week-day-name">${h(g[e])}</span>\n <span class="uc-week-day-num" data-action="day-number" data-date="${t.toISOString()}">${s}</span>\n </div>`}).join(""),k=r?m.map((t,e)=>({event:t,startCol:0,endCol:0,slot:e,isStart:!0,isEnd:!0})):y(m,a),b=k.length?Math.max(...k.map(t=>t.slot))+1:0;let D="";for(const{event:t,startCol:e,endCol:s,slot:n,isStart:i,isEnd:o}of k){const l=100/a.length,r=e*l,c=(s-e+1)*l,d=`calc(${n} * (var(--cal-event-height) + 3px) + 2px)`,u=t.color||"var(--cal-event-bg)",v=i?"var(--cal-event-border-radius)":"0",p=o?"var(--cal-event-border-radius)":"0",y=i?"":" uc-continues-left",g=o?"":" uc-continues-right",_=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";D+=`\n <div class="uc-event-bar${y}${g}"\n style="left:calc(${r}% + 2px);width:calc(${c}% - 4px);top:${d};background:${h(u)};border-radius:${v} ${p} ${p} ${v};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${_}>\n ${i?`<span class="uc-event-title">${h(t.title)}</span>`:" "}\n </div>`}const T=`calc(${Math.max(1,b)} * (var(--cal-event-height) + 3px) + 6px)`,S=new Date,M=60*S.getHours()+S.getMinutes(),C=a.length,x=Array.from({length:24},(t,e)=>`<div class="uc-hour-cell"><span class="uc-hour-label">${h(0===e?"":c(new Date(2e3,0,1,e),"en-US",l))}</span></div>`).join(""),H=a.map(t=>{const e=n(t)?" uc-today":"",a=Array.from({length:24},()=>'<div class="uc-hour-row"><div class="uc-half-hour-line"></div></div>').join(""),o=function(t){if(!t.length)return[];const e=[...t].sort((t,e)=>t.start-e.start||e.end-t.end),a=[],s=[];for(const t of e){let e=a.findIndex(e=>e<=t.start);-1===e?(e=a.length,a.push(t.end)):a[e]=t.end,s.push({event:t,col:e})}return s.map(t=>{const e=s.filter(e=>e.event.start<t.event.end&&e.event.end>t.event.start),a=Math.max(...e.map(t=>t.col))+1;return{...t,totalCols:a}})}(f.filter(e=>s(e.start,t))).map(({event:t,col:e,totalCols:a})=>{const s=60*t.start.getHours()+t.start.getMinutes(),n=60*t.end.getHours()+t.end.getMinutes(),o=`calc(${s} / 60 * var(--cal-hour-height))`,r=`calc(${Math.max(n-s,30)} / 60 * var(--cal-hour-height))`,d=100/a,u=`calc(${e*d}% + 1px)`,v=`calc(${d}% - 2px)`,p=t.color||"var(--cal-event-bg)",y=c(t.start,i,l),g=n-s<=60?" uc-timed-event--short":"",_=this._opts.showTimeInItems?`<span class="uc-event-time">${h(y)}</span>`:"",w=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||t.title)}"`:"";return`\n <div class="uc-timed-event${g}"\n style="top:${o};height:${r};left:${u};width:${v};background:${h(p)};"\n data-event-id="${h(t.id)}" data-action="event-click"\n ${w}>\n ${_}\n <span class="uc-event-title">${h(t.title)}</span>\n </div>`}).join(""),r=n(t)?`<div class="uc-now-indicator" style="top:calc(${M} / 60 * var(--cal-hour-height));">\n <span class="uc-now-dot"></span>\n <span class="uc-now-line"></span>\n </div>`:"";return`<div class="uc-time-col${e}" data-date="${t.toISOString()}" data-action="slot-col">\n ${a}${o}${r}\n </div>`}).join("");return`\n <div class="${r?"uc-day-view":"uc-week-view"}">\n <div class="uc-week-header">\n <div class="uc-time-gutter-spacer"></div>\n <div class="uc-week-day-headers${u}">${$}</div>\n </div>\n <div class="uc-all-day-section">\n <div class="uc-time-gutter-spacer uc-all-day-label">${v(this._opts.locale,"allDay")}</div>\n <div class="uc-all-day-events" style="min-height:${T}">${D}</div>\n </div>\n <div class="uc-time-body">\n <div class="uc-time-grid-inner">\n <div class="uc-time-gutter">${x}</div>\n <div class="uc-time-columns" style="--uc-col-count:${C}">${H}</div>\n </div>\n </div>\n </div>`}_buildListView(){const e=new Date,a=t(e),s=new Date(a);s.setDate(s.getDate()+this._opts.listDaysForward);const n=this._events.filter(t=>t.end>=e&&t.start<=s);n.sort((e,s)=>{const n=(e.start<a?a:t(e.start))-(s.start<a?a:t(s.start));return 0!==n?n:e.start-s.start});const i=new Map;if(n.forEach(e=>{const s=e.start<a?a:t(e.start),n=`${(o=s).getFullYear()}-${String(o.getMonth()+1).padStart(2,"0")}-${String(o.getDate()).padStart(2,"0")}`;var o;i.has(n)||i.set(n,[]),i.get(n).push(e)}),0===i.size)return'\n <div class="uc-list-view">\n <div class="uc-list-empty">\n <p>No upcoming events</p>\n </div>\n </div>';let l="";return i.forEach((t,e)=>{const a=o(e),s=new Intl.DateTimeFormat(this._opts.locale,{weekday:"long",year:"numeric",month:"long",day:"numeric"}).format(a),n=t.map(t=>{const e=p(t)?"All day":c(t.start,this._opts.locale,this._opts.use24Hour),a=t.color||"var(--cal-event-bg)",s=this._opts.showTooltips?`data-tooltip="${h(t.tooltip||t.description||"")}"`:"";return`\n <div class="uc-list-event" data-event-id="${h(t.id)}" data-action="event-click" ${s}>\n <div class="uc-list-event-indicator" style="background: ${h(a)};"></div>\n <div class="uc-list-event-time">${h(e)}</div>\n <div class="uc-list-event-content">\n <div class="uc-list-event-title">${h(t.title)}</div>\n </div>\n </div>`}).join("");l+=`\n <div class="uc-list-date-group">\n <div class="uc-list-date-header">${h(s)}</div>\n <div class="uc-list-events">${n}</div>\n </div>`}),`<div class="uc-list-view">${l}</div>`}_closeYearPicker(){this._yearOutsideHandler&&(document.removeEventListener("click",this._yearOutsideHandler),this._yearOutsideHandler=null),this._yearPickerOpen=!1,this._renderToolbar()}_handleClick(e){const a=e.target.closest("[data-view]");if(a)return void this.setView(a.dataset.view);const s=e.target.closest("[data-action]");if(!s)return;switch(s.dataset.action){case"prev":this.navigate(-1);break;case"next":this.navigate(1);break;case"today":this.goToToday();break;case"year-pick":e.stopPropagation(),this._yearPickerOpen?this._closeYearPicker():(this._yearPickerOpen=!0,this._yearPickerBase=this._date.getFullYear()-4,this._renderToolbar(),this._yearOutsideHandler=t=>{t.target.closest(".uc-year-picker")||t.target.closest('[data-action="year-pick"]')||this._closeYearPicker()},setTimeout(()=>document.addEventListener("click",this._yearOutsideHandler),0));break;case"year-prev":e.stopPropagation(),this._yearPickerBase-=12,this._renderToolbar();break;case"year-next":e.stopPropagation(),this._yearPickerBase+=12,this._renderToolbar();break;case"select-year":{e.stopPropagation();const t=parseInt(s.dataset.year,10);this._date=new Date(this._date.getFullYear()!==t?new Date(this._date).setFullYear(t):this._date),this._closeYearPicker();const a=this._getRange();this._opts.onNavigate&&this._opts.onNavigate(a.start,a.end),this._fetchAndRender();break}case"event-click":{e.stopPropagation();const t=s.dataset.eventId,a=this._events.find(e=>String(e.id)===String(t));a&&this._opts.onEventClick&&this._opts.onEventClick(a,e);break}case"day-click":{if(e.target.closest('[data-action="day-number"]'))break;if(e.target.closest('[data-action="event-click"]'))break;const t=new Date(s.dataset.date);this._opts.onSlotClick&&this._opts.onSlotClick(t,e);break}case"day-number":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"more-click":{if(e.stopPropagation(),!this._opts.enabledViews.includes("day"))break;const a=new Date(s.dataset.date);this._date=t(a),this._view="day",this._opts.onViewChange&&this._opts.onViewChange("day"),this._fetchAndRender();break}case"slot-col":if(e.target.closest('[data-action="event-click"]'))break;if(this._opts.onSlotClick){const t=s,a=t.getBoundingClientRect(),n=e.clientY-a.top,i=parseFloat(getComputedStyle(this._root).getPropertyValue("--cal-hour-height"))||60,o=Math.max(0,Math.floor(n/i*60)),l=Math.floor(o/60)%24,r=15*Math.round(o%60/15),c=new Date(t.dataset.date);c.setHours(l,r,0,0),this._opts.onSlotClick(c,e)}}}_handleTooltipPosition(t){const e=t.target.closest("[data-tooltip]");if(!e||!e.dataset.tooltip)return;if(!e.dataset.tooltip.trim())return;const a=e.getBoundingClientRect(),s=window.innerWidth;e.classList.remove("uc-tooltip-left","uc-tooltip-right","uc-tooltip-bottom");const n=a.width/2-125;a.left+n+250>s-10?e.classList.add("uc-tooltip-left"):a.left+n<10&&e.classList.add("uc-tooltip-right"),a.top<50&&e.classList.add("uc-tooltip-bottom")}_startNowUpdater(){this._nowInterval=setInterval(()=>{const t=this._root.querySelectorAll(".uc-now-indicator");if(!t.length)return;const e=new Date,a=`calc(${60*e.getHours()+e.getMinutes()} / 60 * var(--cal-hour-height))`;t.forEach(t=>t.style.top=a)},6e4)}}});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SimpleCalendarJs v3.0.
|
|
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
|
+
* 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
|
+
* 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.
|
|
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",
|