kalendly 0.2.2 → 0.3.0
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 +202 -29
- package/dist/core/index.d.mts +48 -10
- package/dist/core/index.d.ts +48 -10
- package/dist/core/index.js +60 -26
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +56 -25
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +74 -10
- package/dist/index.d.ts +74 -10
- package/dist/index.js +373 -162
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +369 -161
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +363 -158
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +445 -395
- package/package.json +1 -1
package/dist/index.umd.js
CHANGED
|
@@ -139,26 +139,16 @@ var Kalendly = (() => {
|
|
|
139
139
|
}
|
|
140
140
|
return dates;
|
|
141
141
|
}
|
|
142
|
-
function getPopupPositionClass(selectedDayIndex) {
|
|
143
|
-
if (selectedDayIndex === null) return "popup-center-bottom";
|
|
144
|
-
if (selectedDayIndex < 3) {
|
|
145
|
-
return "popup-right";
|
|
146
|
-
} else if (selectedDayIndex > 4) {
|
|
147
|
-
return "popup-left";
|
|
148
|
-
} else {
|
|
149
|
-
return "popup-center-bottom";
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
142
|
function getCellClasses(calendarDate) {
|
|
153
143
|
const classes = [];
|
|
154
144
|
if (!calendarDate.isCurrentMonth) {
|
|
155
|
-
classes.push("other-month");
|
|
145
|
+
classes.push("calendar-cell-other-month");
|
|
156
146
|
}
|
|
157
147
|
if (calendarDate.isToday) {
|
|
158
|
-
classes.push("
|
|
148
|
+
classes.push("calendar-cell-today");
|
|
159
149
|
}
|
|
160
150
|
if (calendarDate.hasEvents) {
|
|
161
|
-
classes.push("has
|
|
151
|
+
classes.push("calendar-cell-has-event");
|
|
162
152
|
}
|
|
163
153
|
return classes;
|
|
164
154
|
}
|
|
@@ -208,6 +198,30 @@ var Kalendly = (() => {
|
|
|
208
198
|
const color = colorMap[category] || colorMap.other || "#fc8917";
|
|
209
199
|
return isValidHexColor(color) ? color : "#fc8917";
|
|
210
200
|
}
|
|
201
|
+
var HTML_ESCAPES = {
|
|
202
|
+
"&": "&",
|
|
203
|
+
"<": "<",
|
|
204
|
+
">": ">",
|
|
205
|
+
'"': """,
|
|
206
|
+
"'": "'"
|
|
207
|
+
};
|
|
208
|
+
function escapeHtml(value) {
|
|
209
|
+
if (value === null || value === void 0) return "";
|
|
210
|
+
return String(value).replace(/[&<>"']/g, (char) => HTML_ESCAPES[char]);
|
|
211
|
+
}
|
|
212
|
+
function slugifyToken(value) {
|
|
213
|
+
return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
214
|
+
}
|
|
215
|
+
function safeUrl(value) {
|
|
216
|
+
const normalized = String(value).split("").filter((char) => char.charCodeAt(0) > 32).join("");
|
|
217
|
+
if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;
|
|
218
|
+
const schemeless = !/^[^/?#]*:/.test(normalized);
|
|
219
|
+
return schemeless ? normalized : "#";
|
|
220
|
+
}
|
|
221
|
+
function safeColor(value) {
|
|
222
|
+
const trimmed = String(value).trim();
|
|
223
|
+
return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
|
|
224
|
+
}
|
|
211
225
|
|
|
212
226
|
// src/core/calendar-engine.ts
|
|
213
227
|
var CalendarEngine = class {
|
|
@@ -248,24 +262,38 @@ var Kalendly = (() => {
|
|
|
248
262
|
* Get view model with computed properties
|
|
249
263
|
*/
|
|
250
264
|
getViewModel() {
|
|
251
|
-
const
|
|
252
|
-
this.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
265
|
+
const panes = Array.from(
|
|
266
|
+
{ length: Math.max(1, this.config.monthCount ?? 1) },
|
|
267
|
+
(_, offset) => {
|
|
268
|
+
const anchor = new Date(
|
|
269
|
+
this.state.currentYear,
|
|
270
|
+
this.state.currentMonth + offset,
|
|
271
|
+
1
|
|
272
|
+
);
|
|
273
|
+
const year = anchor.getFullYear();
|
|
274
|
+
const month = anchor.getMonth();
|
|
275
|
+
return {
|
|
276
|
+
year,
|
|
277
|
+
month,
|
|
278
|
+
monthAndYearText: getMonthYearText(year, month),
|
|
279
|
+
calendarDates: generateCalendarDates(
|
|
280
|
+
year,
|
|
281
|
+
month,
|
|
282
|
+
this.config.events,
|
|
283
|
+
this.config.weekStartsOn
|
|
284
|
+
)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
256
287
|
);
|
|
257
288
|
return {
|
|
258
289
|
...this.state,
|
|
259
290
|
months: MONTHS,
|
|
260
291
|
days: DAYS,
|
|
261
292
|
years: generateYears(this.config.minYear, this.config.maxYear),
|
|
262
|
-
monthAndYearText:
|
|
263
|
-
this.state.currentYear,
|
|
264
|
-
this.state.currentMonth
|
|
265
|
-
),
|
|
293
|
+
monthAndYearText: panes[0].monthAndYearText,
|
|
266
294
|
scheduleDay: this.state.selectedDate ? formatDateForDisplay(this.state.selectedDate) : "",
|
|
267
|
-
|
|
268
|
-
|
|
295
|
+
panes,
|
|
296
|
+
calendarDates: panes[0].calendarDates
|
|
269
297
|
};
|
|
270
298
|
}
|
|
271
299
|
/**
|
|
@@ -439,7 +467,7 @@ var Kalendly = (() => {
|
|
|
439
467
|
|
|
440
468
|
// src/web-components/CalendarElement.ts
|
|
441
469
|
var isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
442
|
-
var
|
|
470
|
+
var _CalendarElement = class _CalendarElement extends HTMLElement {
|
|
443
471
|
constructor() {
|
|
444
472
|
super(...arguments);
|
|
445
473
|
this.engine = null;
|
|
@@ -460,6 +488,10 @@ var Kalendly = (() => {
|
|
|
460
488
|
this._categoryColors = null;
|
|
461
489
|
this._renderEvent = null;
|
|
462
490
|
this._renderNoEvents = null;
|
|
491
|
+
this._availabilityColors = null;
|
|
492
|
+
this._selectableStatuses = null;
|
|
493
|
+
this._initError = null;
|
|
494
|
+
this.pendingUpdate = null;
|
|
463
495
|
// Selection state (availability mode — range)
|
|
464
496
|
this._rangeStart = null;
|
|
465
497
|
this._rangeEnd = null;
|
|
@@ -472,6 +504,7 @@ var Kalendly = (() => {
|
|
|
472
504
|
return this._events;
|
|
473
505
|
}
|
|
474
506
|
set events(val) {
|
|
507
|
+
this._initError = null;
|
|
475
508
|
this._events = val;
|
|
476
509
|
if (this.engine) {
|
|
477
510
|
this.engine.updateEvents(val);
|
|
@@ -487,13 +520,28 @@ var Kalendly = (() => {
|
|
|
487
520
|
this.engine.updateCategoryColors(val);
|
|
488
521
|
}
|
|
489
522
|
}
|
|
523
|
+
get availabilityColors() {
|
|
524
|
+
return this._availabilityColors ?? {};
|
|
525
|
+
}
|
|
526
|
+
set availabilityColors(val) {
|
|
527
|
+
this._initError = null;
|
|
528
|
+
this._availabilityColors = val;
|
|
529
|
+
if (this.engine) this.scheduleRender();
|
|
530
|
+
}
|
|
531
|
+
get selectableStatuses() {
|
|
532
|
+
return this._selectableStatuses ?? [];
|
|
533
|
+
}
|
|
534
|
+
set selectableStatuses(val) {
|
|
535
|
+
this._selectableStatuses = val;
|
|
536
|
+
if (this.engine) this.scheduleRender();
|
|
537
|
+
}
|
|
490
538
|
set renderEvent(val) {
|
|
491
539
|
this._renderEvent = val;
|
|
492
|
-
if (this.engine) this.
|
|
540
|
+
if (this.engine) this.scheduleRender();
|
|
493
541
|
}
|
|
494
542
|
set renderNoEvents(val) {
|
|
495
543
|
this._renderNoEvents = val;
|
|
496
|
-
if (this.engine) this.
|
|
544
|
+
if (this.engine) this.scheduleRender();
|
|
497
545
|
}
|
|
498
546
|
get loading() {
|
|
499
547
|
return this.hasAttribute("loading");
|
|
@@ -502,10 +550,22 @@ var Kalendly = (() => {
|
|
|
502
550
|
if (val) this.setAttribute("loading", "");
|
|
503
551
|
else this.removeAttribute("loading");
|
|
504
552
|
}
|
|
553
|
+
// Custom element reactions report rather than propagate, so a failure here
|
|
554
|
+
// is kept and resurfaced at the next call the integrator makes
|
|
555
|
+
reaction(run) {
|
|
556
|
+
try {
|
|
557
|
+
run();
|
|
558
|
+
} catch (error) {
|
|
559
|
+
this._initError = error;
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
505
563
|
connectedCallback() {
|
|
506
564
|
this.classList.add("kalendly-calendar");
|
|
507
|
-
this.
|
|
508
|
-
|
|
565
|
+
this.reaction(() => {
|
|
566
|
+
this.initEngine();
|
|
567
|
+
this.render();
|
|
568
|
+
});
|
|
509
569
|
}
|
|
510
570
|
disconnectedCallback() {
|
|
511
571
|
this.cleanup();
|
|
@@ -515,7 +575,7 @@ var Kalendly = (() => {
|
|
|
515
575
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
516
576
|
if (oldVal === newVal) return;
|
|
517
577
|
if (name === "loading") {
|
|
518
|
-
this.
|
|
578
|
+
this.scheduleRender();
|
|
519
579
|
return;
|
|
520
580
|
}
|
|
521
581
|
if (name === "selectable" && newVal === null) {
|
|
@@ -528,6 +588,23 @@ var Kalendly = (() => {
|
|
|
528
588
|
}
|
|
529
589
|
this.reinit();
|
|
530
590
|
}
|
|
591
|
+
get headingText() {
|
|
592
|
+
const heading = this.getAttribute("heading");
|
|
593
|
+
if (heading !== null) return heading;
|
|
594
|
+
const title = this.getAttribute("title");
|
|
595
|
+
if (title !== null && !_CalendarElement.titleDeprecationWarned) {
|
|
596
|
+
_CalendarElement.titleDeprecationWarned = true;
|
|
597
|
+
console.warn(
|
|
598
|
+
`<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.`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return title;
|
|
602
|
+
}
|
|
603
|
+
get monthCount() {
|
|
604
|
+
const requested = Number(this.getAttribute("months") ?? 1);
|
|
605
|
+
if (!Number.isInteger(requested) || requested < 1) return 1;
|
|
606
|
+
return Math.min(requested, 2);
|
|
607
|
+
}
|
|
531
608
|
get minYear() {
|
|
532
609
|
const val = this.getAttribute("min-year");
|
|
533
610
|
return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() - 30;
|
|
@@ -547,19 +624,20 @@ var Kalendly = (() => {
|
|
|
547
624
|
minYear: this.minYear,
|
|
548
625
|
maxYear: this.maxYear,
|
|
549
626
|
weekStartsOn,
|
|
550
|
-
categoryColors: this._categoryColors ?? void 0
|
|
627
|
+
categoryColors: this._categoryColors ?? void 0,
|
|
628
|
+
monthCount: this.monthCount
|
|
551
629
|
});
|
|
552
630
|
this.actions = this.engine.getActions();
|
|
553
631
|
this.applyTheme();
|
|
554
632
|
this.unsubscribe = this.engine.subscribe(() => {
|
|
555
|
-
this.
|
|
633
|
+
this.scheduleRender();
|
|
556
634
|
});
|
|
557
635
|
}
|
|
558
636
|
reinit() {
|
|
559
637
|
if (!this.engine) return;
|
|
560
638
|
this.cleanup();
|
|
561
639
|
this.initEngine();
|
|
562
|
-
this.
|
|
640
|
+
this.scheduleRender();
|
|
563
641
|
}
|
|
564
642
|
cleanup() {
|
|
565
643
|
if (this.unsubscribe) {
|
|
@@ -596,42 +674,84 @@ var Kalendly = (() => {
|
|
|
596
674
|
applyTheme() {
|
|
597
675
|
if (!this._theme) return;
|
|
598
676
|
const root = document.documentElement;
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
root.style.setProperty(
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
677
|
+
for (const key of Object.keys(_CalendarElement.themeMap)) {
|
|
678
|
+
const value = this._theme[key];
|
|
679
|
+
if (value) root.style.setProperty(_CalendarElement.themeMap[key], value);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
get knownBuckets() {
|
|
683
|
+
return [
|
|
684
|
+
..._CalendarElement.BUILT_IN_BUCKETS,
|
|
685
|
+
...Object.keys(this._availabilityColors ?? {})
|
|
686
|
+
];
|
|
687
|
+
}
|
|
688
|
+
get updateComplete() {
|
|
689
|
+
return this.pendingUpdate ?? Promise.resolve();
|
|
690
|
+
}
|
|
691
|
+
scheduleRender() {
|
|
692
|
+
if (this.pendingUpdate) return;
|
|
693
|
+
this.pendingUpdate = Promise.resolve().then(() => {
|
|
694
|
+
this.pendingUpdate = null;
|
|
695
|
+
try {
|
|
696
|
+
this.render();
|
|
697
|
+
} catch (error) {
|
|
698
|
+
this._initError = error;
|
|
699
|
+
throw error;
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
rethrowInitError() {
|
|
704
|
+
if (this._initError) throw this._initError;
|
|
705
|
+
}
|
|
706
|
+
assertStatusesDeclared() {
|
|
707
|
+
if (!this.getAttribute("availability-mode")) return;
|
|
708
|
+
const missing = this._events.filter(
|
|
709
|
+
(event) => typeof event.availabilityStatus !== "string" || event.availabilityStatus === ""
|
|
710
|
+
).map((event) => String(event.id));
|
|
711
|
+
if (missing.length) {
|
|
712
|
+
throw new Error(
|
|
713
|
+
`<kal-calendar> availability-mode requires availabilityStatus on every event. Missing on: ${missing.join(", ")}.`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
assertStatusesKnown() {
|
|
718
|
+
const known = new Set(this.knownBuckets);
|
|
719
|
+
const unknown = this._events.filter((event) => !known.has(event.availabilityStatus)).map((event) => `${event.id} (${event.availabilityStatus})`);
|
|
720
|
+
if (unknown.length) {
|
|
721
|
+
throw new Error(
|
|
722
|
+
`<kal-calendar> availabilityStatus must name a built-in bucket (${_CalendarElement.BUILT_IN_BUCKETS.join(", ")}) or a key of availabilityColors. Unrecognised on: ${unknown.join(", ")}.`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
resolveBucket(date) {
|
|
727
|
+
if (!this.engine) return "open";
|
|
728
|
+
const declared = new Set(
|
|
729
|
+
this.engine.getEventsForDate(date).map((event) => event.availabilityStatus)
|
|
730
|
+
);
|
|
731
|
+
if (declared.size === 0) return "open";
|
|
732
|
+
return this.knownBuckets.find((bucket) => declared.has(bucket));
|
|
733
|
+
}
|
|
734
|
+
isDateSelectable(date) {
|
|
735
|
+
if (!this.engine) return false;
|
|
736
|
+
if (this._selectableStatuses) {
|
|
737
|
+
return this._selectableStatuses.includes(this.resolveBucket(date));
|
|
738
|
+
}
|
|
739
|
+
return this.engine.getEventsForDate(date).length === 0;
|
|
624
740
|
}
|
|
625
741
|
render() {
|
|
626
742
|
if (!this.engine) return;
|
|
627
743
|
const viewModel = this.engine.getViewModel();
|
|
628
744
|
const useShortMonths = this.hasAttribute("use-short-month-names");
|
|
629
|
-
const title = this.
|
|
745
|
+
const title = this.headingText;
|
|
630
746
|
const minYear = this.minYear;
|
|
631
747
|
const maxYear = this.maxYear;
|
|
632
748
|
const availabilityMode = this.getAttribute("availability-mode");
|
|
633
749
|
const selectable = this.hasAttribute("selectable");
|
|
634
750
|
const isLoading = this.loading;
|
|
751
|
+
if (availabilityMode) {
|
|
752
|
+
this.assertStatusesDeclared();
|
|
753
|
+
this.assertStatusesKnown();
|
|
754
|
+
}
|
|
635
755
|
const today = /* @__PURE__ */ new Date();
|
|
636
756
|
const todayMonth = today.getMonth();
|
|
637
757
|
const todayYear = today.getFullYear();
|
|
@@ -640,7 +760,7 @@ var Kalendly = (() => {
|
|
|
640
760
|
const defaultRenderEvent = (event) => {
|
|
641
761
|
const timeRange = formatTimeRange(event);
|
|
642
762
|
const attendeesList = formatAttendees(event.attendees);
|
|
643
|
-
let borderColor = event.color || "#3b82f6";
|
|
763
|
+
let borderColor = safeColor(event.color || "#3b82f6");
|
|
644
764
|
if (event.category) {
|
|
645
765
|
borderColor = this.engine.getCategoryColor(event.category);
|
|
646
766
|
}
|
|
@@ -675,56 +795,56 @@ var Kalendly = (() => {
|
|
|
675
795
|
return `
|
|
676
796
|
<div class="event-card" style="border-left-color: ${borderColor}">
|
|
677
797
|
<div class="event-header">
|
|
678
|
-
<div class="event-title">${event.name}</div>
|
|
798
|
+
<div class="event-title">${escapeHtml(event.name)}</div>
|
|
679
799
|
<div class="event-badges">
|
|
680
|
-
${event.category ? `<span class="badge category-${event.category}">${getCategoryLabel(event.category)}</span>` : ""}
|
|
681
|
-
${event.priority ? `<span class="badge priority-${event.priority}">${getPriorityLabel(event.priority)}</span>` : ""}
|
|
682
|
-
${event.status && event.status !== "scheduled" ? `<span class="badge status-${event.status}">${getStatusLabel(event.status)}</span>` : ""}
|
|
800
|
+
${event.category ? `<span class="badge category category-${slugifyToken(event.category)}">${escapeHtml(getCategoryLabel(event.category))}</span>` : ""}
|
|
801
|
+
${event.priority ? `<span class="badge priority priority-${slugifyToken(event.priority)}">${escapeHtml(getPriorityLabel(event.priority))}</span>` : ""}
|
|
802
|
+
${event.status && event.status !== "scheduled" ? `<span class="badge status status-${slugifyToken(event.status)}">${escapeHtml(getStatusLabel(event.status))}</span>` : ""}
|
|
683
803
|
</div>
|
|
684
804
|
</div>
|
|
685
805
|
|
|
686
806
|
${timeRange ? `
|
|
687
807
|
<div class="event-time">
|
|
688
808
|
<span class="event-time-label">Time:</span>
|
|
689
|
-
<span class="event-time-value">${timeRange}</span>
|
|
809
|
+
<span class="event-time-value">${escapeHtml(timeRange)}</span>
|
|
690
810
|
</div>
|
|
691
811
|
` : ""}
|
|
692
812
|
|
|
693
813
|
${event.description ? `
|
|
694
|
-
<div class="event-description">${event.description}</div>
|
|
814
|
+
<div class="event-description">${escapeHtml(event.description)}</div>
|
|
695
815
|
` : ""}
|
|
696
816
|
|
|
697
817
|
${event.location ? `
|
|
698
818
|
<div class="event-time">
|
|
699
819
|
<span class="event-time-label">Location:</span>
|
|
700
|
-
<span class="event-time-value">${event.location}</span>
|
|
820
|
+
<span class="event-time-value">${escapeHtml(event.location)}</span>
|
|
701
821
|
</div>
|
|
702
822
|
` : ""}
|
|
703
823
|
|
|
704
824
|
${attendeesList ? `
|
|
705
825
|
<div class="event-time">
|
|
706
826
|
<span class="event-time-label">Attendees:</span>
|
|
707
|
-
<span class="event-time-value">${attendeesList}</span>
|
|
827
|
+
<span class="event-time-value">${escapeHtml(attendeesList)}</span>
|
|
708
828
|
</div>
|
|
709
829
|
` : ""}
|
|
710
830
|
|
|
711
831
|
${event.organizer ? `
|
|
712
832
|
<div class="event-time">
|
|
713
833
|
<span class="event-time-label">Organizer:</span>
|
|
714
|
-
<span class="event-time-value">${event.organizer}</span>
|
|
834
|
+
<span class="event-time-value">${escapeHtml(event.organizer)}</span>
|
|
715
835
|
</div>
|
|
716
836
|
` : ""}
|
|
717
837
|
|
|
718
838
|
${event.notes ? `
|
|
719
839
|
<div class="event-time">
|
|
720
840
|
<span class="event-time-label">Notes:</span>
|
|
721
|
-
<span class="event-time-value">${event.notes}</span>
|
|
841
|
+
<span class="event-time-value">${escapeHtml(event.notes)}</span>
|
|
722
842
|
</div>
|
|
723
843
|
` : ""}
|
|
724
844
|
|
|
725
845
|
${event.url ? `
|
|
726
846
|
<div class="event-time">
|
|
727
|
-
<a href="${event.url}" target="_blank" rel="noopener noreferrer" class="event-link">
|
|
847
|
+
<a href="${escapeHtml(safeUrl(event.url))}" target="_blank" rel="noopener noreferrer" class="event-link">
|
|
728
848
|
View Details \u2192
|
|
729
849
|
</a>
|
|
730
850
|
</div>
|
|
@@ -732,7 +852,7 @@ var Kalendly = (() => {
|
|
|
732
852
|
|
|
733
853
|
${event.tags && event.tags.length > 0 ? `
|
|
734
854
|
<div class="event-tags">
|
|
735
|
-
${event.tags.map((tag) => `<span class="event-tag">${tag}</span>`).join("")}
|
|
855
|
+
${event.tags.map((tag) => `<span class="event-tag">${escapeHtml(tag)}</span>`).join("")}
|
|
736
856
|
</div>
|
|
737
857
|
` : ""}
|
|
738
858
|
</div>
|
|
@@ -742,11 +862,17 @@ var Kalendly = (() => {
|
|
|
742
862
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
743
863
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
744
864
|
const renderTimeGrid = (events, date) => {
|
|
865
|
+
const startHour = (time) => {
|
|
866
|
+
if (typeof time !== "string") return null;
|
|
867
|
+
const hour = Number(time.split(":")[0]);
|
|
868
|
+
return Number.isInteger(hour) && hour >= 0 && hour <= 24 ? hour : null;
|
|
869
|
+
};
|
|
745
870
|
const isHourBooked = (hour) => events.some((event) => {
|
|
746
|
-
if (!event.startTime
|
|
747
|
-
const
|
|
748
|
-
const
|
|
749
|
-
|
|
871
|
+
if (!event.startTime) return true;
|
|
872
|
+
const from = startHour(event.startTime);
|
|
873
|
+
const to = event.endTime ? startHour(event.endTime) : 24;
|
|
874
|
+
if (from === null || to === null) return true;
|
|
875
|
+
return hour >= from && hour < to;
|
|
750
876
|
});
|
|
751
877
|
const slots = Array.from({ length: 24 }, (_, hour) => {
|
|
752
878
|
const startTime = `${String(hour).padStart(2, "0")}:00`;
|
|
@@ -757,81 +883,156 @@ var Kalendly = (() => {
|
|
|
757
883
|
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
758
884
|
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
759
885
|
const slotClasses = [
|
|
760
|
-
"time-
|
|
761
|
-
booked ? "time-
|
|
762
|
-
isRangeStart ? "time-
|
|
763
|
-
isRangeEnd ? "time-
|
|
764
|
-
isInRange ? "time-
|
|
886
|
+
"time-grid-slot",
|
|
887
|
+
booked ? "time-grid-slot-blocked" : "time-grid-slot-open",
|
|
888
|
+
isRangeStart ? "time-grid-slot-range-start" : "",
|
|
889
|
+
isRangeEnd ? "time-grid-slot-range-end" : "",
|
|
890
|
+
isInRange ? "time-grid-slot-in-range" : ""
|
|
765
891
|
].filter(Boolean).join(" ");
|
|
766
892
|
const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
|
|
767
893
|
return `
|
|
768
894
|
<div class="${slotClasses}" ${slotAttrs}>
|
|
769
|
-
<span class="time-
|
|
770
|
-
<span class="time-
|
|
895
|
+
<span class="time-grid-label">${startTime}</span>
|
|
896
|
+
<span class="time-grid-status">${booked ? "Booked" : "Available"}</span>
|
|
771
897
|
</div>`;
|
|
772
898
|
});
|
|
773
899
|
return `<div class="time-grid">${slots.join("")}</div>`;
|
|
774
900
|
};
|
|
901
|
+
const multiMonth = viewModel.panes.length > 1;
|
|
902
|
+
const renderPane = (pane) => `
|
|
903
|
+
<div class="calendar-pane">
|
|
904
|
+
${multiMonth ? `<div class="calendar-pane-caption">${escapeHtml(pane.monthAndYearText)}</div>` : ""}
|
|
905
|
+
<table class="calendar-table calendar-table-bordered">
|
|
906
|
+
<thead>
|
|
907
|
+
<tr>
|
|
908
|
+
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
909
|
+
</tr>
|
|
910
|
+
</thead>
|
|
911
|
+
<tbody data-calendar-body>
|
|
912
|
+
${isLoading ? Array.from(
|
|
913
|
+
{ length: 6 },
|
|
914
|
+
() => `<tr>${Array.from(
|
|
915
|
+
{ length: 7 },
|
|
916
|
+
() => `<td class="calendar-skeleton" aria-hidden="true"></td>`
|
|
917
|
+
).join("")}</tr>`
|
|
918
|
+
).join("") : pane.calendarDates.map(
|
|
919
|
+
(week) => `
|
|
920
|
+
<tr>
|
|
921
|
+
${week.map((calendarDate, dayIndex) => {
|
|
922
|
+
const classes = getCellClasses(calendarDate);
|
|
923
|
+
const cellAttrs = [];
|
|
924
|
+
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
925
|
+
const bucket = this.resolveBucket(
|
|
926
|
+
calendarDate.date
|
|
927
|
+
);
|
|
928
|
+
const custom = (this._availabilityColors ?? {})[bucket];
|
|
929
|
+
classes.push(
|
|
930
|
+
`availability-${slugifyToken(bucket)}`
|
|
931
|
+
);
|
|
932
|
+
if (custom) {
|
|
933
|
+
classes.push("availability-status");
|
|
934
|
+
cellAttrs.push(
|
|
935
|
+
`style="--availability-color: ${escapeHtml(safeColor(custom))}"`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
if (!this.isDateSelectable(calendarDate.date)) {
|
|
939
|
+
classes.push("availability-unselectable");
|
|
940
|
+
}
|
|
941
|
+
cellAttrs.push(
|
|
942
|
+
`aria-label="${escapeHtml(bucket)}"`
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
946
|
+
const d = calendarDate.date;
|
|
947
|
+
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
948
|
+
classes.push("availability-range-start");
|
|
949
|
+
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
950
|
+
classes.push("availability-range-end");
|
|
951
|
+
if (this._rangeStart && this._rangeEnd) {
|
|
952
|
+
if (d > this._rangeStart && d < this._rangeEnd)
|
|
953
|
+
classes.push("availability-in-range");
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const dateString = calendarDate.date.toISOString();
|
|
957
|
+
return `
|
|
958
|
+
<td
|
|
959
|
+
class="${classes.join(" ")}"
|
|
960
|
+
data-date="${dateString}"
|
|
961
|
+
data-day-index="${dayIndex}"
|
|
962
|
+
data-clickable="true"
|
|
963
|
+
${cellAttrs.join(" ")}
|
|
964
|
+
>
|
|
965
|
+
${calendarDate.date.getDate()}
|
|
966
|
+
</td>
|
|
967
|
+
`;
|
|
968
|
+
}).join("")}
|
|
969
|
+
</tr>
|
|
970
|
+
`
|
|
971
|
+
).join("")}
|
|
972
|
+
</tbody>
|
|
973
|
+
</table>
|
|
974
|
+
</div>
|
|
975
|
+
`;
|
|
775
976
|
const html = `
|
|
776
977
|
${title ? `
|
|
777
|
-
<div class="
|
|
778
|
-
<h1>${title}</h1>
|
|
978
|
+
<div class="calendar-title">
|
|
979
|
+
<h1>${escapeHtml(title)}</h1>
|
|
779
980
|
</div>
|
|
780
981
|
` : ""}
|
|
781
982
|
|
|
782
|
-
<div class="calendar
|
|
783
|
-
<div class="calendar
|
|
784
|
-
<div class="calendar
|
|
785
|
-
<button type="button" class="calendar
|
|
983
|
+
<div class="calendar-content">
|
|
984
|
+
<div class="calendar-card">
|
|
985
|
+
<div class="calendar-nav-header">
|
|
986
|
+
<button type="button" class="calendar-nav-arrow" data-action="previous" aria-label="Previous month">
|
|
786
987
|
‹
|
|
787
988
|
</button>
|
|
788
989
|
|
|
789
|
-
<div class="calendar
|
|
990
|
+
<div class="calendar-picker-container" data-picker-container>
|
|
790
991
|
<button
|
|
791
992
|
type="button"
|
|
792
|
-
class="calendar
|
|
993
|
+
class="calendar-picker-btn"
|
|
793
994
|
data-action="toggle-picker"
|
|
794
995
|
aria-expanded="${this.pickerOpen ? "true" : "false"}"
|
|
795
996
|
aria-haspopup="true"
|
|
796
997
|
>
|
|
797
998
|
${useShortMonths ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
|
|
798
|
-
<span class="calendar
|
|
999
|
+
<span class="calendar-picker-chevron">▾</span>
|
|
799
1000
|
</button>
|
|
800
1001
|
|
|
801
1002
|
${this.pickerOpen ? `
|
|
802
|
-
<div class="calendar
|
|
803
|
-
<div class="calendar
|
|
1003
|
+
<div class="calendar-picker-dropdown">
|
|
1004
|
+
<div class="calendar-picker-year-row">
|
|
804
1005
|
<button
|
|
805
1006
|
type="button"
|
|
806
|
-
class="calendar
|
|
1007
|
+
class="calendar-picker-year-arrow"
|
|
807
1008
|
data-action="year-prev"
|
|
808
1009
|
${viewModel.currentYear <= minYear ? "disabled" : ""}
|
|
809
1010
|
aria-label="Previous year"
|
|
810
1011
|
>‹</button>
|
|
811
1012
|
<input
|
|
812
1013
|
type="text"
|
|
813
|
-
class="calendar
|
|
814
|
-
value="${this.yearInput || viewModel.currentYear}"
|
|
1014
|
+
class="calendar-picker-year-input${!this.yearInputValid ? " invalid" : ""}"
|
|
1015
|
+
value="${escapeHtml(this.yearInput || viewModel.currentYear)}"
|
|
815
1016
|
data-year-input
|
|
816
1017
|
aria-label="Year"
|
|
817
1018
|
/>
|
|
818
1019
|
<button
|
|
819
1020
|
type="button"
|
|
820
|
-
class="calendar
|
|
1021
|
+
class="calendar-picker-year-arrow"
|
|
821
1022
|
data-action="year-next"
|
|
822
1023
|
${viewModel.currentYear >= maxYear ? "disabled" : ""}
|
|
823
1024
|
aria-label="Next year"
|
|
824
1025
|
>›</button>
|
|
825
1026
|
</div>
|
|
826
1027
|
|
|
827
|
-
<div class="calendar
|
|
1028
|
+
<div class="calendar-picker-months">
|
|
828
1029
|
${(useShortMonths ? MONTHS : MONTHS_FULL).map((month, index) => {
|
|
829
1030
|
const isSelected = index === viewModel.currentMonth;
|
|
830
1031
|
const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
|
|
831
1032
|
return `
|
|
832
1033
|
<button
|
|
833
1034
|
type="button"
|
|
834
|
-
class="calendar
|
|
1035
|
+
class="calendar-picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
|
|
835
1036
|
data-action="select-month"
|
|
836
1037
|
data-month="${index}"
|
|
837
1038
|
>${month}</button>
|
|
@@ -844,70 +1045,22 @@ var Kalendly = (() => {
|
|
|
844
1045
|
|
|
845
1046
|
<button
|
|
846
1047
|
type="button"
|
|
847
|
-
class="calendar
|
|
1048
|
+
class="calendar-today-btn"
|
|
848
1049
|
data-action="today"
|
|
849
1050
|
${isCurrentMonth ? "disabled" : ""}
|
|
850
1051
|
>Today</button>
|
|
851
1052
|
|
|
852
|
-
<button type="button" class="calendar
|
|
1053
|
+
<button type="button" class="calendar-nav-arrow" data-action="next" aria-label="Next month">
|
|
853
1054
|
›
|
|
854
1055
|
</button>
|
|
855
1056
|
</div>
|
|
856
1057
|
|
|
857
|
-
<
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
861
|
-
</tr>
|
|
862
|
-
</thead>
|
|
863
|
-
<tbody data-calendar-body>
|
|
864
|
-
${isLoading ? Array.from(
|
|
865
|
-
{ length: 6 },
|
|
866
|
-
() => `<tr>${Array.from(
|
|
867
|
-
{ length: 7 },
|
|
868
|
-
() => `<td class="calendar--skeleton" aria-hidden="true"></td>`
|
|
869
|
-
).join("")}</tr>`
|
|
870
|
-
).join("") : viewModel.calendarDates.map(
|
|
871
|
-
(week) => `
|
|
872
|
-
<tr>
|
|
873
|
-
${week.map((calendarDate, dayIndex) => {
|
|
874
|
-
const classes = getCellClasses(calendarDate);
|
|
875
|
-
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
876
|
-
classes.push(
|
|
877
|
-
calendarDate.hasEvents ? "availability--booked" : "availability--free"
|
|
878
|
-
);
|
|
879
|
-
}
|
|
880
|
-
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
881
|
-
const d = calendarDate.date;
|
|
882
|
-
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
883
|
-
classes.push("availability--range-start");
|
|
884
|
-
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
885
|
-
classes.push("availability--range-end");
|
|
886
|
-
if (this._rangeStart && this._rangeEnd) {
|
|
887
|
-
if (d > this._rangeStart && d < this._rangeEnd)
|
|
888
|
-
classes.push("availability--in-range");
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
const dateString = calendarDate.date.toISOString();
|
|
892
|
-
return `
|
|
893
|
-
<td
|
|
894
|
-
class="${classes.join(" ")}"
|
|
895
|
-
data-date="${dateString}"
|
|
896
|
-
data-day-index="${dayIndex}"
|
|
897
|
-
data-clickable="true"
|
|
898
|
-
>
|
|
899
|
-
${calendarDate.date.getDate()}
|
|
900
|
-
</td>
|
|
901
|
-
`;
|
|
902
|
-
}).join("")}
|
|
903
|
-
</tr>
|
|
904
|
-
`
|
|
905
|
-
).join("")}
|
|
906
|
-
</tbody>
|
|
907
|
-
</table>
|
|
1058
|
+
<div class="calendar-panes">
|
|
1059
|
+
${viewModel.panes.map(renderPane).join("")}
|
|
1060
|
+
</div>
|
|
908
1061
|
|
|
909
1062
|
${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
|
|
910
|
-
<div class="date-popup
|
|
1063
|
+
<div class="date-popup">
|
|
911
1064
|
<div class="popup-header">
|
|
912
1065
|
<h2>${viewModel.scheduleDay}</h2>
|
|
913
1066
|
<button type="button" class="popup-close" data-action="close-popup" aria-label="Close">\u2715</button>
|
|
@@ -945,8 +1098,7 @@ var Kalendly = (() => {
|
|
|
945
1098
|
const availMode = this.getAttribute("availability-mode");
|
|
946
1099
|
const isSelectable = this.hasAttribute("selectable");
|
|
947
1100
|
if (availMode === "day" && isSelectable) {
|
|
948
|
-
|
|
949
|
-
if (!isBooked) {
|
|
1101
|
+
if (this.isDateSelectable(date)) {
|
|
950
1102
|
let startDate;
|
|
951
1103
|
let endDate;
|
|
952
1104
|
if (this._rangeEnd !== null) {
|
|
@@ -964,7 +1116,7 @@ var Kalendly = (() => {
|
|
|
964
1116
|
cursor.setDate(cursor.getDate() + 1);
|
|
965
1117
|
let blocked = false;
|
|
966
1118
|
while (cursor < e2) {
|
|
967
|
-
if (this.
|
|
1119
|
+
if (!this.isDateSelectable(cursor)) {
|
|
968
1120
|
blocked = true;
|
|
969
1121
|
break;
|
|
970
1122
|
}
|
|
@@ -1198,31 +1350,84 @@ var Kalendly = (() => {
|
|
|
1198
1350
|
this.theme = theme;
|
|
1199
1351
|
}
|
|
1200
1352
|
getCurrentDate() {
|
|
1353
|
+
this.rethrowInitError();
|
|
1201
1354
|
return this.engine?.getViewModel().selectedDate ?? null;
|
|
1202
1355
|
}
|
|
1203
1356
|
goToDate(date) {
|
|
1357
|
+
this.rethrowInitError();
|
|
1204
1358
|
const year = date.getFullYear();
|
|
1205
1359
|
const month = date.getMonth();
|
|
1206
1360
|
this.dispatchMonthChange(year, month);
|
|
1207
1361
|
this.actions?.jump(year, month);
|
|
1208
1362
|
}
|
|
1209
1363
|
getEngine() {
|
|
1364
|
+
this.rethrowInitError();
|
|
1210
1365
|
if (!this.engine)
|
|
1211
1366
|
throw new Error("CalendarElement is not connected to the DOM");
|
|
1212
1367
|
return this.engine;
|
|
1213
1368
|
}
|
|
1214
1369
|
};
|
|
1215
|
-
|
|
1370
|
+
_CalendarElement.observedAttributes = [
|
|
1216
1371
|
"initial-date",
|
|
1217
1372
|
"min-year",
|
|
1218
1373
|
"max-year",
|
|
1219
1374
|
"week-starts-on",
|
|
1375
|
+
"heading",
|
|
1376
|
+
"months",
|
|
1220
1377
|
"title",
|
|
1221
1378
|
"use-short-month-names",
|
|
1222
1379
|
"availability-mode",
|
|
1223
1380
|
"selectable",
|
|
1224
1381
|
"loading"
|
|
1225
1382
|
];
|
|
1383
|
+
_CalendarElement.themeMap = {
|
|
1384
|
+
primary: "--calendar-primary-color",
|
|
1385
|
+
secondary: "--calendar-secondary-color",
|
|
1386
|
+
tertiary: "--calendar-tertiary-color",
|
|
1387
|
+
textColor: "--calendar-text-color",
|
|
1388
|
+
textLight: "--calendar-text-light",
|
|
1389
|
+
background: "--calendar-background",
|
|
1390
|
+
cellHover: "--calendar-cell-hover",
|
|
1391
|
+
borderColor: "--calendar-border-color",
|
|
1392
|
+
todayOutline: "--calendar-today-outline",
|
|
1393
|
+
selectedBg: "--calendar-selected-bg",
|
|
1394
|
+
headerBg: "--calendar-header-bg",
|
|
1395
|
+
popupBg: "--calendar-popup-bg",
|
|
1396
|
+
pickerBg: "--calendar-picker-bg",
|
|
1397
|
+
pickerShadow: "--calendar-picker-shadow",
|
|
1398
|
+
eventIndicator: "--calendar-event-indicator",
|
|
1399
|
+
onAccent: "--calendar-on-accent",
|
|
1400
|
+
link: "--calendar-link",
|
|
1401
|
+
openBg: "--calendar-open-bg",
|
|
1402
|
+
openFg: "--calendar-open-fg",
|
|
1403
|
+
conditionalBg: "--calendar-conditional-bg",
|
|
1404
|
+
conditionalFg: "--calendar-conditional-fg",
|
|
1405
|
+
blockedBg: "--calendar-blocked-bg",
|
|
1406
|
+
blockedFg: "--calendar-blocked-fg",
|
|
1407
|
+
rangeBg: "--calendar-range-bg",
|
|
1408
|
+
rangeOutline: "--calendar-range-outline",
|
|
1409
|
+
inRangeBg: "--calendar-in-range-bg",
|
|
1410
|
+
inRangeOutline: "--calendar-in-range-outline",
|
|
1411
|
+
badgeBg: "--calendar-badge-bg",
|
|
1412
|
+
badgeText: "--calendar-badge-text",
|
|
1413
|
+
badgeSuccessBg: "--calendar-badge-success-bg",
|
|
1414
|
+
badgeSuccessText: "--calendar-badge-success-text",
|
|
1415
|
+
badgeInfoBg: "--calendar-badge-info-bg",
|
|
1416
|
+
badgeInfoText: "--calendar-badge-info-text",
|
|
1417
|
+
badgeWarningBg: "--calendar-badge-warning-bg",
|
|
1418
|
+
badgeWarningText: "--calendar-badge-warning-text",
|
|
1419
|
+
badgeDangerBg: "--calendar-badge-danger-bg",
|
|
1420
|
+
badgeDangerText: "--calendar-badge-danger-text",
|
|
1421
|
+
badgeNeutralBg: "--calendar-badge-neutral-bg",
|
|
1422
|
+
badgeNeutralText: "--calendar-badge-neutral-text",
|
|
1423
|
+
badgePositiveBg: "--calendar-badge-positive-bg",
|
|
1424
|
+
badgePositiveText: "--calendar-badge-positive-text",
|
|
1425
|
+
badgeTentativeBg: "--calendar-badge-tentative-bg",
|
|
1426
|
+
badgeTentativeText: "--calendar-badge-tentative-text"
|
|
1427
|
+
};
|
|
1428
|
+
_CalendarElement.BUILT_IN_BUCKETS = ["blocked", "conditional", "open"];
|
|
1429
|
+
_CalendarElement.titleDeprecationWarned = false;
|
|
1430
|
+
var CalendarElement = _CalendarElement;
|
|
1226
1431
|
function defineCalendarElement(tagName = "kal-calendar") {
|
|
1227
1432
|
if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
|
|
1228
1433
|
customElements.define(tagName, CalendarElement);
|