kalendly 0.3.0 → 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/dist/index.js CHANGED
@@ -43,12 +43,76 @@ function generateYears(minYear, maxYear) {
43
43
  const max = maxYear ?? currentYear + 10;
44
44
  return Array.from({ length: max - min + 1 }, (_, i) => min + i);
45
45
  }
46
- function getEventsForDate(events, date) {
47
- const normalizedTargetDate = normalizeDate(date);
48
- return events.filter((event) => {
49
- const eventDate = normalizeDate(new Date(event.date));
50
- return eventDate.getTime() === normalizedTargetDate.getTime();
46
+ function parseHourRanges(value, slotDuration) {
47
+ const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
48
+ if (parts.length === 0) {
49
+ throw new Error(
50
+ `<kal-calendar> available-hours is empty. Omit the attribute to allow every hour, or name at least one HH:MM-HH:MM range.`
51
+ );
52
+ }
53
+ const ranges = parts.map((part) => {
54
+ const halves = part.split("-");
55
+ if (halves.length !== 2) {
56
+ throw new Error(
57
+ `<kal-calendar> available-hours range "${part}" is not HH:MM-HH:MM.`
58
+ );
59
+ }
60
+ const [start, end] = halves.map((half) => parseTimeToMinutes(half.trim()));
61
+ if (start === null || end === null) {
62
+ throw new Error(
63
+ `<kal-calendar> available-hours range "${part}" has an unreadable time. Use 24-hour HH:MM.`
64
+ );
65
+ }
66
+ if (start >= end) {
67
+ throw new Error(
68
+ `<kal-calendar> available-hours range "${part}" starts at or after it ends.`
69
+ );
70
+ }
71
+ if (start % slotDuration !== 0 || end % slotDuration !== 0) {
72
+ throw new Error(
73
+ `<kal-calendar> available-hours range "${part}" does not land on a ${slotDuration}-minute slot boundary.`
74
+ );
75
+ }
76
+ return [start, end];
51
77
  });
78
+ if (mergeIntervals(ranges).length !== ranges.length) {
79
+ throw new Error(
80
+ `<kal-calendar> available-hours ranges overlap or touch: "${value}". Write each bookable window once.`
81
+ );
82
+ }
83
+ return ranges;
84
+ }
85
+ function isDateWithinWindow(date, min, max) {
86
+ const target = normalizeDate(date).getTime();
87
+ if (min && target < normalizeDate(min).getTime()) return false;
88
+ if (max && target > normalizeDate(max).getTime()) return false;
89
+ return true;
90
+ }
91
+ function isDayAllowed(date, days) {
92
+ return days === null || days.includes(date.getDay());
93
+ }
94
+ function eventCoversDate(event, date) {
95
+ const start = normalizeDate(new Date(event.date)).getTime();
96
+ const target = normalizeDate(date).getTime();
97
+ if (Number.isNaN(start)) return false;
98
+ if (event.endDate === void 0 || event.endDate === null) {
99
+ return start === target;
100
+ }
101
+ const end = normalizeDate(new Date(event.endDate)).getTime();
102
+ if (Number.isNaN(end)) {
103
+ throw new Error(
104
+ `<kal-calendar> event ${event.id} has an unreadable endDate: ${String(event.endDate)}.`
105
+ );
106
+ }
107
+ if (end < start) {
108
+ throw new Error(
109
+ `<kal-calendar> event ${event.id} has an endDate before its date: ${String(event.endDate)} < ${String(event.date)}.`
110
+ );
111
+ }
112
+ return target >= start && target <= end;
113
+ }
114
+ function getEventsForDate(events, date) {
115
+ return events.filter((event) => eventCoversDate(event, date));
52
116
  }
53
117
  function hasEvents(events, date) {
54
118
  return getEventsForDate(events, date).length > 0;
@@ -192,7 +256,67 @@ function safeColor(value) {
192
256
  const trimmed = String(value).trim();
193
257
  return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
194
258
  }
195
- var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS, HTML_ESCAPES;
259
+ function parseTimeToMinutes(time) {
260
+ if (typeof time !== "string") return null;
261
+ const match = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
262
+ if (!match) return null;
263
+ const hours = Number(match[1]);
264
+ const mins = Number(match[2]);
265
+ if (hours > 24 || mins > 59 || hours === 24 && mins > 0) return null;
266
+ return hours * 60 + mins;
267
+ }
268
+ function formatMinutes(total) {
269
+ const hours = Math.floor(total / 60);
270
+ const mins = total % 60;
271
+ return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
272
+ }
273
+ function eventInterval(event, slotDuration, onDay) {
274
+ const day = normalizeDate(onDay).getTime();
275
+ if (Number.isNaN(day)) return null;
276
+ const base = day / 6e4;
277
+ const wholeDay = [base, base + MINUTES_PER_DAY];
278
+ if (event.allDay || !event.startTime) return wholeDay;
279
+ const from = parseTimeToMinutes(event.startTime);
280
+ if (from === null) return wholeDay;
281
+ if (event.endTime === void 0 || event.endTime === null) {
282
+ return [base + from, base + from + slotDuration];
283
+ }
284
+ const to = parseTimeToMinutes(event.endTime);
285
+ if (to === null) return wholeDay;
286
+ return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];
287
+ }
288
+ function mergeIntervals(intervals) {
289
+ const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
290
+ return sorted.reduce((merged, [start, end]) => {
291
+ const last = merged[merged.length - 1];
292
+ if (last && start <= last[1]) last[1] = Math.max(last[1], end);
293
+ else merged.push([start, end]);
294
+ return merged;
295
+ }, []);
296
+ }
297
+ function bookedSlots(events, date, slotDuration = DEFAULT_SLOT_DURATION) {
298
+ const base = normalizeDate(date).getTime() / 6e4;
299
+ const previousDay = new Date(date);
300
+ previousDay.setDate(previousDay.getDate() - 1);
301
+ const intervals = [];
302
+ for (const event of new Set(events)) {
303
+ for (const day of [previousDay, date]) {
304
+ if (!eventCoversDate(event, day)) continue;
305
+ const interval = eventInterval(event, slotDuration, day);
306
+ if (interval) intervals.push(interval);
307
+ }
308
+ }
309
+ const merged = mergeIntervals(intervals);
310
+ return Array.from(
311
+ { length: Math.floor(MINUTES_PER_DAY / slotDuration) },
312
+ (_, index) => {
313
+ const start = base + index * slotDuration;
314
+ const end = start + slotDuration;
315
+ return merged.some(([from, to]) => start < to && end > from);
316
+ }
317
+ );
318
+ }
319
+ var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS, HTML_ESCAPES, MINUTES_PER_DAY, DEFAULT_SLOT_DURATION;
196
320
  var init_utils = __esm({
197
321
  "src/core/utils.ts"() {
198
322
  "use strict";
@@ -248,6 +372,8 @@ var init_utils = __esm({
248
372
  '"': "&quot;",
249
373
  "'": "&#39;"
250
374
  };
375
+ MINUTES_PER_DAY = 1440;
376
+ DEFAULT_SLOT_DURATION = 60;
251
377
  }
252
378
  });
253
379
 
@@ -661,6 +787,29 @@ var init_CalendarElement = __esm({
661
787
  }
662
788
  return title;
663
789
  }
790
+ get slotDuration() {
791
+ const raw = this.getAttribute("slot-duration");
792
+ if (raw === null) return DEFAULT_SLOT_DURATION;
793
+ const minutes = Number(raw);
794
+ if (Number.isInteger(minutes) && minutes > 0 && MINUTES_PER_DAY % minutes === 0) {
795
+ return minutes;
796
+ }
797
+ console.warn(
798
+ `<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}.`
799
+ );
800
+ return DEFAULT_SLOT_DURATION;
801
+ }
802
+ warnMissingEndTime(events) {
803
+ for (const event of events) {
804
+ if (!event.startTime || event.endTime) continue;
805
+ const key = String(event.id);
806
+ if (_CalendarElement.warnedMissingEnd.has(key)) continue;
807
+ _CalendarElement.warnedMissingEnd.add(key);
808
+ console.warn(
809
+ `<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.`
810
+ );
811
+ }
812
+ }
664
813
  get monthCount() {
665
814
  const requested = Number(this.getAttribute("months") ?? 1);
666
815
  if (!Number.isInteger(requested) || requested < 1) return 1;
@@ -674,6 +823,41 @@ var init_CalendarElement = __esm({
674
823
  const val = this.getAttribute("max-year");
675
824
  return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() + 10;
676
825
  }
826
+ boundaryDate(attr) {
827
+ const val = this.getAttribute(attr);
828
+ if (!val) return null;
829
+ const parsed = new Date(val);
830
+ if (Number.isNaN(parsed.getTime())) {
831
+ throw new Error(
832
+ `<kal-calendar> ${attr} is unreadable: "${val}". Use a value Date can parse, the same as initial-date.`
833
+ );
834
+ }
835
+ return parsed;
836
+ }
837
+ get availableDays() {
838
+ const val = this.getAttribute("available-days");
839
+ if (val === null) return null;
840
+ const parts = val.split(",").map((part) => part.trim()).filter(Boolean);
841
+ if (parts.length === 0) {
842
+ throw new Error(
843
+ `<kal-calendar> available-days is empty. Omit the attribute to allow every day, or name at least one weekday.`
844
+ );
845
+ }
846
+ return parts.map((part) => {
847
+ const day = Number(part);
848
+ if (!Number.isInteger(day) || day < 0 || day > 6) {
849
+ throw new Error(
850
+ `<kal-calendar> available-days must list integers 0-6, 0 = Sunday. Got "${part}".`
851
+ );
852
+ }
853
+ return day;
854
+ });
855
+ }
856
+ get availableHours() {
857
+ const val = this.getAttribute("available-hours");
858
+ if (val === null) return null;
859
+ return parseHourRanges(val, this.slotDuration);
860
+ }
677
861
  initEngine() {
678
862
  const initialDateAttr = this.getAttribute("initial-date");
679
863
  const initialDate = initialDateAttr ? new Date(initialDateAttr) : void 0;
@@ -792,8 +976,26 @@ var init_CalendarElement = __esm({
792
976
  if (declared.size === 0) return "open";
793
977
  return this.knownBuckets.find((bucket) => declared.has(bucket));
794
978
  }
979
+ assertHorizonOrdered() {
980
+ const min = this.boundaryDate("min-date");
981
+ const max = this.boundaryDate("max-date");
982
+ if (min && max && min.getTime() > max.getTime()) {
983
+ throw new Error(
984
+ `<kal-calendar> min-date is after max-date: "${this.getAttribute("min-date")}" > "${this.getAttribute("max-date")}".`
985
+ );
986
+ }
987
+ }
988
+ // Whether the vendor offers this day at all, before any event is considered.
989
+ isDateOffered(date) {
990
+ return isDateWithinWindow(
991
+ date,
992
+ this.boundaryDate("min-date"),
993
+ this.boundaryDate("max-date")
994
+ ) && isDayAllowed(date, this.availableDays);
995
+ }
795
996
  isDateSelectable(date) {
796
997
  if (!this.engine) return false;
998
+ if (!this.isDateOffered(date)) return false;
797
999
  if (this._selectableStatuses) {
798
1000
  return this._selectableStatuses.includes(this.resolveBucket(date));
799
1001
  }
@@ -813,6 +1015,11 @@ var init_CalendarElement = __esm({
813
1015
  this.assertStatusesDeclared();
814
1016
  this.assertStatusesKnown();
815
1017
  }
1018
+ this.boundaryDate("min-date");
1019
+ this.boundaryDate("max-date");
1020
+ void this.availableDays;
1021
+ void this.availableHours;
1022
+ this.assertHorizonOrdered();
816
1023
  const today = /* @__PURE__ */ new Date();
817
1024
  const todayMonth = today.getMonth();
818
1025
  const todayYear = today.getFullYear();
@@ -856,7 +1063,7 @@ var init_CalendarElement = __esm({
856
1063
  return `
857
1064
  <div class="event-card" style="border-left-color: ${borderColor}">
858
1065
  <div class="event-header">
859
- <div class="event-title">${escapeHtml(event.name)}</div>
1066
+ ${event.name ? `<div class="event-title">${escapeHtml(event.name)}</div>` : ""}
860
1067
  <div class="event-badges">
861
1068
  ${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
862
1069
  ${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
@@ -922,42 +1129,45 @@ var init_CalendarElement = __esm({
922
1129
  const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
923
1130
  const renderEvent = this._renderEvent || defaultRenderEvent;
924
1131
  const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
925
- const renderTimeGrid = (events, date) => {
926
- const startHour = (time) => {
927
- if (typeof time !== "string") return null;
928
- const hour = Number(time.split(":")[0]);
929
- return Number.isInteger(hour) && hour >= 0 && hour <= 24 ? hour : null;
930
- };
931
- const isHourBooked = (hour) => events.some((event) => {
932
- if (!event.startTime) return true;
933
- const from = startHour(event.startTime);
934
- const to = event.endTime ? startHour(event.endTime) : 24;
935
- if (from === null || to === null) return true;
936
- return hour >= from && hour < to;
937
- });
938
- const slots = Array.from({ length: 24 }, (_, hour) => {
939
- const startTime = `${String(hour).padStart(2, "0")}:00`;
940
- const endTime = `${String(hour + 1).padStart(2, "0")}:00`;
941
- const booked = isHourBooked(hour);
942
- const inTimeRange = !booked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
1132
+ const renderTimeGrid = (date) => {
1133
+ const slotDuration = this.slotDuration;
1134
+ const previousDay = new Date(date);
1135
+ previousDay.setDate(previousDay.getDate() - 1);
1136
+ const events = [
1137
+ ...this.engine.getEventsForDate(previousDay),
1138
+ ...this.engine.getEventsForDate(date)
1139
+ ];
1140
+ this.warnMissingEndTime(events);
1141
+ const booked = bookedSlots(events, date, slotDuration);
1142
+ const hours = this.availableHours;
1143
+ const slots = booked.map((isBooked, index) => {
1144
+ const slotStart = index * slotDuration;
1145
+ const slotEnd = slotStart + slotDuration;
1146
+ const startTime = formatMinutes(slotStart);
1147
+ const endTime = formatMinutes(slotEnd);
1148
+ const offered = hours === null || hours.some(([from, to]) => slotStart >= from && slotEnd <= to);
1149
+ const inTimeRange = offered && !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
943
1150
  const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
944
1151
  const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
945
1152
  const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
946
1153
  const slotClasses = [
947
1154
  "time-grid-slot",
948
- booked ? "time-grid-slot-blocked" : "time-grid-slot-open",
1155
+ offered ? "" : "time-grid-slot-out-of-range",
1156
+ isBooked ? "time-grid-slot-blocked" : "time-grid-slot-open",
949
1157
  isRangeStart ? "time-grid-slot-range-start" : "",
950
1158
  isRangeEnd ? "time-grid-slot-range-end" : "",
951
1159
  isInRange ? "time-grid-slot-in-range" : ""
952
1160
  ].filter(Boolean).join(" ");
953
- const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
1161
+ const slotAttrs = `${offered ? 'data-action="select-slot" ' : ""}data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
1162
+ const status = !offered ? "Closed" : isBooked ? "Booked" : "Available";
954
1163
  return `
955
1164
  <div class="${slotClasses}" ${slotAttrs}>
956
1165
  <span class="time-grid-label">${startTime}</span>
957
- <span class="time-grid-status">${booked ? "Booked" : "Available"}</span>
1166
+ <span class="time-grid-status">${status}</span>
958
1167
  </div>`;
959
1168
  });
960
- return `<div class="time-grid">${slots.join("")}</div>`;
1169
+ const gridClasses = selectable ? "time-grid time-grid-selectable" : "time-grid";
1170
+ return `<div class="${gridClasses}">${slots.join("")}</div>`;
961
1171
  };
962
1172
  const multiMonth = viewModel.panes.length > 1;
963
1173
  const renderPane = (pane) => `
@@ -982,6 +1192,12 @@ var init_CalendarElement = __esm({
982
1192
  ${week.map((calendarDate, dayIndex) => {
983
1193
  const classes = getCellClasses(calendarDate);
984
1194
  const cellAttrs = [];
1195
+ if (viewModel.selectedDate && isSameDay2(
1196
+ calendarDate.date,
1197
+ viewModel.selectedDate
1198
+ )) {
1199
+ classes.push("calendar-cell-selected");
1200
+ }
985
1201
  if (availabilityMode && calendarDate.isCurrentMonth) {
986
1202
  const bucket = this.resolveBucket(
987
1203
  calendarDate.date
@@ -1015,12 +1231,18 @@ var init_CalendarElement = __esm({
1015
1231
  }
1016
1232
  }
1017
1233
  const dateString = calendarDate.date.toISOString();
1234
+ const offered = this.isDateOffered(
1235
+ calendarDate.date
1236
+ );
1237
+ if (!offered) {
1238
+ classes.push("calendar-cell-out-of-range");
1239
+ }
1018
1240
  return `
1019
1241
  <td
1020
1242
  class="${classes.join(" ")}"
1021
1243
  data-date="${dateString}"
1022
1244
  data-day-index="${dayIndex}"
1023
- data-clickable="true"
1245
+ ${offered ? 'data-clickable="true"' : ""}
1024
1246
  ${cellAttrs.join(" ")}
1025
1247
  >
1026
1248
  ${calendarDate.date.getDate()}
@@ -1134,7 +1356,7 @@ var init_CalendarElement = __esm({
1134
1356
  ` : ""}
1135
1357
 
1136
1358
  <div class="events-container">
1137
- ${availabilityMode === "time" ? renderTimeGrid(viewModel.tasks, viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
1359
+ ${availabilityMode === "time" ? renderTimeGrid(viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
1138
1360
  </div>
1139
1361
  </div>
1140
1362
  ` : ""}
@@ -1295,6 +1517,20 @@ var init_CalendarElement = __esm({
1295
1517
  const slotStart = actionEl.dataset.startTime;
1296
1518
  const slotEnd = actionEl.dataset.endTime;
1297
1519
  const slotDate = new Date(actionEl.dataset.date);
1520
+ const slotBooked = actionEl.dataset.booked === "true";
1521
+ this.dispatchEvent(
1522
+ new CustomEvent("cal-slot-select", {
1523
+ bubbles: true,
1524
+ composed: true,
1525
+ detail: {
1526
+ date: slotDate,
1527
+ startTime: slotStart,
1528
+ endTime: slotEnd,
1529
+ booked: slotBooked
1530
+ }
1531
+ })
1532
+ );
1533
+ if (slotBooked || !this.hasAttribute("selectable")) break;
1298
1534
  if (this._timeRangeComplete) {
1299
1535
  this._timeRangeDate = slotDate;
1300
1536
  this._timeRangeStart = slotStart;
@@ -1432,9 +1668,14 @@ var init_CalendarElement = __esm({
1432
1668
  "initial-date",
1433
1669
  "min-year",
1434
1670
  "max-year",
1671
+ "min-date",
1672
+ "max-date",
1673
+ "available-days",
1674
+ "available-hours",
1435
1675
  "week-starts-on",
1436
1676
  "heading",
1437
1677
  "months",
1678
+ "slot-duration",
1438
1679
  "title",
1439
1680
  "use-short-month-names",
1440
1681
  "availability-mode",
@@ -1452,6 +1693,8 @@ var init_CalendarElement = __esm({
1452
1693
  borderColor: "--calendar-border-color",
1453
1694
  todayOutline: "--calendar-today-outline",
1454
1695
  selectedBg: "--calendar-selected-bg",
1696
+ outOfRangeBg: "--calendar-out-of-range-bg",
1697
+ outOfRangeFg: "--calendar-out-of-range-fg",
1455
1698
  headerBg: "--calendar-header-bg",
1456
1699
  popupBg: "--calendar-popup-bg",
1457
1700
  pickerBg: "--calendar-picker-bg",
@@ -1486,6 +1729,7 @@ var init_CalendarElement = __esm({
1486
1729
  badgeTentativeBg: "--calendar-badge-tentative-bg",
1487
1730
  badgeTentativeText: "--calendar-badge-tentative-text"
1488
1731
  };
1732
+ _CalendarElement.warnedMissingEnd = /* @__PURE__ */ new Set();
1489
1733
  _CalendarElement.BUILT_IN_BUCKETS = ["blocked", "conditional", "open"];
1490
1734
  _CalendarElement.titleDeprecationWarned = false;
1491
1735
  CalendarElement = _CalendarElement;
@@ -1499,12 +1743,18 @@ __export(web_components_exports, {
1499
1743
  CalendarEngine: () => CalendarEngine,
1500
1744
  DAYS: () => DAYS,
1501
1745
  DEFAULT_CATEGORY_COLORS: () => DEFAULT_CATEGORY_COLORS,
1746
+ DEFAULT_SLOT_DURATION: () => DEFAULT_SLOT_DURATION,
1747
+ MINUTES_PER_DAY: () => MINUTES_PER_DAY,
1502
1748
  MONTHS: () => MONTHS,
1503
1749
  MONTHS_FULL: () => MONTHS_FULL,
1750
+ bookedSlots: () => bookedSlots,
1504
1751
  defineCalendarElement: () => defineCalendarElement,
1505
1752
  escapeHtml: () => escapeHtml,
1753
+ eventCoversDate: () => eventCoversDate,
1754
+ eventInterval: () => eventInterval,
1506
1755
  formatAttendees: () => formatAttendees,
1507
1756
  formatDateForDisplay: () => formatDateForDisplay,
1757
+ formatMinutes: () => formatMinutes,
1508
1758
  formatTimeRange: () => formatTimeRange,
1509
1759
  generateCalendarDates: () => generateCalendarDates,
1510
1760
  generateYears: () => generateYears,
@@ -1514,11 +1764,16 @@ __export(web_components_exports, {
1514
1764
  getEventsForDate: () => getEventsForDate,
1515
1765
  getMonthYearText: () => getMonthYearText,
1516
1766
  hasEvents: () => hasEvents,
1767
+ isDateWithinWindow: () => isDateWithinWindow,
1768
+ isDayAllowed: () => isDayAllowed,
1517
1769
  isSameDay: () => isSameDay,
1518
1770
  isToday: () => isToday,
1519
1771
  isValidHexColor: () => isValidHexColor,
1520
1772
  mergeCategoryColors: () => mergeCategoryColors,
1773
+ mergeIntervals: () => mergeIntervals,
1521
1774
  normalizeDate: () => normalizeDate,
1775
+ parseHourRanges: () => parseHourRanges,
1776
+ parseTimeToMinutes: () => parseTimeToMinutes,
1522
1777
  safeColor: () => safeColor,
1523
1778
  safeUrl: () => safeUrl,
1524
1779
  slugifyToken: () => slugifyToken,
@@ -1540,12 +1795,18 @@ if (typeof customElements !== "undefined" && !customElements.get("kal-calendar")
1540
1795
  CalendarEngine,
1541
1796
  DAYS,
1542
1797
  DEFAULT_CATEGORY_COLORS,
1798
+ DEFAULT_SLOT_DURATION,
1799
+ MINUTES_PER_DAY,
1543
1800
  MONTHS,
1544
1801
  MONTHS_FULL,
1802
+ bookedSlots,
1545
1803
  defineCalendarElement,
1546
1804
  escapeHtml,
1805
+ eventCoversDate,
1806
+ eventInterval,
1547
1807
  formatAttendees,
1548
1808
  formatDateForDisplay,
1809
+ formatMinutes,
1549
1810
  formatTimeRange,
1550
1811
  generateCalendarDates,
1551
1812
  generateYears,
@@ -1555,11 +1816,16 @@ if (typeof customElements !== "undefined" && !customElements.get("kal-calendar")
1555
1816
  getEventsForDate,
1556
1817
  getMonthYearText,
1557
1818
  hasEvents,
1819
+ isDateWithinWindow,
1820
+ isDayAllowed,
1558
1821
  isSameDay,
1559
1822
  isToday,
1560
1823
  isValidHexColor,
1561
1824
  mergeCategoryColors,
1825
+ mergeIntervals,
1562
1826
  normalizeDate,
1827
+ parseHourRanges,
1828
+ parseTimeToMinutes,
1563
1829
  safeColor,
1564
1830
  safeUrl,
1565
1831
  slugifyToken,