kalendly 0.3.1 → 0.3.2
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 +51 -1
- package/dist/core/index.d.mts +7 -2
- package/dist/core/index.d.ts +7 -2
- package/dist/core/index.js +54 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +51 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +137 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +134 -7
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +131 -7
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +17 -0
- 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();
|
|
@@ -763,6 +811,41 @@ var init_CalendarElement = __esm({
|
|
|
763
811
|
const val = this.getAttribute("max-year");
|
|
764
812
|
return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() + 10;
|
|
765
813
|
}
|
|
814
|
+
boundaryDate(attr) {
|
|
815
|
+
const val = this.getAttribute(attr);
|
|
816
|
+
if (!val) return null;
|
|
817
|
+
const parsed = new Date(val);
|
|
818
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
819
|
+
throw new Error(
|
|
820
|
+
`<kal-calendar> ${attr} is unreadable: "${val}". Use a value Date can parse, the same as initial-date.`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
return parsed;
|
|
824
|
+
}
|
|
825
|
+
get availableDays() {
|
|
826
|
+
const val = this.getAttribute("available-days");
|
|
827
|
+
if (val === null) return null;
|
|
828
|
+
const parts = val.split(",").map((part) => part.trim()).filter(Boolean);
|
|
829
|
+
if (parts.length === 0) {
|
|
830
|
+
throw new Error(
|
|
831
|
+
`<kal-calendar> available-days is empty. Omit the attribute to allow every day, or name at least one weekday.`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
return parts.map((part) => {
|
|
835
|
+
const day = Number(part);
|
|
836
|
+
if (!Number.isInteger(day) || day < 0 || day > 6) {
|
|
837
|
+
throw new Error(
|
|
838
|
+
`<kal-calendar> available-days must list integers 0-6, 0 = Sunday. Got "${part}".`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
return day;
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
get availableHours() {
|
|
845
|
+
const val = this.getAttribute("available-hours");
|
|
846
|
+
if (val === null) return null;
|
|
847
|
+
return parseHourRanges(val, this.slotDuration);
|
|
848
|
+
}
|
|
766
849
|
initEngine() {
|
|
767
850
|
const initialDateAttr = this.getAttribute("initial-date");
|
|
768
851
|
const initialDate = initialDateAttr ? new Date(initialDateAttr) : void 0;
|
|
@@ -881,8 +964,26 @@ var init_CalendarElement = __esm({
|
|
|
881
964
|
if (declared.size === 0) return "open";
|
|
882
965
|
return this.knownBuckets.find((bucket) => declared.has(bucket));
|
|
883
966
|
}
|
|
967
|
+
assertHorizonOrdered() {
|
|
968
|
+
const min = this.boundaryDate("min-date");
|
|
969
|
+
const max = this.boundaryDate("max-date");
|
|
970
|
+
if (min && max && min.getTime() > max.getTime()) {
|
|
971
|
+
throw new Error(
|
|
972
|
+
`<kal-calendar> min-date is after max-date: "${this.getAttribute("min-date")}" > "${this.getAttribute("max-date")}".`
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
// Whether the vendor offers this day at all, before any event is considered.
|
|
977
|
+
isDateOffered(date) {
|
|
978
|
+
return isDateWithinWindow(
|
|
979
|
+
date,
|
|
980
|
+
this.boundaryDate("min-date"),
|
|
981
|
+
this.boundaryDate("max-date")
|
|
982
|
+
) && isDayAllowed(date, this.availableDays);
|
|
983
|
+
}
|
|
884
984
|
isDateSelectable(date) {
|
|
885
985
|
if (!this.engine) return false;
|
|
986
|
+
if (!this.isDateOffered(date)) return false;
|
|
886
987
|
if (this._selectableStatuses) {
|
|
887
988
|
return this._selectableStatuses.includes(this.resolveBucket(date));
|
|
888
989
|
}
|
|
@@ -902,6 +1003,11 @@ var init_CalendarElement = __esm({
|
|
|
902
1003
|
this.assertStatusesDeclared();
|
|
903
1004
|
this.assertStatusesKnown();
|
|
904
1005
|
}
|
|
1006
|
+
this.boundaryDate("min-date");
|
|
1007
|
+
this.boundaryDate("max-date");
|
|
1008
|
+
void this.availableDays;
|
|
1009
|
+
void this.availableHours;
|
|
1010
|
+
this.assertHorizonOrdered();
|
|
905
1011
|
const today = /* @__PURE__ */ new Date();
|
|
906
1012
|
const todayMonth = today.getMonth();
|
|
907
1013
|
const todayYear = today.getFullYear();
|
|
@@ -945,7 +1051,7 @@ var init_CalendarElement = __esm({
|
|
|
945
1051
|
return `
|
|
946
1052
|
<div class="event-card" style="border-left-color: ${borderColor}">
|
|
947
1053
|
<div class="event-header">
|
|
948
|
-
|
|
1054
|
+
${event.name ? `<div class="event-title">${escapeHtml(event.name)}</div>` : ""}
|
|
949
1055
|
<div class="event-badges">
|
|
950
1056
|
${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
|
|
951
1057
|
${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
|
|
@@ -1021,25 +1127,31 @@ var init_CalendarElement = __esm({
|
|
|
1021
1127
|
];
|
|
1022
1128
|
this.warnMissingEndTime(events);
|
|
1023
1129
|
const booked = bookedSlots(events, date, slotDuration);
|
|
1130
|
+
const hours = this.availableHours;
|
|
1024
1131
|
const slots = booked.map((isBooked, index) => {
|
|
1025
|
-
const
|
|
1026
|
-
const
|
|
1027
|
-
const
|
|
1132
|
+
const slotStart = index * slotDuration;
|
|
1133
|
+
const slotEnd = slotStart + slotDuration;
|
|
1134
|
+
const startTime = formatMinutes(slotStart);
|
|
1135
|
+
const endTime = formatMinutes(slotEnd);
|
|
1136
|
+
const offered = hours === null || hours.some(([from, to]) => slotStart >= from && slotEnd <= to);
|
|
1137
|
+
const inTimeRange = offered && !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
1028
1138
|
const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
|
|
1029
1139
|
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
1030
1140
|
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
1031
1141
|
const slotClasses = [
|
|
1032
1142
|
"time-grid-slot",
|
|
1143
|
+
offered ? "" : "time-grid-slot-out-of-range",
|
|
1033
1144
|
isBooked ? "time-grid-slot-blocked" : "time-grid-slot-open",
|
|
1034
1145
|
isRangeStart ? "time-grid-slot-range-start" : "",
|
|
1035
1146
|
isRangeEnd ? "time-grid-slot-range-end" : "",
|
|
1036
1147
|
isInRange ? "time-grid-slot-in-range" : ""
|
|
1037
1148
|
].filter(Boolean).join(" ");
|
|
1038
|
-
const slotAttrs =
|
|
1149
|
+
const slotAttrs = `${offered ? 'data-action="select-slot" ' : ""}data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
|
|
1150
|
+
const status = !offered ? "Closed" : isBooked ? "Booked" : "Available";
|
|
1039
1151
|
return `
|
|
1040
1152
|
<div class="${slotClasses}" ${slotAttrs}>
|
|
1041
1153
|
<span class="time-grid-label">${startTime}</span>
|
|
1042
|
-
<span class="time-grid-status">${
|
|
1154
|
+
<span class="time-grid-status">${status}</span>
|
|
1043
1155
|
</div>`;
|
|
1044
1156
|
});
|
|
1045
1157
|
const gridClasses = selectable ? "time-grid time-grid-selectable" : "time-grid";
|
|
@@ -1107,12 +1219,18 @@ var init_CalendarElement = __esm({
|
|
|
1107
1219
|
}
|
|
1108
1220
|
}
|
|
1109
1221
|
const dateString = calendarDate.date.toISOString();
|
|
1222
|
+
const offered = this.isDateOffered(
|
|
1223
|
+
calendarDate.date
|
|
1224
|
+
);
|
|
1225
|
+
if (!offered) {
|
|
1226
|
+
classes.push("calendar-cell-out-of-range");
|
|
1227
|
+
}
|
|
1110
1228
|
return `
|
|
1111
1229
|
<td
|
|
1112
1230
|
class="${classes.join(" ")}"
|
|
1113
1231
|
data-date="${dateString}"
|
|
1114
1232
|
data-day-index="${dayIndex}"
|
|
1115
|
-
data-clickable="true"
|
|
1233
|
+
${offered ? 'data-clickable="true"' : ""}
|
|
1116
1234
|
${cellAttrs.join(" ")}
|
|
1117
1235
|
>
|
|
1118
1236
|
${calendarDate.date.getDate()}
|
|
@@ -1538,6 +1656,10 @@ var init_CalendarElement = __esm({
|
|
|
1538
1656
|
"initial-date",
|
|
1539
1657
|
"min-year",
|
|
1540
1658
|
"max-year",
|
|
1659
|
+
"min-date",
|
|
1660
|
+
"max-date",
|
|
1661
|
+
"available-days",
|
|
1662
|
+
"available-hours",
|
|
1541
1663
|
"week-starts-on",
|
|
1542
1664
|
"heading",
|
|
1543
1665
|
"months",
|
|
@@ -1559,6 +1681,8 @@ var init_CalendarElement = __esm({
|
|
|
1559
1681
|
borderColor: "--calendar-border-color",
|
|
1560
1682
|
todayOutline: "--calendar-today-outline",
|
|
1561
1683
|
selectedBg: "--calendar-selected-bg",
|
|
1684
|
+
outOfRangeBg: "--calendar-out-of-range-bg",
|
|
1685
|
+
outOfRangeFg: "--calendar-out-of-range-fg",
|
|
1562
1686
|
headerBg: "--calendar-header-bg",
|
|
1563
1687
|
popupBg: "--calendar-popup-bg",
|
|
1564
1688
|
pickerBg: "--calendar-picker-bg",
|
|
@@ -1636,12 +1760,15 @@ export {
|
|
|
1636
1760
|
getEventsForDate,
|
|
1637
1761
|
getMonthYearText,
|
|
1638
1762
|
hasEvents,
|
|
1763
|
+
isDateWithinWindow,
|
|
1764
|
+
isDayAllowed,
|
|
1639
1765
|
isSameDay,
|
|
1640
1766
|
isToday,
|
|
1641
1767
|
isValidHexColor,
|
|
1642
1768
|
mergeCategoryColors,
|
|
1643
1769
|
mergeIntervals,
|
|
1644
1770
|
normalizeDate,
|
|
1771
|
+
parseHourRanges,
|
|
1645
1772
|
parseTimeToMinutes,
|
|
1646
1773
|
safeColor,
|
|
1647
1774
|
safeUrl,
|