kalendly 0.1.7 → 0.2.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.
Files changed (38) hide show
  1. package/README.md +317 -681
  2. package/dist/core/index.js +3 -3
  3. package/dist/index.d.mts +48 -602
  4. package/dist/index.d.ts +48 -602
  5. package/dist/index.js +753 -2359
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +727 -2348
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/{vanilla/index.umd.js → index.umd.js} +218 -218
  10. package/dist/index.umd.js.map +1 -0
  11. package/package.json +41 -91
  12. package/dist/react/index.d.mts +0 -251
  13. package/dist/react/index.d.ts +0 -251
  14. package/dist/react/index.js +0 -969
  15. package/dist/react/index.js.map +0 -1
  16. package/dist/react/index.mjs +0 -924
  17. package/dist/react/index.mjs.map +0 -1
  18. package/dist/react-native/index.d.mts +0 -642
  19. package/dist/react-native/index.d.ts +0 -642
  20. package/dist/react-native/index.js +0 -1649
  21. package/dist/react-native/index.js.map +0 -1
  22. package/dist/react-native/index.mjs +0 -1615
  23. package/dist/react-native/index.mjs.map +0 -1
  24. package/dist/vanilla/index.d.mts +0 -271
  25. package/dist/vanilla/index.d.ts +0 -271
  26. package/dist/vanilla/index.js +0 -1064
  27. package/dist/vanilla/index.js.map +0 -1
  28. package/dist/vanilla/index.mjs +0 -1013
  29. package/dist/vanilla/index.mjs.map +0 -1
  30. package/dist/vanilla/index.umd.js.map +0 -1
  31. package/dist/vue/components/Calendar.vue.d.ts +0 -43
  32. package/dist/vue/components/Calendar.vue.d.ts.map +0 -1
  33. package/dist/vue/index.d.ts +0 -134
  34. package/dist/vue/index.d.ts.map +0 -1
  35. package/dist/vue/index.js +0 -1
  36. package/dist/vue/index.mjs +0 -717
  37. package/dist/vue/types.d.ts +0 -21
  38. package/dist/vue/types.d.ts.map +0 -1
@@ -1,1013 +0,0 @@
1
- // src/core/utils.ts
2
- var MONTHS = [
3
- "Jan",
4
- "Feb",
5
- "Mar",
6
- "Apr",
7
- "May",
8
- "Jun",
9
- "Jul",
10
- "Aug",
11
- "Sep",
12
- "Oct",
13
- "Nov",
14
- "Dec"
15
- ];
16
- var MONTHS_FULL = [
17
- "January",
18
- "February",
19
- "March",
20
- "April",
21
- "May",
22
- "June",
23
- "July",
24
- "August",
25
- "September",
26
- "October",
27
- "November",
28
- "December"
29
- ];
30
- var DAYS = [
31
- "Sunday",
32
- "Monday",
33
- "Tuesday",
34
- "Wednesday",
35
- "Thursday",
36
- "Friday",
37
- "Saturday"
38
- ];
39
- function normalizeDate(date) {
40
- return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
41
- }
42
- function isSameDay(date1, date2) {
43
- return normalizeDate(date1).getTime() === normalizeDate(date2).getTime();
44
- }
45
- function isToday(date) {
46
- return isSameDay(date, /* @__PURE__ */ new Date());
47
- }
48
- function generateYears(minYear, maxYear) {
49
- const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
50
- const min = minYear ?? currentYear - 30;
51
- const max = maxYear ?? currentYear + 10;
52
- return Array.from({ length: max - min + 1 }, (_, i) => min + i);
53
- }
54
- function getEventsForDate(events, date) {
55
- const normalizedTargetDate = normalizeDate(date);
56
- return events.filter((event) => {
57
- const eventDate = normalizeDate(new Date(event.date));
58
- return eventDate.getTime() === normalizedTargetDate.getTime();
59
- });
60
- }
61
- function hasEvents(events, date) {
62
- return getEventsForDate(events, date).length > 0;
63
- }
64
- function generateCalendarDates(year, month, events = [], weekStartsOn = 0) {
65
- const firstDay = new Date(year, month, 1);
66
- const lastDay = new Date(year, month + 1, 0);
67
- const daysInMonth = lastDay.getDate();
68
- const prevMonthLastDay = new Date(year, month, 0).getDate();
69
- let firstDayOfWeek = firstDay.getDay();
70
- if (weekStartsOn === 1) {
71
- firstDayOfWeek = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1;
72
- }
73
- const dates = [];
74
- let day = 1;
75
- let nextMonthDay = 1;
76
- for (let week = 0; week < 6; week++) {
77
- const weekDates = [];
78
- for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
79
- if (week === 0 && dayOfWeek < firstDayOfWeek) {
80
- const prevDay = prevMonthLastDay - firstDayOfWeek + dayOfWeek + 1;
81
- const prevDate = new Date(year, month - 1, prevDay);
82
- const dateEvents = getEventsForDate(events, prevDate);
83
- weekDates.push({
84
- date: prevDate,
85
- isCurrentMonth: false,
86
- isToday: isToday(prevDate),
87
- hasEvents: dateEvents.length > 0,
88
- events: dateEvents
89
- });
90
- } else if (day > daysInMonth) {
91
- const nextDate = new Date(year, month + 1, nextMonthDay);
92
- const dateEvents = getEventsForDate(events, nextDate);
93
- weekDates.push({
94
- date: nextDate,
95
- isCurrentMonth: false,
96
- isToday: isToday(nextDate),
97
- hasEvents: dateEvents.length > 0,
98
- events: dateEvents
99
- });
100
- nextMonthDay++;
101
- } else {
102
- const currentDate = new Date(year, month, day);
103
- const dateEvents = getEventsForDate(events, currentDate);
104
- weekDates.push({
105
- date: currentDate,
106
- isCurrentMonth: true,
107
- isToday: isToday(currentDate),
108
- hasEvents: dateEvents.length > 0,
109
- events: dateEvents
110
- });
111
- day++;
112
- }
113
- }
114
- dates.push(weekDates);
115
- }
116
- return dates;
117
- }
118
- function getPopupPositionClass(selectedDayIndex) {
119
- if (selectedDayIndex === null) return "popup-center-bottom";
120
- if (selectedDayIndex < 3) {
121
- return "popup-right";
122
- } else if (selectedDayIndex > 4) {
123
- return "popup-left";
124
- } else {
125
- return "popup-center-bottom";
126
- }
127
- }
128
- function getCellClasses(calendarDate) {
129
- const classes = [];
130
- if (!calendarDate.isCurrentMonth) {
131
- classes.push("other-month");
132
- }
133
- if (calendarDate.isToday) {
134
- classes.push("schedule--current--exam");
135
- }
136
- if (calendarDate.hasEvents) {
137
- classes.push("has--event");
138
- }
139
- return classes;
140
- }
141
- function formatDateForDisplay(date) {
142
- return `${DAYS[date.getDay()]} ${date.getDate()}`;
143
- }
144
- function getMonthYearText(year, month) {
145
- return `${MONTHS_FULL[month]} ${year}`;
146
- }
147
- function formatTimeRange(event) {
148
- if (event.allDay || !event.startTime && !event.endTime) {
149
- return "All day";
150
- }
151
- if (event.startTime && event.endTime) {
152
- return `${event.startTime} - ${event.endTime}`;
153
- }
154
- if (event.startTime) {
155
- return `${event.startTime}`;
156
- }
157
- return "";
158
- }
159
- function formatAttendees(attendees) {
160
- if (!attendees || attendees.length === 0) return "";
161
- if (attendees.length === 1) return attendees[0];
162
- if (attendees.length === 2) return attendees.join(" and ");
163
- return `${attendees.slice(0, -1).join(", ")}, and ${attendees[attendees.length - 1]}`;
164
- }
165
- function sortEventsByTime(events) {
166
- return [...events].sort((a, b) => {
167
- const aAllDay = a.allDay || !a.startTime && !a.endTime;
168
- const bAllDay = b.allDay || !b.startTime && !b.endTime;
169
- if (aAllDay && !bAllDay) return -1;
170
- if (!aAllDay && bAllDay) return 1;
171
- if (!a.startTime || !b.startTime) return 0;
172
- return a.startTime.localeCompare(b.startTime);
173
- });
174
- }
175
- var DEFAULT_CATEGORY_COLORS = {
176
- work: "#3b82f6",
177
- personal: "#8b5cf6",
178
- meeting: "#10b981",
179
- deadline: "#ef4444",
180
- appointment: "#f59e0b",
181
- other: "#6b7280"
182
- };
183
- function getDefaultEventColor(category, customColors) {
184
- const colorMap = customColors || DEFAULT_CATEGORY_COLORS;
185
- if (category && colorMap[category]) {
186
- return colorMap[category];
187
- }
188
- return "#fc8917";
189
- }
190
- function mergeCategoryColors(customColors) {
191
- return {
192
- ...DEFAULT_CATEGORY_COLORS,
193
- ...customColors
194
- };
195
- }
196
- function isValidHexColor(color) {
197
- return /^#([0-9A-F]{3}){1,2}$/i.test(color);
198
- }
199
- function getCategoryColor(category, customColors) {
200
- const colorMap = mergeCategoryColors(customColors);
201
- const color = colorMap[category] || colorMap.other || "#fc8917";
202
- return isValidHexColor(color) ? color : "#fc8917";
203
- }
204
-
205
- // src/core/calendar-engine.ts
206
- var CalendarEngine = class {
207
- constructor(config) {
208
- this.listeners = /* @__PURE__ */ new Set();
209
- this.config = config;
210
- this.categoryColors = mergeCategoryColors(config.categoryColors);
211
- const initialDate = config.initialDate || /* @__PURE__ */ new Date();
212
- this.state = {
213
- currentYear: initialDate.getFullYear(),
214
- currentMonth: initialDate.getMonth(),
215
- currentDate: initialDate.getDate(),
216
- selectedDate: null,
217
- selectedDayIndex: null,
218
- tasks: []
219
- };
220
- }
221
- /**
222
- * Subscribe to state changes
223
- */
224
- subscribe(listener) {
225
- this.listeners.add(listener);
226
- return () => this.listeners.delete(listener);
227
- }
228
- /**
229
- * Notify all listeners of state changes
230
- */
231
- notify() {
232
- this.listeners.forEach((listener) => listener());
233
- }
234
- /**
235
- * Get current state
236
- */
237
- getState() {
238
- return { ...this.state };
239
- }
240
- /**
241
- * Get view model with computed properties
242
- */
243
- getViewModel() {
244
- const calendarDates = generateCalendarDates(
245
- this.state.currentYear,
246
- this.state.currentMonth,
247
- this.config.events,
248
- this.config.weekStartsOn
249
- );
250
- return {
251
- ...this.state,
252
- months: MONTHS,
253
- days: DAYS,
254
- years: generateYears(this.config.minYear, this.config.maxYear),
255
- monthAndYearText: getMonthYearText(
256
- this.state.currentYear,
257
- this.state.currentMonth
258
- ),
259
- scheduleDay: this.state.selectedDate ? formatDateForDisplay(this.state.selectedDate) : "",
260
- calendarDates,
261
- popupPositionClass: getPopupPositionClass(this.state.selectedDayIndex)
262
- };
263
- }
264
- /**
265
- * Get actions object
266
- */
267
- getActions() {
268
- return {
269
- next: this.next.bind(this),
270
- previous: this.previous.bind(this),
271
- jump: this.jump.bind(this),
272
- goToToday: this.goToToday.bind(this),
273
- isCurrentMonth: this.isCurrentMonth.bind(this),
274
- selectDate: this.selectDate.bind(this),
275
- updateTasks: this.updateTasks.bind(this)
276
- };
277
- }
278
- /**
279
- * Navigate to next month
280
- */
281
- next() {
282
- if (this.state.currentMonth === 11) {
283
- this.state.currentMonth = 0;
284
- this.state.currentYear++;
285
- } else {
286
- this.state.currentMonth++;
287
- }
288
- this.state.selectedDate = null;
289
- this.state.selectedDayIndex = null;
290
- this.updateTasks();
291
- this.notify();
292
- }
293
- /**
294
- * Navigate to previous month
295
- */
296
- previous() {
297
- if (this.state.currentMonth === 0) {
298
- this.state.currentMonth = 11;
299
- this.state.currentYear--;
300
- } else {
301
- this.state.currentMonth--;
302
- }
303
- this.state.selectedDate = null;
304
- this.state.selectedDayIndex = null;
305
- this.updateTasks();
306
- this.notify();
307
- }
308
- /**
309
- * Jump to specific month and year
310
- */
311
- jump(year, month) {
312
- this.state.currentYear = year;
313
- this.state.currentMonth = month;
314
- this.state.selectedDate = null;
315
- this.state.selectedDayIndex = null;
316
- this.updateTasks();
317
- this.notify();
318
- }
319
- /**
320
- * Navigate to current month (today)
321
- */
322
- goToToday() {
323
- const today = /* @__PURE__ */ new Date();
324
- this.state.currentYear = today.getFullYear();
325
- this.state.currentMonth = today.getMonth();
326
- this.state.selectedDate = null;
327
- this.state.selectedDayIndex = null;
328
- this.updateTasks();
329
- this.notify();
330
- }
331
- /**
332
- * Check if currently viewing today's month
333
- */
334
- isCurrentMonth() {
335
- const today = /* @__PURE__ */ new Date();
336
- return this.state.currentYear === today.getFullYear() && this.state.currentMonth === today.getMonth();
337
- }
338
- /**
339
- * Select a specific date
340
- */
341
- selectDate(date, dayIndex) {
342
- this.state.selectedDate = date;
343
- this.state.selectedDayIndex = dayIndex ?? null;
344
- this.state.currentDate = date.getDate();
345
- this.state.currentMonth = date.getMonth();
346
- this.state.currentYear = date.getFullYear();
347
- this.updateTasks();
348
- this.notify();
349
- }
350
- /**
351
- * Update tasks for the currently selected date
352
- */
353
- updateTasks() {
354
- if (!this.state.selectedDate) {
355
- this.state.tasks = [];
356
- return;
357
- }
358
- this.state.tasks = getEventsForDate(
359
- this.config.events,
360
- this.state.selectedDate
361
- );
362
- }
363
- /**
364
- * Update events configuration
365
- */
366
- updateEvents(events) {
367
- this.config.events = events;
368
- this.updateTasks();
369
- this.notify();
370
- }
371
- /**
372
- * Handle date cell click
373
- */
374
- handleDateClick(date, dayIndex) {
375
- this.selectDate(date, dayIndex);
376
- }
377
- /**
378
- * Check if date has events
379
- */
380
- hasEventsForDate(date) {
381
- return getEventsForDate(this.config.events, date).length > 0;
382
- }
383
- /**
384
- * Get events for a specific date
385
- */
386
- getEventsForDate(date) {
387
- return getEventsForDate(this.config.events, date);
388
- }
389
- /**
390
- * Clear selected date
391
- */
392
- clearSelection() {
393
- this.state.selectedDate = null;
394
- this.state.selectedDayIndex = null;
395
- this.state.tasks = [];
396
- this.notify();
397
- }
398
- /**
399
- * Get category color (with custom colors support)
400
- */
401
- getCategoryColor(category) {
402
- if (!category) return "#fc8917";
403
- return getCategoryColor(category, this.categoryColors);
404
- }
405
- /**
406
- * Update category colors dynamically
407
- */
408
- updateCategoryColors(colors) {
409
- this.categoryColors = mergeCategoryColors(colors);
410
- this.notify();
411
- }
412
- /**
413
- * Get all category colors
414
- */
415
- getCategoryColors() {
416
- return { ...this.categoryColors };
417
- }
418
- /**
419
- * Register a new category with color
420
- */
421
- registerCategory(name, color) {
422
- this.categoryColors[name] = color;
423
- this.notify();
424
- }
425
- /**
426
- * Destroy the engine and cleanup listeners
427
- */
428
- destroy() {
429
- this.listeners.clear();
430
- }
431
- };
432
-
433
- // src/vanilla/Calendar.ts
434
- var VanillaCalendar = class {
435
- constructor(props) {
436
- this.unsubscribe = null;
437
- this.pickerOpen = false;
438
- this.yearInput = "";
439
- this.yearInputValid = true;
440
- this.clickOutsideHandler = null;
441
- this.delegatedClickHandler = null;
442
- this.delegatedInputHandler = null;
443
- this.delegatedBlurHandler = null;
444
- this.delegatedKeydownHandler = null;
445
- this.listenersAttached = false;
446
- this.props = props;
447
- if (typeof props.container === "string") {
448
- const element = document.querySelector(props.container);
449
- if (!element) {
450
- throw new Error(`Container element "${props.container}" not found`);
451
- }
452
- this.container = element;
453
- } else {
454
- this.container = props.container;
455
- }
456
- this.engine = new CalendarEngine({
457
- events: props.events,
458
- initialDate: props.initialDate,
459
- minYear: props.minYear,
460
- maxYear: props.maxYear,
461
- weekStartsOn: props.weekStartsOn
462
- });
463
- this.actions = this.engine.getActions();
464
- this.applyTheme();
465
- this.init();
466
- }
467
- applyTheme() {
468
- if (this.props.theme) {
469
- const root = document.documentElement;
470
- const theme = this.props.theme;
471
- if (theme.primary)
472
- root.style.setProperty("--calendar-primary-color", theme.primary);
473
- if (theme.secondary)
474
- root.style.setProperty("--calendar-secondary-color", theme.secondary);
475
- if (theme.tertiary)
476
- root.style.setProperty("--calendar-tertiary-color", theme.tertiary);
477
- if (theme.textColor)
478
- root.style.setProperty("--calendar-text-color", theme.textColor);
479
- if (theme.textLight)
480
- root.style.setProperty("--calendar-text-light", theme.textLight);
481
- if (theme.background)
482
- root.style.setProperty("--calendar-background", theme.background);
483
- if (theme.cellHover)
484
- root.style.setProperty("--calendar-cell-hover", theme.cellHover);
485
- if (theme.borderColor)
486
- root.style.setProperty("--calendar-border-color", theme.borderColor);
487
- if (theme.todayOutline)
488
- root.style.setProperty("--calendar-today-outline", theme.todayOutline);
489
- if (theme.selectedBg)
490
- root.style.setProperty("--calendar-selected-bg", theme.selectedBg);
491
- if (theme.eventIndicator)
492
- root.style.setProperty(
493
- "--calendar-event-indicator",
494
- theme.eventIndicator
495
- );
496
- }
497
- }
498
- init() {
499
- this.container.classList.add("kalendly-calendar");
500
- if (this.props.className) {
501
- this.container.classList.add(this.props.className);
502
- }
503
- this.unsubscribe = this.engine.subscribe(() => {
504
- this.render();
505
- });
506
- this.render();
507
- }
508
- render() {
509
- const viewModel = this.engine.getViewModel();
510
- const defaultRenderEvent = (event) => {
511
- const timeRange = formatTimeRange(event);
512
- const attendeesList = formatAttendees(event.attendees);
513
- let borderColor = event.color || "#3b82f6";
514
- if (event.category) {
515
- borderColor = this.engine.getCategoryColor(event.category);
516
- }
517
- const getCategoryLabel = (category) => {
518
- const labels = {
519
- work: "WORK",
520
- personal: "PERSONAL",
521
- meeting: "MEETING",
522
- deadline: "DEADLINE",
523
- appointment: "APPOINTMENT",
524
- other: "OTHER"
525
- };
526
- return category ? labels[category] || category.toUpperCase() : "";
527
- };
528
- const getPriorityLabel = (priority) => {
529
- const labels = {
530
- high: "HIGH",
531
- medium: "MEDIUM",
532
- low: "LOW"
533
- };
534
- return priority ? labels[priority] || priority.toUpperCase() : "";
535
- };
536
- const getStatusLabel = (status) => {
537
- const labels = {
538
- completed: "COMPLETED",
539
- cancelled: "CANCELLED",
540
- tentative: "TENTATIVE",
541
- scheduled: "SCHEDULED"
542
- };
543
- return status ? labels[status] || status.toUpperCase() : "";
544
- };
545
- return `
546
- <div class="event-card" style="border-left-color: ${borderColor}">
547
- <div class="event-header">
548
- <div class="event-title">${event.name}</div>
549
- <div class="event-badges">
550
- ${event.category ? `<span class="badge category-${event.category}">${getCategoryLabel(event.category)}</span>` : ""}
551
- ${event.priority ? `<span class="badge priority-${event.priority}">${getPriorityLabel(event.priority)}</span>` : ""}
552
- ${event.status && event.status !== "scheduled" ? `<span class="badge status-${event.status}">${getStatusLabel(event.status)}</span>` : ""}
553
- </div>
554
- </div>
555
-
556
- ${timeRange ? `
557
- <div class="event-time">
558
- <span class="event-time-label">Time:</span>
559
- <span class="event-time-value">${timeRange}</span>
560
- </div>
561
- ` : ""}
562
-
563
- ${event.description ? `
564
- <div class="event-description">${event.description}</div>
565
- ` : ""}
566
-
567
- ${event.location ? `
568
- <div class="event-time">
569
- <span class="event-time-label">Location:</span>
570
- <span class="event-time-value">${event.location}</span>
571
- </div>
572
- ` : ""}
573
-
574
- ${attendeesList ? `
575
- <div class="event-time">
576
- <span class="event-time-label">Attendees:</span>
577
- <span class="event-time-value">${attendeesList}</span>
578
- </div>
579
- ` : ""}
580
-
581
- ${event.organizer ? `
582
- <div class="event-time">
583
- <span class="event-time-label">Organizer:</span>
584
- <span class="event-time-value">${event.organizer}</span>
585
- </div>
586
- ` : ""}
587
-
588
- ${event.notes ? `
589
- <div class="event-time">
590
- <span class="event-time-label">Notes:</span>
591
- <span class="event-time-value">${event.notes}</span>
592
- </div>
593
- ` : ""}
594
-
595
- ${event.url ? `
596
- <div class="event-time">
597
- <a href="${event.url}" target="_blank" rel="noopener noreferrer" class="event-link">
598
- View Details \u2192
599
- </a>
600
- </div>
601
- ` : ""}
602
-
603
- ${event.tags && event.tags.length > 0 ? `
604
- <div class="event-tags">
605
- ${event.tags.map((tag) => `<span class="event-tag">${tag}</span>`).join("")}
606
- </div>
607
- ` : ""}
608
- </div>
609
- `;
610
- };
611
- const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
612
- const renderEvent = this.props.renderEvent || defaultRenderEvent;
613
- const renderNoEvents = this.props.renderNoEvents || defaultRenderNoEvents;
614
- const showScrollHint = viewModel.tasks.length > 3;
615
- const today = /* @__PURE__ */ new Date();
616
- const todayMonth = today.getMonth();
617
- const todayYear = today.getFullYear();
618
- const minYear = this.props.minYear ?? todayYear - 30;
619
- const maxYear = this.props.maxYear ?? todayYear + 10;
620
- const isCurrentMonth = viewModel.currentYear === todayYear && viewModel.currentMonth === todayMonth;
621
- const html = `
622
- ${this.props.title ? `
623
- <div class="page--title">
624
- <h1>${this.props.title}</h1>
625
- </div>
626
- ` : ""}
627
-
628
- <div class="calendar--content">
629
- <div class="calendar--card">
630
- <!-- Navigation Header -->
631
- <div class="calendar--nav-header">
632
- <button type="button" class="calendar--nav-arrow" data-action="previous" aria-label="Previous month">
633
- &#8249;
634
- </button>
635
-
636
- <div class="calendar--picker-container" data-picker-container>
637
- <button
638
- type="button"
639
- class="calendar--picker-btn"
640
- data-action="toggle-picker"
641
- aria-expanded="${this.pickerOpen ? "true" : "false"}"
642
- aria-haspopup="true"
643
- >
644
- ${this.props.useShortMonthNames ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
645
- <span class="calendar--picker-chevron">&#9662;</span>
646
- </button>
647
-
648
- ${this.pickerOpen ? `
649
- <div class="calendar--picker-dropdown">
650
- <div class="calendar--picker-year-row">
651
- <button
652
- type="button"
653
- class="calendar--picker-year-arrow"
654
- data-action="year-prev"
655
- ${viewModel.currentYear <= minYear ? "disabled" : ""}
656
- aria-label="Previous year"
657
- >&#8249;</button>
658
- <input
659
- type="text"
660
- class="calendar--picker-year-input${!this.yearInputValid ? " invalid" : ""}"
661
- value="${this.yearInput || viewModel.currentYear}"
662
- data-year-input
663
- aria-label="Year"
664
- />
665
- <button
666
- type="button"
667
- class="calendar--picker-year-arrow"
668
- data-action="year-next"
669
- ${viewModel.currentYear >= maxYear ? "disabled" : ""}
670
- aria-label="Next year"
671
- >&#8250;</button>
672
- </div>
673
-
674
- <div class="calendar--picker-months">
675
- ${(this.props.useShortMonthNames ? MONTHS : MONTHS_FULL).map((month, index) => {
676
- const isSelected = index === viewModel.currentMonth;
677
- const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
678
- return `
679
- <button
680
- type="button"
681
- class="calendar--picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
682
- data-action="select-month"
683
- data-month="${index}"
684
- >${month}</button>
685
- `;
686
- }).join("")}
687
- </div>
688
- </div>
689
- ` : ""}
690
- </div>
691
-
692
- <button
693
- type="button"
694
- class="calendar--today-btn"
695
- data-action="today"
696
- ${isCurrentMonth ? "disabled" : ""}
697
- >Today</button>
698
-
699
- <button type="button" class="calendar--nav-arrow" data-action="next" aria-label="Next month">
700
- &#8250;
701
- </button>
702
- </div>
703
-
704
- <table class="calendar--table calendar--table--bordered">
705
- <thead>
706
- <tr>
707
- ${viewModel.days.map((day) => `<th>${day.slice(0, 3)}</th>`).join("")}
708
- </tr>
709
- </thead>
710
- <tbody data-calendar-body>
711
- ${viewModel.calendarDates.map(
712
- (week) => `
713
- <tr>
714
- ${week.map((calendarDate, dayIndex) => {
715
- const classes = getCellClasses(calendarDate);
716
- const dateString = calendarDate.date.toISOString();
717
- return `
718
- <td
719
- class="${classes.join(" ")}"
720
- data-date="${dateString}"
721
- data-day-index="${dayIndex}"
722
- data-clickable="true"
723
- >
724
- ${calendarDate.date.getDate()}
725
- </td>
726
- `;
727
- }).join("")}
728
- </tr>
729
- `
730
- ).join("")}
731
- </tbody>
732
- </table>
733
-
734
- ${viewModel.selectedDate ? `
735
- <div class="date-popup ${viewModel.popupPositionClass}">
736
- <div class="popup-header">
737
- <h2>${viewModel.scheduleDay}</h2>
738
- <button type="button" class="popup-close" data-action="close-popup" aria-label="Close">\u2715</button>
739
- </div>
740
-
741
- ${showScrollHint ? `
742
- <div class="scroll-hint">
743
- \u2193 Scroll to see more events \u2193
744
- </div>
745
- ` : ""}
746
-
747
- <div class="events-container">
748
- ${viewModel.tasks.length > 0 ? `
749
- ${viewModel.tasks.map((event) => renderEvent(event)).join("")}
750
- ` : renderNoEvents()}
751
- </div>
752
- </div>
753
- ` : ""}
754
- </div>
755
- </div>
756
- `;
757
- this.container.innerHTML = html;
758
- this.attachEventListeners();
759
- }
760
- attachEventListeners() {
761
- if (this.listenersAttached) return;
762
- this.listenersAttached = true;
763
- const today = /* @__PURE__ */ new Date();
764
- const todayYear = today.getFullYear();
765
- const todayMonth = today.getMonth();
766
- const minYear = this.props.minYear ?? todayYear - 30;
767
- const maxYear = this.props.maxYear ?? todayYear + 10;
768
- this.delegatedClickHandler = (e) => {
769
- const target = e.target;
770
- const cell = target.closest('td[data-clickable="true"]');
771
- if (cell && cell.dataset.date) {
772
- e.stopPropagation();
773
- const date = new Date(cell.dataset.date);
774
- const dayIndex = parseInt(cell.dataset.dayIndex || "0");
775
- this.engine.handleDateClick(date, dayIndex);
776
- this.container.dispatchEvent(
777
- new CustomEvent("dateSelect", { detail: { date, dayIndex } })
778
- );
779
- return;
780
- }
781
- const actionEl = target.closest("[data-action]");
782
- if (!actionEl) return;
783
- const action = actionEl.dataset.action;
784
- e.stopPropagation();
785
- switch (action) {
786
- case "previous":
787
- this.actions.previous();
788
- this.dispatchMonthChange();
789
- break;
790
- case "next":
791
- this.actions.next();
792
- this.dispatchMonthChange();
793
- break;
794
- case "today":
795
- this.actions.goToToday();
796
- this.pickerOpen = false;
797
- this.container.dispatchEvent(
798
- new CustomEvent("monthChange", {
799
- detail: { year: todayYear, month: todayMonth }
800
- })
801
- );
802
- break;
803
- case "toggle-picker":
804
- this.pickerOpen = !this.pickerOpen;
805
- if (this.pickerOpen) {
806
- this.yearInput = String(this.engine.getViewModel().currentYear);
807
- this.yearInputValid = true;
808
- }
809
- this.render();
810
- break;
811
- case "year-prev": {
812
- const vm = this.engine.getViewModel();
813
- if (vm.currentYear > minYear) {
814
- const newYear = vm.currentYear - 1;
815
- this.yearInput = String(newYear);
816
- this.updatePickerYear(newYear);
817
- this.actions.jump(newYear, vm.currentMonth);
818
- this.dispatchMonthChange();
819
- }
820
- break;
821
- }
822
- case "year-next": {
823
- const vm = this.engine.getViewModel();
824
- if (vm.currentYear < maxYear) {
825
- const newYear = vm.currentYear + 1;
826
- this.yearInput = String(newYear);
827
- this.updatePickerYear(newYear);
828
- this.actions.jump(newYear, vm.currentMonth);
829
- this.dispatchMonthChange();
830
- }
831
- break;
832
- }
833
- case "select-month": {
834
- const month = parseInt(actionEl.dataset.month || "0", 10);
835
- this.actions.jump(this.engine.getViewModel().currentYear, month);
836
- this.pickerOpen = false;
837
- this.dispatchMonthChange();
838
- break;
839
- }
840
- case "close-popup":
841
- this.engine.clearSelection();
842
- break;
843
- }
844
- };
845
- this.delegatedInputHandler = (e) => {
846
- const target = e.target;
847
- if (!target.matches("[data-year-input]")) return;
848
- const value = target.value;
849
- if (value === "" || /^\d+$/.test(value)) {
850
- this.yearInput = value;
851
- const year = parseInt(value, 10);
852
- this.yearInputValid = value === "" || year >= minYear && year <= maxYear;
853
- target.classList.toggle("invalid", !this.yearInputValid);
854
- }
855
- };
856
- this.delegatedBlurHandler = (e) => {
857
- const target = e.target;
858
- if (!target.matches("[data-year-input]")) return;
859
- const year = parseInt(this.yearInput, 10);
860
- const vm = this.engine.getViewModel();
861
- if (isNaN(year) || year < minYear || year > maxYear) {
862
- this.yearInput = String(vm.currentYear);
863
- this.yearInputValid = true;
864
- target.value = this.yearInput;
865
- target.classList.remove("invalid");
866
- } else if (year !== vm.currentYear) {
867
- this.actions.jump(year, vm.currentMonth);
868
- this.dispatchMonthChange();
869
- }
870
- };
871
- this.delegatedKeydownHandler = (e) => {
872
- const target = e.target;
873
- if (!target.matches("[data-year-input]")) return;
874
- if (e.key === "Enter") {
875
- target.blur();
876
- }
877
- };
878
- this.container.addEventListener("click", this.delegatedClickHandler);
879
- this.container.addEventListener("input", this.delegatedInputHandler);
880
- this.container.addEventListener("blur", this.delegatedBlurHandler, true);
881
- this.container.addEventListener("keydown", this.delegatedKeydownHandler);
882
- this.clickOutsideHandler = (e) => {
883
- const target = e.target;
884
- if (this.container.contains(target)) return;
885
- if (this.pickerOpen) {
886
- this.pickerOpen = false;
887
- this.render();
888
- return;
889
- }
890
- const popup = this.container.querySelector(".date-popup");
891
- if (popup) {
892
- this.engine.clearSelection();
893
- }
894
- };
895
- document.addEventListener("click", this.clickOutsideHandler);
896
- }
897
- // Targeted update for picker year - avoids full DOM rebuild
898
- updatePickerYear(year) {
899
- const input = this.container.querySelector(
900
- "[data-year-input]"
901
- );
902
- if (input) {
903
- input.value = String(year);
904
- }
905
- const today = /* @__PURE__ */ new Date();
906
- const todayYear = today.getFullYear();
907
- const minYear = this.props.minYear ?? todayYear - 30;
908
- const maxYear = this.props.maxYear ?? todayYear + 10;
909
- const prevBtn = this.container.querySelector(
910
- '[data-action="year-prev"]'
911
- );
912
- const nextBtn = this.container.querySelector(
913
- '[data-action="year-next"]'
914
- );
915
- if (prevBtn) prevBtn.disabled = year <= minYear;
916
- if (nextBtn) nextBtn.disabled = year >= maxYear;
917
- }
918
- dispatchMonthChange() {
919
- const vm = this.engine.getViewModel();
920
- this.container.dispatchEvent(
921
- new CustomEvent("monthChange", {
922
- detail: { year: vm.currentYear, month: vm.currentMonth }
923
- })
924
- );
925
- }
926
- updateEvents(events) {
927
- this.engine.updateEvents(events);
928
- }
929
- updateTheme(theme) {
930
- this.props.theme = theme;
931
- this.applyTheme();
932
- }
933
- getCurrentDate() {
934
- return this.engine.getViewModel().selectedDate;
935
- }
936
- goToDate(date) {
937
- this.actions.jump(date.getFullYear(), date.getMonth());
938
- }
939
- getEngine() {
940
- return this.engine;
941
- }
942
- destroy() {
943
- if (this.unsubscribe) {
944
- this.unsubscribe();
945
- this.unsubscribe = null;
946
- }
947
- if (this.delegatedClickHandler) {
948
- this.container.removeEventListener("click", this.delegatedClickHandler);
949
- this.delegatedClickHandler = null;
950
- }
951
- if (this.delegatedInputHandler) {
952
- this.container.removeEventListener("input", this.delegatedInputHandler);
953
- this.delegatedInputHandler = null;
954
- }
955
- if (this.delegatedBlurHandler) {
956
- this.container.removeEventListener(
957
- "blur",
958
- this.delegatedBlurHandler,
959
- true
960
- );
961
- this.delegatedBlurHandler = null;
962
- }
963
- if (this.delegatedKeydownHandler) {
964
- this.container.removeEventListener(
965
- "keydown",
966
- this.delegatedKeydownHandler
967
- );
968
- this.delegatedKeydownHandler = null;
969
- }
970
- if (this.clickOutsideHandler) {
971
- document.removeEventListener("click", this.clickOutsideHandler);
972
- this.clickOutsideHandler = null;
973
- }
974
- this.listenersAttached = false;
975
- this.engine.destroy();
976
- this.container.innerHTML = "";
977
- this.container.classList.remove("kalendly-calendar");
978
- if (this.props.className) {
979
- this.container.classList.remove(this.props.className);
980
- }
981
- }
982
- };
983
- function createCalendar(props) {
984
- return new VanillaCalendar(props);
985
- }
986
- export {
987
- CalendarEngine,
988
- DAYS,
989
- DEFAULT_CATEGORY_COLORS,
990
- MONTHS,
991
- MONTHS_FULL,
992
- VanillaCalendar,
993
- createCalendar,
994
- formatAttendees,
995
- formatDateForDisplay,
996
- formatTimeRange,
997
- generateCalendarDates,
998
- generateYears,
999
- getCategoryColor,
1000
- getCellClasses,
1001
- getDefaultEventColor,
1002
- getEventsForDate,
1003
- getMonthYearText,
1004
- getPopupPositionClass,
1005
- hasEvents,
1006
- isSameDay,
1007
- isToday,
1008
- isValidHexColor,
1009
- mergeCategoryColors,
1010
- normalizeDate,
1011
- sortEventsByTime
1012
- };
1013
- //# sourceMappingURL=index.mjs.map