@lilydesignsystem/angular-date-time-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/README.md ADDED
@@ -0,0 +1,281 @@
1
+ # Lily Design System™ — Angular DateTimePicker
2
+
3
+ A headless Angular 20 control for collecting a **date**, a **time**, or
4
+ **both**. A text field you can type into, plus an icon button that opens a
5
+ WAI-ARIA APG date-picker dialog with a full keyboard contract.
6
+
7
+ Ships zero CSS, zero icons, and zero hardcoded strings. Locale-correct by
8
+ construction: month names, weekday names, first day of week, numeric field
9
+ order, 12- vs 24-hour clock and AM/PM names all come from `Intl`.
10
+
11
+ Canonical contract: [spec/index.md](./spec/index.md). This package is the
12
+ Angular port of the Svelte-canonical
13
+ [`@lilydesignsystem/svelte-date-time-picker`](../../lily-design-system-svelte-helpers/lily-design-system-svelte-date-time-picker/).
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ npm install @lilydesignsystem/angular-date-time-picker
19
+ ```
20
+
21
+ ## Use it
22
+
23
+ ```ts
24
+ import { Component } from "@angular/core";
25
+ import { DateTimePicker } from "@lilydesignsystem/angular-date-time-picker";
26
+
27
+ @Component({
28
+ selector: "app-booking",
29
+ standalone: true,
30
+ imports: [DateTimePicker],
31
+ template: `
32
+ <label for="appointment">Appointment date</label>
33
+ <lily-date-time-picker
34
+ inputId="appointment"
35
+ name="appointment"
36
+ label="Choose an appointment date"
37
+ locale="en-GB"
38
+ [(value)]="appointment"
39
+ [labels]="labels"
40
+ />
41
+ `,
42
+ })
43
+ export class BookingComponent {
44
+ appointment = "";
45
+
46
+ readonly labels = {
47
+ previousYear: "Previous year",
48
+ previousMonth: "Previous month",
49
+ previousWeek: "Previous week",
50
+ previousDay: "Previous day",
51
+ nextDay: "Next day",
52
+ nextWeek: "Next week",
53
+ nextMonth: "Next month",
54
+ nextYear: "Next year",
55
+ confirm: "OK",
56
+ cancel: "Cancel",
57
+ };
58
+ }
59
+ ```
60
+
61
+ `appointment` is now an ISO string: `"2026-03-15"`.
62
+
63
+ ## The value is always ISO
64
+
65
+ | `mode` | `value` |
66
+ | ------ | ------- |
67
+ | `"date"` (default) | `"2026-03-15"` |
68
+ | `"time"` | `"09:30"` |
69
+ | `"datetime"` | `"2026-03-15T09:30"` |
70
+
71
+ Sortable as a string, unambiguous in every locale, and identical to what
72
+ `<input type="date">` posts — so you can swap the native control in or out
73
+ without touching your backend.
74
+
75
+ **No time zone is attached.** A date here is a civil date and a time is a
76
+ wall-clock time. If you need an instant, combine the value with a zone in
77
+ your own code, deliberately.
78
+
79
+ ## Everything it does
80
+
81
+ ### Constrain what can be picked
82
+
83
+ ```html
84
+ <lily-date-time-picker
85
+ [label]="label"
86
+ [labels]="labels"
87
+ min="2026-03-01"
88
+ max="2026-09-30"
89
+ [isDateDisabled]="isClosed"
90
+ />
91
+ ```
92
+
93
+ ```ts
94
+ isClosed = (iso: string) => weekdayOf(iso) === 0 || weekdayOf(iso) === 6;
95
+ ```
96
+
97
+ `min` / `max` are inclusive. `isDateDisabled` vetoes anything else — closed
98
+ days, fully-booked slots, bank holidays. Blocked days render
99
+ `aria-disabled="true"` (plus `data-disabled` for your CSS) rather than the
100
+ `disabled` attribute, so they stay focusable: the keyboard cursor can
101
+ cross them — a screen reader announces each as unavailable instead of
102
+ going silent — but they refuse selection.
103
+
104
+ ### Quick picks
105
+
106
+ ```html
107
+ <lily-date-time-picker
108
+ [label]="label"
109
+ [labels]="labels"
110
+ [shortcuts]="shortcuts"
111
+ (shortcut)="onShortcut($event)"
112
+ />
113
+ ```
114
+
115
+ ```ts
116
+ readonly shortcuts = [
117
+ { id: "today", label: "Today", days: 0 },
118
+ { id: "week", label: "In 1 week", days: 7 },
119
+ { id: "month", label: "In 1 month", months: 1 },
120
+ { id: "review", label: "Review date", date: "2026-09-01" },
121
+ ];
122
+ ```
123
+
124
+ `months` uses calendar months, not 30 days — "+1 month" from 31 January is
125
+ 28 February, not 2 March. A shortcut that resolves to a blocked date does
126
+ nothing rather than landing near it.
127
+
128
+ ### Time, and date-and-time
129
+
130
+ ```html
131
+ <lily-date-time-picker
132
+ mode="datetime"
133
+ [minuteStep]="15"
134
+ [label]="label"
135
+ [labels]="{ ...labels, hour: 'Hour', minute: 'Minute' }"
136
+ />
137
+ ```
138
+
139
+ In `"datetime"` mode a day click sets the pending date only — the user
140
+ still has a time to choose — so the dialog waits for Confirm. An
141
+ incomplete datetime is never committed.
142
+
143
+ ### Week numbers
144
+
145
+ ```html
146
+ <lily-date-time-picker
147
+ [label]="label"
148
+ [labels]="{ ...labels, week: 'Wk' }"
149
+ [showWeekNumbers]="true"
150
+ />
151
+ ```
152
+
153
+ Real ISO-8601 week numbers, with the Thursday rule — so the week
154
+ containing 1 January 2021 is week 53, not week 1.
155
+
156
+ ### Typing
157
+
158
+ The field accepts, in this order: ISO `2026-03-15`; a numeric form in the
159
+ locale's own field order (`03/04/2026` is 3 April in `en-GB`, 4 March in
160
+ `en-US`); and a written month (`27-Jun-2025`, `27 June 2025`, `Sept 5
161
+ 2025`) matched against the locale's month names.
162
+
163
+ Text that will not parse — or that parses outside `min`/`max` — stays in
164
+ the field, sets `aria-invalid="true"`, and fires `(invalidInput)`. It is
165
+ never silently snapped to a nearby legal date. Supply `[parseInput]` to
166
+ plug in your own parser. `Escape` discards a pending edit and shows the
167
+ committed value again.
168
+
169
+ ### Announce refusals, and explain the keyboard
170
+
171
+ Two optional labels make the control markedly better with a screen
172
+ reader; supply both:
173
+
174
+ ```html
175
+ <lily-date-time-picker
176
+ [label]="label"
177
+ [labels]="{
178
+ ...labels,
179
+ invalid: 'Enter a date like 21 3 2026',
180
+ instructions: 'Use the arrow keys to choose a date, Enter to select',
181
+ }"
182
+ />
183
+ ```
184
+
185
+ `invalid` renders a `role="status"` live region (class hook
186
+ `date-time-picker-status`) that announces when typed text is refused, and
187
+ is wired to the field via `aria-errormessage` and `aria-describedby`.
188
+ Without it, `aria-invalid` flips silently. `instructions` renders keyboard
189
+ help inside the dialog (class hook `date-time-picker-instructions`) that
190
+ the dialog references via `aria-describedby`, so a screen reader speaks it
191
+ once on open — hide it visually with your own CSS if you prefer.
192
+
193
+ ### Replace the glyph
194
+
195
+ ```html
196
+ <lily-date-time-picker [label]="label" [labels]="labels">
197
+ <ng-template lilyDateTimePickerIcon let-args>
198
+ <my-calendar-icon [expanded]="args.open" />
199
+ <span class="visually-hidden">{{ args.display }}</span>
200
+ </ng-template>
201
+ </lily-date-time-picker>
202
+ ```
203
+
204
+ The projected template replaces the glyph inside the button, not the
205
+ dialog.
206
+
207
+ ## Keyboard
208
+
209
+ **Field**: `Enter` resolves typed text. `Alt` + `↓` opens the dialog — the
210
+ same shortcut the native `<input type="date">` uses. `Escape` discards a
211
+ pending edit.
212
+
213
+ **Grid**: `←` `→` move a day; `↑` `↓` move a week; `Home` / `End` jump to
214
+ the ends of the week (respecting the locale's first weekday); `Page Up` /
215
+ `Page Down` page the month; add `Shift` to page the year; `Enter` / `Space`
216
+ select.
217
+
218
+ **Anywhere in the dialog**: `Escape` closes without committing; `Tab` and
219
+ `Shift+Tab` cycle inside the dialog. Closing returns focus to whichever
220
+ element opened the dialog — the button, or the field after `Alt` + `↓`.
221
+
222
+ ## You must supply the CSS
223
+
224
+ The package is headless. Nothing positions the dialog for you — without
225
+ your CSS it renders in normal flow rather than as an overlay:
226
+
227
+ ```css
228
+ .date-time-picker {
229
+ position: relative;
230
+ }
231
+ .date-time-picker-dialog {
232
+ position: absolute;
233
+ z-index: 10;
234
+ inset-inline-start: 0;
235
+ }
236
+ .date-time-picker-dialog[hidden] {
237
+ display: none;
238
+ }
239
+ ```
240
+
241
+ Class hooks: `date-time-picker`, `-field`, `-input`, `-button`, `-icon`,
242
+ `-status`, `-dialog`, `-instructions`, `-header`, `-previous-year`,
243
+ `-previous-month`, `-previous-week`, `-previous-day`, `-period`,
244
+ `-next-day`, `-next-week`, `-next-month`, `-next-year`, `-time-zone`,
245
+ `-time-zone-label`, `-time-zone-select`, `-calendar`,
246
+ `-weekday`, `-week-heading`, `-week`, `-day`, `-time`, `-time-label`,
247
+ `-hour`, `-minute`, `-meridiem`, `-shortcuts`, `-shortcut`, `-footer`,
248
+ `-clear`, `-cancel`, `-confirm`.
249
+
250
+ Day cells carry `data-today`, `data-outside`, `data-selected`,
251
+ `data-disabled`, and the root carries `data-mode`, so variants need no
252
+ extra classes.
253
+
254
+ ## Should you use this at all?
255
+
256
+ Often, no. `<input type="date">` is smaller, better supported by assistive
257
+ technology, and gets the platform's own picker for free. Reach for this one
258
+ when you need what the native control cannot do: `isDateDisabled`,
259
+ shortcuts, week numbers, a consistent look across browsers, or a locale
260
+ that differs from the user's OS setting.
261
+
262
+ For a date the user knows by heart — a date of birth — use three separate
263
+ number fields instead. That is the NHS and GOV.UK guidance and it is right:
264
+ nobody wants to page a calendar back forty years.
265
+
266
+ ## Related
267
+
268
+ - [`@lilydesignsystem/angular-theme-picker`](../lily-design-system-angular-theme-picker/)
269
+ - [`@lilydesignsystem/angular-locale-picker`](../lily-design-system-angular-locale-picker/)
270
+ - [`@lilydesignsystem/angular-text-size-picker`](../lily-design-system-angular-text-size-picker/)
271
+ - [`@lilydesignsystem/angular-share-picker`](../lily-design-system-angular-share-picker/)
272
+ - [`@lilydesignsystem/svelte-date-time-picker`](../../lily-design-system-svelte-helpers/lily-design-system-svelte-date-time-picker/) — the canonical Svelte original.
273
+
274
+ ## License
275
+
276
+ MIT or Apache-2.0 or GPL-2.0 or GPL-3.0 or BSD-3-Clause. Contact
277
+ joel@joelparkerhenderson.com for other terms.
278
+
279
+ ---
280
+
281
+ Lily™ and Lily Design System™ are trademarks.