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.js
CHANGED
|
@@ -494,11 +494,12 @@ function defineCalendarElement(tagName = "kal-calendar") {
|
|
|
494
494
|
customElements.define(tagName, CalendarElement);
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
|
-
var CalendarElement;
|
|
497
|
+
var isSameDay2, CalendarElement;
|
|
498
498
|
var init_CalendarElement = __esm({
|
|
499
499
|
"src/web-components/CalendarElement.ts"() {
|
|
500
500
|
"use strict";
|
|
501
501
|
init_core();
|
|
502
|
+
isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
502
503
|
CalendarElement = class extends HTMLElement {
|
|
503
504
|
constructor() {
|
|
504
505
|
super(...arguments);
|
|
@@ -520,6 +521,13 @@ var init_CalendarElement = __esm({
|
|
|
520
521
|
this._categoryColors = null;
|
|
521
522
|
this._renderEvent = null;
|
|
522
523
|
this._renderNoEvents = null;
|
|
524
|
+
// Selection state (availability mode — range)
|
|
525
|
+
this._rangeStart = null;
|
|
526
|
+
this._rangeEnd = null;
|
|
527
|
+
this._timeRangeDate = null;
|
|
528
|
+
this._timeRangeStart = null;
|
|
529
|
+
this._timeRangeEnd = null;
|
|
530
|
+
this._timeRangeComplete = false;
|
|
523
531
|
}
|
|
524
532
|
get events() {
|
|
525
533
|
return this._events;
|
|
@@ -548,6 +556,13 @@ var init_CalendarElement = __esm({
|
|
|
548
556
|
this._renderNoEvents = val;
|
|
549
557
|
if (this.engine) this.render();
|
|
550
558
|
}
|
|
559
|
+
get loading() {
|
|
560
|
+
return this.hasAttribute("loading");
|
|
561
|
+
}
|
|
562
|
+
set loading(val) {
|
|
563
|
+
if (val) this.setAttribute("loading", "");
|
|
564
|
+
else this.removeAttribute("loading");
|
|
565
|
+
}
|
|
551
566
|
connectedCallback() {
|
|
552
567
|
this.classList.add("kalendly-calendar");
|
|
553
568
|
this.initEngine();
|
|
@@ -558,8 +573,20 @@ var init_CalendarElement = __esm({
|
|
|
558
573
|
this.classList.remove("kalendly-calendar");
|
|
559
574
|
this.innerHTML = "";
|
|
560
575
|
}
|
|
561
|
-
attributeChangedCallback(
|
|
576
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
562
577
|
if (oldVal === newVal) return;
|
|
578
|
+
if (name === "loading") {
|
|
579
|
+
this.render();
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (name === "selectable" && newVal === null) {
|
|
583
|
+
this._rangeStart = null;
|
|
584
|
+
this._rangeEnd = null;
|
|
585
|
+
this._timeRangeDate = null;
|
|
586
|
+
this._timeRangeStart = null;
|
|
587
|
+
this._timeRangeEnd = null;
|
|
588
|
+
this._timeRangeComplete = false;
|
|
589
|
+
}
|
|
563
590
|
this.reinit();
|
|
564
591
|
}
|
|
565
592
|
get minYear() {
|
|
@@ -661,6 +688,9 @@ var init_CalendarElement = __esm({
|
|
|
661
688
|
const title = this.getAttribute("title");
|
|
662
689
|
const minYear = this.minYear;
|
|
663
690
|
const maxYear = this.maxYear;
|
|
691
|
+
const availabilityMode = this.getAttribute("availability-mode");
|
|
692
|
+
const selectable = this.hasAttribute("selectable");
|
|
693
|
+
const isLoading = this.loading;
|
|
664
694
|
const today = /* @__PURE__ */ new Date();
|
|
665
695
|
const todayMonth = today.getMonth();
|
|
666
696
|
const todayYear = today.getFullYear();
|
|
@@ -770,6 +800,37 @@ var init_CalendarElement = __esm({
|
|
|
770
800
|
const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
|
|
771
801
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
772
802
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
803
|
+
const renderTimeGrid = (events, date) => {
|
|
804
|
+
const isHourBooked = (hour) => events.some((event) => {
|
|
805
|
+
if (!event.startTime || !event.endTime) return true;
|
|
806
|
+
const [startH] = event.startTime.split(":").map(Number);
|
|
807
|
+
const [endH] = event.endTime.split(":").map(Number);
|
|
808
|
+
return hour >= startH && hour < endH;
|
|
809
|
+
});
|
|
810
|
+
const slots = Array.from({ length: 24 }, (_, hour) => {
|
|
811
|
+
const startTime = `${String(hour).padStart(2, "0")}:00`;
|
|
812
|
+
const endTime = `${String(hour + 1).padStart(2, "0")}:00`;
|
|
813
|
+
const booked = isHourBooked(hour);
|
|
814
|
+
const inTimeRange = !booked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
815
|
+
const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
|
|
816
|
+
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
817
|
+
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
818
|
+
const slotClasses = [
|
|
819
|
+
"time-grid__slot",
|
|
820
|
+
booked ? "time-grid__slot--booked" : "time-grid__slot--free",
|
|
821
|
+
isRangeStart ? "time-grid__slot--range-start" : "",
|
|
822
|
+
isRangeEnd ? "time-grid__slot--range-end" : "",
|
|
823
|
+
isInRange ? "time-grid__slot--in-range" : ""
|
|
824
|
+
].filter(Boolean).join(" ");
|
|
825
|
+
const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
|
|
826
|
+
return `
|
|
827
|
+
<div class="${slotClasses}" ${slotAttrs}>
|
|
828
|
+
<span class="time-grid__label">${startTime}</span>
|
|
829
|
+
<span class="time-grid__status">${booked ? "Booked" : "Available"}</span>
|
|
830
|
+
</div>`;
|
|
831
|
+
});
|
|
832
|
+
return `<div class="time-grid">${slots.join("")}</div>`;
|
|
833
|
+
};
|
|
773
834
|
const html = `
|
|
774
835
|
${title ? `
|
|
775
836
|
<div class="page--title">
|
|
@@ -859,30 +920,52 @@ var init_CalendarElement = __esm({
|
|
|
859
920
|
</tr>
|
|
860
921
|
</thead>
|
|
861
922
|
<tbody data-calendar-body>
|
|
862
|
-
${
|
|
923
|
+
${isLoading ? Array.from(
|
|
924
|
+
{ length: 6 },
|
|
925
|
+
() => `<tr>${Array.from(
|
|
926
|
+
{ length: 7 },
|
|
927
|
+
() => `<td class="calendar--skeleton" aria-hidden="true"></td>`
|
|
928
|
+
).join("")}</tr>`
|
|
929
|
+
).join("") : viewModel.calendarDates.map(
|
|
863
930
|
(week) => `
|
|
864
|
-
|
|
865
|
-
|
|
931
|
+
<tr>
|
|
932
|
+
${week.map((calendarDate, dayIndex) => {
|
|
866
933
|
const classes = getCellClasses(calendarDate);
|
|
934
|
+
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
935
|
+
classes.push(
|
|
936
|
+
calendarDate.hasEvents ? "availability--booked" : "availability--free"
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
940
|
+
const d = calendarDate.date;
|
|
941
|
+
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
942
|
+
classes.push("availability--range-start");
|
|
943
|
+
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
944
|
+
classes.push("availability--range-end");
|
|
945
|
+
if (this._rangeStart && this._rangeEnd) {
|
|
946
|
+
if (d > this._rangeStart && d < this._rangeEnd)
|
|
947
|
+
classes.push("availability--in-range");
|
|
948
|
+
}
|
|
949
|
+
}
|
|
867
950
|
const dateString = calendarDate.date.toISOString();
|
|
868
951
|
return `
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
952
|
+
<td
|
|
953
|
+
class="${classes.join(" ")}"
|
|
954
|
+
data-date="${dateString}"
|
|
955
|
+
data-day-index="${dayIndex}"
|
|
956
|
+
data-clickable="true"
|
|
957
|
+
>
|
|
958
|
+
${calendarDate.date.getDate()}
|
|
959
|
+
</td>
|
|
960
|
+
`;
|
|
878
961
|
}).join("")}
|
|
879
|
-
|
|
880
|
-
|
|
962
|
+
</tr>
|
|
963
|
+
`
|
|
881
964
|
).join("")}
|
|
882
965
|
</tbody>
|
|
883
966
|
</table>
|
|
884
967
|
|
|
885
|
-
${viewModel.selectedDate ? `
|
|
968
|
+
${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
|
|
886
969
|
<div class="date-popup ${viewModel.popupPositionClass}">
|
|
887
970
|
<div class="popup-header">
|
|
888
971
|
<h2>${viewModel.scheduleDay}</h2>
|
|
@@ -896,7 +979,7 @@ var init_CalendarElement = __esm({
|
|
|
896
979
|
` : ""}
|
|
897
980
|
|
|
898
981
|
<div class="events-container">
|
|
899
|
-
${viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
982
|
+
${availabilityMode === "time" ? renderTimeGrid(viewModel.tasks, viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
900
983
|
</div>
|
|
901
984
|
</div>
|
|
902
985
|
` : ""}
|
|
@@ -918,6 +1001,59 @@ var init_CalendarElement = __esm({
|
|
|
918
1001
|
e.stopPropagation();
|
|
919
1002
|
const date = new Date(cell.dataset.date);
|
|
920
1003
|
const dayIndex = parseInt(cell.dataset.dayIndex || "0");
|
|
1004
|
+
const availMode = this.getAttribute("availability-mode");
|
|
1005
|
+
const isSelectable = this.hasAttribute("selectable");
|
|
1006
|
+
if (availMode === "day" && isSelectable) {
|
|
1007
|
+
const isBooked = cell.classList.contains("availability--booked");
|
|
1008
|
+
if (!isBooked) {
|
|
1009
|
+
let startDate;
|
|
1010
|
+
let endDate;
|
|
1011
|
+
if (this._rangeEnd !== null) {
|
|
1012
|
+
this._rangeStart = date;
|
|
1013
|
+
this._rangeEnd = null;
|
|
1014
|
+
startDate = date;
|
|
1015
|
+
endDate = date;
|
|
1016
|
+
} else if (this._rangeStart === null) {
|
|
1017
|
+
this._rangeStart = date;
|
|
1018
|
+
startDate = date;
|
|
1019
|
+
endDate = date;
|
|
1020
|
+
} else {
|
|
1021
|
+
const [s, e2] = this._rangeStart <= date ? [this._rangeStart, date] : [date, this._rangeStart];
|
|
1022
|
+
const cursor = new Date(s);
|
|
1023
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
1024
|
+
let blocked = false;
|
|
1025
|
+
while (cursor < e2) {
|
|
1026
|
+
if (this.engine.getEventsForDate(cursor).length > 0) {
|
|
1027
|
+
blocked = true;
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
1031
|
+
}
|
|
1032
|
+
if (blocked) {
|
|
1033
|
+
this._rangeStart = date;
|
|
1034
|
+
this._rangeEnd = null;
|
|
1035
|
+
} else {
|
|
1036
|
+
this._rangeStart = s;
|
|
1037
|
+
this._rangeEnd = e2;
|
|
1038
|
+
}
|
|
1039
|
+
startDate = this._rangeStart;
|
|
1040
|
+
endDate = this._rangeEnd ?? this._rangeStart;
|
|
1041
|
+
}
|
|
1042
|
+
this.dispatchEvent(
|
|
1043
|
+
new CustomEvent("cal-availability-select", {
|
|
1044
|
+
bubbles: true,
|
|
1045
|
+
composed: true,
|
|
1046
|
+
detail: { startDate, endDate }
|
|
1047
|
+
})
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (availMode === "time") {
|
|
1052
|
+
this._timeRangeDate = null;
|
|
1053
|
+
this._timeRangeStart = null;
|
|
1054
|
+
this._timeRangeEnd = null;
|
|
1055
|
+
this._timeRangeComplete = false;
|
|
1056
|
+
}
|
|
921
1057
|
this.engine.handleDateClick(date, dayIndex);
|
|
922
1058
|
this.dispatchEvent(
|
|
923
1059
|
new CustomEvent("cal-date-select", {
|
|
@@ -933,19 +1069,29 @@ var init_CalendarElement = __esm({
|
|
|
933
1069
|
const action = actionEl.dataset.action;
|
|
934
1070
|
e.stopPropagation();
|
|
935
1071
|
switch (action) {
|
|
936
|
-
case "previous":
|
|
1072
|
+
case "previous": {
|
|
1073
|
+
const vm = this.engine.getViewModel();
|
|
1074
|
+
const tMonth = vm.currentMonth === 0 ? 11 : vm.currentMonth - 1;
|
|
1075
|
+
const tYear = vm.currentMonth === 0 ? vm.currentYear - 1 : vm.currentYear;
|
|
1076
|
+
this.dispatchMonthChange(tYear, tMonth);
|
|
937
1077
|
this.actions.previous();
|
|
938
|
-
this.dispatchMonthChange();
|
|
939
1078
|
break;
|
|
940
|
-
|
|
1079
|
+
}
|
|
1080
|
+
case "next": {
|
|
1081
|
+
const vm = this.engine.getViewModel();
|
|
1082
|
+
const tMonth = vm.currentMonth === 11 ? 0 : vm.currentMonth + 1;
|
|
1083
|
+
const tYear = vm.currentMonth === 11 ? vm.currentYear + 1 : vm.currentYear;
|
|
1084
|
+
this.dispatchMonthChange(tYear, tMonth);
|
|
941
1085
|
this.actions.next();
|
|
942
|
-
this.dispatchMonthChange();
|
|
943
1086
|
break;
|
|
944
|
-
|
|
1087
|
+
}
|
|
1088
|
+
case "today": {
|
|
1089
|
+
const t = /* @__PURE__ */ new Date();
|
|
1090
|
+
this.dispatchMonthChange(t.getFullYear(), t.getMonth());
|
|
945
1091
|
this.actions.goToToday();
|
|
946
1092
|
this.pickerOpen = false;
|
|
947
|
-
this.dispatchMonthChange();
|
|
948
1093
|
break;
|
|
1094
|
+
}
|
|
949
1095
|
case "toggle-picker":
|
|
950
1096
|
this.pickerOpen = !this.pickerOpen;
|
|
951
1097
|
if (this.pickerOpen) {
|
|
@@ -960,8 +1106,8 @@ var init_CalendarElement = __esm({
|
|
|
960
1106
|
const newYear = vm.currentYear - 1;
|
|
961
1107
|
this.yearInput = String(newYear);
|
|
962
1108
|
this.updatePickerYear(newYear);
|
|
1109
|
+
this.dispatchMonthChange(newYear, vm.currentMonth);
|
|
963
1110
|
this.actions.jump(newYear, vm.currentMonth);
|
|
964
|
-
this.dispatchMonthChange();
|
|
965
1111
|
}
|
|
966
1112
|
break;
|
|
967
1113
|
}
|
|
@@ -971,21 +1117,60 @@ var init_CalendarElement = __esm({
|
|
|
971
1117
|
const newYear = vm.currentYear + 1;
|
|
972
1118
|
this.yearInput = String(newYear);
|
|
973
1119
|
this.updatePickerYear(newYear);
|
|
1120
|
+
this.dispatchMonthChange(newYear, vm.currentMonth);
|
|
974
1121
|
this.actions.jump(newYear, vm.currentMonth);
|
|
975
|
-
this.dispatchMonthChange();
|
|
976
1122
|
}
|
|
977
1123
|
break;
|
|
978
1124
|
}
|
|
979
1125
|
case "select-month": {
|
|
980
1126
|
const month = parseInt(actionEl.dataset.month || "0", 10);
|
|
981
|
-
this.
|
|
1127
|
+
const year = this.engine.getViewModel().currentYear;
|
|
1128
|
+
this.dispatchMonthChange(year, month);
|
|
1129
|
+
this.actions.jump(year, month);
|
|
982
1130
|
this.pickerOpen = false;
|
|
983
|
-
this.dispatchMonthChange();
|
|
984
1131
|
break;
|
|
985
1132
|
}
|
|
986
1133
|
case "close-popup":
|
|
1134
|
+
this._timeRangeDate = null;
|
|
1135
|
+
this._timeRangeStart = null;
|
|
1136
|
+
this._timeRangeEnd = null;
|
|
1137
|
+
this._timeRangeComplete = false;
|
|
987
1138
|
this.engine.clearSelection();
|
|
988
1139
|
break;
|
|
1140
|
+
case "select-slot": {
|
|
1141
|
+
const slotStart = actionEl.dataset.startTime;
|
|
1142
|
+
const slotEnd = actionEl.dataset.endTime;
|
|
1143
|
+
const slotDate = new Date(actionEl.dataset.date);
|
|
1144
|
+
if (this._timeRangeComplete) {
|
|
1145
|
+
this._timeRangeDate = slotDate;
|
|
1146
|
+
this._timeRangeStart = slotStart;
|
|
1147
|
+
this._timeRangeEnd = slotEnd;
|
|
1148
|
+
this._timeRangeComplete = false;
|
|
1149
|
+
} else if (this._timeRangeStart === null) {
|
|
1150
|
+
this._timeRangeDate = slotDate;
|
|
1151
|
+
this._timeRangeStart = slotStart;
|
|
1152
|
+
this._timeRangeEnd = slotEnd;
|
|
1153
|
+
} else {
|
|
1154
|
+
const newStart = slotStart < this._timeRangeStart ? slotStart : this._timeRangeStart;
|
|
1155
|
+
const newEnd = slotEnd > this._timeRangeEnd ? slotEnd : this._timeRangeEnd;
|
|
1156
|
+
this._timeRangeStart = newStart;
|
|
1157
|
+
this._timeRangeEnd = newEnd;
|
|
1158
|
+
this._timeRangeComplete = true;
|
|
1159
|
+
}
|
|
1160
|
+
this.render();
|
|
1161
|
+
this.dispatchEvent(
|
|
1162
|
+
new CustomEvent("cal-availability-select", {
|
|
1163
|
+
bubbles: true,
|
|
1164
|
+
composed: true,
|
|
1165
|
+
detail: {
|
|
1166
|
+
date: this._timeRangeDate,
|
|
1167
|
+
startTime: this._timeRangeStart,
|
|
1168
|
+
endTime: this._timeRangeEnd
|
|
1169
|
+
}
|
|
1170
|
+
})
|
|
1171
|
+
);
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
989
1174
|
}
|
|
990
1175
|
};
|
|
991
1176
|
this.delegatedInputHandler = (e) => {
|
|
@@ -1010,8 +1195,8 @@ var init_CalendarElement = __esm({
|
|
|
1010
1195
|
target.value = this.yearInput;
|
|
1011
1196
|
target.classList.remove("invalid");
|
|
1012
1197
|
} else if (year !== vm.currentYear) {
|
|
1198
|
+
this.dispatchMonthChange(year, vm.currentMonth);
|
|
1013
1199
|
this.actions.jump(year, vm.currentMonth);
|
|
1014
|
-
this.dispatchMonthChange();
|
|
1015
1200
|
}
|
|
1016
1201
|
};
|
|
1017
1202
|
this.delegatedKeydownHandler = (e) => {
|
|
@@ -1050,14 +1235,17 @@ var init_CalendarElement = __esm({
|
|
|
1050
1235
|
if (prevBtn) prevBtn.disabled = year <= this.minYear;
|
|
1051
1236
|
if (nextBtn) nextBtn.disabled = year >= this.maxYear;
|
|
1052
1237
|
}
|
|
1053
|
-
dispatchMonthChange() {
|
|
1238
|
+
dispatchMonthChange(year, month) {
|
|
1054
1239
|
if (!this.engine) return;
|
|
1055
1240
|
const vm = this.engine.getViewModel();
|
|
1056
1241
|
this.dispatchEvent(
|
|
1057
1242
|
new CustomEvent("cal-month-change", {
|
|
1058
1243
|
bubbles: true,
|
|
1059
1244
|
composed: true,
|
|
1060
|
-
detail: {
|
|
1245
|
+
detail: {
|
|
1246
|
+
year: year ?? vm.currentYear,
|
|
1247
|
+
month: month ?? vm.currentMonth
|
|
1248
|
+
}
|
|
1061
1249
|
})
|
|
1062
1250
|
);
|
|
1063
1251
|
}
|
|
@@ -1072,7 +1260,10 @@ var init_CalendarElement = __esm({
|
|
|
1072
1260
|
return this.engine?.getViewModel().selectedDate ?? null;
|
|
1073
1261
|
}
|
|
1074
1262
|
goToDate(date) {
|
|
1075
|
-
|
|
1263
|
+
const year = date.getFullYear();
|
|
1264
|
+
const month = date.getMonth();
|
|
1265
|
+
this.dispatchMonthChange(year, month);
|
|
1266
|
+
this.actions?.jump(year, month);
|
|
1076
1267
|
}
|
|
1077
1268
|
getEngine() {
|
|
1078
1269
|
if (!this.engine)
|
|
@@ -1086,7 +1277,10 @@ var init_CalendarElement = __esm({
|
|
|
1086
1277
|
"max-year",
|
|
1087
1278
|
"week-starts-on",
|
|
1088
1279
|
"title",
|
|
1089
|
-
"use-short-month-names"
|
|
1280
|
+
"use-short-month-names",
|
|
1281
|
+
"availability-mode",
|
|
1282
|
+
"selectable",
|
|
1283
|
+
"loading"
|
|
1090
1284
|
];
|
|
1091
1285
|
}
|
|
1092
1286
|
});
|