kalendly 0.2.1 → 0.3.0
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 +263 -32
- package/dist/core/index.d.mts +50 -10
- package/dist/core/index.d.ts +50 -10
- package/dist/core/index.js +60 -26
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +56 -25
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +76 -10
- package/dist/index.d.ts +76 -10
- package/dist/index.js +373 -160
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +369 -159
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +363 -156
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +477 -415
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -95,26 +95,16 @@ function generateCalendarDates(year, month, events = [], weekStartsOn = 0) {
|
|
|
95
95
|
}
|
|
96
96
|
return dates;
|
|
97
97
|
}
|
|
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
98
|
function getCellClasses(calendarDate) {
|
|
109
99
|
const classes = [];
|
|
110
100
|
if (!calendarDate.isCurrentMonth) {
|
|
111
|
-
classes.push("other-month");
|
|
101
|
+
classes.push("calendar-cell-other-month");
|
|
112
102
|
}
|
|
113
103
|
if (calendarDate.isToday) {
|
|
114
|
-
classes.push("
|
|
104
|
+
classes.push("calendar-cell-today");
|
|
115
105
|
}
|
|
116
106
|
if (calendarDate.hasEvents) {
|
|
117
|
-
classes.push("has
|
|
107
|
+
classes.push("calendar-cell-has-event");
|
|
118
108
|
}
|
|
119
109
|
return classes;
|
|
120
110
|
}
|
|
@@ -173,7 +163,24 @@ function getCategoryColor(category, customColors) {
|
|
|
173
163
|
const color = colorMap[category] || colorMap.other || "#fc8917";
|
|
174
164
|
return isValidHexColor(color) ? color : "#fc8917";
|
|
175
165
|
}
|
|
176
|
-
|
|
166
|
+
function escapeHtml(value) {
|
|
167
|
+
if (value === null || value === void 0) return "";
|
|
168
|
+
return String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
|
|
169
|
+
}
|
|
170
|
+
function slugifyToken(value) {
|
|
171
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
172
|
+
}
|
|
173
|
+
function safeUrl(value) {
|
|
174
|
+
const normalized = String(value).split("").filter((char) => char.charCodeAt(0) > 32).join("");
|
|
175
|
+
if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;
|
|
176
|
+
const schemeless = !/^[^/?#]*:/.test(normalized);
|
|
177
|
+
return schemeless ? normalized : "#";
|
|
178
|
+
}
|
|
179
|
+
function safeColor(value) {
|
|
180
|
+
const trimmed = String(value).trim();
|
|
181
|
+
return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
|
|
182
|
+
}
|
|
183
|
+
var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS, HTML_ESCAPES;
|
|
177
184
|
var init_utils = __esm({
|
|
178
185
|
"src/core/utils.ts"() {
|
|
179
186
|
"use strict";
|
|
@@ -222,6 +229,13 @@ var init_utils = __esm({
|
|
|
222
229
|
appointment: "#f59e0b",
|
|
223
230
|
other: "#6b7280"
|
|
224
231
|
};
|
|
232
|
+
HTML_ESCAPES = {
|
|
233
|
+
"&": "&",
|
|
234
|
+
"<": "<",
|
|
235
|
+
">": ">",
|
|
236
|
+
'"': """,
|
|
237
|
+
"'": "'"
|
|
238
|
+
};
|
|
225
239
|
}
|
|
226
240
|
});
|
|
227
241
|
|
|
@@ -269,24 +283,38 @@ var init_calendar_engine = __esm({
|
|
|
269
283
|
* Get view model with computed properties
|
|
270
284
|
*/
|
|
271
285
|
getViewModel() {
|
|
272
|
-
const
|
|
273
|
-
this.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
286
|
+
const panes = Array.from(
|
|
287
|
+
{ length: Math.max(1, this.config.monthCount ?? 1) },
|
|
288
|
+
(_, offset) => {
|
|
289
|
+
const anchor = new Date(
|
|
290
|
+
this.state.currentYear,
|
|
291
|
+
this.state.currentMonth + offset,
|
|
292
|
+
1
|
|
293
|
+
);
|
|
294
|
+
const year = anchor.getFullYear();
|
|
295
|
+
const month = anchor.getMonth();
|
|
296
|
+
return {
|
|
297
|
+
year,
|
|
298
|
+
month,
|
|
299
|
+
monthAndYearText: getMonthYearText(year, month),
|
|
300
|
+
calendarDates: generateCalendarDates(
|
|
301
|
+
year,
|
|
302
|
+
month,
|
|
303
|
+
this.config.events,
|
|
304
|
+
this.config.weekStartsOn
|
|
305
|
+
)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
277
308
|
);
|
|
278
309
|
return {
|
|
279
310
|
...this.state,
|
|
280
311
|
months: MONTHS,
|
|
281
312
|
days: DAYS,
|
|
282
313
|
years: generateYears(this.config.minYear, this.config.maxYear),
|
|
283
|
-
monthAndYearText:
|
|
284
|
-
this.state.currentYear,
|
|
285
|
-
this.state.currentMonth
|
|
286
|
-
),
|
|
314
|
+
monthAndYearText: panes[0].monthAndYearText,
|
|
287
315
|
scheduleDay: this.state.selectedDate ? formatDateForDisplay(this.state.selectedDate) : "",
|
|
288
|
-
|
|
289
|
-
|
|
316
|
+
panes,
|
|
317
|
+
calendarDates: panes[0].calendarDates
|
|
290
318
|
};
|
|
291
319
|
}
|
|
292
320
|
/**
|
|
@@ -482,13 +510,13 @@ function defineCalendarElement(tagName = "kal-calendar") {
|
|
|
482
510
|
customElements.define(tagName, CalendarElement);
|
|
483
511
|
}
|
|
484
512
|
}
|
|
485
|
-
var isSameDay2, CalendarElement;
|
|
513
|
+
var isSameDay2, _CalendarElement, CalendarElement;
|
|
486
514
|
var init_CalendarElement = __esm({
|
|
487
515
|
"src/web-components/CalendarElement.ts"() {
|
|
488
516
|
"use strict";
|
|
489
517
|
init_core();
|
|
490
518
|
isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
491
|
-
|
|
519
|
+
_CalendarElement = class _CalendarElement extends HTMLElement {
|
|
492
520
|
constructor() {
|
|
493
521
|
super(...arguments);
|
|
494
522
|
this.engine = null;
|
|
@@ -509,6 +537,10 @@ var init_CalendarElement = __esm({
|
|
|
509
537
|
this._categoryColors = null;
|
|
510
538
|
this._renderEvent = null;
|
|
511
539
|
this._renderNoEvents = null;
|
|
540
|
+
this._availabilityColors = null;
|
|
541
|
+
this._selectableStatuses = null;
|
|
542
|
+
this._initError = null;
|
|
543
|
+
this.pendingUpdate = null;
|
|
512
544
|
// Selection state (availability mode — range)
|
|
513
545
|
this._rangeStart = null;
|
|
514
546
|
this._rangeEnd = null;
|
|
@@ -521,6 +553,7 @@ var init_CalendarElement = __esm({
|
|
|
521
553
|
return this._events;
|
|
522
554
|
}
|
|
523
555
|
set events(val) {
|
|
556
|
+
this._initError = null;
|
|
524
557
|
this._events = val;
|
|
525
558
|
if (this.engine) {
|
|
526
559
|
this.engine.updateEvents(val);
|
|
@@ -536,13 +569,28 @@ var init_CalendarElement = __esm({
|
|
|
536
569
|
this.engine.updateCategoryColors(val);
|
|
537
570
|
}
|
|
538
571
|
}
|
|
572
|
+
get availabilityColors() {
|
|
573
|
+
return this._availabilityColors ?? {};
|
|
574
|
+
}
|
|
575
|
+
set availabilityColors(val) {
|
|
576
|
+
this._initError = null;
|
|
577
|
+
this._availabilityColors = val;
|
|
578
|
+
if (this.engine) this.scheduleRender();
|
|
579
|
+
}
|
|
580
|
+
get selectableStatuses() {
|
|
581
|
+
return this._selectableStatuses ?? [];
|
|
582
|
+
}
|
|
583
|
+
set selectableStatuses(val) {
|
|
584
|
+
this._selectableStatuses = val;
|
|
585
|
+
if (this.engine) this.scheduleRender();
|
|
586
|
+
}
|
|
539
587
|
set renderEvent(val) {
|
|
540
588
|
this._renderEvent = val;
|
|
541
|
-
if (this.engine) this.
|
|
589
|
+
if (this.engine) this.scheduleRender();
|
|
542
590
|
}
|
|
543
591
|
set renderNoEvents(val) {
|
|
544
592
|
this._renderNoEvents = val;
|
|
545
|
-
if (this.engine) this.
|
|
593
|
+
if (this.engine) this.scheduleRender();
|
|
546
594
|
}
|
|
547
595
|
get loading() {
|
|
548
596
|
return this.hasAttribute("loading");
|
|
@@ -551,10 +599,22 @@ var init_CalendarElement = __esm({
|
|
|
551
599
|
if (val) this.setAttribute("loading", "");
|
|
552
600
|
else this.removeAttribute("loading");
|
|
553
601
|
}
|
|
602
|
+
// Custom element reactions report rather than propagate, so a failure here
|
|
603
|
+
// is kept and resurfaced at the next call the integrator makes
|
|
604
|
+
reaction(run) {
|
|
605
|
+
try {
|
|
606
|
+
run();
|
|
607
|
+
} catch (error) {
|
|
608
|
+
this._initError = error;
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
554
612
|
connectedCallback() {
|
|
555
613
|
this.classList.add("kalendly-calendar");
|
|
556
|
-
this.
|
|
557
|
-
|
|
614
|
+
this.reaction(() => {
|
|
615
|
+
this.initEngine();
|
|
616
|
+
this.render();
|
|
617
|
+
});
|
|
558
618
|
}
|
|
559
619
|
disconnectedCallback() {
|
|
560
620
|
this.cleanup();
|
|
@@ -564,7 +624,7 @@ var init_CalendarElement = __esm({
|
|
|
564
624
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
565
625
|
if (oldVal === newVal) return;
|
|
566
626
|
if (name === "loading") {
|
|
567
|
-
this.
|
|
627
|
+
this.scheduleRender();
|
|
568
628
|
return;
|
|
569
629
|
}
|
|
570
630
|
if (name === "selectable" && newVal === null) {
|
|
@@ -577,6 +637,23 @@ var init_CalendarElement = __esm({
|
|
|
577
637
|
}
|
|
578
638
|
this.reinit();
|
|
579
639
|
}
|
|
640
|
+
get headingText() {
|
|
641
|
+
const heading = this.getAttribute("heading");
|
|
642
|
+
if (heading !== null) return heading;
|
|
643
|
+
const title = this.getAttribute("title");
|
|
644
|
+
if (title !== null && !_CalendarElement.titleDeprecationWarned) {
|
|
645
|
+
_CalendarElement.titleDeprecationWarned = true;
|
|
646
|
+
console.warn(
|
|
647
|
+
`<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.`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
return title;
|
|
651
|
+
}
|
|
652
|
+
get monthCount() {
|
|
653
|
+
const requested = Number(this.getAttribute("months") ?? 1);
|
|
654
|
+
if (!Number.isInteger(requested) || requested < 1) return 1;
|
|
655
|
+
return Math.min(requested, 2);
|
|
656
|
+
}
|
|
580
657
|
get minYear() {
|
|
581
658
|
const val = this.getAttribute("min-year");
|
|
582
659
|
return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() - 30;
|
|
@@ -596,19 +673,20 @@ var init_CalendarElement = __esm({
|
|
|
596
673
|
minYear: this.minYear,
|
|
597
674
|
maxYear: this.maxYear,
|
|
598
675
|
weekStartsOn,
|
|
599
|
-
categoryColors: this._categoryColors ?? void 0
|
|
676
|
+
categoryColors: this._categoryColors ?? void 0,
|
|
677
|
+
monthCount: this.monthCount
|
|
600
678
|
});
|
|
601
679
|
this.actions = this.engine.getActions();
|
|
602
680
|
this.applyTheme();
|
|
603
681
|
this.unsubscribe = this.engine.subscribe(() => {
|
|
604
|
-
this.
|
|
682
|
+
this.scheduleRender();
|
|
605
683
|
});
|
|
606
684
|
}
|
|
607
685
|
reinit() {
|
|
608
686
|
if (!this.engine) return;
|
|
609
687
|
this.cleanup();
|
|
610
688
|
this.initEngine();
|
|
611
|
-
this.
|
|
689
|
+
this.scheduleRender();
|
|
612
690
|
}
|
|
613
691
|
cleanup() {
|
|
614
692
|
if (this.unsubscribe) {
|
|
@@ -645,40 +723,84 @@ var init_CalendarElement = __esm({
|
|
|
645
723
|
applyTheme() {
|
|
646
724
|
if (!this._theme) return;
|
|
647
725
|
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
|
-
|
|
726
|
+
for (const key of Object.keys(_CalendarElement.themeMap)) {
|
|
727
|
+
const value = this._theme[key];
|
|
728
|
+
if (value) root.style.setProperty(_CalendarElement.themeMap[key], value);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
get knownBuckets() {
|
|
732
|
+
return [
|
|
733
|
+
..._CalendarElement.BUILT_IN_BUCKETS,
|
|
734
|
+
...Object.keys(this._availabilityColors ?? {})
|
|
735
|
+
];
|
|
736
|
+
}
|
|
737
|
+
get updateComplete() {
|
|
738
|
+
return this.pendingUpdate ?? Promise.resolve();
|
|
739
|
+
}
|
|
740
|
+
scheduleRender() {
|
|
741
|
+
if (this.pendingUpdate) return;
|
|
742
|
+
this.pendingUpdate = Promise.resolve().then(() => {
|
|
743
|
+
this.pendingUpdate = null;
|
|
744
|
+
try {
|
|
745
|
+
this.render();
|
|
746
|
+
} catch (error) {
|
|
747
|
+
this._initError = error;
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
rethrowInitError() {
|
|
753
|
+
if (this._initError) throw this._initError;
|
|
754
|
+
}
|
|
755
|
+
assertStatusesDeclared() {
|
|
756
|
+
if (!this.getAttribute("availability-mode")) return;
|
|
757
|
+
const missing = this._events.filter(
|
|
758
|
+
(event) => typeof event.availabilityStatus !== "string" || event.availabilityStatus === ""
|
|
759
|
+
).map((event) => String(event.id));
|
|
760
|
+
if (missing.length) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
`<kal-calendar> availability-mode requires availabilityStatus on every event. Missing on: ${missing.join(", ")}.`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
assertStatusesKnown() {
|
|
767
|
+
const known = new Set(this.knownBuckets);
|
|
768
|
+
const unknown = this._events.filter((event) => !known.has(event.availabilityStatus)).map((event) => `${event.id} (${event.availabilityStatus})`);
|
|
769
|
+
if (unknown.length) {
|
|
770
|
+
throw new Error(
|
|
771
|
+
`<kal-calendar> availabilityStatus must name a built-in bucket (${_CalendarElement.BUILT_IN_BUCKETS.join(", ")}) or a key of availabilityColors. Unrecognised on: ${unknown.join(", ")}.`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
resolveBucket(date) {
|
|
776
|
+
if (!this.engine) return "open";
|
|
777
|
+
const declared = new Set(
|
|
778
|
+
this.engine.getEventsForDate(date).map((event) => event.availabilityStatus)
|
|
779
|
+
);
|
|
780
|
+
if (declared.size === 0) return "open";
|
|
781
|
+
return this.knownBuckets.find((bucket) => declared.has(bucket));
|
|
782
|
+
}
|
|
783
|
+
isDateSelectable(date) {
|
|
784
|
+
if (!this.engine) return false;
|
|
785
|
+
if (this._selectableStatuses) {
|
|
786
|
+
return this._selectableStatuses.includes(this.resolveBucket(date));
|
|
787
|
+
}
|
|
788
|
+
return this.engine.getEventsForDate(date).length === 0;
|
|
671
789
|
}
|
|
672
790
|
render() {
|
|
673
791
|
if (!this.engine) return;
|
|
674
792
|
const viewModel = this.engine.getViewModel();
|
|
675
793
|
const useShortMonths = this.hasAttribute("use-short-month-names");
|
|
676
|
-
const title = this.
|
|
794
|
+
const title = this.headingText;
|
|
677
795
|
const minYear = this.minYear;
|
|
678
796
|
const maxYear = this.maxYear;
|
|
679
797
|
const availabilityMode = this.getAttribute("availability-mode");
|
|
680
798
|
const selectable = this.hasAttribute("selectable");
|
|
681
799
|
const isLoading = this.loading;
|
|
800
|
+
if (availabilityMode) {
|
|
801
|
+
this.assertStatusesDeclared();
|
|
802
|
+
this.assertStatusesKnown();
|
|
803
|
+
}
|
|
682
804
|
const today = /* @__PURE__ */ new Date();
|
|
683
805
|
const todayMonth = today.getMonth();
|
|
684
806
|
const todayYear = today.getFullYear();
|
|
@@ -687,7 +809,7 @@ var init_CalendarElement = __esm({
|
|
|
687
809
|
const defaultRenderEvent = (event) => {
|
|
688
810
|
const timeRange = formatTimeRange(event);
|
|
689
811
|
const attendeesList = formatAttendees(event.attendees);
|
|
690
|
-
let borderColor = event.color || "#3b82f6";
|
|
812
|
+
let borderColor = safeColor(event.color || "#3b82f6");
|
|
691
813
|
if (event.category) {
|
|
692
814
|
borderColor = this.engine.getCategoryColor(event.category);
|
|
693
815
|
}
|
|
@@ -722,56 +844,56 @@ var init_CalendarElement = __esm({
|
|
|
722
844
|
return `
|
|
723
845
|
<div class="event-card" style="border-left-color: ${borderColor}">
|
|
724
846
|
<div class="event-header">
|
|
725
|
-
<div class="event-title">${event.name}</div>
|
|
847
|
+
<div class="event-title">${escapeHtml(event.name)}</div>
|
|
726
848
|
<div class="event-badges">
|
|
727
|
-
${event.category ? `<span class="badge category-${event.category}">${getCategoryLabel(event.category)}</span>` : ""}
|
|
728
|
-
${event.priority ? `<span class="badge priority-${event.priority}">${getPriorityLabel(event.priority)}</span>` : ""}
|
|
729
|
-
${event.status && event.status !== "scheduled" ? `<span class="badge status-${event.status}">${getStatusLabel(event.status)}</span>` : ""}
|
|
849
|
+
${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
|
|
850
|
+
${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
|
|
851
|
+
${event.status && event.status !== "scheduled" ? `<span class="badge status status-${slugifyToken(event.status)}">${escapeHtml(getStatusLabel(event.status))}</span>` : ""}
|
|
730
852
|
</div>
|
|
731
853
|
</div>
|
|
732
854
|
|
|
733
855
|
${timeRange ? `
|
|
734
856
|
<div class="event-time">
|
|
735
857
|
<span class="event-time-label">Time:</span>
|
|
736
|
-
<span class="event-time-value">${timeRange}</span>
|
|
858
|
+
<span class="event-time-value">${escapeHtml(timeRange)}</span>
|
|
737
859
|
</div>
|
|
738
860
|
` : ""}
|
|
739
861
|
|
|
740
862
|
${event.description ? `
|
|
741
|
-
<div class="event-description">${event.description}</div>
|
|
863
|
+
<div class="event-description">${escapeHtml(event.description)}</div>
|
|
742
864
|
` : ""}
|
|
743
865
|
|
|
744
866
|
${event.location ? `
|
|
745
867
|
<div class="event-time">
|
|
746
868
|
<span class="event-time-label">Location:</span>
|
|
747
|
-
<span class="event-time-value">${event.location}</span>
|
|
869
|
+
<span class="event-time-value">${escapeHtml(event.location)}</span>
|
|
748
870
|
</div>
|
|
749
871
|
` : ""}
|
|
750
872
|
|
|
751
873
|
${attendeesList ? `
|
|
752
874
|
<div class="event-time">
|
|
753
875
|
<span class="event-time-label">Attendees:</span>
|
|
754
|
-
<span class="event-time-value">${attendeesList}</span>
|
|
876
|
+
<span class="event-time-value">${escapeHtml(attendeesList)}</span>
|
|
755
877
|
</div>
|
|
756
878
|
` : ""}
|
|
757
879
|
|
|
758
880
|
${event.organizer ? `
|
|
759
881
|
<div class="event-time">
|
|
760
882
|
<span class="event-time-label">Organizer:</span>
|
|
761
|
-
<span class="event-time-value">${event.organizer}</span>
|
|
883
|
+
<span class="event-time-value">${escapeHtml(event.organizer)}</span>
|
|
762
884
|
</div>
|
|
763
885
|
` : ""}
|
|
764
886
|
|
|
765
887
|
${event.notes ? `
|
|
766
888
|
<div class="event-time">
|
|
767
889
|
<span class="event-time-label">Notes:</span>
|
|
768
|
-
<span class="event-time-value">${event.notes}</span>
|
|
890
|
+
<span class="event-time-value">${escapeHtml(event.notes)}</span>
|
|
769
891
|
</div>
|
|
770
892
|
` : ""}
|
|
771
893
|
|
|
772
894
|
${event.url ? `
|
|
773
895
|
<div class="event-time">
|
|
774
|
-
<a href="${event.url}" target="_blank" rel="noopener noreferrer" class="event-link">
|
|
896
|
+
<a href="${escapeHtml(safeUrl(event.url))}" target="_blank" rel="noopener noreferrer" class="event-link">
|
|
775
897
|
View Details \u2192
|
|
776
898
|
</a>
|
|
777
899
|
</div>
|
|
@@ -779,7 +901,7 @@ var init_CalendarElement = __esm({
|
|
|
779
901
|
|
|
780
902
|
${event.tags && event.tags.length > 0 ? `
|
|
781
903
|
<div class="event-tags">
|
|
782
|
-
${event.tags.map((tag) => `<span class="event-tag">${tag}</span>`).join("")}
|
|
904
|
+
${event.tags.map((tag) => `<span class="event-tag">${escapeHtml(tag)}</span>`).join("")}
|
|
783
905
|
</div>
|
|
784
906
|
` : ""}
|
|
785
907
|
</div>
|
|
@@ -789,11 +911,17 @@ var init_CalendarElement = __esm({
|
|
|
789
911
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
790
912
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
791
913
|
const renderTimeGrid = (events, date) => {
|
|
914
|
+
const startHour = (time) => {
|
|
915
|
+
if (typeof time !== "string") return null;
|
|
916
|
+
const hour = Number(time.split(":")[0]);
|
|
917
|
+
return Number.isInteger(hour) && hour >= 0 && hour <= 24 ? hour : null;
|
|
918
|
+
};
|
|
792
919
|
const isHourBooked = (hour) => events.some((event) => {
|
|
793
|
-
if (!event.startTime
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
|
|
920
|
+
if (!event.startTime) return true;
|
|
921
|
+
const from = startHour(event.startTime);
|
|
922
|
+
const to = event.endTime ? startHour(event.endTime) : 24;
|
|
923
|
+
if (from === null || to === null) return true;
|
|
924
|
+
return hour >= from && hour < to;
|
|
797
925
|
});
|
|
798
926
|
const slots = Array.from({ length: 24 }, (_, hour) => {
|
|
799
927
|
const startTime = `${String(hour).padStart(2, "0")}:00`;
|
|
@@ -804,81 +932,156 @@ var init_CalendarElement = __esm({
|
|
|
804
932
|
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
805
933
|
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
806
934
|
const slotClasses = [
|
|
807
|
-
"time-
|
|
808
|
-
booked ? "time-
|
|
809
|
-
isRangeStart ? "time-
|
|
810
|
-
isRangeEnd ? "time-
|
|
811
|
-
isInRange ? "time-
|
|
935
|
+
"time-grid-slot",
|
|
936
|
+
booked ? "time-grid-slot-blocked" : "time-grid-slot-open",
|
|
937
|
+
isRangeStart ? "time-grid-slot-range-start" : "",
|
|
938
|
+
isRangeEnd ? "time-grid-slot-range-end" : "",
|
|
939
|
+
isInRange ? "time-grid-slot-in-range" : ""
|
|
812
940
|
].filter(Boolean).join(" ");
|
|
813
941
|
const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
|
|
814
942
|
return `
|
|
815
943
|
<div class="${slotClasses}" ${slotAttrs}>
|
|
816
|
-
<span class="time-
|
|
817
|
-
<span class="time-
|
|
944
|
+
<span class="time-grid-label">${startTime}</span>
|
|
945
|
+
<span class="time-grid-status">${booked ? "Booked" : "Available"}</span>
|
|
818
946
|
</div>`;
|
|
819
947
|
});
|
|
820
948
|
return `<div class="time-grid">${slots.join("")}</div>`;
|
|
821
949
|
};
|
|
950
|
+
const multiMonth = viewModel.panes.length > 1;
|
|
951
|
+
const renderPane = (pane) => `
|
|
952
|
+
<div class="calendar-pane">
|
|
953
|
+
${multiMonth ? `<div class="calendar-pane-caption">${escapeHtml(pane.monthAndYearText)}</div>` : ""}
|
|
954
|
+
<table class="calendar-table calendar-table-bordered">
|
|
955
|
+
<thead>
|
|
956
|
+
<tr>
|
|
957
|
+
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
958
|
+
</tr>
|
|
959
|
+
</thead>
|
|
960
|
+
<tbody data-calendar-body>
|
|
961
|
+
${isLoading ? Array.from(
|
|
962
|
+
{ length: 6 },
|
|
963
|
+
() => `<tr>${Array.from(
|
|
964
|
+
{ length: 7 },
|
|
965
|
+
() => `<td class="calendar-skeleton" aria-hidden="true"></td>`
|
|
966
|
+
).join("")}</tr>`
|
|
967
|
+
).join("") : pane.calendarDates.map(
|
|
968
|
+
(week) => `
|
|
969
|
+
<tr>
|
|
970
|
+
${week.map((calendarDate, dayIndex) => {
|
|
971
|
+
const classes = getCellClasses(calendarDate);
|
|
972
|
+
const cellAttrs = [];
|
|
973
|
+
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
974
|
+
const bucket = this.resolveBucket(
|
|
975
|
+
calendarDate.date
|
|
976
|
+
);
|
|
977
|
+
const custom = (this._availabilityColors ?? {})[bucket];
|
|
978
|
+
classes.push(
|
|
979
|
+
`availability-${slugifyToken(bucket)}`
|
|
980
|
+
);
|
|
981
|
+
if (custom) {
|
|
982
|
+
classes.push("availability-status");
|
|
983
|
+
cellAttrs.push(
|
|
984
|
+
`style="--availability-color: ${escapeHtml(safeColor(custom))}"`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
if (!this.isDateSelectable(calendarDate.date)) {
|
|
988
|
+
classes.push("availability-unselectable");
|
|
989
|
+
}
|
|
990
|
+
cellAttrs.push(
|
|
991
|
+
`aria-label="${escapeHtml(bucket)}"`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
995
|
+
const d = calendarDate.date;
|
|
996
|
+
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
997
|
+
classes.push("availability-range-start");
|
|
998
|
+
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
999
|
+
classes.push("availability-range-end");
|
|
1000
|
+
if (this._rangeStart && this._rangeEnd) {
|
|
1001
|
+
if (d > this._rangeStart && d < this._rangeEnd)
|
|
1002
|
+
classes.push("availability-in-range");
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const dateString = calendarDate.date.toISOString();
|
|
1006
|
+
return `
|
|
1007
|
+
<td
|
|
1008
|
+
class="${classes.join(" ")}"
|
|
1009
|
+
data-date="${dateString}"
|
|
1010
|
+
data-day-index="${dayIndex}"
|
|
1011
|
+
data-clickable="true"
|
|
1012
|
+
${cellAttrs.join(" ")}
|
|
1013
|
+
>
|
|
1014
|
+
${calendarDate.date.getDate()}
|
|
1015
|
+
</td>
|
|
1016
|
+
`;
|
|
1017
|
+
}).join("")}
|
|
1018
|
+
</tr>
|
|
1019
|
+
`
|
|
1020
|
+
).join("")}
|
|
1021
|
+
</tbody>
|
|
1022
|
+
</table>
|
|
1023
|
+
</div>
|
|
1024
|
+
`;
|
|
822
1025
|
const html = `
|
|
823
1026
|
${title ? `
|
|
824
|
-
<div class="
|
|
825
|
-
<h1>${title}</h1>
|
|
1027
|
+
<div class="calendar-title">
|
|
1028
|
+
<h1>${escapeHtml(title)}</h1>
|
|
826
1029
|
</div>
|
|
827
1030
|
` : ""}
|
|
828
1031
|
|
|
829
|
-
<div class="calendar
|
|
830
|
-
<div class="calendar
|
|
831
|
-
<div class="calendar
|
|
832
|
-
<button type="button" class="calendar
|
|
1032
|
+
<div class="calendar-content">
|
|
1033
|
+
<div class="calendar-card">
|
|
1034
|
+
<div class="calendar-nav-header">
|
|
1035
|
+
<button type="button" class="calendar-nav-arrow" data-action="previous" aria-label="Previous month">
|
|
833
1036
|
‹
|
|
834
1037
|
</button>
|
|
835
1038
|
|
|
836
|
-
<div class="calendar
|
|
1039
|
+
<div class="calendar-picker-container" data-picker-container>
|
|
837
1040
|
<button
|
|
838
1041
|
type="button"
|
|
839
|
-
class="calendar
|
|
1042
|
+
class="calendar-picker-btn"
|
|
840
1043
|
data-action="toggle-picker"
|
|
841
1044
|
aria-expanded="${this.pickerOpen ? "true" : "false"}"
|
|
842
1045
|
aria-haspopup="true"
|
|
843
1046
|
>
|
|
844
1047
|
${useShortMonths ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
|
|
845
|
-
<span class="calendar
|
|
1048
|
+
<span class="calendar-picker-chevron">▾</span>
|
|
846
1049
|
</button>
|
|
847
1050
|
|
|
848
1051
|
${this.pickerOpen ? `
|
|
849
|
-
<div class="calendar
|
|
850
|
-
<div class="calendar
|
|
1052
|
+
<div class="calendar-picker-dropdown">
|
|
1053
|
+
<div class="calendar-picker-year-row">
|
|
851
1054
|
<button
|
|
852
1055
|
type="button"
|
|
853
|
-
class="calendar
|
|
1056
|
+
class="calendar-picker-year-arrow"
|
|
854
1057
|
data-action="year-prev"
|
|
855
1058
|
${viewModel.currentYear <= minYear ? "disabled" : ""}
|
|
856
1059
|
aria-label="Previous year"
|
|
857
1060
|
>‹</button>
|
|
858
1061
|
<input
|
|
859
1062
|
type="text"
|
|
860
|
-
class="calendar
|
|
861
|
-
value="${this.yearInput || viewModel.currentYear}"
|
|
1063
|
+
class="calendar-picker-year-input${!this.yearInputValid ? " invalid" : ""}"
|
|
1064
|
+
value="${escapeHtml(this.yearInput || viewModel.currentYear)}"
|
|
862
1065
|
data-year-input
|
|
863
1066
|
aria-label="Year"
|
|
864
1067
|
/>
|
|
865
1068
|
<button
|
|
866
1069
|
type="button"
|
|
867
|
-
class="calendar
|
|
1070
|
+
class="calendar-picker-year-arrow"
|
|
868
1071
|
data-action="year-next"
|
|
869
1072
|
${viewModel.currentYear >= maxYear ? "disabled" : ""}
|
|
870
1073
|
aria-label="Next year"
|
|
871
1074
|
>›</button>
|
|
872
1075
|
</div>
|
|
873
1076
|
|
|
874
|
-
<div class="calendar
|
|
1077
|
+
<div class="calendar-picker-months">
|
|
875
1078
|
${(useShortMonths ? MONTHS : MONTHS_FULL).map((month, index) => {
|
|
876
1079
|
const isSelected = index === viewModel.currentMonth;
|
|
877
1080
|
const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
|
|
878
1081
|
return `
|
|
879
1082
|
<button
|
|
880
1083
|
type="button"
|
|
881
|
-
class="calendar
|
|
1084
|
+
class="calendar-picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
|
|
882
1085
|
data-action="select-month"
|
|
883
1086
|
data-month="${index}"
|
|
884
1087
|
>${month}</button>
|
|
@@ -891,70 +1094,22 @@ var init_CalendarElement = __esm({
|
|
|
891
1094
|
|
|
892
1095
|
<button
|
|
893
1096
|
type="button"
|
|
894
|
-
class="calendar
|
|
1097
|
+
class="calendar-today-btn"
|
|
895
1098
|
data-action="today"
|
|
896
1099
|
${isCurrentMonth ? "disabled" : ""}
|
|
897
1100
|
>Today</button>
|
|
898
1101
|
|
|
899
|
-
<button type="button" class="calendar
|
|
1102
|
+
<button type="button" class="calendar-nav-arrow" data-action="next" aria-label="Next month">
|
|
900
1103
|
›
|
|
901
1104
|
</button>
|
|
902
1105
|
</div>
|
|
903
1106
|
|
|
904
|
-
<
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
908
|
-
</tr>
|
|
909
|
-
</thead>
|
|
910
|
-
<tbody data-calendar-body>
|
|
911
|
-
${isLoading ? Array.from(
|
|
912
|
-
{ length: 6 },
|
|
913
|
-
() => `<tr>${Array.from(
|
|
914
|
-
{ length: 7 },
|
|
915
|
-
() => `<td class="calendar--skeleton" aria-hidden="true"></td>`
|
|
916
|
-
).join("")}</tr>`
|
|
917
|
-
).join("") : viewModel.calendarDates.map(
|
|
918
|
-
(week) => `
|
|
919
|
-
<tr>
|
|
920
|
-
${week.map((calendarDate, dayIndex) => {
|
|
921
|
-
const classes = getCellClasses(calendarDate);
|
|
922
|
-
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
923
|
-
classes.push(
|
|
924
|
-
calendarDate.hasEvents ? "availability--booked" : "availability--free"
|
|
925
|
-
);
|
|
926
|
-
}
|
|
927
|
-
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
928
|
-
const d = calendarDate.date;
|
|
929
|
-
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
930
|
-
classes.push("availability--range-start");
|
|
931
|
-
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
932
|
-
classes.push("availability--range-end");
|
|
933
|
-
if (this._rangeStart && this._rangeEnd) {
|
|
934
|
-
if (d > this._rangeStart && d < this._rangeEnd)
|
|
935
|
-
classes.push("availability--in-range");
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
const dateString = calendarDate.date.toISOString();
|
|
939
|
-
return `
|
|
940
|
-
<td
|
|
941
|
-
class="${classes.join(" ")}"
|
|
942
|
-
data-date="${dateString}"
|
|
943
|
-
data-day-index="${dayIndex}"
|
|
944
|
-
data-clickable="true"
|
|
945
|
-
>
|
|
946
|
-
${calendarDate.date.getDate()}
|
|
947
|
-
</td>
|
|
948
|
-
`;
|
|
949
|
-
}).join("")}
|
|
950
|
-
</tr>
|
|
951
|
-
`
|
|
952
|
-
).join("")}
|
|
953
|
-
</tbody>
|
|
954
|
-
</table>
|
|
1107
|
+
<div class="calendar-panes">
|
|
1108
|
+
${viewModel.panes.map(renderPane).join("")}
|
|
1109
|
+
</div>
|
|
955
1110
|
|
|
956
1111
|
${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
|
|
957
|
-
<div class="date-popup
|
|
1112
|
+
<div class="date-popup">
|
|
958
1113
|
<div class="popup-header">
|
|
959
1114
|
<h2>${viewModel.scheduleDay}</h2>
|
|
960
1115
|
<button type="button" class="popup-close" data-action="close-popup" aria-label="Close">\u2715</button>
|
|
@@ -992,8 +1147,7 @@ var init_CalendarElement = __esm({
|
|
|
992
1147
|
const availMode = this.getAttribute("availability-mode");
|
|
993
1148
|
const isSelectable = this.hasAttribute("selectable");
|
|
994
1149
|
if (availMode === "day" && isSelectable) {
|
|
995
|
-
|
|
996
|
-
if (!isBooked) {
|
|
1150
|
+
if (this.isDateSelectable(date)) {
|
|
997
1151
|
let startDate;
|
|
998
1152
|
let endDate;
|
|
999
1153
|
if (this._rangeEnd !== null) {
|
|
@@ -1011,7 +1165,7 @@ var init_CalendarElement = __esm({
|
|
|
1011
1165
|
cursor.setDate(cursor.getDate() + 1);
|
|
1012
1166
|
let blocked = false;
|
|
1013
1167
|
while (cursor < e2) {
|
|
1014
|
-
if (this.
|
|
1168
|
+
if (!this.isDateSelectable(cursor)) {
|
|
1015
1169
|
blocked = true;
|
|
1016
1170
|
break;
|
|
1017
1171
|
}
|
|
@@ -1245,31 +1399,84 @@ var init_CalendarElement = __esm({
|
|
|
1245
1399
|
this.theme = theme;
|
|
1246
1400
|
}
|
|
1247
1401
|
getCurrentDate() {
|
|
1402
|
+
this.rethrowInitError();
|
|
1248
1403
|
return this.engine?.getViewModel().selectedDate ?? null;
|
|
1249
1404
|
}
|
|
1250
1405
|
goToDate(date) {
|
|
1406
|
+
this.rethrowInitError();
|
|
1251
1407
|
const year = date.getFullYear();
|
|
1252
1408
|
const month = date.getMonth();
|
|
1253
1409
|
this.dispatchMonthChange(year, month);
|
|
1254
1410
|
this.actions?.jump(year, month);
|
|
1255
1411
|
}
|
|
1256
1412
|
getEngine() {
|
|
1413
|
+
this.rethrowInitError();
|
|
1257
1414
|
if (!this.engine)
|
|
1258
1415
|
throw new Error("CalendarElement is not connected to the DOM");
|
|
1259
1416
|
return this.engine;
|
|
1260
1417
|
}
|
|
1261
1418
|
};
|
|
1262
|
-
|
|
1419
|
+
_CalendarElement.observedAttributes = [
|
|
1263
1420
|
"initial-date",
|
|
1264
1421
|
"min-year",
|
|
1265
1422
|
"max-year",
|
|
1266
1423
|
"week-starts-on",
|
|
1424
|
+
"heading",
|
|
1425
|
+
"months",
|
|
1267
1426
|
"title",
|
|
1268
1427
|
"use-short-month-names",
|
|
1269
1428
|
"availability-mode",
|
|
1270
1429
|
"selectable",
|
|
1271
1430
|
"loading"
|
|
1272
1431
|
];
|
|
1432
|
+
_CalendarElement.themeMap = {
|
|
1433
|
+
primary: "--calendar-primary-color",
|
|
1434
|
+
secondary: "--calendar-secondary-color",
|
|
1435
|
+
tertiary: "--calendar-tertiary-color",
|
|
1436
|
+
textColor: "--calendar-text-color",
|
|
1437
|
+
textLight: "--calendar-text-light",
|
|
1438
|
+
background: "--calendar-background",
|
|
1439
|
+
cellHover: "--calendar-cell-hover",
|
|
1440
|
+
borderColor: "--calendar-border-color",
|
|
1441
|
+
todayOutline: "--calendar-today-outline",
|
|
1442
|
+
selectedBg: "--calendar-selected-bg",
|
|
1443
|
+
headerBg: "--calendar-header-bg",
|
|
1444
|
+
popupBg: "--calendar-popup-bg",
|
|
1445
|
+
pickerBg: "--calendar-picker-bg",
|
|
1446
|
+
pickerShadow: "--calendar-picker-shadow",
|
|
1447
|
+
eventIndicator: "--calendar-event-indicator",
|
|
1448
|
+
onAccent: "--calendar-on-accent",
|
|
1449
|
+
link: "--calendar-link",
|
|
1450
|
+
openBg: "--calendar-open-bg",
|
|
1451
|
+
openFg: "--calendar-open-fg",
|
|
1452
|
+
conditionalBg: "--calendar-conditional-bg",
|
|
1453
|
+
conditionalFg: "--calendar-conditional-fg",
|
|
1454
|
+
blockedBg: "--calendar-blocked-bg",
|
|
1455
|
+
blockedFg: "--calendar-blocked-fg",
|
|
1456
|
+
rangeBg: "--calendar-range-bg",
|
|
1457
|
+
rangeOutline: "--calendar-range-outline",
|
|
1458
|
+
inRangeBg: "--calendar-in-range-bg",
|
|
1459
|
+
inRangeOutline: "--calendar-in-range-outline",
|
|
1460
|
+
badgeBg: "--calendar-badge-bg",
|
|
1461
|
+
badgeText: "--calendar-badge-text",
|
|
1462
|
+
badgeSuccessBg: "--calendar-badge-success-bg",
|
|
1463
|
+
badgeSuccessText: "--calendar-badge-success-text",
|
|
1464
|
+
badgeInfoBg: "--calendar-badge-info-bg",
|
|
1465
|
+
badgeInfoText: "--calendar-badge-info-text",
|
|
1466
|
+
badgeWarningBg: "--calendar-badge-warning-bg",
|
|
1467
|
+
badgeWarningText: "--calendar-badge-warning-text",
|
|
1468
|
+
badgeDangerBg: "--calendar-badge-danger-bg",
|
|
1469
|
+
badgeDangerText: "--calendar-badge-danger-text",
|
|
1470
|
+
badgeNeutralBg: "--calendar-badge-neutral-bg",
|
|
1471
|
+
badgeNeutralText: "--calendar-badge-neutral-text",
|
|
1472
|
+
badgePositiveBg: "--calendar-badge-positive-bg",
|
|
1473
|
+
badgePositiveText: "--calendar-badge-positive-text",
|
|
1474
|
+
badgeTentativeBg: "--calendar-badge-tentative-bg",
|
|
1475
|
+
badgeTentativeText: "--calendar-badge-tentative-text"
|
|
1476
|
+
};
|
|
1477
|
+
_CalendarElement.BUILT_IN_BUCKETS = ["blocked", "conditional", "open"];
|
|
1478
|
+
_CalendarElement.titleDeprecationWarned = false;
|
|
1479
|
+
CalendarElement = _CalendarElement;
|
|
1273
1480
|
}
|
|
1274
1481
|
});
|
|
1275
1482
|
|
|
@@ -1291,6 +1498,7 @@ export {
|
|
|
1291
1498
|
MONTHS,
|
|
1292
1499
|
MONTHS_FULL,
|
|
1293
1500
|
defineCalendarElement,
|
|
1501
|
+
escapeHtml,
|
|
1294
1502
|
formatAttendees,
|
|
1295
1503
|
formatDateForDisplay,
|
|
1296
1504
|
formatTimeRange,
|
|
@@ -1301,13 +1509,15 @@ export {
|
|
|
1301
1509
|
getDefaultEventColor,
|
|
1302
1510
|
getEventsForDate,
|
|
1303
1511
|
getMonthYearText,
|
|
1304
|
-
getPopupPositionClass,
|
|
1305
1512
|
hasEvents,
|
|
1306
1513
|
isSameDay,
|
|
1307
1514
|
isToday,
|
|
1308
1515
|
isValidHexColor,
|
|
1309
1516
|
mergeCategoryColors,
|
|
1310
1517
|
normalizeDate,
|
|
1518
|
+
safeColor,
|
|
1519
|
+
safeUrl,
|
|
1520
|
+
slugifyToken,
|
|
1311
1521
|
sortEventsByTime
|
|
1312
1522
|
};
|
|
1313
1523
|
//# sourceMappingURL=index.mjs.map
|