kalendly 0.2.2 → 0.3.1
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 +303 -45
- package/dist/core/index.d.mts +57 -10
- package/dist/core/index.d.ts +57 -10
- package/dist/core/index.js +159 -31
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +147 -30
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +86 -10
- package/dist/index.d.ts +86 -10
- package/dist/index.js +525 -178
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +513 -177
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +499 -174
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +457 -392
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -31,12 +31,28 @@ function generateYears(minYear, maxYear) {
|
|
|
31
31
|
const max = maxYear ?? currentYear + 10;
|
|
32
32
|
return Array.from({ length: max - min + 1 }, (_, i) => min + i);
|
|
33
33
|
}
|
|
34
|
+
function eventCoversDate(event, date) {
|
|
35
|
+
const start = normalizeDate(new Date(event.date)).getTime();
|
|
36
|
+
const target = normalizeDate(date).getTime();
|
|
37
|
+
if (Number.isNaN(start)) return false;
|
|
38
|
+
if (event.endDate === void 0 || event.endDate === null) {
|
|
39
|
+
return start === target;
|
|
40
|
+
}
|
|
41
|
+
const end = normalizeDate(new Date(event.endDate)).getTime();
|
|
42
|
+
if (Number.isNaN(end)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`<kal-calendar> event ${event.id} has an unreadable endDate: ${String(event.endDate)}.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (end < start) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`<kal-calendar> event ${event.id} has an endDate before its date: ${String(event.endDate)} < ${String(event.date)}.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return target >= start && target <= end;
|
|
53
|
+
}
|
|
34
54
|
function getEventsForDate(events, date) {
|
|
35
|
-
|
|
36
|
-
return events.filter((event) => {
|
|
37
|
-
const eventDate = normalizeDate(new Date(event.date));
|
|
38
|
-
return eventDate.getTime() === normalizedTargetDate.getTime();
|
|
39
|
-
});
|
|
55
|
+
return events.filter((event) => eventCoversDate(event, date));
|
|
40
56
|
}
|
|
41
57
|
function hasEvents(events, date) {
|
|
42
58
|
return getEventsForDate(events, date).length > 0;
|
|
@@ -95,26 +111,16 @@ function generateCalendarDates(year, month, events = [], weekStartsOn = 0) {
|
|
|
95
111
|
}
|
|
96
112
|
return dates;
|
|
97
113
|
}
|
|
98
|
-
function getPopupPositionClass(selectedDayIndex) {
|
|
99
|
-
if (selectedDayIndex === null) return "popup-center-bottom";
|
|
100
|
-
if (selectedDayIndex < 3) {
|
|
101
|
-
return "popup-right";
|
|
102
|
-
} else if (selectedDayIndex > 4) {
|
|
103
|
-
return "popup-left";
|
|
104
|
-
} else {
|
|
105
|
-
return "popup-center-bottom";
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
114
|
function getCellClasses(calendarDate) {
|
|
109
115
|
const classes = [];
|
|
110
116
|
if (!calendarDate.isCurrentMonth) {
|
|
111
|
-
classes.push("other-month");
|
|
117
|
+
classes.push("calendar-cell-other-month");
|
|
112
118
|
}
|
|
113
119
|
if (calendarDate.isToday) {
|
|
114
|
-
classes.push("
|
|
120
|
+
classes.push("calendar-cell-today");
|
|
115
121
|
}
|
|
116
122
|
if (calendarDate.hasEvents) {
|
|
117
|
-
classes.push("has
|
|
123
|
+
classes.push("calendar-cell-has-event");
|
|
118
124
|
}
|
|
119
125
|
return classes;
|
|
120
126
|
}
|
|
@@ -173,7 +179,84 @@ function getCategoryColor(category, customColors) {
|
|
|
173
179
|
const color = colorMap[category] || colorMap.other || "#fc8917";
|
|
174
180
|
return isValidHexColor(color) ? color : "#fc8917";
|
|
175
181
|
}
|
|
176
|
-
|
|
182
|
+
function escapeHtml(value) {
|
|
183
|
+
if (value === null || value === void 0) return "";
|
|
184
|
+
return String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
|
|
185
|
+
}
|
|
186
|
+
function slugifyToken(value) {
|
|
187
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
188
|
+
}
|
|
189
|
+
function safeUrl(value) {
|
|
190
|
+
const normalized = String(value).split("").filter((char) => char.charCodeAt(0) > 32).join("");
|
|
191
|
+
if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;
|
|
192
|
+
const schemeless = !/^[^/?#]*:/.test(normalized);
|
|
193
|
+
return schemeless ? normalized : "#";
|
|
194
|
+
}
|
|
195
|
+
function safeColor(value) {
|
|
196
|
+
const trimmed = String(value).trim();
|
|
197
|
+
return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
|
|
198
|
+
}
|
|
199
|
+
function parseTimeToMinutes(time) {
|
|
200
|
+
if (typeof time !== "string") return null;
|
|
201
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
|
|
202
|
+
if (!match) return null;
|
|
203
|
+
const hours = Number(match[1]);
|
|
204
|
+
const mins = Number(match[2]);
|
|
205
|
+
if (hours > 24 || mins > 59 || hours === 24 && mins > 0) return null;
|
|
206
|
+
return hours * 60 + mins;
|
|
207
|
+
}
|
|
208
|
+
function formatMinutes(total) {
|
|
209
|
+
const hours = Math.floor(total / 60);
|
|
210
|
+
const mins = total % 60;
|
|
211
|
+
return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
|
|
212
|
+
}
|
|
213
|
+
function eventInterval(event, slotDuration, onDay) {
|
|
214
|
+
const day = normalizeDate(onDay).getTime();
|
|
215
|
+
if (Number.isNaN(day)) return null;
|
|
216
|
+
const base = day / 6e4;
|
|
217
|
+
const wholeDay = [base, base + MINUTES_PER_DAY];
|
|
218
|
+
if (event.allDay || !event.startTime) return wholeDay;
|
|
219
|
+
const from = parseTimeToMinutes(event.startTime);
|
|
220
|
+
if (from === null) return wholeDay;
|
|
221
|
+
if (event.endTime === void 0 || event.endTime === null) {
|
|
222
|
+
return [base + from, base + from + slotDuration];
|
|
223
|
+
}
|
|
224
|
+
const to = parseTimeToMinutes(event.endTime);
|
|
225
|
+
if (to === null) return wholeDay;
|
|
226
|
+
return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];
|
|
227
|
+
}
|
|
228
|
+
function mergeIntervals(intervals) {
|
|
229
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
230
|
+
return sorted.reduce((merged, [start, end]) => {
|
|
231
|
+
const last = merged[merged.length - 1];
|
|
232
|
+
if (last && start <= last[1]) last[1] = Math.max(last[1], end);
|
|
233
|
+
else merged.push([start, end]);
|
|
234
|
+
return merged;
|
|
235
|
+
}, []);
|
|
236
|
+
}
|
|
237
|
+
function bookedSlots(events, date, slotDuration = DEFAULT_SLOT_DURATION) {
|
|
238
|
+
const base = normalizeDate(date).getTime() / 6e4;
|
|
239
|
+
const previousDay = new Date(date);
|
|
240
|
+
previousDay.setDate(previousDay.getDate() - 1);
|
|
241
|
+
const intervals = [];
|
|
242
|
+
for (const event of new Set(events)) {
|
|
243
|
+
for (const day of [previousDay, date]) {
|
|
244
|
+
if (!eventCoversDate(event, day)) continue;
|
|
245
|
+
const interval = eventInterval(event, slotDuration, day);
|
|
246
|
+
if (interval) intervals.push(interval);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const merged = mergeIntervals(intervals);
|
|
250
|
+
return Array.from(
|
|
251
|
+
{ length: Math.floor(MINUTES_PER_DAY / slotDuration) },
|
|
252
|
+
(_, index) => {
|
|
253
|
+
const start = base + index * slotDuration;
|
|
254
|
+
const end = start + slotDuration;
|
|
255
|
+
return merged.some(([from, to]) => start < to && end > from);
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS, HTML_ESCAPES, MINUTES_PER_DAY, DEFAULT_SLOT_DURATION;
|
|
177
260
|
var init_utils = __esm({
|
|
178
261
|
"src/core/utils.ts"() {
|
|
179
262
|
"use strict";
|
|
@@ -222,6 +305,15 @@ var init_utils = __esm({
|
|
|
222
305
|
appointment: "#f59e0b",
|
|
223
306
|
other: "#6b7280"
|
|
224
307
|
};
|
|
308
|
+
HTML_ESCAPES = {
|
|
309
|
+
"&": "&",
|
|
310
|
+
"<": "<",
|
|
311
|
+
">": ">",
|
|
312
|
+
'"': """,
|
|
313
|
+
"'": "'"
|
|
314
|
+
};
|
|
315
|
+
MINUTES_PER_DAY = 1440;
|
|
316
|
+
DEFAULT_SLOT_DURATION = 60;
|
|
225
317
|
}
|
|
226
318
|
});
|
|
227
319
|
|
|
@@ -269,24 +361,38 @@ var init_calendar_engine = __esm({
|
|
|
269
361
|
* Get view model with computed properties
|
|
270
362
|
*/
|
|
271
363
|
getViewModel() {
|
|
272
|
-
const
|
|
273
|
-
this.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
364
|
+
const panes = Array.from(
|
|
365
|
+
{ length: Math.max(1, this.config.monthCount ?? 1) },
|
|
366
|
+
(_, offset) => {
|
|
367
|
+
const anchor = new Date(
|
|
368
|
+
this.state.currentYear,
|
|
369
|
+
this.state.currentMonth + offset,
|
|
370
|
+
1
|
|
371
|
+
);
|
|
372
|
+
const year = anchor.getFullYear();
|
|
373
|
+
const month = anchor.getMonth();
|
|
374
|
+
return {
|
|
375
|
+
year,
|
|
376
|
+
month,
|
|
377
|
+
monthAndYearText: getMonthYearText(year, month),
|
|
378
|
+
calendarDates: generateCalendarDates(
|
|
379
|
+
year,
|
|
380
|
+
month,
|
|
381
|
+
this.config.events,
|
|
382
|
+
this.config.weekStartsOn
|
|
383
|
+
)
|
|
384
|
+
};
|
|
385
|
+
}
|
|
277
386
|
);
|
|
278
387
|
return {
|
|
279
388
|
...this.state,
|
|
280
389
|
months: MONTHS,
|
|
281
390
|
days: DAYS,
|
|
282
391
|
years: generateYears(this.config.minYear, this.config.maxYear),
|
|
283
|
-
monthAndYearText:
|
|
284
|
-
this.state.currentYear,
|
|
285
|
-
this.state.currentMonth
|
|
286
|
-
),
|
|
392
|
+
monthAndYearText: panes[0].monthAndYearText,
|
|
287
393
|
scheduleDay: this.state.selectedDate ? formatDateForDisplay(this.state.selectedDate) : "",
|
|
288
|
-
|
|
289
|
-
|
|
394
|
+
panes,
|
|
395
|
+
calendarDates: panes[0].calendarDates
|
|
290
396
|
};
|
|
291
397
|
}
|
|
292
398
|
/**
|
|
@@ -482,13 +588,13 @@ function defineCalendarElement(tagName = "kal-calendar") {
|
|
|
482
588
|
customElements.define(tagName, CalendarElement);
|
|
483
589
|
}
|
|
484
590
|
}
|
|
485
|
-
var isSameDay2, CalendarElement;
|
|
591
|
+
var isSameDay2, _CalendarElement, CalendarElement;
|
|
486
592
|
var init_CalendarElement = __esm({
|
|
487
593
|
"src/web-components/CalendarElement.ts"() {
|
|
488
594
|
"use strict";
|
|
489
595
|
init_core();
|
|
490
596
|
isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
491
|
-
|
|
597
|
+
_CalendarElement = class _CalendarElement extends HTMLElement {
|
|
492
598
|
constructor() {
|
|
493
599
|
super(...arguments);
|
|
494
600
|
this.engine = null;
|
|
@@ -509,6 +615,10 @@ var init_CalendarElement = __esm({
|
|
|
509
615
|
this._categoryColors = null;
|
|
510
616
|
this._renderEvent = null;
|
|
511
617
|
this._renderNoEvents = null;
|
|
618
|
+
this._availabilityColors = null;
|
|
619
|
+
this._selectableStatuses = null;
|
|
620
|
+
this._initError = null;
|
|
621
|
+
this.pendingUpdate = null;
|
|
512
622
|
// Selection state (availability mode — range)
|
|
513
623
|
this._rangeStart = null;
|
|
514
624
|
this._rangeEnd = null;
|
|
@@ -521,6 +631,7 @@ var init_CalendarElement = __esm({
|
|
|
521
631
|
return this._events;
|
|
522
632
|
}
|
|
523
633
|
set events(val) {
|
|
634
|
+
this._initError = null;
|
|
524
635
|
this._events = val;
|
|
525
636
|
if (this.engine) {
|
|
526
637
|
this.engine.updateEvents(val);
|
|
@@ -536,13 +647,28 @@ var init_CalendarElement = __esm({
|
|
|
536
647
|
this.engine.updateCategoryColors(val);
|
|
537
648
|
}
|
|
538
649
|
}
|
|
650
|
+
get availabilityColors() {
|
|
651
|
+
return this._availabilityColors ?? {};
|
|
652
|
+
}
|
|
653
|
+
set availabilityColors(val) {
|
|
654
|
+
this._initError = null;
|
|
655
|
+
this._availabilityColors = val;
|
|
656
|
+
if (this.engine) this.scheduleRender();
|
|
657
|
+
}
|
|
658
|
+
get selectableStatuses() {
|
|
659
|
+
return this._selectableStatuses ?? [];
|
|
660
|
+
}
|
|
661
|
+
set selectableStatuses(val) {
|
|
662
|
+
this._selectableStatuses = val;
|
|
663
|
+
if (this.engine) this.scheduleRender();
|
|
664
|
+
}
|
|
539
665
|
set renderEvent(val) {
|
|
540
666
|
this._renderEvent = val;
|
|
541
|
-
if (this.engine) this.
|
|
667
|
+
if (this.engine) this.scheduleRender();
|
|
542
668
|
}
|
|
543
669
|
set renderNoEvents(val) {
|
|
544
670
|
this._renderNoEvents = val;
|
|
545
|
-
if (this.engine) this.
|
|
671
|
+
if (this.engine) this.scheduleRender();
|
|
546
672
|
}
|
|
547
673
|
get loading() {
|
|
548
674
|
return this.hasAttribute("loading");
|
|
@@ -551,10 +677,22 @@ var init_CalendarElement = __esm({
|
|
|
551
677
|
if (val) this.setAttribute("loading", "");
|
|
552
678
|
else this.removeAttribute("loading");
|
|
553
679
|
}
|
|
680
|
+
// Custom element reactions report rather than propagate, so a failure here
|
|
681
|
+
// is kept and resurfaced at the next call the integrator makes
|
|
682
|
+
reaction(run) {
|
|
683
|
+
try {
|
|
684
|
+
run();
|
|
685
|
+
} catch (error) {
|
|
686
|
+
this._initError = error;
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
554
690
|
connectedCallback() {
|
|
555
691
|
this.classList.add("kalendly-calendar");
|
|
556
|
-
this.
|
|
557
|
-
|
|
692
|
+
this.reaction(() => {
|
|
693
|
+
this.initEngine();
|
|
694
|
+
this.render();
|
|
695
|
+
});
|
|
558
696
|
}
|
|
559
697
|
disconnectedCallback() {
|
|
560
698
|
this.cleanup();
|
|
@@ -564,7 +702,7 @@ var init_CalendarElement = __esm({
|
|
|
564
702
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
565
703
|
if (oldVal === newVal) return;
|
|
566
704
|
if (name === "loading") {
|
|
567
|
-
this.
|
|
705
|
+
this.scheduleRender();
|
|
568
706
|
return;
|
|
569
707
|
}
|
|
570
708
|
if (name === "selectable" && newVal === null) {
|
|
@@ -577,6 +715,46 @@ var init_CalendarElement = __esm({
|
|
|
577
715
|
}
|
|
578
716
|
this.reinit();
|
|
579
717
|
}
|
|
718
|
+
get headingText() {
|
|
719
|
+
const heading = this.getAttribute("heading");
|
|
720
|
+
if (heading !== null) return heading;
|
|
721
|
+
const title = this.getAttribute("title");
|
|
722
|
+
if (title !== null && !_CalendarElement.titleDeprecationWarned) {
|
|
723
|
+
_CalendarElement.titleDeprecationWarned = true;
|
|
724
|
+
console.warn(
|
|
725
|
+
`<kal-calendar> the title attribute is deprecated and will be removed in a future release \u2014 use heading instead. title is a global HTML attribute, so the browser also renders it as a tooltip over the whole calendar.`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
return title;
|
|
729
|
+
}
|
|
730
|
+
get slotDuration() {
|
|
731
|
+
const raw = this.getAttribute("slot-duration");
|
|
732
|
+
if (raw === null) return DEFAULT_SLOT_DURATION;
|
|
733
|
+
const minutes = Number(raw);
|
|
734
|
+
if (Number.isInteger(minutes) && minutes > 0 && MINUTES_PER_DAY % minutes === 0) {
|
|
735
|
+
return minutes;
|
|
736
|
+
}
|
|
737
|
+
console.warn(
|
|
738
|
+
`<kal-calendar> slot-duration="${raw}" must be a positive whole number of minutes that divides ${MINUTES_PER_DAY} \u2014 falling back to ${DEFAULT_SLOT_DURATION}.`
|
|
739
|
+
);
|
|
740
|
+
return DEFAULT_SLOT_DURATION;
|
|
741
|
+
}
|
|
742
|
+
warnMissingEndTime(events) {
|
|
743
|
+
for (const event of events) {
|
|
744
|
+
if (!event.startTime || event.endTime) continue;
|
|
745
|
+
const key = String(event.id);
|
|
746
|
+
if (_CalendarElement.warnedMissingEnd.has(key)) continue;
|
|
747
|
+
_CalendarElement.warnedMissingEnd.add(key);
|
|
748
|
+
console.warn(
|
|
749
|
+
`<kal-calendar> event ${key} has startTime but no endTime \u2014 occupying one ${this.slotDuration}-minute slot. Send an endTime to say how long it runs.`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
get monthCount() {
|
|
754
|
+
const requested = Number(this.getAttribute("months") ?? 1);
|
|
755
|
+
if (!Number.isInteger(requested) || requested < 1) return 1;
|
|
756
|
+
return Math.min(requested, 2);
|
|
757
|
+
}
|
|
580
758
|
get minYear() {
|
|
581
759
|
const val = this.getAttribute("min-year");
|
|
582
760
|
return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() - 30;
|
|
@@ -596,19 +774,20 @@ var init_CalendarElement = __esm({
|
|
|
596
774
|
minYear: this.minYear,
|
|
597
775
|
maxYear: this.maxYear,
|
|
598
776
|
weekStartsOn,
|
|
599
|
-
categoryColors: this._categoryColors ?? void 0
|
|
777
|
+
categoryColors: this._categoryColors ?? void 0,
|
|
778
|
+
monthCount: this.monthCount
|
|
600
779
|
});
|
|
601
780
|
this.actions = this.engine.getActions();
|
|
602
781
|
this.applyTheme();
|
|
603
782
|
this.unsubscribe = this.engine.subscribe(() => {
|
|
604
|
-
this.
|
|
783
|
+
this.scheduleRender();
|
|
605
784
|
});
|
|
606
785
|
}
|
|
607
786
|
reinit() {
|
|
608
787
|
if (!this.engine) return;
|
|
609
788
|
this.cleanup();
|
|
610
789
|
this.initEngine();
|
|
611
|
-
this.
|
|
790
|
+
this.scheduleRender();
|
|
612
791
|
}
|
|
613
792
|
cleanup() {
|
|
614
793
|
if (this.unsubscribe) {
|
|
@@ -645,42 +824,84 @@ var init_CalendarElement = __esm({
|
|
|
645
824
|
applyTheme() {
|
|
646
825
|
if (!this._theme) return;
|
|
647
826
|
const root = document.documentElement;
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
root.style.setProperty(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
827
|
+
for (const key of Object.keys(_CalendarElement.themeMap)) {
|
|
828
|
+
const value = this._theme[key];
|
|
829
|
+
if (value) root.style.setProperty(_CalendarElement.themeMap[key], value);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
get knownBuckets() {
|
|
833
|
+
return [
|
|
834
|
+
..._CalendarElement.BUILT_IN_BUCKETS,
|
|
835
|
+
...Object.keys(this._availabilityColors ?? {})
|
|
836
|
+
];
|
|
837
|
+
}
|
|
838
|
+
get updateComplete() {
|
|
839
|
+
return this.pendingUpdate ?? Promise.resolve();
|
|
840
|
+
}
|
|
841
|
+
scheduleRender() {
|
|
842
|
+
if (this.pendingUpdate) return;
|
|
843
|
+
this.pendingUpdate = Promise.resolve().then(() => {
|
|
844
|
+
this.pendingUpdate = null;
|
|
845
|
+
try {
|
|
846
|
+
this.render();
|
|
847
|
+
} catch (error) {
|
|
848
|
+
this._initError = error;
|
|
849
|
+
throw error;
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
rethrowInitError() {
|
|
854
|
+
if (this._initError) throw this._initError;
|
|
855
|
+
}
|
|
856
|
+
assertStatusesDeclared() {
|
|
857
|
+
if (!this.getAttribute("availability-mode")) return;
|
|
858
|
+
const missing = this._events.filter(
|
|
859
|
+
(event) => typeof event.availabilityStatus !== "string" || event.availabilityStatus === ""
|
|
860
|
+
).map((event) => String(event.id));
|
|
861
|
+
if (missing.length) {
|
|
862
|
+
throw new Error(
|
|
863
|
+
`<kal-calendar> availability-mode requires availabilityStatus on every event. Missing on: ${missing.join(", ")}.`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
assertStatusesKnown() {
|
|
868
|
+
const known = new Set(this.knownBuckets);
|
|
869
|
+
const unknown = this._events.filter((event) => !known.has(event.availabilityStatus)).map((event) => `${event.id} (${event.availabilityStatus})`);
|
|
870
|
+
if (unknown.length) {
|
|
871
|
+
throw new Error(
|
|
872
|
+
`<kal-calendar> availabilityStatus must name a built-in bucket (${_CalendarElement.BUILT_IN_BUCKETS.join(", ")}) or a key of availabilityColors. Unrecognised on: ${unknown.join(", ")}.`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
resolveBucket(date) {
|
|
877
|
+
if (!this.engine) return "open";
|
|
878
|
+
const declared = new Set(
|
|
879
|
+
this.engine.getEventsForDate(date).map((event) => event.availabilityStatus)
|
|
880
|
+
);
|
|
881
|
+
if (declared.size === 0) return "open";
|
|
882
|
+
return this.knownBuckets.find((bucket) => declared.has(bucket));
|
|
883
|
+
}
|
|
884
|
+
isDateSelectable(date) {
|
|
885
|
+
if (!this.engine) return false;
|
|
886
|
+
if (this._selectableStatuses) {
|
|
887
|
+
return this._selectableStatuses.includes(this.resolveBucket(date));
|
|
888
|
+
}
|
|
889
|
+
return this.engine.getEventsForDate(date).length === 0;
|
|
673
890
|
}
|
|
674
891
|
render() {
|
|
675
892
|
if (!this.engine) return;
|
|
676
893
|
const viewModel = this.engine.getViewModel();
|
|
677
894
|
const useShortMonths = this.hasAttribute("use-short-month-names");
|
|
678
|
-
const title = this.
|
|
895
|
+
const title = this.headingText;
|
|
679
896
|
const minYear = this.minYear;
|
|
680
897
|
const maxYear = this.maxYear;
|
|
681
898
|
const availabilityMode = this.getAttribute("availability-mode");
|
|
682
899
|
const selectable = this.hasAttribute("selectable");
|
|
683
900
|
const isLoading = this.loading;
|
|
901
|
+
if (availabilityMode) {
|
|
902
|
+
this.assertStatusesDeclared();
|
|
903
|
+
this.assertStatusesKnown();
|
|
904
|
+
}
|
|
684
905
|
const today = /* @__PURE__ */ new Date();
|
|
685
906
|
const todayMonth = today.getMonth();
|
|
686
907
|
const todayYear = today.getFullYear();
|
|
@@ -689,7 +910,7 @@ var init_CalendarElement = __esm({
|
|
|
689
910
|
const defaultRenderEvent = (event) => {
|
|
690
911
|
const timeRange = formatTimeRange(event);
|
|
691
912
|
const attendeesList = formatAttendees(event.attendees);
|
|
692
|
-
let borderColor = event.color || "#3b82f6";
|
|
913
|
+
let borderColor = safeColor(event.color || "#3b82f6");
|
|
693
914
|
if (event.category) {
|
|
694
915
|
borderColor = this.engine.getCategoryColor(event.category);
|
|
695
916
|
}
|
|
@@ -724,56 +945,56 @@ var init_CalendarElement = __esm({
|
|
|
724
945
|
return `
|
|
725
946
|
<div class="event-card" style="border-left-color: ${borderColor}">
|
|
726
947
|
<div class="event-header">
|
|
727
|
-
<div class="event-title">${event.name}</div>
|
|
948
|
+
<div class="event-title">${escapeHtml(event.name)}</div>
|
|
728
949
|
<div class="event-badges">
|
|
729
|
-
${event.category ? `<span class="badge category-${event.category}">${getCategoryLabel(event.category)}</span>` : ""}
|
|
730
|
-
${event.priority ? `<span class="badge priority-${event.priority}">${getPriorityLabel(event.priority)}</span>` : ""}
|
|
731
|
-
${event.status && event.status !== "scheduled" ? `<span class="badge status-${event.status}">${getStatusLabel(event.status)}</span>` : ""}
|
|
950
|
+
${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
|
|
951
|
+
${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
|
|
952
|
+
${event.status && event.status !== "scheduled" ? `<span class="badge status status-${slugifyToken(event.status)}">${escapeHtml(getStatusLabel(event.status))}</span>` : ""}
|
|
732
953
|
</div>
|
|
733
954
|
</div>
|
|
734
955
|
|
|
735
956
|
${timeRange ? `
|
|
736
957
|
<div class="event-time">
|
|
737
958
|
<span class="event-time-label">Time:</span>
|
|
738
|
-
<span class="event-time-value">${timeRange}</span>
|
|
959
|
+
<span class="event-time-value">${escapeHtml(timeRange)}</span>
|
|
739
960
|
</div>
|
|
740
961
|
` : ""}
|
|
741
962
|
|
|
742
963
|
${event.description ? `
|
|
743
|
-
<div class="event-description">${event.description}</div>
|
|
964
|
+
<div class="event-description">${escapeHtml(event.description)}</div>
|
|
744
965
|
` : ""}
|
|
745
966
|
|
|
746
967
|
${event.location ? `
|
|
747
968
|
<div class="event-time">
|
|
748
969
|
<span class="event-time-label">Location:</span>
|
|
749
|
-
<span class="event-time-value">${event.location}</span>
|
|
970
|
+
<span class="event-time-value">${escapeHtml(event.location)}</span>
|
|
750
971
|
</div>
|
|
751
972
|
` : ""}
|
|
752
973
|
|
|
753
974
|
${attendeesList ? `
|
|
754
975
|
<div class="event-time">
|
|
755
976
|
<span class="event-time-label">Attendees:</span>
|
|
756
|
-
<span class="event-time-value">${attendeesList}</span>
|
|
977
|
+
<span class="event-time-value">${escapeHtml(attendeesList)}</span>
|
|
757
978
|
</div>
|
|
758
979
|
` : ""}
|
|
759
980
|
|
|
760
981
|
${event.organizer ? `
|
|
761
982
|
<div class="event-time">
|
|
762
983
|
<span class="event-time-label">Organizer:</span>
|
|
763
|
-
<span class="event-time-value">${event.organizer}</span>
|
|
984
|
+
<span class="event-time-value">${escapeHtml(event.organizer)}</span>
|
|
764
985
|
</div>
|
|
765
986
|
` : ""}
|
|
766
987
|
|
|
767
988
|
${event.notes ? `
|
|
768
989
|
<div class="event-time">
|
|
769
990
|
<span class="event-time-label">Notes:</span>
|
|
770
|
-
<span class="event-time-value">${event.notes}</span>
|
|
991
|
+
<span class="event-time-value">${escapeHtml(event.notes)}</span>
|
|
771
992
|
</div>
|
|
772
993
|
` : ""}
|
|
773
994
|
|
|
774
995
|
${event.url ? `
|
|
775
996
|
<div class="event-time">
|
|
776
|
-
<a href="${event.url}" target="_blank" rel="noopener noreferrer" class="event-link">
|
|
997
|
+
<a href="${escapeHtml(safeUrl(event.url))}" target="_blank" rel="noopener noreferrer" class="event-link">
|
|
777
998
|
View Details \u2192
|
|
778
999
|
</a>
|
|
779
1000
|
</div>
|
|
@@ -781,7 +1002,7 @@ var init_CalendarElement = __esm({
|
|
|
781
1002
|
|
|
782
1003
|
${event.tags && event.tags.length > 0 ? `
|
|
783
1004
|
<div class="event-tags">
|
|
784
|
-
${event.tags.map((tag) => `<span class="event-tag">${tag}</span>`).join("")}
|
|
1005
|
+
${event.tags.map((tag) => `<span class="event-tag">${escapeHtml(tag)}</span>`).join("")}
|
|
785
1006
|
</div>
|
|
786
1007
|
` : ""}
|
|
787
1008
|
</div>
|
|
@@ -790,97 +1011,181 @@ var init_CalendarElement = __esm({
|
|
|
790
1011
|
const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
|
|
791
1012
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
792
1013
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
793
|
-
const renderTimeGrid = (
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const
|
|
1014
|
+
const renderTimeGrid = (date) => {
|
|
1015
|
+
const slotDuration = this.slotDuration;
|
|
1016
|
+
const previousDay = new Date(date);
|
|
1017
|
+
previousDay.setDate(previousDay.getDate() - 1);
|
|
1018
|
+
const events = [
|
|
1019
|
+
...this.engine.getEventsForDate(previousDay),
|
|
1020
|
+
...this.engine.getEventsForDate(date)
|
|
1021
|
+
];
|
|
1022
|
+
this.warnMissingEndTime(events);
|
|
1023
|
+
const booked = bookedSlots(events, date, slotDuration);
|
|
1024
|
+
const slots = booked.map((isBooked, index) => {
|
|
1025
|
+
const startTime = formatMinutes(index * slotDuration);
|
|
1026
|
+
const endTime = formatMinutes((index + 1) * slotDuration);
|
|
1027
|
+
const inTimeRange = !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
805
1028
|
const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
|
|
806
1029
|
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
807
1030
|
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
808
1031
|
const slotClasses = [
|
|
809
|
-
"time-
|
|
810
|
-
|
|
811
|
-
isRangeStart ? "time-
|
|
812
|
-
isRangeEnd ? "time-
|
|
813
|
-
isInRange ? "time-
|
|
1032
|
+
"time-grid-slot",
|
|
1033
|
+
isBooked ? "time-grid-slot-blocked" : "time-grid-slot-open",
|
|
1034
|
+
isRangeStart ? "time-grid-slot-range-start" : "",
|
|
1035
|
+
isRangeEnd ? "time-grid-slot-range-end" : "",
|
|
1036
|
+
isInRange ? "time-grid-slot-in-range" : ""
|
|
814
1037
|
].filter(Boolean).join(" ");
|
|
815
|
-
const slotAttrs =
|
|
1038
|
+
const slotAttrs = `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
|
|
816
1039
|
return `
|
|
817
1040
|
<div class="${slotClasses}" ${slotAttrs}>
|
|
818
|
-
<span class="time-
|
|
819
|
-
<span class="time-
|
|
1041
|
+
<span class="time-grid-label">${startTime}</span>
|
|
1042
|
+
<span class="time-grid-status">${isBooked ? "Booked" : "Available"}</span>
|
|
820
1043
|
</div>`;
|
|
821
1044
|
});
|
|
822
|
-
|
|
1045
|
+
const gridClasses = selectable ? "time-grid time-grid-selectable" : "time-grid";
|
|
1046
|
+
return `<div class="${gridClasses}">${slots.join("")}</div>`;
|
|
823
1047
|
};
|
|
1048
|
+
const multiMonth = viewModel.panes.length > 1;
|
|
1049
|
+
const renderPane = (pane) => `
|
|
1050
|
+
<div class="calendar-pane">
|
|
1051
|
+
${multiMonth ? `<div class="calendar-pane-caption">${escapeHtml(pane.monthAndYearText)}</div>` : ""}
|
|
1052
|
+
<table class="calendar-table calendar-table-bordered">
|
|
1053
|
+
<thead>
|
|
1054
|
+
<tr>
|
|
1055
|
+
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
1056
|
+
</tr>
|
|
1057
|
+
</thead>
|
|
1058
|
+
<tbody data-calendar-body>
|
|
1059
|
+
${isLoading ? Array.from(
|
|
1060
|
+
{ length: 6 },
|
|
1061
|
+
() => `<tr>${Array.from(
|
|
1062
|
+
{ length: 7 },
|
|
1063
|
+
() => `<td class="calendar-skeleton" aria-hidden="true"></td>`
|
|
1064
|
+
).join("")}</tr>`
|
|
1065
|
+
).join("") : pane.calendarDates.map(
|
|
1066
|
+
(week) => `
|
|
1067
|
+
<tr>
|
|
1068
|
+
${week.map((calendarDate, dayIndex) => {
|
|
1069
|
+
const classes = getCellClasses(calendarDate);
|
|
1070
|
+
const cellAttrs = [];
|
|
1071
|
+
if (viewModel.selectedDate && isSameDay2(
|
|
1072
|
+
calendarDate.date,
|
|
1073
|
+
viewModel.selectedDate
|
|
1074
|
+
)) {
|
|
1075
|
+
classes.push("calendar-cell-selected");
|
|
1076
|
+
}
|
|
1077
|
+
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
1078
|
+
const bucket = this.resolveBucket(
|
|
1079
|
+
calendarDate.date
|
|
1080
|
+
);
|
|
1081
|
+
const custom = (this._availabilityColors ?? {})[bucket];
|
|
1082
|
+
classes.push(
|
|
1083
|
+
`availability-${slugifyToken(bucket)}`
|
|
1084
|
+
);
|
|
1085
|
+
if (custom) {
|
|
1086
|
+
classes.push("availability-status");
|
|
1087
|
+
cellAttrs.push(
|
|
1088
|
+
`style="--availability-color: ${escapeHtml(safeColor(custom))}"`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
if (!this.isDateSelectable(calendarDate.date)) {
|
|
1092
|
+
classes.push("availability-unselectable");
|
|
1093
|
+
}
|
|
1094
|
+
cellAttrs.push(
|
|
1095
|
+
`aria-label="${escapeHtml(bucket)}"`
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
1099
|
+
const d = calendarDate.date;
|
|
1100
|
+
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
1101
|
+
classes.push("availability-range-start");
|
|
1102
|
+
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
1103
|
+
classes.push("availability-range-end");
|
|
1104
|
+
if (this._rangeStart && this._rangeEnd) {
|
|
1105
|
+
if (d > this._rangeStart && d < this._rangeEnd)
|
|
1106
|
+
classes.push("availability-in-range");
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
const dateString = calendarDate.date.toISOString();
|
|
1110
|
+
return `
|
|
1111
|
+
<td
|
|
1112
|
+
class="${classes.join(" ")}"
|
|
1113
|
+
data-date="${dateString}"
|
|
1114
|
+
data-day-index="${dayIndex}"
|
|
1115
|
+
data-clickable="true"
|
|
1116
|
+
${cellAttrs.join(" ")}
|
|
1117
|
+
>
|
|
1118
|
+
${calendarDate.date.getDate()}
|
|
1119
|
+
</td>
|
|
1120
|
+
`;
|
|
1121
|
+
}).join("")}
|
|
1122
|
+
</tr>
|
|
1123
|
+
`
|
|
1124
|
+
).join("")}
|
|
1125
|
+
</tbody>
|
|
1126
|
+
</table>
|
|
1127
|
+
</div>
|
|
1128
|
+
`;
|
|
824
1129
|
const html = `
|
|
825
1130
|
${title ? `
|
|
826
|
-
<div class="
|
|
827
|
-
<h1>${title}</h1>
|
|
1131
|
+
<div class="calendar-title">
|
|
1132
|
+
<h1>${escapeHtml(title)}</h1>
|
|
828
1133
|
</div>
|
|
829
1134
|
` : ""}
|
|
830
1135
|
|
|
831
|
-
<div class="calendar
|
|
832
|
-
<div class="calendar
|
|
833
|
-
<div class="calendar
|
|
834
|
-
<button type="button" class="calendar
|
|
1136
|
+
<div class="calendar-content">
|
|
1137
|
+
<div class="calendar-card">
|
|
1138
|
+
<div class="calendar-nav-header">
|
|
1139
|
+
<button type="button" class="calendar-nav-arrow" data-action="previous" aria-label="Previous month">
|
|
835
1140
|
‹
|
|
836
1141
|
</button>
|
|
837
1142
|
|
|
838
|
-
<div class="calendar
|
|
1143
|
+
<div class="calendar-picker-container" data-picker-container>
|
|
839
1144
|
<button
|
|
840
1145
|
type="button"
|
|
841
|
-
class="calendar
|
|
1146
|
+
class="calendar-picker-btn"
|
|
842
1147
|
data-action="toggle-picker"
|
|
843
1148
|
aria-expanded="${this.pickerOpen ? "true" : "false"}"
|
|
844
1149
|
aria-haspopup="true"
|
|
845
1150
|
>
|
|
846
1151
|
${useShortMonths ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
|
|
847
|
-
<span class="calendar
|
|
1152
|
+
<span class="calendar-picker-chevron">▾</span>
|
|
848
1153
|
</button>
|
|
849
1154
|
|
|
850
1155
|
${this.pickerOpen ? `
|
|
851
|
-
<div class="calendar
|
|
852
|
-
<div class="calendar
|
|
1156
|
+
<div class="calendar-picker-dropdown">
|
|
1157
|
+
<div class="calendar-picker-year-row">
|
|
853
1158
|
<button
|
|
854
1159
|
type="button"
|
|
855
|
-
class="calendar
|
|
1160
|
+
class="calendar-picker-year-arrow"
|
|
856
1161
|
data-action="year-prev"
|
|
857
1162
|
${viewModel.currentYear <= minYear ? "disabled" : ""}
|
|
858
1163
|
aria-label="Previous year"
|
|
859
1164
|
>‹</button>
|
|
860
1165
|
<input
|
|
861
1166
|
type="text"
|
|
862
|
-
class="calendar
|
|
863
|
-
value="${this.yearInput || viewModel.currentYear}"
|
|
1167
|
+
class="calendar-picker-year-input${!this.yearInputValid ? " invalid" : ""}"
|
|
1168
|
+
value="${escapeHtml(this.yearInput || viewModel.currentYear)}"
|
|
864
1169
|
data-year-input
|
|
865
1170
|
aria-label="Year"
|
|
866
1171
|
/>
|
|
867
1172
|
<button
|
|
868
1173
|
type="button"
|
|
869
|
-
class="calendar
|
|
1174
|
+
class="calendar-picker-year-arrow"
|
|
870
1175
|
data-action="year-next"
|
|
871
1176
|
${viewModel.currentYear >= maxYear ? "disabled" : ""}
|
|
872
1177
|
aria-label="Next year"
|
|
873
1178
|
>›</button>
|
|
874
1179
|
</div>
|
|
875
1180
|
|
|
876
|
-
<div class="calendar
|
|
1181
|
+
<div class="calendar-picker-months">
|
|
877
1182
|
${(useShortMonths ? MONTHS : MONTHS_FULL).map((month, index) => {
|
|
878
1183
|
const isSelected = index === viewModel.currentMonth;
|
|
879
1184
|
const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
|
|
880
1185
|
return `
|
|
881
1186
|
<button
|
|
882
1187
|
type="button"
|
|
883
|
-
class="calendar
|
|
1188
|
+
class="calendar-picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
|
|
884
1189
|
data-action="select-month"
|
|
885
1190
|
data-month="${index}"
|
|
886
1191
|
>${month}</button>
|
|
@@ -893,70 +1198,22 @@ var init_CalendarElement = __esm({
|
|
|
893
1198
|
|
|
894
1199
|
<button
|
|
895
1200
|
type="button"
|
|
896
|
-
class="calendar
|
|
1201
|
+
class="calendar-today-btn"
|
|
897
1202
|
data-action="today"
|
|
898
1203
|
${isCurrentMonth ? "disabled" : ""}
|
|
899
1204
|
>Today</button>
|
|
900
1205
|
|
|
901
|
-
<button type="button" class="calendar
|
|
1206
|
+
<button type="button" class="calendar-nav-arrow" data-action="next" aria-label="Next month">
|
|
902
1207
|
›
|
|
903
1208
|
</button>
|
|
904
1209
|
</div>
|
|
905
1210
|
|
|
906
|
-
<
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
910
|
-
</tr>
|
|
911
|
-
</thead>
|
|
912
|
-
<tbody data-calendar-body>
|
|
913
|
-
${isLoading ? Array.from(
|
|
914
|
-
{ length: 6 },
|
|
915
|
-
() => `<tr>${Array.from(
|
|
916
|
-
{ length: 7 },
|
|
917
|
-
() => `<td class="calendar--skeleton" aria-hidden="true"></td>`
|
|
918
|
-
).join("")}</tr>`
|
|
919
|
-
).join("") : viewModel.calendarDates.map(
|
|
920
|
-
(week) => `
|
|
921
|
-
<tr>
|
|
922
|
-
${week.map((calendarDate, dayIndex) => {
|
|
923
|
-
const classes = getCellClasses(calendarDate);
|
|
924
|
-
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
925
|
-
classes.push(
|
|
926
|
-
calendarDate.hasEvents ? "availability--booked" : "availability--free"
|
|
927
|
-
);
|
|
928
|
-
}
|
|
929
|
-
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
930
|
-
const d = calendarDate.date;
|
|
931
|
-
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
932
|
-
classes.push("availability--range-start");
|
|
933
|
-
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
934
|
-
classes.push("availability--range-end");
|
|
935
|
-
if (this._rangeStart && this._rangeEnd) {
|
|
936
|
-
if (d > this._rangeStart && d < this._rangeEnd)
|
|
937
|
-
classes.push("availability--in-range");
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
const dateString = calendarDate.date.toISOString();
|
|
941
|
-
return `
|
|
942
|
-
<td
|
|
943
|
-
class="${classes.join(" ")}"
|
|
944
|
-
data-date="${dateString}"
|
|
945
|
-
data-day-index="${dayIndex}"
|
|
946
|
-
data-clickable="true"
|
|
947
|
-
>
|
|
948
|
-
${calendarDate.date.getDate()}
|
|
949
|
-
</td>
|
|
950
|
-
`;
|
|
951
|
-
}).join("")}
|
|
952
|
-
</tr>
|
|
953
|
-
`
|
|
954
|
-
).join("")}
|
|
955
|
-
</tbody>
|
|
956
|
-
</table>
|
|
1211
|
+
<div class="calendar-panes">
|
|
1212
|
+
${viewModel.panes.map(renderPane).join("")}
|
|
1213
|
+
</div>
|
|
957
1214
|
|
|
958
1215
|
${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
|
|
959
|
-
<div class="date-popup
|
|
1216
|
+
<div class="date-popup">
|
|
960
1217
|
<div class="popup-header">
|
|
961
1218
|
<h2>${viewModel.scheduleDay}</h2>
|
|
962
1219
|
<button type="button" class="popup-close" data-action="close-popup" aria-label="Close">\u2715</button>
|
|
@@ -969,7 +1226,7 @@ var init_CalendarElement = __esm({
|
|
|
969
1226
|
` : ""}
|
|
970
1227
|
|
|
971
1228
|
<div class="events-container">
|
|
972
|
-
${availabilityMode === "time" ? renderTimeGrid(viewModel.
|
|
1229
|
+
${availabilityMode === "time" ? renderTimeGrid(viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
973
1230
|
</div>
|
|
974
1231
|
</div>
|
|
975
1232
|
` : ""}
|
|
@@ -994,8 +1251,7 @@ var init_CalendarElement = __esm({
|
|
|
994
1251
|
const availMode = this.getAttribute("availability-mode");
|
|
995
1252
|
const isSelectable = this.hasAttribute("selectable");
|
|
996
1253
|
if (availMode === "day" && isSelectable) {
|
|
997
|
-
|
|
998
|
-
if (!isBooked) {
|
|
1254
|
+
if (this.isDateSelectable(date)) {
|
|
999
1255
|
let startDate;
|
|
1000
1256
|
let endDate;
|
|
1001
1257
|
if (this._rangeEnd !== null) {
|
|
@@ -1013,7 +1269,7 @@ var init_CalendarElement = __esm({
|
|
|
1013
1269
|
cursor.setDate(cursor.getDate() + 1);
|
|
1014
1270
|
let blocked = false;
|
|
1015
1271
|
while (cursor < e2) {
|
|
1016
|
-
if (this.
|
|
1272
|
+
if (!this.isDateSelectable(cursor)) {
|
|
1017
1273
|
blocked = true;
|
|
1018
1274
|
break;
|
|
1019
1275
|
}
|
|
@@ -1131,6 +1387,20 @@ var init_CalendarElement = __esm({
|
|
|
1131
1387
|
const slotStart = actionEl.dataset.startTime;
|
|
1132
1388
|
const slotEnd = actionEl.dataset.endTime;
|
|
1133
1389
|
const slotDate = new Date(actionEl.dataset.date);
|
|
1390
|
+
const slotBooked = actionEl.dataset.booked === "true";
|
|
1391
|
+
this.dispatchEvent(
|
|
1392
|
+
new CustomEvent("cal-slot-select", {
|
|
1393
|
+
bubbles: true,
|
|
1394
|
+
composed: true,
|
|
1395
|
+
detail: {
|
|
1396
|
+
date: slotDate,
|
|
1397
|
+
startTime: slotStart,
|
|
1398
|
+
endTime: slotEnd,
|
|
1399
|
+
booked: slotBooked
|
|
1400
|
+
}
|
|
1401
|
+
})
|
|
1402
|
+
);
|
|
1403
|
+
if (slotBooked || !this.hasAttribute("selectable")) break;
|
|
1134
1404
|
if (this._timeRangeComplete) {
|
|
1135
1405
|
this._timeRangeDate = slotDate;
|
|
1136
1406
|
this._timeRangeStart = slotStart;
|
|
@@ -1247,31 +1517,86 @@ var init_CalendarElement = __esm({
|
|
|
1247
1517
|
this.theme = theme;
|
|
1248
1518
|
}
|
|
1249
1519
|
getCurrentDate() {
|
|
1520
|
+
this.rethrowInitError();
|
|
1250
1521
|
return this.engine?.getViewModel().selectedDate ?? null;
|
|
1251
1522
|
}
|
|
1252
1523
|
goToDate(date) {
|
|
1524
|
+
this.rethrowInitError();
|
|
1253
1525
|
const year = date.getFullYear();
|
|
1254
1526
|
const month = date.getMonth();
|
|
1255
1527
|
this.dispatchMonthChange(year, month);
|
|
1256
1528
|
this.actions?.jump(year, month);
|
|
1257
1529
|
}
|
|
1258
1530
|
getEngine() {
|
|
1531
|
+
this.rethrowInitError();
|
|
1259
1532
|
if (!this.engine)
|
|
1260
1533
|
throw new Error("CalendarElement is not connected to the DOM");
|
|
1261
1534
|
return this.engine;
|
|
1262
1535
|
}
|
|
1263
1536
|
};
|
|
1264
|
-
|
|
1537
|
+
_CalendarElement.observedAttributes = [
|
|
1265
1538
|
"initial-date",
|
|
1266
1539
|
"min-year",
|
|
1267
1540
|
"max-year",
|
|
1268
1541
|
"week-starts-on",
|
|
1542
|
+
"heading",
|
|
1543
|
+
"months",
|
|
1544
|
+
"slot-duration",
|
|
1269
1545
|
"title",
|
|
1270
1546
|
"use-short-month-names",
|
|
1271
1547
|
"availability-mode",
|
|
1272
1548
|
"selectable",
|
|
1273
1549
|
"loading"
|
|
1274
1550
|
];
|
|
1551
|
+
_CalendarElement.themeMap = {
|
|
1552
|
+
primary: "--calendar-primary-color",
|
|
1553
|
+
secondary: "--calendar-secondary-color",
|
|
1554
|
+
tertiary: "--calendar-tertiary-color",
|
|
1555
|
+
textColor: "--calendar-text-color",
|
|
1556
|
+
textLight: "--calendar-text-light",
|
|
1557
|
+
background: "--calendar-background",
|
|
1558
|
+
cellHover: "--calendar-cell-hover",
|
|
1559
|
+
borderColor: "--calendar-border-color",
|
|
1560
|
+
todayOutline: "--calendar-today-outline",
|
|
1561
|
+
selectedBg: "--calendar-selected-bg",
|
|
1562
|
+
headerBg: "--calendar-header-bg",
|
|
1563
|
+
popupBg: "--calendar-popup-bg",
|
|
1564
|
+
pickerBg: "--calendar-picker-bg",
|
|
1565
|
+
pickerShadow: "--calendar-picker-shadow",
|
|
1566
|
+
eventIndicator: "--calendar-event-indicator",
|
|
1567
|
+
onAccent: "--calendar-on-accent",
|
|
1568
|
+
link: "--calendar-link",
|
|
1569
|
+
openBg: "--calendar-open-bg",
|
|
1570
|
+
openFg: "--calendar-open-fg",
|
|
1571
|
+
conditionalBg: "--calendar-conditional-bg",
|
|
1572
|
+
conditionalFg: "--calendar-conditional-fg",
|
|
1573
|
+
blockedBg: "--calendar-blocked-bg",
|
|
1574
|
+
blockedFg: "--calendar-blocked-fg",
|
|
1575
|
+
rangeBg: "--calendar-range-bg",
|
|
1576
|
+
rangeOutline: "--calendar-range-outline",
|
|
1577
|
+
inRangeBg: "--calendar-in-range-bg",
|
|
1578
|
+
inRangeOutline: "--calendar-in-range-outline",
|
|
1579
|
+
badgeBg: "--calendar-badge-bg",
|
|
1580
|
+
badgeText: "--calendar-badge-text",
|
|
1581
|
+
badgeSuccessBg: "--calendar-badge-success-bg",
|
|
1582
|
+
badgeSuccessText: "--calendar-badge-success-text",
|
|
1583
|
+
badgeInfoBg: "--calendar-badge-info-bg",
|
|
1584
|
+
badgeInfoText: "--calendar-badge-info-text",
|
|
1585
|
+
badgeWarningBg: "--calendar-badge-warning-bg",
|
|
1586
|
+
badgeWarningText: "--calendar-badge-warning-text",
|
|
1587
|
+
badgeDangerBg: "--calendar-badge-danger-bg",
|
|
1588
|
+
badgeDangerText: "--calendar-badge-danger-text",
|
|
1589
|
+
badgeNeutralBg: "--calendar-badge-neutral-bg",
|
|
1590
|
+
badgeNeutralText: "--calendar-badge-neutral-text",
|
|
1591
|
+
badgePositiveBg: "--calendar-badge-positive-bg",
|
|
1592
|
+
badgePositiveText: "--calendar-badge-positive-text",
|
|
1593
|
+
badgeTentativeBg: "--calendar-badge-tentative-bg",
|
|
1594
|
+
badgeTentativeText: "--calendar-badge-tentative-text"
|
|
1595
|
+
};
|
|
1596
|
+
_CalendarElement.warnedMissingEnd = /* @__PURE__ */ new Set();
|
|
1597
|
+
_CalendarElement.BUILT_IN_BUCKETS = ["blocked", "conditional", "open"];
|
|
1598
|
+
_CalendarElement.titleDeprecationWarned = false;
|
|
1599
|
+
CalendarElement = _CalendarElement;
|
|
1275
1600
|
}
|
|
1276
1601
|
});
|
|
1277
1602
|
|
|
@@ -1290,11 +1615,18 @@ export {
|
|
|
1290
1615
|
CalendarEngine,
|
|
1291
1616
|
DAYS,
|
|
1292
1617
|
DEFAULT_CATEGORY_COLORS,
|
|
1618
|
+
DEFAULT_SLOT_DURATION,
|
|
1619
|
+
MINUTES_PER_DAY,
|
|
1293
1620
|
MONTHS,
|
|
1294
1621
|
MONTHS_FULL,
|
|
1622
|
+
bookedSlots,
|
|
1295
1623
|
defineCalendarElement,
|
|
1624
|
+
escapeHtml,
|
|
1625
|
+
eventCoversDate,
|
|
1626
|
+
eventInterval,
|
|
1296
1627
|
formatAttendees,
|
|
1297
1628
|
formatDateForDisplay,
|
|
1629
|
+
formatMinutes,
|
|
1298
1630
|
formatTimeRange,
|
|
1299
1631
|
generateCalendarDates,
|
|
1300
1632
|
generateYears,
|
|
@@ -1303,13 +1635,17 @@ export {
|
|
|
1303
1635
|
getDefaultEventColor,
|
|
1304
1636
|
getEventsForDate,
|
|
1305
1637
|
getMonthYearText,
|
|
1306
|
-
getPopupPositionClass,
|
|
1307
1638
|
hasEvents,
|
|
1308
1639
|
isSameDay,
|
|
1309
1640
|
isToday,
|
|
1310
1641
|
isValidHexColor,
|
|
1311
1642
|
mergeCategoryColors,
|
|
1643
|
+
mergeIntervals,
|
|
1312
1644
|
normalizeDate,
|
|
1645
|
+
parseTimeToMinutes,
|
|
1646
|
+
safeColor,
|
|
1647
|
+
safeUrl,
|
|
1648
|
+
slugifyToken,
|
|
1313
1649
|
sortEventsByTime
|
|
1314
1650
|
};
|
|
1315
1651
|
//# sourceMappingURL=index.mjs.map
|