kalendly 0.2.2 → 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 +303 -45
- package/dist/core/index.d.mts +57 -10
- package/dist/core/index.d.ts +57 -10
- package/dist/core/index.js +159 -31
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +147 -30
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +86 -10
- package/dist/index.d.ts +86 -10
- package/dist/index.js +525 -178
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +513 -177
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +499 -174
- package/dist/index.umd.js.map +1 -1
- package/dist/styles/calendar.css +457 -392
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ npm install kalendly
|
|
|
62
62
|
/>
|
|
63
63
|
<script src="https://unpkg.com/kalendly/dist/index.umd.js"></script>
|
|
64
64
|
|
|
65
|
-
<kal-calendar id="cal"
|
|
65
|
+
<kal-calendar id="cal" heading="My Calendar"></kal-calendar>
|
|
66
66
|
|
|
67
67
|
<script>
|
|
68
68
|
const cal = document.getElementById('cal');
|
|
@@ -104,7 +104,7 @@ import 'kalendly/styles';
|
|
|
104
104
|
function App() {
|
|
105
105
|
return (
|
|
106
106
|
<kal-calendar
|
|
107
|
-
|
|
107
|
+
heading="My Calendar"
|
|
108
108
|
events={events}
|
|
109
109
|
oncal-date-select={e => console.log(e.detail.date)}
|
|
110
110
|
oncal-month-change={e => console.log(e.detail.year, e.detail.month)}
|
|
@@ -198,7 +198,7 @@ Without this the component still renders correctly — Vue falls back to a nativ
|
|
|
198
198
|
```vue
|
|
199
199
|
<template>
|
|
200
200
|
<kal-calendar
|
|
201
|
-
|
|
201
|
+
heading="My Calendar"
|
|
202
202
|
:events="events"
|
|
203
203
|
@cal-date-select="onDateSelect"
|
|
204
204
|
@cal-month-change="onMonthChange"
|
|
@@ -236,7 +236,7 @@ import 'kalendly/styles';
|
|
|
236
236
|
```html
|
|
237
237
|
<!-- app.component.html -->
|
|
238
238
|
<kal-calendar
|
|
239
|
-
|
|
239
|
+
heading="My Calendar"
|
|
240
240
|
[events]="events"
|
|
241
241
|
(cal-date-select)="onDateSelect($event)"
|
|
242
242
|
(cal-month-change)="onMonthChange($event)"
|
|
@@ -280,7 +280,7 @@ import 'kalendly/styles';
|
|
|
280
280
|
function App() {
|
|
281
281
|
return (
|
|
282
282
|
<kal-calendar
|
|
283
|
-
|
|
283
|
+
heading="My Calendar"
|
|
284
284
|
prop:events={events}
|
|
285
285
|
on:cal-date-select={e => console.log(e.detail.date)}
|
|
286
286
|
/>
|
|
@@ -316,7 +316,7 @@ kalendly uses Light DOM — all standard CSS techniques work:
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
/* 2. Direct class overrides */
|
|
319
|
-
.kalendly-calendar .calendar
|
|
319
|
+
.kalendly-calendar .calendar-card {
|
|
320
320
|
border-radius: 12px;
|
|
321
321
|
}
|
|
322
322
|
```
|
|
@@ -333,40 +333,50 @@ document.querySelector('kal-calendar').theme = {
|
|
|
333
333
|
|
|
334
334
|
Primitives are set as HTML attributes:
|
|
335
335
|
|
|
336
|
-
| Attribute | Type
|
|
337
|
-
| ----------------------- |
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
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 |
|
|
347
350
|
|
|
348
351
|
## Properties
|
|
349
352
|
|
|
350
353
|
Rich objects are set as JS properties (not attributes):
|
|
351
354
|
|
|
352
|
-
| Property
|
|
353
|
-
|
|
|
354
|
-
| `events`
|
|
355
|
-
| `loading`
|
|
356
|
-
| `theme`
|
|
357
|
-
| `categoryColors`
|
|
358
|
-
| `renderEvent`
|
|
359
|
-
| `renderNoEvents`
|
|
355
|
+
| Property | Type | Description |
|
|
356
|
+
| -------------------- | ---------------------------------- | ------------------------------------------------- |
|
|
357
|
+
| `events` | `CalendarEvent[]` | Events to display |
|
|
358
|
+
| `loading` | `boolean` | `true` = render skeleton cells; `false` = restore |
|
|
359
|
+
| `theme` | `CalendarTheme` | Custom theme colors |
|
|
360
|
+
| `categoryColors` | `CategoryColorMap` | Per-category color overrides |
|
|
361
|
+
| `renderEvent` | `(event: CalendarEvent) => string` | Custom event HTML renderer |
|
|
362
|
+
| `renderNoEvents` | `() => string` | Custom empty-state HTML renderer |
|
|
363
|
+
| `availabilityColors` | `Record<string, string>` | Colour per availability bucket |
|
|
364
|
+
| `selectableStatuses` | `string[]` | Buckets a range may start, end or span |
|
|
360
365
|
|
|
361
366
|
> `renderEvent` and `renderNoEvents` are ignored when `availability-mode` is set.
|
|
362
367
|
|
|
368
|
+
### Why `heading` and not `title`
|
|
369
|
+
|
|
370
|
+
`title` is a global HTML attribute, so the browser renders it as a tooltip floating over the whole calendar as well as using it as the heading. `heading` does the same job without the tooltip. `title` still works and warns once per page; it will be removed in a future release.
|
|
371
|
+
|
|
363
372
|
## Custom Events
|
|
364
373
|
|
|
365
|
-
| Event | `detail` shape | Description
|
|
366
|
-
| ------------------------- | ---------------------------------------------------------------------- |
|
|
367
|
-
| `cal-date-select` | `{ date: Date, events: CalendarEvent[] }` | User clicked a date (normal mode)
|
|
368
|
-
| `cal-month-change` | `{ year: number, month: number }` | Fires **before** the new month renders
|
|
369
|
-
| `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 |
|
|
370
380
|
|
|
371
381
|
All events bubble and are composed (cross Shadow DOM boundaries).
|
|
372
382
|
|
|
@@ -385,7 +395,70 @@ Hides all event details from the end user — only booked/free state is shown. D
|
|
|
385
395
|
<kal-calendar availability-mode="day"></kal-calendar>
|
|
386
396
|
```
|
|
387
397
|
|
|
388
|
-
|
|
398
|
+
Every event must declare `availabilityStatus`. Three buckets ship built in, coloured as a traffic light:
|
|
399
|
+
|
|
400
|
+
| Bucket | Colour | Meaning |
|
|
401
|
+
| ------------- | ------ | ----------------------------------------- |
|
|
402
|
+
| `open` | green | nothing claims this day |
|
|
403
|
+
| `conditional` | amber | claimed, but not necessarily hard-blocked |
|
|
404
|
+
| `blocked` | red | not available |
|
|
405
|
+
|
|
406
|
+
```js
|
|
407
|
+
cal.events = [
|
|
408
|
+
{ id: 1, date: '2026-03-02', availabilityStatus: 'blocked' },
|
|
409
|
+
{ id: 2, date: '2026-03-05', availabilityStatus: 'conditional' },
|
|
410
|
+
];
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Only cells in the current month are colour-coded — other-month cells remain grayed out. Clicking a day fires no popup and reveals no event details.
|
|
414
|
+
|
|
415
|
+
A day holding several events resolves by severity: `blocked` beats `conditional` beats `open`, so a day with both a conditional and a blocked booking reads blocked. Precedence never depends on the order events arrive in.
|
|
416
|
+
|
|
417
|
+
#### Your own buckets
|
|
418
|
+
|
|
419
|
+
`availabilityColors` merges over the built-in three — override one, or add your own:
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
cal.availabilityColors = {
|
|
423
|
+
conditional: '#7c3aed', // recolour a built-in
|
|
424
|
+
maintenance: '#0891b2', // add a bucket
|
|
425
|
+
};
|
|
426
|
+
cal.events = [{ id: 3, date: '2026-03-09', availabilityStatus: 'maintenance' }];
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
A bucket named in `availabilityColors` paints from an inline colour, which takes precedence over the same colour set through `theme`. The built-in three paint from CSS variables and are themeable the usual way.
|
|
430
|
+
|
|
431
|
+
Caller-defined buckets resolve by the order their keys appear in `availabilityColors`.
|
|
432
|
+
|
|
433
|
+
#### Which days are selectable
|
|
434
|
+
|
|
435
|
+
By default only `open` days can start, end or span a range. `selectableStatuses` widens that:
|
|
436
|
+
|
|
437
|
+
```js
|
|
438
|
+
cal.selectableStatuses = ['open', 'conditional'];
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### Misconfiguration throws
|
|
442
|
+
|
|
443
|
+
An event with no `availabilityStatus`, or one naming a bucket that is neither built in nor declared in `availabilityColors`, throws and names the offending events:
|
|
444
|
+
|
|
445
|
+
```
|
|
446
|
+
<kal-calendar> availability-mode requires availabilityStatus on every event. Missing on: 7, 9.
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Setting `events` throws where you set it. One case cannot: markup parsed before the module defines the element configures the calendar inside a custom element upgrade, and the browser reports exceptions there as uncaught rather than passing them to your code. The failure is kept, so the next call to `getEngine()`, `getCurrentDate()` or `goToDate()` throws it, and correcting `events` or `availabilityColors` clears it.
|
|
450
|
+
|
|
451
|
+
#### Two months side by side
|
|
452
|
+
|
|
453
|
+
```html
|
|
454
|
+
<kal-calendar
|
|
455
|
+
availability-mode="day"
|
|
456
|
+
months="2"
|
|
457
|
+
selectable="range"
|
|
458
|
+
></kal-calendar>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Navigation advances one month at a time, so a range spanning a month boundary stays visible. Ranges cross panes freely, and it works in standard and time modes too. Panes stack vertically on narrow screens.
|
|
389
462
|
|
|
390
463
|
<div align="center">
|
|
391
464
|
<img src="./docs/images/day.png" alt="Availability day view — month grid with red booked cells and green free cells"/>
|
|
@@ -408,13 +481,81 @@ cal.events = [
|
|
|
408
481
|
<kal-calendar availability-mode="time"></kal-calendar>
|
|
409
482
|
```
|
|
410
483
|
|
|
411
|
-
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.
|
|
412
513
|
|
|
413
514
|
<div align="center">
|
|
414
515
|
<img src="./docs/images/time.png" alt="Availability time view — day popup showing 24 hourly slots coloured red (booked) or green (available)"/>
|
|
415
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>
|
|
416
517
|
</div>
|
|
417
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
|
+
|
|
418
559
|
### Selectable range
|
|
419
560
|
|
|
420
561
|
Add `selectable="range"` to let the user pick a free day or time slot:
|
|
@@ -489,30 +630,57 @@ cal.getEngine();
|
|
|
489
630
|
|
|
490
631
|
## CalendarEvent Interface
|
|
491
632
|
|
|
633
|
+
> **Event text renders as text.** `name`, `description`, `location`,
|
|
634
|
+
> `organizer`, `notes`, `tags` and `attendees` are HTML-escaped, so markup in
|
|
635
|
+
> those fields shows as characters rather than being parsed. Use `renderEvent`
|
|
636
|
+
> if you need to emit your own markup. `url` accepts `http:`, `https:`,
|
|
637
|
+
> `mailto:` and relative URLs; anything else becomes `#`. `color` accepts hex
|
|
638
|
+
> values and CSS colour keywords.
|
|
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
|
+
|
|
653
|
+
> **Custom values.** `status`, `category` and `priority` accept any string. An
|
|
654
|
+
> unrecognised value renders as an uppercased badge with a neutral fill, which
|
|
655
|
+
> you can style via `.badge.status-<your-value>` or recolour through
|
|
656
|
+
> `--calendar-badge-bg` / `--calendar-badge-text`.
|
|
657
|
+
|
|
492
658
|
```typescript
|
|
493
659
|
interface CalendarEvent {
|
|
494
660
|
id: string | number;
|
|
495
661
|
name: string;
|
|
496
662
|
date: string | Date;
|
|
497
663
|
|
|
664
|
+
endDate?: string | Date; // last day of a span, inclusive
|
|
665
|
+
|
|
498
666
|
startTime?: string; // e.g. "09:00"
|
|
499
667
|
endTime?: string; // e.g. "10:00"
|
|
500
668
|
allDay?: boolean;
|
|
501
669
|
|
|
502
670
|
description?: string;
|
|
503
671
|
color?: string;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
| 'personal'
|
|
507
|
-
|
|
508
|
-
| 'deadline'
|
|
509
|
-
| 'appointment'
|
|
510
|
-
| 'other';
|
|
672
|
+
// Known values keep autocomplete; any other string is accepted
|
|
673
|
+
category?: Open<
|
|
674
|
+
'work' | 'personal' | 'meeting' | 'deadline' | 'appointment' | 'other'
|
|
675
|
+
>;
|
|
511
676
|
location?: string;
|
|
512
677
|
url?: string;
|
|
513
678
|
|
|
514
|
-
status?: 'scheduled' | 'completed' | 'cancelled' | 'tentative'
|
|
515
|
-
priority?: 'low' | 'medium' | 'high'
|
|
679
|
+
status?: Open<'scheduled' | 'completed' | 'cancelled' | 'tentative'>;
|
|
680
|
+
priority?: Open<'low' | 'medium' | 'high'>;
|
|
681
|
+
|
|
682
|
+
// Required under availability-mode; see Availability Mode below
|
|
683
|
+
availabilityStatus?: Open<'open' | 'conditional' | 'blocked'>;
|
|
516
684
|
|
|
517
685
|
attendees?: string[];
|
|
518
686
|
organizer?: string;
|
|
@@ -532,26 +700,77 @@ interface CalendarEvent {
|
|
|
532
700
|
|
|
533
701
|
## Theming
|
|
534
702
|
|
|
535
|
-
###
|
|
703
|
+
### Design tokens
|
|
704
|
+
|
|
705
|
+
Every colour, size, radius, shadow and spacing step is a custom property. Two
|
|
706
|
+
tiers: `--kal-*` holds the raw palette, `--calendar-*` names what each value is
|
|
707
|
+
for. Override the `--calendar-*` layer — the primitives are internal.
|
|
536
708
|
|
|
537
709
|
```css
|
|
538
710
|
:root {
|
|
711
|
+
/* Brand */
|
|
539
712
|
--calendar-primary-color: #fc8917;
|
|
540
713
|
--calendar-secondary-color: #fca045;
|
|
541
714
|
--calendar-tertiary-color: #fdb873;
|
|
715
|
+
|
|
716
|
+
/* Surfaces and text */
|
|
542
717
|
--calendar-text-color: #2c3e50;
|
|
543
718
|
--calendar-text-light: #6b7280;
|
|
544
|
-
--calendar-
|
|
545
|
-
--calendar-today-outline: #f7db04;
|
|
546
|
-
--calendar-event-indicator: #1890ff;
|
|
719
|
+
--calendar-on-accent: #fff;
|
|
547
720
|
--calendar-background: #fff;
|
|
721
|
+
--calendar-border-color: #dee2e6;
|
|
548
722
|
--calendar-cell-hover: #f3f4f6;
|
|
723
|
+
--calendar-header-bg: #f8f9fa;
|
|
549
724
|
--calendar-selected-bg: #eff6ff;
|
|
725
|
+
--calendar-popup-bg: #fff;
|
|
726
|
+
--calendar-picker-bg: #fff;
|
|
727
|
+
--calendar-today-outline: #f7db04;
|
|
728
|
+
--calendar-event-indicator: #1890ff;
|
|
729
|
+
--calendar-input-invalid: #ef4444;
|
|
730
|
+
--calendar-link: #2563eb;
|
|
731
|
+
--calendar-skeleton-base: #f0f0f0;
|
|
732
|
+
--calendar-skeleton-highlight: #e8e8e8;
|
|
733
|
+
|
|
734
|
+
/* Availability */
|
|
735
|
+
--calendar-open-bg: #dcfce7;
|
|
736
|
+
--calendar-open-fg: #16a34a;
|
|
737
|
+
--calendar-conditional-bg: #fef3c7;
|
|
738
|
+
--calendar-conditional-fg: #d97706;
|
|
739
|
+
--calendar-blocked-bg: #fee2e2;
|
|
740
|
+
--calendar-blocked-fg: #dc2626;
|
|
741
|
+
--calendar-range-bg: #16a34a;
|
|
742
|
+
--calendar-range-outline: #15803d;
|
|
743
|
+
--calendar-in-range-bg: #bbf7d0;
|
|
744
|
+
--calendar-in-range-outline: #86efac;
|
|
745
|
+
|
|
746
|
+
/* Badges — bg/text is the fallback for caller-defined values */
|
|
747
|
+
--calendar-badge-bg: #f3f4f6;
|
|
748
|
+
--calendar-badge-text: #4b5563;
|
|
749
|
+
--calendar-badge-success-bg: #d1fae5;
|
|
750
|
+
--calendar-badge-success-text: #059669;
|
|
751
|
+
--calendar-badge-info-bg: #dbeafe;
|
|
752
|
+
--calendar-badge-info-text: #2563eb;
|
|
753
|
+
--calendar-badge-warning-bg: #fef3c7;
|
|
754
|
+
--calendar-badge-warning-text: #d97706;
|
|
755
|
+
--calendar-badge-danger-bg: #fee2e2;
|
|
756
|
+
--calendar-badge-danger-text: #dc2626;
|
|
757
|
+
--calendar-badge-neutral-bg: #f3f4f6;
|
|
758
|
+
--calendar-badge-neutral-text: #6b7280;
|
|
759
|
+
--calendar-badge-positive-bg: #dcfce7;
|
|
760
|
+
--calendar-badge-positive-text: #16a34a;
|
|
761
|
+
--calendar-badge-tentative-bg: #e0e7ff;
|
|
762
|
+
--calendar-badge-tentative-text: #4f46e5;
|
|
550
763
|
}
|
|
551
764
|
```
|
|
552
765
|
|
|
766
|
+
Type, radius, elevation and spacing scales are exposed the same way —
|
|
767
|
+
`--calendar-font-*`, `--calendar-radius-*`, `--calendar-shadow-*` and
|
|
768
|
+
`--calendar-space-*`. See `dist/styles/calendar.css` for the full set.
|
|
769
|
+
|
|
553
770
|
### JS theme property (full reference)
|
|
554
771
|
|
|
772
|
+
Every `--calendar-*` colour token has a matching camelCase theme key.
|
|
773
|
+
|
|
555
774
|
```js
|
|
556
775
|
cal.theme = {
|
|
557
776
|
primary: '#3b82f6',
|
|
@@ -559,15 +778,54 @@ cal.theme = {
|
|
|
559
778
|
tertiary: '#93c5fd',
|
|
560
779
|
textColor: '#111827',
|
|
561
780
|
textLight: '#6b7280',
|
|
781
|
+
onAccent: '#ffffff',
|
|
562
782
|
background: '#ffffff',
|
|
563
783
|
cellHover: '#f3f4f6',
|
|
564
784
|
borderColor: '#e5e7eb',
|
|
565
785
|
todayOutline: '#fbbf24',
|
|
566
786
|
selectedBg: '#eff6ff',
|
|
787
|
+
headerBg: '#f8f9fa',
|
|
788
|
+
popupBg: '#ffffff',
|
|
789
|
+
pickerBg: '#ffffff',
|
|
790
|
+
pickerShadow: '0 4px 20px rgba(0, 0, 0, 0.15)',
|
|
567
791
|
eventIndicator: '#10b981',
|
|
792
|
+
link: '#2563eb',
|
|
793
|
+
|
|
794
|
+
// Availability
|
|
795
|
+
openBg: '#dcfce7',
|
|
796
|
+
openFg: '#16a34a',
|
|
797
|
+
conditionalBg: '#fef3c7',
|
|
798
|
+
conditionalFg: '#d97706',
|
|
799
|
+
blockedBg: '#fee2e2',
|
|
800
|
+
blockedFg: '#dc2626',
|
|
801
|
+
rangeBg: '#16a34a',
|
|
802
|
+
rangeOutline: '#15803d',
|
|
803
|
+
inRangeBg: '#bbf7d0',
|
|
804
|
+
inRangeOutline: '#86efac',
|
|
805
|
+
|
|
806
|
+
// Badges
|
|
807
|
+
badgeBg: '#f3f4f6',
|
|
808
|
+
badgeText: '#4b5563',
|
|
809
|
+
badgeSuccessBg: '#d1fae5',
|
|
810
|
+
badgeSuccessText: '#059669',
|
|
811
|
+
badgeInfoBg: '#dbeafe',
|
|
812
|
+
badgeInfoText: '#2563eb',
|
|
813
|
+
badgeWarningBg: '#fef3c7',
|
|
814
|
+
badgeWarningText: '#d97706',
|
|
815
|
+
badgeDangerBg: '#fee2e2',
|
|
816
|
+
badgeDangerText: '#dc2626',
|
|
817
|
+
badgeNeutralBg: '#f3f4f6',
|
|
818
|
+
badgeNeutralText: '#6b7280',
|
|
819
|
+
badgePositiveBg: '#dcfce7',
|
|
820
|
+
badgePositiveText: '#16a34a',
|
|
821
|
+
badgeTentativeBg: '#e0e7ff',
|
|
822
|
+
badgeTentativeText: '#4f46e5',
|
|
568
823
|
};
|
|
569
824
|
```
|
|
570
825
|
|
|
826
|
+
> `availabilityColors` writes an inline colour on the cell, so for any bucket it
|
|
827
|
+
> names it takes precedence over the matching `theme` key.
|
|
828
|
+
|
|
571
829
|
### Dark theme example
|
|
572
830
|
|
|
573
831
|
```js
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
type Open<T extends string> = T | (string & {});
|
|
1
2
|
interface CalendarEvent {
|
|
2
3
|
id: string | number;
|
|
3
4
|
name: string;
|
|
4
5
|
date: string | Date;
|
|
6
|
+
endDate?: string | Date;
|
|
5
7
|
startTime?: string;
|
|
6
8
|
endTime?: string;
|
|
7
9
|
allDay?: boolean;
|
|
8
10
|
description?: string;
|
|
9
11
|
color?: string;
|
|
10
|
-
category?: 'work' | 'personal' | 'meeting' | 'deadline' | 'appointment' | 'other'
|
|
12
|
+
category?: Open<'work' | 'personal' | 'meeting' | 'deadline' | 'appointment' | 'other'>;
|
|
11
13
|
location?: string;
|
|
12
14
|
url?: string;
|
|
13
|
-
status?: 'scheduled' | 'completed' | 'cancelled' | 'tentative'
|
|
14
|
-
priority?: 'low' | 'medium' | 'high'
|
|
15
|
+
status?: Open<'scheduled' | 'completed' | 'cancelled' | 'tentative'>;
|
|
16
|
+
priority?: Open<'low' | 'medium' | 'high'>;
|
|
17
|
+
availabilityStatus?: Open<'open' | 'conditional' | 'blocked'>;
|
|
15
18
|
attendees?: string[];
|
|
16
19
|
organizer?: string;
|
|
17
20
|
reminders?: number[];
|
|
@@ -52,6 +55,13 @@ interface CalendarConfig {
|
|
|
52
55
|
maxYear?: number;
|
|
53
56
|
weekStartsOn?: 0 | 1;
|
|
54
57
|
categoryColors?: CategoryColorMap;
|
|
58
|
+
monthCount?: number;
|
|
59
|
+
}
|
|
60
|
+
interface CalendarPane {
|
|
61
|
+
year: number;
|
|
62
|
+
month: number;
|
|
63
|
+
monthAndYearText: string;
|
|
64
|
+
calendarDates: CalendarDate[][];
|
|
55
65
|
}
|
|
56
66
|
interface CalendarActions {
|
|
57
67
|
next: () => void;
|
|
@@ -62,18 +72,14 @@ interface CalendarActions {
|
|
|
62
72
|
selectDate: (date: Date) => void;
|
|
63
73
|
updateTasks: () => void;
|
|
64
74
|
}
|
|
65
|
-
interface PopupPosition {
|
|
66
|
-
class: 'popup-left' | 'popup-right' | 'popup-center-top' | 'popup-center-bottom';
|
|
67
|
-
style?: Record<string, string | number>;
|
|
68
|
-
}
|
|
69
75
|
interface CalendarViewModel extends CalendarState {
|
|
70
76
|
months: string[];
|
|
71
77
|
days: string[];
|
|
72
78
|
years: number[];
|
|
73
79
|
monthAndYearText: string;
|
|
74
80
|
scheduleDay: string;
|
|
81
|
+
panes: CalendarPane[];
|
|
75
82
|
calendarDates: CalendarDate[][];
|
|
76
|
-
popupPositionClass: string;
|
|
77
83
|
}
|
|
78
84
|
type CalendarEventHandler = (event: CalendarEvent) => void;
|
|
79
85
|
interface CalendarProps {
|
|
@@ -100,7 +106,37 @@ interface CalendarTheme {
|
|
|
100
106
|
selectedBg?: string;
|
|
101
107
|
headerBg?: string;
|
|
102
108
|
popupBg?: string;
|
|
109
|
+
pickerBg?: string;
|
|
110
|
+
pickerShadow?: string;
|
|
103
111
|
eventIndicator?: string;
|
|
112
|
+
onAccent?: string;
|
|
113
|
+
link?: string;
|
|
114
|
+
openBg?: string;
|
|
115
|
+
openFg?: string;
|
|
116
|
+
conditionalBg?: string;
|
|
117
|
+
conditionalFg?: string;
|
|
118
|
+
blockedBg?: string;
|
|
119
|
+
blockedFg?: string;
|
|
120
|
+
rangeBg?: string;
|
|
121
|
+
rangeOutline?: string;
|
|
122
|
+
inRangeBg?: string;
|
|
123
|
+
inRangeOutline?: string;
|
|
124
|
+
badgeBg?: string;
|
|
125
|
+
badgeText?: string;
|
|
126
|
+
badgeSuccessBg?: string;
|
|
127
|
+
badgeSuccessText?: string;
|
|
128
|
+
badgeInfoBg?: string;
|
|
129
|
+
badgeInfoText?: string;
|
|
130
|
+
badgeWarningBg?: string;
|
|
131
|
+
badgeWarningText?: string;
|
|
132
|
+
badgeDangerBg?: string;
|
|
133
|
+
badgeDangerText?: string;
|
|
134
|
+
badgeNeutralBg?: string;
|
|
135
|
+
badgeNeutralText?: string;
|
|
136
|
+
badgePositiveBg?: string;
|
|
137
|
+
badgePositiveText?: string;
|
|
138
|
+
badgeTentativeBg?: string;
|
|
139
|
+
badgeTentativeText?: string;
|
|
104
140
|
}
|
|
105
141
|
|
|
106
142
|
declare const MONTHS: string[];
|
|
@@ -110,10 +146,10 @@ declare function normalizeDate(date: Date): Date;
|
|
|
110
146
|
declare function isSameDay(date1: Date, date2: Date): boolean;
|
|
111
147
|
declare function isToday(date: Date): boolean;
|
|
112
148
|
declare function generateYears(minYear?: number, maxYear?: number): number[];
|
|
149
|
+
declare function eventCoversDate(event: CalendarEvent, date: Date): boolean;
|
|
113
150
|
declare function getEventsForDate(events: CalendarEvent[], date: Date): CalendarEvent[];
|
|
114
151
|
declare function hasEvents(events: CalendarEvent[], date: Date): boolean;
|
|
115
152
|
declare function generateCalendarDates(year: number, month: number, events?: CalendarEvent[], weekStartsOn?: 0 | 1): CalendarDate[][];
|
|
116
|
-
declare function getPopupPositionClass(selectedDayIndex: number | null): string;
|
|
117
153
|
declare function getCellClasses(calendarDate: CalendarDate): string[];
|
|
118
154
|
declare function formatDateForDisplay(date: Date): string;
|
|
119
155
|
declare function getMonthYearText(year: number, month: number): string;
|
|
@@ -125,6 +161,17 @@ declare function getDefaultEventColor(category?: string, customColors?: Category
|
|
|
125
161
|
declare function mergeCategoryColors(customColors?: CategoryColorMap): CategoryColorMap;
|
|
126
162
|
declare function isValidHexColor(color: string): boolean;
|
|
127
163
|
declare function getCategoryColor(category: string, customColors?: CategoryColorMap): string;
|
|
164
|
+
declare function escapeHtml(value: unknown): string;
|
|
165
|
+
declare function slugifyToken(value: string): string;
|
|
166
|
+
declare function safeUrl(value: string): string;
|
|
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[];
|
|
128
175
|
|
|
129
176
|
declare class CalendarEngine {
|
|
130
177
|
private state;
|
|
@@ -222,4 +269,4 @@ declare class CalendarEngine {
|
|
|
222
269
|
destroy(): void;
|
|
223
270
|
}
|
|
224
271
|
|
|
225
|
-
export { type CalendarActions, type CalendarConfig, type CalendarDate, CalendarEngine, type CalendarEvent, type CalendarEventHandler, type CalendarProps, type CalendarState, type CalendarTheme, type CalendarViewModel, type CategoryColorMap, DAYS, DEFAULT_CATEGORY_COLORS, MONTHS, MONTHS_FULL,
|
|
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 };
|