@bitforgehq/ui 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +482 -0
  2. package/README.md +17 -11
  3. package/ai/MANIFEST.index.json +288 -0
  4. package/ai/MANIFEST.json +8716 -3926
  5. package/ai/REFERENCE.index.json +413 -0
  6. package/ai/REFERENCE.json +4710 -2047
  7. package/ai/UI_GUIDE.md +185 -57
  8. package/ai/components/back-button.md +42 -2
  9. package/ai/components/badge.md +17 -7
  10. package/ai/components/bar-chart.md +188 -0
  11. package/ai/components/breadcrumb-item.md +121 -0
  12. package/ai/components/breadcrumb.md +277 -0
  13. package/ai/components/button.md +82 -15
  14. package/ai/components/calendar.md +300 -0
  15. package/ai/components/card.md +63 -2
  16. package/ai/components/chart-card.md +283 -0
  17. package/ai/components/combobox.md +411 -0
  18. package/ai/components/date-input.md +396 -0
  19. package/ai/components/datetime-input.md +215 -0
  20. package/ai/components/description-item.md +111 -0
  21. package/ai/components/description-list.md +160 -0
  22. package/ai/components/donut-chart.md +169 -0
  23. package/ai/components/header.md +67 -2
  24. package/ai/components/icon-button.md +74 -16
  25. package/ai/components/icon.md +63 -7
  26. package/ai/components/line-chart.md +234 -0
  27. package/ai/components/list.md +70 -6
  28. package/ai/components/menu.md +5 -0
  29. package/ai/components/multi-combobox.md +257 -0
  30. package/ai/components/nav-group.md +178 -0
  31. package/ai/components/nav-item.md +210 -0
  32. package/ai/components/page-header.md +237 -0
  33. package/ai/components/pagination.md +173 -0
  34. package/ai/components/popover.md +250 -0
  35. package/ai/components/side-panel.md +391 -0
  36. package/ai/components/sidebar.md +363 -0
  37. package/ai/components/skeleton.md +215 -0
  38. package/ai/components/sparkline.md +188 -0
  39. package/ai/components/stat.md +233 -0
  40. package/ai/components/tab-group.md +226 -0
  41. package/ai/components/tab-panel.md +163 -0
  42. package/ai/components/table.md +568 -0
  43. package/ai/components/tabs.md +42 -2
  44. package/ai/components/theme-menu-button.md +182 -0
  45. package/ai/components/theme-switch.md +170 -0
  46. package/ai/components/time-input.md +240 -0
  47. package/ai/components/toolbar.md +289 -0
  48. package/ai/components/tooltip.md +230 -0
  49. package/ai/components/top-nav.md +159 -0
  50. package/ai/reference/bf-badge-appearance.md +36 -0
  51. package/ai/reference/bf-badge-variant.md +1 -1
  52. package/ai/reference/bf-bar-chart-orientation.md +27 -0
  53. package/ai/reference/bf-button-appearance.md +36 -0
  54. package/ai/reference/bf-button-variant.md +1 -1
  55. package/ai/reference/bf-calendar-disabled-date.md +23 -0
  56. package/ai/reference/bf-calendar-harness.md +51 -0
  57. package/ai/reference/bf-calendar-weekday.md +31 -0
  58. package/ai/reference/bf-chart-card-heading-level.md +26 -0
  59. package/ai/reference/bf-chart-curve.md +28 -0
  60. package/ai/reference/bf-chart-harness.md +51 -0
  61. package/ai/reference/bf-chart-legend-position.md +30 -0
  62. package/ai/reference/bf-chart-series.md +23 -0
  63. package/ai/reference/bf-chart-value-format.md +23 -0
  64. package/ai/reference/bf-color-scheme-service.md +30 -0
  65. package/ai/reference/bf-color-scheme.md +31 -0
  66. package/ai/reference/bf-combobox-filter.md +27 -0
  67. package/ai/reference/bf-combobox-harness.md +89 -0
  68. package/ai/reference/bf-combobox-option.md +33 -0
  69. package/ai/reference/bf-compare-with.md +2 -0
  70. package/ai/reference/bf-date-input-harness.md +67 -0
  71. package/ai/reference/bf-date-input-picker.md +27 -0
  72. package/ai/reference/bf-date-time-input-harness.md +76 -0
  73. package/ai/reference/bf-date-time-input-picker.md +27 -0
  74. package/ai/reference/bf-description-list-layout.md +26 -0
  75. package/ai/reference/bf-donut-slice.md +22 -0
  76. package/ai/reference/bf-labels.md +67 -20
  77. package/ai/reference/bf-menu-button-variant.md +1 -0
  78. package/ai/reference/bf-menu-placement.md +1 -0
  79. package/ai/reference/bf-overlay-options.md +28 -27
  80. package/ai/reference/bf-overlay-placement.md +34 -0
  81. package/ai/reference/bf-overlay-position.md +1 -1
  82. package/ai/reference/bf-overlay-service.md +5 -3
  83. package/ai/reference/bf-page-header-heading-level.md +26 -0
  84. package/ai/reference/bf-pagination-harness.md +61 -0
  85. package/ai/reference/bf-platform-service.md +17 -15
  86. package/ai/reference/bf-popover-harness.md +42 -0
  87. package/ai/reference/bf-popover-placement.md +28 -0
  88. package/ai/reference/bf-popover-trigger-directive.md +56 -0
  89. package/ai/reference/bf-side-panel-config.md +35 -0
  90. package/ai/reference/bf-side-panel-harness.md +34 -0
  91. package/ai/reference/bf-side-panel-ref.md +44 -0
  92. package/ai/reference/bf-side-panel-service.md +43 -0
  93. package/ai/reference/bf-side-panel-template-context.md +32 -0
  94. package/ai/reference/bf-side-panel-width.md +28 -0
  95. package/ai/reference/bf-size.md +12 -0
  96. package/ai/reference/bf-skeleton-shape.md +28 -0
  97. package/ai/reference/bf-stat-trend-intent.md +27 -0
  98. package/ai/reference/bf-stat-trend.md +27 -0
  99. package/ai/reference/bf-stat-variant.md +26 -0
  100. package/ai/reference/bf-tab-group-harness.md +44 -0
  101. package/ai/reference/bf-table-align.md +26 -0
  102. package/ai/reference/bf-table-cell-context.md +24 -0
  103. package/ai/reference/bf-table-cell-directive.md +22 -0
  104. package/ai/reference/bf-table-column.md +28 -0
  105. package/ai/reference/bf-table-harness.md +77 -0
  106. package/ai/reference/bf-table-row-key.md +21 -0
  107. package/ai/reference/bf-table-sort-direction.md +26 -0
  108. package/ai/reference/bf-table-sort.md +27 -0
  109. package/ai/reference/bf-theme.md +5 -4
  110. package/ai/reference/bf-time-input-harness.md +67 -0
  111. package/ai/reference/bf-time-input-hour-cycle.md +27 -0
  112. package/ai/reference/bf-time-input-picker.md +27 -0
  113. package/ai/reference/bf-tooltip-directive.md +43 -0
  114. package/ai/reference/bf-tooltip-harness.md +43 -0
  115. package/ai/reference/bf-tooltip-placement.md +28 -0
  116. package/ai/reference/bf_color_scheme_storage.md +20 -0
  117. package/ai/reference/bf_side_panel_data.md +17 -0
  118. package/ai/reference/bf_theme_storage.md +3 -1
  119. package/ai/reference/provide-bf-color-scheme.md +20 -0
  120. package/ai/reference/provide-bf-labels.md +12 -4
  121. package/ai/reference/slide-in-from-end.md +24 -0
  122. package/ai/reference/slide-out-to-end.md +24 -0
  123. package/fesm2022/bitforgehq-ui-charts.mjs +3045 -0
  124. package/fesm2022/bitforgehq-ui-charts.mjs.map +1 -0
  125. package/fesm2022/bitforgehq-ui-icons.mjs +107 -3
  126. package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -1
  127. package/fesm2022/bitforgehq-ui-mobile.mjs +14 -59
  128. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
  129. package/fesm2022/bitforgehq-ui-overlay.mjs +6436 -33
  130. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  131. package/fesm2022/bitforgehq-ui-testing.mjs +618 -1
  132. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  133. package/fesm2022/bitforgehq-ui.mjs +5092 -153
  134. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  135. package/llms-full.txt +25177 -14398
  136. package/llms.txt +95 -1
  137. package/package.json +7 -3
  138. package/theme.css +101 -0
  139. package/types/bitforgehq-ui-charts.d.ts +967 -0
  140. package/types/bitforgehq-ui-icons.d.ts +13 -1
  141. package/types/bitforgehq-ui-mobile.d.ts +25 -18
  142. package/types/bitforgehq-ui-overlay.d.ts +1844 -7
  143. package/types/bitforgehq-ui-testing.d.ts +748 -1
  144. package/types/bitforgehq-ui.d.ts +2136 -44
@@ -0,0 +1,396 @@
1
+ # bf-date-input
2
+
3
+ Typed date field with a calendar popover, valued as an ISO date string
4
+
5
+ `BfDateInputComponent` from `@bitforgehq/ui/overlay` · Forms · since 0.3.1
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, computed, signal } from '@angular/core';
11
+ import { BfDateInputComponent } from '@bitforgehq/ui/overlay';
12
+
13
+ /**
14
+ * The bf-date-input overview.
15
+ *
16
+ * Live demo of `bf-date-input`: a due date typed part by part (day, month and year in the
17
+ * British order, each moving on by itself) or picked in the calendar popover. The value is a
18
+ * plain ISO date string; the status line shows it.
19
+ */
20
+ @Component({
21
+ selector: 'app-example-date-input-overview',
22
+ imports: [BfDateInputComponent],
23
+ template: `
24
+ <div class="flex max-w-xs flex-col gap-3">
25
+ <bf-date-input
26
+ label="Due date"
27
+ locale="en-GB"
28
+ helpText="Type it, or pick it in the calendar."
29
+ [(value)]="due"
30
+ />
31
+ <p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
32
+ </div>
33
+ `,
34
+ })
35
+ export class DateInputOverviewExampleComponent {
36
+ /** The due date, 'YYYY-MM-DD'; null while empty or incomplete. */
37
+ readonly due = signal<string | null>('2026-10-14');
38
+
39
+ readonly status = computed(() => `Due: ${this.due() ?? 'none'}`);
40
+ }
41
+ ```
42
+
43
+ ## Overview
44
+
45
+ Date field typed part by part (day, month and year in the locale's order) with a calendar
46
+ popover, valued as an ISO date string and bound like any other field.
47
+
48
+ A date field typed part by part: the day, the month and the year sit in the order the locale writes them (14/10/2026 in British English, 10/14/2026 in American English, 14.10.2026 in German), each a spinbutton that takes digits and moves on by itself. A calendar button opens a `bf-calendar` in a popover anchored to the field. The value is a plain ISO date string (`'2026-10-14'`), never a `Date`, so no time zone can shift the day, and the field binds to Signal Forms like `bf-input`.
49
+
50
+ Import `BfDateInputComponent` from `@bitforgehq/ui/overlay` (it needs `@angular/cdk` for the popover). For a time of day use [bf-time-input](https://bitforge-ui.web.app/components/time-input), for both in one field [bf-datetime-input](https://bitforge-ui.web.app/components/datetime-input).
51
+
52
+ ## When to use
53
+
54
+ Use `bf-date-input` for a date in a form: a due date, a delivery day, a date of birth. Use [bf-calendar](https://bitforge-ui.web.app/components/calendar) instead when the month should stay in view on the page (a booking page, a dashboard filter), and [bf-select](https://bitforge-ui.web.app/components/select) or [bf-segmented](https://bitforge-ui.web.app/components/segmented) when the choice is a few fixed dates (today, tomorrow, next week). It works in both shells; in a Capacitor app set `native`, so a phone shows its own date picker (see Choosing a picker).
55
+
56
+ ## Choosing a picker
57
+
58
+ | Style | How | Best for |
59
+ | ------------------------ | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
60
+ | Inline calendar | [bf-calendar](https://bitforge-ui.web.app/components/calendar) on the page | A date the page is about, with the month always in view |
61
+ | Field + calendar popover | `bf-date-input` (the default) | A date in a form: type it when you know it, browse the month when you do not |
62
+ | Typed only | `picker="none"` | Dates people know by heart (a date of birth) or far from today, where a calendar is a detour |
63
+ | Native on touch | `native` | Capacitor apps and phone browsers: the platform's date wheel or sheet under a coarse pointer |
64
+
65
+ `native` renders an `input type="date"` with the same label, help and error text when the primary pointer is coarse (`matchMedia('(pointer: coarse)')`), and the segmented field under a mouse. Its `min` and `max` go to the native input, `disabledDate` and the calendar do not (the platform picker has no such rule; the error still shows). Recommended in the Capacitor shell, where typing into the parts needs a hardware keyboard.
66
+
67
+ ## Typing a date
68
+
69
+ Each part takes digits and moves on once it has all of them or no further digit could fit: "4" in the month moves on at once, "1" waits for a possible "0" to "2". A separator key (`/`, `.`, `-`, a space) moves on early. A year of one or two digits becomes one of the 2000s when you leave it ("26" is 2026). <kbd>ArrowUp</kbd> and <kbd>ArrowDown</kbd> step a part and wrap around (an empty year starts at this year), <kbd>Backspace</kbd> clears it (and on an empty part moves back), <kbd>ArrowLeft</kbd> and <kbd>ArrowRight</kbd> move between the parts. An empty part shows a muted placeholder: the first letter of its name once per digit (dd/mm/yyyy), so translating the part names translates the placeholder.
70
+
71
+ `value` is `null` while the date is empty or incomplete, and `(changed)` emits the new value (also `null` when a part is cleared) right after a user change, never for a value you set. A part takes its value once it is complete or when you leave it, so typing "14" never passes through the 1st.
72
+
73
+ ```ts
74
+ import { Component, signal } from '@angular/core';
75
+ import { BfDateInputComponent } from '@bitforgehq/ui/overlay';
76
+
77
+ /**
78
+ * Typed only.
79
+ *
80
+ * `picker="none"` drops the calendar button: a date of birth is quicker to type than to find
81
+ * decades back in a calendar.
82
+ */
83
+ @Component({
84
+ selector: 'app-example-date-input-typed-only',
85
+ imports: [BfDateInputComponent],
86
+ template: `
87
+ <bf-date-input
88
+ class="max-w-xs"
89
+ label="Date of birth"
90
+ locale="en-GB"
91
+ picker="none"
92
+ max="2026-10-28"
93
+ [(value)]="birthday"
94
+ />
95
+ `,
96
+ })
97
+ export class DateInputTypedOnlyExampleComponent {
98
+ readonly birthday = signal<string | null>(null);
99
+ }
100
+ ```
101
+
102
+ ## Calendar popover
103
+
104
+ The calendar button (a CSS mark, no icon import) opens a `bf-calendar` in a popover below the field, flipping above it when there is no room; focus moves to the selected day (else today). Picking a day writes the date, closes the popover and returns focus to the button. <kbd>Escape</kbd>, a click outside, <kbd>Tab</kbd> past the calendar or a navigation close it; <kbd>Alt</kbd>+<kbd>ArrowDown</kbd> on a part opens it from the keyboard. `openPicker()` and `closePicker()` do the same from code.
105
+
106
+ ## Min, max and disabled dates
107
+
108
+ `min` and `max` (ISO dates) bound the calendar, and a typed date outside them shows an error (`validation.minDate` / `maxDate` of the labels) once focus leaves the field. `disabledDate` rules out more dates: they show disabled in the calendar, and a typed one shows `dateInputUnavailable`. An incomplete or impossible date (31/02) shows `dateInputInvalid`. These errors are the field's own; add the same rule to your Signal Forms schema when the form must not submit with them.
109
+
110
+ ```ts
111
+ import { Component, signal } from '@angular/core';
112
+ import { type BfCalendarDisabledDate } from '@bitforgehq/ui';
113
+ import { BfDateInputComponent } from '@bitforgehq/ui/overlay';
114
+
115
+ /**
116
+ * Min, max and disabled dates.
117
+ *
118
+ * A delivery date in November 2026 on working days: `min` and `max` bound the calendar and show
119
+ * an error for a typed date outside them, and `disabledDate` rules out weekends in the calendar
120
+ * and for typed dates.
121
+ */
122
+ @Component({
123
+ selector: 'app-example-date-input-min-max',
124
+ imports: [BfDateInputComponent],
125
+ template: `
126
+ <bf-date-input
127
+ class="max-w-xs"
128
+ label="Delivery"
129
+ locale="en-GB"
130
+ helpText="A working day in November 2026."
131
+ min="2026-11-02"
132
+ max="2026-11-30"
133
+ [disabledDate]="weekends"
134
+ [(value)]="delivery"
135
+ />
136
+ `,
137
+ })
138
+ export class DateInputMinMaxExampleComponent {
139
+ readonly delivery = signal<string | null>('2026-11-16');
140
+
141
+ /** Saturdays and Sundays; an ISO date parses as UTC midnight, so getUTCDay is its weekday. */
142
+ readonly weekends: BfCalendarDisabledDate = (iso) => [0, 6].includes(new Date(iso).getUTCDay());
143
+ }
144
+ ```
145
+
146
+ ## Locale
147
+
148
+ `locale` (a BCP 47 tag) sets the order of the parts and their separators (from `Intl.DateTimeFormat`'s `formatToParts`) and the calendar's names and week. Empty, it follows the document's `lang`, else English. The part names, the button's name and the error texts are labels: translate them with `provideBfLabels()`, and put a `lang` around the field so a screen reader reads them in that language.
149
+
150
+ ```ts
151
+ import { Component, signal } from '@angular/core';
152
+ import { provideBfLabels } from '@bitforgehq/ui';
153
+ import { BfDateInputComponent } from '@bitforgehq/ui/overlay';
154
+
155
+ /**
156
+ * A German date input.
157
+ *
158
+ * `locale="de"` puts the day first with dots between the parts and gives the calendar German
159
+ * names; the part names come from `provideBfLabels()`, and their first letters make the
160
+ * placeholder (tt.mm.jjjj).
161
+ */
162
+ @Component({
163
+ selector: 'app-example-date-input-locale',
164
+ imports: [BfDateInputComponent],
165
+ providers: [
166
+ provideBfLabels({
167
+ dateInputDay: 'Tag',
168
+ dateInputMonth: 'Monat',
169
+ dateInputYear: 'Jahr',
170
+ dateInputOpenCalendar: 'Datum wählen',
171
+ dateInputInvalid: 'Gib ein gültiges Datum ein',
172
+ calendarPreviousMonth: 'Vorheriger Monat',
173
+ calendarNextMonth: 'Nächster Monat',
174
+ calendarChooseMonth: 'Monat wählen',
175
+ calendarChooseYear: 'Jahr wählen',
176
+ }),
177
+ ],
178
+ template: `
179
+ <div lang="de" class="max-w-xs">
180
+ <bf-date-input label="Liefertermin" locale="de" [(value)]="delivery" />
181
+ </div>
182
+ `,
183
+ })
184
+ export class DateInputLocaleExampleComponent {
185
+ readonly delivery = signal<string | null>(null);
186
+ }
187
+ ```
188
+
189
+ ## With Signal Forms
190
+
191
+ Bind the field with `[formField]`. Required, touched, errors, disabled and readonly flow in; the first error shows under the field once focus has left it, and `errorMessage` shows at once. The field's own errors (incomplete, outside `min`/`max`, a disabled date) come before the form's; they do not block a submit, so put a rule the form must enforce in its schema too (here a `validate()` rule for Sundays). `focus()` focuses the first empty part, so `focusBoundControl()` lands where the user has to type.
192
+
193
+ Under `[formField]` the form owns `min` and `max`, as it owns `disabled` and `required`: Angular refuses a `min` attribute beside `[formField]`. Signal Forms' `minDate()` works on `Date` fields, so give an ISO string field its limit through a metadata key of your own: `const FIRST_DAY = createMetadataKey<string | undefined>()`, then `metadata(path.day, FIRST_DAY, () => '2026-11-02')` and `metadata(path.day, MIN, () => FIRST_DAY)` in the schema (`MAX` alike). The form then binds it to the field's `min`. Here a booking form, next to a `bf-time-input` limited to opening hours the same way. In specs, `BfDateInputHarness` from `@bitforgehq/ui/testing` types a date (`setValue('14/10/2026')`) or picks one in the calendar (`pickDate('2026-11-03')`).
194
+
195
+ ```ts
196
+ import { Component, signal } from '@angular/core';
197
+ import {
198
+ FormField,
199
+ FormRoot,
200
+ MAX,
201
+ MIN,
202
+ createMetadataKey,
203
+ form,
204
+ metadata,
205
+ required,
206
+ validate,
207
+ } from '@angular/forms/signals';
208
+ import { BfButtonComponent } from '@bitforgehq/ui';
209
+ import { BfDateInputComponent, BfTimeInputComponent } from '@bitforgehq/ui/overlay';
210
+
211
+ interface Visit {
212
+ day: string | null;
213
+ time: string | null;
214
+ }
215
+
216
+ /* Limits for ISO string fields: the form binds them to the field's `min` and `max`. */
217
+ const FIRST_DAY = createMetadataKey<string | undefined>();
218
+ const OPENS = createMetadataKey<string | undefined>();
219
+ const CLOSES = createMetadataKey<string | undefined>();
220
+
221
+ /**
222
+ * A booking form with Signal Forms.
223
+ *
224
+ * The day and the time are required, the day may not be a Sunday (a `validate()` rule with its
225
+ * own message), and the limits come from the schema: under `[formField]` the form binds `min` and
226
+ * `max`, here through `metadata()` keys of its own. Book shows the errors of an empty form.
227
+ */
228
+ @Component({
229
+ selector: 'app-example-date-input-signal-forms',
230
+ imports: [FormField, FormRoot, BfButtonComponent, BfDateInputComponent, BfTimeInputComponent],
231
+ template: `
232
+ <form [formRoot]="visit" class="flex max-w-xs flex-col items-start gap-3">
233
+ <bf-date-input label="Day" locale="en-GB" class="self-stretch" [formField]="visit.day" />
234
+ <bf-time-input label="Time" locale="en-GB" class="self-stretch" [formField]="visit.time" />
235
+ <bf-button type="submit">Book</bf-button>
236
+ <p data-status aria-live="polite" class="text-caption text-text-muted">{{ booked() }}</p>
237
+ </form>
238
+ `,
239
+ })
240
+ export class DateInputSignalFormsExampleComponent {
241
+ private readonly model = signal<Visit>({ day: null, time: null });
242
+ protected readonly booked = signal('Nothing booked yet.');
243
+ protected readonly visit = form(
244
+ this.model,
245
+ (path) => {
246
+ required(path.day);
247
+ required(path.time);
248
+ metadata(path.day, FIRST_DAY, () => '2026-11-02');
249
+ metadata(path.day, MIN, () => FIRST_DAY);
250
+ metadata(path.time, OPENS, () => '09:00');
251
+ metadata(path.time, MIN, () => OPENS);
252
+ metadata(path.time, CLOSES, () => '17:00');
253
+ metadata(path.time, MAX, () => CLOSES);
254
+ validate(path.day, ({ value }) => {
255
+ const day = value();
256
+ // An ISO date parses as UTC midnight, so getUTCDay is its weekday.
257
+ return day && new Date(day).getUTCDay() === 0
258
+ ? { kind: 'sunday', message: 'We are closed on Sundays' }
259
+ : undefined;
260
+ });
261
+ },
262
+ {
263
+ submission: {
264
+ action: () => {
265
+ const { day, time } = this.model();
266
+ this.booked.set(`Booked ${day ?? ''} at ${time ?? ''}.`);
267
+ return Promise.resolve(undefined);
268
+ },
269
+ },
270
+ },
271
+ );
272
+ }
273
+ ```
274
+
275
+ ## Sizes and states
276
+
277
+ `size` sets the field height like `bf-input`: `sm` 32 px, `md` 40 px (default), `lg` 48 px; unset it follows `provideBfDefaults()`'s `fieldSize`. The calendar button sits 3 px inside the border at every step and grows to a 44 px hit area under a coarse pointer. `disabled` greys the field and takes it out of the tab order; `readonly` shows the date, keeps the parts focusable and disables the calendar button.
278
+
279
+ ## Desktop
280
+
281
+ The field box shows the `primary` border and focus ring while any part has focus; the focused part is tinted `primary-soft`. Hovering the box darkens its border, hovering the calendar button tints it `surface-muted`. A click on the label or the box focuses the first empty part. Under a fine pointer each part is at least 24 × 24 px. Desktop density is `md`; `provideBfDefaults({ fieldSize: 'sm' })` gives 32 px fields for a filter bar ([density guide](https://bitforge-ui.web.app/density)).
282
+
283
+ ## Theming
284
+
285
+ - Field: `surface`, a 1 px `border-strong` border (hover `text-muted`, focus `primary` with the `ring` focus ring), `rounded-field`; disabled `surface-muted` with `surface-sunken` and `text-disabled`; read-only `surface-muted`; error `danger-border`.
286
+ - Parts: `text`, placeholders and separators `text-muted`; the focused part `primary-soft` with `primary-text`, `rounded-checkbox`.
287
+ - Calendar button: `text-muted`, hover `surface-muted` with `text`, pressed `surface-pressed`, `rounded-control`.
288
+ - Popover: `surface`, `border`, `rounded-card`, `shadow-floating`; the calendar inside uses its own tokens.
289
+
290
+ ## Accessibility
291
+
292
+ The parts are a `role="group"` named by `label` (else `ariaLabel` or `ariaLabelledby`) and described by the help and error text. Each part is a `role="spinbutton"` named from the `dateInputDay`, `dateInputMonth` and `dateInputYear` labels, with `aria-valuemin`, `aria-valuemax`, `aria-valuenow` (absent while empty) and `aria-valuetext` (the month's name); `aria-invalid`, `aria-errormessage`, `aria-required`, `aria-readonly` and `aria-disabled` reflect the field. The separators are `aria-hidden`. The calendar button is named by `dateInputOpenCalendar` and carries `aria-haspopup="dialog"`, `aria-expanded` and, while open, `aria-controls`; the popover is a non-modal `dialog` named by the button, holding a `bf-calendar` with its own grid semantics. Focus moves into the calendar on open and back to the button on a pick or <kbd>Escape</kbd>. In native mode the platform input carries the label, `aria-invalid` and `aria-describedby`.
293
+
294
+ ### Keyboard
295
+
296
+ | Key | Where | Action |
297
+ | ------------------------------------------------ | -------- | ----------------------------------------------------------------- |
298
+ | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Field | Moves through the parts, then to the calendar button |
299
+ | <kbd>0</kbd>–<kbd>9</kbd> | Part | Types into the part; moves on once no further digit fits |
300
+ | <kbd>ArrowUp</kbd> / <kbd>ArrowDown</kbd> | Part | Next / previous value, wrapping (an empty year starts at today) |
301
+ | <kbd>ArrowRight</kbd> / <kbd>ArrowLeft</kbd> | Part | Next / previous part (swapped under `dir="rtl"`) |
302
+ | <kbd>Backspace</kbd> / <kbd>Delete</kbd> | Part | Clears the part; <kbd>Backspace</kbd> on an empty part moves back |
303
+ | <kbd>/</kbd>, <kbd>.</kbd>, <kbd>-</kbd> | Part | Moves on to the next part |
304
+ | <kbd>Alt</kbd>+<kbd>ArrowDown</kbd> | Part | Opens the calendar |
305
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | Button | Opens the calendar (closes it when open) |
306
+ | <kbd>Escape</kbd> | Calendar | Closes the popover, focus back on the button |
307
+ | <kbd>Tab</kbd> | Calendar | Past the last control closes the popover and moves on |
308
+
309
+ The calendar's own keys (arrows, Page Up/Down, Home, End, Enter) work as on [bf-calendar](https://bitforge-ui.web.app/components/calendar).
310
+
311
+ ## Inputs
312
+
313
+ | Name | Type | Default | Description |
314
+ | ---------------- | ----------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
315
+ | `min` | `string \| undefined` | `undefined` | The earliest valid date (ISO): earlier dates show an error, and the calendar stops there. Set by `[formField]`; do not bind it yourself. |
316
+ | `max` | `string \| undefined` | `undefined` | The latest valid date (ISO): later dates show an error, and the calendar stops there. Set by `[formField]`; do not bind it yourself. |
317
+ | `disabledDate` | `BfCalendarDisabledDate \| null` | `null` | Rules out more dates (weekends, booked days): disabled in the calendar, an error when typed. |
318
+ | `picker` | `BfDateInputPicker` (`'calendar' \| 'none'`) | `'calendar'` | `calendar` adds the button that opens a calendar popover; `none` keeps the field typed only. |
319
+ | `native` | `boolean` | `false` | Renders the platform's date picker (`input type="date"`) under a coarse pointer (touch). |
320
+ | `locale` | `string` | `''` | BCP 47 locale of the part order, the separators and the calendar; empty follows the document's `lang`, else English. |
321
+ | `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by default). |
322
+ | `label` | `string` | `''` | Visible label above the field. |
323
+ | `ariaLabel` | `string` | `''` | Accessible name when no visible label is rendered. Ignored when `label` is set. |
324
+ | `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when `label` is set. |
325
+ | `helpText` | `string` | `''` | Helper text under the field. |
326
+ | `errorMessage` | `string` | `''` | Explicit error text; takes precedence over the field's own errors and Signal Forms errors. |
327
+ | `disabled` | `boolean` | `false` | Disables the field and its calendar button. Set by `[formField]`; do not bind it yourself. |
328
+ | `readonly` | `boolean` | `false` | Shows the date but blocks changes; the parts stay focusable and expose `aria-readonly`. Set by `[formField]`; do not bind it yourself. |
329
+ | `required` | `boolean` | `false` | Marks the field required. Set by `[formField]`; do not bind it yourself. |
330
+ | `invalid` | `boolean` | `false` | Field validity from Signal Forms. Set by `[formField]`; do not bind it yourself. |
331
+ | `touched` | `boolean` | `false` | Whether the user has left the field. Set by `[formField]`; do not bind it yourself. |
332
+ | `errors` | `readonly ValidationError.WithOptionalFieldTree[]` | `[]` | Validation errors from Signal Forms. Set by `[formField]`; do not bind it yourself. |
333
+ | `name` | `string` | `''` | Native name attribute of the platform input (`native` under a coarse pointer). Set by `[formField]`; do not bind it yourself. |
334
+ | `id` | `string` | `generated` | Field id; generated when not set. |
335
+ | `value` | `string \| null` | `null` | The date as an ISO string ('YYYY-MM-DD'), `null` while empty or incomplete; two-way bindable and bound by `[formField]`. Two-way: `[(value)]`. |
336
+
337
+ ## Outputs
338
+
339
+ | Name | Payload | Description |
340
+ | --------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
341
+ | `changed` | `string \| null` | Emitted with the new date right after a user change: `null` once it is cleared or incomplete. |
342
+ | `touch` | `void` | Emitted when focus leaves the field (not into its calendar), so Signal Forms marks it touched. Set by `[formField]`; do not bind it yourself. |
343
+
344
+ ## Methods
345
+
346
+ ### `focus(options?: FocusOptions): void`
347
+
348
+ Focuses the first empty part (else the first part); Signal Forms calls it from
349
+ `focusBoundControl()`.
350
+
351
+ | Parameter | Type | Description |
352
+ | ---------- | -------------- | --------------------------------------------------------------------- |
353
+ | `options?` | `FocusOptions` | Passed on to the native `focus()`, such as `{ preventScroll: true }`. |
354
+
355
+ ### `openPicker(): void`
356
+
357
+ Opens the calendar popover; does nothing without a picker, or while disabled or read-only.
358
+
359
+ ### `closePicker(): void`
360
+
361
+ Closes the calendar popover, focus back on its button; does nothing while it is closed.
362
+
363
+ ## Notes
364
+
365
+ ### Do
366
+
367
+ - Give the field a `label`; it names the group and the popover's calendar.
368
+ - Store and send the value as the ISO string it is; turn it into a `Date` only to display or compute, at UTC midnight.
369
+ - Set `native` in a Capacitor app, so phones get their own picker.
370
+ - Add `min`, `max` and a `disabledDate` rule to your form schema too when the form must not submit an out-of-range date.
371
+
372
+ ### Don't
373
+
374
+ - Don't parse what users type with `new Date()`; read the ISO value.
375
+ - Don't use it for a range of two dates as one field; use two date inputs.
376
+ - Don't drop the calendar for a date near today (an appointment); `picker="none"` suits dates people know by heart.
377
+
378
+ ## Migration notes
379
+
380
+ ### 0.3.1
381
+
382
+ New `bf-date-input` in `@bitforgehq/ui/overlay`: a date field typed part by part, the day, month
383
+ and year as spinbuttons in the locale's order and separators (`Intl.DateTimeFormat`
384
+ `formatToParts`), with muted placeholders (dd/mm/yyyy). Digits type in and move on by themselves,
385
+ Arrow Up/Down step with wrapping, Backspace clears, Arrow Left/Right move between the parts. The
386
+ value is an ISO date string (`'YYYY-MM-DD'`), `null` while empty or incomplete, and `(changed)`
387
+ emits each user change. `picker` (`BfDateInputPicker`: `'calendar'`, the default, or `'none'`)
388
+ adds a button that opens a `bf-calendar` in an anchored popover; a pick closes it and returns
389
+ focus. `min`, `max` and `disabledDate` bound the calendar and show the field's own error for a
390
+ typed date outside them (as for an incomplete or impossible one). `native` renders
391
+ `input type="date"` under a coarse pointer, for the Capacitor shell. `locale`, `size`
392
+ (`fieldSize`), `openPicker()`, `closePicker()`, `focus()`, and the Signal Forms value contract
393
+ like `bf-input`. New label keys `dateInputDay`, `dateInputMonth`, `dateInputYear`,
394
+ `dateInputOpenCalendar`, `dateInputInvalid` and `dateInputUnavailable`, and a
395
+ `BfDateInputHarness` in `@bitforgehq/ui/testing` (`getValue()`, `setValue(text)`, `clear()`,
396
+ `openPicker()`, `pickDate(iso)`).
@@ -0,0 +1,215 @@
1
+ # bf-datetime-input
2
+
3
+ Typed date and time field with a calendar and time list in one popover
4
+
5
+ `BfDateTimeInputComponent` from `@bitforgehq/ui/overlay` · Forms · since 0.3.1
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, computed, signal } from '@angular/core';
11
+ import { BfDateTimeInputComponent } from '@bitforgehq/ui/overlay';
12
+
13
+ /**
14
+ * The bf-datetime-input overview.
15
+ *
16
+ * Live demo of `bf-datetime-input`: a meeting's date and time typed in one field, or picked in
17
+ * the popover, which holds the calendar and a list of times every 15 minutes side by side. The
18
+ * value is a local ISO date and time without a zone; the status line shows it.
19
+ */
20
+ @Component({
21
+ selector: 'app-example-datetime-input-overview',
22
+ imports: [BfDateTimeInputComponent],
23
+ template: `
24
+ <div class="flex max-w-xs flex-col gap-3">
25
+ <bf-datetime-input label="Meeting" locale="en-GB" step="15" [(value)]="meeting" />
26
+ <p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
27
+ </div>
28
+ `,
29
+ })
30
+ export class DatetimeInputOverviewExampleComponent {
31
+ /** The meeting, 'YYYY-MM-DDTHH:mm'; null while empty or incomplete. */
32
+ readonly meeting = signal<string | null>('2026-10-14T09:30');
33
+
34
+ readonly status = computed(() => `Meeting: ${this.meeting() ?? 'none'}`);
35
+ }
36
+ ```
37
+
38
+ ## Overview
39
+
40
+ Date and time field typed part by part, with a popover holding a calendar and a list of
41
+ times, valued as a local ISO date and time without a zone.
42
+
43
+ A date and a time in one field: the date parts and the time parts in the locale's order ("14/10/2026, 09:30" in British English), typed like [bf-date-input](https://bitforge-ui.web.app/components/date-input) and [bf-time-input](https://bitforge-ui.web.app/components/time-input). Its button opens one popover holding the calendar and a list of times side by side. The value is a local ISO date and time without a zone (`'2026-10-14T09:30'`), the format of `input type="datetime-local"`.
44
+
45
+ Import `BfDateTimeInputComponent` from `@bitforgehq/ui/overlay` (it needs `@angular/cdk` for the popover).
46
+
47
+ ## When to use
48
+
49
+ Use `bf-datetime-input` for one moment a person picks as a date and a time: a meeting, a publish time, a reminder. Use [bf-date-input](https://bitforge-ui.web.app/components/date-input) and [bf-time-input](https://bitforge-ui.web.app/components/time-input) side by side instead when the two are edited apart (an all-day toggle, a time that may stay empty), and a [bf-calendar](https://bitforge-ui.web.app/components/calendar) on the page when the month should stay in view. It works in both shells; in a Capacitor app set `native`, so a phone shows its own picker.
50
+
51
+ ## Typing and picking
52
+
53
+ The parts work as in the date and the time inputs: digits move on by themselves, the arrows step, <kbd>Backspace</kbd> clears, and the clock follows the locale (`hourCycle` forces `h12` or `h23`). `value` is `null` until every part is set, and `(changed)` emits the new value right after a user change.
54
+
55
+ The button opens the popover with focus on the calendar's selected day. Picking a day writes the date and moves focus to the list of times beside it (every `step` minutes, 30 by default); picking a time writes it, closes the popover and returns focus to the button. <kbd>Escape</kbd>, a click outside or <kbd>Tab</kbd> past the list close it.
56
+
57
+ ## Min, max and disabled dates
58
+
59
+ `min` and `max` are values like the field's (`'2026-10-14T09:00'`). Their dates bound the calendar; on the day of a limit the list disables the times past it. A typed value outside them shows an error once focus leaves the field: the date limit (`validation.minDate` / `maxDate`) when the day is out of range, the time limit (`timeInputMin` / `timeInputMax`) on the limit's own day. `disabledDate` rules out dates in the calendar and for typed dates (`dateInputUnavailable`). An incomplete date or time shows `dateInputInvalid` or `timeInputInvalid`.
60
+
61
+ ```ts
62
+ import { Component, signal } from '@angular/core';
63
+ import { BfDateTimeInputComponent } from '@bitforgehq/ui/overlay';
64
+
65
+ /**
66
+ * Min and max.
67
+ *
68
+ * A delivery slot from 14 October 2026, 09:00 to 30 October 2026, 17:00: the calendar stops at
69
+ * both days, and on 14 October the list disables the times before 09:00 (on 30 October those
70
+ * after 17:00). A typed value outside them shows the date or the time limit.
71
+ */
72
+ @Component({
73
+ selector: 'app-example-datetime-input-min-max',
74
+ imports: [BfDateTimeInputComponent],
75
+ template: `
76
+ <bf-datetime-input
77
+ class="max-w-xs"
78
+ label="Delivery slot"
79
+ locale="en-GB"
80
+ step="60"
81
+ min="2026-10-14T09:00"
82
+ max="2026-10-30T17:00"
83
+ [(value)]="slot"
84
+ />
85
+ `,
86
+ })
87
+ export class DatetimeInputMinMaxExampleComponent {
88
+ readonly slot = signal<string | null>('2026-10-14T08:00');
89
+ }
90
+ ```
91
+
92
+ ## Time zones
93
+
94
+ The value has no zone: it is the wall-clock time the user picked, in no particular place. Keep it that way for local events (a shop's opening, a meeting in a known office), or turn it into an instant where you know the zone (the user's, or an event's), on the server or with `Intl`. Never parse what users type with `new Date()`; the field never does.
95
+
96
+ ## Native picker
97
+
98
+ `native` renders an `input type="datetime-local"` with the same label, help and error text when the primary pointer is coarse (`matchMedia('(pointer: coarse)')`), and the segmented field under a mouse; `min` and `max` go to it. Recommended in the Capacitor shell.
99
+
100
+ ## With Signal Forms
101
+
102
+ Bind the field with `[formField]`: required, touched, errors, disabled and readonly flow in, and `focus()` focuses the first empty part. Under `[formField]` the form owns `min` and `max`: give them through the schema with a metadata key and `metadata(path, MIN, () => KEY)`, as the [bf-date-input](https://bitforge-ui.web.app/components/date-input) page shows. In specs, `BfDateTimeInputHarness` from `@bitforgehq/ui/testing` types a value (`setValue('14/10/2026, 09:30')`) or picks it (`pickDate('2026-10-14')`, then `pickTime('09:30')`).
103
+
104
+ ## Desktop
105
+
106
+ As the date and the time inputs: the focus ring on the field while a part has focus, a tinted focused part, hover tints on the button and the list's times. The popover is about 29 rem wide at `md`; on a narrow screen the list of times wraps below the calendar. Fields default to `md` (40 px); `size` and `provideBfDefaults({ fieldSize })` set the step ([density guide](https://bitforge-ui.web.app/density)).
107
+
108
+ ## Theming
109
+
110
+ - Field, parts and button: as `bf-date-input` (`surface`, `border-strong`, `primary` focus, `primary-soft` focused part, `text-muted` placeholders).
111
+ - Popover: `surface`, `border`, `rounded-card`, `shadow-floating`.
112
+ - Calendar and list: their own tokens, as on [bf-calendar](https://bitforge-ui.web.app/components/calendar) and `bf-time-input`.
113
+
114
+ ## Accessibility
115
+
116
+ The parts are a `role="group"` named by `label` (else `ariaLabel` or `ariaLabelledby`), each a named `role="spinbutton"` with its range, value and value text, and the field's invalid, required, read-only and disabled state. The button is named by `dateInputOpenCalendar`, with `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`; the popover is a non-modal `dialog` named by the button, holding a `bf-calendar` grid and a `role="listbox"` of times named by `timeInputOpenList` (`aria-activedescendant`, `aria-selected`, `aria-disabled`). Focus moves into the calendar on open, to the list after a day is picked, and back to the button after a time is picked or on <kbd>Escape</kbd>.
117
+
118
+ ### Keyboard
119
+
120
+ | Key | Where | Action |
121
+ | ------------------------------------------------ | ------- | -------------------------------------------------------- |
122
+ | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Field | Moves through the parts, then to the button |
123
+ | <kbd>0</kbd>–<kbd>9</kbd> | Part | Types into the part; moves on once no further digit fits |
124
+ | <kbd>ArrowUp</kbd> / <kbd>ArrowDown</kbd> | Part | Next / previous value, wrapping |
125
+ | <kbd>ArrowRight</kbd> / <kbd>ArrowLeft</kbd> | Part | Next / previous part (swapped under `dir="rtl"`) |
126
+ | <kbd>Backspace</kbd> / <kbd>Delete</kbd> | Part | Clears the part |
127
+ | <kbd>Alt</kbd>+<kbd>ArrowDown</kbd> | Part | Opens the popover |
128
+ | <kbd>Tab</kbd> | Popover | From the calendar to the list; past the list closes it |
129
+ | <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | List | Next / previous time (skipping disabled ones) |
130
+ | <kbd>Home</kbd> / <kbd>End</kbd> | List | First / last time |
131
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | List | Picks the active time and closes the popover |
132
+ | <kbd>Escape</kbd> | Popover | Closes the popover, focus back on the button |
133
+
134
+ The calendar's own keys work as on [bf-calendar](https://bitforge-ui.web.app/components/calendar).
135
+
136
+ ## Inputs
137
+
138
+ | Name | Type | Default | Description |
139
+ | ---------------- | ------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
140
+ | `min` | `string \| undefined` | `undefined` | The earliest valid date and time ('YYYY-MM-DDTHH:mm'): earlier values show an error, and the calendar and the list stop there. Set by `[formField]`; do not bind it yourself. |
141
+ | `max` | `string \| undefined` | `undefined` | The latest valid date and time ('YYYY-MM-DDTHH:mm'): later values show an error, and the calendar and the list stop there. Set by `[formField]`; do not bind it yourself. |
142
+ | `disabledDate` | `BfCalendarDisabledDate \| null` | `null` | Rules out more dates (weekends, booked days): disabled in the calendar, an error when typed. |
143
+ | `step` | `number` | `30` | Minutes between the times of the list, from midnight (1–720). |
144
+ | `hourCycle` | `BfTimeInputHourCycle \| undefined` (`'h12' \| 'h23' \| undefined`) | `undefined` | Forces a 12-hour (`h12`, with AM/PM) or 24-hour (`h23`) clock; unset follows the locale. |
145
+ | `picker` | `BfDateTimeInputPicker` (`'popover' \| 'none'`) | `'popover'` | `popover` adds the button that opens the calendar and the time list; `none` keeps the field typed only. |
146
+ | `native` | `boolean` | `false` | Renders the platform's picker (`input type="datetime-local"`) under a coarse pointer (touch). |
147
+ | `locale` | `string` | `''` | BCP 47 locale of the part order, the separators, the clock and the calendar; empty follows the document's `lang`, else English. |
148
+ | `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by default). |
149
+ | `label` | `string` | `''` | Visible label above the field. |
150
+ | `ariaLabel` | `string` | `''` | Accessible name when no visible label is rendered. Ignored when `label` is set. |
151
+ | `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when `label` is set. |
152
+ | `helpText` | `string` | `''` | Helper text under the field. |
153
+ | `errorMessage` | `string` | `''` | Explicit error text; takes precedence over the field's own errors and Signal Forms errors. |
154
+ | `disabled` | `boolean` | `false` | Disables the field and its picker button. Set by `[formField]`; do not bind it yourself. |
155
+ | `readonly` | `boolean` | `false` | Shows the value but blocks changes; the parts stay focusable and expose `aria-readonly`. Set by `[formField]`; do not bind it yourself. |
156
+ | `required` | `boolean` | `false` | Marks the field required. Set by `[formField]`; do not bind it yourself. |
157
+ | `invalid` | `boolean` | `false` | Field validity from Signal Forms. Set by `[formField]`; do not bind it yourself. |
158
+ | `touched` | `boolean` | `false` | Whether the user has left the field. Set by `[formField]`; do not bind it yourself. |
159
+ | `errors` | `readonly ValidationError.WithOptionalFieldTree[]` | `[]` | Validation errors from Signal Forms. Set by `[formField]`; do not bind it yourself. |
160
+ | `name` | `string` | `''` | Native name attribute of the platform input (`native` under a coarse pointer). Set by `[formField]`; do not bind it yourself. |
161
+ | `id` | `string` | `generated` | Field id; generated when not set. |
162
+ | `value` | `string \| null` | `null` | The local date and time as an ISO string without a zone ('YYYY-MM-DDTHH:mm'), `null` while empty or incomplete; two-way bindable and bound by `[formField]`. Two-way: `[(value)]`. |
163
+
164
+ ## Outputs
165
+
166
+ | Name | Payload | Description |
167
+ | --------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
168
+ | `changed` | `string \| null` | Emitted with the new value right after a user change: `null` once it is cleared or incomplete. |
169
+ | `touch` | `void` | Emitted when focus leaves the field (not into its popover), so Signal Forms marks it touched. Set by `[formField]`; do not bind it yourself. |
170
+
171
+ ## Methods
172
+
173
+ ### `focus(options?: FocusOptions): void`
174
+
175
+ Focuses the first empty part (else the first part); Signal Forms calls it from
176
+ `focusBoundControl()`.
177
+
178
+ | Parameter | Type | Description |
179
+ | ---------- | -------------- | --------------------------------------------------------------------- |
180
+ | `options?` | `FocusOptions` | Passed on to the native `focus()`, such as `{ preventScroll: true }`. |
181
+
182
+ ### `openPicker(): void`
183
+
184
+ Opens the calendar and time popover; does nothing without a picker, or while disabled or
185
+ read-only.
186
+
187
+ ### `closePicker(): void`
188
+
189
+ Closes the popover, focus back on its button; does nothing while it is closed.
190
+
191
+ ## Notes
192
+
193
+ ### Do
194
+
195
+ - Give the field a `label`.
196
+ - Decide where the time zone comes from before you store the value as an instant.
197
+ - Set `step` to the slots you accept.
198
+
199
+ ### Don't
200
+
201
+ - Don't append a zone or a `Z` to the value as if it were UTC; it is a local wall-clock time.
202
+ - Don't use it for an all-day event; use a date input.
203
+
204
+ ## Migration notes
205
+
206
+ ### 0.3.1
207
+
208
+ New `bf-datetime-input` in `@bitforgehq/ui/overlay`: the date and the time parts in one field,
209
+ valued as a local ISO date and time without a zone (`'YYYY-MM-DDTHH:mm'`). `picker`
210
+ (`BfDateTimeInputPicker`: `'popover'`, the default, or `'none'`) opens one popover with the
211
+ calendar and a list of times side by side: a day moves focus to the list, a time closes it.
212
+ `min` and `max` bound the calendar and, on their own day, the list. `step`, `hourCycle`,
213
+ `disabledDate`, `native` (`input type="datetime-local"`) and the field contract as on the date
214
+ and time inputs. New `BfDateTimeInputHarness` in `@bitforgehq/ui/testing` (`pickDate(iso)`,
215
+ `pickTime(time)`, `setValue(text)`).