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/dist/index.umd.js CHANGED
@@ -78,6 +78,54 @@ var Kalendly = (() => {
78
78
  const max = maxYear ?? currentYear + 10;
79
79
  return Array.from({ length: max - min + 1 }, (_, i) => min + i);
80
80
  }
81
+ function parseHourRanges(value, slotDuration) {
82
+ const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
83
+ if (parts.length === 0) {
84
+ throw new Error(
85
+ `<kal-calendar> available-hours is empty. Omit the attribute to allow every hour, or name at least one HH:MM-HH:MM range.`
86
+ );
87
+ }
88
+ const ranges = parts.map((part) => {
89
+ const halves = part.split("-");
90
+ if (halves.length !== 2) {
91
+ throw new Error(
92
+ `<kal-calendar> available-hours range "${part}" is not HH:MM-HH:MM.`
93
+ );
94
+ }
95
+ const [start, end] = halves.map((half) => parseTimeToMinutes(half.trim()));
96
+ if (start === null || end === null) {
97
+ throw new Error(
98
+ `<kal-calendar> available-hours range "${part}" has an unreadable time. Use 24-hour HH:MM.`
99
+ );
100
+ }
101
+ if (start >= end) {
102
+ throw new Error(
103
+ `<kal-calendar> available-hours range "${part}" starts at or after it ends.`
104
+ );
105
+ }
106
+ if (start % slotDuration !== 0 || end % slotDuration !== 0) {
107
+ throw new Error(
108
+ `<kal-calendar> available-hours range "${part}" does not land on a ${slotDuration}-minute slot boundary.`
109
+ );
110
+ }
111
+ return [start, end];
112
+ });
113
+ if (mergeIntervals(ranges).length !== ranges.length) {
114
+ throw new Error(
115
+ `<kal-calendar> available-hours ranges overlap or touch: "${value}". Write each bookable window once.`
116
+ );
117
+ }
118
+ return ranges;
119
+ }
120
+ function isDateWithinWindow(date, min, max) {
121
+ const target = normalizeDate(date).getTime();
122
+ if (min && target < normalizeDate(min).getTime()) return false;
123
+ if (max && target > normalizeDate(max).getTime()) return false;
124
+ return true;
125
+ }
126
+ function isDayAllowed(date, days) {
127
+ return days === null || days.includes(date.getDay());
128
+ }
81
129
  function eventCoversDate(event, date) {
82
130
  const start = normalizeDate(new Date(event.date)).getTime();
83
131
  const target = normalizeDate(date).getTime();
@@ -714,6 +762,41 @@ var Kalendly = (() => {
714
762
  const val = this.getAttribute("max-year");
715
763
  return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() + 10;
716
764
  }
765
+ boundaryDate(attr) {
766
+ const val = this.getAttribute(attr);
767
+ if (!val) return null;
768
+ const parsed = new Date(val);
769
+ if (Number.isNaN(parsed.getTime())) {
770
+ throw new Error(
771
+ `<kal-calendar> ${attr} is unreadable: "${val}". Use a value Date can parse, the same as initial-date.`
772
+ );
773
+ }
774
+ return parsed;
775
+ }
776
+ get availableDays() {
777
+ const val = this.getAttribute("available-days");
778
+ if (val === null) return null;
779
+ const parts = val.split(",").map((part) => part.trim()).filter(Boolean);
780
+ if (parts.length === 0) {
781
+ throw new Error(
782
+ `<kal-calendar> available-days is empty. Omit the attribute to allow every day, or name at least one weekday.`
783
+ );
784
+ }
785
+ return parts.map((part) => {
786
+ const day = Number(part);
787
+ if (!Number.isInteger(day) || day < 0 || day > 6) {
788
+ throw new Error(
789
+ `<kal-calendar> available-days must list integers 0-6, 0 = Sunday. Got "${part}".`
790
+ );
791
+ }
792
+ return day;
793
+ });
794
+ }
795
+ get availableHours() {
796
+ const val = this.getAttribute("available-hours");
797
+ if (val === null) return null;
798
+ return parseHourRanges(val, this.slotDuration);
799
+ }
717
800
  initEngine() {
718
801
  const initialDateAttr = this.getAttribute("initial-date");
719
802
  const initialDate = initialDateAttr ? new Date(initialDateAttr) : void 0;
@@ -832,8 +915,26 @@ var Kalendly = (() => {
832
915
  if (declared.size === 0) return "open";
833
916
  return this.knownBuckets.find((bucket) => declared.has(bucket));
834
917
  }
918
+ assertHorizonOrdered() {
919
+ const min = this.boundaryDate("min-date");
920
+ const max = this.boundaryDate("max-date");
921
+ if (min && max && min.getTime() > max.getTime()) {
922
+ throw new Error(
923
+ `<kal-calendar> min-date is after max-date: "${this.getAttribute("min-date")}" > "${this.getAttribute("max-date")}".`
924
+ );
925
+ }
926
+ }
927
+ // Whether the vendor offers this day at all, before any event is considered.
928
+ isDateOffered(date) {
929
+ return isDateWithinWindow(
930
+ date,
931
+ this.boundaryDate("min-date"),
932
+ this.boundaryDate("max-date")
933
+ ) && isDayAllowed(date, this.availableDays);
934
+ }
835
935
  isDateSelectable(date) {
836
936
  if (!this.engine) return false;
937
+ if (!this.isDateOffered(date)) return false;
837
938
  if (this._selectableStatuses) {
838
939
  return this._selectableStatuses.includes(this.resolveBucket(date));
839
940
  }
@@ -853,6 +954,11 @@ var Kalendly = (() => {
853
954
  this.assertStatusesDeclared();
854
955
  this.assertStatusesKnown();
855
956
  }
957
+ this.boundaryDate("min-date");
958
+ this.boundaryDate("max-date");
959
+ void this.availableDays;
960
+ void this.availableHours;
961
+ this.assertHorizonOrdered();
856
962
  const today = /* @__PURE__ */ new Date();
857
963
  const todayMonth = today.getMonth();
858
964
  const todayYear = today.getFullYear();
@@ -896,7 +1002,7 @@ var Kalendly = (() => {
896
1002
  return `
897
1003
  <div class="event-card" style="border-left-color: ${borderColor}">
898
1004
  <div class="event-header">
899
- <div class="event-title">${escapeHtml(event.name)}</div>
1005
+ ${event.name ? `<div class="event-title">${escapeHtml(event.name)}</div>` : ""}
900
1006
  <div class="event-badges">
901
1007
  ${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
902
1008
  ${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
@@ -972,25 +1078,31 @@ var Kalendly = (() => {
972
1078
  ];
973
1079
  this.warnMissingEndTime(events);
974
1080
  const booked = bookedSlots(events, date, slotDuration);
1081
+ const hours = this.availableHours;
975
1082
  const slots = booked.map((isBooked, index) => {
976
- const startTime = formatMinutes(index * slotDuration);
977
- const endTime = formatMinutes((index + 1) * slotDuration);
978
- const inTimeRange = !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
1083
+ const slotStart = index * slotDuration;
1084
+ const slotEnd = slotStart + slotDuration;
1085
+ const startTime = formatMinutes(slotStart);
1086
+ const endTime = formatMinutes(slotEnd);
1087
+ const offered = hours === null || hours.some(([from, to]) => slotStart >= from && slotEnd <= to);
1088
+ const inTimeRange = offered && !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
979
1089
  const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
980
1090
  const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
981
1091
  const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
982
1092
  const slotClasses = [
983
1093
  "time-grid-slot",
1094
+ offered ? "" : "time-grid-slot-out-of-range",
984
1095
  isBooked ? "time-grid-slot-blocked" : "time-grid-slot-open",
985
1096
  isRangeStart ? "time-grid-slot-range-start" : "",
986
1097
  isRangeEnd ? "time-grid-slot-range-end" : "",
987
1098
  isInRange ? "time-grid-slot-in-range" : ""
988
1099
  ].filter(Boolean).join(" ");
989
- const slotAttrs = `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
1100
+ const slotAttrs = `${offered ? 'data-action="select-slot" ' : ""}data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
1101
+ const status = !offered ? "Closed" : isBooked ? "Booked" : "Available";
990
1102
  return `
991
1103
  <div class="${slotClasses}" ${slotAttrs}>
992
1104
  <span class="time-grid-label">${startTime}</span>
993
- <span class="time-grid-status">${isBooked ? "Booked" : "Available"}</span>
1105
+ <span class="time-grid-status">${status}</span>
994
1106
  </div>`;
995
1107
  });
996
1108
  const gridClasses = selectable ? "time-grid time-grid-selectable" : "time-grid";
@@ -1058,12 +1170,18 @@ var Kalendly = (() => {
1058
1170
  }
1059
1171
  }
1060
1172
  const dateString = calendarDate.date.toISOString();
1173
+ const offered = this.isDateOffered(
1174
+ calendarDate.date
1175
+ );
1176
+ if (!offered) {
1177
+ classes.push("calendar-cell-out-of-range");
1178
+ }
1061
1179
  return `
1062
1180
  <td
1063
1181
  class="${classes.join(" ")}"
1064
1182
  data-date="${dateString}"
1065
1183
  data-day-index="${dayIndex}"
1066
- data-clickable="true"
1184
+ ${offered ? 'data-clickable="true"' : ""}
1067
1185
  ${cellAttrs.join(" ")}
1068
1186
  >
1069
1187
  ${calendarDate.date.getDate()}
@@ -1489,6 +1607,10 @@ var Kalendly = (() => {
1489
1607
  "initial-date",
1490
1608
  "min-year",
1491
1609
  "max-year",
1610
+ "min-date",
1611
+ "max-date",
1612
+ "available-days",
1613
+ "available-hours",
1492
1614
  "week-starts-on",
1493
1615
  "heading",
1494
1616
  "months",
@@ -1510,6 +1632,8 @@ var Kalendly = (() => {
1510
1632
  borderColor: "--calendar-border-color",
1511
1633
  todayOutline: "--calendar-today-outline",
1512
1634
  selectedBg: "--calendar-selected-bg",
1635
+ outOfRangeBg: "--calendar-out-of-range-bg",
1636
+ outOfRangeFg: "--calendar-out-of-range-fg",
1513
1637
  headerBg: "--calendar-header-bg",
1514
1638
  popupBg: "--calendar-popup-bg",
1515
1639
  pickerBg: "--calendar-picker-bg",