@reachweb/alpine-calendar 0.1.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 ADDED
@@ -0,0 +1,645 @@
1
+ # Reach Calendar
2
+
3
+ A lightweight, AlpineJS-native calendar component with inline/popup display, input binding with masking, single/multiple/range selection, month/year pickers, birth-date wizard, TailwindCSS 4 theming, and timezone-safe date handling.
4
+
5
+ ## Installation
6
+
7
+ ### npm / pnpm
8
+
9
+ ```bash
10
+ pnpm add @reachgr/alpine-calendar
11
+ # or
12
+ npm install @reachgr/alpine-calendar
13
+ ```
14
+
15
+ ```js
16
+ import Alpine from 'alpinejs'
17
+ import { calendarPlugin } from '@reachgr/alpine-calendar'
18
+ import '@reachgr/alpine-calendar/css'
19
+
20
+ Alpine.plugin(calendarPlugin)
21
+ Alpine.start()
22
+ ```
23
+
24
+ ### CDN (no bundler)
25
+
26
+ ```html
27
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@reachgr/alpine-calendar/dist/alpine-calendar.css">
28
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>
29
+ <script src="https://cdn.jsdelivr.net/npm/@reachgr/alpine-calendar/dist/alpine-calendar.cdn.js"></script>
30
+ ```
31
+
32
+ The CDN build auto-registers via `alpine:init` — no manual setup needed. Works with Livewire, Statamic, or any server-rendered HTML.
33
+
34
+ ## Quick Start
35
+
36
+ You can use x-data in any block element to load the calendar.
37
+
38
+ ### Inline Single Date
39
+
40
+ ```html
41
+ <div x-data="calendar({ mode: 'single', firstDay: 1 })"></div>
42
+ ```
43
+
44
+ ### Popup with Input
45
+
46
+ ```html
47
+ <div x-data="calendar({ mode: 'single', display: 'popup' })">
48
+ <input x-ref="rc-input" type="text" class="rc-input">
49
+ </div>
50
+ ```
51
+
52
+ Provide your own `<input>` with `x-ref="rc-input"` — the calendar binds to it automatically, attaching focus/blur handlers, input masking, and ARIA attributes. The popup overlay with close button, transitions, and mobile-responsive sizing is auto-rendered alongside the input.
53
+
54
+ To use a custom ref name:
55
+
56
+ ```html
57
+ <div x-data="calendar({ display: 'popup', inputRef: 'dateField' })">
58
+ <input x-ref="dateField" type="text" class="my-custom-input">
59
+ </div>
60
+ ```
61
+
62
+ ### Range Selection (2-Month)
63
+
64
+ ```html
65
+ <div x-data="calendar({ mode: 'range', months: 2, firstDay: 1 })"></div>
66
+ ```
67
+
68
+ ### Multiple Date Selection
69
+
70
+ ```html
71
+ <div x-data="calendar({ mode: 'multiple' })"></div>
72
+ ```
73
+
74
+ ### Birth Date Wizard
75
+
76
+ ```html
77
+ <div x-data="calendar({ mode: 'single', wizard: true })"></div>
78
+ ```
79
+
80
+ Wizard modes: `true` (or `'full'`) for Year → Month → Day, `'year-month'` for Year → Month, `'month-day'` for Month → Day.
81
+
82
+ ### Form Submission
83
+
84
+ ```html
85
+ <form>
86
+ <div x-data="calendar({ mode: 'single', name: 'date' })"></div>
87
+ <button type="submit">Submit</button>
88
+ </form>
89
+ ```
90
+
91
+ When `name` is set, hidden `<input>` elements are auto-generated for form submission.
92
+
93
+ ### Disabling Auto-Rendering
94
+
95
+ Set `template: false` to require a manual template, or provide your own `.rc-calendar` element — the calendar skips auto-rendering when it detects an existing `.rc-calendar`:
96
+
97
+ ```html
98
+ <!-- Manual template (auto-rendering skipped) -->
99
+ <div x-data="calendar({ mode: 'single' })">
100
+ <div class="rc-calendar" @keydown="handleKeydown($event)" tabindex="0" role="application">
101
+ <!-- your custom template here -->
102
+ </div>
103
+ </div>
104
+
105
+ <!-- Explicitly disabled -->
106
+ <div x-data="calendar({ mode: 'single', template: false })"></div>
107
+ ```
108
+
109
+ ## Presetting Values
110
+
111
+ ### Initial Value
112
+
113
+ Set `value` in the config to pre-select dates on load:
114
+
115
+ ```html
116
+ <!-- Single date -->
117
+ <div x-data="calendar({ mode: 'single', value: '2026-03-15' })"></div>
118
+
119
+ <!-- Range -->
120
+ <div x-data="calendar({ mode: 'range', value: '2026-03-10 - 2026-03-20' })"></div>
121
+
122
+ <!-- Multiple dates -->
123
+ <div x-data="calendar({ mode: 'multiple', value: '2026-03-10, 2026-03-15, 2026-03-20' })"></div>
124
+ ```
125
+
126
+ ### Dynamic Updates
127
+
128
+ Use `setValue()` to change the selection after initialization:
129
+
130
+ ```html
131
+ <div x-data="calendar({ mode: 'single' })" x-ref="cal">
132
+ <button @click="$refs.cal.setValue('2026-06-15')">Set June 15</button>
133
+ <button @click="$refs.cal.clear()">Clear</button>
134
+ </div>
135
+ ```
136
+
137
+ ### Server-Rendered / Livewire
138
+
139
+ Pass backend variables directly into the config:
140
+
141
+ ```html
142
+ <div x-data="calendar({ mode: 'single', value: '{{ $date }}' })"></div>
143
+ ```
144
+
145
+ Or with Livewire's `@entangle`:
146
+
147
+ ```html
148
+ <div x-data="calendar({ mode: 'single', value: @entangle('date') })"></div>
149
+ ```
150
+
151
+ ## Configuration
152
+
153
+ All options are passed via `x-data="calendar({ ... })"`.
154
+
155
+ | Option | Type | Default | Description |
156
+ |--------|------|---------|-------------|
157
+ | `mode` | `'single' \| 'multiple' \| 'range'` | `'single'` | Selection mode |
158
+ | `display` | `'inline' \| 'popup'` | `'inline'` | Inline calendar or popup with input |
159
+ | `format` | `string` | `'DD/MM/YYYY'` | Date format (tokens: `DD`, `MM`, `YYYY`, `D`, `M`, `YY`) |
160
+ | `months` | `number` | `1` | Months to display (1=single, 2=dual side-by-side, 3+=scrollable) |
161
+ | `firstDay` | `0–6` | `0` | First day of week (0=Sun, 1=Mon, ...) |
162
+ | `mask` | `boolean` | `true` | Enable input masking |
163
+ | `value` | `string` | — | Initial value (ISO or formatted string) |
164
+ | `name` | `string` | `''` | Input name attribute for form submission |
165
+ | `locale` | `string` | — | BCP 47 locale for month/day names |
166
+ | `timezone` | `string` | — | IANA timezone for resolving "today" |
167
+ | `closeOnSelect` | `boolean` | `true` | Close popup after selection |
168
+ | `wizard` | `boolean \| 'year-month' \| 'month-day'` | `false` | Birth date wizard mode |
169
+ | `beforeSelect` | `(date, ctx) => boolean` | — | Custom validation before selection |
170
+ | `showWeekNumbers` | `boolean` | `false` | Show ISO 8601 week numbers alongside the day grid |
171
+ | `inputId` | `string` | — | ID for the popup input (allows external `<label for="...">`) |
172
+ | `inputRef` | `string` | `'rc-input'` | Alpine `x-ref` name for the input element |
173
+ | `scrollHeight` | `number` | `400` | Max height (px) of scrollable container when `months >= 3` |
174
+ | `presets` | `RangePreset[]` | — | Predefined date range shortcuts (see [Range Presets](#range-presets)) |
175
+ | `constraintMessages` | `ConstraintMessages` | — | Custom tooltip strings for disabled dates |
176
+ | `template` | `boolean` | `true` | Auto-render template when no `.rc-calendar` exists |
177
+
178
+ ### Date Constraints
179
+
180
+ | Option | Type | Description |
181
+ |--------|------|-------------|
182
+ | `minDate` | `string` | Earliest selectable date (ISO) |
183
+ | `maxDate` | `string` | Latest selectable date (ISO) |
184
+ | `disabledDates` | `string[]` | Specific dates to disable (ISO) |
185
+ | `disabledDaysOfWeek` | `number[]` | Days of week to disable (0=Sun, 6=Sat) |
186
+ | `enabledDates` | `string[]` | Force-enable specific dates (overrides day-of-week rules) |
187
+ | `enabledDaysOfWeek` | `number[]` | Only these days are selectable |
188
+ | `disabledMonths` | `number[]` | Months to disable (1=Jan, 12=Dec) |
189
+ | `enabledMonths` | `number[]` | Only these months are selectable |
190
+ | `disabledYears` | `number[]` | Specific years to disable |
191
+ | `enabledYears` | `number[]` | Only these years are selectable |
192
+ | `minRange` | `number` | Minimum range length in days (inclusive) |
193
+ | `maxRange` | `number` | Maximum range length in days (inclusive) |
194
+ | `rules` | `CalendarConfigRule[]` | Period-specific constraint overrides |
195
+
196
+ ### Period-Specific Rules
197
+
198
+ Override constraints for specific date ranges. First matching rule wins; unmatched dates use global constraints.
199
+
200
+ ```html
201
+ <div x-data="calendar({
202
+ mode: 'range',
203
+ minRange: 3,
204
+ rules: [
205
+ {
206
+ from: '2025-06-01',
207
+ to: '2025-08-31',
208
+ minRange: 7,
209
+ disabledDaysOfWeek: [0, 6]
210
+ }
211
+ ]
212
+ })">
213
+ ```
214
+
215
+ ## Reactive State
216
+
217
+ These properties are available in templates via Alpine's reactivity:
218
+
219
+ | Property | Type | Description |
220
+ |----------|------|-------------|
221
+ | `mode` | `string` | Current selection mode |
222
+ | `display` | `string` | `'inline'` or `'popup'` |
223
+ | `month` | `number` | Currently viewed month (1–12) |
224
+ | `year` | `number` | Currently viewed year |
225
+ | `view` | `string` | Current view: `'days'`, `'months'`, or `'years'` |
226
+ | `isOpen` | `boolean` | Whether popup is open |
227
+ | `grid` | `MonthGrid[]` | Day grid data for rendering |
228
+ | `monthGrid` | `MonthCell[][]` | Month picker grid |
229
+ | `yearGrid` | `YearCell[][]` | Year picker grid |
230
+ | `inputValue` | `string` | Formatted selected value |
231
+ | `focusedDate` | `CalendarDate \| null` | Keyboard-focused date |
232
+ | `hoverDate` | `CalendarDate \| null` | Mouse-hovered date (for range preview) |
233
+ | `wizardStep` | `number` | Current wizard step (0=off, 1–3) |
234
+ | `showWeekNumbers` | `boolean` | Whether week numbers are displayed |
235
+ | `presets` | `RangePreset[]` | Configured range presets |
236
+ | `isScrollable` | `boolean` | Whether the calendar uses scrollable layout (months >= 3) |
237
+
238
+ ### Computed Getters
239
+
240
+ | Getter | Type | Description |
241
+ |--------|------|-------------|
242
+ | `selectedDates` | `CalendarDate[]` | Array of selected dates |
243
+ | `formattedValue` | `string` | Formatted display string |
244
+ | `hiddenInputValues` | `string[]` | ISO strings for hidden form inputs |
245
+ | `focusedDateISO` | `string` | ISO string of focused date (for `aria-activedescendant`) |
246
+ | `weekdayHeaders` | `string[]` | Localized weekday abbreviations |
247
+ | `yearLabel` | `string` | Current year as string |
248
+ | `decadeLabel` | `string` | Decade range label (e.g., "2024 – 2035") |
249
+ | `wizardStepLabel` | `string` | Current wizard step name |
250
+ | `canGoPrev` | `boolean` | Whether backward navigation is possible |
251
+ | `canGoNext` | `boolean` | Whether forward navigation is possible |
252
+
253
+ ## Methods
254
+
255
+ ### Navigation
256
+
257
+ | Method | Description |
258
+ |--------|-------------|
259
+ | `prev()` | Navigate to previous month/year/decade |
260
+ | `next()` | Navigate to next month/year/decade |
261
+ | `goToToday()` | Jump to current month |
262
+ | `goTo(year, month?)` | Navigate to specific year/month |
263
+ | `setView(view)` | Switch to `'days'`, `'months'`, or `'years'` |
264
+
265
+ ### Selection
266
+
267
+ | Method | Description |
268
+ |--------|-------------|
269
+ | `selectDate(date)` | Select or toggle a date |
270
+ | `selectMonth(month)` | Select month in month picker |
271
+ | `selectYear(year)` | Select year in year picker |
272
+ | `clearSelection()` | Clear all selected dates |
273
+ | `isSelected(date)` | Check if date is selected |
274
+ | `isInRange(date, hover?)` | Check if date is within range |
275
+ | `isRangeStart(date)` | Check if date is range start |
276
+ | `isRangeEnd(date)` | Check if date is range end |
277
+ | `applyPreset(index)` | Apply a range preset by index |
278
+
279
+ ### Programmatic Control
280
+
281
+ Access these via `$refs`:
282
+
283
+ ```html
284
+ <div x-data="calendar({ ... })" x-ref="cal">
285
+ <button @click="$refs.cal.setValue('2025-06-15')">Set Date</button>
286
+ <button @click="$refs.cal.clear()">Clear</button>
287
+ </div>
288
+ ```
289
+
290
+ | Method | Description |
291
+ |--------|-------------|
292
+ | `setValue(value)` | Set selection (ISO string, string[], or CalendarDate) |
293
+ | `clear()` | Clear selection |
294
+ | `goTo(year, month)` | Navigate without changing selection |
295
+ | `open()` / `close()` / `toggle()` | Popup lifecycle |
296
+ | `getSelection()` | Get current selection as `CalendarDate[]` |
297
+ | `updateConstraints(options)` | Update constraints at runtime |
298
+
299
+ ### Template Helpers
300
+
301
+ | Method | Description |
302
+ |--------|-------------|
303
+ | `dayClasses(cell)` | CSS class object for day cells |
304
+ | `monthClasses(cell)` | CSS class object for month cells |
305
+ | `yearClasses(cell)` | CSS class object for year cells |
306
+ | `monthYearLabel(index)` | Formatted "Month Year" label for grid at index |
307
+ | `handleKeydown(event)` | Keyboard navigation handler |
308
+ | `handleFocus()` | Input focus handler (opens popup) |
309
+ | `handleBlur()` | Input blur handler (parses typed value) |
310
+
311
+ ### Input Binding
312
+
313
+ | Method | Description |
314
+ |--------|-------------|
315
+ | `bindInput(el)` | Manually bind to an input element |
316
+ | `handleInput(event)` | For unbound inputs using `:value` + `@input` |
317
+
318
+ ## Events
319
+
320
+ Listen with Alpine's `@` syntax on the calendar container:
321
+
322
+ ```html
323
+ <div x-data="calendar({ ... })"
324
+ @calendar:change="console.log($event.detail)"
325
+ @calendar:navigate="console.log($event.detail)">
326
+ ```
327
+
328
+ | Event | Detail | Description |
329
+ |-------|--------|-------------|
330
+ | `calendar:change` | `{ value, dates, formatted }` | Selection changed |
331
+ | `calendar:navigate` | `{ year, month, view }` | Month/year navigation |
332
+ | `calendar:open` | — | Popup opened |
333
+ | `calendar:close` | — | Popup closed |
334
+ | `calendar:view-change` | `{ view, year, month }` | View switched (days/months/years) |
335
+
336
+ ## Keyboard Navigation
337
+
338
+ | Key | Action |
339
+ |-----|--------|
340
+ | Arrow keys | Move focus between days |
341
+ | Enter / Space | Select focused day |
342
+ | Page Down / Up | Next / previous month |
343
+ | Shift + Page Down / Up | Next / previous year |
344
+ | Home / End | First / last day of month |
345
+ | Escape | Close popup or return to day view |
346
+
347
+ ## Theming
348
+
349
+ The calendar uses CSS custom properties for all visual styles. Override them in your CSS:
350
+
351
+ ### With TailwindCSS 4
352
+
353
+ ```css
354
+ @theme {
355
+ --color-calendar-bg: var(--color-white);
356
+ --color-calendar-text: var(--color-gray-900);
357
+ --color-calendar-primary: var(--color-indigo-600);
358
+ --color-calendar-primary-text: var(--color-white);
359
+ --color-calendar-hover: var(--color-gray-100);
360
+ --color-calendar-disabled: var(--color-gray-300);
361
+ --color-calendar-range: var(--color-indigo-50);
362
+ --color-calendar-today-ring: var(--color-indigo-400);
363
+ --color-calendar-border: var(--color-gray-200);
364
+ --color-calendar-other-month: var(--color-gray-400);
365
+ --color-calendar-weekday: var(--color-gray-500);
366
+ --color-calendar-focus-ring: var(--color-indigo-600);
367
+ --color-calendar-overlay: oklch(0 0 0 / 0.2);
368
+ --radius-calendar: var(--radius-lg);
369
+ --shadow-calendar: var(--shadow-lg);
370
+ --font-calendar: system-ui, -apple-system, sans-serif;
371
+ }
372
+ ```
373
+
374
+ ### Without Tailwind (plain CSS)
375
+
376
+ ```css
377
+ :root {
378
+ --color-calendar-primary: #4f46e5;
379
+ --color-calendar-primary-text: #ffffff;
380
+ --color-calendar-bg: #ffffff;
381
+ --color-calendar-text: #111827;
382
+ --color-calendar-hover: #f3f4f6;
383
+ --color-calendar-range: #eef2ff;
384
+ --color-calendar-today-ring: #818cf8;
385
+ --color-calendar-disabled: #d1d5db;
386
+ --color-calendar-border: #e5e7eb;
387
+ --color-calendar-other-month: #9ca3af;
388
+ --color-calendar-weekday: #6b7280;
389
+ --color-calendar-focus-ring: #4f46e5;
390
+ --color-calendar-overlay: rgba(0, 0, 0, 0.2);
391
+ --radius-calendar: 0.5rem;
392
+ --shadow-calendar: 0 10px 15px -3px rgb(0 0 0 / 0.1);
393
+ --font-calendar: system-ui, -apple-system, sans-serif;
394
+ }
395
+ ```
396
+
397
+ ### CSS Class Reference
398
+
399
+ All classes use the `.rc-` prefix:
400
+
401
+ | Class | Description |
402
+ |-------|-------------|
403
+ | `.rc-calendar` | Root container |
404
+ | `.rc-header` / `.rc-header__nav` / `.rc-header__label` | Navigation header |
405
+ | `.rc-weekdays` / `.rc-weekday` | Weekday header row |
406
+ | `.rc-grid` | Day grid container |
407
+ | `.rc-day` | Day cell |
408
+ | `.rc-day--today` | Today's date |
409
+ | `.rc-day--selected` | Selected date |
410
+ | `.rc-day--range-start` / `.rc-day--range-end` | Range endpoints |
411
+ | `.rc-day--in-range` | Dates within range |
412
+ | `.rc-day--disabled` | Disabled date |
413
+ | `.rc-day--other-month` | Leading/trailing days |
414
+ | `.rc-day--focused` | Keyboard-focused date |
415
+ | `.rc-month-grid` / `.rc-month` | Month picker |
416
+ | `.rc-year-grid` / `.rc-year` | Year picker |
417
+ | `.rc-months--dual` | Two-month side-by-side layout |
418
+ | `.rc-popup-overlay` | Popup backdrop |
419
+ | `.rc-popup-header` / `.rc-popup-header__close` | Popup close header bar |
420
+ | `.rc-calendar--wizard` | Wizard mode container |
421
+ | `.rc-row--week-numbers` / `.rc-week-number` | Week number row and cell |
422
+ | `.rc-grid--week-numbers` | Grid with week number column |
423
+ | `.rc-presets` / `.rc-preset` | Range preset container and buttons |
424
+ | `.rc-months--scroll` | Scrollable multi-month container |
425
+ | `.rc-header--scroll-sticky` | Sticky header in scrollable layout |
426
+ | `.rc-sr-only` | Screen reader only utility |
427
+
428
+ ## Global Defaults
429
+
430
+ Set defaults that apply to every calendar instance:
431
+
432
+ ```js
433
+ import { calendarPlugin } from '@reachgr/alpine-calendar'
434
+
435
+ calendarPlugin.defaults({ firstDay: 1, locale: 'el' })
436
+ Alpine.plugin(calendarPlugin)
437
+ ```
438
+
439
+ Instance config overrides global defaults.
440
+
441
+ ## Week Numbers
442
+
443
+ Display ISO 8601 week numbers alongside the day grid:
444
+
445
+ ```html
446
+ <div x-data="calendar({ mode: 'single', showWeekNumbers: true, firstDay: 1 })"></div>
447
+ ```
448
+
449
+ Week numbers appear in a narrow column to the left of each row.
450
+
451
+ ## Range Presets
452
+
453
+ Add quick-select buttons for common date ranges. Works with `range` and `single` modes:
454
+
455
+ ```html
456
+ <div x-data="calendar({
457
+ mode: 'range',
458
+ presets: [
459
+ presetToday(),
460
+ presetLastNDays(7),
461
+ presetThisWeek(),
462
+ presetThisMonth(),
463
+ presetLastMonth()
464
+ ]
465
+ })"></div>
466
+ ```
467
+
468
+ Import the built-in factories:
469
+
470
+ ```js
471
+ import {
472
+ presetToday,
473
+ presetYesterday,
474
+ presetLastNDays,
475
+ presetThisWeek,
476
+ presetLastWeek,
477
+ presetThisMonth,
478
+ presetLastMonth,
479
+ presetThisYear,
480
+ presetLastYear,
481
+ } from '@reachgr/alpine-calendar'
482
+ ```
483
+
484
+ All factories accept an optional `label` and `timezone` parameter. `presetThisWeek` and `presetLastWeek` also accept a `firstDay` (default: 1 = Monday).
485
+
486
+ Custom presets:
487
+
488
+ ```js
489
+ const customPreset = {
490
+ label: 'Next 30 Days',
491
+ value: () => {
492
+ const today = CalendarDate.today()
493
+ return [today, today.addDays(29)]
494
+ }
495
+ }
496
+ ```
497
+
498
+ ## Multi-Month Scrollable Layout
499
+
500
+ When `months` is 3 or more, the calendar renders as a vertically scrollable container instead of side-by-side panels:
501
+
502
+ ```html
503
+ <div x-data="calendar({ mode: 'range', months: 6 })"></div>
504
+
505
+ <!-- Custom scroll height -->
506
+ <div x-data="calendar({ mode: 'range', months: 12, scrollHeight: 500 })"></div>
507
+ ```
508
+
509
+ A sticky header tracks the currently visible month as you scroll. Default scroll height is 400px.
510
+
511
+ ## Responsive Behavior
512
+
513
+ - **Mobile (<640px):** Popup renders as a centered fullscreen overlay. Touch-friendly targets (min 44px).
514
+ - **Desktop (>=640px):** Popup renders as a centered modal with scale-in animation.
515
+ - **Two months:** Side-by-side on desktop, stacked on mobile.
516
+ - **Scrollable (3+ months):** Smooth scroll with `-webkit-overflow-scrolling: touch`.
517
+ - **`prefers-reduced-motion`:** All animations are disabled.
518
+
519
+ ## Accessibility
520
+
521
+ The calendar targets WCAG 2.1 AA compliance:
522
+
523
+ - Full keyboard navigation (arrow keys, Enter, Escape, Page Up/Down, Home/End)
524
+ - ARIA roles: `application`, `dialog`, `combobox`, `option`, `group`
525
+ - `aria-live="polite"` announcements for navigation and selection changes
526
+ - `aria-activedescendant` for focus management within the grid
527
+ - `aria-modal="true"` on popup overlays
528
+ - `aria-expanded`, `aria-selected`, `aria-disabled` on interactive elements
529
+ - `:focus-visible` outlines on all interactive elements
530
+ - Screen reader support via `.rc-sr-only` utility class
531
+ - Validated with axe-core (no critical or serious violations)
532
+
533
+ ## Bundle Outputs
534
+
535
+ | File | Format | Size (gzip) | Use case |
536
+ |------|--------|-------------|----------|
537
+ | `alpine-calendar.es.js` | ESM | ~19KB | Bundler (`import`) |
538
+ | `alpine-calendar.umd.js` | UMD | ~12KB | Legacy (`require()`) |
539
+ | `alpine-calendar.cdn.js` | IIFE | ~12KB | CDN / `<script>` tag |
540
+ | `alpine-calendar.css` | CSS | ~4KB | All environments |
541
+
542
+ ## TypeScript
543
+
544
+ Full type definitions are included. Key exports:
545
+
546
+ ```ts
547
+ import {
548
+ calendarPlugin,
549
+ CalendarDate,
550
+ getISOWeekNumber,
551
+ SingleSelection,
552
+ MultipleSelection,
553
+ RangeSelection,
554
+ createCalendarData,
555
+ parseDate,
556
+ formatDate,
557
+ createMask,
558
+ computePosition,
559
+ autoUpdate,
560
+ generateMonth,
561
+ generateMonths,
562
+ generateMonthGrid,
563
+ generateYearGrid,
564
+ createDateConstraint,
565
+ createRangeValidator,
566
+ createDisabledReasons,
567
+ isDateDisabled,
568
+ presetToday,
569
+ presetYesterday,
570
+ presetLastNDays,
571
+ presetThisWeek,
572
+ presetLastWeek,
573
+ presetThisMonth,
574
+ presetLastMonth,
575
+ presetThisYear,
576
+ presetLastYear,
577
+ } from '@reachgr/alpine-calendar'
578
+
579
+ import type {
580
+ CalendarConfig,
581
+ CalendarConfigRule,
582
+ RangePreset,
583
+ DayCell,
584
+ MonthCell,
585
+ YearCell,
586
+ Selection,
587
+ Placement,
588
+ PositionOptions,
589
+ DateConstraintOptions,
590
+ DateConstraintProperties,
591
+ DateConstraintRule,
592
+ ConstraintMessages,
593
+ InputMask,
594
+ MaskEventHandlers,
595
+ } from '@reachgr/alpine-calendar'
596
+ ```
597
+
598
+ ## Livewire Integration
599
+
600
+ ```php
601
+ @push('styles')
602
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@reachgr/alpine-calendar/dist/alpine-calendar.css">
603
+ @endpush
604
+ @push('scripts')
605
+ <script src="https://cdn.jsdelivr.net/npm/@reachgr/alpine-calendar/dist/alpine-calendar.cdn.js"></script>
606
+ @endpush
607
+ ```
608
+
609
+ Use `wire:ignore` on the calendar container to prevent Livewire from morphing it:
610
+
611
+ ```html
612
+ <div wire:ignore>
613
+ <div x-data="calendar({ mode: 'single', display: 'popup' })"
614
+ @calendar:change="$wire.set('date', $event.detail.value)">
615
+ <input x-ref="rc-input" type="text" class="rc-input">
616
+ </div>
617
+ </div>
618
+ ```
619
+
620
+ ## Development
621
+
622
+ ```bash
623
+ pnpm install # Install dependencies
624
+ pnpm dev # Start dev server with demo
625
+ pnpm test # Run tests
626
+ pnpm test:watch # Run tests in watch mode
627
+ pnpm test:coverage # Run tests with coverage report
628
+ pnpm typecheck # Type-check without emitting
629
+ pnpm lint # Lint source files
630
+ pnpm lint:fix # Lint and auto-fix
631
+ pnpm format # Format source files with Prettier
632
+ pnpm build # Build all bundles (ESM + UMD + CDN + CSS + types)
633
+ pnpm build:lib # Build ESM + UMD only
634
+ pnpm build:cdn # Build CDN/IIFE bundle only
635
+ ```
636
+
637
+ Before a release, run the full verification chain:
638
+
639
+ ```bash
640
+ pnpm typecheck && pnpm lint && pnpm test && pnpm build
641
+ ```
642
+
643
+ ## License
644
+
645
+ MIT