kalendly 0.2.0 → 0.2.2

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 CHANGED
@@ -9,6 +9,8 @@ A universal calendar web component — works in React, Vue, Svelte, Angular, Sol
9
9
  - **Themeable**: CSS variables + JS property API
10
10
  - **Type Safe**: Full TypeScript support
11
11
  - **Event-rich**: Categories, priorities, time ranges, attendees, and more
12
+ - **Availability mode**: Day and time views for booking flows — hides event details, shows booked/free cells
13
+ - **Lazy loading**: Per-month on-demand fetch with skeleton shimmer state
12
14
  - **Accessible**: Built with accessibility in mind
13
15
  - **Tree-shakeable**: Import only what you need
14
16
 
@@ -60,11 +62,7 @@ npm install kalendly
60
62
  />
61
63
  <script src="https://unpkg.com/kalendly/dist/index.umd.js"></script>
62
64
 
63
- <kal-calendar
64
- id="cal"
65
- title="My Calendar"
66
- initial-date="2025-01-15"
67
- ></kal-calendar>
65
+ <kal-calendar id="cal" title="My Calendar"></kal-calendar>
68
66
 
69
67
  <script>
70
68
  const cal = document.getElementById('cal');
@@ -147,7 +145,55 @@ function App() {
147
145
 
148
146
  ### Vue 3
149
147
 
150
- Vue 3 supports custom elements natively — bind props with `:` and listen to events with `@`:
148
+ Vue 3 supports custom elements natively — bind props with `:` and listen to events with `@`.
149
+
150
+ > **Vue-specific:** Vue's template compiler warns on unknown tags. React, Solid.js, and Svelte treat hyphenated tags as DOM elements natively — no config needed. Angular uses `CUSTOM_ELEMENTS_SCHEMA` (see the Angular section below).
151
+
152
+ Tell Vue's compiler that `<kal-*>` tags are native custom elements. The config location depends on your build tool:
153
+
154
+ **Vite** (`vite.config.ts`):
155
+
156
+ ```ts
157
+ vue({
158
+ template: {
159
+ compilerOptions: {
160
+ isCustomElement: tag => tag.startsWith('kal-'),
161
+ },
162
+ },
163
+ });
164
+ ```
165
+
166
+ **Nuxt 3** (`nuxt.config.ts`):
167
+
168
+ ```ts
169
+ export default defineNuxtConfig({
170
+ vue: {
171
+ compilerOptions: {
172
+ isCustomElement: tag => tag.startsWith('kal-'),
173
+ },
174
+ },
175
+ });
176
+ ```
177
+
178
+ **webpack / Vue CLI** (`vue.config.js`):
179
+
180
+ ```js
181
+ module.exports = {
182
+ chainWebpack: config => {
183
+ config.module
184
+ .rule('vue')
185
+ .use('vue-loader')
186
+ .tap(options => ({
187
+ ...options,
188
+ compilerOptions: {
189
+ isCustomElement: tag => tag.startsWith('kal-'),
190
+ },
191
+ }));
192
+ },
193
+ };
194
+ ```
195
+
196
+ Without this the component still renders correctly — Vue falls back to a native DOM element. This only suppresses the console warning.
151
197
 
152
198
  ```vue
153
199
  <template>
@@ -242,20 +288,6 @@ function App() {
242
288
  }
243
289
  ```
244
290
 
245
- ## Migration from v0.1.x
246
-
247
- v0.2.0 replaces the four separate framework packages with a single web component.
248
-
249
- | Before (v0.1.x) | After (v0.2.0+) |
250
- | --------------------------------------------------- | ---------------------------------- |
251
- | `import { Calendar } from 'kalendly/react'` | `import 'kalendly'` |
252
- | `import { Calendar } from 'kalendly/vue'` | `import 'kalendly'` |
253
- | `import { Calendar } from 'kalendly/react-native'` | Not supported |
254
- | `import { createCalendar } from 'kalendly/vanilla'` | `import 'kalendly'` |
255
- | `<Calendar events={events} />` | `<kal-calendar events={events} />` |
256
-
257
- React Native is out of scope and not replaced.
258
-
259
291
  ## Styling
260
292
 
261
293
  ### Loading styles
@@ -301,46 +333,158 @@ document.querySelector('kal-calendar').theme = {
301
333
 
302
334
  Primitives are set as HTML attributes:
303
335
 
304
- | Attribute | Type | Default | Description |
305
- | ----------------------- | ---------- | ---------------- | ------------------------------------- |
306
- | `title` | `string` | — | Calendar title |
307
- | `initial-date` | `string` | today | ISO date string for initial view |
308
- | `min-year` | `string` | currentYear - 30 | Minimum year in picker |
309
- | `max-year` | `string` | currentYear + 10 | Maximum year in picker |
310
- | `week-starts-on` | `"0"\|"1"` | `"0"` | Week start: 0 = Sunday, 1 = Monday |
311
- | `use-short-month-names` | `string` | — | Present = use abbreviated month names |
336
+ | Attribute | Type | Default | Description |
337
+ | ----------------------- | ---------------- | ---------------- | ------------------------------------------------ |
338
+ | `title` | `string` | — | Calendar title |
339
+ | `initial-date` | `string` | today | ISO date string for initial view |
340
+ | `min-year` | `string` | currentYear - 30 | Minimum year in picker |
341
+ | `max-year` | `string` | currentYear + 10 | Maximum year in picker |
342
+ | `week-starts-on` | `"0"\|"1"` | `"0"` | Week start: 0 = Sunday, 1 = Monday |
343
+ | `use-short-month-names` | `string` | — | Present = use abbreviated month names |
344
+ | `availability-mode` | `"day"\|"time"` | — | Hides event details; shows booked/free cells |
345
+ | `selectable` | `"range"` | — | Enables day/slot selection (requires avail mode) |
346
+ | `loading` | `boolean` (flag) | — | Present = render skeleton shimmer cells |
312
347
 
313
348
  ## Properties
314
349
 
315
350
  Rich objects are set as JS properties (not attributes):
316
351
 
317
- | Property | Type | Description |
318
- | ---------------- | ---------------------------------- | -------------------------------- |
319
- | `events` | `CalendarEvent[]` | Events to display |
320
- | `theme` | `CalendarTheme` | Custom theme colors |
321
- | `categoryColors` | `CategoryColorMap` | Per-category color overrides |
322
- | `renderEvent` | `(event: CalendarEvent) => string` | Custom event HTML renderer |
323
- | `renderNoEvents` | `() => string` | Custom empty-state HTML renderer |
352
+ | Property | Type | Description |
353
+ | ---------------- | ---------------------------------- | ------------------------------------------------- |
354
+ | `events` | `CalendarEvent[]` | Events to display |
355
+ | `loading` | `boolean` | `true` = render skeleton cells; `false` = restore |
356
+ | `theme` | `CalendarTheme` | Custom theme colors |
357
+ | `categoryColors` | `CategoryColorMap` | Per-category color overrides |
358
+ | `renderEvent` | `(event: CalendarEvent) => string` | Custom event HTML renderer |
359
+ | `renderNoEvents` | `() => string` | Custom empty-state HTML renderer |
360
+
361
+ > `renderEvent` and `renderNoEvents` are ignored when `availability-mode` is set.
324
362
 
325
363
  ## Custom Events
326
364
 
327
- | Event | `detail` shape | Description |
328
- | ------------------ | ----------------------------------------- | ------------------------- |
329
- | `cal-date-select` | `{ date: Date, events: CalendarEvent[] }` | User clicked a date |
330
- | `cal-month-change` | `{ year: number, month: number }` | Month navigation occurred |
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 |
370
+
371
+ All events bubble and are composed (cross Shadow DOM boundaries).
372
+
373
+ `cal-availability-select` detail shape depends on mode:
374
+
375
+ - **Day mode** (`availability-mode="day"`): `{ startDate: Date, endDate: Date }` — first click gives `startDate === endDate`; second click extends the range; third click resets
376
+ - **Time mode** (`availability-mode="time"`): `{ date: Date, startTime: string, endTime: string }` — first click selects a single slot; second click extends; third click resets
377
+
378
+ ## Availability Mode
379
+
380
+ Hides all event details from the end user — only booked/free state is shown. Designed for scheduling and booking flows where the server's event data must not be exposed to the viewer.
381
+
382
+ ### Day view
383
+
384
+ ```html
385
+ <kal-calendar availability-mode="day"></kal-calendar>
386
+ ```
387
+
388
+ Days with events are tinted red (booked); days without events are tinted green (free). 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.
389
+
390
+ <div align="center">
391
+ <img src="./docs/images/day.png" alt="Availability day view — month grid with red booked cells and green free cells"/>
392
+ <p><em>Day view: the month grid shows only booked/free state per day — event names, times, and attendees are never revealed</em></p>
393
+ </div>
394
+
395
+ Pass the minimal event shape — only `id` and `date` are required; `startTime`/`endTime` are optional and mark the whole day as booked regardless:
331
396
 
332
- Both events bubble and are composed (cross Shadow DOM boundaries).
397
+ ```js
398
+ cal.events = [
399
+ { id: 1, date: new Date(2025, 4, 8) },
400
+ { id: 2, date: new Date(2025, 4, 8), startTime: '14:00', endTime: '16:00' },
401
+ { id: 3, date: new Date(2025, 4, 20), startTime: '10:00', endTime: '12:00' },
402
+ ];
403
+ ```
404
+
405
+ ### Time view
406
+
407
+ ```html
408
+ <kal-calendar availability-mode="time"></kal-calendar>
409
+ ```
410
+
411
+ Clicking a day opens a popup with a 24-slot hourly grid (00:00 – 23:00). Each slot shows only "Booked" or "Available" — no event name or organiser is ever rendered. A slot is booked if any event's time window overlaps that hour; the rest are free.
412
+
413
+ <div align="center">
414
+ <img src="./docs/images/time.png" alt="Availability time view — day popup showing 24 hourly slots coloured red (booked) or green (available)"/>
415
+ <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
+ </div>
417
+
418
+ ### Selectable range
419
+
420
+ Add `selectable="range"` to let the user pick a free day or time slot:
421
+
422
+ ```html
423
+ <kal-calendar availability-mode="day" selectable="range"></kal-calendar>
424
+ <kal-calendar availability-mode="time" selectable="range"></kal-calendar>
425
+ ```
426
+
427
+ ```js
428
+ // Day mode — fires on every click
429
+ cal.addEventListener('cal-availability-select', e => {
430
+ const { startDate, endDate } = e.detail;
431
+ console.log('Selected:', startDate, '→', endDate);
432
+ });
433
+
434
+ // Time mode — fires on every slot click
435
+ cal.addEventListener('cal-availability-select', e => {
436
+ const { date, startTime, endTime } = e.detail;
437
+ console.log('Slot:', date, startTime, '–', endTime);
438
+ });
439
+ ```
440
+
441
+ Booked days/slots cannot be selected. The 3-click state machine: first click selects, second extends, third resets.
442
+
443
+ ## Lazy Event Fetching
444
+
445
+ `cal-month-change` fires **before** the new month renders, so you can set `loading = true` synchronously — the calendar shows skeleton shimmer cells from the first frame with no empty-calendar flash.
446
+
447
+ ```js
448
+ cal.addEventListener('cal-month-change', async ({ detail }) => {
449
+ const { year, month } = detail;
450
+ cal.loading = true;
451
+ cal.events = await fetchEvents(year, month); // your API call
452
+ cal.loading = false;
453
+ });
454
+ ```
455
+
456
+ The "dump all events upfront" pattern still works unchanged — `cal-month-change` is optional:
457
+
458
+ ```js
459
+ // Load once, component handles all months
460
+ cal.events = allEvents;
461
+ ```
462
+
463
+ ## Core API
464
+
465
+ `querySelector('kal-calendar')` returns `CalendarElement | null` automatically — no cast needed:
466
+
467
+ ```ts
468
+ import type { CalendarElement } from 'kalendly';
469
+
470
+ const cal = document.querySelector('kal-calendar'); // CalendarElement | null
471
+ cal?.goToDate(new Date());
472
+ cal?.updateEvents(events);
473
+ cal?.updateTheme(theme);
474
+ cal?.getCurrentDate(); // Date | null
475
+ cal?.getEngine(); // CalendarEngine
476
+ ```
333
477
 
334
- ## JavaScript API
478
+ **JavaScript** works the same way without the import:
335
479
 
336
480
  ```js
337
481
  const cal = document.querySelector('kal-calendar');
338
482
 
339
- cal.updateEvents(newEvents); // Re-render with new events
340
- cal.updateTheme(newTheme); // Apply new theme
341
- cal.goToDate(new Date(2025, 5, 1)); // Navigate to date
342
- cal.getCurrentDate(); // Returns currently selected Date
343
- cal.getEngine(); // Access CalendarEngine directly
483
+ cal.updateEvents(newEvents);
484
+ cal.updateTheme(newTheme);
485
+ cal.goToDate(new Date());
486
+ cal.getCurrentDate();
487
+ cal.getEngine();
344
488
  ```
345
489
 
346
490
  ## CalendarEvent Interface
@@ -470,14 +614,6 @@ Custom Elements v1 — Chrome 67+, Firefox 63+, Safari 12.1+, Edge 79+.
470
614
 
471
615
  See [CONTRIBUTING.md](CONTRIBUTING.md).
472
616
 
473
- ```bash
474
- git clone https://github.com/callezenwaka/kalendly.git
475
- cd kalendly
476
- npm install
477
- npm test
478
- npm run dev:examples
479
- ```
480
-
481
617
  ## License
482
618
 
483
619
  MIT © Callis Ezenwaka
@@ -488,7 +624,8 @@ See [CHANGELOG.md](CHANGELOG.md).
488
624
 
489
625
  ### Recent Updates
490
626
 
491
- - **v0.2.0**: Migrated to a single `<kal-calendar>` web component — works natively in React, Vue, Angular, and plain HTML with no framework dependency
627
+ - **v0.2.1**: Add availability mode (day/time views), selectable range, lazy event fetching with skeleton loading
628
+ - **v0.2.0**: Migrated to a single `<kal-calendar>` web component — works natively in React, Vue, Angular, Svelte, Solid.js, and plain HTML with no framework dependency
492
629
  - **v0.1.7**: Vanilla calendar performance optimization with event delegation
493
630
  - **v0.1.6**: Navigation enhancements — Today button, month/year picker, optional `title` prop
494
631
  - **v0.1.5**: Universal theming system, TypeScript improvements
@@ -98,6 +98,8 @@ interface CalendarTheme {
98
98
  borderColor?: string;
99
99
  todayOutline?: string;
100
100
  selectedBg?: string;
101
+ headerBg?: string;
102
+ popupBg?: string;
101
103
  eventIndicator?: string;
102
104
  }
103
105
 
@@ -98,6 +98,8 @@ interface CalendarTheme {
98
98
  borderColor?: string;
99
99
  todayOutline?: string;
100
100
  selectedBg?: string;
101
+ headerBg?: string;
102
+ popupBg?: string;
101
103
  eventIndicator?: string;
102
104
  }
103
105
 
package/dist/index.d.mts CHANGED
@@ -98,6 +98,8 @@ interface CalendarTheme {
98
98
  borderColor?: string;
99
99
  todayOutline?: string;
100
100
  selectedBg?: string;
101
+ headerBg?: string;
102
+ popupBg?: string;
101
103
  eventIndicator?: string;
102
104
  }
103
105
 
@@ -239,15 +241,23 @@ declare class CalendarElement extends HTMLElement {
239
241
  private _categoryColors;
240
242
  private _renderEvent;
241
243
  private _renderNoEvents;
244
+ private _rangeStart;
245
+ private _rangeEnd;
246
+ private _timeRangeDate;
247
+ private _timeRangeStart;
248
+ private _timeRangeEnd;
249
+ private _timeRangeComplete;
242
250
  get events(): CalendarEvent[];
243
251
  set events(val: CalendarEvent[]);
244
252
  set theme(val: CalendarTheme);
245
253
  set categoryColors(val: CategoryColorMap);
246
254
  set renderEvent(val: (e: CalendarEvent) => string);
247
255
  set renderNoEvents(val: () => string);
256
+ get loading(): boolean;
257
+ set loading(val: boolean);
248
258
  connectedCallback(): void;
249
259
  disconnectedCallback(): void;
250
- attributeChangedCallback(_name: string, oldVal: string | null, newVal: string | null): void;
260
+ attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
251
261
  private get minYear();
252
262
  private get maxYear();
253
263
  private initEngine;
@@ -265,6 +275,11 @@ declare class CalendarElement extends HTMLElement {
265
275
  getEngine(): CalendarEngine;
266
276
  }
267
277
  declare function defineCalendarElement(tagName?: string): void;
278
+ declare global {
279
+ interface HTMLElementTagNameMap {
280
+ 'kal-calendar': CalendarElement;
281
+ }
282
+ }
268
283
 
269
284
  interface CalendarElementProps {
270
285
  events?: CalendarEvent[];
package/dist/index.d.ts CHANGED
@@ -98,6 +98,8 @@ interface CalendarTheme {
98
98
  borderColor?: string;
99
99
  todayOutline?: string;
100
100
  selectedBg?: string;
101
+ headerBg?: string;
102
+ popupBg?: string;
101
103
  eventIndicator?: string;
102
104
  }
103
105
 
@@ -239,15 +241,23 @@ declare class CalendarElement extends HTMLElement {
239
241
  private _categoryColors;
240
242
  private _renderEvent;
241
243
  private _renderNoEvents;
244
+ private _rangeStart;
245
+ private _rangeEnd;
246
+ private _timeRangeDate;
247
+ private _timeRangeStart;
248
+ private _timeRangeEnd;
249
+ private _timeRangeComplete;
242
250
  get events(): CalendarEvent[];
243
251
  set events(val: CalendarEvent[]);
244
252
  set theme(val: CalendarTheme);
245
253
  set categoryColors(val: CategoryColorMap);
246
254
  set renderEvent(val: (e: CalendarEvent) => string);
247
255
  set renderNoEvents(val: () => string);
256
+ get loading(): boolean;
257
+ set loading(val: boolean);
248
258
  connectedCallback(): void;
249
259
  disconnectedCallback(): void;
250
- attributeChangedCallback(_name: string, oldVal: string | null, newVal: string | null): void;
260
+ attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
251
261
  private get minYear();
252
262
  private get maxYear();
253
263
  private initEngine;
@@ -265,6 +275,11 @@ declare class CalendarElement extends HTMLElement {
265
275
  getEngine(): CalendarEngine;
266
276
  }
267
277
  declare function defineCalendarElement(tagName?: string): void;
278
+ declare global {
279
+ interface HTMLElementTagNameMap {
280
+ 'kal-calendar': CalendarElement;
281
+ }
282
+ }
268
283
 
269
284
  interface CalendarElementProps {
270
285
  events?: CalendarEvent[];