kalendly 0.2.2 → 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/dist/index.js CHANGED
@@ -43,12 +43,28 @@ 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 eventCoversDate(event, date) {
47
+ const start = normalizeDate(new Date(event.date)).getTime();
48
+ const target = normalizeDate(date).getTime();
49
+ if (Number.isNaN(start)) return false;
50
+ if (event.endDate === void 0 || event.endDate === null) {
51
+ return start === target;
52
+ }
53
+ const end = normalizeDate(new Date(event.endDate)).getTime();
54
+ if (Number.isNaN(end)) {
55
+ throw new Error(
56
+ `<kal-calendar> event ${event.id} has an unreadable endDate: ${String(event.endDate)}.`
57
+ );
58
+ }
59
+ if (end < start) {
60
+ throw new Error(
61
+ `<kal-calendar> event ${event.id} has an endDate before its date: ${String(event.endDate)} < ${String(event.date)}.`
62
+ );
63
+ }
64
+ return target >= start && target <= end;
65
+ }
46
66
  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();
51
- });
67
+ return events.filter((event) => eventCoversDate(event, date));
52
68
  }
53
69
  function hasEvents(events, date) {
54
70
  return getEventsForDate(events, date).length > 0;
@@ -107,26 +123,16 @@ function generateCalendarDates(year, month, events = [], weekStartsOn = 0) {
107
123
  }
108
124
  return dates;
109
125
  }
110
- function getPopupPositionClass(selectedDayIndex) {
111
- if (selectedDayIndex === null) return "popup-center-bottom";
112
- if (selectedDayIndex < 3) {
113
- return "popup-right";
114
- } else if (selectedDayIndex > 4) {
115
- return "popup-left";
116
- } else {
117
- return "popup-center-bottom";
118
- }
119
- }
120
126
  function getCellClasses(calendarDate) {
121
127
  const classes = [];
122
128
  if (!calendarDate.isCurrentMonth) {
123
- classes.push("other-month");
129
+ classes.push("calendar-cell-other-month");
124
130
  }
125
131
  if (calendarDate.isToday) {
126
- classes.push("schedule--current--exam");
132
+ classes.push("calendar-cell-today");
127
133
  }
128
134
  if (calendarDate.hasEvents) {
129
- classes.push("has--event");
135
+ classes.push("calendar-cell-has-event");
130
136
  }
131
137
  return classes;
132
138
  }
@@ -185,7 +191,84 @@ function getCategoryColor(category, customColors) {
185
191
  const color = colorMap[category] || colorMap.other || "#fc8917";
186
192
  return isValidHexColor(color) ? color : "#fc8917";
187
193
  }
188
- var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS;
194
+ function escapeHtml(value) {
195
+ if (value === null || value === void 0) return "";
196
+ return String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
197
+ }
198
+ function slugifyToken(value) {
199
+ return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
200
+ }
201
+ function safeUrl(value) {
202
+ const normalized = String(value).split("").filter((char) => char.charCodeAt(0) > 32).join("");
203
+ if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;
204
+ const schemeless = !/^[^/?#]*:/.test(normalized);
205
+ return schemeless ? normalized : "#";
206
+ }
207
+ function safeColor(value) {
208
+ const trimmed = String(value).trim();
209
+ return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
210
+ }
211
+ function parseTimeToMinutes(time) {
212
+ if (typeof time !== "string") return null;
213
+ const match = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
214
+ if (!match) return null;
215
+ const hours = Number(match[1]);
216
+ const mins = Number(match[2]);
217
+ if (hours > 24 || mins > 59 || hours === 24 && mins > 0) return null;
218
+ return hours * 60 + mins;
219
+ }
220
+ function formatMinutes(total) {
221
+ const hours = Math.floor(total / 60);
222
+ const mins = total % 60;
223
+ return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
224
+ }
225
+ function eventInterval(event, slotDuration, onDay) {
226
+ const day = normalizeDate(onDay).getTime();
227
+ if (Number.isNaN(day)) return null;
228
+ const base = day / 6e4;
229
+ const wholeDay = [base, base + MINUTES_PER_DAY];
230
+ if (event.allDay || !event.startTime) return wholeDay;
231
+ const from = parseTimeToMinutes(event.startTime);
232
+ if (from === null) return wholeDay;
233
+ if (event.endTime === void 0 || event.endTime === null) {
234
+ return [base + from, base + from + slotDuration];
235
+ }
236
+ const to = parseTimeToMinutes(event.endTime);
237
+ if (to === null) return wholeDay;
238
+ return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];
239
+ }
240
+ function mergeIntervals(intervals) {
241
+ const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
242
+ return sorted.reduce((merged, [start, end]) => {
243
+ const last = merged[merged.length - 1];
244
+ if (last && start <= last[1]) last[1] = Math.max(last[1], end);
245
+ else merged.push([start, end]);
246
+ return merged;
247
+ }, []);
248
+ }
249
+ function bookedSlots(events, date, slotDuration = DEFAULT_SLOT_DURATION) {
250
+ const base = normalizeDate(date).getTime() / 6e4;
251
+ const previousDay = new Date(date);
252
+ previousDay.setDate(previousDay.getDate() - 1);
253
+ const intervals = [];
254
+ for (const event of new Set(events)) {
255
+ for (const day of [previousDay, date]) {
256
+ if (!eventCoversDate(event, day)) continue;
257
+ const interval = eventInterval(event, slotDuration, day);
258
+ if (interval) intervals.push(interval);
259
+ }
260
+ }
261
+ const merged = mergeIntervals(intervals);
262
+ return Array.from(
263
+ { length: Math.floor(MINUTES_PER_DAY / slotDuration) },
264
+ (_, index) => {
265
+ const start = base + index * slotDuration;
266
+ const end = start + slotDuration;
267
+ return merged.some(([from, to]) => start < to && end > from);
268
+ }
269
+ );
270
+ }
271
+ var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS, HTML_ESCAPES, MINUTES_PER_DAY, DEFAULT_SLOT_DURATION;
189
272
  var init_utils = __esm({
190
273
  "src/core/utils.ts"() {
191
274
  "use strict";
@@ -234,6 +317,15 @@ var init_utils = __esm({
234
317
  appointment: "#f59e0b",
235
318
  other: "#6b7280"
236
319
  };
320
+ HTML_ESCAPES = {
321
+ "&": "&amp;",
322
+ "<": "&lt;",
323
+ ">": "&gt;",
324
+ '"': "&quot;",
325
+ "'": "&#39;"
326
+ };
327
+ MINUTES_PER_DAY = 1440;
328
+ DEFAULT_SLOT_DURATION = 60;
237
329
  }
238
330
  });
239
331
 
@@ -281,24 +373,38 @@ var init_calendar_engine = __esm({
281
373
  * Get view model with computed properties
282
374
  */
283
375
  getViewModel() {
284
- const calendarDates = generateCalendarDates(
285
- this.state.currentYear,
286
- this.state.currentMonth,
287
- this.config.events,
288
- this.config.weekStartsOn
376
+ const panes = Array.from(
377
+ { length: Math.max(1, this.config.monthCount ?? 1) },
378
+ (_, offset) => {
379
+ const anchor = new Date(
380
+ this.state.currentYear,
381
+ this.state.currentMonth + offset,
382
+ 1
383
+ );
384
+ const year = anchor.getFullYear();
385
+ const month = anchor.getMonth();
386
+ return {
387
+ year,
388
+ month,
389
+ monthAndYearText: getMonthYearText(year, month),
390
+ calendarDates: generateCalendarDates(
391
+ year,
392
+ month,
393
+ this.config.events,
394
+ this.config.weekStartsOn
395
+ )
396
+ };
397
+ }
289
398
  );
290
399
  return {
291
400
  ...this.state,
292
401
  months: MONTHS,
293
402
  days: DAYS,
294
403
  years: generateYears(this.config.minYear, this.config.maxYear),
295
- monthAndYearText: getMonthYearText(
296
- this.state.currentYear,
297
- this.state.currentMonth
298
- ),
404
+ monthAndYearText: panes[0].monthAndYearText,
299
405
  scheduleDay: this.state.selectedDate ? formatDateForDisplay(this.state.selectedDate) : "",
300
- calendarDates,
301
- popupPositionClass: getPopupPositionClass(this.state.selectedDayIndex)
406
+ panes,
407
+ calendarDates: panes[0].calendarDates
302
408
  };
303
409
  }
304
410
  /**
@@ -494,13 +600,13 @@ function defineCalendarElement(tagName = "kal-calendar") {
494
600
  customElements.define(tagName, CalendarElement);
495
601
  }
496
602
  }
497
- var isSameDay2, CalendarElement;
603
+ var isSameDay2, _CalendarElement, CalendarElement;
498
604
  var init_CalendarElement = __esm({
499
605
  "src/web-components/CalendarElement.ts"() {
500
606
  "use strict";
501
607
  init_core();
502
608
  isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
503
- CalendarElement = class extends HTMLElement {
609
+ _CalendarElement = class _CalendarElement extends HTMLElement {
504
610
  constructor() {
505
611
  super(...arguments);
506
612
  this.engine = null;
@@ -521,6 +627,10 @@ var init_CalendarElement = __esm({
521
627
  this._categoryColors = null;
522
628
  this._renderEvent = null;
523
629
  this._renderNoEvents = null;
630
+ this._availabilityColors = null;
631
+ this._selectableStatuses = null;
632
+ this._initError = null;
633
+ this.pendingUpdate = null;
524
634
  // Selection state (availability mode — range)
525
635
  this._rangeStart = null;
526
636
  this._rangeEnd = null;
@@ -533,6 +643,7 @@ var init_CalendarElement = __esm({
533
643
  return this._events;
534
644
  }
535
645
  set events(val) {
646
+ this._initError = null;
536
647
  this._events = val;
537
648
  if (this.engine) {
538
649
  this.engine.updateEvents(val);
@@ -548,13 +659,28 @@ var init_CalendarElement = __esm({
548
659
  this.engine.updateCategoryColors(val);
549
660
  }
550
661
  }
662
+ get availabilityColors() {
663
+ return this._availabilityColors ?? {};
664
+ }
665
+ set availabilityColors(val) {
666
+ this._initError = null;
667
+ this._availabilityColors = val;
668
+ if (this.engine) this.scheduleRender();
669
+ }
670
+ get selectableStatuses() {
671
+ return this._selectableStatuses ?? [];
672
+ }
673
+ set selectableStatuses(val) {
674
+ this._selectableStatuses = val;
675
+ if (this.engine) this.scheduleRender();
676
+ }
551
677
  set renderEvent(val) {
552
678
  this._renderEvent = val;
553
- if (this.engine) this.render();
679
+ if (this.engine) this.scheduleRender();
554
680
  }
555
681
  set renderNoEvents(val) {
556
682
  this._renderNoEvents = val;
557
- if (this.engine) this.render();
683
+ if (this.engine) this.scheduleRender();
558
684
  }
559
685
  get loading() {
560
686
  return this.hasAttribute("loading");
@@ -563,10 +689,22 @@ var init_CalendarElement = __esm({
563
689
  if (val) this.setAttribute("loading", "");
564
690
  else this.removeAttribute("loading");
565
691
  }
692
+ // Custom element reactions report rather than propagate, so a failure here
693
+ // is kept and resurfaced at the next call the integrator makes
694
+ reaction(run) {
695
+ try {
696
+ run();
697
+ } catch (error) {
698
+ this._initError = error;
699
+ throw error;
700
+ }
701
+ }
566
702
  connectedCallback() {
567
703
  this.classList.add("kalendly-calendar");
568
- this.initEngine();
569
- this.render();
704
+ this.reaction(() => {
705
+ this.initEngine();
706
+ this.render();
707
+ });
570
708
  }
571
709
  disconnectedCallback() {
572
710
  this.cleanup();
@@ -576,7 +714,7 @@ var init_CalendarElement = __esm({
576
714
  attributeChangedCallback(name, oldVal, newVal) {
577
715
  if (oldVal === newVal) return;
578
716
  if (name === "loading") {
579
- this.render();
717
+ this.scheduleRender();
580
718
  return;
581
719
  }
582
720
  if (name === "selectable" && newVal === null) {
@@ -589,6 +727,46 @@ var init_CalendarElement = __esm({
589
727
  }
590
728
  this.reinit();
591
729
  }
730
+ get headingText() {
731
+ const heading = this.getAttribute("heading");
732
+ if (heading !== null) return heading;
733
+ const title = this.getAttribute("title");
734
+ if (title !== null && !_CalendarElement.titleDeprecationWarned) {
735
+ _CalendarElement.titleDeprecationWarned = true;
736
+ console.warn(
737
+ `<kal-calendar> the title attribute is deprecated and will be removed in a future release \u2014 use heading instead. title is a global HTML attribute, so the browser also renders it as a tooltip over the whole calendar.`
738
+ );
739
+ }
740
+ return title;
741
+ }
742
+ get slotDuration() {
743
+ const raw = this.getAttribute("slot-duration");
744
+ if (raw === null) return DEFAULT_SLOT_DURATION;
745
+ const minutes = Number(raw);
746
+ if (Number.isInteger(minutes) && minutes > 0 && MINUTES_PER_DAY % minutes === 0) {
747
+ return minutes;
748
+ }
749
+ console.warn(
750
+ `<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}.`
751
+ );
752
+ return DEFAULT_SLOT_DURATION;
753
+ }
754
+ warnMissingEndTime(events) {
755
+ for (const event of events) {
756
+ if (!event.startTime || event.endTime) continue;
757
+ const key = String(event.id);
758
+ if (_CalendarElement.warnedMissingEnd.has(key)) continue;
759
+ _CalendarElement.warnedMissingEnd.add(key);
760
+ console.warn(
761
+ `<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.`
762
+ );
763
+ }
764
+ }
765
+ get monthCount() {
766
+ const requested = Number(this.getAttribute("months") ?? 1);
767
+ if (!Number.isInteger(requested) || requested < 1) return 1;
768
+ return Math.min(requested, 2);
769
+ }
592
770
  get minYear() {
593
771
  const val = this.getAttribute("min-year");
594
772
  return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() - 30;
@@ -608,19 +786,20 @@ var init_CalendarElement = __esm({
608
786
  minYear: this.minYear,
609
787
  maxYear: this.maxYear,
610
788
  weekStartsOn,
611
- categoryColors: this._categoryColors ?? void 0
789
+ categoryColors: this._categoryColors ?? void 0,
790
+ monthCount: this.monthCount
612
791
  });
613
792
  this.actions = this.engine.getActions();
614
793
  this.applyTheme();
615
794
  this.unsubscribe = this.engine.subscribe(() => {
616
- this.render();
795
+ this.scheduleRender();
617
796
  });
618
797
  }
619
798
  reinit() {
620
799
  if (!this.engine) return;
621
800
  this.cleanup();
622
801
  this.initEngine();
623
- this.render();
802
+ this.scheduleRender();
624
803
  }
625
804
  cleanup() {
626
805
  if (this.unsubscribe) {
@@ -657,42 +836,84 @@ var init_CalendarElement = __esm({
657
836
  applyTheme() {
658
837
  if (!this._theme) return;
659
838
  const root = document.documentElement;
660
- const t = this._theme;
661
- if (t.primary)
662
- root.style.setProperty("--calendar-primary-color", t.primary);
663
- if (t.secondary)
664
- root.style.setProperty("--calendar-secondary-color", t.secondary);
665
- if (t.tertiary)
666
- root.style.setProperty("--calendar-tertiary-color", t.tertiary);
667
- if (t.textColor)
668
- root.style.setProperty("--calendar-text-color", t.textColor);
669
- if (t.textLight)
670
- root.style.setProperty("--calendar-text-light", t.textLight);
671
- if (t.background)
672
- root.style.setProperty("--calendar-background", t.background);
673
- if (t.cellHover)
674
- root.style.setProperty("--calendar-cell-hover", t.cellHover);
675
- if (t.borderColor)
676
- root.style.setProperty("--calendar-border-color", t.borderColor);
677
- if (t.todayOutline)
678
- root.style.setProperty("--calendar-today-outline", t.todayOutline);
679
- if (t.selectedBg)
680
- root.style.setProperty("--calendar-selected-bg", t.selectedBg);
681
- if (t.headerBg) root.style.setProperty("--calendar-header-bg", t.headerBg);
682
- if (t.popupBg) root.style.setProperty("--calendar-popup-bg", t.popupBg);
683
- if (t.eventIndicator)
684
- root.style.setProperty("--calendar-event-indicator", t.eventIndicator);
839
+ for (const key of Object.keys(_CalendarElement.themeMap)) {
840
+ const value = this._theme[key];
841
+ if (value) root.style.setProperty(_CalendarElement.themeMap[key], value);
842
+ }
843
+ }
844
+ get knownBuckets() {
845
+ return [
846
+ ..._CalendarElement.BUILT_IN_BUCKETS,
847
+ ...Object.keys(this._availabilityColors ?? {})
848
+ ];
849
+ }
850
+ get updateComplete() {
851
+ return this.pendingUpdate ?? Promise.resolve();
852
+ }
853
+ scheduleRender() {
854
+ if (this.pendingUpdate) return;
855
+ this.pendingUpdate = Promise.resolve().then(() => {
856
+ this.pendingUpdate = null;
857
+ try {
858
+ this.render();
859
+ } catch (error) {
860
+ this._initError = error;
861
+ throw error;
862
+ }
863
+ });
864
+ }
865
+ rethrowInitError() {
866
+ if (this._initError) throw this._initError;
867
+ }
868
+ assertStatusesDeclared() {
869
+ if (!this.getAttribute("availability-mode")) return;
870
+ const missing = this._events.filter(
871
+ (event) => typeof event.availabilityStatus !== "string" || event.availabilityStatus === ""
872
+ ).map((event) => String(event.id));
873
+ if (missing.length) {
874
+ throw new Error(
875
+ `<kal-calendar> availability-mode requires availabilityStatus on every event. Missing on: ${missing.join(", ")}.`
876
+ );
877
+ }
878
+ }
879
+ assertStatusesKnown() {
880
+ const known = new Set(this.knownBuckets);
881
+ const unknown = this._events.filter((event) => !known.has(event.availabilityStatus)).map((event) => `${event.id} (${event.availabilityStatus})`);
882
+ if (unknown.length) {
883
+ throw new Error(
884
+ `<kal-calendar> availabilityStatus must name a built-in bucket (${_CalendarElement.BUILT_IN_BUCKETS.join(", ")}) or a key of availabilityColors. Unrecognised on: ${unknown.join(", ")}.`
885
+ );
886
+ }
887
+ }
888
+ resolveBucket(date) {
889
+ if (!this.engine) return "open";
890
+ const declared = new Set(
891
+ this.engine.getEventsForDate(date).map((event) => event.availabilityStatus)
892
+ );
893
+ if (declared.size === 0) return "open";
894
+ return this.knownBuckets.find((bucket) => declared.has(bucket));
895
+ }
896
+ isDateSelectable(date) {
897
+ if (!this.engine) return false;
898
+ if (this._selectableStatuses) {
899
+ return this._selectableStatuses.includes(this.resolveBucket(date));
900
+ }
901
+ return this.engine.getEventsForDate(date).length === 0;
685
902
  }
686
903
  render() {
687
904
  if (!this.engine) return;
688
905
  const viewModel = this.engine.getViewModel();
689
906
  const useShortMonths = this.hasAttribute("use-short-month-names");
690
- const title = this.getAttribute("title");
907
+ const title = this.headingText;
691
908
  const minYear = this.minYear;
692
909
  const maxYear = this.maxYear;
693
910
  const availabilityMode = this.getAttribute("availability-mode");
694
911
  const selectable = this.hasAttribute("selectable");
695
912
  const isLoading = this.loading;
913
+ if (availabilityMode) {
914
+ this.assertStatusesDeclared();
915
+ this.assertStatusesKnown();
916
+ }
696
917
  const today = /* @__PURE__ */ new Date();
697
918
  const todayMonth = today.getMonth();
698
919
  const todayYear = today.getFullYear();
@@ -701,7 +922,7 @@ var init_CalendarElement = __esm({
701
922
  const defaultRenderEvent = (event) => {
702
923
  const timeRange = formatTimeRange(event);
703
924
  const attendeesList = formatAttendees(event.attendees);
704
- let borderColor = event.color || "#3b82f6";
925
+ let borderColor = safeColor(event.color || "#3b82f6");
705
926
  if (event.category) {
706
927
  borderColor = this.engine.getCategoryColor(event.category);
707
928
  }
@@ -736,56 +957,56 @@ var init_CalendarElement = __esm({
736
957
  return `
737
958
  <div class="event-card" style="border-left-color: ${borderColor}">
738
959
  <div class="event-header">
739
- <div class="event-title">${event.name}</div>
960
+ <div class="event-title">${escapeHtml(event.name)}</div>
740
961
  <div class="event-badges">
741
- ${event.category ? `<span class="badge category-${event.category}">${getCategoryLabel(event.category)}</span>` : ""}
742
- ${event.priority ? `<span class="badge priority-${event.priority}">${getPriorityLabel(event.priority)}</span>` : ""}
743
- ${event.status && event.status !== "scheduled" ? `<span class="badge status-${event.status}">${getStatusLabel(event.status)}</span>` : ""}
962
+ ${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
963
+ ${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
964
+ ${event.status && event.status !== "scheduled" ? `<span class="badge status status-${slugifyToken(event.status)}">${escapeHtml(getStatusLabel(event.status))}</span>` : ""}
744
965
  </div>
745
966
  </div>
746
967
 
747
968
  ${timeRange ? `
748
969
  <div class="event-time">
749
970
  <span class="event-time-label">Time:</span>
750
- <span class="event-time-value">${timeRange}</span>
971
+ <span class="event-time-value">${escapeHtml(timeRange)}</span>
751
972
  </div>
752
973
  ` : ""}
753
974
 
754
975
  ${event.description ? `
755
- <div class="event-description">${event.description}</div>
976
+ <div class="event-description">${escapeHtml(event.description)}</div>
756
977
  ` : ""}
757
978
 
758
979
  ${event.location ? `
759
980
  <div class="event-time">
760
981
  <span class="event-time-label">Location:</span>
761
- <span class="event-time-value">${event.location}</span>
982
+ <span class="event-time-value">${escapeHtml(event.location)}</span>
762
983
  </div>
763
984
  ` : ""}
764
985
 
765
986
  ${attendeesList ? `
766
987
  <div class="event-time">
767
988
  <span class="event-time-label">Attendees:</span>
768
- <span class="event-time-value">${attendeesList}</span>
989
+ <span class="event-time-value">${escapeHtml(attendeesList)}</span>
769
990
  </div>
770
991
  ` : ""}
771
992
 
772
993
  ${event.organizer ? `
773
994
  <div class="event-time">
774
995
  <span class="event-time-label">Organizer:</span>
775
- <span class="event-time-value">${event.organizer}</span>
996
+ <span class="event-time-value">${escapeHtml(event.organizer)}</span>
776
997
  </div>
777
998
  ` : ""}
778
999
 
779
1000
  ${event.notes ? `
780
1001
  <div class="event-time">
781
1002
  <span class="event-time-label">Notes:</span>
782
- <span class="event-time-value">${event.notes}</span>
1003
+ <span class="event-time-value">${escapeHtml(event.notes)}</span>
783
1004
  </div>
784
1005
  ` : ""}
785
1006
 
786
1007
  ${event.url ? `
787
1008
  <div class="event-time">
788
- <a href="${event.url}" target="_blank" rel="noopener noreferrer" class="event-link">
1009
+ <a href="${escapeHtml(safeUrl(event.url))}" target="_blank" rel="noopener noreferrer" class="event-link">
789
1010
  View Details \u2192
790
1011
  </a>
791
1012
  </div>
@@ -793,7 +1014,7 @@ var init_CalendarElement = __esm({
793
1014
 
794
1015
  ${event.tags && event.tags.length > 0 ? `
795
1016
  <div class="event-tags">
796
- ${event.tags.map((tag) => `<span class="event-tag">${tag}</span>`).join("")}
1017
+ ${event.tags.map((tag) => `<span class="event-tag">${escapeHtml(tag)}</span>`).join("")}
797
1018
  </div>
798
1019
  ` : ""}
799
1020
  </div>
@@ -802,97 +1023,181 @@ var init_CalendarElement = __esm({
802
1023
  const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
803
1024
  const renderEvent = this._renderEvent || defaultRenderEvent;
804
1025
  const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
805
- const renderTimeGrid = (events, date) => {
806
- const isHourBooked = (hour) => events.some((event) => {
807
- if (!event.startTime || !event.endTime) return true;
808
- const [startH] = event.startTime.split(":").map(Number);
809
- const [endH] = event.endTime.split(":").map(Number);
810
- return hour >= startH && hour < endH;
811
- });
812
- const slots = Array.from({ length: 24 }, (_, hour) => {
813
- const startTime = `${String(hour).padStart(2, "0")}:00`;
814
- const endTime = `${String(hour + 1).padStart(2, "0")}:00`;
815
- const booked = isHourBooked(hour);
816
- const inTimeRange = !booked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
1026
+ const renderTimeGrid = (date) => {
1027
+ const slotDuration = this.slotDuration;
1028
+ const previousDay = new Date(date);
1029
+ previousDay.setDate(previousDay.getDate() - 1);
1030
+ const events = [
1031
+ ...this.engine.getEventsForDate(previousDay),
1032
+ ...this.engine.getEventsForDate(date)
1033
+ ];
1034
+ this.warnMissingEndTime(events);
1035
+ const booked = bookedSlots(events, date, slotDuration);
1036
+ const slots = booked.map((isBooked, index) => {
1037
+ const startTime = formatMinutes(index * slotDuration);
1038
+ const endTime = formatMinutes((index + 1) * slotDuration);
1039
+ const inTimeRange = !isBooked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
817
1040
  const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
818
1041
  const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
819
1042
  const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
820
1043
  const slotClasses = [
821
- "time-grid__slot",
822
- booked ? "time-grid__slot--booked" : "time-grid__slot--free",
823
- isRangeStart ? "time-grid__slot--range-start" : "",
824
- isRangeEnd ? "time-grid__slot--range-end" : "",
825
- isInRange ? "time-grid__slot--in-range" : ""
1044
+ "time-grid-slot",
1045
+ isBooked ? "time-grid-slot-blocked" : "time-grid-slot-open",
1046
+ isRangeStart ? "time-grid-slot-range-start" : "",
1047
+ isRangeEnd ? "time-grid-slot-range-end" : "",
1048
+ isInRange ? "time-grid-slot-in-range" : ""
826
1049
  ].filter(Boolean).join(" ");
827
- const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
1050
+ const slotAttrs = `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}" data-booked="${isBooked}"`;
828
1051
  return `
829
1052
  <div class="${slotClasses}" ${slotAttrs}>
830
- <span class="time-grid__label">${startTime}</span>
831
- <span class="time-grid__status">${booked ? "Booked" : "Available"}</span>
1053
+ <span class="time-grid-label">${startTime}</span>
1054
+ <span class="time-grid-status">${isBooked ? "Booked" : "Available"}</span>
832
1055
  </div>`;
833
1056
  });
834
- return `<div class="time-grid">${slots.join("")}</div>`;
1057
+ const gridClasses = selectable ? "time-grid time-grid-selectable" : "time-grid";
1058
+ return `<div class="${gridClasses}">${slots.join("")}</div>`;
835
1059
  };
1060
+ const multiMonth = viewModel.panes.length > 1;
1061
+ const renderPane = (pane) => `
1062
+ <div class="calendar-pane">
1063
+ ${multiMonth ? `<div class="calendar-pane-caption">${escapeHtml(pane.monthAndYearText)}</div>` : ""}
1064
+ <table class="calendar-table calendar-table-bordered">
1065
+ <thead>
1066
+ <tr>
1067
+ ${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
1068
+ </tr>
1069
+ </thead>
1070
+ <tbody data-calendar-body>
1071
+ ${isLoading ? Array.from(
1072
+ { length: 6 },
1073
+ () => `<tr>${Array.from(
1074
+ { length: 7 },
1075
+ () => `<td class="calendar-skeleton" aria-hidden="true"></td>`
1076
+ ).join("")}</tr>`
1077
+ ).join("") : pane.calendarDates.map(
1078
+ (week) => `
1079
+ <tr>
1080
+ ${week.map((calendarDate, dayIndex) => {
1081
+ const classes = getCellClasses(calendarDate);
1082
+ const cellAttrs = [];
1083
+ if (viewModel.selectedDate && isSameDay2(
1084
+ calendarDate.date,
1085
+ viewModel.selectedDate
1086
+ )) {
1087
+ classes.push("calendar-cell-selected");
1088
+ }
1089
+ if (availabilityMode && calendarDate.isCurrentMonth) {
1090
+ const bucket = this.resolveBucket(
1091
+ calendarDate.date
1092
+ );
1093
+ const custom = (this._availabilityColors ?? {})[bucket];
1094
+ classes.push(
1095
+ `availability-${slugifyToken(bucket)}`
1096
+ );
1097
+ if (custom) {
1098
+ classes.push("availability-status");
1099
+ cellAttrs.push(
1100
+ `style="--availability-color: ${escapeHtml(safeColor(custom))}"`
1101
+ );
1102
+ }
1103
+ if (!this.isDateSelectable(calendarDate.date)) {
1104
+ classes.push("availability-unselectable");
1105
+ }
1106
+ cellAttrs.push(
1107
+ `aria-label="${escapeHtml(bucket)}"`
1108
+ );
1109
+ }
1110
+ if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
1111
+ const d = calendarDate.date;
1112
+ if (this._rangeStart && isSameDay2(d, this._rangeStart))
1113
+ classes.push("availability-range-start");
1114
+ if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
1115
+ classes.push("availability-range-end");
1116
+ if (this._rangeStart && this._rangeEnd) {
1117
+ if (d > this._rangeStart && d < this._rangeEnd)
1118
+ classes.push("availability-in-range");
1119
+ }
1120
+ }
1121
+ const dateString = calendarDate.date.toISOString();
1122
+ return `
1123
+ <td
1124
+ class="${classes.join(" ")}"
1125
+ data-date="${dateString}"
1126
+ data-day-index="${dayIndex}"
1127
+ data-clickable="true"
1128
+ ${cellAttrs.join(" ")}
1129
+ >
1130
+ ${calendarDate.date.getDate()}
1131
+ </td>
1132
+ `;
1133
+ }).join("")}
1134
+ </tr>
1135
+ `
1136
+ ).join("")}
1137
+ </tbody>
1138
+ </table>
1139
+ </div>
1140
+ `;
836
1141
  const html = `
837
1142
  ${title ? `
838
- <div class="page--title">
839
- <h1>${title}</h1>
1143
+ <div class="calendar-title">
1144
+ <h1>${escapeHtml(title)}</h1>
840
1145
  </div>
841
1146
  ` : ""}
842
1147
 
843
- <div class="calendar--content">
844
- <div class="calendar--card">
845
- <div class="calendar--nav-header">
846
- <button type="button" class="calendar--nav-arrow" data-action="previous" aria-label="Previous month">
1148
+ <div class="calendar-content">
1149
+ <div class="calendar-card">
1150
+ <div class="calendar-nav-header">
1151
+ <button type="button" class="calendar-nav-arrow" data-action="previous" aria-label="Previous month">
847
1152
  &#8249;
848
1153
  </button>
849
1154
 
850
- <div class="calendar--picker-container" data-picker-container>
1155
+ <div class="calendar-picker-container" data-picker-container>
851
1156
  <button
852
1157
  type="button"
853
- class="calendar--picker-btn"
1158
+ class="calendar-picker-btn"
854
1159
  data-action="toggle-picker"
855
1160
  aria-expanded="${this.pickerOpen ? "true" : "false"}"
856
1161
  aria-haspopup="true"
857
1162
  >
858
1163
  ${useShortMonths ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
859
- <span class="calendar--picker-chevron">&#9662;</span>
1164
+ <span class="calendar-picker-chevron">&#9662;</span>
860
1165
  </button>
861
1166
 
862
1167
  ${this.pickerOpen ? `
863
- <div class="calendar--picker-dropdown">
864
- <div class="calendar--picker-year-row">
1168
+ <div class="calendar-picker-dropdown">
1169
+ <div class="calendar-picker-year-row">
865
1170
  <button
866
1171
  type="button"
867
- class="calendar--picker-year-arrow"
1172
+ class="calendar-picker-year-arrow"
868
1173
  data-action="year-prev"
869
1174
  ${viewModel.currentYear <= minYear ? "disabled" : ""}
870
1175
  aria-label="Previous year"
871
1176
  >&#8249;</button>
872
1177
  <input
873
1178
  type="text"
874
- class="calendar--picker-year-input${!this.yearInputValid ? " invalid" : ""}"
875
- value="${this.yearInput || viewModel.currentYear}"
1179
+ class="calendar-picker-year-input${!this.yearInputValid ? " invalid" : ""}"
1180
+ value="${escapeHtml(this.yearInput || viewModel.currentYear)}"
876
1181
  data-year-input
877
1182
  aria-label="Year"
878
1183
  />
879
1184
  <button
880
1185
  type="button"
881
- class="calendar--picker-year-arrow"
1186
+ class="calendar-picker-year-arrow"
882
1187
  data-action="year-next"
883
1188
  ${viewModel.currentYear >= maxYear ? "disabled" : ""}
884
1189
  aria-label="Next year"
885
1190
  >&#8250;</button>
886
1191
  </div>
887
1192
 
888
- <div class="calendar--picker-months">
1193
+ <div class="calendar-picker-months">
889
1194
  ${(useShortMonths ? MONTHS : MONTHS_FULL).map((month, index) => {
890
1195
  const isSelected = index === viewModel.currentMonth;
891
1196
  const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
892
1197
  return `
893
1198
  <button
894
1199
  type="button"
895
- class="calendar--picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
1200
+ class="calendar-picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
896
1201
  data-action="select-month"
897
1202
  data-month="${index}"
898
1203
  >${month}</button>
@@ -905,70 +1210,22 @@ var init_CalendarElement = __esm({
905
1210
 
906
1211
  <button
907
1212
  type="button"
908
- class="calendar--today-btn"
1213
+ class="calendar-today-btn"
909
1214
  data-action="today"
910
1215
  ${isCurrentMonth ? "disabled" : ""}
911
1216
  >Today</button>
912
1217
 
913
- <button type="button" class="calendar--nav-arrow" data-action="next" aria-label="Next month">
1218
+ <button type="button" class="calendar-nav-arrow" data-action="next" aria-label="Next month">
914
1219
  &#8250;
915
1220
  </button>
916
1221
  </div>
917
1222
 
918
- <table class="calendar--table calendar--table--bordered">
919
- <thead>
920
- <tr>
921
- ${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
922
- </tr>
923
- </thead>
924
- <tbody data-calendar-body>
925
- ${isLoading ? Array.from(
926
- { length: 6 },
927
- () => `<tr>${Array.from(
928
- { length: 7 },
929
- () => `<td class="calendar--skeleton" aria-hidden="true"></td>`
930
- ).join("")}</tr>`
931
- ).join("") : viewModel.calendarDates.map(
932
- (week) => `
933
- <tr>
934
- ${week.map((calendarDate, dayIndex) => {
935
- const classes = getCellClasses(calendarDate);
936
- if (availabilityMode && calendarDate.isCurrentMonth) {
937
- classes.push(
938
- calendarDate.hasEvents ? "availability--booked" : "availability--free"
939
- );
940
- }
941
- if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
942
- const d = calendarDate.date;
943
- if (this._rangeStart && isSameDay2(d, this._rangeStart))
944
- classes.push("availability--range-start");
945
- if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
946
- classes.push("availability--range-end");
947
- if (this._rangeStart && this._rangeEnd) {
948
- if (d > this._rangeStart && d < this._rangeEnd)
949
- classes.push("availability--in-range");
950
- }
951
- }
952
- const dateString = calendarDate.date.toISOString();
953
- return `
954
- <td
955
- class="${classes.join(" ")}"
956
- data-date="${dateString}"
957
- data-day-index="${dayIndex}"
958
- data-clickable="true"
959
- >
960
- ${calendarDate.date.getDate()}
961
- </td>
962
- `;
963
- }).join("")}
964
- </tr>
965
- `
966
- ).join("")}
967
- </tbody>
968
- </table>
1223
+ <div class="calendar-panes">
1224
+ ${viewModel.panes.map(renderPane).join("")}
1225
+ </div>
969
1226
 
970
1227
  ${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
971
- <div class="date-popup ${viewModel.popupPositionClass}">
1228
+ <div class="date-popup">
972
1229
  <div class="popup-header">
973
1230
  <h2>${viewModel.scheduleDay}</h2>
974
1231
  <button type="button" class="popup-close" data-action="close-popup" aria-label="Close">\u2715</button>
@@ -981,7 +1238,7 @@ var init_CalendarElement = __esm({
981
1238
  ` : ""}
982
1239
 
983
1240
  <div class="events-container">
984
- ${availabilityMode === "time" ? renderTimeGrid(viewModel.tasks, viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
1241
+ ${availabilityMode === "time" ? renderTimeGrid(viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
985
1242
  </div>
986
1243
  </div>
987
1244
  ` : ""}
@@ -1006,8 +1263,7 @@ var init_CalendarElement = __esm({
1006
1263
  const availMode = this.getAttribute("availability-mode");
1007
1264
  const isSelectable = this.hasAttribute("selectable");
1008
1265
  if (availMode === "day" && isSelectable) {
1009
- const isBooked = cell.classList.contains("availability--booked");
1010
- if (!isBooked) {
1266
+ if (this.isDateSelectable(date)) {
1011
1267
  let startDate;
1012
1268
  let endDate;
1013
1269
  if (this._rangeEnd !== null) {
@@ -1025,7 +1281,7 @@ var init_CalendarElement = __esm({
1025
1281
  cursor.setDate(cursor.getDate() + 1);
1026
1282
  let blocked = false;
1027
1283
  while (cursor < e2) {
1028
- if (this.engine.getEventsForDate(cursor).length > 0) {
1284
+ if (!this.isDateSelectable(cursor)) {
1029
1285
  blocked = true;
1030
1286
  break;
1031
1287
  }
@@ -1143,6 +1399,20 @@ var init_CalendarElement = __esm({
1143
1399
  const slotStart = actionEl.dataset.startTime;
1144
1400
  const slotEnd = actionEl.dataset.endTime;
1145
1401
  const slotDate = new Date(actionEl.dataset.date);
1402
+ const slotBooked = actionEl.dataset.booked === "true";
1403
+ this.dispatchEvent(
1404
+ new CustomEvent("cal-slot-select", {
1405
+ bubbles: true,
1406
+ composed: true,
1407
+ detail: {
1408
+ date: slotDate,
1409
+ startTime: slotStart,
1410
+ endTime: slotEnd,
1411
+ booked: slotBooked
1412
+ }
1413
+ })
1414
+ );
1415
+ if (slotBooked || !this.hasAttribute("selectable")) break;
1146
1416
  if (this._timeRangeComplete) {
1147
1417
  this._timeRangeDate = slotDate;
1148
1418
  this._timeRangeStart = slotStart;
@@ -1259,31 +1529,86 @@ var init_CalendarElement = __esm({
1259
1529
  this.theme = theme;
1260
1530
  }
1261
1531
  getCurrentDate() {
1532
+ this.rethrowInitError();
1262
1533
  return this.engine?.getViewModel().selectedDate ?? null;
1263
1534
  }
1264
1535
  goToDate(date) {
1536
+ this.rethrowInitError();
1265
1537
  const year = date.getFullYear();
1266
1538
  const month = date.getMonth();
1267
1539
  this.dispatchMonthChange(year, month);
1268
1540
  this.actions?.jump(year, month);
1269
1541
  }
1270
1542
  getEngine() {
1543
+ this.rethrowInitError();
1271
1544
  if (!this.engine)
1272
1545
  throw new Error("CalendarElement is not connected to the DOM");
1273
1546
  return this.engine;
1274
1547
  }
1275
1548
  };
1276
- CalendarElement.observedAttributes = [
1549
+ _CalendarElement.observedAttributes = [
1277
1550
  "initial-date",
1278
1551
  "min-year",
1279
1552
  "max-year",
1280
1553
  "week-starts-on",
1554
+ "heading",
1555
+ "months",
1556
+ "slot-duration",
1281
1557
  "title",
1282
1558
  "use-short-month-names",
1283
1559
  "availability-mode",
1284
1560
  "selectable",
1285
1561
  "loading"
1286
1562
  ];
1563
+ _CalendarElement.themeMap = {
1564
+ primary: "--calendar-primary-color",
1565
+ secondary: "--calendar-secondary-color",
1566
+ tertiary: "--calendar-tertiary-color",
1567
+ textColor: "--calendar-text-color",
1568
+ textLight: "--calendar-text-light",
1569
+ background: "--calendar-background",
1570
+ cellHover: "--calendar-cell-hover",
1571
+ borderColor: "--calendar-border-color",
1572
+ todayOutline: "--calendar-today-outline",
1573
+ selectedBg: "--calendar-selected-bg",
1574
+ headerBg: "--calendar-header-bg",
1575
+ popupBg: "--calendar-popup-bg",
1576
+ pickerBg: "--calendar-picker-bg",
1577
+ pickerShadow: "--calendar-picker-shadow",
1578
+ eventIndicator: "--calendar-event-indicator",
1579
+ onAccent: "--calendar-on-accent",
1580
+ link: "--calendar-link",
1581
+ openBg: "--calendar-open-bg",
1582
+ openFg: "--calendar-open-fg",
1583
+ conditionalBg: "--calendar-conditional-bg",
1584
+ conditionalFg: "--calendar-conditional-fg",
1585
+ blockedBg: "--calendar-blocked-bg",
1586
+ blockedFg: "--calendar-blocked-fg",
1587
+ rangeBg: "--calendar-range-bg",
1588
+ rangeOutline: "--calendar-range-outline",
1589
+ inRangeBg: "--calendar-in-range-bg",
1590
+ inRangeOutline: "--calendar-in-range-outline",
1591
+ badgeBg: "--calendar-badge-bg",
1592
+ badgeText: "--calendar-badge-text",
1593
+ badgeSuccessBg: "--calendar-badge-success-bg",
1594
+ badgeSuccessText: "--calendar-badge-success-text",
1595
+ badgeInfoBg: "--calendar-badge-info-bg",
1596
+ badgeInfoText: "--calendar-badge-info-text",
1597
+ badgeWarningBg: "--calendar-badge-warning-bg",
1598
+ badgeWarningText: "--calendar-badge-warning-text",
1599
+ badgeDangerBg: "--calendar-badge-danger-bg",
1600
+ badgeDangerText: "--calendar-badge-danger-text",
1601
+ badgeNeutralBg: "--calendar-badge-neutral-bg",
1602
+ badgeNeutralText: "--calendar-badge-neutral-text",
1603
+ badgePositiveBg: "--calendar-badge-positive-bg",
1604
+ badgePositiveText: "--calendar-badge-positive-text",
1605
+ badgeTentativeBg: "--calendar-badge-tentative-bg",
1606
+ badgeTentativeText: "--calendar-badge-tentative-text"
1607
+ };
1608
+ _CalendarElement.warnedMissingEnd = /* @__PURE__ */ new Set();
1609
+ _CalendarElement.BUILT_IN_BUCKETS = ["blocked", "conditional", "open"];
1610
+ _CalendarElement.titleDeprecationWarned = false;
1611
+ CalendarElement = _CalendarElement;
1287
1612
  }
1288
1613
  });
1289
1614
 
@@ -1294,11 +1619,18 @@ __export(web_components_exports, {
1294
1619
  CalendarEngine: () => CalendarEngine,
1295
1620
  DAYS: () => DAYS,
1296
1621
  DEFAULT_CATEGORY_COLORS: () => DEFAULT_CATEGORY_COLORS,
1622
+ DEFAULT_SLOT_DURATION: () => DEFAULT_SLOT_DURATION,
1623
+ MINUTES_PER_DAY: () => MINUTES_PER_DAY,
1297
1624
  MONTHS: () => MONTHS,
1298
1625
  MONTHS_FULL: () => MONTHS_FULL,
1626
+ bookedSlots: () => bookedSlots,
1299
1627
  defineCalendarElement: () => defineCalendarElement,
1628
+ escapeHtml: () => escapeHtml,
1629
+ eventCoversDate: () => eventCoversDate,
1630
+ eventInterval: () => eventInterval,
1300
1631
  formatAttendees: () => formatAttendees,
1301
1632
  formatDateForDisplay: () => formatDateForDisplay,
1633
+ formatMinutes: () => formatMinutes,
1302
1634
  formatTimeRange: () => formatTimeRange,
1303
1635
  generateCalendarDates: () => generateCalendarDates,
1304
1636
  generateYears: () => generateYears,
@@ -1307,13 +1639,17 @@ __export(web_components_exports, {
1307
1639
  getDefaultEventColor: () => getDefaultEventColor,
1308
1640
  getEventsForDate: () => getEventsForDate,
1309
1641
  getMonthYearText: () => getMonthYearText,
1310
- getPopupPositionClass: () => getPopupPositionClass,
1311
1642
  hasEvents: () => hasEvents,
1312
1643
  isSameDay: () => isSameDay,
1313
1644
  isToday: () => isToday,
1314
1645
  isValidHexColor: () => isValidHexColor,
1315
1646
  mergeCategoryColors: () => mergeCategoryColors,
1647
+ mergeIntervals: () => mergeIntervals,
1316
1648
  normalizeDate: () => normalizeDate,
1649
+ parseTimeToMinutes: () => parseTimeToMinutes,
1650
+ safeColor: () => safeColor,
1651
+ safeUrl: () => safeUrl,
1652
+ slugifyToken: () => slugifyToken,
1317
1653
  sortEventsByTime: () => sortEventsByTime
1318
1654
  });
1319
1655
  module.exports = __toCommonJS(web_components_exports);
@@ -1332,11 +1668,18 @@ if (typeof customElements !== "undefined" && !customElements.get("kal-calendar")
1332
1668
  CalendarEngine,
1333
1669
  DAYS,
1334
1670
  DEFAULT_CATEGORY_COLORS,
1671
+ DEFAULT_SLOT_DURATION,
1672
+ MINUTES_PER_DAY,
1335
1673
  MONTHS,
1336
1674
  MONTHS_FULL,
1675
+ bookedSlots,
1337
1676
  defineCalendarElement,
1677
+ escapeHtml,
1678
+ eventCoversDate,
1679
+ eventInterval,
1338
1680
  formatAttendees,
1339
1681
  formatDateForDisplay,
1682
+ formatMinutes,
1340
1683
  formatTimeRange,
1341
1684
  generateCalendarDates,
1342
1685
  generateYears,
@@ -1345,13 +1688,17 @@ if (typeof customElements !== "undefined" && !customElements.get("kal-calendar")
1345
1688
  getDefaultEventColor,
1346
1689
  getEventsForDate,
1347
1690
  getMonthYearText,
1348
- getPopupPositionClass,
1349
1691
  hasEvents,
1350
1692
  isSameDay,
1351
1693
  isToday,
1352
1694
  isValidHexColor,
1353
1695
  mergeCategoryColors,
1696
+ mergeIntervals,
1354
1697
  normalizeDate,
1698
+ parseTimeToMinutes,
1699
+ safeColor,
1700
+ safeUrl,
1701
+ slugifyToken,
1355
1702
  sortEventsByTime
1356
1703
  });
1357
1704
  //# sourceMappingURL=index.js.map