kalendly 0.3.0 → 0.3.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 +104 -19
- package/dist/core/index.d.mts +10 -1
- package/dist/core/index.d.ts +10 -1
- package/dist/core/index.js +99 -5
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +91 -5
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +165 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +157 -29
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +148 -28
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +16 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -31,12 +31,28 @@ 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 eventCoversDate(event, date) {
|
|
35
|
+
const start = normalizeDate(new Date(event.date)).getTime();
|
|
36
|
+
const target = normalizeDate(date).getTime();
|
|
37
|
+
if (Number.isNaN(start)) return false;
|
|
38
|
+
if (event.endDate === void 0 || event.endDate === null) {
|
|
39
|
+
return start === target;
|
|
40
|
+
}
|
|
41
|
+
const end = normalizeDate(new Date(event.endDate)).getTime();
|
|
42
|
+
if (Number.isNaN(end)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`<kal-calendar> event ${event.id} has an unreadable endDate: ${String(event.endDate)}.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (end < start) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`<kal-calendar> event ${event.id} has an endDate before its date: ${String(event.endDate)} < ${String(event.date)}.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return target >= start && target <= end;
|
|
53
|
+
}
|
|
34
54
|
function getEventsForDate(events, date) {
|
|
35
|
-
|
|
36
|
-
return events.filter((event) => {
|
|
37
|
-
const eventDate = normalizeDate(new Date(event.date));
|
|
38
|
-
return eventDate.getTime() === normalizedTargetDate.getTime();
|
|
39
|
-
});
|
|
55
|
+
return events.filter((event) => eventCoversDate(event, date));
|
|
40
56
|
}
|
|
41
57
|
function hasEvents(events, date) {
|
|
42
58
|
return getEventsForDate(events, date).length > 0;
|
|
@@ -180,7 +196,67 @@ function safeColor(value) {
|
|
|
180
196
|
const trimmed = String(value).trim();
|
|
181
197
|
return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
|
|
182
198
|
}
|
|
183
|
-
|
|
199
|
+
function parseTimeToMinutes(time) {
|
|
200
|
+
if (typeof time !== "string") return null;
|
|
201
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
|
|
202
|
+
if (!match) return null;
|
|
203
|
+
const hours = Number(match[1]);
|
|
204
|
+
const mins = Number(match[2]);
|
|
205
|
+
if (hours > 24 || mins > 59 || hours === 24 && mins > 0) return null;
|
|
206
|
+
return hours * 60 + mins;
|
|
207
|
+
}
|
|
208
|
+
function formatMinutes(total) {
|
|
209
|
+
const hours = Math.floor(total / 60);
|
|
210
|
+
const mins = total % 60;
|
|
211
|
+
return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
|
|
212
|
+
}
|
|
213
|
+
function eventInterval(event, slotDuration, onDay) {
|
|
214
|
+
const day = normalizeDate(onDay).getTime();
|
|
215
|
+
if (Number.isNaN(day)) return null;
|
|
216
|
+
const base = day / 6e4;
|
|
217
|
+
const wholeDay = [base, base + MINUTES_PER_DAY];
|
|
218
|
+
if (event.allDay || !event.startTime) return wholeDay;
|
|
219
|
+
const from = parseTimeToMinutes(event.startTime);
|
|
220
|
+
if (from === null) return wholeDay;
|
|
221
|
+
if (event.endTime === void 0 || event.endTime === null) {
|
|
222
|
+
return [base + from, base + from + slotDuration];
|
|
223
|
+
}
|
|
224
|
+
const to = parseTimeToMinutes(event.endTime);
|
|
225
|
+
if (to === null) return wholeDay;
|
|
226
|
+
return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];
|
|
227
|
+
}
|
|
228
|
+
function mergeIntervals(intervals) {
|
|
229
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
230
|
+
return sorted.reduce((merged, [start, end]) => {
|
|
231
|
+
const last = merged[merged.length - 1];
|
|
232
|
+
if (last && start <= last[1]) last[1] = Math.max(last[1], end);
|
|
233
|
+
else merged.push([start, end]);
|
|
234
|
+
return merged;
|
|
235
|
+
}, []);
|
|
236
|
+
}
|
|
237
|
+
function bookedSlots(events, date, slotDuration = DEFAULT_SLOT_DURATION) {
|
|
238
|
+
const base = normalizeDate(date).getTime() / 6e4;
|
|
239
|
+
const previousDay = new Date(date);
|
|
240
|
+
previousDay.setDate(previousDay.getDate() - 1);
|
|
241
|
+
const intervals = [];
|
|
242
|
+
for (const event of new Set(events)) {
|
|
243
|
+
for (const day of [previousDay, date]) {
|
|
244
|
+
if (!eventCoversDate(event, day)) continue;
|
|
245
|
+
const interval = eventInterval(event, slotDuration, day);
|
|
246
|
+
if (interval) intervals.push(interval);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const merged = mergeIntervals(intervals);
|
|
250
|
+
return Array.from(
|
|
251
|
+
{ length: Math.floor(MINUTES_PER_DAY / slotDuration) },
|
|
252
|
+
(_, index) => {
|
|
253
|
+
const start = base + index * slotDuration;
|
|
254
|
+
const end = start + slotDuration;
|
|
255
|
+
return merged.some(([from, to]) => start < to && end > from);
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS, HTML_ESCAPES, MINUTES_PER_DAY, DEFAULT_SLOT_DURATION;
|
|
184
260
|
var init_utils = __esm({
|
|
185
261
|
"src/core/utils.ts"() {
|
|
186
262
|
"use strict";
|
|
@@ -236,6 +312,8 @@ var init_utils = __esm({
|
|
|
236
312
|
'"': """,
|
|
237
313
|
"'": "'"
|
|
238
314
|
};
|
|
315
|
+
MINUTES_PER_DAY = 1440;
|
|
316
|
+
DEFAULT_SLOT_DURATION = 60;
|
|
239
317
|
}
|
|
240
318
|
});
|
|
241
319
|
|
|
@@ -649,6 +727,29 @@ var init_CalendarElement = __esm({
|
|
|
649
727
|
}
|
|
650
728
|
return title;
|
|
651
729
|
}
|
|
730
|
+
get slotDuration() {
|
|
731
|
+
const raw = this.getAttribute("slot-duration");
|
|
732
|
+
if (raw === null) return DEFAULT_SLOT_DURATION;
|
|
733
|
+
const minutes = Number(raw);
|
|
734
|
+
if (Number.isInteger(minutes) && minutes > 0 && MINUTES_PER_DAY % minutes === 0) {
|
|
735
|
+
return minutes;
|
|
736
|
+
}
|
|
737
|
+
console.warn(
|
|
738
|
+
`<kal-calendar> slot-duration="${raw}" must be a positive whole number of minutes that divides ${MINUTES_PER_DAY} \u2014 falling back to ${DEFAULT_SLOT_DURATION}.`
|
|
739
|
+
);
|
|
740
|
+
return DEFAULT_SLOT_DURATION;
|
|
741
|
+
}
|
|
742
|
+
warnMissingEndTime(events) {
|
|
743
|
+
for (const event of events) {
|
|
744
|
+
if (!event.startTime || event.endTime) continue;
|
|
745
|
+
const key = String(event.id);
|
|
746
|
+
if (_CalendarElement.warnedMissingEnd.has(key)) continue;
|
|
747
|
+
_CalendarElement.warnedMissingEnd.add(key);
|
|
748
|
+
console.warn(
|
|
749
|
+
`<kal-calendar> event ${key} has startTime but no endTime \u2014 occupying one ${this.slotDuration}-minute slot. Send an endTime to say how long it runs.`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
652
753
|
get monthCount() {
|
|
653
754
|
const requested = Number(this.getAttribute("months") ?? 1);
|
|
654
755
|
if (!Number.isInteger(requested) || requested < 1) return 1;
|
|
@@ -910,42 +1011,39 @@ var init_CalendarElement = __esm({
|
|
|
910
1011
|
const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
|
|
911
1012
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
912
1013
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
913
|
-
const renderTimeGrid = (
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
const startTime = `${String(hour).padStart(2, "0")}:00`;
|
|
928
|
-
const endTime = `${String(hour + 1).padStart(2, "0")}:00`;
|
|
929
|
-
const booked = isHourBooked(hour);
|
|
930
|
-
const inTimeRange = !booked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
1014
|
+
const renderTimeGrid = (date) => {
|
|
1015
|
+
const slotDuration = this.slotDuration;
|
|
1016
|
+
const previousDay = new Date(date);
|
|
1017
|
+
previousDay.setDate(previousDay.getDate() - 1);
|
|
1018
|
+
const events = [
|
|
1019
|
+
...this.engine.getEventsForDate(previousDay),
|
|
1020
|
+
...this.engine.getEventsForDate(date)
|
|
1021
|
+
];
|
|
1022
|
+
this.warnMissingEndTime(events);
|
|
1023
|
+
const booked = bookedSlots(events, date, slotDuration);
|
|
1024
|
+
const slots = booked.map((isBooked, index) => {
|
|
1025
|
+
const startTime = formatMinutes(index * slotDuration);
|
|
1026
|
+
const endTime = formatMinutes((index + 1) * slotDuration);
|
|
1027
|
+
const inTimeRange = !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
|
|
931
1028
|
const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
|
|
932
1029
|
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
933
1030
|
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
934
1031
|
const slotClasses = [
|
|
935
1032
|
"time-grid-slot",
|
|
936
|
-
|
|
1033
|
+
isBooked ? "time-grid-slot-blocked" : "time-grid-slot-open",
|
|
937
1034
|
isRangeStart ? "time-grid-slot-range-start" : "",
|
|
938
1035
|
isRangeEnd ? "time-grid-slot-range-end" : "",
|
|
939
1036
|
isInRange ? "time-grid-slot-in-range" : ""
|
|
940
1037
|
].filter(Boolean).join(" ");
|
|
941
|
-
const slotAttrs =
|
|
1038
|
+
const slotAttrs = `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
|
|
942
1039
|
return `
|
|
943
1040
|
<div class="${slotClasses}" ${slotAttrs}>
|
|
944
1041
|
<span class="time-grid-label">${startTime}</span>
|
|
945
|
-
<span class="time-grid-status">${
|
|
1042
|
+
<span class="time-grid-status">${isBooked ? "Booked" : "Available"}</span>
|
|
946
1043
|
</div>`;
|
|
947
1044
|
});
|
|
948
|
-
|
|
1045
|
+
const gridClasses = selectable ? "time-grid time-grid-selectable" : "time-grid";
|
|
1046
|
+
return `<div class="${gridClasses}">${slots.join("")}</div>`;
|
|
949
1047
|
};
|
|
950
1048
|
const multiMonth = viewModel.panes.length > 1;
|
|
951
1049
|
const renderPane = (pane) => `
|
|
@@ -970,6 +1068,12 @@ var init_CalendarElement = __esm({
|
|
|
970
1068
|
${week.map((calendarDate, dayIndex) => {
|
|
971
1069
|
const classes = getCellClasses(calendarDate);
|
|
972
1070
|
const cellAttrs = [];
|
|
1071
|
+
if (viewModel.selectedDate && isSameDay2(
|
|
1072
|
+
calendarDate.date,
|
|
1073
|
+
viewModel.selectedDate
|
|
1074
|
+
)) {
|
|
1075
|
+
classes.push("calendar-cell-selected");
|
|
1076
|
+
}
|
|
973
1077
|
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
974
1078
|
const bucket = this.resolveBucket(
|
|
975
1079
|
calendarDate.date
|
|
@@ -1122,7 +1226,7 @@ var init_CalendarElement = __esm({
|
|
|
1122
1226
|
` : ""}
|
|
1123
1227
|
|
|
1124
1228
|
<div class="events-container">
|
|
1125
|
-
${availabilityMode === "time" ? renderTimeGrid(viewModel.
|
|
1229
|
+
${availabilityMode === "time" ? renderTimeGrid(viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
|
|
1126
1230
|
</div>
|
|
1127
1231
|
</div>
|
|
1128
1232
|
` : ""}
|
|
@@ -1283,6 +1387,20 @@ var init_CalendarElement = __esm({
|
|
|
1283
1387
|
const slotStart = actionEl.dataset.startTime;
|
|
1284
1388
|
const slotEnd = actionEl.dataset.endTime;
|
|
1285
1389
|
const slotDate = new Date(actionEl.dataset.date);
|
|
1390
|
+
const slotBooked = actionEl.dataset.booked === "true";
|
|
1391
|
+
this.dispatchEvent(
|
|
1392
|
+
new CustomEvent("cal-slot-select", {
|
|
1393
|
+
bubbles: true,
|
|
1394
|
+
composed: true,
|
|
1395
|
+
detail: {
|
|
1396
|
+
date: slotDate,
|
|
1397
|
+
startTime: slotStart,
|
|
1398
|
+
endTime: slotEnd,
|
|
1399
|
+
booked: slotBooked
|
|
1400
|
+
}
|
|
1401
|
+
})
|
|
1402
|
+
);
|
|
1403
|
+
if (slotBooked || !this.hasAttribute("selectable")) break;
|
|
1286
1404
|
if (this._timeRangeComplete) {
|
|
1287
1405
|
this._timeRangeDate = slotDate;
|
|
1288
1406
|
this._timeRangeStart = slotStart;
|
|
@@ -1423,6 +1541,7 @@ var init_CalendarElement = __esm({
|
|
|
1423
1541
|
"week-starts-on",
|
|
1424
1542
|
"heading",
|
|
1425
1543
|
"months",
|
|
1544
|
+
"slot-duration",
|
|
1426
1545
|
"title",
|
|
1427
1546
|
"use-short-month-names",
|
|
1428
1547
|
"availability-mode",
|
|
@@ -1474,6 +1593,7 @@ var init_CalendarElement = __esm({
|
|
|
1474
1593
|
badgeTentativeBg: "--calendar-badge-tentative-bg",
|
|
1475
1594
|
badgeTentativeText: "--calendar-badge-tentative-text"
|
|
1476
1595
|
};
|
|
1596
|
+
_CalendarElement.warnedMissingEnd = /* @__PURE__ */ new Set();
|
|
1477
1597
|
_CalendarElement.BUILT_IN_BUCKETS = ["blocked", "conditional", "open"];
|
|
1478
1598
|
_CalendarElement.titleDeprecationWarned = false;
|
|
1479
1599
|
CalendarElement = _CalendarElement;
|
|
@@ -1495,12 +1615,18 @@ export {
|
|
|
1495
1615
|
CalendarEngine,
|
|
1496
1616
|
DAYS,
|
|
1497
1617
|
DEFAULT_CATEGORY_COLORS,
|
|
1618
|
+
DEFAULT_SLOT_DURATION,
|
|
1619
|
+
MINUTES_PER_DAY,
|
|
1498
1620
|
MONTHS,
|
|
1499
1621
|
MONTHS_FULL,
|
|
1622
|
+
bookedSlots,
|
|
1500
1623
|
defineCalendarElement,
|
|
1501
1624
|
escapeHtml,
|
|
1625
|
+
eventCoversDate,
|
|
1626
|
+
eventInterval,
|
|
1502
1627
|
formatAttendees,
|
|
1503
1628
|
formatDateForDisplay,
|
|
1629
|
+
formatMinutes,
|
|
1504
1630
|
formatTimeRange,
|
|
1505
1631
|
generateCalendarDates,
|
|
1506
1632
|
generateYears,
|
|
@@ -1514,7 +1640,9 @@ export {
|
|
|
1514
1640
|
isToday,
|
|
1515
1641
|
isValidHexColor,
|
|
1516
1642
|
mergeCategoryColors,
|
|
1643
|
+
mergeIntervals,
|
|
1517
1644
|
normalizeDate,
|
|
1645
|
+
parseTimeToMinutes,
|
|
1518
1646
|
safeColor,
|
|
1519
1647
|
safeUrl,
|
|
1520
1648
|
slugifyToken,
|