@lekoala/date-picker 0.1.0
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/LICENSE +21 -0
- package/README.md +236 -0
- package/custom-elements.json +366 -0
- package/dist/date-picker.css +344 -0
- package/dist/date-picker.js +2693 -0
- package/dist/date-picker.min.css +1 -0
- package/dist/date-picker.min.js +17 -0
- package/dist/date-picker.standalone.min.js +361 -0
- package/dist/types/calendar-model.d.ts +34 -0
- package/dist/types/calendar-model.d.ts.map +1 -0
- package/dist/types/date-calendar.d.ts +228 -0
- package/dist/types/date-calendar.d.ts.map +1 -0
- package/dist/types/date-field.d.ts +94 -0
- package/dist/types/date-field.d.ts.map +1 -0
- package/dist/types/date-picker.d.ts +258 -0
- package/dist/types/date-picker.d.ts.map +1 -0
- package/dist/types/date-range.d.ts +89 -0
- package/dist/types/date-range.d.ts.map +1 -0
- package/dist/types/date.d.ts +61 -0
- package/dist/types/date.d.ts.map +1 -0
- package/dist/types/define.d.ts +2 -0
- package/dist/types/define.d.ts.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/intl.d.ts +33 -0
- package/dist/types/intl.d.ts.map +1 -0
- package/dist/types/locales/ar.d.ts +22 -0
- package/dist/types/locales/ar.d.ts.map +1 -0
- package/dist/types/locales/de.d.ts +22 -0
- package/dist/types/locales/de.d.ts.map +1 -0
- package/dist/types/locales/en.d.ts +22 -0
- package/dist/types/locales/en.d.ts.map +1 -0
- package/dist/types/locales/es.d.ts +22 -0
- package/dist/types/locales/es.d.ts.map +1 -0
- package/dist/types/locales/fr.d.ts +22 -0
- package/dist/types/locales/fr.d.ts.map +1 -0
- package/dist/types/locales/hi.d.ts +22 -0
- package/dist/types/locales/hi.d.ts.map +1 -0
- package/dist/types/locales/id.d.ts +22 -0
- package/dist/types/locales/id.d.ts.map +1 -0
- package/dist/types/locales/it.d.ts +22 -0
- package/dist/types/locales/it.d.ts.map +1 -0
- package/dist/types/locales/ja.d.ts +22 -0
- package/dist/types/locales/ja.d.ts.map +1 -0
- package/dist/types/locales/ko.d.ts +22 -0
- package/dist/types/locales/ko.d.ts.map +1 -0
- package/dist/types/locales/nl.d.ts +22 -0
- package/dist/types/locales/nl.d.ts.map +1 -0
- package/dist/types/locales/pl.d.ts +22 -0
- package/dist/types/locales/pl.d.ts.map +1 -0
- package/dist/types/locales/pt-BR.d.ts +22 -0
- package/dist/types/locales/pt-BR.d.ts.map +1 -0
- package/dist/types/locales/pt-PT.d.ts +22 -0
- package/dist/types/locales/pt-PT.d.ts.map +1 -0
- package/dist/types/locales/ru.d.ts +22 -0
- package/dist/types/locales/ru.d.ts.map +1 -0
- package/dist/types/locales/tr.d.ts +22 -0
- package/dist/types/locales/tr.d.ts.map +1 -0
- package/dist/types/locales/zh-CN.d.ts +22 -0
- package/dist/types/locales/zh-CN.d.ts.map +1 -0
- package/dist/types/messages.d.ts +43 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/source.d.ts +28 -0
- package/dist/types/source.d.ts.map +1 -0
- package/docs/ACCESSIBILITY.md +128 -0
- package/docs/API.md +289 -0
- package/docs/ARCHITECTURE.md +183 -0
- package/docs/AT-CHECKLIST.md +63 -0
- package/docs/CALENDAR-INTEGRATION.md +70 -0
- package/docs/DECISIONS.md +40 -0
- package/docs/MIGRATION-FLATPICKR.md +92 -0
- package/docs/ROADMAP.md +105 -0
- package/docs/TESTING.md +73 -0
- package/docs/USE_CASES.md +128 -0
- package/package.json +174 -0
- package/src/calendar-model.js +80 -0
- package/src/date-calendar.js +800 -0
- package/src/date-field.js +203 -0
- package/src/date-picker.css +344 -0
- package/src/date-picker.js +1140 -0
- package/src/date-range.js +172 -0
- package/src/date.js +203 -0
- package/src/define.js +9 -0
- package/src/index.js +15 -0
- package/src/intl.js +155 -0
- package/src/locales/ar.js +20 -0
- package/src/locales/de.js +20 -0
- package/src/locales/en.js +20 -0
- package/src/locales/es.js +20 -0
- package/src/locales/fr.js +20 -0
- package/src/locales/hi.js +20 -0
- package/src/locales/id.js +20 -0
- package/src/locales/it.js +20 -0
- package/src/locales/ja.js +20 -0
- package/src/locales/ko.js +20 -0
- package/src/locales/nl.js +20 -0
- package/src/locales/pl.js +20 -0
- package/src/locales/pt-BR.js +20 -0
- package/src/locales/pt-PT.js +20 -0
- package/src/locales/ru.js +20 -0
- package/src/locales/tr.js +20 -0
- package/src/locales/zh-CN.js +20 -0
- package/src/messages.js +31 -0
- package/src/source.js +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../src/locales/tr.js"],"names":[],"mappings":";IACE,UAAU;IACV,UAAU;IACV,aAAa;IACb,SAAS;IACT,KAAK;IACL,IAAI;IACJ,QAAQ;IACR,WAAW;IACX,WAAW;IACX,eAAe;IACf,UAAU;IACV,IAAI;IACJ,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,WAAW;IACX,eAAe;IACf,aAAa"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
chooseDate: string;
|
|
3
|
+
changeDate: string;
|
|
4
|
+
previousMonth: string;
|
|
5
|
+
nextMonth: string;
|
|
6
|
+
month: string;
|
|
7
|
+
year: string;
|
|
8
|
+
calendar: string;
|
|
9
|
+
unavailable: string;
|
|
10
|
+
invalidDate: string;
|
|
11
|
+
unavailableDate: string;
|
|
12
|
+
formatHint: string;
|
|
13
|
+
week: string;
|
|
14
|
+
rangeStart: string;
|
|
15
|
+
rangeInRange: string;
|
|
16
|
+
rangeEnd: string;
|
|
17
|
+
rangeSingle: string;
|
|
18
|
+
rangeOrderStart: string;
|
|
19
|
+
rangeOrderEnd: string;
|
|
20
|
+
};
|
|
21
|
+
export default _default;
|
|
22
|
+
//# sourceMappingURL=zh-CN.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zh-CN.d.ts","sourceRoot":"","sources":["../../../src/locales/zh-CN.js"],"names":[],"mappings":";IACE,UAAU;IACV,UAAU;IACV,aAAa;IACb,SAAS;IACT,KAAK;IACL,IAAI;IACJ,QAAQ;IACR,WAAW;IACX,WAAW;IACX,eAAe;IACf,UAAU;IACV,IAAI;IACJ,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,WAAW;IACX,eAAe;IACf,aAAa"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const DEFAULT_MESSAGES: {
|
|
2
|
+
chooseDate: string;
|
|
3
|
+
changeDate: string;
|
|
4
|
+
previousMonth: string;
|
|
5
|
+
nextMonth: string;
|
|
6
|
+
month: string;
|
|
7
|
+
year: string;
|
|
8
|
+
calendar: string;
|
|
9
|
+
unavailable: string;
|
|
10
|
+
invalidDate: string;
|
|
11
|
+
unavailableDate: string;
|
|
12
|
+
formatHint: string;
|
|
13
|
+
week: string;
|
|
14
|
+
rangeStart: string;
|
|
15
|
+
rangeInRange: string;
|
|
16
|
+
rangeEnd: string;
|
|
17
|
+
rangeSingle: string;
|
|
18
|
+
rangeOrderStart: string;
|
|
19
|
+
rangeOrderEnd: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function getDefaultMessages(): {
|
|
22
|
+
chooseDate: string;
|
|
23
|
+
changeDate: string;
|
|
24
|
+
previousMonth: string;
|
|
25
|
+
nextMonth: string;
|
|
26
|
+
month: string;
|
|
27
|
+
year: string;
|
|
28
|
+
calendar: string;
|
|
29
|
+
unavailable: string;
|
|
30
|
+
invalidDate: string;
|
|
31
|
+
unavailableDate: string;
|
|
32
|
+
formatHint: string;
|
|
33
|
+
week: string;
|
|
34
|
+
rangeStart: string;
|
|
35
|
+
rangeInRange: string;
|
|
36
|
+
rangeEnd: string;
|
|
37
|
+
rangeSingle: string;
|
|
38
|
+
rangeOrderStart: string;
|
|
39
|
+
rangeOrderEnd: string;
|
|
40
|
+
};
|
|
41
|
+
/** @param {Partial<typeof DEFAULT_MESSAGES>} messages */
|
|
42
|
+
export declare function setDefaultMessages(messages: Partial<typeof DEFAULT_MESSAGES>): void;
|
|
43
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/messages.js"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB;IAC3B,UAAU;IACV,UAAU;IACV,aAAa;IACb,SAAS;IACT,KAAK;IACL,IAAI;IACJ,QAAQ;IACR,WAAW;IACX,WAAW;IACX,eAAe;IACf,UAAU;IACV,IAAI;IACJ,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,WAAW;IACX,eAAe;IACf,aAAa;CACd,CAAC;AAIF,wBAAgB,kBAAkB;;;;;;;;;;;;;;;;;;;EAEjC;AAED,yDAAyD;AACzD,wBAAgB,kBAAkB,CAAC,QAAQ,EAD/B,OAAO,CAAC,OAAO,gBAAgB,CACA,QAE1C"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize source payloads to a Map keyed by canonical date.
|
|
3
|
+
* Accepted shapes: { dates: {...} }, { "YYYY-MM-DD": {...} }, or [{date,...}].
|
|
4
|
+
* @param {unknown} payload
|
|
5
|
+
* @returns {Map<string, Record<string, unknown>>}
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeDateStates(payload: unknown): Map<string, Record<string, unknown>>;
|
|
8
|
+
/**
|
|
9
|
+
* Small fetch adapter for legacy/configUrl-like endpoints.
|
|
10
|
+
* @param {string | URL} url
|
|
11
|
+
* @param {{map?:(payload:unknown)=>unknown, params?:(range:{start:string,end:string})=>Record<string,string>}} [options]
|
|
12
|
+
*/
|
|
13
|
+
export declare function createFetchSource(url: string | URL, options?: {
|
|
14
|
+
map?: (payload: unknown) => unknown;
|
|
15
|
+
params?: (range: {
|
|
16
|
+
start: string;
|
|
17
|
+
end: string;
|
|
18
|
+
}) => Record<string, string>;
|
|
19
|
+
}): {
|
|
20
|
+
/** @param {{start:string,end:string}} range @param {{signal?:AbortSignal}} [context] */
|
|
21
|
+
load(range: {
|
|
22
|
+
start: string;
|
|
23
|
+
end: string;
|
|
24
|
+
}, context?: {
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
}): Promise<any>;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/source.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAHhC,OAGgC,GAF9B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsBhD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAH1B,MAAM,GAAG,GAGiB,EAAE,OAAO,AAF3C,CAAsH,EAA9G;IAAC,GAAG,CAAC,EAAC,CAAC,OAAO,EAAC,OAAO,KAAG,OAAO,CAAC;IAAC,MAAM,CAAC,EAAC,CAAC,KAAK,EAAC;QAAC,KAAK,EAAC,MAAM,CAAC;QAAA,GAAG,EAAC,MAAM,CAAA;KAAC,KAAG,MAAM,CAAC,MAAM,EAAC,MAAM,CAAC,CAAA;CAE1D;IAE/C,wFAAwF;IAClF,IAAI,QADE;QAAC,KAAK,EAAC,MAAM,CAAC;QAAA,GAAG,EAAC,MAAM,CAAA;KAAC,YAAgB;QAAC,MAAM,CAAC,EAAC,WAAW,CAAA;KAAC;EAc7E"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
Accessibility is part of the component contract, not a polish milestone.
|
|
4
|
+
|
|
5
|
+
Reference patterns:
|
|
6
|
+
|
|
7
|
+
- WAI-ARIA APG Date Picker Dialog: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/
|
|
8
|
+
- WAI-ARIA APG Date Picker Combobox: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-datepicker/
|
|
9
|
+
- Grid pattern: https://www.w3.org/WAI/ARIA/apg/patterns/grid/
|
|
10
|
+
|
|
11
|
+
APG examples are examples, not drop-in production code. This implementation should be tested with real browser/assistive-technology combinations before 0.1.
|
|
12
|
+
|
|
13
|
+
## Calendar grid
|
|
14
|
+
|
|
15
|
+
The calendar renders a semantic `<table role="grid">`.
|
|
16
|
+
|
|
17
|
+
Because the grid role is applied to a table, native `tr`, `th` and `td` elements provide row/column/gridcell semantics. Exactly one date cell has `tabindex="0"`; every other date cell has `tabindex="-1"`.
|
|
18
|
+
|
|
19
|
+
Selection is represented with `aria-selected="true"` on the selected cell. Today uses `aria-current="date"`.
|
|
20
|
+
|
|
21
|
+
A business-disabled date uses `aria-disabled="true"`, but remains focusable by arrow keys. This lets a user discover the date and hear its description instead of creating unexplained holes in keyboard navigation.
|
|
22
|
+
|
|
23
|
+
Weekday headings use visible short labels plus a full `abbr` value.
|
|
24
|
+
|
|
25
|
+
## Keyboard map
|
|
26
|
+
|
|
27
|
+
| Key | Calendar action |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| Left | previous day |
|
|
30
|
+
| Right | next day |
|
|
31
|
+
| Up | previous week |
|
|
32
|
+
| Down | next week |
|
|
33
|
+
| Home | first day of current week |
|
|
34
|
+
| End | last day of current week |
|
|
35
|
+
| PageUp | same/clamped day in previous month |
|
|
36
|
+
| PageDown | same/clamped day in next month |
|
|
37
|
+
| Shift+PageUp | same/clamped day in previous year |
|
|
38
|
+
| Shift+PageDown | same/clamped day in next year |
|
|
39
|
+
| Enter / Space | activate focused date |
|
|
40
|
+
|
|
41
|
+
Moving focus never changes selection.
|
|
42
|
+
|
|
43
|
+
## Month/year controls
|
|
44
|
+
|
|
45
|
+
The prototype deliberately uses native selects. They are easy to discover, keyboard-operable, and do not require nested custom popovers.
|
|
46
|
+
|
|
47
|
+
The year select is a moving window around the displayed year when no hard min/max exists. Navigating past the current window causes a normal re-render centered on the new year.
|
|
48
|
+
|
|
49
|
+
## Date picker input
|
|
50
|
+
|
|
51
|
+
The visible input remains a native text field. It owns:
|
|
52
|
+
|
|
53
|
+
- focus;
|
|
54
|
+
- label association;
|
|
55
|
+
- required state;
|
|
56
|
+
- browser validation UI;
|
|
57
|
+
- typed text.
|
|
58
|
+
|
|
59
|
+
The picker sets:
|
|
60
|
+
|
|
61
|
+
- `role="combobox"`;
|
|
62
|
+
- `aria-haspopup="dialog"`;
|
|
63
|
+
- `aria-expanded`;
|
|
64
|
+
- `aria-controls`;
|
|
65
|
+
- a format hint through `aria-describedby`.
|
|
66
|
+
|
|
67
|
+
The calendar button has its own accessible name and includes the selected long date when one exists.
|
|
68
|
+
|
|
69
|
+
## Popup semantics
|
|
70
|
+
|
|
71
|
+
The popup is a native manual Popover with `role="dialog"`.
|
|
72
|
+
|
|
73
|
+
It is not currently modal: the implementation does not inert the rest of the page and does not claim `aria-modal="true"`.
|
|
74
|
+
|
|
75
|
+
Open behavior:
|
|
76
|
+
|
|
77
|
+
- the selected date is focused if present;
|
|
78
|
+
- otherwise today is focused;
|
|
79
|
+
- ArrowDown on the input opens the popup;
|
|
80
|
+
- the calendar button opens the popup.
|
|
81
|
+
|
|
82
|
+
Close behavior:
|
|
83
|
+
|
|
84
|
+
- date activation commits and returns focus to the input;
|
|
85
|
+
- Escape closes;
|
|
86
|
+
- outside pointer activation closes.
|
|
87
|
+
|
|
88
|
+
While the popover is open, `Tab` is **not** trapped: focus leaves the popover to the next focusable element in the page. The popup is non-modal (`aria-modal` absent, the rest of the page stays interactive), so capturing the focus the way a modal dialog does would be incoherent — `Escape` remains the predictable way to close and return focus to the field. This deliberately diverges from Duet Date Picker's internal Tab wrap-around.
|
|
89
|
+
|
|
90
|
+
Decision for 0.1: the popup stays **non-modal** on every surface, including small/touch screens. A modal variant would need a separate design (focus trap, inert background, explicit close) and a proposal of its own.
|
|
91
|
+
|
|
92
|
+
## Validation
|
|
93
|
+
|
|
94
|
+
A typed invalid date uses `setCustomValidity()` on the visible input. A parsed but unavailable date uses a distinct message.
|
|
95
|
+
|
|
96
|
+
The same resolved date state used by the grid is used for manual-entry validation.
|
|
97
|
+
|
|
98
|
+
## Forced colors guard (selected day)
|
|
99
|
+
|
|
100
|
+
Forced colors should not rely on the normal selected-day fill tokens.
|
|
101
|
+
|
|
102
|
+
When `forced-colors: active`, the selected day switches to a border-based treatment:
|
|
103
|
+
|
|
104
|
+
- `Canvas` background;
|
|
105
|
+
- `CanvasText` foreground;
|
|
106
|
+
- `Highlight` outer border;
|
|
107
|
+
- an inner `CanvasText` ring.
|
|
108
|
+
|
|
109
|
+
Today keeps the base 1px outline — `border-color: var(--dp-accent)` force-adjusts to `CanvasText` here, so it stays a thin `CanvasText` border. Selected and today therefore remain distinct through border color, width, the inner ring and weight (selected is `700`), not through a shared system key.
|
|
110
|
+
|
|
111
|
+
This avoids relying on white text over a `Highlight` fill, which can become ambiguous when the active high-contrast theme resolves `Highlight` near-white or when small white digits rasterize poorly. The disabled state is `GrayText` at full opacity.
|
|
112
|
+
|
|
113
|
+
Guard: re-run the `color-contrast` review and the forced-colors screenshot bundle whenever `--dp-*` styling or day-cell rendering changes.
|
|
114
|
+
|
|
115
|
+
## Recommended manual test matrix
|
|
116
|
+
|
|
117
|
+
Beyond what the automated suite covers, the following pairings should be exercised before a broad adoption of 0.1. Results are recorded in [AT-CHECKLIST.md](AT-CHECKLIST.md); the human rows there are still open:
|
|
118
|
+
|
|
119
|
+
- Windows + Chrome + NVDA;
|
|
120
|
+
- Windows + Firefox + NVDA;
|
|
121
|
+
- macOS + Safari + VoiceOver;
|
|
122
|
+
- iOS Safari + VoiceOver;
|
|
123
|
+
- Android Chrome + TalkBack;
|
|
124
|
+
- 200% zoom/reflow;
|
|
125
|
+
- forced-colors/high contrast;
|
|
126
|
+
- keyboard only;
|
|
127
|
+
- French and English locale;
|
|
128
|
+
- RTL spike before claiming RTL support.
|
package/docs/API.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
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
|
+
| Name | Type | Default | Meaning |
|
|
10
|
+
|---------------------|------------------|--------------------|----------------------------------------------|
|
|
11
|
+
| `value` | `YYYY-MM-DD` | `""` | Selected date. |
|
|
12
|
+
| `display` | `YYYY-MM` | value/today month | Month being rendered. |
|
|
13
|
+
| `min` | `YYYY-MM-DD` | `""` | Earliest activatable date. |
|
|
14
|
+
| `max` | `YYYY-MM-DD` | `""` | Latest activatable date. |
|
|
15
|
+
| `first-day` | `0..6` | `1` | First weekday; Sunday=0, Monday=1. |
|
|
16
|
+
| `locale` | BCP 47 string | document/navigator | `Intl` locale. |
|
|
17
|
+
| `selection` | `single \| none` | `single` | Whether accepted activation updates `value`. |
|
|
18
|
+
| `fixed-weeks` | boolean | false | Render six rows; date math itself stays 4–6. |
|
|
19
|
+
| `show-week-numbers` | boolean | false | Show ISO week numbers. |
|
|
20
|
+
|
|
21
|
+
`focusedDate` and `highlightedRange` are properties rather than reflected attributes.
|
|
22
|
+
|
|
23
|
+
### `highlightedRange`
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
### Callback properties
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
calendar.isDateDisabled = (date) => boolean;
|
|
31
|
+
calendar.dateState = (date, sourceState) => ({
|
|
32
|
+
disabled,
|
|
33
|
+
enabled, // true = explicit exception to generic disabled rules
|
|
34
|
+
description,
|
|
35
|
+
...applicationMetadata
|
|
36
|
+
});
|
|
37
|
+
calendar.renderDay = (date, state) => Node | string | null;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`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)).
|
|
41
|
+
|
|
42
|
+
### Source
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
calendar.source = async ({ start, end }, { signal }) => payload;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
or:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
calendar.source = {
|
|
52
|
+
load({ start, end }, { signal }) { ... }
|
|
53
|
+
};
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Methods
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
calendar.previousMonth();
|
|
60
|
+
calendar.nextMonth();
|
|
61
|
+
calendar.focusDate("2026-09-10");
|
|
62
|
+
calendar.focusGrid();
|
|
63
|
+
calendar.getDateState("2026-09-10");
|
|
64
|
+
await calendar.ensureDate("2026-09-10");
|
|
65
|
+
await calendar.refreshSource();
|
|
66
|
+
calendar.render();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Events
|
|
70
|
+
|
|
71
|
+
#### `dateactivate`
|
|
72
|
+
|
|
73
|
+
Cancelable. Fired for an allowed date after explicit click / Enter / Space.
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
calendar.addEventListener("dateactivate", (event) => {
|
|
77
|
+
console.log(event.detail.date, event.detail.state);
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Cancel it to refuse the activation before selection:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
calendar.addEventListener("dateactivate", (event) => {
|
|
85
|
+
if (applicationRule(event.detail.date)) event.preventDefault();
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `datechange`
|
|
90
|
+
|
|
91
|
+
Fired when `selection="single"` changes `value`.
|
|
92
|
+
|
|
93
|
+
#### `dateinvalid`
|
|
94
|
+
|
|
95
|
+
Fired when the user tries to activate a disabled/out-of-range date.
|
|
96
|
+
|
|
97
|
+
#### `displaychange`
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
{
|
|
101
|
+
display: "2026-10",
|
|
102
|
+
start: "2026-09-28",
|
|
103
|
+
end: "2026-11-08"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The range reflects rendered cells (and therefore includes six-row padding when enabled).
|
|
108
|
+
|
|
109
|
+
#### Source lifecycle
|
|
110
|
+
|
|
111
|
+
- `dateloadstart`
|
|
112
|
+
- `dateloadend`
|
|
113
|
+
- `dateloaderror`
|
|
114
|
+
|
|
115
|
+
## `<date-picker>`
|
|
116
|
+
|
|
117
|
+
Markup:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<label for="arrival">Arrival</label>
|
|
121
|
+
<date-picker value="2026-09-10" locale="en-GB">
|
|
122
|
+
<input id="arrival" name="arrival" required>
|
|
123
|
+
</date-picker>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The direct child text input is required. On enhancement its original `name` moves to an internal hidden input that submits the canonical ISO value.
|
|
127
|
+
|
|
128
|
+
### Attributes / properties
|
|
129
|
+
|
|
130
|
+
- `value` — canonical selected date;
|
|
131
|
+
- `locale`;
|
|
132
|
+
- `min`;
|
|
133
|
+
- `max`;
|
|
134
|
+
- `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;
|
|
135
|
+
- `open` — read-only: whether the popover is shown;
|
|
136
|
+
- `openOnFocus` — property form of `open-on-focus`.
|
|
137
|
+
|
|
138
|
+
### Native form semantics
|
|
139
|
+
|
|
140
|
+
- `disabled` — non-editable, popup cannot open, field is not submitted.
|
|
141
|
+
- `readonly` — non-editable, value is still submitted, popup cannot open (the trigger is disabled).
|
|
142
|
+
- Clearing the editable input clears the canonical value; the component does not render its own clear button.
|
|
143
|
+
- `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.
|
|
144
|
+
|
|
145
|
+
### Forwarded callback properties
|
|
146
|
+
|
|
147
|
+
- `source`;
|
|
148
|
+
- `dateState`;
|
|
149
|
+
- `renderDay`;
|
|
150
|
+
- `isDateDisabled`;
|
|
151
|
+
- `messages`.
|
|
152
|
+
|
|
153
|
+
### Read-only properties
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
picker.input;
|
|
157
|
+
picker.calendar;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Methods
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
picker.show();
|
|
164
|
+
picker.hide();
|
|
165
|
+
await picker.validate();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Events
|
|
169
|
+
|
|
170
|
+
- `valuechange` — canonical value changed;
|
|
171
|
+
- `open`;
|
|
172
|
+
- `close`.
|
|
173
|
+
|
|
174
|
+
Calendar selection also dispatches normal bubbling `input` and `change` events on the visible input.
|
|
175
|
+
|
|
176
|
+
## `<date-picker range>` — shared range surface
|
|
177
|
+
|
|
178
|
+
```html
|
|
179
|
+
<date-picker range>
|
|
180
|
+
<input data-range-start name="arrival" aria-label="Arrival">
|
|
181
|
+
<input data-range-end name="departure" aria-label="Departure">
|
|
182
|
+
</date-picker>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
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.
|
|
186
|
+
|
|
187
|
+
### Strict API
|
|
188
|
+
|
|
189
|
+
- `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.
|
|
190
|
+
- `value` — `undefined` in range mode; assigning throws a `TypeError`.
|
|
191
|
+
- `input` — `undefined` in range mode; the authored inputs remain the consumer-facing fields.
|
|
192
|
+
- `startValue` / `endValue` — deferred; do not exist yet.
|
|
193
|
+
|
|
194
|
+
Each field keeps its own name, hidden ISO input and validation. There is no `rangeDrop`-style polymorphic pair.
|
|
195
|
+
|
|
196
|
+
### `rangechange`
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
picker.addEventListener("rangechange", (event) => {
|
|
200
|
+
console.log(event.detail); // { start, end } — may be incomplete or inverted
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The business range may be temporarily incomplete or inverted; `calendar.highlightedRange` only ever receives a displayable (ordered) range.
|
|
205
|
+
|
|
206
|
+
### Interaction
|
|
207
|
+
|
|
208
|
+
- 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.
|
|
209
|
+
- 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. Opening from `end` refuses a date before `start` (`dateinvalid`, nothing changes).
|
|
210
|
+
- The shared trigger opens the last active bound, else `start`; it never auto-switches to a `readonly`/`disabled` bound.
|
|
211
|
+
- 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.
|
|
212
|
+
- 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.
|
|
213
|
+
|
|
214
|
+
## `linkDateRange(start, end)`
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
const cleanup = linkDateRange(startPicker, endPicker);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Keeps effective start/end bounds synchronized and revalidates both inputs. Returns an idempotent-style cleanup function (call once to detach the relationship).
|
|
221
|
+
|
|
222
|
+
## `normalizeRange` and `rangePosition`
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
import { normalizeRange, rangePosition } from "@lekoala/date-picker";
|
|
226
|
+
|
|
227
|
+
normalizeRange({ start: "2026-09-10", end: "2026-09-15" }); // { start, end }
|
|
228
|
+
normalizeRange(null); // { start: "", end: "" }
|
|
229
|
+
rangePosition("2026-09-12", range); // "start" | "in" | "end" | "single" | ""
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`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)).
|
|
233
|
+
|
|
234
|
+
## `createDateAdapter(locale)`
|
|
235
|
+
|
|
236
|
+
Locale-aware editable numeric adapter:
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
const adapter = createDateAdapter("fr-BE");
|
|
240
|
+
adapter.placeholder; // DD/MM/YYYY
|
|
241
|
+
adapter.format("2026-09-06"); // 06/09/2026
|
|
242
|
+
adapter.parse("06/09/2026"); // 2026-09-06
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
ISO input is accepted as a fallback even when the locale display is different.
|
|
246
|
+
|
|
247
|
+
## Locales
|
|
248
|
+
|
|
249
|
+
Control-message dictionaries ship per locale and can be attached through the `messages` property:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
import fr from "@lekoala/date-picker/locales/fr";
|
|
253
|
+
calendar.messages = fr;
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
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.
|
|
257
|
+
|
|
258
|
+
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.
|
|
259
|
+
|
|
260
|
+
## `dates`
|
|
261
|
+
|
|
262
|
+
Pure helpers are exported as a namespace:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
import { dates } from "@lekoala/date-picker";
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Public helpers:
|
|
269
|
+
|
|
270
|
+
- `isLeapYear`
|
|
271
|
+
- `daysInMonth`
|
|
272
|
+
- `parseDate`
|
|
273
|
+
- `isDate`
|
|
274
|
+
- `monthKey`
|
|
275
|
+
- `toISODate`
|
|
276
|
+
- `compareDates`
|
|
277
|
+
- `addDays`
|
|
278
|
+
- `addMonths`
|
|
279
|
+
- `addYears`
|
|
280
|
+
- `shiftMonth`
|
|
281
|
+
- `startOfMonth`
|
|
282
|
+
- `endOfMonth`
|
|
283
|
+
- `dayOfWeek`
|
|
284
|
+
- `startOfWeek`
|
|
285
|
+
- `endOfWeek`
|
|
286
|
+
- `getMonthWeeks`
|
|
287
|
+
- `clampDate`
|
|
288
|
+
- `todayISO`
|
|
289
|
+
- `isoWeekNumber`
|