kalendly 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +377 -662
  2. package/dist/core/index.js +3 -3
  3. package/dist/index.d.mts +61 -602
  4. package/dist/index.d.ts +61 -602
  5. package/dist/index.js +963 -2375
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +937 -2364
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/{vanilla/index.umd.js → index.umd.js} +438 -244
  10. package/dist/index.umd.js.map +1 -0
  11. package/dist/styles/calendar.css +96 -0
  12. package/package.json +54 -118
  13. package/dist/react/index.d.mts +0 -251
  14. package/dist/react/index.d.ts +0 -251
  15. package/dist/react/index.js +0 -969
  16. package/dist/react/index.js.map +0 -1
  17. package/dist/react/index.mjs +0 -924
  18. package/dist/react/index.mjs.map +0 -1
  19. package/dist/react-native/index.d.mts +0 -642
  20. package/dist/react-native/index.d.ts +0 -642
  21. package/dist/react-native/index.js +0 -1649
  22. package/dist/react-native/index.js.map +0 -1
  23. package/dist/react-native/index.mjs +0 -1615
  24. package/dist/react-native/index.mjs.map +0 -1
  25. package/dist/vanilla/index.d.mts +0 -271
  26. package/dist/vanilla/index.d.ts +0 -271
  27. package/dist/vanilla/index.js +0 -1064
  28. package/dist/vanilla/index.js.map +0 -1
  29. package/dist/vanilla/index.mjs +0 -1013
  30. package/dist/vanilla/index.mjs.map +0 -1
  31. package/dist/vanilla/index.umd.js.map +0 -1
  32. package/dist/vue/components/Calendar.vue.d.ts +0 -43
  33. package/dist/vue/components/Calendar.vue.d.ts.map +0 -1
  34. package/dist/vue/index.d.ts +0 -134
  35. package/dist/vue/index.d.ts.map +0 -1
  36. package/dist/vue/index.js +0 -1
  37. package/dist/vue/index.mjs +0 -717
  38. package/dist/vue/types.d.ts +0 -21
  39. package/dist/vue/types.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,47 +1,21 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
2
6
  var __export = (target, all) => {
3
- for (var name2 in all)
4
- __defProp(target, name2, { get: all[name2], enumerable: true });
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
5
9
  };
6
10
 
11
+ // src/core/types.ts
12
+ var init_types = __esm({
13
+ "src/core/types.ts"() {
14
+ "use strict";
15
+ }
16
+ });
17
+
7
18
  // src/core/utils.ts
8
- var MONTHS = [
9
- "Jan",
10
- "Feb",
11
- "Mar",
12
- "Apr",
13
- "May",
14
- "Jun",
15
- "Jul",
16
- "Aug",
17
- "Sep",
18
- "Oct",
19
- "Nov",
20
- "Dec"
21
- ];
22
- var MONTHS_FULL = [
23
- "January",
24
- "February",
25
- "March",
26
- "April",
27
- "May",
28
- "June",
29
- "July",
30
- "August",
31
- "September",
32
- "October",
33
- "November",
34
- "December"
35
- ];
36
- var DAYS = [
37
- "Sunday",
38
- "Monday",
39
- "Tuesday",
40
- "Wednesday",
41
- "Thursday",
42
- "Friday",
43
- "Saturday"
44
- ];
45
19
  function normalizeDate(date) {
46
20
  return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
47
21
  }
@@ -178,14 +152,6 @@ function sortEventsByTime(events) {
178
152
  return a.startTime.localeCompare(b.startTime);
179
153
  });
180
154
  }
181
- var DEFAULT_CATEGORY_COLORS = {
182
- work: "#3b82f6",
183
- personal: "#8b5cf6",
184
- meeting: "#10b981",
185
- deadline: "#ef4444",
186
- appointment: "#f59e0b",
187
- other: "#6b7280"
188
- };
189
155
  function getDefaultEventColor(category, customColors) {
190
156
  const colorMap = customColors || DEFAULT_CATEGORY_COLORS;
191
157
  if (category && colorMap[category]) {
@@ -207,2059 +173,553 @@ function getCategoryColor(category, customColors) {
207
173
  const color = colorMap[category] || colorMap.other || "#fc8917";
208
174
  return isValidHexColor(color) ? color : "#fc8917";
209
175
  }
210
-
211
- // src/core/calendar-engine.ts
212
- var CalendarEngine = class {
213
- constructor(config) {
214
- this.listeners = /* @__PURE__ */ new Set();
215
- this.config = config;
216
- this.categoryColors = mergeCategoryColors(config.categoryColors);
217
- const initialDate = config.initialDate || /* @__PURE__ */ new Date();
218
- this.state = {
219
- currentYear: initialDate.getFullYear(),
220
- currentMonth: initialDate.getMonth(),
221
- currentDate: initialDate.getDate(),
222
- selectedDate: null,
223
- selectedDayIndex: null,
224
- tasks: []
176
+ var MONTHS, MONTHS_FULL, DAYS, DEFAULT_CATEGORY_COLORS;
177
+ var init_utils = __esm({
178
+ "src/core/utils.ts"() {
179
+ "use strict";
180
+ MONTHS = [
181
+ "Jan",
182
+ "Feb",
183
+ "Mar",
184
+ "Apr",
185
+ "May",
186
+ "Jun",
187
+ "Jul",
188
+ "Aug",
189
+ "Sep",
190
+ "Oct",
191
+ "Nov",
192
+ "Dec"
193
+ ];
194
+ MONTHS_FULL = [
195
+ "January",
196
+ "February",
197
+ "March",
198
+ "April",
199
+ "May",
200
+ "June",
201
+ "July",
202
+ "August",
203
+ "September",
204
+ "October",
205
+ "November",
206
+ "December"
207
+ ];
208
+ DAYS = [
209
+ "Sunday",
210
+ "Monday",
211
+ "Tuesday",
212
+ "Wednesday",
213
+ "Thursday",
214
+ "Friday",
215
+ "Saturday"
216
+ ];
217
+ DEFAULT_CATEGORY_COLORS = {
218
+ work: "#3b82f6",
219
+ personal: "#8b5cf6",
220
+ meeting: "#10b981",
221
+ deadline: "#ef4444",
222
+ appointment: "#f59e0b",
223
+ other: "#6b7280"
225
224
  };
226
225
  }
227
- /**
228
- * Subscribe to state changes
229
- */
230
- subscribe(listener) {
231
- this.listeners.add(listener);
232
- return () => this.listeners.delete(listener);
233
- }
234
- /**
235
- * Notify all listeners of state changes
236
- */
237
- notify() {
238
- this.listeners.forEach((listener) => listener());
239
- }
240
- /**
241
- * Get current state
242
- */
243
- getState() {
244
- return { ...this.state };
245
- }
246
- /**
247
- * Get view model with computed properties
248
- */
249
- getViewModel() {
250
- const calendarDates = generateCalendarDates(
251
- this.state.currentYear,
252
- this.state.currentMonth,
253
- this.config.events,
254
- this.config.weekStartsOn
255
- );
256
- return {
257
- ...this.state,
258
- months: MONTHS,
259
- days: DAYS,
260
- years: generateYears(this.config.minYear, this.config.maxYear),
261
- monthAndYearText: getMonthYearText(
262
- this.state.currentYear,
263
- this.state.currentMonth
264
- ),
265
- scheduleDay: this.state.selectedDate ? formatDateForDisplay(this.state.selectedDate) : "",
266
- calendarDates,
267
- popupPositionClass: getPopupPositionClass(this.state.selectedDayIndex)
268
- };
269
- }
270
- /**
271
- * Get actions object
272
- */
273
- getActions() {
274
- return {
275
- next: this.next.bind(this),
276
- previous: this.previous.bind(this),
277
- jump: this.jump.bind(this),
278
- goToToday: this.goToToday.bind(this),
279
- isCurrentMonth: this.isCurrentMonth.bind(this),
280
- selectDate: this.selectDate.bind(this),
281
- updateTasks: this.updateTasks.bind(this)
282
- };
283
- }
284
- /**
285
- * Navigate to next month
286
- */
287
- next() {
288
- if (this.state.currentMonth === 11) {
289
- this.state.currentMonth = 0;
290
- this.state.currentYear++;
291
- } else {
292
- this.state.currentMonth++;
293
- }
294
- this.state.selectedDate = null;
295
- this.state.selectedDayIndex = null;
296
- this.updateTasks();
297
- this.notify();
298
- }
299
- /**
300
- * Navigate to previous month
301
- */
302
- previous() {
303
- if (this.state.currentMonth === 0) {
304
- this.state.currentMonth = 11;
305
- this.state.currentYear--;
306
- } else {
307
- this.state.currentMonth--;
308
- }
309
- this.state.selectedDate = null;
310
- this.state.selectedDayIndex = null;
311
- this.updateTasks();
312
- this.notify();
313
- }
314
- /**
315
- * Jump to specific month and year
316
- */
317
- jump(year, month) {
318
- this.state.currentYear = year;
319
- this.state.currentMonth = month;
320
- this.state.selectedDate = null;
321
- this.state.selectedDayIndex = null;
322
- this.updateTasks();
323
- this.notify();
324
- }
325
- /**
326
- * Navigate to current month (today)
327
- */
328
- goToToday() {
329
- const today = /* @__PURE__ */ new Date();
330
- this.state.currentYear = today.getFullYear();
331
- this.state.currentMonth = today.getMonth();
332
- this.state.selectedDate = null;
333
- this.state.selectedDayIndex = null;
334
- this.updateTasks();
335
- this.notify();
336
- }
337
- /**
338
- * Check if currently viewing today's month
339
- */
340
- isCurrentMonth() {
341
- const today = /* @__PURE__ */ new Date();
342
- return this.state.currentYear === today.getFullYear() && this.state.currentMonth === today.getMonth();
343
- }
344
- /**
345
- * Select a specific date
346
- */
347
- selectDate(date, dayIndex) {
348
- this.state.selectedDate = date;
349
- this.state.selectedDayIndex = dayIndex ?? null;
350
- this.state.currentDate = date.getDate();
351
- this.state.currentMonth = date.getMonth();
352
- this.state.currentYear = date.getFullYear();
353
- this.updateTasks();
354
- this.notify();
355
- }
356
- /**
357
- * Update tasks for the currently selected date
358
- */
359
- updateTasks() {
360
- if (!this.state.selectedDate) {
361
- this.state.tasks = [];
362
- return;
363
- }
364
- this.state.tasks = getEventsForDate(
365
- this.config.events,
366
- this.state.selectedDate
367
- );
368
- }
369
- /**
370
- * Update events configuration
371
- */
372
- updateEvents(events) {
373
- this.config.events = events;
374
- this.updateTasks();
375
- this.notify();
376
- }
377
- /**
378
- * Handle date cell click
379
- */
380
- handleDateClick(date, dayIndex) {
381
- this.selectDate(date, dayIndex);
382
- }
383
- /**
384
- * Check if date has events
385
- */
386
- hasEventsForDate(date) {
387
- return getEventsForDate(this.config.events, date).length > 0;
388
- }
389
- /**
390
- * Get events for a specific date
391
- */
392
- getEventsForDate(date) {
393
- return getEventsForDate(this.config.events, date);
394
- }
395
- /**
396
- * Clear selected date
397
- */
398
- clearSelection() {
399
- this.state.selectedDate = null;
400
- this.state.selectedDayIndex = null;
401
- this.state.tasks = [];
402
- this.notify();
403
- }
404
- /**
405
- * Get category color (with custom colors support)
406
- */
407
- getCategoryColor(category) {
408
- if (!category) return "#fc8917";
409
- return getCategoryColor(category, this.categoryColors);
410
- }
411
- /**
412
- * Update category colors dynamically
413
- */
414
- updateCategoryColors(colors) {
415
- this.categoryColors = mergeCategoryColors(colors);
416
- this.notify();
417
- }
418
- /**
419
- * Get all category colors
420
- */
421
- getCategoryColors() {
422
- return { ...this.categoryColors };
423
- }
424
- /**
425
- * Register a new category with color
426
- */
427
- registerCategory(name2, color) {
428
- this.categoryColors[name2] = color;
429
- this.notify();
430
- }
431
- /**
432
- * Destroy the engine and cleanup listeners
433
- */
434
- destroy() {
435
- this.listeners.clear();
436
- }
437
- };
438
-
439
- // src/react/index.tsx
440
- var react_exports = {};
441
- __export(react_exports, {
442
- Calendar: () => Calendar,
443
- CalendarEngine: () => CalendarEngine,
444
- DAYS: () => DAYS,
445
- DEFAULT_CATEGORY_COLORS: () => DEFAULT_CATEGORY_COLORS,
446
- DatePopup: () => DatePopup,
447
- MONTHS: () => MONTHS,
448
- MONTHS_FULL: () => MONTHS_FULL,
449
- formatAttendees: () => formatAttendees,
450
- formatDateForDisplay: () => formatDateForDisplay,
451
- formatTimeRange: () => formatTimeRange,
452
- generateCalendarDates: () => generateCalendarDates,
453
- generateYears: () => generateYears,
454
- getCategoryColor: () => getCategoryColor,
455
- getCellClasses: () => getCellClasses,
456
- getDefaultEventColor: () => getDefaultEventColor,
457
- getEventsForDate: () => getEventsForDate,
458
- getMonthYearText: () => getMonthYearText,
459
- getPopupPositionClass: () => getPopupPositionClass,
460
- hasEvents: () => hasEvents,
461
- isSameDay: () => isSameDay,
462
- isToday: () => isToday,
463
- isValidHexColor: () => isValidHexColor,
464
- mergeCategoryColors: () => mergeCategoryColors,
465
- normalizeDate: () => normalizeDate,
466
- sortEventsByTime: () => sortEventsByTime
467
226
  });
468
227
 
469
- // src/react/components/Calendar.tsx
470
- import {
471
- useEffect,
472
- useState,
473
- useCallback,
474
- useMemo,
475
- useRef
476
- } from "react";
477
-
478
- // src/react/components/DatePopup.tsx
479
- import { jsx, jsxs } from "react/jsx-runtime";
480
- var PriorityBadge = ({ priority }) => {
481
- if (!priority) return null;
482
- const priorityConfig = {
483
- high: { label: "HIGH", className: "badge priority-high" },
484
- medium: { label: "MEDIUM", className: "badge priority-medium" },
485
- low: { label: "LOW", className: "badge priority-low" }
486
- };
487
- const config = priorityConfig[priority];
488
- if (!config) return null;
489
- return /* @__PURE__ */ jsx("span", { className: config.className, children: config.label });
490
- };
491
- var StatusBadge = ({ status }) => {
492
- if (!status || status === "scheduled") return null;
493
- const statusConfig = {
494
- completed: { label: "COMPLETED", className: "badge status-completed" },
495
- cancelled: { label: "CANCELLED", className: "badge status-cancelled" },
496
- tentative: { label: "TENTATIVE", className: "badge status-tentative" }
497
- };
498
- const config = statusConfig[status];
499
- if (!config) return null;
500
- return /* @__PURE__ */ jsx("span", { className: config.className, children: config.label });
501
- };
502
- var CategoryBadge = ({ category }) => {
503
- if (!category) return null;
504
- const categoryConfig = {
505
- work: { label: "WORK", className: "badge category-work" },
506
- personal: { label: "PERSONAL", className: "badge category-personal" },
507
- meeting: { label: "MEETING", className: "badge category-meeting" },
508
- deadline: { label: "DEADLINE", className: "badge category-deadline" },
509
- appointment: {
510
- label: "APPOINTMENT",
511
- className: "badge category-appointment"
512
- },
513
- other: { label: "OTHER", className: "badge category-other" }
514
- };
515
- const config = categoryConfig[category];
516
- if (!config) return null;
517
- return /* @__PURE__ */ jsx("span", { className: config.className, children: config.label });
518
- };
519
- var DatePopup = ({
520
- isVisible,
521
- selectedDate,
522
- events,
523
- scheduleDay,
524
- popupPositionClass,
525
- onEventClick,
526
- onClose,
527
- renderEvent,
528
- renderNoEvents
529
- }) => {
530
- if (!isVisible || !selectedDate) return null;
531
- const handleEventClick = (event) => {
532
- onEventClick?.(event);
533
- };
534
- const showScrollHint = events.length > 3;
535
- const defaultRenderEvent = (event) => {
536
- const timeRange = formatTimeRange(event);
537
- const attendeesList = formatAttendees(event.attendees);
538
- return /* @__PURE__ */ jsxs(
539
- "div",
540
- {
541
- className: `event-card${onEventClick ? " clickable" : ""}`,
542
- onClick: () => handleEventClick(event),
543
- style: {
544
- borderLeftColor: event.color || void 0
545
- },
546
- children: [
547
- /* @__PURE__ */ jsxs("div", { className: "event-header", children: [
548
- /* @__PURE__ */ jsx("div", { className: "event-title", children: event.name }),
549
- /* @__PURE__ */ jsxs("div", { className: "event-badges", children: [
550
- /* @__PURE__ */ jsx(CategoryBadge, { category: event.category }),
551
- /* @__PURE__ */ jsx(PriorityBadge, { priority: event.priority }),
552
- /* @__PURE__ */ jsx(StatusBadge, { status: event.status })
553
- ] })
554
- ] }),
555
- timeRange && /* @__PURE__ */ jsxs("div", { className: "event-time", children: [
556
- /* @__PURE__ */ jsx("span", { className: "event-time-label", children: "Time:" }),
557
- /* @__PURE__ */ jsx("span", { className: "event-time-value", children: timeRange })
558
- ] }),
559
- event.description && /* @__PURE__ */ jsx("div", { className: "event-description", children: event.description }),
560
- event.location && /* @__PURE__ */ jsxs("div", { className: "event-time", children: [
561
- /* @__PURE__ */ jsx("span", { className: "event-time-label", children: "Location:" }),
562
- /* @__PURE__ */ jsx("span", { className: "event-time-value", children: event.location })
563
- ] }),
564
- attendeesList && /* @__PURE__ */ jsxs("div", { className: "event-time", children: [
565
- /* @__PURE__ */ jsx("span", { className: "event-time-label", children: "Attendees:" }),
566
- /* @__PURE__ */ jsx("span", { className: "event-time-value", children: attendeesList })
567
- ] }),
568
- event.organizer && /* @__PURE__ */ jsxs("div", { className: "event-time", children: [
569
- /* @__PURE__ */ jsx("span", { className: "event-time-label", children: "Organizer:" }),
570
- /* @__PURE__ */ jsx("span", { className: "event-time-value", children: event.organizer })
571
- ] }),
572
- event.notes && /* @__PURE__ */ jsxs("div", { className: "event-time", children: [
573
- /* @__PURE__ */ jsx("span", { className: "event-time-label", children: "Notes:" }),
574
- /* @__PURE__ */ jsx("span", { className: "event-time-value", children: event.notes })
575
- ] }),
576
- event.url && /* @__PURE__ */ jsx("div", { className: "event-time", children: /* @__PURE__ */ jsx(
577
- "a",
578
- {
579
- href: event.url,
580
- target: "_blank",
581
- rel: "noopener noreferrer",
582
- className: "event-link",
583
- onClick: (e) => e.stopPropagation(),
584
- children: "View Details \u2192"
585
- }
586
- ) }),
587
- event.tags && event.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: "event-tags", children: event.tags.map((tag, idx) => /* @__PURE__ */ jsx("span", { className: "event-tag", children: tag }, idx)) })
588
- ]
589
- },
590
- event.id || event.name
591
- );
592
- };
593
- const defaultRenderNoEvents = () => /* @__PURE__ */ jsx("div", { className: "no-events-message", children: "No events scheduled for this day." });
594
- return /* @__PURE__ */ jsxs("div", { className: `date-popup ${popupPositionClass}`, children: [
595
- /* @__PURE__ */ jsxs("div", { className: "popup-header", children: [
596
- /* @__PURE__ */ jsx("h2", { children: scheduleDay }),
597
- /* @__PURE__ */ jsx(
598
- "button",
599
- {
600
- type: "button",
601
- className: "popup-close",
602
- onClick: onClose,
603
- "aria-label": "Close",
604
- children: "\u2715"
605
- }
606
- )
607
- ] }),
608
- showScrollHint && /* @__PURE__ */ jsx("div", { className: "scroll-hint", children: "\u2193 Scroll to see more events \u2193" }),
609
- /* @__PURE__ */ jsx("div", { className: "events-container", children: events.length > 0 ? events.map(
610
- (event) => renderEvent ? /* @__PURE__ */ jsx(
611
- "div",
612
- {
613
- className: `event-card${onEventClick ? " clickable" : ""}`,
614
- onClick: () => handleEventClick(event),
615
- children: renderEvent(event)
616
- },
617
- event.id || event.name
618
- ) : defaultRenderEvent(event)
619
- ) : renderNoEvents ? renderNoEvents() : defaultRenderNoEvents() })
620
- ] });
621
- };
622
-
623
- // src/react/components/Calendar.tsx
624
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
625
- var Calendar = ({
626
- events,
627
- initialDate,
628
- minYear,
629
- maxYear,
630
- weekStartsOn = 0,
631
- useShortMonthNames = false,
632
- onDateSelect,
633
- onEventClick,
634
- onMonthChange,
635
- className = "",
636
- style,
637
- renderEvent,
638
- renderNoEvents,
639
- title,
640
- theme
641
- }) => {
642
- const engine = useMemo(
643
- () => new CalendarEngine({
644
- events,
645
- initialDate,
646
- minYear,
647
- maxYear,
648
- weekStartsOn
649
- }),
650
- [events, initialDate, minYear, maxYear, weekStartsOn]
651
- );
652
- const [, forceUpdate] = useState({});
653
- const rerender = useCallback(() => forceUpdate({}), []);
654
- const [pickerOpen, setPickerOpen] = useState(false);
655
- const [yearInput, setYearInput] = useState("");
656
- const [yearInputValid, setYearInputValid] = useState(true);
657
- const pickerRef = useRef(null);
658
- useEffect(() => {
659
- const unsubscribe = engine.subscribe(rerender);
660
- return unsubscribe;
661
- }, [engine, rerender]);
662
- useEffect(() => {
663
- engine.updateEvents(events);
664
- }, [engine, events]);
665
- useEffect(() => {
666
- return () => {
667
- engine.destroy();
668
- };
669
- }, [engine]);
670
- useEffect(() => {
671
- if (theme) {
672
- const root = document.documentElement;
673
- if (theme.primary)
674
- root.style.setProperty("--calendar-primary-color", theme.primary);
675
- if (theme.secondary)
676
- root.style.setProperty("--calendar-secondary-color", theme.secondary);
677
- if (theme.tertiary)
678
- root.style.setProperty("--calendar-tertiary-color", theme.tertiary);
679
- if (theme.textColor)
680
- root.style.setProperty("--calendar-text-color", theme.textColor);
681
- if (theme.textLight)
682
- root.style.setProperty("--calendar-text-light", theme.textLight);
683
- if (theme.background)
684
- root.style.setProperty("--calendar-background", theme.background);
685
- if (theme.cellHover)
686
- root.style.setProperty("--calendar-cell-hover", theme.cellHover);
687
- if (theme.borderColor)
688
- root.style.setProperty("--calendar-border-color", theme.borderColor);
689
- if (theme.todayOutline)
690
- root.style.setProperty("--calendar-today-outline", theme.todayOutline);
691
- if (theme.selectedBg)
692
- root.style.setProperty("--calendar-selected-bg", theme.selectedBg);
693
- if (theme.eventIndicator)
694
- root.style.setProperty(
695
- "--calendar-event-indicator",
696
- theme.eventIndicator
697
- );
698
- }
699
- }, [theme]);
700
- const viewModel = engine.getViewModel();
701
- const actions = engine.getActions();
702
- const { selectedDate, tasks } = viewModel;
703
- const handleDateClick = (event) => {
704
- const td = event.target.closest("td");
705
- if (!td) return;
706
- const tr = td.parentElement;
707
- if (!tr) return;
708
- const weekIndex = tr.rowIndex - 1;
709
- const dayIndex = Array.from(tr.children).indexOf(td);
710
- const calendarDate = viewModel.calendarDates[weekIndex]?.[dayIndex];
711
- if (!calendarDate) return;
712
- engine.handleDateClick(calendarDate.date, dayIndex);
713
- onDateSelect?.(calendarDate.date);
714
- };
715
- const handleMonthChange = useCallback(() => {
716
- onMonthChange?.(viewModel.currentYear, viewModel.currentMonth);
717
- }, [onMonthChange, viewModel.currentYear, viewModel.currentMonth]);
718
- const handleNext = () => {
719
- actions.next();
720
- handleMonthChange();
721
- };
722
- const handlePrevious = () => {
723
- actions.previous();
724
- handleMonthChange();
725
- };
726
- const handleGoToToday = () => {
727
- actions.goToToday();
728
- setPickerOpen(false);
729
- const today2 = /* @__PURE__ */ new Date();
730
- onMonthChange?.(today2.getFullYear(), today2.getMonth());
731
- };
732
- const togglePicker = () => {
733
- const newOpen = !pickerOpen;
734
- setPickerOpen(newOpen);
735
- if (newOpen) {
736
- setYearInput(String(viewModel.currentYear));
737
- setYearInputValid(true);
738
- }
739
- };
740
- const today = /* @__PURE__ */ new Date();
741
- const computedMinYear = minYear ?? today.getFullYear() - 30;
742
- const computedMaxYear = maxYear ?? today.getFullYear() + 10;
743
- const handleYearInputChange = (e) => {
744
- const value = e.target.value;
745
- if (value === "" || /^\d+$/.test(value)) {
746
- setYearInput(value);
747
- const year = parseInt(value, 10);
748
- setYearInputValid(
749
- value === "" || year >= computedMinYear && year <= computedMaxYear
750
- );
751
- }
752
- };
753
- const handleYearInputBlur = () => {
754
- const year = parseInt(yearInput, 10);
755
- if (isNaN(year) || year < computedMinYear || year > computedMaxYear) {
756
- setYearInput(String(viewModel.currentYear));
757
- setYearInputValid(true);
758
- } else {
759
- actions.jump(year, viewModel.currentMonth);
760
- handleMonthChange();
761
- }
762
- };
763
- const handleYearPrev = () => {
764
- const newYear = viewModel.currentYear - 1;
765
- if (newYear >= computedMinYear) {
766
- actions.jump(newYear, viewModel.currentMonth);
767
- setYearInput(String(newYear));
768
- handleMonthChange();
769
- }
770
- };
771
- const handleYearNext = () => {
772
- const newYear = viewModel.currentYear + 1;
773
- if (newYear <= computedMaxYear) {
774
- actions.jump(newYear, viewModel.currentMonth);
775
- setYearInput(String(newYear));
776
- handleMonthChange();
777
- }
778
- };
779
- const handleMonthSelect = (month) => {
780
- actions.jump(viewModel.currentYear, month);
781
- setPickerOpen(false);
782
- handleMonthChange();
783
- };
784
- useEffect(() => {
785
- const handleClickOutside = (event) => {
786
- if (pickerRef.current && !pickerRef.current.contains(event.target)) {
787
- setPickerOpen(false);
228
+ // src/core/calendar-engine.ts
229
+ var CalendarEngine;
230
+ var init_calendar_engine = __esm({
231
+ "src/core/calendar-engine.ts"() {
232
+ "use strict";
233
+ init_utils();
234
+ CalendarEngine = class {
235
+ constructor(config) {
236
+ this.listeners = /* @__PURE__ */ new Set();
237
+ this.config = config;
238
+ this.categoryColors = mergeCategoryColors(config.categoryColors);
239
+ const initialDate = config.initialDate || /* @__PURE__ */ new Date();
240
+ this.state = {
241
+ currentYear: initialDate.getFullYear(),
242
+ currentMonth: initialDate.getMonth(),
243
+ currentDate: initialDate.getDate(),
244
+ selectedDate: null,
245
+ selectedDayIndex: null,
246
+ tasks: []
247
+ };
788
248
  }
789
- };
790
- if (pickerOpen) {
791
- document.addEventListener("mousedown", handleClickOutside);
792
- }
793
- return () => document.removeEventListener("mousedown", handleClickOutside);
794
- }, [pickerOpen]);
795
- return /* @__PURE__ */ jsxs2("div", { className: `kalendly-calendar ${className}`, style, children: [
796
- title && /* @__PURE__ */ jsx2("div", { className: "page--title", children: /* @__PURE__ */ jsx2("h1", { children: title }) }),
797
- /* @__PURE__ */ jsx2("div", { className: "calendar--content", children: /* @__PURE__ */ jsxs2("div", { className: "calendar--card", children: [
798
- /* @__PURE__ */ jsxs2("div", { className: "calendar--nav-header", children: [
799
- /* @__PURE__ */ jsx2(
800
- "button",
801
- {
802
- type: "button",
803
- className: "calendar--nav-arrow",
804
- onClick: handlePrevious,
805
- "aria-label": "Previous month",
806
- children: "\u2039"
807
- }
808
- ),
809
- /* @__PURE__ */ jsxs2("div", { className: "calendar--picker-container", ref: pickerRef, children: [
810
- /* @__PURE__ */ jsxs2(
811
- "button",
812
- {
813
- type: "button",
814
- className: "calendar--picker-btn",
815
- onClick: togglePicker,
816
- ...{ "aria-expanded": pickerOpen },
817
- "aria-haspopup": "true",
818
- children: [
819
- useShortMonthNames ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText,
820
- /* @__PURE__ */ jsx2("span", { className: "calendar--picker-chevron", children: "\u25BE" })
821
- ]
822
- }
823
- ),
824
- pickerOpen && /* @__PURE__ */ jsxs2("div", { className: "calendar--picker-dropdown", children: [
825
- /* @__PURE__ */ jsxs2("div", { className: "calendar--picker-year-row", children: [
826
- /* @__PURE__ */ jsx2(
827
- "button",
828
- {
829
- type: "button",
830
- className: "calendar--picker-year-arrow",
831
- onClick: handleYearPrev,
832
- disabled: viewModel.currentYear <= computedMinYear,
833
- "aria-label": "Previous year",
834
- children: "\u2039"
835
- }
836
- ),
837
- /* @__PURE__ */ jsx2(
838
- "input",
839
- {
840
- type: "text",
841
- className: `calendar--picker-year-input${!yearInputValid ? " invalid" : ""}`,
842
- value: yearInput,
843
- onChange: handleYearInputChange,
844
- onBlur: handleYearInputBlur,
845
- onKeyDown: (e) => {
846
- if (e.key === "Enter") handleYearInputBlur();
847
- },
848
- "aria-label": "Year"
849
- }
850
- ),
851
- /* @__PURE__ */ jsx2(
852
- "button",
853
- {
854
- type: "button",
855
- className: "calendar--picker-year-arrow",
856
- onClick: handleYearNext,
857
- disabled: viewModel.currentYear >= computedMaxYear,
858
- "aria-label": "Next year",
859
- children: "\u203A"
860
- }
861
- )
862
- ] }),
863
- /* @__PURE__ */ jsx2("div", { className: "calendar--picker-months", children: (useShortMonthNames ? MONTHS : MONTHS_FULL).map(
864
- (month, index) => {
865
- const isSelected = index === viewModel.currentMonth;
866
- const isCurrentMonth = index === today.getMonth() && viewModel.currentYear === today.getFullYear();
867
- return /* @__PURE__ */ jsx2(
868
- "button",
869
- {
870
- type: "button",
871
- className: `calendar--picker-month${isSelected ? " selected" : ""}${isCurrentMonth ? " current-month" : ""}`,
872
- onClick: () => handleMonthSelect(index),
873
- children: month
874
- },
875
- month
876
- );
877
- }
878
- ) })
879
- ] })
880
- ] }),
881
- /* @__PURE__ */ jsx2(
882
- "button",
883
- {
884
- type: "button",
885
- className: "calendar--today-btn",
886
- onClick: handleGoToToday,
887
- disabled: actions.isCurrentMonth(),
888
- children: "Today"
889
- }
890
- ),
891
- /* @__PURE__ */ jsx2(
892
- "button",
893
- {
894
- type: "button",
895
- className: "calendar--nav-arrow",
896
- onClick: handleNext,
897
- "aria-label": "Next month",
898
- children: "\u203A"
899
- }
900
- )
901
- ] }),
902
- /* @__PURE__ */ jsxs2("table", { className: "calendar--table calendar--table--bordered", children: [
903
- /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsx2("tr", { children: viewModel.days.map((day) => /* @__PURE__ */ jsx2("th", { children: day.slice(0, 3) }, day)) }) }),
904
- /* @__PURE__ */ jsx2("tbody", { onClick: handleDateClick, children: viewModel.calendarDates.map((week, weekIndex) => /* @__PURE__ */ jsx2("tr", { children: week.map((calendarDate, dayIndex) => {
905
- const cellClasses = getCellClasses(calendarDate);
906
- return /* @__PURE__ */ jsx2(
907
- "td",
908
- {
909
- className: cellClasses.join(" "),
910
- children: calendarDate.date.getDate()
911
- },
912
- `${weekIndex}-${dayIndex}`
913
- );
914
- }) }, weekIndex)) })
915
- ] }),
916
- /* @__PURE__ */ jsx2(
917
- DatePopup,
918
- {
919
- isVisible: !!selectedDate,
920
- selectedDate,
921
- events: tasks,
922
- scheduleDay: viewModel.scheduleDay,
923
- popupPositionClass: viewModel.popupPositionClass,
924
- onClose: () => engine.clearSelection(),
925
- onEventClick,
926
- renderEvent,
927
- renderNoEvents
928
- }
929
- )
930
- ] }) })
931
- ] });
932
- };
933
-
934
- // src/react-native/index.tsx
935
- var react_native_exports = {};
936
- __export(react_native_exports, {
937
- Calendar: () => Calendar2,
938
- CalendarEngine: () => CalendarEngine,
939
- DAYS: () => DAYS,
940
- DEFAULT_CATEGORY_COLORS: () => DEFAULT_CATEGORY_COLORS,
941
- DatePopup: () => DatePopup2,
942
- MONTHS: () => MONTHS,
943
- MONTHS_FULL: () => MONTHS_FULL,
944
- calendarStyles: () => calendarStyles,
945
- formatAttendees: () => formatAttendees,
946
- formatDateForDisplay: () => formatDateForDisplay,
947
- formatTimeRange: () => formatTimeRange,
948
- generateCalendarDates: () => generateCalendarDates,
949
- generateYears: () => generateYears,
950
- getCategoryColor: () => getCategoryColor,
951
- getCellClasses: () => getCellClasses,
952
- getDefaultEventColor: () => getDefaultEventColor,
953
- getEventsForDate: () => getEventsForDate,
954
- getMonthYearText: () => getMonthYearText,
955
- getPopupPositionClass: () => getPopupPositionClass,
956
- hasEvents: () => hasEvents,
957
- isSameDay: () => isSameDay,
958
- isToday: () => isToday,
959
- isValidHexColor: () => isValidHexColor,
960
- mergeCategoryColors: () => mergeCategoryColors,
961
- normalizeDate: () => normalizeDate,
962
- sortEventsByTime: () => sortEventsByTime
963
- });
964
-
965
- // src/react-native/components/Calendar.tsx
966
- import { useEffect as useEffect2, useState as useState2, useCallback as useCallback2, useMemo as useMemo2 } from "react";
967
- import {
968
- View as View2,
969
- Text as Text2,
970
- TouchableOpacity as TouchableOpacity2,
971
- ScrollView as ScrollView2,
972
- TextInput,
973
- Modal as Modal2,
974
- Pressable,
975
- Dimensions,
976
- StyleSheet as StyleSheet2
977
- } from "react-native";
978
-
979
- // src/react-native/components/DatePopup.tsx
980
- import {
981
- Modal,
982
- View,
983
- Text,
984
- TouchableOpacity,
985
- ScrollView,
986
- Linking,
987
- useWindowDimensions
988
- } from "react-native";
989
-
990
- // src/styles/react-native-styles.ts
991
- import { StyleSheet } from "react-native";
992
- var defaultColors = {
993
- primary: "#fc8917",
994
- secondary: "#fca045",
995
- tertiary: "#fdb873",
996
- text: "#2c3e50",
997
- border: "#dee2e6",
998
- todayOutline: "#f7db04",
999
- eventIndicator: "#1890ff",
1000
- background: "#fff",
1001
- white: "#ffffff",
1002
- scrollHint: "#fff3e0",
1003
- scrollHintText: "#f59e0b"
1004
- };
1005
- var getResponsiveStyles = (width, height, customColors) => {
1006
- const colors = { ...defaultColors, ...customColors };
1007
- const isTablet = width >= 768;
1008
- const isSmallPhone = width < 375;
1009
- const isPhone = width < 768;
1010
- const cellSize = (width - 60) / 7;
1011
- const popupWidth = isTablet ? 500 : isPhone ? width * 0.95 : width * 0.9;
1012
- const popupMaxWidth = isTablet ? 500 : isPhone ? width : 400;
1013
- const popupMaxHeight = isTablet ? height * 0.8 : height * 0.85;
1014
- const headerFontSize = isTablet ? 18 : isSmallPhone ? 15 : 16;
1015
- const eventTitleSize = isTablet ? 16 : isSmallPhone ? 14 : 15;
1016
- const eventDetailSize = isTablet ? 13 : isSmallPhone ? 11 : 12;
1017
- const badgeFontSize = isTablet ? 10 : isSmallPhone ? 9 : 9;
1018
- const eventCardPadding = isTablet ? 14 : isSmallPhone ? 10 : 12;
1019
- const containerPadding = isTablet ? 15 : isSmallPhone ? 10 : 12;
1020
- return StyleSheet.create({
1021
- container: {
1022
- flex: 1,
1023
- backgroundColor: colors.background
1024
- },
1025
- titleContainer: {
1026
- paddingVertical: 20,
1027
- alignItems: "center"
1028
- },
1029
- title: {
1030
- fontSize: 24,
1031
- fontWeight: "600",
1032
- color: colors.text
1033
- },
1034
- contentContainer: {
1035
- marginHorizontal: 20,
1036
- marginTop: 20
1037
- },
1038
- card: {
1039
- backgroundColor: colors.background,
1040
- borderRadius: 8,
1041
- shadowColor: "#000",
1042
- shadowOffset: {
1043
- width: 0,
1044
- height: 2
1045
- },
1046
- shadowOpacity: 0.1,
1047
- shadowRadius: 3.84,
1048
- elevation: 5,
1049
- overflow: "hidden"
1050
- },
1051
- cardHeader: {
1052
- backgroundColor: colors.tertiary,
1053
- paddingVertical: 15,
1054
- paddingHorizontal: 20,
1055
- borderBottomWidth: 1,
1056
- borderBottomColor: colors.border
1057
- },
1058
- cardHeaderText: {
1059
- fontSize: 18,
1060
- fontWeight: "500",
1061
- color: colors.text,
1062
- textAlign: "center"
1063
- },
1064
- table: {
1065
- backgroundColor: colors.background
1066
- },
1067
- tableHeader: {
1068
- flexDirection: "row",
1069
- backgroundColor: "rgba(252, 137, 23, 0.1)",
1070
- borderBottomWidth: 2,
1071
- borderBottomColor: colors.border
1072
- },
1073
- tableHeaderCell: {
1074
- flex: 1,
1075
- paddingVertical: 12,
1076
- borderRightWidth: 1,
1077
- borderRightColor: colors.border,
1078
- alignItems: "center",
1079
- justifyContent: "center"
1080
- },
1081
- tableHeaderText: {
1082
- fontSize: 14,
1083
- fontWeight: "600",
1084
- color: colors.text
1085
- },
1086
- tableRow: {
1087
- flexDirection: "row",
1088
- borderBottomWidth: 1,
1089
- borderBottomColor: colors.border
1090
- },
1091
- tableCell: {
1092
- flex: 1,
1093
- height: cellSize,
1094
- borderRightWidth: 1,
1095
- borderRightColor: colors.border,
1096
- alignItems: "center",
1097
- justifyContent: "center",
1098
- position: "relative"
1099
- },
1100
- tableCellText: {
1101
- fontSize: 16,
1102
- color: colors.text
1103
- },
1104
- cellToday: {
1105
- borderWidth: 2,
1106
- borderColor: colors.todayOutline
1107
- },
1108
- cellTodayText: {
1109
- fontWeight: "bold"
1110
- },
1111
- cellWithEvents: {
1112
- backgroundColor: "rgba(252, 160, 69, 0.3)"
1113
- },
1114
- eventIndicator: {
1115
- position: "absolute",
1116
- bottom: 2,
1117
- width: 4,
1118
- height: 4,
1119
- backgroundColor: colors.eventIndicator,
1120
- borderRadius: 2
1121
- },
1122
- navigationContainer: {
1123
- flexDirection: "row",
1124
- justifyContent: "space-between",
1125
- paddingHorizontal: 20,
1126
- paddingVertical: 20,
1127
- gap: 15
1128
- },
1129
- navigationButton: {
1130
- flex: 1,
1131
- backgroundColor: "transparent",
1132
- borderWidth: 1,
1133
- borderColor: colors.primary,
1134
- borderRadius: 6,
1135
- paddingVertical: 12,
1136
- paddingHorizontal: 16,
1137
- alignItems: "center",
1138
- justifyContent: "center"
1139
- },
1140
- navigationButtonText: {
1141
- color: colors.primary,
1142
- fontSize: 16,
1143
- fontWeight: "500"
1144
- },
1145
- navigationButtonPressed: {
1146
- backgroundColor: colors.primary
1147
- },
1148
- navigationButtonTextPressed: {
1149
- color: colors.white
1150
- },
1151
- jumpForm: {
1152
- flexDirection: "row",
1153
- alignItems: "center",
1154
- justifyContent: "center",
1155
- paddingHorizontal: 20,
1156
- paddingBottom: 20,
1157
- gap: 10
1158
- },
1159
- jumpLabel: {
1160
- fontSize: 18,
1161
- fontWeight: "300",
1162
- color: colors.text
1163
- },
1164
- jumpSelect: {
1165
- borderWidth: 1,
1166
- borderColor: colors.border,
1167
- borderRadius: 6,
1168
- paddingHorizontal: 12,
1169
- paddingVertical: 8,
1170
- backgroundColor: colors.background,
1171
- minWidth: 80
1172
- },
1173
- jumpSelectText: {
1174
- fontSize: 16,
1175
- color: colors.text,
1176
- textAlign: "center"
1177
- },
1178
- // RESPONSIVE POPUP STYLES
1179
- popupOverlay: {
1180
- position: "absolute",
1181
- top: 0,
1182
- left: 0,
1183
- right: 0,
1184
- bottom: 0,
1185
- backgroundColor: "rgba(0, 0, 0, 0.5)",
1186
- justifyContent: "center",
1187
- alignItems: "center",
1188
- zIndex: 1e3
1189
- },
1190
- popup: {
1191
- backgroundColor: colors.white,
1192
- borderRadius: 8,
1193
- width: popupWidth,
1194
- maxWidth: popupMaxWidth,
1195
- maxHeight: popupMaxHeight,
1196
- shadowColor: "#000",
1197
- shadowOffset: {
1198
- width: 0,
1199
- height: 4
1200
- },
1201
- shadowOpacity: 0.3,
1202
- shadowRadius: 6,
1203
- elevation: 8,
1204
- overflow: "hidden"
1205
- },
1206
- popupHeader: {
1207
- backgroundColor: colors.primary,
1208
- paddingVertical: isTablet ? 15 : 12,
1209
- paddingHorizontal: isTablet ? 20 : 15,
1210
- flexDirection: "row",
1211
- justifyContent: "space-between",
1212
- alignItems: "center"
1213
- },
1214
- popupHeaderText: {
1215
- fontSize: headerFontSize,
1216
- fontWeight: "600",
1217
- color: colors.white,
1218
- flex: 1
1219
- },
1220
- popupCloseButton: {
1221
- width: isTablet ? 32 : isSmallPhone ? 26 : 28,
1222
- height: isTablet ? 32 : isSmallPhone ? 26 : 28,
1223
- backgroundColor: "rgba(255, 255, 255, 0.2)",
1224
- borderRadius: 16,
1225
- alignItems: "center",
1226
- justifyContent: "center"
1227
- },
1228
- popupCloseText: {
1229
- color: colors.white,
1230
- fontSize: isTablet ? 20 : isSmallPhone ? 16 : 18,
1231
- fontWeight: "bold"
1232
- },
1233
- scrollHint: {
1234
- backgroundColor: colors.scrollHint,
1235
- paddingVertical: isTablet ? 10 : 8,
1236
- paddingHorizontal: containerPadding,
1237
- alignItems: "center"
1238
- },
1239
- scrollHintText: {
1240
- color: colors.scrollHintText,
1241
- fontSize: isTablet ? 13 : isSmallPhone ? 11 : 12,
1242
- fontWeight: "500"
1243
- },
1244
- eventsContainer: {
1245
- maxHeight: popupMaxHeight - 150
1246
- },
1247
- eventsList: {
1248
- padding: containerPadding,
1249
- gap: isTablet ? 12 : 10
1250
- },
1251
- eventCard: {
1252
- backgroundColor: colors.white,
1253
- borderRadius: 6,
1254
- padding: eventCardPadding,
1255
- borderLeftWidth: 4,
1256
- borderLeftColor: colors.primary,
1257
- borderWidth: 1,
1258
- borderColor: "#e5e7eb",
1259
- shadowColor: "#000",
1260
- shadowOffset: {
1261
- width: 0,
1262
- height: 1
1263
- },
1264
- shadowOpacity: 0.05,
1265
- shadowRadius: 2,
1266
- elevation: 1
1267
- },
1268
- eventHeader: {
1269
- marginBottom: 10
1270
- },
1271
- eventTitle: {
1272
- fontSize: eventTitleSize,
1273
- color: "#1f2937",
1274
- fontWeight: "600",
1275
- marginBottom: 8
1276
- },
1277
- eventBadges: {
1278
- flexDirection: "row",
1279
- gap: 6,
1280
- flexWrap: "wrap"
1281
- },
1282
- badge: {
1283
- paddingHorizontal: isTablet ? 8 : 6,
1284
- paddingVertical: isTablet ? 3 : 2,
1285
- borderRadius: 10
1286
- },
1287
- badgeText: {
1288
- fontSize: badgeFontSize,
1289
- fontWeight: "600",
1290
- textTransform: "uppercase",
1291
- letterSpacing: 0.3
1292
- },
1293
- priorityBadgeHigh: {
1294
- backgroundColor: "#fee2e2"
1295
- },
1296
- priorityBadgeMedium: {
1297
- backgroundColor: "#fef3c7"
1298
- },
1299
- priorityBadgeLow: {
1300
- backgroundColor: "#dcfce7"
1301
- },
1302
- statusBadgeCompleted: {
1303
- backgroundColor: "#d1fae5"
1304
- },
1305
- statusBadgeCancelled: {
1306
- backgroundColor: "#fee2e2"
1307
- },
1308
- statusBadgeTentative: {
1309
- backgroundColor: "#e0e7ff"
1310
- },
1311
- categoryBadgeWork: {
1312
- backgroundColor: "#dbeafe"
1313
- },
1314
- categoryBadgePersonal: {
1315
- backgroundColor: "#fef3c7"
1316
- },
1317
- categoryBadgeMeeting: {
1318
- backgroundColor: "#d1fae5"
1319
- },
1320
- categoryBadgeDeadline: {
1321
- backgroundColor: "#fee2e2"
1322
- },
1323
- categoryBadgeAppointment: {
1324
- backgroundColor: "#fef3c7"
1325
- },
1326
- categoryBadgeOther: {
1327
- backgroundColor: "#f3f4f6"
1328
- },
1329
- eventTime: {
1330
- flexDirection: isSmallPhone ? "column" : "row",
1331
- gap: isSmallPhone ? 2 : 8,
1332
- marginBottom: 6
1333
- },
1334
- eventTimeLabel: {
1335
- fontSize: eventDetailSize,
1336
- fontWeight: "600",
1337
- color: "#6b7280",
1338
- minWidth: isSmallPhone ? void 0 : isTablet ? 80 : 70,
1339
- textAlign: "left"
1340
- },
1341
- eventTimeValue: {
1342
- fontSize: eventDetailSize,
1343
- color: "#374151",
1344
- flex: 1
1345
- },
1346
- eventDescription: {
1347
- color: "#4b5563",
1348
- fontSize: eventDetailSize,
1349
- lineHeight: isTablet ? 20 : 18,
1350
- marginTop: 10,
1351
- marginBottom: 10,
1352
- paddingVertical: 8,
1353
- paddingHorizontal: 12,
1354
- backgroundColor: "#f9fafb",
1355
- borderLeftWidth: 2,
1356
- borderLeftColor: "#e5e7eb",
1357
- borderRadius: 4,
1358
- textAlign: "left"
1359
- },
1360
- eventUrlContainer: {
1361
- marginTop: 8
1362
- },
1363
- eventLink: {
1364
- color: "#2563eb",
1365
- fontSize: eventDetailSize,
1366
- fontWeight: "500"
1367
- },
1368
- eventTags: {
1369
- flexDirection: "row",
1370
- flexWrap: "wrap",
1371
- gap: 6,
1372
- marginTop: 8
1373
- },
1374
- eventTag: {
1375
- paddingHorizontal: isTablet ? 10 : 8,
1376
- paddingVertical: isTablet ? 3 : 2,
1377
- backgroundColor: "#e0f2fe",
1378
- borderRadius: 12
1379
- },
1380
- eventTagText: {
1381
- fontSize: isTablet ? 11 : 10,
1382
- color: "#0369a1",
1383
- fontWeight: "500"
1384
- },
1385
- noEventsMessage: {
1386
- padding: containerPadding,
1387
- paddingVertical: isTablet ? 30 : 20,
1388
- alignItems: "center"
1389
- },
1390
- noEventsText: {
1391
- fontSize: isTablet ? 14 : 13,
1392
- color: "#6b7280",
1393
- textAlign: "center"
1394
- }
1395
- });
1396
- };
1397
- var calendarStyles = getResponsiveStyles(375, 667);
1398
-
1399
- // src/react-native/components/DatePopup.tsx
1400
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1401
- var PriorityBadge2 = ({
1402
- priority,
1403
- styles
1404
- }) => {
1405
- if (!priority) return null;
1406
- const priorityConfig = {
1407
- high: { label: "HIGH", style: styles.priorityBadgeHigh },
1408
- medium: { label: "MEDIUM", style: styles.priorityBadgeMedium },
1409
- low: { label: "LOW", style: styles.priorityBadgeLow }
1410
- };
1411
- const config = priorityConfig[priority];
1412
- if (!config) return null;
1413
- return /* @__PURE__ */ jsx3(View, { style: [styles.badge, config.style], children: /* @__PURE__ */ jsx3(Text, { style: styles.badgeText, children: config.label }) });
1414
- };
1415
- var StatusBadge2 = ({
1416
- status,
1417
- styles
1418
- }) => {
1419
- if (!status || status === "scheduled") return null;
1420
- const statusConfig = {
1421
- completed: { label: "COMPLETED", style: styles.statusBadgeCompleted },
1422
- cancelled: { label: "CANCELLED", style: styles.statusBadgeCancelled },
1423
- tentative: { label: "TENTATIVE", style: styles.statusBadgeTentative }
1424
- };
1425
- const config = statusConfig[status];
1426
- if (!config) return null;
1427
- return /* @__PURE__ */ jsx3(View, { style: [styles.badge, config.style], children: /* @__PURE__ */ jsx3(Text, { style: styles.badgeText, children: config.label }) });
1428
- };
1429
- var CategoryBadge2 = ({
1430
- category,
1431
- styles
1432
- }) => {
1433
- if (!category) return null;
1434
- const categoryConfig = {
1435
- work: { label: "WORK", style: styles.categoryBadgeWork },
1436
- personal: { label: "PERSONAL", style: styles.categoryBadgePersonal },
1437
- meeting: { label: "MEETING", style: styles.categoryBadgeMeeting },
1438
- deadline: { label: "DEADLINE", style: styles.categoryBadgeDeadline },
1439
- appointment: {
1440
- label: "APPOINTMENT",
1441
- style: styles.categoryBadgeAppointment
1442
- },
1443
- other: { label: "OTHER", style: styles.categoryBadgeOther }
1444
- };
1445
- const config = categoryConfig[category];
1446
- if (!config) return null;
1447
- return /* @__PURE__ */ jsx3(View, { style: [styles.badge, config.style], children: /* @__PURE__ */ jsx3(Text, { style: styles.badgeText, children: config.label }) });
1448
- };
1449
- var DatePopup2 = ({
1450
- visible,
1451
- selectedDate,
1452
- events,
1453
- scheduleDay,
1454
- onClose,
1455
- onEventClick,
1456
- renderEvent,
1457
- renderNoEvents,
1458
- showCloseButton = true
1459
- }) => {
1460
- const { width, height } = useWindowDimensions();
1461
- const calendarStyles2 = getResponsiveStyles(width, height);
1462
- if (!selectedDate) return null;
1463
- const showScrollHint = events.length > 3;
1464
- const handleEventClick = (event) => {
1465
- onEventClick?.(event);
1466
- };
1467
- const defaultRenderEvent = (event) => {
1468
- const timeRange = formatTimeRange(event);
1469
- const attendeesList = formatAttendees(event.attendees);
1470
- const handleUrlPress = () => {
1471
- if (event.url) {
1472
- Linking.openURL(event.url).catch(
1473
- (err) => console.error("Failed to open URL:", err)
249
+ /**
250
+ * Subscribe to state changes
251
+ */
252
+ subscribe(listener) {
253
+ this.listeners.add(listener);
254
+ return () => this.listeners.delete(listener);
255
+ }
256
+ /**
257
+ * Notify all listeners of state changes
258
+ */
259
+ notify() {
260
+ this.listeners.forEach((listener) => listener());
261
+ }
262
+ /**
263
+ * Get current state
264
+ */
265
+ getState() {
266
+ return { ...this.state };
267
+ }
268
+ /**
269
+ * Get view model with computed properties
270
+ */
271
+ getViewModel() {
272
+ const calendarDates = generateCalendarDates(
273
+ this.state.currentYear,
274
+ this.state.currentMonth,
275
+ this.config.events,
276
+ this.config.weekStartsOn
1474
277
  );
278
+ return {
279
+ ...this.state,
280
+ months: MONTHS,
281
+ days: DAYS,
282
+ years: generateYears(this.config.minYear, this.config.maxYear),
283
+ monthAndYearText: getMonthYearText(
284
+ this.state.currentYear,
285
+ this.state.currentMonth
286
+ ),
287
+ scheduleDay: this.state.selectedDate ? formatDateForDisplay(this.state.selectedDate) : "",
288
+ calendarDates,
289
+ popupPositionClass: getPopupPositionClass(this.state.selectedDayIndex)
290
+ };
1475
291
  }
1476
- };
1477
- const eventContent = /* @__PURE__ */ jsxs3(Fragment, { children: [
1478
- /* @__PURE__ */ jsxs3(View, { style: calendarStyles2.eventHeader, children: [
1479
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTitle, children: event.name }),
1480
- /* @__PURE__ */ jsxs3(View, { style: calendarStyles2.eventBadges, children: [
1481
- /* @__PURE__ */ jsx3(CategoryBadge2, { category: event.category, styles: calendarStyles2 }),
1482
- /* @__PURE__ */ jsx3(PriorityBadge2, { priority: event.priority, styles: calendarStyles2 }),
1483
- /* @__PURE__ */ jsx3(StatusBadge2, { status: event.status, styles: calendarStyles2 })
1484
- ] })
1485
- ] }),
1486
- timeRange && /* @__PURE__ */ jsxs3(View, { style: calendarStyles2.eventTime, children: [
1487
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeLabel, children: "Time:" }),
1488
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeValue, children: timeRange })
1489
- ] }),
1490
- event.description && /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventDescription, children: event.description }),
1491
- event.location && /* @__PURE__ */ jsxs3(View, { style: calendarStyles2.eventTime, children: [
1492
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeLabel, children: "Location:" }),
1493
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeValue, children: event.location })
1494
- ] }),
1495
- attendeesList && /* @__PURE__ */ jsxs3(View, { style: calendarStyles2.eventTime, children: [
1496
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeLabel, children: "Attendees:" }),
1497
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeValue, children: attendeesList })
1498
- ] }),
1499
- event.organizer && /* @__PURE__ */ jsxs3(View, { style: calendarStyles2.eventTime, children: [
1500
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeLabel, children: "Organizer:" }),
1501
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeValue, children: event.organizer })
1502
- ] }),
1503
- event.notes && /* @__PURE__ */ jsxs3(View, { style: calendarStyles2.eventTime, children: [
1504
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeLabel, children: "Notes:" }),
1505
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTimeValue, children: event.notes })
1506
- ] }),
1507
- event.url && /* @__PURE__ */ jsx3(
1508
- TouchableOpacity,
1509
- {
1510
- onPress: handleUrlPress,
1511
- style: calendarStyles2.eventUrlContainer,
1512
- children: /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventLink, children: "View Details \u2192" })
292
+ /**
293
+ * Get actions object
294
+ */
295
+ getActions() {
296
+ return {
297
+ next: this.next.bind(this),
298
+ previous: this.previous.bind(this),
299
+ jump: this.jump.bind(this),
300
+ goToToday: this.goToToday.bind(this),
301
+ isCurrentMonth: this.isCurrentMonth.bind(this),
302
+ selectDate: this.selectDate.bind(this),
303
+ updateTasks: this.updateTasks.bind(this)
304
+ };
305
+ }
306
+ /**
307
+ * Navigate to next month
308
+ */
309
+ next() {
310
+ if (this.state.currentMonth === 11) {
311
+ this.state.currentMonth = 0;
312
+ this.state.currentYear++;
313
+ } else {
314
+ this.state.currentMonth++;
1513
315
  }
1514
- ),
1515
- event.tags && event.tags.length > 0 && /* @__PURE__ */ jsx3(View, { style: calendarStyles2.eventTags, children: event.tags.map((tag, idx) => /* @__PURE__ */ jsx3(View, { style: calendarStyles2.eventTag, children: /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.eventTagText, children: tag }) }, idx)) })
1516
- ] });
1517
- if (onEventClick) {
1518
- return /* @__PURE__ */ jsx3(
1519
- TouchableOpacity,
1520
- {
1521
- style: [
1522
- calendarStyles2.eventCard,
1523
- event.color && { borderLeftColor: event.color }
1524
- ],
1525
- onPress: () => handleEventClick(event),
1526
- activeOpacity: 0.7,
1527
- children: eventContent
1528
- },
1529
- event.id || event.name
1530
- );
1531
- }
1532
- return /* @__PURE__ */ jsx3(
1533
- View,
1534
- {
1535
- style: [
1536
- calendarStyles2.eventCard,
1537
- event.color && { borderLeftColor: event.color }
1538
- ],
1539
- children: eventContent
1540
- },
1541
- event.id || event.name
1542
- );
1543
- };
1544
- const defaultRenderNoEvents = () => /* @__PURE__ */ jsx3(View, { style: calendarStyles2.noEventsMessage, children: /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.noEventsText, children: "No events scheduled for this day." }) });
1545
- return /* @__PURE__ */ jsx3(
1546
- Modal,
1547
- {
1548
- visible,
1549
- transparent: true,
1550
- animationType: "fade",
1551
- onRequestClose: onClose,
1552
- children: /* @__PURE__ */ jsx3(
1553
- TouchableOpacity,
1554
- {
1555
- style: calendarStyles2.popupOverlay,
1556
- activeOpacity: 1,
1557
- onPress: onClose,
1558
- children: /* @__PURE__ */ jsxs3(
1559
- TouchableOpacity,
1560
- {
1561
- style: calendarStyles2.popup,
1562
- activeOpacity: 1,
1563
- onPress: (e) => e.stopPropagation(),
1564
- children: [
1565
- /* @__PURE__ */ jsxs3(View, { style: calendarStyles2.popupHeader, children: [
1566
- /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.popupHeaderText, children: scheduleDay }),
1567
- showCloseButton && /* @__PURE__ */ jsx3(
1568
- TouchableOpacity,
1569
- {
1570
- style: calendarStyles2.popupCloseButton,
1571
- onPress: onClose,
1572
- accessibilityLabel: "Close",
1573
- children: /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.popupCloseText, children: "\u2715" })
1574
- }
1575
- )
1576
- ] }),
1577
- showScrollHint && /* @__PURE__ */ jsx3(View, { style: calendarStyles2.scrollHint, children: /* @__PURE__ */ jsx3(Text, { style: calendarStyles2.scrollHintText, children: "\u2193 Scroll to see more events \u2193" }) }),
1578
- /* @__PURE__ */ jsx3(
1579
- ScrollView,
1580
- {
1581
- style: calendarStyles2.eventsContainer,
1582
- showsVerticalScrollIndicator: true,
1583
- children: events.length > 0 ? /* @__PURE__ */ jsx3(View, { style: calendarStyles2.eventsList, children: events.map(
1584
- (event) => renderEvent ? renderEvent(event) : defaultRenderEvent(event)
1585
- ) }) : renderNoEvents ? renderNoEvents() : defaultRenderNoEvents()
1586
- }
1587
- )
1588
- ]
1589
- }
1590
- )
316
+ this.state.selectedDate = null;
317
+ this.state.selectedDayIndex = null;
318
+ this.updateTasks();
319
+ this.notify();
320
+ }
321
+ /**
322
+ * Navigate to previous month
323
+ */
324
+ previous() {
325
+ if (this.state.currentMonth === 0) {
326
+ this.state.currentMonth = 11;
327
+ this.state.currentYear--;
328
+ } else {
329
+ this.state.currentMonth--;
1591
330
  }
1592
- )
1593
- }
1594
- );
1595
- };
1596
-
1597
- // src/react-native/components/Calendar.tsx
1598
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1599
- var Calendar2 = ({
1600
- events,
1601
- initialDate,
1602
- minYear,
1603
- maxYear,
1604
- weekStartsOn = 0,
1605
- useShortMonthNames = false,
1606
- onDateSelect,
1607
- onEventClick,
1608
- onMonthChange,
1609
- style,
1610
- containerStyle,
1611
- headerStyle,
1612
- headerTextStyle,
1613
- cellStyle,
1614
- cellTextStyle,
1615
- renderEvent,
1616
- renderNoEvents,
1617
- title,
1618
- showCloseButton = true,
1619
- theme
1620
- }) => {
1621
- const engine = useMemo2(
1622
- () => new CalendarEngine({
1623
- events,
1624
- initialDate,
1625
- minYear,
1626
- maxYear,
1627
- weekStartsOn
1628
- }),
1629
- [events, initialDate, minYear, maxYear, weekStartsOn]
1630
- );
1631
- const themeColors = useMemo2(() => {
1632
- if (!theme) return void 0;
1633
- return {
1634
- primary: theme.primary,
1635
- secondary: theme.secondary,
1636
- tertiary: theme.tertiary,
1637
- text: theme.textColor,
1638
- border: theme.borderColor,
1639
- todayOutline: theme.todayOutline,
1640
- eventIndicator: theme.eventIndicator,
1641
- background: theme.background
1642
- };
1643
- }, [theme]);
1644
- const calendarStyles2 = useMemo2(() => {
1645
- const { width, height } = Dimensions.get("window");
1646
- return getResponsiveStyles(width, height, themeColors);
1647
- }, [themeColors]);
1648
- const [, forceUpdate] = useState2({});
1649
- const rerender = useCallback2(() => forceUpdate({}), []);
1650
- const [pickerOpen, setPickerOpen] = useState2(false);
1651
- const [yearInput, setYearInput] = useState2("");
1652
- const [yearInputValid, setYearInputValid] = useState2(true);
1653
- const today = /* @__PURE__ */ new Date();
1654
- const todayMonth = today.getMonth();
1655
- const todayYear = today.getFullYear();
1656
- const computedMinYear = minYear ?? todayYear - 30;
1657
- const computedMaxYear = maxYear ?? todayYear + 10;
1658
- useEffect2(() => {
1659
- const unsubscribe = engine.subscribe(rerender);
1660
- return unsubscribe;
1661
- }, [engine, rerender]);
1662
- useEffect2(() => {
1663
- engine.updateEvents(events);
1664
- }, [engine, events]);
1665
- useEffect2(() => {
1666
- return () => {
1667
- engine.destroy();
1668
- };
1669
- }, [engine]);
1670
- const viewModel = engine.getViewModel();
1671
- const actions = engine.getActions();
1672
- const { selectedDate, tasks } = viewModel;
1673
- const isCurrentMonth = viewModel.currentYear === todayYear && viewModel.currentMonth === todayMonth;
1674
- const handleDatePress = (date, dayIndex) => {
1675
- engine.handleDateClick(date, dayIndex);
1676
- onDateSelect?.(date);
1677
- };
1678
- const handleNext = () => {
1679
- actions.next();
1680
- onMonthChange?.(viewModel.currentYear, viewModel.currentMonth);
1681
- };
1682
- const handlePrevious = () => {
1683
- actions.previous();
1684
- onMonthChange?.(viewModel.currentYear, viewModel.currentMonth);
1685
- };
1686
- const handleGoToToday = () => {
1687
- actions.goToToday();
1688
- setPickerOpen(false);
1689
- onMonthChange?.(todayYear, todayMonth);
1690
- };
1691
- const togglePicker = () => {
1692
- const newOpen = !pickerOpen;
1693
- setPickerOpen(newOpen);
1694
- if (newOpen) {
1695
- setYearInput(String(viewModel.currentYear));
1696
- setYearInputValid(true);
1697
- }
1698
- };
1699
- const handleYearInputChange = (value) => {
1700
- if (value === "" || /^\d+$/.test(value)) {
1701
- setYearInput(value);
1702
- const year = parseInt(value, 10);
1703
- setYearInputValid(
1704
- value === "" || year >= computedMinYear && year <= computedMaxYear
1705
- );
1706
- }
1707
- };
1708
- const handleYearInputSubmit = () => {
1709
- const year = parseInt(yearInput, 10);
1710
- if (isNaN(year) || year < computedMinYear || year > computedMaxYear) {
1711
- setYearInput(String(viewModel.currentYear));
1712
- setYearInputValid(true);
1713
- } else {
1714
- actions.jump(year, viewModel.currentMonth);
1715
- onMonthChange?.(year, viewModel.currentMonth);
1716
- }
1717
- };
1718
- const handleYearPrev = () => {
1719
- const newYear = viewModel.currentYear - 1;
1720
- if (newYear >= computedMinYear) {
1721
- actions.jump(newYear, viewModel.currentMonth);
1722
- setYearInput(String(newYear));
1723
- onMonthChange?.(newYear, viewModel.currentMonth);
1724
- }
1725
- };
1726
- const handleYearNext = () => {
1727
- const newYear = viewModel.currentYear + 1;
1728
- if (newYear <= computedMaxYear) {
1729
- actions.jump(newYear, viewModel.currentMonth);
1730
- setYearInput(String(newYear));
1731
- onMonthChange?.(newYear, viewModel.currentMonth);
1732
- }
1733
- };
1734
- const handleMonthSelect = (month) => {
1735
- actions.jump(viewModel.currentYear, month);
1736
- setPickerOpen(false);
1737
- onMonthChange?.(viewModel.currentYear, month);
1738
- };
1739
- const closePopup = () => {
1740
- engine.clearSelection();
1741
- };
1742
- return /* @__PURE__ */ jsxs4(ScrollView2, { style: [calendarStyles2.container, containerStyle], children: [
1743
- title && /* @__PURE__ */ jsx4(View2, { style: calendarStyles2.titleContainer, children: /* @__PURE__ */ jsx4(Text2, { style: calendarStyles2.title, children: title }) }),
1744
- /* @__PURE__ */ jsx4(View2, { style: [calendarStyles2.contentContainer, style], children: /* @__PURE__ */ jsxs4(View2, { style: calendarStyles2.card, children: [
1745
- /* @__PURE__ */ jsxs4(View2, { style: [pickerStyles.navHeader, headerStyle], children: [
1746
- /* @__PURE__ */ jsx4(
1747
- TouchableOpacity2,
1748
- {
1749
- style: pickerStyles.navArrow,
1750
- onPress: handlePrevious,
1751
- children: /* @__PURE__ */ jsx4(Text2, { style: pickerStyles.navArrowText, children: "\u2039" })
1752
- }
1753
- ),
1754
- /* @__PURE__ */ jsxs4(
1755
- TouchableOpacity2,
1756
- {
1757
- style: pickerStyles.pickerBtn,
1758
- onPress: togglePicker,
1759
- children: [
1760
- /* @__PURE__ */ jsx4(Text2, { style: [pickerStyles.pickerBtnText, headerTextStyle], children: useShortMonthNames ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText }),
1761
- /* @__PURE__ */ jsx4(Text2, { style: pickerStyles.chevron, children: "\u25BC" })
1762
- ]
1763
- }
1764
- ),
1765
- /* @__PURE__ */ jsx4(
1766
- TouchableOpacity2,
1767
- {
1768
- style: [
1769
- pickerStyles.todayBtn,
1770
- isCurrentMonth && pickerStyles.todayBtnDisabled
1771
- ],
1772
- onPress: handleGoToToday,
1773
- disabled: isCurrentMonth,
1774
- children: /* @__PURE__ */ jsx4(
1775
- Text2,
1776
- {
1777
- style: [
1778
- pickerStyles.todayBtnText,
1779
- isCurrentMonth && pickerStyles.todayBtnTextDisabled
1780
- ],
1781
- children: "Today"
1782
- }
1783
- )
1784
- }
1785
- ),
1786
- /* @__PURE__ */ jsx4(
1787
- TouchableOpacity2,
1788
- {
1789
- style: pickerStyles.navArrow,
1790
- onPress: handleNext,
1791
- children: /* @__PURE__ */ jsx4(Text2, { style: pickerStyles.navArrowText, children: "\u203A" })
1792
- }
1793
- )
1794
- ] }),
1795
- /* @__PURE__ */ jsx4(
1796
- Modal2,
1797
- {
1798
- visible: pickerOpen,
1799
- transparent: true,
1800
- animationType: "fade",
1801
- onRequestClose: () => setPickerOpen(false),
1802
- children: /* @__PURE__ */ jsx4(
1803
- Pressable,
1804
- {
1805
- style: pickerStyles.modalOverlay,
1806
- onPress: () => setPickerOpen(false),
1807
- children: /* @__PURE__ */ jsxs4(
1808
- Pressable,
1809
- {
1810
- style: pickerStyles.pickerDropdown,
1811
- onPress: (e) => e.stopPropagation(),
1812
- children: [
1813
- /* @__PURE__ */ jsxs4(View2, { style: pickerStyles.yearRow, children: [
1814
- /* @__PURE__ */ jsx4(
1815
- TouchableOpacity2,
1816
- {
1817
- style: pickerStyles.yearArrow,
1818
- onPress: handleYearPrev,
1819
- disabled: viewModel.currentYear <= computedMinYear,
1820
- children: /* @__PURE__ */ jsx4(
1821
- Text2,
1822
- {
1823
- style: [
1824
- pickerStyles.yearArrowText,
1825
- viewModel.currentYear <= computedMinYear && pickerStyles.yearArrowDisabled
1826
- ],
1827
- children: "\u2039"
1828
- }
1829
- )
1830
- }
1831
- ),
1832
- /* @__PURE__ */ jsx4(
1833
- TextInput,
1834
- {
1835
- style: [
1836
- pickerStyles.yearInput,
1837
- !yearInputValid && pickerStyles.yearInputInvalid
1838
- ],
1839
- value: yearInput,
1840
- onChangeText: handleYearInputChange,
1841
- onSubmitEditing: handleYearInputSubmit,
1842
- onBlur: handleYearInputSubmit,
1843
- keyboardType: "number-pad",
1844
- maxLength: 4
1845
- }
1846
- ),
1847
- /* @__PURE__ */ jsx4(
1848
- TouchableOpacity2,
1849
- {
1850
- style: pickerStyles.yearArrow,
1851
- onPress: handleYearNext,
1852
- disabled: viewModel.currentYear >= computedMaxYear,
1853
- children: /* @__PURE__ */ jsx4(
1854
- Text2,
1855
- {
1856
- style: [
1857
- pickerStyles.yearArrowText,
1858
- viewModel.currentYear >= computedMaxYear && pickerStyles.yearArrowDisabled
1859
- ],
1860
- children: "\u203A"
1861
- }
1862
- )
1863
- }
1864
- )
1865
- ] }),
1866
- /* @__PURE__ */ jsx4(View2, { style: pickerStyles.monthGrid, children: (useShortMonthNames ? MONTHS : MONTHS_FULL).map(
1867
- (month, index) => {
1868
- const isSelected = index === viewModel.currentMonth;
1869
- const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
1870
- return /* @__PURE__ */ jsx4(
1871
- TouchableOpacity2,
1872
- {
1873
- style: [
1874
- pickerStyles.monthBtn,
1875
- isSelected && pickerStyles.monthBtnSelected,
1876
- isCurrent && pickerStyles.monthBtnCurrent
1877
- ],
1878
- onPress: () => handleMonthSelect(index),
1879
- children: /* @__PURE__ */ jsx4(
1880
- Text2,
1881
- {
1882
- style: [
1883
- pickerStyles.monthBtnText,
1884
- isSelected && pickerStyles.monthBtnTextSelected
1885
- ],
1886
- children: month
1887
- }
1888
- )
1889
- },
1890
- month
1891
- );
1892
- }
1893
- ) })
1894
- ]
1895
- }
1896
- )
1897
- }
1898
- )
331
+ this.state.selectedDate = null;
332
+ this.state.selectedDayIndex = null;
333
+ this.updateTasks();
334
+ this.notify();
335
+ }
336
+ /**
337
+ * Jump to specific month and year
338
+ */
339
+ jump(year, month) {
340
+ this.state.currentYear = year;
341
+ this.state.currentMonth = month;
342
+ this.state.selectedDate = null;
343
+ this.state.selectedDayIndex = null;
344
+ this.updateTasks();
345
+ this.notify();
346
+ }
347
+ /**
348
+ * Navigate to current month (today)
349
+ */
350
+ goToToday() {
351
+ const today = /* @__PURE__ */ new Date();
352
+ this.state.currentYear = today.getFullYear();
353
+ this.state.currentMonth = today.getMonth();
354
+ this.state.selectedDate = null;
355
+ this.state.selectedDayIndex = null;
356
+ this.updateTasks();
357
+ this.notify();
358
+ }
359
+ /**
360
+ * Check if currently viewing today's month
361
+ */
362
+ isCurrentMonth() {
363
+ const today = /* @__PURE__ */ new Date();
364
+ return this.state.currentYear === today.getFullYear() && this.state.currentMonth === today.getMonth();
365
+ }
366
+ /**
367
+ * Select a specific date
368
+ */
369
+ selectDate(date, dayIndex) {
370
+ this.state.selectedDate = date;
371
+ this.state.selectedDayIndex = dayIndex ?? null;
372
+ this.state.currentDate = date.getDate();
373
+ this.state.currentMonth = date.getMonth();
374
+ this.state.currentYear = date.getFullYear();
375
+ this.updateTasks();
376
+ this.notify();
377
+ }
378
+ /**
379
+ * Update tasks for the currently selected date
380
+ */
381
+ updateTasks() {
382
+ if (!this.state.selectedDate) {
383
+ this.state.tasks = [];
384
+ return;
1899
385
  }
1900
- ),
1901
- /* @__PURE__ */ jsxs4(View2, { style: calendarStyles2.table, children: [
1902
- /* @__PURE__ */ jsx4(View2, { style: calendarStyles2.tableHeader, children: viewModel.days.map((day) => /* @__PURE__ */ jsx4(View2, { style: calendarStyles2.tableHeaderCell, children: /* @__PURE__ */ jsx4(Text2, { style: calendarStyles2.tableHeaderText, children: day.slice(0, 3) }) }, day)) }),
1903
- viewModel.calendarDates.map((week, weekIndex) => /* @__PURE__ */ jsx4(View2, { style: calendarStyles2.tableRow, children: week.map((calendarDate, dayIndex) => {
1904
- const cellClasses = getCellClasses(calendarDate);
1905
- const isToday2 = cellClasses.includes(
1906
- "schedule--current--exam"
1907
- );
1908
- const hasEvents2 = cellClasses.includes("has--event");
1909
- const isOtherMonth = cellClasses.includes("other-month");
1910
- return /* @__PURE__ */ jsxs4(
1911
- TouchableOpacity2,
1912
- {
1913
- style: [
1914
- calendarStyles2.tableCell,
1915
- cellStyle,
1916
- isToday2 && calendarStyles2.cellToday,
1917
- hasEvents2 && calendarStyles2.cellWithEvents,
1918
- isOtherMonth && pickerStyles.cellOtherMonth
1919
- ],
1920
- onPress: () => {
1921
- handleDatePress(calendarDate.date, dayIndex);
1922
- },
1923
- children: [
1924
- /* @__PURE__ */ jsx4(
1925
- Text2,
1926
- {
1927
- style: [
1928
- calendarStyles2.tableCellText,
1929
- cellTextStyle,
1930
- isToday2 && calendarStyles2.cellTodayText,
1931
- isOtherMonth && pickerStyles.cellOtherMonthText
1932
- ],
1933
- children: calendarDate.date.getDate()
1934
- }
1935
- ),
1936
- hasEvents2 && /* @__PURE__ */ jsx4(View2, { style: calendarStyles2.eventIndicator })
1937
- ]
1938
- },
1939
- `${weekIndex}-${dayIndex}`
1940
- );
1941
- }) }, weekIndex))
1942
- ] })
1943
- ] }) }),
1944
- /* @__PURE__ */ jsx4(
1945
- DatePopup2,
1946
- {
1947
- visible: !!selectedDate,
1948
- selectedDate,
1949
- events: tasks,
1950
- scheduleDay: viewModel.scheduleDay,
1951
- onClose: closePopup,
1952
- onEventClick,
1953
- renderEvent,
1954
- renderNoEvents,
1955
- showCloseButton
386
+ this.state.tasks = getEventsForDate(
387
+ this.config.events,
388
+ this.state.selectedDate
389
+ );
1956
390
  }
1957
- )
1958
- ] });
1959
- };
1960
- var pickerStyles = StyleSheet2.create({
1961
- navHeader: {
1962
- flexDirection: "row",
1963
- alignItems: "center",
1964
- justifyContent: "center",
1965
- paddingVertical: 12,
1966
- paddingHorizontal: 8,
1967
- backgroundColor: "#f8f9fa",
1968
- borderBottomWidth: 2,
1969
- borderBottomColor: "#dee2e6",
1970
- gap: 8
1971
- },
1972
- navArrow: {
1973
- width: 36,
1974
- height: 36,
1975
- borderRadius: 6,
1976
- borderWidth: 1,
1977
- borderColor: "#dee2e6",
1978
- backgroundColor: "#fff",
1979
- alignItems: "center",
1980
- justifyContent: "center"
1981
- },
1982
- navArrowText: {
1983
- fontSize: 20,
1984
- color: "#2c3e50"
1985
- },
1986
- pickerBtn: {
1987
- flexDirection: "row",
1988
- alignItems: "center",
1989
- paddingHorizontal: 16,
1990
- paddingVertical: 8,
1991
- borderRadius: 6,
1992
- borderWidth: 1,
1993
- borderColor: "#dee2e6",
1994
- backgroundColor: "#fff",
1995
- gap: 8
1996
- },
1997
- pickerBtnText: {
1998
- fontSize: 16,
1999
- fontWeight: "600",
2000
- color: "#2c3e50"
2001
- },
2002
- chevron: {
2003
- fontSize: 10,
2004
- color: "#2c3e50"
2005
- },
2006
- todayBtn: {
2007
- paddingHorizontal: 16,
2008
- paddingVertical: 8,
2009
- borderRadius: 6,
2010
- borderWidth: 1,
2011
- borderColor: "#dee2e6",
2012
- backgroundColor: "#fff"
2013
- },
2014
- todayBtnDisabled: {
2015
- opacity: 0.5
2016
- },
2017
- todayBtnText: {
2018
- fontSize: 14,
2019
- fontWeight: "500",
2020
- color: "#2c3e50"
2021
- },
2022
- todayBtnTextDisabled: {
2023
- color: "#9ca3af"
2024
- },
2025
- modalOverlay: {
2026
- flex: 1,
2027
- backgroundColor: "rgba(0, 0, 0, 0.3)",
2028
- justifyContent: "center",
2029
- alignItems: "center"
2030
- },
2031
- pickerDropdown: {
2032
- backgroundColor: "#fff",
2033
- borderRadius: 12,
2034
- padding: 16,
2035
- minWidth: 280,
2036
- shadowColor: "#000",
2037
- shadowOffset: { width: 0, height: 4 },
2038
- shadowOpacity: 0.15,
2039
- shadowRadius: 20,
2040
- elevation: 10
2041
- },
2042
- yearRow: {
2043
- flexDirection: "row",
2044
- alignItems: "center",
2045
- justifyContent: "center",
2046
- gap: 12,
2047
- marginBottom: 16,
2048
- paddingBottom: 12,
2049
- borderBottomWidth: 1,
2050
- borderBottomColor: "#dee2e6"
2051
- },
2052
- yearArrow: {
2053
- width: 32,
2054
- height: 32,
2055
- borderRadius: 6,
2056
- backgroundColor: "#f3f4f6",
2057
- alignItems: "center",
2058
- justifyContent: "center"
2059
- },
2060
- yearArrowText: {
2061
- fontSize: 18,
2062
- fontWeight: "600",
2063
- color: "#2c3e50"
2064
- },
2065
- yearArrowDisabled: {
2066
- opacity: 0.3
2067
- },
2068
- yearInput: {
2069
- width: 80,
2070
- paddingVertical: 6,
2071
- paddingHorizontal: 8,
2072
- borderRadius: 6,
2073
- borderWidth: 1,
2074
- borderColor: "#dee2e6",
2075
- textAlign: "center",
2076
- fontSize: 18,
2077
- fontWeight: "600",
2078
- color: "#2c3e50"
2079
- },
2080
- yearInputInvalid: {
2081
- borderColor: "#ef4444",
2082
- backgroundColor: "#fef2f2"
2083
- },
2084
- monthGrid: {
2085
- flexDirection: "row",
2086
- flexWrap: "wrap",
2087
- gap: 8
2088
- },
2089
- monthBtn: {
2090
- width: "30%",
2091
- paddingVertical: 10,
2092
- borderRadius: 6,
2093
- backgroundColor: "#f9fafb",
2094
- alignItems: "center"
2095
- },
2096
- monthBtnSelected: {
2097
- backgroundColor: "#fc8917"
2098
- },
2099
- monthBtnCurrent: {
2100
- borderWidth: 1,
2101
- borderColor: "#fc8917"
2102
- },
2103
- monthBtnText: {
2104
- fontSize: 14,
2105
- fontWeight: "500",
2106
- color: "#2c3e50"
2107
- },
2108
- monthBtnTextSelected: {
2109
- color: "#fff",
2110
- fontWeight: "600"
2111
- },
2112
- cellOtherMonth: {
2113
- backgroundColor: "#f9fafb"
2114
- },
2115
- cellOtherMonthText: {
2116
- color: "#9ca3af"
391
+ /**
392
+ * Update events configuration
393
+ */
394
+ updateEvents(events) {
395
+ this.config.events = events;
396
+ this.updateTasks();
397
+ this.notify();
398
+ }
399
+ /**
400
+ * Handle date cell click
401
+ */
402
+ handleDateClick(date, dayIndex) {
403
+ this.selectDate(date, dayIndex);
404
+ }
405
+ /**
406
+ * Check if date has events
407
+ */
408
+ hasEventsForDate(date) {
409
+ return getEventsForDate(this.config.events, date).length > 0;
410
+ }
411
+ /**
412
+ * Get events for a specific date
413
+ */
414
+ getEventsForDate(date) {
415
+ return getEventsForDate(this.config.events, date);
416
+ }
417
+ /**
418
+ * Clear selected date
419
+ */
420
+ clearSelection() {
421
+ this.state.selectedDate = null;
422
+ this.state.selectedDayIndex = null;
423
+ this.state.tasks = [];
424
+ this.notify();
425
+ }
426
+ /**
427
+ * Get category color (with custom colors support)
428
+ */
429
+ getCategoryColor(category) {
430
+ if (!category) return "#fc8917";
431
+ return getCategoryColor(category, this.categoryColors);
432
+ }
433
+ /**
434
+ * Update category colors dynamically
435
+ */
436
+ updateCategoryColors(colors) {
437
+ this.categoryColors = mergeCategoryColors(colors);
438
+ this.notify();
439
+ }
440
+ /**
441
+ * Get all category colors
442
+ */
443
+ getCategoryColors() {
444
+ return { ...this.categoryColors };
445
+ }
446
+ /**
447
+ * Register a new category with color
448
+ */
449
+ registerCategory(name, color) {
450
+ this.categoryColors[name] = color;
451
+ this.notify();
452
+ }
453
+ /**
454
+ * Destroy the engine and cleanup listeners
455
+ */
456
+ destroy() {
457
+ this.listeners.clear();
458
+ }
459
+ };
2117
460
  }
2118
461
  });
2119
462
 
2120
- // src/vanilla/index.ts
2121
- var vanilla_exports = {};
2122
- __export(vanilla_exports, {
2123
- CalendarEngine: () => CalendarEngine,
2124
- DAYS: () => DAYS,
2125
- DEFAULT_CATEGORY_COLORS: () => DEFAULT_CATEGORY_COLORS,
2126
- MONTHS: () => MONTHS,
2127
- MONTHS_FULL: () => MONTHS_FULL,
2128
- VanillaCalendar: () => VanillaCalendar,
2129
- createCalendar: () => createCalendar,
2130
- formatAttendees: () => formatAttendees,
2131
- formatDateForDisplay: () => formatDateForDisplay,
2132
- formatTimeRange: () => formatTimeRange,
2133
- generateCalendarDates: () => generateCalendarDates,
2134
- generateYears: () => generateYears,
2135
- getCategoryColor: () => getCategoryColor,
2136
- getCellClasses: () => getCellClasses,
2137
- getDefaultEventColor: () => getDefaultEventColor,
2138
- getEventsForDate: () => getEventsForDate,
2139
- getMonthYearText: () => getMonthYearText,
2140
- getPopupPositionClass: () => getPopupPositionClass,
2141
- hasEvents: () => hasEvents,
2142
- isSameDay: () => isSameDay,
2143
- isToday: () => isToday,
2144
- isValidHexColor: () => isValidHexColor,
2145
- mergeCategoryColors: () => mergeCategoryColors,
2146
- normalizeDate: () => normalizeDate,
2147
- sortEventsByTime: () => sortEventsByTime
463
+ // src/core/index.ts
464
+ var init_core = __esm({
465
+ "src/core/index.ts"() {
466
+ "use strict";
467
+ init_types();
468
+ init_utils();
469
+ init_calendar_engine();
470
+ init_utils();
471
+ }
2148
472
  });
2149
473
 
2150
- // src/vanilla/Calendar.ts
2151
- var VanillaCalendar = class {
2152
- constructor(props) {
2153
- this.unsubscribe = null;
2154
- this.pickerOpen = false;
2155
- this.yearInput = "";
2156
- this.yearInputValid = true;
2157
- this.clickOutsideHandler = null;
2158
- this.delegatedClickHandler = null;
2159
- this.delegatedInputHandler = null;
2160
- this.delegatedBlurHandler = null;
2161
- this.delegatedKeydownHandler = null;
2162
- this.listenersAttached = false;
2163
- this.props = props;
2164
- if (typeof props.container === "string") {
2165
- const element = document.querySelector(props.container);
2166
- if (!element) {
2167
- throw new Error(`Container element "${props.container}" not found`);
2168
- }
2169
- this.container = element;
2170
- } else {
2171
- this.container = props.container;
2172
- }
2173
- this.engine = new CalendarEngine({
2174
- events: props.events,
2175
- initialDate: props.initialDate,
2176
- minYear: props.minYear,
2177
- maxYear: props.maxYear,
2178
- weekStartsOn: props.weekStartsOn
2179
- });
2180
- this.actions = this.engine.getActions();
2181
- this.applyTheme();
2182
- this.init();
2183
- }
2184
- applyTheme() {
2185
- if (this.props.theme) {
2186
- const root = document.documentElement;
2187
- const theme = this.props.theme;
2188
- if (theme.primary)
2189
- root.style.setProperty("--calendar-primary-color", theme.primary);
2190
- if (theme.secondary)
2191
- root.style.setProperty("--calendar-secondary-color", theme.secondary);
2192
- if (theme.tertiary)
2193
- root.style.setProperty("--calendar-tertiary-color", theme.tertiary);
2194
- if (theme.textColor)
2195
- root.style.setProperty("--calendar-text-color", theme.textColor);
2196
- if (theme.textLight)
2197
- root.style.setProperty("--calendar-text-light", theme.textLight);
2198
- if (theme.background)
2199
- root.style.setProperty("--calendar-background", theme.background);
2200
- if (theme.cellHover)
2201
- root.style.setProperty("--calendar-cell-hover", theme.cellHover);
2202
- if (theme.borderColor)
2203
- root.style.setProperty("--calendar-border-color", theme.borderColor);
2204
- if (theme.todayOutline)
2205
- root.style.setProperty("--calendar-today-outline", theme.todayOutline);
2206
- if (theme.selectedBg)
2207
- root.style.setProperty("--calendar-selected-bg", theme.selectedBg);
2208
- if (theme.eventIndicator)
2209
- root.style.setProperty(
2210
- "--calendar-event-indicator",
2211
- theme.eventIndicator
2212
- );
2213
- }
2214
- }
2215
- init() {
2216
- this.container.classList.add("kalendly-calendar");
2217
- if (this.props.className) {
2218
- this.container.classList.add(this.props.className);
2219
- }
2220
- this.unsubscribe = this.engine.subscribe(() => {
2221
- this.render();
2222
- });
2223
- this.render();
474
+ // src/web-components/CalendarElement.ts
475
+ var CalendarElement_exports = {};
476
+ __export(CalendarElement_exports, {
477
+ CalendarElement: () => CalendarElement,
478
+ defineCalendarElement: () => defineCalendarElement
479
+ });
480
+ function defineCalendarElement(tagName = "kal-calendar") {
481
+ if (typeof customElements !== "undefined" && !customElements.get(tagName)) {
482
+ customElements.define(tagName, CalendarElement);
2224
483
  }
2225
- render() {
2226
- const viewModel = this.engine.getViewModel();
2227
- const defaultRenderEvent = (event) => {
2228
- const timeRange = formatTimeRange(event);
2229
- const attendeesList = formatAttendees(event.attendees);
2230
- let borderColor = event.color || "#3b82f6";
2231
- if (event.category) {
2232
- borderColor = this.engine.getCategoryColor(event.category);
484
+ }
485
+ var isSameDay2, CalendarElement;
486
+ var init_CalendarElement = __esm({
487
+ "src/web-components/CalendarElement.ts"() {
488
+ "use strict";
489
+ init_core();
490
+ isSameDay2 = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
491
+ CalendarElement = class extends HTMLElement {
492
+ constructor() {
493
+ super(...arguments);
494
+ this.engine = null;
495
+ this.unsubscribe = null;
496
+ this.actions = null;
497
+ this.pickerOpen = false;
498
+ this.yearInput = "";
499
+ this.yearInputValid = true;
500
+ this.listenersAttached = false;
501
+ this.clickOutsideHandler = null;
502
+ this.delegatedClickHandler = null;
503
+ this.delegatedInputHandler = null;
504
+ this.delegatedBlurHandler = null;
505
+ this.delegatedKeydownHandler = null;
506
+ // Rich property backing fields
507
+ this._events = [];
508
+ this._theme = null;
509
+ this._categoryColors = null;
510
+ this._renderEvent = null;
511
+ this._renderNoEvents = null;
512
+ // Selection state (availability mode — range)
513
+ this._rangeStart = null;
514
+ this._rangeEnd = null;
515
+ this._timeRangeDate = null;
516
+ this._timeRangeStart = null;
517
+ this._timeRangeEnd = null;
518
+ this._timeRangeComplete = false;
2233
519
  }
2234
- const getCategoryLabel = (category) => {
2235
- const labels = {
2236
- work: "WORK",
2237
- personal: "PERSONAL",
2238
- meeting: "MEETING",
2239
- deadline: "DEADLINE",
2240
- appointment: "APPOINTMENT",
2241
- other: "OTHER"
2242
- };
2243
- return category ? labels[category] || category.toUpperCase() : "";
2244
- };
2245
- const getPriorityLabel = (priority) => {
2246
- const labels = {
2247
- high: "HIGH",
2248
- medium: "MEDIUM",
2249
- low: "LOW"
2250
- };
2251
- return priority ? labels[priority] || priority.toUpperCase() : "";
2252
- };
2253
- const getStatusLabel = (status) => {
2254
- const labels = {
2255
- completed: "COMPLETED",
2256
- cancelled: "CANCELLED",
2257
- tentative: "TENTATIVE",
2258
- scheduled: "SCHEDULED"
2259
- };
2260
- return status ? labels[status] || status.toUpperCase() : "";
2261
- };
2262
- return `
520
+ get events() {
521
+ return this._events;
522
+ }
523
+ set events(val) {
524
+ this._events = val;
525
+ if (this.engine) {
526
+ this.engine.updateEvents(val);
527
+ }
528
+ }
529
+ set theme(val) {
530
+ this._theme = val;
531
+ this.applyTheme();
532
+ }
533
+ set categoryColors(val) {
534
+ this._categoryColors = val;
535
+ if (this.engine) {
536
+ this.engine.updateCategoryColors(val);
537
+ }
538
+ }
539
+ set renderEvent(val) {
540
+ this._renderEvent = val;
541
+ if (this.engine) this.render();
542
+ }
543
+ set renderNoEvents(val) {
544
+ this._renderNoEvents = val;
545
+ if (this.engine) this.render();
546
+ }
547
+ get loading() {
548
+ return this.hasAttribute("loading");
549
+ }
550
+ set loading(val) {
551
+ if (val) this.setAttribute("loading", "");
552
+ else this.removeAttribute("loading");
553
+ }
554
+ connectedCallback() {
555
+ this.classList.add("kalendly-calendar");
556
+ this.initEngine();
557
+ this.render();
558
+ }
559
+ disconnectedCallback() {
560
+ this.cleanup();
561
+ this.classList.remove("kalendly-calendar");
562
+ this.innerHTML = "";
563
+ }
564
+ attributeChangedCallback(name, oldVal, newVal) {
565
+ if (oldVal === newVal) return;
566
+ if (name === "loading") {
567
+ this.render();
568
+ return;
569
+ }
570
+ if (name === "selectable" && newVal === null) {
571
+ this._rangeStart = null;
572
+ this._rangeEnd = null;
573
+ this._timeRangeDate = null;
574
+ this._timeRangeStart = null;
575
+ this._timeRangeEnd = null;
576
+ this._timeRangeComplete = false;
577
+ }
578
+ this.reinit();
579
+ }
580
+ get minYear() {
581
+ const val = this.getAttribute("min-year");
582
+ return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() - 30;
583
+ }
584
+ get maxYear() {
585
+ const val = this.getAttribute("max-year");
586
+ return val ? parseInt(val, 10) : (/* @__PURE__ */ new Date()).getFullYear() + 10;
587
+ }
588
+ initEngine() {
589
+ const initialDateAttr = this.getAttribute("initial-date");
590
+ const initialDate = initialDateAttr ? new Date(initialDateAttr) : void 0;
591
+ const weekStartsOnAttr = this.getAttribute("week-starts-on");
592
+ const weekStartsOn = weekStartsOnAttr === "1" ? 1 : 0;
593
+ this.engine = new CalendarEngine({
594
+ events: this._events,
595
+ initialDate,
596
+ minYear: this.minYear,
597
+ maxYear: this.maxYear,
598
+ weekStartsOn,
599
+ categoryColors: this._categoryColors ?? void 0
600
+ });
601
+ this.actions = this.engine.getActions();
602
+ this.applyTheme();
603
+ this.unsubscribe = this.engine.subscribe(() => {
604
+ this.render();
605
+ });
606
+ }
607
+ reinit() {
608
+ if (!this.engine) return;
609
+ this.cleanup();
610
+ this.initEngine();
611
+ this.render();
612
+ }
613
+ cleanup() {
614
+ if (this.unsubscribe) {
615
+ this.unsubscribe();
616
+ this.unsubscribe = null;
617
+ }
618
+ if (this.delegatedClickHandler) {
619
+ this.removeEventListener("click", this.delegatedClickHandler);
620
+ this.delegatedClickHandler = null;
621
+ }
622
+ if (this.delegatedInputHandler) {
623
+ this.removeEventListener("input", this.delegatedInputHandler);
624
+ this.delegatedInputHandler = null;
625
+ }
626
+ if (this.delegatedBlurHandler) {
627
+ this.removeEventListener("blur", this.delegatedBlurHandler, true);
628
+ this.delegatedBlurHandler = null;
629
+ }
630
+ if (this.delegatedKeydownHandler) {
631
+ this.removeEventListener("keydown", this.delegatedKeydownHandler);
632
+ this.delegatedKeydownHandler = null;
633
+ }
634
+ if (this.clickOutsideHandler) {
635
+ document.removeEventListener("click", this.clickOutsideHandler);
636
+ this.clickOutsideHandler = null;
637
+ }
638
+ this.listenersAttached = false;
639
+ if (this.engine) {
640
+ this.engine.destroy();
641
+ this.engine = null;
642
+ }
643
+ this.actions = null;
644
+ }
645
+ applyTheme() {
646
+ if (!this._theme) return;
647
+ const root = document.documentElement;
648
+ const t = this._theme;
649
+ if (t.primary)
650
+ root.style.setProperty("--calendar-primary-color", t.primary);
651
+ if (t.secondary)
652
+ root.style.setProperty("--calendar-secondary-color", t.secondary);
653
+ if (t.tertiary)
654
+ root.style.setProperty("--calendar-tertiary-color", t.tertiary);
655
+ if (t.textColor)
656
+ root.style.setProperty("--calendar-text-color", t.textColor);
657
+ if (t.textLight)
658
+ root.style.setProperty("--calendar-text-light", t.textLight);
659
+ if (t.background)
660
+ root.style.setProperty("--calendar-background", t.background);
661
+ if (t.cellHover)
662
+ root.style.setProperty("--calendar-cell-hover", t.cellHover);
663
+ if (t.borderColor)
664
+ root.style.setProperty("--calendar-border-color", t.borderColor);
665
+ if (t.todayOutline)
666
+ root.style.setProperty("--calendar-today-outline", t.todayOutline);
667
+ if (t.selectedBg)
668
+ root.style.setProperty("--calendar-selected-bg", t.selectedBg);
669
+ if (t.eventIndicator)
670
+ root.style.setProperty("--calendar-event-indicator", t.eventIndicator);
671
+ }
672
+ render() {
673
+ if (!this.engine) return;
674
+ const viewModel = this.engine.getViewModel();
675
+ const useShortMonths = this.hasAttribute("use-short-month-names");
676
+ const title = this.getAttribute("title");
677
+ const minYear = this.minYear;
678
+ const maxYear = this.maxYear;
679
+ const availabilityMode = this.getAttribute("availability-mode");
680
+ const selectable = this.hasAttribute("selectable");
681
+ const isLoading = this.loading;
682
+ const today = /* @__PURE__ */ new Date();
683
+ const todayMonth = today.getMonth();
684
+ const todayYear = today.getFullYear();
685
+ const isCurrentMonth = viewModel.currentYear === todayYear && viewModel.currentMonth === todayMonth;
686
+ const showScrollHint = viewModel.tasks.length > 3;
687
+ const defaultRenderEvent = (event) => {
688
+ const timeRange = formatTimeRange(event);
689
+ const attendeesList = formatAttendees(event.attendees);
690
+ let borderColor = event.color || "#3b82f6";
691
+ if (event.category) {
692
+ borderColor = this.engine.getCategoryColor(event.category);
693
+ }
694
+ const getCategoryLabel = (category) => {
695
+ const labels = {
696
+ work: "WORK",
697
+ personal: "PERSONAL",
698
+ meeting: "MEETING",
699
+ deadline: "DEADLINE",
700
+ appointment: "APPOINTMENT",
701
+ other: "OTHER"
702
+ };
703
+ return category ? labels[category] || category.toUpperCase() : "";
704
+ };
705
+ const getPriorityLabel = (priority) => {
706
+ const labels = {
707
+ high: "HIGH",
708
+ medium: "MEDIUM",
709
+ low: "LOW"
710
+ };
711
+ return priority ? labels[priority] || priority.toUpperCase() : "";
712
+ };
713
+ const getStatusLabel = (status) => {
714
+ const labels = {
715
+ completed: "COMPLETED",
716
+ cancelled: "CANCELLED",
717
+ tentative: "TENTATIVE",
718
+ scheduled: "SCHEDULED"
719
+ };
720
+ return status ? labels[status] || status.toUpperCase() : "";
721
+ };
722
+ return `
2263
723
  <div class="event-card" style="border-left-color: ${borderColor}">
2264
724
  <div class="event-header">
2265
725
  <div class="event-title">${event.name}</div>
@@ -2269,46 +729,46 @@ var VanillaCalendar = class {
2269
729
  ${event.status && event.status !== "scheduled" ? `<span class="badge status-${event.status}">${getStatusLabel(event.status)}</span>` : ""}
2270
730
  </div>
2271
731
  </div>
2272
-
732
+
2273
733
  ${timeRange ? `
2274
734
  <div class="event-time">
2275
735
  <span class="event-time-label">Time:</span>
2276
736
  <span class="event-time-value">${timeRange}</span>
2277
737
  </div>
2278
738
  ` : ""}
2279
-
739
+
2280
740
  ${event.description ? `
2281
741
  <div class="event-description">${event.description}</div>
2282
742
  ` : ""}
2283
-
743
+
2284
744
  ${event.location ? `
2285
745
  <div class="event-time">
2286
746
  <span class="event-time-label">Location:</span>
2287
747
  <span class="event-time-value">${event.location}</span>
2288
748
  </div>
2289
749
  ` : ""}
2290
-
750
+
2291
751
  ${attendeesList ? `
2292
752
  <div class="event-time">
2293
753
  <span class="event-time-label">Attendees:</span>
2294
754
  <span class="event-time-value">${attendeesList}</span>
2295
755
  </div>
2296
756
  ` : ""}
2297
-
757
+
2298
758
  ${event.organizer ? `
2299
759
  <div class="event-time">
2300
760
  <span class="event-time-label">Organizer:</span>
2301
761
  <span class="event-time-value">${event.organizer}</span>
2302
762
  </div>
2303
763
  ` : ""}
2304
-
764
+
2305
765
  ${event.notes ? `
2306
766
  <div class="event-time">
2307
767
  <span class="event-time-label">Notes:</span>
2308
768
  <span class="event-time-value">${event.notes}</span>
2309
769
  </div>
2310
770
  ` : ""}
2311
-
771
+
2312
772
  ${event.url ? `
2313
773
  <div class="event-time">
2314
774
  <a href="${event.url}" target="_blank" rel="noopener noreferrer" class="event-link">
@@ -2316,7 +776,7 @@ var VanillaCalendar = class {
2316
776
  </a>
2317
777
  </div>
2318
778
  ` : ""}
2319
-
779
+
2320
780
  ${event.tags && event.tags.length > 0 ? `
2321
781
  <div class="event-tags">
2322
782
  ${event.tags.map((tag) => `<span class="event-tag">${tag}</span>`).join("")}
@@ -2324,27 +784,50 @@ var VanillaCalendar = class {
2324
784
  ` : ""}
2325
785
  </div>
2326
786
  `;
2327
- };
2328
- const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
2329
- const renderEvent = this.props.renderEvent || defaultRenderEvent;
2330
- const renderNoEvents = this.props.renderNoEvents || defaultRenderNoEvents;
2331
- const showScrollHint = viewModel.tasks.length > 3;
2332
- const today = /* @__PURE__ */ new Date();
2333
- const todayMonth = today.getMonth();
2334
- const todayYear = today.getFullYear();
2335
- const minYear = this.props.minYear ?? todayYear - 30;
2336
- const maxYear = this.props.maxYear ?? todayYear + 10;
2337
- const isCurrentMonth = viewModel.currentYear === todayYear && viewModel.currentMonth === todayMonth;
2338
- const html = `
2339
- ${this.props.title ? `
2340
- <div class="page--title">
2341
- <h1>${this.props.title}</h1>
2342
- </div>
787
+ };
788
+ const defaultRenderNoEvents = () => '<div class="no-events-message">No events scheduled for this day.</div>';
789
+ const renderEvent = this._renderEvent || defaultRenderEvent;
790
+ const renderNoEvents = this._renderNoEvents || defaultRenderNoEvents;
791
+ const renderTimeGrid = (events, date) => {
792
+ const isHourBooked = (hour) => events.some((event) => {
793
+ if (!event.startTime || !event.endTime) return true;
794
+ const [startH] = event.startTime.split(":").map(Number);
795
+ const [endH] = event.endTime.split(":").map(Number);
796
+ return hour >= startH && hour < endH;
797
+ });
798
+ const slots = Array.from({ length: 24 }, (_, hour) => {
799
+ const startTime = `${String(hour).padStart(2, "0")}:00`;
800
+ const endTime = `${String(hour + 1).padStart(2, "0")}:00`;
801
+ const booked = isHourBooked(hour);
802
+ const inTimeRange = !booked && selectable && this._timeRangeStart !== null && this._timeRangeEnd !== null && isSameDay2(this._timeRangeDate, date) && startTime >= this._timeRangeStart && endTime <= this._timeRangeEnd;
803
+ const isRangeStart = inTimeRange && startTime === this._timeRangeStart;
804
+ const isRangeEnd = inTimeRange && endTime === this._timeRangeEnd;
805
+ const isInRange = inTimeRange && !isRangeStart && !isRangeEnd;
806
+ const slotClasses = [
807
+ "time-grid__slot",
808
+ booked ? "time-grid__slot--booked" : "time-grid__slot--free",
809
+ isRangeStart ? "time-grid__slot--range-start" : "",
810
+ isRangeEnd ? "time-grid__slot--range-end" : "",
811
+ isInRange ? "time-grid__slot--in-range" : ""
812
+ ].filter(Boolean).join(" ");
813
+ const slotAttrs = !booked && selectable ? `data-action="select-slot" data-start-time="${startTime}" data-end-time="${endTime}" data-date="${date.toISOString()}"` : "";
814
+ return `
815
+ <div class="${slotClasses}" ${slotAttrs}>
816
+ <span class="time-grid__label">${startTime}</span>
817
+ <span class="time-grid__status">${booked ? "Booked" : "Available"}</span>
818
+ </div>`;
819
+ });
820
+ return `<div class="time-grid">${slots.join("")}</div>`;
821
+ };
822
+ const html = `
823
+ ${title ? `
824
+ <div class="page--title">
825
+ <h1>${title}</h1>
826
+ </div>
2343
827
  ` : ""}
2344
828
 
2345
829
  <div class="calendar--content">
2346
830
  <div class="calendar--card">
2347
- <!-- Navigation Header -->
2348
831
  <div class="calendar--nav-header">
2349
832
  <button type="button" class="calendar--nav-arrow" data-action="previous" aria-label="Previous month">
2350
833
  &#8249;
@@ -2358,7 +841,7 @@ var VanillaCalendar = class {
2358
841
  aria-expanded="${this.pickerOpen ? "true" : "false"}"
2359
842
  aria-haspopup="true"
2360
843
  >
2361
- ${this.props.useShortMonthNames ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
844
+ ${useShortMonths ? `${MONTHS[viewModel.currentMonth]} ${viewModel.currentYear}` : viewModel.monthAndYearText}
2362
845
  <span class="calendar--picker-chevron">&#9662;</span>
2363
846
  </button>
2364
847
 
@@ -2389,10 +872,10 @@ var VanillaCalendar = class {
2389
872
  </div>
2390
873
 
2391
874
  <div class="calendar--picker-months">
2392
- ${(this.props.useShortMonthNames ? MONTHS : MONTHS_FULL).map((month, index) => {
2393
- const isSelected = index === viewModel.currentMonth;
2394
- const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
2395
- return `
875
+ ${(useShortMonths ? MONTHS : MONTHS_FULL).map((month, index) => {
876
+ const isSelected = index === viewModel.currentMonth;
877
+ const isCurrent = index === todayMonth && viewModel.currentYear === todayYear;
878
+ return `
2396
879
  <button
2397
880
  type="button"
2398
881
  class="calendar--picker-month${isSelected ? " selected" : ""}${isCurrent ? " current-month" : ""}"
@@ -2400,7 +883,7 @@ var VanillaCalendar = class {
2400
883
  data-month="${index}"
2401
884
  >${month}</button>
2402
885
  `;
2403
- }).join("")}
886
+ }).join("")}
2404
887
  </div>
2405
888
  </div>
2406
889
  ` : ""}
@@ -2425,30 +908,52 @@ var VanillaCalendar = class {
2425
908
  </tr>
2426
909
  </thead>
2427
910
  <tbody data-calendar-body>
2428
- ${viewModel.calendarDates.map(
2429
- (week) => `
2430
- <tr>
2431
- ${week.map((calendarDate, dayIndex) => {
2432
- const classes = getCellClasses(calendarDate);
2433
- const dateString = calendarDate.date.toISOString();
2434
- return `
2435
- <td
2436
- class="${classes.join(" ")}"
2437
- data-date="${dateString}"
2438
- data-day-index="${dayIndex}"
2439
- data-clickable="true"
2440
- >
2441
- ${calendarDate.date.getDate()}
2442
- </td>
2443
- `;
2444
- }).join("")}
2445
- </tr>
2446
- `
2447
- ).join("")}
911
+ ${isLoading ? Array.from(
912
+ { length: 6 },
913
+ () => `<tr>${Array.from(
914
+ { length: 7 },
915
+ () => `<td class="calendar--skeleton" aria-hidden="true"></td>`
916
+ ).join("")}</tr>`
917
+ ).join("") : viewModel.calendarDates.map(
918
+ (week) => `
919
+ <tr>
920
+ ${week.map((calendarDate, dayIndex) => {
921
+ const classes = getCellClasses(calendarDate);
922
+ if (availabilityMode && calendarDate.isCurrentMonth) {
923
+ classes.push(
924
+ calendarDate.hasEvents ? "availability--booked" : "availability--free"
925
+ );
926
+ }
927
+ if (availabilityMode === "day" && selectable && calendarDate.isCurrentMonth) {
928
+ const d = calendarDate.date;
929
+ if (this._rangeStart && isSameDay2(d, this._rangeStart))
930
+ classes.push("availability--range-start");
931
+ if (this._rangeEnd && isSameDay2(d, this._rangeEnd))
932
+ classes.push("availability--range-end");
933
+ if (this._rangeStart && this._rangeEnd) {
934
+ if (d > this._rangeStart && d < this._rangeEnd)
935
+ classes.push("availability--in-range");
936
+ }
937
+ }
938
+ const dateString = calendarDate.date.toISOString();
939
+ return `
940
+ <td
941
+ class="${classes.join(" ")}"
942
+ data-date="${dateString}"
943
+ data-day-index="${dayIndex}"
944
+ data-clickable="true"
945
+ >
946
+ ${calendarDate.date.getDate()}
947
+ </td>
948
+ `;
949
+ }).join("")}
950
+ </tr>
951
+ `
952
+ ).join("")}
2448
953
  </tbody>
2449
954
  </table>
2450
955
 
2451
- ${viewModel.selectedDate ? `
956
+ ${!isLoading && availabilityMode !== "day" && viewModel.selectedDate ? `
2452
957
  <div class="date-popup ${viewModel.popupPositionClass}">
2453
958
  <div class="popup-header">
2454
959
  <h2>${viewModel.scheduleDay}</h2>
@@ -2462,260 +967,330 @@ var VanillaCalendar = class {
2462
967
  ` : ""}
2463
968
 
2464
969
  <div class="events-container">
2465
- ${viewModel.tasks.length > 0 ? `
2466
- ${viewModel.tasks.map((event) => renderEvent(event)).join("")}
2467
- ` : renderNoEvents()}
970
+ ${availabilityMode === "time" ? renderTimeGrid(viewModel.tasks, viewModel.selectedDate) : viewModel.tasks.length > 0 ? viewModel.tasks.map((event) => renderEvent(event)).join("") : renderNoEvents()}
2468
971
  </div>
2469
972
  </div>
2470
973
  ` : ""}
2471
974
  </div>
2472
975
  </div>
2473
976
  `;
2474
- this.container.innerHTML = html;
2475
- this.attachEventListeners();
2476
- }
2477
- attachEventListeners() {
2478
- if (this.listenersAttached) return;
2479
- this.listenersAttached = true;
2480
- const today = /* @__PURE__ */ new Date();
2481
- const todayYear = today.getFullYear();
2482
- const todayMonth = today.getMonth();
2483
- const minYear = this.props.minYear ?? todayYear - 30;
2484
- const maxYear = this.props.maxYear ?? todayYear + 10;
2485
- this.delegatedClickHandler = (e) => {
2486
- const target = e.target;
2487
- const cell = target.closest('td[data-clickable="true"]');
2488
- if (cell && cell.dataset.date) {
2489
- e.stopPropagation();
2490
- const date = new Date(cell.dataset.date);
2491
- const dayIndex = parseInt(cell.dataset.dayIndex || "0");
2492
- this.engine.handleDateClick(date, dayIndex);
2493
- this.container.dispatchEvent(
2494
- new CustomEvent("dateSelect", { detail: { date, dayIndex } })
2495
- );
2496
- return;
977
+ this.innerHTML = html;
978
+ this.attachEventListeners();
2497
979
  }
2498
- const actionEl = target.closest("[data-action]");
2499
- if (!actionEl) return;
2500
- const action = actionEl.dataset.action;
2501
- e.stopPropagation();
2502
- switch (action) {
2503
- case "previous":
2504
- this.actions.previous();
2505
- this.dispatchMonthChange();
2506
- break;
2507
- case "next":
2508
- this.actions.next();
2509
- this.dispatchMonthChange();
2510
- break;
2511
- case "today":
2512
- this.actions.goToToday();
2513
- this.pickerOpen = false;
2514
- this.container.dispatchEvent(
2515
- new CustomEvent("monthChange", {
2516
- detail: { year: todayYear, month: todayMonth }
2517
- })
2518
- );
2519
- break;
2520
- case "toggle-picker":
2521
- this.pickerOpen = !this.pickerOpen;
2522
- if (this.pickerOpen) {
2523
- this.yearInput = String(this.engine.getViewModel().currentYear);
2524
- this.yearInputValid = true;
980
+ attachEventListeners() {
981
+ if (this.listenersAttached) return;
982
+ this.listenersAttached = true;
983
+ const minYear = this.minYear;
984
+ const maxYear = this.maxYear;
985
+ this.delegatedClickHandler = (e) => {
986
+ const target = e.target;
987
+ const cell = target.closest('td[data-clickable="true"]');
988
+ if (cell && cell.dataset.date) {
989
+ e.stopPropagation();
990
+ const date = new Date(cell.dataset.date);
991
+ const dayIndex = parseInt(cell.dataset.dayIndex || "0");
992
+ const availMode = this.getAttribute("availability-mode");
993
+ const isSelectable = this.hasAttribute("selectable");
994
+ if (availMode === "day" && isSelectable) {
995
+ const isBooked = cell.classList.contains("availability--booked");
996
+ if (!isBooked) {
997
+ let startDate;
998
+ let endDate;
999
+ if (this._rangeEnd !== null) {
1000
+ this._rangeStart = date;
1001
+ this._rangeEnd = null;
1002
+ startDate = date;
1003
+ endDate = date;
1004
+ } else if (this._rangeStart === null) {
1005
+ this._rangeStart = date;
1006
+ startDate = date;
1007
+ endDate = date;
1008
+ } else {
1009
+ const [s, e2] = this._rangeStart <= date ? [this._rangeStart, date] : [date, this._rangeStart];
1010
+ const cursor = new Date(s);
1011
+ cursor.setDate(cursor.getDate() + 1);
1012
+ let blocked = false;
1013
+ while (cursor < e2) {
1014
+ if (this.engine.getEventsForDate(cursor).length > 0) {
1015
+ blocked = true;
1016
+ break;
1017
+ }
1018
+ cursor.setDate(cursor.getDate() + 1);
1019
+ }
1020
+ if (blocked) {
1021
+ this._rangeStart = date;
1022
+ this._rangeEnd = null;
1023
+ } else {
1024
+ this._rangeStart = s;
1025
+ this._rangeEnd = e2;
1026
+ }
1027
+ startDate = this._rangeStart;
1028
+ endDate = this._rangeEnd ?? this._rangeStart;
1029
+ }
1030
+ this.dispatchEvent(
1031
+ new CustomEvent("cal-availability-select", {
1032
+ bubbles: true,
1033
+ composed: true,
1034
+ detail: { startDate, endDate }
1035
+ })
1036
+ );
1037
+ }
1038
+ }
1039
+ if (availMode === "time") {
1040
+ this._timeRangeDate = null;
1041
+ this._timeRangeStart = null;
1042
+ this._timeRangeEnd = null;
1043
+ this._timeRangeComplete = false;
1044
+ }
1045
+ this.engine.handleDateClick(date, dayIndex);
1046
+ this.dispatchEvent(
1047
+ new CustomEvent("cal-date-select", {
1048
+ bubbles: true,
1049
+ composed: true,
1050
+ detail: { date, events: this.engine.getEventsForDate(date) }
1051
+ })
1052
+ );
1053
+ return;
2525
1054
  }
2526
- this.render();
2527
- break;
2528
- case "year-prev": {
2529
- const vm = this.engine.getViewModel();
2530
- if (vm.currentYear > minYear) {
2531
- const newYear = vm.currentYear - 1;
2532
- this.yearInput = String(newYear);
2533
- this.updatePickerYear(newYear);
2534
- this.actions.jump(newYear, vm.currentMonth);
2535
- this.dispatchMonthChange();
1055
+ const actionEl = target.closest("[data-action]");
1056
+ if (!actionEl) return;
1057
+ const action = actionEl.dataset.action;
1058
+ e.stopPropagation();
1059
+ switch (action) {
1060
+ case "previous": {
1061
+ const vm = this.engine.getViewModel();
1062
+ const tMonth = vm.currentMonth === 0 ? 11 : vm.currentMonth - 1;
1063
+ const tYear = vm.currentMonth === 0 ? vm.currentYear - 1 : vm.currentYear;
1064
+ this.dispatchMonthChange(tYear, tMonth);
1065
+ this.actions.previous();
1066
+ break;
1067
+ }
1068
+ case "next": {
1069
+ const vm = this.engine.getViewModel();
1070
+ const tMonth = vm.currentMonth === 11 ? 0 : vm.currentMonth + 1;
1071
+ const tYear = vm.currentMonth === 11 ? vm.currentYear + 1 : vm.currentYear;
1072
+ this.dispatchMonthChange(tYear, tMonth);
1073
+ this.actions.next();
1074
+ break;
1075
+ }
1076
+ case "today": {
1077
+ const t = /* @__PURE__ */ new Date();
1078
+ this.dispatchMonthChange(t.getFullYear(), t.getMonth());
1079
+ this.actions.goToToday();
1080
+ this.pickerOpen = false;
1081
+ break;
1082
+ }
1083
+ case "toggle-picker":
1084
+ this.pickerOpen = !this.pickerOpen;
1085
+ if (this.pickerOpen) {
1086
+ this.yearInput = String(this.engine.getViewModel().currentYear);
1087
+ this.yearInputValid = true;
1088
+ }
1089
+ this.render();
1090
+ break;
1091
+ case "year-prev": {
1092
+ const vm = this.engine.getViewModel();
1093
+ if (vm.currentYear > minYear) {
1094
+ const newYear = vm.currentYear - 1;
1095
+ this.yearInput = String(newYear);
1096
+ this.updatePickerYear(newYear);
1097
+ this.dispatchMonthChange(newYear, vm.currentMonth);
1098
+ this.actions.jump(newYear, vm.currentMonth);
1099
+ }
1100
+ break;
1101
+ }
1102
+ case "year-next": {
1103
+ const vm = this.engine.getViewModel();
1104
+ if (vm.currentYear < maxYear) {
1105
+ const newYear = vm.currentYear + 1;
1106
+ this.yearInput = String(newYear);
1107
+ this.updatePickerYear(newYear);
1108
+ this.dispatchMonthChange(newYear, vm.currentMonth);
1109
+ this.actions.jump(newYear, vm.currentMonth);
1110
+ }
1111
+ break;
1112
+ }
1113
+ case "select-month": {
1114
+ const month = parseInt(actionEl.dataset.month || "0", 10);
1115
+ const year = this.engine.getViewModel().currentYear;
1116
+ this.dispatchMonthChange(year, month);
1117
+ this.actions.jump(year, month);
1118
+ this.pickerOpen = false;
1119
+ break;
1120
+ }
1121
+ case "close-popup":
1122
+ this._timeRangeDate = null;
1123
+ this._timeRangeStart = null;
1124
+ this._timeRangeEnd = null;
1125
+ this._timeRangeComplete = false;
1126
+ this.engine.clearSelection();
1127
+ break;
1128
+ case "select-slot": {
1129
+ const slotStart = actionEl.dataset.startTime;
1130
+ const slotEnd = actionEl.dataset.endTime;
1131
+ const slotDate = new Date(actionEl.dataset.date);
1132
+ if (this._timeRangeComplete) {
1133
+ this._timeRangeDate = slotDate;
1134
+ this._timeRangeStart = slotStart;
1135
+ this._timeRangeEnd = slotEnd;
1136
+ this._timeRangeComplete = false;
1137
+ } else if (this._timeRangeStart === null) {
1138
+ this._timeRangeDate = slotDate;
1139
+ this._timeRangeStart = slotStart;
1140
+ this._timeRangeEnd = slotEnd;
1141
+ } else {
1142
+ const newStart = slotStart < this._timeRangeStart ? slotStart : this._timeRangeStart;
1143
+ const newEnd = slotEnd > this._timeRangeEnd ? slotEnd : this._timeRangeEnd;
1144
+ this._timeRangeStart = newStart;
1145
+ this._timeRangeEnd = newEnd;
1146
+ this._timeRangeComplete = true;
1147
+ }
1148
+ this.render();
1149
+ this.dispatchEvent(
1150
+ new CustomEvent("cal-availability-select", {
1151
+ bubbles: true,
1152
+ composed: true,
1153
+ detail: {
1154
+ date: this._timeRangeDate,
1155
+ startTime: this._timeRangeStart,
1156
+ endTime: this._timeRangeEnd
1157
+ }
1158
+ })
1159
+ );
1160
+ break;
1161
+ }
2536
1162
  }
2537
- break;
2538
- }
2539
- case "year-next": {
1163
+ };
1164
+ this.delegatedInputHandler = (e) => {
1165
+ const target = e.target;
1166
+ if (!target.matches("[data-year-input]")) return;
1167
+ const value = target.value;
1168
+ if (value === "" || /^\d+$/.test(value)) {
1169
+ this.yearInput = value;
1170
+ const year = parseInt(value, 10);
1171
+ this.yearInputValid = value === "" || year >= minYear && year <= maxYear;
1172
+ target.classList.toggle("invalid", !this.yearInputValid);
1173
+ }
1174
+ };
1175
+ this.delegatedBlurHandler = (e) => {
1176
+ const target = e.target;
1177
+ if (!target.matches("[data-year-input]")) return;
1178
+ const year = parseInt(this.yearInput, 10);
2540
1179
  const vm = this.engine.getViewModel();
2541
- if (vm.currentYear < maxYear) {
2542
- const newYear = vm.currentYear + 1;
2543
- this.yearInput = String(newYear);
2544
- this.updatePickerYear(newYear);
2545
- this.actions.jump(newYear, vm.currentMonth);
2546
- this.dispatchMonthChange();
1180
+ if (isNaN(year) || year < minYear || year > maxYear) {
1181
+ this.yearInput = String(vm.currentYear);
1182
+ this.yearInputValid = true;
1183
+ target.value = this.yearInput;
1184
+ target.classList.remove("invalid");
1185
+ } else if (year !== vm.currentYear) {
1186
+ this.dispatchMonthChange(year, vm.currentMonth);
1187
+ this.actions.jump(year, vm.currentMonth);
2547
1188
  }
2548
- break;
2549
- }
2550
- case "select-month": {
2551
- const month = parseInt(actionEl.dataset.month || "0", 10);
2552
- this.actions.jump(this.engine.getViewModel().currentYear, month);
2553
- this.pickerOpen = false;
2554
- this.dispatchMonthChange();
2555
- break;
2556
- }
2557
- case "close-popup":
2558
- this.engine.clearSelection();
2559
- break;
1189
+ };
1190
+ this.delegatedKeydownHandler = (e) => {
1191
+ const target = e.target;
1192
+ if (!target.matches("[data-year-input]")) return;
1193
+ if (e.key === "Enter") target.blur();
1194
+ };
1195
+ this.addEventListener("click", this.delegatedClickHandler);
1196
+ this.addEventListener("input", this.delegatedInputHandler);
1197
+ this.addEventListener("blur", this.delegatedBlurHandler, true);
1198
+ this.addEventListener("keydown", this.delegatedKeydownHandler);
1199
+ this.clickOutsideHandler = (e) => {
1200
+ const target = e.target;
1201
+ if (this.contains(target)) return;
1202
+ if (this.pickerOpen) {
1203
+ this.pickerOpen = false;
1204
+ this.render();
1205
+ return;
1206
+ }
1207
+ const popup = this.querySelector(".date-popup");
1208
+ if (popup) {
1209
+ this.engine.clearSelection();
1210
+ }
1211
+ };
1212
+ document.addEventListener("click", this.clickOutsideHandler);
2560
1213
  }
2561
- };
2562
- this.delegatedInputHandler = (e) => {
2563
- const target = e.target;
2564
- if (!target.matches("[data-year-input]")) return;
2565
- const value = target.value;
2566
- if (value === "" || /^\d+$/.test(value)) {
2567
- this.yearInput = value;
2568
- const year = parseInt(value, 10);
2569
- this.yearInputValid = value === "" || year >= minYear && year <= maxYear;
2570
- target.classList.toggle("invalid", !this.yearInputValid);
1214
+ updatePickerYear(year) {
1215
+ const input = this.querySelector("[data-year-input]");
1216
+ if (input) input.value = String(year);
1217
+ const prevBtn = this.querySelector(
1218
+ '[data-action="year-prev"]'
1219
+ );
1220
+ const nextBtn = this.querySelector(
1221
+ '[data-action="year-next"]'
1222
+ );
1223
+ if (prevBtn) prevBtn.disabled = year <= this.minYear;
1224
+ if (nextBtn) nextBtn.disabled = year >= this.maxYear;
2571
1225
  }
2572
- };
2573
- this.delegatedBlurHandler = (e) => {
2574
- const target = e.target;
2575
- if (!target.matches("[data-year-input]")) return;
2576
- const year = parseInt(this.yearInput, 10);
2577
- const vm = this.engine.getViewModel();
2578
- if (isNaN(year) || year < minYear || year > maxYear) {
2579
- this.yearInput = String(vm.currentYear);
2580
- this.yearInputValid = true;
2581
- target.value = this.yearInput;
2582
- target.classList.remove("invalid");
2583
- } else if (year !== vm.currentYear) {
2584
- this.actions.jump(year, vm.currentMonth);
2585
- this.dispatchMonthChange();
1226
+ dispatchMonthChange(year, month) {
1227
+ if (!this.engine) return;
1228
+ const vm = this.engine.getViewModel();
1229
+ this.dispatchEvent(
1230
+ new CustomEvent("cal-month-change", {
1231
+ bubbles: true,
1232
+ composed: true,
1233
+ detail: {
1234
+ year: year ?? vm.currentYear,
1235
+ month: month ?? vm.currentMonth
1236
+ }
1237
+ })
1238
+ );
2586
1239
  }
2587
- };
2588
- this.delegatedKeydownHandler = (e) => {
2589
- const target = e.target;
2590
- if (!target.matches("[data-year-input]")) return;
2591
- if (e.key === "Enter") {
2592
- target.blur();
1240
+ // Public API
1241
+ updateEvents(events) {
1242
+ this.events = events;
2593
1243
  }
2594
- };
2595
- this.container.addEventListener("click", this.delegatedClickHandler);
2596
- this.container.addEventListener("input", this.delegatedInputHandler);
2597
- this.container.addEventListener("blur", this.delegatedBlurHandler, true);
2598
- this.container.addEventListener("keydown", this.delegatedKeydownHandler);
2599
- this.clickOutsideHandler = (e) => {
2600
- const target = e.target;
2601
- if (this.container.contains(target)) return;
2602
- if (this.pickerOpen) {
2603
- this.pickerOpen = false;
2604
- this.render();
2605
- return;
1244
+ updateTheme(theme) {
1245
+ this.theme = theme;
2606
1246
  }
2607
- const popup = this.container.querySelector(".date-popup");
2608
- if (popup) {
2609
- this.engine.clearSelection();
1247
+ getCurrentDate() {
1248
+ return this.engine?.getViewModel().selectedDate ?? null;
1249
+ }
1250
+ goToDate(date) {
1251
+ const year = date.getFullYear();
1252
+ const month = date.getMonth();
1253
+ this.dispatchMonthChange(year, month);
1254
+ this.actions?.jump(year, month);
1255
+ }
1256
+ getEngine() {
1257
+ if (!this.engine)
1258
+ throw new Error("CalendarElement is not connected to the DOM");
1259
+ return this.engine;
2610
1260
  }
2611
1261
  };
2612
- document.addEventListener("click", this.clickOutsideHandler);
2613
- }
2614
- // Targeted update for picker year - avoids full DOM rebuild
2615
- updatePickerYear(year) {
2616
- const input = this.container.querySelector(
2617
- "[data-year-input]"
2618
- );
2619
- if (input) {
2620
- input.value = String(year);
2621
- }
2622
- const today = /* @__PURE__ */ new Date();
2623
- const todayYear = today.getFullYear();
2624
- const minYear = this.props.minYear ?? todayYear - 30;
2625
- const maxYear = this.props.maxYear ?? todayYear + 10;
2626
- const prevBtn = this.container.querySelector(
2627
- '[data-action="year-prev"]'
2628
- );
2629
- const nextBtn = this.container.querySelector(
2630
- '[data-action="year-next"]'
2631
- );
2632
- if (prevBtn) prevBtn.disabled = year <= minYear;
2633
- if (nextBtn) nextBtn.disabled = year >= maxYear;
1262
+ CalendarElement.observedAttributes = [
1263
+ "initial-date",
1264
+ "min-year",
1265
+ "max-year",
1266
+ "week-starts-on",
1267
+ "title",
1268
+ "use-short-month-names",
1269
+ "availability-mode",
1270
+ "selectable",
1271
+ "loading"
1272
+ ];
2634
1273
  }
2635
- dispatchMonthChange() {
2636
- const vm = this.engine.getViewModel();
2637
- this.container.dispatchEvent(
2638
- new CustomEvent("monthChange", {
2639
- detail: { year: vm.currentYear, month: vm.currentMonth }
2640
- })
2641
- );
2642
- }
2643
- updateEvents(events) {
2644
- this.engine.updateEvents(events);
2645
- }
2646
- updateTheme(theme) {
2647
- this.props.theme = theme;
2648
- this.applyTheme();
2649
- }
2650
- getCurrentDate() {
2651
- return this.engine.getViewModel().selectedDate;
2652
- }
2653
- goToDate(date) {
2654
- this.actions.jump(date.getFullYear(), date.getMonth());
2655
- }
2656
- getEngine() {
2657
- return this.engine;
2658
- }
2659
- destroy() {
2660
- if (this.unsubscribe) {
2661
- this.unsubscribe();
2662
- this.unsubscribe = null;
2663
- }
2664
- if (this.delegatedClickHandler) {
2665
- this.container.removeEventListener("click", this.delegatedClickHandler);
2666
- this.delegatedClickHandler = null;
2667
- }
2668
- if (this.delegatedInputHandler) {
2669
- this.container.removeEventListener("input", this.delegatedInputHandler);
2670
- this.delegatedInputHandler = null;
2671
- }
2672
- if (this.delegatedBlurHandler) {
2673
- this.container.removeEventListener(
2674
- "blur",
2675
- this.delegatedBlurHandler,
2676
- true
2677
- );
2678
- this.delegatedBlurHandler = null;
2679
- }
2680
- if (this.delegatedKeydownHandler) {
2681
- this.container.removeEventListener(
2682
- "keydown",
2683
- this.delegatedKeydownHandler
2684
- );
2685
- this.delegatedKeydownHandler = null;
2686
- }
2687
- if (this.clickOutsideHandler) {
2688
- document.removeEventListener("click", this.clickOutsideHandler);
2689
- this.clickOutsideHandler = null;
2690
- }
2691
- this.listenersAttached = false;
2692
- this.engine.destroy();
2693
- this.container.innerHTML = "";
2694
- this.container.classList.remove("kalendly-calendar");
2695
- if (this.props.className) {
2696
- this.container.classList.remove(this.props.className);
1274
+ });
1275
+
1276
+ // src/web-components/index.ts
1277
+ init_CalendarElement();
1278
+ init_core();
1279
+ if (typeof customElements !== "undefined" && !customElements.get("kal-calendar")) {
1280
+ Promise.resolve().then(() => (init_CalendarElement(), CalendarElement_exports)).then(({ CalendarElement: CalendarElement2 }) => {
1281
+ if (!customElements.get("kal-calendar")) {
1282
+ customElements.define("kal-calendar", CalendarElement2);
2697
1283
  }
2698
- }
2699
- };
2700
- function createCalendar(props) {
2701
- return new VanillaCalendar(props);
1284
+ });
2702
1285
  }
2703
-
2704
- // src/index.ts
2705
- var version = "1.0.0";
2706
- var name = "kalendly";
2707
1286
  export {
1287
+ CalendarElement,
2708
1288
  CalendarEngine,
2709
1289
  DAYS,
2710
1290
  DEFAULT_CATEGORY_COLORS,
2711
1291
  MONTHS,
2712
1292
  MONTHS_FULL,
2713
- react_exports as React,
2714
- Calendar as ReactCalendar,
2715
- react_native_exports as ReactNative,
2716
- Calendar2 as ReactNativeCalendar,
2717
- vanilla_exports as Vanilla,
2718
- createCalendar as createVanillaCalendar,
1293
+ defineCalendarElement,
2719
1294
  formatAttendees,
2720
1295
  formatDateForDisplay,
2721
1296
  formatTimeRange,
@@ -2732,9 +1307,7 @@ export {
2732
1307
  isToday,
2733
1308
  isValidHexColor,
2734
1309
  mergeCategoryColors,
2735
- name,
2736
1310
  normalizeDate,
2737
- sortEventsByTime,
2738
- version
1311
+ sortEventsByTime
2739
1312
  };
2740
1313
  //# sourceMappingURL=index.mjs.map