@arsedizioni/ars-utils 22.5.35 → 22.5.37

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.
@@ -188,21 +188,62 @@ interface LoginResult<T> extends ApiResult<boolean> {
188
188
  * The two shapes differ in one systematic way. A DTO leaves out what it does not have: a place
189
189
  * with no phone number simply has no `phone`. A form model cannot afford that — signal forms only
190
190
  * build a child field for a key whose value is defined, and the string validators need a `string`
191
- * rather than `string | undefined` — so every editable field is present, holding its empty value
192
- * (`''` for text and for the boxes of a datepicker, `false` for a checkbox, `0` for a counter).
191
+ * rather than `string | undefined` — so every editable field is present, holding its empty value.
193
192
  *
194
193
  * Doing that conversion by hand means a `?? ''` per field on the way in and a `|| undefined` per
195
- * field on the way out, twice per form, kept in sync forever. {@link DtoUtils.fromModel} and
196
- * {@link DtoUtils.toModel} do it once, driven by a single "empty model" constant that also serves as
194
+ * field on the way out, twice per form, kept in sync forever. {@link DtoUtils.toForm} and
195
+ * {@link DtoUtils.toPayload} do it once, driven by a single "empty model" constant that also serves as
197
196
  * the initial value of the form and as what a "clear" button restores.
197
+ *
198
+ * ## The convention
199
+ *
200
+ * 1. **A component that edits a DTO owns a form model of its own.** `ProfileEditComponent`
201
+ * receiving a `ProfileModel` declares a `ProfileForm`, derived from it — never binds the DTO
202
+ * to the form directly. The DTO stays free to say "absent"; the form is not.
203
+ * 2. **What is optional in the DTO becomes `| null` in the form model.** `phone?: string` becomes
204
+ * `phone: string` holding `''`, `level?: Level` becomes `level: Level | null`. The optionality
205
+ * is not lost, it moves into the empty value, and {@link Dto} gives it back on the way out.
206
+ * 3. **A date field is `Date | null`.** Always a `Date`, never a `Date | string`; `null` is its
207
+ * empty value, because that is what Material's date accessors — the single datepicker and both
208
+ * boxes of a `mat-date-range-picker` — write when an input is cleared.
209
+ * 4. **The empty model is named `_EMPTY_ITEM`.** One constant per form, next to its interface.
210
+ * 5. **The two conversions are called `toForm()` and `toPayload()`, and are written one after the
211
+ * other.** When a component builds more than one payload, the others are `toPayloadXxx()` and
212
+ * the main one keeps the bare name.
213
+ * 6. **A payload always goes through `toPayload()` before it reaches the API.** When the payload
214
+ * needs adjustments the generic conversion cannot make — a bitmask to rebuild, dates to
215
+ * normalize, a number to coerce, fields the model does not own — the component declares its own
216
+ * `toPayload()` and calls {@link DtoUtils.toPayload} inside it for the field-by-field part.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * interface ProfileForm {
221
+ * name: string; // name?: string in ProfileModel
222
+ * level: Level | null; // level?: Level
223
+ * from: Date | null; // fromDate?: Date, bound to a mat-date-range-picker
224
+ * to: Date | null; // toDate?: Date
225
+ * active: boolean;
226
+ * }
227
+ *
228
+ * const _EMPTY_ITEM: ProfileForm = { name: '', level: null, from: null, to: null, active: false };
229
+ *
230
+ * private toForm(dto?: ProfileModel): ProfileForm {
231
+ * return DtoUtils.toForm(_EMPTY_ITEM, dto as Partial<ProfileForm>);
232
+ * }
233
+ *
234
+ * private toPayload(): ProfileModel {
235
+ * const { from, to, ...rest } = this.model();
236
+ * return { ...DtoUtils.toPayload(rest), fromDate: from ?? undefined, toDate: to ?? undefined };
237
+ * }
238
+ * ```
198
239
  */
199
240
  /**
200
241
  * The DTO shape of a form model: the same fields, without the values that stand for "empty".
201
242
  *
202
- * It mirrors what {@link DtoUtils.toModel} does at runtime, so the payload type says the truth. It
203
- * only works when the empty value is a literal type — `Date | '' | null` narrows to `Date`, while
204
- * a field typed `Date | string` cannot be narrowed, because `string` is not assignable to `''`.
205
- * That is the reason the convention is `| ''` and not `| string`.
243
+ * It mirrors what {@link DtoUtils.toPayload} does at runtime, so the payload type says the truth. It
244
+ * only works when the empty value is `''` or `null` — `Date | null` narrows to `Date`, and
245
+ * `string` (which is what `''` widens to) narrows to `string`. That is the reason a date field is
246
+ * `Date | null` and never `Date | string`: the latter could not be narrowed at all.
206
247
  */
207
248
  type Dto<TModel> = {
208
249
  [K in keyof TModel]: Exclude<TModel[K], '' | null>;
@@ -224,11 +265,8 @@ declare class DtoUtils {
224
265
  * one takes when the DTO does not carry it.
225
266
  * @param dto - The object coming from the API, or `undefined` when creating a new record.
226
267
  * @returns A new model, never the same reference as `empty` nor as `dto`.
227
- * @example
228
- * const EMPTY_PLACE: PlaceForm = { name: '', city: '', phone: '', disabled: false };
229
- * this.form().reset(DtoUtils.fromModel(EMPTY_PLACE, r.value));
230
268
  */
231
- static fromModel<TModel extends object>(empty: TModel, dto?: Partial<TModel> | null): TModel;
269
+ static toForm<TModel extends object>(empty: TModel, dto?: Partial<TModel> | null): TModel;
232
270
  /**
233
271
  * Builds the payload for the API from the current form model.
234
272
  *
@@ -248,10 +286,8 @@ declare class DtoUtils {
248
286
  *
249
287
  * @param model - The current form model.
250
288
  * @returns A shallow copy without the empty fields.
251
- * @example
252
- * this.scmService.places.savePlace(DtoUtils.toModel(this.model()));
253
289
  */
254
- static toModel<TModel extends object>(model: TModel): Dto<TModel>;
290
+ static toPayload<TModel extends object>(model: TModel): Dto<TModel>;
255
291
  }
256
292
 
257
293
  declare const UtilsMessages: {
@@ -191,7 +191,7 @@ declare class DateFnsAdapter extends DateAdapter<Date, Locale> {
191
191
  static ɵprov: i0.ɵɵInjectableDeclaration<DateFnsAdapter>;
192
192
  }
193
193
  /**
194
- * Standalone providers for the ARS date-fns adapter.
194
+ * Standalone providers for the SCM date-fns adapter.
195
195
  *
196
196
  * Configures Angular Material to use {@link DateFnsAdapter} (Europe/Rome timezone)
197
197
  * and the matching {@link MAT_DATE_FNS_FORMATS}. Also supports `mat-timepicker` since
@@ -199,10 +199,10 @@ declare class DateFnsAdapter extends DateAdapter<Date, Locale> {
199
199
  *
200
200
  * @example
201
201
  * bootstrapApplication(AppComponent, {
202
- * providers: [provideArsDateFns()]
202
+ * providers: [provideScmDateFns()]
203
203
  * });
204
204
  */
205
- declare function provideArsDateFns(): EnvironmentProviders;
205
+ declare function provideScmDateFns(): EnvironmentProviders;
206
206
 
207
207
  /** Default application timezone: all dates are serialised as Europe/Rome wall-clock values. */
208
208
  declare const DEFAULT_TIME_ZONE = "Europe/Rome";
@@ -301,4 +301,37 @@ declare function toLocalDateOnlyString(value?: Date | null, timeZone?: string):
301
301
  */
302
302
  declare function toLocalDateTimeString(value?: Date | null, timeZone?: string): string | undefined;
303
303
 
304
- export { ARS_TIME_ZONE, ArsLocalDateInterceptor, DEFAULT_TIME_ZONE, DateFnsAdapter, MAT_DATE_FNS_FORMATS, arsLocalDateInterceptor, provideArsDateFns, provideArsLocalDates, toLocalDateOnlyString, toLocalDateTimeString };
304
+ /**
305
+ * Works around a MatDatepicker/Signal Forms interop bug.
306
+ *
307
+ * `MatDatepickerInputBase._onInput()` only notifies its registered validator-change
308
+ * callback from the branch it does not take when the parsed date is null. Signal
309
+ * Forms recomputes CVA parse errors from that callback or from a model value
310
+ * change, and neither happens while the user erases an unparseable date one
311
+ * character at a time (the model is already `null`, so the signal write is a
312
+ * no-op). The stale `matDatepickerParse` error therefore survives on an empty
313
+ * field. Notifying on every keystroke re-runs the datepicker validators.
314
+ */
315
+ declare class MatDatepickerParseErrorFixDirective {
316
+ private readonly singleDate;
317
+ private readonly startDate;
318
+ private readonly endDate;
319
+ /**
320
+ * Re-runs the datepicker validators after Material has updated its internal
321
+ * parse flag.
322
+ *
323
+ * @returns Nothing.
324
+ */
325
+ protected onInput(): void;
326
+ /**
327
+ * Notifies the validator-change callback of this input and, for range parts,
328
+ * of the sibling input as well.
329
+ *
330
+ * @returns Nothing.
331
+ */
332
+ private revalidate;
333
+ static ɵfac: i0.ɵɵFactoryDeclaration<MatDatepickerParseErrorFixDirective, never>;
334
+ static ɵdir: i0.ɵɵDirectiveDeclaration<MatDatepickerParseErrorFixDirective, "input[matDatepicker], input[matStartDate], input[matEndDate]", never, {}, {}, never, never, true, never>;
335
+ }
336
+
337
+ export { ARS_TIME_ZONE, ArsLocalDateInterceptor, DEFAULT_TIME_ZONE, DateFnsAdapter, MAT_DATE_FNS_FORMATS, MatDatepickerParseErrorFixDirective, arsLocalDateInterceptor, provideArsLocalDates, provideScmDateFns, toLocalDateOnlyString, toLocalDateTimeString };
@@ -124,6 +124,23 @@ interface CredentialsDialogData {
124
124
  mode?: 'email' | 'user' | 'otp';
125
125
  appearance?: MatFormFieldAppearance;
126
126
  }
127
+ /**
128
+ * Form model of the credentials dialog.
129
+ *
130
+ * {@link CredentialsDialogResult} is the DTO: every field of it is optional, so it
131
+ * cannot back a signal form directly. Here every field is present and holds its empty
132
+ * value, and {@link DtoUtils.toPayload} turns the model back into the DTO on the way out.
133
+ */
134
+ interface CredentialsForm {
135
+ /** The account name or email; `''` when the input is empty. */
136
+ user: string;
137
+ /** The password; `''` when the input is empty or the dialog is in `otp` mode. */
138
+ password: string;
139
+ /** Whether the session should be remembered. */
140
+ rememberMe: boolean;
141
+ /** The 6-digit OTP code; `''` outside `otp` mode. */
142
+ code: string;
143
+ }
127
144
  declare class CredentialsDialogComponent {
128
145
  /** Error text of a field: the message declared in the schema, or the shared fallback. */
129
146
  protected readonly getErrorMessage: typeof SignalsUtils.getFieldErrorMessage;
@@ -135,13 +152,26 @@ declare class CredentialsDialogComponent {
135
152
  protected readonly dialogData: _angular_core.WritableSignal<CredentialsDialogData>;
136
153
  /** Model backing the signal form, pre-filled with the user name from dialog data when provided. */
137
154
  private readonly model;
155
+ /**
156
+ * Builds the form model from the dialog data, which may carry the account name to
157
+ * pre-fill.
158
+ * @returns A fresh form model.
159
+ */
160
+ private toForm;
161
+ /**
162
+ * Builds the dialog result from the current form model. The fields the active mode
163
+ * does not use are left empty by the template and dropped here, so they travel as
164
+ * absent instead of as empty strings.
165
+ * @returns The credentials to emit through `done` or `recoveringPassword`.
166
+ */
167
+ private toPayload;
138
168
  /**
139
169
  * Signal form for the credentials. Validators follow the active mode: the user
140
170
  * field is a required email in `email` mode and plain required in `user` mode,
141
171
  * the password is required outside `otp` mode, and the OTP code is a required
142
172
  * complete 6-digit code in `otp` mode.
143
173
  */
144
- protected readonly form: _angular_forms_signals.FieldTree<CredentialsDialogResult, string | number, "writable">;
174
+ protected readonly form: _angular_forms_signals.FieldTree<CredentialsForm, string | number, "writable">;
145
175
  /** Whether the password field is currently shown as plain text. */
146
176
  protected readonly showPassword: _angular_core.WritableSignal<boolean>;
147
177
  /**
@@ -171,6 +201,29 @@ interface ResetPasswordDialogData {
171
201
  userEmail?: string;
172
202
  appearance?: MatFormFieldAppearance;
173
203
  }
204
+ /**
205
+ * Form model of the password-reset dialog.
206
+ *
207
+ * {@link ResetPasswordDialogResult} is the DTO: every field of it is optional, so it
208
+ * cannot back a signal form directly. Here every field is present — `userId` included,
209
+ * which is carried through untouched — and an absent value becomes `null` for the
210
+ * numeric id and `''` for the text fields, the empty states signal forms and the string
211
+ * validators work with.
212
+ */
213
+ interface ResetPasswordForm {
214
+ /** Id of the account whose password is being reset; `null` in the modes that identify it by email. */
215
+ userId: number | null;
216
+ /** Email of the account; `''` outside the modes that ask for it. */
217
+ userEmail: string;
218
+ /** The current password, asked for in `old` mode; `''` elsewhere. */
219
+ oldPassword: string;
220
+ /** The new password. */
221
+ password: string;
222
+ /** Confirmation of the new password; `''` in `admin` mode, where it is not asked for. */
223
+ password2: string;
224
+ /** The 6-digit OTP code, asked for in `otp` mode; `''` elsewhere. */
225
+ code: string;
226
+ }
174
227
  declare class ResetPasswordDialogComponent {
175
228
  /** Error text of a field: the message declared in the schema, or the shared fallback. */
176
229
  protected readonly getErrorMessage: typeof SignalsUtils.getFieldErrorMessage;
@@ -182,6 +235,19 @@ declare class ResetPasswordDialogComponent {
182
235
  protected readonly dialogData: _angular_core.WritableSignal<ResetPasswordDialogData>;
183
236
  /** Model backing the signal form. */
184
237
  private readonly model;
238
+ /**
239
+ * Builds the form model from the dialog data, which identifies the account either by
240
+ * id or by email depending on the mode.
241
+ * @returns A fresh form model.
242
+ */
243
+ private toForm;
244
+ /**
245
+ * Builds the dialog result from the current form model. The fields the active mode
246
+ * does not use are left empty by the template and dropped here, so they travel as
247
+ * absent instead of as empty strings.
248
+ * @returns The reset data to emit through `done`.
249
+ */
250
+ private toPayload;
185
251
  /**
186
252
  * Signal form for the password reset. Validators follow the active mode:
187
253
  * the email is required in `forgot` mode, the old password in `old` mode,
@@ -189,7 +255,7 @@ declare class ResetPasswordDialogComponent {
189
255
  * required, at least 10 characters and strong enough, and its confirmation
190
256
  * (required outside `admin` mode) must match it.
191
257
  */
192
- protected readonly form: _angular_forms_signals.FieldTree<ResetPasswordDialogResult, string | number, "writable">;
258
+ protected readonly form: _angular_forms_signals.FieldTree<ResetPasswordForm, string | number, "writable">;
193
259
  /** Whether the old-password field is currently shown as plain text. */
194
260
  protected readonly showOldPassword: _angular_core.WritableSignal<boolean>;
195
261
  /** Whether the new-password field is currently shown as plain text. */
@@ -212,6 +278,19 @@ declare class ResetPasswordDialogComponent {
212
278
  interface RecoverPasswordDialogData {
213
279
  appearance?: MatFormFieldAppearance;
214
280
  }
281
+ /**
282
+ * Form model of the password-recovery dialog.
283
+ *
284
+ * {@link RecoverPasswordDialogResult} is the DTO: every field of it is optional, so it
285
+ * cannot back a signal form directly. Here every field is present and holds its empty
286
+ * value, and {@link DtoUtils.toPayload} turns the model back into the DTO on the way out.
287
+ */
288
+ interface RecoverPasswordForm {
289
+ /** The email the recovery link is sent to; `''` when the input is empty. */
290
+ email: string;
291
+ /** Whether the recaptcha challenge has been solved. */
292
+ recaptcha: boolean;
293
+ }
215
294
  declare class RecoverPasswordDialogComponent {
216
295
  /** Error text of a field: the message declared in the schema, or the shared fallback. */
217
296
  protected readonly getErrorMessage: typeof SignalsUtils.getFieldErrorMessage;
@@ -221,10 +300,22 @@ declare class RecoverPasswordDialogComponent {
221
300
  protected readonly dialogData: _angular_core.WritableSignal<RecoverPasswordDialogData>;
222
301
  /** Model backing the signal form. */
223
302
  private readonly model;
303
+ /**
304
+ * Builds the form model. The dialog is always opened empty, so the model is the
305
+ * neutral one.
306
+ * @returns A fresh form model.
307
+ */
308
+ private toForm;
309
+ /**
310
+ * Builds the dialog result from the current form model, dropping the email when it
311
+ * was left empty.
312
+ * @returns The result to emit through `done`.
313
+ */
314
+ private toPayload;
224
315
  /**
225
316
  * Signal form for the recovery request: the email is required and must be valid.
226
317
  */
227
- protected readonly form: _angular_forms_signals.FieldTree<RecoverPasswordDialogResult, string | number, "writable">;
318
+ protected readonly form: _angular_forms_signals.FieldTree<RecoverPasswordForm, string | number, "writable">;
228
319
  /**
229
320
  * Emits the current form result to trigger the password-recovery flow.
230
321
  */
@@ -233,20 +324,39 @@ declare class RecoverPasswordDialogComponent {
233
324
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<RecoverPasswordDialogComponent, "ng-component", never, {}, { "done": "done"; }, never, never, true, never>;
234
325
  }
235
326
 
327
+ /**
328
+ * Form model of the OTP dialog.
329
+ *
330
+ * {@link PromptOtpDialogResult} is the DTO: it leaves the code out when there is none.
331
+ * The form cannot, so the field is always present and holds `''` when empty.
332
+ */
333
+ interface PromptOtpForm {
334
+ /** The 6-digit OTP code; `''` while the user has not completed it. */
335
+ value: string;
336
+ }
236
337
  declare class PromptOtpDialogComponent {
237
338
  readonly done: _angular_core.OutputEmitterRef<PromptOtpDialogResult>;
238
339
  private readonly dialogService;
239
340
  protected readonly dialogData: _angular_core.WritableSignal<PromptDialogData>;
240
341
  /** Model backing the signal form. `value` holds the 6-digit OTP code. */
241
342
  private readonly model;
343
+ /**
344
+ * Builds the form model. The dialog has no incoming value, so the model is the
345
+ * neutral one.
346
+ * @returns A fresh form model.
347
+ */
348
+ private toForm;
349
+ /**
350
+ * Builds the dialog result from the current form model, dropping the empty code.
351
+ * @returns The result carrying the entered OTP code.
352
+ */
353
+ private toPayload;
242
354
  /**
243
355
  * Signal form for the OTP code. The code is always required here; the
244
356
  * "complete 6 digits" rule comes from the OtpInputComponent validator,
245
357
  * honoured through the ControlValueAccessor interop.
246
358
  */
247
- protected readonly form: _angular_forms_signals.FieldTree<{
248
- value: string;
249
- }, string | number, "writable">;
359
+ protected readonly form: _angular_forms_signals.FieldTree<PromptOtpForm, string | number, "writable">;
250
360
  /**
251
361
  * Validates the form and emits the OTP result, or shows an error if validation fails.
252
362
  */
@@ -3,6 +3,17 @@ import * as _angular_core from '@angular/core';
3
3
  import { PromptDialogResult, PromptDialogData, PromptDateDialogResult, PromptTimeDialogData } from '@arsedizioni/ars-utils/ui';
4
4
  import { SignalsUtils } from '@arsedizioni/ars-utils/core.validators';
5
5
 
6
+ /**
7
+ * Form model of the generic prompt.
8
+ *
9
+ * The DTO side is {@link PromptDialogData.initialValue} on the way in and
10
+ * {@link PromptDialogResult.value} on the way out; both may be absent, while the form
11
+ * always owns the field. `value` stays `any` because its shape follows the prompt type.
12
+ */
13
+ interface PromptForm {
14
+ /** The prompted value: `''` for textual types, `null` for the number and list types. */
15
+ value: any;
16
+ }
6
17
  declare class PromptDialogComponent {
7
18
  /** Error text of a field: the message declared in the schema, or the shared fallback. */
8
19
  protected readonly getErrorMessage: typeof SignalsUtils.getFieldErrorMessage;
@@ -11,13 +22,31 @@ declare class PromptDialogComponent {
11
22
  protected readonly dialogData: _angular_core.WritableSignal<PromptDialogData>;
12
23
  /** Model backing the signal form. `value` type depends on the prompt type. */
13
24
  private readonly model;
25
+ /**
26
+ * Builds the form model from the value carried by the dialog data.
27
+ *
28
+ * Ad-hoc because the empty value follows the prompt type: native inputs bound via
29
+ * `[formField]` report an empty number or an unselected option as `null`, while a
30
+ * text, date or textarea input reports `''`. The list type never takes an initial
31
+ * value: its options are matched by the template.
32
+ * @returns A fresh form model.
33
+ */
34
+ private toForm;
35
+ /**
36
+ * Builds the dialog result from the current form model.
37
+ *
38
+ * Ad-hoc because the result also carries the prompt type and the caller's `options`,
39
+ * which are not part of the form model; the value itself goes through
40
+ * {@link DtoUtils.toPayload}, so an input left empty travels as absent instead of as
41
+ * an empty string or a `null`.
42
+ * @returns The result to emit through `done`.
43
+ */
44
+ private toPayload;
14
45
  /**
15
46
  * Signal form for the prompt value. Validators mirror the previous template-driven
16
47
  * form: `required` follows the dialog data, `min`/`max` apply to the Number type.
17
48
  */
18
- protected readonly form: _angular_forms_signals.FieldTree<{
19
- value: any;
20
- }, string | number, "writable">;
49
+ protected readonly form: _angular_forms_signals.FieldTree<PromptForm, string | number, "writable">;
21
50
  /**
22
51
  * Validates the form and emits the result, or shows an error if validation fails.
23
52
  */
@@ -26,6 +55,23 @@ declare class PromptDialogComponent {
26
55
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<PromptDialogComponent, "ng-component", never, {}, { "done": "done"; }, never, never, true, never>;
27
56
  }
28
57
 
58
+ /**
59
+ * Form model of the date prompt.
60
+ *
61
+ * The DTO side is {@link PromptDialogData.initialValue} on the way in and
62
+ * {@link PromptDateDialogResult.value} on the way out; both leave the value out when
63
+ * there is none, while the form always owns every field. Dates are always `Date | null`
64
+ * — never a `Date | string` — because `null` is what Material's date accessors write
65
+ * when a datepicker input is cleared.
66
+ */
67
+ interface PromptDateForm {
68
+ /** Value of the single-date type; `null` when the input is empty. */
69
+ date: Date | null;
70
+ /** Start of the range of the date-interval type; `null` when the input is empty. */
71
+ from: Date | null;
72
+ /** End of the range of the date-interval type; `null` when the input is empty. */
73
+ to: Date | null;
74
+ }
29
75
  declare class PromptDateDialogComponent {
30
76
  /** Error text of a field: the message declared in the schema, or the shared fallback. */
31
77
  protected readonly getErrorMessage: typeof SignalsUtils.getFieldErrorMessage;
@@ -53,16 +99,29 @@ declare class PromptDateDialogComponent {
53
99
  * input as `null` anyway; values are normalized to `undefined` on emit.
54
100
  */
55
101
  private readonly model;
102
+ /**
103
+ * Builds the form model from the value carried by the dialog data: a `Date` for the
104
+ * single-date type, a {@link DateInterval} for the date-interval one.
105
+ * @returns A fresh form model, empty when the dialog was opened without a value.
106
+ */
107
+ private toForm;
108
+ /**
109
+ * Builds the dialog result from the current form model.
110
+ *
111
+ * Ad-hoc because the two prompt types produce different values out of the same model
112
+ * — a `Date` or a {@link DateInterval} — and because the result also carries the type
113
+ * and the caller's `options`, which are not part of the form model. The dates
114
+ * themselves go through {@link DtoUtils.toPayload}, so a bound left empty travels as
115
+ * absent instead of as `null`.
116
+ * @returns The result to emit through `done`.
117
+ */
118
+ private toPayload;
56
119
  /**
57
120
  * Signal form for the date value(s). `required` follows the dialog data and only
58
121
  * applies to the fields of the active prompt type; the datepicker's own parse
59
122
  * validators are picked up through the ControlValueAccessor interop.
60
123
  */
61
- protected readonly form: _angular_forms_signals.FieldTree<{
62
- date: Date | null;
63
- from: Date | null;
64
- to: Date | null;
65
- }, string | number, "writable">;
124
+ protected readonly form: _angular_forms_signals.FieldTree<PromptDateForm, string | number, "writable">;
66
125
  /** Debounced stream of keyup events on the interval inputs (shorthand typing support). */
67
126
  private readonly intervalKeyup;
68
127
  constructor();
@@ -90,6 +149,17 @@ declare class PromptDateDialogComponent {
90
149
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<PromptDateDialogComponent, "ng-component", never, {}, { "done": "done"; }, never, never, true, never>;
91
150
  }
92
151
 
152
+ /**
153
+ * Form model of the time prompt.
154
+ *
155
+ * The DTO side is {@link PromptTimeDialogData.initialValue} on the way in and
156
+ * {@link PromptDialogResult.value} on the way out; both leave the value out when there
157
+ * is none, while the form always owns the field and holds `''` when empty.
158
+ */
159
+ interface PromptTimeForm {
160
+ /** The `"HH:MM"` time value; `''` when the input is empty. */
161
+ value: string;
162
+ }
93
163
  declare class PromptTimeDialogComponent {
94
164
  /** Error text of a field: the message declared in the schema, or the shared fallback. */
95
165
  protected readonly getErrorMessage: typeof SignalsUtils.getFieldErrorMessage;
@@ -98,13 +168,25 @@ declare class PromptTimeDialogComponent {
98
168
  protected readonly dialogData: _angular_core.WritableSignal<PromptTimeDialogData>;
99
169
  /** Model backing the signal form. The value is a `"HH:MM"` time string. */
100
170
  private readonly model;
171
+ /**
172
+ * Builds the form model from the value carried by the dialog data.
173
+ * @returns A fresh form model, empty when the dialog was opened without a value.
174
+ */
175
+ private toForm;
176
+ /**
177
+ * Builds the dialog result from the current form model.
178
+ *
179
+ * Ad-hoc because the result also carries the caller's `options`, which are not part
180
+ * of the form model; the time itself goes through {@link DtoUtils.toPayload}, so an
181
+ * empty input travels as absent instead of as an empty string.
182
+ * @returns The result to emit through `done`.
183
+ */
184
+ private toPayload;
101
185
  /**
102
186
  * Signal form for the time value: `required` follows the dialog data and the
103
187
  * time validator enforces format and the optional allowed slots.
104
188
  */
105
- protected readonly form: _angular_forms_signals.FieldTree<{
106
- value: string;
107
- }, string | number, "writable">;
189
+ protected readonly form: _angular_forms_signals.FieldTree<PromptTimeForm, string | number, "writable">;
108
190
  /**
109
191
  * Validates the form and emits the result, or shows an error if validation fails.
110
192
  */
@@ -273,6 +273,21 @@ declare class SelectTreeDialogComponent {
273
273
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SelectTreeDialogComponent, "ng-component", never, {}, { "done": "done"; "append": "append"; }, never, never, true, never>;
274
274
  }
275
275
 
276
+ /**
277
+ * Form model of the send-to dialog.
278
+ *
279
+ * {@link SendToDialogResult} is the DTO: every field of it is optional, so it cannot
280
+ * back a signal form directly. Here every editable field is present and holds `''` when
281
+ * empty; `options` is not editable and is only added back by {@link toPayload}.
282
+ */
283
+ interface SendToForm {
284
+ /** Semicolon-separated recipient list; `''` when the input is empty. */
285
+ recipients: string;
286
+ /** Subject of the message; `''` when the input is empty. */
287
+ subject: string;
288
+ /** Body of the message; `''` when the input is empty. */
289
+ text: string;
290
+ }
276
291
  interface SendToDialogData {
277
292
  count: number;
278
293
  canPopulate?: boolean;
@@ -294,11 +309,24 @@ declare class SendToDialogComponent implements ISendToDialog {
294
309
  protected readonly dialogData: _angular_core.WritableSignal<SendToDialogData>;
295
310
  /** Model backing the signal form. */
296
311
  private readonly model;
312
+ /**
313
+ * Builds the form model from the dialog data, which may pre-fill subject and body.
314
+ * @returns A fresh form model.
315
+ */
316
+ private toForm;
317
+ /**
318
+ * Builds the dialog result from the current form model.
319
+ *
320
+ * Ad-hoc because the result also carries the caller's `options`, which the dialog
321
+ * only passes through and are therefore not part of the form model.
322
+ * @returns The message to emit through `done` or `populate`.
323
+ */
324
+ private toPayload;
297
325
  /**
298
326
  * Signal form for the send-to fields: recipients (a required, valid,
299
327
  * semicolon-separated email list), subject and text are all required.
300
328
  */
301
- protected readonly form: _angular_forms_signals.FieldTree<SendToDialogResult, string | number, "writable">;
329
+ protected readonly form: _angular_forms_signals.FieldTree<SendToForm, string | number, "writable">;
302
330
  /**
303
331
  * Confirm the form and emit the result.
304
332
  */