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.js
CHANGED
|
@@ -43,12 +43,28 @@ function generateYears(minYear, maxYear) {
|
|
|
43
43
|
const max = maxYear ?? currentYear + 10;
|
|
44
44
|
return Array.from({ length: max - min + 1 }, (_, i) => min + i);
|
|
45
45
|
}
|
|
46
|
+
function eventCoversDate(event, date) {
|
|
47
|
+
const start = normalizeDate(new Date(event.date)).getTime();
|
|
48
|
+
const target = normalizeDate(date).getTime();
|
|
49
|
+
if (Number.isNaN(start)) return false;
|
|
50
|
+
if (event.endDate === void 0 || event.endDate === null) {
|
|
51
|
+
return start === target;
|
|
52
|
+
}
|
|
53
|
+
const end = normalizeDate(new Date(event.endDate)).getTime();
|
|
54
|
+
if (Number.isNaN(end)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`<kal-calendar> event ${event.id} has an unreadable endDate: ${String(event.endDate)}.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (end < start) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`<kal-calendar> event ${event.id} has an endDate before its date: ${String(event.endDate)} < ${String(event.date)}.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return target >= start && target <= end;
|
|
65
|
+
}
|
|
46
66
|
function getEventsForDate(events, date) {
|
|
47
|
-
|
|
48
|
-
return events.filter((event) => {
|
|
49
|
-
const eventDate = normalizeDate(new Date(event.date));
|
|
50
|
-
return eventDate.getTime() === normalizedTargetDate.getTime();
|
|
51
|
-
});
|
|
67
|
+
return events.filter((event) => eventCoversDate(event, date));
|
|
52
68
|
}
|
|
53
69
|
function hasEvents(events, date) {
|
|
54
70
|
return getEventsForDate(events, date).length > 0;
|
|
@@ -107,26 +123,16 @@ function generateCalendarDates(year, month, events = [], weekStartsOn = 0) {
|
|
|
107
123
|
}
|
|
108
124
|
return dates;
|
|
109
125
|
}
|
|
110
|
-
function getPopupPositionClass(selectedDayIndex) {
|
|
111
|
-
if (selectedDayIndex === null) return "popup-center-bottom";
|
|
112
|
-
if (selectedDayIndex < 3) {
|
|
113
|
-
return "popup-right";
|
|
114
|
-
} else if (selectedDayIndex > 4) {
|
|
115
|
-
return "popup-left";
|
|
116
|
-
} else {
|
|
117
|
-
return "popup-center-bottom";
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
126
|
function getCellClasses(calendarDate) {
|
|
121
127
|
const classes = [];
|
|
122
128
|
if (!calendarDate.isCurrentMonth) {
|
|
123
|
-
classes.push("other-month");
|
|
129
|
+
classes.push("calendar-cell-other-month");
|
|
124
130
|
}
|
|
125
131
|
if (calendarDate.isToday) {
|
|
126
|
-
classes.push("
|
|
132
|
+
classes.push("calendar-cell-today");
|
|
127
133
|
}
|
|
128
134
|
if (calendarDate.hasEvents) {
|
|
129
|
-
classes.push("has
|
|
135
|
+
classes.push("calendar-cell-has-event");
|
|
130
136
|
}
|
|
131
137
|
return classes;
|
|
132
138
|
}
|
|
@@ -185,7 +191,84 @@ function getCategoryColor(category, customColors) {
|
|
|
185
191
|
const color = colorMap[category] || colorMap.other || "#fc8917";
|
|
186
192
|
return isValidHexColor(color) ? color : "#fc8917";
|
|
187
193
|
}
|
|
188
|
-
|
|
194
|
+
function escapeHtml(value) {
|
|
195
|
+
if (value === null || value === void 0) return "";
|
|
196
|
+
return String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
|
|
197
|
+
}
|
|
198
|
+
function slugifyToken(value) {
|
|
199
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
200
|
+
}
|
|
201
|
+
function safeUrl(value) {
|
|
202
|
+
const normalized = String(value).split("").filter((char) => char.charCodeAt(0) > 32).join("");
|
|
203
|
+
if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;
|
|
204
|
+
const schemeless = !/^[^/?#]*:/.test(normalized);
|
|
205
|
+
return schemeless ? normalized : "#";
|
|
206
|
+
}
|
|
207
|
+
function safeColor(value) {
|
|
208
|
+
const trimmed = String(value).trim();
|
|
209
|
+
return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
|
|
210
|
+
}
|
|
211
|
+
function parseTimeToMinutes(time) {
|
|
212
|
+
if (typeof time !== "string") return null;
|
|
213
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
|
|
214
|
+
if (!match) return null;
|
|
215
|
+
const hours = Number(match[1]);
|
|
216
|
+
const mins = Number(match[2]);
|
|
217
|
+
if (hours > 24 || mins > 59 || hours === 24 && mins > 0) return null;
|
|
218
|
+
return hours * 60 + mins;
|
|
219
|
+
}
|
|
220
|
+
function formatMinutes(total) {
|
|
221
|
+
const hours = Math.floor(total / 60);
|
|
222
|
+
const mins = total % 60;
|
|
223
|
+
return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
|
|
224
|
+
}
|
|
225
|
+
function eventInterval(event, slotDuration, onDay) {
|
|
226
|
+
const day = normalizeDate(onDay).getTime();
|
|
227
|
+
if (Number.isNaN(day)) return null;
|
|
228
|
+
const base = day / 6e4;
|
|
229
|
+
const wholeDay = [base, base + MINUTES_PER_DAY];
|
|
230
|
+
if (event.allDay || !event.startTime) return wholeDay;
|
|
231
|
+
const from = parseTimeToMinutes(event.startTime);
|
|
232
|
+
if (from === null) return wholeDay;
|
|
233
|
+
if (event.endTime === void 0 || event.endTime === null) {
|
|
234
|
+
return [base + from, base + from + slotDuration];
|
|
235
|
+
}
|
|
236
|
+
const to = parseTimeToMinutes(event.endTime);
|
|
237
|
+
if (to === null) return wholeDay;
|
|
238
|
+
return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];
|
|
239
|
+
}
|
|
240
|
+
function mergeIntervals(intervals) {
|
|
241
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
242
|
+
return sorted.reduce((merged, [start, end]) => {
|
|
243
|
+
const last = merged[merged.length - 1];
|
|
244
|
+
if (last && start <= last[1]) last[1] = Math.max(last[1], end);
|
|
245
|
+
else merged.push([start, end]);
|
|
246
|
+
return merged;
|
|
247
|
+
}, []);
|
|
248
|
+
}
|
|
249
|
+
function bookedSlots(events, date, slotDuration = DEFAULT_SLOT_DURATION) {
|
|
250
|
+
const base = normalizeDate(date).getTime() / 6e4;
|
|
251
|
+
const previousDay = new Date(date);
|
|
252
|
+
previousDay.setDate(previousDay.getDate() - 1);
|
|
253
|
+
const intervals = [];
|
|
254
|
+
for (const event of new Set(events)) {
|
|
255
|
+
for (const day of [previousDay, date]) {
|
|
256
|
+
if (!eventCoversDate(event, day)) continue;
|
|
257
|
+
const interval = eventInterval(event, slotDuration, day);
|
|
258
|
+
if (interval) intervals.push(interval);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const merged = mergeIntervals(intervals);
|
|
262
|
+
return Array.from(
|
|
263
|
+
{ length: Math.floor(MINUTES_PER_DAY / slotDuration) },
|
|
264
|
+
(_, index) => {
|
|
265
|
+
const start = base + index * slotDuration;
|
|
266
|
+
const end = start + slotDuration;
|
|
267
|
+
return merged.some(([from, to]) => start < to && end > from);
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS, HTML_ESCAPES, MINUTES_PER_DAY, DEFAULT_SLOT_DURATION;
|
|
189
272
|
var init_utils = __esm({
|
|
190
273
|
"src/core/utils.ts"() {
|
|
191
274
|
"use strict";
|
|
@@ -234,6 +317,15 @@ var init_utils = __esm({
|
|
|
234
317
|
appointment: "#f59e0b",
|
|
235
318
|
other: "#6b7280"
|
|
236
319
|
};
|
|
320
|
+
HTML_ESCAPES = {
|
|
321
|
+
"&": "&",
|
|
322
|
+
"<": "<",
|
|
323
|
+
">": ">",
|
|
324
|
+
'"': """,
|
|
325
|
+
"'": "'"
|
|
326
|
+
};
|
|
327
|
+
MINUTES_PER_DAY = 1440;
|
|
328
|
+
DEFAULT_SLOT_DURATION = 60;
|
|
237
329
|
}
|
|
238
330
|
});
|
|
239
331
|
|
|
@@ -281,24 +373,38 @@ var init_calendar_engine = __esm({
|
|
|
281
373
|
* Get view model with computed properties
|
|
282
374
|
*/
|
|
283
375
|
getViewModel() {
|
|
284
|
-
const
|
|
285
|
-
this.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
376
|
+
const panes = Array.from(
|
|
377
|
+
{ length: Math.max(1, this.config.monthCount ?? 1) },
|
|
378
|
+
(_, offset) => {
|
|
379
|
+
const anchor = new Date(
|
|
380
|
+
this.state.currentYear,
|
|
381
|
+
this.state.currentMonth + offset,
|
|
382
|
+
1
|
|
383
|
+
);
|
|
384
|
+
const year = anchor.getFullYear();
|
|
385
|
+
const month = anchor.getMonth();
|
|
386
|
+
return {
|
|
387
|
+
year,
|
|
388
|
+
month,
|
|
389
|
+
monthAndYearText: getMonthYearText(year, month),
|
|
390
|
+
calendarDates: generateCalendarDates(
|
|
391
|
+
year,
|
|
392
|
+
month,
|
|
393
|
+
this.config.events,
|
|
394
|
+
this.config.weekStartsOn
|
|
395
|
+
)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
289
398
|
);
|
|
290
399
|
return {
|
|
291
400
|
...this.state,
|
|
292
401
|
months: MONTHS,
|
|
293
402
|
days: DAYS,
|
|
294
403
|
years: generateYears(this.config.minYear, this.config.maxYear),
|
|
295
|
-
monthAndYearText:
|
|
296
|
-
this.state.currentYear,
|
|
297
|
-
this.state.currentMonth
|
|
298
|
-
),
|
|
404
|
+
monthAndYearText: panes[0].monthAndYearText,
|
|
299
405
|
scheduleDay: this.state.selectedDate ? formatDateForDisplay(this.state.selectedDate) : "",
|
|
300
|
-
|
|
301
|
-
|
|
406
|
+
panes,
|
|
407
|
+
calendarDates: panes[0].calendarDates
|
|
302
408
|
};
|
|
303
409
|
}
|
|
304
410
|
/**
|
|
@@ -494,13 +600,13 @@ function defineCalendarElement(tagName = "kal-calendar") {
|
|
|
494
600
|
customElements.define(tagName, CalendarElement);
|
|
495
601
|
}
|
|
496
602
|
}
|
|
497
|
-
var isSameDay2, CalendarElement;
|
|
603
|
+
var isSameDay2, _CalendarElement, CalendarElement;
|
|
498
604
|
var init_CalendarElement = __esm({
|
|
499
605
|
"src/web-components/CalendarElement.ts"() {
|
|
500
606
|
"use strict";
|
|
501
607
|
init_core();
|
|
502
608
|
isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
503
|
-
|
|
609
|
+
_CalendarElement = class _CalendarElement extends HTMLElement {
|
|
504
610
|
constructor() {
|
|
505
611
|
super(...arguments);
|
|
506
612
|
this.engine = null;
|
|
@@ -521,6 +627,10 @@ var init_CalendarElement = __esm({
|
|
|
521
627
|
this._categoryColors = null;
|
|
522
628
|
this._renderEvent = null;
|
|
523
629
|
this._renderNoEvents = null;
|
|
630
|
+
this._availabilityColors = null;
|
|
631
|
+
this._selectableStatuses = null;
|
|
632
|
+
this._initError = null;
|
|
633
|
+
this.pendingUpdate = null;
|
|
524
634
|
// Selection state (availability mode — range)
|
|
525
635
|
this._rangeStart = null;
|
|
526
636
|
this._rangeEnd = null;
|
|
@@ -533,6 +643,7 @@ var init_CalendarElement = __esm({
|
|
|
533
643
|
return this._events;
|
|
534
644
|
}
|
|
535
645
|
set events(val) {
|
|
646
|
+
this._initError = null;
|
|
536
647
|
this._events = val;
|
|
537
648
|
if (this.engine) {
|
|
538
649
|
this.engine.updateEvents(val);
|
|
@@ -548,13 +659,28 @@ var init_CalendarElement = __esm({
|
|
|
548
659
|
this.engine.updateCategoryColors(val);
|
|
549
660
|
}
|
|
550
661
|
}
|
|
662
|
+
get availabilityColors() {
|
|
663
|
+
return this._availabilityColors ?? {};
|
|
664
|
+
}
|
|
665
|
+
set availabilityColors(val) {
|
|
666
|
+
this._initError = null;
|
|
667
|
+
this._availabilityColors = val;
|
|
668
|
+
if (this.engine) this.scheduleRender();
|
|
669
|
+
}
|
|
670
|
+
get selectableStatuses() {
|
|
671
|
+
return this._selectableStatuses ?? [];
|
|
672
|
+
}
|
|
673
|
+
set selectableStatuses(val) {
|
|
674
|
+
this._selectableStatuses = val;
|
|
675
|
+
if (this.engine) this.scheduleRender();
|
|
676
|
+
}
|
|
551
677
|
set renderEvent(val) {
|
|
552
678
|
this._renderEvent = val;
|
|
553
|
-
if (this.engine) this.
|
|
679
|
+
if (this.engine) this.scheduleRender();
|
|
554
680
|
}
|
|
555
681
|
set renderNoEvents(val) {
|
|
556
682
|
this._renderNoEvents = val;
|
|
557
|
-
if (this.engine) this.
|
|
683
|
+
if (this.engine) this.scheduleRender();
|
|
558
684
|
}
|
|
559
685
|
get loading() {
|
|
560
686
|
return this.hasAttribute("loading");
|
|
@@ -563,10 +689,22 @@ var init_CalendarElement = __esm({
|
|
|
563
689
|
if (val) this.setAttribute("loading", "");
|
|
564
690
|
else this.removeAttribute("loading");
|
|
565
691
|
}
|
|
692
|
+
// Custom element reactions report rather than propagate, so a failure here
|
|
693
|
+
// is kept and resurfaced at the next call the integrator makes
|
|
694
|
+
reaction(run) {
|
|
695
|
+
try {
|
|
696
|
+
run();
|
|
697
|
+
} catch (error) {
|
|
698
|
+
this._initError = error;
|
|
699
|
+
throw error;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
566
702
|
connectedCallback() {
|
|
567
703
|
this.classList.add("kalendly-calendar");
|
|
568
|
-
this.
|
|
569
|
-
|
|
704
|
+
this.reaction(() => {
|
|
705
|
+
this.initEngine();
|
|
706
|
+
this.render();
|
|
707
|
+
});
|
|
570
708
|
}
|
|
571
709
|
disconnectedCallback() {
|
|
572
710
|
this.cleanup();
|
|
@@ -576,7 +714,7 @@ var init_CalendarElement = __esm({
|
|
|
576
714
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
577
715
|
if (oldVal === newVal) return;
|
|
578
716
|
if (name === "loading") {
|
|
579
|
-
this.
|
|
717
|
+
this.scheduleRender();
|
|
580
718
|
return;
|
|
581
719
|
}
|
|
582
720
|
if (name === "selectable" && newVal === null) {
|
|
@@ -589,6 +727,46 @@ var init_CalendarElement = __esm({
|
|
|
589
727
|
}
|
|
590
728
|
this.reinit();
|
|
591
729
|
}
|
|
730
|
+
get headingText() {
|
|
731
|
+
const heading = this.getAttribute("heading");
|
|
732
|
+
if (heading !== null) return heading;
|
|
733
|
+
const title = this.getAttribute("title");
|
|
734
|
+
if (title !== null && !_CalendarElement.titleDeprecationWarned) {
|
|
735
|
+
_CalendarElement.titleDeprecationWarned = true;
|
|
736
|
+
console.warn(
|
|
737
|
+
`<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.`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
return title;
|
|
741
|
+
}
|
|
742
|
+
get slotDuration() {
|
|
743
|
+
const raw = this.getAttribute("slot-duration");
|
|
744
|
+
if (raw === null) return DEFAULT_SLOT_DURATION;
|
|
745
|
+
const minutes = Number(raw);
|
|
746
|
+
if (Number.isInteger(minutes) && minutes > 0 && MINUTES_PER_DAY % minutes === 0) {
|
|
747
|
+
return minutes;
|
|
748
|
+
}
|
|
749
|
+
console.warn(
|
|
750
|
+
`<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}.`
|
|
751
|
+
);
|
|
752
|
+
return DEFAULT_SLOT_DURATION;
|
|
753
|
+
}
|
|
754
|
+
warnMissingEndTime(events) {
|
|
755
|
+
for (const event of events) {
|
|
756
|
+
if (!event.startTime || event.endTime) continue;
|
|
757
|
+
const key = String(event.id);
|
|
758
|
+
if (_CalendarElement.warnedMissingEnd.has(key)) continue;
|
|
759
|
+
_CalendarElement.warnedMissingEnd.add(key);
|
|
760
|
+
console.warn(
|
|
761
|
+
`<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.`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
get monthCount() {
|
|
766
|
+
const requested = Number(this.getAttribute("months") ?? 1);
|
|
767
|
+
if (!Number.isInteger(requested) || requested < 1) return 1;
|
|
768
|
+
return Math.min(requested, 2);
|
|
769
|
+
}
|
|
592
770
|
get minYear() {
|
|
593
771
|
const val = this.getAttribute("min-year");
|
|
594
772
|
return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() - 30;
|
|
@@ -608,19 +786,20 @@ var init_CalendarElement = __esm({
|
|
|
608
786
|
minYear: this.minYear,
|
|
609
787
|
maxYear: this.maxYear,
|
|
610
788
|
weekStartsOn,
|
|
611
|
-
categoryColors: this._categoryColors ?? void 0
|
|
789
|
+
categoryColors: this._categoryColors ?? void 0,
|
|
790
|
+
monthCount: this.monthCount
|
|
612
791
|
});
|
|
613
792
|
this.actions = this.engine.getActions();
|
|
614
793
|
this.applyTheme();
|
|
615
794
|
this.unsubscribe = this.engine.subscribe(() => {
|
|
616
|
-
this.
|
|
795
|
+
this.scheduleRender();
|
|
617
796
|
});
|
|
618
797
|
}
|
|
619
798
|
reinit() {
|
|
620
799
|
if (!this.engine) return;
|
|
621
800
|
this.cleanup();
|
|
622
801
|
this.initEngine();
|
|
623
|
-
this.
|
|
802
|
+
this.scheduleRender();
|
|
624
803
|
}
|
|
625
804
|
cleanup() {
|
|
626
805
|
if (this.unsubscribe) {
|
|
@@ -657,42 +836,84 @@ var init_CalendarElement = __esm({
|
|
|
657
836
|
applyTheme() {
|
|
658
837
|
if (!this._theme) return;
|
|
659
838
|
const root = document.documentElement;
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
root.style.setProperty(
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
839
|
+
for (const key of Object.keys(_CalendarElement.themeMap)) {
|
|
840
|
+
const value = this._theme[key];
|
|
841
|
+
if (value) root.style.setProperty(_CalendarElement.themeMap[key], value);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
get knownBuckets() {
|
|
845
|
+
return [
|
|
846
|
+
..._CalendarElement.BUILT_IN_BUCKETS,
|
|
847
|
+
...Object.keys(this._availabilityColors ?? {})
|
|
848
|
+
];
|
|
849
|
+
}
|
|
850
|
+
get updateComplete() {
|
|
851
|
+
return this.pendingUpdate ?? Promise.resolve();
|
|
852
|
+
}
|
|
853
|
+
scheduleRender() {
|
|
854
|
+
if (this.pendingUpdate) return;
|
|
855
|
+
this.pendingUpdate = Promise.resolve().then(() => {
|
|
856
|
+
this.pendingUpdate = null;
|
|
857
|
+
try {
|
|
858
|
+
this.render();
|
|
859
|
+
} catch (error) {
|
|
860
|
+
this._initError = error;
|
|
861
|
+
throw error;
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
rethrowInitError() {
|
|
866
|
+
if (this._initError) throw this._initError;
|
|
867
|
+
}
|
|
868
|
+
assertStatusesDeclared() {
|
|
869
|
+
if (!this.getAttribute("availability-mode")) return;
|
|
870
|
+
const missing = this._events.filter(
|
|
871
|
+
(event) => typeof event.availabilityStatus !== "string" || event.availabilityStatus === ""
|
|
872
|
+
).map((event) => String(event.id));
|
|
873
|
+
if (missing.length) {
|
|
874
|
+
throw new Error(
|
|
875
|
+
`<kal-calendar> availability-mode requires availabilityStatus on every event. Missing on: ${missing.join(", ")}.`
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
assertStatusesKnown() {
|
|
880
|
+
const known = new Set(this.knownBuckets);
|
|
881
|
+
const unknown = this._events.filter((event) => !known.has(event.availabilityStatus)).map((event) => `${event.id} (${event.availabilityStatus})`);
|
|
882
|
+
if (unknown.length) {
|
|
883
|
+
throw new Error(
|
|
884
|
+
`<kal-calendar> availabilityStatus must name a built-in bucket (${_CalendarElement.BUILT_IN_BUCKETS.join(", ")}) or a key of availabilityColors. Unrecognised on: ${unknown.join(", ")}.`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
resolveBucket(date) {
|
|
889
|
+
if (!this.engine) return "open";
|
|
890
|
+
const declared = new Set(
|
|
891
|
+
this.engine.getEventsForDate(date).map((event) => event.availabilityStatus)
|
|
892
|
+
);
|
|
893
|
+
if (declared.size === 0) return "open";
|
|
894
|
+
return this.knownBuckets.find((bucket) => declared.has(bucket));
|
|
895
|
+
}
|
|
896
|
+
isDateSelectable(date) {
|
|
897
|
+
if (!this.engine) return false;
|
|
898
|
+
if (this._selectableStatuses) {
|
|
899
|
+
return this._selectableStatuses.includes(this.resolveBucket(date));
|
|
900
|
+
}
|
|
901
|
+
return this.engine.getEventsForDate(date).length === 0;
|
|
685
902
|
}
|
|
686
903
|
render() {
|
|
687
904
|
if (!this.engine) return;
|
|
688
905
|
const viewModel = this.engine.getViewModel();
|
|
689
906
|
const useShortMonths = this.hasAttribute("use-short-month-names");
|
|
690
|
-
const title = this.
|
|
907
|
+
const title = this.headingText;
|
|
691
908
|
const minYear = this.minYear;
|
|
692
909
|
const maxYear = this.maxYear;
|
|
693
910
|
const availabilityMode = this.getAttribute("availability-mode");
|
|
694
911
|
const selectable = this.hasAttribute("selectable");
|
|
695
912
|
const isLoading = this.loading;
|
|
913
|
+
if (availabilityMode) {
|
|
914
|
+
this.assertStatusesDeclared();
|
|
915
|
+
this.assertStatusesKnown();
|
|
916
|
+
}
|
|
696
917
|
const today = /* @__PURE__ */ new Date();
|
|
697
918
|
const todayMonth = today.getMonth();
|
|
698
919
|
const todayYear = today.getFullYear();
|
|
@@ -701,7 +922,7 @@ var init_CalendarElement = __esm({
|
|
|
701
922
|
const defaultRenderEvent = (event) => {
|
|
702
923
|
const timeRange = formatTimeRange(event);
|
|
703
924
|
const attendeesList = formatAttendees(event.attendees);
|
|
704
|
-
let borderColor = event.color || "#3b82f6";
|
|
925
|
+
let borderColor = safeColor(event.color || "#3b82f6");
|
|
705
926
|
if (event.category) {
|
|
706
927
|
borderColor = this.engine.getCategoryColor(event.category);
|
|
707
928
|
}
|
|
@@ -736,56 +957,56 @@ var init_CalendarElement = __esm({
|
|
|
736
957
|
return `
|
|
737
958
|
<div class="event-card" style="border-left-color: ${borderColor}">
|
|
738
959
|
<div class="event-header">
|
|
739
|
-
<div class="event-title">${event.name}</div>
|
|
960
|
+
<div class="event-title">${escapeHtml(event.name)}</div>
|
|
740
961
|
<div class="event-badges">
|
|
741
|
-
${event.category ? `<span class="badge category-${event.category}">${getCategoryLabel(event.category)}</span>` : ""}
|
|
742
|
-
${event.priority ? `<span class="badge priority-${event.priority}">${getPriorityLabel(event.priority)}</span>` : ""}
|
|
743
|
-
${event.status && event.status !== "scheduled" ? `<span class="badge status-${event.status}">${getStatusLabel(event.status)}</span>` : ""}
|
|
962
|
+
${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
|
|
963
|
+
${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
|
|
964
|
+
${event.status && event.status !== "scheduled" ? `<span class="badge status status-${slugifyToken(event.status)}">${escapeHtml(getStatusLabel(event.status))}</span>` : ""}
|
|
744
965
|
</div>
|
|
745
966
|
</div>
|
|
746
967
|
|
|
747
968
|
${timeRange ? `
|
|
748
969
|
<div class="event-time">
|
|
749
970
|
<span class="event-time-label">Time:</span>
|
|
750
|
-
<span class="event-time-value">${timeRange}</span>
|
|
971
|
+
<span class="event-time-value">${escapeHtml(timeRange)}</span>
|
|
751
972
|
</div>
|
|
752
973
|
` : ""}
|
|
753
974
|
|
|
754
975
|
${event.description ? `
|
|
755
|
-
<div class="event-description">${event.description}</div>
|
|
976
|
+
<div class="event-description">${escapeHtml(event.description)}</div>
|
|
756
977
|
` : ""}
|
|
757
978
|
|
|
758
979
|
${event.location ? `
|
|
759
980
|
<div class="event-time">
|
|
760
981
|
<span class="event-time-label">Location:</span>
|
|
761
|
-
<span class="event-time-value">${event.location}</span>
|
|
982
|
+
<span class="event-time-value">${escapeHtml(event.location)}</span>
|
|
762
983
|
</div>
|
|
763
984
|
` : ""}
|
|
764
985
|
|
|
765
986
|
${attendeesList ? `
|
|
766
987
|
<div class="event-time">
|
|
767
988
|
<span class="event-time-label">Attendees:</span>
|
|
768
|
-
<span class="event-time-value">${attendeesList}</span>
|
|
989
|
+
<span class="event-time-value">${escapeHtml(attendeesList)}</span>
|
|
769
990
|
</div>
|
|
770
991
|
` : ""}
|
|
771
992
|
|
|
772
993
|
${event.organizer ? `
|
|
773
994
|
<div class="event-time">
|
|
774
995
|
<span class="event-time-label">Organizer:</span>
|
|
775
|
-
<span class="event-time-value">${event.organizer}</span>
|
|
996
|
+
<span class="event-time-value">${escapeHtml(event.organizer)}</span>
|
|
776
997
|
</div>
|
|
777
998
|
` : ""}
|
|
778
999
|
|
|
779
1000
|
${event.notes ? `
|
|
780
1001
|
<div class="event-time">
|
|
781
1002
|
<span class="event-time-label">Notes:</span>
|
|
782
|
-
<span class="event-time-value">${event.notes}</span>
|
|
1003
|
+
<span class="event-time-value">${escapeHtml(event.notes)}</span>
|
|
783
1004
|
</div>
|
|
784
1005
|
` : ""}
|
|
785
1006
|
|
|
786
1007
|
${event.url ? `
|
|
787
1008
|
<div class="event-time">
|
|
788
|
-
<a href="${event.url}" target="_blank" rel="noopener noreferrer" class="event-link">
|
|
1009
|
+
<a href="${escapeHtml(safeUrl(event.url))}" target="_blank" rel="noopener noreferrer" class="event-link">
|
|
789
1010
|
View Details \u2192
|
|
790
1011
|
</a>
|
|
791
1012
|
</div>
|
|
@@ -793,7 +1014,7 @@ var init_CalendarElement = __esm({
|
|
|
793
1014
|
|
|
794
1015
|
${event.tags && event.tags.length > 0 ? `
|
|
795
1016
|
<div class="event-tags">
|
|
796
|
-
${event.tags.map((tag) => `<span class="event-tag">${tag}</span>`).join("")}
|
|
1017
|
+
${event.tags.map((tag) => `<span class="event-tag">${escapeHtml(tag)}</span>`).join("")}
|
|
797
1018
|
</div>
|
|
798
1019
|
` : ""}
|
|
799
1020
|
</div>
|
|
@@ -802,97 +1023,181 @@ var init_CalendarElement = __esm({
|
|
|
802
1023
|
const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
|
|
803
1024
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
804
1025
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
805
|
-
const renderTimeGrid = (
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
const
|
|
1026
|
+
const renderTimeGrid = (date) => {
|
|
1027
|
+
const slotDuration = this.slotDuration;
|
|
1028
|
+
const previousDay = new Date(date);
|
|
1029
|
+
previousDay.setDate(previousDay.getDate() - 1);
|
|
1030
|
+
const events = [
|
|
1031
|
+
...this.engine.getEventsForDate(previousDay),
|
|
1032
|
+
...this.engine.getEventsForDate(date)
|
|
1033
|
+
];
|
|
1034
|
+
this.warnMissingEndTime(events);
|
|
1035
|
+
const booked = bookedSlots(events, date, slotDuration);
|
|
1036
|
+
const slots = booked.map((isBooked, index) => {
|
|
1037
|
+
const startTime = formatMinutes(index * slotDuration);
|
|
1038
|
+
const endTime = formatMinutes((index + 1) * slotDuration);
|
|
1039
|
+
const inTimeRange = !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
817
1040
|
const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
|
|
818
1041
|
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
819
1042
|
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
820
1043
|
const slotClasses = [
|
|
821
|
-
"time-
|
|
822
|
-
|
|
823
|
-
isRangeStart ? "time-
|
|
824
|
-
isRangeEnd ? "time-
|
|
825
|
-
isInRange ? "time-
|
|
1044
|
+
"time-grid-slot",
|
|
1045
|
+
isBooked ? "time-grid-slot-blocked" : "time-grid-slot-open",
|
|
1046
|
+
isRangeStart ? "time-grid-slot-range-start" : "",
|
|
1047
|
+
isRangeEnd ? "time-grid-slot-range-end" : "",
|
|
1048
|
+
isInRange ? "time-grid-slot-in-range" : ""
|
|
826
1049
|
].filter(Boolean).join(" ");
|
|
827
|
-
const slotAttrs =
|
|
1050
|
+
const slotAttrs = `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
|
|
828
1051
|
return `
|
|
829
1052
|
<div class="${slotClasses}" ${slotAttrs}>
|
|
830
|
-
<span class="time-
|
|
831
|
-
<span class="time-
|
|
1053
|
+
<span class="time-grid-label">${startTime}</span>
|
|
1054
|
+
<span class="time-grid-status">${isBooked ? "Booked" : "Available"}</span>
|
|
832
1055
|
</div>`;
|
|
833
1056
|
});
|
|
834
|
-
|
|
1057
|
+
const gridClasses = selectable ? "time-grid time-grid-selectable" : "time-grid";
|
|
1058
|
+
return `<div class="${gridClasses}">${slots.join("")}</div>`;
|
|
835
1059
|
};
|
|
1060
|
+
const multiMonth = viewModel.panes.length > 1;
|
|
1061
|
+
const renderPane = (pane) => `
|
|
1062
|
+
<div class="calendar-pane">
|
|
1063
|
+
${multiMonth ? `<div class="calendar-pane-caption">${escapeHtml(pane.monthAndYearText)}</div>` : ""}
|
|
1064
|
+
<table class="calendar-table calendar-table-bordered">
|
|
1065
|
+
<thead>
|
|
1066
|
+
<tr>
|
|
1067
|
+
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
1068
|
+
</tr>
|
|
1069
|
+
</thead>
|
|
1070
|
+
<tbody data-calendar-body>
|
|
1071
|
+
${isLoading ? Array.from(
|
|
1072
|
+
{ length: 6 },
|
|
1073
|
+
() => `<tr>${Array.from(
|
|
1074
|
+
{ length: 7 },
|
|
1075
|
+
() => `<td class="calendar-skeleton" aria-hidden="true"></td>`
|
|
1076
|
+
).join("")}</tr>`
|
|
1077
|
+
).join("") : pane.calendarDates.map(
|
|
1078
|
+
(week) => `
|
|
1079
|
+
<tr>
|
|
1080
|
+
${week.map((calendarDate, dayIndex) => {
|
|
1081
|
+
const classes = getCellClasses(calendarDate);
|
|
1082
|
+
const cellAttrs = [];
|
|
1083
|
+
if (viewModel.selectedDate && isSameDay2(
|
|
1084
|
+
calendarDate.date,
|
|
1085
|
+
viewModel.selectedDate
|
|
1086
|
+
)) {
|
|
1087
|
+
classes.push("calendar-cell-selected");
|
|
1088
|
+
}
|
|
1089
|
+
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
1090
|
+
const bucket = this.resolveBucket(
|
|
1091
|
+
calendarDate.date
|
|
1092
|
+
);
|
|
1093
|
+
const custom = (this._availabilityColors ?? {})[bucket];
|
|
1094
|
+
classes.push(
|
|
1095
|
+
`availability-${slugifyToken(bucket)}`
|
|
1096
|
+
);
|
|
1097
|
+
if (custom) {
|
|
1098
|
+
classes.push("availability-status");
|
|
1099
|
+
cellAttrs.push(
|
|
1100
|
+
`style="--availability-color: ${escapeHtml(safeColor(custom))}"`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
if (!this.isDateSelectable(calendarDate.date)) {
|
|
1104
|
+
classes.push("availability-unselectable");
|
|
1105
|
+
}
|
|
1106
|
+
cellAttrs.push(
|
|
1107
|
+
`aria-label="${escapeHtml(bucket)}"`
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
1111
|
+
const d = calendarDate.date;
|
|
1112
|
+
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
1113
|
+
classes.push("availability-range-start");
|
|
1114
|
+
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
1115
|
+
classes.push("availability-range-end");
|
|
1116
|
+
if (this._rangeStart && this._rangeEnd) {
|
|
1117
|
+
if (d > this._rangeStart && d < this._rangeEnd)
|
|
1118
|
+
classes.push("availability-in-range");
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
const dateString = calendarDate.date.toISOString();
|
|
1122
|
+
return `
|
|
1123
|
+
<td
|
|
1124
|
+
class="${classes.join(" ")}"
|
|
1125
|
+
data-date="${dateString}"
|
|
1126
|
+
data-day-index="${dayIndex}"
|
|
1127
|
+
data-clickable="true"
|
|
1128
|
+
${cellAttrs.join(" ")}
|
|
1129
|
+
>
|
|
1130
|
+
${calendarDate.date.getDate()}
|
|
1131
|
+
</td>
|
|
1132
|
+
`;
|
|
1133
|
+
}).join("")}
|
|
1134
|
+
</tr>
|
|
1135
|
+
`
|
|
1136
|
+
).join("")}
|
|
1137
|
+
</tbody>
|
|
1138
|
+
</table>
|
|
1139
|
+
</div>
|
|
1140
|
+
`;
|
|
836
1141
|
const html = `
|
|
837
1142
|
${title ? `
|
|
838
|
-
<div class="
|
|
839
|
-
<h1>${title}</h1>
|
|
1143
|
+
<div class="calendar-title">
|
|
1144
|
+
<h1>${escapeHtml(title)}</h1>
|
|
840
1145
|
</div>
|
|
841
1146
|
` : ""}
|
|
842
1147
|
|
|
843
|
-
<div class="calendar
|
|
844
|
-
<div class="calendar
|
|
845
|
-
<div class="calendar
|
|
846
|
-
<button type="button" class="calendar
|
|
1148
|
+
<div class="calendar-content">
|
|
1149
|
+
<div class="calendar-card">
|
|
1150
|
+
<div class="calendar-nav-header">
|
|
1151
|
+
<button type="button" class="calendar-nav-arrow" data-action="previous" aria-label="Previous month">
|
|
847
1152
|
‹
|
|
848
1153
|
</button>
|
|
849
1154
|
|
|
850
|
-
<div class="calendar
|
|
1155
|
+
<div class="calendar-picker-container" data-picker-container>
|
|
851
1156
|
<button
|
|
852
1157
|
type="button"
|
|
853
|
-
class="calendar
|
|
1158
|
+
class="calendar-picker-btn"
|
|
854
1159
|
data-action="toggle-picker"
|
|
855
1160
|
aria-expanded="${this.pickerOpen ? "true" : "false"}"
|
|
856
1161
|
aria-haspopup="true"
|
|
857
1162
|
>
|
|
858
1163
|
${useShortMonths ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
|
|
859
|
-
<span class="calendar
|
|
1164
|
+
<span class="calendar-picker-chevron">▾</span>
|
|
860
1165
|
</button>
|
|
861
1166
|
|
|
862
1167
|
${this.pickerOpen ? `
|
|
863
|
-
<div class="calendar
|
|
864
|
-
<div class="calendar
|
|
1168
|
+
<div class="calendar-picker-dropdown">
|
|
1169
|
+
<div class="calendar-picker-year-row">
|
|
865
1170
|
<button
|
|
866
1171
|
type="button"
|
|
867
|
-
class="calendar
|
|
1172
|
+
class="calendar-picker-year-arrow"
|
|
868
1173
|
data-action="year-prev"
|
|
869
1174
|
${viewModel.currentYear <= minYear ? "disabled" : ""}
|
|
870
1175
|
aria-label="Previous year"
|
|
871
1176
|
>‹</button>
|
|
872
1177
|
<input
|
|
873
1178
|
type="text"
|
|
874
|
-
class="calendar
|
|
875
|
-
value="${this.yearInput || viewModel.currentYear}"
|
|
1179
|
+
class="calendar-picker-year-input${!this.yearInputValid ? " invalid" : ""}"
|
|
1180
|
+
value="${escapeHtml(this.yearInput || viewModel.currentYear)}"
|
|
876
1181
|
data-year-input
|
|
877
1182
|
aria-label="Year"
|
|
878
1183
|
/>
|
|
879
1184
|
<button
|
|
880
1185
|
type="button"
|
|
881
|
-
class="calendar
|
|
1186
|
+
class="calendar-picker-year-arrow"
|
|
882
1187
|
data-action="year-next"
|
|
883
1188
|
${viewModel.currentYear >= maxYear ? "disabled" : ""}
|
|
884
1189
|
aria-label="Next year"
|
|
885
1190
|
>›</button>
|
|
886
1191
|
</div>
|
|
887
1192
|
|
|
888
|
-
<div class="calendar
|
|
1193
|
+
<div class="calendar-picker-months">
|
|
889
1194
|
${(useShortMonths ? MONTHS : MONTHS_FULL).map((month, index) => {
|
|
890
1195
|
const isSelected = index === viewModel.currentMonth;
|
|
891
1196
|
const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
|
|
892
1197
|
return `
|
|
893
1198
|
<button
|
|
894
1199
|
type="button"
|
|
895
|
-
class="calendar
|
|
1200
|
+
class="calendar-picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
|
|
896
1201
|
data-action="select-month"
|
|
897
1202
|
data-month="${index}"
|
|
898
1203
|
>${month}</button>
|
|
@@ -905,70 +1210,22 @@ var init_CalendarElement = __esm({
|
|
|
905
1210
|
|
|
906
1211
|
<button
|
|
907
1212
|
type="button"
|
|
908
|
-
class="calendar
|
|
1213
|
+
class="calendar-today-btn"
|
|
909
1214
|
data-action="today"
|
|
910
1215
|
${isCurrentMonth ? "disabled" : ""}
|
|
911
1216
|
>Today</button>
|
|
912
1217
|
|
|
913
|
-
<button type="button" class="calendar
|
|
1218
|
+
<button type="button" class="calendar-nav-arrow" data-action="next" aria-label="Next month">
|
|
914
1219
|
›
|
|
915
1220
|
</button>
|
|
916
1221
|
</div>
|
|
917
1222
|
|
|
918
|
-
<
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
922
|
-
</tr>
|
|
923
|
-
</thead>
|
|
924
|
-
<tbody data-calendar-body>
|
|
925
|
-
${isLoading ? Array.from(
|
|
926
|
-
{ length: 6 },
|
|
927
|
-
() => `<tr>${Array.from(
|
|
928
|
-
{ length: 7 },
|
|
929
|
-
() => `<td class="calendar--skeleton" aria-hidden="true"></td>`
|
|
930
|
-
).join("")}</tr>`
|
|
931
|
-
).join("") : viewModel.calendarDates.map(
|
|
932
|
-
(week) => `
|
|
933
|
-
<tr>
|
|
934
|
-
${week.map((calendarDate, dayIndex) => {
|
|
935
|
-
const classes = getCellClasses(calendarDate);
|
|
936
|
-
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
937
|
-
classes.push(
|
|
938
|
-
calendarDate.hasEvents ? "availability--booked" : "availability--free"
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
942
|
-
const d = calendarDate.date;
|
|
943
|
-
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
944
|
-
classes.push("availability--range-start");
|
|
945
|
-
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
946
|
-
classes.push("availability--range-end");
|
|
947
|
-
if (this._rangeStart && this._rangeEnd) {
|
|
948
|
-
if (d > this._rangeStart && d < this._rangeEnd)
|
|
949
|
-
classes.push("availability--in-range");
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
const dateString = calendarDate.date.toISOString();
|
|
953
|
-
return `
|
|
954
|
-
<td
|
|
955
|
-
class="${classes.join(" ")}"
|
|
956
|
-
data-date="${dateString}"
|
|
957
|
-
data-day-index="${dayIndex}"
|
|
958
|
-
data-clickable="true"
|
|
959
|
-
>
|
|
960
|
-
${calendarDate.date.getDate()}
|
|
961
|
-
</td>
|
|
962
|
-
`;
|
|
963
|
-
}).join("")}
|
|
964
|
-
</tr>
|
|
965
|
-
`
|
|
966
|
-
).join("")}
|
|
967
|
-
</tbody>
|
|
968
|
-
</table>
|
|
1223
|
+
<div class="calendar-panes">
|
|
1224
|
+
${viewModel.panes.map(renderPane).join("")}
|
|
1225
|
+
</div>
|
|
969
1226
|
|
|
970
1227
|
${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
|
|
971
|
-
<div class="date-popup
|
|
1228
|
+
<div class="date-popup">
|
|
972
1229
|
<div class="popup-header">
|
|
973
1230
|
<h2>${viewModel.scheduleDay}</h2>
|
|
974
1231
|
<button type="button" class="popup-close" data-action="close-popup" aria-label="Close">\u2715</button>
|
|
@@ -981,7 +1238,7 @@ var init_CalendarElement = __esm({
|
|
|
981
1238
|
` : ""}
|
|
982
1239
|
|
|
983
1240
|
<div class="events-container">
|
|
984
|
-
${availabilityMode === "time" ? renderTimeGrid(viewModel.
|
|
1241
|
+
${availabilityMode === "time" ? renderTimeGrid(viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
985
1242
|
</div>
|
|
986
1243
|
</div>
|
|
987
1244
|
` : ""}
|
|
@@ -1006,8 +1263,7 @@ var init_CalendarElement = __esm({
|
|
|
1006
1263
|
const availMode = this.getAttribute("availability-mode");
|
|
1007
1264
|
const isSelectable = this.hasAttribute("selectable");
|
|
1008
1265
|
if (availMode === "day" && isSelectable) {
|
|
1009
|
-
|
|
1010
|
-
if (!isBooked) {
|
|
1266
|
+
if (this.isDateSelectable(date)) {
|
|
1011
1267
|
let startDate;
|
|
1012
1268
|
let endDate;
|
|
1013
1269
|
if (this._rangeEnd !== null) {
|
|
@@ -1025,7 +1281,7 @@ var init_CalendarElement = __esm({
|
|
|
1025
1281
|
cursor.setDate(cursor.getDate() + 1);
|
|
1026
1282
|
let blocked = false;
|
|
1027
1283
|
while (cursor < e2) {
|
|
1028
|
-
if (this.
|
|
1284
|
+
if (!this.isDateSelectable(cursor)) {
|
|
1029
1285
|
blocked = true;
|
|
1030
1286
|
break;
|
|
1031
1287
|
}
|
|
@@ -1143,6 +1399,20 @@ var init_CalendarElement = __esm({
|
|
|
1143
1399
|
const slotStart = actionEl.dataset.startTime;
|
|
1144
1400
|
const slotEnd = actionEl.dataset.endTime;
|
|
1145
1401
|
const slotDate = new Date(actionEl.dataset.date);
|
|
1402
|
+
const slotBooked = actionEl.dataset.booked === "true";
|
|
1403
|
+
this.dispatchEvent(
|
|
1404
|
+
new CustomEvent("cal-slot-select", {
|
|
1405
|
+
bubbles: true,
|
|
1406
|
+
composed: true,
|
|
1407
|
+
detail: {
|
|
1408
|
+
date: slotDate,
|
|
1409
|
+
startTime: slotStart,
|
|
1410
|
+
endTime: slotEnd,
|
|
1411
|
+
booked: slotBooked
|
|
1412
|
+
}
|
|
1413
|
+
})
|
|
1414
|
+
);
|
|
1415
|
+
if (slotBooked || !this.hasAttribute("selectable")) break;
|
|
1146
1416
|
if (this._timeRangeComplete) {
|
|
1147
1417
|
this._timeRangeDate = slotDate;
|
|
1148
1418
|
this._timeRangeStart = slotStart;
|
|
@@ -1259,31 +1529,86 @@ var init_CalendarElement = __esm({
|
|
|
1259
1529
|
this.theme = theme;
|
|
1260
1530
|
}
|
|
1261
1531
|
getCurrentDate() {
|
|
1532
|
+
this.rethrowInitError();
|
|
1262
1533
|
return this.engine?.getViewModel().selectedDate ?? null;
|
|
1263
1534
|
}
|
|
1264
1535
|
goToDate(date) {
|
|
1536
|
+
this.rethrowInitError();
|
|
1265
1537
|
const year = date.getFullYear();
|
|
1266
1538
|
const month = date.getMonth();
|
|
1267
1539
|
this.dispatchMonthChange(year, month);
|
|
1268
1540
|
this.actions?.jump(year, month);
|
|
1269
1541
|
}
|
|
1270
1542
|
getEngine() {
|
|
1543
|
+
this.rethrowInitError();
|
|
1271
1544
|
if (!this.engine)
|
|
1272
1545
|
throw new Error("CalendarElement is not connected to the DOM");
|
|
1273
1546
|
return this.engine;
|
|
1274
1547
|
}
|
|
1275
1548
|
};
|
|
1276
|
-
|
|
1549
|
+
_CalendarElement.observedAttributes = [
|
|
1277
1550
|
"initial-date",
|
|
1278
1551
|
"min-year",
|
|
1279
1552
|
"max-year",
|
|
1280
1553
|
"week-starts-on",
|
|
1554
|
+
"heading",
|
|
1555
|
+
"months",
|
|
1556
|
+
"slot-duration",
|
|
1281
1557
|
"title",
|
|
1282
1558
|
"use-short-month-names",
|
|
1283
1559
|
"availability-mode",
|
|
1284
1560
|
"selectable",
|
|
1285
1561
|
"loading"
|
|
1286
1562
|
];
|
|
1563
|
+
_CalendarElement.themeMap = {
|
|
1564
|
+
primary: "--calendar-primary-color",
|
|
1565
|
+
secondary: "--calendar-secondary-color",
|
|
1566
|
+
tertiary: "--calendar-tertiary-color",
|
|
1567
|
+
textColor: "--calendar-text-color",
|
|
1568
|
+
textLight: "--calendar-text-light",
|
|
1569
|
+
background: "--calendar-background",
|
|
1570
|
+
cellHover: "--calendar-cell-hover",
|
|
1571
|
+
borderColor: "--calendar-border-color",
|
|
1572
|
+
todayOutline: "--calendar-today-outline",
|
|
1573
|
+
selectedBg: "--calendar-selected-bg",
|
|
1574
|
+
headerBg: "--calendar-header-bg",
|
|
1575
|
+
popupBg: "--calendar-popup-bg",
|
|
1576
|
+
pickerBg: "--calendar-picker-bg",
|
|
1577
|
+
pickerShadow: "--calendar-picker-shadow",
|
|
1578
|
+
eventIndicator: "--calendar-event-indicator",
|
|
1579
|
+
onAccent: "--calendar-on-accent",
|
|
1580
|
+
link: "--calendar-link",
|
|
1581
|
+
openBg: "--calendar-open-bg",
|
|
1582
|
+
openFg: "--calendar-open-fg",
|
|
1583
|
+
conditionalBg: "--calendar-conditional-bg",
|
|
1584
|
+
conditionalFg: "--calendar-conditional-fg",
|
|
1585
|
+
blockedBg: "--calendar-blocked-bg",
|
|
1586
|
+
blockedFg: "--calendar-blocked-fg",
|
|
1587
|
+
rangeBg: "--calendar-range-bg",
|
|
1588
|
+
rangeOutline: "--calendar-range-outline",
|
|
1589
|
+
inRangeBg: "--calendar-in-range-bg",
|
|
1590
|
+
inRangeOutline: "--calendar-in-range-outline",
|
|
1591
|
+
badgeBg: "--calendar-badge-bg",
|
|
1592
|
+
badgeText: "--calendar-badge-text",
|
|
1593
|
+
badgeSuccessBg: "--calendar-badge-success-bg",
|
|
1594
|
+
badgeSuccessText: "--calendar-badge-success-text",
|
|
1595
|
+
badgeInfoBg: "--calendar-badge-info-bg",
|
|
1596
|
+
badgeInfoText: "--calendar-badge-info-text",
|
|
1597
|
+
badgeWarningBg: "--calendar-badge-warning-bg",
|
|
1598
|
+
badgeWarningText: "--calendar-badge-warning-text",
|
|
1599
|
+
badgeDangerBg: "--calendar-badge-danger-bg",
|
|
1600
|
+
badgeDangerText: "--calendar-badge-danger-text",
|
|
1601
|
+
badgeNeutralBg: "--calendar-badge-neutral-bg",
|
|
1602
|
+
badgeNeutralText: "--calendar-badge-neutral-text",
|
|
1603
|
+
badgePositiveBg: "--calendar-badge-positive-bg",
|
|
1604
|
+
badgePositiveText: "--calendar-badge-positive-text",
|
|
1605
|
+
badgeTentativeBg: "--calendar-badge-tentative-bg",
|
|
1606
|
+
badgeTentativeText: "--calendar-badge-tentative-text"
|
|
1607
|
+
};
|
|
1608
|
+
_CalendarElement.warnedMissingEnd = /* @__PURE__ */ new Set();
|
|
1609
|
+
_CalendarElement.BUILT_IN_BUCKETS = ["blocked", "conditional", "open"];
|
|
1610
|
+
_CalendarElement.titleDeprecationWarned = false;
|
|
1611
|
+
CalendarElement = _CalendarElement;
|
|
1287
1612
|
}
|
|
1288
1613
|
});
|
|
1289
1614
|
|
|
@@ -1294,11 +1619,18 @@ __export(web_components_exports, {
|
|
|
1294
1619
|
CalendarEngine: () => CalendarEngine,
|
|
1295
1620
|
DAYS: () => DAYS,
|
|
1296
1621
|
DEFAULT_CATEGORY_COLORS: () => DEFAULT_CATEGORY_COLORS,
|
|
1622
|
+
DEFAULT_SLOT_DURATION: () => DEFAULT_SLOT_DURATION,
|
|
1623
|
+
MINUTES_PER_DAY: () => MINUTES_PER_DAY,
|
|
1297
1624
|
MONTHS: () => MONTHS,
|
|
1298
1625
|
MONTHS_FULL: () => MONTHS_FULL,
|
|
1626
|
+
bookedSlots: () => bookedSlots,
|
|
1299
1627
|
defineCalendarElement: () => defineCalendarElement,
|
|
1628
|
+
escapeHtml: () => escapeHtml,
|
|
1629
|
+
eventCoversDate: () => eventCoversDate,
|
|
1630
|
+
eventInterval: () => eventInterval,
|
|
1300
1631
|
formatAttendees: () => formatAttendees,
|
|
1301
1632
|
formatDateForDisplay: () => formatDateForDisplay,
|
|
1633
|
+
formatMinutes: () => formatMinutes,
|
|
1302
1634
|
formatTimeRange: () => formatTimeRange,
|
|
1303
1635
|
generateCalendarDates: () => generateCalendarDates,
|
|
1304
1636
|
generateYears: () => generateYears,
|
|
@@ -1307,13 +1639,17 @@ __export(web_components_exports, {
|
|
|
1307
1639
|
getDefaultEventColor: () => getDefaultEventColor,
|
|
1308
1640
|
getEventsForDate: () => getEventsForDate,
|
|
1309
1641
|
getMonthYearText: () => getMonthYearText,
|
|
1310
|
-
getPopupPositionClass: () => getPopupPositionClass,
|
|
1311
1642
|
hasEvents: () => hasEvents,
|
|
1312
1643
|
isSameDay: () => isSameDay,
|
|
1313
1644
|
isToday: () => isToday,
|
|
1314
1645
|
isValidHexColor: () => isValidHexColor,
|
|
1315
1646
|
mergeCategoryColors: () => mergeCategoryColors,
|
|
1647
|
+
mergeIntervals: () => mergeIntervals,
|
|
1316
1648
|
normalizeDate: () => normalizeDate,
|
|
1649
|
+
parseTimeToMinutes: () => parseTimeToMinutes,
|
|
1650
|
+
safeColor: () => safeColor,
|
|
1651
|
+
safeUrl: () => safeUrl,
|
|
1652
|
+
slugifyToken: () => slugifyToken,
|
|
1317
1653
|
sortEventsByTime: () => sortEventsByTime
|
|
1318
1654
|
});
|
|
1319
1655
|
module.exports = __toCommonJS(web_components_exports);
|
|
@@ -1332,11 +1668,18 @@ if (typeof customElements !== "undefined" && !customElements.get("kal-calendar")
|
|
|
1332
1668
|
CalendarEngine,
|
|
1333
1669
|
DAYS,
|
|
1334
1670
|
DEFAULT_CATEGORY_COLORS,
|
|
1671
|
+
DEFAULT_SLOT_DURATION,
|
|
1672
|
+
MINUTES_PER_DAY,
|
|
1335
1673
|
MONTHS,
|
|
1336
1674
|
MONTHS_FULL,
|
|
1675
|
+
bookedSlots,
|
|
1337
1676
|
defineCalendarElement,
|
|
1677
|
+
escapeHtml,
|
|
1678
|
+
eventCoversDate,
|
|
1679
|
+
eventInterval,
|
|
1338
1680
|
formatAttendees,
|
|
1339
1681
|
formatDateForDisplay,
|
|
1682
|
+
formatMinutes,
|
|
1340
1683
|
formatTimeRange,
|
|
1341
1684
|
generateCalendarDates,
|
|
1342
1685
|
generateYears,
|
|
@@ -1345,13 +1688,17 @@ if (typeof customElements !== "undefined" && !customElements.get("kal-calendar")
|
|
|
1345
1688
|
getDefaultEventColor,
|
|
1346
1689
|
getEventsForDate,
|
|
1347
1690
|
getMonthYearText,
|
|
1348
|
-
getPopupPositionClass,
|
|
1349
1691
|
hasEvents,
|
|
1350
1692
|
isSameDay,
|
|
1351
1693
|
isToday,
|
|
1352
1694
|
isValidHexColor,
|
|
1353
1695
|
mergeCategoryColors,
|
|
1696
|
+
mergeIntervals,
|
|
1354
1697
|
normalizeDate,
|
|
1698
|
+
parseTimeToMinutes,
|
|
1699
|
+
safeColor,
|
|
1700
|
+
safeUrl,
|
|
1701
|
+
slugifyToken,
|
|
1355
1702
|
sortEventsByTime
|
|
1356
1703
|
});
|
|
1357
1704
|
//# sourceMappingURL=index.js.map
|