ngwr 14.7.0 → 14.8.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.
Files changed (152) hide show
  1. package/bar-chart/styles/_index.scss +1 -0
  2. package/calendar-heatmap/styles/_index.scss +1 -0
  3. package/cascader/styles/_rules.scss +86 -14
  4. package/date-picker/styles/_index.scss +30 -0
  5. package/density/styles/_index.scss +4 -2
  6. package/donut-chart/styles/_index.scss +10 -0
  7. package/dropdown/styles/_index.scss +67 -5
  8. package/event-calendar/styles/_index.scss +209 -35
  9. package/fesm2022/ngwr-bar-chart.mjs +29 -4
  10. package/fesm2022/ngwr-bar-chart.mjs.map +1 -1
  11. package/fesm2022/ngwr-calendar-heatmap.mjs +59 -4
  12. package/fesm2022/ngwr-calendar-heatmap.mjs.map +1 -1
  13. package/fesm2022/ngwr-cascader.mjs +2 -2
  14. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  15. package/fesm2022/ngwr-checkbox.mjs +12 -2
  16. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  17. package/fesm2022/ngwr-date-picker.mjs +705 -46
  18. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  19. package/fesm2022/ngwr-donut-chart.mjs +46 -4
  20. package/fesm2022/ngwr-donut-chart.mjs.map +1 -1
  21. package/fesm2022/ngwr-dropdown.mjs +166 -17
  22. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  23. package/fesm2022/ngwr-event-calendar.mjs +9 -2
  24. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  25. package/fesm2022/ngwr-form.mjs +52 -1
  26. package/fesm2022/ngwr-form.mjs.map +1 -1
  27. package/fesm2022/ngwr-gauge.mjs +33 -4
  28. package/fesm2022/ngwr-gauge.mjs.map +1 -1
  29. package/fesm2022/ngwr-i18n-ar.mjs +2 -0
  30. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  31. package/fesm2022/ngwr-i18n-cs.mjs +2 -0
  32. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  33. package/fesm2022/ngwr-i18n-de.mjs +2 -0
  34. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  35. package/fesm2022/ngwr-i18n-en.mjs +5 -0
  36. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  37. package/fesm2022/ngwr-i18n-es.mjs +2 -0
  38. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  39. package/fesm2022/ngwr-i18n-fr.mjs +2 -0
  40. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  41. package/fesm2022/ngwr-i18n-he.mjs +2 -0
  42. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  43. package/fesm2022/ngwr-i18n-hi.mjs +2 -0
  44. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  45. package/fesm2022/ngwr-i18n-id.mjs +2 -0
  46. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  47. package/fesm2022/ngwr-i18n-it.mjs +2 -0
  48. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  49. package/fesm2022/ngwr-i18n-ja.mjs +2 -0
  50. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  51. package/fesm2022/ngwr-i18n-ko.mjs +2 -0
  52. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  53. package/fesm2022/ngwr-i18n-nl.mjs +2 -0
  54. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  55. package/fesm2022/ngwr-i18n-pl.mjs +2 -0
  56. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  57. package/fesm2022/ngwr-i18n-pt.mjs +2 -0
  58. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  59. package/fesm2022/ngwr-i18n-ru.mjs +2 -0
  60. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  61. package/fesm2022/ngwr-i18n-sv.mjs +2 -0
  62. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  63. package/fesm2022/ngwr-i18n-tr.mjs +2 -0
  64. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  65. package/fesm2022/ngwr-i18n-uk.mjs +2 -0
  66. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  67. package/fesm2022/ngwr-i18n-vi.mjs +2 -0
  68. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  69. package/fesm2022/ngwr-i18n-zh-TW.mjs +2 -0
  70. package/fesm2022/ngwr-i18n-zh-TW.mjs.map +1 -1
  71. package/fesm2022/ngwr-i18n-zh.mjs +2 -0
  72. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  73. package/fesm2022/ngwr-input-number.mjs +12 -0
  74. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  75. package/fesm2022/ngwr-line-chart.mjs +16 -5
  76. package/fesm2022/ngwr-line-chart.mjs.map +1 -1
  77. package/fesm2022/ngwr-meter-group-testing.mjs +7 -3
  78. package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -1
  79. package/fesm2022/ngwr-meter-group.mjs +24 -4
  80. package/fesm2022/ngwr-meter-group.mjs.map +1 -1
  81. package/fesm2022/ngwr-page-header.mjs +10 -2
  82. package/fesm2022/ngwr-page-header.mjs.map +1 -1
  83. package/fesm2022/ngwr-popover.mjs +569 -7
  84. package/fesm2022/ngwr-popover.mjs.map +1 -1
  85. package/fesm2022/ngwr-sidebar.mjs +7 -0
  86. package/fesm2022/ngwr-sidebar.mjs.map +1 -1
  87. package/fesm2022/ngwr-sparkline.mjs +82 -8
  88. package/fesm2022/ngwr-sparkline.mjs.map +1 -1
  89. package/fesm2022/ngwr-table.mjs +1 -1
  90. package/fesm2022/ngwr-table.mjs.map +1 -1
  91. package/fesm2022/ngwr-toolbar.mjs +7 -0
  92. package/fesm2022/ngwr-toolbar.mjs.map +1 -1
  93. package/fesm2022/ngwr-version.mjs +1 -1
  94. package/fesm2022/ngwr-version.mjs.map +1 -1
  95. package/form/styles/_index.scss +11 -1
  96. package/gauge/styles/_index.scss +1 -0
  97. package/i18n/ar.json +3 -1
  98. package/i18n/cs.json +3 -1
  99. package/i18n/de.json +3 -1
  100. package/i18n/en.json +3 -1
  101. package/i18n/es.json +3 -1
  102. package/i18n/fr.json +3 -1
  103. package/i18n/he.json +3 -1
  104. package/i18n/hi.json +3 -1
  105. package/i18n/id.json +3 -1
  106. package/i18n/it.json +3 -1
  107. package/i18n/ja.json +3 -1
  108. package/i18n/ko.json +3 -1
  109. package/i18n/nl.json +3 -1
  110. package/i18n/pl.json +3 -1
  111. package/i18n/pt.json +3 -1
  112. package/i18n/ru.json +3 -1
  113. package/i18n/sv.json +3 -1
  114. package/i18n/tr.json +3 -1
  115. package/i18n/uk.json +3 -1
  116. package/i18n/vi.json +3 -1
  117. package/i18n/zh-TW.json +3 -1
  118. package/i18n/zh.json +3 -1
  119. package/input/styles/_index.scss +19 -0
  120. package/input-number/styles/_index.scss +69 -0
  121. package/line-chart/styles/_index.scss +30 -2
  122. package/llms-full.txt +3 -3
  123. package/meter-group/styles/_index.scss +1 -0
  124. package/overlay/styles/_arrow.scss +27 -2
  125. package/package.json +1 -1
  126. package/page-header/styles/_index.scss +37 -1
  127. package/pagination/styles/_index.scss +9 -5
  128. package/popover/styles/_index.scss +73 -5
  129. package/select/styles/_index.scss +48 -9
  130. package/sidebar/styles/_index.scss +34 -2
  131. package/sparkline/styles/_index.scss +19 -0
  132. package/toolbar/styles/_index.scss +35 -1
  133. package/tree/styles/_index.scss +45 -6
  134. package/types/ngwr-bar-chart.d.ts +19 -1
  135. package/types/ngwr-calendar-heatmap.d.ts +29 -1
  136. package/types/ngwr-checkbox.d.ts +2 -0
  137. package/types/ngwr-date-picker.d.ts +308 -43
  138. package/types/ngwr-donut-chart.d.ts +24 -1
  139. package/types/ngwr-dropdown.d.ts +22 -2
  140. package/types/ngwr-event-calendar.d.ts +8 -0
  141. package/types/ngwr-form.d.ts +35 -5
  142. package/types/ngwr-gauge.d.ts +20 -1
  143. package/types/ngwr-input-number.d.ts +7 -0
  144. package/types/ngwr-line-chart.d.ts +9 -1
  145. package/types/ngwr-meter-group-testing.d.ts +3 -2
  146. package/types/ngwr-meter-group.d.ts +14 -1
  147. package/types/ngwr-page-header.d.ts +10 -2
  148. package/types/ngwr-popover.d.ts +154 -3
  149. package/types/ngwr-sidebar.d.ts +7 -0
  150. package/types/ngwr-sparkline.d.ts +43 -1
  151. package/types/ngwr-toolbar.d.ts +7 -0
  152. package/types/ngwr-version.d.ts +1 -1
@@ -1,8 +1,71 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { ElementRef } from '@angular/core';
2
+ import { Signal, ElementRef } from '@angular/core';
3
3
  import { FormValueControl } from '@angular/forms/signals';
4
4
  import { WrDateFormat } from 'ngwr/date';
5
5
 
6
+ /**
7
+ * @license
8
+ *
9
+ * Use of this source code is governed by an MIT-style license that can be
10
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
11
+ */
12
+ /**
13
+ * Why the text typed into a picker's field was not committed.
14
+ *
15
+ * A picker never guesses: text it cannot read, or a date the calendar would
16
+ * refuse, leaves the bound value exactly as it was. This is the reason it
17
+ * refused, in the shape Angular's forms carry an error — `kind` is the key
18
+ * `<wr-form-field>` resolves a message for (`validation.<kind>` in the
19
+ * `ngwr/i18n` catalogs), and `message` is that sentence already resolved, for
20
+ * a template that renders errors itself.
21
+ *
22
+ * - `dateFormat` — the text does not read as a date in the field's format.
23
+ * `example` is a date written in that format, for the message to show.
24
+ * - `minDate` / `maxDate` — the text reads, and the date is outside the bounds.
25
+ * - `dateFilter` — the text reads, and the `dateFilter` predicate rejects it.
26
+ *
27
+ * The same keys, and the same `min` / `max` payload, as `WrValidators.minDate`
28
+ * and `WrValidators.maxDate`, so one app-wide message covers both.
29
+ */
30
+ type WrDateInputError = {
31
+ readonly kind: 'dateFormat';
32
+ readonly message: string;
33
+ readonly example: string;
34
+ } | {
35
+ readonly kind: 'minDate';
36
+ readonly message: string;
37
+ readonly date: Date;
38
+ readonly min: Date;
39
+ } | {
40
+ readonly kind: 'maxDate';
41
+ readonly message: string;
42
+ readonly date: Date;
43
+ readonly max: Date;
44
+ } | {
45
+ readonly kind: 'dateFilter';
46
+ readonly message: string;
47
+ readonly date: Date;
48
+ };
49
+ /** A {@link WrDateInputError} from one end of a `<wr-date-range-picker>`. */
50
+ type WrDateRangeInputError = WrDateInputError & {
51
+ readonly end: 'start' | 'end';
52
+ };
53
+
54
+ /**
55
+ * @license
56
+ *
57
+ * Use of this source code is governed by an MIT-style license that can be
58
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
59
+ */
60
+ /**
61
+ * The `<wr-date-range-picker>` value — `[start, end]`.
62
+ *
63
+ * Either end can be `null` while the range is half-picked, so a partially
64
+ * filled range is still representable. Structurally identical to
65
+ * `WrCalendarRange`, which the picker forwards it to.
66
+ */
67
+ type WrDateRange = readonly [Date | null, Date | null];
68
+
6
69
  /**
7
70
  * Unified date / time / date-time picker. Same `<input>` + popover skeleton
8
71
  * for every mode — the overlay content swaps based on `[mode]`:
@@ -20,8 +83,12 @@ import { WrDateFormat } from 'ngwr/date';
20
83
  * `[(ngModel)]` / reactive forms keep working through Angular's bridge. Value
21
84
  * type is `Date | null` for every mode.
22
85
  *
23
- * Parses the input on every keystroke (silently — only emits when valid) and
24
- * re-formats canonical on blur. Format is driven by {@link WrDateAdapter}: it
86
+ * Parses the input on every keystroke and emits only a date it can read and the
87
+ * calendar would accept. Anything else is REFUSED, never guessed: the bound value
88
+ * stays where it was, and the text stays in the field so it can be corrected.
89
+ * Enter and leaving the field turn a refusal visible — see {@link inputError}.
90
+ * A committed date is re-formatted canonical on blur and on Enter. Format is
91
+ * driven by {@link WrDateAdapter}: it
25
92
  * accepts both named keys (`'shortDate'`, `'mediumDateTime'`, …) and raw
26
93
  * token strings (`'dd.MM.yyyy'`, `'HH:mm'`). When `format` is left at the
27
94
  * default (`null`), the picker derives the right named key from `mode`:
@@ -91,8 +158,8 @@ declare class WrDatePicker implements FormValueControl<Date | null> {
91
158
  * simply reads as `ltr`.
92
159
  */
93
160
  private readonly dir;
94
- protected readonly inputEl: _angular_core.Signal<ElementRef<HTMLInputElement>>;
95
- protected readonly triggerEl: _angular_core.Signal<ElementRef<HTMLButtonElement> | undefined>;
161
+ protected readonly inputEl: Signal<ElementRef<HTMLInputElement>>;
162
+ protected readonly triggerEl: Signal<ElementRef<HTMLButtonElement> | undefined>;
96
163
  /** The picked Date. Bound by `[formField]`, or two-way via `[(value)]`. */
97
164
  readonly value: _angular_core.ModelSignal<Date | null>;
98
165
  /** Emitted on blur so a bound field can mark itself touched. */
@@ -110,9 +177,9 @@ declare class WrDatePicker implements FormValueControl<Date | null> {
110
177
  /** Whether the panel this open cycle should take focus. */
111
178
  private autoFocusPanel;
112
179
  /** Resolved format — falls back to a mode-appropriate default. */
113
- protected readonly resolvedFormat: _angular_core.Signal<string>;
114
- protected readonly isTime: _angular_core.Signal<boolean>;
115
- protected readonly isDateTime: _angular_core.Signal<boolean>;
180
+ protected readonly resolvedFormat: Signal<string>;
181
+ protected readonly isTime: Signal<boolean>;
182
+ protected readonly isDateTime: Signal<boolean>;
116
183
  private readonly labelDate;
117
184
  private readonly labelTime;
118
185
  private readonly labelDateTime;
@@ -126,8 +193,8 @@ declare class WrDatePicker implements FormValueControl<Date | null> {
126
193
  */
127
194
  readonly ariaLabel: _angular_core.InputSignal<string | null>;
128
195
  private readonly fieldLabel;
129
- protected readonly resolvedAriaLabel: _angular_core.Signal<string>;
130
- protected readonly triggerLabel: _angular_core.Signal<string>;
196
+ protected readonly resolvedAriaLabel: Signal<string>;
197
+ protected readonly triggerLabel: Signal<string>;
131
198
  /**
132
199
  * Accessible name of the popup. The trigger advertises
133
200
  * `aria-haspopup="dialog"`, so the panel is a `role="dialog"` — and an unnamed
@@ -137,8 +204,83 @@ declare class WrDatePicker implements FormValueControl<Date | null> {
137
204
  readonly panelAriaLabel: _angular_core.InputSignal<string | null>;
138
205
  /** Popup id — what the trigger's `aria-controls` points at while open. */
139
206
  protected readonly panelId: string;
140
- protected readonly resolvedPanelLabel: _angular_core.Signal<string>;
141
- protected readonly classes: _angular_core.Signal<string>;
207
+ protected readonly resolvedPanelLabel: Signal<string>;
208
+ private readonly refusals;
209
+ private readonly reportToForm;
210
+ private readonly inputField;
211
+ /**
212
+ * Why the text in the field was not committed — `null` while the text is the value.
213
+ * Raised on the keystroke that makes the text unusable, and reported to a bound form
214
+ * from that keystroke on.
215
+ */
216
+ private readonly refusal;
217
+ /**
218
+ * Whether the refusal is on show. A refusal is raised on the keystroke but shown
219
+ * only once the user has finished — on Enter, or on leaving the field — because
220
+ * every date is unreadable for most of the time it takes to type one, and a field
221
+ * that turned red on the first digit would be telling the user off for typing.
222
+ * Once shown it follows the text live, so a correction clears it immediately.
223
+ *
224
+ * Leaving counts wherever focus goes, including by way of the open panel: moving
225
+ * into the panel waits, since a pick there replaces the text, but focus that leaves
226
+ * the panel for anything other than the field — or a panel that closes without a
227
+ * pick and without handing focus back to the field — has left too.
228
+ */
229
+ private readonly revealed;
230
+ /**
231
+ * Why the text in the field is not the bound value, once the picker is showing it —
232
+ * `null` while the text is the value, and `null` while the user is still typing.
233
+ *
234
+ * A refusal is raised on the keystroke that makes the text unreadable (`dateFormat`),
235
+ * out of `min` / `max` (`minDate` / `maxDate`) or rejected by `dateFilter`, and goes
236
+ * on show on Enter or when the user leaves the field — the same moment the border
237
+ * turns and the reason is announced, so a message rendered from this signal never
238
+ * disagrees with them. Once on show it follows the text, and it clears the moment the
239
+ * text becomes a date the picker commits, is emptied, or is replaced by a pick from
240
+ * the panel, a value written from outside, or a form reset. The value itself is never
241
+ * touched by a refusal.
242
+ *
243
+ * A bound form does NOT wait: `[formField]`, `[formControl]`, `formControlName` and
244
+ * `[(ngModel)]` see the refusal as a parse error from the keystroke, so the form is
245
+ * invalid the whole time the field holds text it could not use, and
246
+ * `<wr-form-field>` shows `message` by its own rule — once the control is touched or
247
+ * dirty. Read this through a template reference when the picker is bound with
248
+ * `[(value)]` alone and the reason should be on screen.
249
+ *
250
+ * @example
251
+ * ```html
252
+ * <wr-date-picker #due [(value)]="due" format="dd.MM.yyyy" />
253
+ * @if (due.inputError(); as error) {
254
+ * <small>{{ error.message }}</small>
255
+ * }
256
+ * ```
257
+ */
258
+ readonly inputError: Signal<WrDateInputError | null>;
259
+ /**
260
+ * Blanked briefly so Enter on an unchanged refusal is announced again. A live region
261
+ * does not repeat text it already holds, and blanking it for one render is not
262
+ * enough: both changes land in the same task, and the accessibility tree sees
263
+ * only the end state. The gap is a timer for that reason — the CDK's
264
+ * `LiveAnnouncer` clears its region the same way.
265
+ */
266
+ private readonly silenced;
267
+ private silenceTimer;
268
+ protected readonly statusId: string;
269
+ /**
270
+ * What the picker's own live region says: the refusal on show, unless a surrounding
271
+ * `<wr-form-field>` is already rendering a message for it — that message is a
272
+ * `role="alert"`, and a second voice would repeat it.
273
+ */
274
+ protected readonly announcement: Signal<string>;
275
+ /**
276
+ * `aria-invalid` and `aria-describedby` for the text input, bound only with NO
277
+ * field around the picker. Inside one, `wrInput` writes both from the field — see
278
+ * `WrDateInputField` for why the picker feeds them through it rather than binding
279
+ * a second writer beside it.
280
+ */
281
+ protected readonly ownAriaInvalid: Signal<"true" | null>;
282
+ protected readonly ownDescribedBy: Signal<string | null>;
283
+ protected readonly classes: Signal<string>;
142
284
  private overlayRef;
143
285
  /** Live calendar ref while the date popover is open — lets the panel track
144
286
  * the typed value in real time (not just on reopen). */
@@ -164,7 +306,41 @@ declare class WrDatePicker implements FormValueControl<Date | null> {
164
306
  * the text field silently moved the value to another day.
165
307
  */
166
308
  private withKeptDate;
167
- protected onBlur(): void;
309
+ protected onBlur(event: FocusEvent): void;
310
+ /**
311
+ * Enter is "this is the date". A date the picker committed is re-formatted
312
+ * canonical and the panel closes; text it refused turns invalid on the spot —
313
+ * border, `aria-invalid`, the reason announced — and emits `touch`, which is what
314
+ * makes a surrounding `<wr-form-field>` show its message. The panel closes in both
315
+ * cases: it opens directly below the field, which is exactly where that message
316
+ * renders.
317
+ *
318
+ * Never `preventDefault`: Enter inside a `<form>` still submits it, and a form
319
+ * bound to this picker is invalid while the refusal stands.
320
+ */
321
+ private onEnter;
322
+ /** Show the committed value canonically, or commit an emptied field as `null`. */
323
+ private settleText;
324
+ /**
325
+ * Record why the current text was not committed, or that it was. Clearing a
326
+ * refusal also takes it off show, so the next one waits for Enter or blur again.
327
+ */
328
+ private refuse;
329
+ /** Put a standing refusal on show — the user has left the field. */
330
+ private reveal;
331
+ /**
332
+ * The bound form was reset while the model stayed where it was — Angular has thrown
333
+ * the parse error away, so the text it was about goes too, and the field shows the
334
+ * value again. (A reset that MOVES the model reaches the text through the sync
335
+ * effect instead, like any other outside write.)
336
+ */
337
+ private onFormReset;
338
+ /**
339
+ * Focus leaving the open panel. Moving within it, or back to the text field, is
340
+ * still the same visit; anywhere else — the page, the trigger, the next control — is
341
+ * leaving, so a refusal that waited while focus went into the panel shows now.
342
+ */
343
+ private readonly onPaneFocusOut;
168
344
  /** Called by the input's click — opens the overlay if it isn't open already. */
169
345
  protected openOnInput(): void;
170
346
  protected toggleOverlay(): void;
@@ -203,29 +379,16 @@ declare class WrDatePicker implements FormValueControl<Date | null> {
203
379
  */
204
380
  private closeOverlay;
205
381
  private dispose;
206
- private isOutOfBounds;
382
+ private outOfBounds;
207
383
  private sameDate;
208
384
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrDatePicker, never>;
209
385
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrDatePicker, "wr-date-picker", never, { "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "dateFilter": { "alias": "dateFilter"; "required": false; "isSignal": true; }; "timeFormat": { "alias": "timeFormat"; "required": false; "isSignal": true; }; "showSeconds": { "alias": "showSeconds"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "panelAriaLabel": { "alias": "panelAriaLabel"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, never, true, never>;
210
386
  }
211
387
 
212
- /**
213
- * @license
214
- *
215
- * Use of this source code is governed by an MIT-style license that can be
216
- * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
217
- */
218
- /**
219
- * The `<wr-date-range-picker>` value — `[start, end]`.
220
- *
221
- * Either end can be `null` while the range is half-picked, so a partially
222
- * filled range is still representable. Structurally identical to
223
- * `WrCalendarRange`, which the picker forwards it to.
224
- */
225
- type WrDateRange = readonly [Date | null, Date | null];
226
-
227
388
  /** Which end of the range an edit applies to. */
228
389
  type RangeEnd = 0 | 1;
390
+ /** One refusal per end, start first — `null` where that end's text is its date. */
391
+ type EndRefusals = readonly [WrDateInputError | null, WrDateInputError | null];
229
392
  /**
230
393
  * Date-range picker — two text inputs sharing one range calendar.
231
394
  *
@@ -248,6 +411,10 @@ type RangeEnd = 0 | 1;
248
411
  * Either end may be `null` while the range is half-picked. Out-of-order ends
249
412
  * are swapped on commit, matching the calendar's own behaviour.
250
413
  *
414
+ * Each end refuses text it cannot use exactly as `<wr-date-picker>` does — never
415
+ * guessing, keeping the text, and showing the refusal on Enter or when focus
416
+ * leaves that end. See {@link inputErrors}.
417
+ *
251
418
  * @example
252
419
  * ```html
253
420
  * <!-- signal forms -->
@@ -322,9 +489,9 @@ declare class WrDateRangePicker implements FormValueControl<WrDateRange | null>
322
489
  * simply reads as `ltr`.
323
490
  */
324
491
  private readonly dir;
325
- protected readonly startEl: _angular_core.Signal<ElementRef<HTMLInputElement>>;
326
- protected readonly endEl: _angular_core.Signal<ElementRef<HTMLInputElement>>;
327
- protected readonly triggerEl: _angular_core.Signal<ElementRef<HTMLButtonElement> | undefined>;
492
+ protected readonly startEl: Signal<ElementRef<HTMLInputElement>>;
493
+ protected readonly endEl: Signal<ElementRef<HTMLInputElement>>;
494
+ protected readonly triggerEl: Signal<ElementRef<HTMLButtonElement> | undefined>;
328
495
  /** The picked range. Bound by `[formField]`, or two-way via `[(value)]`. */
329
496
  readonly value: _angular_core.ModelSignal<WrDateRange | null>;
330
497
  /** Emitted on blur so a bound field can mark itself touched. */
@@ -345,11 +512,11 @@ declare class WrDateRangePicker implements FormValueControl<WrDateRange | null>
345
512
  /** Whether the panel this open cycle should take focus. */
346
513
  private autoFocusPanel;
347
514
  /** Resolved format — falls back to a mode-appropriate default. */
348
- protected readonly resolvedFormat: _angular_core.Signal<string>;
349
- protected readonly isDateTime: _angular_core.Signal<boolean>;
350
- protected readonly triggerLabel: _angular_core.Signal<string>;
351
- protected readonly startLabel: _angular_core.Signal<string>;
352
- protected readonly endLabel: _angular_core.Signal<string>;
515
+ protected readonly resolvedFormat: Signal<string>;
516
+ protected readonly isDateTime: Signal<boolean>;
517
+ protected readonly triggerLabel: Signal<string>;
518
+ protected readonly startLabel: Signal<string>;
519
+ protected readonly endLabel: Signal<string>;
353
520
  private readonly panelLabelRange;
354
521
  private readonly panelLabelRangeDateTime;
355
522
  /**
@@ -361,8 +528,74 @@ declare class WrDateRangePicker implements FormValueControl<WrDateRange | null>
361
528
  readonly panelAriaLabel: _angular_core.InputSignal<string | null>;
362
529
  /** Popup id — what the trigger's `aria-controls` points at while open. */
363
530
  protected readonly panelId: string;
364
- protected readonly resolvedPanelLabel: _angular_core.Signal<string>;
365
- protected readonly classes: _angular_core.Signal<string>;
531
+ protected readonly resolvedPanelLabel: Signal<string>;
532
+ private readonly refusalBuilder;
533
+ private readonly reportToForm;
534
+ private readonly inputField;
535
+ /** Why each end's text was not committed. */
536
+ private readonly refusals;
537
+ /**
538
+ * Every refusal standing, on show or not, each marked with its end — what a bound form
539
+ * hears, from the keystroke.
540
+ */
541
+ private readonly raised;
542
+ /**
543
+ * Which ends have their refusal on show — raised on the keystroke, shown on Enter
544
+ * or when focus leaves that end, for the reason `wr-date-picker` gives at the same
545
+ * name: a date is unreadable for most of the time it takes to type one. Leaving
546
+ * includes leaving by way of the open panel, as it does there.
547
+ */
548
+ private readonly revealed;
549
+ protected readonly shownErrors: Signal<EndRefusals>;
550
+ /**
551
+ * Why the text in either field is not that end of the bound range, for the ends
552
+ * whose refusal the picker is SHOWING — one entry per end, marked with the `end` it
553
+ * came from, and empty while both fields hold their dates or the user is still
554
+ * typing.
555
+ *
556
+ * An end's refusal is raised on the keystroke and goes on show on Enter or when
557
+ * focus leaves that end — the moment its input turns invalid and the reason is
558
+ * announced, so a message rendered from this never disagrees with them. It clears
559
+ * the moment that end's text commits, is emptied, or is replaced by a pick from the
560
+ * panel, a value written from outside, or a form reset.
561
+ *
562
+ * A bound form does NOT wait: it sees every refusal as a parse error from the
563
+ * keystroke, so it is invalid the whole time either field holds text the picker
564
+ * could not use — including while the OTHER end commits — and `<wr-form-field>`
565
+ * shows `message` by its own rule. Read this through a template reference to put
566
+ * the reason on screen for a picker bound with `[(value)]` alone.
567
+ */
568
+ readonly inputErrors: Signal<readonly WrDateRangeInputError[]>;
569
+ /**
570
+ * Blanked briefly so Enter on an unchanged refusal is announced again. A live region
571
+ * does not repeat text it already holds, and blanking it for one render is not
572
+ * enough: both changes land in the same task, and the accessibility tree sees
573
+ * only the end state. The gap is a timer for that reason — the CDK's
574
+ * `LiveAnnouncer` clears its region the same way.
575
+ */
576
+ private readonly silenced;
577
+ private silenceTimer;
578
+ protected readonly statusId: string;
579
+ /**
580
+ * The live region's text: every refusal on show that a surrounding `<wr-form-field>`
581
+ * is not already rendering a message for, each sentence once.
582
+ */
583
+ protected readonly announcement: Signal<string>;
584
+ /**
585
+ * `aria-invalid` / `aria-describedby` for each input, where the picker binds them.
586
+ *
587
+ * The START input reads a surrounding field through `wrInput`, so inside one the
588
+ * picker feeds its refusal through `WrDateInputField` rather than binding a second
589
+ * writer beside `wrInput`, and binds these only with no field around it. The END
590
+ * input is hidden from the field on purpose (see `wrDateRangeEnd`), so its `wrInput`
591
+ * never writes either attribute and the picker always owns them: an unreadable end
592
+ * date marks the end field, not only the start.
593
+ */
594
+ protected readonly startAriaInvalid: Signal<"true" | null>;
595
+ protected readonly endAriaInvalid: Signal<"true" | null>;
596
+ protected readonly startDescribedBy: Signal<string | null>;
597
+ protected readonly endDescribedBy: Signal<string | null>;
598
+ protected readonly classes: Signal<string>;
366
599
  private overlayRef;
367
600
  /** Live panel ref while the popover is open — lets typed edits move the
368
601
  * displayed month in real time (not just on reopen). */
@@ -374,6 +607,38 @@ declare class WrDateRangePicker implements FormValueControl<WrDateRange | null>
374
607
  protected onStartInput(event: Event): void;
375
608
  protected onEndInput(event: Event): void;
376
609
  protected onBlur(event: FocusEvent): void;
610
+ /**
611
+ * Enter in either field is "this is the range". Committed ends are settled — sorted
612
+ * and re-formatted canonical — and the panel closes; a refused end turns invalid on
613
+ * the spot, and `touch` is emitted so a surrounding `<wr-form-field>` shows its
614
+ * message, which the panel would otherwise cover. Never `preventDefault`: Enter in a
615
+ * `<form>` still submits it, and a bound form is invalid while a refusal stands.
616
+ */
617
+ private onEnter;
618
+ /** Put the refusals of the given ends on show; an end with no refusal stays clear. */
619
+ private reveal;
620
+ /**
621
+ * Record why one end's text was not committed, or that it was. Clearing a refusal
622
+ * takes it off show too, so the next one waits for Enter or blur again.
623
+ */
624
+ private refuse;
625
+ /**
626
+ * Put the refusals on show for wherever focus has gone: back into one field leaves
627
+ * the OTHER end, and anywhere outside the pair leaves both.
628
+ */
629
+ private revealFor;
630
+ /**
631
+ * The bound form was reset while the model stayed where it was — Angular has thrown
632
+ * the parse errors away, so the text they were about goes too, and both fields show
633
+ * the range again. (A reset that MOVES the model reaches the text through the sync
634
+ * effect, like any other outside write.)
635
+ */
636
+ private onFormReset;
637
+ /**
638
+ * Focus leaving the open panel. Moving within it is the same visit; anywhere else has
639
+ * left whichever end focus is not going back to — see {@link revealFor}.
640
+ */
641
+ private readonly onPaneFocusOut;
377
642
  /** Called by an input's click — opens the overlay if it isn't open already. */
378
643
  protected openOnInput(end: RangeEnd): void;
379
644
  protected toggleOverlay(): void;
@@ -432,11 +697,11 @@ declare class WrDateRangePicker implements FormValueControl<WrDateRange | null>
432
697
  */
433
698
  private compareEnds;
434
699
  /**
435
- * Whether a typed date must be rejected. Covers `dateFilter` as well as the
436
- * bounds — the calendar disables filtered days, so accepting them from the
700
+ * Why a typed date must be rejected, or `null`. Covers `dateFilter` as well as
701
+ * the bounds — the calendar disables filtered days, so accepting them from the
437
702
  * keyboard would make the two entry paths disagree.
438
703
  */
439
- private isOutOfBounds;
704
+ private outOfBounds;
440
705
  private sameDate;
441
706
  /**
442
707
  * `null` and `[null, null]` are the same VALUE — an empty range — and treating
@@ -453,4 +718,4 @@ declare class WrDateRangePicker implements FormValueControl<WrDateRange | null>
453
718
  }
454
719
 
455
720
  export { WrDatePicker, WrDateRangePicker };
456
- export type { WrDateRange };
721
+ export type { WrDateInputError, WrDateRange, WrDateRangeInputError };
@@ -1,3 +1,4 @@
1
+ import * as ngwr_popover from 'ngwr/popover';
1
2
  import * as _angular_core from '@angular/core';
2
3
 
3
4
  interface WrDonutSegment {
@@ -39,6 +40,13 @@ declare class WrDonutChart {
39
40
  readonly centerValue: _angular_core.InputSignal<string>;
40
41
  /** Smaller label under the value. */
41
42
  readonly centerLabel: _angular_core.InputSignal<string>;
43
+ /**
44
+ * Show a tooltip with the slice's label and value on hover. Pointer only — the
45
+ * legend prints the same numbers, and with `showLegend` off `ariaLabel` is where
46
+ * to say them. @default true
47
+ */
48
+ readonly tooltip: _angular_core.InputSignalWithTransform<boolean, any>;
49
+ private readonly svg;
42
50
  private readonly outerR;
43
51
  protected readonly innerR: _angular_core.Signal<number>;
44
52
  protected readonly total: _angular_core.Signal<number>;
@@ -49,11 +57,26 @@ declare class WrDonutChart {
49
57
  color: string;
50
58
  path: string;
51
59
  percent: number;
60
+ midAngle: number;
52
61
  }[]>;
62
+ /**
63
+ * One tooltip for the whole chart, pointing at the hovered slice: at the middle of
64
+ * its OUTER arc, on the side that part of the ring faces — above for a slice whose
65
+ * middle is in the top quarter, below for the bottom one, beside it for the two at
66
+ * three and nine o'clock. From there the chip sits clear of the ring, and the way
67
+ * onto it runs straight out of the slice that opened it, over nothing else.
68
+ *
69
+ * Not the slice's `<path>` element: its box is most of the ring for any slice past
70
+ * a quarter, so the chip would point at the middle of the chart. And not the middle
71
+ * of the band: a chip there lies over the ring, under a pointer resting on it —
72
+ * measured in Chromium, the inner half of a lower slice landed on its own chip. A
73
+ * tiny slice keeps its own mid-angle, so the arrow says which sliver it is.
74
+ */
75
+ protected readonly tip: ngwr_popover.WrChartTooltip;
53
76
  /** SVG path for one donut slice using outer and inner arcs. */
54
77
  private arcPath;
55
78
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrDonutChart, never>;
56
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrDonutChart, "wr-donut-chart", never, { "segments": { "alias": "segments"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "thickness": { "alias": "thickness"; "required": false; "isSignal": true; }; "showLegend": { "alias": "showLegend"; "required": false; "isSignal": true; }; "centerValue": { "alias": "centerValue"; "required": false; "isSignal": true; }; "centerLabel": { "alias": "centerLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
79
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrDonutChart, "wr-donut-chart", never, { "segments": { "alias": "segments"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "thickness": { "alias": "thickness"; "required": false; "isSignal": true; }; "showLegend": { "alias": "showLegend"; "required": false; "isSignal": true; }; "centerValue": { "alias": "centerValue"; "required": false; "isSignal": true; }; "centerLabel": { "alias": "centerLabel"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
57
80
  }
58
81
 
59
82
  export { WrDonutChart };
@@ -58,8 +58,28 @@ declare class WrDropdown {
58
58
  readonly menu: _angular_core.InputSignal<WrDropdownMenu>;
59
59
  /** How the menu opens. @default 'click' */
60
60
  readonly trigger: _angular_core.InputSignal<WrDropdownTrigger>;
61
- /** Where the menu anchors relative to the trigger. @default 'bottom-start' */
61
+ /**
62
+ * Where the menu anchors relative to the trigger. A placement that does not
63
+ * fit flips to the opposite side (a `left` / `right` menu may also drop below
64
+ * or above, and then stays there until it closes), and the pane's
65
+ * `wr-dropdown-overlay--<placement>` class names the side it actually took.
66
+ * @default 'bottom-start'
67
+ */
62
68
  readonly position: _angular_core.InputSignal<WrDropdownPosition>;
69
+ /**
70
+ * Draw a small arrow on the menu's edge, pointing at the trigger — the one
71
+ * `wr-popover` and `wr-popconfirm` draw, from the same shared rule. It follows
72
+ * the placement the menu actually took, so a flipped menu points the right
73
+ * way, and it widens the gap to the trigger from 0.25rem to popover's 0.5rem
74
+ * so the tip does not touch the control.
75
+ *
76
+ * Never drawn on a bottom sheet (`responsive`), which is anchored to nothing.
77
+ * Turn it off on a menu with no padding of its own (`--wr-dropdown-padding: 0`)
78
+ * or one that clips its overflow: the square reaches about 3px inside the
79
+ * menu's border, over the first row, and `overflow` cuts its tip off.
80
+ * Read when the menu opens. @default true
81
+ */
82
+ readonly arrow: _angular_core.InputSignalWithTransform<boolean, any>;
63
83
  /**
64
84
  * Present the menu as a full-width bottom-sheet on small viewports instead
65
85
  * of an anchored panel. `undefined` follows the app-wide
@@ -161,7 +181,7 @@ declare class WrDropdown {
161
181
  private focusItemAt;
162
182
  private readonly onOverlayMouseLeave;
163
183
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrDropdown, never>;
164
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<WrDropdown, "[wrDropdown]", ["wrDropdown"], { "menu": { "alias": "wrDropdown"; "required": true; "isSignal": true; }; "trigger": { "alias": "trigger"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; "responsive": { "alias": "responsive"; "required": false; "isSignal": true; }; }, { "opened": "opened"; "closed": "closed"; }, never, never, true, never>;
184
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<WrDropdown, "[wrDropdown]", ["wrDropdown"], { "menu": { "alias": "wrDropdown"; "required": true; "isSignal": true; }; "trigger": { "alias": "trigger"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; "arrow": { "alias": "arrow"; "required": false; "isSignal": true; }; "responsive": { "alias": "responsive"; "required": false; "isSignal": true; }; }, { "opened": "opened"; "closed": "closed"; }, never, never, true, never>;
165
185
  }
166
186
 
167
187
  /**
@@ -126,6 +126,11 @@ interface DayCell {
126
126
  interface MonthWeek {
127
127
  readonly key: string;
128
128
  readonly days: readonly DayCell[];
129
+ /**
130
+ * How many lanes the row's chips fill. The chips are absolutely positioned,
131
+ * so this is the only way the row's height can know about them.
132
+ */
133
+ readonly lanes: number;
129
134
  }
130
135
  interface TimeCell {
131
136
  readonly key: string;
@@ -291,6 +296,8 @@ declare class WrEventCalendar {
291
296
  bands: readonly BandChip[];
292
297
  }[]>;
293
298
  protected readonly hasAllDay: _angular_core.Signal<boolean>;
299
+ /** Lanes the all-day band fills — it has no `maxLanes` cap, so its row has to grow instead. */
300
+ protected readonly allDayLanes: _angular_core.Signal<number>;
294
301
  protected readonly rows: _angular_core.Signal<readonly TimeRow[]>;
295
302
  protected step(direction: -1 | 1): void;
296
303
  protected goToday(): void;
@@ -323,6 +330,7 @@ declare class WrEventCalendar {
323
330
  * the cell can say how much it is hiding.
324
331
  */
325
332
  private bandLayout;
333
+ private laneCount;
326
334
  /**
327
335
  * Column layout inside one day: events that overlap in time share the width.
328
336
  * Clusters are built by a running high-water mark — a new cluster starts the
@@ -1,7 +1,6 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { InjectionToken, Signal, EnvironmentProviders } from '@angular/core';
3
- import * as _angular_forms from '@angular/forms';
4
- import { NgControl } from '@angular/forms';
2
+ import { InjectionToken, Signal, AfterContentChecked, EnvironmentProviders } from '@angular/core';
3
+ import { NgControl, ValidationErrors } from '@angular/forms';
5
4
  import * as _angular_forms_signals from '@angular/forms/signals';
6
5
 
7
6
  /**
@@ -153,7 +152,7 @@ declare class WrFormError {
153
152
  *
154
153
  * @see https://ngwr.dev/reference/components/form-field
155
154
  */
156
- declare class WrFormField implements WrFormFieldContext {
155
+ declare class WrFormField implements WrFormFieldContext, AfterContentChecked {
157
156
  /** Label text shown above the control. */
158
157
  readonly label: _angular_core.InputSignal<string>;
159
158
  /** Hint text shown below the control. Hidden when an error is visible. */
@@ -205,10 +204,41 @@ declare class WrFormField implements WrFormFieldContext {
205
204
  * Angular does expose, so the computed depends on this counter instead.
206
205
  * Signal Forms needs none of this — its interop getters read live signals —
207
206
  * and harmlessly contributes no events.
207
+ *
208
+ * Events are not the whole story, which is what {@link ngAfterContentChecked} is for.
208
209
  */
209
210
  private readonly revision;
211
+ /** What the last check saw on a reactive control — see {@link ngAfterContentChecked}. */
212
+ private seen;
213
+ /**
214
+ * Catch the changes a reactive control makes WITHOUT an event.
215
+ *
216
+ * `updateValueAndValidity({ emitEvent: false })`, `markAsTouched({ emitEvent: false })`
217
+ * and their siblings change `errors`, `touched` and `dirty` and emit nothing, so the
218
+ * subscription above never hears them and the message stays whatever it was. That is
219
+ * not only an app's own silent write: Angular reports a custom control's PARSE errors
220
+ * to a `FormControl` exactly that way — a validator re-run with `emitEvent: false` —
221
+ * so a `wr-date-picker` refusing typed text made its control invalid while the field
222
+ * went on showing the message from before, or nothing — and a later pass of change
223
+ * detection did not help, because the message was memoised on a revision only events
224
+ * move.
225
+ *
226
+ * This runs whenever the view holding the field is checked, which is when the page
227
+ * around the control changes — for the date picker, the moment its refusal turns
228
+ * visible. It is `ngAfterContentChecked` and not `ngDoCheck` for an ordering reason
229
+ * read out of Angular's `refreshView`: check hooks run BEFORE the view's effects, and
230
+ * Angular's parse-error validator is re-run from an effect in that same view, so a
231
+ * `DoCheck` saw the errors one pass late and lost the message whenever nothing
232
+ * refreshed the view again. Content-checked hooks run after those effects and before
233
+ * the field's own bindings. Only a change moves the revision.
234
+ *
235
+ * Signal Forms' interop control is skipped: it is reactive already, and its `errors`
236
+ * getter builds a fresh object on every read, which a reference check would call a
237
+ * change on every pass — and re-resolve every message with it.
238
+ */
239
+ ngAfterContentChecked(): void;
210
240
  constructor();
211
- protected readonly errors: _angular_core.Signal<_angular_forms.ValidationErrors | null>;
241
+ protected readonly errors: _angular_core.Signal<ValidationErrors | null>;
212
242
  protected readonly hasError: _angular_core.Signal<boolean>;
213
243
  /** @internal Read by the projected `<wr-form-error>` children to decide which of them renders. */
214
244
  readonly errorKeys: _angular_core.Signal<readonly string[]>;