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.umd.js
CHANGED
|
@@ -438,6 +438,7 @@ var Kalendly = (() => {
|
|
|
438
438
|
};
|
|
439
439
|
|
|
440
440
|
// src/web-components/CalendarElement.ts
|
|
441
|
+
var isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
441
442
|
var CalendarElement = class extends HTMLElement {
|
|
442
443
|
constructor() {
|
|
443
444
|
super(...arguments);
|
|
@@ -459,6 +460,13 @@ var Kalendly = (() => {
|
|
|
459
460
|
this._categoryColors = null;
|
|
460
461
|
this._renderEvent = null;
|
|
461
462
|
this._renderNoEvents = null;
|
|
463
|
+
// Selection state (availability mode — range)
|
|
464
|
+
this._rangeStart = null;
|
|
465
|
+
this._rangeEnd = null;
|
|
466
|
+
this._timeRangeDate = null;
|
|
467
|
+
this._timeRangeStart = null;
|
|
468
|
+
this._timeRangeEnd = null;
|
|
469
|
+
this._timeRangeComplete = false;
|
|
462
470
|
}
|
|
463
471
|
get events() {
|
|
464
472
|
return this._events;
|
|
@@ -487,6 +495,13 @@ var Kalendly = (() => {
|
|
|
487
495
|
this._renderNoEvents = val;
|
|
488
496
|
if (this.engine) this.render();
|
|
489
497
|
}
|
|
498
|
+
get loading() {
|
|
499
|
+
return this.hasAttribute("loading");
|
|
500
|
+
}
|
|
501
|
+
set loading(val) {
|
|
502
|
+
if (val) this.setAttribute("loading", "");
|
|
503
|
+
else this.removeAttribute("loading");
|
|
504
|
+
}
|
|
490
505
|
connectedCallback() {
|
|
491
506
|
this.classList.add("kalendly-calendar");
|
|
492
507
|
this.initEngine();
|
|
@@ -497,8 +512,20 @@ var Kalendly = (() => {
|
|
|
497
512
|
this.classList.remove("kalendly-calendar");
|
|
498
513
|
this.innerHTML = "";
|
|
499
514
|
}
|
|
500
|
-
attributeChangedCallback(
|
|
515
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
501
516
|
if (oldVal === newVal) return;
|
|
517
|
+
if (name === "loading") {
|
|
518
|
+
this.render();
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (name === "selectable" && newVal === null) {
|
|
522
|
+
this._rangeStart = null;
|
|
523
|
+
this._rangeEnd = null;
|
|
524
|
+
this._timeRangeDate = null;
|
|
525
|
+
this._timeRangeStart = null;
|
|
526
|
+
this._timeRangeEnd = null;
|
|
527
|
+
this._timeRangeComplete = false;
|
|
528
|
+
}
|
|
502
529
|
this.reinit();
|
|
503
530
|
}
|
|
504
531
|
get minYear() {
|
|
@@ -600,6 +627,9 @@ var Kalendly = (() => {
|
|
|
600
627
|
const title = this.getAttribute("title");
|
|
601
628
|
const minYear = this.minYear;
|
|
602
629
|
const maxYear = this.maxYear;
|
|
630
|
+
const availabilityMode = this.getAttribute("availability-mode");
|
|
631
|
+
const selectable = this.hasAttribute("selectable");
|
|
632
|
+
const isLoading = this.loading;
|
|
603
633
|
const today = /* @__PURE__ */ new Date();
|
|
604
634
|
const todayMonth = today.getMonth();
|
|
605
635
|
const todayYear = today.getFullYear();
|
|
@@ -709,6 +739,37 @@ var Kalendly = (() => {
|
|
|
709
739
|
const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
|
|
710
740
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
711
741
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
742
|
+
const renderTimeGrid = (events, date) => {
|
|
743
|
+
const isHourBooked = (hour) => events.some((event) => {
|
|
744
|
+
if (!event.startTime || !event.endTime) return true;
|
|
745
|
+
const [startH] = event.startTime.split(":").map(Number);
|
|
746
|
+
const [endH] = event.endTime.split(":").map(Number);
|
|
747
|
+
return hour >= startH && hour < endH;
|
|
748
|
+
});
|
|
749
|
+
const slots = Array.from({ length: 24 }, (_, hour) => {
|
|
750
|
+
const startTime = `${String(hour).padStart(2, "0")}:00`;
|
|
751
|
+
const endTime = `${String(hour + 1).padStart(2, "0")}:00`;
|
|
752
|
+
const booked = isHourBooked(hour);
|
|
753
|
+
const inTimeRange = !booked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
754
|
+
const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
|
|
755
|
+
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
756
|
+
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
757
|
+
const slotClasses = [
|
|
758
|
+
"time-grid__slot",
|
|
759
|
+
booked ? "time-grid__slot--booked" : "time-grid__slot--free",
|
|
760
|
+
isRangeStart ? "time-grid__slot--range-start" : "",
|
|
761
|
+
isRangeEnd ? "time-grid__slot--range-end" : "",
|
|
762
|
+
isInRange ? "time-grid__slot--in-range" : ""
|
|
763
|
+
].filter(Boolean).join(" ");
|
|
764
|
+
const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
|
|
765
|
+
return `
|
|
766
|
+
<div class="${slotClasses}" ${slotAttrs}>
|
|
767
|
+
<span class="time-grid__label">${startTime}</span>
|
|
768
|
+
<span class="time-grid__status">${booked ? "Booked" : "Available"}</span>
|
|
769
|
+
</div>`;
|
|
770
|
+
});
|
|
771
|
+
return `<div class="time-grid">${slots.join("")}</div>`;
|
|
772
|
+
};
|
|
712
773
|
const html = `
|
|
713
774
|
${title ? `
|
|
714
775
|
<div class="page--title">
|
|
@@ -798,30 +859,52 @@ var Kalendly = (() => {
|
|
|
798
859
|
</tr>
|
|
799
860
|
</thead>
|
|
800
861
|
<tbody data-calendar-body>
|
|
801
|
-
${
|
|
862
|
+
${isLoading ? Array.from(
|
|
863
|
+
{ length: 6 },
|
|
864
|
+
() => `<tr>${Array.from(
|
|
865
|
+
{ length: 7 },
|
|
866
|
+
() => `<td class="calendar--skeleton" aria-hidden="true"></td>`
|
|
867
|
+
).join("")}</tr>`
|
|
868
|
+
).join("") : viewModel.calendarDates.map(
|
|
802
869
|
(week) => `
|
|
803
|
-
|
|
804
|
-
|
|
870
|
+
<tr>
|
|
871
|
+
${week.map((calendarDate, dayIndex) => {
|
|
805
872
|
const classes = getCellClasses(calendarDate);
|
|
873
|
+
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
874
|
+
classes.push(
|
|
875
|
+
calendarDate.hasEvents ? "availability--booked" : "availability--free"
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
879
|
+
const d = calendarDate.date;
|
|
880
|
+
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
881
|
+
classes.push("availability--range-start");
|
|
882
|
+
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
883
|
+
classes.push("availability--range-end");
|
|
884
|
+
if (this._rangeStart && this._rangeEnd) {
|
|
885
|
+
if (d > this._rangeStart && d < this._rangeEnd)
|
|
886
|
+
classes.push("availability--in-range");
|
|
887
|
+
}
|
|
888
|
+
}
|
|
806
889
|
const dateString = calendarDate.date.toISOString();
|
|
807
890
|
return `
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
891
|
+
<td
|
|
892
|
+
class="${classes.join(" ")}"
|
|
893
|
+
data-date="${dateString}"
|
|
894
|
+
data-day-index="${dayIndex}"
|
|
895
|
+
data-clickable="true"
|
|
896
|
+
>
|
|
897
|
+
${calendarDate.date.getDate()}
|
|
898
|
+
</td>
|
|
899
|
+
`;
|
|
817
900
|
}).join("")}
|
|
818
|
-
|
|
819
|
-
|
|
901
|
+
</tr>
|
|
902
|
+
`
|
|
820
903
|
).join("")}
|
|
821
904
|
</tbody>
|
|
822
905
|
</table>
|
|
823
906
|
|
|
824
|
-
${viewModel.selectedDate ? `
|
|
907
|
+
${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
|
|
825
908
|
<div class="date-popup ${viewModel.popupPositionClass}">
|
|
826
909
|
<div class="popup-header">
|
|
827
910
|
<h2>${viewModel.scheduleDay}</h2>
|
|
@@ -835,7 +918,7 @@ var Kalendly = (() => {
|
|
|
835
918
|
` : ""}
|
|
836
919
|
|
|
837
920
|
<div class="events-container">
|
|
838
|
-
${viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
921
|
+
${availabilityMode === "time" ? renderTimeGrid(viewModel.tasks, viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
839
922
|
</div>
|
|
840
923
|
</div>
|
|
841
924
|
` : ""}
|
|
@@ -857,6 +940,59 @@ var Kalendly = (() => {
|
|
|
857
940
|
e.stopPropagation();
|
|
858
941
|
const date = new Date(cell.dataset.date);
|
|
859
942
|
const dayIndex = parseInt(cell.dataset.dayIndex || "0");
|
|
943
|
+
const availMode = this.getAttribute("availability-mode");
|
|
944
|
+
const isSelectable = this.hasAttribute("selectable");
|
|
945
|
+
if (availMode === "day" && isSelectable) {
|
|
946
|
+
const isBooked = cell.classList.contains("availability--booked");
|
|
947
|
+
if (!isBooked) {
|
|
948
|
+
let startDate;
|
|
949
|
+
let endDate;
|
|
950
|
+
if (this._rangeEnd !== null) {
|
|
951
|
+
this._rangeStart = date;
|
|
952
|
+
this._rangeEnd = null;
|
|
953
|
+
startDate = date;
|
|
954
|
+
endDate = date;
|
|
955
|
+
} else if (this._rangeStart === null) {
|
|
956
|
+
this._rangeStart = date;
|
|
957
|
+
startDate = date;
|
|
958
|
+
endDate = date;
|
|
959
|
+
} else {
|
|
960
|
+
const [s, e2] = this._rangeStart <= date ? [this._rangeStart, date] : [date, this._rangeStart];
|
|
961
|
+
const cursor = new Date(s);
|
|
962
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
963
|
+
let blocked = false;
|
|
964
|
+
while (cursor < e2) {
|
|
965
|
+
if (this.engine.getEventsForDate(cursor).length > 0) {
|
|
966
|
+
blocked = true;
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
970
|
+
}
|
|
971
|
+
if (blocked) {
|
|
972
|
+
this._rangeStart = date;
|
|
973
|
+
this._rangeEnd = null;
|
|
974
|
+
} else {
|
|
975
|
+
this._rangeStart = s;
|
|
976
|
+
this._rangeEnd = e2;
|
|
977
|
+
}
|
|
978
|
+
startDate = this._rangeStart;
|
|
979
|
+
endDate = this._rangeEnd ?? this._rangeStart;
|
|
980
|
+
}
|
|
981
|
+
this.dispatchEvent(
|
|
982
|
+
new CustomEvent("cal-availability-select", {
|
|
983
|
+
bubbles: true,
|
|
984
|
+
composed: true,
|
|
985
|
+
detail: { startDate, endDate }
|
|
986
|
+
})
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (availMode === "time") {
|
|
991
|
+
this._timeRangeDate = null;
|
|
992
|
+
this._timeRangeStart = null;
|
|
993
|
+
this._timeRangeEnd = null;
|
|
994
|
+
this._timeRangeComplete = false;
|
|
995
|
+
}
|
|
860
996
|
this.engine.handleDateClick(date, dayIndex);
|
|
861
997
|
this.dispatchEvent(
|
|
862
998
|
new CustomEvent("cal-date-select", {
|
|
@@ -872,19 +1008,29 @@ var Kalendly = (() => {
|
|
|
872
1008
|
const action = actionEl.dataset.action;
|
|
873
1009
|
e.stopPropagation();
|
|
874
1010
|
switch (action) {
|
|
875
|
-
case "previous":
|
|
1011
|
+
case "previous": {
|
|
1012
|
+
const vm = this.engine.getViewModel();
|
|
1013
|
+
const tMonth = vm.currentMonth === 0 ? 11 : vm.currentMonth - 1;
|
|
1014
|
+
const tYear = vm.currentMonth === 0 ? vm.currentYear - 1 : vm.currentYear;
|
|
1015
|
+
this.dispatchMonthChange(tYear, tMonth);
|
|
876
1016
|
this.actions.previous();
|
|
877
|
-
this.dispatchMonthChange();
|
|
878
1017
|
break;
|
|
879
|
-
|
|
1018
|
+
}
|
|
1019
|
+
case "next": {
|
|
1020
|
+
const vm = this.engine.getViewModel();
|
|
1021
|
+
const tMonth = vm.currentMonth === 11 ? 0 : vm.currentMonth + 1;
|
|
1022
|
+
const tYear = vm.currentMonth === 11 ? vm.currentYear + 1 : vm.currentYear;
|
|
1023
|
+
this.dispatchMonthChange(tYear, tMonth);
|
|
880
1024
|
this.actions.next();
|
|
881
|
-
this.dispatchMonthChange();
|
|
882
1025
|
break;
|
|
883
|
-
|
|
1026
|
+
}
|
|
1027
|
+
case "today": {
|
|
1028
|
+
const t = /* @__PURE__ */ new Date();
|
|
1029
|
+
this.dispatchMonthChange(t.getFullYear(), t.getMonth());
|
|
884
1030
|
this.actions.goToToday();
|
|
885
1031
|
this.pickerOpen = false;
|
|
886
|
-
this.dispatchMonthChange();
|
|
887
1032
|
break;
|
|
1033
|
+
}
|
|
888
1034
|
case "toggle-picker":
|
|
889
1035
|
this.pickerOpen = !this.pickerOpen;
|
|
890
1036
|
if (this.pickerOpen) {
|
|
@@ -899,8 +1045,8 @@ var Kalendly = (() => {
|
|
|
899
1045
|
const newYear = vm.currentYear - 1;
|
|
900
1046
|
this.yearInput = String(newYear);
|
|
901
1047
|
this.updatePickerYear(newYear);
|
|
1048
|
+
this.dispatchMonthChange(newYear, vm.currentMonth);
|
|
902
1049
|
this.actions.jump(newYear, vm.currentMonth);
|
|
903
|
-
this.dispatchMonthChange();
|
|
904
1050
|
}
|
|
905
1051
|
break;
|
|
906
1052
|
}
|
|
@@ -910,21 +1056,60 @@ var Kalendly = (() => {
|
|
|
910
1056
|
const newYear = vm.currentYear + 1;
|
|
911
1057
|
this.yearInput = String(newYear);
|
|
912
1058
|
this.updatePickerYear(newYear);
|
|
1059
|
+
this.dispatchMonthChange(newYear, vm.currentMonth);
|
|
913
1060
|
this.actions.jump(newYear, vm.currentMonth);
|
|
914
|
-
this.dispatchMonthChange();
|
|
915
1061
|
}
|
|
916
1062
|
break;
|
|
917
1063
|
}
|
|
918
1064
|
case "select-month": {
|
|
919
1065
|
const month = parseInt(actionEl.dataset.month || "0", 10);
|
|
920
|
-
this.
|
|
1066
|
+
const year = this.engine.getViewModel().currentYear;
|
|
1067
|
+
this.dispatchMonthChange(year, month);
|
|
1068
|
+
this.actions.jump(year, month);
|
|
921
1069
|
this.pickerOpen = false;
|
|
922
|
-
this.dispatchMonthChange();
|
|
923
1070
|
break;
|
|
924
1071
|
}
|
|
925
1072
|
case "close-popup":
|
|
1073
|
+
this._timeRangeDate = null;
|
|
1074
|
+
this._timeRangeStart = null;
|
|
1075
|
+
this._timeRangeEnd = null;
|
|
1076
|
+
this._timeRangeComplete = false;
|
|
926
1077
|
this.engine.clearSelection();
|
|
927
1078
|
break;
|
|
1079
|
+
case "select-slot": {
|
|
1080
|
+
const slotStart = actionEl.dataset.startTime;
|
|
1081
|
+
const slotEnd = actionEl.dataset.endTime;
|
|
1082
|
+
const slotDate = new Date(actionEl.dataset.date);
|
|
1083
|
+
if (this._timeRangeComplete) {
|
|
1084
|
+
this._timeRangeDate = slotDate;
|
|
1085
|
+
this._timeRangeStart = slotStart;
|
|
1086
|
+
this._timeRangeEnd = slotEnd;
|
|
1087
|
+
this._timeRangeComplete = false;
|
|
1088
|
+
} else if (this._timeRangeStart === null) {
|
|
1089
|
+
this._timeRangeDate = slotDate;
|
|
1090
|
+
this._timeRangeStart = slotStart;
|
|
1091
|
+
this._timeRangeEnd = slotEnd;
|
|
1092
|
+
} else {
|
|
1093
|
+
const newStart = slotStart < this._timeRangeStart ? slotStart : this._timeRangeStart;
|
|
1094
|
+
const newEnd = slotEnd > this._timeRangeEnd ? slotEnd : this._timeRangeEnd;
|
|
1095
|
+
this._timeRangeStart = newStart;
|
|
1096
|
+
this._timeRangeEnd = newEnd;
|
|
1097
|
+
this._timeRangeComplete = true;
|
|
1098
|
+
}
|
|
1099
|
+
this.render();
|
|
1100
|
+
this.dispatchEvent(
|
|
1101
|
+
new CustomEvent("cal-availability-select", {
|
|
1102
|
+
bubbles: true,
|
|
1103
|
+
composed: true,
|
|
1104
|
+
detail: {
|
|
1105
|
+
date: this._timeRangeDate,
|
|
1106
|
+
startTime: this._timeRangeStart,
|
|
1107
|
+
endTime: this._timeRangeEnd
|
|
1108
|
+
}
|
|
1109
|
+
})
|
|
1110
|
+
);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
928
1113
|
}
|
|
929
1114
|
};
|
|
930
1115
|
this.delegatedInputHandler = (e) => {
|
|
@@ -949,8 +1134,8 @@ var Kalendly = (() => {
|
|
|
949
1134
|
target.value = this.yearInput;
|
|
950
1135
|
target.classList.remove("invalid");
|
|
951
1136
|
} else if (year !== vm.currentYear) {
|
|
1137
|
+
this.dispatchMonthChange(year, vm.currentMonth);
|
|
952
1138
|
this.actions.jump(year, vm.currentMonth);
|
|
953
|
-
this.dispatchMonthChange();
|
|
954
1139
|
}
|
|
955
1140
|
};
|
|
956
1141
|
this.delegatedKeydownHandler = (e) => {
|
|
@@ -989,14 +1174,17 @@ var Kalendly = (() => {
|
|
|
989
1174
|
if (prevBtn) prevBtn.disabled = year <= this.minYear;
|
|
990
1175
|
if (nextBtn) nextBtn.disabled = year >= this.maxYear;
|
|
991
1176
|
}
|
|
992
|
-
dispatchMonthChange() {
|
|
1177
|
+
dispatchMonthChange(year, month) {
|
|
993
1178
|
if (!this.engine) return;
|
|
994
1179
|
const vm = this.engine.getViewModel();
|
|
995
1180
|
this.dispatchEvent(
|
|
996
1181
|
new CustomEvent("cal-month-change", {
|
|
997
1182
|
bubbles: true,
|
|
998
1183
|
composed: true,
|
|
999
|
-
detail: {
|
|
1184
|
+
detail: {
|
|
1185
|
+
year: year ?? vm.currentYear,
|
|
1186
|
+
month: month ?? vm.currentMonth
|
|
1187
|
+
}
|
|
1000
1188
|
})
|
|
1001
1189
|
);
|
|
1002
1190
|
}
|
|
@@ -1011,7 +1199,10 @@ var Kalendly = (() => {
|
|
|
1011
1199
|
return this.engine?.getViewModel().selectedDate ?? null;
|
|
1012
1200
|
}
|
|
1013
1201
|
goToDate(date) {
|
|
1014
|
-
|
|
1202
|
+
const year = date.getFullYear();
|
|
1203
|
+
const month = date.getMonth();
|
|
1204
|
+
this.dispatchMonthChange(year, month);
|
|
1205
|
+
this.actions?.jump(year, month);
|
|
1015
1206
|
}
|
|
1016
1207
|
getEngine() {
|
|
1017
1208
|
if (!this.engine)
|
|
@@ -1025,7 +1216,10 @@ var Kalendly = (() => {
|
|
|
1025
1216
|
"max-year",
|
|
1026
1217
|
"week-starts-on",
|
|
1027
1218
|
"title",
|
|
1028
|
-
"use-short-month-names"
|
|
1219
|
+
"use-short-month-names",
|
|
1220
|
+
"availability-mode",
|
|
1221
|
+
"selectable",
|
|
1222
|
+
"loading"
|
|
1029
1223
|
];
|
|
1030
1224
|
function defineCalendarElement(tagName = "kal-calendar") {
|
|
1031
1225
|
if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
|