kalendly 0.2.1 → 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 +263 -32
- package/dist/core/index.d.mts +50 -10
- package/dist/core/index.d.ts +50 -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 +76 -10
- package/dist/index.d.ts +76 -10
- package/dist/index.js +373 -160
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +369 -159
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +363 -156
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +477 -415
- 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,40 +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
|
-
|
|
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;
|
|
622
740
|
}
|
|
623
741
|
render() {
|
|
624
742
|
if (!this.engine) return;
|
|
625
743
|
const viewModel = this.engine.getViewModel();
|
|
626
744
|
const useShortMonths = this.hasAttribute("use-short-month-names");
|
|
627
|
-
const title = this.
|
|
745
|
+
const title = this.headingText;
|
|
628
746
|
const minYear = this.minYear;
|
|
629
747
|
const maxYear = this.maxYear;
|
|
630
748
|
const availabilityMode = this.getAttribute("availability-mode");
|
|
631
749
|
const selectable = this.hasAttribute("selectable");
|
|
632
750
|
const isLoading = this.loading;
|
|
751
|
+
if (availabilityMode) {
|
|
752
|
+
this.assertStatusesDeclared();
|
|
753
|
+
this.assertStatusesKnown();
|
|
754
|
+
}
|
|
633
755
|
const today = /* @__PURE__ */ new Date();
|
|
634
756
|
const todayMonth = today.getMonth();
|
|
635
757
|
const todayYear = today.getFullYear();
|
|
@@ -638,7 +760,7 @@ var Kalendly = (() => {
|
|
|
638
760
|
const defaultRenderEvent = (event) => {
|
|
639
761
|
const timeRange = formatTimeRange(event);
|
|
640
762
|
const attendeesList = formatAttendees(event.attendees);
|
|
641
|
-
let borderColor = event.color || "#3b82f6";
|
|
763
|
+
let borderColor = safeColor(event.color || "#3b82f6");
|
|
642
764
|
if (event.category) {
|
|
643
765
|
borderColor = this.engine.getCategoryColor(event.category);
|
|
644
766
|
}
|
|
@@ -673,56 +795,56 @@ var Kalendly = (() => {
|
|
|
673
795
|
return `
|
|
674
796
|
<div class="event-card" style="border-left-color: ${borderColor}">
|
|
675
797
|
<div class="event-header">
|
|
676
|
-
<div class="event-title">${event.name}</div>
|
|
798
|
+
<div class="event-title">${escapeHtml(event.name)}</div>
|
|
677
799
|
<div class="event-badges">
|
|
678
|
-
${event.category ? `<span class="badge category-${event.category}">${getCategoryLabel(event.category)}</span>` : ""}
|
|
679
|
-
${event.priority ? `<span class="badge priority-${event.priority}">${getPriorityLabel(event.priority)}</span>` : ""}
|
|
680
|
-
${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>` : ""}
|
|
681
803
|
</div>
|
|
682
804
|
</div>
|
|
683
805
|
|
|
684
806
|
${timeRange ? `
|
|
685
807
|
<div class="event-time">
|
|
686
808
|
<span class="event-time-label">Time:</span>
|
|
687
|
-
<span class="event-time-value">${timeRange}</span>
|
|
809
|
+
<span class="event-time-value">${escapeHtml(timeRange)}</span>
|
|
688
810
|
</div>
|
|
689
811
|
` : ""}
|
|
690
812
|
|
|
691
813
|
${event.description ? `
|
|
692
|
-
<div class="event-description">${event.description}</div>
|
|
814
|
+
<div class="event-description">${escapeHtml(event.description)}</div>
|
|
693
815
|
` : ""}
|
|
694
816
|
|
|
695
817
|
${event.location ? `
|
|
696
818
|
<div class="event-time">
|
|
697
819
|
<span class="event-time-label">Location:</span>
|
|
698
|
-
<span class="event-time-value">${event.location}</span>
|
|
820
|
+
<span class="event-time-value">${escapeHtml(event.location)}</span>
|
|
699
821
|
</div>
|
|
700
822
|
` : ""}
|
|
701
823
|
|
|
702
824
|
${attendeesList ? `
|
|
703
825
|
<div class="event-time">
|
|
704
826
|
<span class="event-time-label">Attendees:</span>
|
|
705
|
-
<span class="event-time-value">${attendeesList}</span>
|
|
827
|
+
<span class="event-time-value">${escapeHtml(attendeesList)}</span>
|
|
706
828
|
</div>
|
|
707
829
|
` : ""}
|
|
708
830
|
|
|
709
831
|
${event.organizer ? `
|
|
710
832
|
<div class="event-time">
|
|
711
833
|
<span class="event-time-label">Organizer:</span>
|
|
712
|
-
<span class="event-time-value">${event.organizer}</span>
|
|
834
|
+
<span class="event-time-value">${escapeHtml(event.organizer)}</span>
|
|
713
835
|
</div>
|
|
714
836
|
` : ""}
|
|
715
837
|
|
|
716
838
|
${event.notes ? `
|
|
717
839
|
<div class="event-time">
|
|
718
840
|
<span class="event-time-label">Notes:</span>
|
|
719
|
-
<span class="event-time-value">${event.notes}</span>
|
|
841
|
+
<span class="event-time-value">${escapeHtml(event.notes)}</span>
|
|
720
842
|
</div>
|
|
721
843
|
` : ""}
|
|
722
844
|
|
|
723
845
|
${event.url ? `
|
|
724
846
|
<div class="event-time">
|
|
725
|
-
<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">
|
|
726
848
|
View Details \u2192
|
|
727
849
|
</a>
|
|
728
850
|
</div>
|
|
@@ -730,7 +852,7 @@ var Kalendly = (() => {
|
|
|
730
852
|
|
|
731
853
|
${event.tags && event.tags.length > 0 ? `
|
|
732
854
|
<div class="event-tags">
|
|
733
|
-
${event.tags.map((tag) => `<span class="event-tag">${tag}</span>`).join("")}
|
|
855
|
+
${event.tags.map((tag) => `<span class="event-tag">${escapeHtml(tag)}</span>`).join("")}
|
|
734
856
|
</div>
|
|
735
857
|
` : ""}
|
|
736
858
|
</div>
|
|
@@ -740,11 +862,17 @@ var Kalendly = (() => {
|
|
|
740
862
|
const renderEvent = this._renderEvent || defaultRenderEvent;
|
|
741
863
|
const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
|
|
742
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
|
+
};
|
|
743
870
|
const isHourBooked = (hour) => events.some((event) => {
|
|
744
|
-
if (!event.startTime
|
|
745
|
-
const
|
|
746
|
-
const
|
|
747
|
-
|
|
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;
|
|
748
876
|
});
|
|
749
877
|
const slots = Array.from({ length: 24 }, (_, hour) => {
|
|
750
878
|
const startTime = `${String(hour).padStart(2, "0")}:00`;
|
|
@@ -755,81 +883,156 @@ var Kalendly = (() => {
|
|
|
755
883
|
const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
|
|
756
884
|
const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
|
|
757
885
|
const slotClasses = [
|
|
758
|
-
"time-
|
|
759
|
-
booked ? "time-
|
|
760
|
-
isRangeStart ? "time-
|
|
761
|
-
isRangeEnd ? "time-
|
|
762
|
-
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" : ""
|
|
763
891
|
].filter(Boolean).join(" ");
|
|
764
892
|
const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
|
|
765
893
|
return `
|
|
766
894
|
<div class="${slotClasses}" ${slotAttrs}>
|
|
767
|
-
<span class="time-
|
|
768
|
-
<span class="time-
|
|
895
|
+
<span class="time-grid-label">${startTime}</span>
|
|
896
|
+
<span class="time-grid-status">${booked ? "Booked" : "Available"}</span>
|
|
769
897
|
</div>`;
|
|
770
898
|
});
|
|
771
899
|
return `<div class="time-grid">${slots.join("")}</div>`;
|
|
772
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
|
+
`;
|
|
773
976
|
const html = `
|
|
774
977
|
${title ? `
|
|
775
|
-
<div class="
|
|
776
|
-
<h1>${title}</h1>
|
|
978
|
+
<div class="calendar-title">
|
|
979
|
+
<h1>${escapeHtml(title)}</h1>
|
|
777
980
|
</div>
|
|
778
981
|
` : ""}
|
|
779
982
|
|
|
780
|
-
<div class="calendar
|
|
781
|
-
<div class="calendar
|
|
782
|
-
<div class="calendar
|
|
783
|
-
<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">
|
|
784
987
|
‹
|
|
785
988
|
</button>
|
|
786
989
|
|
|
787
|
-
<div class="calendar
|
|
990
|
+
<div class="calendar-picker-container" data-picker-container>
|
|
788
991
|
<button
|
|
789
992
|
type="button"
|
|
790
|
-
class="calendar
|
|
993
|
+
class="calendar-picker-btn"
|
|
791
994
|
data-action="toggle-picker"
|
|
792
995
|
aria-expanded="${this.pickerOpen ? "true" : "false"}"
|
|
793
996
|
aria-haspopup="true"
|
|
794
997
|
>
|
|
795
998
|
${useShortMonths ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
|
|
796
|
-
<span class="calendar
|
|
999
|
+
<span class="calendar-picker-chevron">▾</span>
|
|
797
1000
|
</button>
|
|
798
1001
|
|
|
799
1002
|
${this.pickerOpen ? `
|
|
800
|
-
<div class="calendar
|
|
801
|
-
<div class="calendar
|
|
1003
|
+
<div class="calendar-picker-dropdown">
|
|
1004
|
+
<div class="calendar-picker-year-row">
|
|
802
1005
|
<button
|
|
803
1006
|
type="button"
|
|
804
|
-
class="calendar
|
|
1007
|
+
class="calendar-picker-year-arrow"
|
|
805
1008
|
data-action="year-prev"
|
|
806
1009
|
${viewModel.currentYear <= minYear ? "disabled" : ""}
|
|
807
1010
|
aria-label="Previous year"
|
|
808
1011
|
>‹</button>
|
|
809
1012
|
<input
|
|
810
1013
|
type="text"
|
|
811
|
-
class="calendar
|
|
812
|
-
value="${this.yearInput || viewModel.currentYear}"
|
|
1014
|
+
class="calendar-picker-year-input${!this.yearInputValid ? " invalid" : ""}"
|
|
1015
|
+
value="${escapeHtml(this.yearInput || viewModel.currentYear)}"
|
|
813
1016
|
data-year-input
|
|
814
1017
|
aria-label="Year"
|
|
815
1018
|
/>
|
|
816
1019
|
<button
|
|
817
1020
|
type="button"
|
|
818
|
-
class="calendar
|
|
1021
|
+
class="calendar-picker-year-arrow"
|
|
819
1022
|
data-action="year-next"
|
|
820
1023
|
${viewModel.currentYear >= maxYear ? "disabled" : ""}
|
|
821
1024
|
aria-label="Next year"
|
|
822
1025
|
>›</button>
|
|
823
1026
|
</div>
|
|
824
1027
|
|
|
825
|
-
<div class="calendar
|
|
1028
|
+
<div class="calendar-picker-months">
|
|
826
1029
|
${(useShortMonths ? MONTHS : MONTHS_FULL).map((month, index) => {
|
|
827
1030
|
const isSelected = index === viewModel.currentMonth;
|
|
828
1031
|
const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
|
|
829
1032
|
return `
|
|
830
1033
|
<button
|
|
831
1034
|
type="button"
|
|
832
|
-
class="calendar
|
|
1035
|
+
class="calendar-picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
|
|
833
1036
|
data-action="select-month"
|
|
834
1037
|
data-month="${index}"
|
|
835
1038
|
>${month}</button>
|
|
@@ -842,70 +1045,22 @@ var Kalendly = (() => {
|
|
|
842
1045
|
|
|
843
1046
|
<button
|
|
844
1047
|
type="button"
|
|
845
|
-
class="calendar
|
|
1048
|
+
class="calendar-today-btn"
|
|
846
1049
|
data-action="today"
|
|
847
1050
|
${isCurrentMonth ? "disabled" : ""}
|
|
848
1051
|
>Today</button>
|
|
849
1052
|
|
|
850
|
-
<button type="button" class="calendar
|
|
1053
|
+
<button type="button" class="calendar-nav-arrow" data-action="next" aria-label="Next month">
|
|
851
1054
|
›
|
|
852
1055
|
</button>
|
|
853
1056
|
</div>
|
|
854
1057
|
|
|
855
|
-
<
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
|
|
859
|
-
</tr>
|
|
860
|
-
</thead>
|
|
861
|
-
<tbody data-calendar-body>
|
|
862
|
-
${isLoading ? Array.from(
|
|
863
|
-
{ length: 6 },
|
|
864
|
-
() => `<tr>${Array.from(
|
|
865
|
-
{ length: 7 },
|
|
866
|
-
() => `<td class="calendar--skeleton" aria-hidden="true"></td>`
|
|
867
|
-
).join("")}</tr>`
|
|
868
|
-
).join("") : viewModel.calendarDates.map(
|
|
869
|
-
(week) => `
|
|
870
|
-
<tr>
|
|
871
|
-
${week.map((calendarDate, dayIndex) => {
|
|
872
|
-
const classes = getCellClasses(calendarDate);
|
|
873
|
-
if (availabilityMode && calendarDate.isCurrentMonth) {
|
|
874
|
-
classes.push(
|
|
875
|
-
calendarDate.hasEvents ? "availability--booked" : "availability--free"
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
|
|
879
|
-
const d = calendarDate.date;
|
|
880
|
-
if (this._rangeStart && isSameDay2(d, this._rangeStart))
|
|
881
|
-
classes.push("availability--range-start");
|
|
882
|
-
if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
|
|
883
|
-
classes.push("availability--range-end");
|
|
884
|
-
if (this._rangeStart && this._rangeEnd) {
|
|
885
|
-
if (d > this._rangeStart && d < this._rangeEnd)
|
|
886
|
-
classes.push("availability--in-range");
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
const dateString = calendarDate.date.toISOString();
|
|
890
|
-
return `
|
|
891
|
-
<td
|
|
892
|
-
class="${classes.join(" ")}"
|
|
893
|
-
data-date="${dateString}"
|
|
894
|
-
data-day-index="${dayIndex}"
|
|
895
|
-
data-clickable="true"
|
|
896
|
-
>
|
|
897
|
-
${calendarDate.date.getDate()}
|
|
898
|
-
</td>
|
|
899
|
-
`;
|
|
900
|
-
}).join("")}
|
|
901
|
-
</tr>
|
|
902
|
-
`
|
|
903
|
-
).join("")}
|
|
904
|
-
</tbody>
|
|
905
|
-
</table>
|
|
1058
|
+
<div class="calendar-panes">
|
|
1059
|
+
${viewModel.panes.map(renderPane).join("")}
|
|
1060
|
+
</div>
|
|
906
1061
|
|
|
907
1062
|
${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
|
|
908
|
-
<div class="date-popup
|
|
1063
|
+
<div class="date-popup">
|
|
909
1064
|
<div class="popup-header">
|
|
910
1065
|
<h2>${viewModel.scheduleDay}</h2>
|
|
911
1066
|
<button type="button" class="popup-close" data-action="close-popup" aria-label="Close">\u2715</button>
|
|
@@ -943,8 +1098,7 @@ var Kalendly = (() => {
|
|
|
943
1098
|
const availMode = this.getAttribute("availability-mode");
|
|
944
1099
|
const isSelectable = this.hasAttribute("selectable");
|
|
945
1100
|
if (availMode === "day" && isSelectable) {
|
|
946
|
-
|
|
947
|
-
if (!isBooked) {
|
|
1101
|
+
if (this.isDateSelectable(date)) {
|
|
948
1102
|
let startDate;
|
|
949
1103
|
let endDate;
|
|
950
1104
|
if (this._rangeEnd !== null) {
|
|
@@ -962,7 +1116,7 @@ var Kalendly = (() => {
|
|
|
962
1116
|
cursor.setDate(cursor.getDate() + 1);
|
|
963
1117
|
let blocked = false;
|
|
964
1118
|
while (cursor < e2) {
|
|
965
|
-
if (this.
|
|
1119
|
+
if (!this.isDateSelectable(cursor)) {
|
|
966
1120
|
blocked = true;
|
|
967
1121
|
break;
|
|
968
1122
|
}
|
|
@@ -1196,31 +1350,84 @@ var Kalendly = (() => {
|
|
|
1196
1350
|
this.theme = theme;
|
|
1197
1351
|
}
|
|
1198
1352
|
getCurrentDate() {
|
|
1353
|
+
this.rethrowInitError();
|
|
1199
1354
|
return this.engine?.getViewModel().selectedDate ?? null;
|
|
1200
1355
|
}
|
|
1201
1356
|
goToDate(date) {
|
|
1357
|
+
this.rethrowInitError();
|
|
1202
1358
|
const year = date.getFullYear();
|
|
1203
1359
|
const month = date.getMonth();
|
|
1204
1360
|
this.dispatchMonthChange(year, month);
|
|
1205
1361
|
this.actions?.jump(year, month);
|
|
1206
1362
|
}
|
|
1207
1363
|
getEngine() {
|
|
1364
|
+
this.rethrowInitError();
|
|
1208
1365
|
if (!this.engine)
|
|
1209
1366
|
throw new Error("CalendarElement is not connected to the DOM");
|
|
1210
1367
|
return this.engine;
|
|
1211
1368
|
}
|
|
1212
1369
|
};
|
|
1213
|
-
|
|
1370
|
+
_CalendarElement.observedAttributes = [
|
|
1214
1371
|
"initial-date",
|
|
1215
1372
|
"min-year",
|
|
1216
1373
|
"max-year",
|
|
1217
1374
|
"week-starts-on",
|
|
1375
|
+
"heading",
|
|
1376
|
+
"months",
|
|
1218
1377
|
"title",
|
|
1219
1378
|
"use-short-month-names",
|
|
1220
1379
|
"availability-mode",
|
|
1221
1380
|
"selectable",
|
|
1222
1381
|
"loading"
|
|
1223
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;
|
|
1224
1431
|
function defineCalendarElement(tagName = "kal-calendar") {
|
|
1225
1432
|
if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
|
|
1226
1433
|
customElements.define(tagName, CalendarElement);
|