@lekoala/date-picker 0.5.1 → 0.5.3
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 +296 -294
- package/custom-elements.json +3 -0
- package/dist/date-picker.js +256 -91
- package/dist/date-picker.min.css +1 -1
- package/dist/date-picker.min.js +11 -12
- package/dist/date-picker.standalone.min.js +12 -506
- package/dist/types/date-calendar.d.ts +5 -90
- package/dist/types/date-picker.d.ts +12 -314
- package/dist/types/date.d.ts.map +1 -1
- package/docs/API.md +444 -442
- package/docs/ARCHITECTURE.md +2 -0
- package/docs/DECISIONS.md +74 -74
- package/docs/USE_CASES.md +156 -156
- package/package.json +3 -3
- package/src/activation-intents.js +9 -0
- package/src/date-calendar.js +99 -18
- package/src/date-field.js +57 -10
- package/src/date-picker.js +187 -86
- package/src/date.js +4 -1
- package/dist/types/date-calendar.d.ts.map +0 -1
- package/dist/types/date-field.d.ts +0 -104
- package/dist/types/date-field.d.ts.map +0 -1
- package/dist/types/date-picker.d.ts.map +0 -1
- package/dist/types/time.d.ts +0 -8
- package/dist/types/time.d.ts.map +0 -1
- package/dist/types/upgrade-properties.d.ts +0 -8
- package/dist/types/upgrade-properties.d.ts.map +0 -1
package/docs/API.md
CHANGED
|
@@ -1,442 +1,444 @@
|
|
|
1
|
-
# API
|
|
2
|
-
|
|
3
|
-
The public surface — flat event names, `selection` values, `--dp-*` styling tokens and the `dateactivate`/`renderDay()` seams — is frozen in [DECISIONS.md](DECISIONS.md). Everything else stays internal and is free to change within the same major version.
|
|
4
|
-
|
|
5
|
-
## `<date-calendar>`
|
|
6
|
-
|
|
7
|
-
### Attributes / properties
|
|
8
|
-
|
|
9
|
-
- `controlClasses` — JS-only `{ month, year, nav }` partial class map; see header control styling below.
|
|
10
|
-
|
|
11
|
-
| Name | Type | Default | Meaning |
|
|
12
|
-
|---------------------|------------------|--------------------|----------------------------------------------|
|
|
13
|
-
| `value` | `YYYY-MM-DD` | `""` | Selected date. |
|
|
14
|
-
| `display` | `YYYY-MM` | value/today month | Month being rendered. |
|
|
15
|
-
| `min` | `YYYY-MM-DD` | `""` | Earliest activatable date. |
|
|
16
|
-
| `max` | `YYYY-MM-DD` | `""` | Latest activatable date. |
|
|
17
|
-
| `first-day` | `0..6` (`7` alias) | `1` | First weekday; Sunday=0, Monday=1. `7` is accepted as an ISO-style alias of Sunday (`@lekoala/calendar` uses 1..7) and never shifts the grid. Other values fall back to Monday. |
|
|
18
|
-
| `locale` | BCP 47 string | document/navigator | `Intl` locale. |
|
|
19
|
-
| `month-format` | `long \| short` | `long` | Visible month-name style in the month select; anything else falls back to `long`. The accessible grid heading always keeps the long month/year form. |
|
|
20
|
-
| `selection` | `single \| none` | `single` | Whether accepted activation updates `value`. |
|
|
21
|
-
| `fixed-weeks` | boolean | false | Request six full rows, limited by canonical date bounds. |
|
|
22
|
-
| `show-week-numbers` | boolean | false | Show ISO week numbers. The column renders only when `first-day === 1` (Monday-first): the ISO week and the displayed week only coincide then. With Sunday-first (`0`/`7`) the column is silently withheld. |
|
|
23
|
-
|
|
24
|
-
`focusedDate` and `highlightedRange` are properties rather than reflected attributes.
|
|
25
|
-
|
|
26
|
-
### `highlightedRange`
|
|
27
|
-
|
|
28
|
-
Presentation-only range band. It never changes `value`, `selection` or `focusedDate`. It accepts an ordered `{ start, end }` pair of `YYYY-MM-DD` values; `end` may be empty (range in progress), and a `start === end` range renders both endpoints with a dedicated label. `null` and `{ start: "", end: "" }` reset the band. Inverted ranges and `{ start: "", end: "…" }` throw a `TypeError` — the property never reorders or guesses. Styled spans render `data-range-start="true"`, `data-in-range="true"`, `data-range-end="true"` and extend each day's `aria-label` accordingly.
|
|
29
|
-
|
|
30
|
-
### Callback properties
|
|
31
|
-
|
|
32
|
-
```js
|
|
33
|
-
calendar.isDateDisabled = (date) => boolean;
|
|
34
|
-
calendar.dateState = (date, sourceState) => ({
|
|
35
|
-
disabled,
|
|
36
|
-
enabled, // true = lifts isDateDisabled() only; never min/max or resolved disabled
|
|
37
|
-
description,
|
|
38
|
-
...applicationMetadata
|
|
39
|
-
});
|
|
40
|
-
calendar.renderDay = (date, state) => Node | string | null;
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
`renderDay` decorates the day cell; it cannot replace the interactive cell. The returned node is placed inside a component-owned, non-interactive container that is `aria-hidden`. That container has no stable class name: style the nodes you return, not the component's internals (see [DECISIONS.md](DECISIONS.md#d3--css--dp--is-public--dp--is-not)).
|
|
44
|
-
|
|
45
|
-
### Source
|
|
46
|
-
|
|
47
|
-
```js
|
|
48
|
-
calendar.source = async ({ start, end }, { signal }) => payload;
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
or:
|
|
52
|
-
|
|
53
|
-
```js
|
|
54
|
-
calendar.source = {
|
|
55
|
-
load({ start, end }, { signal }) { ... }
|
|
56
|
-
};
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Methods
|
|
60
|
-
|
|
61
|
-
```js
|
|
62
|
-
calendar.previousMonth();
|
|
63
|
-
calendar.nextMonth();
|
|
64
|
-
calendar.focusDate("2026-09-10");
|
|
65
|
-
calendar.focusGrid();
|
|
66
|
-
calendar.getDateState("2026-09-10");
|
|
67
|
-
await calendar.activateDate("2026-09-10");
|
|
68
|
-
await calendar.ensureDate("2026-09-10");
|
|
69
|
-
await calendar.refreshSource();
|
|
70
|
-
calendar.render();
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Events
|
|
74
|
-
|
|
75
|
-
#### `dateactivate`
|
|
76
|
-
|
|
77
|
-
Cancelable. Fired for an allowed date after explicit click / Enter / Space.
|
|
78
|
-
|
|
79
|
-
```js
|
|
80
|
-
calendar.addEventListener("dateactivate", (event) => {
|
|
81
|
-
console.log(event.detail.date, event.detail.state);
|
|
82
|
-
});
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Cancel it to refuse the activation before selection:
|
|
86
|
-
|
|
87
|
-
```js
|
|
88
|
-
calendar.addEventListener("dateactivate", (event) => {
|
|
89
|
-
if (applicationRule(event.detail.date)) event.preventDefault();
|
|
90
|
-
});
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
`activateDate(date)` runs that same path on demand: availability check, then
|
|
94
|
-
the cancelable event, then selection. Click, keyboard and the range endpoint
|
|
95
|
-
drag all go through it, so an application that cancels `dateactivate` cannot be
|
|
96
|
-
bypassed by a different input device.
|
|
97
|
-
|
|
98
|
-
#### `datefocus`
|
|
99
|
-
|
|
100
|
-
Fired when a date cell actually receives focus, with `{ date, state }`. Arrow,
|
|
101
|
-
Home, End, PageUp and PageDown all emit one through the cell that took focus.
|
|
102
|
-
|
|
103
|
-
```js
|
|
104
|
-
calendar.addEventListener("datefocus", (event) => {
|
|
105
|
-
console.log(event.detail.date);
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
It observes navigation and nothing else: a disabled day still reports it (grid
|
|
110
|
-
navigation stays discoverable), hover never does, and assigning `focusedDate`
|
|
111
|
-
without moving DOM focus stays silent. The three verbs are distinct —
|
|
112
|
-
`datefocus` observes, `dateactivate` requests an activation, `datechange`
|
|
113
|
-
observes a selection.
|
|
114
|
-
|
|
115
|
-
#### `datechange`
|
|
116
|
-
|
|
117
|
-
Fired when `selection="single"` changes `value`.
|
|
118
|
-
|
|
119
|
-
#### `dateinvalid`
|
|
120
|
-
|
|
121
|
-
Fired when the user tries to activate a disabled/out-of-range date.
|
|
122
|
-
|
|
123
|
-
#### `displaychange`
|
|
124
|
-
|
|
125
|
-
```js
|
|
126
|
-
{
|
|
127
|
-
display: "2026-10",
|
|
128
|
-
start: "2026-09-28",
|
|
129
|
-
end: "2026-11-08"
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
The range reflects rendered cells (and therefore includes six-row padding when enabled).
|
|
134
|
-
|
|
135
|
-
#### Source lifecycle
|
|
136
|
-
|
|
137
|
-
- `dateloadstart`
|
|
138
|
-
- `dateloadend`
|
|
139
|
-
- `dateloaderror`
|
|
140
|
-
|
|
141
|
-
## `<date-picker>`
|
|
142
|
-
|
|
143
|
-
Markup:
|
|
144
|
-
|
|
145
|
-
```html
|
|
146
|
-
<label for="arrival">Arrival</label>
|
|
147
|
-
<date-picker value="2026-09-10" locale="en-GB">
|
|
148
|
-
<input id="arrival" name="arrival" required>
|
|
149
|
-
</date-picker>
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
The direct child text input is required. On enhancement its original `name` moves to an internal hidden input that submits the canonical ISO value.
|
|
153
|
-
|
|
154
|
-
### Attributes / properties
|
|
155
|
-
|
|
156
|
-
- `controlClasses` — JS-only `{ month, year, nav }` partial class map; see header control styling below.
|
|
157
|
-
|
|
158
|
-
- `value` — canonical selected date;
|
|
159
|
-
- `locale`;
|
|
160
|
-
- `min`;
|
|
161
|
-
- `max`;
|
|
162
|
-
- `open-on-focus` — boolean (default `true`): open the popover when the input receives focus without moving focus; `open-on-focus="false"` keeps focus alone passive;
|
|
163
|
-
- `month-format` — `long | short` (default `long`): visible month-name style forwarded to the popup calendar;
|
|
164
|
-
- `open` — read-only: whether the popover is shown;
|
|
165
|
-
- `openOnFocus` — property form of `open-on-focus`.
|
|
166
|
-
|
|
167
|
-
### Native form semantics
|
|
168
|
-
|
|
169
|
-
- `disabled` — non-editable, popup cannot open, field is not submitted.
|
|
170
|
-
- `readonly` — non-editable, value is still submitted, popup cannot open (the trigger is disabled).
|
|
171
|
-
- Clearing the editable input clears the canonical value; the component does not render its own clear button.
|
|
172
|
-
- `input.defaultValue` (or the input's `value` attribute) is the source of truth for the next `form.reset()`, which restores canonical + visible representations and revalidates.
|
|
173
|
-
|
|
174
|
-
### Forwarded callback properties
|
|
175
|
-
|
|
176
|
-
- `source`;
|
|
177
|
-
- `dateState`;
|
|
178
|
-
- `renderDay`;
|
|
179
|
-
- `isDateDisabled`;
|
|
180
|
-
- `messages`.
|
|
181
|
-
|
|
182
|
-
### Popup positioning
|
|
183
|
-
|
|
184
|
-
- `coordinateSpace` — JS-only, `"viewport"` by default.
|
|
185
|
-
|
|
186
|
-
```text
|
|
187
|
-
"viewport"
|
|
188
|
-
panel uses position: fixed
|
|
189
|
-
general/default integration
|
|
190
|
-
|
|
191
|
-
"document"
|
|
192
|
-
panel uses position: absolute
|
|
193
|
-
explicit optimization when the application knows the picker
|
|
194
|
-
reference moves with root-page scrolling
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
`document` follows the `@lekoala/floating` consumer contract: the picker panel is a top-layer popover, so its absolute positioning resolves against the initial containing block; the application remains responsible for knowing that the `<date-picker>` reference itself moves with the root document. Changing `coordinateSpace` while open configures the next opening, never the current one. See the `@lekoala/floating` documentation for the coordinate-space doctrine.
|
|
198
|
-
|
|
199
|
-
### Read-only properties
|
|
200
|
-
|
|
201
|
-
```js
|
|
202
|
-
picker.input;
|
|
203
|
-
picker.calendar;
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### Methods
|
|
207
|
-
|
|
208
|
-
```js
|
|
209
|
-
picker.show();
|
|
210
|
-
picker.hide();
|
|
211
|
-
await picker.validate();
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
- `
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
<input
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
appointment[
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
period[
|
|
262
|
-
period[
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
- `
|
|
285
|
-
- `
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
-
|
|
304
|
-
-
|
|
305
|
-
- **
|
|
306
|
-
-
|
|
307
|
-
-
|
|
308
|
-
-
|
|
309
|
-
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
model
|
|
340
|
-
model.
|
|
341
|
-
model.activate("2026-09-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
- `
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
adapter
|
|
362
|
-
adapter.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
- `
|
|
395
|
-
- `
|
|
396
|
-
- `
|
|
397
|
-
- `
|
|
398
|
-
- `
|
|
399
|
-
- `
|
|
400
|
-
- `
|
|
401
|
-
- `
|
|
402
|
-
- `
|
|
403
|
-
- `
|
|
404
|
-
- `
|
|
405
|
-
- `
|
|
406
|
-
- `
|
|
407
|
-
- `
|
|
408
|
-
- `
|
|
409
|
-
- `
|
|
410
|
-
- `
|
|
411
|
-
- `
|
|
412
|
-
- `
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
Picker.
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
Both
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
`
|
|
441
|
-
|
|
442
|
-
`
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
The public surface — flat event names, `selection` values, `--dp-*` styling tokens and the `dateactivate`/`renderDay()` seams — is frozen in [DECISIONS.md](DECISIONS.md). Everything else stays internal and is free to change within the same major version.
|
|
4
|
+
|
|
5
|
+
## `<date-calendar>`
|
|
6
|
+
|
|
7
|
+
### Attributes / properties
|
|
8
|
+
|
|
9
|
+
- `controlClasses` — JS-only `{ month, year, nav }` partial class map; see header control styling below.
|
|
10
|
+
|
|
11
|
+
| Name | Type | Default | Meaning |
|
|
12
|
+
|---------------------|------------------|--------------------|----------------------------------------------|
|
|
13
|
+
| `value` | `YYYY-MM-DD` | `""` | Selected date. |
|
|
14
|
+
| `display` | `YYYY-MM` | value/today month | Month being rendered. |
|
|
15
|
+
| `min` | `YYYY-MM-DD` | `""` | Earliest activatable date. |
|
|
16
|
+
| `max` | `YYYY-MM-DD` | `""` | Latest activatable date. |
|
|
17
|
+
| `first-day` | `0..6` (`7` alias) | `1` | First weekday; Sunday=0, Monday=1. `7` is accepted as an ISO-style alias of Sunday (`@lekoala/calendar` uses 1..7) and never shifts the grid. Other values fall back to Monday. |
|
|
18
|
+
| `locale` | BCP 47 string | document/navigator | `Intl` locale. |
|
|
19
|
+
| `month-format` | `long \| short` | `long` | Visible month-name style in the month select; anything else falls back to `long`. The accessible grid heading always keeps the long month/year form. |
|
|
20
|
+
| `selection` | `single \| none` | `single` | Whether accepted activation updates `value`. |
|
|
21
|
+
| `fixed-weeks` | boolean | false | Request six full rows, limited by canonical date bounds. |
|
|
22
|
+
| `show-week-numbers` | boolean | false | Show ISO week numbers. The column renders only when `first-day === 1` (Monday-first): the ISO week and the displayed week only coincide then. With Sunday-first (`0`/`7`) the column is silently withheld. |
|
|
23
|
+
|
|
24
|
+
`focusedDate` and `highlightedRange` are properties rather than reflected attributes.
|
|
25
|
+
|
|
26
|
+
### `highlightedRange`
|
|
27
|
+
|
|
28
|
+
Presentation-only range band. It never changes `value`, `selection` or `focusedDate`. It accepts an ordered `{ start, end }` pair of `YYYY-MM-DD` values; `end` may be empty (range in progress), and a `start === end` range renders both endpoints with a dedicated label. `null` and `{ start: "", end: "" }` reset the band. Inverted ranges and `{ start: "", end: "…" }` throw a `TypeError` — the property never reorders or guesses. Styled spans render `data-range-start="true"`, `data-in-range="true"`, `data-range-end="true"` and extend each day's `aria-label` accordingly.
|
|
29
|
+
|
|
30
|
+
### Callback properties
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
calendar.isDateDisabled = (date) => boolean;
|
|
34
|
+
calendar.dateState = (date, sourceState) => ({
|
|
35
|
+
disabled,
|
|
36
|
+
enabled, // true = lifts isDateDisabled() only; never min/max or resolved disabled
|
|
37
|
+
description,
|
|
38
|
+
...applicationMetadata
|
|
39
|
+
});
|
|
40
|
+
calendar.renderDay = (date, state) => Node | string | null;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`renderDay` decorates the day cell; it cannot replace the interactive cell. The returned node is placed inside a component-owned, non-interactive container that is `aria-hidden`. That container has no stable class name: style the nodes you return, not the component's internals (see [DECISIONS.md](DECISIONS.md#d3--css--dp--is-public--dp--is-not)).
|
|
44
|
+
|
|
45
|
+
### Source
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
calendar.source = async ({ start, end }, { signal }) => payload;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
or:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
calendar.source = {
|
|
55
|
+
load({ start, end }, { signal }) { ... }
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Methods
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
calendar.previousMonth();
|
|
63
|
+
calendar.nextMonth();
|
|
64
|
+
calendar.focusDate("2026-09-10");
|
|
65
|
+
calendar.focusGrid();
|
|
66
|
+
calendar.getDateState("2026-09-10");
|
|
67
|
+
await calendar.activateDate("2026-09-10");
|
|
68
|
+
await calendar.ensureDate("2026-09-10");
|
|
69
|
+
await calendar.refreshSource();
|
|
70
|
+
calendar.render();
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Events
|
|
74
|
+
|
|
75
|
+
#### `dateactivate`
|
|
76
|
+
|
|
77
|
+
Cancelable. Fired for an allowed date after explicit click / Enter / Space.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
calendar.addEventListener("dateactivate", (event) => {
|
|
81
|
+
console.log(event.detail.date, event.detail.state);
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Cancel it to refuse the activation before selection:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
calendar.addEventListener("dateactivate", (event) => {
|
|
89
|
+
if (applicationRule(event.detail.date)) event.preventDefault();
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`activateDate(date)` runs that same path on demand: availability check, then
|
|
94
|
+
the cancelable event, then selection. Click, keyboard and the range endpoint
|
|
95
|
+
drag all go through it, so an application that cancels `dateactivate` cannot be
|
|
96
|
+
bypassed by a different input device.
|
|
97
|
+
|
|
98
|
+
#### `datefocus`
|
|
99
|
+
|
|
100
|
+
Fired when a date cell actually receives focus, with `{ date, state }`. Arrow,
|
|
101
|
+
Home, End, PageUp and PageDown all emit one through the cell that took focus.
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
calendar.addEventListener("datefocus", (event) => {
|
|
105
|
+
console.log(event.detail.date);
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
It observes navigation and nothing else: a disabled day still reports it (grid
|
|
110
|
+
navigation stays discoverable), hover never does, and assigning `focusedDate`
|
|
111
|
+
without moving DOM focus stays silent. The three verbs are distinct —
|
|
112
|
+
`datefocus` observes, `dateactivate` requests an activation, `datechange`
|
|
113
|
+
observes a selection.
|
|
114
|
+
|
|
115
|
+
#### `datechange`
|
|
116
|
+
|
|
117
|
+
Fired when `selection="single"` changes `value`.
|
|
118
|
+
|
|
119
|
+
#### `dateinvalid`
|
|
120
|
+
|
|
121
|
+
Fired when the user tries to activate a disabled/out-of-range date.
|
|
122
|
+
|
|
123
|
+
#### `displaychange`
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
{
|
|
127
|
+
display: "2026-10",
|
|
128
|
+
start: "2026-09-28",
|
|
129
|
+
end: "2026-11-08"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The range reflects rendered cells (and therefore includes six-row padding when enabled).
|
|
134
|
+
|
|
135
|
+
#### Source lifecycle
|
|
136
|
+
|
|
137
|
+
- `dateloadstart`
|
|
138
|
+
- `dateloadend`
|
|
139
|
+
- `dateloaderror`
|
|
140
|
+
|
|
141
|
+
## `<date-picker>`
|
|
142
|
+
|
|
143
|
+
Markup:
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<label for="arrival">Arrival</label>
|
|
147
|
+
<date-picker value="2026-09-10" locale="en-GB">
|
|
148
|
+
<input id="arrival" name="arrival" required>
|
|
149
|
+
</date-picker>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The direct child text input is required. On enhancement its original `name` moves to an internal hidden input that submits the canonical ISO value. Dynamic `name`/`form`/`disabled` changes are synchronized by a `MutationObserver`; code that reads `FormData` in the same task as such a mutation must wait for the next microtask. Removing `name` from the visible enhanced input does not unregister the canonical field because that input no longer owns the submitted name; disable the field to omit it from submission.
|
|
153
|
+
|
|
154
|
+
### Attributes / properties
|
|
155
|
+
|
|
156
|
+
- `controlClasses` — JS-only `{ month, year, nav }` partial class map; see header control styling below.
|
|
157
|
+
|
|
158
|
+
- `value` — canonical selected date;
|
|
159
|
+
- `locale`;
|
|
160
|
+
- `min`;
|
|
161
|
+
- `max`;
|
|
162
|
+
- `open-on-focus` — boolean (default `true`): open the popover when the input receives focus without moving focus; `open-on-focus="false"` keeps focus alone passive;
|
|
163
|
+
- `month-format` — `long | short` (default `long`): visible month-name style forwarded to the popup calendar;
|
|
164
|
+
- `open` — read-only: whether the popover is shown;
|
|
165
|
+
- `openOnFocus` — property form of `open-on-focus`.
|
|
166
|
+
|
|
167
|
+
### Native form semantics
|
|
168
|
+
|
|
169
|
+
- `disabled` — non-editable, popup cannot open, field is not submitted.
|
|
170
|
+
- `readonly` — non-editable, value is still submitted, popup cannot open (the trigger is disabled).
|
|
171
|
+
- Clearing the editable input clears the canonical value; the component does not render its own clear button.
|
|
172
|
+
- `input.defaultValue` (or the input's `value` attribute) is the source of truth for the next `form.reset()`, which restores canonical + visible representations and revalidates.
|
|
173
|
+
|
|
174
|
+
### Forwarded callback properties
|
|
175
|
+
|
|
176
|
+
- `source`;
|
|
177
|
+
- `dateState`;
|
|
178
|
+
- `renderDay`;
|
|
179
|
+
- `isDateDisabled`;
|
|
180
|
+
- `messages`.
|
|
181
|
+
|
|
182
|
+
### Popup positioning
|
|
183
|
+
|
|
184
|
+
- `coordinateSpace` — JS-only, `"viewport"` by default.
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
"viewport"
|
|
188
|
+
panel uses position: fixed
|
|
189
|
+
general/default integration
|
|
190
|
+
|
|
191
|
+
"document"
|
|
192
|
+
panel uses position: absolute
|
|
193
|
+
explicit optimization when the application knows the picker
|
|
194
|
+
reference moves with root-page scrolling
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`document` follows the `@lekoala/floating` consumer contract: the picker panel is a top-layer popover, so its absolute positioning resolves against the initial containing block; the application remains responsible for knowing that the `<date-picker>` reference itself moves with the root document. Changing `coordinateSpace` while open configures the next opening, never the current one. See the `@lekoala/floating` documentation for the coordinate-space doctrine.
|
|
198
|
+
|
|
199
|
+
### Read-only properties
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
picker.input;
|
|
203
|
+
picker.calendar;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Methods
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
picker.show();
|
|
210
|
+
picker.hide();
|
|
211
|
+
await picker.validate();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
When disconnected, `validate()` cannot run source availability checks and returns the current native validity of the authored date/time inputs.
|
|
215
|
+
|
|
216
|
+
### Events
|
|
217
|
+
|
|
218
|
+
- `valuechange` — canonical value changed;
|
|
219
|
+
- `open`;
|
|
220
|
+
- `close`.
|
|
221
|
+
|
|
222
|
+
Calendar selection also dispatches normal bubbling `input` and `change` events on the visible input.
|
|
223
|
+
|
|
224
|
+
### Time companions
|
|
225
|
+
|
|
226
|
+
Date with one time:
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<date-picker>
|
|
230
|
+
<input name="appointment[date]">
|
|
231
|
+
<input type="time" data-time-start name="appointment[time]">
|
|
232
|
+
</date-picker>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```text
|
|
236
|
+
appointment[date] = 2026-09-10
|
|
237
|
+
appointment[time] = 09:30
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Date with from/to times on the same day:
|
|
241
|
+
|
|
242
|
+
```html
|
|
243
|
+
<date-picker>
|
|
244
|
+
<input name="appointment[date]">
|
|
245
|
+
<input type="time" data-time-start name="appointment[from]">
|
|
246
|
+
<input type="time" data-time-end name="appointment[to]">
|
|
247
|
+
</date-picker>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
appointment[date] = 2026-09-10
|
|
252
|
+
appointment[from] = 09:30
|
|
253
|
+
appointment[to] = 11:00
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The submit contract is: `date-picker` owns the civil date selection, the time inputs own their civil time values, the authored HTML `name`s own the submitted structure, and the server/application owns datetime composition plus timezone. The picker never constructs a combined datetime, timestamp or timezone-bearing value.
|
|
257
|
+
|
|
258
|
+
Future range + time projection (not supported yet — range mode leaves marked times native with a console warning):
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
period[start][date]
|
|
262
|
+
period[start][time]
|
|
263
|
+
period[end][date]
|
|
264
|
+
period[end][time]
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Optional native time companions (one or both). They are direct children, fixed at connect time, and stay fully native: no hidden input, no `picker.time` property, no `timechange` event — their `input`/`change` events bubble like the date field's. `value` stays a `YYYY-MM-DD` date. The picker only enforces from/to order (`start <= end`, equality allowed, see [D5](DECISIONS.md)); a missing, empty or `disabled` time lifts the constraint, and `validate()` aggregates date validity, native time validity and that order. Range mode does not support time companions yet: marked times there are left native with a console warning.
|
|
268
|
+
|
|
269
|
+
## `<date-picker range>` — shared range surface
|
|
270
|
+
|
|
271
|
+
```html
|
|
272
|
+
<date-picker range>
|
|
273
|
+
<input data-range-start name="arrival" aria-label="Arrival">
|
|
274
|
+
<input data-range-end name="departure" aria-label="Departure">
|
|
275
|
+
</date-picker>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Two editable fields share one calendar popup. The mode is fixed at connect time (`range` attribute + both marked direct child inputs); switching single/range at runtime is not supported in V1.
|
|
279
|
+
|
|
280
|
+
Each field gets its own calendar trigger, inserted right after it (`start`, then its trigger, `end`, then its trigger), so DOM order matches visual/tab order. Both triggers point at the same popup; activating the other bound's trigger while it is open switches the active bound and keeps it open.
|
|
281
|
+
|
|
282
|
+
### Strict API
|
|
283
|
+
|
|
284
|
+
- `range` — atomic get/set of `{ start, end }` (canonical `YYYY-MM-DD`). The setter validates through `normalizeRange` (ISO-only, ordered; inverted throws) and applies both bounds, the visible fields and both hidden fields **before** one `rangechange` event — no intermediate new-start/old-end state is observable.
|
|
285
|
+
- `value` — `undefined` in range mode; assigning throws a `TypeError`.
|
|
286
|
+
- `input` — `undefined` in range mode; the authored inputs remain the consumer-facing fields.
|
|
287
|
+
- `startValue` / `endValue` — deferred; do not exist yet.
|
|
288
|
+
|
|
289
|
+
Each field keeps its own name, hidden ISO input and validation. There is no `rangeDrop`-style polymorphic pair.
|
|
290
|
+
|
|
291
|
+
### `rangechange`
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
picker.addEventListener("rangechange", (event) => {
|
|
295
|
+
console.log(event.detail); // { start, end } — may be incomplete or inverted
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The business range may be temporarily incomplete or inverted; `calendar.highlightedRange` only ever receives a displayable (ordered) range.
|
|
300
|
+
|
|
301
|
+
### Interaction
|
|
302
|
+
|
|
303
|
+
- When an ordered range is complete, picking before its start updates only the start; picking after its end updates only the end, regardless of the opening field. The popup closes after this update. An uneditable target bound refuses the activation.
|
|
304
|
+
- Otherwise, opening through a field targets that bound (`activeEndpoint`). Picking from `start` commits it, moves the endpoint to `end` without closing, and a second pick commits and closes.
|
|
305
|
+
- **Calendar interaction never produces an inverted range.** While a range is being created, a second pick before the anchor is sorted rather than refused: `10` then `15` gives `10 -> 15`, `10` then `5` gives `5 -> 10`, and `10` then `10` a one-day range. That transition moves **both** bounds at once and is applied in a single write — one `rangechange`, with synthetic `input`/`change` on each bound that moved, and no intermediate inverted pair ever observable. Typed text keeps the opposite contract and may be temporarily inverted (see [DECISIONS D6](DECISIONS.md)).
|
|
306
|
+
- Changing one bound of an already complete pair keeps that bound's identity: moving `end` before `start` is refused (`dateinvalid`, nothing changes) instead of rewriting the other field.
|
|
307
|
+
- **Preview.** While a range is being created, hovering a day or moving the keyboard focus projects the range the next pick would commit onto `calendar.highlightedRange`, and the pick commits exactly what was projected. The projection is visual only: no `rangechange`, no field write, no validation and no source request. It reads the month already loaded, so a day known to be unavailable promises nothing; the real availability check still runs on commit. Pointer hover never moves `focusedDate`, which stays the keyboard target. Leaving the grid, Escape and closing the popup restore the committed band.
|
|
308
|
+
- **Endpoint drag.** On a complete, ordered range whose two ends sit on different days, each endpoint can be dragged with a mouse or pen (pointer events plus capture, armed only once the pointer travels). A handle never crosses the other one — the candidate is clamped to it, equality allowed — an unavailable cell is not a drop target, and `pointercancel` restores the committed band. The drop runs the same projection and the same `ensureDate` + cancelable `dateactivate` path as a click, emits one `rangechange`, and keeps the popup open: a drag adjusts a range, it does not validate it. A press without travel stays a plain click. Touch keeps native scrolling and stays a plain tap.
|
|
309
|
+
- Each bound owns its trigger (`input[data-range-start]` then its trigger, `input[data-range-end]` then its trigger); the two triggers share **one** popup, calendar, source and state. Pressing the other bound's trigger while the popup is open switches the active bound and keeps it open instead of toggling it shut. A trigger never opens a `readonly`/`disabled` bound.
|
|
310
|
+
- Typing stays independent per field. Moving `start` past `end` keeps both values and flags the *modified* bound with `rangeOrderStart`; fixing either side revalidates both. Parse/source errors are never cleared by cross-bound revalidation.
|
|
311
|
+
- A stale availability response resolving after the active endpoint changed (or after a newer selection) never commits; a field focus that switches the endpoint invalidates in-flight activations.
|
|
312
|
+
|
|
313
|
+
## `linkDateRange(start, end)`
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
const cleanup = linkDateRange(startPicker, endPicker);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Keeps effective start/end bounds synchronized and revalidates both inputs. Returns an idempotent-style cleanup function (call once to detach the relationship).
|
|
320
|
+
|
|
321
|
+
## `normalizeRange` and `rangePosition`
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
import { normalizeRange, rangePosition } from "@lekoala/date-picker";
|
|
325
|
+
|
|
326
|
+
normalizeRange({ start: "2026-09-10", end: "2026-09-15" }); // { start, end }
|
|
327
|
+
normalizeRange(null); // { start: "", end: "" }
|
|
328
|
+
rangePosition("2026-09-12", range); // "start" | "in" | "end" | "single" | ""
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`normalizeRange` validates an ordered displayable range and never reorders: an inversion or an `end` without a `start` throws. `rangePosition` classifies one day within a displayable range (see [`highlightedRange`](#highlightedrange)).
|
|
332
|
+
|
|
333
|
+
## `DateRangeController`
|
|
334
|
+
|
|
335
|
+
The pure two-bound state machine behind `date-picker[range]`. It owns the
|
|
336
|
+
selection rules and knows nothing about DOM, popups or async availability.
|
|
337
|
+
|
|
338
|
+
```js
|
|
339
|
+
const model = new DateRangeController();
|
|
340
|
+
model.focus("start");
|
|
341
|
+
model.activate("2026-09-10"); // { status: "pending", endpoint: "start", … }
|
|
342
|
+
model.previewRange("2026-09-05"); // { start: "2026-09-05", end: "2026-09-10" }
|
|
343
|
+
model.activate("2026-09-05"); // changedEndpoints: ["start", "end"]
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
- `project(date, editable?)` / `projectEndpoint(date, endpoint, editable?)` describe a transition without applying it;
|
|
347
|
+
- `activate(date, editable?)` / `moveEndpoint(date, endpoint, editable?)` apply one;
|
|
348
|
+
- `previewRange(date, endpoint?, editable?)` returns the displayable band a candidate would produce, or `null` when there is nothing to promise.
|
|
349
|
+
|
|
350
|
+
A transition is `{ status, endpoint, changedEndpoints, range, activeEndpoint, close? }`.
|
|
351
|
+
`range` is the whole resulting pair, because one interaction can move both
|
|
352
|
+
bounds; `changedEndpoints` names the ones that actually move. Every rule exists
|
|
353
|
+
once, as a projection, and the previews read the same projections the commit
|
|
354
|
+
applies.
|
|
355
|
+
|
|
356
|
+
## `createDateAdapter(locale)`
|
|
357
|
+
|
|
358
|
+
Locale-aware editable numeric adapter:
|
|
359
|
+
|
|
360
|
+
```js
|
|
361
|
+
const adapter = createDateAdapter("fr-BE");
|
|
362
|
+
adapter.placeholder; // DD/MM/YYYY
|
|
363
|
+
adapter.format("2026-09-06"); // 06/09/2026
|
|
364
|
+
adapter.parse("06/09/2026"); // 2026-09-06
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
ISO input is accepted as a fallback even when the locale display is different.
|
|
368
|
+
|
|
369
|
+
## Locales
|
|
370
|
+
|
|
371
|
+
`setDefaultMessages(partial)` merges global defaults and refreshes connected elements without a messages override, including picker field validation, triggers and popup labels. `getDefaultMessages()` returns a copy. Assigning `element.messages` merges that assignment with the current global defaults, not with the previous assignment. Assigning `null` resumes following global defaults.
|
|
372
|
+
|
|
373
|
+
Control-message dictionaries ship per locale and can be attached through the `messages` property:
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
import fr from "@lekoala/date-picker/locales/fr";
|
|
377
|
+
calendar.messages = fr;
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Available tags: `ar`, `de`, `en`, `es`, `fr`, `hi`, `id`, `it`, `ja`, `ko`, `nl`, `pl`, `pt-BR`, `pt-PT`, `ru`, `tr`, `zh-CN`. `en` mirrors the built-in defaults.
|
|
381
|
+
|
|
382
|
+
Only the short control labels (choose/change date, month/year names, grid heading, validation messages, format hint, week heading) are locale data. Everything locale-shaped on screen — month/weekday names, the editable pattern and placeholder, parsing and digit normalization — comes from `Intl` via the `locale` attribute, so a dictionary is never required for a new language to display correctly.
|
|
383
|
+
|
|
384
|
+
## `dates`
|
|
385
|
+
|
|
386
|
+
Pure helpers are exported as a namespace:
|
|
387
|
+
|
|
388
|
+
```js
|
|
389
|
+
import { dates } from "@lekoala/date-picker";
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Public helpers:
|
|
393
|
+
|
|
394
|
+
- `isLeapYear`
|
|
395
|
+
- `daysInMonth`
|
|
396
|
+
- `parseDate`
|
|
397
|
+
- `isDate`
|
|
398
|
+
- `monthKey`
|
|
399
|
+
- `toISODate`
|
|
400
|
+
- `compareDates`
|
|
401
|
+
- `addDays`
|
|
402
|
+
- `addMonths`
|
|
403
|
+
- `addYears`
|
|
404
|
+
- `shiftMonth`
|
|
405
|
+
- `startOfMonth`
|
|
406
|
+
- `endOfMonth`
|
|
407
|
+
- `dayOfWeek`
|
|
408
|
+
- `startOfWeek`
|
|
409
|
+
- `endOfWeek`
|
|
410
|
+
- `normalizeFirstDay`
|
|
411
|
+
- `getMonthWeeks`
|
|
412
|
+
- `clampDate`
|
|
413
|
+
- `todayISO`
|
|
414
|
+
- `isoWeekNumber`
|
|
415
|
+
|
|
416
|
+
`normalizeFirstDay(firstDay)` maps `7` to `0` (Sunday) and returns `0..6` unchanged; any other value throws a `RangeError`. It backs `startOfWeek`, `getMonthWeeks` and `weekdayNames`, so those accept `7` as the ISO-style Sunday alias `@lekoala/calendar` uses while `2..6` keep their current meaning.
|
|
417
|
+
|
|
418
|
+
## Header control styling
|
|
419
|
+
|
|
420
|
+
`setDefaultControlClasses(partial)` and `getDefaultControlClasses()` are named package exports, alongside `setDefaultMessages()` / `getDefaultMessages()`. Class defaults are resolved per role when used and refresh connected elements with inherited roles. The getter returns a copy.
|
|
421
|
+
|
|
422
|
+
The same four functions are available as static methods on both `DateCalendarElement` and `DatePickerElement`. For the classic build, call them through the registry after the script has loaded:
|
|
423
|
+
|
|
424
|
+
```js
|
|
425
|
+
const Picker = customElements.get("date-picker");
|
|
426
|
+
Picker.setDefaultControlClasses({ month: "select sm", year: "input sm", nav: "btn outline sm icon-only" });
|
|
427
|
+
Picker.setDefaultMessages({ chooseDate: "Choose a date" });
|
|
428
|
+
const classes = Picker.getDefaultControlClasses();
|
|
429
|
+
const messages = customElements.get("date-calendar").getDefaultMessages();
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Both constructors delegate to the same defaults as the named ESM exports within that build. Class defaults update inherited roles on connected elements; explicitly assigned roles stay unchanged. Message defaults update elements without a messages override. All writable public properties assigned before definition are replayed through their setters on connection. Picker `value` and `range` are replayed after its fields and controllers are initialized; `range` still requires the `range` attribute. Read-only properties remain read-only.
|
|
433
|
+
|
|
434
|
+
Both elements expose `controlClasses`. Assignment stores only the normalized roles provided, replacing the previous partial map. Each omitted role follows its current global default when read or rendered, regardless of assignment order. An empty string explicitly restores the default skin for a role; `null` resumes following global defaults. The picker forwards only its own partial map to its shared calendar, including in range mode. There is no HTML attribute.
|
|
435
|
+
|
|
436
|
+
Each non-empty role uses only the author's skin (including caret, hover, disabled and focus). Grid layout, year track width and month truncation stay component-owned. Day cells and the picker trigger are excluded. Class lists are whitespace-normalized and deduplicated; tokens containing control characters or using the reserved `dp-*` namespace are ignored. Arbitrary utility classes such as `[&>svg]:size-4`, `data-[state=open]:bg-muted` and `aria-[disabled=true]:opacity-50` are preserved. Values are HTML-escaped on rendering, including quotes and HTML delimiters. Reassigning equivalent normalized classes leaves the rendered controls and grid intact.
|
|
437
|
+
|
|
438
|
+
## Render snapshots and range previews
|
|
439
|
+
|
|
440
|
+
A full `calendar.render()` resolves each visible day's state once through `dateState` and, unless explicitly enabled, `isDateDisabled`. The grid and `renderDay` use that same state snapshot. Range-band updates reuse it without calling these callbacks again. If a callback reads mutable application data, call `calendar.render()` after changing that data. Assigning callbacks, changing calendar options or completing a source load already rebuilds the snapshot. Activation still reads current state and checks source availability.
|
|
441
|
+
|
|
442
|
+
`fixed-weeks` requests six complete rows. At the end of the canonical date window, December 9999 with Saturday as the first weekday renders five rows rather than padding beyond `9999-12-31`. `dates.getMonthWeeks(value, { firstDay, fixedWeeks: true })` uses the same rule; its default remains unpadded.
|
|
443
|
+
|
|
444
|
+
`createFetchSource()` resolves and validates its URL when the adapter is created. Relative URLs use `document.baseURI` in a document or `globalThis.location.href` in a worker. Without either base, an absolute URL is required and a relative URL throws an explicit `TypeError` immediately. There is no implicit localhost base. Cancellation and optional timeouts belong to the caller's `AbortSignal`.
|