@keenmate/web-daterangepicker 1.14.0-rc01 → 1.14.0-rc02
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 +222 -1062
- package/dist/date-picker.d.ts +11 -11
- package/dist/index.d.ts +1 -1
- package/dist/modules/click-events/index.d.ts +2 -2
- package/dist/style.css +1 -1
- package/dist/types.d.ts +28 -28
- package/dist/web-component.d.ts +15 -15
- package/dist/web-daterangepicker.js +291 -290
- package/dist/web-daterangepicker.umd.js +8 -8
- package/package.json +1 -1
- package/src/css/base.css +14 -0
- package/src/css/header-navigation.css +1 -1
- package/src/css/modal.css +5 -5
- package/src/css/states.css +8 -8
- package/src/css/variables.css +9 -1
package/README.md
CHANGED
|
@@ -1,1062 +1,222 @@
|
|
|
1
|
-
# Date Range Picker Web Component
|
|
2
|
-
|
|
3
|
-
A lightweight,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
//
|
|
110
|
-
import '@keenmate/web-daterangepicker
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
|
159
|
-
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
## Events
|
|
224
|
-
|
|
225
|
-
| Event | Detail | Description |
|
|
226
|
-
|-------|--------|-------------|
|
|
227
|
-
| `date-select` | `{ date?, dateRange?, formattedValue }` | Fired when a date is selected |
|
|
228
|
-
| `change` | `{ date?, dateRange?, formattedValue }` | Fired when selection changes |
|
|
229
|
-
| `custom-action` | `{ [key: string]: string }` | Fired when a button with `data-action="custom"` is clicked. Detail contains all `data-*` attributes as camelCase keys. |
|
|
230
|
-
|
|
231
|
-
> There are no separate `apply` or `cancel` events. The Apply button commits the pending selection and dispatches `change`. Pressing Escape with an uncommitted selection silently restores the input value and fires nothing.
|
|
232
|
-
|
|
233
|
-
## Keyboard Shortcuts
|
|
234
|
-
|
|
235
|
-
- **↑ ↓** - Navigate up/down by week
|
|
236
|
-
- **← →** - Navigate left/right by day
|
|
237
|
-
- **Ctrl+← / Ctrl+→** - Previous / next month (maintains day position)
|
|
238
|
-
- **PageUp / PageDown** - Previous / next month (maintains day position)
|
|
239
|
-
- **Home** - First day of current month (repeat to cycle backwards through months)
|
|
240
|
-
- **End** - Last day of current month (repeat to cycle forwards through months)
|
|
241
|
-
- **Ctrl+Home** - January 1st of current year (repeat for previous year)
|
|
242
|
-
- **Ctrl+End** - December 31st of current year (repeat for next year)
|
|
243
|
-
- **Enter** - Select focused day
|
|
244
|
-
- **Escape** - Close calendar
|
|
245
|
-
- **Tab / Shift+Tab** - Switch between month columns (multi-month mode)
|
|
246
|
-
- **T** - Jump to today
|
|
247
|
-
|
|
248
|
-
## ⚠️ Working with Dates Across Timezones
|
|
249
|
-
|
|
250
|
-
This is a **calendar-date picker** — it represents days, not moments in time. Skipping this section will eventually cost you a one-day-shift bug, so please read it.
|
|
251
|
-
|
|
252
|
-
### How the picker represents dates
|
|
253
|
-
|
|
254
|
-
When the user clicks April 30, the picker stores `new Date(year, month - 1, day)` — that's **local midnight** on the picked day. The same is true for `selectedDate`, `selectedStartDate`, `selectedEndDate`, and the Date passed to every callback. There's no UTC anywhere in the picker's internal lookup paths.
|
|
255
|
-
|
|
256
|
-
### The trap: `Date.prototype.toISOString()`
|
|
257
|
-
|
|
258
|
-
`toISOString()` returns the date in **UTC**. For any user not in UTC, that string represents a different *calendar* day than the one they see:
|
|
259
|
-
|
|
260
|
-
```js
|
|
261
|
-
// User in Moscow (UTC+3) picks April 30.
|
|
262
|
-
// selectedDate is `new Date(2026, 3, 30)` = April 30 00:00 MSK = April 29 21:00 UTC.
|
|
263
|
-
|
|
264
|
-
selectedDate.toISOString().split('T')[0] // → "2026-04-29" ❌ wrong day
|
|
265
|
-
selectedDate.getDate() // → 30 ✅ what the user clicked
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
This is what broke the example demos in earlier versions: lookup keys built from `dayOffset()` (local) didn't match keys derived from `toISOString()` (UTC). Badges rendered one day late, tooltips on the right day. Same bug strikes any user who tries `date.toISOString()` to key into a `Map<string, DateInfo>`.
|
|
269
|
-
|
|
270
|
-
### The rule
|
|
271
|
-
|
|
272
|
-
**Format dates from local components, never from `toISOString()`:**
|
|
273
|
-
|
|
274
|
-
```js
|
|
275
|
-
const toLocalISO = (d) =>
|
|
276
|
-
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
277
|
-
|
|
278
|
-
picker.getDateMetadataCallback = (date) => {
|
|
279
|
-
const dateStr = toLocalISO(date); // ✅ matches what the user sees
|
|
280
|
-
// …lookup, return DateInfo…
|
|
281
|
-
};
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### Sending dates to a server / between users
|
|
285
|
-
|
|
286
|
-
`Date` objects can't survive transport — they get serialized. The serialization format determines whether the date stays on the right calendar day across timezones:
|
|
287
|
-
|
|
288
|
-
| What you send | Russian user picks Apr 30 → LA user receives | Verdict |
|
|
289
|
-
|---|---|---|
|
|
290
|
-
| `event.detail.formattedValue` (`"2026-04-30"`) | `"2026-04-30"` → set on LA picker → April 30 | ✅ |
|
|
291
|
-
| `JSON.stringify(detail)` (Date → ISO string) | `"2026-04-29T21:00:00.000Z"` → April 29 in LA | ❌ |
|
|
292
|
-
| `selectedDate.toISOString()` | `"2026-04-29T21:00:00.000Z"` → April 29 in LA | ❌ |
|
|
293
|
-
| `toLocalISO(selectedDate)` (`"2026-04-30"`) | `"2026-04-30"` | ✅ |
|
|
294
|
-
|
|
295
|
-
**Recommendation:** transmit calendar dates as `YYYY-MM-DD` **strings**, never as ISO timestamps or raw `Date`/JSON-serialized payloads.
|
|
296
|
-
|
|
297
|
-
### Receiving a date string
|
|
298
|
-
|
|
299
|
-
`new Date("2026-04-30")` parses as **UTC midnight** — surprising for non-UTC users (in LA it'd be April 29). Build local Dates from the string parts:
|
|
300
|
-
|
|
301
|
-
```js
|
|
302
|
-
// ❌ Don't do this
|
|
303
|
-
const d = new Date("2026-04-30"); // UTC midnight, surprising in non-UTC
|
|
304
|
-
|
|
305
|
-
// ✅ Do this
|
|
306
|
-
const [y, m, day] = "2026-04-30".split("-").map(Number);
|
|
307
|
-
const localDate = new Date(y, m - 1, day); // April 30 local — what the user picked
|
|
308
|
-
|
|
309
|
-
// Or, if you just need to set the picker:
|
|
310
|
-
picker.setInputValue("2026-04-30"); // picker handles parsing internally
|
|
311
|
-
picker.value = "2026-04-30"; // attribute setter
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Quick checklist
|
|
315
|
-
|
|
316
|
-
- [ ] Picker callbacks: format Dates with `toLocalISO()`, not `toISOString()`
|
|
317
|
-
- [ ] Outgoing dates (server, URL, localStorage): send `YYYY-MM-DD` strings
|
|
318
|
-
- [ ] Incoming dates: prefer `picker.setInputValue("YYYY-MM-DD")` over manual `new Date(...)` parsing
|
|
319
|
-
- [ ] Map/dictionary keys for date lookups: build keys with `toLocalISO()` and lookup the same way
|
|
320
|
-
|
|
321
|
-
## Advanced Features
|
|
322
|
-
|
|
323
|
-
### Week Start Day
|
|
324
|
-
|
|
325
|
-
Control which day the week starts on (auto-detected by default from user's locale):
|
|
326
|
-
|
|
327
|
-
```html
|
|
328
|
-
<!-- Auto-detect from locale (default) -->
|
|
329
|
-
<web-daterangepicker week-start-day="auto"></web-daterangepicker>
|
|
330
|
-
|
|
331
|
-
<!-- Force Sunday start -->
|
|
332
|
-
<web-daterangepicker week-start-day="0"></web-daterangepicker>
|
|
333
|
-
|
|
334
|
-
<!-- Force Monday start (common in Europe) -->
|
|
335
|
-
<web-daterangepicker week-start-day="1"></web-daterangepicker>
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Disabled Dates & Date Restrictions
|
|
339
|
-
|
|
340
|
-
#### Simple Restrictions (Attributes)
|
|
341
|
-
|
|
342
|
-
```html
|
|
343
|
-
<!-- Disable weekends -->
|
|
344
|
-
<web-daterangepicker disabled-weekdays="0,6"></web-daterangepicker>
|
|
345
|
-
|
|
346
|
-
<!-- Date range restriction -->
|
|
347
|
-
<web-daterangepicker
|
|
348
|
-
min-date="2025-01-01"
|
|
349
|
-
max-date="2025-12-31">
|
|
350
|
-
</web-daterangepicker>
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
#### Complex Restrictions (JavaScript)
|
|
354
|
-
|
|
355
|
-
```javascript
|
|
356
|
-
const picker = document.querySelector('web-daterangepicker');
|
|
357
|
-
|
|
358
|
-
// Disable specific dates (e.g., public holidays)
|
|
359
|
-
picker.disabledDates = [
|
|
360
|
-
'2025-12-25', // Christmas
|
|
361
|
-
'2025-01-01', // New Year
|
|
362
|
-
new Date(2025, 6, 4) // July 4th
|
|
363
|
-
];
|
|
364
|
-
|
|
365
|
-
// Custom disable logic (e.g., cottage booking)
|
|
366
|
-
picker.getDateMetadataCallback = (date) => {
|
|
367
|
-
// Disable all dates that overlap with existing bookings
|
|
368
|
-
const isBooked = bookedRanges.some(range =>
|
|
369
|
-
date >= range.start && date <= range.end
|
|
370
|
-
);
|
|
371
|
-
return isBooked ? { isDisabled: true } : null;
|
|
372
|
-
};
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### Special Dates (Holidays, Events)
|
|
376
|
-
|
|
377
|
-
Add visual indicators and labels to specific dates:
|
|
378
|
-
|
|
379
|
-
```javascript
|
|
380
|
-
const picker = document.querySelector('web-daterangepicker');
|
|
381
|
-
|
|
382
|
-
picker.specialDates = [
|
|
383
|
-
{
|
|
384
|
-
date: '2025-12-25',
|
|
385
|
-
dayClass: 'holiday', // CSS class for the day cell
|
|
386
|
-
badgeText: '🎄', // Badge overlay (emoji or short text)
|
|
387
|
-
dayTooltip: 'Christmas Day'
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
date: '2025-07-04',
|
|
391
|
-
dayClass: 'holiday',
|
|
392
|
-
badgeText: '🎆',
|
|
393
|
-
dayTooltip: 'Independence Day'
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
date: '2025-02-14',
|
|
397
|
-
dayClass: 'event',
|
|
398
|
-
badgeText: '❤️',
|
|
399
|
-
dayTooltip: 'Valentine\'s Day'
|
|
400
|
-
}
|
|
401
|
-
];
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### Advanced Styling & Info
|
|
405
|
-
|
|
406
|
-
For complete control, use the `getDateMetadataCallback`:
|
|
407
|
-
|
|
408
|
-
```javascript
|
|
409
|
-
// Local-date formatter — see "Working with Dates Across Timezones" above.
|
|
410
|
-
// Don't use date.toISOString() here; it shifts by one day in non-UTC timezones.
|
|
411
|
-
const toLocalISO = (d) =>
|
|
412
|
-
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
413
|
-
|
|
414
|
-
picker.getDateMetadataCallback = (date) => {
|
|
415
|
-
const dateStr = toLocalISO(date);
|
|
416
|
-
|
|
417
|
-
// Check if it's a peak season date
|
|
418
|
-
if (isPeakSeason(date)) {
|
|
419
|
-
return {
|
|
420
|
-
dayClass: 'peak-season',
|
|
421
|
-
badgeText: '$$$',
|
|
422
|
-
dayTooltip: 'Peak season pricing'
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Check if it's a special offer date
|
|
427
|
-
if (specialOffers[dateStr]) {
|
|
428
|
-
return {
|
|
429
|
-
dayClass: 'special-offer',
|
|
430
|
-
badgeText: '%',
|
|
431
|
-
dayTooltip: `${specialOffers[dateStr]}% off!`
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
return null;
|
|
436
|
-
};
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
### CSS Styling for Special Dates
|
|
440
|
-
|
|
441
|
-
Since the component uses Shadow DOM, inject custom styles via the `customStylesCallback`:
|
|
442
|
-
|
|
443
|
-
```javascript
|
|
444
|
-
picker.customStylesCallback = () => `
|
|
445
|
-
/* Holiday styling */
|
|
446
|
-
.drp-date-picker__day.holiday {
|
|
447
|
-
background-color: rgba(239, 68, 68, 0.1);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/* Event styling */
|
|
451
|
-
.drp-date-picker__day.event {
|
|
452
|
-
background-color: rgba(16, 185, 129, 0.1);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/* Custom class example */
|
|
456
|
-
.drp-date-picker__day.peak-season {
|
|
457
|
-
background-color: rgba(251, 191, 36, 0.15);
|
|
458
|
-
font-weight: 600;
|
|
459
|
-
}
|
|
460
|
-
`;
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
### Date Selection Validation (beforeDateSelectCallback)
|
|
464
|
-
|
|
465
|
-
Validate or modify date selections before they're applied. Supports async validation (e.g., API calls):
|
|
466
|
-
|
|
467
|
-
```javascript
|
|
468
|
-
const picker = document.querySelector('web-daterangepicker');
|
|
469
|
-
|
|
470
|
-
picker.beforeDateSelectCallback = async (selection) => {
|
|
471
|
-
// selection is Date (single mode) or { start: Date, end: Date } (range mode)
|
|
472
|
-
|
|
473
|
-
// Example: Check availability via API.
|
|
474
|
-
// Send YYYY-MM-DD strings, not toISOString() — the picker is a calendar-date
|
|
475
|
-
// picker, and ISO timestamps shift by ±1 day across timezones.
|
|
476
|
-
const response = await fetch('/api/check-availability', {
|
|
477
|
-
method: 'POST',
|
|
478
|
-
body: JSON.stringify({
|
|
479
|
-
start: toLocalISO(selection.start),
|
|
480
|
-
end: toLocalISO(selection.end)
|
|
481
|
-
})
|
|
482
|
-
});
|
|
483
|
-
const { available, message } = await response.json();
|
|
484
|
-
|
|
485
|
-
if (!available) {
|
|
486
|
-
return {
|
|
487
|
-
action: 'restore',
|
|
488
|
-
message: message,
|
|
489
|
-
showInvalidRange: true // Keep selection visible with error styling
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return { action: 'accept' };
|
|
494
|
-
};
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
**Return object options:**
|
|
498
|
-
|
|
499
|
-
| Property | Type | Description |
|
|
500
|
-
|----------|------|-------------|
|
|
501
|
-
| `action` | `'accept' \| 'adjust' \| 'restore' \| 'clear'` | **Required.** What to do with the selection |
|
|
502
|
-
| `message` | `string` | Optional message to display in the calendar |
|
|
503
|
-
| `showInvalidRange` | `boolean` | When `true` with `action: 'restore'`, keeps the invalid selection visible with red error styling |
|
|
504
|
-
| `adjustedDate` | `Date` | For `action: 'adjust'` in single mode - the corrected date |
|
|
505
|
-
| `adjustedStartDate` | `Date` | For `action: 'adjust'` in range mode - the corrected start date |
|
|
506
|
-
| `adjustedEndDate` | `Date` | For `action: 'adjust'` in range mode - the corrected end date |
|
|
507
|
-
|
|
508
|
-
**Action behaviors:**
|
|
509
|
-
|
|
510
|
-
- **`accept`**: Apply the selection as-is. Hides any existing message.
|
|
511
|
-
- **`adjust`**: Apply corrected dates instead (use with `adjustedDate` or `adjustedStartDate`/`adjustedEndDate`)
|
|
512
|
-
- **`restore`**: Revert to previous selection. Use `showInvalidRange: true` to show what was attempted.
|
|
513
|
-
- **`clear`**: Clear the selection entirely.
|
|
514
|
-
|
|
515
|
-
**Example: Minimum nights validation with error display:**
|
|
516
|
-
|
|
517
|
-
```javascript
|
|
518
|
-
picker.beforeDateSelectCallback = (range) => {
|
|
519
|
-
const nights = Math.floor((range.end - range.start) / (1000 * 60 * 60 * 24));
|
|
520
|
-
|
|
521
|
-
if (nights < 2) {
|
|
522
|
-
return {
|
|
523
|
-
action: 'restore',
|
|
524
|
-
message: 'Minimum 2 nights required',
|
|
525
|
-
showInvalidRange: true // Shows attempted range with red styling
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (nights > 14) {
|
|
530
|
-
return {
|
|
531
|
-
action: 'restore',
|
|
532
|
-
message: 'Maximum 14 nights allowed',
|
|
533
|
-
showInvalidRange: true
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return { action: 'accept' };
|
|
538
|
-
};
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### Bulk Metadata Loading (beforeMonthChangedCallback)
|
|
542
|
-
|
|
543
|
-
Load metadata for all visible dates in a single API call when the user navigates months. Much more efficient than `getDateMetadataCallback` which is called per-date:
|
|
544
|
-
|
|
545
|
-
```javascript
|
|
546
|
-
const picker = document.querySelector('web-daterangepicker');
|
|
547
|
-
|
|
548
|
-
picker.beforeMonthChangedCallback = async (context) => {
|
|
549
|
-
// context: { year, month, monthIndex, firstVisibleDate, lastVisibleDate }
|
|
550
|
-
|
|
551
|
-
// Fetch availability for all visible dates in one call.
|
|
552
|
-
// Use toLocalISO (see "Working with Dates Across Timezones") so the
|
|
553
|
-
// server receives the calendar dates the user actually sees.
|
|
554
|
-
const response = await fetch('/api/availability', {
|
|
555
|
-
method: 'POST',
|
|
556
|
-
body: JSON.stringify({
|
|
557
|
-
start: toLocalISO(context.firstVisibleDate),
|
|
558
|
-
end: toLocalISO(context.lastVisibleDate)
|
|
559
|
-
})
|
|
560
|
-
});
|
|
561
|
-
const data = await response.json();
|
|
562
|
-
|
|
563
|
-
// Build metadata map (key: YYYY-MM-DD, value: DateInfo)
|
|
564
|
-
const metadata = new Map();
|
|
565
|
-
data.forEach(day => {
|
|
566
|
-
metadata.set(day.date, {
|
|
567
|
-
badgeText: `$${day.price}`,
|
|
568
|
-
isDisabled: !day.available,
|
|
569
|
-
dayTooltip: `${day.roomsLeft} rooms available`
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
return { action: 'accept', metadata };
|
|
574
|
-
};
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
**Return object options:**
|
|
578
|
-
|
|
579
|
-
| Property | Type | Description |
|
|
580
|
-
|----------|------|-------------|
|
|
581
|
-
| `action` | `'accept' \| 'block'` | **Required.** Allow or prevent month navigation |
|
|
582
|
-
| `metadata` | `Map<string, DateInfo>` | Bulk metadata keyed by `YYYY-MM-DD`. Cached and used instead of `getDateMetadataCallback` |
|
|
583
|
-
| `monthHeaders` | `Map<string, string>` | Custom month headers keyed by `YYYY-MM` (e.g., `"2026-01"` → `"Jan 2026 (5 rooms)"`) |
|
|
584
|
-
| `message` | `string` | Optional message to display (useful with `action: 'block'`) |
|
|
585
|
-
|
|
586
|
-
**Performance:** 1 API call per month navigation vs 35-42 calls with `getDateMetadataCallback`.
|
|
587
|
-
|
|
588
|
-
### Messages & Custom Actions
|
|
589
|
-
|
|
590
|
-
Display contextual messages in the calendar with interactive buttons:
|
|
591
|
-
|
|
592
|
-
```javascript
|
|
593
|
-
const picker = document.querySelector('web-daterangepicker');
|
|
594
|
-
|
|
595
|
-
// Show a simple message
|
|
596
|
-
picker.showMessage('<p>Please select a check-in date</p>');
|
|
597
|
-
|
|
598
|
-
// Show a message with a close button
|
|
599
|
-
picker.showMessage(`
|
|
600
|
-
<p>Weekend dates have higher rates</p>
|
|
601
|
-
<button data-action="close-message">Got it</button>
|
|
602
|
-
`);
|
|
603
|
-
|
|
604
|
-
// Show a message with custom action buttons
|
|
605
|
-
picker.showMessage(`
|
|
606
|
-
<p>These dates are unavailable. Try:</p>
|
|
607
|
-
<button data-action="custom" data-start-date="2026-01-14" data-end-date="2026-01-17">
|
|
608
|
-
Jan 14 - Jan 17
|
|
609
|
-
</button>
|
|
610
|
-
`);
|
|
611
|
-
|
|
612
|
-
// Handle custom action button clicks
|
|
613
|
-
picker.addEventListener('custom-action', (e) => {
|
|
614
|
-
const { startDate, endDate } = e.detail;
|
|
615
|
-
if (startDate && endDate) {
|
|
616
|
-
picker.selectedRanges = [{
|
|
617
|
-
start: new Date(startDate),
|
|
618
|
-
end: new Date(endDate)
|
|
619
|
-
}];
|
|
620
|
-
picker.hideMessage();
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
// Hide message programmatically
|
|
625
|
-
picker.hideMessage();
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
**Built-in button actions:**
|
|
629
|
-
- `data-action="close-message"` - Closes the message (no event fired)
|
|
630
|
-
- `data-action="custom"` - Fires `custom-action` event with all `data-*` attributes
|
|
631
|
-
|
|
632
|
-
## Range Selection Modes
|
|
633
|
-
|
|
634
|
-
When selecting date ranges that include disabled dates (e.g., selecting a working week where weekends are disabled), you can control how the selection is handled using the `disabled-dates-handling` attribute:
|
|
635
|
-
|
|
636
|
-
### Mode: 'allow' (default)
|
|
637
|
-
|
|
638
|
-
Allows range selections over disabled dates. Returns both enabled and disabled date arrays:
|
|
639
|
-
|
|
640
|
-
```html
|
|
641
|
-
<web-daterangepicker
|
|
642
|
-
selection-mode="range"
|
|
643
|
-
disabled-weekdays="0,6"
|
|
644
|
-
disabled-dates-handling="allow">
|
|
645
|
-
</web-daterangepicker>
|
|
646
|
-
|
|
647
|
-
<script>
|
|
648
|
-
picker.addEventListener('date-select', (e) => {
|
|
649
|
-
console.log('Enabled dates:', e.detail.enabledDates);
|
|
650
|
-
console.log('Disabled dates:', e.detail.disabledDates);
|
|
651
|
-
console.log('Total days:', e.detail.getTotalDays());
|
|
652
|
-
console.log('Enabled count:', e.detail.getEnabledDateCount());
|
|
653
|
-
});
|
|
654
|
-
</script>
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
**Use case:** Selecting working weeks where you need to know both working days and weekends (e.g., "Select 3 weeks of work" where weekends are included in the range but you get a separate array of working days).
|
|
658
|
-
|
|
659
|
-
### Mode: 'prevent'
|
|
660
|
-
|
|
661
|
-
Prevents selecting disabled dates entirely. Clicking a disabled date does nothing:
|
|
662
|
-
|
|
663
|
-
```html
|
|
664
|
-
<web-daterangepicker
|
|
665
|
-
selection-mode="range"
|
|
666
|
-
disabled-dates-handling="prevent">
|
|
667
|
-
</web-daterangepicker>
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
**Use case:** Strict date selection where disabled dates should never be part of any selection.
|
|
671
|
-
|
|
672
|
-
### Mode: 'block'
|
|
673
|
-
|
|
674
|
-
Prevents range selections from crossing disabled dates. Automatically snaps to the last enabled date before the gap:
|
|
675
|
-
|
|
676
|
-
```html
|
|
677
|
-
<web-daterangepicker
|
|
678
|
-
selection-mode="range"
|
|
679
|
-
disabled-dates-handling="block">
|
|
680
|
-
</web-daterangepicker>
|
|
681
|
-
```
|
|
682
|
-
|
|
683
|
-
When dragging from day 1 to day 7 with days 4-5 disabled, the selection will automatically snap to days 1-3.
|
|
684
|
-
|
|
685
|
-
**Use case:** Cottage booking where you can't book across existing reservations, or any scenario where gaps in the range are not allowed.
|
|
686
|
-
|
|
687
|
-
### Mode: 'split'
|
|
688
|
-
|
|
689
|
-
Returns multiple date ranges separated by disabled dates:
|
|
690
|
-
|
|
691
|
-
```html
|
|
692
|
-
<web-daterangepicker
|
|
693
|
-
selection-mode="range"
|
|
694
|
-
disabled-weekdays="0,6"
|
|
695
|
-
disabled-dates-handling="split">
|
|
696
|
-
</web-daterangepicker>
|
|
697
|
-
|
|
698
|
-
<script>
|
|
699
|
-
picker.addEventListener('date-select', (e) => {
|
|
700
|
-
console.log('Ranges:', e.detail.dateRanges);
|
|
701
|
-
// e.g., [{start: Mon, end: Fri}, {start: Mon, end: Fri}, {start: Mon, end: Fri}]
|
|
702
|
-
console.log('Formatted:', e.detail.formattedValue);
|
|
703
|
-
// "2025-11-03 - 2025-11-07, 2025-11-10 - 2025-11-14, 2025-11-17 - 2025-11-21"
|
|
704
|
-
});
|
|
705
|
-
</script>
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
**Use case:** Reporting or analytics where you need distinct time periods (e.g., "Generate report for these 3 work weeks").
|
|
709
|
-
|
|
710
|
-
### Mode: 'individual'
|
|
711
|
-
|
|
712
|
-
Returns a flat array of individual enabled dates:
|
|
713
|
-
|
|
714
|
-
```html
|
|
715
|
-
<web-daterangepicker
|
|
716
|
-
selection-mode="range"
|
|
717
|
-
disabled-weekdays="0,6"
|
|
718
|
-
disabled-dates-handling="individual">
|
|
719
|
-
</web-daterangepicker>
|
|
720
|
-
|
|
721
|
-
<script>
|
|
722
|
-
picker.addEventListener('date-select', (e) => {
|
|
723
|
-
console.log('Individual dates:', e.detail.dates);
|
|
724
|
-
// [Date(Mon), Date(Tue), Date(Wed), Date(Thu), Date(Fri), Date(Mon), ...]
|
|
725
|
-
console.log('Formatted:', e.detail.formattedValue);
|
|
726
|
-
// "2025-11-03, 2025-11-04, 2025-11-05, 2025-11-06, 2025-11-07, ..."
|
|
727
|
-
});
|
|
728
|
-
</script>
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
**Use case:** Scheduling or event planning where you need a list of specific dates (e.g., "Schedule training sessions on these dates").
|
|
732
|
-
|
|
733
|
-
### Event Detail Structure by Mode
|
|
734
|
-
|
|
735
|
-
| Mode | Properties | Description |
|
|
736
|
-
|------|-----------|-------------|
|
|
737
|
-
| `allow` | `dateRange`, `enabledDates`, `disabledDates`, `getTotalDays()`, `getEnabledDateCount()` | Full range with helper methods |
|
|
738
|
-
| `prevent` | `dateRange`, `dates` | Only enabled dates can be selected |
|
|
739
|
-
| `block` | `dateRange`, `dates` | Single continuous range (no disabled dates) |
|
|
740
|
-
| `split` | `dateRanges`, `dates` | Multiple ranges split by disabled dates |
|
|
741
|
-
| `individual` | `dates` | Flat array of enabled dates |
|
|
742
|
-
|
|
743
|
-
### Visual Highlighting Control
|
|
744
|
-
|
|
745
|
-
By default, when you select a range that includes disabled dates, all dates (both enabled and disabled) within the range are visually highlighted. You can change this behavior with the `highlight-disabled-in-range` attribute:
|
|
746
|
-
|
|
747
|
-
```html
|
|
748
|
-
<!-- Default: highlights all dates in range, including disabled weekends -->
|
|
749
|
-
<web-daterangepicker
|
|
750
|
-
selection-mode="range"
|
|
751
|
-
disabled-weekdays="0,6"
|
|
752
|
-
disabled-dates-handling="split">
|
|
753
|
-
</web-daterangepicker>
|
|
754
|
-
|
|
755
|
-
<!-- Only highlight enabled dates (Mon-Fri), skip weekends -->
|
|
756
|
-
<web-daterangepicker
|
|
757
|
-
selection-mode="range"
|
|
758
|
-
disabled-weekdays="0,6"
|
|
759
|
-
disabled-dates-handling="split"
|
|
760
|
-
highlight-disabled-in-range="false">
|
|
761
|
-
</web-daterangepicker>
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
**When to use `highlight-disabled-in-range="false"`:**
|
|
765
|
-
- Selecting working weeks where you only want to see Monday-Friday highlighted
|
|
766
|
-
- Visual clarity when disabled dates are not relevant to the selection
|
|
767
|
-
- Any scenario where showing gaps in the range is clearer than showing continuous highlighting
|
|
768
|
-
|
|
769
|
-
## Theming
|
|
770
|
-
|
|
771
|
-
### Theme Designer
|
|
772
|
-
|
|
773
|
-
The easiest way to customize the appearance of this component is using the **KeenMate Theme Designer** at:
|
|
774
|
-
|
|
775
|
-
**[theme-designer.keenmate.dev](https://theme-designer.keenmate.dev)**
|
|
776
|
-
|
|
777
|
-
#### How It Works
|
|
778
|
-
|
|
779
|
-
1. **Choose 3 base colors** - background, text, and accent
|
|
780
|
-
2. **Preview changes live** - see your theme applied instantly
|
|
781
|
-
3. **Fine-tune individual variables** - lock specific values while adjusting others
|
|
782
|
-
4. **Export your theme** - copy CSS, JSON, or SCSS to your project
|
|
783
|
-
|
|
784
|
-
#### CSS Variable Layers
|
|
785
|
-
|
|
786
|
-
KeenMate components support a **two-layer theming architecture**:
|
|
787
|
-
|
|
788
|
-
**Standalone Mode (Simple)** - Just override the component-specific variables you need:
|
|
789
|
-
|
|
790
|
-
```css
|
|
791
|
-
:root {
|
|
792
|
-
--drp-accent-color: #your-brand-color;
|
|
793
|
-
--drp-primary-bg: #your-background;
|
|
794
|
-
--drp-text-primary: #your-text-color;
|
|
795
|
-
}
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
**Cascading Mode (Multi-Component)** - When using multiple KeenMate components, you can define a shared base layer:
|
|
799
|
-
|
|
800
|
-
```css
|
|
801
|
-
:root {
|
|
802
|
-
/* Base layer - single source of truth */
|
|
803
|
-
--base-accent-color: #3b82f6;
|
|
804
|
-
--base-main-bg: #ffffff;
|
|
805
|
-
--base-hover-bg: #f3f4f6; /* drives hover surfaces across components */
|
|
806
|
-
--base-text-color-1: #111827;
|
|
807
|
-
|
|
808
|
-
/* Components reference base layer */
|
|
809
|
-
--ms-accent-color: var(--base-accent-color);
|
|
810
|
-
--drp-accent-color: var(--base-accent-color);
|
|
811
|
-
}
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
Change `--base-accent-color` once → all components update automatically.
|
|
815
|
-
|
|
816
|
-
#### Unified Variable Naming
|
|
817
|
-
|
|
818
|
-
All KeenMate components follow a consistent naming convention for **Tier 1 variables** (core theming):
|
|
819
|
-
|
|
820
|
-
| Purpose | web-multiselect | web-daterangepicker |
|
|
821
|
-
|---------|-----------------|---------------------|
|
|
822
|
-
| Brand color | `--ms-accent-color` | `--drp-accent-color` |
|
|
823
|
-
| Background | `--ms-primary-bg` | `--drp-primary-bg` |
|
|
824
|
-
| Text color | `--ms-text-primary` | `--drp-text-primary` |
|
|
825
|
-
| Text on accent | `--ms-text-on-accent` | `--drp-text-on-accent` |
|
|
826
|
-
| Border color | `--ms-border-color` | `--drp-border-color` |
|
|
827
|
-
|
|
828
|
-
Learn the pattern once, apply it across all components.
|
|
829
|
-
|
|
830
|
-
#### Component Variables Manifest
|
|
831
|
-
|
|
832
|
-
This package exports a `component-variables.manifest.json` file that documents all supported CSS variables for tooling integration (e.g., Theme Designer, IDE autocomplete):
|
|
833
|
-
|
|
834
|
-
```javascript
|
|
835
|
-
import manifest from '@keenmate/web-daterangepicker/component-variables.manifest.json';
|
|
836
|
-
// manifest.baseVariables - list of --base-* variables the component responds to
|
|
837
|
-
// manifest.componentVariables - list of --drp-* component-specific variables
|
|
838
|
-
```
|
|
839
|
-
|
|
840
|
-
#### CSS Cascade Layers
|
|
841
|
-
|
|
842
|
-
The component's stylesheet declares three cascade layers:
|
|
843
|
-
|
|
844
|
-
```css
|
|
845
|
-
@layer variables, component, overrides;
|
|
846
|
-
```
|
|
847
|
-
|
|
848
|
-
| Layer | Holds | Priority |
|
|
849
|
-
|-------|-------|----------|
|
|
850
|
-
| `variables` | `:host { --drp-* }` declarations | lowest |
|
|
851
|
-
| `component` | Base rules + every feature partial | middle |
|
|
852
|
-
| `overrides` | Framework-class + per-instance `data-theme` blocks | highest |
|
|
853
|
-
|
|
854
|
-
**Consumer override contract:**
|
|
855
|
-
- Any unlayered consumer rule beats every rule in the component — no `!important` needed.
|
|
856
|
-
- Any `:root { --base-X: … }` declaration beats the `variables` layer trivially.
|
|
857
|
-
- To override the dark-mode blocks (the `overrides` layer), use your own `@layer overrides { … }` or any unlayered rule.
|
|
858
|
-
|
|
859
|
-
#### Dark Mode
|
|
860
|
-
|
|
861
|
-
The picker reacts to five separate dark-mode signals — set any one and the dropdown, text, borders, tooltip, and input flip to their dark palette. No JavaScript involved.
|
|
862
|
-
|
|
863
|
-
| Signal | How to trigger |
|
|
864
|
-
|--------|----------------|
|
|
865
|
-
| **OS preference** | `<html style="color-scheme: light dark">` on the consumer page — `light-dark()` then picks the OS branch |
|
|
866
|
-
| **Page-level scheme** | `<body style="color-scheme: dark">` |
|
|
867
|
-
| **Framework class on an ancestor** | `<html data-theme="dark">`, `<html data-bs-theme="dark">` (Bootstrap 5.3+), or `<html class="dark">` (Tailwind) |
|
|
868
|
-
| **Per-instance attribute** | `<web-daterangepicker data-theme="dark">` on a single component |
|
|
869
|
-
| **Explicit light override** | `data-theme="light"`, `data-bs-theme="light"`, or `.light` to force one widget back to light on an otherwise-dark page |
|
|
870
|
-
|
|
871
|
-
`color-scheme` is deliberately NOT declared on `:host` — that would block the page's setting from inheriting into the shadow DOM. See the comment block at the top of `src/css/variables.css` for the rationale.
|
|
872
|
-
|
|
873
|
-
### CSS Custom Properties
|
|
874
|
-
|
|
875
|
-
Customize the appearance using CSS custom properties:
|
|
876
|
-
|
|
877
|
-
```css
|
|
878
|
-
:root {
|
|
879
|
-
/* Base Unit - scale entire component by changing this */
|
|
880
|
-
--drp-rem: 10px; /* Default base unit (change to scale everything) */
|
|
881
|
-
|
|
882
|
-
/* Colors */
|
|
883
|
-
--drp-dropdown-background: #ffffff;
|
|
884
|
-
--drp-border-color: #e5e7eb;
|
|
885
|
-
--drp-primary-bg: #f3f4f6;
|
|
886
|
-
--drp-primary-bg-hover: #e5e7eb;
|
|
887
|
-
--drp-accent-color: #3b82f6;
|
|
888
|
-
--drp-accent-color-hover: #2563eb;
|
|
889
|
-
--drp-text-primary: #111827;
|
|
890
|
-
--drp-text-secondary: #6b7280;
|
|
891
|
-
--drp-text-on-accent: #ffffff;
|
|
892
|
-
|
|
893
|
-
/* Input Field */
|
|
894
|
-
--drp-input-background: var(--drp-dropdown-background);
|
|
895
|
-
--drp-input-color: var(--drp-text-primary);
|
|
896
|
-
--drp-input-border: var(--base-input-border, var(--drp-border));
|
|
897
|
-
--drp-input-border-hover: var(--base-input-border-hover, var(--drp-border-width-base) solid var(--drp-accent-color));
|
|
898
|
-
--drp-input-border-focus: var(--base-input-border-focus, var(--drp-border-width-base) solid var(--drp-accent-color));
|
|
899
|
-
--drp-input-placeholder-color: var(--drp-text-secondary);
|
|
900
|
-
|
|
901
|
-
/* Typography (all scale with --drp-rem) */
|
|
902
|
-
--drp-font-size-xs: calc(1.2 * var(--drp-rem)); /* 12px */
|
|
903
|
-
--drp-font-size-sm: calc(1.4 * var(--drp-rem)); /* 14px */
|
|
904
|
-
--drp-font-size-base: calc(1.6 * var(--drp-rem)); /* 16px */
|
|
905
|
-
--drp-font-weight-medium: 500;
|
|
906
|
-
--drp-font-weight-semibold: 600;
|
|
907
|
-
|
|
908
|
-
/* Spacing (all scale with --drp-rem) */
|
|
909
|
-
--drp-spacing-xs: calc(0.4 * var(--drp-rem)); /* 4px */
|
|
910
|
-
--drp-spacing-sm: calc(0.8 * var(--drp-rem)); /* 8px */
|
|
911
|
-
--drp-spacing-md: calc(1.6 * var(--drp-rem)); /* 16px */
|
|
912
|
-
|
|
913
|
-
/* Borders */
|
|
914
|
-
--drp-border-width-base: 1px;
|
|
915
|
-
--drp-border-radius-sm: calc(var(--base-border-radius-sm, 0.4) * var(--drp-rem)); /* 4px - day cells, tooltips */
|
|
916
|
-
--drp-border-radius-md: calc(var(--base-border-radius-md, 0.6) * var(--drp-rem)); /* 6px - inputs, buttons */
|
|
917
|
-
--drp-border-radius-lg: calc(var(--base-border-radius-lg, 0.8) * var(--drp-rem)); /* 8px - calendar, dropdowns */
|
|
918
|
-
--drp-border-radius: var(--drp-border-radius-md); /* default alias */
|
|
919
|
-
|
|
920
|
-
/* Shadows */
|
|
921
|
-
--drp-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
|
|
922
|
-
|
|
923
|
-
/* Transitions */
|
|
924
|
-
--drp-transition-fast: 150ms;
|
|
925
|
-
--drp-easing-snappy: cubic-bezier(0.4, 0.0, 0.2, 1);
|
|
926
|
-
}
|
|
927
|
-
```
|
|
928
|
-
|
|
929
|
-
### Input Size Scale
|
|
930
|
-
|
|
931
|
-
The component uses a 10px-based sizing system (`--drp-rem: 10px`) for clean, predictable dimensions:
|
|
932
|
-
|
|
933
|
-
| Size | Attribute | Height | Base Variable |
|
|
934
|
-
|------|-----------|--------|---------------|
|
|
935
|
-
| XS | `input-size="xs"` | 31px | `--base-input-size-xs-height` |
|
|
936
|
-
| SM | `input-size="sm"` | 33px | `--base-input-size-sm-height` |
|
|
937
|
-
| MD | `input-size="md"` | 35px (default) | `--base-input-size-md-height` |
|
|
938
|
-
| LG | `input-size="lg"` | 38px | `--base-input-size-lg-height` |
|
|
939
|
-
| XL | `input-size="xl"` | 41px | `--base-input-size-xl-height` |
|
|
940
|
-
|
|
941
|
-
**Theme Designer Integration**: Input heights reference `--base-input-size-*-height` variables from the [Theme Designer](https://theme-designer.keenmate.dev). This ensures consistent input heights across all KeenMate components (web-multiselect, web-daterangepicker).
|
|
942
|
-
|
|
943
|
-
For complete size variable reference (font sizes, padding, spacing), see [SIZES.md](SIZES.md).
|
|
944
|
-
|
|
945
|
-
#### Customizing Input Heights
|
|
946
|
-
|
|
947
|
-
Four ways to customize input dimensions:
|
|
948
|
-
|
|
949
|
-
```css
|
|
950
|
-
/* Option 1: Via Theme Designer base variables (recommended for multi-component consistency) */
|
|
951
|
-
:root {
|
|
952
|
-
--base-input-size-md-height: 4.2; /* All components: 42px at 10px rem */
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
/* Option 2: Direct px override */
|
|
956
|
-
web-daterangepicker {
|
|
957
|
-
--drp-input-size-md-height: 42px;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/* Option 3: Scale all sizes via --drp-rem */
|
|
961
|
-
web-daterangepicker {
|
|
962
|
-
--drp-rem: 12px; /* MD = 3.5 × 12 = 42px */
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
/* Option 4: Override specific size with calc */
|
|
966
|
-
web-daterangepicker {
|
|
967
|
-
--drp-input-size-md-height: calc(4.2 * var(--drp-rem));
|
|
968
|
-
}
|
|
969
|
-
```
|
|
970
|
-
|
|
971
|
-
### Calendar Scaling
|
|
972
|
-
|
|
973
|
-
Scale the entire calendar by setting `--drp-rem` directly on the `<web-daterangepicker>` element:
|
|
974
|
-
|
|
975
|
-
```css
|
|
976
|
-
/* Compact size (80%) */
|
|
977
|
-
web-daterangepicker.compact {
|
|
978
|
-
--drp-rem: 8px;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
/* Large size (150%) */
|
|
982
|
-
web-daterangepicker.large {
|
|
983
|
-
--drp-rem: 15px;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
/* Or use inline style */
|
|
987
|
-
<web-daterangepicker style="--drp-rem: 12px;"></web-daterangepicker>
|
|
988
|
-
```
|
|
989
|
-
|
|
990
|
-
**Important:** Due to Shadow DOM, CSS variables must be set on the `<web-daterangepicker>` element itself (via class or inline style), not on a wrapper div.
|
|
991
|
-
|
|
992
|
-
For fine-grained control, override individual variables:
|
|
993
|
-
|
|
994
|
-
```css
|
|
995
|
-
web-daterangepicker.custom {
|
|
996
|
-
--drp-rem: 12px;
|
|
997
|
-
--drp-spacing-xs: 2px; /* Tighter gaps */
|
|
998
|
-
--drp-font-size-base: 18px; /* Larger text */
|
|
999
|
-
}
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
See [examples-sizes.html](examples-sizes.html) for interactive demos.
|
|
1003
|
-
|
|
1004
|
-
## Development
|
|
1005
|
-
|
|
1006
|
-
```bash
|
|
1007
|
-
# Install dependencies
|
|
1008
|
-
npm install
|
|
1009
|
-
|
|
1010
|
-
# Start dev server
|
|
1011
|
-
npm run dev
|
|
1012
|
-
|
|
1013
|
-
# Build for production
|
|
1014
|
-
npm run build
|
|
1015
|
-
|
|
1016
|
-
# Preview production build
|
|
1017
|
-
npm run preview
|
|
1018
|
-
```
|
|
1019
|
-
|
|
1020
|
-
## Browser Support
|
|
1021
|
-
|
|
1022
|
-
- Modern browsers with Web Components and CSS `color-mix()` support
|
|
1023
|
-
- Chrome/Edge 111+
|
|
1024
|
-
- Firefox 113+
|
|
1025
|
-
- Safari 16.2+
|
|
1026
|
-
|
|
1027
|
-
For older browser support, use the compiled `dist/style.css` which is processed by Vite.
|
|
1028
|
-
|
|
1029
|
-
## HTML Injection (XSS) Notice
|
|
1030
|
-
|
|
1031
|
-
The following callbacks and methods allow **raw HTML injection** and are intentionally **NOT XSS-safe**. This gives developers full control over rendering but requires sanitizing untrusted data:
|
|
1032
|
-
|
|
1033
|
-
| Callback/Method | Output Used In | Risk Level |
|
|
1034
|
-
|-----------------|---------------|------------|
|
|
1035
|
-
| `showMessage(html)` | Message area (innerHTML) | HTML injection |
|
|
1036
|
-
| `renderDayCallback` | Day cells (innerHTML) | HTML injection |
|
|
1037
|
-
| `renderDayContentCallback` | Day cells (innerHTML) | HTML injection |
|
|
1038
|
-
| `getDateMetadataCallback` (badgeText, dayTooltip) | Badges/tooltips (innerHTML) | HTML injection |
|
|
1039
|
-
| `formatSummaryCallback` | Summary display (innerHTML) | HTML injection |
|
|
1040
|
-
| `getMonthHeaderCallback` | Month headers (innerHTML) | HTML injection |
|
|
1041
|
-
| `getUnifiedHeaderCallback` | Unified header (innerHTML) | HTML injection |
|
|
1042
|
-
| `customStylesCallback` | Style tag (textContent) | CSS injection |
|
|
1043
|
-
| `actionButtons[].label` | Button labels (innerHTML) | HTML injection |
|
|
1044
|
-
|
|
1045
|
-
**Safe callbacks** (output is escaped or used as data):
|
|
1046
|
-
- `beforeDateSelectCallback`, `beforeMonthChangedCallback` (return action objects)
|
|
1047
|
-
- `onSelect`, `onChange` (event handlers)
|
|
1048
|
-
- `getDateMetadataCallback` (isDisabled, dayClass, badgeClass - CSS class names only)
|
|
1049
|
-
|
|
1050
|
-
**If displaying user-generated content**, sanitize it before passing to these callbacks or methods.
|
|
1051
|
-
|
|
1052
|
-
## Changelog
|
|
1053
|
-
|
|
1054
|
-
See [CHANGELOG.md](https://github.com/keenmate/web-daterangepicker/blob/main/CHANGELOG.md) for version history and migration guides.
|
|
1055
|
-
|
|
1056
|
-
## License
|
|
1057
|
-
|
|
1058
|
-
MIT
|
|
1059
|
-
|
|
1060
|
-
## Credits
|
|
1061
|
-
|
|
1062
|
-
Extracted from the [Pure Admin](https://github.com/keenmate/pure-admin) design system.
|
|
1
|
+
# Date Range Picker Web Component
|
|
2
|
+
|
|
3
|
+
A lightweight, framework-agnostic date picker that ships as a single
|
|
4
|
+
custom element. Keyboard-first navigation, single / range / multiple
|
|
5
|
+
selection modes, optional time + datetime modes, and OS-aware dark
|
|
6
|
+
mode out of the box.
|
|
7
|
+
|
|
8
|
+
> **⚠️ Security Notice:** This component intentionally allows raw HTML
|
|
9
|
+
> in rendering callbacks and message content to give developers full
|
|
10
|
+
> control over content display. If you display user-generated content,
|
|
11
|
+
> you must sanitize it yourself. See [HTML Injection (XSS) Notice](#html-injection-xss-notice)
|
|
12
|
+
> for the complete list of affected callbacks and methods.
|
|
13
|
+
|
|
14
|
+
## What is it
|
|
15
|
+
|
|
16
|
+
`@keenmate/web-daterangepicker` is a date picker that runs as a Web
|
|
17
|
+
Component — drop the `<web-daterangepicker>` element into any HTML
|
|
18
|
+
page (or any framework that speaks DOM: React, Vue, Svelte, Angular,
|
|
19
|
+
plain HTML) and it works. No JavaScript framework integration to
|
|
20
|
+
build. No virtualized rendering. No copy-pasted theme tokens to keep
|
|
21
|
+
in sync.
|
|
22
|
+
|
|
23
|
+
It covers the full date-picker matrix in one component: **single date**
|
|
24
|
+
or **date range** selection; **single / range / multiple** modes;
|
|
25
|
+
**date**, **time**, or **datetime** picker modes with four time-display
|
|
26
|
+
UIs (rolls, Material clock face, iOS wheel, iOS compact pills);
|
|
27
|
+
**multi-month** grids; **input masking** with progressive auto-format
|
|
28
|
+
as the user types; **drag-to-adjust** for range endpoints; **disabled
|
|
29
|
+
dates** with five different range-traversal strategies; **special
|
|
30
|
+
dates** with badges, tooltips, and `dayClass` / `badgeClass`
|
|
31
|
+
discriminator hooks; **localized** week start, month names, and labels;
|
|
32
|
+
and **async validation** via `beforeDateSelectCallback` with optional
|
|
33
|
+
bulk metadata loading per month.
|
|
34
|
+
|
|
35
|
+
What makes it different from the rest of the date-picker ecosystem:
|
|
36
|
+
|
|
37
|
+
- **No JavaScript dark-mode detection.** The picker reacts to five
|
|
38
|
+
separate CSS-only signals (OS `prefers-color-scheme`, page-level
|
|
39
|
+
`color-scheme`, framework class on an ancestor, per-instance
|
|
40
|
+
`data-theme` attribute, explicit `light` override) via `light-dark()`
|
|
41
|
+
and `:host-context(...)` selectors. Set whichever your page already
|
|
42
|
+
uses; it flips automatically.
|
|
43
|
+
- **Theme Designer integration.** Optional. If you use the KeenMate
|
|
44
|
+
Theme Designer at [theme-designer.keenmate.dev](https://theme-designer.keenmate.dev),
|
|
45
|
+
the picker reads the cross-component `--base-*` taxonomy so one
|
|
46
|
+
theme drives every KeenMate component on the page.
|
|
47
|
+
- **One component, every selection shape.** Date / range / multiple /
|
|
48
|
+
time / datetime in one custom element with one consistent API —
|
|
49
|
+
switch by changing one attribute, not by installing a different
|
|
50
|
+
package.
|
|
51
|
+
- **First-class keyboard.** Arrow keys, Home / End, Ctrl+Home / End,
|
|
52
|
+
Tab between month columns, `T` to jump to today. Modal mode blurs
|
|
53
|
+
the input so the mobile soft-keyboard collapses.
|
|
54
|
+
|
|
55
|
+
For a more honest accessibility audit including current ARIA gaps,
|
|
56
|
+
see [`docs/accessibility.md`](./docs/accessibility.md).
|
|
57
|
+
|
|
58
|
+
## What's new
|
|
59
|
+
|
|
60
|
+
**v1.14.0-rc02** (latest) — BlissFramework guidelines alignment +
|
|
61
|
+
sibling-picker overlap fix. See [`CHANGELOG.md`](./CHANGELOG.md) for
|
|
62
|
+
the full migration table and history.
|
|
63
|
+
|
|
64
|
+
## Demos & docs
|
|
65
|
+
|
|
66
|
+
- 🚀 [Live demo](https://web-daterangepicker.keenmate.dev)
|
|
67
|
+
- 📘 [Usage / API reference](./docs/usage.md)
|
|
68
|
+
- 🎨 [Theming contract](./docs/theming.md)
|
|
69
|
+
- 📚 [Examples / cookbook](./docs/examples.md)
|
|
70
|
+
- ♿ [Accessibility](./docs/accessibility.md)
|
|
71
|
+
|
|
72
|
+
## Install
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install @keenmate/web-daterangepicker
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`rc` releases are tagged separately — install with `@rc` or pin the
|
|
79
|
+
exact version:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm install @keenmate/web-daterangepicker@rc
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Quick start
|
|
86
|
+
|
|
87
|
+
### Basic HTML
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<!-- Single date -->
|
|
91
|
+
<web-daterangepicker
|
|
92
|
+
selection-mode="single"
|
|
93
|
+
date-format-mask="YYYY-MM-DD"
|
|
94
|
+
placeholder="Select date"
|
|
95
|
+
></web-daterangepicker>
|
|
96
|
+
|
|
97
|
+
<!-- Date range -->
|
|
98
|
+
<web-daterangepicker
|
|
99
|
+
selection-mode="range"
|
|
100
|
+
date-format-mask="YYYY-MM-DD"
|
|
101
|
+
visible-months-count="2"
|
|
102
|
+
placeholder="Select date range"
|
|
103
|
+
></web-daterangepicker>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### With JavaScript / TypeScript
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// Import the component (registers <web-daterangepicker> + injects styles)
|
|
110
|
+
import '@keenmate/web-daterangepicker';
|
|
111
|
+
|
|
112
|
+
const picker = document.querySelector('web-daterangepicker');
|
|
113
|
+
|
|
114
|
+
// Listen for date selection
|
|
115
|
+
picker.addEventListener('date-select', (e) => {
|
|
116
|
+
console.log('Selected:', e.detail.formattedValue);
|
|
117
|
+
console.log('Date object:', e.detail.date);
|
|
118
|
+
console.log('Range:', e.detail.dateRange);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Programmatic API
|
|
122
|
+
picker.show();
|
|
123
|
+
picker.hide();
|
|
124
|
+
picker.toggle();
|
|
125
|
+
picker.clearSelection();
|
|
126
|
+
picker.setInputValue('2025-11-15');
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Using the `DateRangePicker` class directly (without the custom
|
|
130
|
+
element) is fully supported — see [`docs/usage.md`](./docs/usage.md#javascript-instantiation-datepickerangepicker-class)
|
|
131
|
+
for the CSS-loading caveats.
|
|
132
|
+
|
|
133
|
+
## Browser support
|
|
134
|
+
|
|
135
|
+
Modern browsers with Web Components and CSS `color-mix()` support:
|
|
136
|
+
|
|
137
|
+
- Chrome / Edge 111+
|
|
138
|
+
- Firefox 113+
|
|
139
|
+
- Safari 16.2+
|
|
140
|
+
|
|
141
|
+
## Built with BlissFramework
|
|
142
|
+
|
|
143
|
+
Follows the [BlissFramework component guidelines](https://blissframework.dev/)
|
|
144
|
+
for structure, theming, color-scheme, and accessibility. See
|
|
145
|
+
[`VALIDATION-NOTES.md`](./VALIDATION-NOTES.md) for the accepted-deviation
|
|
146
|
+
register and [`docs/theming.md`](./docs/theming.md) for the four-contract
|
|
147
|
+
theming model.
|
|
148
|
+
|
|
149
|
+
## HTML Injection (XSS) Notice
|
|
150
|
+
|
|
151
|
+
The following callbacks and methods allow **raw HTML injection** and
|
|
152
|
+
are intentionally **not XSS-safe**. This gives developers full control
|
|
153
|
+
over rendering but requires sanitizing untrusted data:
|
|
154
|
+
|
|
155
|
+
| Callback / Method | Output used in | Risk |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `showMessage(html)` | Message area (innerHTML) | HTML injection |
|
|
158
|
+
| `renderDayCallback` | Day cells (innerHTML) | HTML injection |
|
|
159
|
+
| `renderDayContentCallback` | Day cells (innerHTML) | HTML injection |
|
|
160
|
+
| `getDateMetadataCallback` (`badgeText`, `dayTooltip`) | Badges / tooltips (innerHTML) | HTML injection |
|
|
161
|
+
| `formatSummaryCallback` | Summary display (innerHTML) | HTML injection |
|
|
162
|
+
| `getMonthHeaderCallback` | Month headers (innerHTML) | HTML injection |
|
|
163
|
+
| `getUnifiedHeaderCallback` | Unified header (innerHTML) | HTML injection |
|
|
164
|
+
| `customStylesCallback` | Style tag (textContent) | CSS injection |
|
|
165
|
+
| `actionButtons[].label` | Button labels (innerHTML) | HTML injection |
|
|
166
|
+
|
|
167
|
+
**Safe callbacks** (output is escaped or used as data):
|
|
168
|
+
|
|
169
|
+
- `beforeDateSelectCallback`, `beforeMonthChangedCallback` (return action objects)
|
|
170
|
+
- `onSelect`, `onChange` (event handlers)
|
|
171
|
+
- `getDateMetadataCallback` (`isDisabled`, `dayClass`, `badgeClass` — CSS class names only)
|
|
172
|
+
|
|
173
|
+
**If displaying user-generated content**, sanitize it before passing
|
|
174
|
+
it to these callbacks or methods.
|
|
175
|
+
|
|
176
|
+
## Known Limitations
|
|
177
|
+
|
|
178
|
+
### Consumer-data class convention — `.holiday` / `.event` / `.badge-{count,number,text}`
|
|
179
|
+
|
|
180
|
+
The shipped CSS includes default styles for a small set of class names
|
|
181
|
+
the picker doesn't emit itself — they're applied by your code via the
|
|
182
|
+
`dayClassMember` / `badgeClassMember` callbacks (or the equivalent
|
|
183
|
+
`getDateMetadataCallback` return shape). The component ships compound
|
|
184
|
+
selectors (`.drp__day.holiday`, `.drp__day.event`,
|
|
185
|
+
`.drp__badge-cell.badge-count`, `.drp__badge-cell.badge-number`,
|
|
186
|
+
`.drp__badge-cell.badge-text`) as ready-to-use hooks for these common
|
|
187
|
+
conventions so the most frequent decoration cases work without writing
|
|
188
|
+
any CSS.
|
|
189
|
+
|
|
190
|
+
This means a few CSS classes inside the shadow root don't follow
|
|
191
|
+
strict BEM (`.<prefix>__element--modifier`) — they're consumer-data
|
|
192
|
+
values used as discriminators next to a BEM block, not
|
|
193
|
+
component-emitted modifiers. The BEM block (`.drp__day`,
|
|
194
|
+
`.drp__badge-cell`) carries the component scope; the discriminator
|
|
195
|
+
carries the data convention.
|
|
196
|
+
|
|
197
|
+
If your data uses different class names, just supply them via the
|
|
198
|
+
callback and provide your own CSS — the defaults won't fight you. To
|
|
199
|
+
retheme the existing defaults without rewriting the CSS, override the
|
|
200
|
+
backing variables:
|
|
201
|
+
|
|
202
|
+
- **Holiday cell:** `--drp-holiday-color`, `--drp-holiday-bg-opacity`, `--drp-holiday-hover-bg-opacity`
|
|
203
|
+
- **Event cell:** `--drp-event-color`, `--drp-event-bg-opacity`, `--drp-event-hover-bg-opacity`
|
|
204
|
+
- **Badge types:** `--drp-badge-number-{bg,color}`, `--drp-badge-count-{bg,color}`, `--drp-badge-text-{bg,color}`
|
|
205
|
+
|
|
206
|
+
These variables are part of the public theming surface (added in
|
|
207
|
+
v1.6.0; see [`CHANGELOG.md`](./CHANGELOG.md)).
|
|
208
|
+
|
|
209
|
+
## Changelog
|
|
210
|
+
|
|
211
|
+
See [`CHANGELOG.md`](./CHANGELOG.md) for version history and migration
|
|
212
|
+
guides.
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT — see [LICENSE](./LICENSE) if present, otherwise the MIT terms
|
|
217
|
+
apply by default per `package.json`.
|
|
218
|
+
|
|
219
|
+
## Credits
|
|
220
|
+
|
|
221
|
+
Extracted from the [Pure Admin](https://github.com/keenmate/pure-admin)
|
|
222
|
+
design system.
|