kalendly 0.3.1 → 0.3.3
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 +89 -2
- package/dist/core/index.d.mts +20 -3
- package/dist/core/index.d.ts +20 -3
- package/dist/core/index.js +61 -5
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +58 -5
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +157 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +154 -15
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +151 -15
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +39 -11
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -31,6 +31,54 @@ function generateYears(minYear, maxYear) {
|
|
|
31
31
|
const max = maxYear ?? currentYear + 10;
|
|
32
32
|
return Array.from({ length: max - min + 1 }, (_, i) => min + i);
|
|
33
33
|
}
|
|
34
|
+
function parseHourRanges(value, slotDuration) {
|
|
35
|
+
const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
36
|
+
if (parts.length === 0) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`<kal-calendar> available-hours is empty. Omit the attribute to allow every hour, or name at least one HH:MM-HH:MM range.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const ranges = parts.map((part) => {
|
|
42
|
+
const halves = part.split("-");
|
|
43
|
+
if (halves.length !== 2) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`<kal-calendar> available-hours range "${part}" is not HH:MM-HH:MM.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const [start, end] = halves.map((half) => parseTimeToMinutes(half.trim()));
|
|
49
|
+
if (start === null || end === null) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`<kal-calendar> available-hours range "${part}" has an unreadable time. Use 24-hour HH:MM.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (start >= end) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`<kal-calendar> available-hours range "${part}" starts at or after it ends.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (start % slotDuration !== 0 || end % slotDuration !== 0) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`<kal-calendar> available-hours range "${part}" does not land on a ${slotDuration}-minute slot boundary.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return [start, end];
|
|
65
|
+
});
|
|
66
|
+
if (mergeIntervals(ranges).length !== ranges.length) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`<kal-calendar> available-hours ranges overlap or touch: "${value}". Write each bookable window once.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return ranges;
|
|
72
|
+
}
|
|
73
|
+
function isDateWithinWindow(date, min, max) {
|
|
74
|
+
const target = normalizeDate(date).getTime();
|
|
75
|
+
if (min && target < normalizeDate(min).getTime()) return false;
|
|
76
|
+
if (max && target > normalizeDate(max).getTime()) return false;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
function isDayAllowed(date, days) {
|
|
80
|
+
return days === null || days.includes(date.getDay());
|
|
81
|
+
}
|
|
34
82
|
function eventCoversDate(event, date) {
|
|
35
83
|
const start = normalizeDate(new Date(event.date)).getTime();
|
|
36
84
|
const target = normalizeDate(date).getTime();
|
|
@@ -472,12 +520,14 @@ var init_calendar_engine = __esm({
|
|
|
472
520
|
/**
|
|
473
521
|
* Select a specific date
|
|
474
522
|
*/
|
|
475
|
-
selectDate(date, dayIndex) {
|
|
523
|
+
selectDate(date, dayIndex, navigate = true) {
|
|
476
524
|
this.state.selectedDate = date;
|
|
477
525
|
this.state.selectedDayIndex = dayIndex ?? null;
|
|
478
526
|
this.state.currentDate = date.getDate();
|
|
479
|
-
|
|
480
|
-
|
|
527
|
+
if (navigate) {
|
|
528
|
+
this.state.currentMonth = date.getMonth();
|
|
529
|
+
this.state.currentYear = date.getFullYear();
|
|
530
|
+
}
|
|
481
531
|
this.updateTasks();
|
|
482
532
|
this.notify();
|
|
483
533
|
}
|
|
@@ -505,8 +555,8 @@ var init_calendar_engine = __esm({
|
|
|
505
555
|
/**
|
|
506
556
|
* Handle date cell click
|
|
507
557
|
*/
|
|
508
|
-
handleDateClick(date, dayIndex) {
|
|
509
|
-
this.selectDate(date, dayIndex);
|
|
558
|
+
handleDateClick(date, dayIndex, options) {
|
|
559
|
+
this.selectDate(date, dayIndex, options?.navigate !== false);
|
|
510
560
|
}
|
|
511
561
|
/**
|
|
512
562
|
* Check if date has events
|
|
@@ -763,6 +813,41 @@ var init_CalendarElement = __esm({
|
|
|
763
813
|
const val = this.getAttribute("max-year");
|
|
764
814
|
return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() + 10;
|
|
765
815
|
}
|
|
816
|
+
boundaryDate(attr) {
|
|
817
|
+
const val = this.getAttribute(attr);
|
|
818
|
+
if (!val) return null;
|
|
819
|
+
const parsed = new Date(val);
|
|
820
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
821
|
+
throw new Error(
|
|
822
|
+
`<kal-calendar> ${attr} is unreadable: "${val}". Use a value Date can parse, the same as initial-date.`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
return parsed;
|
|
826
|
+
}
|
|
827
|
+
get availableDays() {
|
|
828
|
+
const val = this.getAttribute("available-days");
|
|
829
|
+
if (val === null) return null;
|
|
830
|
+
const parts = val.split(",").map((part) => part.trim()).filter(Boolean);
|
|
831
|
+
if (parts.length === 0) {
|
|
832
|
+
throw new Error(
|
|
833
|
+
`<kal-calendar> available-days is empty. Omit the attribute to allow every day, or name at least one weekday.`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
return parts.map((part) => {
|
|
837
|
+
const day = Number(part);
|
|
838
|
+
if (!Number.isInteger(day) || day < 0 || day > 6) {
|
|
839
|
+
throw new Error(
|
|
840
|
+
`<kal-calendar> available-days must list integers 0-6, 0 = Sunday. Got "${part}".`
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
return day;
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
get availableHours() {
|
|
847
|
+
const val = this.getAttribute("available-hours");
|
|
848
|
+
if (val === null) return null;
|
|
849
|
+
return parseHourRanges(val, this.slotDuration);
|
|
850
|
+
}
|
|
766
851
|
initEngine() {
|
|
767
852
|
const initialDateAttr = this.getAttribute("initial-date");
|
|
768
853
|
const initialDate = initialDateAttr ? new Date(initialDateAttr) : void 0;
|
|
@@ -881,8 +966,26 @@ var init_CalendarElement = __esm({
|
|
|
881
966
|
if (declared.size === 0) return "open";
|
|
882
967
|
return this.knownBuckets.find((bucket) => declared.has(bucket));
|
|
883
968
|
}
|
|
969
|
+
assertHorizonOrdered() {
|
|
970
|
+
const min = this.boundaryDate("min-date");
|
|
971
|
+
const max = this.boundaryDate("max-date");
|
|
972
|
+
if (min && max && min.getTime() > max.getTime()) {
|
|
973
|
+
throw new Error(
|
|
974
|
+
`<kal-calendar> min-date is after max-date: "${this.getAttribute("min-date")}" > "${this.getAttribute("max-date")}".`
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
// Whether the vendor offers this day at all, before any event is considered.
|
|
979
|
+
isDateOffered(date) {
|
|
980
|
+
return isDateWithinWindow(
|
|
981
|
+
date,
|
|
982
|
+
this.boundaryDate("min-date"),
|
|
983
|
+
this.boundaryDate("max-date")
|
|
984
|
+
) && isDayAllowed(date, this.availableDays);
|
|
985
|
+
}
|
|
884
986
|
isDateSelectable(date) {
|
|
885
987
|
if (!this.engine) return false;
|
|
988
|
+
if (!this.isDateOffered(date)) return false;
|
|
886
989
|
if (this._selectableStatuses) {
|
|
887
990
|
return this._selectableStatuses.includes(this.resolveBucket(date));
|
|
888
991
|
}
|
|
@@ -902,6 +1005,11 @@ var init_CalendarElement = __esm({
|
|
|
902
1005
|
this.assertStatusesDeclared();
|
|
903
1006
|
this.assertStatusesKnown();
|
|
904
1007
|
}
|
|
1008
|
+
this.boundaryDate("min-date");
|
|
1009
|
+
this.boundaryDate("max-date");
|
|
1010
|
+
void this.availableDays;
|
|
1011
|
+
void this.availableHours;
|
|
1012
|
+
this.assertHorizonOrdered();
|
|
905
1013
|
const today = /* @__PURE__ */ new Date();
|
|
906
1014
|
const todayMonth = today.getMonth();
|
|
907
1015
|
const todayYear = today.getFullYear();
|
|
@@ -945,7 +1053,7 @@ var init_CalendarElement = __esm({
|
|
|
945
1053
|
return `
|
|
946
1054
|
<div class="event-card" style="border-left-color: ${borderColor}">
|
|
947
1055
|
<div class="event-header">
|
|
948
|
-
|
|
1056
|
+
${event.name ? `<div class="event-title">${escapeHtml(event.name)}</div>` : ""}
|
|
949
1057
|
<div class="event-badges">
|
|
950
1058
|
${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
|
|
951
1059
|
${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
|
|
@@ -1021,25 +1129,31 @@ var init_CalendarElement = __esm({
|
|
|
1021
1129
|
];
|
|
1022
1130
|
this.warnMissingEndTime(events);
|
|
1023
1131
|
const booked = bookedSlots(events, date, slotDuration);
|
|
1132
|
+
const hours = this.availableHours;
|
|
1024
1133
|
const slots = booked.map((isBooked, index) => {
|
|
1025
|
-
const
|
|
1026
|
-
const
|
|
1027
|
-
const
|
|
1134
|
+
const slotStart = index * slotDuration;
|
|
1135
|
+
const slotEnd = slotStart + slotDuration;
|
|
1136
|
+
const startTime = formatMinutes(slotStart);
|
|
1137
|
+
const endTime = formatMinutes(slotEnd);
|
|
1138
|
+
const offered = hours === null || hours.some(([from, to]) => slotStart >= from && slotEnd <= to);
|
|
1139
|
+
const inTimeRange = offered && !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
1028
1140
|
const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
|
|
1029
1141
|
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
1030
1142
|
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
1031
1143
|
const slotClasses = [
|
|
1032
1144
|
"time-grid-slot",
|
|
1145
|
+
offered ? "" : "time-grid-slot-out-of-range",
|
|
1033
1146
|
isBooked ? "time-grid-slot-blocked" : "time-grid-slot-open",
|
|
1034
1147
|
isRangeStart ? "time-grid-slot-range-start" : "",
|
|
1035
1148
|
isRangeEnd ? "time-grid-slot-range-end" : "",
|
|
1036
1149
|
isInRange ? "time-grid-slot-in-range" : ""
|
|
1037
1150
|
].filter(Boolean).join(" ");
|
|
1038
|
-
const slotAttrs =
|
|
1151
|
+
const slotAttrs = `${offered ? 'data-action="select-slot" ' : ""}data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
|
|
1152
|
+
const status = !offered ? "Closed" : isBooked ? "Booked" : "Available";
|
|
1039
1153
|
return `
|
|
1040
1154
|
<div class="${slotClasses}" ${slotAttrs}>
|
|
1041
1155
|
<span class="time-grid-label">${startTime}</span>
|
|
1042
|
-
<span class="time-grid-status">${
|
|
1156
|
+
<span class="time-grid-status">${status}</span>
|
|
1043
1157
|
</div>`;
|
|
1044
1158
|
});
|
|
1045
1159
|
const gridClasses = selectable ? "time-grid time-grid-selectable" : "time-grid";
|
|
@@ -1074,7 +1188,7 @@ var init_CalendarElement = __esm({
|
|
|
1074
1188
|
)) {
|
|
1075
1189
|
classes.push("calendar-cell-selected");
|
|
1076
1190
|
}
|
|
1077
|
-
if (availabilityMode
|
|
1191
|
+
if (availabilityMode) {
|
|
1078
1192
|
const bucket = this.resolveBucket(
|
|
1079
1193
|
calendarDate.date
|
|
1080
1194
|
);
|
|
@@ -1095,7 +1209,7 @@ var init_CalendarElement = __esm({
|
|
|
1095
1209
|
`aria-label="${escapeHtml(bucket)}"`
|
|
1096
1210
|
);
|
|
1097
1211
|
}
|
|
1098
|
-
if (availabilityMode === "day" && selectable
|
|
1212
|
+
if (availabilityMode === "day" && selectable) {
|
|
1099
1213
|
const d = calendarDate.date;
|
|
1100
1214
|
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
1101
1215
|
classes.push("availability-range-start");
|
|
@@ -1107,12 +1221,18 @@ var init_CalendarElement = __esm({
|
|
|
1107
1221
|
}
|
|
1108
1222
|
}
|
|
1109
1223
|
const dateString = calendarDate.date.toISOString();
|
|
1224
|
+
const offered = this.isDateOffered(
|
|
1225
|
+
calendarDate.date
|
|
1226
|
+
);
|
|
1227
|
+
if (!offered) {
|
|
1228
|
+
classes.push("calendar-cell-out-of-range");
|
|
1229
|
+
}
|
|
1110
1230
|
return `
|
|
1111
1231
|
<td
|
|
1112
1232
|
class="${classes.join(" ")}"
|
|
1113
1233
|
data-date="${dateString}"
|
|
1114
1234
|
data-day-index="${dayIndex}"
|
|
1115
|
-
data-clickable="true"
|
|
1235
|
+
${offered ? 'data-clickable="true"' : ""}
|
|
1116
1236
|
${cellAttrs.join(" ")}
|
|
1117
1237
|
>
|
|
1118
1238
|
${calendarDate.date.getDate()}
|
|
@@ -1300,7 +1420,7 @@ var init_CalendarElement = __esm({
|
|
|
1300
1420
|
this._timeRangeEnd = null;
|
|
1301
1421
|
this._timeRangeComplete = false;
|
|
1302
1422
|
}
|
|
1303
|
-
this.engine.handleDateClick(date, dayIndex);
|
|
1423
|
+
this.engine.handleDateClick(date, dayIndex, { navigate: false });
|
|
1304
1424
|
this.dispatchEvent(
|
|
1305
1425
|
new CustomEvent("cal-date-select", {
|
|
1306
1426
|
bubbles: true,
|
|
@@ -1538,6 +1658,10 @@ var init_CalendarElement = __esm({
|
|
|
1538
1658
|
"initial-date",
|
|
1539
1659
|
"min-year",
|
|
1540
1660
|
"max-year",
|
|
1661
|
+
"min-date",
|
|
1662
|
+
"max-date",
|
|
1663
|
+
"available-days",
|
|
1664
|
+
"available-hours",
|
|
1541
1665
|
"week-starts-on",
|
|
1542
1666
|
"heading",
|
|
1543
1667
|
"months",
|
|
@@ -1559,6 +1683,18 @@ var init_CalendarElement = __esm({
|
|
|
1559
1683
|
borderColor: "--calendar-border-color",
|
|
1560
1684
|
todayOutline: "--calendar-today-outline",
|
|
1561
1685
|
selectedBg: "--calendar-selected-bg",
|
|
1686
|
+
outOfRangeBg: "--calendar-out-of-range-bg",
|
|
1687
|
+
outOfRangeFg: "--calendar-out-of-range-fg",
|
|
1688
|
+
navArrowFg: "--calendar-nav-arrow-fg",
|
|
1689
|
+
navArrowBg: "--calendar-nav-arrow-bg",
|
|
1690
|
+
navArrowBorder: "--calendar-nav-arrow-border",
|
|
1691
|
+
navArrowHoverFg: "--calendar-nav-arrow-hover-fg",
|
|
1692
|
+
navArrowHoverBg: "--calendar-nav-arrow-hover-bg",
|
|
1693
|
+
inputInvalidBg: "--calendar-input-invalid-bg",
|
|
1694
|
+
popupHeaderFg: "--calendar-popup-header-fg",
|
|
1695
|
+
popupCloseFg: "--calendar-popup-close-fg",
|
|
1696
|
+
popupCloseBg: "--calendar-popup-close-bg",
|
|
1697
|
+
popupCloseHoverBg: "--calendar-popup-close-hover-bg",
|
|
1562
1698
|
headerBg: "--calendar-header-bg",
|
|
1563
1699
|
popupBg: "--calendar-popup-bg",
|
|
1564
1700
|
pickerBg: "--calendar-picker-bg",
|
|
@@ -1636,12 +1772,15 @@ export {
|
|
|
1636
1772
|
getEventsForDate,
|
|
1637
1773
|
getMonthYearText,
|
|
1638
1774
|
hasEvents,
|
|
1775
|
+
isDateWithinWindow,
|
|
1776
|
+
isDayAllowed,
|
|
1639
1777
|
isSameDay,
|
|
1640
1778
|
isToday,
|
|
1641
1779
|
isValidHexColor,
|
|
1642
1780
|
mergeCategoryColors,
|
|
1643
1781
|
mergeIntervals,
|
|
1644
1782
|
normalizeDate,
|
|
1783
|
+
parseHourRanges,
|
|
1645
1784
|
parseTimeToMinutes,
|
|
1646
1785
|
safeColor,
|
|
1647
1786
|
safeUrl,
|