@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 +645 -0
- package/dist/alpine-calendar.cdn.js +2 -0
- package/dist/alpine-calendar.cdn.js.map +1 -0
- package/dist/alpine-calendar.css +1 -0
- package/dist/alpine-calendar.es.js +2946 -0
- package/dist/alpine-calendar.es.js.map +1 -0
- package/dist/alpine-calendar.umd.js +2 -0
- package/dist/alpine-calendar.umd.js.map +1 -0
- package/dist/cdn.d.ts +6 -0
- package/dist/core/calendar-date.d.ts +58 -0
- package/dist/core/constraints.d.ts +193 -0
- package/dist/core/grid.d.ts +81 -0
- package/dist/core/presets.d.ts +84 -0
- package/dist/core/selection.d.ts +88 -0
- package/dist/index.d.ts +39 -0
- package/dist/input/formatter.d.ts +36 -0
- package/dist/input/mask.d.ts +62 -0
- package/dist/input/parser.d.ts +30 -0
- package/dist/plugin/calendar-component.d.ts +506 -0
- package/dist/plugin/template.d.ts +11 -0
- package/dist/positioning/popup.d.ts +52 -0
- package/package.json +86 -0
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
|