kalendly 0.2.0 → 0.2.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 +133 -54
- package/dist/index.d.mts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +227 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +227 -33
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +226 -32
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +96 -0
- package/package.json +20 -34
package/dist/index.mjs
CHANGED
|
@@ -482,11 +482,12 @@ function defineCalendarElement(tagName = "kal-calendar") {
|
|
|
482
482
|
customElements.define(tagName, CalendarElement);
|
|
483
483
|
}
|
|
484
484
|
}
|
|
485
|
-
var CalendarElement;
|
|
485
|
+
var isSameDay2, CalendarElement;
|
|
486
486
|
var init_CalendarElement = __esm({
|
|
487
487
|
"src/web-components/CalendarElement.ts"() {
|
|
488
488
|
"use strict";
|
|
489
489
|
init_core();
|
|
490
|
+
isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
490
491
|
CalendarElement = class extends HTMLElement {
|
|
491
492
|
constructor() {
|
|
492
493
|
super(...arguments);
|
|
@@ -508,6 +509,13 @@ var init_CalendarElement = __esm({
|
|
|
508
509
|
this._categoryColors = null;
|
|
509
510
|
this._renderEvent = null;
|
|
510
511
|
this._renderNoEvents = null;
|
|
512
|
+
// Selection state (availability mode — range)
|
|
513
|
+
this._rangeStart = null;
|
|
514
|
+
this._rangeEnd = null;
|
|
515
|
+
this._timeRangeDate = null;
|
|
516
|
+
this._timeRangeStart = null;
|
|
517
|
+
this._timeRangeEnd = null;
|
|
518
|
+
this._timeRangeComplete = false;
|
|
511
519
|
}
|
|
512
520
|
get events() {
|
|
513
521
|
return this._events;
|
|
@@ -536,6 +544,13 @@ var init_CalendarElement = __esm({
|
|
|
536
544
|
this._renderNoEvents = val;
|
|
537
545
|
if (this.engine) this.render();
|
|
538
546
|
}
|
|
547
|
+
get loading() {
|
|
548
|
+
return this.hasAttribute("loading");
|
|
549
|
+
}
|
|
550
|
+
set loading(val) {
|
|
551
|
+
if (val) this.setAttribute("loading", "");
|
|
552
|
+
else this.removeAttribute("loading");
|
|
553
|
+
}
|
|
539
554
|
connectedCallback() {
|
|
540
555
|
this.classList.add("kalendly-calendar");
|
|
541
556
|
this.initEngine();
|
|
@@ -546,8 +561,20 @@ var init_CalendarElement = __esm({
|
|
|
546
561
|
this.classList.remove("kalendly-calendar");
|
|
547
562
|
this.innerHTML = "";
|
|
548
563
|
}
|
|
549
|
-
attributeChangedCallback(
|
|
564
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
550
565
|
if (oldVal === newVal) return;
|
|
566
|
+
if (name === "loading") {
|
|
567
|
+
this.render();
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (name === "selectable" && newVal === null) {
|
|
571
|
+
this._rangeStart = null;
|
|
572
|
+
this._rangeEnd = null;
|
|
573
|
+
this._timeRangeDate = null;
|
|
574
|
+
this._timeRangeStart = null;
|
|
575
|
+
this._timeRangeEnd = null;
|
|
576
|
+
this._timeRangeComplete = false;
|
|
577
|
+
}
|
|
551
578
|
this.reinit();
|
|
552
579
|
}
|
|
553
580
|
get minYear() {
|
|
@@ -649,6 +676,9 @@ var init_CalendarElement = __esm({
|
|
|
649
676
|
const title = this.getAttribute("title");
|
|
650
677
|
const minYear = this.minYear;
|
|
651
678
|
const maxYear = this.maxYear;
|
|
679
|
+
const availabilityMode = this.getAttribute("availability-mode");
|
|
680
|
+
const selectable = this.hasAttribute("selectable");
|
|
681
|
+
const isLoading = this.loading;
|
|
652
682
|
const today = /* @__PURE__ */ new Date();
|
|
653
683
|
const todayMonth = today.getMonth();
|
|
654
684
|
const todayYear = today.getFullYear();
|
|
@@ -758,6 +788,37 @@ var init_CalendarElement = __esm({
|
|
|
758
788
|
const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
|
|
759
789
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
760
790
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
791
|
+
const renderTimeGrid = (events, date) => {
|
|
792
|
+
const isHourBooked = (hour) => events.some((event) => {
|
|
793
|
+
if (!event.startTime || !event.endTime) return true;
|
|
794
|
+
const [startH] = event.startTime.split(":").map(Number);
|
|
795
|
+
const [endH] = event.endTime.split(":").map(Number);
|
|
796
|
+
return hour >= startH && hour < endH;
|
|
797
|
+
});
|
|
798
|
+
const slots = Array.from({ length: 24 }, (_, hour) => {
|
|
799
|
+
const startTime = `${String(hour).padStart(2, "0")}:00`;
|
|
800
|
+
const endTime = `${String(hour + 1).padStart(2, "0")}:00`;
|
|
801
|
+
const booked = isHourBooked(hour);
|
|
802
|
+
const inTimeRange = !booked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
803
|
+
const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
|
|
804
|
+
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
805
|
+
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
806
|
+
const slotClasses = [
|
|
807
|
+
"time-grid__slot",
|
|
808
|
+
booked ? "time-grid__slot--booked" : "time-grid__slot--free",
|
|
809
|
+
isRangeStart ? "time-grid__slot--range-start" : "",
|
|
810
|
+
isRangeEnd ? "time-grid__slot--range-end" : "",
|
|
811
|
+
isInRange ? "time-grid__slot--in-range" : ""
|
|
812
|
+
].filter(Boolean).join(" ");
|
|
813
|
+
const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
|
|
814
|
+
return `
|
|
815
|
+
<div class="${slotClasses}" ${slotAttrs}>
|
|
816
|
+
<span class="time-grid__label">${startTime}</span>
|
|
817
|
+
<span class="time-grid__status">${booked ? "Booked" : "Available"}</span>
|
|
818
|
+
</div>`;
|
|
819
|
+
});
|
|
820
|
+
return `<div class="time-grid">${slots.join("")}</div>`;
|
|
821
|
+
};
|
|
761
822
|
const html = `
|
|
762
823
|
${title ? `
|
|
763
824
|
<div class="page--title">
|
|
@@ -847,30 +908,52 @@ var init_CalendarElement = __esm({
|
|
|
847
908
|
</tr>
|
|
848
909
|
</thead>
|
|
849
910
|
<tbody data-calendar-body>
|
|
850
|
-
${
|
|
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(
|
|
851
918
|
(week) => `
|
|
852
|
-
|
|
853
|
-
|
|
919
|
+
<tr>
|
|
920
|
+
${week.map((calendarDate, dayIndex) => {
|
|
854
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
|
+
}
|
|
855
938
|
const dateString = calendarDate.date.toISOString();
|
|
856
939
|
return `
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
+
`;
|
|
866
949
|
}).join("")}
|
|
867
|
-
|
|
868
|
-
|
|
950
|
+
</tr>
|
|
951
|
+
`
|
|
869
952
|
).join("")}
|
|
870
953
|
</tbody>
|
|
871
954
|
</table>
|
|
872
955
|
|
|
873
|
-
${viewModel.selectedDate ? `
|
|
956
|
+
${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
|
|
874
957
|
<div class="date-popup ${viewModel.popupPositionClass}">
|
|
875
958
|
<div class="popup-header">
|
|
876
959
|
<h2>${viewModel.scheduleDay}</h2>
|
|
@@ -884,7 +967,7 @@ var init_CalendarElement = __esm({
|
|
|
884
967
|
` : ""}
|
|
885
968
|
|
|
886
969
|
<div class="events-container">
|
|
887
|
-
${viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
970
|
+
${availabilityMode === "time" ? renderTimeGrid(viewModel.tasks, viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
888
971
|
</div>
|
|
889
972
|
</div>
|
|
890
973
|
` : ""}
|
|
@@ -906,6 +989,59 @@ var init_CalendarElement = __esm({
|
|
|
906
989
|
e.stopPropagation();
|
|
907
990
|
const date = new Date(cell.dataset.date);
|
|
908
991
|
const dayIndex = parseInt(cell.dataset.dayIndex || "0");
|
|
992
|
+
const availMode = this.getAttribute("availability-mode");
|
|
993
|
+
const isSelectable = this.hasAttribute("selectable");
|
|
994
|
+
if (availMode === "day" && isSelectable) {
|
|
995
|
+
const isBooked = cell.classList.contains("availability--booked");
|
|
996
|
+
if (!isBooked) {
|
|
997
|
+
let startDate;
|
|
998
|
+
let endDate;
|
|
999
|
+
if (this._rangeEnd !== null) {
|
|
1000
|
+
this._rangeStart = date;
|
|
1001
|
+
this._rangeEnd = null;
|
|
1002
|
+
startDate = date;
|
|
1003
|
+
endDate = date;
|
|
1004
|
+
} else if (this._rangeStart === null) {
|
|
1005
|
+
this._rangeStart = date;
|
|
1006
|
+
startDate = date;
|
|
1007
|
+
endDate = date;
|
|
1008
|
+
} else {
|
|
1009
|
+
const [s, e2] = this._rangeStart <= date ? [this._rangeStart, date] : [date, this._rangeStart];
|
|
1010
|
+
const cursor = new Date(s);
|
|
1011
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
1012
|
+
let blocked = false;
|
|
1013
|
+
while (cursor < e2) {
|
|
1014
|
+
if (this.engine.getEventsForDate(cursor).length > 0) {
|
|
1015
|
+
blocked = true;
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
1018
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
1019
|
+
}
|
|
1020
|
+
if (blocked) {
|
|
1021
|
+
this._rangeStart = date;
|
|
1022
|
+
this._rangeEnd = null;
|
|
1023
|
+
} else {
|
|
1024
|
+
this._rangeStart = s;
|
|
1025
|
+
this._rangeEnd = e2;
|
|
1026
|
+
}
|
|
1027
|
+
startDate = this._rangeStart;
|
|
1028
|
+
endDate = this._rangeEnd ?? this._rangeStart;
|
|
1029
|
+
}
|
|
1030
|
+
this.dispatchEvent(
|
|
1031
|
+
new CustomEvent("cal-availability-select", {
|
|
1032
|
+
bubbles: true,
|
|
1033
|
+
composed: true,
|
|
1034
|
+
detail: { startDate, endDate }
|
|
1035
|
+
})
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (availMode === "time") {
|
|
1040
|
+
this._timeRangeDate = null;
|
|
1041
|
+
this._timeRangeStart = null;
|
|
1042
|
+
this._timeRangeEnd = null;
|
|
1043
|
+
this._timeRangeComplete = false;
|
|
1044
|
+
}
|
|
909
1045
|
this.engine.handleDateClick(date, dayIndex);
|
|
910
1046
|
this.dispatchEvent(
|
|
911
1047
|
new CustomEvent("cal-date-select", {
|
|
@@ -921,19 +1057,29 @@ var init_CalendarElement = __esm({
|
|
|
921
1057
|
const action = actionEl.dataset.action;
|
|
922
1058
|
e.stopPropagation();
|
|
923
1059
|
switch (action) {
|
|
924
|
-
case "previous":
|
|
1060
|
+
case "previous": {
|
|
1061
|
+
const vm = this.engine.getViewModel();
|
|
1062
|
+
const tMonth = vm.currentMonth === 0 ? 11 : vm.currentMonth - 1;
|
|
1063
|
+
const tYear = vm.currentMonth === 0 ? vm.currentYear - 1 : vm.currentYear;
|
|
1064
|
+
this.dispatchMonthChange(tYear, tMonth);
|
|
925
1065
|
this.actions.previous();
|
|
926
|
-
this.dispatchMonthChange();
|
|
927
1066
|
break;
|
|
928
|
-
|
|
1067
|
+
}
|
|
1068
|
+
case "next": {
|
|
1069
|
+
const vm = this.engine.getViewModel();
|
|
1070
|
+
const tMonth = vm.currentMonth === 11 ? 0 : vm.currentMonth + 1;
|
|
1071
|
+
const tYear = vm.currentMonth === 11 ? vm.currentYear + 1 : vm.currentYear;
|
|
1072
|
+
this.dispatchMonthChange(tYear, tMonth);
|
|
929
1073
|
this.actions.next();
|
|
930
|
-
this.dispatchMonthChange();
|
|
931
1074
|
break;
|
|
932
|
-
|
|
1075
|
+
}
|
|
1076
|
+
case "today": {
|
|
1077
|
+
const t = /* @__PURE__ */ new Date();
|
|
1078
|
+
this.dispatchMonthChange(t.getFullYear(), t.getMonth());
|
|
933
1079
|
this.actions.goToToday();
|
|
934
1080
|
this.pickerOpen = false;
|
|
935
|
-
this.dispatchMonthChange();
|
|
936
1081
|
break;
|
|
1082
|
+
}
|
|
937
1083
|
case "toggle-picker":
|
|
938
1084
|
this.pickerOpen = !this.pickerOpen;
|
|
939
1085
|
if (this.pickerOpen) {
|
|
@@ -948,8 +1094,8 @@ var init_CalendarElement = __esm({
|
|
|
948
1094
|
const newYear = vm.currentYear - 1;
|
|
949
1095
|
this.yearInput = String(newYear);
|
|
950
1096
|
this.updatePickerYear(newYear);
|
|
1097
|
+
this.dispatchMonthChange(newYear, vm.currentMonth);
|
|
951
1098
|
this.actions.jump(newYear, vm.currentMonth);
|
|
952
|
-
this.dispatchMonthChange();
|
|
953
1099
|
}
|
|
954
1100
|
break;
|
|
955
1101
|
}
|
|
@@ -959,21 +1105,60 @@ var init_CalendarElement = __esm({
|
|
|
959
1105
|
const newYear = vm.currentYear + 1;
|
|
960
1106
|
this.yearInput = String(newYear);
|
|
961
1107
|
this.updatePickerYear(newYear);
|
|
1108
|
+
this.dispatchMonthChange(newYear, vm.currentMonth);
|
|
962
1109
|
this.actions.jump(newYear, vm.currentMonth);
|
|
963
|
-
this.dispatchMonthChange();
|
|
964
1110
|
}
|
|
965
1111
|
break;
|
|
966
1112
|
}
|
|
967
1113
|
case "select-month": {
|
|
968
1114
|
const month = parseInt(actionEl.dataset.month || "0", 10);
|
|
969
|
-
this.
|
|
1115
|
+
const year = this.engine.getViewModel().currentYear;
|
|
1116
|
+
this.dispatchMonthChange(year, month);
|
|
1117
|
+
this.actions.jump(year, month);
|
|
970
1118
|
this.pickerOpen = false;
|
|
971
|
-
this.dispatchMonthChange();
|
|
972
1119
|
break;
|
|
973
1120
|
}
|
|
974
1121
|
case "close-popup":
|
|
1122
|
+
this._timeRangeDate = null;
|
|
1123
|
+
this._timeRangeStart = null;
|
|
1124
|
+
this._timeRangeEnd = null;
|
|
1125
|
+
this._timeRangeComplete = false;
|
|
975
1126
|
this.engine.clearSelection();
|
|
976
1127
|
break;
|
|
1128
|
+
case "select-slot": {
|
|
1129
|
+
const slotStart = actionEl.dataset.startTime;
|
|
1130
|
+
const slotEnd = actionEl.dataset.endTime;
|
|
1131
|
+
const slotDate = new Date(actionEl.dataset.date);
|
|
1132
|
+
if (this._timeRangeComplete) {
|
|
1133
|
+
this._timeRangeDate = slotDate;
|
|
1134
|
+
this._timeRangeStart = slotStart;
|
|
1135
|
+
this._timeRangeEnd = slotEnd;
|
|
1136
|
+
this._timeRangeComplete = false;
|
|
1137
|
+
} else if (this._timeRangeStart === null) {
|
|
1138
|
+
this._timeRangeDate = slotDate;
|
|
1139
|
+
this._timeRangeStart = slotStart;
|
|
1140
|
+
this._timeRangeEnd = slotEnd;
|
|
1141
|
+
} else {
|
|
1142
|
+
const newStart = slotStart < this._timeRangeStart ? slotStart : this._timeRangeStart;
|
|
1143
|
+
const newEnd = slotEnd > this._timeRangeEnd ? slotEnd : this._timeRangeEnd;
|
|
1144
|
+
this._timeRangeStart = newStart;
|
|
1145
|
+
this._timeRangeEnd = newEnd;
|
|
1146
|
+
this._timeRangeComplete = true;
|
|
1147
|
+
}
|
|
1148
|
+
this.render();
|
|
1149
|
+
this.dispatchEvent(
|
|
1150
|
+
new CustomEvent("cal-availability-select", {
|
|
1151
|
+
bubbles: true,
|
|
1152
|
+
composed: true,
|
|
1153
|
+
detail: {
|
|
1154
|
+
date: this._timeRangeDate,
|
|
1155
|
+
startTime: this._timeRangeStart,
|
|
1156
|
+
endTime: this._timeRangeEnd
|
|
1157
|
+
}
|
|
1158
|
+
})
|
|
1159
|
+
);
|
|
1160
|
+
break;
|
|
1161
|
+
}
|
|
977
1162
|
}
|
|
978
1163
|
};
|
|
979
1164
|
this.delegatedInputHandler = (e) => {
|
|
@@ -998,8 +1183,8 @@ var init_CalendarElement = __esm({
|
|
|
998
1183
|
target.value = this.yearInput;
|
|
999
1184
|
target.classList.remove("invalid");
|
|
1000
1185
|
} else if (year !== vm.currentYear) {
|
|
1186
|
+
this.dispatchMonthChange(year, vm.currentMonth);
|
|
1001
1187
|
this.actions.jump(year, vm.currentMonth);
|
|
1002
|
-
this.dispatchMonthChange();
|
|
1003
1188
|
}
|
|
1004
1189
|
};
|
|
1005
1190
|
this.delegatedKeydownHandler = (e) => {
|
|
@@ -1038,14 +1223,17 @@ var init_CalendarElement = __esm({
|
|
|
1038
1223
|
if (prevBtn) prevBtn.disabled = year <= this.minYear;
|
|
1039
1224
|
if (nextBtn) nextBtn.disabled = year >= this.maxYear;
|
|
1040
1225
|
}
|
|
1041
|
-
dispatchMonthChange() {
|
|
1226
|
+
dispatchMonthChange(year, month) {
|
|
1042
1227
|
if (!this.engine) return;
|
|
1043
1228
|
const vm = this.engine.getViewModel();
|
|
1044
1229
|
this.dispatchEvent(
|
|
1045
1230
|
new CustomEvent("cal-month-change", {
|
|
1046
1231
|
bubbles: true,
|
|
1047
1232
|
composed: true,
|
|
1048
|
-
detail: {
|
|
1233
|
+
detail: {
|
|
1234
|
+
year: year ?? vm.currentYear,
|
|
1235
|
+
month: month ?? vm.currentMonth
|
|
1236
|
+
}
|
|
1049
1237
|
})
|
|
1050
1238
|
);
|
|
1051
1239
|
}
|
|
@@ -1060,7 +1248,10 @@ var init_CalendarElement = __esm({
|
|
|
1060
1248
|
return this.engine?.getViewModel().selectedDate ?? null;
|
|
1061
1249
|
}
|
|
1062
1250
|
goToDate(date) {
|
|
1063
|
-
|
|
1251
|
+
const year = date.getFullYear();
|
|
1252
|
+
const month = date.getMonth();
|
|
1253
|
+
this.dispatchMonthChange(year, month);
|
|
1254
|
+
this.actions?.jump(year, month);
|
|
1064
1255
|
}
|
|
1065
1256
|
getEngine() {
|
|
1066
1257
|
if (!this.engine)
|
|
@@ -1074,7 +1265,10 @@ var init_CalendarElement = __esm({
|
|
|
1074
1265
|
"max-year",
|
|
1075
1266
|
"week-starts-on",
|
|
1076
1267
|
"title",
|
|
1077
|
-
"use-short-month-names"
|
|
1268
|
+
"use-short-month-names",
|
|
1269
|
+
"availability-mode",
|
|
1270
|
+
"selectable",
|
|
1271
|
+
"loading"
|
|
1078
1272
|
];
|
|
1079
1273
|
}
|
|
1080
1274
|
});
|