kalendly 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -19
- package/dist/core/index.d.mts +10 -1
- package/dist/core/index.d.ts +10 -1
- package/dist/core/index.js +99 -5
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +91 -5
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +165 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +157 -29
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +148 -28
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +16 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -333,19 +333,20 @@ document.querySelector('kal-calendar').theme = {
|
|
|
333
333
|
|
|
334
334
|
Primitives are set as HTML attributes:
|
|
335
335
|
|
|
336
|
-
| Attribute | Type
|
|
337
|
-
| ----------------------- |
|
|
338
|
-
| `heading` | `string`
|
|
339
|
-
| `title` | `string`
|
|
340
|
-
| `initial-date` | `string`
|
|
341
|
-
| `months` | `"1"\|"2"`
|
|
342
|
-
| `min-year` | `string`
|
|
343
|
-
| `max-year` | `string`
|
|
344
|
-
| `week-starts-on` | `"0"\|"1"`
|
|
345
|
-
| `use-short-month-names` | `string`
|
|
346
|
-
| `availability-mode` | `"day"\|"time"`
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
336
|
+
| Attribute | Type | Default | Description |
|
|
337
|
+
| ----------------------- | ----------------- | ---------------- | -------------------------------------------------- |
|
|
338
|
+
| `heading` | `string` | — | Calendar heading |
|
|
339
|
+
| `title` | `string` | — | **Deprecated** — use `heading` |
|
|
340
|
+
| `initial-date` | `string` | today | ISO date string for initial view |
|
|
341
|
+
| `months` | `"1"\|"2"` | `"1"` | Render two months side by side |
|
|
342
|
+
| `min-year` | `string` | currentYear - 30 | Minimum year in picker |
|
|
343
|
+
| `max-year` | `string` | currentYear + 10 | Maximum year in picker |
|
|
344
|
+
| `week-starts-on` | `"0"\|"1"` | `"0"` | Week start: 0 = Sunday, 1 = Monday |
|
|
345
|
+
| `use-short-month-names` | `string` | — | Present = use abbreviated month names |
|
|
346
|
+
| `availability-mode` | `"day"\|"time"` | — | Hides event details; shows booked/free cells |
|
|
347
|
+
| `slot-duration` | `string` (number) | `"60"` | Time-grid granularity in minutes; must divide 1440 |
|
|
348
|
+
| `selectable` | `"range"` | — | Enables day/slot selection (requires avail mode) |
|
|
349
|
+
| `loading` | `boolean` (flag) | — | Present = render skeleton shimmer cells |
|
|
349
350
|
|
|
350
351
|
## Properties
|
|
351
352
|
|
|
@@ -370,11 +371,12 @@ Rich objects are set as JS properties (not attributes):
|
|
|
370
371
|
|
|
371
372
|
## Custom Events
|
|
372
373
|
|
|
373
|
-
| Event | `detail` shape | Description
|
|
374
|
-
| ------------------------- | ---------------------------------------------------------------------- |
|
|
375
|
-
| `cal-date-select` | `{ date: Date, events: CalendarEvent[] }` | User clicked a date (normal mode)
|
|
376
|
-
| `cal-month-change` | `{ year: number, month: number }` | Fires **before** the new month renders
|
|
377
|
-
| `cal-availability-select` | `{ startDate: Date, endDate: Date }` or `{ date, startTime, endTime }` | Day/slot selected in availability mode
|
|
374
|
+
| Event | `detail` shape | Description |
|
|
375
|
+
| ------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------- |
|
|
376
|
+
| `cal-date-select` | `{ date: Date, events: CalendarEvent[] }` | User clicked a date (normal mode) |
|
|
377
|
+
| `cal-month-change` | `{ year: number, month: number }` | Fires **before** the new month renders |
|
|
378
|
+
| `cal-availability-select` | `{ startDate: Date, endDate: Date }` or `{ date, startTime, endTime }` | Day/slot selected in availability mode |
|
|
379
|
+
| `cal-slot-select` | `{ date: Date, startTime: string, endTime: string, booked: boolean }` | Any time slot clicked, whether or not `selectable` is set |
|
|
378
380
|
|
|
379
381
|
All events bubble and are composed (cross Shadow DOM boundaries).
|
|
380
382
|
|
|
@@ -479,13 +481,81 @@ cal.events = [
|
|
|
479
481
|
<kal-calendar availability-mode="time"></kal-calendar>
|
|
480
482
|
```
|
|
481
483
|
|
|
482
|
-
Clicking a day opens a popup with a
|
|
484
|
+
Clicking a day opens a popup with a grid of slots. Each shows only "Booked" or "Available" — no event name or organiser is ever rendered. A slot is booked when any booking overlaps it.
|
|
485
|
+
|
|
486
|
+
#### Slot length
|
|
487
|
+
|
|
488
|
+
`slot-duration` sets the grid granularity in minutes. It must divide 1440 evenly; anything else falls back to 60 with a console warning.
|
|
489
|
+
|
|
490
|
+
```html
|
|
491
|
+
<kal-calendar availability-mode="time" slot-duration="30"></kal-calendar>
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
The grid renders `1440 / slot-duration` slots, and `cal-availability-select` emits times on that granularity — half-hour slots produce half-hour selections.
|
|
495
|
+
|
|
496
|
+
Precision below `slot-duration` is not representable: a booking from 17:30 on a 60-minute grid marks 17:00–18:00 booked, because that hour cannot be sold. A vendor working in half-hours sets `slot-duration="30"` rather than expecting the grid to subdivide itself.
|
|
497
|
+
|
|
498
|
+
#### Bookings that cross midnight
|
|
499
|
+
|
|
500
|
+
An `endTime` at or before its `startTime` is treated as the next day, so a booking runs as one interval rather than two half-days:
|
|
501
|
+
|
|
502
|
+
```js
|
|
503
|
+
{ id: 1, date: '2026-03-15', startTime: '22:00', endTime: '06:00' }
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
That marks 22:00–24:00 on 15 March and 00:00–06:00 on the 16th. Each day's grid shows the portion of any booking falling on that day.
|
|
507
|
+
|
|
508
|
+
#### A booking with no end time
|
|
509
|
+
|
|
510
|
+
An event with a `startTime` and no `endTime` occupies **one slot**, and the library warns once naming the event. The supported fix is an end time in your data — the duration is a fallback, not a feature.
|
|
511
|
+
|
|
512
|
+
Overlapping bookings merge rather than double-count, so a 09:00–17:00 meeting and a 17:30–22:00 class on the same day mark 09:00–22:00 booked between them.
|
|
483
513
|
|
|
484
514
|
<div align="center">
|
|
485
515
|
<img src="./docs/images/time.png" alt="Availability time view — day popup showing 24 hourly slots coloured red (booked) or green (available)"/>
|
|
486
516
|
<p><em>Time view: clicking a day opens an hourly grid — booked slots in red, available slots in green; no event details exposed</em></p>
|
|
487
517
|
</div>
|
|
488
518
|
|
|
519
|
+
#### Reacting to a slot click
|
|
520
|
+
|
|
521
|
+
`cal-slot-select` fires on every slot click, the way `cal-date-select` fires for
|
|
522
|
+
every day — including booked slots, and whether or not `selectable` is set. Use it
|
|
523
|
+
to drive your own booking flow without turning on range selection:
|
|
524
|
+
|
|
525
|
+
```js
|
|
526
|
+
cal.addEventListener('cal-slot-select', e => {
|
|
527
|
+
const { date, startTime, endTime, booked } = e.detail;
|
|
528
|
+
if (booked) return showTakenMessage(startTime);
|
|
529
|
+
openBookingForm(date, startTime, endTime);
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
`selectable="range"` still governs `cal-availability-select`, the three-click
|
|
534
|
+
range machine and the range highlighting. It also controls the cursor: slots only
|
|
535
|
+
show a pointer when the grid can actually be booked from.
|
|
536
|
+
|
|
537
|
+
#### Round-tripping a selection
|
|
538
|
+
|
|
539
|
+
`cal-availability-select` emits an inclusive `endDate`, so a saved selection goes
|
|
540
|
+
straight back as a single event — no expanding into one event per day:
|
|
541
|
+
|
|
542
|
+
```js
|
|
543
|
+
cal.addEventListener('cal-availability-select', async e => {
|
|
544
|
+
const { startDate, endDate } = e.detail;
|
|
545
|
+
const booking = await save({ startDate, endDate });
|
|
546
|
+
|
|
547
|
+
cal.events = [
|
|
548
|
+
...cal.events,
|
|
549
|
+
{
|
|
550
|
+
id: booking.id,
|
|
551
|
+
date: startDate,
|
|
552
|
+
endDate,
|
|
553
|
+
availabilityStatus: 'blocked',
|
|
554
|
+
},
|
|
555
|
+
];
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
489
559
|
### Selectable range
|
|
490
560
|
|
|
491
561
|
Add `selectable="range"` to let the user pick a free day or time slot:
|
|
@@ -567,6 +637,19 @@ cal.getEngine();
|
|
|
567
637
|
> `mailto:` and relative URLs; anything else becomes `#`. `color` accepts hex
|
|
568
638
|
> values and CSS colour keywords.
|
|
569
639
|
|
|
640
|
+
> **Multi-day events.** `endDate` is the last day of a span and is **inclusive** —
|
|
641
|
+
> `date: '2026-03-03', endDate: '2026-03-05'` covers three days. That matches the
|
|
642
|
+
> `endDate` `cal-availability-select` emits, so a selection can be handed straight
|
|
643
|
+
> back as one event. It deliberately differs from RFC 5545, whose all-day `DTEND`
|
|
644
|
+
> is exclusive; adjust if you map to iCalendar. An `endDate` before `date`, or one
|
|
645
|
+
> that cannot be read, throws and names the event.
|
|
646
|
+
>
|
|
647
|
+
> Times on a span repeat daily: `09:00`–`17:00` across three days means that
|
|
648
|
+
> window on each of the three, not one continuous block. A hall can hold a
|
|
649
|
+
> 09:00–17:00 meeting and a 17:30–22:00 class on overlapping days.
|
|
650
|
+
>
|
|
651
|
+
> `recurring` is declared on the type but not implemented — nothing reads it.
|
|
652
|
+
|
|
570
653
|
> **Custom values.** `status`, `category` and `priority` accept any string. An
|
|
571
654
|
> unrecognised value renders as an uppercased badge with a neutral fill, which
|
|
572
655
|
> you can style via `.badge.status-<your-value>` or recolour through
|
|
@@ -578,6 +661,8 @@ interface CalendarEvent {
|
|
|
578
661
|
name: string;
|
|
579
662
|
date: string | Date;
|
|
580
663
|
|
|
664
|
+
endDate?: string | Date; // last day of a span, inclusive
|
|
665
|
+
|
|
581
666
|
startTime?: string; // e.g. "09:00"
|
|
582
667
|
endTime?: string; // e.g. "10:00"
|
|
583
668
|
allDay?: boolean;
|
package/dist/core/index.d.mts
CHANGED
|
@@ -3,6 +3,7 @@ interface CalendarEvent {
|
|
|
3
3
|
id: string | number;
|
|
4
4
|
name: string;
|
|
5
5
|
date: string | Date;
|
|
6
|
+
endDate?: string | Date;
|
|
6
7
|
startTime?: string;
|
|
7
8
|
endTime?: string;
|
|
8
9
|
allDay?: boolean;
|
|
@@ -145,6 +146,7 @@ declare function normalizeDate(date: Date): Date;
|
|
|
145
146
|
declare function isSameDay(date1: Date, date2: Date): boolean;
|
|
146
147
|
declare function isToday(date: Date): boolean;
|
|
147
148
|
declare function generateYears(minYear?: number, maxYear?: number): number[];
|
|
149
|
+
declare function eventCoversDate(event: CalendarEvent, date: Date): boolean;
|
|
148
150
|
declare function getEventsForDate(events: CalendarEvent[], date: Date): CalendarEvent[];
|
|
149
151
|
declare function hasEvents(events: CalendarEvent[], date: Date): boolean;
|
|
150
152
|
declare function generateCalendarDates(year: number, month: number, events?: CalendarEvent[], weekStartsOn?: 0 | 1): CalendarDate[][];
|
|
@@ -163,6 +165,13 @@ declare function escapeHtml(value: unknown): string;
|
|
|
163
165
|
declare function slugifyToken(value: string): string;
|
|
164
166
|
declare function safeUrl(value: string): string;
|
|
165
167
|
declare function safeColor(value: string): string;
|
|
168
|
+
declare const MINUTES_PER_DAY = 1440;
|
|
169
|
+
declare const DEFAULT_SLOT_DURATION = 60;
|
|
170
|
+
declare function parseTimeToMinutes(time: unknown): number | null;
|
|
171
|
+
declare function formatMinutes(total: number): string;
|
|
172
|
+
declare function eventInterval(event: CalendarEvent, slotDuration: number, onDay: Date): [number, number] | null;
|
|
173
|
+
declare function mergeIntervals(intervals: Array<[number, number]>): Array<[number, number]>;
|
|
174
|
+
declare function bookedSlots(events: CalendarEvent[], date: Date, slotDuration?: number): boolean[];
|
|
166
175
|
|
|
167
176
|
declare class CalendarEngine {
|
|
168
177
|
private state;
|
|
@@ -260,4 +269,4 @@ declare class CalendarEngine {
|
|
|
260
269
|
destroy(): void;
|
|
261
270
|
}
|
|
262
271
|
|
|
263
|
-
export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarPane, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, MONTHS, MONTHS_FULL, escapeHtml, formatAttendees, formatDateForDisplay, formatTimeRange, generateCalendarDates, generateYears, getCategoryColor, getCellClasses, getDefaultEventColor, getEventsForDate, getMonthYearText, hasEvents, isSameDay, isToday, isValidHexColor, mergeCategoryColors, normalizeDate, safeColor, safeUrl, slugifyToken, sortEventsByTime };
|
|
272
|
+
export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarPane, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, DEFAULT_SLOT_DURATION, MINUTES_PER_DAY, MONTHS, MONTHS_FULL, bookedSlots, escapeHtml, eventCoversDate, eventInterval, formatAttendees, formatDateForDisplay, formatMinutes, formatTimeRange, generateCalendarDates, generateYears, getCategoryColor, getCellClasses, getDefaultEventColor, getEventsForDate, getMonthYearText, hasEvents, isSameDay, isToday, isValidHexColor, mergeCategoryColors, mergeIntervals, normalizeDate, parseTimeToMinutes, safeColor, safeUrl, slugifyToken, sortEventsByTime };
|
package/dist/core/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ interface CalendarEvent {
|
|
|
3
3
|
id: string | number;
|
|
4
4
|
name: string;
|
|
5
5
|
date: string | Date;
|
|
6
|
+
endDate?: string | Date;
|
|
6
7
|
startTime?: string;
|
|
7
8
|
endTime?: string;
|
|
8
9
|
allDay?: boolean;
|
|
@@ -145,6 +146,7 @@ declare function normalizeDate(date: Date): Date;
|
|
|
145
146
|
declare function isSameDay(date1: Date, date2: Date): boolean;
|
|
146
147
|
declare function isToday(date: Date): boolean;
|
|
147
148
|
declare function generateYears(minYear?: number, maxYear?: number): number[];
|
|
149
|
+
declare function eventCoversDate(event: CalendarEvent, date: Date): boolean;
|
|
148
150
|
declare function getEventsForDate(events: CalendarEvent[], date: Date): CalendarEvent[];
|
|
149
151
|
declare function hasEvents(events: CalendarEvent[], date: Date): boolean;
|
|
150
152
|
declare function generateCalendarDates(year: number, month: number, events?: CalendarEvent[], weekStartsOn?: 0 | 1): CalendarDate[][];
|
|
@@ -163,6 +165,13 @@ declare function escapeHtml(value: unknown): string;
|
|
|
163
165
|
declare function slugifyToken(value: string): string;
|
|
164
166
|
declare function safeUrl(value: string): string;
|
|
165
167
|
declare function safeColor(value: string): string;
|
|
168
|
+
declare const MINUTES_PER_DAY = 1440;
|
|
169
|
+
declare const DEFAULT_SLOT_DURATION = 60;
|
|
170
|
+
declare function parseTimeToMinutes(time: unknown): number | null;
|
|
171
|
+
declare function formatMinutes(total: number): string;
|
|
172
|
+
declare function eventInterval(event: CalendarEvent, slotDuration: number, onDay: Date): [number, number] | null;
|
|
173
|
+
declare function mergeIntervals(intervals: Array<[number, number]>): Array<[number, number]>;
|
|
174
|
+
declare function bookedSlots(events: CalendarEvent[], date: Date, slotDuration?: number): boolean[];
|
|
166
175
|
|
|
167
176
|
declare class CalendarEngine {
|
|
168
177
|
private state;
|
|
@@ -260,4 +269,4 @@ declare class CalendarEngine {
|
|
|
260
269
|
destroy(): void;
|
|
261
270
|
}
|
|
262
271
|
|
|
263
|
-
export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarPane, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, MONTHS, MONTHS_FULL, escapeHtml, formatAttendees, formatDateForDisplay, formatTimeRange, generateCalendarDates, generateYears, getCategoryColor, getCellClasses, getDefaultEventColor, getEventsForDate, getMonthYearText, hasEvents, isSameDay, isToday, isValidHexColor, mergeCategoryColors, normalizeDate, safeColor, safeUrl, slugifyToken, sortEventsByTime };
|
|
272
|
+
export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarPane, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, DEFAULT_SLOT_DURATION, MINUTES_PER_DAY, MONTHS, MONTHS_FULL, bookedSlots, escapeHtml, eventCoversDate, eventInterval, formatAttendees, formatDateForDisplay, formatMinutes, formatTimeRange, generateCalendarDates, generateYears, getCategoryColor, getCellClasses, getDefaultEventColor, getEventsForDate, getMonthYearText, hasEvents, isSameDay, isToday, isValidHexColor, mergeCategoryColors, mergeIntervals, normalizeDate, parseTimeToMinutes, safeColor, safeUrl, slugifyToken, sortEventsByTime };
|
package/dist/core/index.js
CHANGED
|
@@ -23,11 +23,17 @@ __export(core_exports, {
|
|
|
23
23
|
CalendarEngine: () => CalendarEngine,
|
|
24
24
|
DAYS: () => DAYS,
|
|
25
25
|
DEFAULT_CATEGORY_COLORS: () => DEFAULT_CATEGORY_COLORS,
|
|
26
|
+
DEFAULT_SLOT_DURATION: () => DEFAULT_SLOT_DURATION,
|
|
27
|
+
MINUTES_PER_DAY: () => MINUTES_PER_DAY,
|
|
26
28
|
MONTHS: () => MONTHS,
|
|
27
29
|
MONTHS_FULL: () => MONTHS_FULL,
|
|
30
|
+
bookedSlots: () => bookedSlots,
|
|
28
31
|
escapeHtml: () => escapeHtml,
|
|
32
|
+
eventCoversDate: () => eventCoversDate,
|
|
33
|
+
eventInterval: () => eventInterval,
|
|
29
34
|
formatAttendees: () => formatAttendees,
|
|
30
35
|
formatDateForDisplay: () => formatDateForDisplay,
|
|
36
|
+
formatMinutes: () => formatMinutes,
|
|
31
37
|
formatTimeRange: () => formatTimeRange,
|
|
32
38
|
generateCalendarDates: () => generateCalendarDates,
|
|
33
39
|
generateYears: () => generateYears,
|
|
@@ -41,7 +47,9 @@ __export(core_exports, {
|
|
|
41
47
|
isToday: () => isToday,
|
|
42
48
|
isValidHexColor: () => isValidHexColor,
|
|
43
49
|
mergeCategoryColors: () => mergeCategoryColors,
|
|
50
|
+
mergeIntervals: () => mergeIntervals,
|
|
44
51
|
normalizeDate: () => normalizeDate,
|
|
52
|
+
parseTimeToMinutes: () => parseTimeToMinutes,
|
|
45
53
|
safeColor: () => safeColor,
|
|
46
54
|
safeUrl: () => safeUrl,
|
|
47
55
|
slugifyToken: () => slugifyToken,
|
|
@@ -102,12 +110,28 @@ function generateYears(minYear, maxYear) {
|
|
|
102
110
|
const max = maxYear ?? currentYear + 10;
|
|
103
111
|
return Array.from({ length: max - min + 1 }, (_, i) => min + i);
|
|
104
112
|
}
|
|
113
|
+
function eventCoversDate(event, date) {
|
|
114
|
+
const start = normalizeDate(new Date(event.date)).getTime();
|
|
115
|
+
const target = normalizeDate(date).getTime();
|
|
116
|
+
if (Number.isNaN(start)) return false;
|
|
117
|
+
if (event.endDate === void 0 || event.endDate === null) {
|
|
118
|
+
return start === target;
|
|
119
|
+
}
|
|
120
|
+
const end = normalizeDate(new Date(event.endDate)).getTime();
|
|
121
|
+
if (Number.isNaN(end)) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`<kal-calendar> event ${event.id} has an unreadable endDate: ${String(event.endDate)}.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (end < start) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`<kal-calendar> event ${event.id} has an endDate before its date: ${String(event.endDate)} < ${String(event.date)}.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return target >= start && target <= end;
|
|
132
|
+
}
|
|
105
133
|
function getEventsForDate(events, date) {
|
|
106
|
-
|
|
107
|
-
return events.filter((event) => {
|
|
108
|
-
const eventDate = normalizeDate(new Date(event.date));
|
|
109
|
-
return eventDate.getTime() === normalizedTargetDate.getTime();
|
|
110
|
-
});
|
|
134
|
+
return events.filter((event) => eventCoversDate(event, date));
|
|
111
135
|
}
|
|
112
136
|
function hasEvents(events, date) {
|
|
113
137
|
return getEventsForDate(events, date).length > 0;
|
|
@@ -266,6 +290,68 @@ function safeColor(value) {
|
|
|
266
290
|
const trimmed = String(value).trim();
|
|
267
291
|
return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed) ? trimmed : "#3b82f6";
|
|
268
292
|
}
|
|
293
|
+
var MINUTES_PER_DAY = 1440;
|
|
294
|
+
var DEFAULT_SLOT_DURATION = 60;
|
|
295
|
+
function parseTimeToMinutes(time) {
|
|
296
|
+
if (typeof time !== "string") return null;
|
|
297
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(time.trim());
|
|
298
|
+
if (!match) return null;
|
|
299
|
+
const hours = Number(match[1]);
|
|
300
|
+
const mins = Number(match[2]);
|
|
301
|
+
if (hours > 24 || mins > 59 || hours === 24 && mins > 0) return null;
|
|
302
|
+
return hours * 60 + mins;
|
|
303
|
+
}
|
|
304
|
+
function formatMinutes(total) {
|
|
305
|
+
const hours = Math.floor(total / 60);
|
|
306
|
+
const mins = total % 60;
|
|
307
|
+
return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
|
|
308
|
+
}
|
|
309
|
+
function eventInterval(event, slotDuration, onDay) {
|
|
310
|
+
const day = normalizeDate(onDay).getTime();
|
|
311
|
+
if (Number.isNaN(day)) return null;
|
|
312
|
+
const base = day / 6e4;
|
|
313
|
+
const wholeDay = [base, base + MINUTES_PER_DAY];
|
|
314
|
+
if (event.allDay || !event.startTime) return wholeDay;
|
|
315
|
+
const from = parseTimeToMinutes(event.startTime);
|
|
316
|
+
if (from === null) return wholeDay;
|
|
317
|
+
if (event.endTime === void 0 || event.endTime === null) {
|
|
318
|
+
return [base + from, base + from + slotDuration];
|
|
319
|
+
}
|
|
320
|
+
const to = parseTimeToMinutes(event.endTime);
|
|
321
|
+
if (to === null) return wholeDay;
|
|
322
|
+
return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];
|
|
323
|
+
}
|
|
324
|
+
function mergeIntervals(intervals) {
|
|
325
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
326
|
+
return sorted.reduce((merged, [start, end]) => {
|
|
327
|
+
const last = merged[merged.length - 1];
|
|
328
|
+
if (last && start <= last[1]) last[1] = Math.max(last[1], end);
|
|
329
|
+
else merged.push([start, end]);
|
|
330
|
+
return merged;
|
|
331
|
+
}, []);
|
|
332
|
+
}
|
|
333
|
+
function bookedSlots(events, date, slotDuration = DEFAULT_SLOT_DURATION) {
|
|
334
|
+
const base = normalizeDate(date).getTime() / 6e4;
|
|
335
|
+
const previousDay = new Date(date);
|
|
336
|
+
previousDay.setDate(previousDay.getDate() - 1);
|
|
337
|
+
const intervals = [];
|
|
338
|
+
for (const event of new Set(events)) {
|
|
339
|
+
for (const day of [previousDay, date]) {
|
|
340
|
+
if (!eventCoversDate(event, day)) continue;
|
|
341
|
+
const interval = eventInterval(event, slotDuration, day);
|
|
342
|
+
if (interval) intervals.push(interval);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const merged = mergeIntervals(intervals);
|
|
346
|
+
return Array.from(
|
|
347
|
+
{ length: Math.floor(MINUTES_PER_DAY / slotDuration) },
|
|
348
|
+
(_, index) => {
|
|
349
|
+
const start = base + index * slotDuration;
|
|
350
|
+
const end = start + slotDuration;
|
|
351
|
+
return merged.some(([from, to]) => start < to && end > from);
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
}
|
|
269
355
|
|
|
270
356
|
// src/core/calendar-engine.ts
|
|
271
357
|
var CalendarEngine = class {
|
|
@@ -513,11 +599,17 @@ var CalendarEngine = class {
|
|
|
513
599
|
CalendarEngine,
|
|
514
600
|
DAYS,
|
|
515
601
|
DEFAULT_CATEGORY_COLORS,
|
|
602
|
+
DEFAULT_SLOT_DURATION,
|
|
603
|
+
MINUTES_PER_DAY,
|
|
516
604
|
MONTHS,
|
|
517
605
|
MONTHS_FULL,
|
|
606
|
+
bookedSlots,
|
|
518
607
|
escapeHtml,
|
|
608
|
+
eventCoversDate,
|
|
609
|
+
eventInterval,
|
|
519
610
|
formatAttendees,
|
|
520
611
|
formatDateForDisplay,
|
|
612
|
+
formatMinutes,
|
|
521
613
|
formatTimeRange,
|
|
522
614
|
generateCalendarDates,
|
|
523
615
|
generateYears,
|
|
@@ -531,7 +623,9 @@ var CalendarEngine = class {
|
|
|
531
623
|
isToday,
|
|
532
624
|
isValidHexColor,
|
|
533
625
|
mergeCategoryColors,
|
|
626
|
+
mergeIntervals,
|
|
534
627
|
normalizeDate,
|
|
628
|
+
parseTimeToMinutes,
|
|
535
629
|
safeColor,
|
|
536
630
|
safeUrl,
|
|
537
631
|
slugifyToken,
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/index.ts","../../src/core/utils.ts","../../src/core/calendar-engine.ts"],"sourcesContent":["export * from './types';\nexport * from './utils';\nexport { CalendarEngine } from './calendar-engine';\n\n// Re-export commonly used utilities\nexport {\n MONTHS,\n MONTHS_FULL,\n DAYS,\n normalizeDate,\n isSameDay,\n isToday,\n generateYears,\n getEventsForDate,\n hasEvents,\n generateCalendarDates,\n getCellClasses,\n formatDateForDisplay,\n getMonthYearText,\n} from './utils';\n","import { CalendarEvent, CalendarDate, CategoryColorMap } from './types';\n\nexport const MONTHS = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\nexport const MONTHS_FULL = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\n\nexport const DAYS = [\n 'Sunday',\n 'Monday',\n 'Tuesday',\n 'Wednesday',\n 'Thursday',\n 'Friday',\n 'Saturday',\n];\n\nexport function normalizeDate(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);\n}\n\nexport function isSameDay(date1: Date, date2: Date): boolean {\n return normalizeDate(date1).getTime() === normalizeDate(date2).getTime();\n}\n\nexport function isToday(date: Date): boolean {\n return isSameDay(date, new Date());\n}\n\nexport function generateYears(minYear?: number, maxYear?: number): number[] {\n const currentYear = new Date().getFullYear();\n const min = minYear ?? currentYear - 30;\n const max = maxYear ?? currentYear + 10;\n\n return Array.from({ length: max - min + 1 }, (_, i) => min + i);\n}\n\nexport function getEventsForDate(\n events: CalendarEvent[],\n date: Date\n): CalendarEvent[] {\n const normalizedTargetDate = normalizeDate(date);\n\n return events.filter(event => {\n const eventDate = normalizeDate(new Date(event.date));\n return eventDate.getTime() === normalizedTargetDate.getTime();\n });\n}\n\nexport function hasEvents(events: CalendarEvent[], date: Date): boolean {\n return getEventsForDate(events, date).length > 0;\n}\n\nexport function generateCalendarDates(\n year: number,\n month: number,\n events: CalendarEvent[] = [],\n weekStartsOn: 0 | 1 = 0\n): CalendarDate[][] {\n const firstDay = new Date(year, month, 1);\n const lastDay = new Date(year, month + 1, 0);\n const daysInMonth = lastDay.getDate();\n const prevMonthLastDay = new Date(year, month, 0).getDate();\n\n let firstDayOfWeek = firstDay.getDay();\n if (weekStartsOn === 1) {\n firstDayOfWeek = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1;\n }\n\n const dates: CalendarDate[][] = [];\n let day = 1;\n let nextMonthDay = 1;\n\n for (let week = 0; week < 6; week++) {\n const weekDates: CalendarDate[] = [];\n\n for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {\n if (week === 0 && dayOfWeek < firstDayOfWeek) {\n // Previous month days\n const prevDay = prevMonthLastDay - firstDayOfWeek + dayOfWeek + 1;\n const prevDate = new Date(year, month - 1, prevDay);\n const dateEvents = getEventsForDate(events, prevDate);\n weekDates.push({\n date: prevDate,\n isCurrentMonth: false,\n isToday: isToday(prevDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n } else if (day > daysInMonth) {\n // Next month days\n const nextDate = new Date(year, month + 1, nextMonthDay);\n const dateEvents = getEventsForDate(events, nextDate);\n weekDates.push({\n date: nextDate,\n isCurrentMonth: false,\n isToday: isToday(nextDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n nextMonthDay++;\n } else {\n // Current month days\n const currentDate = new Date(year, month, day);\n const dateEvents = getEventsForDate(events, currentDate);\n weekDates.push({\n date: currentDate,\n isCurrentMonth: true,\n isToday: isToday(currentDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n day++;\n }\n }\n\n dates.push(weekDates);\n }\n\n return dates;\n}\n\nexport function getCellClasses(calendarDate: CalendarDate): string[] {\n const classes: string[] = [];\n\n if (!calendarDate.isCurrentMonth) {\n classes.push('calendar-cell-other-month');\n }\n\n if (calendarDate.isToday) {\n classes.push('calendar-cell-today');\n }\n\n if (calendarDate.hasEvents) {\n classes.push('calendar-cell-has-event');\n }\n\n return classes;\n}\n\nexport function formatDateForDisplay(date: Date): string {\n return `${DAYS[date.getDay()]} ${date.getDate()}`;\n}\n\nexport function getMonthYearText(year: number, month: number): string {\n return `${MONTHS_FULL[month]} ${year}`;\n}\n\nexport function formatTimeRange(event: CalendarEvent): string {\n if (event.allDay || (!event.startTime && !event.endTime)) {\n return 'All day';\n }\n\n if (event.startTime && event.endTime) {\n return `${event.startTime} - ${event.endTime}`;\n }\n\n if (event.startTime) {\n return `${event.startTime}`;\n }\n\n return '';\n}\n\nexport function formatAttendees(attendees?: string[]): string {\n if (!attendees || attendees.length === 0) return '';\n\n if (attendees.length === 1) return attendees[0];\n if (attendees.length === 2) return attendees.join(' and ');\n\n return `${attendees.slice(0, -1).join(', ')}, and ${attendees[attendees.length - 1]}`;\n}\n\nexport function sortEventsByTime(events: CalendarEvent[]): CalendarEvent[] {\n return [...events].sort((a, b) => {\n const aAllDay = a.allDay || (!a.startTime && !a.endTime);\n const bAllDay = b.allDay || (!b.startTime && !b.endTime);\n\n if (aAllDay && !bAllDay) return -1;\n if (!aAllDay && bAllDay) return 1;\n\n if (!a.startTime || !b.startTime) return 0;\n return a.startTime.localeCompare(b.startTime);\n });\n}\n\nexport const DEFAULT_CATEGORY_COLORS: CategoryColorMap = {\n work: '#3b82f6',\n personal: '#8b5cf6',\n meeting: '#10b981',\n deadline: '#ef4444',\n appointment: '#f59e0b',\n other: '#6b7280',\n};\n\n// Dynamic function that accepts custom colors\nexport function getDefaultEventColor(\n category?: string,\n customColors?: CategoryColorMap\n): string {\n const colorMap = customColors || DEFAULT_CATEGORY_COLORS;\n\n if (category && colorMap[category]) {\n return colorMap[category];\n }\n\n // Default fallback color\n return '#fc8917';\n}\n\n// Helper to merge custom colors with defaults\nexport function mergeCategoryColors(\n customColors?: CategoryColorMap\n): CategoryColorMap {\n return {\n ...DEFAULT_CATEGORY_COLORS,\n ...customColors,\n };\n}\n\n// Validate if a color is a valid hex color\nexport function isValidHexColor(color: string): boolean {\n return /^#([0-9A-F]{3}){1,2}$/i.test(color);\n}\n\n// Get color for a category with validation\nexport function getCategoryColor(\n category: string,\n customColors?: CategoryColorMap\n): string {\n const colorMap = mergeCategoryColors(customColors);\n const color = colorMap[category] || colorMap.other || '#fc8917';\n\n return isValidHexColor(color) ? color : '#fc8917';\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n// Escape a value for interpolation into HTML text or a quoted attribute\nexport function escapeHtml(value: unknown): string {\n if (value === null || value === undefined) return '';\n\n return String(value).replace(/[&<>\"']/g, char => HTML_ESCAPES[char]);\n}\n\n// Reduce a caller-supplied value to a single safe CSS class token\nexport function slugifyToken(value: string): string {\n return String(value)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n// Allow http(s), mailto and relative URLs; anything else (javascript:, data:,\n// vbscript:) collapses to a harmless anchor\nexport function safeUrl(value: string): string {\n // Browsers ignore control characters when resolving a scheme, so\n // `java\\0script:` and `java\\tscript:` both navigate. Strip them before\n // testing rather than after.\n const normalized = String(value)\n .split('')\n .filter(char => char.charCodeAt(0) > 0x20)\n .join('');\n\n if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;\n\n // Relative URLs have no scheme at all — a colon before the first slash,\n // question mark or hash means someone is naming one\n const schemeless = !/^[^/?#]*:/.test(normalized);\n\n return schemeless ? normalized : '#';\n}\n\n// Accept hex colors and bare CSS color keywords; reject anything that could\n// terminate the declaration or escape the style attribute\nexport function safeColor(value: string): string {\n const trimmed = String(value).trim();\n\n return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed)\n ? trimmed\n : '#3b82f6';\n}\n","import {\n CalendarEvent,\n CalendarState,\n CalendarConfig,\n CalendarActions,\n CalendarViewModel,\n CalendarPane,\n CategoryColorMap,\n} from './types';\nimport {\n generateCalendarDates,\n getEventsForDate,\n generateYears,\n getMonthYearText,\n formatDateForDisplay,\n mergeCategoryColors,\n getCategoryColor,\n MONTHS,\n DAYS,\n} from './utils';\n\nexport class CalendarEngine {\n private state: CalendarState;\n private config: CalendarConfig;\n private listeners: Set<() => void> = new Set();\n private categoryColors: CategoryColorMap;\n\n constructor(config: CalendarConfig) {\n this.config = config;\n this.categoryColors = mergeCategoryColors(config.categoryColors);\n\n const initialDate = config.initialDate || new Date();\n this.state = {\n currentYear: initialDate.getFullYear(),\n currentMonth: initialDate.getMonth(),\n currentDate: initialDate.getDate(),\n selectedDate: null,\n selectedDayIndex: null,\n tasks: [],\n };\n }\n\n /**\n * Subscribe to state changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Notify all listeners of state changes\n */\n private notify(): void {\n this.listeners.forEach(listener => listener());\n }\n\n /**\n * Get current state\n */\n getState(): CalendarState {\n return { ...this.state };\n }\n\n /**\n * Get view model with computed properties\n */\n getViewModel(): CalendarViewModel {\n const panes: CalendarPane[] = Array.from(\n { length: Math.max(1, this.config.monthCount ?? 1) },\n (_, offset) => {\n const anchor = new Date(\n this.state.currentYear,\n this.state.currentMonth + offset,\n 1\n );\n const year = anchor.getFullYear();\n const month = anchor.getMonth();\n\n return {\n year,\n month,\n monthAndYearText: getMonthYearText(year, month),\n calendarDates: generateCalendarDates(\n year,\n month,\n this.config.events,\n this.config.weekStartsOn\n ),\n };\n }\n );\n\n return {\n ...this.state,\n months: MONTHS,\n days: DAYS,\n years: generateYears(this.config.minYear, this.config.maxYear),\n monthAndYearText: panes[0].monthAndYearText,\n scheduleDay: this.state.selectedDate\n ? formatDateForDisplay(this.state.selectedDate)\n : '',\n panes,\n calendarDates: panes[0].calendarDates,\n };\n }\n\n /**\n * Get actions object\n */\n getActions(): CalendarActions {\n return {\n next: this.next.bind(this),\n previous: this.previous.bind(this),\n jump: this.jump.bind(this),\n goToToday: this.goToToday.bind(this),\n isCurrentMonth: this.isCurrentMonth.bind(this),\n selectDate: this.selectDate.bind(this),\n updateTasks: this.updateTasks.bind(this),\n };\n }\n\n /**\n * Navigate to next month\n */\n private next(): void {\n if (this.state.currentMonth === 11) {\n this.state.currentMonth = 0;\n this.state.currentYear++;\n } else {\n this.state.currentMonth++;\n }\n\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Navigate to previous month\n */\n private previous(): void {\n if (this.state.currentMonth === 0) {\n this.state.currentMonth = 11;\n this.state.currentYear--;\n } else {\n this.state.currentMonth--;\n }\n\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Jump to specific month and year\n */\n private jump(year: number, month: number): void {\n this.state.currentYear = year;\n this.state.currentMonth = month;\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Navigate to current month (today)\n */\n private goToToday(): void {\n const today = new Date();\n this.state.currentYear = today.getFullYear();\n this.state.currentMonth = today.getMonth();\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Check if currently viewing today's month\n */\n private isCurrentMonth(): boolean {\n const today = new Date();\n return (\n this.state.currentYear === today.getFullYear() &&\n this.state.currentMonth === today.getMonth()\n );\n }\n\n /**\n * Select a specific date\n */\n private selectDate(date: Date, dayIndex?: number): void {\n this.state.selectedDate = date;\n this.state.selectedDayIndex = dayIndex ?? null;\n this.state.currentDate = date.getDate();\n this.state.currentMonth = date.getMonth();\n this.state.currentYear = date.getFullYear();\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Update tasks for the currently selected date\n */\n private updateTasks(): void {\n if (!this.state.selectedDate) {\n this.state.tasks = [];\n return;\n }\n\n this.state.tasks = getEventsForDate(\n this.config.events,\n this.state.selectedDate\n );\n }\n\n /**\n * Update events configuration\n */\n updateEvents(events: CalendarEvent[]): void {\n this.config.events = events;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Handle date cell click\n */\n handleDateClick(date: Date, dayIndex?: number): void {\n this.selectDate(date, dayIndex);\n }\n\n /**\n * Check if date has events\n */\n hasEventsForDate(date: Date): boolean {\n return getEventsForDate(this.config.events, date).length > 0;\n }\n\n /**\n * Get events for a specific date\n */\n getEventsForDate(date: Date): CalendarEvent[] {\n return getEventsForDate(this.config.events, date);\n }\n\n /**\n * Clear selected date\n */\n clearSelection(): void {\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.state.tasks = [];\n this.notify();\n }\n\n /**\n * Get category color (with custom colors support)\n */\n getCategoryColor(category?: string): string {\n if (!category) return '#fc8917';\n return getCategoryColor(category, this.categoryColors);\n }\n\n /**\n * Update category colors dynamically\n */\n updateCategoryColors(colors: CategoryColorMap): void {\n this.categoryColors = mergeCategoryColors(colors);\n this.notify();\n }\n\n /**\n * Get all category colors\n */\n getCategoryColors(): CategoryColorMap {\n return { ...this.categoryColors };\n }\n\n /**\n * Register a new category with color\n */\n registerCategory(name: string, color: string): void {\n this.categoryColors[name] = color;\n this.notify();\n }\n\n /**\n * Destroy the engine and cleanup listeners\n */\n destroy(): void {\n this.listeners.clear();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/index.ts","../../src/core/utils.ts","../../src/core/calendar-engine.ts"],"sourcesContent":["export * from './types';\nexport * from './utils';\nexport { CalendarEngine } from './calendar-engine';\n\n// Re-export commonly used utilities\nexport {\n MONTHS,\n MONTHS_FULL,\n DAYS,\n normalizeDate,\n isSameDay,\n isToday,\n generateYears,\n getEventsForDate,\n hasEvents,\n generateCalendarDates,\n getCellClasses,\n formatDateForDisplay,\n getMonthYearText,\n} from './utils';\n","import { CalendarEvent, CalendarDate, CategoryColorMap } from './types';\n\nexport const MONTHS = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\nexport const MONTHS_FULL = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\n\nexport const DAYS = [\n 'Sunday',\n 'Monday',\n 'Tuesday',\n 'Wednesday',\n 'Thursday',\n 'Friday',\n 'Saturday',\n];\n\nexport function normalizeDate(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);\n}\n\nexport function isSameDay(date1: Date, date2: Date): boolean {\n return normalizeDate(date1).getTime() === normalizeDate(date2).getTime();\n}\n\nexport function isToday(date: Date): boolean {\n return isSameDay(date, new Date());\n}\n\nexport function generateYears(minYear?: number, maxYear?: number): number[] {\n const currentYear = new Date().getFullYear();\n const min = minYear ?? currentYear - 30;\n const max = maxYear ?? currentYear + 10;\n\n return Array.from({ length: max - min + 1 }, (_, i) => min + i);\n}\n\n// endDate is inclusive, matching the endDate cal-availability-select emits, so\n// a selection can be handed straight back as one event.\nexport function eventCoversDate(event: CalendarEvent, date: Date): boolean {\n const start = normalizeDate(new Date(event.date)).getTime();\n const target = normalizeDate(date).getTime();\n if (Number.isNaN(start)) return false;\n\n if (event.endDate === undefined || event.endDate === null) {\n return start === target;\n }\n\n const end = normalizeDate(new Date(event.endDate)).getTime();\n if (Number.isNaN(end)) {\n throw new Error(\n `<kal-calendar> event ${event.id} has an unreadable endDate: ` +\n `${String(event.endDate)}.`\n );\n }\n if (end < start) {\n throw new Error(\n `<kal-calendar> event ${event.id} has an endDate before its date: ` +\n `${String(event.endDate)} < ${String(event.date)}.`\n );\n }\n\n return target >= start && target <= end;\n}\n\nexport function getEventsForDate(\n events: CalendarEvent[],\n date: Date\n): CalendarEvent[] {\n return events.filter(event => eventCoversDate(event, date));\n}\n\nexport function hasEvents(events: CalendarEvent[], date: Date): boolean {\n return getEventsForDate(events, date).length > 0;\n}\n\nexport function generateCalendarDates(\n year: number,\n month: number,\n events: CalendarEvent[] = [],\n weekStartsOn: 0 | 1 = 0\n): CalendarDate[][] {\n const firstDay = new Date(year, month, 1);\n const lastDay = new Date(year, month + 1, 0);\n const daysInMonth = lastDay.getDate();\n const prevMonthLastDay = new Date(year, month, 0).getDate();\n\n let firstDayOfWeek = firstDay.getDay();\n if (weekStartsOn === 1) {\n firstDayOfWeek = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1;\n }\n\n const dates: CalendarDate[][] = [];\n let day = 1;\n let nextMonthDay = 1;\n\n for (let week = 0; week < 6; week++) {\n const weekDates: CalendarDate[] = [];\n\n for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {\n if (week === 0 && dayOfWeek < firstDayOfWeek) {\n // Previous month days\n const prevDay = prevMonthLastDay - firstDayOfWeek + dayOfWeek + 1;\n const prevDate = new Date(year, month - 1, prevDay);\n const dateEvents = getEventsForDate(events, prevDate);\n weekDates.push({\n date: prevDate,\n isCurrentMonth: false,\n isToday: isToday(prevDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n } else if (day > daysInMonth) {\n // Next month days\n const nextDate = new Date(year, month + 1, nextMonthDay);\n const dateEvents = getEventsForDate(events, nextDate);\n weekDates.push({\n date: nextDate,\n isCurrentMonth: false,\n isToday: isToday(nextDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n nextMonthDay++;\n } else {\n // Current month days\n const currentDate = new Date(year, month, day);\n const dateEvents = getEventsForDate(events, currentDate);\n weekDates.push({\n date: currentDate,\n isCurrentMonth: true,\n isToday: isToday(currentDate),\n hasEvents: dateEvents.length > 0,\n events: dateEvents,\n });\n day++;\n }\n }\n\n dates.push(weekDates);\n }\n\n return dates;\n}\n\nexport function getCellClasses(calendarDate: CalendarDate): string[] {\n const classes: string[] = [];\n\n if (!calendarDate.isCurrentMonth) {\n classes.push('calendar-cell-other-month');\n }\n\n if (calendarDate.isToday) {\n classes.push('calendar-cell-today');\n }\n\n if (calendarDate.hasEvents) {\n classes.push('calendar-cell-has-event');\n }\n\n return classes;\n}\n\nexport function formatDateForDisplay(date: Date): string {\n return `${DAYS[date.getDay()]} ${date.getDate()}`;\n}\n\nexport function getMonthYearText(year: number, month: number): string {\n return `${MONTHS_FULL[month]} ${year}`;\n}\n\nexport function formatTimeRange(event: CalendarEvent): string {\n if (event.allDay || (!event.startTime && !event.endTime)) {\n return 'All day';\n }\n\n if (event.startTime && event.endTime) {\n return `${event.startTime} - ${event.endTime}`;\n }\n\n if (event.startTime) {\n return `${event.startTime}`;\n }\n\n return '';\n}\n\nexport function formatAttendees(attendees?: string[]): string {\n if (!attendees || attendees.length === 0) return '';\n\n if (attendees.length === 1) return attendees[0];\n if (attendees.length === 2) return attendees.join(' and ');\n\n return `${attendees.slice(0, -1).join(', ')}, and ${attendees[attendees.length - 1]}`;\n}\n\nexport function sortEventsByTime(events: CalendarEvent[]): CalendarEvent[] {\n return [...events].sort((a, b) => {\n const aAllDay = a.allDay || (!a.startTime && !a.endTime);\n const bAllDay = b.allDay || (!b.startTime && !b.endTime);\n\n if (aAllDay && !bAllDay) return -1;\n if (!aAllDay && bAllDay) return 1;\n\n if (!a.startTime || !b.startTime) return 0;\n return a.startTime.localeCompare(b.startTime);\n });\n}\n\nexport const DEFAULT_CATEGORY_COLORS: CategoryColorMap = {\n work: '#3b82f6',\n personal: '#8b5cf6',\n meeting: '#10b981',\n deadline: '#ef4444',\n appointment: '#f59e0b',\n other: '#6b7280',\n};\n\n// Dynamic function that accepts custom colors\nexport function getDefaultEventColor(\n category?: string,\n customColors?: CategoryColorMap\n): string {\n const colorMap = customColors || DEFAULT_CATEGORY_COLORS;\n\n if (category && colorMap[category]) {\n return colorMap[category];\n }\n\n // Default fallback color\n return '#fc8917';\n}\n\n// Helper to merge custom colors with defaults\nexport function mergeCategoryColors(\n customColors?: CategoryColorMap\n): CategoryColorMap {\n return {\n ...DEFAULT_CATEGORY_COLORS,\n ...customColors,\n };\n}\n\n// Validate if a color is a valid hex color\nexport function isValidHexColor(color: string): boolean {\n return /^#([0-9A-F]{3}){1,2}$/i.test(color);\n}\n\n// Get color for a category with validation\nexport function getCategoryColor(\n category: string,\n customColors?: CategoryColorMap\n): string {\n const colorMap = mergeCategoryColors(customColors);\n const color = colorMap[category] || colorMap.other || '#fc8917';\n\n return isValidHexColor(color) ? color : '#fc8917';\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n};\n\n// Escape a value for interpolation into HTML text or a quoted attribute\nexport function escapeHtml(value: unknown): string {\n if (value === null || value === undefined) return '';\n\n return String(value).replace(/[&<>\"']/g, char => HTML_ESCAPES[char]);\n}\n\n// Reduce a caller-supplied value to a single safe CSS class token\nexport function slugifyToken(value: string): string {\n return String(value)\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n// Allow http(s), mailto and relative URLs; anything else (javascript:, data:,\n// vbscript:) collapses to a harmless anchor\nexport function safeUrl(value: string): string {\n // Browsers ignore control characters when resolving a scheme, so\n // `java\\0script:` and `java\\tscript:` both navigate. Strip them before\n // testing rather than after.\n const normalized = String(value)\n .split('')\n .filter(char => char.charCodeAt(0) > 0x20)\n .join('');\n\n if (/^(?:https?:|mailto:)/i.test(normalized)) return normalized;\n\n // Relative URLs have no scheme at all — a colon before the first slash,\n // question mark or hash means someone is naming one\n const schemeless = !/^[^/?#]*:/.test(normalized);\n\n return schemeless ? normalized : '#';\n}\n\n// Accept hex colors and bare CSS color keywords; reject anything that could\n// terminate the declaration or escape the style attribute\nexport function safeColor(value: string): string {\n const trimmed = String(value).trim();\n\n return isValidHexColor(trimmed) || /^[a-z]+$/i.test(trimmed)\n ? trimmed\n : '#3b82f6';\n}\n\nexport const MINUTES_PER_DAY = 1440;\nexport const DEFAULT_SLOT_DURATION = 60;\n\nexport function parseTimeToMinutes(time: unknown): number | null {\n if (typeof time !== 'string') return null;\n\n const match = /^(\\d{1,2}):(\\d{2})$/.exec(time.trim());\n if (!match) return null;\n\n const hours = Number(match[1]);\n const mins = Number(match[2]);\n if (hours > 24 || mins > 59 || (hours === 24 && mins > 0)) return null;\n\n return hours * 60 + mins;\n}\n\nexport function formatMinutes(total: number): string {\n const hours = Math.floor(total / 60);\n const mins = total % 60;\n return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;\n}\n\n// Absolute [start, end) in minutes. An end at or before the start is on the\n// following day, so a booking that crosses midnight is one interval rather than\n// two half-days. An unreadable time occupies the whole day — a booking surface\n// that cannot parse a time must not offer the slot.\nexport function eventInterval(\n event: CalendarEvent,\n slotDuration: number,\n onDay: Date\n): [number, number] | null {\n const day = normalizeDate(onDay).getTime();\n if (Number.isNaN(day)) return null;\n\n const base = day / 60000;\n const wholeDay: [number, number] = [base, base + MINUTES_PER_DAY];\n\n if (event.allDay || !event.startTime) return wholeDay;\n\n const from = parseTimeToMinutes(event.startTime);\n if (from === null) return wholeDay;\n\n if (event.endTime === undefined || event.endTime === null) {\n return [base + from, base + from + slotDuration];\n }\n\n const to = parseTimeToMinutes(event.endTime);\n if (to === null) return wholeDay;\n\n return [base + from, base + (to <= from ? to + MINUTES_PER_DAY : to)];\n}\n\nexport function mergeIntervals(\n intervals: Array<[number, number]>\n): Array<[number, number]> {\n const sorted = [...intervals].sort((a, b) => a[0] - b[0]);\n\n return sorted.reduce<Array<[number, number]>>((merged, [start, end]) => {\n const last = merged[merged.length - 1];\n if (last && start <= last[1]) last[1] = Math.max(last[1], end);\n else merged.push([start, end]);\n return merged;\n }, []);\n}\n\n// Which of the day's slots any booking overlaps. Events from the previous day\n// belong in `events` so a booking crossing midnight marks the morning it runs into.\nexport function bookedSlots(\n events: CalendarEvent[],\n date: Date,\n slotDuration: number = DEFAULT_SLOT_DURATION\n): boolean[] {\n const base = normalizeDate(date).getTime() / 60000;\n\n const previousDay = new Date(date);\n previousDay.setDate(previousDay.getDate() - 1);\n\n // A span's times repeat on each day it covers, so the window is anchored to\n // the day being drawn rather than the day the span began\n const intervals: Array<[number, number]> = [];\n for (const event of new Set(events)) {\n for (const day of [previousDay, date]) {\n if (!eventCoversDate(event, day)) continue;\n const interval = eventInterval(event, slotDuration, day);\n if (interval) intervals.push(interval);\n }\n }\n\n const merged = mergeIntervals(intervals);\n\n return Array.from(\n { length: Math.floor(MINUTES_PER_DAY / slotDuration) },\n (_, index) => {\n const start = base + index * slotDuration;\n const end = start + slotDuration;\n return merged.some(([from, to]) => start < to && end > from);\n }\n );\n}\n","import {\n CalendarEvent,\n CalendarState,\n CalendarConfig,\n CalendarActions,\n CalendarViewModel,\n CalendarPane,\n CategoryColorMap,\n} from './types';\nimport {\n generateCalendarDates,\n getEventsForDate,\n generateYears,\n getMonthYearText,\n formatDateForDisplay,\n mergeCategoryColors,\n getCategoryColor,\n MONTHS,\n DAYS,\n} from './utils';\n\nexport class CalendarEngine {\n private state: CalendarState;\n private config: CalendarConfig;\n private listeners: Set<() => void> = new Set();\n private categoryColors: CategoryColorMap;\n\n constructor(config: CalendarConfig) {\n this.config = config;\n this.categoryColors = mergeCategoryColors(config.categoryColors);\n\n const initialDate = config.initialDate || new Date();\n this.state = {\n currentYear: initialDate.getFullYear(),\n currentMonth: initialDate.getMonth(),\n currentDate: initialDate.getDate(),\n selectedDate: null,\n selectedDayIndex: null,\n tasks: [],\n };\n }\n\n /**\n * Subscribe to state changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Notify all listeners of state changes\n */\n private notify(): void {\n this.listeners.forEach(listener => listener());\n }\n\n /**\n * Get current state\n */\n getState(): CalendarState {\n return { ...this.state };\n }\n\n /**\n * Get view model with computed properties\n */\n getViewModel(): CalendarViewModel {\n const panes: CalendarPane[] = Array.from(\n { length: Math.max(1, this.config.monthCount ?? 1) },\n (_, offset) => {\n const anchor = new Date(\n this.state.currentYear,\n this.state.currentMonth + offset,\n 1\n );\n const year = anchor.getFullYear();\n const month = anchor.getMonth();\n\n return {\n year,\n month,\n monthAndYearText: getMonthYearText(year, month),\n calendarDates: generateCalendarDates(\n year,\n month,\n this.config.events,\n this.config.weekStartsOn\n ),\n };\n }\n );\n\n return {\n ...this.state,\n months: MONTHS,\n days: DAYS,\n years: generateYears(this.config.minYear, this.config.maxYear),\n monthAndYearText: panes[0].monthAndYearText,\n scheduleDay: this.state.selectedDate\n ? formatDateForDisplay(this.state.selectedDate)\n : '',\n panes,\n calendarDates: panes[0].calendarDates,\n };\n }\n\n /**\n * Get actions object\n */\n getActions(): CalendarActions {\n return {\n next: this.next.bind(this),\n previous: this.previous.bind(this),\n jump: this.jump.bind(this),\n goToToday: this.goToToday.bind(this),\n isCurrentMonth: this.isCurrentMonth.bind(this),\n selectDate: this.selectDate.bind(this),\n updateTasks: this.updateTasks.bind(this),\n };\n }\n\n /**\n * Navigate to next month\n */\n private next(): void {\n if (this.state.currentMonth === 11) {\n this.state.currentMonth = 0;\n this.state.currentYear++;\n } else {\n this.state.currentMonth++;\n }\n\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Navigate to previous month\n */\n private previous(): void {\n if (this.state.currentMonth === 0) {\n this.state.currentMonth = 11;\n this.state.currentYear--;\n } else {\n this.state.currentMonth--;\n }\n\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Jump to specific month and year\n */\n private jump(year: number, month: number): void {\n this.state.currentYear = year;\n this.state.currentMonth = month;\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Navigate to current month (today)\n */\n private goToToday(): void {\n const today = new Date();\n this.state.currentYear = today.getFullYear();\n this.state.currentMonth = today.getMonth();\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Check if currently viewing today's month\n */\n private isCurrentMonth(): boolean {\n const today = new Date();\n return (\n this.state.currentYear === today.getFullYear() &&\n this.state.currentMonth === today.getMonth()\n );\n }\n\n /**\n * Select a specific date\n */\n private selectDate(date: Date, dayIndex?: number): void {\n this.state.selectedDate = date;\n this.state.selectedDayIndex = dayIndex ?? null;\n this.state.currentDate = date.getDate();\n this.state.currentMonth = date.getMonth();\n this.state.currentYear = date.getFullYear();\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Update tasks for the currently selected date\n */\n private updateTasks(): void {\n if (!this.state.selectedDate) {\n this.state.tasks = [];\n return;\n }\n\n this.state.tasks = getEventsForDate(\n this.config.events,\n this.state.selectedDate\n );\n }\n\n /**\n * Update events configuration\n */\n updateEvents(events: CalendarEvent[]): void {\n this.config.events = events;\n this.updateTasks();\n this.notify();\n }\n\n /**\n * Handle date cell click\n */\n handleDateClick(date: Date, dayIndex?: number): void {\n this.selectDate(date, dayIndex);\n }\n\n /**\n * Check if date has events\n */\n hasEventsForDate(date: Date): boolean {\n return getEventsForDate(this.config.events, date).length > 0;\n }\n\n /**\n * Get events for a specific date\n */\n getEventsForDate(date: Date): CalendarEvent[] {\n return getEventsForDate(this.config.events, date);\n }\n\n /**\n * Clear selected date\n */\n clearSelection(): void {\n this.state.selectedDate = null;\n this.state.selectedDayIndex = null;\n this.state.tasks = [];\n this.notify();\n }\n\n /**\n * Get category color (with custom colors support)\n */\n getCategoryColor(category?: string): string {\n if (!category) return '#fc8917';\n return getCategoryColor(category, this.categoryColors);\n }\n\n /**\n * Update category colors dynamically\n */\n updateCategoryColors(colors: CategoryColorMap): void {\n this.categoryColors = mergeCategoryColors(colors);\n this.notify();\n }\n\n /**\n * Get all category colors\n */\n getCategoryColors(): CategoryColorMap {\n return { ...this.categoryColors };\n }\n\n /**\n * Register a new category with color\n */\n registerCategory(name: string, color: string): void {\n this.categoryColors[name] = color;\n this.notify();\n }\n\n /**\n * Destroy the engine and cleanup listeners\n */\n destroy(): void {\n this.listeners.clear();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,SAAS;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,cAAc;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,OAAO;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,cAAc,MAAkB;AAC9C,SAAO,IAAI,KAAK,KAAK,YAAY,GAAG,KAAK,SAAS,GAAG,KAAK,QAAQ,GAAG,GAAG,GAAG,CAAC;AAC9E;AAEO,SAAS,UAAU,OAAa,OAAsB;AAC3D,SAAO,cAAc,KAAK,EAAE,QAAQ,MAAM,cAAc,KAAK,EAAE,QAAQ;AACzE;AAEO,SAAS,QAAQ,MAAqB;AAC3C,SAAO,UAAU,MAAM,oBAAI,KAAK,CAAC;AACnC;AAEO,SAAS,cAAc,SAAkB,SAA4B;AAC1E,QAAM,eAAc,oBAAI,KAAK,GAAE,YAAY;AAC3C,QAAM,MAAM,WAAW,cAAc;AACrC,QAAM,MAAM,WAAW,cAAc;AAErC,SAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,MAAM,CAAC;AAChE;AAIO,SAAS,gBAAgB,OAAsB,MAAqB;AACzE,QAAM,QAAQ,cAAc,IAAI,KAAK,MAAM,IAAI,CAAC,EAAE,QAAQ;AAC1D,QAAM,SAAS,cAAc,IAAI,EAAE,QAAQ;AAC3C,MAAI,OAAO,MAAM,KAAK,EAAG,QAAO;AAEhC,MAAI,MAAM,YAAY,UAAa,MAAM,YAAY,MAAM;AACzD,WAAO,UAAU;AAAA,EACnB;AAEA,QAAM,MAAM,cAAc,IAAI,KAAK,MAAM,OAAO,CAAC,EAAE,QAAQ;AAC3D,MAAI,OAAO,MAAM,GAAG,GAAG;AACrB,UAAM,IAAI;AAAA,MACR,wBAAwB,MAAM,EAAE,+BAC3B,OAAO,MAAM,OAAO,CAAC;AAAA,IAC5B;AAAA,EACF;AACA,MAAI,MAAM,OAAO;AACf,UAAM,IAAI;AAAA,MACR,wBAAwB,MAAM,EAAE,oCAC3B,OAAO,MAAM,OAAO,CAAC,MAAM,OAAO,MAAM,IAAI,CAAC;AAAA,IACpD;AAAA,EACF;AAEA,SAAO,UAAU,SAAS,UAAU;AACtC;AAEO,SAAS,iBACd,QACA,MACiB;AACjB,SAAO,OAAO,OAAO,WAAS,gBAAgB,OAAO,IAAI,CAAC;AAC5D;AAEO,SAAS,UAAU,QAAyB,MAAqB;AACtE,SAAO,iBAAiB,QAAQ,IAAI,EAAE,SAAS;AACjD;AAEO,SAAS,sBACd,MACA,OACA,SAA0B,CAAC,GAC3B,eAAsB,GACJ;AAClB,QAAM,WAAW,IAAI,KAAK,MAAM,OAAO,CAAC;AACxC,QAAM,UAAU,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC;AAC3C,QAAM,cAAc,QAAQ,QAAQ;AACpC,QAAM,mBAAmB,IAAI,KAAK,MAAM,OAAO,CAAC,EAAE,QAAQ;AAE1D,MAAI,iBAAiB,SAAS,OAAO;AACrC,MAAI,iBAAiB,GAAG;AACtB,qBAAiB,mBAAmB,IAAI,IAAI,iBAAiB;AAAA,EAC/D;AAEA,QAAM,QAA0B,CAAC;AACjC,MAAI,MAAM;AACV,MAAI,eAAe;AAEnB,WAAS,OAAO,GAAG,OAAO,GAAG,QAAQ;AACnC,UAAM,YAA4B,CAAC;AAEnC,aAAS,YAAY,GAAG,YAAY,GAAG,aAAa;AAClD,UAAI,SAAS,KAAK,YAAY,gBAAgB;AAE5C,cAAM,UAAU,mBAAmB,iBAAiB,YAAY;AAChE,cAAM,WAAW,IAAI,KAAK,MAAM,QAAQ,GAAG,OAAO;AAClD,cAAM,aAAa,iBAAiB,QAAQ,QAAQ;AACpD,kBAAU,KAAK;AAAA,UACb,MAAM;AAAA,UACN,gBAAgB;AAAA,UAChB,SAAS,QAAQ,QAAQ;AAAA,UACzB,WAAW,WAAW,SAAS;AAAA,UAC/B,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,WAAW,MAAM,aAAa;AAE5B,cAAM,WAAW,IAAI,KAAK,MAAM,QAAQ,GAAG,YAAY;AACvD,cAAM,aAAa,iBAAiB,QAAQ,QAAQ;AACpD,kBAAU,KAAK;AAAA,UACb,MAAM;AAAA,UACN,gBAAgB;AAAA,UAChB,SAAS,QAAQ,QAAQ;AAAA,UACzB,WAAW,WAAW,SAAS;AAAA,UAC/B,QAAQ;AAAA,QACV,CAAC;AACD;AAAA,MACF,OAAO;AAEL,cAAM,cAAc,IAAI,KAAK,MAAM,OAAO,GAAG;AAC7C,cAAM,aAAa,iBAAiB,QAAQ,WAAW;AACvD,kBAAU,KAAK;AAAA,UACb,MAAM;AAAA,UACN,gBAAgB;AAAA,UAChB,SAAS,QAAQ,WAAW;AAAA,UAC5B,WAAW,WAAW,SAAS;AAAA,UAC/B,QAAQ;AAAA,QACV,CAAC;AACD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,SAAS;AAAA,EACtB;AAEA,SAAO;AACT;AAEO,SAAS,eAAe,cAAsC;AACnE,QAAM,UAAoB,CAAC;AAE3B,MAAI,CAAC,aAAa,gBAAgB;AAChC,YAAQ,KAAK,2BAA2B;AAAA,EAC1C;AAEA,MAAI,aAAa,SAAS;AACxB,YAAQ,KAAK,qBAAqB;AAAA,EACpC;AAEA,MAAI,aAAa,WAAW;AAC1B,YAAQ,KAAK,yBAAyB;AAAA,EACxC;AAEA,SAAO;AACT;AAEO,SAAS,qBAAqB,MAAoB;AACvD,SAAO,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;AACjD;AAEO,SAAS,iBAAiB,MAAc,OAAuB;AACpE,SAAO,GAAG,YAAY,KAAK,CAAC,IAAI,IAAI;AACtC;AAEO,SAAS,gBAAgB,OAA8B;AAC5D,MAAI,MAAM,UAAW,CAAC,MAAM,aAAa,CAAC,MAAM,SAAU;AACxD,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,aAAa,MAAM,SAAS;AACpC,WAAO,GAAG,MAAM,SAAS,MAAM,MAAM,OAAO;AAAA,EAC9C;AAEA,MAAI,MAAM,WAAW;AACnB,WAAO,GAAG,MAAM,SAAS;AAAA,EAC3B;AAEA,SAAO;AACT;AAEO,SAAS,gBAAgB,WAA8B;AAC5D,MAAI,CAAC,aAAa,UAAU,WAAW,EAAG,QAAO;AAEjD,MAAI,UAAU,WAAW,EAAG,QAAO,UAAU,CAAC;AAC9C,MAAI,UAAU,WAAW,EAAG,QAAO,UAAU,KAAK,OAAO;AAEzD,SAAO,GAAG,UAAU,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,SAAS,UAAU,UAAU,SAAS,CAAC,CAAC;AACrF;AAEO,SAAS,iBAAiB,QAA0C;AACzE,SAAO,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM;AAChC,UAAM,UAAU,EAAE,UAAW,CAAC,EAAE,aAAa,CAAC,EAAE;AAChD,UAAM,UAAU,EAAE,UAAW,CAAC,EAAE,aAAa,CAAC,EAAE;AAEhD,QAAI,WAAW,CAAC,QAAS,QAAO;AAChC,QAAI,CAAC,WAAW,QAAS,QAAO;AAEhC,QAAI,CAAC,EAAE,aAAa,CAAC,EAAE,UAAW,QAAO;AACzC,WAAO,EAAE,UAAU,cAAc,EAAE,SAAS;AAAA,EAC9C,CAAC;AACH;AAEO,IAAM,0BAA4C;AAAA,EACvD,MAAM;AAAA,EACN,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,aAAa;AAAA,EACb,OAAO;AACT;AAGO,SAAS,qBACd,UACA,cACQ;AACR,QAAM,WAAW,gBAAgB;AAEjC,MAAI,YAAY,SAAS,QAAQ,GAAG;AAClC,WAAO,SAAS,QAAQ;AAAA,EAC1B;AAGA,SAAO;AACT;AAGO,SAAS,oBACd,cACkB;AAClB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACF;AAGO,SAAS,gBAAgB,OAAwB;AACtD,SAAO,yBAAyB,KAAK,KAAK;AAC5C;AAGO,SAAS,iBACd,UACA,cACQ;AACR,QAAM,WAAW,oBAAoB,YAAY;AACjD,QAAM,QAAQ,SAAS,QAAQ,KAAK,SAAS,SAAS;AAEtD,SAAO,gBAAgB,KAAK,IAAI,QAAQ;AAC1C;AAEA,IAAM,eAAuC;AAAA,EAC3C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAGO,SAAS,WAAW,OAAwB;AACjD,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAElD,SAAO,OAAO,KAAK,EAAE,QAAQ,YAAY,UAAQ,aAAa,IAAI,CAAC;AACrE;AAGO,SAAS,aAAa,OAAuB;AAClD,SAAO,OAAO,KAAK,EAChB,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE;AAC3B;AAIO,SAAS,QAAQ,OAAuB;AAI7C,QAAM,aAAa,OAAO,KAAK,EAC5B,MAAM,EAAE,EACR,OAAO,UAAQ,KAAK,WAAW,CAAC,IAAI,EAAI,EACxC,KAAK,EAAE;AAEV,MAAI,wBAAwB,KAAK,UAAU,EAAG,QAAO;AAIrD,QAAM,aAAa,CAAC,YAAY,KAAK,UAAU;AAE/C,SAAO,aAAa,aAAa;AACnC;AAIO,SAAS,UAAU,OAAuB;AAC/C,QAAM,UAAU,OAAO,KAAK,EAAE,KAAK;AAEnC,SAAO,gBAAgB,OAAO,KAAK,YAAY,KAAK,OAAO,IACvD,UACA;AACN;AAEO,IAAM,kBAAkB;AACxB,IAAM,wBAAwB;AAE9B,SAAS,mBAAmB,MAA8B;AAC/D,MAAI,OAAO,SAAS,SAAU,QAAO;AAErC,QAAM,QAAQ,sBAAsB,KAAK,KAAK,KAAK,CAAC;AACpD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,OAAO,MAAM,CAAC,CAAC;AAC7B,QAAM,OAAO,OAAO,MAAM,CAAC,CAAC;AAC5B,MAAI,QAAQ,MAAM,OAAO,MAAO,UAAU,MAAM,OAAO,EAAI,QAAO;AAElE,SAAO,QAAQ,KAAK;AACtB;AAEO,SAAS,cAAc,OAAuB;AACnD,QAAM,QAAQ,KAAK,MAAM,QAAQ,EAAE;AACnC,QAAM,OAAO,QAAQ;AACrB,SAAO,GAAG,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,IAAI,EAAE,SAAS,GAAG,GAAG,CAAC;AAC3E;AAMO,SAAS,cACd,OACA,cACA,OACyB;AACzB,QAAM,MAAM,cAAc,KAAK,EAAE,QAAQ;AACzC,MAAI,OAAO,MAAM,GAAG,EAAG,QAAO;AAE9B,QAAM,OAAO,MAAM;AACnB,QAAM,WAA6B,CAAC,MAAM,OAAO,eAAe;AAEhE,MAAI,MAAM,UAAU,CAAC,MAAM,UAAW,QAAO;AAE7C,QAAM,OAAO,mBAAmB,MAAM,SAAS;AAC/C,MAAI,SAAS,KAAM,QAAO;AAE1B,MAAI,MAAM,YAAY,UAAa,MAAM,YAAY,MAAM;AACzD,WAAO,CAAC,OAAO,MAAM,OAAO,OAAO,YAAY;AAAA,EACjD;AAEA,QAAM,KAAK,mBAAmB,MAAM,OAAO;AAC3C,MAAI,OAAO,KAAM,QAAO;AAExB,SAAO,CAAC,OAAO,MAAM,QAAQ,MAAM,OAAO,KAAK,kBAAkB,GAAG;AACtE;AAEO,SAAS,eACd,WACyB;AACzB,QAAM,SAAS,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAExD,SAAO,OAAO,OAAgC,CAAC,QAAQ,CAAC,OAAO,GAAG,MAAM;AACtE,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,QAAI,QAAQ,SAAS,KAAK,CAAC,EAAG,MAAK,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,GAAG;AAAA,QACxD,QAAO,KAAK,CAAC,OAAO,GAAG,CAAC;AAC7B,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AACP;AAIO,SAAS,YACd,QACA,MACA,eAAuB,uBACZ;AACX,QAAM,OAAO,cAAc,IAAI,EAAE,QAAQ,IAAI;AAE7C,QAAM,cAAc,IAAI,KAAK,IAAI;AACjC,cAAY,QAAQ,YAAY,QAAQ,IAAI,CAAC;AAI7C,QAAM,YAAqC,CAAC;AAC5C,aAAW,SAAS,IAAI,IAAI,MAAM,GAAG;AACnC,eAAW,OAAO,CAAC,aAAa,IAAI,GAAG;AACrC,UAAI,CAAC,gBAAgB,OAAO,GAAG,EAAG;AAClC,YAAM,WAAW,cAAc,OAAO,cAAc,GAAG;AACvD,UAAI,SAAU,WAAU,KAAK,QAAQ;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,SAAS,eAAe,SAAS;AAEvC,SAAO,MAAM;AAAA,IACX,EAAE,QAAQ,KAAK,MAAM,kBAAkB,YAAY,EAAE;AAAA,IACrD,CAAC,GAAG,UAAU;AACZ,YAAM,QAAQ,OAAO,QAAQ;AAC7B,YAAM,MAAM,QAAQ;AACpB,aAAO,OAAO,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,QAAQ,MAAM,MAAM,IAAI;AAAA,IAC7D;AAAA,EACF;AACF;;;AC9ZO,IAAM,iBAAN,MAAqB;AAAA,EAM1B,YAAY,QAAwB;AAHpC,SAAQ,YAA6B,oBAAI,IAAI;AAI3C,SAAK,SAAS;AACd,SAAK,iBAAiB,oBAAoB,OAAO,cAAc;AAE/D,UAAM,cAAc,OAAO,eAAe,oBAAI,KAAK;AACnD,SAAK,QAAQ;AAAA,MACX,aAAa,YAAY,YAAY;AAAA,MACrC,cAAc,YAAY,SAAS;AAAA,MACnC,aAAa,YAAY,QAAQ;AAAA,MACjC,cAAc;AAAA,MACd,kBAAkB;AAAA,MAClB,OAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAAkC;AAC1C,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKQ,SAAe;AACrB,SAAK,UAAU,QAAQ,cAAY,SAAS,CAAC;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,WAA0B;AACxB,WAAO,EAAE,GAAG,KAAK,MAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAkC;AAChC,UAAM,QAAwB,MAAM;AAAA,MAClC,EAAE,QAAQ,KAAK,IAAI,GAAG,KAAK,OAAO,cAAc,CAAC,EAAE;AAAA,MACnD,CAAC,GAAG,WAAW;AACb,cAAM,SAAS,IAAI;AAAA,UACjB,KAAK,MAAM;AAAA,UACX,KAAK,MAAM,eAAe;AAAA,UAC1B;AAAA,QACF;AACA,cAAM,OAAO,OAAO,YAAY;AAChC,cAAM,QAAQ,OAAO,SAAS;AAE9B,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA,kBAAkB,iBAAiB,MAAM,KAAK;AAAA,UAC9C,eAAe;AAAA,YACb;AAAA,YACA;AAAA,YACA,KAAK,OAAO;AAAA,YACZ,KAAK,OAAO;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,GAAG,KAAK;AAAA,MACR,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,cAAc,KAAK,OAAO,SAAS,KAAK,OAAO,OAAO;AAAA,MAC7D,kBAAkB,MAAM,CAAC,EAAE;AAAA,MAC3B,aAAa,KAAK,MAAM,eACpB,qBAAqB,KAAK,MAAM,YAAY,IAC5C;AAAA,MACJ;AAAA,MACA,eAAe,MAAM,CAAC,EAAE;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAA8B;AAC5B,WAAO;AAAA,MACL,MAAM,KAAK,KAAK,KAAK,IAAI;AAAA,MACzB,UAAU,KAAK,SAAS,KAAK,IAAI;AAAA,MACjC,MAAM,KAAK,KAAK,KAAK,IAAI;AAAA,MACzB,WAAW,KAAK,UAAU,KAAK,IAAI;AAAA,MACnC,gBAAgB,KAAK,eAAe,KAAK,IAAI;AAAA,MAC7C,YAAY,KAAK,WAAW,KAAK,IAAI;AAAA,MACrC,aAAa,KAAK,YAAY,KAAK,IAAI;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAa;AACnB,QAAI,KAAK,MAAM,iBAAiB,IAAI;AAClC,WAAK,MAAM,eAAe;AAC1B,WAAK,MAAM;AAAA,IACb,OAAO;AACL,WAAK,MAAM;AAAA,IACb;AAEA,SAAK,MAAM,eAAe;AAC1B,SAAK,MAAM,mBAAmB;AAC9B,SAAK,YAAY;AACjB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAiB;AACvB,QAAI,KAAK,MAAM,iBAAiB,GAAG;AACjC,WAAK,MAAM,eAAe;AAC1B,WAAK,MAAM;AAAA,IACb,OAAO;AACL,WAAK,MAAM;AAAA,IACb;AAEA,SAAK,MAAM,eAAe;AAC1B,SAAK,MAAM,mBAAmB;AAC9B,SAAK,YAAY;AACjB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,KAAK,MAAc,OAAqB;AAC9C,SAAK,MAAM,cAAc;AACzB,SAAK,MAAM,eAAe;AAC1B,SAAK,MAAM,eAAe;AAC1B,SAAK,MAAM,mBAAmB;AAC9B,SAAK,YAAY;AACjB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAkB;AACxB,UAAM,QAAQ,oBAAI,KAAK;AACvB,SAAK,MAAM,cAAc,MAAM,YAAY;AAC3C,SAAK,MAAM,eAAe,MAAM,SAAS;AACzC,SAAK,MAAM,eAAe;AAC1B,SAAK,MAAM,mBAAmB;AAC9B,SAAK,YAAY;AACjB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAA0B;AAChC,UAAM,QAAQ,oBAAI,KAAK;AACvB,WACE,KAAK,MAAM,gBAAgB,MAAM,YAAY,KAC7C,KAAK,MAAM,iBAAiB,MAAM,SAAS;AAAA,EAE/C;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,MAAY,UAAyB;AACtD,SAAK,MAAM,eAAe;AAC1B,SAAK,MAAM,mBAAmB,YAAY;AAC1C,SAAK,MAAM,cAAc,KAAK,QAAQ;AACtC,SAAK,MAAM,eAAe,KAAK,SAAS;AACxC,SAAK,MAAM,cAAc,KAAK,YAAY;AAC1C,SAAK,YAAY;AACjB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC1B,QAAI,CAAC,KAAK,MAAM,cAAc;AAC5B,WAAK,MAAM,QAAQ,CAAC;AACpB;AAAA,IACF;AAEA,SAAK,MAAM,QAAQ;AAAA,MACjB,KAAK,OAAO;AAAA,MACZ,KAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAA+B;AAC1C,SAAK,OAAO,SAAS;AACrB,SAAK,YAAY;AACjB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,MAAY,UAAyB;AACnD,SAAK,WAAW,MAAM,QAAQ;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,MAAqB;AACpC,WAAO,iBAAiB,KAAK,OAAO,QAAQ,IAAI,EAAE,SAAS;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,MAA6B;AAC5C,WAAO,iBAAiB,KAAK,OAAO,QAAQ,IAAI;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAuB;AACrB,SAAK,MAAM,eAAe;AAC1B,SAAK,MAAM,mBAAmB;AAC9B,SAAK,MAAM,QAAQ,CAAC;AACpB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAA2B;AAC1C,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,iBAAiB,UAAU,KAAK,cAAc;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqB,QAAgC;AACnD,SAAK,iBAAiB,oBAAoB,MAAM;AAChD,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAsC;AACpC,WAAO,EAAE,GAAG,KAAK,eAAe;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,MAAc,OAAqB;AAClD,SAAK,eAAe,IAAI,IAAI;AAC5B,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,UAAU,MAAM;AAAA,EACvB;AACF;","names":[]}
|